Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6844a46c93 | |||
| e9e45707da | |||
| b9d02ff4c4 | |||
| 29b64300bc | |||
| 777355e86e | |||
| 620a177ce8 | |||
| 683c2dfbeb | |||
| 266cbcc6f4 | |||
| 8a45602ae6 | |||
| 711419a797 | |||
| 603f3057eb | |||
| fa6fc1f871 | |||
| 49fa320989 | |||
| 2f301bf150 | |||
| b4892ce17f | |||
| 151c171616 | |||
| 79477a2de3 | |||
| 78aadda931 | |||
| b50706505f | |||
| 9114821fb1 | |||
| 1195582da8 | |||
| 258987b0ff | |||
| a6a734c70d | |||
| d6c23a2a9b | |||
| db028904d7 |
@@ -57,7 +57,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: oracle-actions/setup-java@v1 # set latest java version by default
|
||||
uses: oracle-actions/setup-java@v1
|
||||
with:
|
||||
release: 18
|
||||
|
||||
- name: Print Java version
|
||||
shell: bash
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
|
||||
@@ -6,5 +6,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.diffplug.spotless' version '6.9.1'
|
||||
id 'com.github.ben-manes.versions' version '0.43.0'
|
||||
id 'com.diffplug.spotless' version '6.11.0'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -26,16 +26,16 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.3'
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.7.0'
|
||||
testImplementation 'org.mockito:mockito-core:4.8.0'
|
||||
testImplementation 'org.assertj:assertj-core:3.23.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
|
||||
|
||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies {
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
implementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
}
|
||||
|
||||
application {
|
||||
|
||||
@@ -8,8 +8,8 @@ dependencies {
|
||||
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.2-7984345'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.5' // forcing latest version
|
||||
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.21.8' // forcing latest version
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
|
||||
@@ -19,7 +19,7 @@ dependencies {
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
|
||||
|
||||
testImplementation 'org.eclipse.jdt:ecj:3.30.0'
|
||||
testImplementation 'org.eclipse.jdt:ecj:3.31.0'
|
||||
testImplementation 'tools.profiler:async-profiler:1.8.3'
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ public class ClassGen {
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !cls.isEnum()) {
|
||||
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
@@ -322,19 +322,25 @@ public class ClassGen {
|
||||
if (inlineAttr == null || inlineAttr.notNeeded()) {
|
||||
return false;
|
||||
}
|
||||
if (mth.getUseIn().isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
try {
|
||||
if (mth.getUseIn().isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||
.collect(Collectors.toList());
|
||||
if (useInCompleted.isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
// check failed => keep method
|
||||
mth.addWarnComment("Failed to check method usage", e);
|
||||
return false;
|
||||
}
|
||||
List<MethodNode> useInCompleted = mth.getUseIn().stream()
|
||||
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
|
||||
.collect(Collectors.toList());
|
||||
if (useInCompleted.isEmpty()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
return true;
|
||||
}
|
||||
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isMethodsPresents() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ConditionGen extends InsnGen {
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||
private final Queue<IfCondition> stack = new ArrayDeque<>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
|
||||
@@ -21,6 +21,7 @@ public enum AFlag {
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||
REMOVE, // can be completely removed
|
||||
REMOVE_SUPER_CLASS, // don't add super class
|
||||
|
||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
public class PhiListAttr implements IJadxAttribute {
|
||||
|
||||
private final List<PhiInsn> list = new LinkedList<>();
|
||||
private final List<PhiInsn> list = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public AType<PhiListAttr> getAttrType() {
|
||||
|
||||
@@ -805,7 +805,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
}
|
||||
|
||||
public void addCodegenDep(ClassNode dep) {
|
||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||
if (!codegenDeps.contains(dep)) {
|
||||
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalDepsCount() {
|
||||
|
||||
@@ -156,7 +156,7 @@ public final class IfCondition extends AttrNode {
|
||||
return i;
|
||||
}
|
||||
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
|
||||
cond = not(new IfCondition(c.invert()));
|
||||
cond = new IfCondition(Mode.NOT, Collections.singletonList(new IfCondition(c.invert())));
|
||||
} else {
|
||||
c.normalize();
|
||||
}
|
||||
|
||||
@@ -62,45 +62,47 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
|| insn.getResult() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SSAVar sVar = insn.getResult().getSVar();
|
||||
InsnArg constArg;
|
||||
Runnable onSuccess = null;
|
||||
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
|
||||
constArg = insn.getArg(0);
|
||||
if (!constArg.isLiteral()) {
|
||||
switch (insn.getType()) {
|
||||
case CONST:
|
||||
case MOVE: {
|
||||
constArg = insn.getArg(0);
|
||||
if (!constArg.isLiteral()) {
|
||||
return;
|
||||
}
|
||||
long lit = ((LiteralArg) constArg).getLiteral();
|
||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
||||
// all usages forbids inlining
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONST_STR: {
|
||||
String s = ((ConstStringNode) insn).getString();
|
||||
FieldNode f = mth.getParentClass().getConstField(s);
|
||||
if (f == null) {
|
||||
InsnNode copy = insn.copyWithoutResult();
|
||||
constArg = InsnArg.wrapArg(copy);
|
||||
} else {
|
||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||
constArg = InsnArg.wrapArg(constGet);
|
||||
constArg.setType(ArgType.STRING);
|
||||
onSuccess = () -> f.addUseIn(mth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONST_CLASS: {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||
constArg.setType(ArgType.CLASS);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
long lit = ((LiteralArg) constArg).getLiteral();
|
||||
if (lit == 0 && forbidNullInlines(sVar)) {
|
||||
// all usages forbids inlining
|
||||
return;
|
||||
}
|
||||
} else if (insnType == InsnType.CONST_STR) {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
String s = ((ConstStringNode) insn).getString();
|
||||
FieldNode f = mth.getParentClass().getConstField(s);
|
||||
if (f == null) {
|
||||
InsnNode copy = insn.copyWithoutResult();
|
||||
constArg = InsnArg.wrapArg(copy);
|
||||
} else {
|
||||
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
|
||||
constArg = InsnArg.wrapArg(constGet);
|
||||
constArg.setType(ArgType.STRING);
|
||||
onSuccess = () -> f.addUseIn(mth);
|
||||
}
|
||||
} else if (insnType == InsnType.CONST_CLASS) {
|
||||
if (sVar.isUsedInPhi()) {
|
||||
return;
|
||||
}
|
||||
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
|
||||
constArg.setType(ArgType.CLASS);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// all check passed, run replace
|
||||
|
||||
@@ -37,9 +37,13 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockInsnPair;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
@@ -47,6 +51,7 @@ import jadx.core.utils.InsnRemover;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.InsnUtils.checkInsnType;
|
||||
import static jadx.core.utils.InsnUtils.getSingleArg;
|
||||
@@ -55,8 +60,16 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class },
|
||||
runBefore = { ExtractFieldInit.class }
|
||||
runAfter = {
|
||||
CodeShrinkVisitor.class, // all possible instructions already inlined
|
||||
ModVisitor.class,
|
||||
ReSugarCode.class,
|
||||
IfRegionVisitor.class, // ternary operator inlined
|
||||
CheckRegions.class // regions processing finished
|
||||
},
|
||||
runBefore = {
|
||||
ExtractFieldInit.class
|
||||
}
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@@ -81,82 +94,67 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
boolean converted;
|
||||
try {
|
||||
converted = convertToEnum(cls);
|
||||
} catch (Exception e) {
|
||||
cls.addWarnComment("Enum visitor error", e);
|
||||
converted = false;
|
||||
}
|
||||
if (!converted) {
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (accessFlags.isEnum()) {
|
||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed");
|
||||
if (cls.isEnum()) {
|
||||
boolean converted;
|
||||
try {
|
||||
converted = convertToEnum(cls);
|
||||
} catch (Exception e) {
|
||||
cls.addWarnComment("Enum visitor error", e);
|
||||
converted = false;
|
||||
}
|
||||
if (!converted) {
|
||||
AccessInfo accessFlags = cls.getAccessFlags();
|
||||
if (accessFlags.isEnum()) {
|
||||
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
|
||||
cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean convertToEnum(ClassNode cls) {
|
||||
if (!cls.isEnum()) {
|
||||
return false;
|
||||
ArgType superType = cls.getSuperClass();
|
||||
if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
cls.add(AFlag.REMOVE_SUPER_CLASS);
|
||||
}
|
||||
MethodNode classInitMth = cls.getClassInitMth();
|
||||
if (classInitMth == null) {
|
||||
cls.addWarnComment("Enum class init method not found");
|
||||
return false;
|
||||
}
|
||||
if (classInitMth.getBasicBlocks().isEmpty()) {
|
||||
Region staticRegion = classInitMth.getRegion();
|
||||
if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
|
||||
// search "$VALUES" field (holds all enum values)
|
||||
List<FieldNode> valuesCandidates = cls.getFields().stream()
|
||||
.filter(f -> f.getAccessFlags().isStatic())
|
||||
.filter(f -> f.getType().isArray())
|
||||
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (valuesCandidates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
||||
if (valuesOpt.isPresent()) {
|
||||
valuesCandidates.clear();
|
||||
valuesCandidates.add(valuesOpt.get());
|
||||
// collect blocks on linear part of static method (ignore branching on method end)
|
||||
List<BlockNode> staticBlocks = new ArrayList<>();
|
||||
for (IContainer subBlock : staticRegion.getSubBlocks()) {
|
||||
if (subBlock instanceof BlockNode) {
|
||||
staticBlocks.add((BlockNode) subBlock);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valuesCandidates.size() != 1) {
|
||||
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
||||
if (staticBlocks.isEmpty()) {
|
||||
cls.addWarnComment("Unexpected branching in enum static init block");
|
||||
return false;
|
||||
}
|
||||
FieldNode valuesField = valuesCandidates.get(0);
|
||||
List<InsnNode> toRemove = new ArrayList<>();
|
||||
|
||||
// search "$VALUES" array init and collect enum fields
|
||||
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
|
||||
if (valuesInitPair == null) {
|
||||
EnumData data = new EnumData(cls, classInitMth, staticBlocks);
|
||||
if (!searchValuesField(data)) {
|
||||
return false;
|
||||
}
|
||||
BlockNode staticBlock = valuesInitPair.getBlock();
|
||||
InsnNode valuesInitInsn = valuesInitPair.getInsn();
|
||||
|
||||
List<EnumField> enumFields = null;
|
||||
InsnArg arrArg = valuesInitInsn.getArg(0);
|
||||
InsnArg arrArg = data.valuesInitInsn.getArg(0);
|
||||
if (arrArg.isInsnWrap()) {
|
||||
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
|
||||
enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
||||
enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
|
||||
}
|
||||
if (enumFields == null) {
|
||||
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
|
||||
return false;
|
||||
}
|
||||
toRemove.add(valuesInitInsn);
|
||||
data.toRemove.add(data.valuesInitInsn);
|
||||
|
||||
// all checks complete, perform transform
|
||||
EnumClassAttr attr = new EnumClassAttr(enumFields);
|
||||
@@ -176,59 +174,92 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
fieldNode.getFieldInfo().setAlias(name);
|
||||
}
|
||||
fieldNode.add(AFlag.DONT_GENERATE);
|
||||
processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove);
|
||||
processConstructorInsn(data, enumField, classInitMth);
|
||||
}
|
||||
valuesField.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove);
|
||||
data.valuesField.add(AFlag.DONT_GENERATE);
|
||||
InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove);
|
||||
if (classInitMth.countInsns() == 0) {
|
||||
classInitMth.add(AFlag.DONT_GENERATE);
|
||||
} else if (!toRemove.isEmpty()) {
|
||||
} else if (!data.toRemove.isEmpty()) {
|
||||
CodeShrinkVisitor.shrinkMethod(classInitMth);
|
||||
}
|
||||
removeEnumMethods(cls, clsType, valuesField);
|
||||
removeEnumMethods(cls, data.valuesField);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth,
|
||||
BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
ClassInfo enumClsInfo = co.getClassType();
|
||||
if (!enumClsInfo.equals(cls.getClassInfo())) {
|
||||
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
|
||||
if (enumCls != null) {
|
||||
processEnumCls(cls, enumField, enumCls);
|
||||
/**
|
||||
* Search "$VALUES" field (holds all enum values)
|
||||
*/
|
||||
private boolean searchValuesField(EnumData data) {
|
||||
ArgType clsType = data.cls.getClassInfo().getType();
|
||||
List<FieldNode> valuesCandidates = data.cls.getFields().stream()
|
||||
.filter(f -> f.getAccessFlags().isStatic())
|
||||
.filter(f -> f.getType().isArray())
|
||||
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (valuesCandidates.isEmpty()) {
|
||||
data.cls.addWarnComment("$VALUES field not found");
|
||||
return false;
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
|
||||
}
|
||||
if (valuesCandidates.size() > 1) {
|
||||
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
|
||||
if (valuesOpt.isPresent()) {
|
||||
valuesCandidates.clear();
|
||||
valuesCandidates.add(valuesOpt.get());
|
||||
}
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
|
||||
if (valuesCandidates.size() != 1) {
|
||||
data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
|
||||
return false;
|
||||
}
|
||||
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
|
||||
data.valuesField = valuesCandidates.get(0);
|
||||
|
||||
// search "$VALUES" array init and collect enum fields
|
||||
BlockInsnPair valuesInitPair = getValuesInitInsn(data);
|
||||
if (valuesInitPair == null) {
|
||||
return false;
|
||||
}
|
||||
data.valuesInitInsn = valuesInitPair.getInsn();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) {
|
||||
ConstructorInsn co = enumField.getConstrInsn();
|
||||
ClassInfo enumClsInfo = co.getClassType();
|
||||
if (!enumClsInfo.equals(data.cls.getClassInfo())) {
|
||||
ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo);
|
||||
if (enumCls != null) {
|
||||
processEnumCls(data.cls, enumField, enumCls);
|
||||
}
|
||||
}
|
||||
MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth());
|
||||
if (ctrMth != null) {
|
||||
markArgsForSkip(ctrMth);
|
||||
}
|
||||
RegisterArg coResArg = co.getResult();
|
||||
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
|
||||
toRemove.add(co);
|
||||
data.toRemove.add(co);
|
||||
} else {
|
||||
// constructor result used in other places -> replace constructor with enum field get (SGET)
|
||||
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
|
||||
enumGet.setResult(coResArg.duplicate());
|
||||
BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet);
|
||||
BlockUtils.replaceInsn(classInitMth, co, enumGet);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock,
|
||||
InsnNode wrappedInsn, List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) {
|
||||
switch (wrappedInsn.getType()) {
|
||||
case FILLED_NEW_ARRAY:
|
||||
return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove);
|
||||
return extractEnumFieldsFromFilledArray(enumData, wrappedInsn);
|
||||
|
||||
case INVOKE:
|
||||
// handle redirection of values array fill (added in java 15)
|
||||
return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove);
|
||||
return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn);
|
||||
|
||||
case NEW_ARRAY:
|
||||
InsnArg arg = wrappedInsn.getArg(0);
|
||||
@@ -243,10 +274,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock,
|
||||
InvokeNode invokeNode, List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) {
|
||||
MethodInfo callMth = invokeNode.getCallMth();
|
||||
MethodNode valuesMth = cls.root().resolveMethod(callMth);
|
||||
MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth);
|
||||
if (valuesMth == null || valuesMth.isVoidReturn()) {
|
||||
return null;
|
||||
}
|
||||
@@ -256,16 +286,16 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (wrappedInsn == null) {
|
||||
return null;
|
||||
}
|
||||
List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
|
||||
List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
|
||||
if (enumFields != null) {
|
||||
valuesMth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
return enumFields;
|
||||
}
|
||||
|
||||
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
|
||||
FieldInfo searchField = valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
|
||||
private BlockInsnPair getValuesInitInsn(EnumData data) {
|
||||
FieldInfo searchField = data.valuesField.getFieldInfo();
|
||||
for (BlockNode blockNode : data.staticBlocks) {
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
|
||||
@@ -279,50 +309,49 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock,
|
||||
List<InsnNode> toRemove) {
|
||||
private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) {
|
||||
List<EnumField> enumFields = new ArrayList<>();
|
||||
for (InsnArg arg : arrFillInsn.getArguments()) {
|
||||
EnumField field = null;
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove);
|
||||
field = processEnumFieldByWrappedInsn(enumData, wrappedInsn);
|
||||
} else if (arg.isRegister()) {
|
||||
field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove);
|
||||
field = processEnumFieldByRegister(enumData, (RegisterArg) arg);
|
||||
}
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
enumFields.add(field);
|
||||
}
|
||||
toRemove.add(arrFillInsn);
|
||||
enumData.toRemove.add(arrFillInsn);
|
||||
return enumFields;
|
||||
}
|
||||
|
||||
private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) {
|
||||
if (wrappedInsn.getType() == InsnType.SGET) {
|
||||
return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove);
|
||||
return processEnumFieldByField(data, wrappedInsn);
|
||||
}
|
||||
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
|
||||
if (constructorInsn != null) {
|
||||
FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset());
|
||||
cls.addField(enumFieldNode);
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn);
|
||||
FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset());
|
||||
data.cls.addField(enumFieldNode);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) {
|
||||
if (sgetInsn.getType() != InsnType.SGET) {
|
||||
return null;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
|
||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
||||
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||
if (enumFieldNode == null) {
|
||||
return null;
|
||||
}
|
||||
InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode);
|
||||
InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode);
|
||||
if (sputInsn == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -333,17 +362,17 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
RegisterArg sgetResult = sgetInsn.getResult();
|
||||
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
|
||||
toRemove.add(sgetInsn);
|
||||
data.toRemove.add(sgetInsn);
|
||||
}
|
||||
toRemove.add(sputInsn);
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, co);
|
||||
data.toRemove.add(sputInsn);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, co);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) {
|
||||
private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) {
|
||||
InsnNode assignInsn = arg.getAssignInsn();
|
||||
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
|
||||
return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove);
|
||||
return processEnumFieldByField(data, assignInsn);
|
||||
}
|
||||
|
||||
SSAVar ssaVar = arg.getSVar();
|
||||
@@ -354,12 +383,12 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
|
||||
return null;
|
||||
}
|
||||
FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove);
|
||||
FieldNode enumFieldNode = searchEnumField(data, ssaVar);
|
||||
if (enumFieldNode == null) {
|
||||
enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum());
|
||||
cls.addField(enumFieldNode);
|
||||
enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum());
|
||||
data.cls.addField(enumFieldNode);
|
||||
}
|
||||
return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
||||
return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn);
|
||||
}
|
||||
|
||||
private FieldNode createFakeField(ClassNode cls, String name) {
|
||||
@@ -372,17 +401,17 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) {
|
||||
private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) {
|
||||
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
|
||||
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
|
||||
return null;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode enumFieldNode = cls.searchField(fieldInfo);
|
||||
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
|
||||
if (enumFieldNode == null) {
|
||||
return null;
|
||||
}
|
||||
toRemove.add(sputInsn);
|
||||
data.toRemove.add(sputInsn);
|
||||
return enumFieldNode;
|
||||
}
|
||||
|
||||
@@ -409,24 +438,32 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (ctrMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> regs = new ArrayList<>();
|
||||
co.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables");
|
||||
}
|
||||
return new EnumField(enumFieldNode, co);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) {
|
||||
for (InsnNode sputInsn : staticBlock.getInstructions()) {
|
||||
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode fieldNode = cls.searchField(f);
|
||||
if (Objects.equals(fieldNode, enumFieldNode)) {
|
||||
return sputInsn;
|
||||
private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) {
|
||||
for (BlockNode block : data.staticBlocks) {
|
||||
for (InsnNode sputInsn : block.getInstructions()) {
|
||||
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
|
||||
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
|
||||
FieldNode fieldNode = data.cls.searchField(f);
|
||||
if (Objects.equals(fieldNode, enumFieldNode)) {
|
||||
return sputInsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
|
||||
private void removeEnumMethods(ClassNode cls, FieldNode valuesField) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||
MethodNode valuesMethod = null;
|
||||
// remove compiler generated methods
|
||||
@@ -605,4 +642,19 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class EnumData {
|
||||
final ClassNode cls;
|
||||
final MethodNode classInitMth;
|
||||
final List<BlockNode> staticBlocks;
|
||||
final List<InsnNode> toRemove = new ArrayList<>();
|
||||
FieldNode valuesField;
|
||||
InsnNode valuesInitInsn;
|
||||
|
||||
public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) {
|
||||
this.cls = cls;
|
||||
this.classInitMth = classInitMth;
|
||||
this.staticBlocks = staticBlocks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -45,7 +45,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
||||
}
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
|
||||
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
|
||||
return isSynthetic && canInlineMethod(mth, accessFlags);
|
||||
}
|
||||
|
||||
private static boolean canInlineMethod(MethodNode mth, AccessInfo accessFlags) {
|
||||
if (accessFlags.isStatic()) {
|
||||
return true;
|
||||
}
|
||||
return mth.isConstructor() && mth.root().getArgs().isInlineAnonymousClasses();
|
||||
}
|
||||
|
||||
private static void fixClassDependencies(MethodNode mth) {
|
||||
@@ -54,8 +61,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
||||
// remove possible cross dependency
|
||||
// to force class with inline method to be processed before its usage
|
||||
ClassNode useTopCls = useInMth.getTopParentClass();
|
||||
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
|
||||
useTopCls.addCodegenDep(parentClass);
|
||||
if (useTopCls != parentClass) {
|
||||
parentClass.removeDependency(useTopCls);
|
||||
useTopCls.addCodegenDep(parentClass);
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
parentClass.addDebugComment("Remove dependency: " + useTopCls + " to inline " + mth);
|
||||
useTopCls.addDebugComment("Add dependency: " + parentClass + " to inline " + mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.visitors.blocks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -432,7 +431,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
BlockNode loopHeader = loop.getStart();
|
||||
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||
if (preds.size() > 2) {
|
||||
List<BlockNode> blocks = new LinkedList<>(preds);
|
||||
List<BlockNode> blocks = new ArrayList<>(preds);
|
||||
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||
BlockNode first = blocks.remove(0);
|
||||
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||
|
||||
@@ -19,6 +19,10 @@ public class DepthRegionTraversal {
|
||||
traverseInternal(mth, visitor, mth.getRegion());
|
||||
}
|
||||
|
||||
public static void traverse(MethodNode mth, IContainer container, IRegionVisitor visitor) {
|
||||
traverseInternal(mth, visitor, container);
|
||||
}
|
||||
|
||||
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
|
||||
boolean repeat;
|
||||
int k = 0;
|
||||
|
||||
@@ -3,8 +3,10 @@ package jadx.core.dex.visitors.regions;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
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.conditions.IfCondition;
|
||||
@@ -45,7 +47,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryReturnStatement")
|
||||
@SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
|
||||
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
|
||||
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
|
||||
return;
|
||||
@@ -79,9 +81,15 @@ public class IfRegionVisitor extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
|
||||
boolean lastRegion = RegionUtils.hasExitEdge(ifRegion);
|
||||
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
|
||||
// single return at method end will be removed later
|
||||
InsnNode lastElseInsn = RegionUtils.getLastInsn(ifRegion.getElseRegion());
|
||||
if (lastElseInsn != null && lastElseInsn.getType() == InsnType.THROW) {
|
||||
// move `throw` into `then` block
|
||||
invertIfRegion(ifRegion);
|
||||
} else {
|
||||
// single return at method end will be removed later
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!lastRegion) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
return false;
|
||||
}
|
||||
// can't make loop if argument from increment instruction is assign in loop
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
List<RegisterArg> args = new ArrayList<>();
|
||||
incrInsn.getRegisterArgs(args);
|
||||
for (RegisterArg iArg : args) {
|
||||
try {
|
||||
@@ -268,7 +268,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
|
||||
return false;
|
||||
}
|
||||
List<InsnNode> toSkip = new LinkedList<>();
|
||||
List<InsnNode> toSkip = new ArrayList<>();
|
||||
RegisterArg iterVar;
|
||||
if (nextCall.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
|
||||
|
||||
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
@@ -73,11 +74,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
if (elseRegion == null) {
|
||||
if (mth.isConstructor()) {
|
||||
// force ternary conversion to inline all code in 'super' or 'this' calls
|
||||
return processOneBranchTernary(mth, ifRegion);
|
||||
}
|
||||
return false;
|
||||
return processOneBranchTernary(mth, ifRegion);
|
||||
}
|
||||
BlockNode tb = getTernaryInsnBlock(thenRegion);
|
||||
BlockNode eb = getTernaryInsnBlock(elseRegion);
|
||||
@@ -93,21 +90,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// no debug info
|
||||
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
|
||||
// don't make nested ternary by default
|
||||
// TODO: add addition checks
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!verifyLineHints(mth, thenInsn, elseInsn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RegisterArg thenResArg = thenInsn.getResult();
|
||||
@@ -184,6 +168,20 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean verifyLineHints(MethodNode mth, InsnNode thenInsn, InsnNode elseInsn) {
|
||||
if (mth.contains(AFlag.USE_LINES_HINTS)
|
||||
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
return checkLineStats(thenInsn, elseInsn);
|
||||
}
|
||||
// don't make nested ternary by default
|
||||
// TODO: add addition checks
|
||||
return !containsTernary(thenInsn) && !containsTernary(elseInsn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
|
||||
for (BlockNode block : conditionBlocks) {
|
||||
if (block != header) {
|
||||
@@ -277,6 +275,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg.getSVar().getUseList().size() != 1) {
|
||||
@@ -296,17 +295,45 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
|
||||
if (otherArg == null) {
|
||||
return;
|
||||
}
|
||||
InsnNode elseAssign = otherArg.getAssignInsn();
|
||||
if (mth.isConstructor() || (mth.getParentClass().isEnum() && mth.getMethodInfo().isClassInit())) {
|
||||
// forcing ternary inline for constructors (will help in moving super call to the top) and enums
|
||||
// skip code style checks
|
||||
} else {
|
||||
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||
if (!verifyLineHints(mth, insn, elseAssign)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (insn.getResult().sameCodeVar(otherArg)) {
|
||||
// don't use same variable in else branch to prevent: l = (l == 0) ? 1 : l
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
BlockNode header = ifRegion.getConditionBlocks().get(0);
|
||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||
return;
|
||||
}
|
||||
InsnArg elseArg;
|
||||
if (elseAssign != null && elseAssign.isConstInsn()) {
|
||||
// inline constant
|
||||
SSAVar elseVar = elseAssign.getResult().getSVar();
|
||||
if (elseVar.getUseCount() == 1 && elseVar.getOnlyOneUseInPhi() == phiInsn) {
|
||||
InsnRemover.remove(mth, elseAssign);
|
||||
}
|
||||
elseArg = InsnArg.wrapInsnIntoArg(elseAssign);
|
||||
} else {
|
||||
elseArg = otherArg;
|
||||
}
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
||||
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg);
|
||||
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), elseArg);
|
||||
ternInsn.simplifyCondition();
|
||||
|
||||
InsnRemover.unbindResult(mth, insn);
|
||||
InsnList.remove(block, insn);
|
||||
|
||||
InsnRemover.unbindAllArgs(mth, phiInsn);
|
||||
header.getInstructions().clear();
|
||||
ternInsn.rebindArgs();
|
||||
|
||||
@@ -14,6 +14,9 @@ import jadx.core.Consts;
|
||||
import jadx.core.codegen.json.JsonMappingGen;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -195,7 +198,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
Set<String> names = new HashSet<>(methods.size());
|
||||
for (MethodNode mth : methods) {
|
||||
String signature = mth.getMethodInfo().makeSignature(true, false);
|
||||
if (!names.add(signature)) {
|
||||
if (!names.add(signature) && canRename(mth)) {
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
|
||||
}
|
||||
@@ -203,6 +206,23 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canRename(MethodNode mth) {
|
||||
if (mth.contains(AFlag.DONT_RENAME)) {
|
||||
return false;
|
||||
}
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||
if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) {
|
||||
// ignore rename if exists related method from same class (bridge method in most cases)
|
||||
// such rename will also rename current method and will not help to resolve name collision
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
|
||||
Set<String> rootPkgs = collectRootPkgs(classes);
|
||||
root.getCacheStorage().setRootPkgs(rootPkgs);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.dex.visitors.shrink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -30,7 +30,7 @@ final class ArgsInfo {
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
List<RegisterArg> args = new ArrayList<>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.core.dex.visitors.ssa;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -81,7 +81,7 @@ public class SSATransform extends AbstractVisitor {
|
||||
int blocksCount = blocks.size();
|
||||
BitSet hasPhi = new BitSet(blocksCount);
|
||||
BitSet processed = new BitSet(blocksCount);
|
||||
Deque<BlockNode> workList = new LinkedList<>();
|
||||
Deque<BlockNode> workList = new ArrayDeque<>();
|
||||
|
||||
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
||||
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
||||
@@ -136,7 +136,7 @@ public class SSATransform extends AbstractVisitor {
|
||||
RenameState initState = RenameState.init(mth);
|
||||
initPhiInEnterBlock(initState);
|
||||
|
||||
Deque<RenameState> stack = new LinkedList<>();
|
||||
Deque<RenameState> stack = new ArrayDeque<>();
|
||||
stack.push(initState);
|
||||
while (!stack.isEmpty()) {
|
||||
RenameState state = stack.pop();
|
||||
|
||||
+4
-9
@@ -913,25 +913,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
boolean fixed = false;
|
||||
for (ITypeBound bound : typeInfo.getBounds()) {
|
||||
if (bound.getBound() == BoundEnum.USE
|
||||
&& fixBooleanUsage(mth, bound)) {
|
||||
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
|
||||
if (fixBooleanUsage(mth, arg)) {
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
private boolean fixBooleanUsage(MethodNode mth, ITypeBound bound) {
|
||||
ArgType boundType = bound.getType();
|
||||
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
|
||||
ArgType boundType = boundArg.getInitType();
|
||||
if (boundType == ArgType.BOOLEAN
|
||||
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg boundArg = bound.getArg();
|
||||
if (boundArg == null) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = boundArg.getParentInsn();
|
||||
if (insn == null || insn.getType() == InsnType.IF) {
|
||||
return false;
|
||||
|
||||
@@ -17,6 +17,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -228,6 +229,18 @@ public class InsnRemover {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
|
||||
public static void removeAllAndUnbind(MethodNode mth, IContainer container, List<InsnNode> insns) {
|
||||
unbindInsns(mth, insns);
|
||||
RegionUtils.visitBlocks(mth, container, b -> removeAll(b.getInstructions(), insns));
|
||||
}
|
||||
|
||||
public static void removeAllAndUnbind(MethodNode mth, List<InsnNode> insns) {
|
||||
unbindInsns(mth, insns);
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
|
||||
removeAll(block.getInstructions(), insns);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -26,6 +27,8 @@ 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;
|
||||
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegionUtils {
|
||||
@@ -146,20 +149,6 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static IContainer getLastRegion(@Nullable IContainer container) {
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
if (container instanceof IBlock || container instanceof IBranchRegion) {
|
||||
return container;
|
||||
}
|
||||
if (container instanceof IRegion) {
|
||||
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
|
||||
}
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
|
||||
public static boolean isExitBlock(MethodNode mth, IContainer container) {
|
||||
if (container instanceof BlockNode) {
|
||||
return BlockUtils.isExitBlock(mth, (BlockNode) container);
|
||||
@@ -487,4 +476,13 @@ public class RegionUtils {
|
||||
}
|
||||
return "Unknown container type: " + container.getClass();
|
||||
}
|
||||
|
||||
public static void visitBlocks(MethodNode mth, IContainer container, Consumer<IBlock> visitor) {
|
||||
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void processBlock(MethodNode mth, IBlock block) {
|
||||
visitor.accept(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
|
||||
@@ -175,7 +173,7 @@ public class ManifestAttributes {
|
||||
if (attr.getType() == MAttrType.ENUM) {
|
||||
return attr.getValues().get(value);
|
||||
} else if (attr.getType() == MAttrType.FLAG) {
|
||||
List<String> flagList = new LinkedList<>();
|
||||
List<String> flagList = new ArrayList<>();
|
||||
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
|
||||
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
|
||||
for (Long key : attrKeys) {
|
||||
@@ -188,7 +186,7 @@ public class ManifestAttributes {
|
||||
value ^= key;
|
||||
}
|
||||
}
|
||||
return flagList.stream().collect(Collectors.joining("|"));
|
||||
return String.join("|", flagList);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,12 @@ public class ParserConstants {
|
||||
protected static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
|
||||
protected static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
|
||||
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||
protected static final int RES_TABLE_TYPE_TYPE = 0x0201;
|
||||
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200; // 512
|
||||
protected static final int RES_TABLE_TYPE_TYPE = 0x0201; // 513
|
||||
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; // 514
|
||||
protected static final int RES_TABLE_TYPE_LIBRARY = 0x0203; // 515
|
||||
protected static final int RES_TABLE_TYPE_OVERLAY = 0x0204; // 516
|
||||
protected static final int RES_TABLE_TYPE_STAGED_ALIAS = 0x0206; // 517
|
||||
|
||||
/**
|
||||
* Type constants
|
||||
|
||||
@@ -153,13 +153,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
while (is.getPos() < endPos) {
|
||||
long chunkStart = is.getPos();
|
||||
int type = is.readInt16();
|
||||
if (type == RES_NULL_TYPE) {
|
||||
continue;
|
||||
}
|
||||
if (type == RES_TABLE_TYPE_SPEC_TYPE) {
|
||||
parseTypeSpecChunk();
|
||||
} else if (type == RES_TABLE_TYPE_TYPE) {
|
||||
parseTypeChunk(chunkStart, pkg);
|
||||
LOG.trace("res package chunk start at {} type {}", chunkStart, type);
|
||||
switch (type) {
|
||||
case RES_NULL_TYPE:
|
||||
LOG.info("Null chunk type encountered at offset {}", chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_TYPE: // 0x0201
|
||||
parseTypeChunk(chunkStart, pkg);
|
||||
break;
|
||||
case RES_TABLE_TYPE_SPEC_TYPE: // 0x0202
|
||||
parseTypeSpecChunk(chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_LIBRARY: // 0x0203
|
||||
parseLibraryTypeChunk(chunkStart);
|
||||
break;
|
||||
case RES_TABLE_TYPE_OVERLAY: // 0x0204
|
||||
throw new IOException(
|
||||
String.format("Encountered unsupported chunk type TYPE_OVERLAY at offset 0x%x ", chunkStart));
|
||||
case RES_TABLE_TYPE_STAGED_ALIAS: // 0x0206
|
||||
throw new IOException(
|
||||
String.format("Encountered unsupported chunk type TYPE_STAGED_ALIAS at offset 0x%x ", chunkStart));
|
||||
default:
|
||||
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
|
||||
}
|
||||
}
|
||||
return pkg;
|
||||
@@ -192,10 +207,10 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void parseTypeSpecChunk() throws IOException {
|
||||
private void parseTypeSpecChunk(long chunkStart) throws IOException {
|
||||
is.checkInt16(0x0010, "Unexpected type spec header size");
|
||||
/* int size = */
|
||||
is.readInt32();
|
||||
int chunkSize = is.readInt32();
|
||||
long expectedEndPos = chunkStart + chunkSize;
|
||||
|
||||
int id = is.readInt8();
|
||||
is.skip(3);
|
||||
@@ -203,6 +218,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
int entryFlag = is.readInt32();
|
||||
}
|
||||
if (is.getPos() != expectedEndPos) {
|
||||
throw new IOException(String.format("Error reading type spec chunk at offset 0x%x", chunkStart));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseLibraryTypeChunk(long chunkStart) throws IOException {
|
||||
LOG.trace("parsing library type chunk starting at offset {}", chunkStart);
|
||||
is.checkInt16(12, "Unexpected header size");
|
||||
int chunkSize = is.readInt32();
|
||||
long expectedEndPos = chunkStart + chunkSize;
|
||||
int count = is.readInt32();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int packageId = is.readInt32();
|
||||
String packageName = is.readString16Fixed(128);
|
||||
LOG.info("Found resource shared library {}, pkgId: {}", packageName, packageId);
|
||||
if (is.getPos() > expectedEndPos) {
|
||||
throw new IOException("reading after chunk end");
|
||||
}
|
||||
}
|
||||
if (is.getPos() != expectedEndPos) {
|
||||
throw new IOException(String.format("Error reading library chunk at offset 0x%x", chunkStart));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
|
||||
@@ -241,6 +278,13 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
parseEntry(pkg, id, i, config.getQualifiers());
|
||||
}
|
||||
}
|
||||
if (chunkEnd > is.getPos()) {
|
||||
// Skip remaining unknown data in this chunk (e.g. type 8 entries")
|
||||
long skipSize = chunkEnd - is.getPos();
|
||||
LOG.debug("Unknown data at the end of type chunk encountered, skipping {} bytes and continuing at offset {}", skipSize,
|
||||
chunkEnd);
|
||||
is.skip(skipSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseEntry(PackageChunk pkg, int typeId, int entryId, String config) throws IOException {
|
||||
|
||||
@@ -49,7 +49,7 @@ public class ResourcesSaver implements Runnable {
|
||||
private void save(ResContainer rc, File outDir) {
|
||||
File outFile = new File(outDir, rc.getFileName());
|
||||
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
|
||||
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
|
||||
LOG.error("Invalid resource name or path traversal attack detected: {}", outFile.getPath());
|
||||
return;
|
||||
}
|
||||
saveToFile(rc, outFile);
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
@SuppressWarnings("CommentedOutCode")
|
||||
public class TestBooleanToInt extends SmaliTest {
|
||||
|
||||
// @formatter:off
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
@SuppressWarnings("CommentedOutCode")
|
||||
public class TestBooleanToInt2 extends SmaliTest {
|
||||
|
||||
// @formatter:off
|
||||
/*
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
boolean v = getValue();
|
||||
use1(Integer.valueOf(v));
|
||||
use2(v);
|
||||
}
|
||||
|
||||
private boolean getValue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void use1(Integer v) {
|
||||
}
|
||||
|
||||
private void use2(int v) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("use1(Integer.valueOf(value ? 1 : 0));")
|
||||
.containsOne("use2(value ? 1 : 0);");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestElseIfCodeStyle extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class TestCls {
|
||||
|
||||
public void test(String str) {
|
||||
if ("a".equals(str)) {
|
||||
call(1);
|
||||
} else if ("b".equals(str)) {
|
||||
call(2);
|
||||
} else if ("c".equals(str)) {
|
||||
call(3);
|
||||
}
|
||||
}
|
||||
|
||||
private void call(int i) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.doesNotContain("!\"c\".equals(str)")
|
||||
.doesNotContain("{" + ICodeWriter.NL + indent(2) + "} else {"); // no empty `then` block
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
public class TestSwitchTryBreak extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void test(int x) {
|
||||
switch (x) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
String res;
|
||||
if ("android".equals(toString())) {
|
||||
res = "hello";
|
||||
} else {
|
||||
try {
|
||||
if (String.CASE_INSENSITIVE_ORDER != null) {
|
||||
break;
|
||||
}
|
||||
res = "hi";
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
System.out.println("returning");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@NotYetImplemented
|
||||
public void test() {
|
||||
getClassNode(TestCls.class);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package jadx.tests.integration.conditions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
public class TestSwitchTryBreak2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
|
||||
public void test(int x) {
|
||||
switch (x) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
String res;
|
||||
if ("android".equals(toString())) {
|
||||
res = "hello";
|
||||
} else {
|
||||
try {
|
||||
if (x == 5) {
|
||||
break;
|
||||
}
|
||||
res = "hi";
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(res);
|
||||
}
|
||||
System.out.println("returning");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@NotYetImplemented
|
||||
public void test() {
|
||||
getClassNode(TestCls.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnumsWithCustomInit extends IntegrationTest {
|
||||
|
||||
public enum TestCls {
|
||||
ONE("I"),
|
||||
TWO("II"),
|
||||
THREE("III");
|
||||
|
||||
public static final Map<String, TestCls> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (TestCls value : values()) {
|
||||
MAP.put(value.toString(), value);
|
||||
}
|
||||
}
|
||||
|
||||
private final String str;
|
||||
|
||||
TestCls(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("ONE(\"I\"),")
|
||||
.doesNotContain("new TestEnumsWithCustomInit$TestCls(");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.tests.integration.enums;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestEnumsWithTernary extends IntegrationTest {
|
||||
|
||||
public enum TestCls {
|
||||
FIRST(useNumber() ? "1" : "A"),
|
||||
SECOND(useNumber() ? "2" : "B"),
|
||||
ANY(useNumber() ? "1" : "2");
|
||||
|
||||
private final String str;
|
||||
|
||||
TestCls(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static boolean useNumber() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.D8_J8 })
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("ANY(useNumber() ? \"1\" : \"2\");")
|
||||
.doesNotContain("static {");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.tests.integration.inline;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.extensions.profiles.TestProfile;
|
||||
import jadx.tests.api.extensions.profiles.TestWithProfiles;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestSyntheticBridgeRename extends IntegrationTest {
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public static class TestCls {
|
||||
private abstract class Inner<V> {
|
||||
public abstract V get(String value);
|
||||
}
|
||||
|
||||
public class IntInner extends Inner<Integer> {
|
||||
public Integer get(String value) {
|
||||
return value.length();
|
||||
}
|
||||
}
|
||||
|
||||
public void test() {
|
||||
IntInner inner = new IntInner();
|
||||
call(inner.get("a"));
|
||||
}
|
||||
|
||||
private static void call(Integer value) {
|
||||
}
|
||||
}
|
||||
|
||||
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(searchCls(cls.getInnerClasses(), "IntInner").getMethods())
|
||||
.as("check that bridge method was generated by compiler")
|
||||
.haveAtLeastOne(new Condition<>(mth -> mth.getAccessFlags().isBridge(), "bridge"));
|
||||
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.doesNotContain("mo0get")
|
||||
.containsOne("call(inner.get(\"a\"));");
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package jadx.tests.integration.names;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -26,7 +26,7 @@ public class TestNameAssign2 extends IntegrationTest {
|
||||
int blocksCount = blocks.size();
|
||||
BitSet hasPhi = new BitSet(blocksCount);
|
||||
BitSet processed = new BitSet(blocksCount);
|
||||
Deque<BlockNode> workList = new LinkedList<>();
|
||||
Deque<BlockNode> workList = new ArrayDeque<>();
|
||||
|
||||
BitSet assignBlocks = la.getAssignBlocks(regNum);
|
||||
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.class public Lconditions/TestBooleanToInt2;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public test()V
|
||||
.registers 3
|
||||
invoke-direct {p0}, Lconditions/TestBooleanToInt2;->getValue()Z
|
||||
move-result v0
|
||||
invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
|
||||
move-result-object v1
|
||||
invoke-direct {p0, v1}, Lconditions/TestBooleanToInt2;->use1(Ljava/lang/Integer;)V
|
||||
invoke-direct {p0, v0}, Lconditions/TestBooleanToInt2;->use2(I)V
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private getValue()Z
|
||||
.registers 2
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
.end method
|
||||
|
||||
.method private use1(Ljava/lang/Integer;)V
|
||||
.registers 2
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private use2(I)V
|
||||
.registers 2
|
||||
return-void
|
||||
.end method
|
||||
+10
-10
@@ -9,25 +9,25 @@ dependencies {
|
||||
implementation(project(':jadx-core'))
|
||||
implementation(project(":jadx-cli"))
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.11'
|
||||
implementation 'ch.qos.logback:logback-classic:1.3.4'
|
||||
|
||||
implementation 'com.fifesoft:rsyntaxtextarea:3.2.0'
|
||||
implementation 'com.fifesoft:rsyntaxtextarea:3.3.0'
|
||||
implementation files('libs/jfontchooser-1.0.5.jar')
|
||||
implementation 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
|
||||
implementation 'com.formdev:flatlaf:2.4'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.4'
|
||||
implementation 'com.formdev:flatlaf-extras:2.4'
|
||||
implementation 'com.formdev:svgSalamander:1.1.3'
|
||||
implementation 'com.formdev:flatlaf:2.6'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:2.6'
|
||||
implementation 'com.formdev:flatlaf-extras:2.6'
|
||||
implementation 'com.formdev:svgSalamander:1.1.4'
|
||||
|
||||
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 'org.apache.commons:commons-text:1.10.0'
|
||||
|
||||
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.2'
|
||||
implementation 'io.github.hqktech:jdwp:1.0'
|
||||
implementation 'com.android.tools.build:apksig:7.3.1'
|
||||
implementation 'io.github.skylot:jdwp:2.0.0'
|
||||
|
||||
// TODO: Switch back to upstream once this PR gets merged:
|
||||
// https://github.com/FabricMC/mapping-io/pull/19
|
||||
@@ -90,7 +90,7 @@ launch4j {
|
||||
windowTitle = 'jadx'
|
||||
companyName = 'jadx'
|
||||
jreMinVersion = '11'
|
||||
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC']
|
||||
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC', '-Djava.util.Arrays.useLegacyMergeSort=true']
|
||||
jreRuntimeBits = "64"
|
||||
bundledJre64Bit = true
|
||||
initialHeapPercent = 5
|
||||
|
||||
@@ -44,11 +44,12 @@ import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
|
||||
private static final String ONCREATE_SIGNATURE = "onCreate(Landroid/os/Bundle;)V";
|
||||
private static final Map<String, RuntimeType> TYPE_MAP = new HashMap<>();
|
||||
private static final RuntimeType[] POSSIBLE_TYPES = { RuntimeType.OBJECT, RuntimeType.INT, RuntimeType.LONG };
|
||||
private static final int DEFAULT_CACHE_SIZE = 512;
|
||||
|
||||
private JDebuggerPanel debuggerPanel;
|
||||
private SmaliDebugger debugger;
|
||||
@@ -1115,7 +1116,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
private long thisID;
|
||||
|
||||
public FrameNode(long threadID, SmaliDebugger.Frame frame) {
|
||||
cache = new StringBuilder(16);
|
||||
cache = new StringBuilder(DEFAULT_CACHE_SIZE);
|
||||
this.frame = frame;
|
||||
this.threadID = threadID;
|
||||
regNodes = Collections.emptyList();
|
||||
@@ -1153,7 +1154,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
public void setSignatures(String clsSig, String mthSig) {
|
||||
this.clsSig = clsSig;
|
||||
this.mthSig = mthSig;
|
||||
this.cache.delete(0, this.cache.length());
|
||||
resetCache();
|
||||
}
|
||||
|
||||
public String getClsSig() {
|
||||
@@ -1167,7 +1168,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
public void updateCodeOffset(long codeOffset) {
|
||||
this.codeOffset = codeOffset;
|
||||
if (this.codeOffset > -1) {
|
||||
this.cache.delete(0, this.cache.length());
|
||||
resetCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,27 +1210,34 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCache() {
|
||||
// Do not reuse thee existing cache instance as this can result in
|
||||
// multi-threading access issues in case toString() method is active
|
||||
this.cache = new StringBuilder(DEFAULT_CACHE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (cache.length() == 0) {
|
||||
StringBuilder sbCache = cache;
|
||||
if (sbCache.length() == 0) {
|
||||
long off = getCodeOffset();
|
||||
if (off < 0) {
|
||||
cache.append(String.format("index: %-4d ", off));
|
||||
sbCache.append(String.format("index: %-4d ", off));
|
||||
} else {
|
||||
cache.append(String.format("index: %04x ", off));
|
||||
sbCache.append(String.format("index: %04x ", off));
|
||||
}
|
||||
if (clsSig == null) {
|
||||
cache.append("clsID: ").append(frame.getClassID());
|
||||
sbCache.append("clsID: ").append(frame.getClassID());
|
||||
} else {
|
||||
cache.append(clsSig).append("->");
|
||||
sbCache.append(clsSig).append("->");
|
||||
}
|
||||
if (mthSig == null) {
|
||||
cache.append(" mthID: ").append(frame.getMethodID());
|
||||
sbCache.append(" mthID: ").append(frame.getMethodID());
|
||||
} else {
|
||||
cache.append(mthSig);
|
||||
sbCache.append(mthSig);
|
||||
}
|
||||
}
|
||||
return cache.toString();
|
||||
return sbCache.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
|
||||
|
||||
abstract class EventListenerAdapter {
|
||||
void onVMStart(VMStartEvent event) {
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.device.protocol.ADBDevice;
|
||||
import jadx.gui.ui.panel.LogcatPanel;
|
||||
|
||||
public class LogcatController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);
|
||||
|
||||
private final ADBDevice adbDevice;
|
||||
private final LogcatPanel logcatPanel;
|
||||
private Timer timer;
|
||||
private final String timezone;
|
||||
private LogcatInfo recent = null;
|
||||
private ArrayList<LogcatInfo> events = new ArrayList<>();
|
||||
private LogcatFilter filter = new LogcatFilter(null, null);
|
||||
private String status = "null";
|
||||
|
||||
public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
|
||||
this.adbDevice = adbDevice;
|
||||
this.logcatPanel = logcatPanel;
|
||||
this.timezone = adbDevice.getTimezone();
|
||||
this.startLogcat();
|
||||
}
|
||||
|
||||
public void startLogcat() {
|
||||
timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
getLog();
|
||||
}
|
||||
}, 0, 1000);
|
||||
this.status = "running";
|
||||
}
|
||||
|
||||
public void stopLogcat() {
|
||||
timer.cancel();
|
||||
this.status = "stopped";
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public void clearLogcat() {
|
||||
try {
|
||||
adbDevice.clearLogcat();
|
||||
clearEvents();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clear Logcat", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void getLog() {
|
||||
if (!logcatPanel.isReady()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
byte[] buf;
|
||||
if (recent == null) {
|
||||
buf = adbDevice.getBinaryLogcat();
|
||||
} else {
|
||||
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
|
||||
}
|
||||
if (buf == null) {
|
||||
return;
|
||||
}
|
||||
ByteBuffer in = ByteBuffer.wrap(buf);
|
||||
in.order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (in.remaining() > 20) {
|
||||
|
||||
LogcatInfo eInfo = null;
|
||||
byte[] msgBuf;
|
||||
short eLen = in.getShort();
|
||||
short eHdrLen = in.getShort();
|
||||
if (eLen + eHdrLen > in.remaining()) {
|
||||
return;
|
||||
}
|
||||
switch (eHdrLen) {
|
||||
case 20: // header length 20 == version 1
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
case 24: // header length 24 == version 2 / 3
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
case 28: // header length 28 == version 4
|
||||
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
|
||||
in.get());
|
||||
msgBuf = new byte[eLen];
|
||||
in.get(msgBuf, 0, eLen - 1);
|
||||
eInfo.setMsg(msgBuf);
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
if (eInfo == null) {
|
||||
return;
|
||||
}
|
||||
if (recent == null) {
|
||||
recent = eInfo;
|
||||
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
|
||||
recent = eInfo;
|
||||
}
|
||||
|
||||
if (filter.doFilter(eInfo)) {
|
||||
logcatPanel.log(eInfo);
|
||||
}
|
||||
events.add(eInfo);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get logcat message", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean reload() {
|
||||
stopLogcat();
|
||||
boolean ok = logcatPanel.clearLogcatArea();
|
||||
if (ok) {
|
||||
events.forEach((eInfo) -> {
|
||||
if (filter.doFilter(eInfo)) {
|
||||
logcatPanel.log(eInfo);
|
||||
}
|
||||
});
|
||||
startLogcat();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void clearEvents() {
|
||||
this.recent = null;
|
||||
this.events = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
stopLogcat();
|
||||
filter = new LogcatFilter(null, null);
|
||||
recent = null;
|
||||
}
|
||||
|
||||
public LogcatFilter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public class LogcatFilter {
|
||||
private final ArrayList<Integer> pid;
|
||||
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
|
||||
{
|
||||
add((byte) 1);
|
||||
add((byte) 2);
|
||||
add((byte) 3);
|
||||
add((byte) 4);
|
||||
add((byte) 5);
|
||||
add((byte) 6);
|
||||
add((byte) 7);
|
||||
add((byte) 8);
|
||||
}
|
||||
};
|
||||
|
||||
public LogcatFilter(ArrayList<Integer> pid, ArrayList<Byte> msgType) {
|
||||
if (pid != null) {
|
||||
this.pid = pid;
|
||||
} else {
|
||||
this.pid = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (msgType != null) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
}
|
||||
|
||||
public void addPid(int pid) {
|
||||
|
||||
if (!this.pid.contains(pid)) {
|
||||
this.pid.add(pid);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePid(int pid) {
|
||||
int pidPos = this.pid.indexOf(pid);
|
||||
if (pidPos >= 0) {
|
||||
this.pid.remove(pidPos);
|
||||
}
|
||||
}
|
||||
|
||||
public void togglePid(int pid, boolean state) {
|
||||
if (state) {
|
||||
addPid(pid);
|
||||
} else {
|
||||
removePid(pid);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMsgType(byte msgType) {
|
||||
if (!this.msgType.contains(msgType)) {
|
||||
this.msgType.add(msgType);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMsgType(byte msgType) {
|
||||
int typePos = this.msgType.indexOf(msgType);
|
||||
if (typePos >= 0) {
|
||||
this.msgType.remove(typePos);
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleMsgType(byte msgType, boolean state) {
|
||||
if (state) {
|
||||
addMsgType(msgType);
|
||||
} else {
|
||||
removeMsgType(msgType);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean doFilter(LogcatInfo inInfo) {
|
||||
if (pid.contains(inInfo.getPid())) {
|
||||
return msgType.contains(inInfo.getMsgType());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
|
||||
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
|
||||
inInfoList.forEach((inInfo) -> {
|
||||
if (doFilter(inInfo)) {
|
||||
outInfoList.add(inInfo);
|
||||
}
|
||||
});
|
||||
return outInfoList;
|
||||
}
|
||||
}
|
||||
|
||||
public class LogcatInfo {
|
||||
private String msg;
|
||||
private final byte msgType;
|
||||
private final int nsec;
|
||||
private final int pid;
|
||||
private final int sec;
|
||||
private final int tid;
|
||||
private final short hdrSize;
|
||||
private final short len;
|
||||
private final short version;
|
||||
private int lid;
|
||||
private int uid;
|
||||
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.version = 1;
|
||||
}
|
||||
|
||||
// Version 2 and 3 both have the same arguments
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.lid = lid;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.version = 3;
|
||||
}
|
||||
|
||||
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
|
||||
this.hdrSize = hdrSize;
|
||||
this.len = len;
|
||||
this.lid = lid;
|
||||
this.msgType = msgType;
|
||||
this.nsec = nsec;
|
||||
this.pid = pid;
|
||||
this.sec = sec;
|
||||
this.tid = tid;
|
||||
this.uid = uid;
|
||||
this.version = 4;
|
||||
}
|
||||
|
||||
public void setMsg(byte[] msg) {
|
||||
this.msg = new String(msg);
|
||||
}
|
||||
|
||||
public short getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public short getLen() {
|
||||
return this.len;
|
||||
}
|
||||
|
||||
public short getHeaderLen() {
|
||||
return this.hdrSize;
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return this.pid;
|
||||
}
|
||||
|
||||
public int getTid() {
|
||||
return this.tid;
|
||||
}
|
||||
|
||||
public int getSec() {
|
||||
return this.sec;
|
||||
}
|
||||
|
||||
public int getNSec() {
|
||||
return this.nsec;
|
||||
}
|
||||
|
||||
public int getLid() {
|
||||
return this.lid;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
return this.uid;
|
||||
}
|
||||
|
||||
public Instant getInstant() {
|
||||
return Instant.ofEpochSecond(getSec(), getNSec());
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
|
||||
return dtFormat.format(getInstant());
|
||||
}
|
||||
|
||||
public String getAfterTimestamp() {
|
||||
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
|
||||
return dtFormat.format(getInstant().plusMillis(1));
|
||||
}
|
||||
|
||||
public byte getMsgType() {
|
||||
return this.msgType;
|
||||
}
|
||||
|
||||
public String getMsgTypeString() {
|
||||
switch (getMsgType()) {
|
||||
case 0:
|
||||
return "Unknown";
|
||||
case 1:
|
||||
return "Default";
|
||||
case 2:
|
||||
return "Verbose";
|
||||
case 3:
|
||||
return "Debug";
|
||||
case 4:
|
||||
return "Info";
|
||||
case 5:
|
||||
return "Warn";
|
||||
case 6:
|
||||
return "Error";
|
||||
case 7:
|
||||
return "Fatal";
|
||||
case 8:
|
||||
return "Silent";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.device.debugger;
|
||||
|
||||
import io.github.hqktech.JDWP;
|
||||
import io.github.skylot.jdwp.JDWP;
|
||||
|
||||
public enum RuntimeType {
|
||||
ARRAY(91, "[]"),
|
||||
|
||||
@@ -21,56 +21,56 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hqktech.JDWP;
|
||||
import io.github.hqktech.JDWP.ArrayReference.Length.LengthReplyData;
|
||||
import io.github.hqktech.JDWP.ByteBuffer;
|
||||
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.EventData;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.ClassMatchRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.CountRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.LocationOnlyRequest;
|
||||
import io.github.hqktech.JDWP.EventRequest.Set.StepRequest;
|
||||
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
|
||||
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
|
||||
import io.github.hqktech.JDWP.ObjectReference;
|
||||
import io.github.hqktech.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
|
||||
import io.github.hqktech.JDWP.ObjectReference.SetValues.FieldValueSetter;
|
||||
import io.github.hqktech.JDWP.Packet;
|
||||
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.ReferenceType.Signature.SignatureReplyData;
|
||||
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesReplyData;
|
||||
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesSlots;
|
||||
import io.github.hqktech.JDWP.StackFrame.SetValues.SlotValueSetter;
|
||||
import io.github.hqktech.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
|
||||
import io.github.hqktech.JDWP.StringReference.Value.ValueReplyData;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyData;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
|
||||
import io.github.hqktech.JDWP.ThreadReference.Name.NameReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
||||
import io.github.hqktech.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
||||
import io.github.skylot.jdwp.JDWP;
|
||||
import io.github.skylot.jdwp.JDWP.ArrayReference.Length.LengthReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ByteBuffer;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.EventData;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
|
||||
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.ClassMatchRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.CountRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.LocationOnlyRequest;
|
||||
import io.github.skylot.jdwp.JDWP.EventRequest.Set.StepRequest;
|
||||
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ObjectReference.SetValues.FieldValueSetter;
|
||||
import io.github.skylot.jdwp.JDWP.Packet;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ReferenceType.Signature.SignatureReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesSlots;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.SetValues.SlotValueSetter;
|
||||
import io.github.skylot.jdwp.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.StringReference.Value.ValueReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
|
||||
import io.github.skylot.jdwp.JDWP.ThreadReference.Name.NameReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
|
||||
import io.github.skylot.jdwp.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -25,7 +27,7 @@ public class ADBDevice {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class);
|
||||
|
||||
private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
|
||||
|
||||
private static final Pattern TIMESTAMP_FORMAT = Pattern.compile("^[0-9]{2}\\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}$");
|
||||
ADBDeviceInfo info;
|
||||
String androidReleaseVer;
|
||||
volatile Socket jdwpListenerSock;
|
||||
@@ -120,6 +122,49 @@ public class ADBDevice {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat
|
||||
*/
|
||||
public byte[] getBinaryLogcat() throws IOException {
|
||||
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "logcat -dB";
|
||||
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat after provided timestamp
|
||||
* Timestamp is in the format 09-08 02:18:03.131
|
||||
*/
|
||||
public byte[] getBinaryLogcat(String timestamp) throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
Matcher matcher = TIMESTAMP_FORMAT.matcher(timestamp);
|
||||
if (!matcher.find()) {
|
||||
LOG.error("Invalid Logcat Timestamp " + timestamp);
|
||||
}
|
||||
String cmd = "logcat -dB -t \"" + timestamp + "\"";
|
||||
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Return binary output of logcat -c
|
||||
*/
|
||||
public void clearLogcat() throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "logcat -c";
|
||||
ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timezone for the attached android device
|
||||
*/
|
||||
public String getTimezone() throws IOException {
|
||||
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
|
||||
String cmd = "getprop persist.sys.timezone";
|
||||
byte[] tz = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
|
||||
return new String(tz).trim();
|
||||
}
|
||||
|
||||
public String getAndroidReleaseVersion() {
|
||||
if (!StringUtils.isEmpty(androidReleaseVer)) {
|
||||
return androidReleaseVer;
|
||||
@@ -160,15 +205,15 @@ public class ADBDevice {
|
||||
}
|
||||
|
||||
public List<Process> getProcessByPkg(String pkg) throws IOException {
|
||||
return getProcessList("ps | grep " + pkg, 0);
|
||||
return getProcessList("ps | grep " + pkg);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Process> getProcessList() throws IOException {
|
||||
return getProcessList("ps", 1);
|
||||
return getProcessList("ps");
|
||||
}
|
||||
|
||||
private List<Process> getProcessList(String cmd, int index) throws IOException {
|
||||
private List<Process> getProcessList(String cmd) throws IOException {
|
||||
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
|
||||
List<Process> procs = new ArrayList<>();
|
||||
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class QuarkDialog extends JDialog {
|
||||
private static final long serialVersionUID = 4855753773520368215L;
|
||||
@@ -59,7 +60,7 @@ public class QuarkDialog extends JDialog {
|
||||
description.setAlignmentX(0.5f);
|
||||
|
||||
fileSelectCombo = new JComboBox<>(files.toArray(new Path[0]));
|
||||
fileSelectCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getFileName().toString()));
|
||||
fileSelectCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new NodeLabel(value.getFileName().toString()));
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
textPane.add(description);
|
||||
|
||||
@@ -44,6 +44,7 @@ import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class QuarkReportPanel extends ContentPanel {
|
||||
private static final long serialVersionUID = -242266836695889206L;
|
||||
@@ -211,7 +212,7 @@ public class QuarkReportPanel extends ContentPanel {
|
||||
|
||||
@Override
|
||||
public Component render() {
|
||||
JLabel label = new JLabel(((String) getUserObject()));
|
||||
JLabel label = new NodeLabel(((String) getUserObject()));
|
||||
label.setFont(bold ? boldFont : font);
|
||||
label.setIcon(null);
|
||||
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
@@ -320,7 +321,7 @@ public class QuarkReportPanel extends ContentPanel {
|
||||
|
||||
@Override
|
||||
public Component render() {
|
||||
JLabel label = new JLabel(mth.toString());
|
||||
JLabel label = new NodeLabel(mth.toString());
|
||||
label.setFont(font);
|
||||
label.setIcon(jnode.getIcon());
|
||||
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -35,7 +34,6 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
private final TaskProgress taskProgress = new TaskProgress();
|
||||
|
||||
private final AtomicInteger resultsCount = new AtomicInteger(0);
|
||||
private final AtomicBoolean complete = new AtomicBoolean(false);
|
||||
private int resultsLimit;
|
||||
private Future<TaskStatus> future;
|
||||
|
||||
@@ -60,7 +58,6 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
throw new IllegalStateException("Previous task not yet finished");
|
||||
}
|
||||
resetCancel();
|
||||
complete.set(false);
|
||||
resultsCount.set(0);
|
||||
taskProgress.updateTotal(jobs.stream().mapToInt(s -> s.getProvider().total()).sum());
|
||||
future = backgroundExecutor.execute(this);
|
||||
@@ -74,7 +71,6 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
this.resultsListener.accept(resultNode);
|
||||
if (resultsLimit != 0 && resultsCount.incrementAndGet() >= resultsLimit) {
|
||||
cancel();
|
||||
complete.set(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -79,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 int searchResultsPerPage = 50;
|
||||
private boolean useAutoSearch = true;
|
||||
private boolean keepCommonDialogOpen = false;
|
||||
private boolean smaliAreaShowBytecode = false;
|
||||
@@ -541,6 +542,14 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
srhResourceFileExt = all.trim();
|
||||
}
|
||||
|
||||
public int getSearchResultsPerPage() {
|
||||
return searchResultsPerPage;
|
||||
}
|
||||
|
||||
public void setSearchResultsPerPage(int searchResultsPerPage) {
|
||||
this.searchResultsPerPage = searchResultsPerPage;
|
||||
}
|
||||
|
||||
public boolean isUseAutoSearch() {
|
||||
return useAutoSearch;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ import javax.swing.SpinnerNumberModel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -676,48 +672,26 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
|
||||
private SettingsGroup makeSearchResGroup() {
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.search_res_title"));
|
||||
int prevSize = settings.getSrhResourceSkipSize();
|
||||
String prevExts = settings.getSrhResourceFileExt();
|
||||
SpinnerNumberModel sizeLimitModel = new SpinnerNumberModel(prevSize,
|
||||
0, Integer.MAX_VALUE, 1);
|
||||
JSpinner spinner = new JSpinner(sizeLimitModel);
|
||||
JSpinner resultsPerPage = new JSpinner(
|
||||
new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1));
|
||||
resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue()));
|
||||
|
||||
JSpinner sizeLimit = new JSpinner(
|
||||
new SpinnerNumberModel(settings.getSrhResourceSkipSize(), 0, Integer.MAX_VALUE, 1));
|
||||
sizeLimit.addChangeListener(ev -> settings.setSrhResourceSkipSize((Integer) sizeLimit.getValue()));
|
||||
|
||||
JTextField fileExtField = new JTextField();
|
||||
group.addRow(NLS.str("preferences.res_skip_file"), spinner);
|
||||
group.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
|
||||
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener((ev) -> {
|
||||
String ext = fileExtField.getText();
|
||||
settings.setSrhResourceFileExt(ext);
|
||||
}));
|
||||
fileExtField.setText(settings.getSrhResourceFileExt());
|
||||
|
||||
spinner.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
int size = (Integer) spinner.getValue();
|
||||
settings.setSrhResourceSkipSize(size);
|
||||
}
|
||||
});
|
||||
|
||||
fileExtField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
private void update() {
|
||||
String ext = fileExtField.getText();
|
||||
settings.setSrhResourceFileExt(ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
fileExtField.setText(prevExts);
|
||||
|
||||
return group;
|
||||
SettingsGroup searchGroup = new SettingsGroup(NLS.str("preferences.search_group_title"));
|
||||
searchGroup.addRow(NLS.str("preferences.search_results_per_page"), resultsPerPage);
|
||||
searchGroup.addRow(NLS.str("preferences.res_skip_file"), sizeLimit);
|
||||
searchGroup.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
|
||||
return searchGroup;
|
||||
}
|
||||
|
||||
private void needReload() {
|
||||
|
||||
@@ -71,6 +71,11 @@ public class CodeNode extends JNode {
|
||||
return jNode.makeLongStringHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableHtml() {
|
||||
return jNode.disableHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return jNode.getSyntaxName();
|
||||
|
||||
@@ -97,6 +97,11 @@ public class JField extends JNode {
|
||||
return UiUtils.typeStr(field.getType()) + " " + field.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableHtml() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDescString() {
|
||||
return false;
|
||||
|
||||
@@ -155,6 +155,11 @@ public class JMethod extends JNode {
|
||||
return UiUtils.typeFormatHtml(name, getReturnType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableHtml() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeDescString() {
|
||||
return UiUtils.typeStr(getReturnType()) + " " + makeBaseString();
|
||||
|
||||
@@ -81,6 +81,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return makeLongString();
|
||||
}
|
||||
|
||||
public boolean disableHtml() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
JavaNode javaNode = getJavaNode();
|
||||
if (javaNode == null) {
|
||||
|
||||
@@ -142,6 +142,7 @@ import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
|
||||
import jadx.gui.utils.logs.LogCollector;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
@@ -1281,6 +1282,7 @@ public class MainWindow extends JFrame {
|
||||
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused);
|
||||
if (value instanceof JNode) {
|
||||
JNode jNode = (JNode) value;
|
||||
NodeLabel.disableHtml(this, jNode.disableHtml());
|
||||
setText(jNode.makeStringHtml());
|
||||
setIcon(jNode.getIcon());
|
||||
setToolTipText(jNode.getTooltip());
|
||||
|
||||
@@ -23,6 +23,7 @@ import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class TabComponent extends JPanel {
|
||||
private static final long serialVersionUID = -8147035487543610321L;
|
||||
@@ -58,7 +59,7 @@ public class TabComponent extends JPanel {
|
||||
} else {
|
||||
tabTitle = node.makeLongStringHtml();
|
||||
}
|
||||
label = new JLabel(tabTitle);
|
||||
label = new NodeLabel(tabTitle, node.disableHtml());
|
||||
label.setFont(getLabelFont());
|
||||
String toolTip = contentPanel.getTabTooltip();
|
||||
if (toolTip != null) {
|
||||
|
||||
@@ -361,7 +361,9 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
|
||||
debugSetter.name,
|
||||
debugSetter.device.getDeviceInfo().getAdbHost(),
|
||||
debugSetter.forwardTcpPort,
|
||||
debugSetter.ver);
|
||||
debugSetter.ver,
|
||||
debugSetter.device,
|
||||
debugSetter.pid);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to attach to process", e);
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,7 @@ import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
|
||||
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
|
||||
@@ -402,9 +403,9 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
}
|
||||
|
||||
protected final class ResultsTableCellRenderer implements TableCellRenderer {
|
||||
private final JLabel label;
|
||||
private final NodeLabel label;
|
||||
private final RSyntaxTextArea codeArea;
|
||||
private final JLabel emptyLabel;
|
||||
private final NodeLabel emptyLabel;
|
||||
private final Color codeSelectedColor;
|
||||
private final Color codeBackground;
|
||||
|
||||
@@ -414,11 +415,11 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
codeArea.setRows(1);
|
||||
codeBackground = codeArea.getBackground();
|
||||
codeSelectedColor = codeArea.getSelectionColor();
|
||||
label = new JLabel();
|
||||
label = new NodeLabel();
|
||||
label.setOpaque(true);
|
||||
label.setFont(codeArea.getFont());
|
||||
label.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
emptyLabel = new JLabel();
|
||||
emptyLabel = new NodeLabel();
|
||||
emptyLabel.setOpaque(true);
|
||||
}
|
||||
|
||||
@@ -454,8 +455,9 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
private Component makeCell(JNode node, int column) {
|
||||
if (column == 0) {
|
||||
label.setText(node.makeLongStringHtml());
|
||||
label.setToolTipText(label.getText());
|
||||
label.setToolTipText(node.getTooltip());
|
||||
label.setIcon(node.getIcon());
|
||||
label.disableHtml(node.disableHtml());
|
||||
return label;
|
||||
}
|
||||
if (!node.hasDescString()) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -60,6 +59,7 @@ import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class RenameDialog extends JDialog {
|
||||
private static final long serialVersionUID = -3269715644416902410L;
|
||||
@@ -313,7 +313,7 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private void initUI() {
|
||||
JLabel lbl = new JLabel(NLS.str("popup.rename"));
|
||||
JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT);
|
||||
JLabel nodeLabel = NodeLabel.longName(node);
|
||||
lbl.setLabelFor(nodeLabel);
|
||||
|
||||
renameField = new JTextField(40);
|
||||
|
||||
@@ -409,7 +409,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
updateTableHighlight();
|
||||
prepareForSearch();
|
||||
});
|
||||
this.searchTask.setResultsLimit(50);
|
||||
this.searchTask.setResultsLimit(mainWindow.getSettings().getSearchResultsPerPage());
|
||||
this.searchTask.setProgressListener(this::updateProgress);
|
||||
this.searchTask.fetchResults();
|
||||
LOG.debug("Total search items count estimation: {}", this.searchTask.getTaskProgress().total());
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.Map;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
@@ -31,6 +30,7 @@ import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class UsageDialog extends CommonSearchDialog {
|
||||
private static final long serialVersionUID = -5105405789969134105L;
|
||||
@@ -147,7 +147,7 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
Font codeFont = settings.getFont();
|
||||
JLabel lbl = new JLabel(NLS.str("usage_dialog.label"));
|
||||
lbl.setFont(codeFont);
|
||||
JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT);
|
||||
JLabel nodeLabel = NodeLabel.longName(node);
|
||||
nodeLabel.setFont(codeFont);
|
||||
lbl.setLabelFor(nodeLabel);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.JTree;
|
||||
@@ -39,10 +40,14 @@ import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.reactivex.annotations.Nullable;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.device.debugger.DebugController;
|
||||
import jadx.gui.device.protocol.ADBDevice;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.SmaliArea;
|
||||
@@ -53,6 +58,7 @@ import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JDebuggerPanel extends JPanel {
|
||||
private static final long serialVersionUID = -1111111202102181631L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
|
||||
|
||||
private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute");
|
||||
private static final ImageIcon ICON_RERUN = UiUtils.openSvgIcon("debugger/rerun");
|
||||
@@ -76,6 +82,7 @@ public class JDebuggerPanel extends JPanel {
|
||||
private final transient JSplitPane rightSplitter;
|
||||
private final transient JSplitPane leftSplitter;
|
||||
private final transient IDebugController controller;
|
||||
private final LogcatPanel logcatPanel;
|
||||
|
||||
private final transient VarTreePopupMenu varTreeMenu;
|
||||
private transient KeyEventDispatcher controllerShortCutDispatcher;
|
||||
@@ -131,11 +138,14 @@ public class JDebuggerPanel extends JPanel {
|
||||
|
||||
varTreeMenu = new VarTreePopupMenu(mainWindow);
|
||||
|
||||
JPanel loggerPanel = new JPanel(new CardLayout());
|
||||
JTabbedPane loggerPanel = new JTabbedPane();
|
||||
logger = new JTextArea();
|
||||
logger.setEditable(false);
|
||||
logger.setLineWrap(true);
|
||||
loggerPanel.add(new JScrollPane(logger));
|
||||
JScrollPane loggerScroll = new JScrollPane(logger);
|
||||
loggerPanel.addTab("Debugger Log", null, loggerScroll, null);
|
||||
this.logcatPanel = new LogcatPanel(this);
|
||||
loggerPanel.addTab(NLS.str("logcat.logcat"), null, logcatPanel, null);
|
||||
|
||||
leftSplitter.setLeftComponent(stackFramePanel);
|
||||
leftSplitter.setRightComponent(rightSplitter);
|
||||
@@ -159,6 +169,7 @@ public class JDebuggerPanel extends JPanel {
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
if (what == JOptionPane.OK_OPTION) {
|
||||
controller.exit();
|
||||
logcatPanel.exit();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -373,10 +384,16 @@ public class JDebuggerPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean showDebugger(String procName, String host, int port, int androidVer) {
|
||||
public boolean showDebugger(String procName, String host, int port, int androidVer, ADBDevice device, String pid) {
|
||||
boolean ok = controller.startDebugger(this, host, port, androidVer);
|
||||
if (ok) {
|
||||
log(String.format("Attached %s %s:%d", procName, host, port));
|
||||
try {
|
||||
logcatPanel.init(device, pid);
|
||||
} catch (Exception e) {
|
||||
log(NLS.str("logcat.error_fail_start"));
|
||||
LOG.error("Logcat failed to start", e);
|
||||
}
|
||||
leftSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerStackFrameSplitterLoc());
|
||||
rightSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerVarTreeSplitterLoc());
|
||||
mainWindow.showDebuggerPanel();
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
package jadx.gui.ui.panel;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.BoundedRangeModel;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollBar;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyleContext;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.device.debugger.LogcatController;
|
||||
import jadx.gui.device.protocol.ADB;
|
||||
import jadx.gui.device.protocol.ADBDevice;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
public class LogcatPanel extends JPanel {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
|
||||
|
||||
StyleContext sc = StyleContext.getDefaultStyleContext();
|
||||
private final AttributeSet defaultAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#6c71c4"));
|
||||
private final AttributeSet verboseAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#2aa198"));
|
||||
private final AttributeSet debugAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#859900"));
|
||||
private final AttributeSet infoAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#586e75"));
|
||||
private final AttributeSet warningAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#b58900"));
|
||||
private final AttributeSet errorAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#dc322f"));
|
||||
private final AttributeSet fatalAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#d33682"));
|
||||
private final AttributeSet silentAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#002b36"));
|
||||
|
||||
private static final ImageIcon ICON_PAUSE = UiUtils.openSvgIcon("debugger/threadFrozen");
|
||||
private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute");
|
||||
private static final ImageIcon CLEAR_LOGCAT = UiUtils.openSvgIcon("debugger/trash");
|
||||
|
||||
private transient JTextPane logcatPane;
|
||||
private final transient JDebuggerPanel debugPanel;
|
||||
private LogcatController logcatController;
|
||||
private boolean ready = false;
|
||||
private List<ADB.Process> procs;
|
||||
|
||||
public LogcatPanel(JDebuggerPanel debugPanel) {
|
||||
this.debugPanel = debugPanel;
|
||||
}
|
||||
|
||||
private ArrayList<Integer> pids;
|
||||
private JScrollPane logcatScroll;
|
||||
private int pid;
|
||||
|
||||
private final AbstractAction pauseButton = new AbstractAction(NLS.str("logcat.pause"), ICON_PAUSE) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
toggleLogcat();
|
||||
}
|
||||
};
|
||||
|
||||
private final AbstractAction clearButton = new AbstractAction(NLS.str("logcat.clear"), CLEAR_LOGCAT) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
clearLogcat();
|
||||
}
|
||||
};
|
||||
|
||||
public boolean showLogcat() {
|
||||
ArrayList<String> pkgs = new ArrayList<>();
|
||||
pids = new ArrayList<>();
|
||||
JPanel procBox;
|
||||
for (ADB.Process proc : procs.subList(1, procs.size())) { // skipping first element because it contains the column label
|
||||
pkgs.add(String.format("[pid: %-6s] %s", proc.pid, proc.name));
|
||||
pids.add(Integer.valueOf(proc.pid));
|
||||
}
|
||||
|
||||
String[] msgTypes = {
|
||||
NLS.str("logcat.default"),
|
||||
NLS.str("logcat.verbose"),
|
||||
NLS.str("logcat.debug"),
|
||||
NLS.str("logcat.info"),
|
||||
NLS.str("logcat.warn"),
|
||||
NLS.str("logcat.error"),
|
||||
NLS.str("logcat.fatal"),
|
||||
NLS.str("logcat.silent") };
|
||||
Integer[] msgIndex = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
||||
|
||||
this.setLayout(new BorderLayout());
|
||||
logcatPane = new JTextPane();
|
||||
logcatPane.setEditable(false);
|
||||
logcatScroll = new JScrollPane(logcatPane);
|
||||
JToolBar menuPanel = new JToolBar();
|
||||
|
||||
CheckCombo procObj = new CheckCombo(NLS.str("logcat.process"), 1, pids.toArray(new Integer[0]), pkgs.toArray(new String[0]));
|
||||
procBox = procObj.getContent();
|
||||
procObj.selectAllBut(this.pids.indexOf(this.pid));
|
||||
|
||||
JPanel msgTypeBox = new CheckCombo(NLS.str("logcat.level"), 2, msgIndex, msgTypes).getContent();
|
||||
|
||||
menuPanel.add(procBox);
|
||||
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
menuPanel.add(msgTypeBox);
|
||||
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
menuPanel.add(pauseButton);
|
||||
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
menuPanel.add(clearButton);
|
||||
|
||||
this.add(menuPanel, BorderLayout.NORTH);
|
||||
this.add(logcatScroll, BorderLayout.CENTER);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean clearLogcatArea() {
|
||||
logcatPane.setText("");
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean init(ADBDevice device, String pid) {
|
||||
this.pid = Integer.parseInt(pid);
|
||||
try {
|
||||
this.logcatController = new LogcatController(this, device);
|
||||
this.procs = device.getProcessList();
|
||||
if (!this.showLogcat()) {
|
||||
debugPanel.log(NLS.str("logcat.error_fail_start"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
this.ready = false;
|
||||
LOG.error("Failed to start logcat", e);
|
||||
return false;
|
||||
}
|
||||
this.ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void toggleLogcat() {
|
||||
if (Objects.equals(this.logcatController.getStatus(), "running")) {
|
||||
this.logcatController.stopLogcat();
|
||||
this.pauseButton.putValue(Action.SMALL_ICON, ICON_RUN);
|
||||
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.start"));
|
||||
} else if (Objects.equals(this.logcatController.getStatus(), "stopped")) {
|
||||
this.logcatController.startLogcat();
|
||||
this.pauseButton.putValue(Action.SMALL_ICON, ICON_PAUSE);
|
||||
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.pause"));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearLogcat() {
|
||||
boolean running = false;
|
||||
if (Objects.equals(this.logcatController.getStatus(), "running")) {
|
||||
this.logcatController.stopLogcat();
|
||||
running = true;
|
||||
}
|
||||
this.logcatController.clearLogcat();
|
||||
clearLogcatArea();
|
||||
this.debugPanel.log(this.logcatController.getStatus());
|
||||
if (running) {
|
||||
this.logcatController.startLogcat();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
private boolean isAtBottom(JScrollBar scrollbar) {
|
||||
BoundedRangeModel model = scrollbar.getModel();
|
||||
return (model.getExtent() + model.getValue()) == model.getMaximum();
|
||||
}
|
||||
|
||||
public void log(LogcatController.LogcatInfo logcatInfo) {
|
||||
boolean atBottom = false;
|
||||
|
||||
int len = logcatPane.getDocument().getLength();
|
||||
JScrollBar scrollbar = logcatScroll.getVerticalScrollBar();
|
||||
if (isAtBottom(scrollbar)) {
|
||||
atBottom = true;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" > ")
|
||||
.append(logcatInfo.getTimestamp())
|
||||
.append(" [pid: ")
|
||||
.append(logcatInfo.getPid())
|
||||
.append("] ")
|
||||
.append(logcatInfo.getMsgTypeString())
|
||||
.append(": ")
|
||||
.append(logcatInfo.getMsg())
|
||||
.append("\n");
|
||||
|
||||
try {
|
||||
switch (logcatInfo.getMsgType()) {
|
||||
case 0: // Unknown
|
||||
break;
|
||||
case 1: // Default
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), defaultAset);
|
||||
break;
|
||||
case 2: // Verbose
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), verboseAset);
|
||||
break;
|
||||
case 3: // Debug
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), debugAset);
|
||||
break;
|
||||
case 4: // Info
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), infoAset);
|
||||
break;
|
||||
case 5: // Warn
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), warningAset);
|
||||
break;
|
||||
case 6: // Error
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), errorAset);
|
||||
break;
|
||||
case 7: // Fatal
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), fatalAset);
|
||||
break;
|
||||
case 8: // Silent
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), silentAset);
|
||||
break;
|
||||
default:
|
||||
logcatPane.getDocument().insertString(len, sb.toString(), null);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to write logcat message", e);
|
||||
}
|
||||
|
||||
if (atBottom) {
|
||||
EventQueue.invokeLater(() -> scrollbar.setValue(scrollbar.getMaximum()));
|
||||
}
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
logcatController.exit();
|
||||
clearLogcatArea();
|
||||
this.logcatController.clearEvents();
|
||||
|
||||
}
|
||||
|
||||
class CheckCombo implements ActionListener {
|
||||
private final String[] ids;
|
||||
private final int type;
|
||||
private final String label;
|
||||
private final Integer[] index;
|
||||
private JComboBox<CheckComboStore> combo;
|
||||
|
||||
public CheckCombo(String label, int type, Integer[] index, String[] ids) {
|
||||
this.ids = ids;
|
||||
this.type = type;
|
||||
this.label = label;
|
||||
this.index = index;
|
||||
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JComboBox cb = (JComboBox) e.getSource();
|
||||
CheckComboStore store = (CheckComboStore) cb.getSelectedItem();
|
||||
CheckComboRenderer ccr = (CheckComboRenderer) cb.getRenderer();
|
||||
store.state = !store.state;
|
||||
ccr.checkBox.setSelected(store.state);
|
||||
|
||||
switch (this.type) {
|
||||
case 1: // process
|
||||
logcatController.getFilter().togglePid(store.index, store.state);
|
||||
logcatController.reload();
|
||||
break;
|
||||
case 2: // label
|
||||
logcatController.getFilter().toggleMsgType((byte) store.index, store.state);
|
||||
logcatController.reload();
|
||||
break;
|
||||
default:
|
||||
LOG.error("Invalid Logcat Filter Type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public JPanel getContent() {
|
||||
JLabel label = NodeLabel.noHtml(this.label + ": ");
|
||||
CheckComboStore[] stores = new CheckComboStore[ids.length];
|
||||
for (int j = 0; j < ids.length; j++) {
|
||||
stores[j] = new CheckComboStore(index[j], ids[j], Boolean.TRUE);
|
||||
}
|
||||
combo = new JComboBox<>(stores);
|
||||
combo.setRenderer(new CheckComboRenderer());
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.insets = new Insets(0, 1, 0, 1);
|
||||
panel.add(label, c);
|
||||
c.weightx = 1;
|
||||
c.gridwidth = GridBagConstraints.REMAINDER;
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.insets = new Insets(0, 1, 0, 1);
|
||||
panel.add(combo, c);
|
||||
combo.addActionListener(this);
|
||||
combo.addMouseListener(new FilterClickListener(this));
|
||||
return panel;
|
||||
}
|
||||
|
||||
public void toggleAll(boolean checked) {
|
||||
for (int i = 0; i < combo.getItemCount(); i++) {
|
||||
CheckComboStore ccs = combo.getItemAt(i);
|
||||
ccs.state = checked;
|
||||
switch (type) {
|
||||
case 1: // process
|
||||
logcatController.getFilter().togglePid(ccs.index, checked);
|
||||
break;
|
||||
case 2: // level
|
||||
logcatController.getFilter().toggleMsgType((byte) ccs.index, checked);
|
||||
break;
|
||||
default:
|
||||
LOG.error("Invalid Logcat Toggle Filter Encountered");
|
||||
break;
|
||||
}
|
||||
}
|
||||
logcatController.reload();
|
||||
}
|
||||
|
||||
public void selectAllBut(int ind) {
|
||||
for (int i = 0; i < combo.getItemCount(); i++) {
|
||||
CheckComboStore ccs = combo.getItemAt(i);
|
||||
if (i != ind) {
|
||||
ccs.state = false;
|
||||
} else {
|
||||
ccs.state = true;
|
||||
}
|
||||
switch (type) {
|
||||
case 1: // process
|
||||
logcatController.getFilter().togglePid(ccs.index, ccs.state);
|
||||
break;
|
||||
case 2: // level
|
||||
logcatController.getFilter().toggleMsgType((byte) ccs.index, ccs.state);
|
||||
break;
|
||||
default:
|
||||
LOG.error("Invalid Logcat selectAllBut filter encountered");
|
||||
break;
|
||||
}
|
||||
}
|
||||
logcatController.reload();
|
||||
}
|
||||
}
|
||||
|
||||
class CheckComboRenderer implements ListCellRenderer {
|
||||
JCheckBox checkBox;
|
||||
ArrayList<JCheckBox> boxes = new ArrayList<>();
|
||||
|
||||
public CheckComboRenderer() {
|
||||
checkBox = new JCheckBox();
|
||||
}
|
||||
|
||||
public Component getListCellRendererComponent(JList list, Object value,
|
||||
int index, boolean isSelected, boolean cellHasFocus) {
|
||||
CheckComboStore store = (CheckComboStore) value;
|
||||
checkBox.setText(store.id);
|
||||
checkBox.setSelected(store.state);
|
||||
boxes.add(checkBox);
|
||||
return checkBox;
|
||||
}
|
||||
}
|
||||
|
||||
static class CheckComboStore {
|
||||
String id;
|
||||
Boolean state;
|
||||
int index;
|
||||
|
||||
public CheckComboStore(int index, String id, Boolean state) {
|
||||
this.id = id;
|
||||
this.state = state;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
class FilterClickListener extends MouseAdapter {
|
||||
CheckCombo combo;
|
||||
|
||||
public FilterClickListener(CheckCombo combo) {
|
||||
this.combo = combo;
|
||||
}
|
||||
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
doPop(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
doPop(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doPop(MouseEvent e) {
|
||||
FilterPopup menu = new FilterPopup(combo);
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
|
||||
class FilterPopup extends JPopupMenu {
|
||||
CheckCombo combo;
|
||||
JMenuItem selectAll;
|
||||
JMenuItem unselectAll;
|
||||
JMenuItem selectAttached;
|
||||
|
||||
public FilterPopup(CheckCombo combo) {
|
||||
this.combo = combo;
|
||||
selectAll = new JMenuItem(NLS.str("logcat.select_all"));
|
||||
selectAll.addActionListener(actionEvent -> combo.toggleAll(true));
|
||||
|
||||
unselectAll = new JMenuItem(NLS.str("logcat.unselect_all"));
|
||||
unselectAll.addActionListener(actionEvent -> combo.toggleAll(false));
|
||||
|
||||
if (combo.type == 1) {
|
||||
selectAttached = new JMenuItem(NLS.str("logcat.select_attached"));
|
||||
selectAttached.addActionListener(actionEvent -> combo.selectAllBut(pids.indexOf(pid)));
|
||||
add(selectAttached);
|
||||
}
|
||||
|
||||
add(selectAll);
|
||||
add(unselectAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.gui.utils.ui;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public class NodeLabel extends JLabel {
|
||||
|
||||
public static NodeLabel longName(JNode node) {
|
||||
NodeLabel label = new NodeLabel(node.makeLongStringHtml(), node.disableHtml());
|
||||
label.setIcon(node.getIcon());
|
||||
label.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
return label;
|
||||
}
|
||||
|
||||
public static NodeLabel noHtml(String label) {
|
||||
return new NodeLabel(label, true);
|
||||
}
|
||||
|
||||
public static void disableHtml(JLabel label, boolean disable) {
|
||||
label.putClientProperty("html.disable", disable);
|
||||
}
|
||||
|
||||
private boolean htmlDisabled = false;
|
||||
|
||||
public NodeLabel() {
|
||||
disableHtml(true);
|
||||
}
|
||||
|
||||
public NodeLabel(String label) {
|
||||
disableHtml(true);
|
||||
setText(label);
|
||||
}
|
||||
|
||||
public NodeLabel(String label, boolean disableHtml) {
|
||||
disableHtml(disableHtml);
|
||||
setText(label);
|
||||
}
|
||||
|
||||
public void disableHtml(boolean disable) {
|
||||
if (htmlDisabled != disable) {
|
||||
htmlDisabled = disable;
|
||||
disableHtml(this, disable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,8 @@ preferences.rename=Umbenennen
|
||||
preferences.rename_case=System unterscheidet zwischen Groß/Kleinschreibung
|
||||
preferences.rename_valid=Ist eine gültige Kennung
|
||||
preferences.rename_printable=Ist druckbar
|
||||
preferences.search_res_title=Ressourcen durchsuchen
|
||||
preferences.search_group_title=Ressourcen durchsuchen
|
||||
#preferences.search_results_per_page=Results per page (0 - no limit)
|
||||
preferences.res_file_ext=Dateierweiterungen (z.B. .xml|.html), * bedeutet alle
|
||||
preferences.res_skip_file=Dateien überspringen (MB)
|
||||
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Wechsel zu 0
|
||||
debugger.popup_change_to_one=Wechsel zu 1
|
||||
debugger.popup_copy_value=Wert kopieren
|
||||
|
||||
#logcat.pause=Pause Logcat
|
||||
#logcat.start=Resume Logcat
|
||||
#logcat.clear=Clear Logcat
|
||||
|
||||
#logcat.error_fail_start=Failed to start logcat
|
||||
#logcat.process=Process
|
||||
#logcat.level=Level
|
||||
#logcat.default=Default
|
||||
#logcat.verbose=Verbose
|
||||
#logcat.debug=Debug
|
||||
#logcat.info=Info
|
||||
#logcat.warn=Warn
|
||||
#logcat.error=Error
|
||||
#logcat.fatal=Fatal
|
||||
#logcat.silent=Silent
|
||||
#logcat.logcat=Logcat
|
||||
#logcat.select_attached=Select Attached
|
||||
#logcat.select_all=Select All
|
||||
#logcat.unselect_all=Unselect All
|
||||
|
||||
set_value_dialog.label_value=Wert
|
||||
set_value_dialog.btn_set=Wert einstellen
|
||||
set_value_dialog.title=Wert Einstellen
|
||||
|
||||
@@ -12,7 +12,7 @@ menu.alwaysSelectOpened=Always Select Opened File/Class
|
||||
menu.navigation=Navigation
|
||||
menu.text_search=Text search
|
||||
menu.class_search=Class search
|
||||
menu.comment_search=Comment search
|
||||
menu.comment_search=Comment searchF
|
||||
menu.tools=Tools
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
@@ -194,9 +194,10 @@ preferences.rename=Rename identifiers
|
||||
preferences.rename_case=To fix case sensitivity issues
|
||||
preferences.rename_valid=To make them valid
|
||||
preferences.rename_printable=To make printable
|
||||
preferences.search_res_title=Search Resource
|
||||
preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all
|
||||
preferences.res_skip_file=Skip files exceed (MB)
|
||||
preferences.search_group_title=Search
|
||||
preferences.search_results_per_page=Results per page (0 - no limit)
|
||||
preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
|
||||
preferences.res_skip_file=Skip resources files if larger (MB)
|
||||
|
||||
msg.open_file=Please open file
|
||||
msg.saving_sources=Saving sources
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Change to 0
|
||||
debugger.popup_change_to_one=Change to 1
|
||||
debugger.popup_copy_value=Copy Value
|
||||
|
||||
logcat.pause=Pause Logcat
|
||||
logcat.start=Resume Logcat
|
||||
logcat.clear=Clear Logcat
|
||||
|
||||
logcat.error_fail_start=Failed to start logcat
|
||||
logcat.process=Process
|
||||
logcat.level=Level
|
||||
logcat.default=Default
|
||||
logcat.verbose=Verbose
|
||||
logcat.debug=Debug
|
||||
logcat.info=Info
|
||||
logcat.warn=Warn
|
||||
logcat.error=Error
|
||||
logcat.fatal=Fatal
|
||||
logcat.silent=Silent
|
||||
logcat.logcat=Logcat
|
||||
logcat.select_attached=Select Attached
|
||||
logcat.select_all=Select All
|
||||
logcat.unselect_all=Unselect All
|
||||
|
||||
set_value_dialog.label_value=Value
|
||||
set_value_dialog.btn_set=Set Value
|
||||
set_value_dialog.title=Set Value
|
||||
|
||||
@@ -194,9 +194,10 @@ preferences.reset_title=Reestablecer preferencias
|
||||
#preferences.rename_case=
|
||||
#preferences.rename_valid=
|
||||
#preferences.rename_printable=
|
||||
#preferences.search_res_title=
|
||||
#preferences.res_file_ext=
|
||||
#preferences.res_skip_file=
|
||||
#preferences.search_group_title=Search
|
||||
#preferences.search_results_per_page=Results per page (0 - no limit)
|
||||
#preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
|
||||
#preferences.res_skip_file=Skip resources files if larger (MB)
|
||||
|
||||
msg.open_file=Por favor, abra un archivo
|
||||
msg.saving_sources=Guardando fuente
|
||||
@@ -287,6 +288,26 @@ certificate.serialPubKeyY=Y
|
||||
#debugger.popup_change_to_one=Change to 1
|
||||
#debugger.popup_copy_value=Copy Value
|
||||
|
||||
#logcat.pause=Pause Logcat
|
||||
#logcat.start=Resume Logcat
|
||||
#logcat.clear=Clear Logcat
|
||||
|
||||
#logcat.error_fail_start=Failed to start logcat
|
||||
#logcat.process=Process
|
||||
#logcat.level=Level
|
||||
#logcat.default=Default
|
||||
#logcat.verbose=Verbose
|
||||
#logcat.debug=Debug
|
||||
#logcat.info=Info
|
||||
#logcat.warn=Warn
|
||||
#logcat.error=Error
|
||||
#logcat.fatal=Fatal
|
||||
#logcat.silent=Silent
|
||||
#logcat.logcat=Logcat
|
||||
#logcat.select_attached=Select Attached
|
||||
#logcat.select_all=Select All
|
||||
#logcat.unselect_all=Unselect All
|
||||
|
||||
#set_value_dialog.label_value=Value
|
||||
#set_value_dialog.btn_set=Set Value
|
||||
#set_value_dialog.title=Set Value
|
||||
|
||||
@@ -23,31 +23,31 @@ menu.update_label=새 버전 %s 이(가) 존재합니다!
|
||||
file.open_action=파일 열기 ...
|
||||
file.add_files_action=파일 추가
|
||||
file.open_title=파일 열기
|
||||
#file.open_project=Open project
|
||||
file.open_project=프로젝트 열기
|
||||
file.new_project=새 프로젝트
|
||||
file.save_project=프로젝트 저장
|
||||
file.save_project_as=다른 이름으로 프로젝트 저장...
|
||||
#file.reload=Reload files
|
||||
#file.live_reload=Live reload
|
||||
#file.live_reload_desc=Auto reload files on changes
|
||||
#file.export_mappings_as=
|
||||
file.reload=파일 다시 로드
|
||||
file.live_reload=라이브 로드
|
||||
file.live_reload_desc=파일 내용 변경 시 자동으로 다시 로드
|
||||
file.export_mappings_as=다른 이름으로 매핑 내보내기...
|
||||
file.save_all=모두 저장
|
||||
file.export_gradle=Gradle 프로젝트로 저장
|
||||
file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택
|
||||
file.exit=나가기
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
start_page.title=페이지 시작
|
||||
start_page.start=시작
|
||||
start_page.recent=최근 프로젝트
|
||||
|
||||
tree.sources_title=소스코드
|
||||
tree.resources_title=리소스
|
||||
tree.loading=로딩중...
|
||||
|
||||
progress.load=로딩중
|
||||
#progress.export_mappings=
|
||||
progress.export_mappings=매핑 내보내는 중
|
||||
progress.decompile=디컴파일 중
|
||||
#progress.canceling=Canceling
|
||||
progress.canceling=취소 중
|
||||
|
||||
error_dialog.title=오류
|
||||
|
||||
@@ -89,16 +89,16 @@ common_dialog.add=추가
|
||||
common_dialog.update=업데이트
|
||||
common_dialog.remove=삭제
|
||||
|
||||
#file_dialog.supported_files=Supported files
|
||||
#file_dialog.load_dir_title=Load directory
|
||||
#file_dialog.load_dir_confirm=Load all files from directory?
|
||||
file_dialog.supported_files=지원되는 파일
|
||||
file_dialog.load_dir_title=디렉토리 불러오기
|
||||
file_dialog.load_dir_confirm=디렉토리에서 모든 파일을 불러오시겠습니까?
|
||||
|
||||
search_dialog.open=열기
|
||||
search_dialog.cancel=취소
|
||||
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_button=검색
|
||||
search_dialog.search_history=검색 기록
|
||||
search_dialog.auto_search=자동 검색
|
||||
search_dialog.search_in=정의 검색 :
|
||||
search_dialog.class=클래스
|
||||
search_dialog.method=메소드
|
||||
@@ -106,14 +106,14 @@ search_dialog.field=필드
|
||||
search_dialog.code=코드
|
||||
search_dialog.options=옵션 검색:
|
||||
search_dialog.ignorecase=대소문자 구분 안함
|
||||
#search_dialog.load_more=Load more
|
||||
#search_dialog.load_all=Load all
|
||||
#search_dialog.stop=Stop
|
||||
#search_dialog.results_incomplete=Found %d+
|
||||
#search_dialog.results_complete=Found %d (complete)
|
||||
search_dialog.load_more=더보기
|
||||
search_dialog.load_all=모두 로드
|
||||
search_dialog.stop=정지
|
||||
search_dialog.results_incomplete=%d+개 찾음
|
||||
search_dialog.results_complete=%d개 찾음 (검색 완료)
|
||||
search_dialog.col_node=노드
|
||||
search_dialog.col_code=코드
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.sort_results=결과 정렬
|
||||
search_dialog.regex=정규식
|
||||
search_dialog.active_tab=열려 있는 탭에서만 검색
|
||||
search_dialog.comments=주석
|
||||
@@ -138,28 +138,28 @@ preferences.title=설정
|
||||
preferences.deobfuscation=난독화 해제
|
||||
preferences.appearance=외관
|
||||
preferences.decompile=디컴파일
|
||||
#preferences.plugins=Plugins
|
||||
preferences.plugins=플러그인
|
||||
preferences.project=프로젝트
|
||||
preferences.other=기타
|
||||
preferences.language=언어
|
||||
preferences.lineNumbersMode=편집기 줄 번호 모드
|
||||
#preferences.jumpOnDoubleClick=Enable jump on double click
|
||||
preferences.jumpOnDoubleClick=더블 클릭 시 점프 활성화
|
||||
preferences.check_for_updates=시작시 업데이트 확인
|
||||
#preferences.useDx=Use dx/d8 to convert java bytecode
|
||||
#preferences.decompilationMode=Decompilation mode
|
||||
#preferences.codeCacheMode=Code cache mode
|
||||
preferences.useDx=dx/d8을 사용하여 Java 바이트 코드 변환
|
||||
preferences.decompilationMode=디컴파일 모드
|
||||
preferences.codeCacheMode=코드 캐시 모드
|
||||
preferences.showInconsistentCode=디컴파일 안된 코드 표시
|
||||
preferences.escapeUnicode=유니코드 이스케이프
|
||||
preferences.replaceConsts=상수 바꾸기
|
||||
preferences.respectBytecodeAccessModifiers=바이트코드 액세스 수정자 존중
|
||||
preferences.useImports=import 문 사용
|
||||
#preferences.useDebugInfo=Use debug info
|
||||
preferences.useDebugInfo=디버그 정보 사용
|
||||
preferences.inlineAnonymous=인라인 익명 클래스
|
||||
preferences.inlineMethods=인라인 메서드
|
||||
#preferences.extractFinally=Extract finally block
|
||||
preferences.extractFinally=finally 블록 추출
|
||||
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
|
||||
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
|
||||
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
|
||||
preferences.useKotlinMethodsForVarNames=변수 이름 바꾸기에 kotlin 메서드 사용
|
||||
preferences.commentsLevel=코드 주석 수준
|
||||
preferences.autoSave=자동 저장
|
||||
preferences.threads=처리 스레드 수
|
||||
@@ -177,12 +177,12 @@ preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
|
||||
preferences.select_font=변경
|
||||
preferences.select_smali_font=변경
|
||||
preferences.deobfuscation_on=난독 해제 활성화
|
||||
#preferences.deobfuscation_map_file_mode=Map file handle mode
|
||||
preferences.deobfuscation_map_file_mode=맵 파일 처리 모드
|
||||
preferences.deobfuscation_min_len=최소 이름 길이
|
||||
preferences.deobfuscation_max_len=최대 이름 길이
|
||||
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
|
||||
preferences.deobfuscation_kotlin_metadata=클래스 및 패키지 이름에 대한 Kotlin 메타 데이터 파싱
|
||||
#preferences.deobfuscation_res_name_source=Better resources name source
|
||||
preferences.deobfuscation_res_name_source=더 나은 리소스 이름 소스
|
||||
preferences.save=저장
|
||||
preferences.cancel=취소
|
||||
preferences.reset=재설정
|
||||
@@ -194,7 +194,8 @@ preferences.rename=이름 바꾸기
|
||||
preferences.rename_case=시스템 대소문자 구분
|
||||
preferences.rename_valid=유효한 식별자로 바꾸기
|
||||
preferences.rename_printable=출력 가능하게 바꾸기
|
||||
preferences.search_res_title=리소스 검색
|
||||
preferences.search_group_title=리소스 검색
|
||||
#preferences.search_results_per_page=Results per page (0 - no limit)
|
||||
preferences.res_file_ext=파일 확장자 (예: .xml|.html) (* 은 전체를 의미)
|
||||
preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB)
|
||||
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=0으로 변경
|
||||
debugger.popup_change_to_one=1로 변경
|
||||
debugger.popup_copy_value=값 복사
|
||||
|
||||
#logcat.pause=Pause Logcat
|
||||
#logcat.start=Resume Logcat
|
||||
#logcat.clear=Clear Logcat
|
||||
|
||||
#logcat.error_fail_start=Failed to start logcat
|
||||
#logcat.process=Process
|
||||
#logcat.level=Level
|
||||
#logcat.default=Default
|
||||
#logcat.verbose=Verbose
|
||||
#logcat.debug=Debug
|
||||
#logcat.info=Info
|
||||
#logcat.warn=Warn
|
||||
#logcat.error=Error
|
||||
#logcat.fatal=Fatal
|
||||
#logcat.silent=Silent
|
||||
#logcat.logcat=Logcat
|
||||
#logcat.select_attached=Select Attached
|
||||
#logcat.select_all=Select All
|
||||
#logcat.unselect_all=Unselect All
|
||||
|
||||
set_value_dialog.label_value=값
|
||||
set_value_dialog.btn_set=값 설정
|
||||
set_value_dialog.title=값 설정
|
||||
|
||||
@@ -23,7 +23,7 @@ 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.open_project=Abrir projeto
|
||||
file.new_project=Novo projeto
|
||||
file.save_project=Salvar projeto
|
||||
file.save_project_as=Salvar projeto como...
|
||||
@@ -36,9 +36,9 @@ 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
|
||||
start_page.title=Página inicial
|
||||
start_page.start=Começar
|
||||
start_page.recent=Projetos recentes
|
||||
|
||||
tree.sources_title=Código fonte
|
||||
tree.resources_title=Recursos
|
||||
@@ -95,10 +95,10 @@ 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.open_by_name=Buscar por texto:
|
||||
search_dialog.search_button=Buscar
|
||||
search_dialog.search_history=Buscar histórico
|
||||
search_dialog.auto_search=Busca automática
|
||||
search_dialog.search_in=Buscar definições de:
|
||||
search_dialog.class=Classe
|
||||
search_dialog.method=Método
|
||||
@@ -108,13 +108,13 @@ 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.stop=Parar
|
||||
search_dialog.results_incomplete=Encontradas %d+
|
||||
search_dialog.results_complete=Encontradas %d (complete)
|
||||
search_dialog.results_complete=Encontradas %d (completos)
|
||||
search_dialog.col_node=Nó
|
||||
search_dialog.col_code=Código
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.sort_results=Ordenar resultados
|
||||
search_dialog.regex=Expressão regular
|
||||
search_dialog.active_tab=Apenas abas ativas
|
||||
search_dialog.comments=Comentários
|
||||
search_dialog.resource=Recursos
|
||||
@@ -194,7 +194,8 @@ 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.search_group_title=Buscar recursos
|
||||
#preferences.search_results_per_page=Results per page (0 - no limit)
|
||||
preferences.res_file_ext=Extensões de arquivos (ex: .xml|.html), * significa todas
|
||||
preferences.res_skip_file=Pular arquivos excedidos
|
||||
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Alterar para 0
|
||||
debugger.popup_change_to_one=Alterar para 1
|
||||
debugger.popup_copy_value=Copiar valor
|
||||
|
||||
#logcat.pause=Pause Logcat
|
||||
#logcat.start=Resume Logcat
|
||||
#logcat.clear=Clear Logcat
|
||||
|
||||
#logcat.error_fail_start=Failed to start logcat
|
||||
#logcat.process=Process
|
||||
#logcat.level=Level
|
||||
#logcat.default=Default
|
||||
#logcat.verbose=Verbose
|
||||
#logcat.debug=Debug
|
||||
#logcat.info=Info
|
||||
#logcat.warn=Warn
|
||||
#logcat.error=Error
|
||||
#logcat.fatal=Fatal
|
||||
#logcat.silent=Silent
|
||||
#logcat.logcat=Logcat
|
||||
#logcat.select_attached=Select Attached
|
||||
#logcat.select_all=Select All
|
||||
#logcat.unselect_all=Unselect All
|
||||
|
||||
set_value_dialog.label_value=Valor
|
||||
set_value_dialog.btn_set=Definir Valor
|
||||
set_value_dialog.title=Definir Valor
|
||||
|
||||
@@ -194,7 +194,8 @@ preferences.rename=重命名标识符
|
||||
preferences.rename_case=需要标识符能区分大小写
|
||||
preferences.rename_valid=需要标识符能符合规范
|
||||
preferences.rename_printable=需要标识符可正常显示
|
||||
preferences.search_res_title=搜索资源
|
||||
preferences.search_group_title=搜索资源
|
||||
preferences.search_results_per_page=每页结果数(0 - 无限制)
|
||||
preferences.res_file_ext=文件扩展名 (e.g. .xml|.html),* 表示所有
|
||||
preferences.res_skip_file=跳过文件大小(MB)
|
||||
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=修改为0
|
||||
debugger.popup_change_to_one=修改为1
|
||||
debugger.popup_copy_value=复制值
|
||||
|
||||
logcat.pause=暂停 Logcat
|
||||
logcat.start=恢复 Logcat
|
||||
logcat.clear=清空 Logcat
|
||||
|
||||
logcat.error_fail_start=无法启动Logcat
|
||||
logcat.process=进程
|
||||
logcat.level=级别
|
||||
logcat.default=默认
|
||||
logcat.verbose=Verbose
|
||||
logcat.debug=Debug
|
||||
logcat.info=Info
|
||||
logcat.warn=Warn
|
||||
logcat.error=Error
|
||||
logcat.fatal=Fatal
|
||||
logcat.silent=Silent
|
||||
logcat.logcat=Logcat
|
||||
logcat.select_attached=选择附加
|
||||
logcat.select_all=选择所有
|
||||
logcat.unselect_all=反选所有
|
||||
|
||||
set_value_dialog.label_value=值
|
||||
set_value_dialog.btn_set=修改
|
||||
set_value_dialog.title=设置新值
|
||||
|
||||
@@ -23,7 +23,7 @@ menu.update_label=新版本 %s 可供下載!
|
||||
file.open_action=開啟檔案...
|
||||
file.add_files_action=新增檔案
|
||||
file.open_title=開啟檔案
|
||||
#file.open_project=Open project
|
||||
file.open_project=開啟專案
|
||||
file.new_project=新建專案
|
||||
file.save_project=儲存專案
|
||||
file.save_project_as=另存專案...
|
||||
@@ -36,9 +36,9 @@ file.export_gradle=另存為 gradle 專案
|
||||
file.save_all_msg=選擇儲存反編譯原始碼的路徑
|
||||
file.exit=離開
|
||||
|
||||
#start_page.title=Start page
|
||||
#start_page.start=Start
|
||||
#start_page.recent=Recent projects
|
||||
start_page.title=開始頁面
|
||||
start_page.start=開始
|
||||
start_page.recent=近期專案
|
||||
|
||||
tree.sources_title=原始碼
|
||||
tree.resources_title=資源
|
||||
@@ -96,9 +96,9 @@ file_dialog.load_dir_confirm=要載入目錄中的所有檔案嗎?
|
||||
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_button=搜尋
|
||||
search_dialog.search_history=搜尋記錄
|
||||
search_dialog.auto_search=自動搜尋
|
||||
search_dialog.search_in=搜尋定義:
|
||||
search_dialog.class=類別
|
||||
search_dialog.method=方法
|
||||
@@ -108,12 +108,12 @@ search_dialog.options=搜尋選項:
|
||||
search_dialog.ignorecase=不區分大小寫
|
||||
search_dialog.load_more=載入更多
|
||||
search_dialog.load_all=載入全部
|
||||
#search_dialog.stop=Stop
|
||||
search_dialog.stop=停止
|
||||
search_dialog.results_incomplete=找到 %d+
|
||||
search_dialog.results_complete=找到 %d (完整)
|
||||
search_dialog.col_node=無
|
||||
search_dialog.col_code=程式碼
|
||||
#search_dialog.sort_results=Sort results
|
||||
search_dialog.sort_results=排序結果
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.active_tab=僅使用中分頁
|
||||
search_dialog.comments=註解
|
||||
@@ -156,7 +156,7 @@ preferences.useImports=使用 import 陳述式
|
||||
preferences.useDebugInfo=使用除錯資訊
|
||||
preferences.inlineAnonymous=內嵌匿名類別
|
||||
preferences.inlineMethods=內嵌方式
|
||||
#preferences.extractFinally=Extract finally block
|
||||
preferences.extractFinally=擷取 finally 區塊
|
||||
preferences.fsCaseSensitive=檔案系統區分大小寫
|
||||
preferences.skipResourcesDecode=不要為資源解碼
|
||||
preferences.useKotlinMethodsForVarNames=使用 Kotlin 方法來為變數重新命名
|
||||
@@ -194,7 +194,8 @@ preferences.rename=重新命名識別碼
|
||||
preferences.rename_case=以修復區分大小寫問題
|
||||
preferences.rename_valid=以使其有效
|
||||
preferences.rename_printable=以使其可列印
|
||||
preferences.search_res_title=搜尋資源
|
||||
preferences.search_group_title=搜尋資源
|
||||
preferences.search_results_per_page=每頁的搜尋結果數 (0 - 無限制)
|
||||
preferences.res_file_ext=副檔名 (e.g. .xml|.html), * 表示全部
|
||||
preferences.res_skip_file=略過大於此值的檔案 (MB)
|
||||
|
||||
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=修改為 0
|
||||
debugger.popup_change_to_one=修改為 1
|
||||
debugger.popup_copy_value=複製數值
|
||||
|
||||
logcat.pause=暫停 Logcat
|
||||
logcat.start=繼續 Logcat
|
||||
logcat.clear=清除 Logcat
|
||||
|
||||
logcat.error_fail_start=無法啟動 Logcat
|
||||
logcat.process=程序
|
||||
logcat.level=等級
|
||||
logcat.default=預設
|
||||
logcat.verbose=詳細資訊
|
||||
logcat.debug=偵錯
|
||||
logcat.info=資訊
|
||||
logcat.warn=警告
|
||||
logcat.error=錯誤
|
||||
logcat.fatal=嚴重錯誤
|
||||
logcat.silent=靜音
|
||||
logcat.logcat=Logcat
|
||||
logcat.select_attached=選取附件
|
||||
logcat.select_all=全選
|
||||
logcat.unselect_all=取消全選
|
||||
|
||||
set_value_dialog.label_value=數值
|
||||
set_value_dialog.btn_set=設置數值
|
||||
set_value_dialog.title=設置數值
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0002 3H12.0002H13.0002V4H12.0002V13L11.0002 14H4.00024L3.00024 13V4H2.00024V3H5.00024V2C5.00024 1.73478 5.10555 1.48038 5.29309 1.29285C5.48063 1.10531 5.73503 1 6.00024 1H9.00024C9.26546 1 9.51986 1.10531 9.7074 1.29285C9.89493 1.48038 10.0002 1.73478 10.0002 2V3ZM9.00024 2H6.00024V3H9.00024V2ZM4.00024 13H11.0002V4H4.00024V13ZM6.00024 5H5.00024V12H6.00024V5ZM7.00024 5H8.00024V12H7.00024V5ZM9.00024 5H10.0002V12H9.00024V5Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 600 B |
@@ -7,7 +7,7 @@ dependencies {
|
||||
|
||||
implementation(project(":jadx-plugins:jadx-dex-input"))
|
||||
implementation('com.jakewharton.android.repackaged:dalvik-dx:11.0.0_r3')
|
||||
implementation('com.android.tools:r8:3.3.28')
|
||||
implementation('com.android.tools:r8:3.3.75')
|
||||
|
||||
implementation 'org.ow2.asm:asm:9.3'
|
||||
implementation 'org.ow2.asm:asm:9.4'
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ public class ZipSecurity {
|
||||
if (isInSubDirectoryInternal(currentPath, canonical)) {
|
||||
return true;
|
||||
}
|
||||
LOG.error("Path traversal attack detected, invalid name: {}", entryName);
|
||||
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Path traversal attack detected, invalid name: {}", entryName);
|
||||
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user