From b26abed6862c028e65e747090c107b8c1c693f0c Mon Sep 17 00:00:00 2001 From: Mino Date: Wed, 31 Jul 2024 17:00:54 +0100 Subject: [PATCH] feat(gui): export resource/class/package (PR #2228) * 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> --- .../main/java/jadx/gui/treemodel/JClass.java | 4 +- .../java/jadx/gui/treemodel/JResource.java | 8 + .../jadx/gui/ui/codearea/mode/JCodeMode.java | 5 + .../java/jadx/gui/ui/dialog/RenameDialog.java | 11 +- .../gui/ui/filedialog/FileDialogWrapper.java | 14 ++ .../jadx/gui/ui/filedialog/FileOpenMode.java | 4 +- .../gui/ui/popupmenu/JClassExportType.java | 14 ++ .../gui/ui/popupmenu/JClassPopupMenu.java | 111 ++++++++++++ .../gui/ui/popupmenu/JPackagePopupMenu.java | 53 ++++++ .../gui/ui/popupmenu/JResourcePopupMenu.java | 169 ++++++++++++++++++ .../resources/i18n/Messages_de_DE.properties | 2 + .../resources/i18n/Messages_en_US.properties | 2 + .../resources/i18n/Messages_es_ES.properties | 2 + .../resources/i18n/Messages_id_ID.properties | 2 + .../resources/i18n/Messages_ko_KR.properties | 2 + .../resources/i18n/Messages_pt_BR.properties | 2 + .../resources/i18n/Messages_ru_RU.properties | 2 + .../resources/i18n/Messages_zh_CN.properties | 2 + .../resources/i18n/Messages_zh_TW.properties | 2 + 19 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassExportType.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassPopupMenu.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JResourcePopupMenu.java diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index d74498167e2..c157bbc87ad 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -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; @@ -130,7 +130,7 @@ public String getSyntaxName() { @Override public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { - return RenameDialog.buildRenamePopup(mainWindow, this); + return new JClassPopupMenu(mainWindow, this); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 2db2ed119c3..8f115aa3a1f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -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; @@ -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; @@ -140,6 +143,11 @@ private static void sortResNodes(List nodes) { return new CodeContentPanel(tabbedPane, this); } + @Override + public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { + return new JResourcePopupMenu(mainWindow, this); + } + @Override public synchronized ICodeInfo getCodeInfo() { if (loaded) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java index bebbc885c87..94df5dd7c7a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java @@ -52,4 +52,9 @@ public ICodeInfo getCodeInfo() { public String getSyntaxName() { return SyntaxConstants.SYNTAX_STYLE_JAVA; } + + @Override + public String getName() { + return jCls.getName(); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java index 78c5c46d465..bff7e205a9d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java index d406a5108af..d31057cc826 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileDialogWrapper.java @@ -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; } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileOpenMode.java b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileOpenMode.java index 6043210337c..61d03137626 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileOpenMode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/filedialog/FileOpenMode.java @@ -7,5 +7,7 @@ public enum FileOpenMode { SAVE_PROJECT, EXPORT, CUSTOM_SAVE, - CUSTOM_OPEN + CUSTOM_OPEN, + EXPORT_NODE, + EXPORT_NODE_FOLDER, } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassExportType.java b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassExportType.java new file mode 100644 index 00000000000..7be79ad85b7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassExportType.java @@ -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; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassPopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassPopupMenu.java new file mode 100644 index 00000000000..dec79ee09ae --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JClassPopupMenu.java @@ -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 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); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java index 40d41f2a553..c4eb78742a3 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java @@ -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; @@ -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; @@ -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) { @@ -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 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; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JResourcePopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JResourcePopupMenu.java new file mode 100644 index 00000000000..e6313d228ee --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JResourcePopupMenu.java @@ -0,0 +1,169 @@ +package jadx.gui.ui.popupmenu; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +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.JMenuItem; +import javax.swing.JPopupMenu; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.ResourcesLoader; +import jadx.api.plugins.utils.CommonFileUtils; +import jadx.gui.treemodel.JResource; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.filedialog.FileDialogWrapper; +import jadx.gui.ui.filedialog.FileOpenMode; +import jadx.gui.utils.NLS; + +public class JResourcePopupMenu extends JPopupMenu { + private static final long serialVersionUID = -7781009781149260806L; + + private static final Logger LOG = LoggerFactory.getLogger(JResourcePopupMenu.class); + + private final transient MainWindow mainWindow; + + public JResourcePopupMenu(MainWindow mainWindow, JResource resource) { + this.mainWindow = mainWindow; + + if (resource.getType() != JResource.JResType.ROOT) { + add(makeExportMenuItem(resource)); + } + } + + private JMenuItem makeExportMenuItem(JResource resource) { + JMenuItem exportMenu = new JMenuItem(NLS.str("popup.export")); + exportMenu.addActionListener(event -> { + Path savePath = null; + switch (resource.getType()) { + case ROOT: + case DIR: + savePath = getSaveDirPath(resource); + break; + case FILE: + savePath = getSaveFilePath(resource); + break; + } + + if (savePath == null) { + return; + } + + saveJResource(resource, savePath, true); + + LOG.info("Done saving {}", savePath); + }); + return exportMenu; + } + + private Path getSaveFilePath(JResource resource) { + String extension = CommonFileUtils.getFileExtension(resource.getName()); + + FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.EXPORT_NODE); + fileDialog.setFileExtList(Collections.singletonList(extension)); + Path currentDir = fileDialog.getCurrentDir(); + if (currentDir != null) { + fileDialog.setSelectedFile(currentDir.resolve(resource.getName())); + } + + List selectedPaths = fileDialog.show(); + if (selectedPaths.size() != 1) { + return null; + } + + Path selectedPath = selectedPaths.get(0); + Path savePath; + // Append file extension if missing + if (extension != null + && !selectedPath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(extension)) { + savePath = selectedPath.resolveSibling(selectedPath.getFileName() + "." + extension); + } else { + savePath = selectedPath; + } + + return savePath; + } + + private Path getSaveDirPath(JResource resource) { + FileDialogWrapper fileDialog = new FileDialogWrapper(mainWindow, FileOpenMode.EXPORT_NODE_FOLDER); + + List selectedPaths = fileDialog.show(); + if (selectedPaths.size() != 1) { + return null; + } + + return selectedPaths.get(0); + } + + private static void saveJResource(JResource resource, Path savePath, boolean comingFromDialog) { + switch (resource.getType()) { + case ROOT: + case DIR: + saveJResourceDir(resource, savePath, comingFromDialog); + break; + case FILE: + saveJResourceFile(resource, savePath, comingFromDialog); + break; + } + } + + private static void saveJResourceDir(JResource resource, Path savePath, boolean comingFromDialog) { + Path subSavePath = savePath.resolve(resource.getName()); + try { + if (!Files.isDirectory(subSavePath)) { + Files.createDirectory(subSavePath); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + for (JResource subResource : resource.getSubNodes()) { + saveJResource(subResource, subSavePath, false); + } + } + + private static void saveJResourceFile(JResource resource, Path savePath, boolean comingFromDialog) { + if (!comingFromDialog) { + Path fileName = Path.of(resource.getName()).getFileName(); + savePath = savePath.resolve(fileName); + } + switch (resource.getResFile().getType()) { + case MANIFEST: + case XML: + exportString(resource, savePath); + break; + default: + exportBinary(resource, savePath); + break; + } + } + + private static void exportString(JResource resource, Path savePath) { + try (Writer writer = Files.newBufferedWriter(savePath, StandardCharsets.UTF_8)) { + writer.write(resource.getCodeInfo().getCodeStr()); + } catch (Exception e) { + throw new RuntimeException("Error saving file " + resource.getName(), e); + } + } + + private static void exportBinary(JResource resource, Path savePath) { + try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(savePath.toFile()))) { + byte[] bytes = ResourcesLoader.decodeStream(resource.getResFile(), (size, is) -> is.readAllBytes()); + + if (bytes == null) { + bytes = new byte[0]; + } + os.write(bytes); + } catch (Exception e) { + throw new RuntimeException("Error saving file " + resource.getName(), e); + } + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 3726623535f..bb6a21ae2b0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -46,6 +46,7 @@ file.save_all=Alles speichern file.export_gradle=Als Gradle-Projekt speichern file.save_all_msg=Verzeichnis für das Speichern dekompilierter Ressourcen auswählen file.exit=Beenden +#file.export_node=Export file #start_page.title=Start page #start_page.start=Start @@ -306,6 +307,7 @@ popup.search_global=Globale Suche "%s" #popup.add_scripts=Add scripts #popup.new_script=New script #popup.json_prettify=JSON Prettify +#popup.export=Export #script.run=Run #script.save=Save diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 0cda42609ca..3bc33fc2542 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -46,6 +46,7 @@ file.save=Save file.export_gradle=Save as gradle project file.save_all_msg=Select directory for save decompiled sources file.exit=Exit +file.export_node=Export file start_page.title=Start page start_page.start=Start @@ -306,6 +307,7 @@ popup.add_files=Add files popup.add_scripts=Add scripts popup.new_script=New script popup.json_prettify=JSON Prettify +popup.export=Export script.run=Run script.save=Save diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index a57479aa697..1728233a4ba 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -46,6 +46,7 @@ file.save_all=Guardar todo file.export_gradle=Guardar como proyecto Gradle file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas file.exit=Salir +#file.export_node=Export file #start_page.title=Start page #start_page.start=Start @@ -306,6 +307,7 @@ popup.rename=Nimeta ümber #popup.add_scripts=Add scripts #popup.new_script=New script #popup.json_prettify=JSON Prettify +#popup.export=Export #script.run=Run #script.save=Save diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 0db70ace3d0..8ab53d3a433 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -46,6 +46,7 @@ file.save=Simpan file.export_gradle=Simpan sebagai proyek gradle file.save_all_msg=Pilih direktori untuk menyimpan sumber yang telah didekompilasi file.exit=Keluar +#file.export_node=Export file start_page.title=Halaman Awal start_page.start=Mulai @@ -306,6 +307,7 @@ popup.add_files=Tambahkan berkas popup.add_scripts=Tambahkan skrip popup.new_script=Skrip baru popup.json_prettify=JSON Prettify +#popup.export=Export script.run=Jalankan script.save=Simpan diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index c0685fabbaa..48cd11dbe2c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -46,6 +46,7 @@ file.save_all=모두 저장 file.export_gradle=Gradle 프로젝트로 저장 file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택 file.exit=나가기 +#file.export_node=Export file start_page.title=페이지 시작 start_page.start=시작 @@ -306,6 +307,7 @@ popup.search_global="%s" 전역 검색 #popup.add_scripts=Add scripts #popup.new_script=New script #popup.json_prettify=JSON Prettify +#popup.export=Export #script.run=Run #script.save=Save diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 300cf907285..fbaad8b3371 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -46,6 +46,7 @@ file.save_all=Salvar tudo file.export_gradle=Salvar como um projeto gradle file.save_all_msg=Selecionar diretório para salvar arquivos descompilados file.exit=Sair +#file.export_node=Export file start_page.title=Página inicial start_page.start=Começar @@ -306,6 +307,7 @@ popup.search_global=Busca global "%s" #popup.add_scripts=Add scripts #popup.new_script=New script #popup.json_prettify=JSON Prettify +#popup.export=Export #script.run=Run #script.save=Save diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index a901b9fbd33..c7d63e58a9e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -46,6 +46,7 @@ file.save=Сохранить file.export_gradle=Сохранить как Gradle проект file.save_all_msg=Выбрать папку для декомпилированных проектов file.exit=Выход +#file.export_node=Export file start_page.title=Начальная страница start_page.start=Начать @@ -306,6 +307,7 @@ popup.add_files=Добавить файлы popup.add_scripts=Добавить скрипты popup.new_script=Новый скрипт popup.json_prettify=Форматировать JSON +#popup.export=Export script.run=Запустить script.save=Сохранить diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 3499a5f878b..f2a3dc3fc1e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -46,6 +46,7 @@ file.save=保存 file.export_gradle=另存为 Gradle 项目 file.save_all_msg=请选择保存反编译资源的目录 file.exit=退出 +#file.export_node=Export file start_page.title=开始页面 start_page.start=开始 @@ -306,6 +307,7 @@ popup.add_files=添加文件 popup.add_scripts=添加脚本 popup.new_script=新建脚本 popup.json_prettify=JSON 格式化 +#popup.export=Export script.run=运行 script.save=保存 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 5c1b6eb8146..18177899104 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -46,6 +46,7 @@ file.save=儲存 file.export_gradle=另存為 gradle 專案 file.save_all_msg=選擇儲存反編譯原始碼的路徑 file.exit=離開 +#file.export_node=Export file start_page.title=開始頁面 start_page.start=開始 @@ -306,6 +307,7 @@ popup.add_files=新增檔案 popup.add_scripts=加入腳本 popup.new_script=新增腳本 popup.json_prettify=JSON 格式化 +#popup.export=Export script.run=執行 script.save=儲存