Commit ef8a6856 authored by Skylot's avatar Skylot

resources: initial version of .arsc file decode

parent e4fef402
......@@ -3,7 +3,6 @@ package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
......@@ -15,6 +14,7 @@ import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
import java.io.File;
import java.io.IOException;
......@@ -150,7 +150,7 @@ public final class JadxDecompiler {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
private ExecutorService getSaveExecutor(boolean saveSources, final boolean saveResources) {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
......@@ -172,17 +172,7 @@ public final class JadxDecompiler {
}
if (saveResources) {
for (final ResourceFile resourceFile : getResources()) {
executor.execute(new Runnable() {
@Override
public void run() {
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
CodeWriter cw = resourceFile.getContent();
if (cw != null) {
cw.save(new File(outDir, resourceFile.getName()));
}
}
}
});
executor.execute(new ResourcesSaver(outDir, resourceFile));
}
}
return executor;
......@@ -294,7 +284,7 @@ public final class JadxDecompiler {
return root;
}
BinaryXMLParser getXmlParser() {
synchronized BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
......@@ -321,4 +311,5 @@ public final class JadxDecompiler {
public String toString() {
return "jadx decompiler " + getVersion();
}
}
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
import java.io.File;
......@@ -48,7 +48,7 @@ public class ResourceFile {
return type;
}
public CodeWriter getContent() {
public ResContainer getContent() {
return ResourcesLoader.loadContent(decompiler, this);
}
......
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
super(null, name, type);
this.content = content;
}
@Override
public ResContainer getContent() {
return ResContainer.singleFile(getName(), content);
}
}
......@@ -34,7 +34,6 @@ public enum ResourceType {
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case ARSC:
case LIB:
case FONT:
case IMG:
......@@ -43,6 +42,7 @@ public enum ResourceType {
case MANIFEST:
case XML:
case ARSC:
return true;
}
return false;
......
......@@ -5,6 +5,7 @@ import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import java.io.BufferedInputStream;
......@@ -43,17 +44,17 @@ public final class ResourcesLoader {
}
public interface ResourceDecoder {
Object decode(long size, InputStream is) throws IOException;
ResContainer decode(long size, InputStream is) throws IOException;
}
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
Object result = null;
ResContainer result = null;
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
......@@ -79,16 +80,17 @@ public final class ResourcesLoader {
return result;
}
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
try {
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
return decodeStream(rf, new ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
public ResContainer 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 ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: "
+ String.format("%.2f KB", size / 1024.)));
}
return loadContent(jadxRef, rf.getType(), is);
return loadContent(jadxRef, rf, is);
}
});
} catch (JadxException e) {
......@@ -96,21 +98,22 @@ public final class ResourcesLoader {
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return cw;
return ResContainer.singleFile(rf.getName(), cw);
}
}
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
switch (type) {
switch (rf.getType()) {
case MANIFEST:
case XML:
return jadxRef.getXmlParser().parse(inputStream);
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
case ARSC:
return new ResTableParser().decodeToCodeWriter(inputStream);
return new ResTableParser().decodeFiles(inputStream);
}
return loadToCodeWriter(inputStream);
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
}
private void loadFile(List<ResourceFile> list, File file) {
......
......@@ -10,6 +10,7 @@ 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.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
......@@ -74,7 +75,7 @@ public class RootNode {
try {
ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
@Override
public Object decode(long size, InputStream is) throws IOException {
public ResContainer decode(long size, InputStream is) throws IOException {
parser.decode(is);
return null;
}
......
......@@ -27,30 +27,14 @@ public class StringUtils {
private static void processChar(int c, StringBuilder res) {
switch (c) {
case '\n':
res.append("\\n");
break;
case '\r':
res.append("\\r");
break;
case '\t':
res.append("\\t");
break;
case '\b':
res.append("\\b");
break;
case '\f':
res.append("\\f");
break;
case '\'':
res.append('\'');
break;
case '"':
res.append("\\\"");
break;
case '\\':
res.append("\\\\");
break;
case '\n': res.append("\\n"); break;
case '\r': res.append("\\r"); break;
case '\t': res.append("\\t"); break;
case '\b': res.append("\\b"); break;
case '\f': res.append("\\f"); break;
case '\'': res.append('\''); break;
case '"': res.append("\\\""); break;
case '\\': res.append("\\\\"); break;
default:
if (32 <= c && c <= 126) {
......@@ -114,4 +98,29 @@ public class StringUtils {
}
return sb.toString();
}
public static String escapeResValue(String str) {
int len = str.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
switch (c) {
case '&': sb.append("&amp;"); break;
case '<': sb.append("&lt;"); break;
case '>': sb.append("&gt;"); break;
case '"': sb.append("&quot;"); break;
case '\'': sb.append("&apos;"); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
case '\b': sb.append("\\b"); break;
case '\f': sb.append("\\f"); break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
}
......@@ -34,6 +34,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class);
private static final String ANDROID_R_STYLE_CLS = "android.R$style";
private static final boolean ATTR_NEW_LINE = false;
private CodeWriter writer;
private String[] strings;
......@@ -76,7 +77,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
resNames = root.getResourcesNames();
attributes = new ManifestAttributes();
attributes.parse();
attributes.parseAll();
} catch (Exception e) {
throw new JadxRuntimeException("BinaryXMLParser init error", e);
}
......@@ -221,12 +222,13 @@ public class BinaryXMLParser extends CommonBinaryParser {
int comment = is.readInt32();
int startNS = is.readInt32();
int startNSName = is.readInt32(); // actually is elementName...
if (!wasOneLiner && !"ERROR".equals(currentTag) && !currentTag.equals(strings[startNSName])) {
if (!wasOneLiner && !"ERROR".equals(currentTag)
&& !currentTag.equals(strings[startNSName])) {
writer.add(">");
}
wasOneLiner = false;
currentTag = strings[startNSName];
writer.startLine("<").add(strings[startNSName]);
writer.startLine("<").add(currentTag);
writer.attachSourceLine(elementBegLineNumber);
int attributeStart = is.readInt16();
if (attributeStart != 0x14) {
......@@ -240,22 +242,16 @@ public class BinaryXMLParser extends CommonBinaryParser {
int idIndex = is.readInt16();
int classIndex = is.readInt16();
int styleIndex = is.readInt16();
if ("manifest".equals(strings[startNSName])) {
writer.add(" xmlns:\"").add(nsURI).add("\"");
}
if (attributeCount > 0) {
writer.add(" ");
if ("manifest".equals(currentTag) || writer.getIndent() == 0) {
writer.add(" xmlns:android=\"").add(nsURI).add("\"");
}
boolean attrNewLine = attributeCount == 1 ? false : ATTR_NEW_LINE;
for (int i = 0; i < attributeCount; i++) {
parseAttribute(i);
writer.add('"');
if (i + 1 < attributeCount) {
writer.add(" ");
}
parseAttribute(i, attrNewLine);
}
}
private void parseAttribute(int i) throws IOException {
private void parseAttribute(int i, boolean newLine) throws IOException {
int attributeNS = is.readInt32();
int attributeName = is.readInt32();
int attributeRawValue = is.readInt32();
......@@ -268,10 +264,16 @@ public class BinaryXMLParser extends CommonBinaryParser {
}
int attrValDataType = is.readInt8();
int attrValData = is.readInt32();
String attrName = strings[attributeName];
if (newLine) {
writer.startLine().addIndent();
} else {
writer.add(' ');
}
if (attributeNS != -1) {
writer.add(nsPrefix).add(':');
}
String attrName = strings[attributeName];
writer.add(attrName).add("=\"");
String decodedAttr = attributes.decode(attrName, attrValData);
if (decodedAttr != null) {
......@@ -279,6 +281,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
} else {
decodeAttribute(attributeNS, attrValDataType, attrValData);
}
writer.add('"');
}
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData) {
......@@ -295,7 +298,11 @@ public class BinaryXMLParser extends CommonBinaryParser {
FieldNode field = localStyleMap.get(attrValData);
if (field != null) {
String cls = field.getParentClass().getShortName().toLowerCase();
writer.add("@").add(cls).add("/").add(field.getName());
writer.add("@");
if ("id".equals(cls)) {
writer.add('+');
}
writer.add(cls).add("/").add(field.getName());
} else {
String resName = resNames.get(attrValData);
if (resName != null) {
......
......@@ -4,6 +4,8 @@ import jadx.core.utils.exceptions.JadxException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
......@@ -15,10 +17,12 @@ import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ManifestAttributes {
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
private static final String ATTR_XML = "/android/attrs.xml";
private static final String MANIFEST_ATTR_XML = "/android/attrs_manifest.xml";
private enum MAttrType {
......@@ -27,7 +31,7 @@ public class ManifestAttributes {
private static class MAttr {
private final MAttrType type;
private final Map<Integer, String> values = new LinkedHashMap<Integer, String>();
private final Map<Long, String> values = new LinkedHashMap<Long, String>();
public MAttr(MAttrType type) {
this.type = type;
......@@ -37,7 +41,7 @@ public class ManifestAttributes {
return type;
}
public Map<Integer, String> getValues() {
public Map<Long, String> getValues() {
return values;
}
......@@ -47,15 +51,23 @@ public class ManifestAttributes {
}
}
private final Document doc;
private final Map<String, MAttr> attrMap = new HashMap<String, MAttr>();
public ManifestAttributes() throws Exception {
InputStream xmlStream = null;
}
public void parseAll() throws Exception {
parse(loadXML(ATTR_XML));
parse(loadXML(MANIFEST_ATTR_XML));
LOG.debug("Loaded android attributes count: {}", attrMap.size());
}
private Document loadXML(String xml) throws JadxException, ParserConfigurationException, SAXException, IOException {
Document doc;InputStream xmlStream = null;
try {
xmlStream = ManifestAttributes.class.getResourceAsStream(MANIFEST_ATTR_XML);
xmlStream = ManifestAttributes.class.getResourceAsStream(xml);
if (xmlStream == null) {
throw new JadxException(MANIFEST_ATTR_XML + " not found in classpath");
throw new JadxException(xml + " not found in classpath");
}
DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
doc = dBuilder.parse(xmlStream);
......@@ -64,9 +76,10 @@ public class ManifestAttributes {
xmlStream.close();
}
}
return doc;
}
public void parse() {
private void parse(Document doc) {
NodeList nodeList = doc.getChildNodes();
for (int count = 0; count < nodeList.getLength(); count++) {
Node node = nodeList.item(count);
......@@ -127,13 +140,13 @@ public class ManifestAttributes {
Node valueNode = attributes.getNamedItem("value");
if (valueNode != null) {
try {
int key;
long key;
String nodeValue = valueNode.getNodeValue();
if (attr.getType() == MAttrType.ENUM) {
key = Integer.parseInt(nodeValue);
if (nodeValue.startsWith("0x")) {
nodeValue = nodeValue.substring(2);
key = Long.parseLong(nodeValue, 16);
} else {
nodeValue = nodeValue.replace("0x", "");
key = Integer.parseInt(nodeValue, 16);
key = Long.parseLong(nodeValue);
}
attr.getValues().put(key, nameNode.getNodeValue());
} catch (NumberFormatException e) {
......@@ -145,7 +158,7 @@ public class ManifestAttributes {
}
}
public String decode(String attrName, int value) {
public String decode(String attrName, long value) {
MAttr attr = attrMap.get(attrName);
if (attr == null) {
return null;
......@@ -157,7 +170,7 @@ public class ManifestAttributes {
}
} else if (attr.getType() == MAttrType.FLAG) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<Integer, String> entry : attr.getValues().entrySet()) {
for (Map.Entry<Long, String> entry : attr.getValues().entrySet()) {
if ((value & entry.getKey()) != 0) {
sb.append(entry.getValue()).append('|');
}
......@@ -166,6 +179,6 @@ public class ManifestAttributes {
return sb.deleteCharAt(sb.length() - 1).toString();
}
}
return "UNKNOWN_DATA_0x" + Integer.toHexString(value);
return "UNKNOWN_DATA_0x" + Long.toHexString(value);
}
}
package jadx.core.xmlgen;
import java.util.HashMap;
import java.util.Map;
public class ParserConstants {
/**
......@@ -141,6 +144,7 @@ public class ParserConstants {
protected static final int ATTR_MAX = ResMakeInternal(2);
// Localization of this resource is can be encouraged or required with an aapt flag if this is set
protected static final int ATTR_L10N = ResMakeInternal(3);
// for plural support, see android.content.res.PluralRules#attrForQuantity(int)
protected static final int ATTR_OTHER = ResMakeInternal(4);
protected static final int ATTR_ZERO = ResMakeInternal(5);
......@@ -149,6 +153,17 @@ public class ParserConstants {
protected static final int ATTR_FEW = ResMakeInternal(8);
protected static final int ATTR_MANY = ResMakeInternal(9);
protected static final Map<Integer, String> PLURALS_MAP = new HashMap<Integer, String>() {
{
put(ATTR_OTHER, "other");
put(ATTR_ZERO, "zero");
put(ATTR_ONE, "one");
put(ATTR_TWO, "two");
put(ATTR_FEW, "few");
put(ATTR_MANY, "many");
}
};
private static int ResMakeInternal(int entry) {
return 0x01000000 | entry & 0xFFFF;
}
......
package jadx.core.xmlgen;
import jadx.core.codegen.CodeWriter;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public class ResContainer implements Comparable<ResContainer> {
private final String name;
@Nullable
private CodeWriter content;
private final List<ResContainer> subFiles;
private ResContainer(String name, @Nullable CodeWriter content, List<ResContainer> subFiles) {
this.name = name;
this.content = content;
this.subFiles = subFiles;
}
public static ResContainer singleFile(String name, CodeWriter content) {
return new ResContainer(name, content, Collections.<ResContainer>emptyList());
}
public static ResContainer multiFile(String name) {
return new ResContainer(name, null, new ArrayList<ResContainer>());
}
public String getName() {
return name;
}
public String getFileName() {
return name.replace("/", File.separator);
}
@Nullable
public CodeWriter getContent() {
return content;
}
public void setContent(@Nullable CodeWriter content) {
this.content = content;
}
public List<ResContainer> getSubFiles() {
return subFiles;
}
@Override
public int compareTo(ResContainer o) {
return name.compareTo(o.name);
}
@Override
public String toString() {
return "ResContainer{" +
"name='" + name + "'" +
", content=" + content +
", subFiles=" + subFiles +
"}";
}
}
......@@ -58,9 +58,19 @@ public class ResTableParser extends CommonBinaryParser {
resStorage.finish();
}
public CodeWriter decodeToCodeWriter(InputStream inputStream) throws IOException {
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
decode(inputStream);
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResContainer res = ResContainer.multiFile("res");
res.setContent(makeDump());
res.getSubFiles().addAll(resGen.makeResourcesXml());
return res;
}
public CodeWriter makeDump() throws IOException {
CodeWriter writer = new CodeWriter();
writer.add("app package: ").add(resStorage.getAppPackage());
writer.startLine();
......
package jadx.core.xmlgen;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.StringUtils;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ResXmlGen {
private static final Logger LOG = LoggerFactory.getLogger(ResXmlGen.class);
private static final Set<String> SKIP_RES_TYPES = new HashSet<String>(Arrays.asList(
"layout",
"mipmap",
"id"
));
private final ResourceStorage resStorage;
private final ValuesParser vp;
public ResXmlGen(ResourceStorage resStorage, ValuesParser vp) {
this.resStorage = resStorage;
this.vp = vp;
}
public List<ResContainer> makeResourcesXml() {
Map<String, CodeWriter> contMap = new HashMap<String, CodeWriter>();
for (ResourceEntry ri : resStorage.getResources()) {
if (SKIP_RES_TYPES.contains(ri.getTypeName())) {
continue;
}
String fn = getFileName(ri);
CodeWriter cw = contMap.get(fn);
if (cw == null) {
cw = new CodeWriter();
cw.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
cw.startLine("<resources>");
cw.incIndent();
contMap.put(fn, cw);
}
addValue(cw, ri);
}
List<ResContainer> files = new ArrayList<ResContainer>(contMap.size());
for (Map.Entry<String, CodeWriter> entry : contMap.entrySet()) {
String fileName = entry.getKey();
CodeWriter content = entry.getValue();
content.decIndent();
content.startLine("</resources>");
content.finish();
files.add(ResContainer.singleFile(fileName, content));
}
Collections.sort(files);
return files;
}
private void addValue(CodeWriter cw, ResourceEntry ri) {
if (ri.getSimpleValue() != null) {
String valueStr = vp.decodeValue(ri.getSimpleValue());
addSimpleValue(cw, ri.getTypeName(), "name", ri.getKeyName(), valueStr);
} else {
cw.startLine();
cw.add('<').add(ri.getTypeName()).add(' ');
cw.add("name=\"").add(ri.getKeyName()).add("\">");
cw.incIndent();
for (RawNamedValue value : ri.getNamedValues()) {
addItem(cw, value);
}
cw.decIndent();
cw.startLine().add("</").add(ri.getTypeName()).add('>');
}
}
private void addItem(CodeWriter cw, RawNamedValue value) {
String keyName = null;
String keyValue = null;
int nameRef = value.getNameRef();
if (ParserConstants.isResInternalId(nameRef)) {
keyValue = ParserConstants.PLURALS_MAP.get(nameRef);
if (keyValue != null) {
keyName = "quantity";
}
}
String valueStr = vp.decodeValue(value.getRawValue());
addSimpleValue(cw, "item", keyName, keyValue, valueStr);
}
private void addSimpleValue(CodeWriter cw, String typeName, String attrName, String attrValue, String valueStr) {
cw.startLine();
cw.add('<').add(typeName);
if (attrName != null && attrValue != null) {
cw.add(' ').add(attrName).add("=\"").add(attrValue).add('"');
}
cw.add('>');
cw.add(StringUtils.escapeResValue(valueStr));
cw.add("</").add(typeName).add('>');
}
private String getFileName(ResourceEntry ri) {
StringBuilder sb = new StringBuilder();
String locale = ri.getConfig().getLocale();
sb.append("res/values");
if (!locale.isEmpty()) {
sb.append('-').append(locale);
}
sb.append('/');
sb.append(ri.getTypeName());
if (!ri.getTypeName().endsWith("s")) {
sb.append('s');
}
sb.append(".xml");
return sb.toString();
}
}
package jadx.core.xmlgen;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.core.codegen.CodeWriter;
import java.io.File;
import java.util.List;
public class ResourcesSaver implements Runnable {
private final ResourceFile resourceFile;
private File outDir;
public ResourcesSaver(File outDir, ResourceFile resourceFile) {
this.resourceFile = resourceFile;
this.outDir = outDir;
}
@Override
public void run() {
if (!ResourceType.isSupportedForUnpack(resourceFile.getType())) {
return;
}
ResContainer rc = resourceFile.getContent();
if (rc != null) {
saveResources(rc);
}
}
private void saveResources(ResContainer rc) {
if (rc == null) {
return;
}
List<ResContainer> subFiles = rc.getSubFiles();
if (subFiles.isEmpty()) {
CodeWriter cw = rc.getContent();
if (cw != null) {
cw.save(new File(outDir, rc.getFileName()));
}
} else {
for (ResContainer subFile : subFiles) {
saveResources(subFile);
}
}
}
}
......@@ -20,8 +20,7 @@ public class EntryConfig {
return country;
}
@Override
public String toString() {
public String getLocale() {
StringBuilder sb = new StringBuilder();
if (language != null) {
sb.append(language);
......@@ -29,6 +28,13 @@ public class EntryConfig {
if (country != null) {
sb.append("-r").append(country);
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getLocale());
if (sb.length() != 0) {
sb.insert(0, " [");
sb.append(']');
......
......@@ -16,4 +16,9 @@ public class RawNamedValue {
public RawValue getRawValue() {
return rawValue;
}
@Override
public String toString() {
return "RawNamedValue{nameRef=" + nameRef + ", rawValue=" + rawValue + '}';
}
}
package jadx.gui.treemodel;
import jadx.api.ResourceFile;
import jadx.api.ResourceFileContent;
import jadx.api.ResourceType;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.Utils;
......@@ -31,6 +33,7 @@ public class JResource extends JNode implements Comparable<JResource> {
}
private final String name;
private final String shortName;
private final List<JResource> files = new ArrayList<JResource>(1);
private final JResType type;
private final ResourceFile resFile;
......@@ -40,12 +43,18 @@ public class JResource extends JNode implements Comparable<JResource> {
private Map<Integer, Integer> lineMapping;
public JResource(ResourceFile resFile, String name, JResType type) {
this(resFile, name, name, type);
}
public JResource(ResourceFile resFile, String name, String shortName, JResType type) {
this.resFile = resFile;
this.name = name;
this.shortName = shortName;
this.type = type;
}
public final void update() {
loadContent();
removeAllChildren();
for (JResource res : files) {
res.update();
......@@ -53,6 +62,13 @@ public class JResource extends JNode implements Comparable<JResource> {
}
}
protected void loadContent() {
getContent();
for (JResource res : files) {
res.loadContent();
}
}
public String getName() {
return name;
}
......@@ -65,16 +81,64 @@ public class JResource extends JNode implements Comparable<JResource> {
if (!loaded && resFile != null && type == JResType.FILE) {
loaded = true;
if (isSupportedForView(resFile.getType())) {
CodeWriter cw = resFile.getContent();
if (cw != null) {
lineMapping = cw.getLineMapping();
content = cw.toString();
ResContainer rc = resFile.getContent();
if (rc != null) {
addSubFiles(rc, this, 0);
}
}
}
return content;
}
protected void addSubFiles(ResContainer rc, JResource root, int depth) {
CodeWriter cw = rc.getContent();
if (cw != null) {
if (depth == 0) {
root.lineMapping = cw.getLineMapping();
root.content = cw.toString();
} else {
String name = rc.getName();
String[] path = name.split("/");
String shortName = path.length == 0 ? name : path[path.length - 1];
ResourceFileContent fileContent = new ResourceFileContent(shortName, ResourceType.XML, cw);
addPath(path, root, new JResource(fileContent, name, shortName, JResType.FILE));
}
}
List<ResContainer> subFiles = rc.getSubFiles();
if (!subFiles.isEmpty()) {
for (ResContainer subFile : subFiles) {
addSubFiles(subFile, root, depth + 1);
}
}
}
private static void addPath(String[] path, JResource root, JResource jResource) {
if (path.length == 1) {
root.getFiles().add(jResource);
return;
}
int last = path.length - 1;
for (int i = 0; i <= last; i++) {
String f = path[i];
if (i == last) {
root.getFiles().add(jResource);
} else {
root = getResDir(root, f);
}
}
}
private static JResource getResDir(JResource root, String dirName) {
for (JResource file : root.getFiles()) {
if (file.getName().equals(dirName)) {
return file;
}
}
JResource resDir = new JResource(null, dirName, JResType.DIR);
root.getFiles().add(resDir);
return resDir;
}
@Override
public Integer getSourceLine(int line) {
if (lineMapping == null) {
......@@ -170,7 +234,7 @@ public class JResource extends JNode implements Comparable<JResource> {
@Override
public String makeString() {
return name;
return shortName;
}
@Override
......
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