fix: check if inner classes for missing R class already exist (#1269)
This commit is contained in:
@@ -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<ClassInfo> {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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<ClassNode> 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<String, FieldNode> fieldsMap = new HashMap<>();
|
||||
|
||||
private ResClsInfo(ClassNode typeCls) {
|
||||
this.typeCls = typeCls;
|
||||
}
|
||||
|
||||
public ClassNode getTypeCls() {
|
||||
return typeCls;
|
||||
}
|
||||
|
||||
public Map<String, FieldNode> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) {
|
||||
Map<Integer, FieldNode> resFieldsMap = fillResFieldsMap(resCls);
|
||||
Map<String, ClassNode> innerClsMap = new TreeMap<>();
|
||||
Map<String, ResClsInfo> 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
|
||||
|
||||
Reference in New Issue
Block a user