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 {
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
root.loadResources(getResources());
root.initAppResClass();
}
void processClass(ClassNode cls) {
......
......@@ -49,13 +49,17 @@ public class ResourceFile {
}
public CodeWriter getContent() {
return ResourcesLoader.loadContent(decompiler, zipRef, type);
return ResourcesLoader.loadContent(decompiler, this);
}
void setZipRef(ZipRef zipRef) {
this.zipRef = zipRef;
}
ZipRef getZipRef() {
return zipRef;
}
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
......
......@@ -41,7 +41,12 @@ public final class ResourcesLoader {
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) {
return null;
}
......@@ -50,18 +55,13 @@ public final class ResourcesLoader {
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry != null) {
if (entry.getSize() > LOAD_SIZE_LIMIT) {
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);
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
} catch (IOException e) {
LOG.error("Error load: " + zipRef, e);
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
return decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error load: " + zipRef, e);
} finally {
try {
if (zipFile != null) {
......@@ -74,10 +74,27 @@ public final class ResourcesLoader {
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;
}
private static CodeWriter decode(JadxDecompiler jadxRef, ResourceType type,
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
InputStream inputStream) throws IOException {
switch (type) {
case MANIFEST:
......@@ -103,7 +120,7 @@ public final class ResourcesLoader {
addEntry(list, file, entry);
}
} catch (IOException e) {
LOG.debug("Not a zip file: " + file.getAbsolutePath());
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
} finally {
if (zip != null) {
try {
......
......@@ -35,6 +35,7 @@ import com.android.dex.ClassData;
import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dx.rop.code.AccessFlags;
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
......@@ -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) {
int offset = cls.getAnnotationsOffset();
if (offset != 0) {
......@@ -266,6 +278,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
if (field == null && searchGlobal) {
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;
}
......
......@@ -23,6 +23,13 @@ public class FieldNode extends LineAttrNode {
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() {
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;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
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.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private final ErrorsCounter errorsCounter = new ErrorsCounter();
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 {
dexNodes = new ArrayList<DexNode>(dexFiles.size());
......@@ -49,6 +68,58 @@ public class RootNode {
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 {
if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph();
......@@ -111,4 +182,17 @@ public class RootNode {
public ErrorsCounter getErrorsCounter() {
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 {
}
PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings);
if (id == 0x7F) {
resStorage.setAppPackage(name);
}
while (is.getPos() < endPos) {
long chunkStart = is.getPos();
......
......@@ -19,6 +19,7 @@ public class ResourceStorage {
};
private final List<ResourceEntry> list = new ArrayList<ResourceEntry>();
private String appPackage;
public Collection<ResourceEntry> getResources() {
return list;
......@@ -40,4 +41,12 @@ public class ResourceStorage {
}
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;
import jadx.core.dex.attributes.AType;
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.JadxException;
......@@ -26,8 +27,10 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.JarOutputStream;
import static org.hamcrest.CoreMatchers.containsString;
......@@ -47,9 +50,10 @@ public abstract class IntegrationTest extends TestUtils {
protected boolean outputCFG = false;
protected boolean isFallback = false;
protected boolean deleteTmpFiles = true;
protected boolean withDebugInfo = true;
protected Map<Integer, String> resMap = Collections.emptyMap();
protected String outDir = "test-out-tmp";
protected boolean compile = true;
......@@ -72,7 +76,10 @@ public abstract class IntegrationTest extends TestUtils {
} catch (JadxException e) {
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);
assertEquals(cls.getFullName(), clsName);
......@@ -340,6 +347,10 @@ public abstract class IntegrationTest extends TestUtils {
return files;
}
public void setResMap(Map<Integer, String> resMap) {
this.resMap = resMap;
}
protected void noDebugInfo() {
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