Skip to content

Commit

Permalink
feat(gui): export resource/class/package (PR #2228)
Browse files Browse the repository at this point in the history
* feat: export resource

* feat: export class

* restructure code: introduce enum for exporting classes

* feat: export package

* feat: export resource folder

* check directory exists before creation

* apply code formatting

* fix code formatting

* Apply suggestions from code review

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
  • Loading branch information
Mino260806 and skylot authored Jul 31, 2024
1 parent c179bee commit b26abed
Show file tree
Hide file tree
Showing 19 changed files with 405 additions and 6 deletions.
4 changes: 2 additions & 2 deletions jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import jadx.core.dex.nodes.ICodeNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.popupmenu.JClassPopupMenu;
import jadx.gui.ui.tab.TabbedPane;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.Icons;
Expand Down Expand Up @@ -130,7 +130,7 @@ public String getSyntaxName() {

@Override
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
return RenameDialog.buildRenamePopup(mainWindow, this);
return new JClassPopupMenu(mainWindow, this);
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;

import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.Nullable;
Expand All @@ -19,10 +20,12 @@
import jadx.core.utils.ListUtils;
import jadx.core.utils.Utils;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.BinaryContentPanel;
import jadx.gui.ui.codearea.CodeContentPanel;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.panel.ImagePanel;
import jadx.gui.ui.popupmenu.JResourcePopupMenu;
import jadx.gui.ui.tab.TabbedPane;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
Expand Down Expand Up @@ -140,6 +143,11 @@ private static void sortResNodes(List<JResource> nodes) {
return new CodeContentPanel(tabbedPane, this);
}

@Override
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
return new JResourcePopupMenu(mainWindow, this);
}

@Override
public synchronized ICodeInfo getCodeInfo() {
if (loaded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ public ICodeInfo getCodeInfo() {
public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_JAVA;
}

@Override
public String getName() {
return jCls.getName();
}
}
11 changes: 8 additions & 3 deletions jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,17 @@ public static boolean rename(MainWindow mainWindow, JRenameNode node) {
}

public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JRenameNode node) {
JPopupMenu menu = new JPopupMenu();
menu.add(buildRenamePopupMenuItem(mainWindow, node));
return menu;
}

public static JMenuItem buildRenamePopupMenuItem(MainWindow mainWindow, JRenameNode node) {
JMenuItem jmi = new JMenuItem(NLS.str("popup.rename"));
jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node));
jmi.setEnabled(node.canRename());
JPopupMenu menu = new JPopupMenu();
menu.add(jmi);
return menu;

return jmi;
}

