@@ -231,10 +231,6 @@ public class JadxCLIArgs {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class JadxGUI {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
NLS.setLocale(settings.getLangLocale());
|
||||
SwingUtilities.invokeLater(new MainWindow(settings)::open);
|
||||
SwingUtilities.invokeLater(new MainWindow(settings)::init);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error: {}", e.getMessage(), e);
|
||||
System.exit(1);
|
||||
|
||||
@@ -47,27 +47,24 @@ public class JadxWrapper {
|
||||
}
|
||||
|
||||
public void saveAll(final File dir, final ProgressMonitor progressMonitor) {
|
||||
Runnable save = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
decompiler.getArgs().setRootDir(dir);
|
||||
ThreadPoolExecutor ex = (ThreadPoolExecutor) decompiler.getSaveExecutor();
|
||||
ex.shutdown();
|
||||
while (ex.isTerminating()) {
|
||||
long total = ex.getTaskCount();
|
||||
long done = ex.getCompletedTaskCount();
|
||||
progressMonitor.setProgress((int) (done * 100.0 / total));
|
||||
Thread.sleep(500);
|
||||
}
|
||||
progressMonitor.close();
|
||||
LOG.info("decompilation complete, freeing memory ...");
|
||||
decompiler.getClasses().forEach(JavaClass::unload);
|
||||
LOG.info("done");
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Save interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
Runnable save = () -> {
|
||||
try {
|
||||
decompiler.getArgs().setRootDir(dir);
|
||||
ThreadPoolExecutor ex = (ThreadPoolExecutor) decompiler.getSaveExecutor();
|
||||
ex.shutdown();
|
||||
while (ex.isTerminating()) {
|
||||
long total = ex.getTaskCount();
|
||||
long done = ex.getCompletedTaskCount();
|
||||
progressMonitor.setProgress((int) (done * 100.0 / total));
|
||||
Thread.sleep(500);
|
||||
}
|
||||
progressMonitor.close();
|
||||
LOG.info("decompilation complete, freeing memory ...");
|
||||
decompiler.getClasses().forEach(JavaClass::unload);
|
||||
LOG.info("done");
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Save interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
};
|
||||
new Thread(save).start();
|
||||
|
||||
@@ -58,8 +58,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
if (searchIndex != null && searchIndex.getSkippedCount() > 0) {
|
||||
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
|
||||
searchIndex.getSkippedCount(), Utils.memoryInfo());
|
||||
String msg = NLS.str("message.indexingClassesSkipped");
|
||||
msg = String.format(msg, searchIndex.getSkippedCount());
|
||||
String msg = NLS.str("message.indexingClassesSkipped", searchIndex.getSkippedCount());
|
||||
JOptionPane.showMessageDialog(null, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.gui.utils.PathTypeAdapter;
|
||||
|
||||
public class JadxProject {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxProject.class);
|
||||
private static final int CURRENT_SETTINGS_VERSION = 0;
|
||||
|
||||
public static final String PROJECT_EXTENSION = "jadx";
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
|
||||
.create();
|
||||
|
||||
private transient JadxSettings settings;
|
||||
private transient String name = "New Project";
|
||||
private transient Path projectPath;
|
||||
private List<Path> filesPath;
|
||||
private transient boolean saved;
|
||||
private transient boolean initial = true;
|
||||
|
||||
private int projectVersion = 0;
|
||||
|
||||
public JadxProject(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public Path getProjectPath() {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
private void setProjectPath(Path projectPath) {
|
||||
this.projectPath = projectPath;
|
||||
if (projectVersion != CURRENT_SETTINGS_VERSION) {
|
||||
upgradeSettings(projectVersion);
|
||||
}
|
||||
name = projectPath.getFileName().toString();
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
changed();
|
||||
}
|
||||
|
||||
public Path getFilePath() {
|
||||
return filesPath == null ? null : filesPath.get(0);
|
||||
}
|
||||
|
||||
public void setFilePath(Path filePath) {
|
||||
if (!filePath.equals(getFilePath())) {
|
||||
this.filesPath = Arrays.asList(filePath);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
if (settings.isAutoSaveProject()) {
|
||||
save();
|
||||
}
|
||||
else {
|
||||
saved = false;
|
||||
}
|
||||
initial = false;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
|
||||
public boolean isInitial() {
|
||||
return initial;
|
||||
}
|
||||
|
||||
public void saveAs(Path path) {
|
||||
setProjectPath(path);
|
||||
save();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(getProjectPath())) {
|
||||
writer.write(GSON.toJson(this));
|
||||
saved = true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving project", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static JadxProject from(Path path, JadxSettings settings) {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
|
||||
if (!lines.isEmpty()) {
|
||||
JadxProject project = GSON.fromJson(lines.get(0), JadxProject.class);
|
||||
project.settings = settings;
|
||||
project.setProjectPath(path);
|
||||
project.saved = true;
|
||||
return project;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading project", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void upgradeSettings(int fromVersion) {
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||
if (fromVersion == 0) {
|
||||
fromVersion++;
|
||||
}
|
||||
projectVersion = CURRENT_SETTINGS_VERSION;
|
||||
save();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import java.awt.Font;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Window;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -27,25 +29,27 @@ import jadx.gui.utils.Utils;
|
||||
public class JadxSettings extends JadxCLIArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
|
||||
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final int RECENT_FILES_COUNT = 15;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 8;
|
||||
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
|
||||
private static final int RECENT_PROJECTS_COUNT = 15;
|
||||
private static final int CURRENT_SETTINGS_VERSION = 9;
|
||||
|
||||
private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
|
||||
|
||||
static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList(
|
||||
"files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp"
|
||||
));
|
||||
private String lastOpenFilePath = USER_HOME;
|
||||
private String lastSaveFilePath = USER_HOME;
|
||||
private Path lastSaveProjectPath = USER_HOME;
|
||||
private Path lastOpenFilePath = USER_HOME;
|
||||
private Path lastSaveFilePath = USER_HOME;
|
||||
private boolean flattenPackage = false;
|
||||
private boolean checkForUpdates = false;
|
||||
private List<String> recentFiles = new ArrayList<>();
|
||||
private List<Path> recentProjects = new ArrayList<>();
|
||||
private String fontStr = "";
|
||||
private String editorThemePath = "";
|
||||
private LangLocale langLocale = NLS.defaultLocale();
|
||||
private boolean autoStartJobs = false;
|
||||
protected String excludedPackages = "";
|
||||
private boolean autoSaveProject = false;
|
||||
|
||||
private boolean showHeapUsageBar = true;
|
||||
|
||||
@@ -84,20 +88,29 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public String getLastOpenFilePath() {
|
||||
public Path getLastOpenFilePath() {
|
||||
return lastOpenFilePath;
|
||||
}
|
||||
|
||||
public void setLastOpenFilePath(String lastOpenFilePath) {
|
||||
public void setLastOpenFilePath(Path lastOpenFilePath) {
|
||||
this.lastOpenFilePath = lastOpenFilePath;
|
||||
partialSync(settings -> settings.lastOpenFilePath = JadxSettings.this.lastOpenFilePath);
|
||||
}
|
||||
|
||||
public String getLastSaveFilePath() {
|
||||
public Path getLastSaveProjectPath() {
|
||||
return lastSaveProjectPath;
|
||||
}
|
||||
|
||||
public Path getLastSaveFilePath() {
|
||||
return lastSaveFilePath;
|
||||
}
|
||||
|
||||
public void setLastSaveFilePath(String lastSaveFilePath) {
|
||||
public void setLastSaveProjectPath(Path lastSaveProjectPath) {
|
||||
this.lastSaveProjectPath = lastSaveProjectPath;
|
||||
partialSync(settings -> settings.lastSaveProjectPath = JadxSettings.this.lastSaveProjectPath);
|
||||
}
|
||||
|
||||
public void setLastSaveFilePath(Path lastSaveFilePath) {
|
||||
this.lastSaveFilePath = lastSaveFilePath;
|
||||
partialSync(settings -> settings.lastSaveFilePath = JadxSettings.this.lastSaveFilePath);
|
||||
}
|
||||
@@ -120,18 +133,18 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
sync();
|
||||
}
|
||||
|
||||
public Iterable<String> getRecentFiles() {
|
||||
return recentFiles;
|
||||
public Iterable<Path> getRecentProjects() {
|
||||
return recentProjects;
|
||||
}
|
||||
|
||||
public void addRecentFile(String filePath) {
|
||||
recentFiles.remove(filePath);
|
||||
recentFiles.add(0, filePath);
|
||||
int count = recentFiles.size();
|
||||
if (count > RECENT_FILES_COUNT) {
|
||||
recentFiles.subList(RECENT_FILES_COUNT, count).clear();
|
||||
public void addRecentProject(Path projectPath) {
|
||||
recentProjects.remove(projectPath);
|
||||
recentProjects.add(0, projectPath);
|
||||
int count = recentProjects.size();
|
||||
if (count > RECENT_PROJECTS_COUNT) {
|
||||
recentProjects.subList(RECENT_PROJECTS_COUNT, count).clear();
|
||||
}
|
||||
partialSync(settings -> settings.recentFiles = recentFiles);
|
||||
partialSync(settings -> settings.recentProjects = recentProjects);
|
||||
}
|
||||
|
||||
public void saveWindowPos(Window window) {
|
||||
@@ -265,6 +278,14 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.autoStartJobs = autoStartJobs;
|
||||
}
|
||||
|
||||
public boolean isAutoSaveProject() {
|
||||
return autoSaveProject;
|
||||
}
|
||||
|
||||
public void setAutoSaveProject(boolean autoSaveProject) {
|
||||
this.autoSaveProject = autoSaveProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
@@ -343,6 +364,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
outDir = null;
|
||||
outDirSrc = null;
|
||||
outDirRes = null;
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 8) {
|
||||
fromVersion++;
|
||||
}
|
||||
settingsVersion = CURRENT_SETTINGS_VERSION;
|
||||
sync();
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Path;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.JadxGUI;
|
||||
import jadx.gui.utils.PathTypeAdapter;
|
||||
|
||||
public class JadxSettingsAdapter {
|
||||
|
||||
@@ -34,7 +37,10 @@ public class JadxSettingsAdapter {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
private static final GsonBuilder GSON_BUILDER = new GsonBuilder().setExclusionStrategies(EXCLUDE_FIELDS);
|
||||
private static final GsonBuilder GSON_BUILDER = new GsonBuilder()
|
||||
.setExclusionStrategies(EXCLUDE_FIELDS)
|
||||
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
|
||||
;
|
||||
private static final Gson GSON = GSON_BUILDER.create();
|
||||
|
||||
private JadxSettingsAdapter() {
|
||||
|
||||
@@ -52,6 +52,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
panel.add(makeDeobfuscationGroup());
|
||||
panel.add(makeDecompilationGroup());
|
||||
panel.add(makeProjectGroup());
|
||||
panel.add(makeEditorGroup());
|
||||
panel.add(makeOtherGroup());
|
||||
|
||||
@@ -168,6 +169,18 @@ public class JadxSettingsWindow extends JDialog {
|
||||
connectedComponents.forEach(comp -> comp.setEnabled(enabled));
|
||||
}
|
||||
|
||||
private SettingsGroup makeProjectGroup() {
|
||||
JCheckBox autoSave = new JCheckBox();
|
||||
autoSave.setSelected(settings.isAutoSaveProject());
|
||||
autoSave.addItemListener(e ->
|
||||
settings.setAutoSaveProject(e.getStateChange() == ItemEvent.SELECTED));
|
||||
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.project"));
|
||||
group.addRow(NLS.str("preferences.autoSave"), autoSave);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private SettingsGroup makeEditorGroup() {
|
||||
JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
|
||||
|
||||
@@ -186,9 +199,9 @@ public class JadxSettingsWindow extends JDialog {
|
||||
mainWindow.loadSettings();
|
||||
});
|
||||
|
||||
SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor"));
|
||||
JLabel fontLabel = other.addRow(getFontLabelStr(), fontBtn);
|
||||
other.addRow(NLS.str("preferences.theme"), themesCbx);
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.editor"));
|
||||
JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn);
|
||||
group.addRow(NLS.str("preferences.theme"), themesCbx);
|
||||
|
||||
fontBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -205,7 +218,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
}
|
||||
});
|
||||
return other;
|
||||
return group;
|
||||
}
|
||||
|
||||
private String getFontLabelStr() {
|
||||
@@ -263,7 +276,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED));
|
||||
|
||||
JCheckBox escapeUnicode = new JCheckBox();
|
||||
escapeUnicode.setSelected(settings.escapeUnicode());
|
||||
escapeUnicode.setSelected(settings.isEscapeUnicode());
|
||||
escapeUnicode.addItemListener(e -> {
|
||||
settings.setEscapeUnicode(e.getStateChange() == ItemEvent.SELECTED);
|
||||
needReload();
|
||||
@@ -333,12 +346,12 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
SettingsGroup other = new SettingsGroup(NLS.str("preferences.other"));
|
||||
other.addRow(NLS.str("preferences.language"), languageCbx);
|
||||
other.addRow(NLS.str("preferences.check_for_updates"), update);
|
||||
other.addRow(NLS.str("preferences.cfg"), cfg);
|
||||
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
|
||||
return other;
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.other"));
|
||||
group.addRow(NLS.str("preferences.language"), languageCbx);
|
||||
group.addRow(NLS.str("preferences.check_for_updates"), update);
|
||||
group.addRow(NLS.str("preferences.cfg"), cfg);
|
||||
group.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
|
||||
return group;
|
||||
}
|
||||
|
||||
private void needReload() {
|
||||
|
||||
@@ -77,15 +77,15 @@ public class ApkSignature extends JNode {
|
||||
|
||||
final String err = NLS.str("apkSignature.errors");
|
||||
final String warn = NLS.str("apkSignature.warnings");
|
||||
final String sigSucc = NLS.str("apkSignature.signatureSuccess");
|
||||
final String sigFail = NLS.str("apkSignature.signatureFailed");
|
||||
final String sigSuccKey = "apkSignature.signatureSuccess";
|
||||
final String sigFailKey = "apkSignature.signatureFailed";
|
||||
|
||||
writeIssues(builder, err, result.getErrors());
|
||||
writeIssues(builder, warn, result.getWarnings());
|
||||
|
||||
if (!result.getV1SchemeSigners().isEmpty()) {
|
||||
builder.append("<h2>");
|
||||
builder.escape(String.format(result.isVerifiedUsingV1Scheme() ? sigSucc : sigFail, 1));
|
||||
builder.escape(NLS.str(result.isVerifiedUsingV1Scheme() ? sigSuccKey : sigFailKey, 1));
|
||||
builder.append("</h2>\n");
|
||||
|
||||
builder.append("<blockquote>");
|
||||
@@ -106,7 +106,7 @@ public class ApkSignature extends JNode {
|
||||
}
|
||||
if (!result.getV2SchemeSigners().isEmpty()) {
|
||||
builder.append("<h2>");
|
||||
builder.escape(String.format(result.isVerifiedUsingV2Scheme() ? sigSucc : sigFail, 2));
|
||||
builder.escape(NLS.str(result.isVerifiedUsingV2Scheme() ? sigSuccKey : sigFailKey, 2));
|
||||
builder.append("</h2>\n");
|
||||
|
||||
builder.append("<blockquote>");
|
||||
|
||||
@@ -232,11 +232,10 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
}
|
||||
|
||||
protected void updateProgressLabel() {
|
||||
String statusText = String.format(
|
||||
NLS.str("search_dialog.info_label"),
|
||||
resultsModel.getDisplayedResultsStart(),
|
||||
resultsModel.getDisplayedResultsEnd(),
|
||||
resultsModel.getResultCount()
|
||||
String statusText = NLS.str("search_dialog.info_label",
|
||||
resultsModel.getDisplayedResultsStart(),
|
||||
resultsModel.getDisplayedResultsEnd(),
|
||||
resultsModel.getResultCount()
|
||||
);
|
||||
resultsInfoLabel.setText(statusText);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,9 @@ public class HeapUsageBar extends JProgressBar implements ActionListener {
|
||||
private final transient Runtime runtime = Runtime.getRuntime();
|
||||
private final transient Timer timer;
|
||||
|
||||
private final String textFormat;
|
||||
private final double maxGB;
|
||||
|
||||
public HeapUsageBar() {
|
||||
this.textFormat = NLS.str("heapUsage.text");
|
||||
setBorderPainted(false);
|
||||
setStringPainted(true);
|
||||
setValue(10);
|
||||
@@ -54,7 +52,7 @@ public class HeapUsageBar extends JProgressBar implements ActionListener {
|
||||
long used = runtime.totalMemory() - runtime.freeMemory();
|
||||
int usedKB = (int) (used / 1024);
|
||||
setValue(usedKB);
|
||||
setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB));
|
||||
setString(NLS.str("heapUsage.text", (usedKB / TWO_TO_20), maxGB));
|
||||
|
||||
if ((used + Utils.MIN_FREE_MEMORY) > runtime.maxMemory()) {
|
||||
setForeground(RED);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class MainDropTarget implements DropTargetListener {
|
||||
if (!transferData.isEmpty()) {
|
||||
dtde.dropComplete(true);
|
||||
// load first file
|
||||
mainWindow.openFile(transferData.get(0));
|
||||
mainWindow.open(transferData.get(0).toPath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("File drop operation failed", e);
|
||||
|
||||
@@ -1,6 +1,54 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.DisplayMode;
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.ProgressMonitor;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.MenuEvent;
|
||||
import javax.swing.event.MenuListener;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
@@ -12,20 +60,6 @@ import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.awt.*;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Theme;
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,6 +71,7 @@ import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.BackgroundWorker;
|
||||
import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.JadxSettingsWindow;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
@@ -56,8 +91,6 @@ import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MainWindow extends JFrame {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
|
||||
@@ -86,6 +119,9 @@ public class MainWindow extends JFrame {
|
||||
private final transient JadxWrapper wrapper;
|
||||
private final transient JadxSettings settings;
|
||||
private final transient CacheObject cacheObject;
|
||||
private transient JadxProject project;
|
||||
private transient Action newProjectAction;
|
||||
private transient Action saveProjectAction;
|
||||
|
||||
private JPanel mainPanel;
|
||||
|
||||
@@ -119,20 +155,26 @@ public class MainWindow extends JFrame {
|
||||
Utils.setWindowIcons(this);
|
||||
loadSettings();
|
||||
checkForUpdate();
|
||||
newProject();
|
||||
}
|
||||
|
||||
public void open() {
|
||||
public void init() {
|
||||
pack();
|
||||
setLocationAndPosition();
|
||||
heapUsageBar.setVisible(settings.isShowHeapUsageBar());
|
||||
setVisible(true);
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent e) {
|
||||
closeWindow();
|
||||
}
|
||||
});
|
||||
|
||||
if (settings.getFiles().isEmpty()) {
|
||||
openFile();
|
||||
openFileOrProject();
|
||||
} else {
|
||||
openFile(new File(settings.getFiles().get(0)));
|
||||
open(Paths.get(settings.getFiles().get(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +188,7 @@ public class MainWindow extends JFrame {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLink.setText(String.format(NLS.str("menu.update_label"), r.getName()));
|
||||
updateLink.setText(NLS.str("menu.update_label", r.getName()));
|
||||
updateLink.setVisible(true);
|
||||
}
|
||||
});
|
||||
@@ -154,33 +196,160 @@ public class MainWindow extends JFrame {
|
||||
});
|
||||
}
|
||||
|
||||
public void openFile() {
|
||||
public void openFileOrProject() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setAcceptAllFileFilterUsed(true);
|
||||
String[] exts = {"apk", "dex", "jar", "class", "zip", "aar", "arsc"};
|
||||
String[] exts = {JadxProject.PROJECT_EXTENSION, "apk", "dex", "jar", "class", "zip", "aar", "arsc"};
|
||||
String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')');
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts));
|
||||
fileChooser.setToolTipText(NLS.str("file.open_action"));
|
||||
String currentDirectory = settings.getLastOpenFilePath();
|
||||
if (!currentDirectory.isEmpty()) {
|
||||
fileChooser.setCurrentDirectory(new File(currentDirectory));
|
||||
Path currentDirectory = settings.getLastOpenFilePath();
|
||||
if (currentDirectory != null) {
|
||||
fileChooser.setCurrentDirectory(currentDirectory.toFile());
|
||||
}
|
||||
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.open_title"));
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
settings.setLastOpenFilePath(fileChooser.getCurrentDirectory().getPath());
|
||||
openFile(fileChooser.getSelectedFile());
|
||||
settings.setLastOpenFilePath(fileChooser.getCurrentDirectory().toPath());
|
||||
open(fileChooser.getSelectedFile().toPath());
|
||||
}
|
||||
}
|
||||
|
||||
public void openFile(File file) {
|
||||
private void newProject() {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = new JadxProject(settings);
|
||||
update();
|
||||
clearTree();
|
||||
}
|
||||
|
||||
private void clearTree() {
|
||||
tabbedPane.closeAllTabs();
|
||||
resetCache();
|
||||
wrapper.openFile(file);
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
settings.addRecentFile(file.getAbsolutePath());
|
||||
initTree();
|
||||
setTitle(DEFAULT_TITLE + " - " + file.getName());
|
||||
runBackgroundJobs();
|
||||
treeRoot = null;
|
||||
treeModel.setRoot(treeRoot);
|
||||
treeModel.reload();
|
||||
}
|
||||
|
||||
private void saveProject() {
|
||||
if (project.getProjectPath() == null) {
|
||||
saveProjectAs();
|
||||
}
|
||||
else {
|
||||
project.save();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveProjectAs() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setAcceptAllFileFilterUsed(true);
|
||||
String[] exts = {JadxProject.PROJECT_EXTENSION};
|
||||
String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')');
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts));
|
||||
fileChooser.setToolTipText(NLS.str("file.save_project"));
|
||||
Path currentDirectory = settings.getLastSaveProjectPath();
|
||||
if (currentDirectory != null) {
|
||||
fileChooser.setCurrentDirectory(currentDirectory.toFile());
|
||||
}
|
||||
int ret = fileChooser.showSaveDialog(mainPanel);
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
settings.setLastSaveProjectPath(fileChooser.getCurrentDirectory().toPath());
|
||||
|
||||
Path path = fileChooser.getSelectedFile().toPath();
|
||||
if (!path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) {
|
||||
path = path.resolveSibling(path.getFileName() + "." + JadxProject.PROJECT_EXTENSION);
|
||||
}
|
||||
|
||||
if (Files.exists(path)) {
|
||||
int res = JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
NLS.str("confirm.save_as_message", path.getFileName()),
|
||||
NLS.str("confirm.save_as_title"),
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (res == JOptionPane.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
project.saveAs(path);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void open(Path path) {
|
||||
if (path.getFileName().toString().toLowerCase(Locale.ROOT)
|
||||
.endsWith(JadxProject.PROJECT_EXTENSION)) {
|
||||
openProject(path);
|
||||
}
|
||||
else {
|
||||
project.setFilePath(path);
|
||||
tabbedPane.closeAllTabs();
|
||||
resetCache();
|
||||
wrapper.openFile(path.toFile());
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
initTree();
|
||||
update();
|
||||
runBackgroundJobs();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureProjectIsSaved() {
|
||||
if (project != null && !project.isSaved() && !project.isInitial()) {
|
||||
int res = JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
NLS.str("confirm.not_saved_message"),
|
||||
NLS.str("confirm.not_saved_title"),
|
||||
JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
if (res == JOptionPane.CANCEL_OPTION) {
|
||||
return false;
|
||||
}
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
project.save();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void openProject(Path path) {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = JadxProject.from(path, settings);
|
||||
if (project == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
NLS.str("msg.project_error"),
|
||||
NLS.str("msg.project_error_title"),
|
||||
JOptionPane.INFORMATION_MESSAGE
|
||||
);
|
||||
return;
|
||||
}
|
||||
update();
|
||||
settings.addRecentProject(path);
|
||||
Path filePath = project.getFilePath();
|
||||
if (filePath == null) {
|
||||
clearTree();
|
||||
}
|
||||
else {
|
||||
open(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
newProjectAction.setEnabled(!project.isInitial());
|
||||
saveProjectAction.setEnabled(!project.isSaved());
|
||||
|
||||
Path projectPath = project.getProjectPath();
|
||||
String pathString;
|
||||
if (projectPath == null) {
|
||||
pathString = "";
|
||||
}
|
||||
else {
|
||||
pathString = " [" + projectPath.getParent().toAbsolutePath() + ']';
|
||||
}
|
||||
setTitle((project.isSaved() ? "" : '*')
|
||||
+ project.getName() + pathString + " - " + DEFAULT_TITLE);
|
||||
|
||||
}
|
||||
|
||||
protected void resetCache() {
|
||||
@@ -215,7 +384,7 @@ public class MainWindow extends JFrame {
|
||||
public void reOpenFile() {
|
||||
File openedFile = wrapper.getOpenFile();
|
||||
if (openedFile != null) {
|
||||
openFile(openedFile);
|
||||
open(openedFile.toPath());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,12 +393,12 @@ public class MainWindow extends JFrame {
|
||||
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
|
||||
|
||||
String currentDirectory = settings.getLastSaveFilePath();
|
||||
if (!currentDirectory.isEmpty()) {
|
||||
fileChooser.setCurrentDirectory(new File(currentDirectory));
|
||||
Path currentDirectory = settings.getLastSaveFilePath();
|
||||
if (currentDirectory != null) {
|
||||
fileChooser.setCurrentDirectory(currentDirectory.toFile());
|
||||
}
|
||||
|
||||
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
|
||||
int ret = fileChooser.showSaveDialog(mainPanel);
|
||||
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||
JadxArgs decompilerArgs = wrapper.getArgs();
|
||||
decompilerArgs.setExportAsGradleProject(export);
|
||||
@@ -240,7 +409,7 @@ public class MainWindow extends JFrame {
|
||||
decompilerArgs.setSkipSources(settings.isSkipSources());
|
||||
decompilerArgs.setSkipResources(settings.isSkipResources());
|
||||
}
|
||||
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
|
||||
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().toPath());
|
||||
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
|
||||
progressMonitor.setMillisToPopup(0);
|
||||
wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor);
|
||||
@@ -351,12 +520,36 @@ public class MainWindow extends JFrame {
|
||||
Action openAction = new AbstractAction(NLS.str("file.open_action"), ICON_OPEN) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
openFile();
|
||||
openFileOrProject();
|
||||
}
|
||||
};
|
||||
openAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.open_action"));
|
||||
openAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_DOWN_MASK));
|
||||
|
||||
newProjectAction = new AbstractAction(NLS.str("file.new_project")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
newProject();
|
||||
}
|
||||
};
|
||||
newProjectAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.new_project"));
|
||||
|
||||
saveProjectAction = new AbstractAction(NLS.str("file.save_project")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
saveProject();
|
||||
}
|
||||
};
|
||||
saveProjectAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project"));
|
||||
|
||||
Action saveProjectAsAction = new AbstractAction(NLS.str("file.save_project_as")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
saveProjectAs();
|
||||
}
|
||||
};
|
||||
saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as"));
|
||||
|
||||
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -375,8 +568,8 @@ public class MainWindow extends JFrame {
|
||||
exportAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.export_gradle"));
|
||||
exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_DOWN_MASK));
|
||||
|
||||
JMenu recentFiles = new JMenu(NLS.str("menu.recent_files"));
|
||||
recentFiles.addMenuListener(new RecentFilesMenuListener(recentFiles));
|
||||
JMenu recentProjects = new JMenu(NLS.str("menu.recent_projects"));
|
||||
recentProjects.addMenuListener(new RecentProjectsMenuListener(recentProjects));
|
||||
|
||||
Action prefsAction = new AbstractAction(NLS.str("menu.preferences"), ICON_PREF) {
|
||||
@Override
|
||||
@@ -491,10 +684,15 @@ public class MainWindow extends JFrame {
|
||||
JMenu file = new JMenu(NLS.str("menu.file"));
|
||||
file.setMnemonic(KeyEvent.VK_F);
|
||||
file.add(openAction);
|
||||
file.addSeparator();
|
||||
file.add(newProjectAction);
|
||||
file.add(saveProjectAction);
|
||||
file.add(saveProjectAsAction);
|
||||
file.addSeparator();
|
||||
file.add(saveAllAction);
|
||||
file.add(exportAction);
|
||||
file.addSeparator();
|
||||
file.add(recentFiles);
|
||||
file.add(recentProjects);
|
||||
file.addSeparator();
|
||||
file.add(prefsAction);
|
||||
file.addSeparator();
|
||||
@@ -694,11 +892,13 @@ public class MainWindow extends JFrame {
|
||||
tabbedPane.loadSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
private void closeWindow() {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
settings.saveWindowPos(this);
|
||||
cancelBackgroundJobs();
|
||||
super.dispose();
|
||||
dispose();
|
||||
}
|
||||
|
||||
public JadxWrapper getWrapper() {
|
||||
@@ -721,28 +921,27 @@ public class MainWindow extends JFrame {
|
||||
return backgroundWorker;
|
||||
}
|
||||
|
||||
private class RecentFilesMenuListener implements MenuListener {
|
||||
private final JMenu recentFiles;
|
||||
private class RecentProjectsMenuListener implements MenuListener {
|
||||
private final JMenu recentProjects;
|
||||
|
||||
public RecentFilesMenuListener(JMenu recentFiles) {
|
||||
this.recentFiles = recentFiles;
|
||||
public RecentProjectsMenuListener(JMenu recentProjects) {
|
||||
this.recentProjects = recentProjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void menuSelected(MenuEvent menuEvent) {
|
||||
recentFiles.removeAll();
|
||||
recentProjects.removeAll();
|
||||
File openFile = wrapper.getOpenFile();
|
||||
String currentFile = openFile == null ? "" : openFile.getAbsolutePath();
|
||||
for (final String file : settings.getRecentFiles()) {
|
||||
if (file.equals(currentFile)) {
|
||||
continue;
|
||||
Path currentPath = openFile == null ? null : openFile.toPath();
|
||||
for (final Path path : settings.getRecentProjects()) {
|
||||
if (!path.equals(currentPath)) {
|
||||
JMenuItem menuItem = new JMenuItem(path.toAbsolutePath().toString());
|
||||
recentProjects.add(menuItem);
|
||||
menuItem.addActionListener(e -> open(path));
|
||||
}
|
||||
JMenuItem menuItem = new JMenuItem(file);
|
||||
recentFiles.add(menuItem);
|
||||
menuItem.addActionListener(e -> openFile(new File(file)));
|
||||
}
|
||||
if (recentFiles.getItemCount() == 0) {
|
||||
recentFiles.add(new JMenuItem(NLS.str("menu.no_recent_files")));
|
||||
if (recentProjects.getItemCount() == 0) {
|
||||
recentProjects.add(new JMenuItem(NLS.str("menu.no_recent_projects")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,12 +61,14 @@ public class NLS {
|
||||
i18nMessagesMap.put(locale, bundle);
|
||||
}
|
||||
|
||||
public static String str(String key) {
|
||||
public static String str(String key, Object... parameters) {
|
||||
String value;
|
||||
try {
|
||||
return localizedMessagesMap.getString(key);
|
||||
value = localizedMessagesMap.getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
return fallbackMessagesMap.getString(key); // definitely exists
|
||||
value = fallbackMessagesMap.getString(key); // definitely exists
|
||||
}
|
||||
return String.format(value, parameters);
|
||||
}
|
||||
|
||||
public static String str(String key, LangLocale locale) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
public class PathTypeAdapter {
|
||||
|
||||
private static TypeAdapter<Path> SINGLETON;
|
||||
|
||||
public static TypeAdapter<Path> singleton() {
|
||||
if (SINGLETON == null) {
|
||||
SINGLETON = new TypeAdapter<Path>() {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Path value) throws IOException {
|
||||
if (value == null) {
|
||||
out.nullValue();
|
||||
} else {
|
||||
out.value(value.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
return Paths.get(in.nextString());
|
||||
}
|
||||
};
|
||||
}
|
||||
return SINGLETON;
|
||||
}
|
||||
|
||||
private PathTypeAdapter() {
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ language.name=English
|
||||
|
||||
menu.file=File
|
||||
menu.view=View
|
||||
menu.recent_files=Recent Files
|
||||
menu.no_recent_files=No recent files
|
||||
menu.recent_projects=Recent projects
|
||||
menu.no_recent_projects=No recent projects
|
||||
menu.preferences=Preferences
|
||||
menu.sync=Sync with editor
|
||||
menu.flatten=Show flatten packages
|
||||
@@ -20,17 +20,18 @@ menu.update_label=New version %s available!
|
||||
|
||||
file.open_action=Open file...
|
||||
file.open_title=Open file
|
||||
file.new_project=New project
|
||||
file.save_project=Save project
|
||||
file.save_project_as=Save project as...
|
||||
file.save_all=Save all
|
||||
file.export_gradle=Save as gradle project
|
||||
file.save_all_msg=Select directory for save decompiled sources
|
||||
file.select=Select
|
||||
file.exit=Exit
|
||||
|
||||
tree.sources_title=Source code
|
||||
tree.resources_title=Resources
|
||||
tree.loading=Loading...
|
||||
|
||||
search=Search
|
||||
search.previous=Previous
|
||||
search.next=Next
|
||||
search.mark_all=Mark All
|
||||
@@ -79,6 +80,7 @@ preferences.title=Preferences
|
||||
preferences.deobfuscation=Deobfuscation
|
||||
preferences.editor=Editor
|
||||
preferences.decompile=Decompilation
|
||||
preferences.project=Project
|
||||
preferences.other=Other
|
||||
preferences.language=Language
|
||||
preferences.check_for_updates=Check for updates on startup
|
||||
@@ -89,6 +91,7 @@ preferences.replaceConsts=Replace constants
|
||||
preferences.respectBytecodeAccessModifiers=Respect bytecode access modifiers
|
||||
preferences.useImports=Use import statements
|
||||
preferences.skipResourcesDecode=Don't decode resources
|
||||
preferences.autoSave=Auto save
|
||||
preferences.threads=Processing threads count
|
||||
preferences.excludedPackages=Excluded packages
|
||||
preferences.excludedPackages.tooltip=List of space separated package names that will not be decompiled or indexed (saves RAM)
|
||||
@@ -116,6 +119,8 @@ msg.saving_sources=Saving sources...
|
||||
msg.language_changed_title=Language changed
|
||||
msg.language_changed=New language will be displayed the next time application starts.
|
||||
msg.index_not_initialized=Index not initialized, search will be disabled!
|
||||
msg.project_error_title=Error
|
||||
msg.project_error=Project could not be loaded
|
||||
|
||||
popup.undo=Undo
|
||||
popup.redo=Redo
|
||||
@@ -127,11 +132,15 @@ popup.select_all=Select All
|
||||
popup.find_usage=Find Usage
|
||||
popup.exclude=Exclude
|
||||
|
||||
confirm.save_as_title=Confirm Save as
|
||||
confirm.save_as_message=%s already exists.\nDo you want to replace it?
|
||||
confirm.not_saved_title=Save project
|
||||
confirm.not_saved_message=Save the current project before opening the new one?
|
||||
|
||||
certificate.title=Certificate
|
||||
certificate.cert_type=Type
|
||||
certificate.serialSigVer=Version
|
||||
certificate.serialNumber=Serial number
|
||||
certificate.cert_issuer=Issuer
|
||||
certificate.cert_subject=Subject
|
||||
certificate.serialValidFrom=Valid from
|
||||
certificate.serialValidUntil=Valid until
|
||||
|
||||
@@ -2,8 +2,8 @@ language.name=Español
|
||||
|
||||
menu.file=Archivo
|
||||
menu.view=Vista
|
||||
menu.recent_files=Archivos recientes
|
||||
menu.no_recent_files=No hay archivos recientes
|
||||
#menu.recent_projects=
|
||||
#menu.no_recent_projects=
|
||||
menu.preferences=Preferencias
|
||||
menu.sync=Sincronizar con el editor
|
||||
menu.flatten=Mostrar paquetes en vista plana
|
||||
@@ -20,17 +20,18 @@ menu.update_label=¡Nueva versión %s disponible!
|
||||
|
||||
file.open_action=Abrir archivo...
|
||||
file.open_title=Abrir archivo
|
||||
#file.new_project=
|
||||
#file.save_project=
|
||||
#file.save_project_as=
|
||||
file.save_all=Guardar todo
|
||||
file.export_gradle=Guardar como proyecto Gradle
|
||||
file.save_all_msg=Seleccionar carpeta para guardar fuentes descompiladas
|
||||
file.select=Seleccionar
|
||||
file.exit=Salir
|
||||
|
||||
tree.sources_title=Código fuente
|
||||
tree.resources_title=Recursos
|
||||
tree.loading=Cargando...
|
||||
|
||||
search=Buscar
|
||||
search.previous=Anterior
|
||||
search.next=Siguiente
|
||||
search.mark_all=Marcar todo
|
||||
@@ -79,6 +80,7 @@ preferences.title=Preferencias
|
||||
preferences.deobfuscation=Desofuscación
|
||||
preferences.editor=Editor
|
||||
preferences.decompile=Descompilación
|
||||
#preferences.project=
|
||||
preferences.other=Otros
|
||||
preferences.language=Idioma
|
||||
preferences.check_for_updates=Buscar actualizaciones al iniciar
|
||||
@@ -89,6 +91,7 @@ preferences.replaceConsts=Reemplazar constantes
|
||||
#preferences.respectBytecodeAccessModifiers=
|
||||
#preferences.useImports=
|
||||
preferences.skipResourcesDecode=No descodificar recursos
|
||||
#preferences.autoSave=
|
||||
preferences.threads=Número de hilos a procesar
|
||||
#preferences.excludedPackages=
|
||||
#preferences.excludedPackages.tooltip=
|
||||
@@ -116,6 +119,8 @@ msg.saving_sources=Guardando fuente...
|
||||
msg.language_changed_title=Idioma cambiado
|
||||
msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie.
|
||||
msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará!
|
||||
#msg.project_error_title=
|
||||
#msg.project_error=
|
||||
|
||||
popup.undo=Deshacer
|
||||
popup.redo=Rehacer
|
||||
@@ -127,11 +132,15 @@ popup.select_all=Seleccionar todo
|
||||
#popup.find_usage=
|
||||
#popup.exclude=
|
||||
|
||||
#confirm.save_as_title=
|
||||
#confirm.save_as_message=
|
||||
#confirm.not_saved_title=
|
||||
#confirm.not_saved_message=
|
||||
|
||||
certificate.title=Certificado
|
||||
certificate.cert_type=Tipo
|
||||
certificate.serialSigVer=Versión
|
||||
certificate.serialNumber=Número de serial
|
||||
certificate.cert_issuer=Issuer
|
||||
certificate.cert_subject=Subject
|
||||
certificate.serialValidFrom=Válido desde
|
||||
certificate.serialValidUntil=Válido hasta
|
||||
|
||||
@@ -2,8 +2,8 @@ language.name=中文(简体)
|
||||
|
||||
menu.file=文件
|
||||
menu.view=视图
|
||||
menu.recent_files=最近打开的文件
|
||||
menu.no_recent_files=无最近打开的文件
|
||||
#menu.recent_projects=
|
||||
#menu.no_recent_projects=
|
||||
menu.preferences=首选项
|
||||
menu.sync=与编辑器同步
|
||||
menu.flatten=展开显示代码包
|
||||
@@ -20,17 +20,18 @@ menu.update_label=发现新版本 %s!
|
||||
|
||||
file.open_action=打开文件...
|
||||
file.open_title=打开文件
|
||||
#file.new_project=
|
||||
#file.save_project=
|
||||
#file.save_project_as=
|
||||
file.save_all=全部保存
|
||||
file.export_gradle=另存为 Gradle 项目
|
||||
file.save_all_msg=选择反编译资源路径
|
||||
file.select=选择
|
||||
file.exit=退出
|
||||
|
||||
tree.sources_title=源代码
|
||||
tree.resources_title=资源文件
|
||||
tree.loading=稍等...
|
||||
|
||||
search=搜索
|
||||
search.previous=上一个
|
||||
search.next=下一个
|
||||
search.mark_all=标记全部
|
||||
@@ -79,6 +80,7 @@ preferences.title=首选项
|
||||
preferences.deobfuscation=反混淆
|
||||
preferences.editor=编辑器
|
||||
preferences.decompile=反编译
|
||||
#preferences.project=
|
||||
preferences.other=其他
|
||||
preferences.language=语言
|
||||
preferences.check_for_updates=启动时检查更新
|
||||
@@ -89,6 +91,7 @@ preferences.replaceConsts=替换常量
|
||||
preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符
|
||||
preferences.useImports=使用 import 语句
|
||||
preferences.skipResourcesDecode=不反编译资源文件
|
||||
#preferences.autoSave=
|
||||
preferences.threads=并行线程数
|
||||
preferences.excludedPackages=排除的包
|
||||
preferences.excludedPackages.tooltip=将不被解压缩或索引的以空格分隔的包名称列表(节省 RAM)
|
||||
@@ -116,6 +119,8 @@ msg.saving_sources=正在导出源代码...
|
||||
msg.language_changed_title=语言已更改
|
||||
msg.language_changed=在下次启动时将会显示新的语言。
|
||||
msg.index_not_initialized=索引尚未初始化,无法进行搜索!
|
||||
#msg.project_error_title=
|
||||
#msg.project_error=
|
||||
|
||||
popup.undo=撤销
|
||||
popup.redo=重做
|
||||
@@ -127,11 +132,15 @@ popup.select_all=全选
|
||||
popup.find_usage=查找用例
|
||||
#popup.exclude=
|
||||
|
||||
#confirm.save_as_title=
|
||||
#confirm.save_as_message=
|
||||
#confirm.not_saved_title=
|
||||
#confirm.not_saved_message=
|
||||
|
||||
certificate.title=证书
|
||||
certificate.cert_type=类型
|
||||
certificate.serialSigVer=版本
|
||||
certificate.serialNumber=序列号
|
||||
certificate.cert_issuer=颁发者
|
||||
certificate.cert_subject=主题
|
||||
certificate.serialValidFrom=有效期始
|
||||
certificate.serialValidUntil=有效期至
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
package jadx.gui;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TestI18n {
|
||||
|
||||
private static Path guiJavaPath;
|
||||
private static Path i18nPath;
|
||||
|
||||
private List<String> reference;
|
||||
private String referenceName;
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
i18nPath = Paths.get("src/main/resources/i18n");
|
||||
assertTrue(Files.exists(i18nPath));
|
||||
guiJavaPath = Paths.get("src/main/java");
|
||||
assertTrue(Files.exists(guiJavaPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filesExactlyMatch() throws IOException {
|
||||
Path path = Paths.get("./src/main/resources/i18n");
|
||||
assertTrue(Files.exists(path));
|
||||
Files.list(path).forEach(p -> {
|
||||
Files.list(i18nPath).forEach(p -> {
|
||||
List<String> lines;
|
||||
try {
|
||||
lines = Files.readAllLines(p);
|
||||
@@ -45,12 +64,12 @@ public class TestI18n {
|
||||
if (p0 != -1) {
|
||||
String prefix = line.substring(0, p0 + 1);
|
||||
if (i >= lines.size() || !trimComment(lines.get(i)).startsWith(prefix)) {
|
||||
fail(path, i + 1);
|
||||
failLine(path, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lines.size() != reference.size()) {
|
||||
fail(path, reference.size());
|
||||
failLine(path, reference.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +77,37 @@ public class TestI18n {
|
||||
return string.startsWith("#") ? string.substring(1) : string;
|
||||
}
|
||||
|
||||
private void fail(Path path, int line) {
|
||||
Assertions.fail("I18n files " + path.getFileName() + " and " + referenceName + " differ in line " + line);
|
||||
private void failLine(Path path, int line) {
|
||||
fail("I18n files " + path.getFileName() + " and " + referenceName + " differ in line " + line);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyIsUsed() throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (Reader reader = Files.newBufferedReader(i18nPath.resolve("Messages_en_US.properties"))) {
|
||||
properties.load(reader);
|
||||
}
|
||||
|
||||
Set<String> keys = new HashSet<>();
|
||||
for (Object key : properties.keySet()) {
|
||||
keys.add("\"" + key + '"');
|
||||
}
|
||||
|
||||
Files.walk(guiJavaPath).filter(p -> Files.isRegularFile(p)).forEach(p -> {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(p);
|
||||
for (String line : lines) {
|
||||
for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
|
||||
if (line.contains(it.next())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
assertThat("keys not used", keys, empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user