Commit 82d0d622 authored by Skylot's avatar Skylot

fix: refactor, improve performance and fix some issues in resource processing

fix(gui): instead gradle export was executed normal export
fix(gui): content of some resource files was not shown
perf: direct resource files saving without full length buffer in memory
perf(gui): line numbers will be disabled on big files due to performance issue
feat(gui): click on HeapUsageBar will run GC and update memory info
feat(gui): add more file types for syntax highlights
refactor: ResContainer class changed for support more types of data (added link to resource file)
parent bcaca781
......@@ -35,6 +35,13 @@ public class ResourceFile {
private final ResourceType type;
private ZipRef zipRef;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
......@@ -65,11 +72,4 @@ public class ResourceFile {
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
}
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
super(null, name, type);
this.content = content;
}
@Override
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
}
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFileContent(name, type, content);
return ResContainer.textResource(getName(), content);
}
}
......@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
......@@ -31,8 +32,6 @@ import static jadx.core.utils.files.FileUtils.copyStream;
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
......@@ -47,11 +46,11 @@ public final class ResourcesLoader {
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
......@@ -80,44 +79,48 @@ public final class ResourcesLoader {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return ResContainer.singleFile(rf.getName(), cw);
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser()
.decodeFiles(inputStream);
return new ResTableParser().decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return ResContainer.singleBinaryFile(rf.getName(), inputStream);
return decodeImage(rf, inputStream);
default:
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
return ResContainer.resourceFileLink(rf);
}
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
......@@ -141,7 +144,7 @@ public final class ResourcesLoader {
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
......@@ -153,7 +156,7 @@ public final class ResourcesLoader {
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
......
package jadx.core.codegen;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
......@@ -21,6 +20,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
public class NameGen {
......@@ -31,20 +31,21 @@ public class NameGen {
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d"
);
}
public NameGen(MethodNode mth, boolean fallback) {
......
......@@ -21,7 +21,6 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
......@@ -81,17 +80,16 @@ public class RootNode {
LOG.debug("'.arsc' file not found");
return;
}
ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResTableParser parser = new ResTableParser();
parser.decode(is);
return null;
return parser.getResStorage();
});
} catch (JadxException e) {
processResources(resStorage);
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
processResources(parser.getResStorage());
}
public void processResources(ResourceStorage resStorage) {
......
......@@ -5,8 +5,10 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import jadx.api.JadxDecompiler;
......@@ -161,4 +163,16 @@ public class Utils {
}
return new ImmutableList<>(list);
}
public static Map<String, String> newConstStringMap(String... parameters) {
int len = parameters.length;
if (len == 0) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>(len / 2);
for (int i = 0; i < len; i += 2) {
result.put(parameters[i], parameters[i + 1]);
}
return Collections.unmodifiableMap(result);
}
}
package jadx.core.xmlgen;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ResContainer implements Comparable<ResContainer> {
private static final Logger LOG = LoggerFactory.getLogger(ResContainer.class);
public enum DataType {
TEXT, DECODED_DATA, RES_LINK, RES_TABLE
}
private final DataType dataType;
private final String name;
private final Object data;
private final List<ResContainer> subFiles;
@Nullable
private CodeWriter content;
@Nullable
private BufferedImage image;
@Nullable
private InputStream binary;
private ResContainer(String name, List<ResContainer> subFiles) {
this.name = name;
this.subFiles = subFiles;
}
public static ResContainer singleFile(String name, CodeWriter content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
resContainer.content = content;
return resContainer;
}
public static ResContainer singleImageFile(String name, InputStream content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
InputStream newContent = content;
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(content, os);
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
newContent = new ByteArrayInputStream(os.toByteArray());
}
try {
resContainer.image = ImageIO.read(newContent);
} catch (Exception e) {
throw new JadxRuntimeException("Image load error", e);
}
return resContainer;
public static ResContainer textResource(String name, CodeWriter content) {
return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT);
}
public static ResContainer singleBinaryFile(String name, InputStream content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
try {
// TODO: don't store binary files in memory
resContainer.binary = new ByteArrayInputStream(IOUtils.toByteArray(content));
} catch (Exception e) {
LOG.warn("Contents of the binary resource '{}' not saved, got exception", name, e);
}
return resContainer;
public static ResContainer decodedData(String name, byte[] data) {
return new ResContainer(name, Collections.emptyList(), data, DataType.DECODED_DATA);
}
public static ResContainer resourceFileLink(ResourceFile resFile) {
return new ResContainer(resFile.getName(), Collections.emptyList(), resFile, DataType.RES_LINK);
}
public static ResContainer multiFile(String name) {
return new ResContainer(name, new ArrayList<>());
public static ResContainer resourceTable(String name, List<ResContainer> subFiles, CodeWriter rootContent) {
return new ResContainer(name, subFiles, rootContent, DataType.RES_TABLE);
}
private ResContainer(String name, List<ResContainer> subFiles, Object data, DataType dataType) {
this.name = Objects.requireNonNull(name);
this.subFiles = Objects.requireNonNull(subFiles);
this.data = Objects.requireNonNull(data);
this.dataType = Objects.requireNonNull(dataType);
}
public String getName() {
......@@ -89,27 +52,24 @@ public class ResContainer implements Comparable<ResContainer> {
return name.replace("/", File.separator);
}
@Nullable
public CodeWriter getContent() {
return content;
public List<ResContainer> getSubFiles() {
return subFiles;
}
@Nullable
public InputStream getBinary() {
return binary;
public DataType getDataType() {
return dataType;
}
public void setContent(@Nullable CodeWriter content) {
this.content = content;
public CodeWriter getText() {
return (CodeWriter) data;
}
@Nullable
public BufferedImage getImage() {
return image;
public byte[] getDecodedData() {
return (byte[]) data;
}
public List<ResContainer> getSubFiles() {
return subFiles;
public ResourceFile getResLink() {
return (ResourceFile) data;
}
@Override
......@@ -136,6 +96,6 @@ public class ResContainer implements Comparable<ResContainer> {
@Override
public String toString() {
return "Res{" + name + ", subFiles=" + subFiles + "}";
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + "}";
}
}
......@@ -66,23 +66,9 @@ public class ResTableParser extends CommonBinaryParser {
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResContainer res = ResContainer.multiFile("res");
res.setContent(makeXmlDump());
res.getSubFiles().addAll(resGen.makeResourcesXml());
return res;
}
public CodeWriter makeDump() {
CodeWriter writer = new CodeWriter();
writer.add("app package: ").add(resStorage.getAppPackage());
writer.startLine();
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
for (ResourceEntry ri : resStorage.getResources()) {
writer.startLine(ri + ": " + vp.getValueString(ri));
}
writer.finish();
return writer;
CodeWriter content = makeXmlDump();
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
return ResContainer.resourceTable("res", xmlFiles, content);
}
public CodeWriter makeXmlDump() {
......
......@@ -57,7 +57,7 @@ public class ResXmlGen {
content.decIndent();
content.startLine("</resources>");
content.finish();
files.add(ResContainer.singleFile(fileName, content));
files.add(ResContainer.textResource(fileName, content));
}
Collections.sort(files);
return files;
......
package jadx.core.xmlgen;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.nio.file.Files;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import static jadx.core.utils.files.FileUtils.prepareFile;
public class ResourcesSaver implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class);
......@@ -33,76 +29,74 @@ public class ResourcesSaver implements Runnable {
@Override
public void run() {
ResContainer rc = resourceFile.loadContent();
if (rc != null) {
saveResources(rc);
}
saveResources(resourceFile.loadContent());
}
private void saveResources(ResContainer rc) {
if (rc == null) {
return;
}
List<ResContainer> subFiles = rc.getSubFiles();
if (subFiles.isEmpty()) {
save(rc, outDir);
} else {
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
saveToFile(rc, new File(outDir, "res/values/public.xml"));
for (ResContainer subFile : subFiles) {
for (ResContainer subFile : rc.getSubFiles()) {
saveResources(subFile);
}
} else {
save(rc, outDir);
}
}
private void save(ResContainer rc, File outDir) {
File outFile = new File(outDir, rc.getFileName());
BufferedImage image = rc.getImage();
if (image != null) {
String ext = FilenameUtils.getExtension(outFile.getName());
try {
outFile = prepareFile(outFile);
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
LOG.error("Path traversal attack detected, invalid resource name: {}",
outFile.getPath());
return;
}
ImageIO.write(image, ext, outFile);
} catch (IOException e) {
LOG.error("Failed to save image: {}", rc.getName(), e);
}
return;
}
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
LOG.error("Path traversal attack detected, invalid resource name: {}",
rc.getFileName());
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
return;
}
saveToFile(rc, outFile);
}
private void saveToFile(ResContainer rc, File outFile) {
CodeWriter cw = rc.getContent();
if (cw != null) {
cw.save(outFile);
return;
}
InputStream binary = rc.getBinary();
if (binary != null) {
try {
switch (rc.getDataType()) {
case TEXT:
case RES_TABLE:
CodeWriter cw = rc.getText();
cw.save(outFile);
return;
case DECODED_DATA:
byte[] data = rc.getDecodedData();
FileUtils.makeDirsForFile(outFile);
try {
Files.write(outFile.toPath(), data);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
}
return;
case RES_LINK:
ResourceFile resFile = rc.getResLink();
FileUtils.makeDirsForFile(outFile);
try (FileOutputStream binaryFileStream = new FileOutputStream(outFile)) {
IOUtils.copy(binary, binaryFileStream);
} finally {
binary.close();
try {
saveResourceFile(resFile, outFile);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
}
return;
default:
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
break;
}
}
private void saveResourceFile(ResourceFile resFile, File outFile) throws JadxException {
ResourcesLoader.decodeStream(resFile, (size, is) -> {
try (FileOutputStream fileStream = new FileOutputStream(outFile)) {
IOUtils.copy(is, fileStream);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
throw new JadxRuntimeException("Resource file save error", e);
}
return;
}
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
return null;
});
}
}
......@@ -10,6 +10,7 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
......@@ -105,7 +106,7 @@ public class JadxWrapper {
return openFile;
}
public JadxSettings getSettings() {
return settings;
public JadxArgs getArgs() {
return decompiler.getArgs();
}
}
......@@ -2,6 +2,7 @@ package jadx.gui.treemodel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -11,14 +12,13 @@ import org.jetbrains.annotations.NotNull;
import jadx.api.ResourceFile;
import jadx.api.ResourceFileContent;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.utils.NLS;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.Utils;
import static jadx.api.ResourceFileContent.createResourceFileContentInstance;
public class JResource extends JLoadableNode implements Comparable<JResource> {
private static final long serialVersionUID = -201018424302612434L;
......@@ -43,7 +43,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
private transient boolean loaded;
private transient String content;
private transient Map<Integer, Integer> lineMapping;
private transient Map<Integer, Integer> lineMapping = Collections.emptyMap();
public JResource(ResourceFile resFile, String name, JResType type) {
this(resFile, name, name, type);
......@@ -58,15 +58,16 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
public final void update() {
removeAllChildren();
if (!loaded) {
if (files.isEmpty()) {
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
add(new TextNode(NLS.str("tree.loading")));
}
} else {
loadContent();
removeAllChildren();
for (JResource res : files) {
res.update();
add(res);
......@@ -76,13 +77,8 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
@Override
public void loadNode() {
loadContent();
loaded = true;
update();
}
private void loadContent() {
getContent();
update();
}
@Override
......@@ -95,40 +91,68 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
@Override
public String getContent() {
if (!loaded && resFile != null && type == JResType.FILE) {
loaded = true;
if (isSupportedForView(resFile.getType())) {
ResContainer rc = resFile.loadContent();
if (rc != null) {
addSubFiles(rc, this, 0);
}
public synchronized String getContent() {
if (loaded) {
return content;
}
if (resFile == null || type != JResType.FILE) {
return null;
}
if (!isSupportedForView(resFile.getType())) {
return null;
}
ResContainer rc = resFile.loadContent();
if (rc == null) {
return null;
}
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
content = loadCurrentSingleRes(rc);
for (ResContainer subFile : rc.getSubFiles()) {
loadSubNodes(this, subFile, 1);
}
loaded = true;
return content;
}
return content;
// single node
return loadCurrentSingleRes(rc);
}
private 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 resName = rc.getName();
String[] path = resName.split("/");
String resShortName = path.length == 0 ? resName : path[path.length - 1];
ResourceFileContent fileContent = createResourceFileContentInstance(resShortName, ResourceType.XML, cw);
if (fileContent != null) {
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
private String loadCurrentSingleRes(ResContainer rc) {
switch (rc.getDataType()) {
case TEXT:
case RES_TABLE:
CodeWriter cw = rc.getText();
lineMapping = cw.getLineMapping();
return cw.toString();
case RES_LINK:
try {
return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
if (size > 10 * 1024 * 1024L) {
return "File too large for view";
}
return ResourcesLoader.loadToCodeWriter(is).toString();
});
} catch (Exception e) {
return "Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e);
}
}
case DECODED_DATA:
default:
return "Unexpected resource type: " + rc;
}
List<ResContainer> subFiles = rc.getSubFiles();
if (!subFiles.isEmpty()) {
for (ResContainer subFile : subFiles) {
addSubFiles(subFile, root, depth + 1);
}
}
private void loadSubNodes(JResource root, ResContainer rc, int depth) {
String resName = rc.getName();
String[] path = resName.split("/");
String resShortName = path.length == 0 ? resName : path[path.length - 1];
CodeWriter cw = rc.getText();
ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, cw);
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
for (ResContainer subFile : rc.getSubFiles()) {
loadSubNodes(root, subFile, depth + 1);
}
}
......@@ -190,25 +214,29 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
}
private static final Map<String, String> EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap(
"java", SyntaxConstants.SYNTAX_STYLE_JAVA,
"js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT,
"ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT,
"json", SyntaxConstants.SYNTAX_STYLE_JSON,
"css", SyntaxConstants.SYNTAX_STYLE_CSS,
"less", SyntaxConstants.SYNTAX_STYLE_LESS,
"html", SyntaxConstants.SYNTAX_STYLE_HTML,
"xml", SyntaxConstants.SYNTAX_STYLE_XML,
"yaml", SyntaxConstants.SYNTAX_STYLE_YAML,
"properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE,
"ini", SyntaxConstants.SYNTAX_STYLE_INI,
"sql", SyntaxConstants.SYNTAX_STYLE_SQL,
"arsc", SyntaxConstants.SYNTAX_STYLE_XML
);
private String getSyntaxByExtension(String name) {
int dot = name.lastIndexOf('.');
if (dot == -1) {
return null;
}
String ext = name.substring(dot + 1);
if (ext.equals("js")) {
return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
}
if (ext.equals("css")) {
return SyntaxConstants.SYNTAX_STYLE_CSS;
}
if (ext.equals("html")) {
return SyntaxConstants.SYNTAX_STYLE_HTML;
}
if (ext.equals("arsc")) {
return SyntaxConstants.SYNTAX_STYLE_XML;
}
return null;
return EXTENSION_TO_FILE_SYNTAX.get(ext);
}
@Override
......@@ -256,6 +284,10 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
return resFile;
}
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
}
@Override
public JClass getJParent() {
return null;
......
......@@ -4,11 +4,17 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
public class HeapUsageBar extends JProgressBar implements ActionListener {
private static final Logger LOG = LoggerFactory.getLogger(HeapUsageBar.class);
private static final long serialVersionUID = -8739563124249884967L;
private static final double TWO_TO_20 = 1048576d;
......@@ -32,6 +38,16 @@ public class HeapUsageBar extends JProgressBar implements ActionListener {
maxGB = maxKB / TWO_TO_20;
update();
timer = new Timer(2000, this);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Runtime.getRuntime().gc();
update();
if (LOG.isDebugEnabled()) {
LOG.debug("Memory used: {}", Utils.memoryInfo());
}
}
});
}
public void update() {
......
package jadx.gui.ui;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import hu.kazocsaba.imageviewer.ImageViewer;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.codearea.CodeArea;
public class ImagePanel extends ContentPanel {
private static final long serialVersionUID = 4071356367073142688L;
ImagePanel(TabbedPane panel, JResource res) {
super(panel, res);
setLayout(new BorderLayout());
try {
BufferedImage img = loadImage(res);
ImageViewer imageViewer = new ImageViewer(img);
add(imageViewer.getComponent());
} catch (Exception e) {
RSyntaxTextArea textArea = CodeArea.getDefaultArea(panel.getMainWindow());
textArea.setText("Image load error: \n" + Utils.getStackTrace(e));
add(textArea);
}
}
private BufferedImage loadImage(JResource res) {
ResourceFile resFile = res.getResFile();
BufferedImage img = resFile.loadContent().getImage();
ImageViewer imageViewer = new ImageViewer(img);
imageViewer.setZoomFactor(2.);
setLayout(new BorderLayout());
add(imageViewer.getComponent());
ResContainer resContainer = resFile.loadContent();
ResContainer.DataType dataType = resContainer.getDataType();
if (dataType == ResContainer.DataType.DECODED_DATA) {
try {
return ImageIO.read(new ByteArrayInputStream(resContainer.getDecodedData()));
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load image", e);
}
} else if (dataType == ResContainer.DataType.RES_LINK) {
try {
return ResourcesLoader.decodeStream(resFile, (size, is) -> ImageIO.read(is));
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load image", e);
}
} else {
throw new JadxRuntimeException("Unsupported resource image data type: " + resFile);
}
}
@Override
......
......@@ -31,6 +31,7 @@ import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundWorker;
......@@ -221,12 +222,6 @@ public class MainWindow extends JFrame {
}
private void saveAll(boolean export) {
settings.setExportAsGradleProject(export);
if (export) {
settings.setSkipSources(false);
settings.setSkipResources(false);
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
......@@ -238,6 +233,15 @@ public class MainWindow extends JFrame {
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
if (ret == JFileChooser.APPROVE_OPTION) {
JadxArgs decompilerArgs = wrapper.getArgs();
decompilerArgs.setExportAsGradleProject(export);
if (export) {
decompilerArgs.setSkipSources(false);
decompilerArgs.setSkipResources(false);
} else {
decompilerArgs.setSkipSources(settings.isSkipSources());
decompilerArgs.setSkipResources(settings.isSkipResources());
}
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
progressMonitor.setMillisToPopup(0);
......@@ -289,6 +293,9 @@ public class MainWindow extends JFrame {
private void treeClickAction() {
try {
Object obj = tree.getLastSelectedPathComponent();
if (obj == null) {
return;
}
if (obj instanceof JResource) {
JResource res = (JResource) obj;
ResourceFile resFile = res.getResFile();
......
......@@ -6,13 +6,14 @@ import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.TabbedPane;
import jadx.gui.utils.Utils;
public final class CodePanel extends ContentPanel {
private static final long serialVersionUID = 5310536092010045565L;
private final SearchBar searchBar;
......@@ -24,7 +25,6 @@ public final class CodePanel extends ContentPanel {
codeArea = new CodeArea(this);
searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea);
initLineNumbers();
......@@ -37,7 +37,23 @@ public final class CodePanel extends ContentPanel {
}
private void initLineNumbers() {
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
// TODO: fix slow line rendering on big files
if (codeArea.getDocument().getLength() <= 100_000) {
LineNumbers numbers = new LineNumbers(codeArea);
numbers.setUseSourceLines(isUseSourceLines());
scrollPane.setRowHeaderView(numbers);
}
}
private boolean isUseSourceLines() {
if (node instanceof JClass) {
return true;
}
if (node instanceof JResource) {
JResource resNode = (JResource) node;
return !resNode.getLineMapping().isEmpty();
}
return false;
}
private class SearchAction extends AbstractAction {
......
......@@ -219,4 +219,8 @@ public class LineNumbers extends JPanel implements CaretListener {
lastLine = currentLine;
}
}
public void setUseSourceLines(boolean useSourceLines) {
this.useSourceLines = useSourceLines;
}
}
......@@ -135,18 +135,15 @@ public class Utils {
public static String memoryInfo() {
Runtime runtime = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
long maxMemory = runtime.maxMemory();
long allocatedMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
sb.append("heap: ").append(format(allocatedMemory - freeMemory));
sb.append(", allocated: ").append(format(allocatedMemory));
sb.append(", free: ").append(format(freeMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
sb.append(", max: ").append(format(maxMemory));
return sb.toString();
return "heap: " + format(allocatedMemory - freeMemory) +
", allocated: " + format(allocatedMemory) +
", free: " + format(freeMemory) +
", total free: " + format(freeMemory + maxMemory - allocatedMemory) +
", max: " + format(maxMemory);
}
private static String format(long mem) {
......
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