private RenameDialog(MainWindow mainWindow, JRenameNode node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ private void initForMode(FileOpenMode mode) {
isOpen = true;
currentDir = mainWindow.getSettings().getLastOpenFilePath();
break;

case EXPORT_NODE:
isOpen = false;
title = NLS.str("file.export_node");
currentDir = mainWindow.getSettings().getLastSaveFilePath();
selectionMode = JFileChooser.FILES_ONLY;
break;

case EXPORT_NODE_FOLDER:
isOpen = true;
title = NLS.str("file.save_all_msg");
currentDir = mainWindow.getSettings().getLastSaveFilePath();
selectionMode = JFileChooser.DIRECTORIES_ONLY;
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ public enum FileOpenMode {
SAVE_PROJECT,
EXPORT,
CUSTOM_SAVE,
CUSTOM_OPEN
CUSTOM_OPEN,
EXPORT_NODE,
EXPORT_NODE_FOLDER,
}
14 changes: 14 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassExportType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package jadx.gui.ui.popupmenu;

public enum JClassExportType {
Code("java"),
Smali("smali"),
Simple("java"),
Fallback("java");

final String extension;

JClassExportType(String extension) {
this.extension = extension;
}
}
111 changes: 111 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassPopupMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package jadx.gui.ui.popupmenu;

import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.api.DecompilationMode;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.mode.JCodeMode;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.ui.filedialog.FileDialogWrapper;
import jadx.gui.ui.filedialog.FileOpenMode;
import jadx.gui.utils.NLS;

public class JClassPopupMenu extends JPopupMenu {
private static final long serialVersionUID = -7781009781149260806L;

private static final Logger LOG = LoggerFactory.getLogger(JClassPopupMenu.class);

private final transient MainWindow mainWindow;

public JClassPopupMenu(MainWindow mainWindow, JClass jClass) {
this.mainWindow = mainWindow;

add(RenameDialog.buildRenamePopupMenuItem(mainWindow, jClass));
add(makeExportSubMenu(jClass));
}

private JMenuItem makeExportSubMenu(JClass jClass) {
JMenu exportSubMenu = new JMenu(NLS.str("popup.export"));

exportSubMenu.add(makeExportMenuItem(jClass, NLS.str("tabs.code"), JClassExportType.Code));
exportSubMenu.add(makeExportMenuItem(jClass, NLS.str("tabs.smali"), JClassExportType.Smali));
exportSubMenu.add(makeExportMenuItem(jClass, "Simple", JClassExportType.Simple));
exportSubMenu.add(makeExportMenuItem(jClass, "Fallback", JClassExportType.Fallback));

return exportSubMenu;
}

public JMenuItem makeExportMenuItem(JClass jClass, String label, JClassExportType exportType) {
JMenuItem exportMenuItem = new JMenuItem(label);
exportMenuItem.addActionListener(event -> {
String fileName = jClass.getName() + "." + exportType.extension;

FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.EXPORT_NODE);
fileDialog.setFileExtList(Collections.singletonList(exportType.extension));
Path currentDir = fileDialog.getCurrentDir();
if (currentDir != null) {
fileDialog.setSelectedFile(currentDir.resolve(fileName));
}

List<Path> selectedPaths = fileDialog.show();
if (selectedPaths.size() != 1) {
return;
}

Path selectedPath = selectedPaths.get(0);
Path savePath;
// Append file extension if missing
if (!selectedPath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(exportType.extension)) {
savePath = selectedPath.resolveSibling(selectedPath.getFileName() + "." + exportType.extension);
} else {
savePath = selectedPath;
}

saveJClass(jClass, savePath, exportType);

LOG.info("Done saving {}", savePath);
});

return exportMenuItem;
}

public static void saveJClass(JClass jClass, Path savePath, JClassExportType exportType) {
try (Writer writer = Files.newBufferedWriter(savePath, StandardCharsets.UTF_8)) {
writer.write(getCode(jClass, exportType));
} catch (Exception e) {
throw new RuntimeException("Error saving project", e);
}
}

private static String getCode(JClass jClass, JClassExportType exportType) {
switch (exportType) {
case Code:
return jClass.getCodeInfo().getCodeStr();
case Smali:
return jClass.getSmali();
case Simple:
JNode jClassSimple = new JCodeMode(jClass, DecompilationMode.SIMPLE);
return jClassSimple.getCodeInfo().getCodeStr();
case Fallback:
JNode jClassFallback = new JCodeMode(jClass, DecompilationMode.FALLBACK);
return jClassFallback.getCodeInfo().getCodeStr();
default:
throw new RuntimeException("Unsupported JClassExportType " + exportType);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package jadx.gui.ui.popupmenu;

import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import javax.swing.AbstractAction;
Expand All @@ -13,10 +16,13 @@
import org.slf4j.LoggerFactory;

import jadx.gui.JadxWrapper;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JPackage;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.ExcludePkgDialog;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.ui.filedialog.FileDialogWrapper;
import jadx.gui.ui.filedialog.FileOpenMode;
import jadx.gui.utils.NLS;
import jadx.gui.utils.pkgs.JRenamePackage;
import jadx.gui.utils.pkgs.PackageHelper;
Expand All @@ -34,6 +40,7 @@ public JPackagePopupMenu(MainWindow mainWindow, JPackage pkg) {
add(makeExcludeItem(pkg));
add(makeExcludeItem());
add(makeRenameMenuItem(pkg));
add(makeExportSubMenu(pkg));
}

private JMenuItem makeRenameMenuItem(JPackage pkg) {
Expand Down Expand Up @@ -69,6 +76,52 @@ private JMenuItem makeExcludeItem(JPackage pkg) {
return excludeItem;
}

private JMenuItem makeExportSubMenu(JPackage pkg) {
JMenu exportSubMenu = new JMenu(NLS.str("popup.export"));

exportSubMenu.add(makeExportMenuItem(pkg, NLS.str("tabs.code"), JClassExportType.Code));
exportSubMenu.add(makeExportMenuItem(pkg, NLS.str("tabs.smali"), JClassExportType.Smali));
exportSubMenu.add(makeExportMenuItem(pkg, "Simple", JClassExportType.Simple));
exportSubMenu.add(makeExportMenuItem(pkg, "Fallback", JClassExportType.Fallback));

return exportSubMenu;
}

public JMenuItem makeExportMenuItem(JPackage pkg, String label, JClassExportType exportType) {
JMenuItem exportMenuItem = new JMenuItem(label);
exportMenuItem.addActionListener(event -> {
FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.EXPORT_NODE_FOLDER);

List<Path> selectedPaths = fileDialog.show();
if (selectedPaths.size() != 1) {
return;
}

Path savePath = selectedPaths.get(0);
saveJPackage(pkg, savePath, exportType);
});

return exportMenuItem;
}

private static void saveJPackage(JPackage pkg, Path savePath, JClassExportType exportType) {
Path subSavePath = savePath.resolve(pkg.getName());
try {
if (!Files.isDirectory(subSavePath)) {
Files.createDirectory(subSavePath);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
for (JClass jClass : pkg.getClasses()) {
String fileName = jClass.getName() + "." + exportType.extension;
JClassPopupMenu.saveJClass(jClass, subSavePath.resolve(fileName), exportType);
}
for (JPackage subPkg : pkg.getSubPackages()) {
saveJPackage(subPkg, subSavePath, exportType);
}
}

private JMenuItem makeExcludeItem() {
return new JMenuItem(new AbstractAction(NLS.str("popup.exclude_packages")) {
private static final long serialVersionUID = -1111111202104151028L;
Expand Down
Loading

0 comments on commit b26abed

Please # to comment.