fix: use text file for store android resource mapping (#1020)
Signed-off-by: Skylot <skylot@gmail.com>
This commit is contained in:
@@ -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.
Reference in New Issue
Block a user