Merge branch 'master' into type-inference-wip
This commit is contained in:
@@ -48,6 +48,7 @@ allprojects {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
jacoco {
|
||||
|
||||
@@ -35,6 +35,13 @@ public class ResourceFile {
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
|
||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
|
||||
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
@@ -65,11 +72,4 @@ public class ResourceFile {
|
||||
public String toString() {
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
|
||||
}
|
||||
|
||||
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
|
||||
private final CodeWriter content;
|
||||
|
||||
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.singleFile(getName(), content);
|
||||
}
|
||||
|
||||
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFileContent(name, type, content);
|
||||
return ResContainer.textResource(getName(), content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
@@ -31,8 +32,6 @@ import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
@@ -47,11 +46,11 @@ public final class ResourcesLoader {
|
||||
return list;
|
||||
}
|
||||
|
||||
public interface ResourceDecoder {
|
||||
ResContainer decode(long size, InputStream is) throws IOException;
|
||||
public interface ResourceDecoder<T> {
|
||||
T decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
@@ -80,46 +79,50 @@ public final class ResourcesLoader {
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Decode error", e);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
cw.startLine(Utils.getStackTrace(e.getCause()));
|
||||
return ResContainer.singleFile(rf.getName(), cw);
|
||||
return ResContainer.textResource(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
jadxRef.getXmlParser().parse(inputStream));
|
||||
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser()
|
||||
.decodeFiles(inputStream);
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
|
||||
case CODE:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case UNKNOWN:
|
||||
return ResContainer.singleBinaryFile(rf.getName(), inputStream);
|
||||
return decodeImage(rf, inputStream);
|
||||
|
||||
default:
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getName(), os.toByteArray());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
}
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
@@ -141,7 +144,7 @@ public final class ResourcesLoader {
|
||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||
String name = file.getAbsolutePath();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
list.add(rf);
|
||||
}
|
||||
@@ -153,7 +156,7 @@ public final class ResourcesLoader {
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -21,6 +20,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class NameGen {
|
||||
|
||||
@@ -31,21 +31,22 @@ public class NameGen {
|
||||
private final boolean fallback;
|
||||
|
||||
static {
|
||||
OBJ_ALIAS = new HashMap<>();
|
||||
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
|
||||
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
|
||||
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
|
||||
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
|
||||
OBJ_ALIAS.put("java.util.Iterator", "it");
|
||||
OBJ_ALIAS.put("java.lang.Boolean", "bool");
|
||||
OBJ_ALIAS.put("java.lang.Short", "sh");
|
||||
OBJ_ALIAS.put("java.lang.Integer", "num");
|
||||
OBJ_ALIAS.put("java.lang.Character", "ch");
|
||||
OBJ_ALIAS.put("java.lang.Byte", "b");
|
||||
OBJ_ALIAS.put("java.lang.Float", "f");
|
||||
OBJ_ALIAS.put("java.lang.Long", "l");
|
||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||
OBJ_ALIAS.put("java.lang.StringBuilder", "sb");
|
||||
OBJ_ALIAS = Utils.newConstStringMap(
|
||||
Consts.CLASS_STRING, "str",
|
||||
Consts.CLASS_CLASS, "cls",
|
||||
Consts.CLASS_THROWABLE, "th",
|
||||
Consts.CLASS_OBJECT, "obj",
|
||||
"java.util.Iterator", "it",
|
||||
"java.lang.Boolean", "bool",
|
||||
"java.lang.Short", "sh",
|
||||
"java.lang.Integer", "num",
|
||||
"java.lang.Character", "ch",
|
||||
"java.lang.Byte", "b",
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb"
|
||||
);
|
||||
}
|
||||
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -11,6 +12,7 @@ import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
@@ -306,16 +308,23 @@ public class RegionGen extends InsnGen {
|
||||
return;
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
useClass(code, it.next());
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
code.add(" | ");
|
||||
useClass(code, it.next());
|
||||
}
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
declareVar(code, (RegisterArg) arg);
|
||||
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
useClass(code, handler.getCatchType());
|
||||
}
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
code.add(") {");
|
||||
|
||||
@@ -243,6 +243,10 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameField(FieldNode field) {
|
||||
field.getFieldInfo().setAlias(makeFieldAlias(field));
|
||||
}
|
||||
|
||||
public void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
@@ -253,6 +257,13 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameMethod(MethodNode mth) {
|
||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
||||
PackageNode pkg = getPackageNode(origPkgName, true);
|
||||
pkg.setAlias(pkgAlias);
|
||||
|
||||
@@ -2,12 +2,14 @@ package jadx.core.dex.info;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public final class ClassInfo {
|
||||
public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
|
||||
private final ArgType type;
|
||||
private String pkg;
|
||||
@@ -194,4 +196,9 @@ public final class ClassInfo {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClassInfo o) {
|
||||
return fullName.compareTo(o.fullName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ public final class FieldInfo {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean equalsNameAndType(FieldInfo other) {
|
||||
return name.equals(other.name) && type.equals(other.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
||||
@@ -321,6 +321,15 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldNode searchFieldByNameAndType(FieldInfo field) {
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getFieldInfo().equalsNameAndType(field)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldNode searchFieldByName(String name) {
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getName().equals(name)) {
|
||||
|
||||
@@ -152,16 +152,15 @@ public class DexNode implements IDexNode {
|
||||
|
||||
@Nullable
|
||||
FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) {
|
||||
FieldNode field = cls.searchFieldByName(fieldInfo.getName());
|
||||
FieldNode field = cls.searchFieldByNameAndType(fieldInfo);
|
||||
if (field != null) {
|
||||
return field;
|
||||
}
|
||||
FieldNode found;
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
found = deepResolveField(superNode, fieldInfo);
|
||||
FieldNode found = deepResolveField(superNode, fieldInfo);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
@@ -170,7 +169,7 @@ public class DexNode implements IDexNode {
|
||||
for (ArgType iFaceType : cls.getInterfaces()) {
|
||||
ClassNode iFaceNode = resolveClass(iFaceType);
|
||||
if (iFaceNode != null) {
|
||||
found = deepResolveField(iFaceNode, fieldInfo);
|
||||
FieldNode found = deepResolveField(iFaceNode, fieldInfo);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -117,16 +117,16 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
|
||||
DexNode dex = parentClass.dex();
|
||||
Code mthCode = dex.readCode(methodData);
|
||||
regsCount = mthCode.getRegistersSize();
|
||||
this.regsCount = mthCode.getRegistersSize();
|
||||
initMethodTypes();
|
||||
|
||||
InsnDecoder decoder = new InsnDecoder(this);
|
||||
decoder.decodeInsns(mthCode);
|
||||
instructions = decoder.process();
|
||||
codeSize = instructions.length;
|
||||
this.instructions = decoder.process();
|
||||
this.codeSize = instructions.length;
|
||||
|
||||
initTryCatches(mthCode);
|
||||
initJumps();
|
||||
initTryCatches(this, mthCode, instructions);
|
||||
initJumps(instructions);
|
||||
|
||||
this.debugInfoOffset = mthCode.getDebugInfoOffset();
|
||||
} catch (Exception e) {
|
||||
@@ -263,37 +263,37 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
return genericMap;
|
||||
}
|
||||
|
||||
private void initTryCatches(Code mthCode) {
|
||||
InsnNode[] insnByOffset = instructions;
|
||||
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
|
||||
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
|
||||
Try[] tries = mthCode.getTries();
|
||||
if (catchBlocks.length == 0 && tries.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hc = 0;
|
||||
int handlersCount = 0;
|
||||
Set<Integer> addrs = new HashSet<>();
|
||||
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);
|
||||
|
||||
for (CatchHandler handler : catchBlocks) {
|
||||
TryCatchBlock tcBlock = new TryCatchBlock();
|
||||
catches.add(tcBlock);
|
||||
for (int i = 0; i < handler.getAddresses().length; i++) {
|
||||
int addr = handler.getAddresses()[i];
|
||||
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
|
||||
tcBlock.addHandler(this, addr, type);
|
||||
int[] handlerAddrArr = handler.getAddresses();
|
||||
for (int i = 0; i < handlerAddrArr.length; i++) {
|
||||
int addr = handlerAddrArr[i];
|
||||
ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]);
|
||||
tcBlock.addHandler(mth, addr, type);
|
||||
addrs.add(addr);
|
||||
hc++;
|
||||
handlersCount++;
|
||||
}
|
||||
int addr = handler.getCatchAllAddress();
|
||||
if (addr >= 0) {
|
||||
tcBlock.addHandler(this, addr, null);
|
||||
tcBlock.addHandler(mth, addr, null);
|
||||
addrs.add(addr);
|
||||
hc++;
|
||||
handlersCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (hc > 0 && hc != addrs.size()) {
|
||||
if (handlersCount > 0 && handlersCount != addrs.size()) {
|
||||
// resolve nested try blocks:
|
||||
// inner block contains all handlers from outer block => remove these handlers from inner block
|
||||
// each handler must be only in one try/catch block
|
||||
@@ -301,7 +301,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
for (TryCatchBlock ct2 : catches) {
|
||||
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
|
||||
for (ExceptionHandler h : ct1.getHandlers()) {
|
||||
ct2.removeHandler(this, h);
|
||||
ct2.removeHandler(mth, h);
|
||||
h.setTryBlock(ct1);
|
||||
}
|
||||
}
|
||||
@@ -315,6 +315,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
for (ExceptionHandler eh : ct.getHandlers()) {
|
||||
int addr = eh.getHandleOffset();
|
||||
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
|
||||
// TODO: don't override existing attribute
|
||||
insnByOffset[addr].addAttr(ehAttr);
|
||||
}
|
||||
}
|
||||
@@ -341,8 +342,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
}
|
||||
}
|
||||
|
||||
private void initJumps() {
|
||||
InsnNode[] insnByOffset = instructions;
|
||||
private static void initJumps(InsnNode[] insnByOffset) {
|
||||
for (int offset = 0; offset < insnByOffset.length; offset++) {
|
||||
InsnNode insn = insnByOffset[offset];
|
||||
if (insn == null) {
|
||||
@@ -490,7 +490,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
exceptionHandlers = new ArrayList<>(2);
|
||||
} else {
|
||||
for (ExceptionHandler h : exceptionHandlers) {
|
||||
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
|
||||
if (h.equals(handler)) {
|
||||
return h;
|
||||
}
|
||||
if (h.getHandleOffset() == handler.getHandleOffset()) {
|
||||
if (h.getTryBlock() == handler.getTryBlock()) {
|
||||
for (ClassInfo catchType : handler.getCatchTypes()) {
|
||||
h.addCatchType(catchType);
|
||||
}
|
||||
} else {
|
||||
// same handlers from different try blocks
|
||||
// will merge later
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
@@ -84,17 +83,16 @@ public class RootNode {
|
||||
LOG.debug("'.arsc' file not found");
|
||||
return;
|
||||
}
|
||||
ResTableParser parser = new ResTableParser();
|
||||
try {
|
||||
ResourcesLoader.decodeStream(arsc, (size, is) -> {
|
||||
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
|
||||
ResTableParser parser = new ResTableParser();
|
||||
parser.decode(is);
|
||||
return null;
|
||||
return parser.getResStorage();
|
||||
});
|
||||
} catch (JadxException e) {
|
||||
processResources(resStorage);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse '.arsc' file", e);
|
||||
return;
|
||||
}
|
||||
processResources(parser.getResStorage());
|
||||
}
|
||||
|
||||
public void processResources(ResourceStorage resStorage) {
|
||||
|
||||
@@ -30,6 +30,6 @@ public class ExcHandlerAttr implements IAttribute {
|
||||
public String toString() {
|
||||
return "ExcHandler: " + (handler.isFinally()
|
||||
? " FINALLY"
|
||||
: (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg());
|
||||
: handler.catchTypeStr() + " " + handler.getArg());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
package jadx.core.dex.trycatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ExceptionHandler {
|
||||
|
||||
private final ClassInfo catchType;
|
||||
private final Set<ClassInfo> catchTypes = new TreeSet<>();
|
||||
private final int handleOffset;
|
||||
|
||||
private BlockNode handlerBlock;
|
||||
@@ -23,17 +32,57 @@ public class ExceptionHandler {
|
||||
private TryCatchBlock tryBlock;
|
||||
private boolean isFinally;
|
||||
|
||||
public ExceptionHandler(int addr, ClassInfo type) {
|
||||
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
|
||||
this.handleOffset = addr;
|
||||
this.catchType = type;
|
||||
addCatchType(type);
|
||||
}
|
||||
|
||||
public ClassInfo getCatchType() {
|
||||
return catchType;
|
||||
/**
|
||||
* Add exception type to catch block
|
||||
* @param type - null for 'all' or 'Throwable' handler
|
||||
*/
|
||||
public void addCatchType(@Nullable ClassInfo type) {
|
||||
if (type != null) {
|
||||
this.catchTypes.add(type);
|
||||
} else {
|
||||
if (!this.catchTypes.isEmpty()) {
|
||||
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addCatchTypes(Collection<ClassInfo> types) {
|
||||
for (ClassInfo type : types) {
|
||||
addCatchType(type);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<ClassInfo> getCatchTypes() {
|
||||
return catchTypes;
|
||||
}
|
||||
|
||||
public ArgType getArgType() {
|
||||
if (isCatchAll()) {
|
||||
return ArgType.THROWABLE;
|
||||
}
|
||||
Set<ClassInfo> types = getCatchTypes();
|
||||
if (types.size() == 1) {
|
||||
return types.iterator().next().getType();
|
||||
} else {
|
||||
return ArgType.THROWABLE;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCatchAll() {
|
||||
return catchType == null || catchType.getFullName().equals(Consts.CLASS_THROWABLE);
|
||||
if (catchTypes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (ClassInfo classInfo : catchTypes) {
|
||||
if (classInfo.getFullName().equals(Consts.CLASS_THROWABLE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getHandleOffset() {
|
||||
@@ -89,35 +138,30 @@ public class ExceptionHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExceptionHandler that = (ExceptionHandler) o;
|
||||
return handleOffset == that.handleOffset &&
|
||||
catchTypes.equals(that.catchTypes) &&
|
||||
Objects.equals(tryBlock, that.tryBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExceptionHandler other = (ExceptionHandler) obj;
|
||||
if (catchType == null) {
|
||||
if (other.catchType != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!catchType.equals(other.catchType)) {
|
||||
return false;
|
||||
}
|
||||
return handleOffset == other.handleOffset;
|
||||
public int hashCode() {
|
||||
return Objects.hash(catchTypes, handleOffset /*, tryBlock*/);
|
||||
}
|
||||
|
||||
public String catchTypeStr() {
|
||||
return catchTypes.isEmpty() ? "all" : Utils.listToString(catchTypes, " | ", ClassInfo::getShortName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (catchType == null ? "all"
|
||||
: catchType.getShortName()) + " -> " + InsnUtils.formatOffset(handleOffset);
|
||||
return catchTypeStr() + " -> " + InsnUtils.formatOffset(handleOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -40,12 +42,14 @@ public class TryCatchBlock {
|
||||
return handlers.containsAll(tb.handlers);
|
||||
}
|
||||
|
||||
public ExceptionHandler addHandler(MethodNode mth, int addr, ClassInfo type) {
|
||||
public ExceptionHandler addHandler(MethodNode mth, int addr, @Nullable ClassInfo type) {
|
||||
ExceptionHandler handler = new ExceptionHandler(addr, type);
|
||||
handler = mth.addExceptionHandler(handler);
|
||||
handlers.add(handler);
|
||||
handler.setTryBlock(this);
|
||||
return handler;
|
||||
ExceptionHandler addedHandler = mth.addExceptionHandler(handler);
|
||||
if (addedHandler == handler || addedHandler.getTryBlock() != this) {
|
||||
handlers.add(addedHandler);
|
||||
}
|
||||
return addedHandler;
|
||||
}
|
||||
|
||||
public void removeHandler(MethodNode mth, ExceptionHandler handler) {
|
||||
|
||||
@@ -343,7 +343,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
|
||||
// result arg used both in this insn and exception handler,
|
||||
RegisterArg resArg = insn.getResult();
|
||||
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
|
||||
ArgType type = excHandler.getArgType();
|
||||
String name = excHandler.isCatchAll() ? "th" : "e";
|
||||
if (resArg.getName() == null) {
|
||||
resArg.setName(name);
|
||||
|
||||
@@ -12,6 +12,7 @@ import jadx.core.Consts;
|
||||
import jadx.core.deobf.Deobfuscator;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -19,7 +20,6 @@ import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
@@ -49,20 +49,12 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
checkClasses(root, isCaseSensitive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
checkFields(cls);
|
||||
checkMethods(cls);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkClasses(RootNode root, boolean caseSensitive) {
|
||||
Set<String> clsNames = new HashSet<>();
|
||||
for (ClassNode cls : root.getClasses(true)) {
|
||||
checkClassName(cls);
|
||||
checkFields(cls);
|
||||
checkMethods(cls);
|
||||
if (!caseSensitive) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String clsFileName = classInfo.getAlias().getFullPath();
|
||||
@@ -103,7 +95,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String fieldName = fieldInfo.getAlias();
|
||||
if (!names.add(fieldName) || !NameMapper.isValidIdentifier(fieldName)) {
|
||||
deobfuscator.renameField(field);
|
||||
deobfuscator.forceRenameField(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,12 +103,16 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
private void checkMethods(ClassNode cls) {
|
||||
Set<String> names = new HashSet<>();
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE) || mth.getAccessFlags().isConstructor()) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isConstructor()
|
||||
|| accessFlags.isBridge()
|
||||
|| accessFlags.isSynthetic()
|
||||
|| mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) {
|
||||
deobfuscator.renameMethod(mth);
|
||||
deobfuscator.forceRenameMethod(mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
ExceptionHandler excHandler = handlerAttr.getHandler();
|
||||
ArgType argType = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
|
||||
ArgType argType = excHandler.getArgType();
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
InsnNode me = block.getInstructions().get(0);
|
||||
if (me.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.blocksmaker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -20,6 +21,9 @@ import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
@@ -372,7 +376,17 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean modifyBlocksTree(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
||||
for (BlockNode block : basicBlocks) {
|
||||
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
||||
throw new JadxRuntimeException("Unreachable block: " + block);
|
||||
}
|
||||
}
|
||||
if (mergeExceptionHandlers(mth)) {
|
||||
removeMarkedBlocks(mth);
|
||||
return true;
|
||||
}
|
||||
for (BlockNode block : basicBlocks) {
|
||||
if (checkLoops(mth, block)) {
|
||||
return true;
|
||||
}
|
||||
@@ -492,6 +506,85 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge handlers for multi-exception catch
|
||||
*/
|
||||
private static boolean mergeExceptionHandlers(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||
if (excHandlerAttr != null) {
|
||||
List<BlockNode> blocksForMerge = collectExcHandlerBlocks(block, excHandlerAttr);
|
||||
if (mergeHandlers(mth, blocksForMerge, excHandlerAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<BlockNode> collectExcHandlerBlocks(BlockNode block, ExcHandlerAttr excHandlerAttr) {
|
||||
List<BlockNode> successors = block.getSuccessors();
|
||||
if (successors.size() != 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
RegisterArg reg = getMoveExceptionRegister(block);
|
||||
if (reg == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
|
||||
List<BlockNode> blocksForMerge = new ArrayList<>();
|
||||
BlockNode nextBlock = successors.get(0);
|
||||
for (BlockNode predBlock : nextBlock.getPredecessors()) {
|
||||
if (predBlock != block
|
||||
&& checkOtherExcHandler(predBlock, tryBlock, reg)) {
|
||||
blocksForMerge.add(predBlock);
|
||||
}
|
||||
}
|
||||
return blocksForMerge;
|
||||
}
|
||||
|
||||
private static boolean checkOtherExcHandler(BlockNode predBlock, TryCatchBlock tryBlock, RegisterArg reg) {
|
||||
ExcHandlerAttr otherExcHandlerAttr = predBlock.get(AType.EXC_HANDLER);
|
||||
if (otherExcHandlerAttr == null) {
|
||||
return false;
|
||||
}
|
||||
TryCatchBlock otherTryBlock = otherExcHandlerAttr.getTryBlock();
|
||||
if (tryBlock != otherTryBlock) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg otherReg = getMoveExceptionRegister(predBlock);
|
||||
if (otherReg == null || reg.getRegNum() != otherReg.getRegNum()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static RegisterArg getMoveExceptionRegister(BlockNode block) {
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode insn = block.getInstructions().get(0);
|
||||
if (insn.getType() != InsnType.MOVE_EXCEPTION) {
|
||||
return null;
|
||||
}
|
||||
return insn.getResult();
|
||||
}
|
||||
|
||||
private static boolean mergeHandlers(MethodNode mth, List<BlockNode> blocksForMerge, ExcHandlerAttr excHandlerAttr) {
|
||||
if (blocksForMerge.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
|
||||
for (BlockNode block : blocksForMerge) {
|
||||
ExcHandlerAttr otherExcHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||
ExceptionHandler excHandler = otherExcHandlerAttr.getHandler();
|
||||
excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes());
|
||||
tryBlock.removeHandler(mth, excHandler);
|
||||
detachBlock(block);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice return block if several predecessors presents
|
||||
*/
|
||||
@@ -590,6 +683,20 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
});
|
||||
}
|
||||
|
||||
private static void detachBlock(BlockNode block) {
|
||||
for (BlockNode pred : block.getPredecessors()) {
|
||||
pred.getSuccessors().remove(block);
|
||||
pred.updateCleanSuccessors();
|
||||
}
|
||||
for (BlockNode successor : block.getSuccessors()) {
|
||||
successor.getPredecessors().remove(block);
|
||||
}
|
||||
block.add(AFlag.REMOVE);
|
||||
block.getInstructions().clear();
|
||||
block.getPredecessors().clear();
|
||||
block.getSuccessors().clear();
|
||||
}
|
||||
|
||||
private static void clearBlocksState(MethodNode mth) {
|
||||
mth.getBasicBlocks().forEach(block -> {
|
||||
block.remove(AType.LOOP);
|
||||
|
||||
@@ -44,6 +44,7 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
|
||||
mth.initBasicBlocks();
|
||||
splitBasicBlocks(mth);
|
||||
removeJumpAttr(mth);
|
||||
removeInsns(mth);
|
||||
removeEmptyDetachedBlocks(mth);
|
||||
initBlocksInTargetNodes(mth);
|
||||
@@ -299,6 +300,14 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
return block;
|
||||
}
|
||||
|
||||
private void removeJumpAttr(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
insn.remove(AType.JUMP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeInsns(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
block.getInstructions().removeIf(insn -> {
|
||||
|
||||
@@ -5,8 +5,10 @@ import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
@@ -39,8 +41,16 @@ public class Utils {
|
||||
if (objects == null) {
|
||||
return "";
|
||||
}
|
||||
return listToString(objects, joiner, Object::toString);
|
||||
}
|
||||
|
||||
public static <T> String listToString(Iterable<T> objects, Function<T, String> toStr) {
|
||||
return listToString(objects, ", ", toStr);
|
||||
}
|
||||
|
||||
public static <T> String listToString(Iterable<T> objects, String joiner, Function<T, String> toStr) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
listToString(sb, objects, joiner, Object::toString);
|
||||
listToString(sb, objects, joiner, toStr);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -152,4 +162,16 @@ public class Utils {
|
||||
}
|
||||
return new ImmutableList<>(list);
|
||||
}
|
||||
|
||||
public static Map<String, String> newConstStringMap(String... parameters) {
|
||||
int len = parameters.length;
|
||||
if (len == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String> result = new HashMap<>(len / 2);
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
result.put(parameters[i], parameters[i + 1]);
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,47 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ResContainer implements Comparable<ResContainer> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResContainer.class);
|
||||
public enum DataType {
|
||||
TEXT, DECODED_DATA, RES_LINK, RES_TABLE
|
||||
}
|
||||
|
||||
private final DataType dataType;
|
||||
private final String name;
|
||||
private final Object data;
|
||||
private final List<ResContainer> subFiles;
|
||||
|
||||
@Nullable
|
||||
private CodeWriter content;
|
||||
@Nullable
|
||||
private BufferedImage image;
|
||||
@Nullable
|
||||
private InputStream binary;
|
||||
|
||||
private ResContainer(String name, List<ResContainer> subFiles) {
|
||||
this.name = name;
|
||||
this.subFiles = subFiles;
|
||||
public static ResContainer textResource(String name, CodeWriter content) {
|
||||
return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT);
|
||||
}
|
||||
|
||||
public static ResContainer singleFile(String name, CodeWriter content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
|
||||
resContainer.content = content;
|
||||
return resContainer;
|
||||
public static ResContainer decodedData(String name, byte[] data) {
|
||||
return new ResContainer(name, Collections.emptyList(), data, DataType.DECODED_DATA);
|
||||
}
|
||||
|
||||
public static ResContainer singleImageFile(String name, InputStream content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
|
||||
InputStream newContent = content;
|
||||
if (name.endsWith(".9.png")) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
decoder.decode(content, os);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
newContent = new ByteArrayInputStream(os.toByteArray());
|
||||
}
|
||||
try {
|
||||
resContainer.image = ImageIO.read(newContent);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Image load error", e);
|
||||
}
|
||||
return resContainer;
|
||||
public static ResContainer resourceFileLink(ResourceFile resFile) {
|
||||
return new ResContainer(resFile.getName(), Collections.emptyList(), resFile, DataType.RES_LINK);
|
||||
}
|
||||
|
||||
public static ResContainer singleBinaryFile(String name, InputStream content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
|
||||
try {
|
||||
// TODO: don't store binary files in memory
|
||||
resContainer.binary = new ByteArrayInputStream(IOUtils.toByteArray(content));
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Contents of the binary resource '{}' not saved, got exception", name, e);
|
||||
}
|
||||
return resContainer;
|
||||
public static ResContainer resourceTable(String name, List<ResContainer> subFiles, CodeWriter rootContent) {
|
||||
return new ResContainer(name, subFiles, rootContent, DataType.RES_TABLE);
|
||||
}
|
||||
|
||||
public static ResContainer multiFile(String name) {
|
||||
return new ResContainer(name, new ArrayList<>());
|
||||
private ResContainer(String name, List<ResContainer> subFiles, Object data, DataType dataType) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.subFiles = Objects.requireNonNull(subFiles);
|
||||
this.data = Objects.requireNonNull(data);
|
||||
this.dataType = Objects.requireNonNull(dataType);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -89,29 +52,26 @@ public class ResContainer implements Comparable<ResContainer> {
|
||||
return name.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodeWriter getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InputStream getBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
public void setContent(@Nullable CodeWriter content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public List<ResContainer> getSubFiles() {
|
||||
return subFiles;
|
||||
}
|
||||
|
||||
public DataType getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public CodeWriter getText() {
|
||||
return (CodeWriter) data;
|
||||
}
|
||||
|
||||
public byte[] getDecodedData() {
|
||||
return (byte[]) data;
|
||||
}
|
||||
|
||||
public ResourceFile getResLink() {
|
||||
return (ResourceFile) data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ResContainer o) {
|
||||
return name.compareTo(o.name);
|
||||
@@ -136,6 +96,6 @@ public class ResContainer implements Comparable<ResContainer> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Res{" + name + ", subFiles=" + subFiles + "}";
|
||||
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,23 +66,9 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||
|
||||
ResContainer res = ResContainer.multiFile("res");
|
||||
res.setContent(makeXmlDump());
|
||||
res.getSubFiles().addAll(resGen.makeResourcesXml());
|
||||
return res;
|
||||
}
|
||||
|
||||
public CodeWriter makeDump() {
|
||||
CodeWriter writer = new CodeWriter();
|
||||
writer.add("app package: ").add(resStorage.getAppPackage());
|
||||
writer.startLine();
|
||||
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
for (ResourceEntry ri : resStorage.getResources()) {
|
||||
writer.startLine(ri + ": " + vp.getValueString(ri));
|
||||
}
|
||||
writer.finish();
|
||||
return writer;
|
||||
CodeWriter content = makeXmlDump();
|
||||
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
|
||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||
}
|
||||
|
||||
public CodeWriter makeXmlDump() {
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ResXmlGen {
|
||||
content.decIndent();
|
||||
content.startLine("</resources>");
|
||||
content.finish();
|
||||
files.add(ResContainer.singleFile(fileName, content));
|
||||
files.add(ResContainer.textResource(fileName, content));
|
||||
}
|
||||
Collections.sort(files);
|
||||
return files;
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.prepareFile;
|
||||
|
||||
public class ResourcesSaver implements Runnable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class);
|
||||
|
||||
@@ -33,76 +29,74 @@ public class ResourcesSaver implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ResContainer rc = resourceFile.loadContent();
|
||||
if (rc != null) {
|
||||
saveResources(rc);
|
||||
}
|
||||
saveResources(resourceFile.loadContent());
|
||||
}
|
||||
|
||||
private void saveResources(ResContainer rc) {
|
||||
if (rc == null) {
|
||||
return;
|
||||
}
|
||||
List<ResContainer> subFiles = rc.getSubFiles();
|
||||
if (subFiles.isEmpty()) {
|
||||
save(rc, outDir);
|
||||
} else {
|
||||
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
|
||||
saveToFile(rc, new File(outDir, "res/values/public.xml"));
|
||||
for (ResContainer subFile : subFiles) {
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
saveResources(subFile);
|
||||
}
|
||||
} else {
|
||||
save(rc, outDir);
|
||||
}
|
||||
}
|
||||
|
||||
private void save(ResContainer rc, File outDir) {
|
||||
File outFile = new File(outDir, rc.getFileName());
|
||||
BufferedImage image = rc.getImage();
|
||||
if (image != null) {
|
||||
String ext = FilenameUtils.getExtension(outFile.getName());
|
||||
try {
|
||||
outFile = prepareFile(outFile);
|
||||
|
||||
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
|
||||
LOG.error("Path traversal attack detected, invalid resource name: {}",
|
||||
outFile.getPath());
|
||||
return;
|
||||
}
|
||||
|
||||
ImageIO.write(image, ext, outFile);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to save image: {}", rc.getName(), e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
|
||||
LOG.error("Path traversal attack detected, invalid resource name: {}",
|
||||
rc.getFileName());
|
||||
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
|
||||
return;
|
||||
}
|
||||
saveToFile(rc, outFile);
|
||||
}
|
||||
|
||||
private void saveToFile(ResContainer rc, File outFile) {
|
||||
CodeWriter cw = rc.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(outFile);
|
||||
return;
|
||||
}
|
||||
InputStream binary = rc.getBinary();
|
||||
if (binary != null) {
|
||||
try {
|
||||
switch (rc.getDataType()) {
|
||||
case TEXT:
|
||||
case RES_TABLE:
|
||||
CodeWriter cw = rc.getText();
|
||||
cw.save(outFile);
|
||||
return;
|
||||
|
||||
case DECODED_DATA:
|
||||
byte[] data = rc.getDecodedData();
|
||||
FileUtils.makeDirsForFile(outFile);
|
||||
try (FileOutputStream binaryFileStream = new FileOutputStream(outFile)) {
|
||||
IOUtils.copy(binary, binaryFileStream);
|
||||
} finally {
|
||||
binary.close();
|
||||
try {
|
||||
Files.write(outFile.toPath(), data);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
|
||||
}
|
||||
return;
|
||||
return;
|
||||
|
||||
case RES_LINK:
|
||||
ResourceFile resFile = rc.getResLink();
|
||||
FileUtils.makeDirsForFile(outFile);
|
||||
try {
|
||||
saveResourceFile(resFile, outFile);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
|
||||
break;
|
||||
}
|
||||
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
|
||||
}
|
||||
|
||||
private void saveResourceFile(ResourceFile resFile, File outFile) throws JadxException {
|
||||
ResourcesLoader.decodeStream(resFile, (size, is) -> {
|
||||
try (FileOutputStream fileStream = new FileOutputStream(outFile)) {
|
||||
IOUtils.copy(is, fileStream);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Resource file save error", e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.tests.integration.names;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestDuplicatedNames extends SmaliTest {
|
||||
/*
|
||||
public static class TestCls {
|
||||
|
||||
public Object fieldName;
|
||||
public String fieldName;
|
||||
|
||||
public Object run() {
|
||||
return this.fieldName;
|
||||
}
|
||||
|
||||
public String run() {
|
||||
return this.fieldName;
|
||||
}
|
||||
}
|
||||
*/
|
||||
@Test
|
||||
public void test() {
|
||||
commonChecks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithDeobf() {
|
||||
enableDeobfuscation();
|
||||
commonChecks();
|
||||
}
|
||||
|
||||
private void commonChecks() {
|
||||
ClassNode cls = getClassNodeFromSmaliWithPath("names", "TestDuplicatedNames");
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("Object fieldName;"));
|
||||
assertThat(code, containsOne("String f0fieldName"));
|
||||
|
||||
assertThat(code, containsOne("this.fieldName"));
|
||||
assertThat(code, containsOne("this.f0fieldName"));
|
||||
|
||||
assertThat(code, containsOne("public Object run() {"));
|
||||
assertThat(code, containsOne("public String m0run() {"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import java.security.ProviderException;
|
||||
import java.time.DateTimeException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestMultiExceptionCatch extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
try {
|
||||
System.out.println("Test");
|
||||
} catch (ProviderException | DateTimeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("try {"));
|
||||
assertThat(code, containsOne("} catch (ProviderException | DateTimeException e) {"));
|
||||
assertThat(code, containsOne("throw new RuntimeException(e);"));
|
||||
assertThat(code, not(containsString("RuntimeException e;")));
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestMultiExceptionCatchSameJump extends SmaliTest {
|
||||
/*
|
||||
public static class TestCls {
|
||||
public void test() {
|
||||
try {
|
||||
System.out.println("Test");
|
||||
} catch (ProviderException | DateTimeException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestMultiExceptionCatchSameJump");
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("try {"));
|
||||
assertThat(code, containsOne("} catch (ProviderException | DateTimeException e) {"));
|
||||
assertThat(code, containsOne("throw new RuntimeException(e);"));
|
||||
assertThat(code, not(containsString("RuntimeException e;")));
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ public class TestPrimitivesInIf extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
setOutputCFG();
|
||||
noDebugInfo();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.class public LTestDuplicatedNames;
|
||||
.super Ljava/lang/Object;
|
||||
.source "TestDuplicatedNames.java"
|
||||
|
||||
|
||||
# instance fields
|
||||
.field public fieldName:Ljava/lang/String;
|
||||
.field public fieldName:Ljava/lang/Object;
|
||||
|
||||
|
||||
# direct methods
|
||||
.method public constructor <init>()V
|
||||
.registers 1
|
||||
|
||||
.prologue
|
||||
.line 3
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public run()Ljava/lang/String;
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
iget-object v0, p0, LTestDuplicatedNames;->fieldName:Ljava/lang/String;
|
||||
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
.method public run()Ljava/lang/Object;
|
||||
.registers 2
|
||||
|
||||
.prologue
|
||||
iget-object v0, p0, LTestDuplicatedNames;->fieldName:Ljava/lang/Object;
|
||||
|
||||
return-object v0
|
||||
.end method
|
||||
@@ -0,0 +1,37 @@
|
||||
.class public Ltrycatch/TestMultiExceptionCatchSameJump;
|
||||
.super Ljava/lang/Object;
|
||||
.source "TestMultiExceptionCatchSameJump.java"
|
||||
|
||||
.method public test()V
|
||||
.locals 2
|
||||
|
||||
.line 17
|
||||
:try_start_0
|
||||
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
|
||||
const-string v1, "Test"
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
:try_end_0
|
||||
.catch Ljava/security/ProviderException; {:try_start_0 .. :try_end_0} :catch_0
|
||||
.catch Ljava/time/DateTimeException; {:try_start_0 .. :try_end_0} :catch_0
|
||||
|
||||
.line 20
|
||||
nop
|
||||
|
||||
.line 22
|
||||
return-void
|
||||
|
||||
.line 18
|
||||
:catch_0
|
||||
move-exception v0
|
||||
|
||||
.line 19
|
||||
.local v0, "e":Ljava/lang/RuntimeException;
|
||||
new-instance v1, Ljava/lang/RuntimeException;
|
||||
|
||||
invoke-direct {v1, v0}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/Throwable;)V
|
||||
|
||||
throw v1
|
||||
.end method
|
||||
|
||||
@@ -16,9 +16,11 @@ dependencies {
|
||||
compile 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
|
||||
compile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
compile 'org.apache.commons:commons-text:1.6'
|
||||
|
||||
compile 'io.reactivex.rxjava2:rxjava:2.2.5'
|
||||
compile "com.github.akarnokd:rxjava2-swing:0.3.3"
|
||||
compile 'com.android.tools.build:apksig:3.3.0'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
@@ -54,7 +56,7 @@ launch4j {
|
||||
mainClassName = 'jadx.gui.JadxGUI'
|
||||
copyConfigurable = project.tasks.shadowJar.outputs.files
|
||||
jar = "lib/${project.tasks.shadowJar.archiveName}"
|
||||
// icon = "${projectDir}/icons/myApp.ico"
|
||||
icon = "${projectDir}/src/main/resources/logos/jadx-logo.ico"
|
||||
outfile = "jadx-gui-${version}.exe"
|
||||
copyright = 'Skylot'
|
||||
windowTitle = 'jadx'
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
@@ -67,7 +68,6 @@ public class JadxWrapper {
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
* @return
|
||||
*/
|
||||
public List<JavaClass> getClasses() {
|
||||
return decompiler.getClasses();
|
||||
@@ -75,19 +75,20 @@ public class JadxWrapper {
|
||||
|
||||
/**
|
||||
* Get all classes that are not excluded by the excluded packages settings
|
||||
* @return
|
||||
*/
|
||||
public List<JavaClass> getIncludedClasses() {
|
||||
List<JavaClass> classList = decompiler.getClasses();
|
||||
String excludedPackages = settings.getExcludedPackages().trim();
|
||||
if (excludedPackages.length() == 0)
|
||||
if (excludedPackages.length() == 0) {
|
||||
return classList;
|
||||
}
|
||||
String[] excluded = excludedPackages.split("[ ]+");
|
||||
|
||||
return classList.stream().filter(cls -> {
|
||||
for (String exclude : excluded) {
|
||||
if (cls.getFullName().startsWith(exclude))
|
||||
if (cls.getFullName().startsWith(exclude)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
@@ -105,7 +106,7 @@ public class JadxWrapper {
|
||||
return openFile;
|
||||
}
|
||||
|
||||
public JadxSettings getSettings() {
|
||||
return settings;
|
||||
public JadxArgs getArgs() {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package jadx.gui.jobs;
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import jadx.gui.utils.NLS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.ui.ProgressPanel;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
@@ -27,12 +27,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
if (isDone()) {
|
||||
return;
|
||||
}
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
progressPane.setVisible(true);
|
||||
}
|
||||
});
|
||||
SwingUtilities.invokeLater(() -> progressPane.setVisible(true));
|
||||
addPropertyChangeListener(progressPane);
|
||||
execute();
|
||||
}
|
||||
@@ -46,7 +41,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
protected Void doInBackground() {
|
||||
try {
|
||||
System.gc();
|
||||
LOG.debug("Memory usage: Before decompile: {}", Utils.memoryInfo());
|
||||
|
||||
@@ -11,12 +11,7 @@ public class DecompileJob extends BackgroundJob {
|
||||
|
||||
protected void runJob() {
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
}
|
||||
});
|
||||
addTask(cls::decompile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isShowHeapUsageBar() {
|
||||
return showHeapUsageBar;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class JadxSettingsAdapter {
|
||||
|
||||
private static final Preferences PREFS = Preferences.userNodeForPackage(JadxGUI.class);
|
||||
|
||||
private static ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
|
||||
private static final ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return JadxSettings.SKIP_FIELDS.contains(f.getName())
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@@ -19,8 +17,6 @@ import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static jadx.gui.utils.Utils.FONT_HACK;
|
||||
|
||||
public class JadxSettingsWindow extends JDialog {
|
||||
private static final long serialVersionUID = -1804570470377354148L;
|
||||
|
||||
@@ -40,7 +36,6 @@ public class JadxSettingsWindow extends JDialog {
|
||||
this.prevLang = settings.getLangLocale();
|
||||
|
||||
initUI();
|
||||
registerBundledFonts();
|
||||
|
||||
setTitle(NLS.str("preferences.title"));
|
||||
setSize(400, 550);
|
||||
@@ -50,13 +45,6 @@ public class JadxSettingsWindow extends JDialog {
|
||||
setLocationRelativeTo(null);
|
||||
}
|
||||
|
||||
public static void registerBundledFonts() {
|
||||
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
if (FONT_HACK != null) {
|
||||
grEnv.registerFont(FONT_HACK);
|
||||
}
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
|
||||
@@ -204,7 +192,6 @@ public class JadxSettingsWindow extends JDialog {
|
||||
int i = themesCbx.getSelectedIndex();
|
||||
EditorTheme editorTheme = editorThemes[i];
|
||||
settings.setEditorThemePath(editorTheme.getPath());
|
||||
mainWindow.setEditorTheme(editorTheme.getPath());
|
||||
mainWindow.loadSettings();
|
||||
});
|
||||
|
||||
@@ -245,11 +232,11 @@ public class JadxSettingsWindow extends JDialog {
|
||||
});
|
||||
|
||||
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
|
||||
editExcludedPackages.addActionListener( event -> {
|
||||
editExcludedPackages.addActionListener(event -> {
|
||||
|
||||
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
|
||||
settings.getExcludedPackages());
|
||||
if (result !=null) {
|
||||
if (result != null) {
|
||||
settings.setExcludedPackages(result);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.android.apksig.ApkVerifier;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.utils.CertificateManager;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public class ApkSignature extends JNode {
|
||||
private static final long serialVersionUID = -9121321926113143407L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ApkSignature.class);
|
||||
|
||||
private static final ImageIcon CERTIFICATE_ICON = Utils.openIcon("certificate_obj");
|
||||
|
||||
private final transient File openFile;
|
||||
private String content;
|
||||
|
||||
public static ApkSignature getApkSignature(JadxWrapper wrapper) {
|
||||
// Only show the ApkSignature node if an AndroidManifest.xml is present.
|
||||
// Without a manifest the Google ApkVerifier refuses to work.
|
||||
if (wrapper.getResources().stream().noneMatch(r -> "AndroidManifest.xml".equals(r.getName()))) {
|
||||
return null;
|
||||
}
|
||||
File openFile = wrapper.getOpenFile();
|
||||
return new ApkSignature(openFile);
|
||||
}
|
||||
|
||||
public ApkSignature(File openFile) {
|
||||
this.openFile = openFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return CERTIFICATE_ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return "APK signature";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
if (content != null) {
|
||||
return this.content;
|
||||
}
|
||||
ApkVerifier verifier = new ApkVerifier.Builder(openFile).build();
|
||||
try {
|
||||
ApkVerifier.Result result = verifier.verify();
|
||||
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
|
||||
builder.append("<h1>APK signature verification result:</h1>");
|
||||
|
||||
builder.append("<p><b>");
|
||||
if (result.isVerified()) {
|
||||
builder.escape(NLS.str("apkSignature.verificationSuccess"));
|
||||
} else {
|
||||
builder.escape(NLS.str("apkSignature.verificationFailed"));
|
||||
}
|
||||
builder.append("</b></p>");
|
||||
|
||||
final String err = NLS.str("apkSignature.errors");
|
||||
final String warn = NLS.str("apkSignature.warnings");
|
||||
final String sigSucc = NLS.str("apkSignature.signatureSuccess");
|
||||
final String sigFail = NLS.str("apkSignature.signatureFailed");
|
||||
|
||||
writeIssues(builder, err, result.getErrors());
|
||||
writeIssues(builder, warn, result.getWarnings());
|
||||
|
||||
if (!result.getV1SchemeSigners().isEmpty()) {
|
||||
builder.append("<h2>");
|
||||
builder.escape(String.format(result.isVerifiedUsingV1Scheme() ? sigSucc : sigFail, 1));
|
||||
builder.append("</h2>\n");
|
||||
|
||||
builder.append("<blockquote>");
|
||||
for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {
|
||||
builder.append("<h3>");
|
||||
builder.escape(NLS.str("apkSignature.signer"));
|
||||
builder.append(" ");
|
||||
builder.escape(signer.getName());
|
||||
builder.append(" (");
|
||||
builder.escape(signer.getSignatureFileName());
|
||||
builder.append(")");
|
||||
builder.append("</h3>");
|
||||
writeCertificate(builder, signer.getCertificate());
|
||||
writeIssues(builder, err, signer.getErrors());
|
||||
writeIssues(builder, warn, signer.getWarnings());
|
||||
}
|
||||
builder.append("</blockquote>");
|
||||
}
|
||||
if (!result.getV2SchemeSigners().isEmpty()) {
|
||||
builder.append("<h2>");
|
||||
builder.escape(String.format(result.isVerifiedUsingV2Scheme() ? sigSucc : sigFail, 2));
|
||||
builder.append("</h2>\n");
|
||||
|
||||
builder.append("<blockquote>");
|
||||
for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {
|
||||
builder.append("<h3>");
|
||||
builder.escape(NLS.str("apkSignature.signer"));
|
||||
builder.append(" ");
|
||||
builder.append(Integer.toString(signer.getIndex() + 1));
|
||||
builder.append("</h3>");
|
||||
writeCertificate(builder, signer.getCertificate());
|
||||
writeIssues(builder, err, signer.getErrors());
|
||||
writeIssues(builder, warn, signer.getWarnings());
|
||||
}
|
||||
builder.append("</blockquote>");
|
||||
}
|
||||
this.content = builder.toString();
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
|
||||
builder.append("<h1>");
|
||||
builder.escape(NLS.str("apkSignature.exception"));
|
||||
builder.append("</h1><pre>");
|
||||
builder.escape(ExceptionUtils.getStackTrace(e));
|
||||
builder.append("</pre>");
|
||||
return builder.toString();
|
||||
}
|
||||
return this.content;
|
||||
}
|
||||
|
||||
private void writeCertificate(StringEscapeUtils.Builder builder, Certificate cert) {
|
||||
CertificateManager certMgr = new CertificateManager(cert);
|
||||
builder.append("<blockquote><pre>");
|
||||
builder.escape(certMgr.generateHeader());
|
||||
builder.append("</pre><pre>");
|
||||
builder.escape(certMgr.generatePublicKey());
|
||||
builder.append("</pre><pre>");
|
||||
builder.escape(certMgr.generateSignature());
|
||||
builder.append("</pre><pre>");
|
||||
builder.append(certMgr.generateFingerprint());
|
||||
builder.append("</pre></blockquote>");
|
||||
}
|
||||
|
||||
private void writeIssues(StringEscapeUtils.Builder builder, String issueType, List<ApkVerifier.IssueWithParams> issueList) {
|
||||
if (!issueList.isEmpty()) {
|
||||
builder.append("<h3>");
|
||||
builder.escape(issueType);
|
||||
builder.append("</h3>");
|
||||
builder.append("<blockquote>");
|
||||
// Unprotected Zip entry issues are very common, handle them separately
|
||||
List<ApkVerifier.IssueWithParams> unprotIssues = issueList.stream().filter(i ->
|
||||
i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
|
||||
if (!unprotIssues.isEmpty()) {
|
||||
builder.append("<h4>");
|
||||
builder.escape(NLS.str("apkSignature.unprotectedEntry"));
|
||||
builder.append("</h4><blockquote>");
|
||||
for (ApkVerifier.IssueWithParams issue : unprotIssues) {
|
||||
builder.escape((String) issue.getParams()[0]);
|
||||
builder.append("<br>");
|
||||
}
|
||||
builder.append("</blockquote>");
|
||||
}
|
||||
List<ApkVerifier.IssueWithParams> remainingIssues = issueList.stream().filter(i ->
|
||||
i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
|
||||
if (!remainingIssues.isEmpty()) {
|
||||
builder.append("<pre>\n");
|
||||
for (ApkVerifier.IssueWithParams issue : remainingIssues) {
|
||||
builder.escape(issue.toString());
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append("</pre>\n");
|
||||
}
|
||||
builder.append("</blockquote>");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -11,14 +12,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceFileContent;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.OverlayIcon;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import static jadx.api.ResourceFileContent.createResourceFileContentInstance;
|
||||
|
||||
public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
private static final long serialVersionUID = -201018424302612434L;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
|
||||
private transient boolean loaded;
|
||||
private transient String content;
|
||||
private transient Map<Integer, Integer> lineMapping;
|
||||
private transient Map<Integer, Integer> lineMapping = Collections.emptyMap();
|
||||
|
||||
public JResource(ResourceFile resFile, String name, JResType type) {
|
||||
this(resFile, name, name, type);
|
||||
@@ -58,15 +58,16 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
}
|
||||
|
||||
public final void update() {
|
||||
removeAllChildren();
|
||||
if (!loaded) {
|
||||
if (files.isEmpty()) {
|
||||
if (type == JResType.DIR
|
||||
|| type == JResType.ROOT
|
||||
|| resFile.getType() == ResourceType.ARSC) {
|
||||
// fake leaf to force show expand button
|
||||
// real sub nodes will load on expand in loadNode() method
|
||||
add(new TextNode(NLS.str("tree.loading")));
|
||||
}
|
||||
} else {
|
||||
loadContent();
|
||||
removeAllChildren();
|
||||
for (JResource res : files) {
|
||||
res.update();
|
||||
add(res);
|
||||
@@ -76,13 +77,8 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
|
||||
@Override
|
||||
public void loadNode() {
|
||||
loadContent();
|
||||
loaded = true;
|
||||
update();
|
||||
}
|
||||
|
||||
private void loadContent() {
|
||||
getContent();
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,40 +91,68 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
if (!loaded && resFile != null && type == JResType.FILE) {
|
||||
loaded = true;
|
||||
if (isSupportedForView(resFile.getType())) {
|
||||
ResContainer rc = resFile.loadContent();
|
||||
if (rc != null) {
|
||||
addSubFiles(rc, this, 0);
|
||||
}
|
||||
}
|
||||
public synchronized String getContent() {
|
||||
if (loaded) {
|
||||
return content;
|
||||
}
|
||||
return content;
|
||||
if (resFile == null || type != JResType.FILE) {
|
||||
return null;
|
||||
}
|
||||
if (!isSupportedForView(resFile.getType())) {
|
||||
return null;
|
||||
}
|
||||
ResContainer rc = resFile.loadContent();
|
||||
if (rc == null) {
|
||||
return null;
|
||||
}
|
||||
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
|
||||
content = loadCurrentSingleRes(rc);
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
loadSubNodes(this, subFile, 1);
|
||||
}
|
||||
loaded = true;
|
||||
return content;
|
||||
}
|
||||
// single node
|
||||
return loadCurrentSingleRes(rc);
|
||||
}
|
||||
|
||||
private void addSubFiles(ResContainer rc, JResource root, int depth) {
|
||||
CodeWriter cw = rc.getContent();
|
||||
if (cw != null) {
|
||||
if (depth == 0) {
|
||||
root.lineMapping = cw.getLineMapping();
|
||||
root.content = cw.toString();
|
||||
} else {
|
||||
String resName = rc.getName();
|
||||
String[] path = resName.split("/");
|
||||
String resShortName = path.length == 0 ? resName : path[path.length - 1];
|
||||
ResourceFileContent fileContent = createResourceFileContentInstance(resShortName, ResourceType.XML, cw);
|
||||
if (fileContent != null) {
|
||||
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
|
||||
private String loadCurrentSingleRes(ResContainer rc) {
|
||||
switch (rc.getDataType()) {
|
||||
case TEXT:
|
||||
case RES_TABLE:
|
||||
CodeWriter cw = rc.getText();
|
||||
lineMapping = cw.getLineMapping();
|
||||
return cw.toString();
|
||||
|
||||
case RES_LINK:
|
||||
try {
|
||||
return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
|
||||
if (size > 10 * 1024 * 1024L) {
|
||||
return "File too large for view";
|
||||
}
|
||||
return ResourcesLoader.loadToCodeWriter(is).toString();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return "Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
case DECODED_DATA:
|
||||
default:
|
||||
return "Unexpected resource type: " + rc;
|
||||
}
|
||||
List<ResContainer> subFiles = rc.getSubFiles();
|
||||
if (!subFiles.isEmpty()) {
|
||||
for (ResContainer subFile : subFiles) {
|
||||
addSubFiles(subFile, root, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSubNodes(JResource root, ResContainer rc, int depth) {
|
||||
String resName = rc.getName();
|
||||
String[] path = resName.split("/");
|
||||
String resShortName = path.length == 0 ? resName : path[path.length - 1];
|
||||
CodeWriter cw = rc.getText();
|
||||
ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, cw);
|
||||
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
|
||||
|
||||
for (ResContainer subFile : rc.getSubFiles()) {
|
||||
loadSubNodes(root, subFile, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,25 +214,29 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, String> EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap(
|
||||
"java", SyntaxConstants.SYNTAX_STYLE_JAVA,
|
||||
"js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT,
|
||||
"ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT,
|
||||
"json", SyntaxConstants.SYNTAX_STYLE_JSON,
|
||||
"css", SyntaxConstants.SYNTAX_STYLE_CSS,
|
||||
"less", SyntaxConstants.SYNTAX_STYLE_LESS,
|
||||
"html", SyntaxConstants.SYNTAX_STYLE_HTML,
|
||||
"xml", SyntaxConstants.SYNTAX_STYLE_XML,
|
||||
"yaml", SyntaxConstants.SYNTAX_STYLE_YAML,
|
||||
"properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE,
|
||||
"ini", SyntaxConstants.SYNTAX_STYLE_INI,
|
||||
"sql", SyntaxConstants.SYNTAX_STYLE_SQL,
|
||||
"arsc", SyntaxConstants.SYNTAX_STYLE_XML
|
||||
);
|
||||
|
||||
private String getSyntaxByExtension(String name) {
|
||||
int dot = name.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
return null;
|
||||
}
|
||||
String ext = name.substring(dot + 1);
|
||||
if (ext.equals("js")) {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
|
||||
}
|
||||
if (ext.equals("css")) {
|
||||
return SyntaxConstants.SYNTAX_STYLE_CSS;
|
||||
}
|
||||
if (ext.equals("html")) {
|
||||
return SyntaxConstants.SYNTAX_STYLE_HTML;
|
||||
}
|
||||
if (ext.equals("arsc")) {
|
||||
return SyntaxConstants.SYNTAX_STYLE_XML;
|
||||
}
|
||||
return null;
|
||||
return EXTENSION_TO_FILE_SYNTAX.get(ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,6 +284,10 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
return resFile;
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
|
||||
@@ -37,6 +37,11 @@ public class JRoot extends JNode {
|
||||
add(jRes);
|
||||
}
|
||||
|
||||
ApkSignature signature = ApkSignature.getApkSignature(wrapper);
|
||||
if (signature != null) {
|
||||
add(signature);
|
||||
}
|
||||
|
||||
JCertificate certificate = getCertificate(wrapper.getResources());
|
||||
if (certificate != null) {
|
||||
add(certificate);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class JSources extends JNode {
|
||||
}
|
||||
}
|
||||
// use identity set for collect inner packages
|
||||
Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<JPackage, Boolean>());
|
||||
Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
innerPackages.addAll(pkg.getInnerPackages());
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package jadx.gui.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.gui.utils.NLS;
|
||||
@@ -42,11 +40,7 @@ class AboutDialog extends JDialog {
|
||||
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
|
||||
|
||||
JButton close = new JButton(NLS.str("tabs.close"));
|
||||
close.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
close.addActionListener(event -> dispose());
|
||||
close.setAlignmentX(0.5f);
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
|
||||
@@ -33,14 +33,14 @@ import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
public abstract class CommonSearchDialog extends JDialog {
|
||||
private static final long serialVersionUID = 8939332306115370276L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
|
||||
private static final long serialVersionUID = 8939332306115370276L;
|
||||
|
||||
public static final int RESULTS_PER_PAGE = 100;
|
||||
|
||||
@@ -397,12 +397,14 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
|
||||
protected class ResultsTableCellRenderer implements TableCellRenderer {
|
||||
private final JLabel emptyLabel = new JLabel();
|
||||
private final Font font;
|
||||
private final Color codeSelectedColor;
|
||||
private final Color codeBackground;
|
||||
private Map<Integer, Component> componentCache = new HashMap<>();
|
||||
private final Map<Integer, Component> componentCache = new HashMap<>();
|
||||
|
||||
public ResultsTableCellRenderer() {
|
||||
RSyntaxTextArea area = CodeArea.getDefaultArea(mainWindow);
|
||||
this.font = area.getFont();
|
||||
this.codeSelectedColor = area.getSelectionColor();
|
||||
this.codeBackground = area.getBackground();
|
||||
}
|
||||
@@ -414,7 +416,7 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
Component comp = componentCache.get(id);
|
||||
if (comp == null) {
|
||||
if (obj instanceof JNode) {
|
||||
comp = makeCell(table, (JNode) obj, column);
|
||||
comp = makeCell((JNode) obj, column);
|
||||
componentCache.put(id, comp);
|
||||
} else {
|
||||
comp = emptyLabel;
|
||||
@@ -442,10 +444,10 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
}
|
||||
}
|
||||
|
||||
private Component makeCell(JTable table, JNode node, int column) {
|
||||
private Component makeCell(JNode node, int column) {
|
||||
if (column == 0) {
|
||||
JLabel label = new JLabel(node.makeLongString() + " ", node.getIcon(), SwingConstants.LEFT);
|
||||
label.setFont(table.getFont());
|
||||
label.setFont(font);
|
||||
label.setOpaque(true);
|
||||
label.setToolTipText(label.getText());
|
||||
return label;
|
||||
|
||||
@@ -1,50 +1,62 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public class HeapUsageBar extends JProgressBar implements ActionListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HeapUsageBar.class);
|
||||
private static final long serialVersionUID = -8739563124249884967L;
|
||||
|
||||
private static final double TWO_TO_20 = 1048576d; // 1024 * 1024
|
||||
private static final double TWO_TO_20 = 1048576d;
|
||||
|
||||
private final Color GREEN = new Color(0, 180, 0);
|
||||
private final Color RED = new Color(200, 0, 0);
|
||||
private static final Color GREEN = new Color(0, 180, 0);
|
||||
private static final Color RED = new Color(200, 0, 0);
|
||||
|
||||
private final Runtime r;
|
||||
|
||||
private String maxHeapStr;
|
||||
|
||||
private final Timer timer;
|
||||
|
||||
private final double maxGB;
|
||||
private final transient Runtime runtime = Runtime.getRuntime();
|
||||
private final transient Timer timer;
|
||||
|
||||
private final String textFormat;
|
||||
private final double maxGB;
|
||||
|
||||
public HeapUsageBar() {
|
||||
super();
|
||||
textFormat = NLS.str("heapUsage.text");
|
||||
r = Runtime.getRuntime();
|
||||
this.textFormat = NLS.str("heapUsage.text");
|
||||
setBorderPainted(false);
|
||||
setStringPainted(true);
|
||||
setValue(10);
|
||||
int maxKB = (int) (r.maxMemory() / 1024);
|
||||
int maxKB = (int) (runtime.maxMemory() / 1024);
|
||||
setMaximum(maxKB);
|
||||
maxGB = maxKB / TWO_TO_20;
|
||||
update();
|
||||
timer = new Timer(1000, this);
|
||||
timer = new Timer(2000, this);
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
Runtime.getRuntime().gc();
|
||||
update();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Memory used: {}", Utils.memoryInfo());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void update() {
|
||||
long used = r.totalMemory() - r.freeMemory();
|
||||
long used = runtime.totalMemory() - runtime.freeMemory();
|
||||
int usedKB = (int) (used / 1024);
|
||||
setValue(usedKB);
|
||||
setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB));
|
||||
|
||||
if (used > r.totalMemory() * 0.8) {
|
||||
if ((used + Utils.MIN_FREE_MEMORY) > runtime.maxMemory()) {
|
||||
setForeground(RED);
|
||||
} else {
|
||||
setForeground(GREEN);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public final class HtmlPanel extends ContentPanel {
|
||||
private static final long serialVersionUID = -6251262855835426245L;
|
||||
|
||||
private final JHtmlPane textArea;
|
||||
|
||||
public HtmlPanel(TabbedPane panel, JNode jnode) {
|
||||
super(panel, jnode);
|
||||
setLayout(new BorderLayout());
|
||||
textArea = new JHtmlPane();
|
||||
loadSettings();
|
||||
textArea.setText(jnode.getContent());
|
||||
textArea.setCaretPosition(0); // otherwise the start view will be the last line
|
||||
textArea.setEditable(false);
|
||||
JScrollPane sp = new JScrollPane(textArea);
|
||||
add(sp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSettings() {
|
||||
JadxSettings settings = getTabbedPane().getMainWindow().getSettings();
|
||||
textArea.setFont(settings.getFont());
|
||||
}
|
||||
|
||||
private static final class JHtmlPane extends JEditorPane {
|
||||
private static final long serialVersionUID = 6886040384052136157L;
|
||||
|
||||
public JHtmlPane() {
|
||||
setContentType("text/html");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
Graphics2D g2d = (Graphics2D) g.create();
|
||||
try {
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
super.paint(g2d);
|
||||
} finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,57 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import hu.kazocsaba.imageviewer.ImageViewer;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
|
||||
public class ImagePanel extends ContentPanel {
|
||||
|
||||
private static final long serialVersionUID = 4071356367073142688L;
|
||||
|
||||
ImagePanel(TabbedPane panel, JResource res) {
|
||||
super(panel, res);
|
||||
|
||||
ResourceFile resFile = res.getResFile();
|
||||
BufferedImage img = resFile.loadContent().getImage();
|
||||
ImageViewer imageViewer = new ImageViewer(img);
|
||||
imageViewer.setZoomFactor(2.);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(imageViewer.getComponent());
|
||||
try {
|
||||
BufferedImage img = loadImage(res);
|
||||
ImageViewer imageViewer = new ImageViewer(img);
|
||||
add(imageViewer.getComponent());
|
||||
} catch (Exception e) {
|
||||
RSyntaxTextArea textArea = CodeArea.getDefaultArea(panel.getMainWindow());
|
||||
textArea.setText("Image load error: \n" + Utils.getStackTrace(e));
|
||||
add(textArea);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage loadImage(JResource res) {
|
||||
ResourceFile resFile = res.getResFile();
|
||||
ResContainer resContainer = resFile.loadContent();
|
||||
ResContainer.DataType dataType = resContainer.getDataType();
|
||||
if (dataType == ResContainer.DataType.DECODED_DATA) {
|
||||
try {
|
||||
return ImageIO.read(new ByteArrayInputStream(resContainer.getDecodedData()));
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load image", e);
|
||||
}
|
||||
} else if (dataType == ResContainer.DataType.RES_LINK) {
|
||||
try {
|
||||
return ResourcesLoader.decodeStream(resFile, (size, is) -> ImageIO.read(is));
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load image", e);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unsupported resource image data type: " + resFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -59,7 +59,7 @@ public class MainDropTarget implements DropTargetListener {
|
||||
try {
|
||||
Transferable transferable = dtde.getTransferable();
|
||||
List<File> transferData = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
|
||||
if (transferData != null && transferData.size() > 0) {
|
||||
if (!transferData.isEmpty()) {
|
||||
dtde.dropComplete(true);
|
||||
// load first file
|
||||
mainWindow.openFile(transferData.get(0));
|
||||
|
||||
@@ -9,7 +9,6 @@ import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.ExpandVetoException;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
@@ -24,7 +23,9 @@ import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -32,6 +33,7 @@ import org.fife.ui.rsyntaxtextarea.Theme;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.BackgroundWorker;
|
||||
@@ -39,6 +41,7 @@ import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsWindow;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
import jadx.gui.treemodel.JCertificate;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JLoadableNode;
|
||||
@@ -49,9 +52,9 @@ import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.JadxUpdate.IUpdateCallback;
|
||||
import jadx.gui.update.data.Release;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
@@ -95,7 +98,6 @@ public class MainWindow extends JFrame {
|
||||
private boolean isFlattenPackage;
|
||||
private JToggleButton flatPkgButton;
|
||||
private JCheckBoxMenuItem flatPkgMenuItem;
|
||||
private JCheckBoxMenuItem heapUsageBarMenuItem;
|
||||
|
||||
private JToggleButton deobfToggleBtn;
|
||||
private JCheckBoxMenuItem deobfMenuItem;
|
||||
@@ -111,16 +113,21 @@ public class MainWindow extends JFrame {
|
||||
this.cacheObject = new CacheObject();
|
||||
|
||||
resetCache();
|
||||
registerBundledFonts();
|
||||
initUI();
|
||||
initMenuAndToolbar();
|
||||
applySettings();
|
||||
checkForUpdate();
|
||||
setWindowIcons();
|
||||
}
|
||||
|
||||
private void applySettings() {
|
||||
setFont(settings.getFont());
|
||||
setEditorTheme(settings.getEditorThemePath());
|
||||
private void setWindowIcons() {
|
||||
List<Image> icons = new ArrayList<>();
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-16px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-32px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-48px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo.png"));
|
||||
setIconImages(icons);
|
||||
loadSettings();
|
||||
checkForUpdate();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
@@ -222,12 +229,6 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void saveAll(boolean export) {
|
||||
settings.setExportAsGradleProject(export);
|
||||
if (export) {
|
||||
settings.setSkipSources(false);
|
||||
settings.setSkipResources(false);
|
||||
}
|
||||
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
|
||||
@@ -239,6 +240,15 @@ public class MainWindow extends JFrame {
|
||||
|
||||
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
JadxArgs decompilerArgs = wrapper.getArgs();
|
||||
decompilerArgs.setExportAsGradleProject(export);
|
||||
if (export) {
|
||||
decompilerArgs.setSkipSources(false);
|
||||
decompilerArgs.setSkipResources(false);
|
||||
} else {
|
||||
decompilerArgs.setSkipSources(settings.isSkipSources());
|
||||
decompilerArgs.setSkipResources(settings.isSkipResources());
|
||||
}
|
||||
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
|
||||
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
|
||||
progressMonitor.setMillisToPopup(0);
|
||||
@@ -290,15 +300,17 @@ public class MainWindow extends JFrame {
|
||||
private void treeClickAction() {
|
||||
try {
|
||||
Object obj = tree.getLastSelectedPathComponent();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
if (obj instanceof JResource) {
|
||||
JResource res = (JResource) obj;
|
||||
ResourceFile resFile = res.getResFile();
|
||||
if (resFile != null && JResource.isSupportedForView(resFile.getType())) {
|
||||
tabbedPane.showResource(res);
|
||||
}
|
||||
} else if (obj instanceof JCertificate) {
|
||||
JCertificate cert = (JCertificate) obj;
|
||||
tabbedPane.showCertificate(cert);
|
||||
} else if ((obj instanceof JCertificate) || (obj instanceof ApkSignature)) {
|
||||
tabbedPane.showSimpleNode((JNode) obj);
|
||||
} else if (obj instanceof JNode) {
|
||||
JNode node = (JNode) obj;
|
||||
JClass cls = node.getRootClass();
|
||||
@@ -388,7 +400,7 @@ public class MainWindow extends JFrame {
|
||||
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
|
||||
flatPkgMenuItem.setState(isFlattenPackage);
|
||||
|
||||
heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
|
||||
JCheckBoxMenuItem heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
|
||||
heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar());
|
||||
heapUsageBarMenuItem.addActionListener(event -> {
|
||||
settings.setShowHeapUsageBar(!settings.isShowHeapUsageBar());
|
||||
@@ -595,7 +607,7 @@ public class MainWindow extends JFrame {
|
||||
});
|
||||
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
|
||||
@Override
|
||||
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
|
||||
public void treeWillExpand(TreeExpansionEvent event) {
|
||||
TreePath path = event.getPath();
|
||||
Object node = path.getLastPathComponent();
|
||||
if (node instanceof JLoadableNode) {
|
||||
@@ -604,7 +616,8 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
|
||||
public void treeWillCollapse(TreeExpansionEvent event) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
@@ -643,7 +656,14 @@ public class MainWindow extends JFrame {
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
public void setEditorTheme(String editorThemePath) {
|
||||
public static void registerBundledFonts() {
|
||||
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
if (Utils.FONT_HACK != null) {
|
||||
grEnv.registerFont(Utils.FONT_HACK);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEditorTheme(String editorThemePath) {
|
||||
try {
|
||||
editorTheme = Theme.load(getClass().getResourceAsStream(editorThemePath));
|
||||
} catch (Exception e) {
|
||||
@@ -661,6 +681,14 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
public void loadSettings() {
|
||||
Font font = settings.getFont();
|
||||
Font largerFont = font.deriveFont(font.getSize() + 2.f);
|
||||
|
||||
setFont(largerFont);
|
||||
setEditorTheme(settings.getEditorThemePath());
|
||||
tree.setFont(largerFont);
|
||||
tree.setRowHeight(-1);
|
||||
|
||||
tabbedPane.loadSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
.subscribeOn(Schedulers.single())
|
||||
.doOnNext(r -> LOG.debug("search event: {}", r))
|
||||
.switchMap(text -> prepareSearch(text)
|
||||
.doOnError(e -> LOG.error("Error prepare search: {}", e.getMessage(), e))
|
||||
.subscribeOn(Schedulers.single())
|
||||
.toList()
|
||||
.toFlowable(), 1)
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
import jadx.gui.treemodel.JCertificate;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -93,8 +94,8 @@ public class TabbedPane extends JTabbedPane {
|
||||
SwingUtilities.invokeLater(() -> setSelectedComponent(contentPanel));
|
||||
}
|
||||
|
||||
public void showCertificate(JCertificate cert) {
|
||||
final ContentPanel contentPanel = getContentPanel(cert);
|
||||
public void showSimpleNode(JNode node) {
|
||||
final ContentPanel contentPanel = getContentPanel(node);
|
||||
if (contentPanel == null) {
|
||||
return;
|
||||
}
|
||||
@@ -170,6 +171,9 @@ public class TabbedPane extends JTabbedPane {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (node instanceof ApkSignature) {
|
||||
return new HtmlPanel(this, node);
|
||||
}
|
||||
if (node instanceof JCertificate) {
|
||||
return new CertificatePanel(this, node);
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.ContentPanel;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public final class CodePanel extends ContentPanel {
|
||||
|
||||
private static final long serialVersionUID = 5310536092010045565L;
|
||||
|
||||
private final SearchBar searchBar;
|
||||
@@ -24,7 +25,6 @@ public final class CodePanel extends ContentPanel {
|
||||
|
||||
codeArea = new CodeArea(this);
|
||||
searchBar = new SearchBar(codeArea);
|
||||
|
||||
scrollPane = new JScrollPane(codeArea);
|
||||
initLineNumbers();
|
||||
|
||||
@@ -37,7 +37,23 @@ public final class CodePanel extends ContentPanel {
|
||||
}
|
||||
|
||||
private void initLineNumbers() {
|
||||
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
|
||||
// TODO: fix slow line rendering on big files
|
||||
if (codeArea.getDocument().getLength() <= 100_000) {
|
||||
LineNumbers numbers = new LineNumbers(codeArea);
|
||||
numbers.setUseSourceLines(isUseSourceLines());
|
||||
scrollPane.setRowHeaderView(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUseSourceLines() {
|
||||
if (node instanceof JClass) {
|
||||
return true;
|
||||
}
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
return !resNode.getLineMapping().isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class SearchAction extends AbstractAction {
|
||||
|
||||
@@ -32,16 +32,16 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
private static final int NUM_HEIGHT = Integer.MAX_VALUE - 1000000;
|
||||
private static final Map<?, ?> DESKTOP_HINTS = (Map<?, ?>) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
|
||||
|
||||
private CodeArea codeArea;
|
||||
private final CodeArea codeArea;
|
||||
private boolean useSourceLines = true;
|
||||
|
||||
private int lastDigits;
|
||||
private int lastLine;
|
||||
private Map<String, FontMetrics> fonts;
|
||||
|
||||
private transient final Color numberColor;
|
||||
private transient final Color currentColor;
|
||||
private transient final Border border;
|
||||
private final transient Color numberColor;
|
||||
private final transient Color currentColor;
|
||||
private final transient Border border;
|
||||
|
||||
public LineNumbers(CodeArea component) {
|
||||
this.codeArea = component;
|
||||
@@ -199,12 +199,10 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
|
||||
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
|
||||
String key = fontFamily + fontSize;
|
||||
FontMetrics fm = fonts.get(key);
|
||||
if (fm == null) {
|
||||
FontMetrics fm = fonts.computeIfAbsent(key, k -> {
|
||||
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
|
||||
fm = codeArea.getFontMetrics(font);
|
||||
fonts.put(key, fm);
|
||||
}
|
||||
return codeArea.getFontMetrics(font);
|
||||
});
|
||||
descent = Math.max(descent, fm.getDescent());
|
||||
}
|
||||
}
|
||||
@@ -221,4 +219,8 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
lastLine = currentLine;
|
||||
}
|
||||
}
|
||||
|
||||
public void setUseSourceLines(boolean useSourceLines) {
|
||||
this.useSourceLines = useSourceLines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
@@ -32,12 +31,8 @@ public class JadxUpdate {
|
||||
private static final Type RELEASES_LIST_TYPE = new TypeToken<List<Release>>() {
|
||||
}.getType();
|
||||
|
||||
private static final Comparator<Release> RELEASE_COMPARATOR = new Comparator<Release>() {
|
||||
@Override
|
||||
public int compare(Release o1, Release o2) {
|
||||
return VersionComparator.checkAndCompare(o1.getName(), o2.getName());
|
||||
}
|
||||
};
|
||||
private static final Comparator<Release> RELEASE_COMPARATOR = (o1, o2) ->
|
||||
VersionComparator.checkAndCompare(o1.getName(), o2.getName());
|
||||
|
||||
public interface IUpdateCallback {
|
||||
void onUpdate(Release r);
|
||||
@@ -47,17 +42,14 @@ public class JadxUpdate {
|
||||
}
|
||||
|
||||
public static void check(final IUpdateCallback callback) {
|
||||
Runnable run = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Release release = checkForNewRelease();
|
||||
if (release != null) {
|
||||
callback.onUpdate(release);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Jadx update error", e);
|
||||
Runnable run = () -> {
|
||||
try {
|
||||
Release release = checkForNewRelease();
|
||||
if (release != null) {
|
||||
callback.onUpdate(release);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Jadx update error", e);
|
||||
}
|
||||
};
|
||||
Thread thread = new Thread(run);
|
||||
@@ -77,17 +69,11 @@ public class JadxUpdate {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
for (Iterator<Release> it = list.iterator(); it.hasNext(); ) {
|
||||
Release release = it.next();
|
||||
if (release.getName().equalsIgnoreCase(version)
|
||||
|| release.isPreRelease()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
list.removeIf(release -> release.getName().equalsIgnoreCase(version) || release.isPreRelease());
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(list, RELEASE_COMPARATOR);
|
||||
list.sort(RELEASE_COMPARATOR);
|
||||
Release latest = list.get(list.size() - 1);
|
||||
if (VersionComparator.checkAndCompare(version, latest.getName()) >= 0) {
|
||||
return null;
|
||||
@@ -101,7 +87,7 @@ public class JadxUpdate {
|
||||
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
|
||||
con.setRequestMethod("GET");
|
||||
if (con.getResponseCode() == 200) {
|
||||
Reader reader = new InputStreamReader(con.getInputStream(), "UTF-8");
|
||||
Reader reader = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8);
|
||||
return GSON.fromJson(reader, type);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -21,8 +21,8 @@ public class CertificateManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CertificateManager.class);
|
||||
private static final String CERTIFICATE_TYPE_NAME = "X.509";
|
||||
|
||||
private final Certificate cert;
|
||||
private X509Certificate x509cert;
|
||||
private Certificate cert;
|
||||
|
||||
public static String decode(InputStream in) {
|
||||
StringBuilder strBuild = new StringBuilder();
|
||||
@@ -54,7 +54,7 @@ public class CertificateManager {
|
||||
}
|
||||
}
|
||||
|
||||
String generateHeader() {
|
||||
public String generateHeader() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
append(builder, NLS.str("certificate.cert_type"), x509cert.getType());
|
||||
append(builder, NLS.str("certificate.serialSigVer"), ((Integer) x509cert.getVersion()).toString());
|
||||
@@ -70,14 +70,14 @@ public class CertificateManager {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
String generateSignature() {
|
||||
public String generateSignature() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
append(builder, NLS.str("certificate.serialSigType"), x509cert.getSigAlgName());
|
||||
append(builder, NLS.str("certificate.serialSigOID"), x509cert.getSigAlgOID());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
String generateFingerprint() {
|
||||
public String generateFingerprint() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
append(builder, NLS.str("certificate.serialMD5"), getThumbPrint(x509cert, "MD5"));
|
||||
@@ -89,7 +89,7 @@ public class CertificateManager {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
String generatePublicKey() {
|
||||
public String generatePublicKey() {
|
||||
PublicKey publicKey = x509cert.getPublicKey();
|
||||
if (publicKey instanceof RSAPublicKey) {
|
||||
return generateRSAPublicKey();
|
||||
@@ -106,6 +106,8 @@ public class CertificateManager {
|
||||
|
||||
append(builder, NLS.str("certificate.serialPubKeyType"), pub.getAlgorithm());
|
||||
append(builder, NLS.str("certificate.serialPubKeyExponent"), pub.getPublicExponent().toString(10));
|
||||
append(builder, NLS.str("certificate.serialPubKeyModulusSize"), Integer.toString(
|
||||
pub.getModulus().toString(2).length()));
|
||||
append(builder, NLS.str("certificate.serialPubKeyModulus"), pub.getModulus().toString(10));
|
||||
|
||||
return builder.toString();
|
||||
@@ -120,7 +122,7 @@ public class CertificateManager {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
String generateTextForX509() {
|
||||
public String generateTextForX509() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (x509cert != null) {
|
||||
builder.append(generateHeader());
|
||||
@@ -136,7 +138,7 @@ public class CertificateManager {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String generateText() {
|
||||
public String generateText() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
String type = cert.getType();
|
||||
if (type.equals(CERTIFICATE_TYPE_NAME)) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
|
||||
public class CodeLinesInfo {
|
||||
private NavigableMap<Integer, JavaNode> map = new TreeMap<>();
|
||||
private final NavigableMap<Integer, JavaNode> map = new TreeMap<>();
|
||||
|
||||
public CodeLinesInfo(JavaClass cls) {
|
||||
addClass(cls);
|
||||
|
||||
@@ -2,9 +2,9 @@ package jadx.gui.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaClass;
|
||||
@@ -21,6 +21,10 @@ public class CodeUsageInfo {
|
||||
public List<CodeNode> getUsageList() {
|
||||
return usageList;
|
||||
}
|
||||
|
||||
public synchronized void addUsage(CodeNode codeNode) {
|
||||
usageList.add(codeNode);
|
||||
}
|
||||
}
|
||||
|
||||
private final JNodeCache nodeCache;
|
||||
@@ -29,7 +33,7 @@ public class CodeUsageInfo {
|
||||
this.nodeCache = nodeCache;
|
||||
}
|
||||
|
||||
private final Map<JNode, UsageInfo> usageMap = new HashMap<>();
|
||||
private final Map<JNode, UsageInfo> usageMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List<StringRef> lines) {
|
||||
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
|
||||
@@ -42,17 +46,13 @@ public class CodeUsageInfo {
|
||||
|
||||
private void addUsage(JNode jNode, JavaClass javaClass,
|
||||
CodeLinesInfo linesInfo, CodePosition codePosition, List<StringRef> lines) {
|
||||
UsageInfo usageInfo = usageMap.get(jNode);
|
||||
if (usageInfo == null) {
|
||||
usageInfo = new UsageInfo();
|
||||
usageMap.put(jNode, usageInfo);
|
||||
}
|
||||
UsageInfo usageInfo = usageMap.computeIfAbsent(jNode, key -> new UsageInfo());
|
||||
int line = codePosition.getLine();
|
||||
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
|
||||
StringRef codeLine = lines.get(line - 1);
|
||||
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
|
||||
CodeNode codeNode = new CodeNode(node, line, codeLine);
|
||||
usageInfo.getUsageList().add(codeNode);
|
||||
usageInfo.addUsage(codeNode);
|
||||
}
|
||||
|
||||
public List<CodeNode> getUsageList(JNode node) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
|
||||
public class JumpManager {
|
||||
|
||||
private List<JumpPosition> list = new ArrayList<>();
|
||||
private final List<JumpPosition> list = new ArrayList<>();
|
||||
private int currentPos = 0;
|
||||
|
||||
public void addPosition(JumpPosition pos) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package jadx.gui.utils;
|
||||
import java.util.Locale;
|
||||
|
||||
public class LangLocale {
|
||||
private Locale locale;
|
||||
private final Locale locale;
|
||||
|
||||
public LangLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class Link extends JLabel implements MouseListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Link.class);
|
||||
|
||||
private String url;
|
||||
private final String url;
|
||||
|
||||
public Link(String text, String url) {
|
||||
super(text);
|
||||
|
||||
@@ -4,24 +4,29 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class NLS {
|
||||
|
||||
private static Vector<LangLocale> i18nLocales = new Vector<>();
|
||||
private static final Vector<LangLocale> i18nLocales = new Vector<>();
|
||||
|
||||
private static Map<LangLocale, ResourceBundle> i18nMessagesMap = new HashMap<>();
|
||||
private static final Map<LangLocale, ResourceBundle> i18nMessagesMap = new HashMap<>();
|
||||
|
||||
private static final ResourceBundle fallbackMessagesMap;
|
||||
private static final LangLocale localLocale;
|
||||
|
||||
// Use these two fields to avoid invoking Map.get() method twice.
|
||||
private static ResourceBundle localizedMessagesMap;
|
||||
private static ResourceBundle fallbackMessagesMap;
|
||||
|
||||
private static LangLocale currentLocale;
|
||||
private static LangLocale localLocale;
|
||||
|
||||
static {
|
||||
localLocale = new LangLocale(Locale.getDefault());
|
||||
@@ -45,10 +50,13 @@ public class NLS {
|
||||
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
String resName = String.format("i18n/Messages_%s.properties", locale.get());
|
||||
URL bundleUrl = classLoader.getResource(resName);
|
||||
if (bundleUrl == null) {
|
||||
throw new JadxRuntimeException("Locale resource not found: " + resName);
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(bundleUrl.openStream(), StandardCharsets.UTF_8)) {
|
||||
bundle = new PropertyResourceBundle(reader);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load " + resName, e);
|
||||
throw new JadxRuntimeException("Failed to load " + resName, e);
|
||||
}
|
||||
i18nMessagesMap.put(locale, bundle);
|
||||
}
|
||||
@@ -66,7 +74,8 @@ public class NLS {
|
||||
if (bundle != null) {
|
||||
try {
|
||||
return bundle.getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
} catch (MissingResourceException ignored) {
|
||||
// use fallback string
|
||||
}
|
||||
}
|
||||
return fallbackMessagesMap.getString(key); // definitely exists
|
||||
|
||||
@@ -26,6 +26,16 @@ public class Utils {
|
||||
|
||||
public static final Font FONT_HACK = openFontTTF("Hack-Regular");
|
||||
|
||||
/**
|
||||
* The minimum about of memory in bytes we are trying to keep free, otherwise the application may run out of heap
|
||||
* which ends up in a Java garbage collector running "amok" (CPU utilization 100% for each core and the UI is
|
||||
* not responsive).
|
||||
* <p>
|
||||
* We can calculate and store this value here as the maximum heap is fixed for each JVM instance
|
||||
* and can't be changed at runtime.
|
||||
*/
|
||||
public static final long MIN_FREE_MEMORY = calculateMinFreeMemory();
|
||||
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
@@ -38,6 +48,14 @@ public class Utils {
|
||||
return new ImageIcon(resource);
|
||||
}
|
||||
|
||||
public static Image openImage(String path) {
|
||||
URL resource = Utils.class.getResource(path);
|
||||
if (resource == null) {
|
||||
throw new JadxRuntimeException("Image not found: " + path);
|
||||
}
|
||||
return Toolkit.getDefaultToolkit().createImage(resource);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Font openFontTTF(String name) {
|
||||
String fontPath = "/fonts/" + name + ".ttf";
|
||||
@@ -107,31 +125,37 @@ public class Utils {
|
||||
return overIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 20% of the maximum heap size limited to 512 MB (bytes)
|
||||
*/
|
||||
public static long calculateMinFreeMemory() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long minFree = (long) (runtime.maxMemory() * 0.2);
|
||||
return Math.min(minFree, 512 * 1024L * 1024L);
|
||||
}
|
||||
|
||||
public static boolean isFreeMemoryAvailable() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
long totalFree = runtime.freeMemory() + maxMemory - runtime.totalMemory();
|
||||
return totalFree > maxMemory * 0.2;
|
||||
long totalFree = runtime.freeMemory() + (maxMemory - runtime.totalMemory());
|
||||
return totalFree > MIN_FREE_MEMORY;
|
||||
}
|
||||
|
||||
public static String memoryInfo() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
long allocatedMemory = runtime.totalMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
|
||||
sb.append("heap: ").append(format(allocatedMemory - freeMemory));
|
||||
sb.append(", allocated: ").append(format(allocatedMemory));
|
||||
sb.append(", free: ").append(format(freeMemory));
|
||||
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
|
||||
sb.append(", max: ").append(format(maxMemory));
|
||||
|
||||
return sb.toString();
|
||||
return "heap: " + format(allocatedMemory - freeMemory) +
|
||||
", allocated: " + format(allocatedMemory) +
|
||||
", free: " + format(freeMemory) +
|
||||
", total free: " + format(freeMemory + maxMemory - allocatedMemory) +
|
||||
", max: " + format(maxMemory);
|
||||
}
|
||||
|
||||
private static String format(long mem) {
|
||||
return Long.toString((long) (mem / 1024. / 1024.)) + "MB";
|
||||
return (long) (mem / (double) (1024L * 1024L)) + "MB";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,10 +12,14 @@ public class SimpleIndex<T> implements SearchIndex<T> {
|
||||
private final List<String> keys = new ArrayList<>();
|
||||
private final List<T> values = new ArrayList<>();
|
||||
|
||||
private final Object syncData = new Object();
|
||||
|
||||
@Override
|
||||
public void put(String str, T value) {
|
||||
keys.add(str);
|
||||
values.add(value);
|
||||
synchronized (syncData) {
|
||||
keys.add(str);
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,13 +43,15 @@ public class SimpleIndex<T> implements SearchIndex<T> {
|
||||
@Override
|
||||
public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
|
||||
return Flowable.create(emitter -> {
|
||||
int size = size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
|
||||
emitter.onNext(values.get(i));
|
||||
}
|
||||
if (emitter.isCancelled()) {
|
||||
return;
|
||||
synchronized (syncData) {
|
||||
int size = keys.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
|
||||
emitter.onNext(values.get(i));
|
||||
}
|
||||
if (emitter.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
emitter.onComplete();
|
||||
@@ -54,6 +60,8 @@ public class SimpleIndex<T> implements SearchIndex<T> {
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.size();
|
||||
synchronized (syncData) {
|
||||
return keys.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ tabs.closeAll=Close All
|
||||
nav.back=Back
|
||||
nav.forward=Forward
|
||||
|
||||
message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d classes were not indexed.<br>If you want all classes to be indexed restart Jadx with increased maximum heap size.</html>
|
||||
message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d classes were not indexed.<br>If you want all classes to be indexed restart Jadx with increased maximum heap size.</html>
|
||||
|
||||
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
|
||||
|
||||
@@ -136,9 +136,20 @@ certificate.serialValidUntil=Valid until
|
||||
certificate.serialPubKeyType=Public key type
|
||||
certificate.serialPubKeyExponent=Exponent
|
||||
certificate.serialPubKeyModulus=Modulus
|
||||
certificate.serialPubKeyModulusSize=Modulus size (bits)
|
||||
certificate.serialSigType=Signature type
|
||||
certificate.serialSigOID=Signature OID
|
||||
certificate.serialMD5=MD5 Fingerprint
|
||||
certificate.serialSHA1=SHA-1 Fingerprint
|
||||
certificate.serialSHA256=SHA-256 Fingerprint
|
||||
certificate.serialPubKeyY=Y
|
||||
|
||||
apkSignature.signer=Signer
|
||||
apkSignature.verificationSuccess=Signature verification succeeded
|
||||
apkSignature.verificationFailed=Signature verification succeeded
|
||||
apkSignature.signatureSuccess=Valid APK signature v%d found
|
||||
apkSignature.signatureFailed=Invalid APK signature v%d found
|
||||
apkSignature.errors=Errors
|
||||
apkSignature.warnings=Warnings
|
||||
apkSignature.exception=APK verification failed
|
||||
apkSignature.unprotectedEntry=Files that are not protected by signature. Unauthorized modifications to this JAR entry will not be detected.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 814 B |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,5 @@
|
||||
package jadx.gui.tests
|
||||
|
||||
import jadx.gui.utils.search.StringRef
|
||||
import spock.lang.Specification
|
||||
|
||||
|
||||
@@ -1,112 +1,122 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Collection;
|
||||
public class CertificateManagerTest {
|
||||
private static final String CERTIFICATE_TEST_DIR = "certificate-test/";
|
||||
private static final String DSA = "CERT.DSA";
|
||||
private static final String RSA = "CERT.RSA";
|
||||
private static final String EMPTY = "EMPTY.txt";
|
||||
|
||||
private String emptyPath;
|
||||
private CertificateManager certificateManagerRSA;
|
||||
private CertificateManager certificateManagerDSA;
|
||||
|
||||
public class CertificateManagerTest {
|
||||
private static final String DSA = "CERT.DSA";
|
||||
private static final String RSA = "CERT.RSA";
|
||||
private static final String EMPTY = "EMPTY.txt";
|
||||
private String emptyPath;
|
||||
CertificateManager certificateManagerRSA;
|
||||
CertificateManager certificateManagerDSA;
|
||||
private CertificateManager getCertificateManger(String resName) {
|
||||
String certPath = getResourcePath(resName);
|
||||
try (InputStream in = new FileInputStream(certPath)) {
|
||||
Collection<? extends Certificate> certificates = CertificateManager.readCertificates(in);
|
||||
Certificate cert = certificates.iterator().next();
|
||||
return new CertificateManager(cert);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create CertificateManager");
|
||||
}
|
||||
}
|
||||
|
||||
private CertificateManager getCertificateManger(String resName)
|
||||
{
|
||||
String sertPath = getClass().getClassLoader().getResource(resName).getPath();
|
||||
try (InputStream in = new FileInputStream(sertPath)) {
|
||||
Collection<? extends Certificate> certificates = CertificateManager.readCertificates(in);
|
||||
Certificate cert = (Certificate)certificates.toArray()[0];
|
||||
return new CertificateManager(cert);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Before
|
||||
public void setUp() {
|
||||
emptyPath = getResourcePath(EMPTY);
|
||||
certificateManagerRSA = getCertificateManger(RSA);
|
||||
certificateManagerDSA = getCertificateManger(DSA);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
emptyPath = getClass().getClassLoader().getResource(EMPTY).getPath();
|
||||
certificateManagerRSA = getCertificateManger(RSA);
|
||||
certificateManagerDSA = getCertificateManger(DSA);
|
||||
}
|
||||
@Test
|
||||
public void decodeNotCertificateFile() throws IOException {
|
||||
try (InputStream in = new FileInputStream(emptyPath)) {
|
||||
String result = CertificateManager.decode(in);
|
||||
Assert.assertEquals("", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeRSAKeyHeader() {
|
||||
String string = certificateManagerRSA.generateHeader();
|
||||
Assert.assertTrue(string.contains("X.509"));
|
||||
Assert.assertTrue(string.contains("0x4bd68052"));
|
||||
Assert.assertTrue(string.contains("CN=test cert, OU=test unit, O=OOO TestOrg, L=St.Peterburg, ST=Russia, C=123456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeNotCertificateFile() {
|
||||
try (InputStream in = new FileInputStream(emptyPath)) {
|
||||
String result = CertificateManager.decode(in);
|
||||
Assert.assertEquals(result, "");
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAKeyHeader() {
|
||||
String string = certificateManagerDSA.generateHeader();
|
||||
Assert.assertTrue(string.contains("X.509"));
|
||||
Assert.assertTrue(string.contains("0x16420ba2"));
|
||||
Assert.assertTrue(string.contains("O=\"UJMRFVV CN=EDCVBGT C=TG\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeRSAKeyHeader() {
|
||||
String string = certificateManagerRSA.generateHeader();
|
||||
Assert.assertTrue(string.contains("X.509"));
|
||||
Assert.assertTrue(string.contains("0x4bd68052"));
|
||||
Assert.assertTrue(string.contains("CN=test cert, OU=test unit, O=OOO TestOrg, L=St.Peterburg, ST=Russia, C=123456"));
|
||||
@Test
|
||||
public void decodeRSAKeySignature() {
|
||||
String string = certificateManagerRSA.generateSignature();
|
||||
Assert.assertTrue(string.contains("SHA256withRSA"));
|
||||
Assert.assertTrue(string.contains("1.2.840.113549.1.1.11"));
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAKeySignature() {
|
||||
String string = certificateManagerDSA.generateSignature();
|
||||
Assert.assertTrue(string.contains("SHA1withDSA"));
|
||||
Assert.assertTrue(string.contains("1.2.840.10040.4.3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeDSAKeyHeader() {
|
||||
String string = certificateManagerDSA.generateHeader();
|
||||
Assert.assertTrue(string.contains("X.509"));
|
||||
Assert.assertTrue(string.contains("0x16420ba2"));
|
||||
Assert.assertTrue(string.contains("O=\"UJMRFVV CN=EDCVBGT C=TG\""));
|
||||
@Test
|
||||
public void decodeRSAFingerprint() {
|
||||
String string = certificateManagerRSA.generateFingerprint();
|
||||
Assert.assertTrue(string.contains("61 18 0A 71 3F C9 55 16 4E 04 E3 C5 45 08 D9 11"));
|
||||
Assert.assertTrue(string.contains("A0 6E A6 06 DB 2C 6F 3A 16 56 7F 75 97 7B AE 85 C2 13 09 37"));
|
||||
Assert.assertTrue(string.contains("12 53 E8 BB C8 AA 27 A8 49 9B F8 0D 6E 68 CE 32 35 50 DE 55 A7 E7 8C 29 51 00 96 D7 56 F4 54 " +
|
||||
"44"));
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void decodeRSAKeySignature() {
|
||||
String string = certificateManagerRSA.generateSignature();
|
||||
Assert.assertTrue(string.contains("SHA256withRSA"));
|
||||
Assert.assertTrue(string.contains("1.2.840.113549.1.1.11"));
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAKeySignature() {
|
||||
String string = certificateManagerDSA.generateSignature();
|
||||
Assert.assertTrue(string.contains("SHA1withDSA"));
|
||||
Assert.assertTrue(string.contains("1.2.840.10040.4.3"));
|
||||
}
|
||||
@Test
|
||||
public void decodeRSAFingerprint() {
|
||||
String string = certificateManagerRSA.generateFingerprint();
|
||||
Assert.assertTrue(string.contains("61 18 0A 71 3F C9 55 16 4E 04 E3 C5 45 08 D9 11"));
|
||||
Assert.assertTrue(string.contains("A0 6E A6 06 DB 2C 6F 3A 16 56 7F 75 97 7B AE 85 C2 13 09 37"));
|
||||
Assert.assertTrue(string.contains("12 53 E8 BB C8 AA 27 A8 49 9B F8 0D 6E 68 CE 32 35 50 DE 55 A7 E7 8C 29 51 00 96 D7 56 F4 54 44"));
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAFingerprint() {
|
||||
String string = certificateManagerDSA.generateFingerprint();
|
||||
Assert.assertTrue(string.contains("D9 06 A6 2D 1F 79 8C 9D A6 EF 40 C7 2E C2 EA 0B"));
|
||||
Assert.assertTrue(string.contains("18 E9 9C D4 A1 40 8F 63 FA EC 2E 62 A0 F2 AE B7 3F C3 C2 04"));
|
||||
Assert.assertTrue(string.contains("74 F9 48 64 EE AC 92 26 53 2C 7A 0E 55 BE 5E D8 2F A7 D9 A9 99 F5 D5 21 2C 51 21 C4 31 AD 73 40"));
|
||||
}
|
||||
@Test
|
||||
public void decodeRSAPubKey() {
|
||||
String string = certificateManagerRSA.generatePublicKey();
|
||||
Assert.assertTrue(string.contains("RSA"));
|
||||
Assert.assertTrue(string.contains("65537"));
|
||||
Assert.assertTrue(string.contains("16819531290318044625546437357099080306019392752925688951114880688329201213180109168890384305768067101521914473763638669503560977521269328582980060332888147680193318231260043189411794465899645633586173494259691101582064441956032924396850221679489313043628562082670183392670094163371858684118480409374749790551473773845213427476236147328434427272177623018935282929152308753854314219987617604037468769472089902090243358285991739642170211970862773121939911777280101937073243006335384636193260583579409760790138329893534549366882523130765297472656435892831796545149793228897111760122091442123535919361963075454640516520743"));
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAPubKey() {
|
||||
String string = certificateManagerDSA.generatePublicKey();
|
||||
Assert.assertTrue(string.contains("DSA"));
|
||||
Assert.assertTrue(string.contains("19323367605058154682563301282345453222279312104889899001698209626254725581511375469963812461090495963838615773832867364330457010553974237985991904800958394169421485070378434746792379708805563793253282995274293621162504943287538455944652344378242226897507369146942411692220922477368782490423187845815262510366"));
|
||||
}
|
||||
@Test
|
||||
public void decodeDSAFingerprint() {
|
||||
String string = certificateManagerDSA.generateFingerprint();
|
||||
Assert.assertTrue(string.contains("D9 06 A6 2D 1F 79 8C 9D A6 EF 40 C7 2E C2 EA 0B"));
|
||||
Assert.assertTrue(string.contains("18 E9 9C D4 A1 40 8F 63 FA EC 2E 62 A0 F2 AE B7 3F C3 C2 04"));
|
||||
Assert.assertTrue(string.contains("74 F9 48 64 EE AC 92 26 53 2C 7A 0E 55 BE 5E D8 2F A7 D9 A9 99 F5 D5 21 2C 51 21 C4 31 AD 73 " +
|
||||
"40"));
|
||||
}
|
||||
|
||||
}
|
||||
@Test
|
||||
public void decodeRSAPubKey() {
|
||||
String string = certificateManagerRSA.generatePublicKey();
|
||||
Assert.assertTrue(string.contains("RSA"));
|
||||
Assert.assertTrue(string.contains("65537"));
|
||||
Assert.assertTrue(string.contains(
|
||||
"16819531290318044625546437357099080306019392752925688951114880688329201213180109168890384305768067101521914473763638669503560977521269328582980060332888147680193318231260043189411794465899645633586173494259691101582064441956032924396850221679489313043628562082670183392670094163371858684118480409374749790551473773845213427476236147328434427272177623018935282929152308753854314219987617604037468769472089902090243358285991739642170211970862773121939911777280101937073243006335384636193260583579409760790138329893534549366882523130765297472656435892831796545149793228897111760122091442123535919361963075454640516520743"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeDSAPubKey() {
|
||||
String string = certificateManagerDSA.generatePublicKey();
|
||||
Assert.assertTrue(string.contains("DSA"));
|
||||
Assert.assertTrue(string.contains(
|
||||
"19323367605058154682563301282345453222279312104889899001698209626254725581511375469963812461090495963838615773832867364330457010553974237985991904800958394169421485070378434746792379708805563793253282995274293621162504943287538455944652344378242226897507369146942411692220922477368782490423187845815262510366"));
|
||||
}
|
||||
|
||||
private String getResourcePath(String resName) {
|
||||
URL resource = getClass().getClassLoader().getResource(CERTIFICATE_TEST_DIR + resName);
|
||||
if (resource == null) {
|
||||
throw new RuntimeException("Resource not found: " + resName);
|
||||
}
|
||||
return resource.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user