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)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
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,46 +79,50 @@ 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) {
|
||||
if (file == null) {
|
||||
return;
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,47 @@
|
||||
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 textResource(String name, CodeWriter content) {
|
||||
return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT);
|
||||
}
|
||||
|
||||
public static ResContainer singleFile(String name, CodeWriter content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
|
||||
resContainer.content = content;
|
||||
return resContainer;
|
||||
public static ResContainer decodedData(String name, byte[] data) {
|
||||
return new ResContainer(name, Collections.emptyList(), data, DataType.DECODED_DATA);
|
||||
}
|
||||
|
||||
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 resourceFileLink(ResourceFile resFile) {
|
||||
return new ResContainer(resFile.getName(), Collections.emptyList(), resFile, DataType.RES_LINK);
|
||||
}
|
||||
|
||||
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 resourceTable(String name, List<ResContainer> subFiles, CodeWriter rootContent) {
|
||||
return new ResContainer(name, subFiles, rootContent, DataType.RES_TABLE);
|
||||
}
|
||||
|
||||
public static ResContainer multiFile(String name) {
|
||||
return new ResContainer(name, new ArrayList<>());
|
||||
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,29 +52,26 @@ public class ResContainer implements Comparable<ResContainer> {
|
||||
return name.replace("/", File.separator);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodeWriter getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InputStream getBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
public void setContent(@Nullable CodeWriter content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public List<ResContainer> getSubFiles() {
|
||||
return subFiles;
|
||||
}
|
||||
|
||||
public DataType getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public CodeWriter getText() {
|
||||
return (CodeWriter) data;
|
||||
}
|
||||
|
||||
public byte[] getDecodedData() {
|
||||
return (byte[]) data;
|
||||
}
|
||||
|
||||
public ResourceFile getResLink() {
|
||||
return (ResourceFile) data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ResContainer o) {
|
||||
return name.compareTo(o.name);
|
||||
@@ -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;
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
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 (FileOutputStream binaryFileStream = new FileOutputStream(outFile)) {
|
||||
IOUtils.copy(binary, binaryFileStream);
|
||||
} finally {
|
||||
binary.close();
|
||||
try {
|
||||
Files.write(outFile.toPath(), data);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
|
||||
}
|
||||
return;
|
||||
return;
|
||||
|
||||
case RES_LINK:
|
||||
ResourceFile resFile = rc.getResLink();
|
||||
FileUtils.makeDirsForFile(outFile);
|
||||
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;
|
||||
}
|
||||
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new JadxRuntimeException("Resource file save error", e);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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() {
|
||||
|
||||
@@ -1,27 +1,57 @@
|
||||
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);
|
||||
|
||||
ResourceFile resFile = res.getResFile();
|
||||
BufferedImage img = resFile.loadContent().getImage();
|
||||
ImageViewer imageViewer = new ImageViewer(img);
|
||||
imageViewer.setZoomFactor(2.);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(imageViewer.getComponent());
|
||||
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();
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user