diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index f1089ce5a..dca6acf63 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -1,8 +1,6 @@ package jadx.api; import java.io.File; -import java.io.StringWriter; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -16,13 +14,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; -import org.jf.baksmali.Adaptors.ClassDefinition; -import org.jf.baksmali.BaksmaliOptions; -import org.jf.dexlib2.DexFileFactory; -import org.jf.dexlib2.Opcodes; -import org.jf.dexlib2.dexbacked.DexBackedClassDef; -import org.jf.dexlib2.dexbacked.DexBackedDexFile; -import org.jf.util.IndentingWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +26,6 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.SaveCode; import jadx.core.export.ExportGradleProject; -import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.InputFile; import jadx.core.xmlgen.BinaryXMLParser; @@ -291,30 +281,6 @@ public final class JadxDecompiler { root.getErrorsCounter().printReport(); } - void generateSmali(ClassNode cls) { - Path path = cls.dex().getDexFile().getPath(); - String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject()); - try { - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault()); - boolean decompiled = false; - for (DexBackedClassDef classDef : dexFile.getClasses()) { - if (classDef.getType().equals(className)) { - ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef); - StringWriter sw = new StringWriter(); - classDefinition.writeTo(new IndentingWriter(sw)); - cls.setSmali(sw.toString()); - decompiled = true; - break; - } - } - if (!decompiled) { - LOG.error("Failed to find smali class {}", className); - } - } catch (Exception e) { - LOG.error("Error generating smali", e); - } - } - RootNode getRoot() { return root; } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index faa8d5a1c..4b31a41e4 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -59,12 +59,6 @@ public final class JavaClass implements JavaNode { } public synchronized String getSmali() { - if (decompiler == null) { - return null; - } - if (cls.getSmali() == null) { - decompiler.generateSmali(cls); - } return cls.getSmali(); } diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 12ff1fa35..6aa99fc4c 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -111,9 +111,8 @@ public final class ResourcesLoader { 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 { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + Res9patchStreamDecoder decoder = new Res9patchStreamDecoder(); decoder.decode(inputStream, os); return ResContainer.decodedData(rf.getName(), os.toByteArray()); } catch (Exception e) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index eb916d77e..1e20d5167 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -35,6 +35,7 @@ import jadx.core.dex.nodes.parser.AnnotationsParser; import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; +import jadx.core.utils.SmaliUtils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -45,6 +46,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final DexNode dex; + private final int clsDefOffset; private final ClassInfo clsInfo; private AccessInfo accessFlags; private ArgType superClass; @@ -68,6 +70,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { public ClassNode(DexNode dex, ClassDef cls) { this.dex = dex; + this.clsDefOffset = cls.getOffset(); this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); try { if (cls.getSupertypeIndex() == DexNode.NO_INDEX) { @@ -135,6 +138,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { // empty synthetic class public ClassNode(DexNode dex, String name, int accessFlags) { this.dex = dex; + this.clsDefOffset = 0; this.clsInfo = ClassInfo.fromName(dex.root(), name); this.interfaces = new ArrayList<>(); this.methods = new ArrayList<>(); @@ -495,11 +499,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { return clsInfo.getAliasPkg(); } - public void setSmali(String smali) { - this.smali = smali; - } - public String getSmali() { + if (smali == null) { + smali = SmaliUtils.getSmaliCode(dex, clsDefOffset); + } return smali; } diff --git a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java new file mode 100644 index 000000000..c3a454849 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java @@ -0,0 +1,57 @@ +package jadx.core.utils; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; + +import org.jetbrains.annotations.NotNull; +import org.jf.baksmali.Adaptors.ClassDefinition; +import org.jf.baksmali.BaksmaliOptions; +import org.jf.dexlib2.DexFileFactory; +import org.jf.dexlib2.dexbacked.DexBackedClassDef; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.smali.Smali; +import org.jf.smali.SmaliOptions; +import org.jf.util.IndentingWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.dex.nodes.DexNode; +import jadx.core.utils.exceptions.JadxRuntimeException; + +// TODO: move smali dependencies out from jadx-core +public class SmaliUtils { + + private static final Logger LOG = LoggerFactory.getLogger(SmaliUtils.class); + + public static void assembleDex(String outputDexFile, String inputSmali) { + try { + SmaliOptions options = new SmaliOptions(); + options.outputDexFile = outputDexFile; + Smali.assemble(options, inputSmali); + } catch (Exception e) { + throw new JadxRuntimeException("Smali assemble error", e); + } + } + + @NotNull + public static String getSmaliCode(DexNode dex, int clsDefOffset) { + try { + Path path = dex.getDexFile().getPath(); + DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null); + DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset); + return getSmaliCode(dexBackedClassDef); + } catch (Exception e) { + LOG.error("Error generating smali", e); + return "Error generating smali code: " + e.getMessage() + + '\n' + Utils.getStackTrace(e); + } + } + + private static String getSmaliCode(DexBackedClassDef classDef) throws IOException { + ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef); + StringWriter sw = new StringWriter(); + classDefinition.writeTo(new IndentingWriter(sw)); + return sw.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/android/Res9patchStreamDecoder.java b/jadx-core/src/main/java/jadx/core/utils/android/Res9patchStreamDecoder.java index 1be0be7b7..3c0c5db85 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/Res9patchStreamDecoder.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/Res9patchStreamDecoder.java @@ -17,7 +17,6 @@ package jadx.core.utils.android; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.IOException; import java.io.InputStream; @@ -25,27 +24,23 @@ import java.io.OutputStream; import javax.imageio.ImageIO; -import com.google.common.io.ByteStreams; - -import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; /** * @author Ryszard Wiśniewski */ public class Res9patchStreamDecoder { - public void decode(InputStream in, OutputStream out) throws JadxException { + public void decode(InputStream in, OutputStream out) { try { - byte[] data = ByteStreams.toByteArray(in); - - BufferedImage im = ImageIO.read(new ByteArrayInputStream(data)); + BufferedImage im = ImageIO.read(in); int w = im.getWidth(); int h = im.getHeight(); BufferedImage im2 = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB); im2.createGraphics().drawImage(im, 1, 1, w, h, null); - NinePatch np = getNinePatch(data); + NinePatch np = getNinePatch(in); drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight); drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom); @@ -60,25 +55,25 @@ public class Res9patchStreamDecoder { } ImageIO.write(im2, "png", out); - } catch (IOException | NullPointerException ex) { - throw new JadxException(ex.toString()); + } catch (Exception e) { + throw new JadxRuntimeException("9patch image decode error", e); } } - private NinePatch getNinePatch(byte[] data) throws JadxException, IOException { - ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data)); + private NinePatch getNinePatch(InputStream in) throws IOException { + ExtDataInput di = new ExtDataInput(in); find9patchChunk(di); return NinePatch.decode(di); } - private void find9patchChunk(DataInput di) throws JadxException, IOException { + private void find9patchChunk(DataInput di) throws IOException { di.skipBytes(8); while (true) { int size; try { size = di.readInt(); } catch (IOException ex) { - throw new JadxException("Cant find nine patch chunk", ex); + throw new JadxRuntimeException("Cant find nine patch chunk", ex); } if (di.readInt() == NP_CHUNK_TYPE) { return; diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index bec2aa1be..75c15299d 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -1,6 +1,7 @@ package jadx.core.utils.files; import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; @@ -122,6 +123,13 @@ public class FileUtils { } } + public static byte[] streamToByteArray(InputStream input) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + copyStream(input, out); + return out.toByteArray(); + } + } + public static void close(Closeable c) { if (c == null) { return; diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java index 0fc6072ac..6b1cf9ee2 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java @@ -14,8 +14,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.jetbrains.annotations.Nullable; -import org.jf.smali.Smali; -import org.jf.smali.SmaliOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +21,7 @@ import com.android.dex.Dex; import com.android.dex.DexException; import jadx.core.utils.AsmUtils; +import jadx.core.utils.SmaliUtils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -58,9 +57,7 @@ public class InputFile { } if (fileName.endsWith(".smali")) { Path output = FileUtils.createTempFile(".dex"); - SmaliOptions options = new SmaliOptions(); - options.outputDexFile = output.toAbsolutePath().toString(); - Smali.assemble(options, file.getAbsolutePath()); + SmaliUtils.assembleDex(output.toAbsolutePath().toString(), file.getAbsolutePath()); addDexFile(fileName, output); return; }