@@ -13,7 +13,7 @@ dependencies {
|
||||
}
|
||||
compile 'com.google.guava:guava:27.1-jre'
|
||||
|
||||
testCompile 'org.smali:baksmali:2.2.7'
|
||||
compile 'org.smali:baksmali:2.2.7'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.baksmali.Baksmali;
|
||||
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.dexlib2.iface.ClassDef;
|
||||
import org.jf.util.IndentingWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -26,6 +44,8 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
@@ -290,6 +310,31 @@ public final class JadxDecompiler {
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
void generateSmali(ClassNode cls) {
|
||||
Path path = cls.dex().getDexFile().getPath();
|
||||
String className = cls.getAlias().makeRawFullName();
|
||||
className = 'L' + className.replace('.', '/') + ';';
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
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 (IOException e) {
|
||||
LOG.error("Error generating smali", e);
|
||||
}
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,16 @@ 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();
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dex.ClassData;
|
||||
import com.android.dex.ClassData.Field;
|
||||
import com.android.dex.ClassData.Method;
|
||||
import com.android.dex.ClassDef;
|
||||
import com.android.dex.Dex;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
@@ -35,8 +38,6 @@ import jadx.core.dex.nodes.parser.StaticValuesParser;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
@@ -53,6 +54,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
|
||||
// store decompiled code
|
||||
private CodeWriter code;
|
||||
// store smali
|
||||
private String smali;
|
||||
// store parent for inner classes or 'this' otherwise
|
||||
private ClassNode parentClass;
|
||||
|
||||
@@ -482,6 +485,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setSmali(String smali) {
|
||||
this.smali = smali;
|
||||
}
|
||||
|
||||
public String getSmali() {
|
||||
return smali;
|
||||
}
|
||||
|
||||
public ProcessState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package jadx.core.utils.files;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.android.dex.Dex;
|
||||
|
||||
public class DexFile {
|
||||
private final InputFile inputFile;
|
||||
private final String name;
|
||||
private final Dex dexBuf;
|
||||
private final Path path;
|
||||
|
||||
public DexFile(InputFile inputFile, String name, Dex dexBuf) {
|
||||
public DexFile(InputFile inputFile, String name, Dex dexBuf, Path path) {
|
||||
this.inputFile = inputFile;
|
||||
this.name = name;
|
||||
this.dexBuf = dexBuf;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -21,6 +25,10 @@ public class DexFile {
|
||||
return dexBuf;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public InputFile getInputFile() {
|
||||
return inputFile;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class InputFile {
|
||||
String fileName = file.getName();
|
||||
|
||||
if (fileName.endsWith(".dex")) {
|
||||
addDexFile(new Dex(file));
|
||||
addDexFile(fileName, new Dex(file), file.toPath());
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".smali")) {
|
||||
@@ -62,12 +62,12 @@ public class InputFile {
|
||||
SmaliOptions options = new SmaliOptions();
|
||||
options.outputDexFile = output.toAbsolutePath().toString();
|
||||
Smali.assemble(options, file.getAbsolutePath());
|
||||
addDexFile(new Dex(output.toFile()));
|
||||
addDexFile("", new Dex(output.toFile()), output);
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".class")) {
|
||||
for (Dex dex : loadFromClassFile(file)) {
|
||||
addDexFile(dex);
|
||||
for (Path path : loadFromClassFile(file)) {
|
||||
addDexFile(path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -81,8 +81,8 @@ public class InputFile {
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".jar")) {
|
||||
for (Dex dex : loadFromJar(file.toPath())) {
|
||||
addDexFile(dex);
|
||||
for (Path path : loadFromJar(file.toPath())) {
|
||||
addDexFile(path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -98,12 +98,16 @@ public class InputFile {
|
||||
LOG.warn("No dex files found in {}", file);
|
||||
}
|
||||
|
||||
private void addDexFile(Dex dexBuf) {
|
||||
addDexFile("", dexBuf);
|
||||
private void addDexFile(Path path) throws IOException {
|
||||
addDexFile("", path);
|
||||
}
|
||||
|
||||
private void addDexFile(String fileName, Dex dexBuf) {
|
||||
dexFiles.add(new DexFile(this, fileName, dexBuf));
|
||||
private void addDexFile(String fileName, Path path) throws IOException {
|
||||
addDexFile(fileName, new Dex(Files.readAllBytes(path)), path);
|
||||
}
|
||||
|
||||
private void addDexFile(String fileName, Dex dexBuf, Path path) {
|
||||
dexFiles.add(new DexFile(this, fileName, dexBuf, path));
|
||||
}
|
||||
|
||||
private boolean loadFromZip(String ext) throws IOException, DecodeException {
|
||||
@@ -125,9 +129,9 @@ public class InputFile {
|
||||
|| entryName.endsWith(instantRunDexSuffix)) {
|
||||
switch (ext) {
|
||||
case ".dex":
|
||||
Dex dexBuf = makeDexBuf(entryName, inputStream);
|
||||
if (dexBuf != null) {
|
||||
addDexFile(entryName, dexBuf);
|
||||
Path path = makeDexBuf(entryName, inputStream);
|
||||
if (path != null) {
|
||||
addDexFile(entryName, path);
|
||||
index++;
|
||||
}
|
||||
break;
|
||||
@@ -136,8 +140,8 @@ public class InputFile {
|
||||
index++;
|
||||
Path jarFile = FileUtils.createTempFile(entryName);
|
||||
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
for (Dex dex : loadFromJar(jarFile)) {
|
||||
addDexFile(entryName, dex);
|
||||
for (Path p : loadFromJar(jarFile)) {
|
||||
addDexFile(entryName, p);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -164,28 +168,26 @@ public class InputFile {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Dex makeDexBuf(String entryName, InputStream inputStream) {
|
||||
private Path makeDexBuf(String entryName, InputStream inputStream) {
|
||||
try {
|
||||
return new Dex(inputStream);
|
||||
Path path = FileUtils.createTempFile(".dex");
|
||||
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
|
||||
return path;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Dex> loadFromJar(Path jar) throws DecodeException {
|
||||
private static List<Path> loadFromJar(Path jar) throws DecodeException {
|
||||
JavaToDex j2d = new JavaToDex();
|
||||
try {
|
||||
LOG.info("converting to dex: {} ...", jar.getFileName());
|
||||
List<byte[]> byteList = j2d.convert(jar);
|
||||
if (byteList.isEmpty()) {
|
||||
List<Path> pathList = j2d.convert(jar);
|
||||
if (pathList.isEmpty()) {
|
||||
throw new JadxException("Empty dx output");
|
||||
}
|
||||
List<Dex> dexList = new ArrayList<>(byteList.size());
|
||||
for (byte[] b : byteList) {
|
||||
dexList.add(new Dex(b));
|
||||
}
|
||||
return dexList;
|
||||
return pathList;
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
|
||||
} finally {
|
||||
@@ -195,7 +197,7 @@ public class InputFile {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Dex> loadFromClassFile(File file) throws IOException, DecodeException {
|
||||
private static List<Path> loadFromClassFile(File file) throws IOException, DecodeException {
|
||||
Path outFile = FileUtils.createTempFile(".jar");
|
||||
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) {
|
||||
String clsName = AsmUtils.getNameFromClassFile(file);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class JavaToDex {
|
||||
|
||||
private String dxErrors;
|
||||
|
||||
public List<byte[]> convert(Path jar) throws JadxException {
|
||||
public List<Path> convert(Path jar) throws JadxException {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream errOut = new ByteArrayOutputStream()) {
|
||||
DxContext context = new DxContext(out, errOut);
|
||||
@@ -51,14 +51,14 @@ public class JavaToDex {
|
||||
if (result != 0) {
|
||||
throw new JadxException("Java to dex conversion error, code: " + result);
|
||||
}
|
||||
List<byte[]> list = new ArrayList<>();
|
||||
List<Path> list = new ArrayList<>();
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
|
||||
for (Path child : ds) {
|
||||
list.add(Files.readAllBytes(child));
|
||||
Files.delete(child);
|
||||
list.add(child);
|
||||
child.toFile().deleteOnExit();
|
||||
}
|
||||
}
|
||||
Files.delete(dir);
|
||||
dir.toFile().deleteOnExit();
|
||||
return list;
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("dx exception: " + e.getMessage(), e);
|
||||
|
||||
@@ -81,6 +81,11 @@ public class JClass extends JLoadableNode {
|
||||
return cls.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSmali() {
|
||||
return cls.getSmali();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
|
||||
@@ -28,6 +28,10 @@ public abstract class JNode extends DefaultMutableTreeNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSmali() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_NONE;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
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.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public final class CodePanel extends ContentPanel {
|
||||
@@ -17,22 +22,36 @@ public final class CodePanel extends ContentPanel {
|
||||
|
||||
private final SearchBar searchBar;
|
||||
private final CodeArea codeArea;
|
||||
private final JScrollPane scrollPane;
|
||||
private final SmaliArea smaliArea;
|
||||
private final JScrollPane codeScrollPane;
|
||||
private final JScrollPane smaliScrollPane;
|
||||
private JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
|
||||
|
||||
public CodePanel(TabbedPane panel, JNode jnode) {
|
||||
super(panel, jnode);
|
||||
|
||||
codeArea = new CodeArea(this);
|
||||
smaliArea = new SmaliArea(this);
|
||||
searchBar = new SearchBar(codeArea);
|
||||
scrollPane = new JScrollPane(codeArea);
|
||||
codeScrollPane = new JScrollPane(codeArea);
|
||||
smaliScrollPane = new JScrollPane(smaliArea);
|
||||
initLineNumbers();
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(searchBar, BorderLayout.NORTH);
|
||||
add(scrollPane);
|
||||
|
||||
areaTabbedPane.add(codeScrollPane, NLS.str("tabs.code"));
|
||||
areaTabbedPane.add(smaliScrollPane, NLS.str("tabs.smali"));
|
||||
add(areaTabbedPane);
|
||||
|
||||
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.ctrlButton());
|
||||
Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction());
|
||||
|
||||
areaTabbedPane.addChangeListener(e -> {
|
||||
if (areaTabbedPane.getSelectedComponent() == smaliScrollPane) {
|
||||
smaliArea.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initLineNumbers() {
|
||||
@@ -40,7 +59,7 @@ public final class CodePanel extends ContentPanel {
|
||||
if (codeArea.getDocument().getLength() <= 100_000) {
|
||||
LineNumbers numbers = new LineNumbers(codeArea);
|
||||
numbers.setUseSourceLines(isUseSourceLines());
|
||||
scrollPane.setRowHeaderView(numbers);
|
||||
codeScrollPane.setRowHeaderView(numbers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +108,4 @@ public final class CodePanel extends ContentPanel {
|
||||
return codeArea;
|
||||
}
|
||||
|
||||
JScrollPane getScrollPane() {
|
||||
return scrollPane;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public final class SmaliArea extends RSyntaxTextArea {
|
||||
private static final long serialVersionUID = 1334485631870306494L;
|
||||
|
||||
private final JNode node;
|
||||
|
||||
SmaliArea(CodePanel panel) {
|
||||
node = panel.getNode();
|
||||
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
void load() {
|
||||
if (getText().isEmpty()) {
|
||||
setText(node.getSmali());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name
|
||||
tabs.close=Close
|
||||
tabs.closeOthers=Close Others
|
||||
tabs.closeAll=Close All
|
||||
tabs.code=Code
|
||||
tabs.smali=Smali
|
||||
|
||||
nav.back=Back
|
||||
nav.forward=Forward
|
||||
|
||||
@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name
|
||||
tabs.close=Cerrar
|
||||
tabs.closeOthers=Cerrar otros
|
||||
tabs.closeAll=Cerrar todo
|
||||
#tabs.code=
|
||||
#tabs.smali=
|
||||
|
||||
nav.back=Atrás
|
||||
nav.forward=Adelante
|
||||
|
||||
@@ -44,6 +44,8 @@ tabs.copy_class_name=复制类名
|
||||
tabs.close=关闭
|
||||
tabs.closeOthers=关闭其他文件
|
||||
tabs.closeAll=全部关闭
|
||||
#tabs.code=
|
||||
#tabs.smali=
|
||||
|
||||
nav.back=后退
|
||||
nav.forward=前进
|
||||
|
||||
Reference in New Issue
Block a user