mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
* Implement proto parse * fix code formatting * fix tests with empty input * revert not needed code style changes * Implement parse of resources.pb for AAB Co-authored-by: bagipro <bugi@MacBook-Pro-2.local> Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
parent
4e5fac4b88
commit
9ef99a2b92
@ -12,7 +12,7 @@
|
||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||
|
||||
**Main features:**
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
|
||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||
- deobfuscator included
|
||||
|
||||
@ -65,7 +65,7 @@ and also packed to `build/jadx-<version>.zip`
|
||||
|
||||
### Usage
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
|
||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-ds, --output-dir-src - output directory for sources
|
||||
|
@ -19,7 +19,7 @@ import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
|
@ -6,6 +6,7 @@ dependencies {
|
||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.android.tools.build:aapt2-proto:4.1.2-6503028'
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.11'
|
||||
|
||||
|
@ -40,6 +40,7 @@ import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ProtoXMLParser;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
@ -79,7 +80,8 @@ public final class JadxDecompiler implements Closeable {
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
private BinaryXMLParser binaryXmlParser;
|
||||
private ProtoXMLParser protoXmlParser;
|
||||
|
||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
@ -122,7 +124,8 @@ public final class JadxDecompiler implements Closeable {
|
||||
root = null;
|
||||
classes = null;
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
binaryXmlParser = null;
|
||||
protoXmlParser = null;
|
||||
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
@ -341,11 +344,18 @@ public final class JadxDecompiler implements Closeable {
|
||||
return root;
|
||||
}
|
||||
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
synchronized BinaryXMLParser getBinaryXmlParser() {
|
||||
if (binaryXmlParser == null) {
|
||||
binaryXmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
return binaryXmlParser;
|
||||
}
|
||||
|
||||
synchronized ProtoXMLParser getProtoXmlParser() {
|
||||
if (protoXmlParser == null) {
|
||||
protoXmlParser = new ProtoXMLParser(root);
|
||||
}
|
||||
return protoXmlParser;
|
||||
}
|
||||
|
||||
private void loadJavaClass(JavaClass javaClass) {
|
||||
|
@ -41,6 +41,9 @@ public enum ResourceType {
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
if (fileName.matches("[^/]+/resources.pb")) {
|
||||
return ARSC;
|
||||
}
|
||||
int dot = fileName.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
||||
|
@ -17,11 +17,13 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResProtoParser;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
@ -91,14 +93,25 @@ public final class ResourcesLoader {
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream) throws IOException {
|
||||
RootNode root = jadxRef.getRoot();
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
|
||||
case XML: {
|
||||
ICodeInfo content;
|
||||
if (root.isProto()) {
|
||||
content = jadxRef.getProtoXmlParser().parse(inputStream);
|
||||
} else {
|
||||
content = jadxRef.getBinaryXmlParser().parse(inputStream);
|
||||
}
|
||||
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||
}
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
if (root.isProto()) {
|
||||
return new ResProtoParser(root).decodeFiles(inputStream);
|
||||
} else {
|
||||
return new ResTableParser(root).decodeFiles(inputStream);
|
||||
}
|
||||
|
||||
case IMG:
|
||||
return decodeImage(rf, inputStream);
|
||||
|
@ -71,6 +71,7 @@ public class RootNode {
|
||||
private String appPackage;
|
||||
@Nullable
|
||||
private ClassNode appResClass;
|
||||
private boolean isProto;
|
||||
|
||||
public RootNode(JadxArgs args) {
|
||||
this.args = args;
|
||||
@ -82,6 +83,7 @@ public class RootNode {
|
||||
this.codeCache = args.getCodeCache();
|
||||
this.methodUtils = new MethodUtils(this);
|
||||
this.typeUtils = new TypeUtils(this);
|
||||
this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab");
|
||||
}
|
||||
|
||||
public void loadClasses(List<ILoadResult> loadedInputs) {
|
||||
@ -502,4 +504,8 @@ public class RootNode {
|
||||
public TypeUtils getTypeUtils() {
|
||||
return typeUtils;
|
||||
}
|
||||
|
||||
public boolean isProto() {
|
||||
return isProto;
|
||||
}
|
||||
}
|
||||
|
141
jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java
Normal file
141
jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java
Normal file
@ -0,0 +1,141 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import com.android.aapt.Resources.XmlAttribute;
|
||||
import com.android.aapt.Resources.XmlElement;
|
||||
import com.android.aapt.Resources.XmlNamespace;
|
||||
import com.android.aapt.Resources.XmlNode;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class ProtoXMLParser {
|
||||
private Map<String, String> nsMap;
|
||||
private final Map<String, String> tagAttrDeobfNames = new HashMap<>();
|
||||
|
||||
private ICodeWriter writer;
|
||||
|
||||
private final RootNode rootNode;
|
||||
private String currentTag;
|
||||
private String appPackageName;
|
||||
|
||||
public ProtoXMLParser(RootNode rootNode) {
|
||||
this.rootNode = rootNode;
|
||||
}
|
||||
|
||||
public synchronized ICodeInfo parse(InputStream inputStream) throws IOException {
|
||||
nsMap = new HashMap<>();
|
||||
writer = rootNode.makeCodeWriter();
|
||||
writer.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
decode(decodeProto(inputStream));
|
||||
nsMap = null;
|
||||
return writer.finish();
|
||||
}
|
||||
|
||||
private void decode(XmlNode n) throws IOException {
|
||||
if (n.hasSource()) {
|
||||
writer.attachSourceLine(n.getSource().getLineNumber());
|
||||
}
|
||||
writer.add(StringUtils.escapeXML(n.getText().trim()));
|
||||
if (n.hasElement()) {
|
||||
decode(n.getElement());
|
||||
}
|
||||
}
|
||||
|
||||
private void decode(XmlElement e) throws IOException {
|
||||
String tag = deobfClassName(e.getName());
|
||||
tag = getValidTagAttributeName(tag);
|
||||
currentTag = tag;
|
||||
writer.startLine('<').add(tag);
|
||||
for (int i = 0; i < e.getNamespaceDeclarationCount(); i++) {
|
||||
decode(e.getNamespaceDeclaration(i));
|
||||
}
|
||||
for (int i = 0; i < e.getAttributeCount(); i++) {
|
||||
decode(e.getAttribute(i));
|
||||
}
|
||||
if (e.getChildCount() > 0) {
|
||||
writer.add('>');
|
||||
writer.incIndent();
|
||||
for (int i = 0; i < e.getChildCount(); i++) {
|
||||
Map<String, String> oldNsMap = new HashMap<>(nsMap);
|
||||
decode(e.getChild(i));
|
||||
nsMap = oldNsMap;
|
||||
}
|
||||
writer.decIndent();
|
||||
writer.startLine("</").add(tag).add('>');
|
||||
} else {
|
||||
writer.add("/>");
|
||||
}
|
||||
}
|
||||
|
||||
private void decode(XmlAttribute a) {
|
||||
writer.add(' ');
|
||||
String namespace = a.getNamespaceUri();
|
||||
if (!namespace.isEmpty()) {
|
||||
writer.add(nsMap.get(namespace)).add(':');
|
||||
}
|
||||
String name = a.getName();
|
||||
String value = deobfClassName(a.getValue());
|
||||
writer.add(name).add("=\"").add(value).add('\"');
|
||||
memorizePackageName(name, value);
|
||||
}
|
||||
|
||||
private void decode(XmlNamespace n) {
|
||||
String prefix = n.getPrefix();
|
||||
String uri = n.getUri();
|
||||
nsMap.put(uri, prefix);
|
||||
writer.add(" xmlns:").add(prefix).add("=\"").add(uri).add('"');
|
||||
}
|
||||
|
||||
private void memorizePackageName(String attrName, String attrValue) {
|
||||
if ("manifest".equals(currentTag) && "package".equals(attrName)) {
|
||||
appPackageName = attrValue;
|
||||
}
|
||||
}
|
||||
|
||||
private String deobfClassName(String className) {
|
||||
String newName = XmlDeobf.deobfClassName(rootNode, className, appPackageName);
|
||||
if (newName != null) {
|
||||
return newName;
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
private String getValidTagAttributeName(String originalName) {
|
||||
if (XMLChar.isValidName(originalName)) {
|
||||
return originalName;
|
||||
}
|
||||
if (tagAttrDeobfNames.containsKey(originalName)) {
|
||||
return tagAttrDeobfNames.get(originalName);
|
||||
}
|
||||
String generated;
|
||||
do {
|
||||
generated = generateTagAttrName();
|
||||
} while (tagAttrDeobfNames.containsValue(generated));
|
||||
tagAttrDeobfNames.put(originalName, generated);
|
||||
return generated;
|
||||
}
|
||||
|
||||
private static String generateTagAttrName() {
|
||||
final int length = 6;
|
||||
Random r = new Random();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i <= length; i++) {
|
||||
sb.append((char) (r.nextInt(26) + 'a'));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private XmlNode decodeProto(InputStream inputStream)
|
||||
throws InvalidProtocolBufferException, IOException {
|
||||
return XmlNode.parseFrom(XmlGenUtils.readData(inputStream));
|
||||
}
|
||||
}
|
248
jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java
Normal file
248
jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java
Normal file
@ -0,0 +1,248 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.aapt.ConfigurationOuterClass.Configuration;
|
||||
import com.android.aapt.Resources.Array;
|
||||
import com.android.aapt.Resources.Attribute;
|
||||
import com.android.aapt.Resources.CompoundValue;
|
||||
import com.android.aapt.Resources.ConfigValue;
|
||||
import com.android.aapt.Resources.Entry;
|
||||
import com.android.aapt.Resources.Item;
|
||||
import com.android.aapt.Resources.Package;
|
||||
import com.android.aapt.Resources.Plural;
|
||||
import com.android.aapt.Resources.Primitive;
|
||||
import com.android.aapt.Resources.ResourceTable;
|
||||
import com.android.aapt.Resources.Style;
|
||||
import com.android.aapt.Resources.Styleable;
|
||||
import com.android.aapt.Resources.Type;
|
||||
import com.android.aapt.Resources.Value;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.xmlgen.entry.EntryConfig;
|
||||
import jadx.core.xmlgen.entry.ProtoValue;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
public class ResProtoParser {
|
||||
private final RootNode root;
|
||||
private final ResourceStorage resStorage = new ResourceStorage();
|
||||
|
||||
public ResProtoParser(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
|
||||
ResourceTable table = decodeProto(inputStream);
|
||||
for (Package p : table.getPackageList()) {
|
||||
parse(p);
|
||||
}
|
||||
resStorage.finish();
|
||||
ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames());
|
||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
|
||||
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
|
||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||
}
|
||||
|
||||
private void parse(Package p) {
|
||||
String name = p.getPackageName();
|
||||
resStorage.setAppPackage(name);
|
||||
parse(name, p.getTypeList());
|
||||
}
|
||||
|
||||
private void parse(String packageName, List<Type> types) {
|
||||
for (Type type : types) {
|
||||
String typeName = type.getName();
|
||||
for (Entry entry : type.getEntryList()) {
|
||||
int id = entry.getEntryId().getId();
|
||||
String entryName = entry.getName();
|
||||
for (ConfigValue configValue : entry.getConfigValueList()) {
|
||||
String config = parse(configValue.getConfig());
|
||||
ResourceEntry resEntry = new ResourceEntry(id, packageName, typeName, entryName, config);
|
||||
resStorage.add(resEntry);
|
||||
|
||||
ProtoValue protoValue;
|
||||
if (configValue.getValue().getValueCase() == Value.ValueCase.ITEM) {
|
||||
protoValue = new ProtoValue(parse(configValue.getValue().getItem()));
|
||||
} else {
|
||||
protoValue = parse(configValue.getValue().getCompoundValue());
|
||||
}
|
||||
resEntry.setProtoValue(protoValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProtoValue parse(Style s) {
|
||||
List<ProtoValue> namedValues = new ArrayList<>(s.getEntryCount());
|
||||
String parent = s.getParent().getName();
|
||||
if (parent.isEmpty()) {
|
||||
parent = null;
|
||||
} else {
|
||||
parent = '@' + parent;
|
||||
}
|
||||
for (int i = 0; i < s.getEntryCount(); i++) {
|
||||
Style.Entry entry = s.getEntry(i);
|
||||
String name = entry.getKey().getName();
|
||||
String value = parse(entry.getItem());
|
||||
namedValues.add(new ProtoValue(value).setName(name));
|
||||
}
|
||||
return new ProtoValue().setNamedValues(namedValues).setParent(parent);
|
||||
}
|
||||
|
||||
private ProtoValue parse(Styleable s) {
|
||||
List<ProtoValue> namedValues = new ArrayList<>(s.getEntryCount());
|
||||
for (int i = 0; i < s.getEntryCount(); i++) {
|
||||
Styleable.Entry e = s.getEntry(i);
|
||||
namedValues.add(new ProtoValue('@' + e.getAttr().getName()));
|
||||
}
|
||||
return new ProtoValue().setNamedValues(namedValues);
|
||||
}
|
||||
|
||||
private ProtoValue parse(Array a) {
|
||||
List<ProtoValue> namedValues = new ArrayList<>(a.getElementCount());
|
||||
for (int i = 0; i < a.getElementCount(); i++) {
|
||||
Array.Element e = a.getElement(i);
|
||||
String value = parse(e.getItem());
|
||||
namedValues.add(new ProtoValue(value));
|
||||
}
|
||||
return new ProtoValue().setNamedValues(namedValues);
|
||||
}
|
||||
|
||||
private ProtoValue parse(Attribute a) {
|
||||
String format = XmlGenUtils.getAttrTypeAsString(a.getFormatFlags());
|
||||
List<ProtoValue> namedValues = new ArrayList<>(a.getSymbolCount());
|
||||
for (int i = 0; i < a.getSymbolCount(); i++) {
|
||||
Attribute.Symbol s = a.getSymbol(i);
|
||||
int type = s.getType();
|
||||
String name = s.getName().getName();
|
||||
String value = String.valueOf(s.getValue());
|
||||
namedValues.add(new ProtoValue(value).setName(name).setType(type));
|
||||
}
|
||||
return new ProtoValue(format).setNamedValues(namedValues);
|
||||
}
|
||||
|
||||
private ProtoValue parse(Plural p) {
|
||||
List<ProtoValue> namedValues = new ArrayList<>(p.getEntryCount());
|
||||
for (int i = 0; i < p.getEntryCount(); i++) {
|
||||
Plural.Entry e = p.getEntry(i);
|
||||
String name = e.getArity().name();
|
||||
String value = parse(e.getItem());
|
||||
namedValues.add(new ProtoValue(value).setName(name));
|
||||
}
|
||||
return new ProtoValue().setNamedValues(namedValues);
|
||||
}
|
||||
|
||||
private ProtoValue parse(CompoundValue c) {
|
||||
switch (c.getValueCase()) {
|
||||
case STYLE:
|
||||
return parse(c.getStyle());
|
||||
case STYLEABLE:
|
||||
return parse(c.getStyleable());
|
||||
case ARRAY:
|
||||
return parse(c.getArray());
|
||||
case ATTR:
|
||||
return parse(c.getAttr());
|
||||
case PLURAL:
|
||||
return parse(c.getPlural());
|
||||
default:
|
||||
return new ProtoValue("Unresolved value");
|
||||
}
|
||||
}
|
||||
|
||||
private String parse(Configuration c) {
|
||||
char[] language = c.getLocale().toCharArray();
|
||||
if (language.length == 0) {
|
||||
language = new char[] { '\00' };
|
||||
}
|
||||
short mcc = (short) c.getMcc();
|
||||
short mnc = (short) c.getMnc();
|
||||
byte orientation = (byte) c.getOrientationValue();
|
||||
short screenWidth = (short) c.getScreenWidth();
|
||||
short screenHeight = (short) c.getScreenHeight();
|
||||
short screenWidthDp = (short) c.getScreenWidthDp();
|
||||
short screenHeightDp = (short) c.getScreenHeightDp();
|
||||
short smallestScreenWidthDp = (short) c.getSmallestScreenWidthDp();
|
||||
short sdkVersion = (short) c.getSdkVersion();
|
||||
byte keyboard = (byte) c.getKeyboardValue();
|
||||
byte touchscreen = (byte) c.getTouchscreenValue();
|
||||
int density = c.getDensity();
|
||||
byte screenLayout = (byte) c.getScreenLayoutLongValue();
|
||||
byte colorMode = (byte) (c.getHdrValue() | c.getWideColorGamutValue());
|
||||
byte screenLayout2 = (byte) (c.getLayoutDirectionValue() | c.getScreenRoundValue());
|
||||
byte navigation = (byte) c.getNavigationValue();
|
||||
byte inputFlags = (byte) (c.getKeysHiddenValue() | c.getNavHiddenValue());
|
||||
int size = c.getSerializedSize();
|
||||
byte uiMode = (byte) (c.getUiModeNightValue() | c.getUiModeTypeValue());
|
||||
|
||||
c.getScreenLayoutSize(); // unknown field
|
||||
c.getProduct(); // unknown field
|
||||
|
||||
return new EntryConfig(mcc, mnc, language, new char[] { '\00' },
|
||||
orientation, touchscreen, density, keyboard, navigation,
|
||||
inputFlags, screenWidth, screenHeight, sdkVersion,
|
||||
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
|
||||
screenHeightDp, new char[] { '\00' }, new char[] { '\00' }, screenLayout2,
|
||||
colorMode, false, size).getQualifiers();
|
||||
}
|
||||
|
||||
private String parse(Item i) {
|
||||
if (i.hasRawStr()) {
|
||||
return i.getRawStr().getValue();
|
||||
}
|
||||
if (i.hasStr()) {
|
||||
return i.getStr().getValue();
|
||||
}
|
||||
if (i.hasStyledStr()) {
|
||||
return i.getStyledStr().getValue();
|
||||
}
|
||||
if (i.hasPrim()) {
|
||||
Primitive prim = i.getPrim();
|
||||
switch (prim.getOneofValueCase()) {
|
||||
case NULL_VALUE:
|
||||
return null;
|
||||
case INT_DECIMAL_VALUE:
|
||||
return String.valueOf(prim.getIntDecimalValue());
|
||||
case INT_HEXADECIMAL_VALUE:
|
||||
return Integer.toHexString(prim.getIntHexadecimalValue());
|
||||
case BOOLEAN_VALUE:
|
||||
return String.valueOf(prim.getBooleanValue());
|
||||
case FLOAT_VALUE:
|
||||
return String.valueOf(prim.getFloatValue());
|
||||
case COLOR_ARGB4_VALUE:
|
||||
return String.format("#%04x", prim.getColorArgb4Value());
|
||||
case COLOR_ARGB8_VALUE:
|
||||
return String.format("#%08x", prim.getColorArgb8Value());
|
||||
case COLOR_RGB4_VALUE:
|
||||
return String.format("#%03x", prim.getColorRgb4Value());
|
||||
case COLOR_RGB8_VALUE:
|
||||
return String.format("#%06x", prim.getColorRgb8Value());
|
||||
case DIMENSION_VALUE:
|
||||
return XmlGenUtils.decodeComplex(prim.getDimensionValue(), false);
|
||||
case FRACTION_VALUE:
|
||||
return XmlGenUtils.decodeComplex(prim.getDimensionValue(), true);
|
||||
case EMPTY_VALUE:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (i.hasRef()) {
|
||||
return '@' + i.getRef().getName();
|
||||
}
|
||||
if (i.hasFile()) {
|
||||
return i.getFile().getPath();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private ResourceTable decodeProto(InputStream inputStream)
|
||||
throws InvalidProtocolBufferException, IOException {
|
||||
return ResourceTable.parseFrom(XmlGenUtils.readData(inputStream));
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@ -89,30 +88,11 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||
|
||||
ICodeInfo content = makeXmlDump();
|
||||
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
|
||||
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
|
||||
return ResContainer.resourceTable("res", xmlFiles, content);
|
||||
}
|
||||
|
||||
public ICodeInfo makeXmlDump() {
|
||||
ICodeWriter writer = root.makeCodeWriter();
|
||||
writer.startLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
writer.startLine("<resources>");
|
||||
writer.incIndent();
|
||||
|
||||
Set<String> addedValues = new HashSet<>();
|
||||
for (ResourceEntry ri : resStorage.getResources()) {
|
||||
if (addedValues.add(ri.getTypeName() + '.' + ri.getKeyName())) {
|
||||
String format = String.format("<public type=\"%s\" name=\"%s\" id=\"%s\" />",
|
||||
ri.getTypeName(), ri.getKeyName(), ri.getId());
|
||||
writer.startLine(format);
|
||||
}
|
||||
}
|
||||
writer.decIndent();
|
||||
writer.startLine("</resources>");
|
||||
return writer.finish();
|
||||
}
|
||||
|
||||
public ResourceStorage getResStorage() {
|
||||
return resStorage;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.xmlgen.entry.ProtoValue;
|
||||
import jadx.core.xmlgen.entry.RawNamedValue;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
@ -67,7 +68,31 @@ public class ResXmlGen {
|
||||
}
|
||||
|
||||
private void addValue(ICodeWriter cw, ResourceEntry ri) {
|
||||
if (ri.getSimpleValue() != null) {
|
||||
if (ri.getProtoValue() != null) {
|
||||
ProtoValue protoValue = ri.getProtoValue();
|
||||
if (protoValue.getValue() != null && protoValue.getNamedValues() == null) {
|
||||
addSimpleValue(cw, ri.getTypeName(), ri.getTypeName(), "name", ri.getKeyName(), protoValue.getValue());
|
||||
} else {
|
||||
cw.startLine();
|
||||
cw.add('<').add(ri.getTypeName()).add(' ');
|
||||
String itemTag = "item";
|
||||
cw.add("name=\"").add(ri.getKeyName()).add('\"');
|
||||
if (ri.getTypeName().equals("attr") && protoValue.getValue() != null) {
|
||||
cw.add(" format=\"").add(protoValue.getValue()).add('\"');
|
||||
}
|
||||
if (protoValue.getParent() != null) {
|
||||
cw.add(" parent=\"").add(protoValue.getParent()).add('\"');
|
||||
}
|
||||
cw.add(">");
|
||||
|
||||
cw.incIndent();
|
||||
for (ProtoValue value : protoValue.getNamedValues()) {
|
||||
addProtoItem(cw, itemTag, ri.getTypeName(), value);
|
||||
}
|
||||
cw.decIndent();
|
||||
cw.startLine().add("</").add(ri.getTypeName()).add('>');
|
||||
}
|
||||
} else if (ri.getSimpleValue() != null) {
|
||||
String valueStr = vp.decodeValue(ri.getSimpleValue());
|
||||
addSimpleValue(cw, ri.getTypeName(), ri.getTypeName(), "name", ri.getKeyName(), valueStr);
|
||||
} else {
|
||||
@ -82,7 +107,7 @@ public class ResXmlGen {
|
||||
} else if ((type & ValuesParser.ATTR_TYPE_FLAGS) != 0) {
|
||||
itemTag = "flag";
|
||||
}
|
||||
String formatValue = getTypeAsString(type);
|
||||
String formatValue = XmlGenUtils.getAttrTypeAsString(type);
|
||||
if (formatValue != null) {
|
||||
cw.add("\" format=\"").add(formatValue);
|
||||
}
|
||||
@ -105,36 +130,27 @@ public class ResXmlGen {
|
||||
}
|
||||
}
|
||||
|
||||
private String getTypeAsString(int type) {
|
||||
String s = "";
|
||||
if ((type & ValuesParser.ATTR_TYPE_REFERENCE) != 0) {
|
||||
s += "|reference";
|
||||
private void addProtoItem(ICodeWriter cw, String itemTag, String typeName, ProtoValue protoValue) {
|
||||
String name = protoValue.getName();
|
||||
String value = protoValue.getValue();
|
||||
switch (typeName) {
|
||||
case "attr":
|
||||
if (name != null) {
|
||||
addSimpleValue(cw, typeName, itemTag, name, value, "");
|
||||
}
|
||||
break;
|
||||
case "style":
|
||||
if (name != null) {
|
||||
addSimpleValue(cw, typeName, itemTag, name, "", value);
|
||||
}
|
||||
break;
|
||||
case "plurals":
|
||||
addSimpleValue(cw, typeName, itemTag, "quantity", name, value);
|
||||
break;
|
||||
default:
|
||||
addSimpleValue(cw, typeName, itemTag, null, null, value);
|
||||
break;
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_STRING) != 0) {
|
||||
s += "|string";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_INTEGER) != 0) {
|
||||
s += "|integer";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_BOOLEAN) != 0) {
|
||||
s += "|boolean";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_COLOR) != 0) {
|
||||
s += "|color";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FLOAT) != 0) {
|
||||
s += "|float";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_DIMENSION) != 0) {
|
||||
s += "|dimension";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FRACTION) != 0) {
|
||||
s += "|fraction";
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return s.substring(1);
|
||||
}
|
||||
|
||||
private void addItem(ICodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
||||
|
@ -21,6 +21,7 @@ public class XmlDeobf {
|
||||
|
||||
@Nullable
|
||||
public static String deobfClassName(RootNode rootNode, String potencialClassName, String packageName) {
|
||||
potencialClassName = potencialClassName.replace('$', '.');
|
||||
if (packageName != null && potencialClassName.startsWith(".")) {
|
||||
potencialClassName = packageName + potencialClassName;
|
||||
}
|
||||
|
141
jadx-core/src/main/java/jadx/core/xmlgen/XmlGenUtils.java
Normal file
141
jadx-core/src/main/java/jadx/core/xmlgen/XmlGenUtils.java
Normal file
@ -0,0 +1,141 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
public class XmlGenUtils {
|
||||
private XmlGenUtils() {
|
||||
}
|
||||
|
||||
public static byte[] readData(InputStream i) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
byte[] data = new byte[16384];
|
||||
int read;
|
||||
while ((read = i.read(data, 0, data.length)) != -1) {
|
||||
buffer.write(data, 0, read);
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public static ICodeInfo makeXmlDump(ICodeWriter writer, ResourceStorage resStorage) {
|
||||
writer.startLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
writer.startLine("<resources>");
|
||||
writer.incIndent();
|
||||
|
||||
Set<String> addedValues = new HashSet<>();
|
||||
for (ResourceEntry ri : resStorage.getResources()) {
|
||||
if (addedValues.add(ri.getTypeName() + '.' + ri.getKeyName())) {
|
||||
String format = String.format("<public type=\"%s\" name=\"%s\" id=\"%s\" />",
|
||||
ri.getTypeName(), ri.getKeyName(), ri.getId());
|
||||
writer.startLine(format);
|
||||
}
|
||||
}
|
||||
writer.decIndent();
|
||||
writer.startLine("</resources>");
|
||||
return writer.finish();
|
||||
}
|
||||
|
||||
public static String decodeComplex(int data, boolean isFraction) {
|
||||
double value = (data & ParserConstants.COMPLEX_MANTISSA_MASK << ParserConstants.COMPLEX_MANTISSA_SHIFT)
|
||||
* ParserConstants.RADIX_MULTS[data >> ParserConstants.COMPLEX_RADIX_SHIFT & ParserConstants.COMPLEX_RADIX_MASK];
|
||||
int unitType = data & ParserConstants.COMPLEX_UNIT_MASK;
|
||||
String unit;
|
||||
if (isFraction) {
|
||||
value *= 100;
|
||||
switch (unitType) {
|
||||
case ParserConstants.COMPLEX_UNIT_FRACTION:
|
||||
unit = "%";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_FRACTION_PARENT:
|
||||
unit = "%p";
|
||||
break;
|
||||
|
||||
default:
|
||||
unit = "?f" + Integer.toHexString(unitType);
|
||||
}
|
||||
} else {
|
||||
switch (unitType) {
|
||||
case ParserConstants.COMPLEX_UNIT_PX:
|
||||
unit = "px";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_DIP:
|
||||
unit = "dp";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_SP:
|
||||
unit = "sp";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_PT:
|
||||
unit = "pt";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_IN:
|
||||
unit = "in";
|
||||
break;
|
||||
case ParserConstants.COMPLEX_UNIT_MM:
|
||||
unit = "mm";
|
||||
break;
|
||||
|
||||
default:
|
||||
unit = "?d" + Integer.toHexString(unitType);
|
||||
}
|
||||
}
|
||||
return doubleToString(value) + unit;
|
||||
}
|
||||
|
||||
public static String doubleToString(double value) {
|
||||
if (Double.compare(value, Math.floor(value)) == 0
|
||||
&& !Double.isInfinite(value)) {
|
||||
return Integer.toString((int) value);
|
||||
}
|
||||
// remove trailing zeroes
|
||||
NumberFormat f = NumberFormat.getInstance(Locale.ROOT);
|
||||
f.setMaximumFractionDigits(4);
|
||||
f.setMinimumIntegerDigits(1);
|
||||
return f.format(value);
|
||||
}
|
||||
|
||||
public static String floatToString(float value) {
|
||||
return doubleToString(value);
|
||||
}
|
||||
|
||||
public static String getAttrTypeAsString(int type) {
|
||||
String s = "";
|
||||
if ((type & ValuesParser.ATTR_TYPE_REFERENCE) != 0) {
|
||||
s += "|reference";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_STRING) != 0) {
|
||||
s += "|string";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_INTEGER) != 0) {
|
||||
s += "|integer";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_BOOLEAN) != 0) {
|
||||
s += "|boolean";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_COLOR) != 0) {
|
||||
s += "|color";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FLOAT) != 0) {
|
||||
s += "|float";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_DIMENSION) != 0) {
|
||||
s += "|dimension";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FRACTION) != 0) {
|
||||
s += "|fraction";
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return s.substring(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package jadx.core.xmlgen.entry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ProtoValue {
|
||||
private String parent;
|
||||
private String name;
|
||||
private String value;
|
||||
private int type;
|
||||
private List<ProtoValue> namedValues;
|
||||
|
||||
public ProtoValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ProtoValue() {
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ProtoValue setType(int type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public ProtoValue setParent(String parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProtoValue setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ProtoValue setNamedValues(List<ProtoValue> namedValues) {
|
||||
this.namedValues = namedValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<ProtoValue> getNamedValues() {
|
||||
return namedValues;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ public final class ResourceEntry {
|
||||
private final String config;
|
||||
|
||||
private int parentRef;
|
||||
private ProtoValue protoValue;
|
||||
private RawValue simpleValue;
|
||||
private List<RawNamedValue> namedValues;
|
||||
|
||||
@ -25,6 +26,7 @@ public final class ResourceEntry {
|
||||
public ResourceEntry copy(String newKeyName) {
|
||||
ResourceEntry copy = new ResourceEntry(id, pkgName, typeName, newKeyName, config);
|
||||
copy.parentRef = this.parentRef;
|
||||
copy.protoValue = this.protoValue;
|
||||
copy.simpleValue = this.simpleValue;
|
||||
copy.namedValues = this.namedValues;
|
||||
return copy;
|
||||
@ -62,6 +64,14 @@ public final class ResourceEntry {
|
||||
return parentRef;
|
||||
}
|
||||
|
||||
public ProtoValue getProtoValue() {
|
||||
return protoValue;
|
||||
}
|
||||
|
||||
public void setProtoValue(ProtoValue protoValue) {
|
||||
this.protoValue = protoValue;
|
||||
}
|
||||
|
||||
public RawValue getSimpleValue() {
|
||||
return simpleValue;
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
package jadx.core.xmlgen.entry;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -14,6 +12,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.utils.android.TextResMapFile;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.ParserConstants;
|
||||
import jadx.core.xmlgen.XmlGenUtils;
|
||||
|
||||
public class ValuesParser extends ParserConstants {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ValuesParser.class);
|
||||
@ -46,6 +45,10 @@ public class ValuesParser extends ParserConstants {
|
||||
|
||||
@Nullable
|
||||
public String getSimpleValueString(ResourceEntry ri) {
|
||||
ProtoValue protoValue = ri.getProtoValue();
|
||||
if (protoValue != null) {
|
||||
return protoValue.getValue();
|
||||
}
|
||||
RawValue simpleValue = ri.getSimpleValue();
|
||||
if (simpleValue == null) {
|
||||
return null;
|
||||
@ -55,6 +58,22 @@ public class ValuesParser extends ParserConstants {
|
||||
|
||||
@Nullable
|
||||
public String getValueString(ResourceEntry ri) {
|
||||
ProtoValue protoValue = ri.getProtoValue();
|
||||
if (protoValue != null) {
|
||||
if (protoValue.getValue() != null) {
|
||||
return protoValue.getValue();
|
||||
}
|
||||
List<ProtoValue> values = protoValue.getNamedValues();
|
||||
List<String> strList = new ArrayList<>(values.size());
|
||||
for (ProtoValue value : values) {
|
||||
if (value.getName() == null) {
|
||||
strList.add(value.getValue());
|
||||
} else {
|
||||
strList.add(value.getName() + '=' + value.getValue());
|
||||
}
|
||||
}
|
||||
return strList.toString();
|
||||
}
|
||||
RawValue simpleValue = ri.getSimpleValue();
|
||||
if (simpleValue != null) {
|
||||
return decodeValue(simpleValue);
|
||||
@ -94,8 +113,7 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_INT_BOOLEAN:
|
||||
return data == 0 ? "false" : "true";
|
||||
case TYPE_FLOAT:
|
||||
return floatToString(Float.intBitsToFloat(data));
|
||||
|
||||
return XmlGenUtils.floatToString(Float.intBitsToFloat(data));
|
||||
case TYPE_INT_COLOR_ARGB8:
|
||||
return String.format("#%08x", data);
|
||||
case TYPE_INT_COLOR_RGB8:
|
||||
@ -134,9 +152,9 @@ public class ValuesParser extends ParserConstants {
|
||||
}
|
||||
|
||||
case TYPE_DIMENSION:
|
||||
return decodeComplex(data, false);
|
||||
return XmlGenUtils.decodeComplex(data, false);
|
||||
case TYPE_FRACTION:
|
||||
return decodeComplex(data, true);
|
||||
return XmlGenUtils.decodeComplex(data, true);
|
||||
case TYPE_DYNAMIC_ATTRIBUTE:
|
||||
LOG.warn("Data type TYPE_DYNAMIC_ATTRIBUTE not yet supported: {}", data);
|
||||
return " TYPE_DYNAMIC_ATTRIBUTE: " + data;
|
||||
@ -166,66 +184,4 @@ public class ValuesParser extends ParserConstants {
|
||||
}
|
||||
return "?0x" + Integer.toHexString(nameRef);
|
||||
}
|
||||
|
||||
private String decodeComplex(int data, boolean isFraction) {
|
||||
double value = (data & COMPLEX_MANTISSA_MASK << COMPLEX_MANTISSA_SHIFT)
|
||||
* RADIX_MULTS[data >> COMPLEX_RADIX_SHIFT & COMPLEX_RADIX_MASK];
|
||||
int unitType = data & COMPLEX_UNIT_MASK;
|
||||
String unit;
|
||||
if (isFraction) {
|
||||
value *= 100;
|
||||
switch (unitType) {
|
||||
case COMPLEX_UNIT_FRACTION:
|
||||
unit = "%";
|
||||
break;
|
||||
case COMPLEX_UNIT_FRACTION_PARENT:
|
||||
unit = "%p";
|
||||
break;
|
||||
|
||||
default:
|
||||
unit = "?f" + Integer.toHexString(unitType);
|
||||
}
|
||||
} else {
|
||||
switch (unitType) {
|
||||
case COMPLEX_UNIT_PX:
|
||||
unit = "px";
|
||||
break;
|
||||
case COMPLEX_UNIT_DIP:
|
||||
unit = "dp";
|
||||
break;
|
||||
case COMPLEX_UNIT_SP:
|
||||
unit = "sp";
|
||||
break;
|
||||
case COMPLEX_UNIT_PT:
|
||||
unit = "pt";
|
||||
break;
|
||||
case COMPLEX_UNIT_IN:
|
||||
unit = "in";
|
||||
break;
|
||||
case COMPLEX_UNIT_MM:
|
||||
unit = "mm";
|
||||
break;
|
||||
|
||||
default:
|
||||
unit = "?d" + Integer.toHexString(unitType);
|
||||
}
|
||||
}
|
||||
return doubleToString(value) + unit;
|
||||
}
|
||||
|
||||
private static String doubleToString(double value) {
|
||||
if (Double.compare(value, Math.floor(value)) == 0
|
||||
&& !Double.isInfinite(value)) {
|
||||
return Integer.toString((int) value);
|
||||
}
|
||||
// remove trailing zeroes
|
||||
NumberFormat f = NumberFormat.getInstance(Locale.ROOT);
|
||||
f.setMaximumFractionDigits(4);
|
||||
f.setMinimumIntegerDigits(1);
|
||||
return f.format(value);
|
||||
}
|
||||
|
||||
private static String floatToString(float value) {
|
||||
return doubleToString(value);
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +190,8 @@ public class ApkSignature extends JNode {
|
||||
builder.append("<blockquote>");
|
||||
// Unprotected Zip entry issues are very common, handle them separately
|
||||
List<ApkVerifier.IssueWithParams> unprotIssues = issueList.stream()
|
||||
.filter(i -> i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
|
||||
.filter(i -> i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY)
|
||||
.collect(Collectors.toList());
|
||||
if (!unprotIssues.isEmpty()) {
|
||||
builder.append("<h4>");
|
||||
builder.escape(NLS.str("apkSignature.unprotectedEntry"));
|
||||
@ -202,7 +203,8 @@ public class ApkSignature extends JNode {
|
||||
builder.append("</blockquote>");
|
||||
}
|
||||
List<ApkVerifier.IssueWithParams> remainingIssues = issueList.stream()
|
||||
.filter(i -> i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
|
||||
.filter(i -> i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY)
|
||||
.collect(Collectors.toList());
|
||||
if (!remainingIssues.isEmpty()) {
|
||||
builder.append("<pre>\n");
|
||||
for (ApkVerifier.IssueWithParams issue : remainingIssues) {
|
||||
|
@ -5,7 +5,8 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -63,8 +64,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
|
||||
public final void update() {
|
||||
if (files.isEmpty()) {
|
||||
if (type == JResType.DIR
|
||||
|| type == JResType.ROOT
|
||||
if (type == JResType.DIR || type == JResType.ROOT
|
||||
|| resFile.getType() == ResourceType.ARSC) {
|
||||
// fake leaf to force show expand button
|
||||
// real sub nodes will load on expand in loadNode() method
|
||||
@ -278,6 +278,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
case CODE:
|
||||
case FONT:
|
||||
case LIB:
|
||||
case MEDIA:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
|
@ -279,7 +279,7 @@ public class MainWindow extends JFrame {
|
||||
if (addFiles) {
|
||||
exts = new String[] { "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc" };
|
||||
} else {
|
||||
exts = new String[] { JadxProject.PROJECT_EXTENSION, "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc" };
|
||||
exts = new String[] { JadxProject.PROJECT_EXTENSION, "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "aab" };
|
||||
}
|
||||
String description = "Supported files: (" + Utils.arrayToStr(exts) + ')';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user