Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcd58ae76f | |||
| df380dea27 | |||
| 9d88592391 | |||
| c906c11b0f | |||
| 4fbc56cdb0 | |||
| 98c0416b20 | |||
| fa41874e30 | |||
| 2aa6c99c90 | |||
| 5f60c0f1bb | |||
| cb741db623 | |||
| 1df217c4a0 | |||
| 81f209ba9e | |||
| 34a31aa7df | |||
| 5099e02c9b | |||
| f364b39b29 | |||
| 4cd4746f9a | |||
| 6448f0e32b | |||
| e07332d49a | |||
| bd8a44c4c9 |
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Set update schedule for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Build Artifacts
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist copyExe
|
||||
|
||||
- name: Save bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
- name: Save exe artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
build-win-bundle:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
run: java -version
|
||||
|
||||
- name: Set jadx version
|
||||
shell: bash
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean dist -PbundleJRE=true
|
||||
|
||||
- name: Save exe bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
|
||||
path: jadx-gui/build/*-with-jre-win/*
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, build-test ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: 8
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
|
||||
- name: Set jadx version
|
||||
run: |
|
||||
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
|
||||
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: burrunan/gradle-cache-action@v1
|
||||
name: Build with Gradle
|
||||
env:
|
||||
TERM: dumb
|
||||
TEST_INPUT_PLUGIN: dx
|
||||
with:
|
||||
arguments: clean build dist copyExe --warning-mode=all
|
||||
|
||||
- name: Save bundle artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-{0}', env.JADX_VERSION) }}
|
||||
# Waiting fix for https://github.com/actions/upload-artifact/issues/39 to upload zip file
|
||||
# Upload unpacked files for now
|
||||
path: build/jadx/**/*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Save exe artifact
|
||||
if: success() && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
|
||||
path: build/*.exe
|
||||
if-no-files-found: error
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
queries: +security-extended
|
||||
languages: ${{ matrix.language }}
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -33,8 +33,9 @@ See these features in action here: [jadx-gui features overview](https://github.c
|
||||
<img src="https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png" width="700"/>
|
||||
|
||||
### Download
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build/master)
|
||||
- release
|
||||
from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- latest [unstable build](https://nightly.link/skylot/jadx/workflows/build-artifacts/master)
|
||||
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>com.github.skylot.jadx</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>Apache-2.0</project_license>
|
||||
<name>JADX</name>
|
||||
<summary>Dex to Java decompiler</summary>
|
||||
<description>
|
||||
<p>Command line and GUI tools for producing Java source code from Android Dex and Apk files</p>
|
||||
<ul>
|
||||
<li>decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files</li>
|
||||
<li>decode AndroidManifest.xml and other resources from resources.arsc</li>
|
||||
<li>deobfuscator included</li>
|
||||
<li>view decompiled code with highlighted syntax</li>
|
||||
<li>jump to declaration</li>
|
||||
<li>find usage</li>
|
||||
<li>full text search</li>
|
||||
<li>smali debugger</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://user-images.githubusercontent.com/118523/142730720-839f017e-38db-423e-b53f-39f5f0a0316f.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
<launchable type="desktop-id">com.github.skylot.jadx.desktop</launchable>
|
||||
<url type="homepage">https://github.com/skylot/jadx</url>
|
||||
<url type="bugtracker">https://github.com/skylot/jadx/issues</url>
|
||||
<releases>
|
||||
<release version="1.3.4" date="2022-03-20" />
|
||||
</releases>
|
||||
</component>
|
||||
+3
-10
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.5.0'
|
||||
id 'com.diffplug.spotless' version '6.6.1'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -14,7 +14,6 @@ allprojects {
|
||||
version = jadxVersion
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
@@ -32,7 +31,7 @@ allprojects {
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.5.1'
|
||||
testImplementation 'org.mockito:mockito-core:4.6.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.22.0'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
@@ -64,13 +63,7 @@ spotless {
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
if (JavaVersion.current() < JavaVersion.VERSION_16) {
|
||||
removeUnusedImports()
|
||||
} else {
|
||||
// google-format on Java 16+ issue: https://github.com/diffplug/spotless/issues/834
|
||||
println('Warning! Unused imports remove is disabled for Java 16+'
|
||||
+ ' (use workaround from https://github.com/diffplug/spotless/tree/main/plugin-gradle#google-java-format)')
|
||||
}
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
org.gradle.warning.mode=all
|
||||
org.gradle.parallel=true
|
||||
|
||||
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||
org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' \
|
||||
--add-exports='jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -22,6 +23,7 @@ import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
@@ -50,12 +52,24 @@ public class JCommanderWrapper<T> {
|
||||
if (parameter.isAssigned()) {
|
||||
// copy assigned field value to obj
|
||||
Parameterized parameterized = parameter.getParameterized();
|
||||
Object val = parameterized.get(parameter.getObject());
|
||||
parameterized.set(obj, val);
|
||||
Object providedValue = parameterized.get(parameter.getObject());
|
||||
Object newValue = mergeValues(parameterized.getType(), providedValue, () -> parameterized.get(obj));
|
||||
parameterized.set(obj, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static Object mergeValues(Class<?> type, Object value, Supplier<Object> prevValueProvider) {
|
||||
if (type.isAssignableFrom(Map.class)) {
|
||||
// merge maps instead replacing whole map
|
||||
Map prevMap = (Map) prevValueProvider.get();
|
||||
return Utils.mergeMaps(prevMap, (Map) value); // value map will override keys in prevMap
|
||||
}
|
||||
// simple override
|
||||
return value;
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.Utils.newConstStringMap;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@@ -47,6 +52,40 @@ public class JadxCLIArgsTest {
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPluginOptionsOverride() {
|
||||
// add key to empty base map
|
||||
checkPluginOptionsMerge(
|
||||
Collections.emptyMap(),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// override one key
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option", "value"),
|
||||
"-Poption=otherValue",
|
||||
newConstStringMap("option", "otherValue"));
|
||||
|
||||
// merge different keys
|
||||
checkPluginOptionsMerge(
|
||||
Collections.singletonMap("option1", "value1"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
|
||||
// merge and override
|
||||
checkPluginOptionsMerge(
|
||||
newConstStringMap("option1", "value1", "option2", "value2"),
|
||||
"-Poption2=otherValue2",
|
||||
newConstStringMap("option1", "value1", "option2", "otherValue2"));
|
||||
}
|
||||
|
||||
private void checkPluginOptionsMerge(Map<String, String> baseMap, String providedArgs, Map<String, String> expectedMap) {
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.pluginOptions = baseMap;
|
||||
Map<String, String> resultMap = override(args, providedArgs).getPluginOptions();
|
||||
assertThat(resultMap, Matchers.equalTo(expectedMap));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
@@ -19,6 +22,7 @@ import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
@@ -122,6 +126,19 @@ public class JadxArgs {
|
||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
inputFiles.clear();
|
||||
if (codeCache != null) {
|
||||
codeCache.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close JadxArgs", e);
|
||||
} finally {
|
||||
codeCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInputFiles() {
|
||||
return inputFiles;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
|
||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||
|
||||
@@ -161,8 +161,13 @@ public final class JadxDecompiler implements Closeable {
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
fieldsMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
closeInputs();
|
||||
args.close();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
@@ -176,11 +181,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadedInputs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void loadPlugins(JadxArgs args) {
|
||||
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
|
||||
pluginManager.load();
|
||||
@@ -202,6 +202,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
}
|
||||
@@ -467,23 +468,10 @@ public final class JadxDecompiler implements Closeable {
|
||||
return protoXmlParser;
|
||||
}
|
||||
|
||||
private void loadJavaClass(JavaClass javaClass) {
|
||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
||||
|
||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||
loadJavaClass(innerCls);
|
||||
}
|
||||
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
|
||||
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
|
||||
loadJavaClass(inlinedCls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JavaClass by ClassNode without loading and decompilation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
JavaClass convertClassNode(ClassNode cls) {
|
||||
return classesMap.compute(cls, (node, prevJavaCls) -> {
|
||||
if (prevJavaCls != null && prevJavaCls.getClassNode() == cls) {
|
||||
@@ -497,66 +485,23 @@ public final class JadxDecompiler implements Closeable {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable("For not generated classes")
|
||||
@ApiStatus.Internal
|
||||
public JavaClass getJavaClassByNode(ClassNode cls) {
|
||||
JavaClass javaClass = classesMap.get(cls);
|
||||
if (javaClass != null && javaClass.getClassNode() == cls) {
|
||||
return javaClass;
|
||||
}
|
||||
// load parent class if inner
|
||||
ClassNode parentClass = cls.getTopParentClass();
|
||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||
if (parentJavaClass == null) {
|
||||
getClasses();
|
||||
parentJavaClass = classesMap.get(parentClass);
|
||||
}
|
||||
if (parentJavaClass != null) {
|
||||
loadJavaClass(parentJavaClass);
|
||||
javaClass = classesMap.get(cls);
|
||||
if (javaClass != null) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
// class or parent classes can be excluded from generation
|
||||
if (cls.hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
||||
JavaField convertFieldNode(FieldNode field) {
|
||||
return fieldsMap.computeIfAbsent(field, fldNode -> {
|
||||
JavaClass parentCls = convertClassNode(fldNode.getParentClass());
|
||||
return new JavaField(parentCls, fldNode);
|
||||
});
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Nullable
|
||||
public JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null && javaMethod.getMethodNode() == mth) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
ClassNode codeCls = getCodeParentClass(parentClass);
|
||||
JavaClass javaClass = getJavaClassByNode(codeCls);
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (parentClass.hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||
JavaMethod convertMethodNode(MethodNode method) {
|
||||
return methodsMap.computeIfAbsent(method, mthNode -> {
|
||||
ClassNode codeCls = getCodeParentClass(mthNode.getParentClass());
|
||||
return new JavaMethod(convertClassNode(codeCls), mthNode);
|
||||
});
|
||||
}
|
||||
|
||||
private ClassNode getCodeParentClass(ClassNode cls) {
|
||||
private static ClassNode getCodeParentClass(ClassNode cls) {
|
||||
ClassNode codeCls;
|
||||
InlinedAttr inlinedAttr = cls.get(AType.INLINED);
|
||||
if (inlinedAttr != null) {
|
||||
@@ -570,35 +515,12 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getCodeParentClass(codeCls);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Nullable
|
||||
public JavaField getJavaFieldByNode(FieldNode fld) {
|
||||
JavaField javaField = fieldsMap.get(fld);
|
||||
if (javaField != null && javaField.getFieldNode() == fld) {
|
||||
return javaField;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
if (fld.getParentClass().hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@@ -619,9 +541,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
.orElse(null);
|
||||
if (node != null) {
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return getJavaClassByNode(node.getTopParentClass());
|
||||
return convertClassNode(node.getTopParentClass());
|
||||
} else {
|
||||
return getJavaClassByNode(node);
|
||||
return convertClassNode(node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -632,7 +554,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getAliasFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.map(this::convertClassNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@@ -650,9 +572,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
case CLASS:
|
||||
return convertClassNode((ClassNode) ann);
|
||||
case METHOD:
|
||||
return getJavaMethodByNode((MethodNode) ann);
|
||||
return convertMethodNode((MethodNode) ann);
|
||||
case FIELD:
|
||||
return getJavaFieldByNode((FieldNode) ann);
|
||||
return convertFieldNode((FieldNode) ann);
|
||||
case DECLARATION:
|
||||
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
|
||||
case VAR:
|
||||
@@ -670,7 +592,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
@Nullable
|
||||
private JavaVariable resolveVarNode(VarNode varNode) {
|
||||
MethodNode mthNode = varNode.getMth();
|
||||
JavaMethod mth = getJavaMethodByNode(mthNode);
|
||||
JavaMethod mth = convertMethodNode(mthNode);
|
||||
if (mth == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -683,10 +605,13 @@ public final class JadxDecompiler implements Closeable {
|
||||
throw new JadxRuntimeException("Missing code info for resolve VarRef: " + varRef);
|
||||
}
|
||||
ICodeAnnotation varNodeAnn = codeInfo.getCodeMetadata().getAt(varRef.getRefPos());
|
||||
if (varNodeAnn == null) {
|
||||
return null;
|
||||
if (varNodeAnn != null && varNodeAnn.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||
ICodeNodeRef nodeRef = ((NodeDeclareRef) varNodeAnn).getNode();
|
||||
if (nodeRef.getAnnType() == ICodeAnnotation.AnnType.VAR) {
|
||||
return resolveVarNode((VarNode) nodeRef);
|
||||
}
|
||||
}
|
||||
return (JavaVariable) getJavaNodeByCodeAnnotation(codeInfo, varNodeAnn);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<JavaNode> convertNodes(Collection<? extends ICodeNodeRef> nodesList) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.Map;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
@@ -20,8 +22,10 @@ import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.ListUtils;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final ClassNode cls;
|
||||
@@ -83,6 +87,14 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getDisassembledCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||
return ann.equals(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
@@ -94,7 +106,6 @@ public final class JavaClass implements JavaNode {
|
||||
/**
|
||||
* Decompile class and loads internal lists of fields, methods, etc.
|
||||
* Do nothing if already loaded.
|
||||
* Return not null on first call only (for actual loading)
|
||||
*/
|
||||
@Nullable
|
||||
private synchronized void load() {
|
||||
@@ -135,10 +146,9 @@ public final class JavaClass implements JavaNode {
|
||||
if (fieldsCount != 0) {
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
// if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaField javaField = new JavaField(this, f);
|
||||
flds.add(javaField);
|
||||
// }
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
flds.add(rootDecompiler.convertFieldNode(f));
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
}
|
||||
@@ -148,8 +158,7 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
mths.add(rootDecompiler.convertMethodNode(m));
|
||||
}
|
||||
}
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
@@ -157,7 +166,7 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
protected JadxDecompiler getRootDecompiler() {
|
||||
JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
@@ -188,23 +197,16 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public List<Integer> getUsePlacesFor(ICodeInfo codeInfo, JavaNode javaNode) {
|
||||
Map<Integer, ICodeAnnotation> map = codeInfo.getCodeMetadata().getAsMap();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JadxDecompiler rootDec = getRootDecompiler();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : map.entrySet()) {
|
||||
ICodeAnnotation ann = entry.getValue();
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) {
|
||||
// ignore declarations
|
||||
continue;
|
||||
codeInfo.getCodeMetadata().searchDown(0, (pos, ann) -> {
|
||||
if (javaNode.isOwnCodeAnnotation(ann)) {
|
||||
result.add(pos);
|
||||
}
|
||||
JavaNode annNode = rootDec.getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
if (javaNode.equals(annNode)) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -285,7 +287,16 @@ public final class JavaClass implements JavaNode {
|
||||
if (methodNode == null) {
|
||||
return null;
|
||||
}
|
||||
return new JavaMethod(this, methodNode);
|
||||
return getRootDecompiler().convertMethodNode(methodNode);
|
||||
}
|
||||
|
||||
public List<JavaClass> getDependencies() {
|
||||
JadxDecompiler d = getRootDecompiler();
|
||||
return ListUtils.map(cls.getDependencies(), d::convertClassNode);
|
||||
}
|
||||
|
||||
public int getTotalDepsCount() {
|
||||
return cls.getTotalDepsCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -65,6 +66,14 @@ public final class JavaField implements JavaNode {
|
||||
this.field.getFieldInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.FIELD) {
|
||||
return ann.equals(field);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -18,6 +19,7 @@ import jadx.core.utils.Utils;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
||||
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
|
||||
@@ -78,7 +80,7 @@ public final class JavaMethod implements JavaNode {
|
||||
JadxDecompiler decompiler = getDeclaringClass().getRootDecompiler();
|
||||
return ovrdAttr.getRelatedMthNodes().stream()
|
||||
.map(m -> {
|
||||
JavaMethod javaMth = decompiler.getJavaMethodByNode(m);
|
||||
JavaMethod javaMth = decompiler.convertMethodNode(m);
|
||||
if (javaMth == null) {
|
||||
LOG.warn("Failed convert to java method: {}", m);
|
||||
}
|
||||
@@ -106,6 +108,14 @@ public final class JavaMethod implements JavaNode {
|
||||
this.mth.getMethodInfo().removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.METHOD) {
|
||||
return ann.equals(mth);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
@@ -18,4 +20,6 @@ public interface JavaNode {
|
||||
|
||||
default void removeAlias() {
|
||||
}
|
||||
|
||||
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
|
||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final String name;
|
||||
private final List<JavaClass> classes;
|
||||
@@ -49,6 +51,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -5,7 +5,10 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class JavaVariable implements JavaNode {
|
||||
private final JavaMethod mth;
|
||||
@@ -43,6 +46,10 @@ public class JavaVariable implements JavaNode {
|
||||
return varNode.getType() + " " + varNode.getName() + " (r" + varNode.getReg() + "v" + varNode.getSsa() + ")";
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return ArgType.tryToResolveClassAlias(mth.getMethodNode().root(), varNode.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return mth.getDeclaringClass();
|
||||
@@ -63,6 +70,15 @@ public class JavaVariable implements JavaNode {
|
||||
return Collections.singletonList(mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
VarRef varRef = (VarRef) ann;
|
||||
return varRef.getRefPos() == getDefPos();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return varNode.hashCode();
|
||||
|
||||
@@ -168,10 +168,11 @@ public class InsnGen {
|
||||
* Variable definition without type, only var name
|
||||
*/
|
||||
private void defVar(ICodeWriter code, CodeVar codeVar) {
|
||||
String varName = mgen.getNameGen().assignArg(codeVar);
|
||||
if (code.isMetadataSupported()) {
|
||||
code.attachDefinition(VarNode.get(mth, codeVar));
|
||||
}
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
code.add(varName);
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
|
||||
@@ -379,7 +379,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
}
|
||||
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
|
||||
codeCache.add(clsRawName, codeInfo);
|
||||
if (codeInfo != ICodeInfo.EMPTY) {
|
||||
codeCache.add(clsRawName, codeInfo);
|
||||
}
|
||||
return codeInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,21 +115,25 @@ public class RootNode {
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
String typeStr = classData.getType();
|
||||
String name = null;
|
||||
try {
|
||||
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
|
||||
if (clsInfo != null) {
|
||||
name = clsInfo.getShortName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get name for class with type {}", typeStr, e);
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
} catch (Exception innerExc) {
|
||||
LOG.error("Failed to load class from file: {}", classData.getInputFileName(), exc);
|
||||
}
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "CLASS_" + typeStr;
|
||||
}
|
||||
ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags());
|
||||
ErrorsCounter.error(clsNode, "Load error", exc);
|
||||
}
|
||||
|
||||
private static void markDuplicatedClasses(List<ClassNode> classes) {
|
||||
|
||||
@@ -13,9 +13,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.IDecompileScheduler;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class DecompilerScheduler implements IDecompileScheduler {
|
||||
@@ -24,18 +22,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
private static final int MERGED_BATCH_SIZE = 16;
|
||||
private static final boolean DEBUG_BATCHES = false;
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
|
||||
public DecompilerScheduler(JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
|
||||
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
|
||||
List<List<JavaClass>> result = internalBatches(classes);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
||||
}
|
||||
@@ -53,14 +44,14 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
* Put classes with many dependencies at the end.
|
||||
* Build batches for dependencies of single class to avoid locking from another thread.
|
||||
*/
|
||||
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
|
||||
public List<List<JavaClass>> internalBatches(List<JavaClass> classes) {
|
||||
List<DepInfo> deps = sumDependencies(classes);
|
||||
Set<ClassNode> added = new HashSet<>(classes.size());
|
||||
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount);
|
||||
List<List<ClassNode>> result = new ArrayList<>();
|
||||
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
Set<JavaClass> added = new HashSet<>(classes.size());
|
||||
Comparator<JavaClass> cmpDepSize = Comparator.comparingInt(JavaClass::getTotalDepsCount);
|
||||
List<List<JavaClass>> result = new ArrayList<>();
|
||||
List<JavaClass> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
for (DepInfo depInfo : deps) {
|
||||
ClassNode cls = depInfo.getCls();
|
||||
JavaClass cls = depInfo.getCls();
|
||||
if (!added.add(cls)) {
|
||||
continue;
|
||||
}
|
||||
@@ -73,9 +64,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
|
||||
}
|
||||
} else {
|
||||
List<ClassNode> batch = new ArrayList<>(depsSize + 1);
|
||||
for (ClassNode dep : cls.getDependencies()) {
|
||||
ClassNode topDep = dep.getTopParentClass();
|
||||
List<JavaClass> batch = new ArrayList<>(depsSize + 1);
|
||||
for (JavaClass dep : cls.getDependencies()) {
|
||||
JavaClass topDep = dep.getTopParentClass();
|
||||
if (!added.contains(topDep)) {
|
||||
batch.add(topDep);
|
||||
added.add(topDep);
|
||||
@@ -95,11 +86,11 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<DepInfo> sumDependencies(List<ClassNode> classes) {
|
||||
private static List<DepInfo> sumDependencies(List<JavaClass> classes) {
|
||||
List<DepInfo> deps = new ArrayList<>(classes.size());
|
||||
for (ClassNode cls : classes) {
|
||||
for (JavaClass cls : classes) {
|
||||
int count = 0;
|
||||
for (ClassNode dep : cls.getDependencies()) {
|
||||
for (JavaClass dep : cls.getDependencies()) {
|
||||
count += 1 + dep.getTotalDepsCount();
|
||||
}
|
||||
deps.add(new DepInfo(cls, count));
|
||||
@@ -109,15 +100,15 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
}
|
||||
|
||||
private static final class DepInfo implements Comparable<DepInfo> {
|
||||
private final ClassNode cls;
|
||||
private final JavaClass cls;
|
||||
private final int depsCount;
|
||||
|
||||
private DepInfo(ClassNode cls, int depsCount) {
|
||||
private DepInfo(JavaClass cls, int depsCount) {
|
||||
this.cls = cls;
|
||||
this.depsCount = depsCount;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
public JavaClass getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -129,7 +120,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
|
||||
int deps = Integer.compare(depsCount, o.depsCount);
|
||||
if (deps == 0) {
|
||||
return cls.compareTo(o.cls);
|
||||
return cls.getClassNode().compareTo(o.cls.getClassNode());
|
||||
}
|
||||
return deps;
|
||||
}
|
||||
@@ -147,9 +138,9 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
|
||||
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
|
||||
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
||||
int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1);
|
||||
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
|
||||
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
||||
LOG.info("Batches stats:"
|
||||
+ "\n input classes: " + classes.size()
|
||||
|
||||
@@ -103,6 +103,10 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteFileIfExists(Path filePath) throws IOException {
|
||||
Files.deleteIfExists(filePath);
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
File[] content = dir.listFiles();
|
||||
if (content != null) {
|
||||
@@ -115,17 +119,22 @@ public class FileUtils {
|
||||
|
||||
public static void deleteDirIfExists(Path dir) {
|
||||
if (Files.exists(dir)) {
|
||||
deleteDir(dir);
|
||||
try {
|
||||
deleteDir(dir);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to delete dir: " + dir.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteDir(Path dir) {
|
||||
private static void deleteDir(Path dir) {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
pathStream.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(file -> {
|
||||
if (!file.delete()) {
|
||||
LOG.warn("Failed to remove file: {}", file.getAbsolutePath());
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -152,11 +161,11 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
public static void deleteTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
public static void clearTempRootDir() {
|
||||
deleteDir(TEMP_ROOT_DIR);
|
||||
deleteDirIfExists(TEMP_ROOT_DIR);
|
||||
makeDirs(TEMP_ROOT_DIR);
|
||||
}
|
||||
|
||||
@@ -217,6 +226,15 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeFile(Path file, String data) throws IOException {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String readFile(Path textFile) throws IOException {
|
||||
return new String(Files.readAllBytes(textFile), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static File prepareFile(File file) {
|
||||
File saveFile = cutFileName(file);
|
||||
@@ -254,6 +272,28 @@ public class FileUtils {
|
||||
return new String(hexChars, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero padded hex string for first byte
|
||||
*/
|
||||
public static String byteToHex(int value) {
|
||||
int v = value & 0xFF;
|
||||
byte[] hexChars = new byte[] { HEX_ARRAY[v >>> 4], HEX_ARRAY[v & 0x0F] };
|
||||
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero padded hex string for int value
|
||||
*/
|
||||
public static String intToHex(int value) {
|
||||
byte[] hexChars = new byte[8];
|
||||
int v = value;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
hexChars[i] = HEX_ARRAY[v & 0x0F];
|
||||
v >>>= 4;
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public static boolean isZipFile(File file) {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
byte[] headers = new byte[4];
|
||||
|
||||
@@ -89,7 +89,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
is.mark(4);
|
||||
int v = is.readInt16(); // version
|
||||
int h = is.readInt16(); // header size
|
||||
if (v == 0x0003 && h == 0x0008) {
|
||||
// Some APK Manifest.xml the version is 0
|
||||
if (h == 0x0008) {
|
||||
return true;
|
||||
}
|
||||
is.reset();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class JadxInternalAccess {
|
||||
@@ -7,4 +10,16 @@ public class JadxInternalAccess {
|
||||
public static RootNode getRoot(JadxDecompiler d) {
|
||||
return d.getRoot();
|
||||
}
|
||||
|
||||
public static JavaClass convertClassNode(JadxDecompiler d, ClassNode clsNode) {
|
||||
return d.convertClassNode(clsNode);
|
||||
}
|
||||
|
||||
public static JavaMethod convertMethodNode(JadxDecompiler d, MethodNode mthNode) {
|
||||
return d.convertMethodNode(mthNode);
|
||||
}
|
||||
|
||||
public static JavaField convertFieldNode(JadxDecompiler d, FieldNode fldNode) {
|
||||
return d.convertFieldNode(fldNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
@@ -46,8 +46,8 @@ public class TestCodeMetadata extends IntegrationTest {
|
||||
int callDefPos = callMth.getDefPosition();
|
||||
assertThat(callDefPos).isNotZero();
|
||||
|
||||
JavaClass javaClass = Objects.requireNonNull(jadxDecompiler.getJavaClassByNode(cls));
|
||||
JavaMethod callJavaMethod = Objects.requireNonNull(jadxDecompiler.getJavaMethodByNode(callMth));
|
||||
JavaClass javaClass = JadxInternalAccess.convertClassNode(jadxDecompiler, cls);
|
||||
JavaMethod callJavaMethod = JadxInternalAccess.convertMethodNode(jadxDecompiler, callMth);
|
||||
List<Integer> callUsePlaces = javaClass.getUsePlacesFor(javaClass.getCodeInfo(), callJavaMethod);
|
||||
assertThat(callUsePlaces).hasSize(1);
|
||||
int callUse = callUsePlaces.get(0);
|
||||
|
||||
+15
-7
@@ -16,9 +16,9 @@ dependencies {
|
||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
|
||||
implementation 'com.formdev:flatlaf:2.2'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.2'
|
||||
implementation 'com.formdev:flatlaf-extras:2.2'
|
||||
implementation 'com.formdev:flatlaf:2.3'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.3'
|
||||
implementation 'com.formdev:flatlaf-extras:2.3'
|
||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
@@ -100,7 +100,6 @@ runtime {
|
||||
addModules(
|
||||
'java.desktop',
|
||||
'java.naming',
|
||||
//'java.sql', // TODO: GSON register adapter for java.sql.Time
|
||||
'java.xml',
|
||||
)
|
||||
jpackage {
|
||||
@@ -113,10 +112,9 @@ runtime {
|
||||
}
|
||||
}
|
||||
|
||||
task distWinWithJre(type: Zip, dependsOn: ['runtime', 'createExe']) {
|
||||
task copyDistWinWithJre(type: Copy, dependsOn: ['runtime', 'createExe']) {
|
||||
group 'jadx'
|
||||
destinationDirectory = buildDir
|
||||
archiveFileName = "jadx-gui-${jadxVersion}-with-jre-win.zip"
|
||||
destinationDir = new File(buildDir, "jadx-gui-${jadxVersion}-with-jre-win")
|
||||
from(runtime.jreDir) {
|
||||
include '**/*'
|
||||
into 'jre'
|
||||
@@ -126,3 +124,13 @@ task distWinWithJre(type: Zip, dependsOn: ['runtime', 'createExe']) {
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task distWinWithJre(type: Zip, dependsOn: ['copyDistWinWithJre']) {
|
||||
group 'jadx'
|
||||
destinationDirectory = buildDir
|
||||
archiveFileName = "jadx-gui-${jadxVersion}-with-jre-win.zip"
|
||||
from(copyDistWinWithJre.outputs) {
|
||||
include '**/*'
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.gui;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -11,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
@@ -20,10 +18,10 @@ import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ProcessState;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.codecache.CodeStringCache;
|
||||
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
||||
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||
@@ -35,29 +33,24 @@ import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||
public class JadxWrapper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxWrapper.class);
|
||||
|
||||
private final JadxSettings settings;
|
||||
private final MainWindow mainWindow;
|
||||
private JadxDecompiler decompiler;
|
||||
private @Nullable JadxProject project;
|
||||
private List<Path> openPaths = Collections.emptyList();
|
||||
|
||||
public JadxWrapper(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
this.decompiler = new JadxDecompiler(settings.toJadxArgs());
|
||||
public JadxWrapper(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void openFile(List<Path> paths) {
|
||||
public void open() {
|
||||
close();
|
||||
this.openPaths = paths;
|
||||
try {
|
||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
||||
jadxArgs.setInputFiles(FileUtils.toFiles(paths));
|
||||
if (project != null) {
|
||||
jadxArgs.setCodeData(project.getCodeData());
|
||||
}
|
||||
closeCodeCache();
|
||||
JadxProject project = getProject();
|
||||
JadxArgs jadxArgs = getSettings().toJadxArgs();
|
||||
jadxArgs.setInputFiles(FileUtils.toFiles(project.getFilePaths()));
|
||||
jadxArgs.setCodeData(project.getCodeData());
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.decompiler.load();
|
||||
initCodeCache(jadxArgs);
|
||||
initCodeCache();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Jadx init error", e);
|
||||
close();
|
||||
@@ -75,56 +68,36 @@ public class JadxWrapper {
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
decompiler.close();
|
||||
closeCodeCache();
|
||||
if (decompiler != null) {
|
||||
decompiler.close();
|
||||
mainWindow.getCacheObject().reset();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx decompiler close error", e);
|
||||
LOG.error("Jadx decompiler close error", e);
|
||||
} finally {
|
||||
decompiler = null;
|
||||
}
|
||||
this.openPaths = Collections.emptyList();
|
||||
}
|
||||
|
||||
private void initCodeCache(JadxArgs jadxArgs) {
|
||||
switch (settings.getCodeCacheMode()) {
|
||||
private void initCodeCache() {
|
||||
switch (getSettings().getCodeCacheMode()) {
|
||||
case MEMORY:
|
||||
jadxArgs.setCodeCache(new InMemoryCodeCache());
|
||||
getArgs().setCodeCache(new InMemoryCodeCache());
|
||||
break;
|
||||
case DISK_WITH_CACHE:
|
||||
jadxArgs.setCodeCache(new CodeStringCache(buildBufferedDiskCache()));
|
||||
getArgs().setCodeCache(new CodeStringCache(buildBufferedDiskCache()));
|
||||
break;
|
||||
case DISK:
|
||||
jadxArgs.setCodeCache(buildBufferedDiskCache());
|
||||
getArgs().setCodeCache(buildBufferedDiskCache());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferCodeCache buildBufferedDiskCache() {
|
||||
DiskCodeCache diskCache = new DiskCodeCache(decompiler.getRoot(), getCacheDir());
|
||||
DiskCodeCache diskCache = new DiskCodeCache(decompiler.getRoot(), getProject().getCacheDir());
|
||||
return new BufferCodeCache(diskCache);
|
||||
}
|
||||
|
||||
private Path getCacheDir() {
|
||||
if (project != null && project.getProjectPath() != null) {
|
||||
Path projectPath = project.getProjectPath();
|
||||
return projectPath.resolveSibling(projectPath.getFileName() + ".cache");
|
||||
}
|
||||
if (!openPaths.isEmpty()) {
|
||||
Path path = openPaths.get(0);
|
||||
return path.resolveSibling(path.getFileName() + ".cache");
|
||||
}
|
||||
throw new JadxRuntimeException("Can't get working dir");
|
||||
}
|
||||
|
||||
public void closeCodeCache() {
|
||||
ICodeCache codeCache = getArgs().getCodeCache();
|
||||
if (codeCache != null) {
|
||||
try {
|
||||
codeCache.close();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error on cache close", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
*/
|
||||
@@ -177,29 +150,29 @@ public class JadxWrapper {
|
||||
|
||||
// TODO: move to CLI and filter classes in JadxDecompiler
|
||||
public List<String> getExcludedPackages() {
|
||||
String excludedPackages = settings.getExcludedPackages().trim();
|
||||
String excludedPackages = getSettings().getExcludedPackages().trim();
|
||||
if (excludedPackages.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(excludedPackages.split("[ ]+"));
|
||||
return Arrays.asList(excludedPackages.split(" +"));
|
||||
}
|
||||
|
||||
public void setExcludedPackages(List<String> packagesToExclude) {
|
||||
settings.setExcludedPackages(String.join(" ", packagesToExclude).trim());
|
||||
settings.sync();
|
||||
getSettings().setExcludedPackages(String.join(" ", packagesToExclude).trim());
|
||||
getSettings().sync();
|
||||
}
|
||||
|
||||
public void addExcludedPackage(String packageToExclude) {
|
||||
String newExclusion = settings.getExcludedPackages() + ' ' + packageToExclude;
|
||||
settings.setExcludedPackages(newExclusion.trim());
|
||||
settings.sync();
|
||||
String newExclusion = getSettings().getExcludedPackages() + ' ' + packageToExclude;
|
||||
getSettings().setExcludedPackages(newExclusion.trim());
|
||||
getSettings().sync();
|
||||
}
|
||||
|
||||
public void removeExcludedPackage(String packageToRemoveFromExclusion) {
|
||||
List<String> list = new ArrayList<>(getExcludedPackages());
|
||||
list.remove(packageToRemoveFromExclusion);
|
||||
settings.setExcludedPackages(String.join(" ", list));
|
||||
settings.sync();
|
||||
getSettings().setExcludedPackages(String.join(" ", list));
|
||||
getSettings().sync();
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
@@ -210,10 +183,6 @@ public class JadxWrapper {
|
||||
return decompiler.getResources();
|
||||
}
|
||||
|
||||
public List<Path> getOpenPaths() {
|
||||
return openPaths;
|
||||
}
|
||||
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
@@ -222,8 +191,12 @@ public class JadxWrapper {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
|
||||
public void setProject(JadxProject project) {
|
||||
this.project = project;
|
||||
public JadxProject getProject() {
|
||||
return mainWindow.getProject();
|
||||
}
|
||||
|
||||
public JadxSettings getSettings() {
|
||||
return mainWindow.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,7 @@ public class QuarkDialog extends JDialog {
|
||||
this.files = filterOpenFiles(mainWindow);
|
||||
if (files.isEmpty()) {
|
||||
UiUtils.errorMessage(mainWindow, "Quark is unable to analyze loaded files");
|
||||
LOG.error("Quark: The files cannot be analyzed: {}", mainWindow.getWrapper().getOpenPaths());
|
||||
LOG.error("Quark: The files cannot be analyzed: {}", mainWindow.getProject().getFilePaths());
|
||||
return;
|
||||
}
|
||||
initUI();
|
||||
@@ -47,7 +47,7 @@ public class QuarkDialog extends JDialog {
|
||||
|
||||
private List<Path> filterOpenFiles(MainWindow mainWindow) {
|
||||
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**.{apk,dex}");
|
||||
return mainWindow.getWrapper().getOpenPaths()
|
||||
return mainWindow.getProject().getFilePaths()
|
||||
.stream()
|
||||
.filter(matcher::matches)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -41,30 +43,26 @@ public class JadxProject {
|
||||
private static final int CURRENT_PROJECT_VERSION = 1;
|
||||
public static final String PROJECT_EXTENSION = "jadx";
|
||||
|
||||
private transient MainWindow mainWindow;
|
||||
private transient JadxSettings settings;
|
||||
private final transient MainWindow mainWindow;
|
||||
|
||||
private transient String name = "New Project";
|
||||
private transient Path projectPath;
|
||||
private transient @Nullable Path projectPath;
|
||||
|
||||
private transient boolean initial = true;
|
||||
private transient boolean saved;
|
||||
|
||||
private ProjectData data = new ProjectData();
|
||||
|
||||
public void setSettings(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setMainWindow(MainWindow mainWindow) {
|
||||
public JadxProject(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path getProjectPath() {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
private void setProjectPath(Path projectPath) {
|
||||
private void setProjectPath(@NotNull Path projectPath) {
|
||||
this.projectPath = projectPath;
|
||||
this.name = CommonFileUtils.removeFileExtension(projectPath.getFileName().toString());
|
||||
changed();
|
||||
@@ -74,7 +72,7 @@ public class JadxProject {
|
||||
return data.getFiles();
|
||||
}
|
||||
|
||||
public void setFilePath(List<Path> files) {
|
||||
public void setFilePaths(List<Path> files) {
|
||||
if (!files.equals(getFilePaths())) {
|
||||
data.setFiles(files);
|
||||
String joinedName = files.stream().map(p -> CommonFileUtils.removeFileExtension(p.getFileName().toString()))
|
||||
@@ -144,22 +142,52 @@ public class JadxProject {
|
||||
return data.getActiveTab();
|
||||
}
|
||||
|
||||
public @NotNull Path getCacheDir() {
|
||||
Path cacheDir = data.getCacheDir();
|
||||
if (cacheDir != null) {
|
||||
return cacheDir;
|
||||
}
|
||||
Path newCacheDir = buildCacheDir();
|
||||
setCacheDir(newCacheDir);
|
||||
return newCacheDir;
|
||||
}
|
||||
|
||||
public void setCacheDir(Path cacheDir) {
|
||||
data.setCacheDir(cacheDir);
|
||||
changed();
|
||||
}
|
||||
|
||||
private Path buildCacheDir() {
|
||||
if (projectPath != null) {
|
||||
return projectPath.resolveSibling(projectPath.getFileName() + ".cache");
|
||||
}
|
||||
List<Path> files = data.getFiles();
|
||||
if (!files.isEmpty()) {
|
||||
Path path = files.get(0);
|
||||
return path.resolveSibling(path.getFileName() + ".cache");
|
||||
}
|
||||
throw new JadxRuntimeException("Can't get working dir");
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
JadxSettings settings = mainWindow.getSettings();
|
||||
if (settings != null && settings.isAutoSaveProject()) {
|
||||
save();
|
||||
} else {
|
||||
saved = false;
|
||||
}
|
||||
initial = false;
|
||||
if (mainWindow != null) {
|
||||
mainWindow.updateProject(this);
|
||||
}
|
||||
mainWindow.updateProject(this);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isSaveFileSelected() {
|
||||
return projectPath != null;
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
@@ -186,10 +214,10 @@ public class JadxProject {
|
||||
}
|
||||
}
|
||||
|
||||
public static JadxProject from(Path path) {
|
||||
public static JadxProject load(MainWindow mainWindow, Path path) {
|
||||
Path basePath = path.toAbsolutePath().getParent();
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
JadxProject project = new JadxProject();
|
||||
JadxProject project = new JadxProject(mainWindow);
|
||||
project.data = buildGson(basePath).fromJson(reader, ProjectData.class);
|
||||
project.saved = true;
|
||||
project.setProjectPath(path);
|
||||
|
||||
@@ -138,7 +138,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (needReload) {
|
||||
mainWindow.reOpenFile();
|
||||
mainWindow.reopen();
|
||||
}
|
||||
if (!settings.getLangLocale().equals(prevLang)) {
|
||||
JOptionPane.showMessageDialog(
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
|
||||
public class ProjectData {
|
||||
@@ -16,6 +18,7 @@ public class ProjectData {
|
||||
private JadxCodeData codeData = new JadxCodeData();
|
||||
private List<TabViewState> openTabs = Collections.emptyList();
|
||||
private int activeTab = -1;
|
||||
private @Nullable Path cacheDir;
|
||||
|
||||
public List<Path> getFiles() {
|
||||
return files;
|
||||
@@ -82,4 +85,13 @@ public class ProjectData {
|
||||
this.activeTab = activeTab;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path getCacheDir() {
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
public void setCacheDir(Path cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,12 @@ public class JField extends JNode {
|
||||
return UiUtils.typeFormatHtml(field.getFullName(), field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip() {
|
||||
String fullType = UiUtils.escapeHtml(field.getType().toString());
|
||||
return UiUtils.wrapHtml(fullType + ' ' + UiUtils.escapeHtml(field.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeDescString() {
|
||||
return UiUtils.typeStr(field.getType()) + " " + field.getName();
|
||||
|
||||
@@ -133,7 +133,7 @@ public class JRoot extends JNode {
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
List<Path> paths = wrapper.getOpenPaths();
|
||||
List<Path> paths = wrapper.getProject().getFilePaths();
|
||||
int count = paths.size();
|
||||
if (count == 0) {
|
||||
return "File not open";
|
||||
@@ -146,7 +146,7 @@ public class JRoot extends JNode {
|
||||
|
||||
@Override
|
||||
public String getTooltip() {
|
||||
List<Path> paths = wrapper.getOpenPaths();
|
||||
List<Path> paths = wrapper.getProject().getFilePaths();
|
||||
int count = paths.size();
|
||||
if (count < 2) {
|
||||
return null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import javax.swing.Icon;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JVariable extends JNode {
|
||||
private static final long serialVersionUID = -3002100457834453783L;
|
||||
@@ -55,6 +56,18 @@ public class JVariable extends JNode {
|
||||
return var.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeLongStringHtml() {
|
||||
return UiUtils.typeFormatHtml(var.getName(), var.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTooltip() {
|
||||
String name = var.getName() + " (r" + var.getReg() + "v" + var.getSsa() + ")";
|
||||
String fullType = UiUtils.escapeHtml(var.getType().toString());
|
||||
return UiUtils.wrapHtml(fullType + ' ' + UiUtils.escapeHtml(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
return true;
|
||||
|
||||
@@ -85,7 +85,6 @@ import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.device.debugger.BreakpointManager;
|
||||
@@ -170,8 +169,9 @@ public class MainWindow extends JFrame {
|
||||
private final transient JadxSettings settings;
|
||||
private final transient CacheObject cacheObject;
|
||||
private final transient BackgroundExecutor backgroundExecutor;
|
||||
@NotNull
|
||||
private transient JadxProject project = new JadxProject();
|
||||
|
||||
private transient @NotNull JadxProject project;
|
||||
|
||||
private transient Action newProjectAction;
|
||||
private transient Action saveProjectAction;
|
||||
|
||||
@@ -202,7 +202,8 @@ public class MainWindow extends JFrame {
|
||||
public MainWindow(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
this.cacheObject = new CacheObject();
|
||||
this.wrapper = new JadxWrapper(settings);
|
||||
this.project = new JadxProject(this);
|
||||
this.wrapper = new JadxWrapper(this);
|
||||
|
||||
resetCache();
|
||||
FontUtils.registerBundledFonts();
|
||||
@@ -211,11 +212,11 @@ public class MainWindow extends JFrame {
|
||||
registerMouseNavigationButtons();
|
||||
UiUtils.setWindowIcons(this);
|
||||
loadSettings();
|
||||
update();
|
||||
|
||||
this.backgroundExecutor = new BackgroundExecutor(this);
|
||||
|
||||
checkForUpdate();
|
||||
newProject();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
@@ -275,6 +276,10 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
public void openFileOrProject() {
|
||||
saveAll();
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.OPEN);
|
||||
List<Path> openPaths = fileDialog.show();
|
||||
if (!openPaths.isEmpty()) {
|
||||
@@ -287,20 +292,25 @@ public class MainWindow extends JFrame {
|
||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.ADD);
|
||||
List<Path> addPaths = fileDialog.show();
|
||||
if (!addPaths.isEmpty()) {
|
||||
open(ListUtils.distinctMergeSortedLists(addPaths, wrapper.getOpenPaths()));
|
||||
addFiles(addPaths);
|
||||
}
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> addPaths) {
|
||||
project.setFilePaths(ListUtils.distinctMergeSortedLists(addPaths, project.getFilePaths()));
|
||||
reopen();
|
||||
}
|
||||
|
||||
private void newProject() {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
closeAll();
|
||||
updateProject(new JadxProject());
|
||||
updateProject(new JadxProject(this));
|
||||
}
|
||||
|
||||
private void saveProject() {
|
||||
if (project.getProjectPath() == null) {
|
||||
if (!project.isSaveFileSelected()) {
|
||||
saveProjectAs();
|
||||
} else {
|
||||
project.save();
|
||||
@@ -312,9 +322,8 @@ public class MainWindow extends JFrame {
|
||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.SAVE_PROJECT);
|
||||
if (project.getFilePaths().size() == 1) {
|
||||
// If there is only one file loaded we suggest saving the jadx project file next to the loaded file
|
||||
Path loadedFile = this.project.getFilePaths().get(0);
|
||||
String fileName = loadedFile.getFileName() + "." + JadxProject.PROJECT_EXTENSION;
|
||||
fileDialog.setSelectedFile(loadedFile.resolveSibling(fileName));
|
||||
Path projectPath = getProjectPathForFile(this.project.getFilePaths().get(0));
|
||||
fileDialog.setSelectedFile(projectPath);
|
||||
}
|
||||
List<Path> saveFiles = fileDialog.show();
|
||||
if (saveFiles.isEmpty()) {
|
||||
@@ -344,29 +353,66 @@ public class MainWindow extends JFrame {
|
||||
open(paths, EMPTY_RUNNABLE);
|
||||
}
|
||||
|
||||
void open(List<Path> paths, Runnable onFinish) {
|
||||
private void open(List<Path> paths, Runnable onFinish) {
|
||||
saveAll();
|
||||
closeAll();
|
||||
if (paths.size() == 1) {
|
||||
Path singleFile = paths.get(0);
|
||||
String fileExtension = CommonFileUtils.getFileExtension(singleFile.getFileName().toString());
|
||||
if (fileExtension != null && fileExtension.equalsIgnoreCase(JadxProject.PROJECT_EXTENSION)) {
|
||||
List<Path> projectFiles = openProject(singleFile);
|
||||
if (!Utils.isEmpty(projectFiles)) {
|
||||
openFiles(projectFiles, onFinish);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (paths.size() == 1 && openSingleFile(paths.get(0), onFinish)) {
|
||||
return;
|
||||
}
|
||||
openFiles(paths, onFinish);
|
||||
// start new project
|
||||
project = new JadxProject(this);
|
||||
project.setFilePaths(paths);
|
||||
loadFiles(onFinish);
|
||||
}
|
||||
|
||||
private void openFiles(List<Path> paths, Runnable onFinish) {
|
||||
project.setFilePath(paths);
|
||||
if (paths.isEmpty()) {
|
||||
private boolean openSingleFile(Path singleFile, Runnable onFinish) {
|
||||
String fileExtension = CommonFileUtils.getFileExtension(singleFile.getFileName().toString());
|
||||
if (fileExtension != null && fileExtension.equalsIgnoreCase(JadxProject.PROJECT_EXTENSION)) {
|
||||
openProject(singleFile, onFinish);
|
||||
return true;
|
||||
}
|
||||
// check if project file already saved with default name
|
||||
Path projectPath = getProjectPathForFile(singleFile);
|
||||
if (Files.exists(projectPath)) {
|
||||
LOG.info("Loading project for this file");
|
||||
openProject(projectPath, onFinish);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Path getProjectPathForFile(Path loadedFile) {
|
||||
String fileName = loadedFile.getFileName() + "." + JadxProject.PROJECT_EXTENSION;
|
||||
return loadedFile.resolveSibling(fileName);
|
||||
}
|
||||
|
||||
public void reopen() {
|
||||
saveAll();
|
||||
closeAll();
|
||||
loadFiles(EMPTY_RUNNABLE);
|
||||
}
|
||||
|
||||
private void openProject(Path path, Runnable onFinish) {
|
||||
JadxProject jadxProject = JadxProject.load(this, path);
|
||||
if (jadxProject == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
NLS.str("msg.project_error"),
|
||||
NLS.str("msg.project_error_title"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
jadxProject = new JadxProject(this);
|
||||
}
|
||||
settings.addRecentProject(path);
|
||||
project = jadxProject;
|
||||
loadFiles(onFinish);
|
||||
}
|
||||
|
||||
private void loadFiles(Runnable onFinish) {
|
||||
if (project.getFilePaths().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||
() -> wrapper.openFile(paths),
|
||||
wrapper::open,
|
||||
status -> {
|
||||
if (status == TaskStatus.CANCEL_BY_MEMORY) {
|
||||
showHeapUsageBar();
|
||||
@@ -374,16 +420,20 @@ public class MainWindow extends JFrame {
|
||||
return;
|
||||
}
|
||||
checkLoadedStatus();
|
||||
onOpen(paths);
|
||||
onOpen();
|
||||
onFinish.run();
|
||||
});
|
||||
}
|
||||
|
||||
private void saveAll() {
|
||||
saveOpenTabs();
|
||||
BreakpointManager.saveAndExit();
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
cancelBackgroundJobs();
|
||||
saveOpenTabs();
|
||||
clearTree();
|
||||
BreakpointManager.saveAndExit();
|
||||
resetCache();
|
||||
LogCollector.getInstance().reset();
|
||||
wrapper.close();
|
||||
tabbedPane.closeAllTabs();
|
||||
@@ -409,11 +459,11 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void onOpen(List<Path> paths) {
|
||||
private void onOpen() {
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
initTree();
|
||||
update();
|
||||
BreakpointManager.init(paths.get(0).toAbsolutePath().getParent());
|
||||
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
|
||||
|
||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||
this::restoreOpenTabs,
|
||||
@@ -426,7 +476,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private boolean ensureProjectIsSaved() {
|
||||
if (project != null && !project.isSaved() && !project.isInitial()) {
|
||||
if (!project.isSaved() && !project.isInitial()) {
|
||||
int res = JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
NLS.str("confirm.not_saved_message"),
|
||||
@@ -442,29 +492,8 @@ public class MainWindow extends JFrame {
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Path> openProject(Path path) {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JadxProject jadxProject = JadxProject.from(path);
|
||||
if (jadxProject == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
NLS.str("msg.project_error"),
|
||||
NLS.str("msg.project_error_title"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
jadxProject = new JadxProject();
|
||||
}
|
||||
updateProject(jadxProject);
|
||||
settings.addRecentProject(path);
|
||||
return jadxProject.getFilePaths();
|
||||
}
|
||||
|
||||
public void updateProject(@NotNull JadxProject jadxProject) {
|
||||
jadxProject.setSettings(settings);
|
||||
jadxProject.setMainWindow(this);
|
||||
this.project = jadxProject;
|
||||
this.wrapper.setProject(jadxProject);
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -548,14 +577,6 @@ public class MainWindow extends JFrame {
|
||||
backgroundExecutor.cancelAll();
|
||||
}
|
||||
|
||||
public void reOpenFile() {
|
||||
List<Path> openedFile = wrapper.getOpenPaths();
|
||||
if (openedFile != null) {
|
||||
saveOpenTabs();
|
||||
open(openedFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAll(boolean export) {
|
||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.EXPORT);
|
||||
List<Path> saveDirs = fileDialog.show();
|
||||
@@ -588,7 +609,6 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private void clearTree() {
|
||||
tabbedPane.reset();
|
||||
resetCache();
|
||||
treeRoot = null;
|
||||
treeModel.setRoot(null);
|
||||
treeModel.reload();
|
||||
@@ -651,7 +671,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
deobfToggleBtn.setSelected(deobfOn);
|
||||
deobfMenuItem.setState(deobfOn);
|
||||
reOpenFile();
|
||||
reopen();
|
||||
}
|
||||
|
||||
private boolean nodeClickAction(@Nullable Object obj) {
|
||||
@@ -1311,7 +1331,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void closeWindow() {
|
||||
saveOpenTabs();
|
||||
saveAll();
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
@@ -1321,20 +1341,16 @@ public class MainWindow extends JFrame {
|
||||
if (debuggerPanel != null) {
|
||||
saveSplittersInfo();
|
||||
}
|
||||
cancelBackgroundJobs();
|
||||
wrapper.close();
|
||||
heapUsageBar.reset();
|
||||
dispose();
|
||||
closeAll();
|
||||
|
||||
BreakpointManager.saveAndExit();
|
||||
FileUtils.deleteTempRootDir();
|
||||
dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private void saveOpenTabs() {
|
||||
if (project != null) {
|
||||
project.saveOpenTabs(tabbedPane.getEditorViewStates(), tabbedPane.getSelectedIndex());
|
||||
}
|
||||
project.saveOpenTabs(tabbedPane.getEditorViewStates(), tabbedPane.getSelectedIndex());
|
||||
}
|
||||
|
||||
private void restoreOpenTabs() {
|
||||
@@ -1432,7 +1448,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
@Override
|
||||
public void menuSelected(MenuEvent menuEvent) {
|
||||
Set<Path> current = new HashSet<>(wrapper.getOpenPaths());
|
||||
Set<Path> current = new HashSet<>(project.getFilePaths());
|
||||
List<JMenuItem> items = settings.getRecentProjects()
|
||||
.stream()
|
||||
.filter(path -> !current.contains(path))
|
||||
|
||||
@@ -20,7 +20,6 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -244,8 +243,8 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
ICodeInfo codeInfo = getCodeInfo();
|
||||
if (codeInfo.hasMetadata()) {
|
||||
ICodeAnnotation ann = codeInfo.getCodeMetadata().getAt(pos);
|
||||
if (ann instanceof ClassNode) {
|
||||
return getDecompiler().getJavaClassByNode(((ClassNode) ann));
|
||||
if (ann != null && ann.getAnnType() == ICodeAnnotation.AnnType.CLASS) {
|
||||
return (JavaClass) getDecompiler().getJavaNodeByCodeAnnotation(codeInfo, ann);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -92,15 +92,24 @@ public final class FridaAction extends JNodeAction {
|
||||
} else {
|
||||
functionUntilImplementation = String.format("%s[\"%s\"].implementation", shortClassName, methodName);
|
||||
}
|
||||
String functionParametersString = String.join(", ", collectMethodArgNames(javaMethod));
|
||||
|
||||
List<String> methodArgNames = collectMethodArgNames(javaMethod);
|
||||
|
||||
String functionParametersString = String.join(", ", methodArgNames);
|
||||
String logParametersString =
|
||||
methodArgNames.stream().map(e -> String.format("'%s: ' + %s", e, e)).collect(Collectors.joining(" + ', ' + "));
|
||||
if (logParametersString.length() > 0) {
|
||||
logParametersString = " + ', ' + " + logParametersString;
|
||||
}
|
||||
String functionParameterAndBody = String.format(
|
||||
"%s = function(%s){\n"
|
||||
+ " console.log('%s is called');\n"
|
||||
"%s = function (%s) {\n"
|
||||
+ " console.log('%s is called'%s);\n"
|
||||
+ " let ret = this.%s(%s);\n"
|
||||
+ " console.log('%s ret value is ' + ret);\n"
|
||||
+ " return ret;\n"
|
||||
+ "};",
|
||||
functionUntilImplementation, functionParametersString, methodName, methodName, functionParametersString, methodName);
|
||||
functionUntilImplementation, functionParametersString, methodName, logParametersString, methodName,
|
||||
functionParametersString, methodName);
|
||||
|
||||
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ExcludePkgDialog extends JDialog {
|
||||
|
||||
btnOk.addActionListener(e -> {
|
||||
mainWindow.getWrapper().setExcludedPackages(getExcludes());
|
||||
mainWindow.reOpenFile();
|
||||
mainWindow.reopen();
|
||||
dispose();
|
||||
});
|
||||
btnAll.addActionListener(e -> {
|
||||
|
||||
@@ -51,7 +51,15 @@ public class FileDialog {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
currentDir = fileChooser.getCurrentDirectory().toPath();
|
||||
return FileUtils.toPaths(fileChooser.getSelectedFiles());
|
||||
File[] selectedFiles = fileChooser.getSelectedFiles();
|
||||
if (selectedFiles.length != 0) {
|
||||
return FileUtils.toPaths(selectedFiles);
|
||||
}
|
||||
File chosenFile = fileChooser.getSelectedFile();
|
||||
if (chosenFile != null) {
|
||||
return Collections.singletonList(chosenFile.toPath());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public Path getCurrentDir() {
|
||||
|
||||
@@ -100,10 +100,6 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler();
|
||||
List<Integer> usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
|
||||
for (int pos : usePositions) {
|
||||
if (searchNode.getTopParentClass().equals(topUseClass) && pos == searchNode.getDefPos()) {
|
||||
// skip declaration
|
||||
continue;
|
||||
}
|
||||
String line = CodeUtils.getLineForPos(code, pos);
|
||||
if (line.startsWith("import ")) {
|
||||
continue;
|
||||
|
||||
@@ -120,7 +120,7 @@ public class JPackagePopupMenu extends JPopupMenu {
|
||||
} else {
|
||||
wrapper.removeExcludedPackage(fullName);
|
||||
}
|
||||
mainWindow.reOpenFile();
|
||||
mainWindow.reopen();
|
||||
});
|
||||
return excludeItem;
|
||||
}
|
||||
|
||||
@@ -120,9 +120,15 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static String typeFormatHtml(String name, ArgType type) {
|
||||
return "<html><body><nobr>" + escapeHtml(name)
|
||||
+ "<span style='color:#888888;'> " + escapeHtml(typeStr(type)) + "</span>"
|
||||
+ "</nobr></body></html>";
|
||||
return wrapHtml(escapeHtml(name) + ' ' + fadeHtml(escapeHtml(typeStr(type))));
|
||||
}
|
||||
|
||||
public static String fadeHtml(String htmlStr) {
|
||||
return "<span style='color:#888888;'>" + htmlStr + "</span>"; // TODO: get color from theme
|
||||
}
|
||||
|
||||
public static String wrapHtml(String htmlStr) {
|
||||
return "<html><body><nobr>" + htmlStr + "</nobr></body></html>";
|
||||
}
|
||||
|
||||
public static String escapeHtml(String str) {
|
||||
|
||||
@@ -25,6 +25,8 @@ import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.utils.codecache.disk.adapters.CodeAnnotationAdapter;
|
||||
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt;
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
@@ -68,8 +70,8 @@ public class CodeMetadataAdapter {
|
||||
private void writeLines(DataOutput out, Map<Integer, Integer> lines) throws IOException {
|
||||
out.writeInt(lines.size());
|
||||
for (Map.Entry<Integer, Integer> entry : lines.entrySet()) {
|
||||
out.writeShort(entry.getKey());
|
||||
out.writeShort(entry.getValue());
|
||||
writeUVInt(out, entry.getKey());
|
||||
writeUVInt(out, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +82,8 @@ public class CodeMetadataAdapter {
|
||||
}
|
||||
Map<Integer, Integer> lines = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int key = in.readShort();
|
||||
int value = in.readShort();
|
||||
int key = readUVInt(in);
|
||||
int value = readUVInt(in);
|
||||
lines.put(key, value);
|
||||
}
|
||||
return lines;
|
||||
@@ -90,7 +92,7 @@ public class CodeMetadataAdapter {
|
||||
private void writeAnnotations(DataOutputStream out, Map<Integer, ICodeAnnotation> annotations) throws IOException {
|
||||
out.writeInt(annotations.size());
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : annotations.entrySet()) {
|
||||
out.writeInt(entry.getKey());
|
||||
writeUVInt(out, entry.getKey());
|
||||
codeAnnotationAdapter.write(out, entry.getValue());
|
||||
}
|
||||
}
|
||||
@@ -102,7 +104,7 @@ public class CodeMetadataAdapter {
|
||||
}
|
||||
Map<Integer, ICodeAnnotation> map = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int pos = in.readInt();
|
||||
int pos = readUVInt(in);
|
||||
ICodeAnnotation ann = codeAnnotationAdapter.read(in);
|
||||
if (ann != null) {
|
||||
map.put(pos, ann);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package jadx.gui.utils.codecache.disk;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -34,30 +34,38 @@ import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
public class DiskCodeCache implements ICodeCache {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DiskCodeCache.class);
|
||||
|
||||
private static final int DATA_FORMAT_VERSION = 7;
|
||||
private static final int DATA_FORMAT_VERSION = 11;
|
||||
|
||||
private static final byte[] JADX_NAMES_MAP_HEADER = "jadxnm".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
private final Path srcDir;
|
||||
private final Path metaDir;
|
||||
private final Path codeVersionFile;
|
||||
private final Path namesMapFile;
|
||||
private final String codeVersion;
|
||||
private final CodeMetadataAdapter codeMetadataAdapter;
|
||||
private final ExecutorService writePool;
|
||||
private final Map<String, ICodeInfo> writeOps = new ConcurrentHashMap<>();
|
||||
private final Set<String> cachedKeys = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Map<String, Integer> namesMap = new ConcurrentHashMap<>();
|
||||
|
||||
public DiskCodeCache(RootNode root, Path baseDir) {
|
||||
srcDir = baseDir.resolve("sources");
|
||||
metaDir = baseDir.resolve("metadata");
|
||||
codeVersionFile = baseDir.resolve("code-version");
|
||||
namesMapFile = baseDir.resolve("names-map");
|
||||
JadxArgs args = root.getArgs();
|
||||
codeVersion = buildCodeVersion(args);
|
||||
writePool = Executors.newFixedThreadPool(args.getThreadsCount());
|
||||
codeMetadataAdapter = new CodeMetadataAdapter(root);
|
||||
if (checkCodeVersion()) {
|
||||
collectCachedItems();
|
||||
loadNamesMap();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
@@ -68,7 +76,7 @@ public class DiskCodeCache implements ICodeCache {
|
||||
if (!Files.exists(codeVersionFile)) {
|
||||
return false;
|
||||
}
|
||||
String currentCodeVer = readFileToString(codeVersionFile);
|
||||
String currentCodeVer = FileUtils.readFile(codeVersionFile);
|
||||
return currentCodeVer.equals(codeVersion);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to load code version file", e);
|
||||
@@ -82,38 +90,17 @@ public class DiskCodeCache implements ICodeCache {
|
||||
LOG.info("Resetting disk code cache, base dir: {}", srcDir.getParent().toAbsolutePath());
|
||||
FileUtils.deleteDirIfExists(srcDir);
|
||||
FileUtils.deleteDirIfExists(metaDir);
|
||||
FileUtils.deleteFileIfExists(namesMapFile);
|
||||
FileUtils.makeDirs(srcDir);
|
||||
FileUtils.makeDirs(metaDir);
|
||||
writeFile(codeVersionFile, codeVersion);
|
||||
cachedKeys.clear();
|
||||
FileUtils.writeFile(codeVersionFile, codeVersion);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.info("Reset done in: {}ms", System.currentTimeMillis() - start);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to reset code cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectCachedItems() {
|
||||
cachedKeys.clear();
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
PathMatcher matcher = metaDir.getFileSystem().getPathMatcher("glob:**.jadxmd");
|
||||
Files.walkFileTree(metaDir, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (matcher.matches(file)) {
|
||||
Path relPath = metaDir.relativize(file);
|
||||
String filePath = relPath.toString();
|
||||
String clsName = filePath.substring(0, filePath.length() - 7).replace(File.separatorChar, '.');
|
||||
cachedKeys.add(clsName);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
LOG.info("Found {} classes in disk cache in {} ms", cachedKeys.size(), System.currentTimeMillis() - start);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to collect cached items", e);
|
||||
} finally {
|
||||
namesMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +110,11 @@ public class DiskCodeCache implements ICodeCache {
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
writeOps.put(clsFullName, codeInfo);
|
||||
cachedKeys.add(clsFullName);
|
||||
int clsId = getClsId(clsFullName);
|
||||
writePool.execute(() -> {
|
||||
try {
|
||||
writeFile(getJavaFile(clsFullName), codeInfo.getCodeStr());
|
||||
codeMetadataAdapter.write(getMetadataFile(clsFullName), codeInfo.getCodeMetadata());
|
||||
FileUtils.writeFile(getJavaFile(clsId), codeInfo.getCodeStr());
|
||||
codeMetadataAdapter.write(getMetadataFile(clsId), codeInfo.getCodeMetadata());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to write code cache for " + clsFullName, e);
|
||||
remove(clsFullName);
|
||||
@@ -147,11 +134,12 @@ public class DiskCodeCache implements ICodeCache {
|
||||
if (wrtCodeInfo != null) {
|
||||
return wrtCodeInfo.getCodeStr();
|
||||
}
|
||||
Path javaFile = getJavaFile(clsFullName);
|
||||
int clsId = getClsId(clsFullName);
|
||||
Path javaFile = getJavaFile(clsId);
|
||||
if (!Files.exists(javaFile)) {
|
||||
return null;
|
||||
}
|
||||
return readFileToString(javaFile);
|
||||
return FileUtils.readFile(javaFile);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to read class code for {}", clsFullName, e);
|
||||
return null;
|
||||
@@ -168,12 +156,13 @@ public class DiskCodeCache implements ICodeCache {
|
||||
if (wrtCodeInfo != null) {
|
||||
return wrtCodeInfo;
|
||||
}
|
||||
Path javaFile = getJavaFile(clsFullName);
|
||||
int clsId = getClsId(clsFullName);
|
||||
Path javaFile = getJavaFile(clsId);
|
||||
if (!Files.exists(javaFile)) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
String code = readFileToString(javaFile);
|
||||
return codeMetadataAdapter.readAndBuild(getMetadataFile(clsFullName), code);
|
||||
String code = FileUtils.readFile(javaFile);
|
||||
return codeMetadataAdapter.readAndBuild(getMetadataFile(clsId), code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to read code cache for {}", clsFullName, e);
|
||||
return ICodeInfo.EMPTY;
|
||||
@@ -182,34 +171,23 @@ public class DiskCodeCache implements ICodeCache {
|
||||
|
||||
@Override
|
||||
public boolean contains(String clsFullName) {
|
||||
return cachedKeys.contains(clsFullName);
|
||||
return namesMap.containsKey(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
try {
|
||||
LOG.debug("Removing class info from disk: {}", clsFullName);
|
||||
cachedKeys.remove(clsFullName);
|
||||
Files.deleteIfExists(getJavaFile(clsFullName));
|
||||
Files.deleteIfExists(getMetadataFile(clsFullName));
|
||||
Integer clsId = namesMap.remove(clsFullName);
|
||||
if (clsId != null) {
|
||||
Files.deleteIfExists(getJavaFile(clsId));
|
||||
Files.deleteIfExists(getMetadataFile(clsId));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to remove code cache for " + clsFullName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String readFileToString(Path textFile) throws IOException {
|
||||
return new String(Files.readAllBytes(textFile), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private void writeFile(Path file, String data) {
|
||||
try {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to write file: {}", file.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildCodeVersion(JadxArgs args) {
|
||||
return DATA_FORMAT_VERSION
|
||||
+ ":" + args.makeCodeArgsHash()
|
||||
@@ -237,18 +215,65 @@ public class DiskCodeCache implements ICodeCache {
|
||||
}
|
||||
}
|
||||
|
||||
private Path getJavaFile(String clsFullName) {
|
||||
return srcDir.resolve(clsFullName.replace('.', File.separatorChar) + ".java");
|
||||
private int getClsId(String clsFullName) {
|
||||
return namesMap.computeIfAbsent(clsFullName, n -> namesMap.size());
|
||||
}
|
||||
|
||||
private Path getMetadataFile(String clsFullName) {
|
||||
return metaDir.resolve(clsFullName.replace('.', File.separatorChar) + ".jadxmd");
|
||||
private void saveNamesMap() {
|
||||
LOG.debug("Saving names map for disk cache...");
|
||||
try (OutputStream fileOutput = Files.newOutputStream(namesMapFile, WRITE, CREATE, TRUNCATE_EXISTING);
|
||||
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fileOutput))) {
|
||||
out.write(JADX_NAMES_MAP_HEADER);
|
||||
out.writeInt(namesMap.size());
|
||||
for (Map.Entry<String, Integer> entry : namesMap.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
out.writeInt(entry.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to save names map file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNamesMap() {
|
||||
if (!Files.exists(namesMapFile)) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
namesMap.clear();
|
||||
try (InputStream fileInput = Files.newInputStream(namesMapFile);
|
||||
DataInputStream in = new DataInputStream(new BufferedInputStream(fileInput))) {
|
||||
in.skipBytes(JADX_NAMES_MAP_HEADER.length);
|
||||
int count = in.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String clsName = in.readUTF();
|
||||
int clsId = in.readInt();
|
||||
namesMap.put(clsName, clsId);
|
||||
}
|
||||
LOG.info("Found {} classes in disk cache, dir: {}", count, metaDir.getParent());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load names map file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getJavaFile(int clsId) {
|
||||
return srcDir.resolve(getPathForClsId(clsId, ".java"));
|
||||
}
|
||||
|
||||
private Path getMetadataFile(int clsId) {
|
||||
return metaDir.resolve(getPathForClsId(clsId, ".jadxmd"));
|
||||
}
|
||||
|
||||
private Path getPathForClsId(int clsId, String ext) {
|
||||
// all classes divided between 256 top level folders
|
||||
String firstByte = FileUtils.byteToHex(clsId);
|
||||
return Paths.get(firstByte, FileUtils.intToHex(clsId) + ext);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
saveNamesMap();
|
||||
writePool.shutdown();
|
||||
writePool.awaitTermination(2, TimeUnit.MINUTES);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
+131
-13
@@ -3,37 +3,155 @@ package jadx.gui.utils.codecache.disk.adapters;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ArgTypeAdapter implements DataAdapter<ArgType> {
|
||||
|
||||
public static final ArgTypeAdapter INSTANCE = new ArgTypeAdapter();
|
||||
|
||||
private enum Types {
|
||||
NULL,
|
||||
UNKNOWN,
|
||||
PRIMITIVE,
|
||||
ARRAY,
|
||||
OBJECT,
|
||||
WILDCARD,
|
||||
GENERIC,
|
||||
TYPE_VARIABLE,
|
||||
OUTER_GENERIC
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out, ArgType value) throws IOException {
|
||||
if (value == null) {
|
||||
out.writeByte(0);
|
||||
} else if (!value.isTypeKnown()) {
|
||||
out.write(1);
|
||||
} else {
|
||||
out.writeByte(2);
|
||||
out.writeUTF(TypeGen.signature(value));
|
||||
writeType(out, Types.NULL);
|
||||
return;
|
||||
}
|
||||
if (!value.isTypeKnown()) {
|
||||
writeType(out, Types.UNKNOWN);
|
||||
return;
|
||||
}
|
||||
if (value.isPrimitive()) {
|
||||
writeType(out, Types.PRIMITIVE);
|
||||
out.writeByte(value.getPrimitiveType().getShortName().charAt(0));
|
||||
return;
|
||||
}
|
||||
if (value.getOuterType() != null) {
|
||||
writeType(out, Types.OUTER_GENERIC);
|
||||
write(out, value.getOuterType());
|
||||
write(out, value.getInnerType());
|
||||
return;
|
||||
}
|
||||
if (value.getWildcardType() != null) {
|
||||
writeType(out, Types.WILDCARD);
|
||||
ArgType.WildcardBound bound = value.getWildcardBound();
|
||||
out.writeByte(bound.getNum());
|
||||
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||
write(out, value.getWildcardType());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (value.isGeneric()) {
|
||||
writeType(out, Types.GENERIC);
|
||||
out.writeUTF(value.getObject());
|
||||
writeTypesList(out, value.getGenericTypes());
|
||||
return;
|
||||
}
|
||||
if (value.isGenericType()) {
|
||||
writeType(out, Types.TYPE_VARIABLE);
|
||||
out.writeUTF(value.getObject());
|
||||
writeTypesList(out, value.getExtendTypes());
|
||||
return;
|
||||
}
|
||||
if (value.isObject()) {
|
||||
writeType(out, Types.OBJECT);
|
||||
out.writeUTF(value.getObject());
|
||||
return;
|
||||
}
|
||||
if (value.isArray()) {
|
||||
writeType(out, Types.ARRAY);
|
||||
out.writeByte(value.getArrayDimension());
|
||||
write(out, value.getArrayRootElement());
|
||||
return;
|
||||
}
|
||||
throw new JadxRuntimeException("Cannot save type: " + value + ", cls: " + value.getClass());
|
||||
}
|
||||
|
||||
private void writeType(DataOutput out, Types type) throws IOException {
|
||||
out.writeByte(type.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType read(DataInput in) throws IOException {
|
||||
switch (in.readByte()) {
|
||||
case 0:
|
||||
byte typeOrdinal = in.readByte();
|
||||
Types type = Types.values()[typeOrdinal];
|
||||
switch (type) {
|
||||
case NULL:
|
||||
return null;
|
||||
case 1:
|
||||
|
||||
case UNKNOWN:
|
||||
return ArgType.UNKNOWN;
|
||||
case 2:
|
||||
return ArgType.parse(in.readUTF());
|
||||
|
||||
case PRIMITIVE:
|
||||
char shortName = (char) in.readByte();
|
||||
return ArgType.parse(shortName);
|
||||
|
||||
case OUTER_GENERIC:
|
||||
ArgType outerType = read(in);
|
||||
ArgType innerType = read(in);
|
||||
return ArgType.outerGeneric(outerType, innerType);
|
||||
|
||||
case WILDCARD:
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||
return ArgType.WILDCARD;
|
||||
}
|
||||
ArgType objType = read(in);
|
||||
return ArgType.wildcard(objType, bound);
|
||||
|
||||
case GENERIC:
|
||||
String clsType = in.readUTF();
|
||||
return ArgType.generic(clsType, readTypesList(in));
|
||||
|
||||
case TYPE_VARIABLE:
|
||||
String typeVar = in.readUTF();
|
||||
List<ArgType> extendTypes = readTypesList(in);
|
||||
return ArgType.genericType(typeVar, extendTypes);
|
||||
|
||||
case OBJECT:
|
||||
return ArgType.object(in.readUTF());
|
||||
|
||||
case ARRAY:
|
||||
int dim = in.readByte();
|
||||
ArgType rootType = read(in);
|
||||
return ArgType.array(rootType, dim);
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Unexpected arg type tag");
|
||||
throw new RuntimeException("Unexpected arg type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTypesList(DataOutput out, List<ArgType> types) throws IOException {
|
||||
out.writeByte(types.size());
|
||||
for (ArgType type : types) {
|
||||
write(out, type);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ArgType> readTypesList(DataInput in) throws IOException {
|
||||
byte size = in.readByte();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(read(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class BaseDataAdapter<T> implements DataAdapter<T> {
|
||||
|
||||
public void writeNullableUTF(DataOutput out, @Nullable String str) throws IOException {
|
||||
if (str == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(1);
|
||||
out.writeUTF(str);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String readNullableUTF(DataInput in) throws IOException {
|
||||
if (in.readByte() == 0) {
|
||||
return null;
|
||||
}
|
||||
return in.readUTF();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DataAdapterHelper {
|
||||
|
||||
public static void writeNullableUTF(DataOutput out, @Nullable String str) throws IOException {
|
||||
if (str == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(1);
|
||||
out.writeUTF(str);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String readNullableUTF(DataInput in) throws IOException {
|
||||
if (in.readByte() == 0) {
|
||||
return null;
|
||||
}
|
||||
return in.readUTF();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write unsigned variable length integer (ULEB128 encoding)
|
||||
*/
|
||||
public static void writeUVInt(DataOutput out, int val) throws IOException {
|
||||
if (val < 0) {
|
||||
throw new IllegalArgumentException("Expect value >= 0, got: " + val);
|
||||
}
|
||||
int current = val;
|
||||
int next = val;
|
||||
while (true) {
|
||||
next >>>= 7;
|
||||
if (next == 0) {
|
||||
// last byte
|
||||
out.writeByte(current & 0x7f);
|
||||
return;
|
||||
}
|
||||
out.writeByte((current & 0x7f) | 0x80);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read unsigned variable length integer (ULEB128 encoding)
|
||||
*/
|
||||
public static int readUVInt(DataInput in) throws IOException {
|
||||
int result = 0;
|
||||
int shift = 0;
|
||||
while (true) {
|
||||
byte v = in.readByte();
|
||||
result |= (v & (byte) 0x7f) << shift;
|
||||
shift += 7;
|
||||
if ((v & 0x80) != 0x80) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,11 +12,11 @@ public class InsnCodeOffsetAdapter implements DataAdapter<InsnCodeOffset> {
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out, InsnCodeOffset value) throws IOException {
|
||||
out.writeShort(value.getOffset());
|
||||
DataAdapterHelper.writeUVInt(out, value.getOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnCodeOffset read(DataInput in) throws IOException {
|
||||
return new InsnCodeOffset(in.readShort());
|
||||
return new InsnCodeOffset(DataAdapterHelper.readUVInt(in));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,13 +21,13 @@ public class NodeDeclareRefAdapter implements DataAdapter<NodeDeclareRef> {
|
||||
throw new RuntimeException("Null node in NodeDeclareRef");
|
||||
}
|
||||
refAdapter.write(out, node);
|
||||
out.writeShort(value.getDefPos());
|
||||
DataAdapterHelper.writeUVInt(out, value.getDefPos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeDeclareRef read(DataInput in) throws IOException {
|
||||
ICodeNodeRef ref = (ICodeNodeRef) refAdapter.read(in);
|
||||
int defPos = in.readShort();
|
||||
int defPos = DataAdapterHelper.readUVInt(in);
|
||||
NodeDeclareRef nodeDeclareRef = new NodeDeclareRef(ref);
|
||||
nodeDeclareRef.setDefPos(defPos);
|
||||
// restore def position if loading metadata without actual decompilation
|
||||
|
||||
@@ -8,7 +8,12 @@ import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class VarNodeAdapter extends BaseDataAdapter<VarNode> {
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readNullableUTF;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeNullableUTF;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt;
|
||||
|
||||
public class VarNodeAdapter implements DataAdapter<VarNode> {
|
||||
private final MethodNodeAdapter mthAdapter;
|
||||
|
||||
public VarNodeAdapter(MethodNodeAdapter mthAdapter) {
|
||||
@@ -18,8 +23,8 @@ public class VarNodeAdapter extends BaseDataAdapter<VarNode> {
|
||||
@Override
|
||||
public void write(DataOutput out, VarNode value) throws IOException {
|
||||
mthAdapter.write(out, value.getMth());
|
||||
out.writeShort(value.getReg());
|
||||
out.writeShort(value.getSsa());
|
||||
writeUVInt(out, value.getReg());
|
||||
writeUVInt(out, value.getSsa());
|
||||
ArgTypeAdapter.INSTANCE.write(out, value.getType());
|
||||
writeNullableUTF(out, value.getName());
|
||||
}
|
||||
@@ -27,8 +32,8 @@ public class VarNodeAdapter extends BaseDataAdapter<VarNode> {
|
||||
@Override
|
||||
public VarNode read(DataInput in) throws IOException {
|
||||
MethodNode mth = mthAdapter.read(in);
|
||||
int reg = in.readShort();
|
||||
int ssa = in.readShort();
|
||||
int reg = readUVInt(in);
|
||||
int ssa = readUVInt(in);
|
||||
ArgType type = ArgTypeAdapter.INSTANCE.read(in);
|
||||
String name = readNullableUTF(in);
|
||||
return new VarNode(mth, reg, ssa, type, name);
|
||||
|
||||
@@ -6,21 +6,19 @@ import java.io.IOException;
|
||||
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
|
||||
public class VarRefAdapter extends BaseDataAdapter<VarRef> {
|
||||
public class VarRefAdapter implements DataAdapter<VarRef> {
|
||||
|
||||
public static final VarRefAdapter INSTANCE = new VarRefAdapter();
|
||||
|
||||
@Override
|
||||
public void write(DataOutput out, VarRef value) throws IOException {
|
||||
int refPos = value.getRefPos();
|
||||
if (refPos == 0) {
|
||||
throw new RuntimeException("Variable refPos is zero: " + value);
|
||||
}
|
||||
out.writeShort(refPos);
|
||||
DataAdapterHelper.writeUVInt(out, refPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VarRef read(DataInput in) throws IOException {
|
||||
return VarRef.fromPos(in.readShort());
|
||||
int refPos = DataAdapterHelper.readUVInt(in);
|
||||
return VarRef.fromPos(refPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ menu.flatten=展开显示代码包
|
||||
menu.heapUsageBar=显示内存使用栏
|
||||
menu.alwaysSelectOpened=始终选中打开的文件和类
|
||||
menu.navigation=导航
|
||||
menu.text_search=搜索文本
|
||||
menu.class_search=搜索类
|
||||
menu.comment_search=搜索注释
|
||||
menu.text_search=文本搜索
|
||||
menu.class_search=类名搜索
|
||||
menu.comment_search=注释搜索
|
||||
menu.tools=工具
|
||||
menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
@@ -37,7 +37,7 @@ tree.loading=加载中...
|
||||
|
||||
progress.load=正在加载
|
||||
progress.decompile=反编译中
|
||||
#progress.canceling=Canceling
|
||||
progress.canceling=正在取消
|
||||
|
||||
error_dialog.title=错误
|
||||
|
||||
@@ -51,10 +51,10 @@ search.find=查找
|
||||
|
||||
tabs.copy_class_name=复制类名
|
||||
tabs.close=关闭
|
||||
tabs.closeOthers=关闭其他文件
|
||||
tabs.closeOthers=关闭其他
|
||||
tabs.closeAll=全部关闭
|
||||
tabs.code=代码
|
||||
#tabs.smali=
|
||||
tabs.smali=Smali
|
||||
|
||||
nav.back=后退
|
||||
nav.forward=前进
|
||||
@@ -71,7 +71,7 @@ message.saveIncomplete=<html>保存未完成。<br> %s<br> %d 类或资源未被
|
||||
message.indexIncomplete=<html>已跳过某些类索引。<br> %s<br> %d 类未被索引,不会出现在搜索结果中!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx 的内存不足。因此,%d 个类没有编入索引。<br>如果要将所有类编入索引,请增加的最大堆空间后重新启动 Jadx。</html>
|
||||
|
||||
heapUsage.text=JADX 内存使用率:%.2f GB 共 %.2f GB
|
||||
heapUsage.text=JADX 内存使用率:%.2f GB / %.2f GB
|
||||
|
||||
common_dialog.ok=确定
|
||||
common_dialog.cancel=取消
|
||||
@@ -79,9 +79,9 @@ common_dialog.add=添加
|
||||
common_dialog.update=更新
|
||||
common_dialog.remove=移除
|
||||
|
||||
#file_dialog.supported_files=Supported files
|
||||
#file_dialog.load_dir_title=Load directory
|
||||
#file_dialog.load_dir_confirm=Load all files from directory?
|
||||
file_dialog.supported_files=支持的文件
|
||||
file_dialog.load_dir_title=加载目录
|
||||
file_dialog.load_dir_confirm=从目录中加载所有文件?
|
||||
|
||||
search_dialog.open=转到
|
||||
search_dialog.cancel=取消
|
||||
@@ -93,10 +93,10 @@ search_dialog.field=字段名
|
||||
search_dialog.code=代码
|
||||
search_dialog.options=搜索选项:
|
||||
search_dialog.ignorecase=忽略大小写
|
||||
#search_dialog.load_more=Load more
|
||||
#search_dialog.load_all=Load all
|
||||
#search_dialog.results_incomplete=Found %d+
|
||||
#search_dialog.results_complete=Found %d (complete)
|
||||
search_dialog.load_more=加载更多
|
||||
search_dialog.load_all=加载所有
|
||||
search_dialog.results_incomplete=已找到 %d+
|
||||
search_dialog.results_complete=全部找到 %d
|
||||
search_dialog.col_node=节点
|
||||
search_dialog.col_code=代码
|
||||
search_dialog.regex=正则表达式
|
||||
@@ -123,34 +123,34 @@ preferences.title=首选项
|
||||
preferences.deobfuscation=反混淆
|
||||
preferences.appearance=界面
|
||||
preferences.decompile=反编译
|
||||
#preferences.plugins=Plugins
|
||||
preferences.plugins=插件
|
||||
preferences.project=项目
|
||||
preferences.other=其他
|
||||
preferences.language=语言
|
||||
preferences.lineNumbersMode=编辑器行号模式
|
||||
preferences.check_for_updates=启动时检查更新
|
||||
#preferences.useDx=Use dx/d8 to convert java bytecode
|
||||
#preferences.decompilationMode=Decompilation mode
|
||||
#preferences.codeCacheMode=Code cache mode
|
||||
preferences.useDx=使用 dx/d8 来转换java字节码
|
||||
preferences.decompilationMode=反编译模式
|
||||
preferences.codeCacheMode=代码缓存模式
|
||||
preferences.showInconsistentCode=显示不一致的代码
|
||||
preferences.escapeUnicode=将 Unicode 字符转义
|
||||
preferences.escapeUnicode=Unicode 字符转义
|
||||
preferences.replaceConsts=替换常量
|
||||
preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符
|
||||
preferences.useImports=使用 import 语句
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
preferences.useDebugInfo=启用调试信息
|
||||
preferences.inlineAnonymous=内联匿名类
|
||||
preferences.inlineMethods=内联方法
|
||||
preferences.fsCaseSensitive=文件系统区分大小写
|
||||
preferences.skipResourcesDecode=不反编译资源文件
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.useKotlinMethodsForVarNames=使用Kotlin方法来重命名变量
|
||||
preferences.commentsLevel=代码注释等级
|
||||
preferences.autoSave=自动保存
|
||||
preferences.threads=并行线程数
|
||||
preferences.excludedPackages=排除的包
|
||||
preferences.excludedPackages.tooltip=将不被解压缩或索引的以空格分隔的包名称列表(节省 RAM)
|
||||
preferences.excludedPackages.tooltip=排除于反编译或索引的以空格分隔的包名列表(节省 RAM)
|
||||
preferences.excludedPackages.button=编辑
|
||||
preferences.excludedPackages.editDialog=<html>将不被解压或索引的以空格分隔的包名称列表(节省 RAM)<br>例如<code>android.support</code></html>
|
||||
preferences.cfg=生成方法的 CFG 图(以 .dot 格式保存)
|
||||
preferences.excludedPackages.editDialog=<html>排除于反编译或索引的以空格分隔的包名列表(节省 RAM)<br>例如<code>android.support</code></html>
|
||||
preferences.cfg=生成方法的 CFG 图('.dot' 格式)
|
||||
preferences.raw_cfg=生成原始的 CFG 图
|
||||
preferences.font=编辑器字体
|
||||
preferences.smali_font=Smali编辑器字体
|
||||
@@ -160,22 +160,22 @@ preferences.start_jobs=自动进行后台反编译
|
||||
preferences.select_font=修改
|
||||
preferences.select_smali_font=修改
|
||||
preferences.deobfuscation_on=启用反混淆
|
||||
#preferences.deobfuscation_map_file_mode=Map file handle mode
|
||||
preferences.deobfuscation_map_file_mode=映射文件句柄模式
|
||||
preferences.deobfuscation_min_len=最小命名长度
|
||||
preferences.deobfuscation_max_len=最大命名长度
|
||||
preferences.deobfuscation_source_alias=使用资源名作为类的别名
|
||||
preferences.deobfuscation_kotlin_metadata=解析Kotlin元数据以获得类和包名
|
||||
preferences.deobfuscation_kotlin_metadata=解析Kotlin元数据以获得类名和包名
|
||||
preferences.save=保存
|
||||
preferences.cancel=取消
|
||||
preferences.reset=重置
|
||||
preferences.reset_message=要恢复默认设置吗?
|
||||
preferences.reset_message=要恢复为默认设置吗?
|
||||
preferences.reset_title=重置设置
|
||||
preferences.copy=复制到剪切板
|
||||
preferences.copy_message=所有设置都已复制
|
||||
preferences.rename=重命名
|
||||
preferences.rename_case=系统区分大小写
|
||||
preferences.rename_valid=是有效的标识符
|
||||
preferences.rename_printable=是可打印
|
||||
preferences.rename=重命名标识符
|
||||
preferences.rename_case=需要标识符能区分大小写
|
||||
preferences.rename_valid=需要标识符能符合规范
|
||||
preferences.rename_printable=需要标识符可正常显示
|
||||
preferences.search_res_title=搜索资源
|
||||
preferences.res_file_ext=文件扩展名 (e.g. .xml|.html),* 表示所有
|
||||
preferences.res_skip_file=跳过文件大小(MB)
|
||||
@@ -183,7 +183,7 @@ preferences.res_skip_file=跳过文件大小(MB)
|
||||
msg.open_file=请打开文件
|
||||
msg.saving_sources=正在导出源代码
|
||||
msg.language_changed_title=语言已更改
|
||||
msg.language_changed=在下次启动时将会显示新的语言。
|
||||
msg.language_changed=新的语言将在下次应用程序启动时显示。
|
||||
msg.project_error_title=错误
|
||||
msg.project_error=项目无法加载
|
||||
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
|
||||
@@ -258,21 +258,21 @@ debugger.step_into=步入 (F7)
|
||||
debugger.step_over=步过 (F8)
|
||||
debugger.step_out=步出 (Shift + F8)
|
||||
debugger.run=运行 (F9)
|
||||
debugger.stop=停止调试并终止APP
|
||||
debugger.stop=停止调试并终止应用
|
||||
debugger.pause=暂停
|
||||
debugger.rerun=重新运行
|
||||
debugger.cfm_dialog_title=在调试时退出
|
||||
debugger.cfm_dialog_msg=您确定要终止调试器吗?
|
||||
|
||||
debugger.popup_set_value=修改值
|
||||
debugger.popup_change_to_zero=改变为0
|
||||
debugger.popup_change_to_one=改变为1
|
||||
debugger.popup_change_to_zero=修改为0
|
||||
debugger.popup_change_to_one=修改为1
|
||||
debugger.popup_copy_value=复制值
|
||||
|
||||
set_value_dialog.label_value=值
|
||||
set_value_dialog.btn_set=修改
|
||||
set_value_dialog.title=修改值
|
||||
set_value_dialog.neg_msg=修改值失败。
|
||||
set_value_dialog.title=设置新值
|
||||
set_value_dialog.neg_msg=修改数值失败。
|
||||
set_value_dialog.sel_type=选择要修改值的类型。
|
||||
|
||||
adb_dialog.addr=ADB IP
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DataAdapterHelperTest {
|
||||
|
||||
@Test
|
||||
void uvInt() throws IOException {
|
||||
checkUVIntFor(0);
|
||||
checkUVIntFor(7);
|
||||
checkUVIntFor(0x7f);
|
||||
checkUVIntFor(0x80);
|
||||
checkUVIntFor(0x256);
|
||||
checkUVIntFor(Byte.MAX_VALUE);
|
||||
checkUVIntFor(Short.MAX_VALUE);
|
||||
checkUVIntFor(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void checkUVIntFor(int val) throws IOException {
|
||||
assertThat(writeReadUVInt(val)).isEqualTo(val);
|
||||
}
|
||||
|
||||
private int writeReadUVInt(int val) throws IOException {
|
||||
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(byteOut);
|
||||
DataAdapterHelper.writeUVInt(out, val);
|
||||
|
||||
DataInput in = new DataInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
|
||||
return DataAdapterHelper.readUVInt(in);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user