Commit 365c1faf authored by Skylot's avatar Skylot

Merge branch 'master' into type-inference-wip

parents cf79a519 9797fe5b
......@@ -12,8 +12,7 @@ before_install:
- chmod +x gradlew
# override install to skip 'gradle assemble'
install:
- true
install: true
env:
global:
......@@ -21,34 +20,21 @@ env:
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
matrix:
jdk:
- openjdk8
- oraclejdk8
- openjdk11
script: ./gradlew clean build
jobs:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
- env: JDK=openjdk11
jdk: openjdk11
script:
- java -version
- ./gradlew clean build
deploy:
- provider: script
skip_cleanup: true
on:
branch: master
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-master.sh
- provider: script
skip_cleanup: true
on:
branch: release
tags: false
condition: $JDK = oracle-8
script: bash scripts/travis-release.sh
notifications:
email:
- skylot@gmail.com
- stage: deploy-unstable
jdk: openjdk8
if: branch = master AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-master.sh
- stage: deploy-release
jdk: openjdk8
if: branch = release AND repo = env(MAIN_REPO) AND type = push
script: bash scripts/travis-release.sh
plugins {
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.20.0'
id 'com.github.ben-manes.versions' version '0.21.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
......@@ -35,12 +35,12 @@ allprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.25'
compile 'org.slf4j:slf4j-api:1.7.26'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.23.4'
testCompile 'org.mockito:mockito-core:2.25.0'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
}
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
......@@ -5,13 +5,17 @@ dependencies {
compile files('lib/dx-1.16.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:7.0'
compile 'org.jetbrains:annotations:16.0.3'
compile 'uk.com.robust-it:cloning:1.9.11'
compile 'org.ow2.asm:asm:7.1'
compile 'org.jetbrains:annotations:17.0.0'
compile 'uk.com.robust-it:cloning:1.9.12'
testCompile 'org.smali:smali:2.2.5'
testCompile 'org.smali:baksmali:2.2.5'
testCompile 'org.smali:smali:2.2.6'
testCompile 'org.smali:baksmali:2.2.6'
testCompile 'org.apache.commons:commons-lang3:3.8.1'
// update dependency in smali
testCompile 'com.google.guava:guava:27.1-jre'
testCompile 'com.beust:jcommander:1.74'
}
......@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
......@@ -59,7 +58,6 @@ public final class JadxDecompiler {
private RootNode root;
private List<IDexTreeVisitor> passes;
private CodeGen codeGen;
private List<JavaClass> classes;
private List<ResourceFile> resources;
......@@ -97,7 +95,6 @@ public final class JadxDecompiler {
void init() {
this.passes = Jadx.getPassesList(args);
this.codeGen = new CodeGen();
}
void reset() {
......@@ -106,7 +103,6 @@ public final class JadxDecompiler {
xmlParser = null;
root = null;
passes = null;
codeGen = null;
}
public static String getVersion() {
......@@ -215,9 +211,11 @@ public final class JadxDecompiler {
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
if (!classNode.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(classNode, this);
clsList.add(javaClass);
classesMap.put(classNode, javaClass);
}
}
classes = Collections.unmodifiableList(clsList);
}
......@@ -289,7 +287,7 @@ public final class JadxDecompiler {
}
void processClass(ClassNode cls) {
ProcessClass.process(cls, passes, codeGen);
ProcessClass.process(cls, passes, true);
}
RootNode getRoot() {
......
......@@ -2,8 +2,6 @@ package jadx.core;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
......@@ -19,8 +17,8 @@ public final class ProcessClass {
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
if (codeGen == null && cls.getState() == PROCESSED) {
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
if (!generateCode && cls.getState() == PROCESSED) {
return;
}
synchronized (getSyncObj(cls)) {
......@@ -33,9 +31,9 @@ public final class ProcessClass {
}
cls.setState(PROCESSED);
}
if (cls.getState() == PROCESSED && codeGen != null) {
if (cls.getState() == PROCESSED && generateCode) {
processDependencies(cls, passes);
codeGen.visit(cls);
CodeGen.generate(cls);
}
} catch (Exception e) {
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
......@@ -48,6 +46,6 @@ public final class ProcessClass {
}
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
}
}
......@@ -97,7 +97,7 @@ public class ClassGen {
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
return clsCode.finish();
}
public void addClassCode(CodeWriter code) throws CodegenException {
......
package jadx.core.codegen;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.CodegenException;
public class CodeGen {
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
public static void generate(ClassNode cls) throws CodegenException {
if (cls.contains(AFlag.DONT_GENERATE)) {
cls.setCode(CodeWriter.EMPTY);
} else {
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
cls.setCode(clsGen.makeClass());
}
}
private CodeGen() {
}
}
......@@ -24,6 +24,8 @@ public class CodeWriter {
public static final String NL = System.getProperty("line.separator");
public static final String INDENT_STR = " ";
public static final CodeWriter EMPTY = new CodeWriter().finish();
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
......@@ -250,7 +252,7 @@ public class CodeWriter {
return lineMap;
}
public void finish() {
public CodeWriter finish() {
removeFirstEmptyLine();
buf.trimToSize();
code = buf.toString();
......@@ -266,11 +268,12 @@ public class CodeWriter {
it.remove();
}
}
return this;
}
private void removeFirstEmptyLine() {
int len = NL.length();
if (buf.substring(0, len).equals(NL)) {
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
}
......
......@@ -48,7 +48,6 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -549,7 +548,7 @@ public class InsnGen {
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
inlineAnonymousConstr(code, cls, insn);
inlineAnonymousConstructor(code, cls, insn);
return;
}
if (insn.isSelf()) {
......@@ -567,20 +566,14 @@ public class InsnGen {
generateMethodArguments(code, insn, 0, callMth);
}
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
// anonymous class construction
if (cls.contains(AFlag.DONT_GENERATE)) {
code.add("/* anonymous class already generated */");
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
return;
}
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
cls.add(AFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
......
......@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
......@@ -123,7 +124,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
accFlagsValue = cls.getAccessFlags();
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
markAnonymousClass(this);
buildCache();
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
......@@ -394,6 +395,29 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
&& getDefaultConstructor() != null;
}
public boolean isLambdaCls() {
return accessFlags.isSynthetic() && accessFlags.isFinal()
&& clsInfo.getType().getObject().contains(".-$$Lambda$")
&& countStaticFields() == 0;
}
private int countStaticFields() {
int c = 0;
for (FieldNode field : fields) {
if (field.getAccessFlags().isStatic()) {
c++;
}
}
return c;
}
private static void markAnonymousClass(ClassNode cls) {
if (cls.isAnonymous() || cls.isLambdaCls()) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
}
}
@Nullable
public MethodNode getClassInitMth() {
return searchMethodByName("<clinit>()V");
......
......@@ -327,4 +327,24 @@ public class InsnNode extends LineAttrNode {
}
return INSN_CLONER.deepClone(this);
}
public boolean canThrowException() {
switch (getType()) {
case RETURN:
case IF:
case GOTO:
case MOVE:
case MOVE_EXCEPTION:
case NEG:
case CONST:
case CONST_STR:
case CONST_CLASS:
case CMP_L:
case CMP_G:
return false;
default:
return true;
}
}
}
......@@ -329,16 +329,24 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
InsnNode insn = insnByOffset[offset];
insn.add(AFlag.TRY_ENTER);
boolean tryBlockStarted = false;
InsnNode insn = null;
while (offset <= end && offset >= 0) {
insn = insnByOffset[offset];
catchBlock.addInsn(insn);
if (insn != null) {
if (tryBlockStarted) {
catchBlock.addInsn(insn);
} else if (insn.canThrowException()) {
insn.add(AFlag.TRY_ENTER);
catchBlock.addInsn(insn);
tryBlockStarted = true;
}
}
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
}
if (insnByOffset[end] != null) {
insnByOffset[end].add(AFlag.TRY_LEAVE);
} else {
} else if (insn != null) {
insn.add(AFlag.TRY_LEAVE);
}
}
......
......@@ -51,11 +51,10 @@ public class ClassModifier extends AbstractVisitor {
cls.add(AFlag.DONT_GENERATE);
return false;
}
markAnonymousClass(cls);
removeSyntheticFields(cls);
cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth));
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
markAnonymousClass(cls);
return false;
}
......@@ -69,29 +68,36 @@ public class ClassModifier extends AbstractVisitor {
private void markAnonymousClass(ClassNode cls) {
if (cls.isAnonymous()) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
}
}
/**
* Remove synthetic fields if type is outer class or class will be inlined (anonymous)
*/
private static void removeSyntheticFields(ClassNode cls) {
if (!cls.getClassInfo().isInner() || cls.getAccessFlags().isStatic()) {
if (cls.getAccessFlags().isStatic()) {
return;
}
// remove fields if it is synthetic and type is a outer class
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
ClassInfo parentClass = cls.getClassInfo().getParentClass();
if (fieldsCls != null && parentClass.equals(fieldsCls.getClassInfo())) {
int found = 0;
for (MethodNode mth : cls.getMethods()) {
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
found++;
boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS);
if (inline || cls.getClassInfo().isInner()) {
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
ClassInfo parentClass = cls.getClassInfo().getParentClass();
if (fieldsCls != null
&& (inline || parentClass.equals(fieldsCls.getClassInfo()))) {
int found = 0;
for (MethodNode mth : cls.getMethods()) {
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
found++;
}
}
if (found != 0) {
field.addAttr(new FieldReplaceAttr(fieldsCls.getClassInfo()));
field.add(AFlag.DONT_GENERATE);
}
}
if (found != 0) {
field.addAttr(new FieldReplaceAttr(parentClass));
field.add(AFlag.DONT_GENERATE);
}
}
}
......@@ -137,7 +143,7 @@ public class ClassModifier extends AbstractVisitor {
return true;
}
private static void removeSyntheticMethods(ClassNode cls, MethodNode mth) {
private static void removeSyntheticMethods(MethodNode mth) {
if (mth.isNoCode()) {
return;
}
......@@ -145,6 +151,7 @@ public class ClassModifier extends AbstractVisitor {
if (!af.isSynthetic()) {
return;
}
ClassNode cls = mth.getParentClass();
if (removeBridgeMethod(cls, mth)) {
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
......
......@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.exceptions.JadxException;
public class DependencyCollector extends AbstractVisitor {
......@@ -41,6 +42,12 @@ public class DependencyCollector extends AbstractVisitor {
}
for (FieldNode fieldNode : cls.getFields()) {
addDep(dex, depList, fieldNode.getType());
// process instructions from field init
FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT);
if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) {
processInsn(dex, depList, fieldInitAttr.getInsn());
}
}
// TODO: process annotations and generics
for (MethodNode methodNode : cls.getMethods()) {
......
......@@ -5,6 +5,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode;
......@@ -136,7 +138,10 @@ public class RegionUtils {
return !notEmpty(container);
}
public static boolean notEmpty(IContainer container) {
public static boolean notEmpty(@Nullable IContainer container) {
if (container == null) {
return false;
}
if (container instanceof IBlock) {
return !((IBlock) container).getInstructions().isEmpty();
} else if (container instanceof IRegion) {
......
package jadx.core.utils.android;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.android.dx.rop.code.AccessFlags;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
......@@ -86,6 +91,7 @@ public class AndroidResourcesUtils {
}
private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) {
Map<Integer, FieldNode> resFieldsMap = fillResFieldsMap(resCls);
Map<String, ClassNode> innerClsMap = new TreeMap<>();
if (rClsExists) {
for (ClassNode innerClass : resCls.getInnerClasses()) {
......@@ -93,18 +99,20 @@ public class AndroidResourcesUtils {
}
}
for (ResourceEntry resource : resStorage.getResources()) {
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + name,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
resCls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
}
return newTypeCls;
});
FieldNode rField = typeCls.searchFieldByName(resource.getKeyName());
final String resTypeName = resource.getTypeName();
ClassNode typeCls = innerClsMap.computeIfAbsent(
resTypeName,
name -> addClassForResType(resCls, rClsExists, name)
);
final String resName;
if ("style".equals(resTypeName)) {
resName = resource.getKeyName().replace('.', '_');
} else {
resName = resource.getKeyName();
}
FieldNode rField = typeCls.searchFieldByName(resName);
if (rField == null) {
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resource.getKeyName(), ArgType.INT);
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
rField.addAttr(FieldInitAttr.constValue(resource.getId()));
typeCls.getFields().add(rField);
......@@ -112,6 +120,42 @@ public class AndroidResourcesUtils {
rField.addAttr(AType.COMMENTS, "added by JADX");
}
}
FieldNode fieldNode = resFieldsMap.get(resource.getId());
if (fieldNode != null
&& !fieldNode.getName().equals(resName)
&& NameMapper.isValidIdentifier(resName)) {
fieldNode.getFieldInfo().setAlias(resName);
}
}
}
@NotNull
private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) {
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + typeName,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
resCls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
}
return newTypeCls;
}
@NotNull
private static Map<Integer, FieldNode> fillResFieldsMap(ClassNode resCls) {
Map<Integer, FieldNode> resFieldsMap = new HashMap<>();
ConstStorage constStorage = resCls.root().getConstValues();
Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
for (Map.Entry<Object, FieldNode> entry : constFields.entrySet()) {
Object key = entry.getKey();
FieldNode field = entry.getValue();
AccessInfo accessFlags = field.getAccessFlags();
if (field.getType().equals(ArgType.INT)
&& accessFlags.isStatic()
&& accessFlags.isFinal()
&& key instanceof Integer) {
resFieldsMap.put((Integer) key, field);
}
}
return resFieldsMap;
}
}
......@@ -59,8 +59,8 @@ public class InputFile {
loadFromZip(".dex");
return;
}
if (fileName.endsWith(".jar")) {
// check if jar contains '.dex' files
if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) {
// check if jar/aar contains '.dex' files
if (loadFromZip(".dex")) {
return;
}
......
......@@ -15,8 +15,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -40,7 +38,6 @@ public class BinaryXMLParser extends CommonBinaryParser {
private static final boolean ATTR_NEW_LINE = false;
private final Map<Integer, String> styleMap = new HashMap<>();
private final Map<Integer, FieldNode> localStyleMap = new HashMap<>();
private final Map<Integer, String> resNames;
private final Map<String, String> nsMap = new HashMap<>();
private Set<String> nsMapGenerated;
......@@ -63,16 +60,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
this.rootNode = rootNode;
try {
readAndroidRStyleClass();
// add application constants
ConstStorage constStorage = rootNode.getConstValues();
Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
for (Map.Entry<Object, FieldNode> entry : constFields.entrySet()) {
Object key = entry.getKey();
FieldNode field = entry.getValue();
if (field.getType().equals(ArgType.INT) && key instanceof Integer) {
localStyleMap.put((Integer) key, field);
}
}
resNames = constStorage.getResourcesNames();
} catch (Exception e) {
throw new JadxRuntimeException("BinaryXMLParser init error", e);
......@@ -381,38 +369,27 @@ public class BinaryXMLParser extends CommonBinaryParser {
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData,
String shortNsName, String attrName) {
if (attrValDataType == TYPE_REFERENCE) {
// reference custom processing
String name = styleMap.get(attrValData);
if (name != null) {
writer.add("@style/").add(name.replaceAll("_", "."));
} else {
FieldNode field = localStyleMap.get(attrValData);
if (field != null) {
String cls = field.getParentClass().getShortName().toLowerCase();
String resName = resNames.get(attrValData);
if (resName != null) {
writer.add("@");
if ("id".equals(cls)) {
writer.add('+');
if (resName.startsWith("id/")) {
writer.add("+");
}
writer.add(cls).add("/").add(field.getName());
writer.add(resName);
} else {
String resName = resNames.get(attrValData);
resName = ValuesParser.getAndroidResMap().get(attrValData);
if (resName != null) {
writer.add("@");
if (resName.startsWith("id/")) {
writer.add("+");
}
writer.add(resName);
writer.add("@android:").add(resName);
} else if (attrValData == 0) {
writer.add("@null");
} else {
resName = ValuesParser.getAndroidResMap().get(attrValData);
if (resName != null) {
writer.add("@android:").add(resName);
} else if (attrValData == 0) {
writer.add("@null");
} else {
writer.add("0x").add(Integer.toHexString(attrValData));
}
writer.add("0x").add(Integer.toHexString(attrValData));
}
}
}
......
......@@ -140,7 +140,7 @@ public abstract class IntegrationTest extends TestUtils {
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
ProcessClass.process(cls, passes, new CodeGen());
ProcessClass.process(cls, passes, true);
}
protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) {
......@@ -154,7 +154,7 @@ public abstract class IntegrationTest extends TestUtils {
protected void generateClsCode(ClassNode cls) {
try {
new CodeGen().visit(cls);
CodeGen.generate(cls);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
......
......@@ -6,10 +6,8 @@ import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import java.io.IOException;
import java.security.SecureClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static javax.tools.JavaFileObject.Kind;
......
......@@ -15,12 +15,11 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess;
import jadx.api.JavaClass;
import jadx.core.codegen.CodeGen;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.tests.api.IntegrationTest;
......@@ -99,15 +98,10 @@ public abstract class BaseExternalTest extends IntegrationTest {
if (!decompile) {
return false;
}
// ProcessClass.process(classNode, passes, new CodeGen());
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, classNode);
}
try {
new CodeGen().visit(classNode);
ProcessClass.process(classNode, passes, true);
} catch (Exception e) {
throw new JadxRuntimeException("Codegen failed", e);
throw new JadxRuntimeException("Class process failed", e);
}
LOG.info("----------------------------------------------------------------");
LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex());
......
......@@ -3,7 +3,6 @@ package jadx.tests.integration.inner;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
......
......@@ -3,10 +3,6 @@ package jadx.tests.integration.inner;
import org.junit.Test;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.SmaliTest;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
public class TestInnerClassSyntheticConstructor extends IntegrationTest {
......
......@@ -5,7 +5,6 @@ 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.Matchers.containsString;
import static org.junit.Assert.assertThat;
......
......@@ -4,9 +4,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
......
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.junit.Assert.assertThat;
public class TestTryCatchStartOnMove extends SmaliTest {
// private static void test(String s) {
// try {
// call(s);
// } catch (Exception unused) {
// System.out.println("Failed call for " + s);
// }
// }
//
// private static void call(String s) {
// }
@Test
public void test() {
ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryCatchStartOnMove");
String code = cls.getCode().toString();
assertThat(code, containsOne("try {"));
assertThat(code, containsOne("} catch (Exception e) {"));
assertThat(code, containsOne("System.out.println(\"Failed call for \" + str"));
}
}
.class public Ltrycatch/TestTryCatchStartOnMove;
.super Ljava/lang/Object;
# direct methods
.method private static test(Ljava/lang/String;)V
.registers 5
:try_start
move v3, p0
invoke-static {v3}, Ltrycatch/TestTryCatchStartOnMove;->call(Ljava/lang/String;)V
:try_end
.catch Ljava/lang/Exception; {:try_start .. :try_end} :catch
:goto_ret
return-void
:catch
move-exception v0
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
new-instance v1, Ljava/lang/StringBuilder;
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
const-string v2, "Failed call for "
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v1, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
goto :goto_ret
.end method
.method public constructor <init>()V
.registers 1
invoke-direct {p0}, Ljadx/tests/api/SmaliTest;-><init>()V
return-void
.end method
.method private static call(Ljava/lang/String;)V
.registers 1
return-void
.end method
plugins {
id 'edu.sc.seis.launch4j' version '2.4.4'
id 'com.github.johnrengelman.shadow' version '4.0.3'
id 'edu.sc.seis.launch4j' version '2.4.5'
id 'com.github.johnrengelman.shadow' version '5.0.0'
}
apply plugin: 'application'
......@@ -13,7 +13,8 @@ targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
compile(project(":jadx-core"))
compile(project(":jadx-cli"))
compile 'com.fifesoft:rsyntaxtextarea:3.0.0'
compile 'com.fifesoft:rsyntaxtextarea:3.0.2'
compile 'com.google.code.gson:gson:2.8.5'
compile files('libs/jfontchooser-1.0.5.jar')
compile 'hu.kazocsaba:image-viewer:1.2.3'
......@@ -21,9 +22,9 @@ dependencies {
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'
compile 'io.reactivex.rxjava2:rxjava:2.2.7'
compile "com.github.akarnokd:rxjava2-swing:0.3.4"
compile 'com.android.tools.build:apksig:3.3.2'
}
applicationDistribution.with {
......
package jadx.gui;
import javax.swing.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
import javax.swing.ProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -78,14 +81,13 @@ public class JadxWrapper {
*/
public List<JavaClass> getIncludedClasses() {
List<JavaClass> classList = decompiler.getClasses();
String excludedPackages = settings.getExcludedPackages().trim();
if (excludedPackages.length() == 0) {
List<String> excludedPackages = getExcludedPackages();
if (excludedPackages.isEmpty()) {
return classList;
}
String[] excluded = excludedPackages.split("[ ]+");
return classList.stream().filter(cls -> {
for (String exclude : excluded) {
for (String exclude : excludedPackages) {
if (cls.getFullName().startsWith(exclude)) {
return false;
}
......@@ -94,6 +96,23 @@ public class JadxWrapper {
}).collect(Collectors.toList());
}
public List<String> getExcludedPackages() {
String excludedPackages = settings.getExcludedPackages().trim();
return Arrays.asList(excludedPackages.split("[ ]+"));
}
public void addExcludedPackage(String packageToExclude) {
settings.setExcludedPackages(settings.getExcludedPackages() + ' ' + packageToExclude);
settings.sync();
}
public void removeExcludedPackage(String packageToRemoveFromExclusion) {
List<String> list = new ArrayList<>(getExcludedPackages());
list.remove(packageToRemoveFromExclusion);
settings.setExcludedPackages(String.join(" ", list));
settings.sync();
}
public List<JavaPackage> getPackages() {
return decompiler.getPackages();
}
......
......@@ -333,4 +333,5 @@ public class JadxSettings extends JadxCLIArgs {
settingsVersion = CURRENT_SETTINGS_VERSION;
sync();
}
}
......@@ -247,10 +247,14 @@ public class JadxSettingsWindow extends JDialog {
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
editExcludedPackages.addActionListener(event -> {
String oldExcludedPackages = settings.getExcludedPackages();
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
settings.getExcludedPackages());
if (result != null) {
settings.setExcludedPackages(result);
if (!oldExcludedPackages.equals(result)) {
needReload();
}
}
});
......
package jadx.gui.treemodel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.jetbrains.annotations.NotNull;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.Utils;
public class JPackage extends JNode implements Comparable<JPackage> {
......@@ -15,12 +18,16 @@ public class JPackage extends JNode implements Comparable<JPackage> {
private static final ImageIcon PACKAGE_ICON = Utils.openIcon("package_obj");
private final String fullName;
private String name;
private boolean enabled;
private final List<JClass> classes;
private final List<JPackage> innerPackages = new ArrayList<>(1);
public JPackage(JavaPackage pkg) {
public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
this.fullName = pkg.getName();
this.name = pkg.getName();
setEnabled(wrapper);
List<JavaClass> javaClasses = pkg.getClasses();
this.classes = new ArrayList<>(javaClasses.size());
for (JavaClass javaClass : javaClasses) {
......@@ -29,20 +36,30 @@ public class JPackage extends JNode implements Comparable<JPackage> {
update();
}
public JPackage(String name) {
public JPackage(String name, JadxWrapper wrapper) {
this.fullName = name;
this.name = name;
setEnabled(wrapper);
this.classes = new ArrayList<>(1);
}
private void setEnabled(JadxWrapper wrapper) {
List<String> excludedPackages = wrapper.getExcludedPackages();
this.enabled = excludedPackages.isEmpty()
|| excludedPackages.stream().filter(p -> !p.isEmpty()).noneMatch(p -> name.startsWith(p));
}
public final void update() {
removeAllChildren();
for (JPackage pkg : innerPackages) {
pkg.update();
add(pkg);
}
for (JClass cls : classes) {
cls.update();
add(cls);
if (isEnabled()) {
for (JPackage pkg : innerPackages) {
pkg.update();
add(pkg);
}
for (JClass cls : classes) {
cls.update();
add(cls);
}
}
}
......@@ -51,6 +68,10 @@ public class JPackage extends JNode implements Comparable<JPackage> {
return name;
}
public String getFullName() {
return fullName;
}
public void setName(String name) {
this.name = name;
}
......@@ -108,4 +129,8 @@ public class JPackage extends JNode implements Comparable<JPackage> {
public String makeLongString() {
return name;
}
public boolean isEnabled() {
return enabled;
}
}
......@@ -3,6 +3,7 @@ package jadx.gui.treemodel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
......@@ -68,6 +69,14 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
} else {
removeAllChildren();
Comparator<JResource> typeComparator
= (r1, r2) -> r1.type.ordinal() - r2.type.ordinal();
Comparator<JResource> nameComparator
= Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER);
files.sort(typeComparator.thenComparing(nameComparator));
for (JResource res : files) {
res.update();
add(res);
......
......@@ -33,7 +33,7 @@ public class JSources extends JNode {
removeAllChildren();
if (flatPackages) {
for (JavaPackage pkg : wrapper.getPackages()) {
add(new JPackage(pkg));
add(new JPackage(pkg, wrapper));
}
} else {
// build packages hierarchy
......@@ -54,7 +54,7 @@ public class JSources extends JNode {
List<JPackage> getHierarchyPackages(List<JavaPackage> packages) {
Map<String, JPackage> pkgMap = new HashMap<>();
for (JavaPackage pkg : packages) {
addPackage(pkgMap, new JPackage(pkg));
addPackage(pkgMap, new JPackage(pkg, wrapper));
}
// merge packages without classes
boolean repeat;
......@@ -114,7 +114,7 @@ public class JSources extends JNode {
pkg.setName(shortName);
JPackage prevPkg = pkgs.get(prevPart);
if (prevPkg == null) {
prevPkg = new JPackage(prevPart);
prevPkg = new JPackage(prevPart, wrapper);
addPackage(pkgs, prevPkg);
}
prevPkg.getInnerPackages().add(pkg);
......
......@@ -2,9 +2,11 @@ package jadx.gui.ui;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
import jadx.api.JadxDecompiler;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
class AboutDialog extends JDialog {
private static final long serialVersionUID = 5763493590584039096L;
......@@ -16,7 +18,10 @@ class AboutDialog extends JDialog {
public final void initUI() {
Font font = new Font("Serif", Font.BOLD, 13);
JLabel name = new JLabel("jadx");
URL logoURL = getClass().getResource("/logos/jadx-logo-48px.png");
Icon logo = new ImageIcon(logoURL, "jadx logo");
JLabel name = new JLabel("jadx", logo, SwingConstants.CENTER);
name.setFont(font);
name.setAlignmentX(0.5f);
......@@ -24,10 +29,24 @@ class AboutDialog extends JDialog {
desc.setFont(font);
desc.setAlignmentX(0.5f);
JLabel version = new JLabel("version: " + JadxDecompiler.getVersion());
JLabel version = new JLabel("jadx version: " + JadxDecompiler.getVersion());
version.setFont(font);
version.setAlignmentX(0.5f);
String javaVm = System.getProperty("java.vm.name");
String javaVer = System.getProperty("java.vm.version");
javaVm = javaVm == null ? "" : javaVm;
JLabel javaVmLabel = new JLabel("Java VM: " + javaVm);
javaVmLabel.setFont(font);
javaVmLabel.setAlignmentX(0.5f);
javaVer = javaVer == null ? "" : javaVer;
JLabel javaVerLabel = new JLabel("Java version: " + javaVer);
javaVerLabel.setFont(font);
javaVerLabel.setAlignmentX(0.5f);
JPanel textPane = new JPanel();
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS));
......@@ -38,6 +57,9 @@ class AboutDialog extends JDialog {
textPane.add(Box.createRigidArea(new Dimension(0, 10)));
textPane.add(version);
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
textPane.add(javaVmLabel);
textPane.add(javaVerLabel);
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
JButton close = new JButton(NLS.str("tabs.close"));
close.addActionListener(event -> dispose());
......@@ -47,6 +69,8 @@ class AboutDialog extends JDialog {
contentPane.add(textPane, BorderLayout.CENTER);
contentPane.add(close, BorderLayout.PAGE_END);
Utils.setWindowIcons(this);
setModalityType(ModalityType.APPLICATION_MODAL);
setTitle(NLS.str("about_dialog.title"));
......
package jadx.gui.ui;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import static javax.swing.KeyStroke.getKeyStroke;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
......@@ -23,12 +18,42 @@ 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;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ProgressMonitor;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,6 +71,7 @@ import jadx.gui.treemodel.JCertificate;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JLoadableNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JResource;
import jadx.gui.treemodel.JRoot;
import jadx.gui.update.JadxUpdate;
......@@ -57,8 +83,6 @@ import jadx.gui.utils.Link;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import static javax.swing.KeyStroke.getKeyStroke;
@SuppressWarnings("serial")
public class MainWindow extends JFrame {
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
......@@ -82,6 +106,7 @@ public class MainWindow extends JFrame {
private static final ImageIcon ICON_PREF = Utils.openIcon("wrench");
private static final ImageIcon ICON_DEOBF = Utils.openIcon("lock_edit");
private static final ImageIcon ICON_LOG = Utils.openIcon("report");
private static final ImageIcon ICON_JADX = Utils.openIcon("jadx-logo");
private final transient JadxWrapper wrapper;
private final transient JadxSettings settings;
......@@ -116,16 +141,7 @@ public class MainWindow extends JFrame {
registerBundledFonts();
initUI();
initMenuAndToolbar();
setWindowIcons();
}
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);
Utils.setWindowIcons(this);
loadSettings();
checkForUpdate();
}
......@@ -323,6 +339,14 @@ public class MainWindow extends JFrame {
}
}
private void treeRightClickAction(MouseEvent e) {
Object obj = tree.getLastSelectedPathComponent();
if (obj instanceof JPackage) {
JPackagePopUp menu = new JPackagePopUp((JPackage) obj);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
private void syncWithEditor() {
ContentPanel selectedContentPanel = tabbedPane.getSelectedCodePanel();
if (selectedContentPanel == null) {
......@@ -462,7 +486,7 @@ public class MainWindow extends JFrame {
logAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_L,
KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
Action aboutAction = new AbstractAction(NLS.str("menu.about")) {
Action aboutAction = new AbstractAction(NLS.str("menu.about"), ICON_JADX) {
@Override
public void actionPerformed(ActionEvent e) {
new AboutDialog().setVisible(true);
......@@ -582,7 +606,12 @@ public class MainWindow extends JFrame {
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
treeClickAction();
if (SwingUtilities.isRightMouseButton(e)) {
treeRightClickAction(e);
}
else {
treeClickAction();
}
}
});
tree.addKeyListener(new KeyAdapter() {
......@@ -602,6 +631,9 @@ public class MainWindow extends JFrame {
if (value instanceof JNode) {
setIcon(((JNode) value).getIcon());
}
if (value instanceof JPackage) {
setEnabled(((JPackage) value).isEnabled());
}
return c;
}
});
......@@ -748,4 +780,23 @@ public class MainWindow extends JFrame {
public void menuCanceled(MenuEvent e) {
}
}
private class JPackagePopUp extends JPopupMenu {
JMenuItem excludeItem = new JCheckBoxMenuItem("Exclude");
public JPackagePopUp(JPackage pkg) {
excludeItem.setSelected(!pkg.isEnabled());
add(excludeItem);
excludeItem.addItemListener(e -> {
String fullName = pkg.getFullName();
if (excludeItem.isSelected()) {
wrapper.addExcludedPackage(fullName);
}
else {
wrapper.removeExcludedPackage(fullName);
}
reOpenFile();
});
}
}
}
......@@ -7,6 +7,8 @@ import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
......@@ -191,4 +193,13 @@ public class Utils {
}
return sb.toString().trim();
}
public static void setWindowIcons(Window window) {
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"));
window.setIconImages(icons);
}
}
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 252.23 252"><defs><style>.cls-1,.cls-14{fill:none;}.cls-2{fill:#231f20;}.cls-3{fill:#461bbc;}.cls-4{clip-path:url(#clip-path);}.cls-5{clip-path:url(#clip-path-2);}.cls-6{fill:#008275;}.cls-7{font-size:132.44px;}.cls-12,.cls-7{fill:#fff;font-family:FuturaPT-Heavy, Futura PT;}.cls-8{letter-spacing:-0.03em;}.cls-9{clip-path:url(#clip-path-3);}.cls-10{fill:#f15a29;}.cls-11{fill:#9e1f63;}.cls-12{font-size:134.04px;letter-spacing:-0.01em;}.cls-13{letter-spacing:0em;}.cls-14{stroke:#fff;stroke-miterlimit:10;stroke-width:8.91px;}</style><clipPath id="clip-path" transform="translate(0.23)"><circle class="cls-1" cx="126" cy="126" r="125.72"/></clipPath><clipPath id="clip-path-2" transform="translate(0.23)"><polygon class="cls-1" points="45.29 223.57 176.25 75.9 258.09 69.98 258.09 242.47 45.29 242.47 45.29 223.57"/></clipPath><clipPath id="clip-path-3" transform="translate(0.23)"><polygon class="cls-1" points="57.93 195.53 179.7 63.23 69.69 5.58 3.53 19.58 -15.33 132.35 57.93 195.53"/></clipPath></defs><title>jadxlogo</title><circle class="cls-2" cx="126.23" cy="126" r="126"/><path class="cls-3" d="M122.66-68.19" transform="translate(0.23)"/><g class="cls-4"><g class="cls-5"><polygon class="cls-3" points="198.71 223.57 37.48 223.57 142.14 105.88 198.71 223.57"/><polygon class="cls-6" points="170.52 74.83 238.84 87.48 247.15 198.65 190.4 207.09 142.14 105.88 170.52 74.83"/><text class="cls-7" transform="translate(82.68 208.04) scale(0.95 1)"><tspan class="cls-8">D</tspan><tspan x="86.48" y="0">X</tspan></text></g><g class="cls-9"><polyline class="cls-10" points="73.68 193.85 0 126.28 140.52 53.49 171.12 88.7"/><polyline class="cls-11" points="30.86 19.58 30.86 110.86 129.83 59.59 160.42 94.8 181.86 67.19"/><text class="cls-12" transform="translate(10.71 121.84) scale(1.01 1)">J<tspan class="cls-13" x="54.29" y="0">A</tspan></text></g></g><circle class="cls-14" cx="126.08" cy="125.86" r="120.86"/></svg>
\ No newline at end of file
#!/usr/bin/env bash
set -xe
set -e
export JFROG_CLI_OFFER_CONFIG=false
export JFROG_CLI_LOG_LEVEL=DEBUG
npm install -g jfrog-cli-go
TARGET=skylot/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION}
CREDENTIALS="--user=skylot --key=${BINTRAY_KEY}"
TARGET=${BINTRAY_USER}/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION}
CREDENTIALS="--user=${BINTRAY_USER} --key=${BINTRAY_KEY}"
jfrog bt version-create ${TARGET} ${CREDENTIALS} --desc=${JADX_VERSION}
jfrog bt upload 'build/jadx.*\.(zip|exe)' ${TARGET} ${CREDENTIALS} --regexp=true --publish=true
......
#!/usr/bin/env bash
set -xe
set -e
# upload coverage to codecov
./gradlew clean build jacocoTestReport
......
#!/usr/bin/env bash
set -xe
set -e
npm install -g semantic-release
npm install -g semantic-release/exec
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment