fix: use text file for store android resource mapping (#1020)

Signed-off-by: Skylot <skylot@gmail.com>
This commit is contained in:
Skylot
2020-11-19 10:22:52 +00:00
parent 71aa29cc71
commit d3f5154c19
8 changed files with 12056 additions and 20 deletions
@@ -0,0 +1,80 @@
package jadx.cli.tools;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.xmlgen.ResTableParser;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
*/
public class ConvertArscFile {
private static final Logger LOG = LoggerFactory.getLogger(ConvertArscFile.class);
private static int rewritesCount;
public static void usage() {
LOG.info("<res-map file> <input .arsc files>");
LOG.info("");
LOG.info("Note: If res-map already exists - it will be merged and updated");
}
public static void main(String[] args) throws IOException {
if (args.length < 2) {
usage();
System.exit(1);
}
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path resMapFile = inputPaths.remove(0);
Map<Integer, String> resMap;
if (Files.isReadable(resMapFile)) {
resMap = TextResMapFile.read(resMapFile);
} else {
resMap = new HashMap<>();
}
LOG.info("Input entries count: {}", resMap.size());
RootNode root = new RootNode(new JadxArgs()); // not really needed
rewritesCount = 0;
for (Path resFile : inputPaths) {
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(resFile))) {
ResTableParser resTableParser = new ResTableParser(root);
resTableParser.decode(inputStream);
Map<Integer, String> singleResMap = resTableParser.getResStorage().getResourcesNames();
mergeResMaps(resMap, singleResMap);
LOG.info("{} entries count: {}, after merge: {}", resFile.getFileName(), singleResMap.size(), resMap.size());
}
}
LOG.info("Output entries count: {}", resMap.size());
LOG.info("Total rewrites count: {}", rewritesCount);
TextResMapFile.write(resMapFile, resMap);
LOG.info("Result file size: {} B", resMapFile.toFile().length());
LOG.info("done");
}
private static void mergeResMaps(Map<Integer, String> mainResMap, Map<Integer, String> newResMap) {
for (Map.Entry<Integer, String> entry : newResMap.entrySet()) {
Integer id = entry.getKey();
String name = entry.getValue();
String prevName = mainResMap.put(id, name);
if (prevName != null && !name.equals(prevName)) {
LOG.debug("Rewrite id: {} from: '{}' to: '{}'", Integer.toHexString(id), prevName, name);
rewritesCount++;
}
}
}
}
@@ -173,7 +173,7 @@ public class RootNode {
long start = System.currentTimeMillis();
int renamedCount = 0;
ResourceStorage resStorage = parser.getResStorage();
ValuesParser valuesParser = new ValuesParser(this, parser.getStrings(), resStorage.getResourcesNames());
ValuesParser valuesParser = new ValuesParser(parser.getStrings(), resStorage.getResourcesNames());
Map<String, ResourceEntry> entryNames = new HashMap<>();
for (ResourceEntry resEntry : resStorage.getResources()) {
String val = valuesParser.getSimpleValueString(resEntry);
@@ -0,0 +1,42 @@
package jadx.core.utils.android;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TextResMapFile {
private static final int SPLIT_POS = 8;
public static Map<Integer, String> read(Path resMapFile) {
try {
Map<Integer, String> resMap = new HashMap<>();
for (String line : Files.readAllLines(resMapFile)) {
int id = Integer.parseInt(line.substring(0, SPLIT_POS), 16);
String name = line.substring(SPLIT_POS + 1);
resMap.put(id, name);
}
return resMap;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to read res-map file", e);
}
}
public static void write(Path resMapFile, Map<Integer, String> inputResMap) {
try {
Map<Integer, String> resMap = new TreeMap<>(inputResMap);
List<String> lines = new ArrayList<>(resMap.size());
for (Map.Entry<Integer, String> entry : resMap.entrySet()) {
lines.add(String.format("%08x=%s", entry.getKey(), entry.getValue()));
}
Files.write(resMapFile, lines);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to write res-map file", e);
}
}
}
@@ -117,7 +117,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
break;
case RES_STRING_POOL_TYPE:
strings = parseStringPoolNoType();
valuesParser = new ValuesParser(rootNode, strings, resNames);
valuesParser = new ValuesParser(strings, resNames);
break;
case RES_XML_RESOURCE_MAP_TYPE:
parseResourceMap();
@@ -73,7 +73,7 @@ public class ResTableParser extends CommonBinaryParser {
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
decode(inputStream);
ValuesParser vp = new ValuesParser(root, strings, resStorage.getResourcesNames());
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ICodeInfo content = makeXmlDump();
@@ -1,8 +1,7 @@
package jadx.core.xmlgen.entry;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
@@ -13,9 +12,9 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ParserConstants;
import jadx.core.xmlgen.ResTableParser;
public class ValuesParser extends ParserConstants {
private static final Logger LOG = LoggerFactory.getLogger(ValuesParser.class);
@@ -25,25 +24,21 @@ public class ValuesParser extends ParserConstants {
private final String[] strings;
private final Map<Integer, String> resMap;
public ValuesParser(RootNode root, String[] strings, Map<Integer, String> resMap) {
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
this.strings = strings;
this.resMap = resMap;
if (androidResMap == null) {
try {
decodeAndroid(root);
} catch (Exception e) {
LOG.error("Failed to decode Android Resource file", e);
}
androidResMap = loadAndroidResMap();
}
}
// TODO: store only needed data instead full resources.arsc file
private static void decodeAndroid(RootNode root) throws IOException {
try (InputStream inputStream = new BufferedInputStream(ValuesParser.class.getResourceAsStream("/resources.arsc"))) {
ResTableParser androidParser = new ResTableParser(root);
androidParser.decode(inputStream);
androidResMap = androidParser.getResStorage().getResourcesNames();
private static Map<Integer, String> loadAndroidResMap() {
try {
URL resMapUrl = ValuesParser.class.getResource("/android/res-map.txt");
return TextResMapFile.read(Paths.get(resMapUrl.toURI()));
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load android resource file", e);
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.