diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 0a33e823e..65ff881dd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -249,6 +250,11 @@ public final class ClassInfo implements Comparable { splitAndApplyNames(root, type, false); } + public void convertToInner(ClassNode parent) { + this.parentClass = parent.getClassInfo(); + splitAndApplyNames(parent.root(), type, true); + } + public void updateNames(RootNode root) { splitAndApplyNames(root, type, isInner()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 337e4978b..aac5e64ae 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -179,7 +179,16 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) { - ClassNode cls = new ClassNode(root, name, accessFlags); + ClassInfo clsInfo = ClassInfo.fromName(root, name); + ClassNode existCls = root.resolveClass(clsInfo); + if (existCls != null) { + throw new JadxRuntimeException("Class already exist: " + name); + } + return addSyntheticClass(root, clsInfo, accessFlags); + } + + public static ClassNode addSyntheticClass(RootNode root, ClassInfo clsInfo, int accessFlags) { + ClassNode cls = new ClassNode(root, clsInfo, accessFlags); cls.add(AFlag.SYNTHETIC); cls.setState(ProcessState.PROCESS_COMPLETE); root.addClassNode(cls); @@ -187,10 +196,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } // Create empty class - private ClassNode(RootNode root, String name, int accessFlags) { + private ClassNode(RootNode root, ClassInfo clsInfo, int accessFlags) { this.root = root; this.clsData = null; - this.clsInfo = ClassInfo.fromName(root, name); + this.clsInfo = clsInfo; this.interfaces = new ArrayList<>(); this.methods = new ArrayList<>(); this.fields = new ArrayList<>(); diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index 96a51d384..48d52db02 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -23,7 +23,6 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.ProcessState; import jadx.core.dex.nodes.RootNode; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; @@ -40,7 +39,8 @@ public class AndroidResourcesUtils { public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) { String appPackage = root.getAppPackage(); String fullName = appPackage != null ? appPackage + ".R" : "R"; - ClassNode resCls = root.resolveClass(fullName); + ClassInfo clsInfo = ClassInfo.fromName(root, fullName); + ClassNode resCls = root.resolveClass(clsInfo); if (resCls != null) { addResourceFields(resCls, resStorage, true); return resCls; @@ -48,17 +48,18 @@ public class AndroidResourcesUtils { LOG.info("Can't find 'R' class in app package: {}", appPackage); List candidates = root.searchClassByShortName("R"); if (candidates.size() == 1) { - resCls = candidates.get(0); - addResourceFields(resCls, resStorage, true); - return resCls; + ClassNode resClsCandidate = candidates.get(0); + addResourceFields(resClsCandidate, resStorage, true); + return resClsCandidate; } if (!candidates.isEmpty()) { LOG.info("Found several 'R' class candidates: {}", candidates); } LOG.info("App 'R' class not found, put all resources ids into : '{}'", fullName); - ClassNode newResCls = makeClass(root, fullName, resStorage); - addResourceFields(newResCls, resStorage, false); - return newResCls; + ClassNode rCls = ClassNode.addSyntheticClass(root, clsInfo, AccessFlags.PUBLIC | AccessFlags.FINAL); + rCls.addInfoComment("This class is generated by JADX"); + addResourceFields(rCls, resStorage, false); + return rCls; } public static boolean handleAppResField(ICodeWriter code, ClassGen clsGen, ClassInfo declClass) { @@ -88,42 +89,52 @@ public class AndroidResourcesUtils { return parentClass != null && parentClass.getShortName().equals("R"); } - private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) { - ClassNode rCls = ClassNode.addSyntheticClass(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); - rCls.addInfoComment("This class is generated by JADX"); - rCls.setState(ProcessState.PROCESS_COMPLETE); - return rCls; + private static final class ResClsInfo { + private final ClassNode typeCls; + private final Map fieldsMap = new HashMap<>(); + + private ResClsInfo(ClassNode typeCls) { + this.typeCls = typeCls; + } + + public ClassNode getTypeCls() { + return typeCls; + } + + public Map getFieldsMap() { + return fieldsMap; + } } private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) { Map resFieldsMap = fillResFieldsMap(resCls); - Map innerClsMap = new TreeMap<>(); + Map innerClsMap = new TreeMap<>(); if (rClsExists) { for (ClassNode innerClass : resCls.getInnerClasses()) { - innerClsMap.put(innerClass.getShortName(), innerClass); + ResClsInfo innerResCls = new ResClsInfo(innerClass); + innerClass.getFields().forEach(field -> innerResCls.getFieldsMap().put(field.getName(), field)); + innerClsMap.put(innerClass.getShortName(), innerResCls); } } for (ResourceEntry resource : resStorage.getResources()) { - final String resTypeName = resource.getTypeName(); - ClassNode typeCls = innerClsMap.computeIfAbsent( + String resTypeName = resource.getTypeName(); + String resName = resTypeName.equals("style") ? resource.getKeyName().replace('.', '_') : resource.getKeyName(); + + ResClsInfo typeClsInfo = 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) { + name -> getClassForResType(resCls, rClsExists, name)); + typeClsInfo.getFieldsMap().computeIfAbsent(resName, name -> { + ClassNode typeCls = typeClsInfo.getTypeCls(); FieldInfo rFieldInfo = FieldInfo.from(typeCls.root(), typeCls.getClassInfo(), resName, ArgType.INT); - rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); - rField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId())); - typeCls.getFields().add(rField); + FieldNode newResField = new FieldNode(typeCls, rFieldInfo, + AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); + newResField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId())); + typeCls.getFields().add(newResField); if (rClsExists) { - rField.addInfoComment("Added by JADX"); + newResField.addInfoComment("Added by JADX"); } - } + return newResField; + }); FieldNode fieldNode = resFieldsMap.get(resource.getId()); if (fieldNode != null && !fieldNode.getName().equals(resName) @@ -136,14 +147,27 @@ public class AndroidResourcesUtils { } @NotNull - private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { - ClassNode newTypeCls = ClassNode.addSyntheticClass(resCls.root(), resCls.getFullName() + '$' + typeName, + private static ResClsInfo getClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { + String clsFullName = resCls.getFullName() + '$' + typeName; + ClassInfo clsInfo = ClassInfo.fromName(resCls.root(), clsFullName); + ClassNode existCls = resCls.root().resolveClass(clsInfo); + if (existCls != null) { + if (!rClsExists && !existCls.isInner()) { + // convert found res cls to inner for R class + existCls.getClassInfo().convertToInner(resCls); + resCls.addInnerClass(existCls); + } + ResClsInfo resClsInfo = new ResClsInfo(existCls); + existCls.getFields().forEach(field -> resClsInfo.getFieldsMap().put(field.getName(), field)); + return resClsInfo; + } + ClassNode newTypeCls = ClassNode.addSyntheticClass(resCls.root(), clsInfo, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); resCls.addInnerClass(newTypeCls); if (rClsExists) { newTypeCls.addInfoComment("Added by JADX"); } - return newTypeCls; + return new ResClsInfo(newTypeCls); } @NotNull