feat: mapping-io import support (#1531)(PR #1532)

* Add new CLI args for mapping files and deprecate args regarding jobf files (will be moved to the cache dir in the future)

* Add support for importing method arg mappings

Also change `mapping-file` to `mappings-path`, since folders are supported, too

* Add GUI for importing mappings

* Also show save file dialog when exporting mappings

* Fix crash on startup when `--mappings-path` parameter is set

* Include imported renames when exporting mappings

* Add "close mappings" menu entry

* Don't instantiate MappingTree unless actually needed

* Terminology: `import` → `open`; `export` → `save`

* Save location of open mapping file into project data

* Correctly reset cache when loading new mappings

* Remove unused import

* Save opened mappings' last modified date to reset cache when changed

* Fix if statement

* Correctly handle absence of mappings path in project data

* Show overwrite warning for folders only if not empty

* Prevent crash when imported mappings don't have any namespaces

* Handle wrong mappings namespace count error

* Replace unneeded public with private

* Add option for saving open mappings directly to disk

* Correctly propagate and throw exceptions during decompiler init

* Respect opened mappings' existing namespaces; fix related crash

* Deduplicate code, add `DalvikToJavaBytecodeUtils` class

* Small cleanup; move more functionality to utility class

* Support for importing class, field and method mappings

* Handle mappings in RenameDialog

* Fix checkstyle

* Fix wrong naming order

* Use modified mapping-io JAR from https://github.com/skylot/jadx/commit/18070eb7a649db0b0daef38d456316d5b4650072

That commit got rid of redundant embedded libraries

* Add null checks

* Check if mapping tree is null before running MappingsVisitor

* Use working mapping-io build

* Handle cache invalidation directly in DiskCodeCache class

* Don't reset UserRenamesMappingsMode if project is just reloaded

* Fix checkstyle

Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
Julian Burner
2022-08-10 16:37:55 +02:00
committed by Skylot
parent cb1f3e9843
commit cb91c8c41c
37 changed files with 1156 additions and 198 deletions
@@ -50,6 +50,7 @@ public class JadxWrapper {
private final MainWindow mainWindow;
private volatile @Nullable JadxDecompiler decompiler;
private PluginsContext pluginsContext;
private boolean resetDiskCacheOnNextReload = false;
public JadxWrapper(MainWindow mainWindow) {
this.mainWindow = mainWindow;
@@ -71,8 +72,8 @@ public class JadxWrapper {
initCodeCache();
}
} catch (Exception e) {
LOG.error("Jadx decompiler wrapper init error", e);
close();
throw new JadxRuntimeException("Jadx decompiler wrapper init error", e);
}
}
@@ -118,8 +119,15 @@ public class JadxWrapper {
}
}
public void resetDiskCacheOnNextReload() {
resetDiskCacheOnNextReload = true;
}
private BufferCodeCache buildBufferedDiskCache() {
DiskCodeCache diskCache = new DiskCodeCache(getDecompiler().getRoot(), getProject().getCacheDir());
DiskCodeCache diskCache = new DiskCodeCache(getDecompiler().getRoot(), getProject(), getSettings());
if (resetDiskCacheOnNextReload) {
diskCache.reset();
}
return new BufferCodeCache(diskCache);
}
@@ -231,6 +239,12 @@ public class JadxWrapper {
public void reloadCodeData() {
getDecompiler().reloadCodeData();
mainWindow.renamesChanged();
}
public void reloadMappings() {
getDecompiler().reloadMappings();
mainWindow.renamesChanged();
}
public JavaNode getJavaNodeByRef(ICodeNodeRef nodeRef) {
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -41,6 +42,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.mappings.DalvikToJavaBytecodeUtils;
public class MappingExporter {
private static final Logger LOG = LoggerFactory.getLogger(MappingExporter.class);
@@ -50,32 +52,6 @@ public class MappingExporter {
this.root = rootNode;
}
private List<VarNode> collectMethodArgs(MethodNode methodNode) {
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
int mthDefPos = methodNode.getDefPosition();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
List<VarNode> args = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
if (pos > lineEndPos) {
// Stop at line end
return Boolean.TRUE;
}
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
if (declRef instanceof VarNode) {
VarNode varNode = (VarNode) declRef;
if (!varNode.getMth().equals(methodNode)) {
// Stop if we've gone too far and have entered a different method
return Boolean.TRUE;
}
args.add(varNode);
}
}
return null;
});
return args;
}
private List<SimpleEntry<VarNode, Integer>> collectMethodVars(MethodNode methodNode) {
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
int mthDefPos = methodNode.getDefPosition();
@@ -160,8 +136,14 @@ public class MappingExporter {
FileUtils.makeDirs(path);
}
String srcNamespace = MappingUtil.NS_SOURCE_FALLBACK;
String dstNamespace = MappingUtil.NS_TARGET_FALLBACK;
if (root.getMappingTree() != null && root.getMappingTree().getDstNamespaces() != null) {
srcNamespace = root.getMappingTree().getSrcNamespace();
dstNamespace = root.getMappingTree().getDstNamespaces().get(0);
}
mappingTree.visitHeader();
mappingTree.visitNamespaces("official", Arrays.asList("named"));
mappingTree.visitNamespaces(srcNamespace, Arrays.asList(dstNamespace));
mappingTree.visitContent();
for (ClassNode cls : root.getClasses()) {
@@ -215,10 +197,12 @@ public class MappingExporter {
}
// Method args
int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1;
int lastArgLvIndex = lvtIndex - 1;
List<VarNode> args = collectMethodArgs(mth);
List<VarNode> args = mth.collectArgsWithoutLoading();
for (VarNode arg : args) {
int lvIndex = arg.getReg() - args.get(0).getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1);
Integer lvIndex = DalvikToJavaBytecodeUtils.getMethodArgLvIndex(arg);
if (lvIndex == null) {
lvIndex = -1;
}
String key = rawClassName + methodInfo.getShortId()
+ JadxCodeRef.forVar(arg.getReg(), arg.getSsa());
if (mappedMethodArgsAndVars.containsKey(key)) {
@@ -226,7 +210,6 @@ public class MappingExporter {
mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, mappedMethodArgsAndVars.get(key));
mappedMethodArgsAndVars.remove(key);
}
lastArgLvIndex = lvIndex;
lvtIndex++;
// Not checking for comments since method args can't have any
}
@@ -235,7 +218,10 @@ public class MappingExporter {
for (SimpleEntry<VarNode, Integer> entry : vars) {
VarNode var = entry.getKey();
int offset = entry.getValue();
int lvIndex = lastArgLvIndex + var.getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1);
Integer lvIndex = DalvikToJavaBytecodeUtils.getMethodVarLvIndex(var);
if (lvIndex == null) {
lvIndex = -1;
}
String key = rawClassName + methodInfo.getShortId()
+ JadxCodeRef.forVar(var.getReg(), var.getSsa());
if (mappedMethodArgsAndVars.containsKey(key)) {
@@ -251,7 +237,11 @@ public class MappingExporter {
}
}
}
// Copy mappings from potentially imported mappings file
if (root.getMappingTree() != null && root.getMappingTree().getDstNamespaces() != null) {
root.getMappingTree().accept(mappingTree);
}
// Write file
MappingWriter writer = MappingWriter.create(path, mappingFormat);
mappingTree.accept(writer);
mappingTree.visitEnd();
@@ -159,6 +159,21 @@ public class JadxProject {
return data.getActiveTab();
}
public Path getMappingsPath() {
return data.getMappingsPath();
}
public void setMappingsPath(Path mappingsPath) {
if (mappingsPath == null) {
data.setMappingsPath(mappingsPath);
changed();
} else if (mappingsPath != getMappingsPath()
&& mappingsPath.toFile().exists()) {
data.setMappingsPath(mappingsPath);
changed();
}
}
public @NotNull Path getCacheDir() {
Path cacheDir = data.getCacheDir();
if (cacheDir != null) {
@@ -29,8 +29,9 @@ import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.cli.JadxCLIArgs;
import jadx.cli.LogHelper;
import jadx.gui.ui.MainWindow;
@@ -337,6 +338,14 @@ public class JadxSettings extends JadxCLIArgs {
this.debugInfo = useDebugInfo;
}
public void setUserRenamesMappingsPath(Path path) {
this.userRenamesMappingsPath = path;
}
public void setUserRenamesMappingsMode(UserRenamesMappingsMode mode) {
this.userRenamesMappingsMode = mode;
}
public void setDeobfuscationOn(boolean deobfuscationOn) {
this.deobfuscationOn = deobfuscationOn;
}
@@ -349,8 +358,8 @@ public class JadxSettings extends JadxCLIArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode mode) {
this.deobfuscationMapFileMode = mode;
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) {
this.generatedRenamesMappingFileMode = mode;
}
public void setDeobfuscationUseSourceNameAsAlias(boolean deobfuscationUseSourceNameAsAlias) {
@@ -678,7 +687,7 @@ public class JadxSettings extends JadxCLIArgs {
setDeobfuscationMaxLength(64);
setDeobfuscationUseSourceNameAsAlias(true);
setDeobfuscationParseKotlinMetadata(true);
setDeobfuscationMapFileMode(DeobfuscationMapFileMode.READ);
setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.getDefault());
setThreadsCount(JadxArgs.DEFAULT_THREADS_COUNT);
setReplaceConsts(true);
setSkipResources(false);
@@ -750,7 +759,7 @@ public class JadxSettings extends JadxCLIArgs {
fromVersion++;
}
if (fromVersion == 15) {
deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
fromVersion++;
}
if (fromVersion == 16) {
@@ -59,7 +59,7 @@ import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
@@ -262,12 +262,14 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
JComboBox<DeobfuscationMapFileMode> deobfMapFileModeCB = new JComboBox<>(DeobfuscationMapFileMode.values());
deobfMapFileModeCB.setSelectedItem(settings.getDeobfuscationMapFileMode());
deobfMapFileModeCB.addActionListener(e -> {
DeobfuscationMapFileMode newValue = (DeobfuscationMapFileMode) deobfMapFileModeCB.getSelectedItem();
if (newValue != settings.getDeobfuscationMapFileMode()) {
settings.setDeobfuscationMapFileMode(newValue);
JComboBox<GeneratedRenamesMappingFileMode> generatedRenamesMappingFileModeCB =
new JComboBox<>(GeneratedRenamesMappingFileMode.values());
generatedRenamesMappingFileModeCB.setSelectedItem(settings.getGeneratedRenamesMappingFileMode());
generatedRenamesMappingFileModeCB.addActionListener(e -> {
GeneratedRenamesMappingFileMode newValue =
(GeneratedRenamesMappingFileMode) generatedRenamesMappingFileModeCB.getSelectedItem();
if (newValue != settings.getGeneratedRenamesMappingFileMode()) {
settings.setGeneratedRenamesMappingFileMode(newValue);
needReload();
}
});
@@ -277,7 +279,7 @@ public class JadxSettingsWindow extends JDialog {
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_res_name_source"), resNamesSource);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_map_file_mode"), deobfMapFileModeCB);
deobfGroup.addRow(NLS.str("preferences.generated_renames_mapping_file_mode"), generatedRenamesMappingFileModeCB);
deobfGroup.end();
Collection<JComponent> connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner);
@@ -18,6 +18,7 @@ public class ProjectData {
private JadxCodeData codeData = new JadxCodeData();
private List<TabViewState> openTabs = Collections.emptyList();
private int activeTab = -1;
private @Nullable Path mappingsPath;
private @Nullable Path cacheDir;
private boolean enableLiveReload = false;
private List<String> searchHistory = new ArrayList<>();
@@ -88,6 +89,15 @@ public class ProjectData {
return true;
}
@Nullable
public Path getMappingsPath() {
return mappingsPath;
}
public void setMappingsPath(Path mappingsPath) {
this.mappingsPath = mappingsPath;
}
@Nullable
public Path getCacheDir() {
return cacheDir;
@@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
@@ -38,7 +39,9 @@ import java.util.Locale;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction;
import javax.swing.Action;
@@ -80,16 +83,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import jadx.api.JadxArgs;
import jadx.api.JavaNode;
import jadx.api.ResourceFile;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.core.Jadx;
import jadx.core.export.TemplateFile;
import jadx.core.utils.ListUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.gui.JadxWrapper;
import jadx.gui.device.debugger.BreakpointManager;
@@ -180,10 +188,16 @@ public class MainWindow extends JFrame {
private final transient BackgroundExecutor backgroundExecutor;
private transient @NotNull JadxProject project;
private boolean projectOpen = false;
private transient Action newProjectAction;
private transient Action saveProjectAction;
private transient JMenu exportMappingsMenu;
private transient JMenu openMappingsMenu;
private transient Action saveMappingsAction;
private transient JMenu saveMappingsAsMenu;
private transient Action closeMappingsAction;
private MappingFormat currentMappingFormat;
private boolean renamesChanged = false;
private JPanel mainPanel;
private JSplitPane splitPane;
@@ -332,8 +346,7 @@ public class MainWindow extends JFrame {
if (!ensureProjectIsSaved()) {
return;
}
closeAll();
exportMappingsMenu.setEnabled(false);
closeAll(false);
updateProject(new JadxProject(this));
}
@@ -377,31 +390,126 @@ public class MainWindow extends JFrame {
update();
}
private void exportMappings(MappingFormat mappingFormat) {
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE);
fileDialog.setTitle(NLS.str("file.export_mappings_as"));
Path workingDir = project.getWorkingDir();
Path baseDir = workingDir != null ? workingDir : settings.getLastSaveFilePath();
private void openMappings(MappingFormat mappingFormat) {
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_OPEN);
fileDialog.setTitle(NLS.str("file.open_mappings"));
if (mappingFormat.hasSingleFile()) {
fileDialog.setSelectedFile(baseDir.resolve("mappings." + mappingFormat.fileExt));
fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt));
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
} else {
fileDialog.setCurrentDir(baseDir);
fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
List<Path> paths = fileDialog.show();
if (paths.size() != 1) {
List<Path> selectedPaths = fileDialog.show();
if (selectedPaths.size() != 1) {
return;
}
Path savePath = paths.get(0);
LOG.info("Export mappings to: {}", savePath.toAbsolutePath());
backgroundExecutor.execute(NLS.str("progress.export_mappings"),
() -> new MappingExporter(wrapper.getDecompiler().getRoot())
.exportMappings(savePath, project.getCodeData(), mappingFormat),
settings.setLastOpenFilePath(fileDialog.getCurrentDir());
Path filePath = selectedPaths.get(0);
LOG.info("Loading mappings from: {}", filePath.toAbsolutePath());
MemoryMappingTree mappingTree = new MemoryMappingTree();
try {
MappingReader.read(filePath, mappingTree);
} catch (IOException e) {
throw new JadxRuntimeException("Failed to load mappings file", e);
}
if (mappingTree.getSrcNamespace() == null) {
mappingTree.setSrcNamespace(MappingUtil.NS_SOURCE_FALLBACK);
}
if (mappingTree.getDstNamespaces() == null || mappingTree.getDstNamespaces().isEmpty()) {
mappingTree.setDstNamespaces(Arrays.asList(MappingUtil.NS_TARGET_FALLBACK));
} else if (mappingTree.getDstNamespaces().size() > 1) {
JOptionPane.showMessageDialog(
this,
NLS.str("msg.mapping_namespace_count_error", mappingTree.getDstNamespaces().size()),
NLS.str("msg.mapping_namespace_count_error_title"),
JOptionPane.ERROR_MESSAGE);
return;
}
closeMappings(true);
project.setMappingsPath(filePath);
reopen();
}
private void closeMappings(boolean resetMappingsMode) {
if (projectOpen) {
wrapper.getRootNode().setMappingTree(null);
}
if (resetMappingsMode) {
wrapper.getSettings().setUserRenamesMappingsPath(null);
wrapper.getSettings().setUserRenamesMappingsMode(UserRenamesMappingsMode.getDefault());
}
}
private void closeMappingsAndRemoveFromProject() {
closeMappings(true);
project.setMappingsPath(null);
}
private void saveMappings() {
Path savePath = project.getMappingsPath();
if (currentMappingFormat == null) {
try {
currentMappingFormat = MappingReader.detectFormat(savePath);
} catch (IOException e) {
throw new JadxRuntimeException("Failed to save mappings", e);
}
}
renamesChanged = false;
backgroundExecutor.execute(NLS.str("progress.save_mappings"),
() -> {
new MappingExporter(wrapper.getDecompiler().getRoot())
.exportMappings(savePath, project.getCodeData(), currentMappingFormat);
project.setMappingsPath(savePath);
},
s -> update());
}
private void saveMappingsAs(MappingFormat mappingFormat) {
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE);
fileDialog.setTitle(NLS.str("file.save_mappings_as"));
if (mappingFormat.hasSingleFile()) {
fileDialog.setSelectedFile(fileDialog.getCurrentDir().resolve("mappings." + mappingFormat.fileExt));
fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt));
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
} else {
fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
List<Path> selectedPaths = fileDialog.show();
if (selectedPaths.size() != 1) {
return;
}
settings.setLastSaveFilePath(fileDialog.getCurrentDir());
Path savePath = selectedPaths.get(0);
// Append file extension if missing
if (mappingFormat.hasSingleFile() && !savePath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(mappingFormat.fileExt)) {
savePath = savePath.resolveSibling(savePath.getFileName() + "." + mappingFormat.fileExt);
}
// If the target file already exists (and it's not an empty directory), show an overwrite
// confirmation
if (Files.exists(savePath)) {
boolean emptyDir = false;
try (Stream<Path> entries = Files.list(savePath)) {
emptyDir = !entries.findFirst().isPresent();
} catch (IOException ignored) {
}
if (!emptyDir) {
int res = JOptionPane.showConfirmDialog(
this,
NLS.str("confirm.save_as_message", savePath.getFileName()),
NLS.str("confirm.save_as_title"),
JOptionPane.YES_NO_OPTION);
if (res == JOptionPane.NO_OPTION) {
return;
}
}
}
LOG.info("Saving mappings to: {}", savePath.toAbsolutePath());
project.setMappingsPath(savePath);
currentMappingFormat = mappingFormat;
saveMappings();
}
public void addNewScript() {
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE);
fileDialog.setTitle(NLS.str("file.save"));
@@ -446,14 +554,14 @@ public class MainWindow extends JFrame {
private void open(List<Path> paths, Runnable onFinish) {
saveAll();
closeAll();
closeAll(false);
if (paths.size() == 1 && openSingleFile(paths.get(0), onFinish)) {
return;
}
// start new project
project = new JadxProject(this);
project.setFilePaths(paths);
loadFiles(onFinish);
loadFiles(false, onFinish);
}
private boolean openSingleFile(Path singleFile, Runnable onFinish) {
@@ -479,8 +587,8 @@ public class MainWindow extends JFrame {
public synchronized void reopen() {
saveAll();
closeAll();
loadFiles(EMPTY_RUNNABLE);
closeAll(true);
loadFiles(true, EMPTY_RUNNABLE);
}
private void openProject(Path path, Runnable onFinish) {
@@ -495,17 +603,61 @@ public class MainWindow extends JFrame {
}
settings.addRecentProject(path);
project = jadxProject;
loadFiles(onFinish);
loadFiles(false, onFinish);
}
private void loadFiles(Runnable onFinish) {
exportMappingsMenu.setEnabled(false);
private void loadFiles(boolean reopening, Runnable onFinish) {
if (project.getFilePaths().isEmpty()) {
return;
}
JadxSettings settings = wrapper.getSettings();
if (settings.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE) {
// Use CLI specified mappings path if present
if (settings.getUserRenamesMappingsPath() != null && settings.getUserRenamesMappingsPath().toFile().exists()) {
project.setMappingsPath(settings.getUserRenamesMappingsPath());
} else {
if (settings.getUserRenamesMappingsPath() != null) {
LOG.error("The specified mappings path doesn't exist, falling back to the project's previously loaded ones");
}
MappingFormat mappingFormat = null;
try {
mappingFormat = MappingReader.detectFormat(project.getMappingsPath());
} catch (Exception ignored) {
}
// Use the project's last opened mappings, if present
if (mappingFormat != null) {
settings.setUserRenamesMappingsPath(project.getMappingsPath());
currentMappingFormat = mappingFormat;
} else {
if (project.getMappingsPath() != null
|| (project.getMappingsPath() == null && settings.getUserRenamesMappingsPath() != null)) {
LOG.error("The project's last opened mappings path is corrupted, resetting");
}
// None of the mapping paths exist, so remove them from the settings
settings.setUserRenamesMappingsPath(null);
project.setMappingsPath(null);
}
}
}
AtomicReference<Exception> wrapperException = new AtomicReference<>();
backgroundExecutor.execute(NLS.str("progress.load"),
wrapper::open,
() -> {
try {
wrapper.open();
} catch (Exception e) {
wrapperException.set(e);
}
},
status -> {
if (wrapperException.get() != null) {
closeAll(reopening);
Exception e = wrapperException.get();
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new JadxRuntimeException("Project load error", e);
}
}
if (status == TaskStatus.CANCEL_BY_MEMORY) {
showHeapUsageBar();
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
@@ -517,7 +669,6 @@ public class MainWindow extends JFrame {
}
checkLoadedStatus();
onOpen();
exportMappingsMenu.setEnabled(true);
onFinish.run();
});
}
@@ -527,16 +678,22 @@ public class MainWindow extends JFrame {
BreakpointManager.saveAndExit();
}
private void closeAll() {
private void closeAll(boolean reopening) {
notifyLoadListeners(false);
cancelBackgroundJobs();
clearTree();
if (projectOpen) {
closeMappings(!reopening);
}
resetCache();
LogCollector.getInstance().reset();
wrapper.close();
tabbedPane.closeAllTabs();
UiUtils.resetClipboardOwner();
System.gc();
projectOpen = false;
renamesChanged = false;
update();
}
private void checkLoadedStatus() {
@@ -561,6 +718,7 @@ public class MainWindow extends JFrame {
private void onOpen() {
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree();
projectOpen = true;
update();
updateLiveReload(project.isEnableLiveReload());
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
@@ -597,6 +755,10 @@ public class MainWindow extends JFrame {
private boolean ensureProjectIsSaved() {
if (!project.isSaved() && !project.isInitial()) {
if (wrapper.getRootNode().getMappingTree() != null
&& wrapper.getSettings().getUserRenamesMappingsMode() == UserRenamesMappingsMode.READ_AND_AUTOSAVE_BEFORE_CLOSING) {
saveMappings();
}
int res = JOptionPane.showConfirmDialog(
this,
NLS.str("confirm.not_saved_message"),
@@ -619,7 +781,12 @@ public class MainWindow extends JFrame {
private void update() {
newProjectAction.setEnabled(!project.isInitial());
saveProjectAction.setEnabled(!project.isSaved());
saveProjectAction.setEnabled(projectOpen && !project.isSaved());
openMappingsMenu.setEnabled(projectOpen);
saveMappingsAction.setEnabled(projectOpen && renamesChanged == true);
saveMappingsAsMenu.setEnabled(projectOpen && (!project.getCodeData().getRenames().isEmpty()
|| !project.getCodeData().getComments().isEmpty() || wrapper.getRootNode().getMappingTree() != null));
closeMappingsAction.setEnabled(projectOpen && wrapper.getRootNode().getMappingTree() != null);
Path projectPath = project.getProjectPath();
String pathString;
@@ -632,6 +799,16 @@ public class MainWindow extends JFrame {
+ project.getName() + pathString + " - " + DEFAULT_TITLE);
}
public void renamesChanged() {
UserRenamesMappingsMode mode = wrapper.getSettings().getUserRenamesMappingsMode();
if (mode == UserRenamesMappingsMode.READ_AND_AUTOSAVE_EVERY_CHANGE) {
saveMappings();
} else {
renamesChanged = true;
update();
}
}
protected void resetCache() {
cacheObject.reset();
}
@@ -896,35 +1073,80 @@ public class MainWindow extends JFrame {
liveReloadMenuItem = new JCheckBoxMenuItem(liveReload);
liveReloadMenuItem.setState(project.isEnableLiveReload());
Action exportMappingsAsTiny2 = new AbstractAction("Tiny v2 file") {
Action openTiny2Mappings = new AbstractAction("Tiny v2 file") {
@Override
public void actionPerformed(ActionEvent e) {
exportMappings(MappingFormat.TINY_2);
openMappings(MappingFormat.TINY_2);
}
};
exportMappingsAsTiny2.putValue(Action.SHORT_DESCRIPTION, "Tiny v2 file");
openTiny2Mappings.putValue(Action.SHORT_DESCRIPTION, "Tiny v2 file");
Action exportMappingsAsEnigma = new AbstractAction("Enigma file") {
Action openEnigmaMappings = new AbstractAction("Enigma file") {
@Override
public void actionPerformed(ActionEvent e) {
exportMappings(MappingFormat.ENIGMA);
openMappings(MappingFormat.ENIGMA);
}
};
exportMappingsAsEnigma.putValue(Action.SHORT_DESCRIPTION, "Enigma file");
openEnigmaMappings.putValue(Action.SHORT_DESCRIPTION, "Enigma file");
Action exportMappingsAsEnigmaDir = new AbstractAction("Enigma directory") {
Action openEnigmaDirMappings = new AbstractAction("Enigma directory") {
@Override
public void actionPerformed(ActionEvent e) {
exportMappings(MappingFormat.ENIGMA_DIR);
openMappings(MappingFormat.ENIGMA_DIR);
}
};
exportMappingsAsEnigmaDir.putValue(Action.SHORT_DESCRIPTION, "Enigma directory");
openEnigmaDirMappings.putValue(Action.SHORT_DESCRIPTION, "Enigma directory");
exportMappingsMenu = new JMenu(NLS.str("file.export_mappings_as"));
exportMappingsMenu.add(exportMappingsAsTiny2);
exportMappingsMenu.add(exportMappingsAsEnigma);
exportMappingsMenu.add(exportMappingsAsEnigmaDir);
exportMappingsMenu.setEnabled(false);
openMappingsMenu = new JMenu(NLS.str("file.open_mappings"));
openMappingsMenu.add(openTiny2Mappings);
openMappingsMenu.add(openEnigmaMappings);
openMappingsMenu.add(openEnigmaDirMappings);
saveMappingsAction = new AbstractAction(NLS.str("file.save_mappings")) {
@Override
public void actionPerformed(ActionEvent e) {
saveMappings();
}
};
saveMappingsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_mappings"));
Action saveMappingsAsTiny2 = new AbstractAction("Tiny v2 file") {
@Override
public void actionPerformed(ActionEvent e) {
saveMappingsAs(MappingFormat.TINY_2);
}
};
saveMappingsAsTiny2.putValue(Action.SHORT_DESCRIPTION, "Tiny v2 file");
Action saveMappingsAsEnigma = new AbstractAction("Enigma file") {
@Override
public void actionPerformed(ActionEvent e) {
saveMappingsAs(MappingFormat.ENIGMA);
}
};
saveMappingsAsEnigma.putValue(Action.SHORT_DESCRIPTION, "Enigma file");
Action saveMappingsAsEnigmaDir = new AbstractAction("Enigma directory") {
@Override
public void actionPerformed(ActionEvent e) {
saveMappingsAs(MappingFormat.ENIGMA_DIR);
}
};
saveMappingsAsEnigmaDir.putValue(Action.SHORT_DESCRIPTION, "Enigma directory");
saveMappingsAsMenu = new JMenu(NLS.str("file.save_mappings_as"));
saveMappingsAsMenu.add(saveMappingsAsTiny2);
saveMappingsAsMenu.add(saveMappingsAsEnigma);
saveMappingsAsMenu.add(saveMappingsAsEnigmaDir);
closeMappingsAction = new AbstractAction(NLS.str("file.close_mappings")) {
@Override
public void actionPerformed(ActionEvent e) {
closeMappingsAndRemoveFromProject();
reopen();
}
};
closeMappingsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.close_mappings"));
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) {
@Override
@@ -1120,7 +1342,10 @@ public class MainWindow extends JFrame {
file.add(reload);
file.add(liveReloadMenuItem);
file.addSeparator();
file.add(exportMappingsMenu);
file.add(openMappingsMenu);
file.add(saveMappingsAction);
file.add(saveMappingsAsMenu);
file.add(closeMappingsAction);
file.addSeparator();
file.add(saveAllAction);
file.add(exportAction);
@@ -1498,7 +1723,7 @@ public class MainWindow extends JFrame {
saveSplittersInfo();
}
heapUsageBar.reset();
closeAll();
closeAll(false);
FileUtils.deleteTempRootDir();
dispose();
@@ -1,7 +1,6 @@
package jadx.gui.ui.codearea;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -12,14 +11,9 @@ import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.utils.CodeUtils;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -94,7 +88,8 @@ public final class FridaAction extends JNodeAction {
} else {
overload = "";
}
List<String> argNames = collectMethodArgNames(jMth.getJavaMethod());
List<String> argNames = mth.collectArgsWithoutLoading().stream()
.map(VarNode::getName).collect(Collectors.toList());
String args = String.join(", ", argNames);
String logArgs;
if (argNames.isEmpty()) {
@@ -102,7 +97,7 @@ public final class FridaAction extends JNodeAction {
} else {
logArgs = ": " + argNames.stream().map(arg -> arg + "=${" + arg + "}").collect(Collectors.joining(", "));
}
String shortClassName = mth.getParentClass().getShortName();
String shortClassName = mth.getParentClass().getAlias();
String classSnippet = generateClassSnippet(jMth.getJParent());
if (methodInfo.isConstructor() || methodInfo.getReturnType() == ArgType.VOID) {
// no return value
@@ -121,33 +116,6 @@ public final class FridaAction extends JNodeAction {
+ "};";
}
private List<String> collectMethodArgNames(JavaMethod javaMethod) {
ICodeInfo codeInfo = javaMethod.getTopParentClass().getCodeInfo();
int mthDefPos = javaMethod.getDefPos();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
List<String> argNames = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
if (pos > lineEndPos) {
return Boolean.TRUE; // stop at line end
}
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
if (declRef instanceof VarNode) {
VarNode varNode = (VarNode) declRef;
if (varNode.getMth().equals(javaMethod.getMethodNode())) {
argNames.add(varNode.getName());
}
}
}
return null;
});
int argsCount = javaMethod.getMethodNode().getMethodInfo().getArgsCount();
if (argNames.size() != argsCount) {
LOG.warn("Incorrect args count, expected: {}, got: {}", argsCount, argNames.size());
}
return argNames;
}
private String generateClassSnippet(JClass jc) {
JavaClass javaClass = jc.getCls();
String rawClassName = StringEscapeUtils.escapeEcmaScript(javaClass.getRawName());
@@ -31,15 +31,29 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.data.ICodeRename;
import jadx.api.data.impl.JadxCodeData;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.Utils;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JRenameNode;
import jadx.gui.treemodel.JVariable;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
@@ -129,6 +143,97 @@ public class RenameDialog extends JDialog {
if (!newName.isEmpty()) {
renames.add(rename);
}
MemoryMappingTree mappingTree = mainWindow.getWrapper().getRootNode().getMappingTree();
if (mappingTree == null) {
return;
}
if (newName.isEmpty() || (javaNode != null && newName.equals(javaNode.getName()))) {
newName = null;
}
if (node instanceof JMethod) {
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
String classPath = javaMethod.getDeclaringClass().getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
String methodName = javaMethod.getMethodNode().getMethodInfo().getName();
String methodDesc = javaMethod.getMethodNode().getMethodInfo().getShortId().substring(methodName.length());
if (newName == null) {
MethodMapping mapping = mappingTree.getMethod(classPath, methodName, methodDesc);
if (mapping == null || deleteMappingIfEmpty(mapping, methodName, methodDesc)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitMethod(methodName, methodDesc);
mappingTree.visitDstName(MappedElementKind.METHOD, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JField) {
JavaField javaField = ((JField) node).getJavaField();
String classPath = javaField.getDeclaringClass().getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
String fieldName = javaField.getFieldNode().getFieldInfo().getName();
String fieldDesc = TypeGen.signature(javaField.getFieldNode().getFieldInfo().getType());
if (newName == null) {
FieldMapping mapping = mappingTree.getField(classPath, fieldName, fieldDesc);
if (mapping == null || deleteMappingIfEmpty(mapping, fieldName, fieldDesc)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitField(fieldName, fieldDesc);
mappingTree.visitDstName(MappedElementKind.FIELD, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JClass) {
JavaClass javaClass = ((JClass) node).getCls();
String classPath = javaClass.getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
if (newName == null) {
ClassMapping mapping = mappingTree.getClass(classPath);
if (mapping == null || deleteMappingIfEmpty(mapping)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitDstName(MappedElementKind.CLASS, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JPackage) {
JPackage jPackage = (JPackage) node;
String origPackageName = jPackage.getFullName().replace('.', '/');
for (ClassMapping cls : mappingTree.getClasses()) {
if (!cls.getSrcName().startsWith(origPackageName)) {
continue;
}
if (newName == null) {
newName = "";
}
String newDstName = newName.replace('.', '/') + cls.getDstName(0).substring(newName.length() + 1);
cls.setDstName(newDstName, 0);
}
} else if (node instanceof JVariable) {
// TODO
}
}
private boolean deleteMappingIfEmpty(ClassMapping mapping) {
if (mapping.getFields().isEmpty() && mapping.getMethods().isEmpty()) {
mapping.getTree().removeClass(mapping.getSrcName());
return true;
}
return false;
}
private boolean deleteMappingIfEmpty(MethodMapping mapping, String methodName, String methodDesc) {
if (mapping.getArgs().isEmpty() && mapping.getVars().isEmpty()) {
mapping.getOwner().removeMethod(methodName, methodDesc);
deleteMappingIfEmpty(mapping.getOwner());
return true;
}
return false;
}
private boolean deleteMappingIfEmpty(FieldMapping mapping, String fieldName, String fieldDesc) {
mapping.getOwner().removeMethod(fieldName, fieldDesc);
if (mapping.getOwner().getFields().isEmpty() && mapping.getOwner().getMethods().isEmpty()) {
mapping.getTree().removeClass(mapping.getOwner().getSrcName());
return true;
}
return false;
}
private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
@@ -30,12 +30,15 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.Jadx;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
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 static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
@@ -59,13 +62,14 @@ public class DiskCodeCache implements ICodeCache {
private final Map<String, Integer> namesMap = new ConcurrentHashMap<>();
private final Map<String, Integer> allClsIds;
public DiskCodeCache(RootNode root, Path baseDir) {
public DiskCodeCache(RootNode root, JadxProject project, JadxSettings settings) {
Path baseDir = project.getCacheDir();
srcDir = baseDir.resolve("sources");
metaDir = baseDir.resolve("metadata");
codeVersionFile = baseDir.resolve("code-version");
namesMapFile = baseDir.resolve("names-map");
JadxArgs args = root.getArgs();
codeVersion = buildCodeVersion(args);
codeVersion = buildCodeVersion(args, project, settings);
writePool = Executors.newFixedThreadPool(args.getThreadsCount());
codeMetadataAdapter = new CodeMetadataAdapter(root);
allClsIds = buildClassIdsMap(root.getClasses());
@@ -89,7 +93,7 @@ public class DiskCodeCache implements ICodeCache {
}
}
private void reset() {
public void reset() {
try {
long start = System.currentTimeMillis();
LOG.info("Resetting disk code cache, base dir: {}", srcDir.getParent().toAbsolutePath());
@@ -194,11 +198,19 @@ public class DiskCodeCache implements ICodeCache {
}
}
private String buildCodeVersion(JadxArgs args) {
private String buildCodeVersion(JadxArgs args, JadxProject project, JadxSettings settings) {
long mappingsLastModified = -1;
if (settings.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE
&& project.getMappingsPath() != null
&& project.getMappingsPath().toFile().exists()) {
mappingsLastModified = project.getMappingsPath().toFile().lastModified();
}
return DATA_FORMAT_VERSION
+ ":" + Jadx.getVersion()
+ ":" + args.makeCodeArgsHash()
+ ":" + buildInputsHash(args.getInputFiles());
+ ":" + buildInputsHash(args.getInputFiles())
+ ":" + mappingsLastModified;
}
/**
@@ -32,7 +32,10 @@ file.save_project_as=Projekt speichern als…
file.reload=Dateien neu laden
file.live_reload=Live nachladen
file.live_reload_desc=Dateien bei Änderungen autom. neuladen
file.export_mappings_as=Zuordnungen exportieren als…
#file.open_mappings=
#file.save_mappings=
#file.save_mappings_as=
#file.close_mappings=Zuordnungen exportieren als…
file.save_all=Alles speichern
#file.save=Save
file.export_gradle=Als Gradle-Projekt speichern
@@ -51,7 +54,7 @@ tree.resources_title=Ressourcen
tree.loading=Laden…
progress.load=Laden
progress.export_mappings=Zuordnungen exportieren
progress.save_mappings=Zuordnungen exportieren
progress.decompile=Dekompilieren
progress.canceling=Breche ab
@@ -188,7 +191,7 @@ preferences.start_jobs=Autom. Hintergrunddekompilierung starten
preferences.select_font=Ändern
preferences.select_smali_font=Ändern
preferences.deobfuscation_on=Deobfuskierung aktivieren
preferences.deobfuscation_map_file_mode=Umgang mit Map-Dateien
preferences.generated_renames_mapping_file_mode=Umgang mit Map-Dateien
preferences.deobfuscation_min_len=Minimale Namenlänge
preferences.deobfuscation_max_len=Maximale Namenlänge
preferences.deobfuscation_source_alias=Quelldateiname als Klassennamen-Alias verwenden
@@ -216,6 +219,8 @@ msg.language_changed_title=Sprache speichern
msg.language_changed=Die neue Sprache wird beim nächsten Start der Anwendung angezeigt.
msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen
@@ -32,7 +32,10 @@ file.save_project_as=Save project as...
file.reload=Reload files
file.live_reload=Live reload
file.live_reload_desc=Auto reload files on changes
file.export_mappings_as=Export mappings as...
file.open_mappings=Open mappings...
file.save_mappings=Save mappings
file.save_mappings_as=Save mappings as...
file.close_mappings=Close mappings
file.save_all=Save all
file.save=Save
file.export_gradle=Save as gradle project
@@ -51,7 +54,7 @@ tree.resources_title=Resources
tree.loading=Loading...
progress.load=Loading
progress.export_mappings=Exporting mappings
progress.save_mappings=Saving mappings
progress.decompile=Decompiling
progress.canceling=Canceling
@@ -188,7 +191,7 @@ preferences.start_jobs=Auto start background decompilation
preferences.select_font=Change
preferences.select_smali_font=Change
preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_map_file_mode=Map file handle mode
preferences.generated_renames_mapping_file_mode=Map file handle mode
preferences.deobfuscation_min_len=Minimum name length
preferences.deobfuscation_max_len=Maximum name length
preferences.deobfuscation_source_alias=Use source file name as class name alias
@@ -216,6 +219,8 @@ msg.language_changed_title=Language changed
msg.language_changed=New language will be displayed the next time application starts.
msg.project_error_title=Error
msg.project_error=Project could not be loaded
msg.mapping_namespace_count_error_title=Error
msg.mapping_namespace_count_error=JADX only supports mappings with just one destination namespace! The provided ones have %s.
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
msg.cant_add_comment=Can't add comment here
@@ -32,7 +32,10 @@ file.open_title=Abrir archivo
#file.reload=Reload files
#file.live_reload=Live reload
#file.live_reload_desc=Auto reload files on changes
#file.export_mappings_as=
#file.open_mappings=
#file.save_mappings=
#file.save_mappings_as=
#file.close_mappings=
file.save_all=Guardar todo
#file.save=Save
file.export_gradle=Guardar como proyecto Gradle
@@ -51,7 +54,7 @@ tree.resources_title=Recursos
tree.loading=Cargando...
progress.load=Cargando
#progress.export_mappings=
#progress.save_mappings=
progress.decompile=Decompiling
#progress.canceling=Canceling
@@ -188,7 +191,7 @@ preferences.start_jobs=Inicio autom. descompilación de fondo
preferences.select_font=Seleccionar
#preferences.select_smali_font=
preferences.deobfuscation_on=Activar desobfuscación
#preferences.deobfuscation_map_file_mode=Map file handle mode
#preferences.generated_renames_mapping_file_mode=Map file handle mode
preferences.deobfuscation_min_len=Longitud mínima del nombre
preferences.deobfuscation_max_len=Longitud máxima del nombre
preferences.deobfuscation_source_alias=Usar el nombre del source como alias para la clase
@@ -216,6 +219,8 @@ 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.project_error_title=
#msg.project_error=
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
#msg.cmd_select_class_error=
#msg.cant_add_comment=Can't add comment here
@@ -32,7 +32,10 @@ file.save_project_as=다른 이름으로 프로젝트 저장...
file.reload=파일 다시 로드
file.live_reload=라이브 로드
file.live_reload_desc=파일 내용 변경 시 자동으로 다시 로드
file.export_mappings_as=다른 이름으로 매핑 내보내기...
#file.open_mappings=
file.save_mappings=다른 이름으로 매핑 내보내기...
#file.save_mappings_as=
#file.close_mappings=다른 이름으로 매핑 내보내기...
file.save_all=모두 저장
#file.save=Save
file.export_gradle=Gradle 프로젝트로 저장
@@ -51,7 +54,7 @@ tree.resources_title=리소스
tree.loading=로딩중...
progress.load=로딩중
progress.export_mappings=매핑 내보내는 중
progress.save_mappings=매핑 내보내는 중
progress.decompile=디컴파일 중
progress.canceling=취소 중
@@ -188,7 +191,7 @@ preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경
preferences.select_smali_font=변경
preferences.deobfuscation_on=난독 해제 활성화
preferences.deobfuscation_map_file_mode=맵 파일 처리 모드
preferences.generated_renames_mapping_file_mode=맵 파일 처리 모드
preferences.deobfuscation_min_len=최소 이름 길이
preferences.deobfuscation_max_len=최대 이름 길이
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
@@ -216,6 +219,8 @@ msg.language_changed_title=언어 변경됨
msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다.
msg.project_error_title=오류
msg.project_error=프로젝트를 로드 할 수 없습니다.
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
msg.cant_add_comment=여기에 주석을 추가할수 없음
@@ -32,7 +32,10 @@ file.save_project_as=Salvar projeto como...
file.reload=Recarregar arquivos
file.live_reload=Recarregar em tempo real
file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados
file.export_mappings_as=Exportar mappings como...
#file.open_mappings=Open mappings...
#file.save_mappings=Save mappings
#file.save_mappings_as=Save mappings as...
#file.close_mappings=Close mappings
file.save_all=Salvar tudo
#file.save=Save
file.export_gradle=Salvar como um projeto gradle
@@ -51,7 +54,7 @@ tree.resources_title=Recursos
tree.loading=Carregando...
progress.load=Carregando
progress.export_mappings=Exportando mappings
#progress.save_mappings=Saving mappings
progress.decompile=Descompilando
progress.canceling=Cancelando
@@ -188,7 +191,7 @@ preferences.start_jobs=Inicializar descompilação automaticamente em segundo-pl
preferences.select_font=Alterar
preferences.select_smali_font=Alterar
preferences.deobfuscation_on=Ativar desofuscação
preferences.deobfuscation_map_file_mode=Modo do arquivo Map
#preferences.generated_renames_mapping_file_mode=Map file handle mode
preferences.deobfuscation_min_len=Tamanho mínimo do nome
preferences.deobfuscation_max_len=Tamanho máximo do nome
preferences.deobfuscation_source_alias=Utilizar nome do arquivo como apelido da classe
@@ -216,6 +219,8 @@ msg.language_changed_title=Idioma alterado
msg.language_changed=Novo idioma será mostrado na próxima inicialização.
msg.project_error_title=Erro
msg.project_error=Projeto não pôde ser carregado
#msg.mapping_namespace_count_error_title=Error
#msg.mapping_namespace_count_error=JADX only supports mappings with just one destination namespace! The provided ones have %s.
msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe.
msg.cant_add_comment=Não é possível adicionar comentários aqui
@@ -32,7 +32,10 @@ file.save_project_as=另存项目为…
file.reload=重新加载文件
file.live_reload=实时重加载
file.live_reload_desc=文件变动时自动重载
file.export_mappings_as=导出映射为…
file.open_mappings=
#file.save_mappings=
#file.save_mappings_as=
#file.close_mappings=导出映射为…
file.save_all=全部保存
#file.save=Save
file.export_gradle=另存为 Gradle 项目
@@ -51,7 +54,7 @@ tree.resources_title=资源文件
tree.loading=加载中…
progress.load=正在加载
progress.export_mappings=导出映射
progress.save_mappings=导出映射
progress.decompile=反编译中
progress.canceling=正在取消
@@ -188,7 +191,7 @@ preferences.start_jobs=自动进行后台反编译
preferences.select_font=修改
preferences.select_smali_font=修改
preferences.deobfuscation_on=启用反混淆
preferences.deobfuscation_map_file_mode=映射文件句柄模式
preferences.generated_renames_mapping_file_mode=映射文件句柄模式
preferences.deobfuscation_min_len=最小命名长度
preferences.deobfuscation_max_len=最大命名长度
preferences.deobfuscation_source_alias=使用资源名作为类的别名
@@ -216,6 +219,8 @@ msg.language_changed_title=语言已更改
msg.language_changed=新的语言将在下次应用程序启动时显示。
msg.project_error_title=错误
msg.project_error=项目无法加载
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
msg.cant_add_comment=无法在此添加注释
@@ -32,7 +32,10 @@ file.save_project_as=另存專案...
file.reload=重新載入檔案
file.live_reload=實時重新載入
file.live_reload_desc=更動後自動重新載入檔案
file.export_mappings_as=匯出對應為...
file.open_mappings=
#file.save_mappings=
#file.save_mappings_as=
#file.close_mappings=匯出對應為...
file.save_all=全部儲存
#file.save=Save
file.export_gradle=另存為 gradle 專案
@@ -51,7 +54,7 @@ tree.resources_title=資源
tree.loading=載入中...
progress.load=載入中
progress.export_mappings=正在匯出對應
progress.save_mappings=正在匯出對應
progress.decompile=正在反編譯
progress.canceling=正在取消
@@ -188,7 +191,7 @@ preferences.start_jobs=自動開始背景反編譯
preferences.select_font=變更
preferences.select_smali_font=變更
preferences.deobfuscation_on=啟用去模糊化
preferences.deobfuscation_map_file_mode=Map 檔案處理模式
preferences.generated_renames_mapping_file_mode=Map 檔案處理模式
preferences.deobfuscation_min_len=最小名稱長度
preferences.deobfuscation_max_len=最大名稱長度
preferences.deobfuscation_source_alias=將原始檔案名稱作為類別別名
@@ -216,6 +219,8 @@ msg.language_changed_title=已更改語言
msg.language_changed=新語言將於下次應用程式啟動時套用。
msg.project_error_title=錯誤
msg.project_error=無法載入專案
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
msg.cant_add_comment=無法在此新增註解
@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.impl.NoOpCodeCache;
import jadx.core.dex.nodes.ClassNode;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.codecache.disk.DiskCodeCache;
import jadx.tests.api.IntegrationTest;
@@ -29,7 +32,10 @@ class DiskCodeCacheTest extends IntegrationTest {
ClassNode clsNode = getClassNode(DiskCodeCacheTest.class);
ICodeInfo codeInfo = clsNode.getCode();
DiskCodeCache cache = new DiskCodeCache(clsNode.root(), tempDir);
JadxSettings settings = new JadxSettings();
JadxProject project = new JadxProject(new MainWindow(settings));
project.setCacheDir(tempDir);
DiskCodeCache cache = new DiskCodeCache(clsNode.root(), project, settings);
String clsKey = clsNode.getFullName();
cache.add(clsKey, codeInfo);