From afdd2d8b397e2f83929885a6e0ebfca09d52b250 Mon Sep 17 00:00:00 2001 From: Hidoni Date: Sun, 2 Feb 2025 01:03:42 +0200 Subject: [PATCH] feat(gui): add button to install desktop file directly from jadx-gui (PR #2404) * fix: missing/incorrect .desktop file values - correct comment - Add StartupWMClass * feat: add button to install desktop file from jadx-gui (#1392) * fix: use POSIX compliant realpath instead of readlink * fix: get XDG executable from PATH --- contrib/jadx-gui.desktop | 3 +- .../java/jadx/core/utils/files/FileUtils.java | 15 ++ jadx-gui/build.gradle.kts | 12 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 15 ++ .../java/jadx/gui/ui/action/ActionModel.java | 1 + .../jadx/gui/utils/DesktopEntryUtils.java | 155 ++++++++++++++++++ .../resources/files/jadx-gui.desktop.tmpl | 10 ++ .../resources/i18n/Messages_de_DE.properties | 4 + .../resources/i18n/Messages_en_US.properties | 4 + .../resources/i18n/Messages_es_ES.properties | 4 + .../resources/i18n/Messages_id_ID.properties | 4 + .../resources/i18n/Messages_ko_KR.properties | 4 + .../resources/i18n/Messages_pt_BR.properties | 4 + .../resources/i18n/Messages_ru_RU.properties | 4 + .../resources/i18n/Messages_zh_CN.properties | 4 + .../resources/i18n/Messages_zh_TW.properties | 4 + 16 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/DesktopEntryUtils.java create mode 100644 jadx-gui/src/main/resources/files/jadx-gui.desktop.tmpl diff --git a/contrib/jadx-gui.desktop b/contrib/jadx-gui.desktop index 950cc0da9b4..80e2ae5c5c3 100644 --- a/contrib/jadx-gui.desktop +++ b/contrib/jadx-gui.desktop @@ -1,9 +1,10 @@ [Desktop Entry] Name=JADX GUI -Comment=Dex to Java compiler +Comment=Dex to Java decompiler Icon=jadx Exec=jadx-gui %f Terminal=false Type=Application Categories=Development;Java; Keywords=Java;Decompiler; +StartupWMClass=jadx-gui-JadxGUI 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 58580d4a2a3..835485c8086 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 @@ -231,6 +231,16 @@ public static Path createTempFileNoDelete(String suffix) { } } + public static Path createTempFileNonPrefixed(String fileName) { + try { + Path path = Files.createFile(tempRootDir.resolve(fileName)); + path.toFile().deleteOnExit(); + return path; + } catch (Exception e) { + throw new JadxRuntimeException("Failed to create non-prefixed temp file: " + fileName, e); + } + } + public static void copyStream(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[READ_BUFFER_SIZE]; while (true) { @@ -266,6 +276,11 @@ public static void writeFile(Path file, String data) throws IOException { StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } + public static void writeFile(Path file, InputStream is) throws IOException { + FileUtils.makeDirsForFile(file); + Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING); + } + public static String readFile(Path textFile) throws IOException { return Files.readString(textFile); } diff --git a/jadx-gui/build.gradle.kts b/jadx-gui/build.gradle.kts index c638de1d231..b4612d03144 100644 --- a/jadx-gui/build.gradle.kts +++ b/jadx-gui/build.gradle.kts @@ -110,11 +110,19 @@ project.components.withType(AdhocComponentWithVariants::class.java).forEach { c tasks.startShadowScripts { doLast { - val newContent = + val newWindowsScriptContent = windowsScript.readText() .replace("java.exe", "javaw.exe") .replace("\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS%", "start \"jadx-gui\" /B \"%JAVA_EXE%\" %DEFAULT_JVM_OPTS%") - windowsScript.writeText(newContent) + // Add launch script path as a property + val newUnixScriptContent = + unixScript.readText() + .replace( + Regex("DEFAULT_JVM_OPTS=.+", RegexOption.MULTILINE), + { result -> result.value + "\" \\\"-Djadx.launchScript.path=\$(realpath $0)\\\"\"" }, + ) + windowsScript.writeText(newWindowsScriptContent) + unixScript.writeText(newUnixScriptContent) } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 319a044f162..cc8f74119eb 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -153,11 +153,13 @@ import jadx.gui.ui.treenodes.SummaryNode; import jadx.gui.update.JadxUpdate; import jadx.gui.utils.CacheObject; +import jadx.gui.utils.DesktopEntryUtils; import jadx.gui.utils.FontUtils; import jadx.gui.utils.ILoadListener; import jadx.gui.utils.LafManager; import jadx.gui.utils.Link; import jadx.gui.utils.NLS; +import jadx.gui.utils.SystemInfo; import jadx.gui.utils.UiUtils; import jadx.gui.utils.dbg.UIWatchDog; import jadx.gui.utils.fileswatcher.LiveReloadWorker; @@ -1186,6 +1188,9 @@ private void initMenuAndToolbar() { JMenu help = new JadxMenu(NLS.str("menu.help"), shortcutsController); help.setMnemonic(KeyEvent.VK_H); help.add(showLogAction); + if (SystemInfo.IS_UNIX && !SystemInfo.IS_MAC) { + help.add(new JadxGuiAction(ActionModel.CREATE_DESKTOP_ENTRY, this::createDesktopEntry)); + } if (Jadx.isDevVersion()) { help.add(new AbstractAction("Show sample error report") { @Override @@ -1724,6 +1729,16 @@ public void addToPluginsMenu(Action item) { pluginsMenu.add(item); } + private void createDesktopEntry() { + if (DesktopEntryUtils.createDesktopEntry()) { + JOptionPane.showMessageDialog(this, NLS.str("message.desktop_entry_creation_success"), + NLS.str("message.success_title"), JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, NLS.str("message.desktop_entry_creation_error"), + NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE); + } + } + public RenameMappingsGui getRenameMappings() { return renameMappings; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java b/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java index d398523f3d2..a686521e23a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java @@ -65,6 +65,7 @@ public enum ActionModel { Shortcut.keyboard(KeyEvent.VK_D, UiUtils.ctrlButton() | KeyEvent.ALT_DOWN_MASK)), SHOW_LOG(MENU_TOOLBAR, "menu.log", "menu.log", "ui/logVerbose", Shortcut.keyboard(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)), + CREATE_DESKTOP_ENTRY(MENU_TOOLBAR, "menu.create_desktop_entry", "menu.create_desktop_entry", null, Shortcut.none()), BACK(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left", Shortcut.keyboard(KeyEvent.VK_ESCAPE)), BACK_V(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left", diff --git a/jadx-gui/src/main/java/jadx/gui/utils/DesktopEntryUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/DesktopEntryUtils.java new file mode 100644 index 00000000000..07f87b28e58 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/DesktopEntryUtils.java @@ -0,0 +1,155 @@ +package jadx.gui.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.export.TemplateFile; +import jadx.core.utils.files.FileUtils; + +public class DesktopEntryUtils { + private static final Logger LOG = LoggerFactory.getLogger(DesktopEntryUtils.class); + private static final Map SIZE_TO_LOGO_MAP = Map.of( + 16, "jadx-logo-16px.png", + 32, "jadx-logo-32px.png", + 48, "jadx-logo-48px.png", + 252, "jadx-logo.png", + 256, "jadx-logo.png"); + private static final Path XDG_DESKTOP_MENU_COMMAND_PATH = findExecutablePath("xdg-desktop-menu"); + private static final Path XDG_ICON_RESOURCE_COMMAND_PATH = findExecutablePath("xdg-icon-resource"); + + public static boolean createDesktopEntry() { + if (XDG_DESKTOP_MENU_COMMAND_PATH == null) { + LOG.error("xdg-desktop-menu was not found in $PATH"); + return false; + } + if (XDG_ICON_RESOURCE_COMMAND_PATH == null) { + LOG.error("xdg-icon-resource was not found in $PATH"); + return false; + } + Path desktopTempFile = FileUtils.createTempFileNonPrefixed("jadx-gui.desktop"); + Path iconTempFolder = FileUtils.createTempDir("logos"); + LOG.debug("Creating desktop with temp files: {}, {}", desktopTempFile, iconTempFolder); + try { + return createDesktopEntry(desktopTempFile, iconTempFolder); + } finally { + try { + FileUtils.deleteFileIfExists(desktopTempFile); + FileUtils.deleteDirIfExists(iconTempFolder); + } catch (IOException e) { + LOG.error("Failed to clean up temp files", e); + } + } + } + + private static boolean createDesktopEntry(Path desktopTempFile, Path iconTempFolder) { + String launchScriptPath = getLaunchScriptPath(); + if (launchScriptPath == null) { + return false; + } + for (Map.Entry entry : SIZE_TO_LOGO_MAP.entrySet()) { + Path path = iconTempFolder.resolve(entry.getKey() + ".png"); + if (!writeLogoFile(entry.getValue(), path)) { + return false; + } + if (!installIcon(entry.getKey(), path)) { + return false; + } + } + if (!writeDesktopFile(launchScriptPath, desktopTempFile)) { + return false; + } + return installDesktopEntry(desktopTempFile); + } + + private static boolean installDesktopEntry(Path desktopTempFile) { + try { + ProcessBuilder desktopFileInstallCommand = new ProcessBuilder(Objects.requireNonNull(XDG_DESKTOP_MENU_COMMAND_PATH).toString(), + "install", desktopTempFile.toString()); + Process process = desktopFileInstallCommand.start(); + int statusCode = process.waitFor(); + if (statusCode != 0) { + LOG.error("Got error code {} while installing desktop file", statusCode); + return false; + } + } catch (Exception e) { + LOG.error("Failed to install desktop file", e); + return false; + } + LOG.info("Successfully installed desktop file"); + return true; + } + + private static boolean installIcon(int size, Path iconPath) { + try { + ProcessBuilder iconInstallCommand = + new ProcessBuilder(Objects.requireNonNull(XDG_ICON_RESOURCE_COMMAND_PATH).toString(), "install", "--novendor", "--size", + String.valueOf(size), iconPath.toString(), + "jadx"); + Process process = iconInstallCommand.start(); + int statusCode = process.waitFor(); + if (statusCode != 0) { + LOG.error("Got error code {} while installing icon of size {}", statusCode, size); + return false; + } + } catch (Exception e) { + LOG.error("Failed to install icon of size {}", size, e); + return false; + } + LOG.info("Successfully installed icon of size {}", size); + return true; + } + + private static Path findExecutablePath(String executableName) { + for (String pathDirectory : System.getenv("PATH").split(File.pathSeparator)) { + Path path = Paths.get(pathDirectory, executableName); + if (path.toFile().isFile() && path.toFile().canExecute()) { + return path; + } + } + return null; + } + + private static boolean writeDesktopFile(String launchScriptPath, Path desktopFilePath) { + try { + TemplateFile tmpl = TemplateFile.fromResources("/files/jadx-gui.desktop.tmpl"); + tmpl.add("launchScriptPath", launchScriptPath); + FileUtils.writeFile(desktopFilePath, tmpl.build()); + } catch (Exception e) { + LOG.error("Failed to save .desktop file at: {}", desktopFilePath, e); + return false; + } + LOG.debug("Wrote .desktop file to {}", desktopFilePath); + return true; + } + + private static boolean writeLogoFile(String logoFile, Path logoPath) { + try (InputStream is = DesktopEntryUtils.class.getResourceAsStream("/logos/" + logoFile)) { + FileUtils.writeFile(logoPath, is); + } catch (Exception e) { + LOG.error("Failed to write logo file at: {}", logoPath, e); + return false; + } + LOG.debug("Wrote logo file to: {}", logoPath); + return true; + } + + public static @Nullable String getLaunchScriptPath() { + String launchScriptPath = System.getProperty("jadx.launchScript.path"); + if (launchScriptPath.isEmpty()) { + LOG.error( + "The jadx.launchScript.path property is not set. Please launch JADX with the bundled launch script or set it to the appropriate value yourself."); + return null; + } + LOG.debug("JADX launch script path: {}", launchScriptPath); + return launchScriptPath; + } +} diff --git a/jadx-gui/src/main/resources/files/jadx-gui.desktop.tmpl b/jadx-gui/src/main/resources/files/jadx-gui.desktop.tmpl new file mode 100644 index 00000000000..71da516eb10 --- /dev/null +++ b/jadx-gui/src/main/resources/files/jadx-gui.desktop.tmpl @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=JADX GUI +Comment=Dex to Java decompiler +Icon=jadx +Exec={{launchScriptPath}} %f +Terminal=false +Type=Application +Categories=Development;Java; +Keywords=Java;Decompiler; +StartupWMClass=jadx-gui-JadxGUI 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 31c377a504b..69cf2093e65 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -24,6 +24,7 @@ menu.tools=Tools #menu.reset_cache=Reset code cache menu.deobfuscation=Deobfuskierung menu.log=Log-Anzeige +#menu.create_desktop_entry=Create Desktop Entry menu.help=Hilfe menu.about=Über #menu.quark=Quark Engine @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx hat nur noch wenig Speicherplatz. Dahe #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=JADX-Speicherauslastung: %.2f GB von %.2f GB 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 90cbbd8d2ca..58fbee61ef7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -24,6 +24,7 @@ menu.decompile_all=Decompile all classes menu.reset_cache=Reset code cache menu.deobfuscation=Deobfuscation menu.log=Log Viewer +menu.create_desktop_entry=Create Desktop Entry menu.help=Help menu.about=About menu.quark=Quark Engine @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx is running low on memory. Therefore %d message.enter_new_name=Enter new name message.could_not_rename=Can't rename the file message.confirm_remove_script=Do you really want to remove script? +message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +message.desktop_entry_creation_success=Desktop entry created successfully! +message.success_title=Success heapUsage.text=JADX memory usage: %.2f GB of %.2f GB 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 53e2661483c..338ec6c661c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -24,6 +24,7 @@ menu.tools=Herramientas #menu.reset_cache=Reset code cache menu.deobfuscation=Desofuscación menu.log=Visor log +#menu.create_desktop_entry=Create Desktop Entry menu.help=Ayuda menu.about=Acerca de... #menu.quark=Quark Engine @@ -113,6 +114,9 @@ nav.forward=Adelante #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success #heapUsage.text=JADX memory usage: %.2f GB of %.2f GB 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 bc6c3bfa5ce..352b917d883 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -24,6 +24,7 @@ menu.decompile_all=Deskompilasi semua kelas menu.reset_cache=Reset cache kode menu.deobfuscation=Deobfikasi menu.log=Pemantau Log +#menu.create_desktop_entry=Create Desktop Entry menu.help=Bantuan menu.about=Tentang menu.quark=Mesin Quark @@ -113,6 +114,9 @@ message.indexingClassesSkipped=JADX kekurangan memori. Oleh karena itu %d #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=Penggunaan memori JADX: %.2f GB dari %.2f GB 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 5ebba6089fd..f3be41889a1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -24,6 +24,7 @@ menu.tools=도구 #menu.reset_cache=Reset code cache menu.deobfuscation=난독화 해제 menu.log=로그 뷰어 +#menu.create_desktop_entry=Create Desktop Entry menu.help=도움말 menu.about=정보 #menu.quark=Quark Engine @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx의 메모리가 부족합니다. 따 #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=JADX 메모리 사용량 : %.2f GB / %.2f GB 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 edc7e3f31b1..673e63e3042 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -24,6 +24,7 @@ menu.tools=Ferramentas #menu.reset_cache=Reset code cache menu.deobfuscation=Desofuscar menu.log=Visualizador de log +#menu.create_desktop_entry=Create Desktop Entry menu.help=Ajuda menu.about=Sobre #menu.quark=Quark Engine @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx está rodando com pouca memória. Por #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=Uso de memória do JADX: %.2f GB of %.2f GB 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 9ca7a45ff91..220a6b00ae7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -24,6 +24,7 @@ menu.decompile_all=Декомпилировать все menu.reset_cache=Сбросить кэш menu.deobfuscation=Деобфускация menu.log=Просмотр логов +#menu.create_desktop_entry=Create Desktop Entry menu.help=Помощь menu.about=О программе #menu.quark=Quark Engine @@ -113,6 +114,9 @@ message.indexingClassesSkipped=JaDX запущен с малым коли #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=JADX использует: %.2f ГБ из %.2f ГБ 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 0e5f4bb7350..1ce6591a7f8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -24,6 +24,7 @@ menu.decompile_all=反编译所有类 menu.reset_cache=重置代码缓存 menu.deobfuscation=反混淆 menu.log=日志查看器 +#menu.create_desktop_entry=Create Desktop Entry menu.help=帮助 menu.about=关于 menu.quark=Quark 引擎 @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx 的内存不足。因此,%d 个类 message.enter_new_name=输入新名称 message.could_not_rename=无法重命名文件 message.confirm_remove_script=您真的要删除脚本吗? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=JADX 内存使用率:%.2f GB / %.2f GB 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 41500ef2aaa..106d14c71cb 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -24,6 +24,7 @@ menu.decompile_all=反編譯所有類別 menu.reset_cache=重設程式碼快取 menu.deobfuscation=去模糊化 menu.log=記錄檔檢視器 +#menu.create_desktop_entry=Create Desktop Entry menu.help=幫助 menu.about=關於 menu.quark= Quark 引擎 @@ -113,6 +114,9 @@ message.indexingClassesSkipped=Jadx 的記憶體不足。故 %d 個類別 #message.enter_new_name=Enter new name #message.could_not_rename=Can't rename the file #message.confirm_remove_script=Do you really want to remove script? +#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details). +#message.desktop_entry_creation_success=Desktop entry created successfully! +#message.success_title=Success heapUsage.text=JADX 記憶體使用率:%.2f GB / %.2f GB