diff --git a/jadx-core/src/main/java/jadx/core/utils/BetterName.java b/jadx-core/src/main/java/jadx/core/utils/BetterName.java index e5a081508..0f88676e2 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BetterName.java +++ b/jadx-core/src/main/java/jadx/core/utils/BetterName.java @@ -14,7 +14,7 @@ import jadx.core.deobf.TldHelper; public class BetterName { private static final Logger LOG = LoggerFactory.getLogger(BetterName.class); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; public static String compareAndGet(String first, String second) { if (Objects.equals(first, second)) { diff --git a/jadx-gui/build.gradle.kts b/jadx-gui/build.gradle.kts index 25308b9f4..215e136b6 100644 --- a/jadx-gui/build.gradle.kts +++ b/jadx-gui/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation("com.google.code.gson:gson:2.10.1") implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.apache.commons:commons-text:1.10.0") + implementation("commons-io:commons-io:2.13.0") implementation("io.reactivex.rxjava2:rxjava:2.2.21") implementation("com.github.akarnokd:rxjava2-swing:0.3.7") diff --git a/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheEntry.java b/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheEntry.java new file mode 100644 index 000000000..98dc4042a --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheEntry.java @@ -0,0 +1,45 @@ +package jadx.gui.cache.manager; + +import org.jetbrains.annotations.NotNull; + +public class CacheEntry implements Comparable { + + private String project; + private String cache; + private long timestamp; + + public String getProject() { + return project; + } + + public void setProject(String project) { + this.project = project; + } + + public String getCache() { + return cache; + } + + public void setCache(String cache) { + this.cache = cache; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public int compareTo(@NotNull CacheEntry other) { + // recent entries first + return -Long.compare(timestamp, other.timestamp); + } + + @Override + public String toString() { + return "CacheEntry{project=" + project + ", cache=" + cache + "}"; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheManager.java b/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheManager.java new file mode 100644 index 000000000..8a06c2beb --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/manager/CacheManager.java @@ -0,0 +1,261 @@ +package jadx.gui.cache.manager; + +import java.io.BufferedReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import jadx.api.plugins.utils.CommonFileUtils; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.gui.settings.JadxProject; +import jadx.gui.settings.JadxSettings; +import jadx.gui.settings.data.ProjectData; +import jadx.gui.utils.files.JadxFiles; + +public class CacheManager { + private static final Logger LOG = LoggerFactory.getLogger(CacheManager.class); + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Type CACHES_TYPE = new TypeToken>() { + }.getType(); + + private final Map cacheMap; + private final JadxSettings settings; + + public CacheManager(JadxSettings settings) { + this.settings = settings; + this.cacheMap = loadCaches(); + } + + /** + * If project cache is set -> check if cache entry exists for this project. + * If not -> calculate new and add entry. + */ + public Path getCacheDir(JadxProject project, @Nullable String cacheDirStr) { + if (cacheDirStr == null) { + Path newProjectCacheDir = buildCacheDir(project); + addEntry(projectToKey(project), newProjectCacheDir); + return newProjectCacheDir; + } + Path cacheDir = resolveCacheDirStr(cacheDirStr, project.getProjectPath()); + return verifyEntry(project, cacheDir); + } + + public void projectPathUpdate(JadxProject project, Path newPath) { + if (Objects.equals(project.getProjectPath(), newPath)) { + return; + } + String key = projectToKey(project); + CacheEntry prevEntry = cacheMap.remove(key); + if (prevEntry == null) { + return; + } + CacheEntry newEntry = new CacheEntry(); + newEntry.setProject(pathToString(newPath)); + newEntry.setCache(prevEntry.getCache()); + addEntry(newEntry); + } + + public List getCachesList() { + List list = new ArrayList<>(cacheMap.values()); + Collections.sort(list); + return list; + } + + public synchronized void removeCacheEntry(CacheEntry entry) { + try { + cacheMap.remove(entry.getProject()); + saveCaches(cacheMap); + FileUtils.deleteDirIfExists(Paths.get(entry.getCache())); + } catch (Exception e) { + LOG.error("Failed to remove cache entry: " + entry.getCache(), e); + } + } + + private Path resolveCacheDirStr(String cacheDirStr, Path projectPath) { + Path path = Paths.get(cacheDirStr); + if (path.isAbsolute() || projectPath == null) { + return path; + } + return projectPath.resolveSibling(path); + } + + public String buildCacheDirStr(Path dir) { + if (Objects.equals(settings.getCacheDir(), ".")) { + return dir.getFileName().toString(); + } + return pathToString(dir); + } + + private Path buildCacheDir(JadxProject project) { + String cacheDirValue = settings.getCacheDir(); + if (Objects.equals(cacheDirValue, ".")) { + return buildLocalCacheDir(project); + } + Path cacheBaseDir = cacheDirValue == null ? JadxFiles.CACHE_DIR : Paths.get(cacheDirValue); + return cacheBaseDir.resolve(buildProjectUniqName(project)); + } + + private static Path buildLocalCacheDir(JadxProject project) { + Path projectPath = project.getProjectPath(); + if (projectPath != null) { + return projectPath.resolveSibling(projectPath.getFileName() + ".cache"); + } + List files = project.getFilePaths(); + if (files.isEmpty()) { + throw new JadxRuntimeException("Failed to build local cache dir"); + } + Path path = files.stream() + .filter(p -> !p.getFileName().toString().endsWith(".jadx.kts")) + .findFirst() + .orElseGet(() -> files.get(0)); + String name = CommonFileUtils.removeFileExtension(path.getFileName().toString()); + return path.resolveSibling(name + ".jadx.cache"); + } + + private Path verifyEntry(JadxProject project, Path cacheDir) { + boolean cacheExists = Files.exists(cacheDir); + String key = projectToKey(project); + CacheEntry entry = cacheMap.get(key); + if (entry == null) { + Path newCacheDir = cacheExists ? cacheDir : buildCacheDir(project); + addEntry(key, newCacheDir); + return newCacheDir; + } + if (entry.getCache().equals(pathToString(cacheDir)) && cacheExists) { + // same and exists + return cacheDir; + } + // remove previous cache dir + FileUtils.deleteDirIfExists(Paths.get(entry.getCache())); + + Path newCacheDir = cacheExists ? cacheDir : buildCacheDir(project); + entry.setCache(pathToString(newCacheDir)); + entry.setTimestamp(System.currentTimeMillis()); + saveCaches(cacheMap); + return newCacheDir; + } + + private void addEntry(String projectKey, Path cacheDir) { + CacheEntry entry = new CacheEntry(); + entry.setProject(projectKey); + entry.setCache(pathToString(cacheDir)); + addEntry(entry); + } + + private void addEntry(CacheEntry entry) { + entry.setTimestamp(System.currentTimeMillis()); + cacheMap.put(entry.getProject(), entry); + saveCaches(cacheMap); + } + + private String projectToKey(JadxProject project) { + Path projectPath = project.getProjectPath(); + if (projectPath != null) { + return pathToString(projectPath); + } + return "tmp:" + buildProjectUniqName(project); + } + + private static String buildProjectUniqName(JadxProject project) { + return project.getName() + "-" + FileUtils.buildInputsHash(project.getFilePaths()); + } + + public static String pathToString(Path path) { + try { + return path.toAbsolutePath().normalize().toString(); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to expand path: " + path, e); + } + } + + private synchronized Map loadCaches() { + List list = null; + if (Files.exists(JadxFiles.CACHES_LIST)) { + try (BufferedReader reader = Files.newBufferedReader(JadxFiles.CACHES_LIST)) { + list = GSON.fromJson(reader, CACHES_TYPE); + } catch (Exception e) { + LOG.warn("Failed to load caches list", e); + } + } else { + return initFromRecentProjects(); + } + if (Utils.isEmpty(list)) { + return new HashMap<>(); + } + Map map = new HashMap<>(list.size()); + for (CacheEntry entry : list) { + map.put(entry.getProject(), entry); + } + return map; + } + + private synchronized void saveCaches(Map map) { + List list = new ArrayList<>(map.values()); + Collections.sort(list); + String json = GSON.toJson(list, CACHES_TYPE); + try { + Files.writeString(JadxFiles.CACHES_LIST, json, StandardCharsets.UTF_8, + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to write caches file", e); + } + } + + /** + * Load caches info from recent projects list. + * Help for initial migration. + */ + private Map initFromRecentProjects() { + try { + Map map = new HashMap<>(); + long t = System.currentTimeMillis(); + for (Path project : settings.getRecentProjects()) { + try { + ProjectData data = JadxProject.loadProjectData(project); + String cacheDir = data.getCacheDir(); + if (cacheDir == null) { + // no cache dir, ignore + continue; + } + Path cachePath = resolveCacheDirStr(cacheDir, project); + if (!Files.isDirectory(cachePath)) { + continue; + } + String key = pathToString(project); + CacheEntry entry = new CacheEntry(); + entry.setProject(key); + entry.setCache(pathToString(cachePath)); + entry.setTimestamp(t++); // keep projects order + map.put(key, entry); + } catch (Exception e) { + LOG.warn("Failed to load project file: {}", project, e); + } + } + saveCaches(map); + return map; + } catch (Exception e) { + LOG.warn("Failed to fill cache list from recent projects", e); + return new HashMap<>(); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java index 6fe5d690a..89a1987c9 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java @@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -35,6 +36,7 @@ import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.utils.GsonUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; +import jadx.gui.cache.manager.CacheManager; import jadx.gui.settings.data.ProjectData; import jadx.gui.settings.data.TabViewState; import jadx.gui.ui.MainWindow; @@ -84,8 +86,10 @@ public class JadxProject { return null; } - @Nullable - public Path getProjectPath() { + /** + * @return null if project not saved + */ + public @Nullable Path getProjectPath() { return projectPath; } @@ -100,13 +104,23 @@ public class JadxProject { } public void setFilePaths(List files) { - if (!files.equals(getFilePaths())) { - data.setFiles(files); - String joinedName = files.stream().map(p -> CommonFileUtils.removeFileExtension(p.getFileName().toString())) - .collect(Collectors.joining("_")); - this.name = StringUtils.abbreviate(joinedName, 100); - changed(); + if (files.equals(getFilePaths())) { + return; } + if (files.isEmpty()) { + data.setFiles(files); + name = ""; + } else { + Collections.sort(files); + data.setFiles(files); + String joinedName = files.stream() + .map(p -> p.getFileName().toString()) + .filter(file -> !file.endsWith(".jadx.kts")) + .map(CommonFileUtils::removeFileExtension) + .collect(Collectors.joining("_")); + name = StringUtils.abbreviate(joinedName, 100); + } + changed(); } public List getTreeExpansions() { @@ -186,33 +200,30 @@ public class JadxProject { return data.getPluginOptions().get(key); } - public @NotNull Path getCacheDir() { - Path cacheDir = data.getCacheDir(); - if (cacheDir != null) { - return cacheDir; + private Path cacheDir; + + public Path getCacheDir() { + if (cacheDir == null) { + cacheDir = resolveCachePath(data.getCacheDir()); + } + return cacheDir; + } + + public void resetCacheDir() { + cacheDir = resolveCachePath(null); + } + + private Path resolveCachePath(@Nullable String cacheDirStr) { + CacheManager cacheManager = mainWindow.getCacheManager(); + Path newCacheDir = cacheManager.getCacheDir(this, cacheDirStr); + String newCacheStr = cacheManager.buildCacheDirStr(newCacheDir); + if (!newCacheStr.equals(cacheDirStr)) { + data.setCacheDir(newCacheStr); + changed(); } - Path newCacheDir = buildCacheDir(); - setCacheDir(newCacheDir); return newCacheDir; } - public void setCacheDir(Path cacheDir) { - data.setCacheDir(cacheDir); - changed(); - } - - private Path buildCacheDir() { - if (projectPath != null) { - return projectPath.resolveSibling(projectPath.getFileName() + ".cache"); - } - List files = data.getFiles(); - if (!files.isEmpty()) { - Path path = files.get(0); - return path.resolveSibling(path.getFileName() + ".cache"); - } - throw new JadxRuntimeException("Failed to build cache dir"); - } - public boolean isEnableLiveReload() { return data.isEnableLiveReload(); } @@ -273,6 +284,7 @@ public class JadxProject { } public void saveAs(Path path) { + mainWindow.getCacheManager().projectPathUpdate(this, path); setProjectPath(path); save(); } @@ -291,10 +303,9 @@ public class JadxProject { } public static JadxProject load(MainWindow mainWindow, Path path) { - Path basePath = path.toAbsolutePath().getParent(); - try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + try { JadxProject project = new JadxProject(mainWindow); - project.data = buildGson(basePath).fromJson(reader, ProjectData.class); + project.data = loadProjectData(path); project.saved = true; project.setProjectPath(path); project.upgrade(); @@ -305,6 +316,15 @@ public class JadxProject { } } + public static ProjectData loadProjectData(Path path) { + Path basePath = path.toAbsolutePath().getParent(); + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + return buildGson(basePath).fromJson(reader, ProjectData.class); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to load project file: " + path, e); + } + } + private static Gson buildGson(Path basePath) { return new GsonBuilder() .registerTypeHierarchyAdapter(Path.class, new RelativePathTypeAdapter(basePath)) diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 1bbfd54bc..1ab3e5233 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -1,6 +1,10 @@ package jadx.gui.settings; -import java.awt.*; +import java.awt.Font; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Rectangle; +import java.awt.Window; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -13,7 +17,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; -import javax.swing.*; +import javax.swing.JFrame; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.jetbrains.annotations.Nullable; @@ -44,7 +48,7 @@ public class JadxSettings extends JadxCLIArgs { private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); private static final Path USER_HOME = Paths.get(System.getProperty("user.home")); - private static final int RECENT_PROJECTS_COUNT = 15; + private static final int RECENT_PROJECTS_COUNT = 30; private static final int CURRENT_SETTINGS_VERSION = 18; private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont(); @@ -93,8 +97,10 @@ public class JadxSettings extends JadxCLIArgs { private String adbDialogHost = "localhost"; private String adbDialogPort = "5037"; - private CodeCacheMode codeCacheMode = CodeCacheMode.DISK_WITH_CACHE; + private CodeCacheMode codeCacheMode = CodeCacheMode.DISK; private UsageCacheMode usageCacheMode = UsageCacheMode.DISK; + private @Nullable String cacheDir = null; // null - default (system), "." - at project dir, other - custom + private boolean jumpOnDoubleClick = true; /** @@ -206,8 +212,9 @@ public class JadxSettings extends JadxCLIArgs { if (projectPath == null) { return; } - recentProjects.remove(projectPath); - recentProjects.add(0, projectPath); + Path normPath = projectPath.toAbsolutePath().normalize(); + recentProjects.remove(normPath); + recentProjects.add(0, normPath); int count = recentProjects.size(); if (count > RECENT_PROJECTS_COUNT) { recentProjects.subList(RECENT_PROJECTS_COUNT, count).clear(); @@ -681,6 +688,14 @@ public class JadxSettings extends JadxCLIArgs { this.usageCacheMode = usageCacheMode; } + public @Nullable String getCacheDir() { + return cacheDir; + } + + public void setCacheDir(@Nullable String cacheDir) { + this.cacheDir = cacheDir; + } + public boolean isJumpOnDoubleClick() { return jumpOnDoubleClick; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsStorage.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsStorage.java index 7340ccda3..fb09c070d 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsStorage.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsStorage.java @@ -3,18 +3,16 @@ package jadx.gui.settings; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.prefs.Preferences; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dev.dirs.ProjectDirectories; - import jadx.core.utils.StringUtils; import jadx.core.utils.files.FileUtils; import jadx.gui.JadxGUI; +import jadx.gui.utils.files.JadxFiles; /** * Jadx settings storage. Select first available option: @@ -38,8 +36,7 @@ public class JadxSettingsStorage { } private static Path initConfigFile() { - ProjectDirectories jadxDirs = ProjectDirectories.from("io.github", "skylot", "jadx"); - Path confPath = Paths.get(jadxDirs.configDir, "gui.json"); + Path confPath = JadxFiles.GUI_CONF; if (!Files.exists(confPath)) { copyFromPreferences(confPath); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java index 30ad305a1..43558289e 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java @@ -20,7 +20,7 @@ public class ProjectData { private JadxCodeData codeData = new JadxCodeData(); private List openTabs = Collections.emptyList(); private @Nullable Path mappingsPath; - private @Nullable Path cacheDir; + private @Nullable String cacheDir; // don't use relative path adapter private boolean enableLiveReload = false; private List searchHistory = new ArrayList<>(); protected Map pluginOptions = new HashMap<>(); @@ -78,12 +78,11 @@ public class ProjectData { this.mappingsPath = mappingsPath; } - @Nullable - public Path getCacheDir() { + public @Nullable String getCacheDir() { return cacheDir; } - public void setCacheDir(Path cacheDir) { + public void setCacheDir(@Nullable String cacheDir) { this.cacheDir = cacheDir; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index d11f29170..f874340a8 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -57,11 +57,10 @@ import jadx.api.args.IntegerFormat; import jadx.api.args.ResourceNameSource; import jadx.api.plugins.events.JadxEvents; import jadx.api.plugins.gui.ISettingsGroup; -import jadx.gui.cache.code.CodeCacheMode; -import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettingsAdapter; import jadx.gui.settings.LineNumbersMode; +import jadx.gui.settings.ui.cache.CacheSettingsGroup; import jadx.gui.settings.ui.plugins.PluginsSettings; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; @@ -125,6 +124,7 @@ public class JadxSettingsWindow extends JDialog { groups.add(makeDecompilationGroup()); groups.add(makeDeobfuscationGroup()); groups.add(makeRenameGroup()); + groups.add(new CacheSettingsGroup(this)); groups.add(makeAppearanceGroup()); groups.add(makeSearchResGroup()); groups.add(makeProjectGroup()); @@ -402,21 +402,6 @@ public class JadxSettingsWindow extends JDialog { needReload(); }); - JComboBox codeCacheModeComboBox = new JComboBox<>(CodeCacheMode.values()); - codeCacheModeComboBox.setSelectedItem(settings.getCodeCacheMode()); - codeCacheModeComboBox.addActionListener(e -> { - settings.setCodeCacheMode((CodeCacheMode) codeCacheModeComboBox.getSelectedItem()); - needReload(); - }); - String codeCacheModeToolTip = CodeCacheMode.buildToolTip(); - - JComboBox usageCacheModeComboBox = new JComboBox<>(UsageCacheMode.values()); - usageCacheModeComboBox.setSelectedItem(settings.getUsageCacheMode()); - usageCacheModeComboBox.addActionListener(e -> { - settings.setUsageCacheMode((UsageCacheMode) usageCacheModeComboBox.getSelectedItem()); - needReload(); - }); - JCheckBox showInconsistentCode = new JCheckBox(); showInconsistentCode.setSelected(settings.isShowInconsistentCode()); showInconsistentCode.addItemListener(e -> { @@ -563,8 +548,6 @@ public class JadxSettingsWindow extends JDialog { NLS.str("preferences.excludedPackages.tooltip"), editExcludedPackages); other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs); other.addRow(NLS.str("preferences.decompilationMode"), decompilationModeComboBox); - other.addRow(NLS.str("preferences.codeCacheMode"), codeCacheModeToolTip, codeCacheModeComboBox); - other.addRow(NLS.str("preferences.usageCacheMode"), usageCacheModeComboBox); other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode); other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts); @@ -712,7 +695,7 @@ public class JadxSettingsWindow extends JDialog { NLS.str("preferences.copy_message")); } - void needReload() { + public void needReload() { needReload = true; } @@ -726,6 +709,10 @@ public class JadxSettingsWindow extends JDialog { return settings.toJadxArgs().makeCodeArgsHash(decompiler); } + public MainWindow getMainWindow() { + return mainWindow; + } + @Override public void dispose() { settings.saveWindowPos(this); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java index 6946f1bef..96d1ec61d 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/SettingsGroup.java @@ -80,8 +80,8 @@ public class SettingsGroup implements ISettingsGroup { return title; } - public JPanel getPanel() { - return panel; + public JPanel getGridPanel() { + return gridPanel; } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CacheSettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CacheSettingsGroup.java new file mode 100644 index 000000000..91c9b2121 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CacheSettingsGroup.java @@ -0,0 +1,201 @@ +package jadx.gui.settings.ui.cache; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.UIManager; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.gui.ISettingsGroup; +import jadx.gui.cache.code.CodeCacheMode; +import jadx.gui.cache.usage.UsageCacheMode; +import jadx.gui.settings.JadxSettings; +import jadx.gui.settings.ui.JadxSettingsWindow; +import jadx.gui.settings.ui.SettingsGroup; +import jadx.gui.ui.filedialog.FileDialogWrapper; +import jadx.gui.ui.filedialog.FileOpenMode; +import jadx.gui.utils.NLS; +import jadx.gui.utils.files.JadxFiles; +import jadx.gui.utils.ui.DocumentUpdateListener; + +public class CacheSettingsGroup implements ISettingsGroup { + + private final String title = NLS.str("preferences.cache"); + private final JadxSettingsWindow settingsWindow; + + private JTextField customDirField; + private JButton selectDirBtn; + + public CacheSettingsGroup(JadxSettingsWindow settingsWindow) { + this.settingsWindow = settingsWindow; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public JComponent buildComponent() { + JPanel options = new JPanel(); + options.setLayout(new BoxLayout(options, BoxLayout.PAGE_AXIS)); + options.add(buildBaseOptions()); + options.add(buildLocationSelector()); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(options, BorderLayout.PAGE_START); + mainPanel.add(buildCachesView(), BorderLayout.CENTER); + return mainPanel; + } + + private JPanel buildCachesView() { + CachesTable cachesTable = new CachesTable(settingsWindow.getMainWindow()); + JScrollPane scrollPane = new JScrollPane(cachesTable); + cachesTable.setFillsViewportHeight(true); + cachesTable.updateData(); + + JButton calcUsage = new JButton(NLS.str("preferences.cache.btn.usage")); + calcUsage.addActionListener(ev -> cachesTable.updateSizes()); + + JButton deleteSelected = new JButton(NLS.str("preferences.cache.btn.delete_selected")); + deleteSelected.addActionListener(ev -> cachesTable.deleteSelected()); + + JButton deleteAll = new JButton(NLS.str("preferences.cache.btn.delete_all")); + deleteAll.addActionListener(ev -> cachesTable.deleteAll()); + + JPanel buttons = new JPanel(); + buttons.setLayout(new BoxLayout(buttons, BoxLayout.LINE_AXIS)); + buttons.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); + buttons.add(calcUsage); + buttons.add(Box.createHorizontalGlue()); + buttons.add(deleteSelected); + buttons.add(deleteAll); + + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder(NLS.str("preferences.cache.table.title"))); + panel.add(scrollPane, BorderLayout.CENTER); + panel.add(buttons, BorderLayout.PAGE_END); + return panel; + } + + private JComponent buildLocationSelector() { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(0, 1)); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(NLS.str("preferences.cache.location")), + BorderFactory.createEmptyBorder(10, 10, 10, 10))); + + customDirField = new JTextField(); + customDirField.setColumns(10); + customDirField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> { + settingsWindow.getMainWindow().getSettings().setCacheDir(customDirField.getText()); + })); + + selectDirBtn = new JButton(); + selectDirBtn.setIcon(UIManager.getIcon("Tree.closedIcon")); + selectDirBtn.addActionListener(e -> { + FileDialogWrapper fd = new FileDialogWrapper(settingsWindow.getMainWindow(), FileOpenMode.CUSTOM_OPEN); + fd.setFileExtList(Collections.emptyList()); + fd.setSelectionMode(JFileChooser.DIRECTORIES_ONLY); + List paths = fd.show(); + if (!paths.isEmpty()) { + String dir = paths.get(0).toAbsolutePath().toString(); + customDirField.setText(dir); + settingsWindow.getMainWindow().getSettings().setCacheDir(dir); + } + }); + + JRadioButton defOpt = new JRadioButton(NLS.str("preferences.cache.location_default")); + defOpt.setToolTipText(JadxFiles.CACHE_DIR.toString()); + defOpt.addActionListener(e -> changeCacheLocation(null)); + JRadioButton localOpt = new JRadioButton(NLS.str("preferences.cache.location_local")); + localOpt.addActionListener(e -> changeCacheLocation(".")); + JRadioButton customOpt = new JRadioButton(NLS.str("preferences.cache.location_custom")); + customOpt.addActionListener(e -> changeCacheLocation("")); + + ButtonGroup group = new ButtonGroup(); + group.add(defOpt); + group.add(localOpt); + group.add(customOpt); + + panel.add(defOpt); + panel.add(localOpt); + + JPanel custom = new JPanel(); + custom.setLayout(new BoxLayout(custom, BoxLayout.LINE_AXIS)); + custom.add(customOpt); + custom.add(Box.createHorizontalStrut(15)); + custom.add(customDirField); + custom.add(selectDirBtn); + panel.add(custom); + + String cacheDir = settingsWindow.getMainWindow().getSettings().getCacheDir(); + if (cacheDir == null) { + defOpt.setSelected(true); + changeCacheLocation(null); + } else if (cacheDir.equals(".")) { + localOpt.setSelected(true); + changeCacheLocation(cacheDir); + } else { + customOpt.setSelected(true); + customDirField.setText(cacheDir); + changeCacheLocation(""); + } + JLabel notice = new JLabel(NLS.str("preferences.cache.change_notice")); + notice.setEnabled(false); + panel.add(notice); + return panel; + } + + private void changeCacheLocation(@Nullable String locValue) { + boolean custom = Objects.equals(locValue, ""); + customDirField.setEnabled(custom); + selectDirBtn.setEnabled(custom); + if (!custom) { + settingsWindow.getMainWindow().getSettings().setCacheDir(locValue); + } + } + + private JComponent buildBaseOptions() { + JadxSettings settings = settingsWindow.getMainWindow().getSettings(); + + JComboBox codeCacheModeComboBox = new JComboBox<>(CodeCacheMode.values()); + codeCacheModeComboBox.setSelectedItem(settings.getCodeCacheMode()); + codeCacheModeComboBox.addActionListener(e -> { + settings.setCodeCacheMode((CodeCacheMode) codeCacheModeComboBox.getSelectedItem()); + settingsWindow.needReload(); + }); + + JComboBox usageCacheModeComboBox = new JComboBox<>(UsageCacheMode.values()); + usageCacheModeComboBox.setSelectedItem(settings.getUsageCacheMode()); + usageCacheModeComboBox.addActionListener(e -> { + settings.setUsageCacheMode((UsageCacheMode) usageCacheModeComboBox.getSelectedItem()); + settingsWindow.needReload(); + }); + + SettingsGroup group = new SettingsGroup(title); + group.addRow(NLS.str("preferences.codeCacheMode"), CodeCacheMode.buildToolTip(), codeCacheModeComboBox); + group.addRow(NLS.str("preferences.usageCacheMode"), usageCacheModeComboBox); + return group.buildComponent(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTable.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTable.java new file mode 100644 index 000000000..b79515b78 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTable.java @@ -0,0 +1,152 @@ +package jadx.gui.settings.ui.cache; + +import java.awt.Dimension; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.swing.JTable; +import javax.swing.ListSelectionModel; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.utils.ListUtils; +import jadx.core.utils.Utils; +import jadx.gui.cache.manager.CacheManager; +import jadx.gui.settings.JadxProject; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; +import jadx.gui.utils.ui.MousePressedHandler; + +public class CachesTable extends JTable { + private static final long serialVersionUID = 5984107298264276049L; + + private static final Logger LOG = LoggerFactory.getLogger(CachesTable.class); + + private final MainWindow mainWindow; + private final CachesTableModel dataModel; + + public CachesTable(MainWindow mainWindow) { + this.mainWindow = mainWindow; + this.dataModel = new CachesTableModel(); + setModel(dataModel); + setDefaultRenderer(Object.class, new CachesTableRenderer()); + + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + setShowHorizontalLines(true); + setDragEnabled(false); + setColumnSelectionAllowed(false); + setAutoscrolls(true); + setFocusable(false); + + addMouseListener(new MousePressedHandler(ev -> { + int row = rowAtPoint(ev.getPoint()); + if (row != -1) { + dataModel.changeSelection(row); + UiUtils.uiRun(this::updateUI); + } + })); + } + + public void updateData() { + List rows = mainWindow.getCacheManager().getCachesList().stream() + .map(TableRow::new) + .collect(Collectors.toList()); + updateRows(rows); + } + + public void reloadData() { + Map prevUsageMap = dataModel.getRows().stream() + .collect(Collectors.toMap(TableRow::getProject, TableRow::getUsage)); + + List rows = mainWindow.getCacheManager().getCachesList().stream() + .map(TableRow::new) + .peek(r -> r.setUsage(Utils.getOrElse(prevUsageMap.get(r.getProject()), "-"))) + .collect(Collectors.toList()); + updateRows(rows); + } + + private void updateRows(List rows) { + dataModel.setRows(rows); + + // fix allocated space for default 20 rows + int width = getPreferredSize().width; + int height = rows.size() * getRowHeight(); + setPreferredScrollableViewportSize(new Dimension(width, height)); + + UiUtils.uiRun(this::updateUI); + } + + public void updateSizes() { + List list = dataModel.getRows().stream() + .map(row -> (Runnable) () -> calcSize(row)) + .collect(Collectors.toList()); + mainWindow.getBackgroundExecutor().execute( + NLS.str("preferences.cache.task.usage"), + list, + status -> updateUI()); + } + + private void calcSize(TableRow row) { + String cacheDir = row.getCacheEntry().getCache(); + try { + File dir = new File(cacheDir); + if (dir.exists()) { + long size = FileUtils.sizeOfDirectory(dir); + row.setUsage(FileUtils.byteCountToDisplaySize(size)); + } else { + row.setUsage("not found"); + } + } catch (Exception e) { + LOG.warn("Failed to calculate size of directory: {}", cacheDir, e); + row.setUsage("error"); + } + } + + public void deleteSelected() { + delete(ListUtils.filter(dataModel.getRows(), TableRow::isSelected)); + } + + public void deleteAll() { + delete(dataModel.getRows()); + } + + private void delete(List rows) { + // force reload if cache for current project is deleted + boolean reload = searchCurrentProject(rows); + + List list = rows.stream() + .map(TableRow::getCacheEntry) + .map(entry -> (Runnable) () -> mainWindow.getCacheManager().removeCacheEntry(entry)) + .collect(Collectors.toList()); + mainWindow.getBackgroundExecutor().execute( + NLS.str("preferences.cache.task.delete"), + list, + status -> { + reloadData(); + if (reload) { + mainWindow.reopen(); + } + }); + } + + private boolean searchCurrentProject(List rows) { + JadxProject project = mainWindow.getProject(); + if (!project.getFilePaths().isEmpty()) { + String cacheStr = CacheManager.pathToString(project.getCacheDir()); + for (TableRow row : rows) { + if (row.getCacheEntry().getCache().equals(cacheStr)) { + project.resetCacheDir(); + LOG.debug("Found current project in cache delete list -> request full reload"); + return true; + } + } + } + return false; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableModel.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableModel.java new file mode 100644 index 000000000..825a9f91e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableModel.java @@ -0,0 +1,57 @@ +package jadx.gui.settings.ui.cache; + +import java.util.Collections; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import jadx.gui.utils.NLS; + +public class CachesTableModel extends AbstractTableModel { + private static final long serialVersionUID = -7725573085995496397L; + + private static final String[] COLUMN_NAMES = { + NLS.str("preferences.cache.table.project"), + NLS.str("preferences.cache.table.size") + }; + + private transient List rows = Collections.emptyList(); + + public void setRows(List list) { + this.rows = list; + } + + public List getRows() { + return rows; + } + + @Override + public int getRowCount() { + return rows.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public String getColumnName(int index) { + return COLUMN_NAMES[index]; + } + + @Override + public Class getColumnClass(int columnIndex) { + return TableRow.class; + } + + @Override + public TableRow getValueAt(int rowIndex, int columnIndex) { + return rows.get(rowIndex); + } + + public void changeSelection(int idx) { + TableRow row = rows.get(idx); + row.setSelected(!row.isSelected()); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableRenderer.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableRenderer.java new file mode 100644 index 000000000..b23d1dbde --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/CachesTableRenderer.java @@ -0,0 +1,40 @@ +package jadx.gui.settings.ui.cache; + +import java.awt.Component; + +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; + +public class CachesTableRenderer implements TableCellRenderer { + + private final JLabel label; + + public CachesTableRenderer() { + label = new JLabel(); + label.setOpaque(true); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + TableRow obj = (TableRow) value; + switch (column) { + case 0: + label.setText(obj.getProject()); + break; + case 1: + label.setText(obj.getUsage()); + break; + } + label.setToolTipText(obj.getCacheEntry().getCache()); + + if (obj.isSelected()) { + label.setBackground(table.getSelectionBackground()); + label.setForeground(table.getSelectionForeground()); + } else { + label.setBackground(table.getBackground()); + label.setForeground(table.getForeground()); + } + return label; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/TableRow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/TableRow.java new file mode 100644 index 000000000..4ac28fd5b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/cache/TableRow.java @@ -0,0 +1,52 @@ +package jadx.gui.settings.ui.cache; + +import java.nio.file.Paths; + +import jadx.api.plugins.utils.CommonFileUtils; +import jadx.gui.cache.manager.CacheEntry; + +final class TableRow { + private final CacheEntry cacheEntry; + private final String project; + private String usage; + private boolean selected = false; + + public TableRow(CacheEntry cacheEntry) { + this.cacheEntry = cacheEntry; + this.project = cutProjectName(cacheEntry.getProject()); + this.usage = "-"; + } + + private String cutProjectName(String project) { + if (project.startsWith("tmp:")) { + int hashStart = project.lastIndexOf('-'); + int endIdx = hashStart != -1 ? hashStart : project.length(); + return project.substring(4, endIdx) + " (Temp)"; + } + return CommonFileUtils.removeFileExtension(Paths.get(project).getFileName().toString()); + } + + public CacheEntry getCacheEntry() { + return cacheEntry; + } + + public String getProject() { + return project; + } + + public String getUsage() { + return usage; + } + + public void setUsage(String usage) { + this.usage = usage; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java index d9b37cb84..f4a4a1b1d 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/plugins/PluginsSettingsGroup.java @@ -104,9 +104,10 @@ class PluginsSettingsGroup implements ISettingsGroup { JScrollPane scrollPane = new JScrollPane(pluginsList); detailsPanel = new JPanel(new BorderLayout(5, 5)); - detailsPanel.setBorder(BorderFactory.createTitledBorder("Plugin details")); + detailsPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(NLS.str("preferences.plugins.details")), + BorderFactory.createEmptyBorder(10, 10, 10, 10))); detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.PAGE_AXIS)); - detailsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); JSplitPane splitPanel = new JSplitPane(); splitPanel.setBorder(BorderFactory.createEmptyBorder(10, 2, 2, 2)); 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 037f38170..5886cfda1 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -91,6 +91,7 @@ import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; import jadx.gui.JadxWrapper; +import jadx.gui.cache.manager.CacheManager; import jadx.gui.device.debugger.BreakpointManager; import jadx.gui.jobs.BackgroundExecutor; import jadx.gui.jobs.DecompileTask; @@ -179,6 +180,7 @@ public class MainWindow extends JFrame { private final transient JadxWrapper wrapper; private final transient JadxSettings settings; private final transient CacheObject cacheObject; + private final transient CacheManager cacheManager; private final transient BackgroundExecutor backgroundExecutor; private transient @NotNull JadxProject project; @@ -231,6 +233,7 @@ public class MainWindow extends JFrame { this.wrapper = new JadxWrapper(this); this.liveReloadWorker = new LiveReloadWorker(this); this.renameMappings = new RenameMappingsGui(this); + this.cacheManager = new CacheManager(settings); resetCache(); FontUtils.registerBundledFonts(); @@ -655,6 +658,15 @@ public class MainWindow extends JFrame { backgroundExecutor.execute(new DecompileTask(this)); } + public void resetCodeCache() { + Path cacheDir = project.getCacheDir(); + project.resetCacheDir(); + backgroundExecutor.execute( + NLS.str("preferences.cache.task.delete"), + () -> FileUtils.deleteDirIfExists(cacheDir), + status -> reopen()); + } + public void cancelBackgroundJobs() { backgroundExecutor.cancelAll(); } @@ -1019,6 +1031,10 @@ public class MainWindow extends JFrame { decompileAllAction.setNameAndDesc(NLS.str("menu.decompile_all")); decompileAllAction.setIcon(ICON_DECOMPILE_ALL); + ActionHandler resetCacheAction = new ActionHandler(ev -> resetCodeCache()); + resetCacheAction.setNameAndDesc(NLS.str("menu.reset_cache")); + resetCacheAction.setIcon(Icons.RESET); + Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) { @Override public void actionPerformed(ActionEvent e) { @@ -1130,6 +1146,7 @@ public class MainWindow extends JFrame { JMenu tools = new JMenu(NLS.str("menu.tools")); tools.setMnemonic(KeyEvent.VK_T); tools.add(decompileAllAction); + tools.add(resetCacheAction); tools.add(deobfMenuItem); tools.add(quarkAction); tools.add(openDeviceAction); @@ -1658,6 +1675,10 @@ public class MainWindow extends JFrame { return renameMappings; } + public CacheManager getCacheManager() { + return cacheManager; + } + /** * Events instance if decompiler not yet available */ diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java index 7e20ac47a..5d741d1e6 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java @@ -33,4 +33,5 @@ public class Icons { public static final ImageIcon RUN = UiUtils.openSvgIcon("ui/run"); public static final ImageIcon CHECK = UiUtils.openSvgIcon("ui/checkConstraint"); public static final ImageIcon FORMAT = UiUtils.openSvgIcon("ui/toolWindowMessages"); + public static final ImageIcon RESET = UiUtils.openSvgIcon("ui/reset"); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/files/JadxFiles.java b/jadx-gui/src/main/java/jadx/gui/utils/files/JadxFiles.java new file mode 100644 index 000000000..dee3b0014 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/files/JadxFiles.java @@ -0,0 +1,23 @@ +package jadx.gui.utils.files; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import dev.dirs.ProjectDirectories; + +import jadx.core.utils.files.FileUtils; + +public class JadxFiles { + + private static final ProjectDirectories DIRS = ProjectDirectories.from("io.github", "skylot", "jadx"); + private static final String CONFIG_DIR = DIRS.configDir; + + public static final Path GUI_CONF = Paths.get(CONFIG_DIR, "gui.json"); + public static final Path CACHES_LIST = Paths.get(CONFIG_DIR, "caches.json"); + + public static final Path CACHE_DIR = Paths.get(DIRS.cacheDir); + + static { + FileUtils.makeDirs(Paths.get(CONFIG_DIR)); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/ui/MousePressedHandler.java b/jadx-gui/src/main/java/jadx/gui/utils/ui/MousePressedHandler.java new file mode 100644 index 000000000..16f9054f1 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/ui/MousePressedHandler.java @@ -0,0 +1,19 @@ +package jadx.gui.utils.ui; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; + +public class MousePressedHandler extends MouseAdapter { + + private final Consumer listener; + + public MousePressedHandler(Consumer listener) { + this.listener = listener; + } + + @Override + public void mousePressed(MouseEvent ev) { + listener.accept(ev); + } +} 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 b374659ed..9b8a571b1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -17,6 +17,7 @@ menu.comment_search=Kommentar suchen menu.tools=Tools #menu.plugins=Plugins #menu.decompile_all=Decompile all classes +#menu.reset_cache=Reset code cache menu.deobfuscation=Deobfuskierung menu.log=Log-Anzeige menu.help=Hilfe @@ -229,10 +230,26 @@ preferences.res_skip_file=Dateien überspringen (MB) #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or #preferences.plugins.update_all=Update All +#preferences.plugins.details=Plugin details #preferences.plugins.task.installing=Installing plugin #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=Bitte Datei öffnen msg.saving_sources=Quelltexte speichern msg.language_changed_title=Sprache speichern 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 605c41329..62bec5a39 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -17,6 +17,7 @@ menu.comment_search=Comment search menu.tools=Tools menu.plugins=Plugins menu.decompile_all=Decompile all classes +menu.reset_cache=Reset code cache menu.deobfuscation=Deobfuscation menu.log=Log Viewer menu.help=Help @@ -229,10 +230,26 @@ preferences.plugins.location_id_label=Location id: preferences.plugins.plugin_jar=Select Plugin jar preferences.plugins.plugin_jar_label=or preferences.plugins.update_all=Update All +preferences.plugins.details=Plugin details preferences.plugins.task.installing=Installing plugin preferences.plugins.task.uninstalling=Uninstalling plugin preferences.plugins.task.updating=Updating plugins +preferences.cache=Cache +preferences.cache.location=Cache location +preferences.cache.location_default=App cache system directory +preferences.cache.location_local=Same directory as project file +preferences.cache.location_custom=Custom location: +preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +preferences.cache.table.title=Caches list +preferences.cache.table.project=Cache for project +preferences.cache.table.size=Disk usage +preferences.cache.btn.usage=Calculate usage +preferences.cache.btn.delete_selected=Delete Selected +preferences.cache.btn.delete_all=Delete All +preferences.cache.task.usage=Calculating cache size +preferences.cache.task.delete=Deleting caches + msg.open_file=Please open file msg.saving_sources=Saving sources msg.language_changed_title=Language changed 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 2812734c6..86b702de7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -17,6 +17,7 @@ menu.class_search=Buscar clase menu.tools=Herramientas #menu.plugins=Plugins #menu.decompile_all=Decompile all classes +#menu.reset_cache=Reset code cache menu.deobfuscation=Desofuscación menu.log=Visor log menu.help=Ayuda @@ -229,10 +230,26 @@ preferences.reset_title=Reestablecer preferencias #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or #preferences.plugins.update_all=Update All +#preferences.plugins.details=Plugin details #preferences.plugins.task.installing=Installing plugin #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=Por favor, abra un archivo msg.saving_sources=Guardando fuente msg.language_changed_title=Idioma cambiado 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 999da823e..0e69cea55 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -17,6 +17,7 @@ menu.comment_search=주석 검색 menu.tools=도구 #menu.plugins=Plugins #menu.decompile_all=Decompile all classes +#menu.reset_cache=Reset code cache menu.deobfuscation=난독화 해제 menu.log=로그 뷰어 menu.help=도움말 @@ -229,10 +230,26 @@ preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB) #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or #preferences.plugins.update_all=Update All +#preferences.plugins.details=Plugin details #preferences.plugins.task.installing=Installing plugin #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=파일을 여십시오 msg.saving_sources=소스 저장 중 msg.language_changed_title=언어 변경됨 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 5ed709034..c492c2bcf 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -17,6 +17,7 @@ menu.comment_search=Busca por comentário menu.tools=Ferramentas #menu.plugins=Plugins #menu.decompile_all=Decompile all classes +#menu.reset_cache=Reset code cache menu.deobfuscation=Desofuscar menu.log=Visualizador de log menu.help=Ajuda @@ -229,10 +230,26 @@ preferences.res_skip_file=Pular arquivos excedidos #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or #preferences.plugins.update_all=Update All +#preferences.plugins.details=Plugin details #preferences.plugins.task.installing=Installing plugin #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=Abra um arquivo msg.saving_sources=Salvando recursos msg.language_changed_title=Idioma alterado 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 a2620f5a1..b18b53fbe 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -17,6 +17,7 @@ menu.comment_search=Поиск комментариев menu.tools=Инструменты #menu.plugins=Plugins #menu.decompile_all=Decompile all classes +#menu.reset_cache=Reset code cache menu.deobfuscation=Деобфускация menu.log=Просмотр логов menu.help=Помощь @@ -229,10 +230,26 @@ preferences.res_skip_file=Пропускать ресурсы больше че #preferences.plugins.plugin_jar=Select Plugin jar #preferences.plugins.plugin_jar_label=or #preferences.plugins.update_all=Update All +#preferences.plugins.details=Plugin details #preferences.plugins.task.installing=Installing plugin #preferences.plugins.task.uninstalling=Uninstalling plugin #preferences.plugins.task.updating=Updating plugins +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=Пожалуйста, откройте файл msg.saving_sources=Сохранение ресурсов msg.language_changed_title=Язык изменен 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 28bd09388..22977c0f8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -17,6 +17,7 @@ menu.comment_search=注释搜索 menu.tools=工具 menu.plugins=插件 menu.decompile_all=反编译所有类 +#menu.reset_cache=Reset code cache menu.deobfuscation=反混淆 menu.log=日志查看器 menu.help=帮助 @@ -229,10 +230,26 @@ preferences.plugins.location_id_label=位置ID: preferences.plugins.plugin_jar=选择插件 jar preferences.plugins.plugin_jar_label=或 preferences.plugins.update_all=更新所有 +#preferences.plugins.details=Plugin details preferences.plugins.task.installing=安装插件中 preferences.plugins.task.uninstalling=卸载插件中 preferences.plugins.task.updating=更新插件中 +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=请打开文件 msg.saving_sources=正在导出源代码 msg.language_changed_title=语言已更改 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 f956daaad..71303b424 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -17,6 +17,7 @@ menu.comment_search=註解搜尋 menu.tools=工具 menu.plugins=外掛程式 menu.decompile_all=反編譯所有類別 +#menu.reset_cache=Reset code cache menu.deobfuscation=去模糊化 menu.log=記錄檔檢視器 menu.help=幫助 @@ -229,10 +230,26 @@ preferences.plugins.location_id_label=位置 id: preferences.plugins.plugin_jar=選擇外掛程式 jar preferences.plugins.plugin_jar_label=或 preferences.plugins.update_all=全部更新 +#preferences.plugins.details=Plugin details preferences.plugins.task.installing=正在安裝外掛程式 preferences.plugins.task.uninstalling=正在解除安裝外掛程式 preferences.plugins.task.updating=正在更新外掛程式 +#preferences.cache=Cache +#preferences.cache.location=Cache location +#preferences.cache.location_default=App cache system directory +#preferences.cache.location_local=Same directory as project file +#preferences.cache.location_custom=Custom location: +#preferences.cache.change_notice=* for exists caches change will be applied after cache remove or manual reset +#preferences.cache.table.title=Caches list +#preferences.cache.table.project=Cache for project +#preferences.cache.table.size=Disk usage +#preferences.cache.btn.usage=Calculate usage +#preferences.cache.btn.delete_selected=Delete Selected +#preferences.cache.btn.delete_all=Delete All +#preferences.cache.task.usage=Calculating cache size +#preferences.cache.task.delete=Deleting caches + msg.open_file=請開啟檔案 msg.saving_sources=正在儲存原始碼 msg.language_changed_title=已更改語言 diff --git a/jadx-gui/src/main/resources/icons/ui/reset.svg b/jadx-gui/src/main/resources/icons/ui/reset.svg new file mode 100644 index 000000000..cc7e51e29 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/ui/reset.svg @@ -0,0 +1,7 @@ + + + + + + +