Commit aa691af6 authored by Skylot's avatar Skylot

core: replace resources ids with names from '.arsc' file

parent e0ffb018
...@@ -253,6 +253,8 @@ public final class JadxDecompiler { ...@@ -253,6 +253,8 @@ public final class JadxDecompiler {
root = new RootNode(); root = new RootNode();
LOG.info("loading ..."); LOG.info("loading ...");
root.load(inputFiles); root.load(inputFiles);
root.loadResources(getResources());
root.initAppResClass();
} }
void processClass(ClassNode cls) { void processClass(ClassNode cls) {
......
...@@ -49,13 +49,17 @@ public class ResourceFile { ...@@ -49,13 +49,17 @@ public class ResourceFile {
} }
public CodeWriter getContent() { public CodeWriter getContent() {
return ResourcesLoader.loadContent(decompiler, zipRef, type); return ResourcesLoader.loadContent(decompiler, this);
} }
void setZipRef(ZipRef zipRef) { void setZipRef(ZipRef zipRef) {
this.zipRef = zipRef; this.zipRef = zipRef;
} }
ZipRef getZipRef() {
return zipRef;
}
@Override @Override
public String toString() { public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}"; return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
......
...@@ -41,7 +41,12 @@ public final class ResourcesLoader { ...@@ -41,7 +41,12 @@ public final class ResourcesLoader {
return list; return list;
} }
static CodeWriter loadContent(JadxDecompiler jadxRef, ZipRef zipRef, ResourceType type) { public static interface ResourceDecoder {
Object decode(long size, InputStream is) throws IOException;
}
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) { if (zipRef == null) {
return null; return null;
} }
...@@ -50,18 +55,13 @@ public final class ResourcesLoader { ...@@ -50,18 +55,13 @@ public final class ResourcesLoader {
try { try {
zipFile = new ZipFile(zipRef.getZipFile()); zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName()); ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry != null) { if (entry == null) {
if (entry.getSize() > LOAD_SIZE_LIMIT) { throw new IOException("Zip entry not found: " + zipRef);
return new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", entry.getSize() / 1024.));
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
return decode(jadxRef, type, inputStream);
} else {
LOG.warn("Zip entry not found: {}", zipRef);
} }
} catch (IOException e) { inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
LOG.error("Error load: " + zipRef, e); return decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error load: " + zipRef, e);
} finally { } finally {
try { try {
if (zipFile != null) { if (zipFile != null) {
...@@ -74,10 +74,27 @@ public final class ResourcesLoader { ...@@ -74,10 +74,27 @@ public final class ResourcesLoader {
LOG.debug("Error close zip file: " + zipRef, e); LOG.debug("Error close zip file: " + zipRef, e);
} }
} }
}
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
try {
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
if (size > LOAD_SIZE_LIMIT) {
return new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", size / 1024.));
}
return loadContent(jadxRef, rf.getType(), is);
}
});
} catch (JadxException e) {
LOG.error("Decode error", e);
}
return null; return null;
} }
private static CodeWriter decode(JadxDecompiler jadxRef, ResourceType type, private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
InputStream inputStream) throws IOException { InputStream inputStream) throws IOException {
switch (type) { switch (type) {
case MANIFEST: case MANIFEST:
...@@ -103,7 +120,7 @@ public final class ResourcesLoader { ...@@ -103,7 +120,7 @@ public final class ResourcesLoader {
addEntry(list, file, entry); addEntry(list, file, entry);
} }
} catch (IOException e) { } catch (IOException e) {
LOG.debug("Not a zip file: " + file.getAbsolutePath()); LOG.debug("Not a zip file: {}", file.getAbsolutePath());
} finally { } finally {
if (zip != null) { if (zip != null) {
try { try {
......
...@@ -35,6 +35,7 @@ import com.android.dex.ClassData; ...@@ -35,6 +35,7 @@ import com.android.dex.ClassData;
import com.android.dex.ClassData.Field; import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method; import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef; import com.android.dex.ClassDef;
import com.android.dx.rop.code.AccessFlags;
public class ClassNode extends LineAttrNode implements ILoadable { public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
...@@ -126,6 +127,17 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -126,6 +127,17 @@ public class ClassNode extends LineAttrNode implements ILoadable {
} }
} }
// empty synthetic class
public ClassNode(DexNode dex, ClassInfo clsInfo) {
this.dex = dex;
this.clsInfo = clsInfo;
this.interfaces = Collections.emptyList();
this.methods = Collections.emptyList();
this.fields = Collections.emptyList();
this.accessFlags = new AccessInfo(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_SYNTHETIC, AFType.CLASS);
this.parentClass = this;
}
private void loadAnnotations(ClassDef cls) { private void loadAnnotations(ClassDef cls) {
int offset = cls.getAnnotationsOffset(); int offset = cls.getAnnotationsOffset();
if (offset != 0) { if (offset != 0) {
...@@ -266,6 +278,12 @@ public class ClassNode extends LineAttrNode implements ILoadable { ...@@ -266,6 +278,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
if (field == null && searchGlobal) { if (field == null && searchGlobal) {
field = dex.getConstFields().get(obj); field = dex.getConstFields().get(obj);
} }
if (field == null && obj instanceof Integer) {
String str = dex.root().getResourcesNames().get(obj);
if (str != null) {
return new ResRefField(dex, str);
}
}
return field; return field;
} }
......
...@@ -23,6 +23,13 @@ public class FieldNode extends LineAttrNode { ...@@ -23,6 +23,13 @@ public class FieldNode extends LineAttrNode {
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD); this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
} }
public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) {
this.parent = cls;
this.fieldInfo = fieldInfo;
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
}
public FieldInfo getFieldInfo() { public FieldInfo getFieldInfo() {
return fieldInfo; return fieldInfo;
} }
......
package jadx.core.dex.nodes;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import com.android.dx.rop.code.AccessFlags;
public class ResRefField extends FieldNode {
public ResRefField(DexNode dex, String str) {
super(dex.root().getAppResClass(),
new FieldInfo(dex.root().getAppResClass().getClassInfo(), str, ArgType.INT),
AccessFlags.ACC_PUBLIC);
}
}
package jadx.core.dex.nodes; package jadx.core.dex.nodes;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile; import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RootNode { public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>(); private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final ErrorsCounter errorsCounter = new ErrorsCounter();
private List<DexNode> dexNodes; private List<DexNode> dexNodes;
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
@Nullable
private String appPackage;
private ClassNode appResClass;
public void load(List<InputFile> dexFiles) throws DecodeException { public void load(List<InputFile> dexFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size()); dexNodes = new ArrayList<DexNode>(dexFiles.size());
...@@ -49,6 +68,58 @@ public class RootNode { ...@@ -49,6 +68,58 @@ public class RootNode {
initInnerClasses(classes); initInnerClasses(classes);
} }
public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = null;
for (ResourceFile rf : resources) {
if (rf.getType() == ResourceType.ARSC) {
arsc = rf;
break;
}
}
if (arsc == null) {
LOG.debug("'.arsc' file not found");
return;
}
final ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
parser.decode(is);
return null;
}
});
} catch (JadxException e) {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
ResourceStorage resStorage = parser.getResStorage();
appPackage = resStorage.getAppPackage();
for (ResourceEntry entry : resStorage.getResources()) {
resourcesNames.put(entry.getId(), entry.getTypeName() + "." + entry.getKeyName());
}
}
public void initAppResClass() {
ClassNode resCls = null;
if (appPackage != null) {
resCls = searchClassByName(appPackage + ".R");
} else {
for (ClassNode cls : names.values()) {
if (cls.getShortName().equals("R")) {
resCls = cls;
break;
}
}
}
if (resCls != null) {
appResClass = resCls;
return;
}
appResClass = new ClassNode(dexNodes.get(0), ClassInfo.fromName("R"));
}
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException { private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
if (!ArgType.isClspSet()) { if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph(); ClspGraph clsp = new ClspGraph();
...@@ -111,4 +182,17 @@ public class RootNode { ...@@ -111,4 +182,17 @@ public class RootNode {
public ErrorsCounter getErrorsCounter() { public ErrorsCounter getErrorsCounter() {
return errorsCounter; return errorsCounter;
} }
public Map<Integer, String> getResourcesNames() {
return resourcesNames;
}
@Nullable
public String getAppPackage() {
return appPackage;
}
public ClassNode getAppResClass() {
return appResClass;
}
} }
...@@ -132,6 +132,9 @@ public class ResTableParser extends CommonBinaryParser { ...@@ -132,6 +132,9 @@ public class ResTableParser extends CommonBinaryParser {
} }
PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings); PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings);
if (id == 0x7F) {
resStorage.setAppPackage(name);
}
while (is.getPos() < endPos) { while (is.getPos() < endPos) {
long chunkStart = is.getPos(); long chunkStart = is.getPos();
......
...@@ -19,6 +19,7 @@ public class ResourceStorage { ...@@ -19,6 +19,7 @@ public class ResourceStorage {
}; };
private final List<ResourceEntry> list = new ArrayList<ResourceEntry>(); private final List<ResourceEntry> list = new ArrayList<ResourceEntry>();
private String appPackage;
public Collection<ResourceEntry> getResources() { public Collection<ResourceEntry> getResources() {
return list; return list;
...@@ -40,4 +41,12 @@ public class ResourceStorage { ...@@ -40,4 +41,12 @@ public class ResourceStorage {
} }
return list.get(index); return list.get(index);
} }
public String getAppPackage() {
return appPackage;
}
public void setAppPackage(String appPackage) {
this.appPackage = appPackage;
}
} }
...@@ -8,6 +8,7 @@ import jadx.core.dex.attributes.AFlag; ...@@ -8,6 +8,7 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
...@@ -26,8 +27,10 @@ import java.net.URISyntaxException; ...@@ -26,8 +27,10 @@ import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
...@@ -47,9 +50,10 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -47,9 +50,10 @@ public abstract class IntegrationTest extends TestUtils {
protected boolean outputCFG = false; protected boolean outputCFG = false;
protected boolean isFallback = false; protected boolean isFallback = false;
protected boolean deleteTmpFiles = true; protected boolean deleteTmpFiles = true;
protected boolean withDebugInfo = true; protected boolean withDebugInfo = true;
protected Map<Integer, String> resMap = Collections.emptyMap();
protected String outDir = "test-out-tmp"; protected String outDir = "test-out-tmp";
protected boolean compile = true; protected boolean compile = true;
...@@ -72,7 +76,10 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -72,7 +76,10 @@ public abstract class IntegrationTest extends TestUtils {
} catch (JadxException e) { } catch (JadxException e) {
fail(e.getMessage()); fail(e.getMessage());
} }
ClassNode cls = JadxInternalAccess.getRoot(d).searchClassByName(clsName); RootNode root = JadxInternalAccess.getRoot(d);
root.getResourcesNames().putAll(resMap);
ClassNode cls = root.searchClassByName(clsName);
assertNotNull("Class not found: " + clsName, cls); assertNotNull("Class not found: " + clsName, cls);
assertEquals(cls.getFullName(), clsName); assertEquals(cls.getFullName(), clsName);
...@@ -340,6 +347,10 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -340,6 +347,10 @@ public abstract class IntegrationTest extends TestUtils {
return files; return files;
} }
public void setResMap(Map<Integer, String> resMap) {
this.resMap = resMap;
}
protected void noDebugInfo() { protected void noDebugInfo() {
this.withDebugInfo = false; this.withDebugInfo = false;
} }
......
package jadx.tests.integration.inner;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestRFieldRestore extends IntegrationTest {
public static class TestCls {
public int test() {
return 2131230730;
}
}
@Test
public void test() {
// unknown R class
disableCompilation();
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(2131230730, "id.Button");
setResMap(map);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("return R.id.Button;"));
}
}
package jadx.tests.integration.inner;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestRFieldRestore2 extends IntegrationTest {
public static class TestCls {
public static class R {
}
public int test() {
return 2131230730;
}
}
@Test
public void test() {
// unknown id.Button
disableCompilation();
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(2131230730, "id.Button");
setResMap(map);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("return R.id.Button;"));
}
}
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