Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63a571306c | |||
| bc4db61e25 | |||
| c7e6e28830 | |||
| 1d7b6fdb2c | |||
| ce5d8eeff8 | |||
| 894e0e6132 | |||
| 127f0ecf3f | |||
| cf7767e702 | |||
| e0aedc7949 | |||
| bad78de74c | |||
| 12df8a169f | |||
| 15c9d33339 | |||
| 7e0fafbaf1 | |||
| 57b9c1dd7a | |||
| 8ba0c17259 | |||
| cd32151083 | |||
| 75b52d672e | |||
| 11d04508f7 | |||
| e641b773b5 | |||
| 6e5899c654 | |||
| c66ffaa7f9 | |||
| 5193c6a5d8 | |||
| e7212af547 | |||
| 3ca1357af4 | |||
| 90e95213e4 | |||
| ae2d4da585 | |||
| 691d5cd1e6 | |||
| 58a46c6417 | |||
| d3f6160e62 | |||
| 03e4afb12f | |||
| 6802f6028e | |||
| 5ca61cfe18 | |||
| 32d55b48f2 | |||
| ab4b6f9e54 | |||
| 9100ad1220 | |||
| 8b4f8fb572 |
@@ -102,6 +102,7 @@ options:
|
||||
--add-debug-lines - add comments with debug line numbers if available
|
||||
--no-inline-anonymous - disable anonymous classes inline
|
||||
--no-inline-methods - disable methods inline
|
||||
--no-finally - don't extract finally block
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||
@@ -116,6 +117,10 @@ options:
|
||||
'ignore' - don't read and don't save
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
|
||||
--deobf-res-name-source - better name source for resources:
|
||||
'auto' - automatically select best name (default)
|
||||
'resources' - use resources names
|
||||
'code' - use R class fields names
|
||||
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
|
||||
--rename-flags - fix options (comma-separated list of):
|
||||
'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.8.0'
|
||||
id 'com.diffplug.spotless' version '6.9.1'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -31,11 +31,11 @@ allprojects {
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.6.1'
|
||||
testImplementation 'org.mockito:mockito-core:4.7.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||
|
||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -205,6 +205,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
echo "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+8
-6
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -21,6 +21,7 @@ import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@@ -88,6 +89,9 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
|
||||
protected boolean inlineMethods = true;
|
||||
|
||||
@Parameter(names = "--no-finally", description = "don't extract finally block")
|
||||
protected boolean extractFinally = true;
|
||||
|
||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@@ -129,6 +133,16 @@ public class JadxCLIArgs {
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--deobf-res-name-source" },
|
||||
description = "better name source for resources:"
|
||||
+ "\n 'auto' - automatically select best name (default)"
|
||||
+ "\n 'resources' - use resources names"
|
||||
+ "\n 'code' - use R class fields names",
|
||||
converter = ResourceNameSourceConverter.class
|
||||
)
|
||||
protected ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||
|
||||
@Parameter(
|
||||
names = { "--use-kotlin-methods-for-var-names" },
|
||||
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
|
||||
@@ -262,6 +276,7 @@ public class JadxCLIArgs {
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
|
||||
args.setResourceNameSource(resourceNameSource);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
@@ -270,6 +285,7 @@ public class JadxCLIArgs {
|
||||
args.setInsertDebugLines(addDebugLines);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setInlineMethods(inlineMethods);
|
||||
args.setExtractFinally(extractFinally);
|
||||
args.setRenameFlags(renameFlags);
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
@@ -350,6 +366,10 @@ public class JadxCLIArgs {
|
||||
return inlineMethods;
|
||||
}
|
||||
|
||||
public boolean isExtractFinally() {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
@@ -378,6 +398,10 @@ public class JadxCLIArgs {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
||||
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
|
||||
return useKotlinMethodsForVarNames;
|
||||
}
|
||||
@@ -502,6 +526,19 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourceNameSourceConverter implements IStringConverter<ResourceNameSource> {
|
||||
@Override
|
||||
public ResourceNameSource convert(String value) {
|
||||
try {
|
||||
return ResourceNameSource.valueOf(value.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + value + "' is unknown, possible values are: "
|
||||
+ JadxCLIArgs.enumValuesString(ResourceNameSource.values()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DecompilationModeConverter implements IStringConverter<DecompilationMode> {
|
||||
@Override
|
||||
public DecompilationMode convert(String value) {
|
||||
|
||||
@@ -5,11 +5,11 @@ plugins {
|
||||
dependencies {
|
||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'com.google.code.gson:gson:2.9.1'
|
||||
|
||||
// TODO: move resources decoding to separate plugin module
|
||||
implementation 'com.android.tools.build:aapt2-proto:7.2.1-7984345'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.2' // forcing latest version
|
||||
implementation 'com.android.tools.build:aapt2-proto:7.2.2-7984345'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.5' // forcing latest version
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
@@ -72,6 +73,7 @@ public class JadxArgs {
|
||||
private File deobfuscationMapFile = null;
|
||||
|
||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
@@ -369,6 +371,14 @@ public class JadxArgs {
|
||||
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
return resourceNameSource;
|
||||
}
|
||||
|
||||
public void setResourceNameSource(ResourceNameSource resourceNameSource) {
|
||||
this.resourceNameSource = resourceNameSource;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
@@ -540,6 +550,7 @@ public class JadxArgs {
|
||||
String argStr = "args:" + decompilationMode + useImports + showInconsistentCode
|
||||
+ inlineAnonymousClasses + inlineMethods
|
||||
+ deobfuscationOn + deobfuscationMinLength + deobfuscationMaxLength
|
||||
+ resourceNameSource
|
||||
+ parseKotlinMetadata + useKotlinMethodsForVarNames
|
||||
+ insertDebugLines + extractFinally
|
||||
+ debugInfo + useSourceNameAsClassAlias + escapeUnicode + replaceConsts
|
||||
@@ -564,6 +575,7 @@ public class JadxArgs {
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.api.args;
|
||||
|
||||
/**
|
||||
* Resources original name source (for deobfuscation)
|
||||
*/
|
||||
public enum ResourceNameSource {
|
||||
|
||||
/**
|
||||
* Automatically select best name (default)
|
||||
*/
|
||||
AUTO,
|
||||
|
||||
/**
|
||||
* Force use resources provided names
|
||||
*/
|
||||
RESOURCES,
|
||||
|
||||
/**
|
||||
* Force use resources names from R class
|
||||
*/
|
||||
CODE,
|
||||
}
|
||||
@@ -191,7 +191,6 @@ public class Jadx {
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new MethodVisitor(mth -> mth.add(AFlag.DISABLE_BLOCKS_LOCK)));
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
@@ -215,9 +214,6 @@ public class Jadx {
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new MethodVisitor(mth -> mth.remove(AFlag.DONT_GENERATE)));
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
@@ -828,7 +828,11 @@ public class InsnGen {
|
||||
if (insn.contains(AFlag.FORCE_RAW_NAME)) {
|
||||
code.add(callMth.getName());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
if (callMthNode != null) {
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
}
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,9 @@ public class SimpleModeHelper {
|
||||
startLabel.set(block.getId());
|
||||
} else if (predsCount == 1 && prev != null) {
|
||||
if (!prev.equals(preds.get(0))) {
|
||||
startLabel.set(block.getId());
|
||||
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
|
||||
startLabel.set(block.getId());
|
||||
}
|
||||
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
|
||||
endGoto.set(prev.getId());
|
||||
}
|
||||
|
||||
@@ -428,7 +428,9 @@ public class Deobfuscator {
|
||||
return "Enum";
|
||||
}
|
||||
String result = "";
|
||||
if (cls.getAccessFlags().isAbstract()) {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
result += "Interface";
|
||||
} else if (cls.getAccessFlags().isAbstract()) {
|
||||
result += "Abstract";
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,19 @@ public class LoopInfo {
|
||||
this.parentLoop = parentLoop;
|
||||
}
|
||||
|
||||
public boolean hasParent(LoopInfo searchLoop) {
|
||||
LoopInfo parent = parentLoop;
|
||||
while (true) {
|
||||
if (parent == null) {
|
||||
return false;
|
||||
}
|
||||
if (parent == searchLoop) {
|
||||
return true;
|
||||
}
|
||||
parent = parent.getParentLoop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LOOP:" + id + ": " + start + "->" + end;
|
||||
|
||||
@@ -19,29 +19,59 @@ import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public final class BlockNode extends AttrNode implements IBlock, Comparable<BlockNode> {
|
||||
|
||||
/**
|
||||
* Const ID
|
||||
*/
|
||||
private final int cid;
|
||||
|
||||
/**
|
||||
* ID linked to position in blocks list (easier to use BitSet)
|
||||
* TODO: rename to avoid confusion
|
||||
*/
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* Offset in methods bytecode
|
||||
*/
|
||||
private final int startOffset;
|
||||
|
||||
private final List<InsnNode> instructions = new ArrayList<>(2);
|
||||
|
||||
private List<BlockNode> predecessors = new ArrayList<>(1);
|
||||
private List<BlockNode> successors = new ArrayList<>(1);
|
||||
private List<BlockNode> cleanSuccessors;
|
||||
|
||||
// all dominators
|
||||
/**
|
||||
* All dominators, excluding self
|
||||
*/
|
||||
private BitSet doms = EmptyBitSet.EMPTY;
|
||||
// dominance frontier
|
||||
|
||||
/**
|
||||
* Dominance frontier
|
||||
*/
|
||||
private BitSet domFrontier;
|
||||
// immediate dominator
|
||||
|
||||
/**
|
||||
* Immediate dominator
|
||||
*/
|
||||
private BlockNode idom;
|
||||
// blocks on which dominates this block
|
||||
|
||||
/**
|
||||
* Blocks on which dominates this block
|
||||
*/
|
||||
private List<BlockNode> dominatesOn = new ArrayList<>(3);
|
||||
|
||||
public BlockNode(int id, int offset) {
|
||||
public BlockNode(int cid, int id, int offset) {
|
||||
this.cid = cid;
|
||||
this.id = id;
|
||||
this.startOffset = offset;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
public int getCId() {
|
||||
return cid;
|
||||
}
|
||||
|
||||
void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -188,12 +218,12 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
return false;
|
||||
}
|
||||
BlockNode other = (BlockNode) obj;
|
||||
return id == other.id && startOffset == other.startOffset;
|
||||
return cid == other.cid && startOffset == other.startOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull BlockNode o) {
|
||||
return Integer.compare(id, o.id);
|
||||
return Integer.compare(cid, o.cid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -203,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
|
||||
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +469,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
|
||||
public void addField(FieldNode fld) {
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
fields = new ArrayList<>(1);
|
||||
}
|
||||
fields.add(fld);
|
||||
}
|
||||
|
||||
@@ -657,6 +660,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return contains(AType.ANONYMOUS_CLASS);
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return contains(AFlag.SYNTHETIC);
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return parentClass != this;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return accFlags.isStatic();
|
||||
}
|
||||
|
||||
public boolean isInstance() {
|
||||
return !accFlags.isStatic();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return fieldInfo.getName();
|
||||
}
|
||||
@@ -65,6 +69,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return fieldInfo.getAlias();
|
||||
}
|
||||
|
||||
public void rename(String alias) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -73,6 +81,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getTopParentClass() {
|
||||
return parentClass.getTopParentClass();
|
||||
}
|
||||
|
||||
public List<MethodNode> getUseIn() {
|
||||
return useIn;
|
||||
}
|
||||
|
||||
@@ -264,21 +264,6 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canReorderRecursive() {
|
||||
if (!canReorder()) {
|
||||
return false;
|
||||
}
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (!wrapInsn.canReorderRecursive()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean containsWrappedInsn() {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
|
||||
@@ -61,6 +61,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
private List<RegisterArg> argsList;
|
||||
private InsnNode[] instructions;
|
||||
private List<BlockNode> blocks;
|
||||
private int blocksMaxCId;
|
||||
private BlockNode enterBlock;
|
||||
private BlockNode exitBlock;
|
||||
private List<SSAVar> sVars;
|
||||
@@ -318,6 +319,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
|
||||
public void setBasicBlocks(List<BlockNode> blocks) {
|
||||
this.blocks = blocks;
|
||||
int i = 0;
|
||||
for (BlockNode block : blocks) {
|
||||
block.setId(i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public int getNextBlockCId() {
|
||||
return blocksMaxCId++;
|
||||
}
|
||||
|
||||
public BlockNode getEnterBlock() {
|
||||
|
||||
@@ -49,6 +49,10 @@ public final class LoopRegion extends ConditionRegion {
|
||||
return header;
|
||||
}
|
||||
|
||||
public boolean isEndless() {
|
||||
return header == null;
|
||||
}
|
||||
|
||||
public IRegion getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ public class TryCatchBlockAttr implements IJadxAttribute {
|
||||
return throwFound;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<ExceptionHandler> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
if (insnArr == null) {
|
||||
return;
|
||||
}
|
||||
BlockNode block = new BlockNode(0, 0);
|
||||
BlockNode block = new BlockNode(0, 0, 0);
|
||||
List<InsnNode> insnList = block.getInstructions();
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null) {
|
||||
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.add("color=red,");
|
||||
}
|
||||
dot.add("label=\"{");
|
||||
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
|
||||
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
|
||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||
if (!attrs.isEmpty()) {
|
||||
dot.add('|').add(attrs);
|
||||
@@ -230,10 +230,10 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
if (PRINT_DOMINATORS) {
|
||||
for (BlockNode c : block.getDominatesOn()) {
|
||||
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
|
||||
conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
|
||||
}
|
||||
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
||||
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
|
||||
conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
private String makeName(IContainer c) {
|
||||
String name;
|
||||
if (c instanceof BlockNode) {
|
||||
name = "Node_" + ((BlockNode) c).getId();
|
||||
name = "Node_" + ((BlockNode) c).getCId();
|
||||
} else if (c instanceof IBlock) {
|
||||
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,7 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@@ -45,20 +46,22 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
moveStaticFieldsInit(cls);
|
||||
moveCommonFieldsInit(cls);
|
||||
if (!cls.getFields().isEmpty()) {
|
||||
moveStaticFieldsInit(cls);
|
||||
moveCommonFieldsInit(cls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class FieldInitInfo {
|
||||
final FieldNode fieldNode;
|
||||
final IndexInsnNode putInsn;
|
||||
final boolean singlePath;
|
||||
final boolean canMove;
|
||||
|
||||
public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean singlePath) {
|
||||
public FieldInitInfo(FieldNode fieldNode, IndexInsnNode putInsn, boolean canMove) {
|
||||
this.fieldNode = fieldNode;
|
||||
this.putInsn = putInsn;
|
||||
this.singlePath = singlePath;
|
||||
this.canMove = canMove;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,9 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
|| classInitMth.getBasicBlocks() == null) {
|
||||
return;
|
||||
}
|
||||
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isStatic)) {
|
||||
return;
|
||||
}
|
||||
while (processStaticFields(cls, classInitMth)) {
|
||||
// sometimes instructions moved to field init prevent from vars inline -> inline and try again
|
||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||
@@ -116,15 +122,15 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void moveCommonFieldsInit(ClassNode cls) {
|
||||
if (ListUtils.noneMatch(cls.getFields(), FieldNode::isInstance)) {
|
||||
return;
|
||||
}
|
||||
List<MethodNode> constructors = getConstructorsList(cls);
|
||||
if (constructors.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<ConstructorInitInfo> infoList = new ArrayList<>(constructors.size());
|
||||
for (MethodNode constructorMth : constructors) {
|
||||
if (constructorMth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<FieldInitInfo> inits = collectFieldsInit(cls, constructorMth, InsnType.IPUT);
|
||||
filterFieldsInit(inits);
|
||||
if (inits.isEmpty()) {
|
||||
@@ -168,19 +174,25 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
Set<BlockNode> singlePathBlocks = new HashSet<>();
|
||||
BlockUtils.visitSinglePath(mth.getEnterBlock(), singlePathBlocks::add);
|
||||
|
||||
boolean canReorder = true;
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
boolean fieldInsn = false;
|
||||
if (insn.getType() == putType) {
|
||||
IndexInsnNode putInsn = (IndexInsnNode) insn;
|
||||
FieldInfo field = (FieldInfo) putInsn.getIndex();
|
||||
if (field.getDeclClass().equals(cls.getClassInfo())) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null) {
|
||||
boolean singlePath = singlePathBlocks.contains(block);
|
||||
fieldsInit.add(new FieldInitInfo(fn, putInsn, singlePath));
|
||||
boolean canMove = canReorder && singlePathBlocks.contains(block);
|
||||
fieldsInit.add(new FieldInitInfo(fn, putInsn, canMove));
|
||||
fieldInsn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fieldInsn && canReorder && !insn.canReorder()) {
|
||||
canReorder = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldsInit;
|
||||
@@ -226,14 +238,14 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean checkInsn(FieldInitInfo initInfo) {
|
||||
if (!initInfo.singlePath) {
|
||||
if (!initInfo.canMove) {
|
||||
return false;
|
||||
}
|
||||
IndexInsnNode insn = initInfo.putInsn;
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (!wrapInsn.canReorderRecursive() && insn.contains(AType.EXC_CATCH)) {
|
||||
if (!wrapInsn.canReorder() && insn.contains(AType.EXC_CATCH)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -364,7 +376,7 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
AccessInfo accFlags = mth.getAccessFlags();
|
||||
if (!accFlags.isStatic() && accFlags.isConstructor()) {
|
||||
list.add(mth);
|
||||
if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
if (mth.isNoCode() || BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,10 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
// exclude self usage
|
||||
return null;
|
||||
}
|
||||
if (ctrUseCls.getTopParentClass().equals(cls)) {
|
||||
// exclude usage inside inner classes
|
||||
return null;
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth == ctr) {
|
||||
continue;
|
||||
|
||||
@@ -171,10 +171,6 @@ public class BlockExceptionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected static void removeTmpConnections(MethodNode mth) {
|
||||
mth.getBasicBlocks().forEach(BlockExceptionHandler::removeTmpConnection);
|
||||
}
|
||||
|
||||
private static void removeTmpConnection(BlockNode block) {
|
||||
TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE);
|
||||
if (tmpEdgeAttr != null) {
|
||||
@@ -402,6 +398,13 @@ public class BlockExceptionHandler {
|
||||
}
|
||||
BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks);
|
||||
if (topDom != null) {
|
||||
// dominator always return one up block if blocks already contains dominator, use successor instead
|
||||
if (topDom.getSuccessors().size() == 1) {
|
||||
BlockNode upBlock = topDom.getSuccessors().get(0);
|
||||
if (blocks.contains(upBlock)) {
|
||||
return upBlock;
|
||||
}
|
||||
}
|
||||
return adjustTopBlock(topDom);
|
||||
}
|
||||
throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks);
|
||||
|
||||
@@ -137,8 +137,9 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
}
|
||||
|
||||
static BlockNode startNewBlock(MethodNode mth, int offset) {
|
||||
BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset);
|
||||
mth.getBasicBlocks().add(block);
|
||||
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||
BlockNode block = new BlockNode(mth.getNextBlockCId(), blocks.size(), offset);
|
||||
blocks.add(block);
|
||||
return block;
|
||||
}
|
||||
|
||||
@@ -391,7 +392,8 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
&& block.getSuccessors().size() <= 1
|
||||
&& !block.getPredecessors().isEmpty()
|
||||
&& !block.contains(AFlag.MTH_ENTER_BLOCK)
|
||||
&& !block.contains(AFlag.MTH_EXIT_BLOCK);
|
||||
&& !block.contains(AFlag.MTH_EXIT_BLOCK)
|
||||
&& !block.getSuccessors().contains(block); // no self loop
|
||||
}
|
||||
|
||||
static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) {
|
||||
|
||||
@@ -25,23 +25,16 @@ public class DominatorTree {
|
||||
List<BlockNode> sorted = sortBlocks(mth);
|
||||
BlockNode[] doms = build(sorted);
|
||||
apply(sorted, doms);
|
||||
mth.setBasicBlocks(sorted);
|
||||
}
|
||||
|
||||
private static List<BlockNode> sortBlocks(MethodNode mth) {
|
||||
int blocksCount = mth.getBasicBlocks().size();
|
||||
BitSet reachSet = new BitSet(blocksCount);
|
||||
List<BlockNode> sorted = new ArrayList<>(blocksCount);
|
||||
BlockUtils.dfsVisit(mth, b -> {
|
||||
sorted.add(b);
|
||||
reachSet.set(b.getId());
|
||||
});
|
||||
if (reachSet.cardinality() != blocksCount) {
|
||||
BlockUtils.dfsVisit(mth, sorted::add);
|
||||
if (sorted.size() != blocksCount) {
|
||||
throw new JadxRuntimeException("Found unreachable blocks");
|
||||
}
|
||||
for (int i = 0; i < blocksCount; i++) {
|
||||
sorted.get(i).setId(i);
|
||||
}
|
||||
mth.setBasicBlocks(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -19,6 +20,10 @@ public class FinallyExtractInfo {
|
||||
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
|
||||
private final BlockNode startBlock;
|
||||
|
||||
private InsnsSlice curDupSlice;
|
||||
private List<InsnNode> curDupInsns;
|
||||
private int curDupInsnsOffset;
|
||||
|
||||
public FinallyExtractInfo(MethodNode mth, ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
|
||||
this.mth = mth;
|
||||
this.finallyHandler = finallyHandler;
|
||||
@@ -54,6 +59,27 @@ public class FinallyExtractInfo {
|
||||
return startBlock;
|
||||
}
|
||||
|
||||
public InsnsSlice getCurDupSlice() {
|
||||
return curDupSlice;
|
||||
}
|
||||
|
||||
public void setCurDupSlice(InsnsSlice curDupSlice) {
|
||||
this.curDupSlice = curDupSlice;
|
||||
}
|
||||
|
||||
public List<InsnNode> getCurDupInsns() {
|
||||
return curDupInsns;
|
||||
}
|
||||
|
||||
public int getCurDupInsnsOffset() {
|
||||
return curDupInsnsOffset;
|
||||
}
|
||||
|
||||
public void setCurDupInsns(List<InsnNode> insns, int offset) {
|
||||
this.curDupInsns = insns;
|
||||
this.curDupInsnsOffset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FinallyExtractInfo{"
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -28,6 +29,7 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
@@ -376,6 +378,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
* 'Finally' instructions can start in the middle of the first block.
|
||||
*/
|
||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||
extractInfo.setCurDupSlice(null);
|
||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||
int dupSize = dupInsns.size();
|
||||
@@ -386,7 +389,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
int startPos;
|
||||
int endPos = 0;
|
||||
if (dupSize == finSize) {
|
||||
if (!checkInsns(dupInsns, finallyInsns, 0)) {
|
||||
if (!checkInsns(extractInfo, dupInsns, finallyInsns, 0)) {
|
||||
return null;
|
||||
}
|
||||
startPos = 0;
|
||||
@@ -394,11 +397,11 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
// dupSize > finSize
|
||||
startPos = dupSize - finSize;
|
||||
// fast check from end of block
|
||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||
if (!checkInsns(extractInfo, dupInsns, finallyInsns, startPos)) {
|
||||
// search start insn
|
||||
boolean found = false;
|
||||
for (int i = 1; i < startPos; i++) {
|
||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
||||
if (checkInsns(extractInfo, dupInsns, finallyInsns, i)) {
|
||||
startPos = i;
|
||||
endPos = finSize + i;
|
||||
found = true;
|
||||
@@ -414,6 +417,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
// put instructions into slices
|
||||
boolean complete;
|
||||
InsnsSlice slice = new InsnsSlice();
|
||||
extractInfo.setCurDupSlice(slice);
|
||||
int endIndex;
|
||||
if (endPos != 0) {
|
||||
endIndex = endPos + 1;
|
||||
@@ -453,11 +457,12 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
return slice;
|
||||
}
|
||||
|
||||
private static boolean checkInsns(List<InsnNode> remInsns, List<InsnNode> finallyInsns, int delta) {
|
||||
private static boolean checkInsns(FinallyExtractInfo extractInfo, List<InsnNode> dupInsns, List<InsnNode> finallyInsns, int delta) {
|
||||
extractInfo.setCurDupInsns(dupInsns, delta);
|
||||
for (int i = finallyInsns.size() - 1; i >= 0; i--) {
|
||||
InsnNode startInsn = finallyInsns.get(i);
|
||||
InsnNode remInsn = remInsns.get(delta + i);
|
||||
if (!sameInsns(remInsn, startInsn)) {
|
||||
InsnNode dupInsn = dupInsns.get(delta + i);
|
||||
if (!sameInsns(extractInfo, dupInsn, startInsn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -509,8 +514,9 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
if (dupInsnCount < finallyInsnCount) {
|
||||
return false;
|
||||
}
|
||||
extractInfo.setCurDupInsns(dupInsns, 0);
|
||||
for (int i = 0; i < finallyInsnCount; i++) {
|
||||
if (!sameInsns(dupInsns.get(i), finallyInsns.get(i))) {
|
||||
if (!sameInsns(extractInfo, dupInsns.get(i), finallyInsns.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -524,26 +530,85 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn) {
|
||||
if (!remInsn.isSame(fInsn)) {
|
||||
private static boolean sameInsns(FinallyExtractInfo extractInfo, InsnNode dupInsn, InsnNode fInsn) {
|
||||
if (!dupInsn.isSame(fInsn)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: check instance arg in ConstructorInsn
|
||||
// TODO: compare literals
|
||||
for (int i = 0; i < remInsn.getArgsCount(); i++) {
|
||||
InsnArg remArg = remInsn.getArg(i);
|
||||
for (int i = 0; i < dupInsn.getArgsCount(); i++) {
|
||||
InsnArg dupArg = dupInsn.getArg(i);
|
||||
InsnArg fArg = fInsn.getArg(i);
|
||||
if (remArg.isRegister() != fArg.isRegister()) {
|
||||
if (!isSameArgs(extractInfo, dupArg, fArg)) {
|
||||
return false;
|
||||
}
|
||||
boolean remConst = remArg.isConst();
|
||||
if (remConst != fArg.isConst()) {
|
||||
return false;
|
||||
}
|
||||
if (remConst && !remArg.isSameConst(fArg)) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
private static boolean isSameArgs(FinallyExtractInfo extractInfo, InsnArg dupArg, InsnArg fArg) {
|
||||
boolean isReg = dupArg.isRegister();
|
||||
if (isReg != fArg.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
if (isReg) {
|
||||
RegisterArg dupReg = (RegisterArg) dupArg;
|
||||
RegisterArg fReg = (RegisterArg) fArg;
|
||||
if (!dupReg.sameCodeVar(fReg)
|
||||
&& !sameDebugInfo(dupReg, fReg)
|
||||
&& assignedOutsideHandler(extractInfo, dupReg, fReg)
|
||||
&& assignInsnDifferent(dupReg, fReg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boolean remConst = dupArg.isConst();
|
||||
if (remConst != fArg.isConst()) {
|
||||
return false;
|
||||
}
|
||||
if (remConst && !dupArg.isSameConst(fArg)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameDebugInfo(RegisterArg dupReg, RegisterArg fReg) {
|
||||
RegDebugInfoAttr fDbgInfo = fReg.get(AType.REG_DEBUG_INFO);
|
||||
RegDebugInfoAttr dupDbgInfo = dupReg.get(AType.REG_DEBUG_INFO);
|
||||
if (fDbgInfo == null || dupDbgInfo == null) {
|
||||
return false;
|
||||
}
|
||||
return dupDbgInfo.equals(fDbgInfo);
|
||||
}
|
||||
|
||||
private static boolean assignInsnDifferent(RegisterArg dupReg, RegisterArg fReg) {
|
||||
InsnNode assignInsn = fReg.getAssignInsn();
|
||||
InsnNode dupAssign = dupReg.getAssignInsn();
|
||||
if (assignInsn == null || dupAssign == null) {
|
||||
return true;
|
||||
}
|
||||
if (!assignInsn.isSame(dupAssign)) {
|
||||
return true;
|
||||
}
|
||||
if (assignInsn.isConstInsn() && dupAssign.isConstInsn()) {
|
||||
return !assignInsn.isDeepEquals(dupAssign);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
private static boolean assignedOutsideHandler(FinallyExtractInfo extractInfo, RegisterArg dupReg, RegisterArg fReg) {
|
||||
if (InsnList.contains(extractInfo.getFinallyInsnsSlice().getInsnsList(), fReg.getAssignInsn())) {
|
||||
return false;
|
||||
}
|
||||
InsnNode dupAssign = dupReg.getAssignInsn();
|
||||
InsnsSlice curDupSlice = extractInfo.getCurDupSlice();
|
||||
if (curDupSlice != null && InsnList.contains(curDupSlice.getInsnsList(), dupAssign)) {
|
||||
return false;
|
||||
}
|
||||
List<InsnNode> curDupInsns = extractInfo.getCurDupInsns();
|
||||
if (Utils.notEmpty(curDupInsns) && InsnList.contains(curDupInsns, dupAssign, extractInfo.getCurDupInsnsOffset())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
|
||||
public class CleanRegions extends AbstractVisitor {
|
||||
@@ -42,6 +43,13 @@ public class CleanRegions extends AbstractVisitor {
|
||||
BlockNode block = (BlockNode) container;
|
||||
return block.getInstructions().isEmpty();
|
||||
}
|
||||
if (container instanceof LoopRegion) {
|
||||
LoopRegion loopRegion = (LoopRegion) container;
|
||||
if (loopRegion.isEndless()) {
|
||||
// keep empty endless loops
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
|
||||
for (IContainer subBlock : subBlocks) {
|
||||
|
||||
@@ -464,7 +464,7 @@ public class RegionMaker {
|
||||
BlockNode exitEnd = BlockUtils.followEmptyPath(exit);
|
||||
List<LoopInfo> loops = exitEnd.getAll(AType.LOOP);
|
||||
for (LoopInfo loopAtEnd : loops) {
|
||||
if (loopAtEnd != loop) {
|
||||
if (loopAtEnd != loop && loop.hasParent(loopAtEnd)) {
|
||||
insertEdge = exitEdge;
|
||||
confirm = true;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.deobf.TldHelper;
|
||||
|
||||
public class BetterName {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BetterName.class);
|
||||
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
public static String compareAndGet(String first, String second) {
|
||||
if (Objects.equals(first, second)) {
|
||||
return first;
|
||||
}
|
||||
int firstRating = calcRating(first);
|
||||
int secondRating = calcRating(second);
|
||||
boolean firstBetter = firstRating >= secondRating;
|
||||
if (DEBUG) {
|
||||
if (firstBetter) {
|
||||
LOG.info("Better name: '{}' > '{}' ({} > {})", first, second, firstRating, secondRating);
|
||||
} else {
|
||||
LOG.info("Better name: '{}' > '{}' ({} > {})", second, first, secondRating, firstRating);
|
||||
}
|
||||
}
|
||||
return firstBetter ? first : second;
|
||||
}
|
||||
|
||||
public static int calcRating(String str) {
|
||||
int rating = str.length() * 3;
|
||||
rating += differentCharsCount(str) * 20;
|
||||
|
||||
if (NameMapper.isAllCharsPrintable(str)) {
|
||||
rating += 100;
|
||||
}
|
||||
if (NameMapper.isValidIdentifier(str)) {
|
||||
rating += 50;
|
||||
}
|
||||
if (TldHelper.contains(str)) {
|
||||
rating += 20;
|
||||
}
|
||||
if (str.contains("_")) {
|
||||
// rare in obfuscated names
|
||||
rating += 100;
|
||||
}
|
||||
return rating;
|
||||
}
|
||||
|
||||
private static int differentCharsCount(String str) {
|
||||
String lower = str.toLowerCase(Locale.ROOT);
|
||||
Set<Integer> chars = new HashSet<>();
|
||||
StringUtils.visitCodePoints(lower, chars::add);
|
||||
return chars.size();
|
||||
}
|
||||
}
|
||||
@@ -697,7 +697,7 @@ public class BlockUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Search lowest common ancestor in dominator tree for input set.
|
||||
* Search the lowest common ancestor in dominator tree for input set.
|
||||
*/
|
||||
@Nullable
|
||||
public static BlockNode getCommonDominator(MethodNode mth, List<BlockNode> blocks) {
|
||||
|
||||
@@ -29,8 +29,12 @@ public final class InsnList implements Iterable<InsnNode> {
|
||||
}
|
||||
|
||||
public static int getIndex(List<InsnNode> list, InsnNode insn) {
|
||||
return getIndex(list, insn, 0);
|
||||
}
|
||||
|
||||
public static int getIndex(List<InsnNode> list, InsnNode insn, int startOffset) {
|
||||
int size = list.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int i = startOffset; i < size; i++) {
|
||||
if (list.get(i) == insn) {
|
||||
return i;
|
||||
}
|
||||
@@ -38,6 +42,14 @@ public final class InsnList implements Iterable<InsnNode> {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean contains(List<InsnNode> list, InsnNode insn) {
|
||||
return getIndex(list, insn, 0) != -1;
|
||||
}
|
||||
|
||||
public static boolean contains(List<InsnNode> list, InsnNode insn, int startOffset) {
|
||||
return getIndex(list, insn, startOffset) != -1;
|
||||
}
|
||||
|
||||
public int getIndex(InsnNode insn) {
|
||||
return getIndex(list, insn);
|
||||
}
|
||||
|
||||
@@ -160,6 +160,10 @@ public class ListUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static <T> boolean noneMatch(Collection<T> list, Predicate<T> test) {
|
||||
return !anyMatch(list, test);
|
||||
}
|
||||
|
||||
public static <T> boolean anyMatch(Collection<T> list, Predicate<T> test) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -22,6 +22,7 @@ import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlockAttr;
|
||||
@@ -289,7 +290,11 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (container instanceof IRegion) {
|
||||
}
|
||||
if (container instanceof LoopRegion) {
|
||||
return true;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
for (IContainer block : region.getSubBlocks()) {
|
||||
if (notEmpty(block)) {
|
||||
@@ -297,9 +302,8 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static void getAllRegionBlocks(IContainer container, Set<IBlock> blocks) {
|
||||
|
||||
@@ -129,7 +129,7 @@ public class AndroidResourcesUtils {
|
||||
FieldNode newResField = new FieldNode(typeCls, rFieldInfo,
|
||||
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
|
||||
newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
|
||||
typeCls.getFields().add(newResField);
|
||||
typeCls.addField(newResField);
|
||||
if (rClsExists) {
|
||||
newResField.addInfoComment("Added by JADX");
|
||||
}
|
||||
|
||||
@@ -10,12 +10,16 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.JarEntry;
|
||||
@@ -108,13 +112,8 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
public static boolean deleteDir(File dir) {
|
||||
File[] content = dir.listFiles();
|
||||
if (content != null) {
|
||||
for (File file : content) {
|
||||
deleteDir(file);
|
||||
}
|
||||
}
|
||||
return dir.delete();
|
||||
deleteDir(dir.toPath());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void deleteDirIfExists(Path dir) {
|
||||
@@ -127,16 +126,23 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleFileVisitor<Path> FILE_DELETE_VISITOR = new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
};
|
||||
|
||||
private static void deleteDir(Path dir) {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
pathStream.sorted(Comparator.reverseOrder())
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete path: " + path.toAbsolutePath(), e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
Files.walkFileTree(dir, Collections.emptySet(), Integer.MAX_VALUE, FILE_DELETE_VISITOR);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to delete directory " + dir, e);
|
||||
}
|
||||
@@ -228,7 +234,8 @@ public class FileUtils {
|
||||
|
||||
public static void writeFile(Path file, String data) throws IOException {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
Files.write(file, data.getBytes(StandardCharsets.UTF_8));
|
||||
Files.write(file, data.getBytes(StandardCharsets.UTF_8),
|
||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
|
||||
public static String readFile(Path textFile) throws IOException {
|
||||
|
||||
@@ -10,14 +10,18 @@ import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.BetterName;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.entry.EntryConfig;
|
||||
import jadx.core.xmlgen.entry.RawNamedValue;
|
||||
import jadx.core.xmlgen.entry.RawValue;
|
||||
@@ -287,35 +291,66 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
if (renamedKey != null) {
|
||||
return renamedKey;
|
||||
}
|
||||
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
||||
if (constField != null) {
|
||||
constField.add(AFlag.DONT_RENAME);
|
||||
return constField.getName();
|
||||
}
|
||||
// styles might contain dots in name, use VALID_RES_KEY_PATTERN only for resource file name
|
||||
// styles might contain dots in name, search for alias only for resources names
|
||||
if (typeName.equals("style")) {
|
||||
return origKeyName;
|
||||
} else if (VALID_RES_KEY_PATTERN.matcher(origKeyName).matches()) {
|
||||
return origKeyName;
|
||||
}
|
||||
FieldNode constField = root.getConstValues().getGlobalConstFields().get(resRef);
|
||||
String resAlias = getResAlias(resRef, origKeyName, constField);
|
||||
resStorage.addRename(resRef, resAlias);
|
||||
if (constField != null) {
|
||||
constField.rename(resAlias);
|
||||
constField.add(AFlag.DONT_RENAME);
|
||||
}
|
||||
return resAlias;
|
||||
}
|
||||
|
||||
private String getResAlias(int resRef, String origKeyName, @Nullable FieldNode constField) {
|
||||
String name;
|
||||
if (constField == null || constField.getTopParentClass().isSynthetic()) {
|
||||
name = origKeyName;
|
||||
} else {
|
||||
name = getBetterName(root.getArgs().getResourceNameSource(), origKeyName, constField.getName());
|
||||
}
|
||||
Matcher matcher = VALID_RES_KEY_PATTERN.matcher(name);
|
||||
if (matcher.matches()) {
|
||||
return name;
|
||||
}
|
||||
// Making sure origKeyName compliant with resource file name rules
|
||||
Matcher m = VALID_RES_KEY_PATTERN.matcher(origKeyName);
|
||||
String cleanedResName = cleanName(matcher);
|
||||
String newResName = String.format("res_0x%08x", resRef);
|
||||
if (cleanedResName.isEmpty()) {
|
||||
return newResName;
|
||||
}
|
||||
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
|
||||
return newResName + "_" + cleanedResName.toLowerCase();
|
||||
}
|
||||
|
||||
public static String getBetterName(ResourceNameSource nameSource, String resName, String codeName) {
|
||||
switch (nameSource) {
|
||||
case AUTO:
|
||||
return BetterName.compareAndGet(resName, codeName);
|
||||
case RESOURCES:
|
||||
return resName;
|
||||
case CODE:
|
||||
return codeName;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected ResourceNameSource value: " + nameSource);
|
||||
}
|
||||
}
|
||||
|
||||
private String cleanName(Matcher matcher) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
while (m.find()) {
|
||||
while (matcher.find()) {
|
||||
if (!first) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(m.group());
|
||||
sb.append(matcher.group());
|
||||
first = false;
|
||||
}
|
||||
// autogenerate key name, appended with cleaned origKeyName to be human-friendly
|
||||
String newResName = String.format("res_0x%08x", resRef);
|
||||
String cleanedResName = sb.toString();
|
||||
if (!cleanedResName.isEmpty()) {
|
||||
newResName += "_" + cleanedResName.toLowerCase();
|
||||
}
|
||||
return newResName;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private RawNamedValue parseValueMap() throws IOException {
|
||||
|
||||
@@ -168,11 +168,13 @@ public class ResXmlGen {
|
||||
private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
||||
String nameStr = vp.decodeNameRef(value.getNameRef());
|
||||
String valueStr = vp.decodeValue(value.getRawValue());
|
||||
int dataType = value.getRawValue().getDataType();
|
||||
|
||||
if (!typeName.equals("attr")) {
|
||||
if (valueStr == null || valueStr.equals("0")) {
|
||||
if (dataType == ParserConstants.TYPE_REFERENCE && (valueStr == null || valueStr.equals("0"))) {
|
||||
valueStr = "@null";
|
||||
}
|
||||
if (nameStr != null) {
|
||||
if (dataType == ParserConstants.TYPE_INT_DEC && nameStr != null) {
|
||||
try {
|
||||
int intVal = Integer.parseInt(valueStr);
|
||||
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static jadx.core.utils.BetterName.calcRating;
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestBetterName {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
expectFirst("color_main", "t0");
|
||||
expectFirst("done", "oOo0oO0o");
|
||||
}
|
||||
|
||||
private void expectFirst(String first, String second) {
|
||||
String best = BetterName.compareAndGet(first, second);
|
||||
assertThat(best)
|
||||
.as(() -> String.format("'%s'=%d, '%s'=%d", first, calcRating(first), second, calcRating(second)))
|
||||
.isEqualTo(first);
|
||||
}
|
||||
}
|
||||
@@ -555,7 +555,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void enableDeobfuscation() {
|
||||
args.setDeobfuscationOn(true);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.OVERWRITE);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||
args.setDeobfuscationMinLength(2);
|
||||
args.setDeobfuscationMaxLength(64);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class TestCompiler implements Closeable {
|
||||
assertNotNull(mth, "Failed to get method " + methodName + '(' + Arrays.toString(types) + ')');
|
||||
return mth.invoke(inst, args);
|
||||
} catch (Throwable e) {
|
||||
IntegrationTest.rethrow("Invoke error", e);
|
||||
IntegrationTest.rethrow("Invoke error for method: " + methodName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package jadx.tests.integration.deobf;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestInheritedMethodRename extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public static class A extends B {
|
||||
}
|
||||
|
||||
public static class B {
|
||||
public void call() {
|
||||
System.out.println("call");
|
||||
}
|
||||
}
|
||||
|
||||
public void test(A a) {
|
||||
// reference to A.call() not renamed,
|
||||
// should be resolved to B.call() and use alias
|
||||
a.call();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
enableDeobfuscation();
|
||||
getArgs().setDeobfuscationMinLength(99);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("public void m0call() {")
|
||||
.doesNotContain(".call();")
|
||||
.containsOne(".m0call();");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.tests.integration.inner;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestAnonymousClass20 extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings({ "unused", "checkstyle:TypeName", "Convert2Lambda", "Anonymous2MethodRef" })
|
||||
public static class Test$Cls {
|
||||
public Runnable test() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Test$Cls();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(Test$Cls.class);
|
||||
assertThat(cls.get(AType.ANONYMOUS_CLASS)).isNull();
|
||||
|
||||
JavaClass javaClass = JadxInternalAccess.convertClassNode(jadxDecompiler, cls);
|
||||
assertThat(javaClass.getTopParentClass()).isEqualTo(javaClass);
|
||||
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.containsOne("new TestAnonymousClass20$Test$Cls();");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package jadx.tests.integration.loops;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
/**
|
||||
* Empty endless loop, issue #1611
|
||||
*/
|
||||
public class TestEndlessLoop2 extends SmaliTest {
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.countString(2, "while (true) {");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.tests.integration.names;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.CommentsLevel;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
/**
|
||||
* Negative case for field initialization move (#1599).
|
||||
* Can't reorder with other instance methods.
|
||||
*/
|
||||
public class TestFieldInitNegative extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
StringBuilder sb;
|
||||
int field;
|
||||
|
||||
public TestCls() {
|
||||
initBuilder(new StringBuilder("sb"));
|
||||
this.field = initField();
|
||||
this.sb.append(this.field);
|
||||
}
|
||||
|
||||
private void initBuilder(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
}
|
||||
|
||||
private int initField() {
|
||||
return sb.length();
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(new TestCls().getStr()).isEqualTo("sb2"); // no NPE
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.doesNotContain("int field = initField();")
|
||||
.containsOne("this.field = initField();");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestNestedTryCatch4 extends SmaliTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.doesNotContain("?? ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
/**
|
||||
* Negative test case for finally extract (issue 1592).
|
||||
* Different registers incorrectly merged into one.
|
||||
*/
|
||||
@SuppressWarnings({ "CommentedOutCode", "GrazieInspection" })
|
||||
public class TestTryCatchFinally15 extends SmaliTest {
|
||||
|
||||
// @formatter:off
|
||||
/*
|
||||
protected final Parcel test(int i, Parcel parcel) throws RemoteException {
|
||||
Parcel obtain = Parcel.obtain();
|
||||
try {
|
||||
try {
|
||||
this.zza.transact(i, parcel, obtain, 0);
|
||||
obtain.readException();
|
||||
return obtain;
|
||||
} catch (RuntimeException e) {
|
||||
obtain.recycle();
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
disableCompilation();
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.doesNotContain("parcel = Parcel.obtain();")
|
||||
.containsOne("this.zza.transact(i, parcel, obtain, 0);");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsLines;
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class TestTryCatchFinally6 extends IntegrationTest {
|
||||
@@ -54,15 +55,7 @@ public class TestTryCatchFinally6 extends IntegrationTest {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsLines(2,
|
||||
"FileInputStream fileInputStream = null;",
|
||||
"try {",
|
||||
indent() + "call();",
|
||||
indent() + "fileInputStream = new FileInputStream(\"1.txt\");",
|
||||
"} finally {",
|
||||
indent() + "if (fileInputStream != null) {",
|
||||
indent() + indent() + "fileInputStream.close();",
|
||||
indent() + '}',
|
||||
"}"));
|
||||
// impossible to proof that variables should be merged, so can't restore finally block here
|
||||
assertThat(code, containsOne("if (0 != 0) {"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
.class Lloops/TestEndlessLoop2;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.field instanceCount:J
|
||||
|
||||
.method test([Ljava/lang/String;)V
|
||||
.registers 10
|
||||
|
||||
const/16 p1, 0xb
|
||||
invoke-virtual {p0, p1}, Lloops/TestEndlessLoop2;->vMeth(I)V
|
||||
const/16 v0, 0xf1
|
||||
const-wide/high16 v1, 0x4032000000000000L # 18.0
|
||||
|
||||
:goto_a
|
||||
const-wide/high16 v3, 0x4076000000000000L # 352.0
|
||||
const/4 v5, 0x1
|
||||
cmpg-double v6, v1, v3
|
||||
if-gez v6, :cond_1c
|
||||
const/4 v0, 0x1
|
||||
|
||||
:goto_12
|
||||
add-int/2addr v0, v5
|
||||
const/16 v3, 0x4b
|
||||
if-ge v0, v3, :cond_18
|
||||
goto :goto_12
|
||||
|
||||
:cond_18
|
||||
const-wide/high16 v3, 0x3ff0000000000000L # 1.0
|
||||
add-double/2addr v1, v3
|
||||
goto :goto_a
|
||||
|
||||
:cond_1c
|
||||
iget-wide v3, p0, Lloops/TestEndlessLoop2;->instanceCount:J
|
||||
long-to-int v4, v3
|
||||
const/16 v3, 0xb
|
||||
|
||||
:goto_21
|
||||
const/16 v6, 0xf3
|
||||
|
||||
if-ge v5, v6, :cond_41
|
||||
rem-int/lit8 v6, v5, 0x9
|
||||
add-int/lit8 v6, v6, 0x12
|
||||
if-eq v6, p1, :cond_3e
|
||||
const/16 v7, 0x15
|
||||
if-eq v6, v7, :cond_36
|
||||
const/16 v7, 0x16
|
||||
if-eq v6, v7, :cond_34
|
||||
goto :goto_3b
|
||||
|
||||
|
||||
:cond_34
|
||||
add-int/2addr v4, v0
|
||||
goto :goto_3b
|
||||
|
||||
:cond_36
|
||||
const v6, 0xeed9
|
||||
div-int/2addr v3, v6
|
||||
nop
|
||||
|
||||
:goto_3b
|
||||
add-int/lit8 v5, v5, 0x1
|
||||
goto :goto_21
|
||||
|
||||
:cond_3e
|
||||
nop
|
||||
|
||||
:goto_3f
|
||||
nop
|
||||
# endless loop with empty body
|
||||
goto :goto_3f
|
||||
|
||||
:cond_41
|
||||
sget-object p1, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
invoke-static {v1, v2}, Ljava/lang/Double;->doubleToLongBits(D)J
|
||||
move-result-wide v0
|
||||
new-instance v2, Ljava/lang/StringBuilder;
|
||||
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V
|
||||
const-string v5, "i21 d2 i22 = "
|
||||
invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||
const-string v3, ","
|
||||
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
invoke-virtual {v2, v0, v1}, Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;
|
||||
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
invoke-virtual {v2, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
|
||||
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
move-result-object v0
|
||||
invoke-virtual {p1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
return-void
|
||||
.end method
|
||||
@@ -0,0 +1,601 @@
|
||||
.class public Ltrycatch/TestNestedTryCatch4;
|
||||
.super Landroid/app/NativeActivity;
|
||||
|
||||
.method private test(Landroid/content/Intent;)V
|
||||
.registers 11
|
||||
.annotation system Ldalvik/annotation/MethodParameters;
|
||||
accessFlags = {
|
||||
0x0
|
||||
}
|
||||
names = {
|
||||
"intent"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
const-string v0, "IOException while closing input stream\n"
|
||||
|
||||
if-nez p1, :cond_5
|
||||
|
||||
return-void
|
||||
|
||||
:cond_5
|
||||
const-string v1, "intent_cmd"
|
||||
|
||||
.line 1740
|
||||
invoke-virtual {p1, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
const-string v2, "MCPE"
|
||||
|
||||
if-eqz v1, :cond_80
|
||||
|
||||
.line 1741
|
||||
invoke-virtual {v1}, Ljava/lang/String;->length()I
|
||||
|
||||
move-result v3
|
||||
|
||||
if-lez v3, :cond_80
|
||||
|
||||
.line 1743
|
||||
:try_start_15
|
||||
new-instance p1, Lorg/json/JSONObject;
|
||||
|
||||
invoke-direct {p1, v1}, Lorg/json/JSONObject;-><init>(Ljava/lang/String;)V
|
||||
|
||||
const-string v0, "Command"
|
||||
|
||||
.line 1744
|
||||
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
const-string v1, "keyboardResult"
|
||||
|
||||
.line 1745
|
||||
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v1
|
||||
|
||||
if-eqz v1, :cond_33
|
||||
|
||||
const-string v0, "Text"
|
||||
|
||||
.line 1746
|
||||
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {p0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeSetTextboxText(Ljava/lang/String;)V
|
||||
|
||||
goto/16 :goto_208
|
||||
|
||||
:cond_33
|
||||
const-string v1, "fileDialogResult"
|
||||
|
||||
.line 1748
|
||||
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v0
|
||||
|
||||
if-eqz v0, :cond_208
|
||||
|
||||
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||
|
||||
const-wide/16 v3, 0x0
|
||||
|
||||
cmp-long v5, v0, v3
|
||||
|
||||
if-eqz v5, :cond_208
|
||||
|
||||
const-string v0, "Result"
|
||||
|
||||
.line 1749
|
||||
invoke-virtual {p1, v0}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
const-string v1, "Ok"
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v0
|
||||
|
||||
if-eqz v0, :cond_5d
|
||||
|
||||
.line 1750
|
||||
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||
|
||||
const-string v5, "Path"
|
||||
|
||||
invoke-virtual {p1, v5}, Lorg/json/JSONObject;->getString(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {p0, v0, v1, p1}, Ltrycatch/TestNestedTryCatch4;->nativeOnPickImageSuccess(JLjava/lang/String;)V
|
||||
|
||||
goto :goto_62
|
||||
|
||||
.line 1753
|
||||
:cond_5d
|
||||
iget-wide v0, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||
|
||||
invoke-virtual {p0, v0, v1}, Ltrycatch/TestNestedTryCatch4;->nativeOnPickImageCanceled(J)V
|
||||
|
||||
.line 1755
|
||||
:goto_62
|
||||
iput-wide v3, p0, Ltrycatch/TestNestedTryCatch4;->mFileDialogCallback:J
|
||||
:try_end_64
|
||||
.catch Lorg/json/JSONException; {:try_start_15 .. :try_end_64} :catch_66
|
||||
|
||||
goto/16 :goto_208
|
||||
|
||||
:catch_66
|
||||
move-exception p1
|
||||
|
||||
.line 1759
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v1, "JSONObject exception:"
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {p1}, Lorg/json/JSONException;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-static {v2, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
|
||||
|
||||
return-void
|
||||
|
||||
.line 1765
|
||||
:cond_80
|
||||
invoke-virtual {p1}, Landroid/content/Intent;->getAction()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
.line 1766
|
||||
invoke-virtual {p1}, Landroid/content/Intent;->getType()Ljava/lang/String;
|
||||
|
||||
const-string/jumbo v3, "xbox_live_game_invite"
|
||||
|
||||
.line 1768
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v3
|
||||
|
||||
if-eqz v3, :cond_b0
|
||||
|
||||
const-string/jumbo v0, "xbl"
|
||||
|
||||
.line 1771
|
||||
invoke-virtual {p1, v0}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
.line 1772
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v3, "[XboxLive] Received Invite "
|
||||
|
||||
invoke-virtual {v0, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
invoke-static {v2, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
|
||||
|
||||
.line 1776
|
||||
invoke-virtual {p0, v1, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||
|
||||
goto/16 :goto_208
|
||||
|
||||
:cond_b0
|
||||
const-string v3, "android.intent.action.VIEW"
|
||||
|
||||
.line 1779
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v3
|
||||
|
||||
if-nez v3, :cond_c0
|
||||
|
||||
const-string v3, "org.chromium.arc.intent.action.VIEW"
|
||||
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||
|
||||
move-result v1
|
||||
|
||||
if-eqz v1, :cond_208
|
||||
|
||||
.line 1780
|
||||
:cond_c0
|
||||
invoke-virtual {p1}, Landroid/content/Intent;->getScheme()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
.line 1781
|
||||
invoke-virtual {p1}, Landroid/content/Intent;->getData()Landroid/net/Uri;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
if-nez p1, :cond_cb
|
||||
|
||||
return-void
|
||||
|
||||
:cond_cb
|
||||
const-string v3, "minecraft"
|
||||
|
||||
.line 1787
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||
|
||||
move-result v3
|
||||
|
||||
if-nez v3, :cond_1f9
|
||||
|
||||
const-string v3, "minecraftedu"
|
||||
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||
|
||||
move-result v3
|
||||
|
||||
if-eqz v3, :cond_dd
|
||||
|
||||
goto/16 :goto_1f9
|
||||
|
||||
:cond_dd
|
||||
const-string v3, "file"
|
||||
|
||||
.line 1795
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||
|
||||
move-result v3
|
||||
|
||||
const-string v4, "&"
|
||||
|
||||
if-eqz v3, :cond_108
|
||||
|
||||
.line 1798
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
const-string v0, "fileIntent"
|
||||
|
||||
invoke-virtual {p0, v0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||
|
||||
goto/16 :goto_208
|
||||
|
||||
:cond_108
|
||||
const-string v3, "content"
|
||||
|
||||
.line 1800
|
||||
invoke-virtual {v3, v1}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
|
||||
|
||||
move-result v1
|
||||
|
||||
if-eqz v1, :cond_208
|
||||
|
||||
.line 1803
|
||||
new-instance v1, Ljava/io/File;
|
||||
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||
|
||||
move-result-object v3
|
||||
|
||||
invoke-direct {v1, v3}, Ljava/io/File;-><init>(Ljava/lang/String;)V
|
||||
|
||||
invoke-virtual {v1}, Ljava/io/File;->getName()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
.line 1804
|
||||
new-instance v3, Ljava/io/File;
|
||||
|
||||
new-instance v5, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
invoke-virtual {p0}, Ltrycatch/TestNestedTryCatch4;->getApplicationContext()Landroid/content/Context;
|
||||
|
||||
move-result-object v6
|
||||
|
||||
invoke-virtual {v6}, Landroid/content/Context;->getCacheDir()Ljava/io/File;
|
||||
|
||||
move-result-object v6
|
||||
|
||||
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
|
||||
|
||||
const-string v6, "/"
|
||||
|
||||
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v5, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
invoke-direct {v3, v1}, Ljava/io/File;-><init>(Ljava/lang/String;)V
|
||||
|
||||
.line 1806
|
||||
invoke-virtual {p0}, Ltrycatch/TestNestedTryCatch4;->getContentResolver()Landroid/content/ContentResolver;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
.line 1810
|
||||
:try_start_142
|
||||
invoke-virtual {v1, p1}, Landroid/content/ContentResolver;->openInputStream(Landroid/net/Uri;)Ljava/io/InputStream;
|
||||
|
||||
move-result-object v1
|
||||
:try_end_146
|
||||
.catch Ljava/io/IOException; {:try_start_142 .. :try_end_146} :catch_1df
|
||||
|
||||
.line 1818
|
||||
:try_start_146
|
||||
new-instance v5, Ljava/io/FileOutputStream;
|
||||
|
||||
invoke-direct {v5, v3}, Ljava/io/FileOutputStream;-><init>(Ljava/io/File;)V
|
||||
|
||||
const/high16 v6, 0x100000
|
||||
|
||||
new-array v6, v6, [B
|
||||
|
||||
.line 1824
|
||||
:goto_14f
|
||||
invoke-virtual {v1, v6}, Ljava/io/InputStream;->read([B)I
|
||||
|
||||
move-result v7
|
||||
|
||||
const/4 v8, -0x1
|
||||
|
||||
if-eq v7, v8, :cond_15b
|
||||
|
||||
const/4 v8, 0x0
|
||||
|
||||
.line 1825
|
||||
invoke-virtual {v5, v6, v8, v7}, Ljava/io/OutputStream;->write([BII)V
|
||||
|
||||
goto :goto_14f
|
||||
|
||||
.line 1828
|
||||
:cond_15b
|
||||
invoke-virtual {v5}, Ljava/io/OutputStream;->close()V
|
||||
|
||||
const-string v5, "contentIntent"
|
||||
|
||||
.line 1831
|
||||
new-instance v6, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v6}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getPath()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v6, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v6, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v3}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v6, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v6}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {p0, v5, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||
:try_end_17d
|
||||
.catch Ljava/io/IOException; {:try_start_146 .. :try_end_17d} :catch_18b
|
||||
.catchall {:try_start_146 .. :try_end_17d} :catchall_189
|
||||
|
||||
.line 1842
|
||||
:try_start_17d
|
||||
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||
:try_end_180
|
||||
.catch Ljava/io/IOException; {:try_start_17d .. :try_end_180} :catch_182
|
||||
|
||||
goto/16 :goto_208
|
||||
|
||||
:catch_182
|
||||
move-exception p1
|
||||
|
||||
.line 1845
|
||||
new-instance v1, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
goto :goto_1b1
|
||||
|
||||
:catchall_189
|
||||
move-exception p1
|
||||
|
||||
goto :goto_1c3
|
||||
|
||||
:catch_18b
|
||||
move-exception p1
|
||||
|
||||
.line 1833
|
||||
:try_start_18c
|
||||
new-instance v4, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v5, "IOException while copying file from content intent\n"
|
||||
|
||||
invoke-virtual {v4, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v4, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||
:try_end_1a4
|
||||
.catchall {:try_start_18c .. :try_end_1a4} :catchall_189
|
||||
|
||||
.line 1837
|
||||
:try_start_1a4
|
||||
invoke-virtual {v3}, Ljava/io/File;->delete()Z
|
||||
:try_end_1a7
|
||||
.catch Ljava/lang/Exception; {:try_start_1a4 .. :try_end_1a7} :catch_1a7
|
||||
.catchall {:try_start_1a4 .. :try_end_1a7} :catchall_189
|
||||
|
||||
.line 1842
|
||||
:catch_1a7
|
||||
:try_start_1a7
|
||||
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||
:try_end_1aa
|
||||
.catch Ljava/io/IOException; {:try_start_1a7 .. :try_end_1aa} :catch_1ab
|
||||
|
||||
goto :goto_208
|
||||
|
||||
:catch_1ab
|
||||
move-exception p1
|
||||
|
||||
.line 1845
|
||||
new-instance v1, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
:goto_1b1
|
||||
invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||
|
||||
goto :goto_208
|
||||
|
||||
.line 1842
|
||||
:goto_1c3
|
||||
:try_start_1c3
|
||||
invoke-virtual {v1}, Ljava/io/InputStream;->close()V
|
||||
:try_end_1c6
|
||||
.catch Ljava/io/IOException; {:try_start_1c3 .. :try_end_1c6} :catch_1c7
|
||||
|
||||
goto :goto_1de
|
||||
|
||||
:catch_1c7
|
||||
move-exception v1
|
||||
|
||||
.line 1845
|
||||
new-instance v3, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
invoke-virtual {v3, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
invoke-static {v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||
|
||||
.line 1847
|
||||
:goto_1de
|
||||
throw p1
|
||||
|
||||
:catch_1df
|
||||
move-exception p1
|
||||
|
||||
.line 1813
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v1, "IOException while opening file from content intent\n"
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {p1}, Ljava/io/IOException;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
invoke-static {v2, p1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
|
||||
|
||||
return-void
|
||||
|
||||
.line 1788
|
||||
:cond_1f9
|
||||
:goto_1f9
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getHost()Ljava/lang/String;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
.line 1789
|
||||
invoke-virtual {p1}, Landroid/net/Uri;->getQuery()Ljava/lang/String;
|
||||
|
||||
move-result-object p1
|
||||
|
||||
if-nez v0, :cond_205
|
||||
|
||||
if-eqz p1, :cond_208
|
||||
|
||||
.line 1792
|
||||
:cond_205
|
||||
invoke-virtual {p0, v0, p1}, Ltrycatch/TestNestedTryCatch4;->nativeProcessIntentUriQuery(Ljava/lang/String;Ljava/lang/String;)V
|
||||
|
||||
:cond_208
|
||||
:goto_208
|
||||
return-void
|
||||
.end method
|
||||
@@ -0,0 +1,60 @@
|
||||
.class public Ltrycatch/TestTryCatchFinally15;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.implements Landroid/os/IInterface;
|
||||
|
||||
.field private final zza:Landroid/os/IBinder;
|
||||
.field private final zzb:Ljava/lang/String;
|
||||
|
||||
.method protected final test(ILandroid/os/Parcel;)Landroid/os/Parcel;
|
||||
.registers 6
|
||||
.annotation system Ldalvik/annotation/Throws;
|
||||
value = {
|
||||
Landroid/os/RemoteException;
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.line 1
|
||||
invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
|
||||
move-result-object v0
|
||||
|
||||
:try_start_4
|
||||
iget-object v1, p0, Ltrycatch/TestTryCatchFinally15;->zza:Landroid/os/IBinder;
|
||||
const/4 v2, 0x0
|
||||
|
||||
.line 2
|
||||
invoke-interface {v1, p1, p2, v0, v2}, Landroid/os/IBinder;->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
|
||||
.line 3
|
||||
invoke-virtual {v0}, Landroid/os/Parcel;->readException()V
|
||||
:try_end_d
|
||||
.catch Ljava/lang/RuntimeException; {:try_start_4 .. :try_end_d} :catch_13
|
||||
.catchall {:try_start_4 .. :try_end_d} :catchall_11
|
||||
|
||||
.line 6
|
||||
invoke-virtual {p2}, Landroid/os/Parcel;->recycle()V
|
||||
return-object v0
|
||||
|
||||
:catchall_11
|
||||
move-exception p1
|
||||
goto :goto_18
|
||||
|
||||
.line 5
|
||||
:catch_13
|
||||
move-exception p1
|
||||
|
||||
.line 4
|
||||
:try_start_14
|
||||
invoke-virtual {v0}, Landroid/os/Parcel;->recycle()V
|
||||
|
||||
.line 5
|
||||
throw p1
|
||||
:try_end_18
|
||||
.catchall {:try_start_14 .. :try_end_18} :catchall_11
|
||||
|
||||
.line 6
|
||||
:goto_18
|
||||
invoke-virtual {p2}, Landroid/os/Parcel;->recycle()V
|
||||
|
||||
.line 7
|
||||
throw p1
|
||||
.end method
|
||||
@@ -20,13 +20,13 @@ dependencies {
|
||||
implementation 'com.formdev:flatlaf-extras:2.4'
|
||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'com.google.code.gson:gson:2.9.1'
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.apache.commons:commons-text:1.9'
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
|
||||
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
|
||||
implementation 'com.android.tools.build:apksig:7.2.1'
|
||||
implementation 'com.android.tools.build:apksig:7.2.2'
|
||||
implementation 'io.github.hqktech:jdwp:1.0'
|
||||
|
||||
// TODO: Switch back to upstream once this PR gets merged:
|
||||
@@ -42,7 +42,8 @@ application {
|
||||
mainClass.set('jadx.gui.JadxGUI')
|
||||
// The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC
|
||||
applicationDefaultJvmArgs = ['-Xms128M', '-XX:MaxRAMPercentage=70.0', '-XX:+UseG1GC',
|
||||
'-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true']
|
||||
'-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true',
|
||||
'-Djava.util.Arrays.useLegacyMergeSort=true']
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
@@ -138,3 +139,8 @@ task distWinWithJre(type: Zip, dependsOn: ['copyDistWinWithJre']) {
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task addNewNLSLines(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass.set('jadx.gui.utils.tools.NLSAddNewLines')
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ADB {
|
||||
}
|
||||
return result;
|
||||
} catch (SocketException e) {
|
||||
LOG.error("Aborting readServiceProtocol: socket closed");
|
||||
LOG.warn("Aborting readServiceProtocol: {}", e.toString());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to read readServiceProtocol", e);
|
||||
}
|
||||
@@ -207,7 +207,7 @@ public class ADB {
|
||||
List<ADBDeviceInfo> deviceInfoList = new ArrayList<>(deviceLines.length);
|
||||
for (String deviceLine : deviceLines) {
|
||||
if (!deviceLine.trim().isEmpty()) {
|
||||
deviceInfoList.add(ADBDeviceInfo.make(deviceLine, host, port));
|
||||
deviceInfoList.add(new ADBDeviceInfo(deviceLine, host, port));
|
||||
}
|
||||
}
|
||||
listener.onDeviceStatusChange(deviceInfoList);
|
||||
|
||||
@@ -39,7 +39,10 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public boolean updateDeviceInfo(ADBDeviceInfo info) {
|
||||
boolean matched = this.info.serial.equals(info.serial);
|
||||
if (info.getSerial() == null || info.getSerial().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean matched = this.info.getSerial().equals(info.getSerial());
|
||||
if (matched) {
|
||||
this.info = info;
|
||||
}
|
||||
@@ -47,21 +50,21 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public String getSerial() {
|
||||
return info.serial;
|
||||
return info.getSerial();
|
||||
}
|
||||
|
||||
public boolean removeForward(String localPort) throws IOException {
|
||||
return ADB.removeForward(info.adbHost, info.adbPort, info.serial, localPort);
|
||||
return ADB.removeForward(info.getAdbHost(), info.getAdbPort(), info.getSerial(), localPort);
|
||||
}
|
||||
|
||||
public ForwardResult forwardJDWP(String localPort, String jdwpPid) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
String cmd = String.format("host:forward:tcp:%s;jdwp:%s", localPort, jdwpPid);
|
||||
cmd = String.format("%04x%s", cmd.length(), cmd);
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
ForwardResult rst;
|
||||
if (ADB.setSerial(info.serial, outputStream, inputStream)) {
|
||||
if (ADB.setSerial(info.getSerial(), outputStream, inputStream)) {
|
||||
outputStream.write(cmd.getBytes());
|
||||
if (!ADB.isOkay(inputStream)) {
|
||||
rst = new ForwardResult(1, ADB.readServiceProtocol(inputStream));
|
||||
@@ -99,9 +102,9 @@ public class ADBDevice {
|
||||
*/
|
||||
public int launchApp(String fullAppName) throws IOException, InterruptedException {
|
||||
byte[] res;
|
||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
String cmd = "am start -D -n " + fullAppName;
|
||||
res = ADB.execShellCommandRaw(info.serial, cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
res = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
if (res == null) {
|
||||
return -1;
|
||||
}
|
||||
@@ -134,13 +137,13 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public List<String> getProp(String entry) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
List<String> props = Collections.emptyList();
|
||||
String cmd = "getprop";
|
||||
if (!StringUtils.isEmpty(entry)) {
|
||||
cmd += " " + entry;
|
||||
}
|
||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
||||
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||
socket.getOutputStream(), socket.getInputStream());
|
||||
if (payload != null) {
|
||||
props = new ArrayList<>();
|
||||
@@ -166,9 +169,9 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
private List<Process> getProcessList(String cmd, int index) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.adbHost, info.adbPort)) {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
List<Process> procs = new ArrayList<>();
|
||||
byte[] payload = ADB.execShellCommandRaw(info.serial, cmd,
|
||||
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||
socket.getOutputStream(), socket.getInputStream());
|
||||
if (payload != null) {
|
||||
String ps = new String(payload);
|
||||
@@ -194,10 +197,10 @@ public class ADBDevice {
|
||||
if (this.jdwpListenerSock != null) {
|
||||
return false;
|
||||
}
|
||||
jdwpListenerSock = ADB.connect(this.info.adbHost, this.info.adbPort);
|
||||
jdwpListenerSock = ADB.connect(this.info.getAdbHost(), this.info.getAdbPort());
|
||||
InputStream inputStream = jdwpListenerSock.getInputStream();
|
||||
OutputStream outputStream = jdwpListenerSock.getOutputStream();
|
||||
if (ADB.setSerial(info.serial, outputStream, inputStream)
|
||||
if (ADB.setSerial(info.getSerial(), outputStream, inputStream)
|
||||
&& ADB.execCommandAsync(outputStream, inputStream, CMD_TRACK_JDWP)) {
|
||||
Executors.newFixedThreadPool(1).execute(() -> {
|
||||
for (;;) {
|
||||
@@ -244,19 +247,20 @@ public class ADBDevice {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return info.serial.hashCode();
|
||||
return info.getSerial().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ADBDevice) {
|
||||
return ((ADBDevice) obj).getDeviceInfo().serial.equals(info.serial);
|
||||
String otherSerial = ((ADBDevice) obj).getDeviceInfo().getSerial();
|
||||
return otherSerial.equals(info.getSerial());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.allInfo;
|
||||
return info.getAllInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,91 @@
|
||||
package jadx.gui.device.protocol;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.log.LogUtils;
|
||||
|
||||
public class ADBDeviceInfo {
|
||||
public String adbHost;
|
||||
public int adbPort;
|
||||
public String serial;
|
||||
public String state;
|
||||
public String model;
|
||||
public String allInfo;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ADBDeviceInfo.class);
|
||||
private final String adbHost;
|
||||
private final int adbPort;
|
||||
private final String serial;
|
||||
private final String state;
|
||||
private final String model;
|
||||
private final String allInfo;
|
||||
|
||||
/**
|
||||
* Store the device info property values like "device" "model" "product" or "transport_id"
|
||||
*/
|
||||
private final Map<String, String> propertiesMap = new TreeMap<>();
|
||||
|
||||
ADBDeviceInfo(String info, String host, int port) {
|
||||
String[] infoFields = info.trim().split("\\s+");
|
||||
allInfo = String.join(" ", infoFields);
|
||||
if (infoFields.length > 2) {
|
||||
serial = infoFields[0];
|
||||
state = infoFields[1];
|
||||
|
||||
for (int i = 2; i < infoFields.length; i++) {
|
||||
String field = infoFields[i];
|
||||
int idx = field.indexOf(':');
|
||||
if (idx > 0) {
|
||||
String key = field.substring(0, idx);
|
||||
String value = field.substring(idx + 1);
|
||||
if (!value.isEmpty()) {
|
||||
propertiesMap.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
model = propertiesMap.getOrDefault("model", serial);
|
||||
} else {
|
||||
LOG.error("Unable to extract device information from {}", LogUtils.escape(info));
|
||||
serial = "";
|
||||
state = "unknown";
|
||||
model = "unknown";
|
||||
}
|
||||
adbHost = host;
|
||||
adbPort = port;
|
||||
}
|
||||
|
||||
public boolean isOnline() {
|
||||
return state.equals("device");
|
||||
}
|
||||
|
||||
public String getAdbHost() {
|
||||
return adbHost;
|
||||
}
|
||||
|
||||
public int getAdbPort() {
|
||||
return adbPort;
|
||||
}
|
||||
|
||||
public String getSerial() {
|
||||
return serial;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getAllInfo() {
|
||||
return allInfo;
|
||||
}
|
||||
|
||||
public String getProperty(String key) {
|
||||
return this.propertiesMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return allInfo;
|
||||
}
|
||||
|
||||
static ADBDeviceInfo make(String info, String host, int port) {
|
||||
ADBDeviceInfo deviceInfo = new ADBDeviceInfo();
|
||||
String[] infoFields = info.trim().split("\\s+");
|
||||
deviceInfo.allInfo = String.join(" ", infoFields);
|
||||
if (infoFields.length > 2) {
|
||||
deviceInfo.serial = infoFields[0];
|
||||
deviceInfo.state = infoFields[1];
|
||||
}
|
||||
int pos = info.indexOf("model:");
|
||||
if (pos != -1) {
|
||||
int spacePos = info.indexOf(" ", pos);
|
||||
if (spacePos != -1) {
|
||||
deviceInfo.model = info.substring(pos + "model:".length(), spacePos);
|
||||
}
|
||||
}
|
||||
if (deviceInfo.model == null || deviceInfo.model.equals("")) {
|
||||
deviceInfo.model = deviceInfo.serial;
|
||||
}
|
||||
deviceInfo.adbHost = host;
|
||||
deviceInfo.adbPort = port;
|
||||
return deviceInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ public class BackgroundExecutor {
|
||||
task.onDone(this);
|
||||
// treat UI task operations as part of the task to not mix with others
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
task.onFinish(this);
|
||||
progressPane.setVisible(false);
|
||||
task.onFinish(this);
|
||||
});
|
||||
} finally {
|
||||
taskComplete(id);
|
||||
@@ -190,13 +190,21 @@ public class BackgroundExecutor {
|
||||
performCancel(executor);
|
||||
return cancelStatus;
|
||||
}
|
||||
updateProgress(executor);
|
||||
k++;
|
||||
Thread.sleep(k < 10 ? 200 : 1000); // faster update for short tasks
|
||||
if (jobsCount == 1 && k == 3) {
|
||||
if (k < 10) {
|
||||
// faster update for short tasks
|
||||
Thread.sleep(200);
|
||||
if (k == 5) {
|
||||
updateProgress(executor);
|
||||
}
|
||||
} else {
|
||||
updateProgress(executor);
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (jobsCount == 1 && k == 5) {
|
||||
// small delay before show progress to reduce blinking on short tasks
|
||||
progressPane.changeVisibility(this, true);
|
||||
}
|
||||
k++;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.debug("Task wait interrupted");
|
||||
@@ -210,7 +218,7 @@ public class BackgroundExecutor {
|
||||
}
|
||||
|
||||
private void updateProgress(ThreadPoolExecutor executor) {
|
||||
Consumer<ITaskProgress> onProgressListener = task.getOnProgressListener();
|
||||
Consumer<ITaskProgress> onProgressListener = task.getProgressListener();
|
||||
ITaskProgress taskProgress = task.getTaskProgress();
|
||||
if (taskProgress == null) {
|
||||
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||
@@ -231,13 +239,16 @@ public class BackgroundExecutor {
|
||||
// force termination
|
||||
task.cancel();
|
||||
executor.shutdown();
|
||||
if (executor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
int cancelTimeout = task.getCancelTimeoutMS();
|
||||
if (cancelTimeout != 0) {
|
||||
if (executor.awaitTermination(cancelTimeout, TimeUnit.MILLISECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.debug("Forcing tasks cancel");
|
||||
executor.shutdownNow();
|
||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
boolean complete = executor.awaitTermination(task.getShutdownTimeoutMS(), TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Forced task cancel status: {}",
|
||||
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
||||
}
|
||||
@@ -301,5 +312,13 @@ public class BackgroundExecutor {
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TaskWorker{status=" + status
|
||||
+ ", jobsCount=" + jobsCount
|
||||
+ ", jobsComplete=" + jobsComplete
|
||||
+ ", time=" + time + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,12 @@ public interface Cancelable {
|
||||
boolean isCanceled();
|
||||
|
||||
void cancel();
|
||||
|
||||
default int getCancelTimeoutMS() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
default int getShutdownTimeoutMS() {
|
||||
return 5000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public interface IBackgroundTask extends Cancelable {
|
||||
/**
|
||||
* Return progress notifications listener (use executor tick rate and thread) (Optional)
|
||||
*/
|
||||
default @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
||||
default @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class QuarkManager {
|
||||
private void loadReport() {
|
||||
try {
|
||||
QuarkReportNode quarkNode = new QuarkReportNode(reportFile);
|
||||
JRoot root = mainWindow.getCacheObject().getJRoot();
|
||||
JRoot root = mainWindow.getTreeRoot();
|
||||
root.replaceCustomNode(quarkNode);
|
||||
root.update();
|
||||
mainWindow.reloadTree();
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -27,8 +29,8 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SearchTask.class);
|
||||
|
||||
private final BackgroundExecutor backgroundExecutor;
|
||||
private final Consumer<ITaskInfo> onFinish;
|
||||
private final Consumer<JNode> results;
|
||||
private final Consumer<JNode> resultsListener;
|
||||
private final BiConsumer<ITaskInfo, Boolean> onFinish;
|
||||
private final List<SearchJob> jobs = new ArrayList<>();
|
||||
private final TaskProgress taskProgress = new TaskProgress();
|
||||
|
||||
@@ -39,9 +41,9 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
|
||||
private Consumer<ITaskProgress> progressListener;
|
||||
|
||||
public SearchTask(MainWindow mainWindow, Consumer<JNode> results, Consumer<ITaskInfo> onFinish) {
|
||||
public SearchTask(MainWindow mainWindow, Consumer<JNode> results, BiConsumer<ITaskInfo, Boolean> onFinish) {
|
||||
this.backgroundExecutor = mainWindow.getBackgroundExecutor();
|
||||
this.results = results;
|
||||
this.resultsListener = results;
|
||||
this.onFinish = onFinish;
|
||||
}
|
||||
|
||||
@@ -55,8 +57,7 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
|
||||
public synchronized void fetchResults() {
|
||||
if (future != null) {
|
||||
cancel();
|
||||
waitTask();
|
||||
throw new IllegalStateException("Previous task not yet finished");
|
||||
}
|
||||
resetCancel();
|
||||
complete.set(false);
|
||||
@@ -70,9 +71,10 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
// ignore new results after cancel
|
||||
return true;
|
||||
}
|
||||
this.results.accept(resultNode);
|
||||
this.resultsListener.accept(resultNode);
|
||||
if (resultsLimit != 0 && resultsCount.incrementAndGet() >= resultsLimit) {
|
||||
cancel();
|
||||
complete.set(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -81,14 +83,17 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
public synchronized void waitTask() {
|
||||
if (future != null) {
|
||||
try {
|
||||
future.get(2, TimeUnit.SECONDS);
|
||||
future.get(200, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
LOG.debug("Search task wait timeout");
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Wait search task failed", e);
|
||||
LOG.warn("Search task wait error", e);
|
||||
} finally {
|
||||
future.cancel(true);
|
||||
future = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,18 +106,12 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
return jobs;
|
||||
}
|
||||
|
||||
public boolean isSearchComplete() {
|
||||
return complete.get() && !isCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(ITaskInfo taskInfo) {
|
||||
this.complete.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(ITaskInfo status) {
|
||||
this.onFinish.accept(status);
|
||||
public void onFinish(ITaskInfo task) {
|
||||
boolean complete = !isCanceled()
|
||||
&& task.getStatus() == TaskStatus.COMPLETE
|
||||
&& task.getJobsComplete() == task.getJobsCount();
|
||||
this.onFinish.accept(task, complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,7 +130,17 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
||||
public @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||
return this.progressListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCancelTimeoutMS() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShutdownTimeoutMS() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package jadx.gui.search.providers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import javax.swing.tree.TreeNode;
|
||||
|
||||
@@ -18,56 +15,55 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
import jadx.gui.jobs.Cancelable;
|
||||
import jadx.gui.search.ISearchProvider;
|
||||
import jadx.gui.search.SearchSettings;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResSearchNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
|
||||
public class ResourceSearchProvider implements ISearchProvider {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceSearchProvider.class);
|
||||
|
||||
private final CacheObject cache;
|
||||
private final SearchSettings searchSettings;
|
||||
private final Set<String> extSet = new HashSet<>();
|
||||
|
||||
private List<JResource> resNodes;
|
||||
private String fileExts;
|
||||
private final Set<String> extSet;
|
||||
private final int sizeLimit;
|
||||
private boolean anyExt;
|
||||
private int sizeLimit;
|
||||
|
||||
private int progress;
|
||||
/**
|
||||
* Resources queue for process. Using UI nodes to reuse loading cache
|
||||
*/
|
||||
private final Deque<JResource> resQueue;
|
||||
private int pos;
|
||||
|
||||
public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings) {
|
||||
this.cache = mw.getCacheObject();
|
||||
this.searchSettings = searchSettings;
|
||||
this.sizeLimit = mw.getSettings().getSrhResourceSkipSize() * 1048576;
|
||||
this.extSet = buildAllowedFilesExtensions(mw.getSettings().getSrhResourceFileExt());
|
||||
this.resQueue = initResQueue(mw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JNode next(Cancelable cancelable) {
|
||||
if (resNodes == null) {
|
||||
load();
|
||||
}
|
||||
if (resNodes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
while (true) {
|
||||
if (cancelable.isCanceled()) {
|
||||
return null;
|
||||
}
|
||||
JResource resNode = resNodes.get(progress);
|
||||
JResource resNode = getNextResFile(cancelable);
|
||||
if (resNode == null) {
|
||||
return null;
|
||||
}
|
||||
JNode newResult = search(resNode);
|
||||
if (newResult != null) {
|
||||
return newResult;
|
||||
}
|
||||
progress++;
|
||||
pos = 0;
|
||||
if (progress >= resNodes.size()) {
|
||||
resQueue.removeLast();
|
||||
addChildren(resNode);
|
||||
if (resQueue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -94,133 +90,107 @@ public class ResourceSearchProvider implements ISearchProvider {
|
||||
return new JResSearchNode(resNode, line.trim(), newPos);
|
||||
}
|
||||
|
||||
private synchronized void load() {
|
||||
resNodes = new ArrayList<>();
|
||||
sizeLimit = cache.getJadxSettings().getSrhResourceSkipSize() * 1048576;
|
||||
fileExts = cache.getJadxSettings().getSrhResourceFileExt();
|
||||
for (String extStr : fileExts.split("\\|")) {
|
||||
private @Nullable JResource getNextResFile(Cancelable cancelable) {
|
||||
while (true) {
|
||||
JResource node = resQueue.peekLast();
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
node.loadNode();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load resource node: {}", node, e);
|
||||
resQueue.removeLast();
|
||||
continue;
|
||||
}
|
||||
if (cancelable.isCanceled()) {
|
||||
return null;
|
||||
}
|
||||
if (node.getType() == JResource.JResType.FILE) {
|
||||
if (shouldProcess(node)) {
|
||||
return node;
|
||||
}
|
||||
resQueue.removeLast();
|
||||
} else {
|
||||
// dir
|
||||
resQueue.removeLast();
|
||||
addChildren(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addChildren(JResource resNode) {
|
||||
resQueue.addAll(resNode.getSubNodes());
|
||||
}
|
||||
|
||||
private static Deque<JResource> initResQueue(MainWindow mw) {
|
||||
JRoot jRoot = mw.getTreeRoot();
|
||||
Deque<JResource> deque = new ArrayDeque<>(jRoot.getChildCount());
|
||||
Enumeration<TreeNode> children = jRoot.children();
|
||||
while (children.hasMoreElements()) {
|
||||
TreeNode node = children.nextElement();
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
deque.add(resNode);
|
||||
}
|
||||
}
|
||||
return deque;
|
||||
}
|
||||
|
||||
private Set<String> buildAllowedFilesExtensions(String srhResourceFileExt) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (String extStr : srhResourceFileExt.split("[|.]")) {
|
||||
String ext = extStr.trim();
|
||||
if (!ext.isEmpty()) {
|
||||
anyExt = ext.equals("*");
|
||||
if (anyExt) {
|
||||
break;
|
||||
}
|
||||
extSet.add(ext);
|
||||
set.add(ext);
|
||||
}
|
||||
}
|
||||
try (ZipFile zipFile = getZipFile(cache.getJRoot())) {
|
||||
traverseTree(cache.getJRoot(), zipFile); // reindex
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to apply settings to resource index", e);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void traverseTree(TreeNode root, @Nullable ZipFile zip) {
|
||||
for (int i = 0; i < root.getChildCount(); i++) {
|
||||
TreeNode node = root.getChildAt(i);
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
try {
|
||||
resNode.loadNode();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load resource node: {}", resNode, e);
|
||||
return;
|
||||
}
|
||||
ResourceFile resFile = resNode.getResFile();
|
||||
if (resFile == null) {
|
||||
traverseTree(node, zip);
|
||||
} else {
|
||||
if (resFile.getType() == ResourceType.ARSC && shouldSearchXML()) {
|
||||
resFile.loadContent();
|
||||
resNode.getFiles().forEach(t -> traverseTree(t, null));
|
||||
} else {
|
||||
filter(resNode, zip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSearchXML() {
|
||||
return anyExt || fileExts.contains(".xml");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ZipFile getZipFile(TreeNode res) {
|
||||
for (int i = 0; i < res.getChildCount(); i++) {
|
||||
TreeNode node = res.getChildAt(i);
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
try {
|
||||
resNode.loadNode();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load resource node: {}", resNode, e);
|
||||
return null;
|
||||
}
|
||||
ResourceFile file = resNode.getResFile();
|
||||
if (file == null) {
|
||||
ZipFile zip = getZipFile(resNode);
|
||||
if (zip != null) {
|
||||
return zip;
|
||||
}
|
||||
} else {
|
||||
ResourceFile.ZipRef zipRef = file.getZipRef();
|
||||
if (zipRef != null) {
|
||||
File zfile = zipRef.getZipFile();
|
||||
if (FileUtils.isZipFile(zfile)) {
|
||||
try {
|
||||
return new ZipFile(zfile);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void filter(JResource resNode, ZipFile zip) {
|
||||
private boolean shouldProcess(JResource resNode) {
|
||||
ResourceFile resFile = resNode.getResFile();
|
||||
if (JResource.isSupportedForView(resFile.getType())) {
|
||||
long size = -1;
|
||||
if (zip != null) {
|
||||
ZipEntry entry = zip.getEntry(resFile.getOriginalName());
|
||||
if (entry != null) {
|
||||
size = entry.getSize();
|
||||
}
|
||||
if (resFile.getType() == ResourceType.ARSC) {
|
||||
// don't check size of generated resource table, it will also skip all sub files
|
||||
return anyExt || extSet.contains("xml");
|
||||
}
|
||||
if (!anyExt) {
|
||||
String fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName());
|
||||
if (fileExt == null) {
|
||||
return false;
|
||||
}
|
||||
if (size == -1) { // resource from ARSC is unknown size
|
||||
try {
|
||||
size = resNode.getCodeInfo().getCodeStr().length();
|
||||
} catch (Exception ignore) {
|
||||
return;
|
||||
}
|
||||
if (!extSet.contains(fileExt)) {
|
||||
return false;
|
||||
}
|
||||
if (size <= sizeLimit) {
|
||||
if (!anyExt) {
|
||||
for (String ext : extSet) {
|
||||
if (resFile.getOriginalName().endsWith(ext)) {
|
||||
resNodes.add(resNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resNodes.add(resNode);
|
||||
}
|
||||
} else {
|
||||
LOG.debug("Resource index skipped because of size limit: {} res size {} bytes", resNode, size);
|
||||
}
|
||||
if (sizeLimit == 0) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
int charsCount = resNode.getCodeInfo().getCodeStr().length();
|
||||
long size = charsCount * 8L;
|
||||
if (size > sizeLimit) {
|
||||
LOG.debug("Resource search skipped because of size limit: {} res size {} bytes", resNode, size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource load error: {}", resNode, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int progress() {
|
||||
return progress;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int total() {
|
||||
return resNodes == null ? 0 : resNodes.size();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ public class JadxProject {
|
||||
private static final int CURRENT_PROJECT_VERSION = 1;
|
||||
public static final String PROJECT_EXTENSION = "jadx";
|
||||
|
||||
private static final int SEARCH_HISTORY_LIMIT = 30;
|
||||
|
||||
private final transient MainWindow mainWindow;
|
||||
|
||||
private transient String name = "New Project";
|
||||
@@ -135,6 +137,9 @@ public class JadxProject {
|
||||
.map(TabStateViewAdapter::build)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
if (tabStateList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
boolean dataChanged;
|
||||
dataChanged = data.setOpenTabs(tabStateList);
|
||||
dataChanged |= data.setActiveTab(activeTab);
|
||||
@@ -192,6 +197,27 @@ public class JadxProject {
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getSearchHistory() {
|
||||
return data.getSearchHistory();
|
||||
}
|
||||
|
||||
public void addToSearchHistory(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> list = data.getSearchHistory();
|
||||
if (!list.isEmpty() && list.get(0).equals(str)) {
|
||||
return;
|
||||
}
|
||||
list.remove(str);
|
||||
list.add(0, str);
|
||||
if (list.size() > SEARCH_HISTORY_LIMIT) {
|
||||
list.remove(list.size() - 1);
|
||||
}
|
||||
data.setSearchHistory(list);
|
||||
changed();
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
JadxSettings settings = mainWindow.getSettings();
|
||||
if (settings != null && settings.isAutoSaveProject()) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import jadx.api.CommentsLevel;
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.cli.JadxCLIArgs;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -78,6 +79,7 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private boolean codeAreaLineWrap = false;
|
||||
private int srhResourceSkipSize = 1000;
|
||||
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
|
||||
private boolean useAutoSearch = true;
|
||||
private boolean keepCommonDialogOpen = false;
|
||||
private boolean smaliAreaShowBytecode = false;
|
||||
private LineNumbersMode lineNumbersMode = LineNumbersMode.AUTO;
|
||||
@@ -209,6 +211,11 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
partialSync(settings -> settings.recentProjects = recentProjects);
|
||||
}
|
||||
|
||||
public void removeRecentProject(Path projectPath) {
|
||||
recentProjects.remove(projectPath);
|
||||
partialSync(settings -> settings.recentProjects = recentProjects);
|
||||
}
|
||||
|
||||
public void saveWindowPos(Window window) {
|
||||
WindowLocation pos = new WindowLocation(window.getClass().getSimpleName(), window.getBounds());
|
||||
windowPos.put(pos.getWindowId(), pos);
|
||||
@@ -348,6 +355,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
|
||||
}
|
||||
|
||||
public void setResourceNameSource(ResourceNameSource source) {
|
||||
this.resourceNameSource = source;
|
||||
}
|
||||
|
||||
public void updateRenameFlag(JadxArgs.RenameEnum flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
@@ -380,6 +391,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.inlineMethods = inlineMethods;
|
||||
}
|
||||
|
||||
public void setExtractFinally(boolean extractFinally) {
|
||||
this.extractFinally = extractFinally;
|
||||
}
|
||||
|
||||
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||
this.fsCaseSensitive = fsCaseSensitive;
|
||||
}
|
||||
@@ -526,6 +541,15 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
srhResourceFileExt = all.trim();
|
||||
}
|
||||
|
||||
public boolean isUseAutoSearch() {
|
||||
return useAutoSearch;
|
||||
}
|
||||
|
||||
public void setUseAutoSearch(boolean useAutoSearch) {
|
||||
this.useAutoSearch = useAutoSearch;
|
||||
partialSync(settings -> settings.useAutoSearch = useAutoSearch);
|
||||
}
|
||||
|
||||
public void setKeepCommonDialogOpen(boolean yes) {
|
||||
keepCommonDialogOpen = yes;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import jadx.api.DecompilationMode;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
@@ -271,6 +272,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<ResourceNameSource> resNamesSource = new JComboBox<>(ResourceNameSource.values());
|
||||
resNamesSource.setSelectedItem(settings.getResourceNameSource());
|
||||
resNamesSource.addActionListener(e -> {
|
||||
settings.setResourceNameSource((ResourceNameSource) resNamesSource.getSelectedItem());
|
||||
needReload();
|
||||
});
|
||||
|
||||
JComboBox<DeobfuscationMapFileMode> deobfMapFileModeCB = new JComboBox<>(DeobfuscationMapFileMode.values());
|
||||
deobfMapFileModeCB.setSelectedItem(settings.getDeobfuscationMapFileMode());
|
||||
deobfMapFileModeCB.addActionListener(e -> {
|
||||
@@ -287,6 +295,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_source_alias"), deobfSourceAlias);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_kotlin_metadata"), deobfKotlinMetadata);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_name_source"), resNamesSource);
|
||||
deobfGroup.addRow(NLS.str("preferences.deobfuscation_map_file_mode"), deobfMapFileModeCB);
|
||||
deobfGroup.end();
|
||||
|
||||
@@ -533,6 +542,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox extractFinally = new JCheckBox();
|
||||
extractFinally.setSelected(settings.isExtractFinally());
|
||||
extractFinally.addItemListener(e -> {
|
||||
settings.setExtractFinally(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox fsCaseSensitive = new JCheckBox();
|
||||
fsCaseSensitive.setSelected(settings.isFsCaseSensitive());
|
||||
fsCaseSensitive.addItemListener(e -> {
|
||||
@@ -569,6 +585,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
other.addRow(NLS.str("preferences.useDebugInfo"), useDebugInfo);
|
||||
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
|
||||
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
|
||||
other.addRow(NLS.str("preferences.extractFinally"), extractFinally);
|
||||
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
|
||||
other.addRow(NLS.str("preferences.useDx"), useDx);
|
||||
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
|
||||
|
||||
@@ -20,6 +20,7 @@ public class ProjectData {
|
||||
private int activeTab = -1;
|
||||
private @Nullable Path cacheDir;
|
||||
private boolean enableLiveReload = false;
|
||||
private List<String> searchHistory = new ArrayList<>();
|
||||
|
||||
public List<Path> getFiles() {
|
||||
return files;
|
||||
@@ -103,4 +104,12 @@ public class ProjectData {
|
||||
public void setEnableLiveReload(boolean enableLiveReload) {
|
||||
this.enableLiveReload = enableLiveReload;
|
||||
}
|
||||
|
||||
public List<String> getSearchHistory() {
|
||||
return searchHistory;
|
||||
}
|
||||
|
||||
public void setSearchHistory(List<String> searchHistory) {
|
||||
this.searchHistory = searchHistory;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.OverlayIcon;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -84,10 +85,10 @@ public class JMethod extends JNode {
|
||||
|
||||
OverlayIcon overIcon = new OverlayIcon(icon);
|
||||
if (accessFlags.isFinal()) {
|
||||
overIcon.add(UiUtils.ICON_FINAL);
|
||||
overIcon.add(Icons.FINAL);
|
||||
}
|
||||
if (accessFlags.isStatic()) {
|
||||
overIcon.add(UiUtils.ICON_STATIC);
|
||||
overIcon.add(Icons.STATIC);
|
||||
}
|
||||
|
||||
return overIcon;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -14,10 +14,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceFileContent;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
@@ -26,6 +26,7 @@ import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.panel.ImagePanel;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.res.ResTableHelper;
|
||||
|
||||
public class JResource extends JLoadableNode {
|
||||
private static final long serialVersionUID = -201018424302612434L;
|
||||
@@ -41,6 +42,10 @@ public class JResource extends JLoadableNode {
|
||||
private static final ImageIcon JAVA_ICON = UiUtils.openSvgIcon("nodes/java");
|
||||
private static final ImageIcon UNKNOWN_ICON = UiUtils.openSvgIcon("nodes/unknown");
|
||||
|
||||
public static final Comparator<JResource> RESOURCES_COMPARATOR =
|
||||
Comparator.<JResource>comparingInt(r -> r.type.ordinal())
|
||||
.thenComparing(JResource::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
public enum JResType {
|
||||
ROOT,
|
||||
DIR,
|
||||
@@ -49,11 +54,11 @@ public class JResource extends JLoadableNode {
|
||||
|
||||
private final transient String name;
|
||||
private final transient String shortName;
|
||||
private final transient List<JResource> files = new ArrayList<>(1);
|
||||
private final transient JResType type;
|
||||
private final transient ResourceFile resFile;
|
||||
|
||||
private transient boolean loaded;
|
||||
private transient volatile boolean loaded;
|
||||
private transient List<JResource> subNodes = Collections.emptyList();
|
||||
private transient ICodeInfo content;
|
||||
|
||||
public JResource(ResourceFile resFile, String name, JResType type) {
|
||||
@@ -69,7 +74,8 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
|
||||
public final void update() {
|
||||
if (files.isEmpty()) {
|
||||
removeAllChildren();
|
||||
if (Utils.isEmpty(subNodes)) {
|
||||
if (type == JResType.DIR || type == JResType.ROOT
|
||||
|| resFile.getType() == ResourceType.ARSC) {
|
||||
// fake leaf to force show expand button
|
||||
@@ -77,14 +83,7 @@ public class JResource extends JLoadableNode {
|
||||
add(new TextNode(NLS.str("tree.loading")));
|
||||
}
|
||||
} else {
|
||||
removeAllChildren();
|
||||
|
||||
Comparator<JResource> typeComparator = Comparator.comparingInt(r -> r.type.ordinal());
|
||||
Comparator<JResource> nameComparator = Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
files.sort(typeComparator.thenComparing(nameComparator));
|
||||
|
||||
for (JResource res : files) {
|
||||
for (JResource res : subNodes) {
|
||||
res.update();
|
||||
add(res);
|
||||
}
|
||||
@@ -92,7 +91,7 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNode() {
|
||||
public synchronized void loadNode() {
|
||||
getCodeInfo();
|
||||
update();
|
||||
}
|
||||
@@ -102,8 +101,27 @@ public class JResource extends JLoadableNode {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<JResource> getFiles() {
|
||||
return files;
|
||||
public JResType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<JResource> getSubNodes() {
|
||||
return subNodes;
|
||||
}
|
||||
|
||||
public void addSubNode(JResource node) {
|
||||
subNodes = ListUtils.safeAdd(subNodes, node);
|
||||
}
|
||||
|
||||
public void sortSubNodes() {
|
||||
sortResNodes(subNodes);
|
||||
}
|
||||
|
||||
private static void sortResNodes(List<JResource> nodes) {
|
||||
if (Utils.notEmpty(nodes)) {
|
||||
nodes.forEach(JResource::sortSubNodes);
|
||||
nodes.sort(RESOURCES_COMPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,9 +159,9 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
|
||||
ICodeInfo codeInfo = loadCurrentSingleRes(rc);
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
loadSubNodes(this, subFile, 1);
|
||||
}
|
||||
List<JResource> nodes = ResTableHelper.buildTree(rc);
|
||||
sortResNodes(nodes);
|
||||
subNodes = nodes;
|
||||
return codeInfo;
|
||||
}
|
||||
// single node
|
||||
@@ -174,47 +192,6 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSubNodes(JResource root, ResContainer rc, int depth) {
|
||||
String resName = rc.getName();
|
||||
String[] path = resName.split("/");
|
||||
String resShortName = path.length == 0 ? resName : path[path.length - 1];
|
||||
ICodeInfo code = rc.getText();
|
||||
ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, code);
|
||||
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
|
||||
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
loadSubNodes(root, subFile, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addPath(String[] path, JResource root, JResource jResource) {
|
||||
if (path.length == 1) {
|
||||
root.getFiles().add(jResource);
|
||||
return;
|
||||
}
|
||||
JResource currentRoot = root;
|
||||
int last = path.length - 1;
|
||||
for (int i = 0; i <= last; i++) {
|
||||
String f = path[i];
|
||||
if (i == last) {
|
||||
currentRoot.getFiles().add(jResource);
|
||||
} else {
|
||||
currentRoot = getResDir(currentRoot, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static JResource getResDir(JResource root, String dirName) {
|
||||
for (JResource file : root.getFiles()) {
|
||||
if (file.getName().equals(dirName)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
JResource resDir = new JResource(null, dirName, JResType.DIR);
|
||||
root.getFiles().add(resDir);
|
||||
return resDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
if (resFile == null) {
|
||||
|
||||
@@ -66,19 +66,20 @@ public class JRoot extends JNode {
|
||||
if (i != count - 1) {
|
||||
subRF = new JResource(null, name, JResType.DIR);
|
||||
} else {
|
||||
subRF = new JResource(rf, rf.getOriginalName(), name, JResType.FILE);
|
||||
subRF = new JResource(rf, rf.getDeobfName(), name, JResType.FILE);
|
||||
}
|
||||
curRf.getFiles().add(subRF);
|
||||
curRf.addSubNode(subRF);
|
||||
}
|
||||
curRf = subRF;
|
||||
}
|
||||
}
|
||||
root.sortSubNodes();
|
||||
root.update();
|
||||
return root;
|
||||
}
|
||||
|
||||
private JResource getResourceByName(JResource rf, String name) {
|
||||
for (JResource sub : rf.getFiles()) {
|
||||
for (JResource sub : rf.getSubNodes()) {
|
||||
if (sub.getName().equals(name)) {
|
||||
return sub;
|
||||
}
|
||||
|
||||
@@ -125,12 +125,15 @@ import jadx.gui.ui.panel.IssuesPanel;
|
||||
import jadx.gui.ui.panel.JDebuggerPanel;
|
||||
import jadx.gui.ui.panel.ProgressPanel;
|
||||
import jadx.gui.ui.popupmenu.JPackagePopupMenu;
|
||||
import jadx.gui.ui.treenodes.StartPageNode;
|
||||
import jadx.gui.ui.treenodes.SummaryNode;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.JadxUpdate.IUpdateCallback;
|
||||
import jadx.gui.update.data.Release;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.ILoadListener;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
@@ -152,7 +155,6 @@ public class MainWindow extends JFrame {
|
||||
private static final double WINDOW_RATIO = 1 - BORDER_RATIO * 2;
|
||||
public static final double SPLIT_PANE_RESIZE_WEIGHT = 0.15;
|
||||
|
||||
private static final ImageIcon ICON_OPEN = UiUtils.openSvgIcon("ui/openDisk");
|
||||
private static final ImageIcon ICON_ADD_FILES = UiUtils.openSvgIcon("ui/addFile");
|
||||
private static final ImageIcon ICON_SAVE_ALL = UiUtils.openSvgIcon("ui/menu-saveall");
|
||||
private static final ImageIcon ICON_RELOAD = UiUtils.openSvgIcon("ui/refresh");
|
||||
@@ -210,6 +212,9 @@ public class MainWindow extends JFrame {
|
||||
private JDebuggerPanel debuggerPanel;
|
||||
private JSplitPane verticalSplitter;
|
||||
|
||||
private List<ILoadListener> loadListeners = new ArrayList<>();
|
||||
private boolean loaded;
|
||||
|
||||
public MainWindow(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
this.cacheObject = new CacheObject();
|
||||
@@ -249,7 +254,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private void processCommandLineArgs() {
|
||||
if (settings.getFiles().isEmpty()) {
|
||||
openFileOrProject();
|
||||
tabbedPane.showNode(new StartPageNode());
|
||||
} else {
|
||||
open(FileUtils.fileNamesToPaths(settings.getFiles()), this::handleSelectClassOption);
|
||||
}
|
||||
@@ -286,12 +291,20 @@ public class MainWindow extends JFrame {
|
||||
});
|
||||
}
|
||||
|
||||
public void openFileOrProject() {
|
||||
public void openFileDialog() {
|
||||
showOpenDialog(FileDialog.OpenMode.OPEN);
|
||||
}
|
||||
|
||||
public void openProjectDialog() {
|
||||
showOpenDialog(FileDialog.OpenMode.OPEN_PROJECT);
|
||||
}
|
||||
|
||||
private void showOpenDialog(FileDialog.OpenMode mode) {
|
||||
saveAll();
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.OPEN);
|
||||
FileDialog fileDialog = new FileDialog(this, mode);
|
||||
List<Path> openPaths = fileDialog.show();
|
||||
if (!openPaths.isEmpty()) {
|
||||
settings.setLastOpenFilePath(fileDialog.getCurrentDir());
|
||||
@@ -313,6 +326,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void newProject() {
|
||||
saveAll();
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
@@ -386,7 +400,11 @@ public class MainWindow extends JFrame {
|
||||
s -> update());
|
||||
}
|
||||
|
||||
void open(List<Path> paths) {
|
||||
public void open(Path path) {
|
||||
open(Collections.singletonList(path), EMPTY_RUNNABLE);
|
||||
}
|
||||
|
||||
public void open(List<Path> paths) {
|
||||
open(paths, EMPTY_RUNNABLE);
|
||||
}
|
||||
|
||||
@@ -411,7 +429,7 @@ public class MainWindow extends JFrame {
|
||||
// check if project file already saved with default name
|
||||
Path projectPath = getProjectPathForFile(singleFile);
|
||||
if (Files.exists(projectPath)) {
|
||||
LOG.info("Loading project for this file");
|
||||
LOG.info("Loading project {}", projectPath);
|
||||
openProject(projectPath, onFinish);
|
||||
return true;
|
||||
}
|
||||
@@ -474,6 +492,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
notifyLoadListeners(false);
|
||||
cancelBackgroundJobs();
|
||||
clearTree();
|
||||
resetCache();
|
||||
@@ -512,7 +531,10 @@ public class MainWindow extends JFrame {
|
||||
|
||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||
this::restoreOpenTabs,
|
||||
status -> runInitialBackgroundJobs());
|
||||
status -> {
|
||||
runInitialBackgroundJobs();
|
||||
notifyLoadListeners(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void updateLiveReload(boolean state) {
|
||||
@@ -574,8 +596,6 @@ public class MainWindow extends JFrame {
|
||||
|
||||
protected void resetCache() {
|
||||
cacheObject.reset();
|
||||
cacheObject.setJRoot(treeRoot);
|
||||
cacheObject.setJadxSettings(settings);
|
||||
}
|
||||
|
||||
synchronized void runInitialBackgroundJobs() {
|
||||
@@ -658,13 +678,11 @@ public class MainWindow extends JFrame {
|
||||
|
||||
public void initTree() {
|
||||
treeRoot = new JRoot(wrapper);
|
||||
cacheObject.setJRoot(treeRoot);
|
||||
treeRoot.setFlatPackages(isFlattenPackage);
|
||||
treeModel.setRoot(treeRoot);
|
||||
addTreeCustomNodes();
|
||||
treeRoot.update();
|
||||
reloadTree();
|
||||
cacheObject.setJadxSettings(settings);
|
||||
}
|
||||
|
||||
private void clearTree() {
|
||||
@@ -825,14 +843,15 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void initMenuAndToolbar() {
|
||||
Action openAction = new AbstractAction(NLS.str("file.open_action"), ICON_OPEN) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
openFileOrProject();
|
||||
}
|
||||
};
|
||||
openAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.open_action"));
|
||||
openAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_O, UiUtils.ctrlButton()));
|
||||
ActionHandler openAction = new ActionHandler(this::openFileDialog);
|
||||
openAction.setNameAndDesc(NLS.str("file.open_action"));
|
||||
openAction.setIcon(Icons.OPEN);
|
||||
openAction.setKeyBinding(getKeyStroke(KeyEvent.VK_O, UiUtils.ctrlButton()));
|
||||
|
||||
ActionHandler openProject = new ActionHandler(this::openProjectDialog);
|
||||
openProject.setNameAndDesc(NLS.str("file.open_project"));
|
||||
openProject.setIcon(Icons.OPEN_PROJECT);
|
||||
openProject.setKeyBinding(getKeyStroke(KeyEvent.VK_O, InputEvent.SHIFT_DOWN_MASK | UiUtils.ctrlButton()));
|
||||
|
||||
Action addFilesAction = new AbstractAction(NLS.str("file.add_files_action"), ICON_ADD_FILES) {
|
||||
@Override
|
||||
@@ -842,7 +861,7 @@ public class MainWindow extends JFrame {
|
||||
};
|
||||
addFilesAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.add_files_action"));
|
||||
|
||||
newProjectAction = new AbstractAction(NLS.str("file.new_project")) {
|
||||
newProjectAction = new AbstractAction(NLS.str("file.new_project"), Icons.NEW_PROJECT) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
newProject();
|
||||
@@ -1089,6 +1108,7 @@ public class MainWindow extends JFrame {
|
||||
JMenu file = new JMenu(NLS.str("menu.file"));
|
||||
file.setMnemonic(KeyEvent.VK_F);
|
||||
file.add(openAction);
|
||||
file.add(openProject);
|
||||
file.add(addFilesAction);
|
||||
file.addSeparator();
|
||||
file.add(newProjectAction);
|
||||
@@ -1194,6 +1214,22 @@ public class MainWindow extends JFrame {
|
||||
toolbar.add(updateLink);
|
||||
|
||||
mainPanel.add(toolbar, BorderLayout.NORTH);
|
||||
|
||||
addLoadListener(loaded -> {
|
||||
textSearchAction.setEnabled(loaded);
|
||||
clsSearchAction.setEnabled(loaded);
|
||||
commentSearchAction.setEnabled(loaded);
|
||||
backAction.setEnabled(loaded);
|
||||
forwardAction.setEnabled(loaded);
|
||||
syncAction.setEnabled(loaded);
|
||||
saveAllAction.setEnabled(loaded);
|
||||
exportAction.setEnabled(loaded);
|
||||
saveProjectAsAction.setEnabled(loaded);
|
||||
reload.setEnabled(loaded);
|
||||
deobfAction.setEnabled(loaded);
|
||||
quarkAction.setEnabled(loaded);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
@@ -1483,6 +1519,17 @@ public class MainWindow extends JFrame {
|
||||
settings.setDebuggerVarTreeSplitterLoc(debuggerPanel.getRightSplitterLocation());
|
||||
}
|
||||
|
||||
public void addLoadListener(ILoadListener loadListener) {
|
||||
this.loadListeners.add(loadListener);
|
||||
// set initial value
|
||||
loadListener.update(loaded);
|
||||
}
|
||||
|
||||
public void notifyLoadListeners(boolean loaded) {
|
||||
this.loaded = loaded;
|
||||
loadListeners.removeIf(listener -> listener.update(loaded));
|
||||
}
|
||||
|
||||
public JadxWrapper getWrapper() {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
@@ -21,15 +20,13 @@ import javax.swing.plaf.basic.BasicButtonUI;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class TabComponent extends JPanel {
|
||||
private static final long serialVersionUID = -8147035487543610321L;
|
||||
|
||||
private static final ImageIcon ICON_CLOSE = UiUtils.openSvgIcon("ui/closeHovered");
|
||||
private static final ImageIcon ICON_CLOSE_INACTIVE = UiUtils.openSvgIcon("ui/close");
|
||||
|
||||
private final TabbedPane tabbedPane;
|
||||
private final ContentPanel contentPanel;
|
||||
|
||||
@@ -71,8 +68,8 @@ public class TabComponent extends JPanel {
|
||||
label.setIcon(node.getIcon());
|
||||
|
||||
final JButton closeBtn = new JButton();
|
||||
closeBtn.setIcon(ICON_CLOSE_INACTIVE);
|
||||
closeBtn.setRolloverIcon(ICON_CLOSE);
|
||||
closeBtn.setIcon(Icons.CLOSE_INACTIVE);
|
||||
closeBtn.setRolloverIcon(Icons.CLOSE);
|
||||
closeBtn.setRolloverEnabled(true);
|
||||
closeBtn.setOpaque(false);
|
||||
closeBtn.setUI(new BasicButtonUI());
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.gui.ui.codearea;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.UsageDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
@@ -18,7 +19,15 @@ public final class FindUsageAction extends JNodeAction {
|
||||
|
||||
@Override
|
||||
public void runAction(JNode node) {
|
||||
UsageDialog usageDialog = new UsageDialog(getCodeArea().getMainWindow(), node);
|
||||
MainWindow mw = getCodeArea().getMainWindow();
|
||||
UsageDialog usageDialog = new UsageDialog(mw, node);
|
||||
mw.addLoadListener(loaded -> {
|
||||
if (!loaded) {
|
||||
usageDialog.dispose();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
usageDialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,11 @@ public final class FridaAction extends JNodeAction {
|
||||
JavaMethod javaMethod = jMth.getJavaMethod();
|
||||
MethodInfo methodInfo = javaMethod.getMethodNode().getMethodInfo();
|
||||
String methodName = StringEscapeUtils.escapeEcmaScript(methodInfo.getName());
|
||||
String callMethodName = methodName;
|
||||
|
||||
if (methodInfo.isConstructor()) {
|
||||
methodName = "$init";
|
||||
callMethodName = "$new";
|
||||
}
|
||||
String shortClassName = javaMethod.getDeclaringClass().getName();
|
||||
|
||||
@@ -108,7 +111,7 @@ public final class FridaAction extends JNodeAction {
|
||||
+ " console.log('%s ret value is ' + ret);\n"
|
||||
+ " return ret;\n"
|
||||
+ "};",
|
||||
functionUntilImplementation, functionParametersString, methodName, logParametersString, methodName,
|
||||
functionUntilImplementation, functionParametersString, methodName, logParametersString, callMethodName,
|
||||
functionParametersString, methodName);
|
||||
|
||||
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
|
||||
|
||||
@@ -359,7 +359,7 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
try {
|
||||
return mainWindow.getDebuggerPanel().showDebugger(
|
||||
debugSetter.name,
|
||||
debugSetter.device.getDeviceInfo().adbHost,
|
||||
debugSetter.device.getDeviceInfo().getAdbHost(),
|
||||
debugSetter.forwardTcpPort,
|
||||
debugSetter.ver);
|
||||
} catch (Exception e) {
|
||||
@@ -569,10 +569,10 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
|
||||
void refresh() {
|
||||
ADBDeviceInfo info = device.getDeviceInfo();
|
||||
String text = info.model;
|
||||
String text = info.getModel();
|
||||
if (text != null) {
|
||||
if (!text.equals(info.serial)) {
|
||||
text += String.format(" [serial: %s]", info.serial);
|
||||
if (!text.equals(info.getSerial())) {
|
||||
text += String.format(" [serial: %s]", info.getSerial());
|
||||
}
|
||||
text += String.format(" [state: %s]", info.isOnline() ? "online" : "offline");
|
||||
tNode.setUserObject(text);
|
||||
@@ -668,8 +668,8 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
String jdwpPid = " jdwp:" + pid;
|
||||
String tcpPort = " tcp:" + forwardTcpPort;
|
||||
try {
|
||||
List<String> list = ADB.listForward(device.getDeviceInfo().adbHost,
|
||||
device.getDeviceInfo().adbPort);
|
||||
List<String> list = ADB.listForward(device.getDeviceInfo().getAdbHost(),
|
||||
device.getDeviceInfo().getAdbPort());
|
||||
for (String s : list) {
|
||||
if (s.startsWith(device.getSerial()) && s.endsWith(jdwpPid) && !s.contains(tcpPort)) {
|
||||
String[] fields = s.split("\\s+");
|
||||
@@ -693,8 +693,8 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
String jdwpPid = " jdwp:" + pid;
|
||||
String tcpPort = " tcp:" + forwardTcpPort;
|
||||
try {
|
||||
List<String> list = ADB.listForward(device.getDeviceInfo().adbHost,
|
||||
device.getDeviceInfo().adbPort);
|
||||
List<String> list = ADB.listForward(device.getDeviceInfo().getAdbHost(),
|
||||
device.getDeviceInfo().getAdbPort());
|
||||
for (String s : list) {
|
||||
if (s.startsWith(device.getSerial()) && s.endsWith(jdwpPid)) {
|
||||
return !s.contains(tcpPort);
|
||||
|
||||
@@ -16,9 +16,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -77,9 +75,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
protected JLabel warnLabel;
|
||||
protected ProgressPanel progressPane;
|
||||
|
||||
private String highlightText;
|
||||
protected boolean highlightTextCaseInsensitive = false;
|
||||
protected boolean highlightTextUseRegex = false;
|
||||
private SearchContext highlightContext;
|
||||
|
||||
public CommonSearchDialog(MainWindow mainWindow, String title) {
|
||||
this.mainWindow = mainWindow;
|
||||
@@ -88,7 +84,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
this.codeFont = mainWindow.getSettings().getFont();
|
||||
this.windowTitle = title;
|
||||
UiUtils.setWindowIcons(this);
|
||||
updateTitle();
|
||||
updateTitle("");
|
||||
}
|
||||
|
||||
protected abstract void openInit();
|
||||
@@ -103,17 +99,24 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
if (highlightText == null || highlightText.trim().isEmpty()) {
|
||||
private void updateTitle(String searchText) {
|
||||
if (searchText == null || searchText.isEmpty() || searchText.trim().isEmpty()) {
|
||||
setTitle(windowTitle);
|
||||
} else {
|
||||
setTitle(windowTitle + ": " + highlightText);
|
||||
setTitle(windowTitle + ": " + searchText);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHighlightText(String highlightText) {
|
||||
this.highlightText = highlightText;
|
||||
updateTitle();
|
||||
public void updateHighlightContext(String text, boolean caseSensitive, boolean regexp) {
|
||||
updateTitle(text);
|
||||
highlightContext = new SearchContext(text);
|
||||
highlightContext.setMatchCase(caseSensitive);
|
||||
highlightContext.setRegularExpression(regexp);
|
||||
highlightContext.setMarkAll(true);
|
||||
}
|
||||
|
||||
public void disableHighlight() {
|
||||
highlightContext = null;
|
||||
}
|
||||
|
||||
protected void registerInitOnOpen() {
|
||||
@@ -147,11 +150,16 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
@Nullable
|
||||
private JNode getSelectedNode() {
|
||||
int selectedId = resultsTable.getSelectedRow();
|
||||
if (selectedId == -1) {
|
||||
try {
|
||||
int selectedId = resultsTable.getSelectedRow();
|
||||
if (selectedId == -1 || selectedId >= resultsTable.getRowCount()) {
|
||||
return null;
|
||||
}
|
||||
return (JNode) resultsModel.getValueAt(selectedId, 0);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get results table selected object", e);
|
||||
return null;
|
||||
}
|
||||
return (JNode) resultsModel.getValueAt(selectedId, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,16 +182,16 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
openBtn.addActionListener(event -> openSelectedItem());
|
||||
getRootPane().setDefaultButton(openBtn);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
|
||||
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> {
|
||||
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
|
||||
mainWindow.getSettings().sync();
|
||||
});
|
||||
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.add(cbKeepOpen);
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(15, 0)));
|
||||
buttonPane.add(progressPane);
|
||||
@@ -197,7 +205,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
protected JPanel initResultsTable() {
|
||||
ResultsTableCellRenderer renderer = new ResultsTableCellRenderer();
|
||||
resultsModel = new ResultsModel(renderer);
|
||||
resultsModel = new ResultsModel();
|
||||
resultsModel.addTableModelListener(e -> updateProgressLabel(false));
|
||||
|
||||
resultsTable = new ResultsTable(resultsModel, renderer);
|
||||
@@ -247,28 +255,27 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
warnLabel.setVisible(false);
|
||||
|
||||
JScrollPane scroll = new JScrollPane(resultsTable, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
// scroll.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
|
||||
|
||||
resultsInfoLabel = new JLabel("");
|
||||
resultsInfoLabel.setFont(mainWindow.getSettings().getFont());
|
||||
|
||||
JPanel resultsActionsPanel = new JPanel();
|
||||
resultsActionsPanel.setLayout(new BoxLayout(resultsActionsPanel, BoxLayout.LINE_AXIS));
|
||||
resultsActionsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
|
||||
addCustomResultsActions(resultsActionsPanel);
|
||||
resultsInfoLabel = new JLabel("");
|
||||
resultsInfoLabel.setFont(mainWindow.getSettings().getFont());
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
|
||||
resultsActionsPanel.add(resultsInfoLabel);
|
||||
resultsActionsPanel.add(Box.createHorizontalGlue());
|
||||
addResultsActions(resultsActionsPanel);
|
||||
|
||||
JPanel resultsPanel = new JPanel();
|
||||
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.PAGE_AXIS));
|
||||
resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
resultsPanel.add(warnLabel, BorderLayout.PAGE_START);
|
||||
resultsPanel.add(scroll, BorderLayout.CENTER);
|
||||
resultsPanel.add(resultsActionsPanel, BorderLayout.PAGE_END);
|
||||
return resultsPanel;
|
||||
}
|
||||
|
||||
protected void addCustomResultsActions(JPanel actionsPanel) {
|
||||
protected void addResultsActions(JPanel resultsActionsPanel) {
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
|
||||
resultsActionsPanel.add(resultsInfoLabel);
|
||||
resultsActionsPanel.add(Box.createHorizontalGlue());
|
||||
}
|
||||
|
||||
protected void updateProgressLabel(boolean complete) {
|
||||
@@ -288,13 +295,12 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
protected static final class ResultsTable extends JTable {
|
||||
private static final long serialVersionUID = 3901184054736618969L;
|
||||
private final transient ResultsTableCellRenderer renderer;
|
||||
private final transient ResultsModel model;
|
||||
|
||||
public ResultsTable(ResultsModel resultsModel, ResultsTableCellRenderer renderer) {
|
||||
super(resultsModel);
|
||||
this.model = resultsModel;
|
||||
this.renderer = renderer;
|
||||
setRowHeight(renderer.getMaxRowHeight());
|
||||
}
|
||||
|
||||
public void initColumnWidth() {
|
||||
@@ -309,6 +315,11 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
public void updateTable() {
|
||||
UiUtils.uiThreadGuard();
|
||||
int rowCount = getRowCount();
|
||||
if (rowCount == 0) {
|
||||
updateUI();
|
||||
return;
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
int width = getParent().getWidth();
|
||||
TableColumn firstColumn = columnModel.getColumn(0);
|
||||
@@ -325,30 +336,6 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
} else {
|
||||
firstColumn.setPreferredWidth(width);
|
||||
}
|
||||
int rowCount = getRowCount();
|
||||
int columnCount = getColumnCount();
|
||||
Map<Class<?>, Integer> heightByType = new HashMap<>();
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
Object value = model.getValueAt(row, 0);
|
||||
Class<?> valueType = value.getClass();
|
||||
Integer cachedHeight = heightByType.get(valueType);
|
||||
if (cachedHeight != null) {
|
||||
setRowHeight(row, cachedHeight);
|
||||
} else {
|
||||
int height = 0;
|
||||
for (int col = 0; col < columnCount; col++) {
|
||||
Component comp = prepareRenderer(renderer, row, col);
|
||||
if (comp == null) {
|
||||
continue;
|
||||
}
|
||||
Dimension preferredSize = comp.getPreferredSize();
|
||||
int h = Math.max(comp.getHeight(), preferredSize.height);
|
||||
height = Math.max(height, h);
|
||||
}
|
||||
heightByType.put(valueType, height);
|
||||
setRowHeight(row, height);
|
||||
}
|
||||
}
|
||||
updateUI();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update results table in {}ms, count: {}", System.currentTimeMillis() - start, rowCount);
|
||||
@@ -365,14 +352,9 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
private static final long serialVersionUID = -7821286846923903208L;
|
||||
private static final String[] COLUMN_NAMES = { NLS.str("search_dialog.col_node"), NLS.str("search_dialog.col_code") };
|
||||
|
||||
private final transient List<JNode> rows = Collections.synchronizedList(new ArrayList<>());
|
||||
private final transient ResultsTableCellRenderer renderer;
|
||||
private final transient List<JNode> rows = new ArrayList<>();
|
||||
private transient boolean addDescColumn;
|
||||
|
||||
public ResultsModel(ResultsTableCellRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public void addAll(Collection<? extends JNode> nodes) {
|
||||
rows.addAll(nodes);
|
||||
if (!addDescColumn) {
|
||||
@@ -388,7 +370,10 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
public void clear() {
|
||||
addDescColumn = false;
|
||||
rows.clear();
|
||||
renderer.clear();
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
Collections.sort(rows);
|
||||
}
|
||||
|
||||
public boolean isAddDescColumn() {
|
||||
@@ -417,38 +402,39 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
}
|
||||
|
||||
protected final class ResultsTableCellRenderer implements TableCellRenderer {
|
||||
private final JLabel emptyLabel = new JLabel();
|
||||
private final Font font;
|
||||
private final JLabel label;
|
||||
private final RSyntaxTextArea codeArea;
|
||||
private final JLabel emptyLabel;
|
||||
private final Color codeSelectedColor;
|
||||
private final Color codeBackground;
|
||||
private final Map<Integer, Component> componentCache = new HashMap<>();
|
||||
|
||||
public ResultsTableCellRenderer() {
|
||||
RSyntaxTextArea area = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
this.font = area.getFont();
|
||||
this.codeSelectedColor = area.getSelectionColor();
|
||||
this.codeBackground = area.getBackground();
|
||||
codeArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
codeArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
codeArea.setRows(1);
|
||||
codeBackground = codeArea.getBackground();
|
||||
codeSelectedColor = codeArea.getSelectionColor();
|
||||
label = new JLabel();
|
||||
label.setOpaque(true);
|
||||
label.setFont(codeArea.getFont());
|
||||
label.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
emptyLabel = new JLabel();
|
||||
emptyLabel.setOpaque(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object obj,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Component comp = componentCache.computeIfAbsent(makeID(row, column), id -> {
|
||||
if (obj instanceof JNode) {
|
||||
return makeCell((JNode) obj, column);
|
||||
}
|
||||
if (obj == null || table == null) {
|
||||
return emptyLabel;
|
||||
});
|
||||
updateSelection(table, comp, isSelected);
|
||||
}
|
||||
Component comp = makeCell((JNode) obj, column);
|
||||
updateSelection(table, comp, column, isSelected);
|
||||
return comp;
|
||||
}
|
||||
|
||||
private int makeID(int row, int col) {
|
||||
return row << 2 | (col & 0b11);
|
||||
}
|
||||
|
||||
private void updateSelection(JTable table, Component comp, boolean isSelected) {
|
||||
if (comp instanceof RSyntaxTextArea) {
|
||||
private void updateSelection(JTable table, Component comp, int column, boolean isSelected) {
|
||||
if (column == 1) {
|
||||
if (isSelected) {
|
||||
comp.setBackground(codeSelectedColor);
|
||||
} else {
|
||||
@@ -467,39 +453,32 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
private Component makeCell(JNode node, int column) {
|
||||
if (column == 0) {
|
||||
JLabel label = new JLabel(node.makeLongStringHtml(), node.getIcon(), SwingConstants.LEFT);
|
||||
label.setFont(font);
|
||||
label.setOpaque(true);
|
||||
label.setText(node.makeLongStringHtml());
|
||||
label.setToolTipText(label.getText());
|
||||
label.setIcon(node.getIcon());
|
||||
return label;
|
||||
}
|
||||
if (!node.hasDescString()) {
|
||||
return emptyLabel;
|
||||
}
|
||||
|
||||
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
textArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
codeArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
String descStr = node.makeDescString();
|
||||
textArea.setText(descStr);
|
||||
if (descStr.contains("\n")) {
|
||||
textArea.setRows(textArea.getLineCount());
|
||||
} else {
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(descStr.length() + 1);
|
||||
codeArea.setText(descStr);
|
||||
codeArea.setColumns(descStr.length() + 1);
|
||||
if (highlightContext != null) {
|
||||
SearchEngine.markAll(codeArea, highlightContext);
|
||||
}
|
||||
if (highlightText != null) {
|
||||
SearchContext searchContext = new SearchContext(highlightText);
|
||||
searchContext.setMatchCase(!highlightTextCaseInsensitive);
|
||||
searchContext.setRegularExpression(highlightTextUseRegex);
|
||||
searchContext.setMarkAll(true);
|
||||
SearchEngine.markAll(textArea, searchContext);
|
||||
}
|
||||
textArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
return textArea;
|
||||
return codeArea;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
componentCache.clear();
|
||||
public int getMaxRowHeight() {
|
||||
label.setText("Text");
|
||||
codeArea.setText("Text");
|
||||
return Math.max(getCompHeight(label), getCompHeight(codeArea));
|
||||
}
|
||||
|
||||
private int getCompHeight(Component comp) {
|
||||
return Math.max(comp.getHeight(), comp.getPreferredSize().height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class FileDialog {
|
||||
|
||||
public enum OpenMode {
|
||||
OPEN,
|
||||
OPEN_PROJECT,
|
||||
ADD,
|
||||
SAVE_PROJECT,
|
||||
EXPORT,
|
||||
@@ -95,13 +96,19 @@ public class FileDialog {
|
||||
private void initForMode(OpenMode mode) {
|
||||
switch (mode) {
|
||||
case OPEN:
|
||||
case OPEN_PROJECT:
|
||||
case ADD:
|
||||
fileExtList = new ArrayList<>(Arrays.asList("apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc"));
|
||||
if (mode == OpenMode.OPEN) {
|
||||
fileExtList.addAll(Arrays.asList(JadxProject.PROJECT_EXTENSION, "aab"));
|
||||
if (mode == OpenMode.OPEN_PROJECT) {
|
||||
fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION);
|
||||
title = NLS.str("file.open_title");
|
||||
} else {
|
||||
title = NLS.str("file.add_files_action");
|
||||
fileExtList = new ArrayList<>(Arrays.asList("apk", "dex", "jar", "class", "smali", "zip", "xapk", "aar", "arsc"));
|
||||
if (mode == OpenMode.OPEN) {
|
||||
fileExtList.addAll(Arrays.asList(JadxProject.PROJECT_EXTENSION, "aab"));
|
||||
title = NLS.str("file.open_title");
|
||||
} else {
|
||||
title = NLS.str("file.add_files_action");
|
||||
}
|
||||
}
|
||||
selectionMode = JFileChooser.FILES_AND_DIRECTORIES;
|
||||
currentDir = mainWindow.getSettings().getLastOpenFilePath();
|
||||
|
||||
@@ -2,11 +2,13 @@ package jadx.gui.ui.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
@@ -20,10 +22,11 @@ import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.logs.ILogListener;
|
||||
import jadx.gui.utils.logs.LogCollector;
|
||||
|
||||
public class LogViewerDialog extends JDialog {
|
||||
public class LogViewerDialog extends JFrame {
|
||||
private static final long serialVersionUID = -2188700277429054641L;
|
||||
private static final Level[] LEVEL_ITEMS = { Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR };
|
||||
|
||||
@@ -31,6 +34,9 @@ public class LogViewerDialog extends JDialog {
|
||||
|
||||
private final transient JadxSettings settings;
|
||||
private transient RSyntaxTextArea textPane;
|
||||
private JComboBox<Level> levelCb;
|
||||
|
||||
private static LogViewerDialog openLogDialog;
|
||||
|
||||
public static void open(MainWindow mainWindow) {
|
||||
openWithLevel(mainWindow, level);
|
||||
@@ -38,7 +44,16 @@ public class LogViewerDialog extends JDialog {
|
||||
|
||||
public static void openWithLevel(MainWindow mainWindow, Level newLevel) {
|
||||
level = newLevel;
|
||||
new LogViewerDialog(mainWindow).setVisible(true);
|
||||
if (openLogDialog == null) {
|
||||
LogViewerDialog newLogDialog = new LogViewerDialog(mainWindow);
|
||||
newLogDialog.setVisible(true);
|
||||
openLogDialog = newLogDialog;
|
||||
} else {
|
||||
LogViewerDialog logDialog = openLogDialog;
|
||||
logDialog.levelCb.setSelectedItem(level);
|
||||
logDialog.setVisible(true);
|
||||
logDialog.toFront();
|
||||
}
|
||||
}
|
||||
|
||||
private LogViewerDialog(MainWindow mainWindow) {
|
||||
@@ -46,25 +61,33 @@ public class LogViewerDialog extends JDialog {
|
||||
initUI(mainWindow);
|
||||
registerLogListener();
|
||||
settings.loadWindowPos(this);
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
openLogDialog = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public final void initUI(MainWindow mainWindow) {
|
||||
UiUtils.setWindowIcons(this);
|
||||
|
||||
textPane = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||
|
||||
JPanel controlPane = new JPanel();
|
||||
controlPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
final JComboBox<Level> cb = new JComboBox<>(LEVEL_ITEMS);
|
||||
cb.setSelectedItem(level);
|
||||
cb.addActionListener(e -> {
|
||||
int i = cb.getSelectedIndex();
|
||||
levelCb = new JComboBox<>(LEVEL_ITEMS);
|
||||
levelCb.setSelectedItem(level);
|
||||
levelCb.addActionListener(e -> {
|
||||
int i = levelCb.getSelectedIndex();
|
||||
level = LEVEL_ITEMS[i];
|
||||
registerLogListener();
|
||||
});
|
||||
JLabel levelLabel = new JLabel(NLS.str("log_viewer.log_level"));
|
||||
levelLabel.setLabelFor(cb);
|
||||
levelLabel.setLabelFor(levelCb);
|
||||
controlPane.add(levelLabel);
|
||||
controlPane.add(cb);
|
||||
controlPane.add(levelCb);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(textPane);
|
||||
|
||||
@@ -81,7 +104,6 @@ public class LogViewerDialog extends JDialog {
|
||||
pack();
|
||||
setSize(800, 600);
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setModalityType(ModalityType.MODELESS);
|
||||
setLocationRelativeTo(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,16 @@ package jadx.gui.ui.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -21,25 +20,29 @@ import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import hu.akarnokd.rxjava2.swing.SwingSchedulers;
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.icons.FlatSearchWithHistoryIcon;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Emitter;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.gui.jobs.ITaskInfo;
|
||||
import jadx.gui.jobs.ITaskProgress;
|
||||
import jadx.gui.search.SearchSettings;
|
||||
import jadx.gui.search.SearchTask;
|
||||
@@ -58,6 +61,7 @@ import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.rx.RxUtils;
|
||||
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.ACTIVE_TAB;
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.CLASS;
|
||||
@@ -77,17 +81,28 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
public static void search(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, Collections.emptySet());
|
||||
searchDialog.setVisible(true);
|
||||
show(searchDialog, window);
|
||||
}
|
||||
|
||||
public static void searchInActiveTab(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, EnumSet.of(SearchOptions.ACTIVE_TAB));
|
||||
searchDialog.setVisible(true);
|
||||
show(searchDialog, window);
|
||||
}
|
||||
|
||||
public static void searchText(MainWindow window, String text) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, SearchPreset.TEXT, Collections.emptySet());
|
||||
searchDialog.initSearchText = text;
|
||||
show(searchDialog, window);
|
||||
}
|
||||
|
||||
private static void show(SearchDialog searchDialog, MainWindow mw) {
|
||||
mw.addLoadListener(loaded -> {
|
||||
if (!loaded) {
|
||||
searchDialog.dispose();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
@@ -118,6 +133,8 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
private transient @Nullable SearchTask searchTask;
|
||||
private transient JButton loadAllButton;
|
||||
private transient JButton loadMoreButton;
|
||||
private transient JButton stopBtn;
|
||||
private transient JButton sortBtn;
|
||||
|
||||
private transient Disposable searchDisposable;
|
||||
private transient SearchEventEmitter searchEmitter;
|
||||
@@ -128,6 +145,11 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
// temporal list for pending results
|
||||
private final List<JNode> pendingResults = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Use single thread to do all background work, so additional synchronisation not needed
|
||||
*/
|
||||
private final Executor searchBackgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptions> additionalOptions) {
|
||||
super(mainWindow, NLS.str("menu.text_search"));
|
||||
this.searchPreset = preset;
|
||||
@@ -136,7 +158,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
loadWindowPos();
|
||||
initUI();
|
||||
searchFieldSubscribe();
|
||||
initSearchEvents();
|
||||
registerInitOnOpen();
|
||||
registerActiveTabListener();
|
||||
}
|
||||
@@ -148,13 +170,10 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
resultsModel.clear();
|
||||
removeActiveTabListener();
|
||||
if (searchTask != null) {
|
||||
searchTask.cancel();
|
||||
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"), () -> {
|
||||
stopSearchTask();
|
||||
unloadTempData();
|
||||
});
|
||||
}
|
||||
searchBackgroundExecutor.execute(() -> {
|
||||
stopSearchTask();
|
||||
unloadTempData();
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -167,7 +186,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
case TEXT:
|
||||
if (searchOptions.isEmpty()) {
|
||||
searchOptions.add(SearchOptions.CODE);
|
||||
searchOptions.add(SearchOptions.IGNORE_CASE);
|
||||
searchOptions.add(IGNORE_CASE);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -204,17 +223,44 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
searchFieldDefaultBgColor = searchField.getBackground();
|
||||
searchField.setAlignmentX(LEFT_ALIGNMENT);
|
||||
TextStandardActions.attach(searchField);
|
||||
addSearchHistoryButton();
|
||||
searchField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
|
||||
|
||||
boolean autoSearch = mainWindow.getSettings().isUseAutoSearch();
|
||||
JButton searchBtn = new JButton(NLS.str("search_dialog.search_button"));
|
||||
searchBtn.setVisible(!autoSearch);
|
||||
searchBtn.addActionListener(ev -> searchEmitter.emitSearch());
|
||||
|
||||
JCheckBox autoSearchCB = new JCheckBox(NLS.str("search_dialog.auto_search"));
|
||||
autoSearchCB.setSelected(autoSearch);
|
||||
autoSearchCB.addActionListener(ev -> {
|
||||
boolean newValue = autoSearchCB.isSelected();
|
||||
mainWindow.getSettings().setUseAutoSearch(newValue);
|
||||
searchBtn.setVisible(!newValue);
|
||||
initSearchEvents();
|
||||
if (newValue) {
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
});
|
||||
|
||||
JPanel searchLinePanel = new JPanel();
|
||||
searchLinePanel.setLayout(new BoxLayout(searchLinePanel, BoxLayout.LINE_AXIS));
|
||||
searchLinePanel.add(searchField);
|
||||
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
searchLinePanel.add(searchBtn);
|
||||
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
searchLinePanel.add(autoSearchCB);
|
||||
searchLinePanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
findLabel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JPanel searchFieldPanel = new JPanel();
|
||||
searchFieldPanel.setLayout(new BoxLayout(searchFieldPanel, BoxLayout.PAGE_AXIS));
|
||||
searchFieldPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
|
||||
searchFieldPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
searchFieldPanel.add(findLabel);
|
||||
searchFieldPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchFieldPanel.add(searchField);
|
||||
searchFieldPanel.add(searchLinePanel);
|
||||
|
||||
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
|
||||
@@ -227,18 +273,17 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.active_tab"), SearchOptions.ACTIVE_TAB));
|
||||
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT));
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT, 0, 0));
|
||||
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
optionsPanel.add(searchInPanel);
|
||||
optionsPanel.add(searchOptions);
|
||||
|
||||
JPanel searchPane = new JPanel();
|
||||
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
|
||||
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
searchPane.add(searchFieldPanel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(optionsPanel);
|
||||
@@ -247,38 +292,69 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPane.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BorderLayout(5, 5));
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
contentPanel.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPanel.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getContentPane().add(contentPanel);
|
||||
|
||||
searchField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
if (resultsModel.getRowCount() != 0) {
|
||||
resultsTable.setRowSelectionInterval(0, 0);
|
||||
}
|
||||
resultsTable.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
}
|
||||
|
||||
protected void addCustomResultsActions(JPanel resultsActionsPanel) {
|
||||
private void addSearchHistoryButton() {
|
||||
JButton searchHistoryButton = new JButton(new FlatSearchWithHistoryIcon(true));
|
||||
searchHistoryButton.setToolTipText(NLS.str("search_dialog.search_history"));
|
||||
searchHistoryButton.addActionListener(e -> {
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
List<String> searchHistory = mainWindow.getProject().getSearchHistory();
|
||||
if (searchHistory.isEmpty()) {
|
||||
popupMenu.add("(empty)");
|
||||
} else {
|
||||
for (String str : searchHistory) {
|
||||
JMenuItem item = popupMenu.add(str);
|
||||
item.addActionListener(ev -> searchField.setText(str));
|
||||
}
|
||||
}
|
||||
popupMenu.show(searchHistoryButton, 0, searchHistoryButton.getHeight());
|
||||
});
|
||||
searchField.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_COMPONENT, searchHistoryButton);
|
||||
}
|
||||
|
||||
protected void addResultsActions(JPanel resultsActionsPanel) {
|
||||
loadAllButton = new JButton(NLS.str("search_dialog.load_all"));
|
||||
loadAllButton.addActionListener(e -> loadAll());
|
||||
loadAllButton.addActionListener(e -> loadMoreResults(true));
|
||||
loadAllButton.setEnabled(false);
|
||||
|
||||
loadMoreButton = new JButton(NLS.str("search_dialog.load_more"));
|
||||
loadMoreButton.addActionListener(e -> loadMore());
|
||||
loadMoreButton.addActionListener(e -> loadMoreResults(false));
|
||||
loadMoreButton.setEnabled(false);
|
||||
|
||||
stopBtn = new JButton(NLS.str("search_dialog.stop"));
|
||||
stopBtn.addActionListener(e -> pauseSearch());
|
||||
stopBtn.setEnabled(false);
|
||||
|
||||
sortBtn = new JButton(NLS.str("search_dialog.sort_results"));
|
||||
sortBtn.addActionListener(e -> {
|
||||
synchronized (pendingResults) {
|
||||
resultsModel.sort();
|
||||
resultsTable.updateTable();
|
||||
}
|
||||
});
|
||||
sortBtn.setEnabled(false);
|
||||
|
||||
resultsActionsPanel.add(loadAllButton);
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
resultsActionsPanel.add(loadMoreButton);
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
resultsActionsPanel.add(stopBtn);
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
resultsActionsPanel.add(stopBtn);
|
||||
super.addResultsActions(resultsActionsPanel);
|
||||
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
resultsActionsPanel.add(sortBtn);
|
||||
}
|
||||
|
||||
private class SearchEventEmitter {
|
||||
@@ -302,26 +378,50 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
}
|
||||
|
||||
private void searchFieldSubscribe() {
|
||||
private void initSearchEvents() {
|
||||
if (searchDisposable != null) {
|
||||
searchDisposable.dispose();
|
||||
searchDisposable = null;
|
||||
}
|
||||
searchEmitter = new SearchEventEmitter();
|
||||
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
||||
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
||||
Flowable<String> searchEvents;
|
||||
if (mainWindow.getSettings().isUseAutoSearch()) {
|
||||
searchEvents = Flowable.merge(RxUtils.textFieldChanges(searchField),
|
||||
RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
|
||||
} else {
|
||||
searchEvents = Flowable.merge(RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
|
||||
}
|
||||
searchDisposable = searchEvents
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(SwingSchedulers.edt())
|
||||
.debounce(50, TimeUnit.MILLISECONDS)
|
||||
.observeOn(Schedulers.from(searchBackgroundExecutor))
|
||||
.subscribe(this::search);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized void search(String text) {
|
||||
UiUtils.uiThreadGuard();
|
||||
resetSearch();
|
||||
if (text == null || options.isEmpty()) {
|
||||
private void search(String text) {
|
||||
UiUtils.notUiThreadGuard();
|
||||
stopSearchTask();
|
||||
UiUtils.uiRun(this::resetSearch);
|
||||
searchTask = prepareSearch(text);
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
updateTableHighlight();
|
||||
prepareForSearch();
|
||||
});
|
||||
this.searchTask.setResultsLimit(50);
|
||||
this.searchTask.setProgressListener(this::updateProgress);
|
||||
this.searchTask.fetchResults();
|
||||
LOG.debug("Total search items count estimation: {}", this.searchTask.getTaskProgress().total());
|
||||
}
|
||||
|
||||
private SearchTask prepareSearch(String text) {
|
||||
if (text == null || options.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Building search for '{}', options: {}", text, options);
|
||||
boolean ignoreCase = options.contains(IGNORE_CASE);
|
||||
@@ -335,24 +435,16 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
} else {
|
||||
searchField.setBackground(SEARCH_FIELD_ERROR_COLOR);
|
||||
resultsInfoLabel.setText(error);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
searchTask = new SearchTask(mainWindow, this::addSearchResult, s -> searchComplete());
|
||||
if (!buildSearch(text, searchSettings)) {
|
||||
return;
|
||||
SearchTask newSearchTask = new SearchTask(mainWindow, this::addSearchResult, this::searchFinished);
|
||||
if (!buildSearch(newSearchTask, text, searchSettings)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateTableHighlight();
|
||||
startSearch();
|
||||
searchTask.setResultsLimit(100);
|
||||
searchTask.setProgressListener(this::updateProgress);
|
||||
searchTask.fetchResults();
|
||||
LOG.debug("Total search items count estimation: {}", searchTask.getTaskProgress().total());
|
||||
return newSearchTask;
|
||||
}
|
||||
|
||||
private boolean buildSearch(String text, SearchSettings searchSettings) {
|
||||
Objects.requireNonNull(searchTask);
|
||||
|
||||
private boolean buildSearch(SearchTask newSearchTask, String text, SearchSettings searchSettings) {
|
||||
List<JavaClass> allClasses;
|
||||
if (options.contains(ACTIVE_TAB)) {
|
||||
JumpPosition currentPos = mainWindow.getTabbedPane().getCurrentPosition();
|
||||
@@ -368,7 +460,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && options.contains(SearchOptions.COMMENT)) {
|
||||
searchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
return true;
|
||||
}
|
||||
// using ordered execution for fast tasks
|
||||
@@ -384,86 +476,119 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
if (options.contains(CODE)) {
|
||||
if (allClasses.size() == 1) {
|
||||
searchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
} else {
|
||||
List<JavaClass> topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
|
||||
for (List<JavaClass> batch : mainWindow.getWrapper().buildDecompileBatches(topClasses)) {
|
||||
searchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch));
|
||||
List<List<JavaClass>> batches = mainWindow.getCacheObject().getDecompileBatches();
|
||||
if (batches == null) {
|
||||
List<JavaClass> topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
|
||||
batches = mainWindow.getWrapper().buildDecompileBatches(topClasses);
|
||||
mainWindow.getCacheObject().setDecompileBatches(batches);
|
||||
}
|
||||
for (List<JavaClass> batch : batches) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.contains(RESOURCE)) {
|
||||
searchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings));
|
||||
}
|
||||
if (options.contains(COMMENT)) {
|
||||
searchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
}
|
||||
merged.prepare();
|
||||
searchTask.addProviderJob(merged);
|
||||
newSearchTask.addProviderJob(merged);
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized void stopSearchTask() {
|
||||
@Override
|
||||
protected void openItem(JNode node) {
|
||||
if (mainWindow.getSettings().isUseAutoSearch()) {
|
||||
// for auto search save only searches which leads to node opening
|
||||
mainWindow.getProject().addToSearchHistory(searchField.getText());
|
||||
}
|
||||
super.openItem(node);
|
||||
}
|
||||
|
||||
private void pauseSearch() {
|
||||
stopBtn.setEnabled(false);
|
||||
searchBackgroundExecutor.execute(() -> {
|
||||
if (searchTask != null) {
|
||||
searchTask.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stopSearchTask() {
|
||||
UiUtils.notUiThreadGuard();
|
||||
if (searchTask != null) {
|
||||
searchTask.cancel();
|
||||
searchTask.waitTask();
|
||||
searchTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadMore() {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
startSearch();
|
||||
searchTask.fetchResults();
|
||||
private void loadMoreResults(boolean all) {
|
||||
searchBackgroundExecutor.execute(() -> {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
searchTask.cancel();
|
||||
searchTask.waitTask();
|
||||
UiUtils.uiRunAndWait(this::prepareForSearch);
|
||||
if (all) {
|
||||
searchTask.setResultsLimit(0);
|
||||
}
|
||||
searchTask.fetchResults();
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void loadAll() {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
startSearch();
|
||||
searchTask.setResultsLimit(0);
|
||||
searchTask.fetchResults();
|
||||
}
|
||||
|
||||
private synchronized void resetSearch() {
|
||||
private void resetSearch() {
|
||||
UiUtils.uiThreadGuard();
|
||||
resultsModel.clear();
|
||||
updateTable();
|
||||
resultsTable.updateTable();
|
||||
synchronized (pendingResults) {
|
||||
pendingResults.clear();
|
||||
}
|
||||
progressPane.setVisible(false);
|
||||
warnLabel.setVisible(false);
|
||||
loadAllButton.setEnabled(false);
|
||||
loadMoreButton.setEnabled(false);
|
||||
stopSearchTask();
|
||||
}
|
||||
|
||||
private void startSearch() {
|
||||
private void prepareForSearch() {
|
||||
UiUtils.uiThreadGuard();
|
||||
stopBtn.setEnabled(true);
|
||||
sortBtn.setEnabled(false);
|
||||
showSearchState();
|
||||
progressStartCommon();
|
||||
}
|
||||
|
||||
private void addSearchResult(JNode node) {
|
||||
Objects.requireNonNull(node);
|
||||
synchronized (pendingResults) {
|
||||
UiUtils.notUiThreadGuard();
|
||||
pendingResults.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTable() {
|
||||
synchronized (pendingResults) {
|
||||
UiUtils.uiThreadGuard();
|
||||
Collections.sort(pendingResults);
|
||||
resultsModel.addAll(pendingResults);
|
||||
pendingResults.clear();
|
||||
resultsTable.updateTable();
|
||||
}
|
||||
resultsTable.updateTable();
|
||||
}
|
||||
|
||||
private void updateTableHighlight() {
|
||||
String text = searchField.getText();
|
||||
setHighlightText(text);
|
||||
highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE);
|
||||
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
|
||||
updateHighlightContext(text, !options.contains(IGNORE_CASE), options.contains(USE_REGEX));
|
||||
cache.setLastSearch(text);
|
||||
cache.getLastSearchOptions().put(searchPreset, options);
|
||||
if (!mainWindow.getSettings().isUseAutoSearch()) {
|
||||
mainWindow.getProject().addToSearchHistory(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProgress(ITaskProgress progress) {
|
||||
@@ -473,17 +598,16 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void searchComplete() {
|
||||
private void searchFinished(ITaskInfo status, Boolean complete) {
|
||||
UiUtils.uiThreadGuard();
|
||||
LOG.debug("Search complete");
|
||||
updateTable();
|
||||
|
||||
boolean complete = searchTask == null || searchTask.isSearchComplete();
|
||||
LOG.debug("Search complete: {}, complete: {}", status, complete);
|
||||
loadAllButton.setEnabled(!complete);
|
||||
loadMoreButton.setEnabled(!complete);
|
||||
updateProgressLabel(complete);
|
||||
unloadTempData();
|
||||
stopBtn.setEnabled(false);
|
||||
progressFinishedCommon();
|
||||
updateTable();
|
||||
updateProgressLabel(complete);
|
||||
sortBtn.setEnabled(resultsModel.getRowCount() != 0);
|
||||
}
|
||||
|
||||
private void unloadTempData() {
|
||||
@@ -491,48 +615,6 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
System.gc();
|
||||
}
|
||||
|
||||
private static Flowable<String> onTextFieldChanges(final JTextField textField) {
|
||||
return Flowable.<String>create(emitter -> {
|
||||
DocumentListener listener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
public void change() {
|
||||
emitter.onNext(textField.getText());
|
||||
}
|
||||
};
|
||||
|
||||
textField.getDocument().addDocumentListener(listener);
|
||||
emitter.setDisposable(new Disposable() {
|
||||
private boolean disposed = false;
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
textField.getDocument().removeDocumentListener(listener);
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
});
|
||||
}, BackpressureStrategy.LATEST)
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
|
||||
final JCheckBox chBox = new JCheckBox(name);
|
||||
chBox.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.gui.ui.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
@@ -83,6 +82,15 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
for (JavaMethod mth : getMethodWithOverrides(javaMethod)) {
|
||||
map.put(mth, mth.getUseIn());
|
||||
}
|
||||
} else if (node instanceof JClass) {
|
||||
JavaClass javaCls = ((JClass) node).getCls();
|
||||
map.put(javaCls, javaCls.getUseIn());
|
||||
// add constructors usage into class usage
|
||||
for (JavaMethod javaMth : javaCls.getMethods()) {
|
||||
if (javaMth.isConstructor()) {
|
||||
map.put(javaMth, javaMth.getUseIn());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
map.put(javaNode, javaNode.getUseIn());
|
||||
@@ -123,8 +131,7 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
|
||||
Collections.sort(usageList);
|
||||
resultsModel.addAll(usageList);
|
||||
// TODO: highlight only needed node usage
|
||||
setHighlightText(null);
|
||||
updateHighlightContext(node.getName(), true, false);
|
||||
resultsTable.initColumnWidth();
|
||||
resultsTable.updateTable();
|
||||
updateProgressLabel(true);
|
||||
@@ -154,10 +161,13 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPane.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BorderLayout(5, 5));
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
contentPanel.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPanel.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getContentPane().add(contentPanel);
|
||||
|
||||
pack();
|
||||
setSize(800, 500);
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package jadx.gui.ui.panel;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.TitledBorder;
|
||||
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.treenodes.StartPageNode;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public class StartPagePanel extends ContentPanel {
|
||||
|
||||
public StartPagePanel(TabbedPane tabbedPane, StartPageNode node) {
|
||||
super(tabbedPane, node);
|
||||
MainWindow mainWindow = tabbedPane.getMainWindow();
|
||||
Font baseFont = mainWindow.getSettings().getFont();
|
||||
|
||||
JButton openFile = new JButton(NLS.str("file.open_title"), Icons.OPEN);
|
||||
openFile.addActionListener(ev -> mainWindow.openFileDialog());
|
||||
|
||||
JButton openProject = new JButton(NLS.str("file.open_project"), Icons.OPEN_PROJECT);
|
||||
openProject.addActionListener(ev -> mainWindow.openProjectDialog());
|
||||
|
||||
JPanel start = new JPanel();
|
||||
start.setBorder(sectionFrame(NLS.str("start_page.start"), baseFont));
|
||||
start.setLayout(new BoxLayout(start, BoxLayout.LINE_AXIS));
|
||||
start.add(openFile);
|
||||
start.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
start.add(openProject);
|
||||
start.add(Box.createHorizontalGlue());
|
||||
|
||||
JPanel recentPanel = new JPanel();
|
||||
JScrollPane scrollPane = new JScrollPane(recentPanel);
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||
scrollPane.setPreferredSize(new Dimension(400, 200));
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
|
||||
fillRecentPanel(recentPanel, scrollPane, mainWindow);
|
||||
|
||||
JPanel recent = new JPanel();
|
||||
recent.setBorder(sectionFrame(NLS.str("start_page.recent"), baseFont));
|
||||
recent.setLayout(new BoxLayout(recent, BoxLayout.PAGE_AXIS));
|
||||
recent.add(scrollPane);
|
||||
|
||||
JPanel center = new JPanel();
|
||||
center.setLayout(new BorderLayout(10, 10));
|
||||
center.add(start, BorderLayout.PAGE_START);
|
||||
center.add(recent, BorderLayout.CENTER);
|
||||
center.setMaximumSize(new Dimension(700, 600));
|
||||
center.setAlignmentX(CENTER_ALIGNMENT);
|
||||
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50));
|
||||
add(Box.createVerticalGlue());
|
||||
add(center);
|
||||
add(Box.createVerticalGlue());
|
||||
}
|
||||
|
||||
private void fillRecentPanel(JPanel panel, JScrollPane scrollPane, MainWindow mainWindow) {
|
||||
JadxSettings settings = mainWindow.getSettings();
|
||||
List<Path> recentProjects = settings.getRecentProjects();
|
||||
panel.removeAll();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
|
||||
Font baseFont = settings.getFont();
|
||||
Font font = baseFont.deriveFont(baseFont.getSize() - 1f);
|
||||
for (Path path : recentProjects) {
|
||||
JButton openBtn = new JButton(path.getFileName().toString());
|
||||
openBtn.setToolTipText(path.toAbsolutePath().toString());
|
||||
openBtn.setFont(font);
|
||||
openBtn.setBorderPainted(false);
|
||||
openBtn.addActionListener(ev -> mainWindow.open(path));
|
||||
|
||||
JButton removeBtn = new JButton();
|
||||
removeBtn.setIcon(Icons.CLOSE_INACTIVE);
|
||||
removeBtn.setRolloverIcon(Icons.CLOSE);
|
||||
removeBtn.setRolloverEnabled(true);
|
||||
removeBtn.setFocusable(false);
|
||||
removeBtn.setBorder(null);
|
||||
removeBtn.setBorderPainted(false);
|
||||
removeBtn.setContentAreaFilled(false);
|
||||
removeBtn.setOpaque(true);
|
||||
removeBtn.addActionListener(e -> {
|
||||
mainWindow.getSettings().removeRecentProject(path);
|
||||
fillRecentPanel(panel, scrollPane, mainWindow);
|
||||
panel.revalidate();
|
||||
scrollPane.repaint();
|
||||
});
|
||||
JPanel linePanel = new JPanel();
|
||||
linePanel.setLayout(new BoxLayout(linePanel, BoxLayout.LINE_AXIS));
|
||||
linePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
linePanel.add(openBtn);
|
||||
linePanel.add(Box.createHorizontalGlue());
|
||||
linePanel.add(removeBtn);
|
||||
|
||||
panel.add(linePanel);
|
||||
}
|
||||
panel.add(Box.createVerticalGlue());
|
||||
}
|
||||
|
||||
private static Border sectionFrame(String title, Font font) {
|
||||
TitledBorder titledBorder = BorderFactory.createTitledBorder(title);
|
||||
titledBorder.setTitleFont(font.deriveFont(Font.BOLD, font.getSize() + 1));
|
||||
Border spacing = BorderFactory.createEmptyBorder(10, 10, 10, 10);
|
||||
return BorderFactory.createCompoundBorder(titledBorder, spacing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSettings() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.gui.ui.treenodes;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.panel.StartPagePanel;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public class StartPageNode extends JNode {
|
||||
private static final long serialVersionUID = 8983134608645736174L;
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new StartPagePanel(tabbedPane, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return NLS.str("start_page.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Icons.START_PAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
|
||||
public class CacheObject {
|
||||
@@ -16,19 +16,17 @@ public class CacheObject {
|
||||
private JNodeCache jNodeCache;
|
||||
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
|
||||
private JRoot jRoot;
|
||||
private JadxSettings settings;
|
||||
private List<List<JavaClass>> decompileBatches;
|
||||
|
||||
public CacheObject() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
jRoot = null;
|
||||
settings = null;
|
||||
lastSearch = null;
|
||||
jNodeCache = new JNodeCache();
|
||||
lastSearchOptions = new HashMap<>();
|
||||
decompileBatches = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -48,19 +46,11 @@ public class CacheObject {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
public void setJadxSettings(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
public @Nullable List<List<JavaClass>> getDecompileBatches() {
|
||||
return decompileBatches;
|
||||
}
|
||||
|
||||
public JadxSettings getJadxSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
public JRoot getJRoot() {
|
||||
return jRoot;
|
||||
}
|
||||
|
||||
public void setJRoot(JRoot jRoot) {
|
||||
this.jRoot = jRoot;
|
||||
public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
|
||||
this.decompileBatches = decompileBatches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
public interface ILoadListener {
|
||||
|
||||
/**
|
||||
* Update files/project loaded state
|
||||
*
|
||||
* @return true to remove listener
|
||||
*/
|
||||
boolean update(boolean loaded);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import static jadx.gui.utils.UiUtils.openSvgIcon;
|
||||
|
||||
public class Icons {
|
||||
|
||||
public static final ImageIcon OPEN = openSvgIcon("ui/openDisk");
|
||||
public static final ImageIcon OPEN_PROJECT = openSvgIcon("ui/projectDirectory");
|
||||
public static final ImageIcon NEW_PROJECT = openSvgIcon("ui/newFolder");
|
||||
|
||||
public static final ImageIcon CLOSE = openSvgIcon("ui/closeHovered");
|
||||
public static final ImageIcon CLOSE_INACTIVE = openSvgIcon("ui/close");
|
||||
|
||||
public static final ImageIcon STATIC = openSvgIcon("nodes/staticMark");
|
||||
public static final ImageIcon FINAL = openSvgIcon("nodes/finalMark");
|
||||
|
||||
public static final ImageIcon START_PAGE = openSvgIcon("nodes/newWindow");
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public class NLS {
|
||||
LANG_LOCALES.add(new LangLocale("es", "ES"));
|
||||
LANG_LOCALES.add(new LangLocale("de", "DE"));
|
||||
LANG_LOCALES.add(new LangLocale("ko", "KR"));
|
||||
LANG_LOCALES.add(new LangLocale("pt", "BR"));
|
||||
|
||||
LANG_LOCALES.forEach(NLS::load);
|
||||
|
||||
|
||||
@@ -46,9 +46,6 @@ import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
public class UiUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UiUtils.class);
|
||||
|
||||
public static final ImageIcon ICON_STATIC = openSvgIcon("nodes/staticMark");
|
||||
public static final ImageIcon ICON_FINAL = openSvgIcon("nodes/finalMark");
|
||||
|
||||
/**
|
||||
* The minimum about of memory in bytes we are trying to keep free, otherwise the application may
|
||||
* run out of heap
|
||||
@@ -190,10 +187,10 @@ public class UiUtils {
|
||||
}
|
||||
OverlayIcon overIcon = new OverlayIcon(icon);
|
||||
if (af.isFinal()) {
|
||||
overIcon.add(ICON_FINAL);
|
||||
overIcon.add(Icons.FINAL);
|
||||
}
|
||||
if (af.isStatic()) {
|
||||
overIcon.add(ICON_STATIC);
|
||||
overIcon.add(Icons.STATIC);
|
||||
}
|
||||
return overIcon;
|
||||
}
|
||||
@@ -377,6 +374,8 @@ public class UiUtils {
|
||||
}
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(runnable);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn("UI thread interrupted, runnable: {}", runnable, e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -388,6 +387,12 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void notUiThreadGuard() {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
LOG.warn("Expect background thread, got: {}", Thread.currentThread(), new JadxRuntimeException());
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void debugTimer(int periodInSeconds, Runnable action) {
|
||||
if (!LOG.isDebugEnabled()) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -30,6 +31,7 @@ import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -42,7 +44,7 @@ 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 = 12;
|
||||
private static final int DATA_FORMAT_VERSION = 13;
|
||||
|
||||
private static final byte[] JADX_NAMES_MAP_HEADER = "jadxnm".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
@@ -55,6 +57,7 @@ public class DiskCodeCache implements ICodeCache {
|
||||
private final ExecutorService writePool;
|
||||
private final Map<String, ICodeInfo> writeOps = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> namesMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> allClsIds;
|
||||
|
||||
public DiskCodeCache(RootNode root, Path baseDir) {
|
||||
srcDir = baseDir.resolve("sources");
|
||||
@@ -65,6 +68,7 @@ public class DiskCodeCache implements ICodeCache {
|
||||
codeVersion = buildCodeVersion(args);
|
||||
writePool = Executors.newFixedThreadPool(args.getThreadsCount());
|
||||
codeMetadataAdapter = new CodeMetadataAdapter(root);
|
||||
allClsIds = buildClassIdsMap(root.getClasses());
|
||||
if (checkCodeVersion()) {
|
||||
loadNamesMap();
|
||||
} else {
|
||||
@@ -112,6 +116,7 @@ public class DiskCodeCache implements ICodeCache {
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
writeOps.put(clsFullName, codeInfo);
|
||||
int clsId = getClsId(clsFullName);
|
||||
namesMap.put(clsFullName, clsId);
|
||||
writePool.execute(() -> {
|
||||
try {
|
||||
FileUtils.writeFile(getJavaFile(clsId), codeInfo.getCodeStr());
|
||||
@@ -218,7 +223,11 @@ public class DiskCodeCache implements ICodeCache {
|
||||
}
|
||||
|
||||
private int getClsId(String clsFullName) {
|
||||
return namesMap.computeIfAbsent(clsFullName, n -> namesMap.size());
|
||||
Integer id = allClsIds.get(clsFullName);
|
||||
if (id == null) {
|
||||
throw new JadxRuntimeException("Unknown class name: " + clsFullName);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private void saveNamesMap() {
|
||||
@@ -250,6 +259,13 @@ public class DiskCodeCache implements ICodeCache {
|
||||
String clsName = in.readUTF();
|
||||
int clsId = in.readInt();
|
||||
namesMap.put(clsName, clsId);
|
||||
Integer prevId = allClsIds.get(clsName);
|
||||
if (prevId == null || prevId != clsId) {
|
||||
LOG.debug("Unexpected class id, got: {}, expect: {}", clsId, prevId);
|
||||
LOG.warn("Inconsistent disk cache, resetting...");
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.info("Found {} classes in disk cache, dir: {}", count, metaDir.getParent());
|
||||
} catch (Exception e) {
|
||||
@@ -271,6 +287,16 @@ public class DiskCodeCache implements ICodeCache {
|
||||
return Paths.get(firstByte, FileUtils.intToHex(clsId) + ext);
|
||||
}
|
||||
|
||||
private Map<String, Integer> buildClassIdsMap(List<ClassNode> classes) {
|
||||
int clsCount = classes.size();
|
||||
Map<String, Integer> map = new HashMap<>(clsCount);
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClassNode cls = classes.get(i);
|
||||
map.put(cls.getRawName(), i);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package jadx.gui.utils.res;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ResourceFileContent;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
|
||||
public class ResTableHelper {
|
||||
|
||||
/**
|
||||
* Build UI tree for resource table container.
|
||||
*
|
||||
* @return root nodes
|
||||
*/
|
||||
public static List<JResource> buildTree(ResContainer resTable) {
|
||||
ResTableHelper resTableHelper = new ResTableHelper();
|
||||
resTableHelper.process(resTable);
|
||||
return resTableHelper.roots;
|
||||
}
|
||||
|
||||
private final List<JResource> roots = new ArrayList<>();
|
||||
private final Map<String, JResource> dirs = new HashMap<>();
|
||||
|
||||
private ResTableHelper() {
|
||||
}
|
||||
|
||||
private void process(ResContainer resTable) {
|
||||
for (ResContainer subFile : resTable.getSubFiles()) {
|
||||
loadSubNodes(subFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSubNodes(ResContainer rc) {
|
||||
String resName = rc.getName();
|
||||
int split = resName.lastIndexOf('/');
|
||||
String dir;
|
||||
String name;
|
||||
if (split == -1) {
|
||||
dir = null;
|
||||
name = resName;
|
||||
} else {
|
||||
dir = resName.substring(0, split);
|
||||
name = resName.substring(split + 1);
|
||||
}
|
||||
ICodeInfo code = rc.getText();
|
||||
ResourceFileContent fileContent = new ResourceFileContent(name, ResourceType.XML, code);
|
||||
JResource resFile = new JResource(fileContent, resName, name, JResource.JResType.FILE);
|
||||
addResFile(dir, resFile);
|
||||
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
loadSubNodes(subFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void addResFile(@Nullable String dir, JResource resFile) {
|
||||
if (dir == null) {
|
||||
roots.add(resFile);
|
||||
return;
|
||||
}
|
||||
JResource dirRes = dirs.get(dir);
|
||||
if (dirRes != null) {
|
||||
dirRes.addSubNode(resFile);
|
||||
return;
|
||||
}
|
||||
JResource parentDir = null;
|
||||
int splitPos = -1;
|
||||
while (true) {
|
||||
int prevStart = splitPos + 1;
|
||||
splitPos = dir.indexOf('/', prevStart);
|
||||
boolean last = splitPos == -1;
|
||||
String path = last ? dir : dir.substring(0, splitPos);
|
||||
JResource curDir = dirs.get(path);
|
||||
if (curDir == null) {
|
||||
String dirName = last ? dir.substring(prevStart) : dir.substring(prevStart, splitPos);
|
||||
curDir = new JResource(null, dirName, JResource.JResType.DIR);
|
||||
dirs.put(path, curDir);
|
||||
if (parentDir == null) {
|
||||
roots.add(curDir);
|
||||
} else {
|
||||
parentDir.addSubNode(curDir);
|
||||
}
|
||||
}
|
||||
if (last) {
|
||||
curDir.addSubNode(resFile);
|
||||
return;
|
||||
}
|
||||
parentDir = curDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package jadx.gui.utils.rx;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public class CustomDisposable implements Disposable {
|
||||
|
||||
private final AtomicBoolean disposed = new AtomicBoolean(false);
|
||||
private final Runnable disposeTask;
|
||||
|
||||
public CustomDisposable(Runnable disposeTask) {
|
||||
this.disposeTask = disposeTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
try {
|
||||
disposeTask.run();
|
||||
} finally {
|
||||
disposed.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisposed() {
|
||||
return disposed.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package jadx.gui.utils.rx;
|
||||
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.FlowableOnSubscribe;
|
||||
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
|
||||
public class RxUtils {
|
||||
|
||||
public static Flowable<String> textFieldChanges(final JTextField textField) {
|
||||
FlowableOnSubscribe<String> source = emitter -> {
|
||||
DocumentListener listener = new DocumentUpdateListener(ev -> emitter.onNext(textField.getText()));
|
||||
textField.getDocument().addDocumentListener(listener);
|
||||
emitter.setDisposable(new CustomDisposable(() -> textField.getDocument().removeDocumentListener(listener)));
|
||||
};
|
||||
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
|
||||
}
|
||||
|
||||
public static Flowable<String> textFieldEnterPress(final JTextField textField) {
|
||||
FlowableOnSubscribe<String> source = emitter -> {
|
||||
KeyListener keyListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent ev) {
|
||||
if (ev.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
emitter.onNext(textField.getText());
|
||||
}
|
||||
}
|
||||
};
|
||||
textField.addKeyListener(keyListener);
|
||||
emitter.setDisposable(new CustomDisposable(() -> textField.removeKeyListener(keyListener)));
|
||||
};
|
||||
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package jadx.gui.utils.tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Automatically add new i18n lines from reference (EN) into others languages
|
||||
*/
|
||||
public class NLSAddNewLines {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NLSAddNewLines.class);
|
||||
|
||||
private static final Path I18N_PATH = Paths.get("src/main/resources/i18n/");
|
||||
private static final String GUI_MODULE_DIR = "jadx-gui";
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
process();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to add new i18n lines", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void process() throws Exception {
|
||||
String reference = "Messages_en_US.properties";
|
||||
Path refPath = getRefPath(reference);
|
||||
List<String> refLines = Files.readAllLines(refPath);
|
||||
|
||||
try (Stream<Path> pathStream = Files.list(refPath.toAbsolutePath().getParent())) {
|
||||
pathStream.forEach(path -> {
|
||||
if (path.getFileName().equals(refPath.getFileName())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
applyFix(refLines, path);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyFix(List<String> refLines, Path path) throws IOException {
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
int linesCount = lines.size();
|
||||
if (refLines.size() <= linesCount) {
|
||||
LOG.info("Skip {}, already fixed", path);
|
||||
return;
|
||||
}
|
||||
boolean updated = false;
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String line = lines.get(i);
|
||||
String refLine = refLines.get(i);
|
||||
if (!isSameKey(refLine, line)) {
|
||||
if (refLine.isEmpty()) {
|
||||
lines.add(i, "");
|
||||
} else {
|
||||
lines.add(i, "#" + refLine);
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
LOG.info("Updating {}", path);
|
||||
Files.write(path, lines, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSameKey(String refLine, String line) {
|
||||
int refLen = refLine.length();
|
||||
int len = line.length();
|
||||
if (refLen == 0) {
|
||||
return len == 0;
|
||||
}
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
int pos = 0;
|
||||
// skip comment and spaces
|
||||
while (pos < len) {
|
||||
char ch = line.charAt(pos);
|
||||
if (ch == '#' || ch == ' ') {
|
||||
pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int refPos = 0;
|
||||
while (true) {
|
||||
char refCh = refLine.charAt(refPos);
|
||||
if (refCh == ' ' || refCh == '=') {
|
||||
return true;
|
||||
}
|
||||
char ch = line.charAt(pos);
|
||||
if (refCh != ch) {
|
||||
return false;
|
||||
}
|
||||
refPos++;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getRefPath(String reference) {
|
||||
Path path = I18N_PATH.resolve(reference);
|
||||
if (Files.exists(path)) {
|
||||
return path;
|
||||
}
|
||||
Path rootPath = Paths.get(GUI_MODULE_DIR).resolve(I18N_PATH).resolve(reference);
|
||||
if (Files.exists(rootPath)) {
|
||||
return rootPath;
|
||||
}
|
||||
throw new RuntimeException("Can't find reference I18N: " + reference);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,10 @@ public class ActionHandler extends AbstractAction {
|
||||
|
||||
private final Consumer<ActionEvent> consumer;
|
||||
|
||||
public ActionHandler(Runnable action) {
|
||||
this.consumer = ev -> action.run();
|
||||
}
|
||||
|
||||
public ActionHandler(Consumer<ActionEvent> consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ menu.file=Datei
|
||||
menu.view=Ansicht
|
||||
menu.recent_projects=Aktuelle Projekte
|
||||
menu.no_recent_projects=Keine aktuellen Projekte
|
||||
menu.preferences=Präferenzen
|
||||
menu.preferences=Einstellungen
|
||||
menu.sync=Mit Editor synchronisieren
|
||||
menu.flatten=Codepaket erweitern
|
||||
menu.heapUsageBar=Speicherverbrauchsleiste anzeigen
|
||||
@@ -23,26 +23,31 @@ menu.update_label=Neue Version %s verfügbar!
|
||||
file.open_action=Datei öffnen…
|
||||
file.add_files_action=Dateien hinzufügen…
|
||||
file.open_title=Datei öffnen
|
||||
#file.open_project=Open project
|
||||
file.new_project=Neues Projekt
|
||||
file.save_project=Projekt speichern
|
||||
file.save_project_as=Projekt speichern als…
|
||||
#file.reload=Reload files
|
||||
#file.live_reload=Live reload
|
||||
#file.live_reload_desc=Auto reload files on changes
|
||||
#file.export_mappings_as=
|
||||
file.reload=Dateien neu laden
|
||||
file.live_reload=Live nachladen
|
||||
file.live_reload_desc=Dateien bei Änderungen autom. neuladen
|
||||
file.export_mappings_as=Zuordnungen exportieren als…
|
||||
file.save_all=Alles speichern
|
||||
file.export_gradle=Als Gradle-Projekt speichern
|
||||
file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen
|
||||
file.exit=Beenden
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
tree.sources_title=Quelltexte
|
||||
tree.resources_title=Ressourcen
|
||||
tree.loading=Laden…
|
||||
|
||||
progress.load=Laden
|
||||
#progress.export_mappings=
|
||||
progress.export_mappings=Zuordnungen exportieren
|
||||
progress.decompile=Dekompilieren
|
||||
#progress.canceling=Canceling
|
||||
progress.canceling=Breche ab
|
||||
|
||||
error_dialog.title=Fehler
|
||||
|
||||
@@ -69,7 +74,7 @@ message.userCancelTask=Aufgabe wurde vom Benutzer abgebrochen.
|
||||
message.memoryLow=Jadx hat zu wenig Speicherplatz. Bitte mit erhöhter maximaler Heap-Größe erneut starten.
|
||||
message.taskError=Die Aufgabe ist durch Fehler fehlgeschlagen (siehe Protokoll für Details).
|
||||
message.errorTitle=Fehler
|
||||
message.load_errors=Laden fehlgeschlagen.\nAnzahl der Fehler: %d\nKlicke auf OK, um die Log-Ansicht zu öffnen"
|
||||
message.load_errors=Laden fehlgeschlagen.\nAnzahl der Fehler: %d\nKlicke auf OK, um die Log-Ansicht zu öffnen.
|
||||
message.no_classes=Keine Klassen geladen, nichts zu dekompilieren!
|
||||
|
||||
message.saveIncomplete=<html>Speichern unvollständig.<br> %s<br> %d Klassen oder Ressourcen wurden nicht gespeichert!</html>
|
||||
@@ -84,13 +89,16 @@ common_dialog.add=Hinzufügen
|
||||
common_dialog.update=Aktualisieren
|
||||
common_dialog.remove=Entfernen
|
||||
|
||||
#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=Unterstützte Dateien
|
||||
file_dialog.load_dir_title=Verzeichnis laden
|
||||
file_dialog.load_dir_confirm=Alle Dateien aus dem Verzeichnis laden?
|
||||
|
||||
search_dialog.open=Öffnen
|
||||
search_dialog.cancel=Beenden
|
||||
search_dialog.open_by_name=Nach Text suchen:
|
||||
#search_dialog.search_button=Search
|
||||
#search_dialog.search_history=Search history
|
||||
#search_dialog.auto_search=Auto search
|
||||
search_dialog.search_in=Suche in Definitionen von:
|
||||
search_dialog.class=Klassen
|
||||
search_dialog.method=Methoden
|
||||
@@ -98,12 +106,14 @@ search_dialog.field=Felder
|
||||
search_dialog.code=Code
|
||||
search_dialog.options=Suchoptionen:
|
||||
search_dialog.ignorecase=Groß/Kleinschreibung ignorieren
|
||||
#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=Mehr laden
|
||||
search_dialog.load_all=Alle laden
|
||||
#search_dialog.stop=Stop
|
||||
search_dialog.results_incomplete=%d+ gefunden
|
||||
search_dialog.results_complete=%d gefunden (komplett)
|
||||
search_dialog.col_node=Knoten
|
||||
search_dialog.col_code=Code
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.active_tab=Nur aktiver Tab
|
||||
search_dialog.comments=Kommentare
|
||||
@@ -128,28 +138,29 @@ preferences.title=Einstellungen
|
||||
preferences.deobfuscation=Deobfuskierung
|
||||
preferences.appearance=Aussehen
|
||||
preferences.decompile=Dekompilierung
|
||||
#preferences.plugins=Plugins
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Projekt
|
||||
preferences.other=Andere
|
||||
preferences.language=Sprache
|
||||
preferences.lineNumbersMode=Editor Zeilennummern-Modus
|
||||
#preferences.jumpOnDoubleClick=Enable jump on double click
|
||||
preferences.jumpOnDoubleClick=Sprung bei Doppelklick aktivieren
|
||||
preferences.check_for_updates=Nach Updates beim Start suchen
|
||||
#preferences.useDx=Use dx/d8 to convert java bytecode
|
||||
#preferences.decompilationMode=Decompilation mode
|
||||
#preferences.codeCacheMode=Code cache mode
|
||||
preferences.useDx=dx/d8 zur Konvertierung von Java Bytecode verwenden
|
||||
preferences.decompilationMode=Dekompilierungsmodus
|
||||
preferences.codeCacheMode=Cache-Code-Modus
|
||||
preferences.showInconsistentCode=Inkonsistenten Code anzeigen
|
||||
preferences.escapeUnicode=Unicodezeichen escapen
|
||||
preferences.replaceConsts=Konstanten ersetzen
|
||||
preferences.respectBytecodeAccessModifiers=Modifikatoren für Bytecode-Zugriff beachten
|
||||
preferences.useImports=Import statements generieren
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
preferences.useDebugInfo=Debug-Infos verwenden
|
||||
preferences.inlineAnonymous=Anonyme Inline-Klassen
|
||||
preferences.inlineMethods=Inline-Methoden
|
||||
#preferences.extractFinally=Extract finally block
|
||||
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
|
||||
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
#preferences.commentsLevel=Code comments level
|
||||
preferences.useKotlinMethodsForVarNames=Kotlin-Methoden für die Umbenennung von Variablen verwenden
|
||||
preferences.commentsLevel=Level der Code Kommentierungen
|
||||
preferences.autoSave=Autom. speichern
|
||||
preferences.threads=Verarbeitungs-Thread-Anzahl
|
||||
preferences.excludedPackages=Ausgeschlossene Pakete
|
||||
@@ -166,11 +177,12 @@ preferences.start_jobs=Autom. Hintergrunddekompilierung starten
|
||||
preferences.select_font=Ändern
|
||||
preferences.select_smali_font=Ändern
|
||||
preferences.deobfuscation_on=Deobfuskierung aktivieren
|
||||
#preferences.deobfuscation_map_file_mode=Map file handle mode
|
||||
preferences.deobfuscation_map_file_mode=Umgang mit Map-Dateien
|
||||
preferences.deobfuscation_min_len=Minimale Namenlänge
|
||||
preferences.deobfuscation_max_len=Maximale Namenlänge
|
||||
preferences.deobfuscation_source_alias=Quelldateiname als Klassennamen-Alias verwenden
|
||||
preferences.deobfuscation_kotlin_metadata=Kotlin-Metadaten nach Klassen- und Paketnamen analysieren
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Speichern
|
||||
preferences.cancel=Abbrechen
|
||||
preferences.reset=Zurücksetzen
|
||||
@@ -213,7 +225,7 @@ popup.exclude_packages=Pakete ausschließen
|
||||
popup.add_comment=Kommentar
|
||||
popup.search_comment=Kommentar suchen
|
||||
popup.rename=Umbennen
|
||||
popup.search=Search "%s"
|
||||
popup.search=Suche "%s"
|
||||
popup.search_global=Globale Suche "%s"
|
||||
|
||||
exclude_dialog.title=Paketauswahl
|
||||
|
||||
@@ -23,6 +23,7 @@ menu.update_label=New version %s available!
|
||||
file.open_action=Open files ...
|
||||
file.add_files_action=Add files
|
||||
file.open_title=Open file
|
||||
file.open_project=Open project
|
||||
file.new_project=New project
|
||||
file.save_project=Save project
|
||||
file.save_project_as=Save project as...
|
||||
@@ -35,6 +36,10 @@ file.export_gradle=Save as gradle project
|
||||
file.save_all_msg=Select directory for save decompiled sources
|
||||
file.exit=Exit
|
||||
|
||||
start_page.title=Start page
|
||||
start_page.start=Start
|
||||
start_page.recent=Recent projects
|
||||
|
||||
tree.sources_title=Source code
|
||||
tree.resources_title=Resources
|
||||
tree.loading=Loading...
|
||||
@@ -69,7 +74,7 @@ message.userCancelTask=Task was canceled by user.
|
||||
message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
message.taskError=Task failed with error (check log for details).
|
||||
message.errorTitle=Error
|
||||
message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer.
|
||||
message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
@@ -91,19 +96,24 @@ file_dialog.load_dir_confirm=Load all files from directory?
|
||||
search_dialog.open=Open
|
||||
search_dialog.cancel=Cancel
|
||||
search_dialog.open_by_name=Search for text:
|
||||
search_dialog.search_button=Search
|
||||
search_dialog.search_history=Search history
|
||||
search_dialog.auto_search=Auto search
|
||||
search_dialog.search_in=Search definitions of:
|
||||
search_dialog.class=Class
|
||||
search_dialog.method=Method
|
||||
search_dialog.field=Field
|
||||
search_dialog.code=Code
|
||||
search_dialog.options=Search options:
|
||||
search_dialog.ignorecase=Case insensitive
|
||||
search_dialog.ignorecase=Case-insensitive
|
||||
search_dialog.load_more=Load more
|
||||
search_dialog.load_all=Load all
|
||||
search_dialog.stop=Stop
|
||||
search_dialog.results_incomplete=Found %d+
|
||||
search_dialog.results_complete=Found %d (complete)
|
||||
search_dialog.col_node=Node
|
||||
search_dialog.col_code=Code
|
||||
search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.active_tab=Active tab only
|
||||
search_dialog.comments=Comments
|
||||
@@ -146,7 +156,8 @@ preferences.useImports=Use import statements
|
||||
preferences.useDebugInfo=Use debug info
|
||||
preferences.inlineAnonymous=Inline anonymous classes
|
||||
preferences.inlineMethods=Inline methods
|
||||
preferences.fsCaseSensitive=File system is case sensitive
|
||||
preferences.extractFinally=Extract finally block
|
||||
preferences.fsCaseSensitive=File system is case-sensitive
|
||||
preferences.skipResourcesDecode=Don't decode resources
|
||||
preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.commentsLevel=Code comments level
|
||||
@@ -171,6 +182,7 @@ preferences.deobfuscation_min_len=Minimum name length
|
||||
preferences.deobfuscation_max_len=Maximum name length
|
||||
preferences.deobfuscation_source_alias=Use source file name as class name alias
|
||||
preferences.deobfuscation_kotlin_metadata=Parse Kotlin metadata for class and package names
|
||||
preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Save
|
||||
preferences.cancel=Cancel
|
||||
preferences.reset=Reset
|
||||
@@ -298,7 +310,7 @@ adb_dialog.disconnected=ADB server disconnected.
|
||||
adb_dialog.start_okay=ADB server started on port: %s.
|
||||
adb_dialog.start_fail=Failed to start ADB server on port: %s!
|
||||
adb_dialog.forward_fail=Failed to forward for some reasons.
|
||||
adb_dialog.being_debugged_msg=This process seems like it's being debugged, should we proceed?"
|
||||
adb_dialog.being_debugged_msg=This process seems like it's being debugged, should we proceed?
|
||||
adb_dialog.unknown_android_ver=Failed to get Android release version, use Android 8 as default?
|
||||
adb_dialog.being_debugged_title=It's Debugging by other.
|
||||
adb_dialog.init_dbg_fail=Failed to init debugger.
|
||||
|
||||
@@ -23,6 +23,7 @@ menu.update_label=¡Nueva versión %s disponible!
|
||||
file.open_action=Abrir archivo...
|
||||
#file.add_files_action=Add files ...
|
||||
file.open_title=Abrir archivo
|
||||
#file.open_project=Open project
|
||||
#file.new_project=
|
||||
#file.save_project=
|
||||
#file.save_project_as=
|
||||
@@ -35,6 +36,10 @@ file.export_gradle=Guardar como proyecto Gradle
|
||||
file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas
|
||||
file.exit=Salir
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
tree.sources_title=Código fuente
|
||||
tree.resources_title=Recursos
|
||||
tree.loading=Cargando...
|
||||
@@ -69,7 +74,7 @@ nav.forward=Adelante
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer.
|
||||
#message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
@@ -91,6 +96,9 @@ nav.forward=Adelante
|
||||
search_dialog.open=Abrir
|
||||
search_dialog.cancel=Cancelar
|
||||
search_dialog.open_by_name=Buscar texto:
|
||||
#search_dialog.search_button=Search
|
||||
#search_dialog.search_history=Search history
|
||||
#search_dialog.auto_search=Auto search
|
||||
search_dialog.search_in=Buscar definiciones de:
|
||||
search_dialog.class=Clase
|
||||
search_dialog.method=Método
|
||||
@@ -100,10 +108,12 @@ search_dialog.options=Opciones de búsqueda:
|
||||
search_dialog.ignorecase=Ignorar minúsculas/mayúsculas
|
||||
#search_dialog.load_more=Load more
|
||||
#search_dialog.load_all=Load all
|
||||
#search_dialog.stop=Stop
|
||||
#search_dialog.results_incomplete=Found %d+
|
||||
#search_dialog.results_complete=Found %d (complete)
|
||||
search_dialog.col_node=Nodo
|
||||
search_dialog.col_code=Código
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=Regex
|
||||
#search_dialog.active_tab=Active tab only
|
||||
#search_dialog.comments=Comments
|
||||
@@ -146,6 +156,7 @@ preferences.replaceConsts=Reemplazar constantes
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
#preferences.inlineAnonymous=
|
||||
#preferences.inlineMethods=Inline methods
|
||||
#preferences.extractFinally=Extract finally block
|
||||
#preferences.fsCaseSensitive=
|
||||
preferences.skipResourcesDecode=No descodificar recursos
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
@@ -171,6 +182,7 @@ preferences.deobfuscation_min_len=Longitud mínima del nombre
|
||||
preferences.deobfuscation_max_len=Longitud máxima del nombre
|
||||
preferences.deobfuscation_source_alias=Usar el nombre del source como alias para la clase
|
||||
preferences.deobfuscation_kotlin_metadata=Parse Kotlin metadatos para nombres de clase y paquete
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=Guardar
|
||||
preferences.cancel=Cancelar
|
||||
preferences.reset=Reestablecer
|
||||
@@ -298,7 +310,7 @@ certificate.serialPubKeyY=Y
|
||||
#adb_dialog.start_okay=ADB server started on port: %s.
|
||||
#adb_dialog.start_fail=Failed to start ADB server on port: %s!
|
||||
#adb_dialog.forward_fail=Failed to forward for some reasons.
|
||||
#adb_dialog.being_debugged_msg=This process seems like it's being debugged, should we proceed?"
|
||||
#adb_dialog.being_debugged_msg=This process seems like it's being debugged, should we proceed?
|
||||
#adb_dialog.unknown_android_ver=Failed to get Android release version, use Android 8 as default?
|
||||
#adb_dialog.being_debugged_title=It's Debugging by other.
|
||||
#adb_dialog.init_dbg_fail=Failed to init debugger.
|
||||
|
||||
@@ -23,6 +23,7 @@ menu.update_label=새 버전 %s 이(가) 존재합니다!
|
||||
file.open_action=파일 열기 ...
|
||||
file.add_files_action=파일 추가
|
||||
file.open_title=파일 열기
|
||||
#file.open_project=Open project
|
||||
file.new_project=새 프로젝트
|
||||
file.save_project=프로젝트 저장
|
||||
file.save_project_as=다른 이름으로 프로젝트 저장...
|
||||
@@ -35,6 +36,10 @@ file.export_gradle=Gradle 프로젝트로 저장
|
||||
file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택
|
||||
file.exit=나가기
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
tree.sources_title=소스코드
|
||||
tree.resources_title=리소스
|
||||
tree.loading=로딩중...
|
||||
@@ -69,7 +74,7 @@ message.userCancelTask=사용자가 작업을 취소했습니다.
|
||||
message.memoryLow=Jadx의 메모리가 부족합니다. 최대 힙 크기를 늘린 후 다시 시작하십시오.
|
||||
message.taskError=작업이 오류로 실패했습니다. (자세한 내용은 로그 확인)
|
||||
message.errorTitle=오류
|
||||
message.load_errors=로드하지 못했습니다.\n오류 수: %d\n로그 뷰어를 열려면 확인을 클릭하십시오."
|
||||
message.load_errors=로드하지 못했습니다.\n오류 수: %d\n로그 뷰어를 열려면 확인을 클릭하십시오.
|
||||
message.no_classes=로드된 클래스 없음, 디컴파일 대상 존재하지 않음
|
||||
|
||||
message.saveIncomplete=<html>저장이 완료되지 않았습니다.<br> %s<br> %d개의 클래스 또는 리소스가 저장되지 않았습니다!</html>
|
||||
@@ -91,6 +96,9 @@ common_dialog.remove=삭제
|
||||
search_dialog.open=열기
|
||||
search_dialog.cancel=취소
|
||||
search_dialog.open_by_name=텍스트 검색 :
|
||||
#search_dialog.search_button=Search
|
||||
#search_dialog.search_history=Search history
|
||||
#search_dialog.auto_search=Auto search
|
||||
search_dialog.search_in=정의 검색 :
|
||||
search_dialog.class=클래스
|
||||
search_dialog.method=메소드
|
||||
@@ -100,10 +108,12 @@ search_dialog.options=옵션 검색:
|
||||
search_dialog.ignorecase=대소문자 구분 안함
|
||||
#search_dialog.load_more=Load more
|
||||
#search_dialog.load_all=Load all
|
||||
#search_dialog.stop=Stop
|
||||
#search_dialog.results_incomplete=Found %d+
|
||||
#search_dialog.results_complete=Found %d (complete)
|
||||
search_dialog.col_node=노드
|
||||
search_dialog.col_code=코드
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=정규식
|
||||
search_dialog.active_tab=열려 있는 탭에서만 검색
|
||||
search_dialog.comments=주석
|
||||
@@ -146,6 +156,7 @@ preferences.useImports=import 문 사용
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
preferences.inlineAnonymous=인라인 익명 클래스
|
||||
preferences.inlineMethods=인라인 메서드
|
||||
#preferences.extractFinally=Extract finally block
|
||||
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
|
||||
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
@@ -171,6 +182,7 @@ preferences.deobfuscation_min_len=최소 이름 길이
|
||||
preferences.deobfuscation_max_len=최대 이름 길이
|
||||
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
|
||||
preferences.deobfuscation_kotlin_metadata=클래스 및 패키지 이름에 대한 Kotlin 메타 데이터 파싱
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.save=저장
|
||||
preferences.cancel=취소
|
||||
preferences.reset=재설정
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
language.name=Português
|
||||
|
||||
menu.file=Arquivo
|
||||
menu.view=Ver
|
||||
menu.recent_projects=Projetos recentes
|
||||
menu.no_recent_projects=Nenhum projeto recente
|
||||
menu.preferences=Preferências
|
||||
menu.sync=Sincronizar com editor
|
||||
menu.flatten=Mostrar pacotes achatados
|
||||
menu.heapUsageBar=Mostrar uso de memória
|
||||
menu.alwaysSelectOpened=Sempre selecionar arquivo/classe aberta
|
||||
menu.navigation=Navegação
|
||||
menu.text_search=Buscar por texto
|
||||
menu.class_search=Buscar por classe
|
||||
menu.comment_search=Busca por comentário
|
||||
menu.tools=Ferramentas
|
||||
menu.deobfuscation=Desofuscar
|
||||
menu.log=Visualizador de log
|
||||
menu.help=Ajuda
|
||||
menu.about=Sobre
|
||||
menu.update_label=Nova versão %s disponível!
|
||||
|
||||
file.open_action=Abrir arquivos...
|
||||
file.add_files_action=Adicionar arquivos
|
||||
file.open_title=Abrir arquivo
|
||||
#file.open_project=Open project
|
||||
file.new_project=Novo projeto
|
||||
file.save_project=Salvar projeto
|
||||
file.save_project_as=Salvar projeto como...
|
||||
file.reload=Recarregar arquivos
|
||||
file.live_reload=Recarregar em tempo real
|
||||
file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados
|
||||
file.export_mappings_as=Exportar mappings como...
|
||||
file.save_all=Salvar tudo
|
||||
file.export_gradle=Salvar como um projeto gradle
|
||||
file.save_all_msg=Selecionar diretório para salvar arquivos descompilados
|
||||
file.exit=Sair
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
|
||||
tree.sources_title=Código fonte
|
||||
tree.resources_title=Recursos
|
||||
tree.loading=Carregando...
|
||||
|
||||
progress.load=Carregando
|
||||
progress.export_mappings=Exportando mappings
|
||||
progress.decompile=Descompilando
|
||||
progress.canceling=Cancelando
|
||||
|
||||
error_dialog.title=Erro
|
||||
|
||||
search.previous=Anterior
|
||||
search.next=Próximo
|
||||
search.mark_all=Marcar todos
|
||||
search.regex=Regex
|
||||
search.match_case=Match Case
|
||||
search.whole_word=Palavra inteira
|
||||
search.find=Encontrar
|
||||
|
||||
tabs.copy_class_name=Copiar nome
|
||||
tabs.close=Fechar
|
||||
tabs.closeOthers=Fechar outros
|
||||
tabs.closeAll=Fechar todos
|
||||
tabs.code=Código
|
||||
tabs.smali=Smali
|
||||
|
||||
nav.back=Voltar
|
||||
nav.forward=Avançar
|
||||
|
||||
message.taskTimeout=Tarefa excedeu limite de tempo de %d ms.
|
||||
message.userCancelTask=Tarefa foi cancelada pelo usuário.
|
||||
message.memoryLow=Jadx está rodando com pouca memória. Por favor reinicie com um limite de memória heap maior.
|
||||
message.taskError=Tarefa falhou com erro (cheque o log para detalhes).
|
||||
message.errorTitle=Erro
|
||||
message.load_errors=Carregamento falhou.\nNúmero de erros: %d\nClique em ok para abrir o visualizador de log.
|
||||
message.no_classes=Nenhuma classe carregada, nada para ser descompilado!
|
||||
|
||||
message.saveIncomplete=<html>Operação não foi completa.<br> %s<br> %d classes ou recursos não foram salvos!</html>
|
||||
message.indexIncomplete=<html>Indexação de algumas classes foram ignoradas.<br> %s<br> %d classes não foram indexadas não vão aparecer nos resultados de busca!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx está rodando com pouca memória. Por conta disso, %d classes não foram indexadas.<br>Se você deseja que todas classes sejam indexadas, reinicie com um limite de memória heap maior.</html>
|
||||
|
||||
heapUsage.text=Uso de memória do JADX: %.2f GB of %.2f GB
|
||||
|
||||
common_dialog.ok=Ok
|
||||
common_dialog.cancel=Cancelar
|
||||
common_dialog.add=Adicionar
|
||||
common_dialog.update=Atualizar
|
||||
common_dialog.remove=Remover
|
||||
|
||||
file_dialog.supported_files=Arquivos suportados
|
||||
file_dialog.load_dir_title=Carregar diretório
|
||||
file_dialog.load_dir_confirm=Carregar todos arquivos do diretório?
|
||||
|
||||
search_dialog.open=Abrir
|
||||
search_dialog.cancel=Cancelar
|
||||
search_dialog.open_by_name=Buscar por text:
|
||||
#search_dialog.search_button=Search
|
||||
#search_dialog.search_history=Search history
|
||||
#search_dialog.auto_search=Auto search
|
||||
search_dialog.search_in=Buscar definições de:
|
||||
search_dialog.class=Classe
|
||||
search_dialog.method=Método
|
||||
search_dialog.field=Propriedade
|
||||
search_dialog.code=Código
|
||||
search_dialog.options=Opções de busca:
|
||||
search_dialog.ignorecase=Não diferencia maiúsculas de minúsculas
|
||||
search_dialog.load_more=Carregar mais
|
||||
search_dialog.load_all=Carregar todas
|
||||
#search_dialog.stop=Stop
|
||||
search_dialog.results_incomplete=Encontradas %d+
|
||||
search_dialog.results_complete=Encontradas %d (complete)
|
||||
search_dialog.col_node=Nó
|
||||
search_dialog.col_code=Código
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.active_tab=Apenas abas ativas
|
||||
search_dialog.comments=Comentários
|
||||
search_dialog.resource=Recursos
|
||||
search_dialog.keep_open=Manter aberto
|
||||
search_dialog.tip_searching=Buscando
|
||||
|
||||
usage_dialog.title=Busca por utilização
|
||||
usage_dialog.label=Usado por:
|
||||
|
||||
comment_dialog.title.add=Adicionar comentário ao código
|
||||
comment_dialog.title.update=Atualizar comentário do código
|
||||
comment_dialog.label=Comentário:
|
||||
comment_dialog.usage=Use Shift + Enter para pular uma linha
|
||||
|
||||
log_viewer.title=Visualizador de log
|
||||
log_viewer.log_level=Nível do log:
|
||||
|
||||
about_dialog.title=Sobre o JADX
|
||||
|
||||
preferences.title=Preferências
|
||||
preferences.deobfuscation=Desofuscar
|
||||
preferences.appearance=Aparência
|
||||
preferences.decompile=Descompilação
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Projeto
|
||||
preferences.other=Outro
|
||||
preferences.language=Idioma
|
||||
preferences.lineNumbersMode=Modo do contador de linhas do editor
|
||||
preferences.jumpOnDoubleClick=Ativar salto no duplo clique
|
||||
preferences.check_for_updates=Verificar por atualizações ao inicializar
|
||||
preferences.useDx=Usar dx/d8 para converter bytecode Java
|
||||
preferences.decompilationMode=Modo de descompilação
|
||||
preferences.codeCacheMode=Modo de cachê do código
|
||||
preferences.showInconsistentCode=Mostrar código inconsistent
|
||||
preferences.escapeUnicode=Escapar unicode
|
||||
preferences.replaceConsts=Substituir constantes
|
||||
preferences.respectBytecodeAccessModifiers=Respeitar modificadores de acesso do bytecode
|
||||
preferences.useImports=Utilizar declaração de imports
|
||||
preferences.useDebugInfo=Utilizar informação de depuração
|
||||
preferences.inlineAnonymous=Classes anônimas de uma linha
|
||||
preferences.inlineMethods=Métodos de uma linha
|
||||
preferences.extractFinally=Extrair blocos finally
|
||||
preferences.fsCaseSensitive=Sistema de arquivo diferencia maiúsculas de minúsculas
|
||||
preferences.skipResourcesDecode=Não decodificar recursos
|
||||
preferences.useKotlinMethodsForVarNames=Usar métodos do kotlin para renomear variáveis
|
||||
preferences.commentsLevel=Nível de comentários do código
|
||||
preferences.autoSave=Salvar automaticamente
|
||||
preferences.threads=Número de threads no processo
|
||||
preferences.excludedPackages=Pacotes ignorados
|
||||
preferences.excludedPackages.tooltip=Lista espaço de pacotes que não vão ser descompilados ou indexados (economiza RAM)
|
||||
preferences.excludedPackages.button=Editar
|
||||
preferences.excludedPackages.editDialog=<html>Lista espaço de pacotes que não vão ser descompilados ou indexados (economiza RAM)<br>ex: <code>android.support</code></html>
|
||||
preferences.cfg=Gera gráficos de métodos CFG no formato de pontos ('dot')
|
||||
preferences.raw_cfg=Gera gráficos CFG no formato RAW
|
||||
preferences.font=Fonte do editor
|
||||
preferences.smali_font=Fonte do editor de smali
|
||||
preferences.laf_theme=Tema
|
||||
preferences.theme=Tema do editor
|
||||
preferences.start_jobs=Inicializar descompilação automaticamente em segundo-plano
|
||||
preferences.select_font=Alterar
|
||||
preferences.select_smali_font=Alterar
|
||||
preferences.deobfuscation_on=Ativar desofuscação
|
||||
preferences.deobfuscation_map_file_mode=Modo do arquivo Map
|
||||
preferences.deobfuscation_min_len=Tamanho mínimo do nome
|
||||
preferences.deobfuscation_max_len=Tamanho máximo do nome
|
||||
preferences.deobfuscation_source_alias=Utilizar nome do arquivo como apelido da classe
|
||||
preferences.deobfuscation_kotlin_metadata=Parsear metadados do kotlin para nome de classes e pacotes
|
||||
preferences.deobfuscation_res_name_source=Melhora nome da fonte dos recursos
|
||||
preferences.save=Salvar
|
||||
preferences.cancel=Cancelar
|
||||
preferences.reset=Redefinir
|
||||
preferences.reset_message=Redefinir configurações para o padrão?
|
||||
preferences.reset_title=Redefinir configurações
|
||||
preferences.copy=Copy to clipboard
|
||||
preferences.copy_message=Todas configurações foram copiadas para área de transferência
|
||||
preferences.rename=Renomear identificadores
|
||||
preferences.rename_case=Corrigir problemas de capitalização (case sensitivity)
|
||||
preferences.rename_valid=Deixá-las válidas
|
||||
preferences.rename_printable=Deixá-las imprimíveis (printable)
|
||||
preferences.search_res_title=Buscar recursos
|
||||
preferences.res_file_ext=Extensões de arquivos (ex: .xml|.html), * significa todas
|
||||
preferences.res_skip_file=Pular arquivos excedidos
|
||||
|
||||
msg.open_file=Abra um arquivo
|
||||
msg.saving_sources=Salvando recursos
|
||||
msg.language_changed_title=Idioma alterado
|
||||
msg.language_changed=Novo idioma será mostrado na próxima inicialização.
|
||||
msg.project_error_title=Erro
|
||||
msg.project_error=Projeto não pôde ser carregado
|
||||
msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe.
|
||||
msg.cant_add_comment=Não é possível adicionar comentários aqui
|
||||
|
||||
popup.bytecode_col=Mostrar Dalvik Bytecode
|
||||
popup.line_wrap=Quebra de linha
|
||||
popup.undo=Desfazer
|
||||
popup.redo=Refazer
|
||||
popup.cut=Cortar
|
||||
popup.copy=Copiar
|
||||
popup.paste=Colar
|
||||
popup.delete=Apagar
|
||||
popup.select_all=Selecionar todos
|
||||
popup.frida=Copiar como snippet frida
|
||||
popup.xposed=Copiar como snippet xposed
|
||||
popup.find_usage=Buscar uso
|
||||
popup.go_to_declaration=Ir para declaração
|
||||
popup.exclude=Ignorar
|
||||
popup.exclude_packages=Pacotes ignorados
|
||||
popup.add_comment=Comentar
|
||||
popup.search_comment=Buscar comentários
|
||||
popup.rename=Renomear
|
||||
popup.search=Buscar "%s"
|
||||
popup.search_global=Busca global "%s"
|
||||
|
||||
exclude_dialog.title=Selecionar pacote
|
||||
exclude_dialog.ok=OK
|
||||
exclude_dialog.select_all=Selecionar tudo
|
||||
exclude_dialog.deselect=Remover seleção
|
||||
exclude_dialog.invert=Inverter
|
||||
|
||||
confirm.save_as_title=Confirmar operação
|
||||
confirm.save_as_message=%s Já existe.\nVocê deseja substituir?
|
||||
confirm.not_saved_title=Salvar projeto
|
||||
confirm.not_saved_message=Salvar projeto atual antes de continuar?
|
||||
|
||||
certificate.cert_type=Tipo
|
||||
certificate.serialSigVer=Versão
|
||||
certificate.serialNumber=Número de série (serial)
|
||||
certificate.cert_subject=Remetente
|
||||
certificate.serialValidFrom=Válido de
|
||||
certificate.serialValidUntil=Válido até
|
||||
certificate.serialPubKeyType=Tipo de chave pública
|
||||
certificate.serialPubKeyExponent=Expoente
|
||||
certificate.serialPubKeyModulus=Módulo
|
||||
certificate.serialPubKeyModulusSize=Tamanho do módulo (bits)
|
||||
certificate.serialSigType=Tipo de assinatura
|
||||
certificate.serialSigOID=Assinatura OID
|
||||
certificate.serialMD5=Assinatura digital MD5
|
||||
certificate.serialSHA1=Assinatura digital SHA-1
|
||||
certificate.serialSHA256=Assinatura digital SHA-256
|
||||
certificate.serialPubKeyY=Y
|
||||
|
||||
apkSignature.signer=Assinador
|
||||
apkSignature.verificationSuccess=Verificação de assinatura bem-sucedida
|
||||
apkSignature.verificationFailed=Verificação de assinatura falhou
|
||||
apkSignature.signatureSuccess=Assinatura válida de apk v%d encontrada
|
||||
apkSignature.signatureFailed=Assinatura inválida de apk v%d encontrada
|
||||
apkSignature.errors=Erros
|
||||
apkSignature.warnings=Avisos
|
||||
apkSignature.exception=Verificação do APK falhou
|
||||
apkSignature.unprotectedEntry=Arquivos que não são protegidos pela assinatura v1. Modificações não autorizadas a essas entradas só podem ser detectadas por uma assinatura de versão 2 ou maior.
|
||||
|
||||
issues_panel.label=Problemas:
|
||||
issues_panel.errors=%d error
|
||||
issues_panel.warnings=%d avisos
|
||||
issues_panel.tooltip=Abrir no visualizador de log
|
||||
|
||||
debugger.process_selector=Selecionar um processo para depurar
|
||||
debugger.step_into=Passar para próxima chamada de função (F7)
|
||||
debugger.step_over=Passar sobre próxima chamada de função (F8)
|
||||
debugger.step_out=Sair da função atual (Shift + F8)
|
||||
debugger.run=Executar (F9)
|
||||
debugger.stop=Parar depurador e matar o aplicativo
|
||||
debugger.pause=Pausar
|
||||
debugger.rerun=Reexecutar
|
||||
debugger.cfm_dialog_title=Sair enquanto depura
|
||||
debugger.cfm_dialog_msg=Você tem certeza de que deseja terminar o depurador?
|
||||
|
||||
debugger.popup_set_value=Definir valor
|
||||
debugger.popup_change_to_zero=Alterar para 0
|
||||
debugger.popup_change_to_one=Alterar para 1
|
||||
debugger.popup_copy_value=Copiar valor
|
||||
|
||||
set_value_dialog.label_value=Valor
|
||||
set_value_dialog.btn_set=Definir Valor
|
||||
set_value_dialog.title=Definir Valor
|
||||
set_value_dialog.neg_msg=Falha ao definir valor.
|
||||
set_value_dialog.sel_type=Selecionar um tipo para definir valor.
|
||||
|
||||
adb_dialog.addr=Endereço do ADB
|
||||
adb_dialog.port=Porta do ADB
|
||||
adb_dialog.path=Caminho do ADB
|
||||
adb_dialog.launch_app=Iniciar Aplicativo
|
||||
adb_dialog.start_server=Iniciar servidor ADB
|
||||
adb_dialog.refresh=Recarregar
|
||||
adb_dialog.tip_devices=%d dispositivos
|
||||
adb_dialog.device_node=dispositivo
|
||||
adb_dialog.missing_path=Caminho do ADB deve ser especificado para iniciar o servidor.
|
||||
adb_dialog.waiting=Aguardando para conectar no servidor ADB...
|
||||
adb_dialog.connecting=Conectando ao servidor ADB, endereço: %s:%s...
|
||||
adb_dialog.connect_okay=Servidor ADB conectado, endereço: %s:%s
|
||||
adb_dialog.connect_fail=Falha ao se conectar ao servidor ADB.
|
||||
adb_dialog.disconnected=Servidor ADB desconectado.
|
||||
adb_dialog.start_okay=Servidor ADB inicializado na porta: %s.
|
||||
adb_dialog.start_fail=Falha ao iniciar servidor ADB na porta: %s!
|
||||
adb_dialog.forward_fail=Falha ao encaminhar por um motivo não especificado.
|
||||
adb_dialog.being_debugged_msg=Esse processo parece estar sendo depurado, deseja continuar?
|
||||
adb_dialog.unknown_android_ver=Falha ao obter versão do Android, deseja usar o Android 8 como padrão?
|
||||
adb_dialog.being_debugged_title=Está sendo depurado por outro processo.
|
||||
adb_dialog.init_dbg_fail=Falha ao iniciar depurador.
|
||||
adb_dialog.msg_read_mani_fail=Falha ao decodificar AndroidManifest.xml
|
||||
adb_dialog.no_devices=Não foi possível encontrar nenhum dispositivo para iniciar o aplicativo.
|
||||
adb_dialog.restart_while_debugging_title=Reiniciar enquanto depura
|
||||
adb_dialog.restart_while_debugging_msg=Você está depurando um aplicativo, você tem certeza que deseja reiniciar a sessão?
|
||||
adb_dialog.starting_debugger=Iniciando depurador...
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user