From cb91c8c41c53aebf094db7fb93ebccccb4083132 Mon Sep 17 00:00:00 2001 From: Julian Burner <48808497+NebelNidas@users.noreply.github.com> Date: Wed, 10 Aug 2022 16:37:55 +0200 Subject: [PATCH] feat: mapping-io import support (#1531)(PR #1532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .../src/main/java/jadx/cli/JadxCLIArgs.java | 60 +++- jadx-core/build.gradle | 7 + .../libs/mapping-io-0.4.0-SNAPSHOT.jar | Bin 128110 -> 128124 bytes .../src/main/java/jadx/api/JadxArgs.java | 57 +++- .../main/java/jadx/api/JadxDecompiler.java | 4 + ...a => GeneratedRenamesMappingFileMode.java} | 7 +- .../api/args/UserRenamesMappingsMode.java | 36 ++ jadx-core/src/main/java/jadx/core/Jadx.java | 5 + .../java/jadx/core/deobf/DeobfPresets.java | 6 +- .../jadx/core/deobf/DeobfuscatorVisitor.java | 2 +- .../jadx/core/deobf/SaveDeobfMapping.java | 6 +- .../dex/nodes/IMappingsUpdateListener.java | 8 + .../java/jadx/core/dex/nodes/MethodNode.java | 35 ++ .../java/jadx/core/dex/nodes/RootNode.java | 44 +++ .../visitors/rename/CodeMappingsVisitor.java | 107 ++++++ .../dex/visitors/rename/MappingsVisitor.java | 109 ++++++ .../mappings/DalvikToJavaBytecodeUtils.java | 151 +++++++++ .../java/jadx/tests/api/IntegrationTest.java | 6 +- jadx-gui/build.gradle | 5 - .../src/main/java/jadx/gui/JadxWrapper.java | 18 +- .../gui/plugins/mappings/MappingExporter.java | 56 ++-- .../java/jadx/gui/settings/JadxProject.java | 15 + .../java/jadx/gui/settings/JadxSettings.java | 19 +- .../jadx/gui/settings/JadxSettingsWindow.java | 18 +- .../jadx/gui/settings/data/ProjectData.java | 10 + .../src/main/java/jadx/gui/ui/MainWindow.java | 313 +++++++++++++++--- .../jadx/gui/ui/codearea/FridaAction.java | 38 +-- .../java/jadx/gui/ui/dialog/RenameDialog.java | 105 ++++++ .../utils/codecache/disk/DiskCodeCache.java | 22 +- .../resources/i18n/Messages_de_DE.properties | 11 +- .../resources/i18n/Messages_en_US.properties | 11 +- .../resources/i18n/Messages_es_ES.properties | 11 +- .../resources/i18n/Messages_ko_KR.properties | 11 +- .../resources/i18n/Messages_pt_BR.properties | 11 +- .../resources/i18n/Messages_zh_CN.properties | 11 +- .../resources/i18n/Messages_zh_TW.properties | 11 +- .../utils/codecache/DiskCodeCacheTest.java | 8 +- 37 files changed, 1156 insertions(+), 198 deletions(-) rename {jadx-gui => jadx-core}/libs/mapping-io-0.4.0-SNAPSHOT.jar (90%) rename jadx-core/src/main/java/jadx/api/args/{DeobfuscationMapFileMode.java => GeneratedRenamesMappingFileMode.java} (75%) create mode 100644 jadx-core/src/main/java/jadx/api/args/UserRenamesMappingsMode.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/IMappingsUpdateListener.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/rename/CodeMappingsVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/rename/MappingsVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/utils/mappings/DalvikToJavaBytecodeUtils.java diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 686482e02..1ff3b56aa 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -1,5 +1,6 @@ package jadx.cli; +import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; @@ -22,8 +23,9 @@ import jadx.api.JadxArgs; import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxDecompiler; -import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.args.ResourceNameSource; +import jadx.api.args.UserRenamesMappingsMode; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.files.FileUtils; @@ -106,6 +108,22 @@ public class JadxCLIArgs { @Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers") protected boolean respectBytecodeAccessModifiers = false; + @Parameter( + names = { "--mappings-path" }, + description = "deobfuscation mappings file or directory. Allowed formats: Tiny and Tiny v2 (both '.tiny'), Enigma (.mapping) or Enigma directory" + ) + protected Path userRenamesMappingsPath; + + @Parameter( + names = { "--mappings-mode" }, + description = "set mode for handling the deobfuscation mapping file:" + + "\n 'read' - just read, user can always save manually (default)" + + "\n 'read-and-autosave-every-change' - read and autosave after every change" + + "\n 'read-and-autosave-before-closing' - read and autosave before exiting the app or closing the project" + + "\n 'ignore' - don't read or save (can be used to skip loading mapping files referenced in the project file)" + ) + protected UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault(); + @Parameter(names = { "--deobf" }, description = "activate deobfuscation") protected boolean deobfuscationOn = false; @@ -115,22 +133,24 @@ public class JadxCLIArgs { @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer") protected int deobfuscationMaxLength = 64; + @Deprecated @Parameter( names = { "--deobf-cfg-file" }, - description = "deobfuscation map file, default: same dir and name as input file with '.jobf' extension" + description = "deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension (deprecated)" ) - protected String deobfuscationMapFile; + protected String generatedRenamesMappingFile; + @Deprecated @Parameter( names = { "--deobf-cfg-file-mode" }, - description = "set mode for handle deobfuscation map file:" + description = "set mode for handling the JADX auto-generated names' deobfuscation map file (deprecated):" + "\n 'read' - read if found, don't save (default)" + "\n 'read-or-save' - read if found, save otherwise (don't overwrite)" + "\n 'overwrite' - don't read, always save" + "\n 'ignore' - don't read and don't save", converter = DeobfuscationMapFileModeConverter.class ) - protected DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ; + protected GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault(); @Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias") protected boolean deobfuscationUseSourceNameAsAlias = false; @@ -273,9 +293,13 @@ public class JadxCLIArgs { args.setCfgOutput(cfgOutput); args.setRawCFGOutput(rawCfgOutput); args.setReplaceConsts(replaceConsts); + if (userRenamesMappingsPath != null) { + args.setUserRenamesMappingsPath(userRenamesMappingsPath); + } + args.setUserRenamesMappingsMode(userRenamesMappingsMode); args.setDeobfuscationOn(deobfuscationOn); - args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile)); - args.setDeobfuscationMapFileMode(deobfuscationMapFileMode); + args.setGeneratedRenamesMappingFile(FileUtils.toFile(generatedRenamesMappingFile)); + args.setGeneratedRenamesMappingFileMode(generatedRenamesMappingFileMode); args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); @@ -380,6 +404,14 @@ public class JadxCLIArgs { return extractFinally; } + public Path getUserRenamesMappingsPath() { + return userRenamesMappingsPath; + } + + public UserRenamesMappingsMode getUserRenamesMappingsMode() { + return userRenamesMappingsMode; + } + public boolean isDeobfuscationOn() { return deobfuscationOn; } @@ -392,12 +424,14 @@ public class JadxCLIArgs { return deobfuscationMaxLength; } - public String getDeobfuscationMapFile() { - return deobfuscationMapFile; + @Deprecated + public String getGeneratedRenamesMappingFile() { + return generatedRenamesMappingFile; } - public DeobfuscationMapFileMode getDeobfuscationMapFileMode() { - return deobfuscationMapFileMode; + @Deprecated + public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() { + return generatedRenamesMappingFileMode; } public boolean isDeobfuscationUseSourceNameAsAlias() { @@ -509,9 +543,9 @@ public class JadxCLIArgs { } } - public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter { + public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter { public DeobfuscationMapFileModeConverter() { - super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values); + super(GeneratedRenamesMappingFileMode::valueOf, GeneratedRenamesMappingFileMode::values); } } diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index 0e0993bd1..cfd3e978d 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -5,6 +5,13 @@ plugins { dependencies { api(project(':jadx-plugins:jadx-plugins-api')) + // TODO: Switch back to upstream once this PR gets merged: + // https://github.com/FabricMC/mapping-io/pull/19 + // api 'net.fabricmc:mapping-io:0.3.0' + api files('libs/mapping-io-0.4.0-SNAPSHOT.jar') + // mapping-io's dependencies + runtimeOnly 'org.ow2.asm:asm:9.3' + implementation 'com.google.code.gson:gson:2.10.1' // TODO: move resources decoding to separate plugin module diff --git a/jadx-gui/libs/mapping-io-0.4.0-SNAPSHOT.jar b/jadx-core/libs/mapping-io-0.4.0-SNAPSHOT.jar similarity index 90% rename from jadx-gui/libs/mapping-io-0.4.0-SNAPSHOT.jar rename to jadx-core/libs/mapping-io-0.4.0-SNAPSHOT.jar index d06bcd83664e83c3525ca5ba3ff71ee3121be713..68be1ce1e070721dfc6ab5e7beb7a56930c13605 100644 GIT binary patch delta 5688 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

UKTJpQop9cV6q?%ERe$c8}~5&zUnM+ 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 diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index c5fe652c0..2e517cfad 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -2,6 +2,7 @@ package jadx.api; import java.io.File; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; @@ -15,8 +16,9 @@ import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.args.ResourceNameSource; +import jadx.api.args.UserRenamesMappingsMode; import jadx.api.data.ICodeData; import jadx.api.deobf.IAliasProvider; import jadx.api.deobf.IRenameCondition; @@ -72,12 +74,15 @@ public class JadxArgs { */ private boolean includeDependencies = false; + private Path userRenamesMappingsPath = null; + private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault(); + private boolean deobfuscationOn = false; private boolean useSourceNameAsClassAlias = false; private boolean parseKotlinMetadata = false; - private File deobfuscationMapFile = null; - private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ; + private File generatedRenamesMappingFile = null; + private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault(); private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO; private int deobfuscationMinLength = 0; @@ -326,6 +331,22 @@ public class JadxArgs { this.classFilter = classFilter; } + public Path getUserRenamesMappingsPath() { + return userRenamesMappingsPath; + } + + public void setUserRenamesMappingsPath(Path path) { + this.userRenamesMappingsPath = path; + } + + public UserRenamesMappingsMode getUserRenamesMappingsMode() { + return userRenamesMappingsMode; + } + + public void setUserRenamesMappingsMode(UserRenamesMappingsMode mode) { + this.userRenamesMappingsMode = mode; + } + public boolean isDeobfuscationOn() { return deobfuscationOn; } @@ -336,22 +357,24 @@ public class JadxArgs { @Deprecated public boolean isDeobfuscationForceSave() { - return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE; + return generatedRenamesMappingFileMode == GeneratedRenamesMappingFileMode.OVERWRITE; } @Deprecated public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { if (deobfuscationForceSave) { - this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE; + this.generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.OVERWRITE; } } - public DeobfuscationMapFileMode getDeobfuscationMapFileMode() { - return deobfuscationMapFileMode; + @Deprecated + public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() { + return generatedRenamesMappingFileMode; } - public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) { - this.deobfuscationMapFileMode = deobfuscationMapFileMode; + @Deprecated + public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) { + this.generatedRenamesMappingFileMode = mode; } public boolean isUseSourceNameAsClassAlias() { @@ -386,12 +409,14 @@ public class JadxArgs { this.deobfuscationMaxLength = deobfuscationMaxLength; } - public File getDeobfuscationMapFile() { - return deobfuscationMapFile; + @Deprecated + public File getGeneratedRenamesMappingFile() { + return generatedRenamesMappingFile; } - public void setDeobfuscationMapFile(File deobfuscationMapFile) { - this.deobfuscationMapFile = deobfuscationMapFile; + @Deprecated + public void setGeneratedRenamesMappingFile(File file) { + this.generatedRenamesMappingFile = file; } public ResourceNameSource getResourceNameSource() { @@ -611,9 +636,11 @@ public class JadxArgs { + ", skipResources=" + skipResources + ", skipSources=" + skipSources + ", includeDependencies=" + includeDependencies + + ", userRenamesMappingsPath=" + userRenamesMappingsPath + + ", userRenamesMappingsMode=" + userRenamesMappingsMode + ", deobfuscationOn=" + deobfuscationOn - + ", deobfuscationMapFile=" + deobfuscationMapFile - + ", deobfuscationMapFileMode=" + deobfuscationMapFileMode + + ", generatedRenamesMappingFile=" + generatedRenamesMappingFile + + ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode + ", resourceNameSource=" + resourceNameSource + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", parseKotlinMetadata=" + parseKotlinMetadata diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index efd44b964..1f3c93587 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -673,6 +673,10 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable { root.notifyCodeDataListeners(); } + public void reloadMappings() { + root.notifyMappingsListeners(); + } + public JadxArgs getArgs() { return args; } diff --git a/jadx-core/src/main/java/jadx/api/args/DeobfuscationMapFileMode.java b/jadx-core/src/main/java/jadx/api/args/GeneratedRenamesMappingFileMode.java similarity index 75% rename from jadx-core/src/main/java/jadx/api/args/DeobfuscationMapFileMode.java rename to jadx-core/src/main/java/jadx/api/args/GeneratedRenamesMappingFileMode.java index 367491d52..d028d9ffe 100644 --- a/jadx-core/src/main/java/jadx/api/args/DeobfuscationMapFileMode.java +++ b/jadx-core/src/main/java/jadx/api/args/GeneratedRenamesMappingFileMode.java @@ -1,6 +1,7 @@ package jadx.api.args; -public enum DeobfuscationMapFileMode { +@Deprecated +public enum GeneratedRenamesMappingFileMode { /** * Load if found, don't save (default) @@ -22,6 +23,10 @@ public enum DeobfuscationMapFileMode { */ IGNORE; + public static GeneratedRenamesMappingFileMode getDefault() { + return READ; + } + public boolean shouldRead() { return this == READ || this == READ_OR_SAVE; } diff --git a/jadx-core/src/main/java/jadx/api/args/UserRenamesMappingsMode.java b/jadx-core/src/main/java/jadx/api/args/UserRenamesMappingsMode.java new file mode 100644 index 000000000..1dcc83a15 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/args/UserRenamesMappingsMode.java @@ -0,0 +1,36 @@ +package jadx.api.args; + +public enum UserRenamesMappingsMode { + + /** + * Just read, user can save manually (default) + */ + READ, + + /** + * Read and autosave after every change + */ + READ_AND_AUTOSAVE_EVERY_CHANGE, + + /** + * Read and autosave before exiting the app or closing the project + */ + READ_AND_AUTOSAVE_BEFORE_CLOSING, + + /** + * Don't load and don't save + */ + IGNORE; + + public static UserRenamesMappingsMode getDefault() { + return READ; + } + + public boolean shouldRead() { + return this != IGNORE; + } + + public boolean shouldWrite() { + return this == READ_AND_AUTOSAVE_EVERY_CHANGE || this == READ_AND_AUTOSAVE_BEFORE_CLOSING; + } +} diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index a1097ddb7..7634f161c 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -60,7 +60,9 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.variables.ProcessVariables; +import jadx.core.dex.visitors.rename.CodeMappingsVisitor; import jadx.core.dex.visitors.rename.CodeRenameVisitor; +import jadx.core.dex.visitors.rename.MappingsVisitor; import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.ssa.SSATransform; @@ -97,6 +99,7 @@ public class Jadx { // rename and deobfuscation passes.add(new DeobfuscatorVisitor()); passes.add(new RenameVisitor()); + passes.add(new MappingsVisitor()); passes.add(new SaveDeobfMapping()); passes.add(new UsageInfoVisitor()); @@ -143,6 +146,7 @@ public class Jadx { passes.add(new ProcessKotlinInternals()); } passes.add(new CodeRenameVisitor()); + passes.add(new CodeMappingsVisitor()); if (args.isInlineMethods()) { passes.add(new InlineMethods()); } @@ -214,6 +218,7 @@ public class Jadx { } passes.add(new FinishTypeInference()); passes.add(new CodeRenameVisitor()); + passes.add(new CodeMappingsVisitor()); passes.add(new DeboxingVisitor()); passes.add(new ModVisitor()); passes.add(new CodeShrinkVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java index 9728d9aed..1a51a8140 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java @@ -16,7 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; -import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.deobf.IAliasProvider; import jadx.api.deobf.impl.AlwaysRename; import jadx.core.dex.info.ClassInfo; @@ -45,7 +45,7 @@ public class DeobfPresets { public static DeobfPresets build(RootNode root) { Path deobfMapPath = getPathDeobfMapPath(root); - if (root.getArgs().getDeobfuscationMapFileMode() != DeobfuscationMapFileMode.IGNORE) { + if (root.getArgs().getGeneratedRenamesMappingFileMode() != GeneratedRenamesMappingFileMode.IGNORE) { LOG.debug("Deobfuscation map file set to: {}", deobfMapPath); } return new DeobfPresets(deobfMapPath); @@ -53,7 +53,7 @@ public class DeobfPresets { private static Path getPathDeobfMapPath(RootNode root) { JadxArgs jadxArgs = root.getArgs(); - File deobfMapFile = jadxArgs.getDeobfuscationMapFile(); + File deobfMapFile = jadxArgs.getGeneratedRenamesMappingFile(); if (deobfMapFile != null) { return deobfMapFile.toPath(); } diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java index a743500bc..fb9e0f313 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfuscatorVisitor.java @@ -20,7 +20,7 @@ public class DeobfuscatorVisitor extends AbstractVisitor { return; } DeobfPresets mapping = DeobfPresets.build(root); - if (args.getDeobfuscationMapFileMode().shouldRead()) { + if (args.getGeneratedRenamesMappingFileMode().shouldRead()) { if (mapping.load()) { mapping.apply(root); } diff --git a/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java index 7e267c5bb..85dcf62c5 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java +++ b/jadx-core/src/main/java/jadx/core/deobf/SaveDeobfMapping.java @@ -7,7 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; -import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.core.codegen.json.JsonMappingGen; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.AbstractVisitor; @@ -28,13 +28,13 @@ public class SaveDeobfMapping extends AbstractVisitor { } private void saveMappings(RootNode root) { - DeobfuscationMapFileMode mode = root.getArgs().getDeobfuscationMapFileMode(); + GeneratedRenamesMappingFileMode mode = root.getArgs().getGeneratedRenamesMappingFileMode(); if (!mode.shouldWrite()) { return; } DeobfPresets mapping = DeobfPresets.build(root); Path deobfMapFile = mapping.getDeobfMapFile(); - if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) { + if (mode == GeneratedRenamesMappingFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) { return; } try { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IMappingsUpdateListener.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IMappingsUpdateListener.java new file mode 100644 index 000000000..423c7f659 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IMappingsUpdateListener.java @@ -0,0 +1,8 @@ +package jadx.core.dex.nodes; + +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public interface IMappingsUpdateListener { + + void updated(MemoryMappingTree mappingTree); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 3eebbd796..f87195036 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -10,13 +10,18 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.ICodeInfo; import jadx.api.JavaMethod; import jadx.api.core.nodes.IMethodNode; +import jadx.api.metadata.ICodeNodeRef; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.api.metadata.annotations.VarNode; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.IDebugInfo; import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr; +import jadx.api.utils.CodeUtils; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.LoopInfo; @@ -249,6 +254,36 @@ public class MethodNode extends NotificationAttrNode implements IMethodNode, return mthInfo.getReturnType().equals(ArgType.VOID); } + public List collectArgsWithoutLoading() { + ICodeInfo codeInfo = getTopParentClass().getCode(); + int mthDefPos = getDefPosition(); + int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); + int argsCount = mthInfo.getArgsCount(); + List args = new ArrayList<>(argsCount); + 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(this)) { + // Stop if we've gone too far and have entered a different method + return Boolean.TRUE; + } + args.add(varNode); + } + } + return null; + }); + if (args.size() != argsCount) { + LOG.warn("Incorrect args count, expected: {}, got: {}", argsCount, args.size()); + } + return args; + } + public List getArgRegs() { if (argsList == null) { throw new JadxRuntimeException("Method arg registers not loaded: " + this diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 48e8b0774..0f9e1a209 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -1,6 +1,7 @@ package jadx.core.dex.nodes; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -13,6 +14,10 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.MappingUtil; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + import jadx.api.ICodeCache; import jadx.api.ICodeWriter; import jadx.api.JadxArgs; @@ -20,6 +25,7 @@ import jadx.api.JadxDecompiler; import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.api.ResourcesLoader; +import jadx.api.args.UserRenamesMappingsMode; import jadx.api.core.nodes.IRootNode; import jadx.api.data.ICodeData; import jadx.api.impl.passes.DecompilePassWrapper; @@ -66,11 +72,13 @@ public class RootNode implements IRootNode { private final JadxArgs args; private final List preDecompilePasses; private final List codeDataUpdateListeners = new ArrayList<>(); + private final List mappingsUpdateListeners = new ArrayList<>(); private final ProcessClass processClasses; private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final StringUtils stringUtils; private final ConstStorage constValues; + private MemoryMappingTree mappingTree; private final InfoStorage infoStorage = new InfoStorage(); private final CacheStorage cacheStorage = new CacheStorage(); private final TypeUpdate typeUpdate; @@ -211,6 +219,26 @@ public class RootNode implements IRootNode { } catch (Exception e) { LOG.error("Failed to parse '.arsc' file", e); } + if (args.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE + && args.getUserRenamesMappingsPath() != null) { + try { + mappingTree = new MemoryMappingTree(); + MappingReader.read(args.getUserRenamesMappingsPath(), mappingTree); + 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) { + throw new JadxRuntimeException( + String.format("JADX only supports mappings with just one destination namespace! The provided ones have %s.", + mappingTree.getDstNamespaces().size())); + } + } catch (Exception e) { + mappingTree = null; + throw new JadxRuntimeException("Failed to load mappings", e); + } + } } private void updateManifestAttribMap(IResParser parser) { @@ -565,11 +593,19 @@ public class RootNode implements IRootNode { this.codeDataUpdateListeners.add(listener); } + public void registerMappingsUpdateListener(IMappingsUpdateListener listener) { + this.mappingsUpdateListeners.add(listener); + } + public void notifyCodeDataListeners() { ICodeData codeData = args.getCodeData(); codeDataUpdateListeners.forEach(l -> l.updated(codeData)); } + public void notifyMappingsListeners() { + mappingsUpdateListeners.forEach(l -> l.updated(mappingTree)); + } + public ClspGraph getClsp() { return clsp; } @@ -596,6 +632,14 @@ public class RootNode implements IRootNode { return constValues; } + public MemoryMappingTree getMappingTree() { + return mappingTree; + } + + public void setMappingTree(MemoryMappingTree mappingTree) { + this.mappingTree = mappingTree; + } + public InfoStorage getInfoStorage() { return infoStorage; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/CodeMappingsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/CodeMappingsVisitor.java new file mode 100644 index 000000000..b661a1dbb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/CodeMappingsVisitor.java @@ -0,0 +1,107 @@ +package jadx.core.dex.visitors.rename; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; +import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; +import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.InitCodeVariables; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; +import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.mappings.DalvikToJavaBytecodeUtils; + +@JadxVisitor( + name = "ApplyCodeMappings", + desc = "Apply mappings to method args and vars", + runAfter = { + InitCodeVariables.class, + DebugInfoApplyVisitor.class + } +) +public class CodeMappingsVisitor extends AbstractVisitor { + + private static final Logger LOG = LoggerFactory.getLogger(CodeMappingsVisitor.class); + + private Map clsRenamesMap; + + @Override + public void init(RootNode root) throws JadxException { + updateMappingsMap(root.getMappingTree()); + root.registerMappingsUpdateListener(this::updateMappingsMap); + } + + @Override + public boolean visit(ClassNode cls) { + ClassMapping classMapping = getMapping(cls); + if (classMapping != null) { + applyRenames(cls, classMapping); + } + cls.getInnerClasses().forEach(this::visit); + return false; + } + + private static void applyRenames(ClassNode cls, ClassMapping classMapping) { + for (MethodNode mth : cls.getMethods()) { + String methodName = mth.getMethodInfo().getName(); + String methodDesc = mth.getMethodInfo().getShortId().substring(methodName.length()); + List ssaVars = mth.getSVars(); + if (ssaVars.isEmpty()) { + continue; + } + MethodMapping methodMapping = classMapping.getMethod(methodName, methodDesc); + if (methodMapping == null) { + continue; + } + // Method args + for (MethodArgMapping argMapping : methodMapping.getArgs()) { + Integer mappingLvIndex = argMapping.getLvIndex(); + for (SSAVar ssaVar : ssaVars) { + Integer actualLvIndex = DalvikToJavaBytecodeUtils.getMethodArgLvIndex(ssaVar, mth); + if (actualLvIndex.equals(mappingLvIndex)) { + ssaVar.getCodeVar().setName(argMapping.getDstName(0)); + break; + } + } + } + // TODO: Method vars (if ever feasible) + } + } + + private ClassMapping getMapping(ClassNode cls) { + if (clsRenamesMap == null || clsRenamesMap.isEmpty()) { + return null; + } + String classPath = cls.getClassInfo().makeRawFullName().replace('.', '/'); + ClassMapping clsMapping = clsRenamesMap.get(classPath); + return clsMapping; + } + + private void updateMappingsMap(@Nullable MemoryMappingTree mappingTree) { + clsRenamesMap = new HashMap<>(); + if (mappingTree == null) { + return; + } + for (ClassMapping cls : mappingTree.getClasses()) { + for (MethodMapping mth : cls.getMethods()) { + if (!mth.getArgs().isEmpty() || !mth.getVars().isEmpty()) { + clsRenamesMap.put(cls.getSrcName(), cls); + break; + } + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/MappingsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/MappingsVisitor.java new file mode 100644 index 000000000..c5be4c420 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/MappingsVisitor.java @@ -0,0 +1,109 @@ +package jadx.core.dex.visitors.rename; + +import java.io.File; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; +import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; +import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; + +import jadx.core.codegen.TypeGen; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.MethodOverrideAttr; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.JadxVisitor; + +@JadxVisitor( + name = "MappingsVisitor", + desc = "Apply mappings to classes, fields and methods", + runAfter = { + RenameVisitor.class + } +) +public class MappingsVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(MappingsVisitor.class); + + @Override + public void init(RootNode root) { + List inputFiles = root.getArgs().getInputFiles(); + if (inputFiles.isEmpty()) { + return; + } + + MappingTree tree = root.getMappingTree(); + if (tree == null) { + return; + } + + for (ClassNode cls : root.getClasses(true)) { + ClassMapping mapping = tree.getClass(cls.getClassInfo().makeRawFullName().replace('.', '/')); + if (mapping == null) { + continue; + } + processClass(cls, mapping); + } + } + + private static void processClass(ClassNode cls, ClassMapping classMapping) { + if (classMapping.getDstName(0) != null) { + cls.getClassInfo().changeShortName(classMapping.getDstName(0)); + } + if (classMapping.getComment() != null) { + cls.addInfoComment(classMapping.getComment()); + } + + // Fields + for (FieldNode field : cls.getFields()) { + FieldMapping fieldMapping = + classMapping.getField(field.getFieldInfo().getName(), TypeGen.signature(field.getFieldInfo().getType())); + + if (fieldMapping == null) { + continue; + } + if (fieldMapping.getDstName(0) != null) { + field.getFieldInfo().setAlias(fieldMapping.getDstName(0)); + } + if (fieldMapping.getComment() != null) { + field.addInfoComment(fieldMapping.getComment()); + } + } + // Methods + String methodName; + String methodDesc; + for (MethodNode method : cls.getMethods()) { + methodName = method.getMethodInfo().getName(); + methodDesc = method.getMethodInfo().getShortId().substring(methodName.length()); + MethodMapping methodMapping = classMapping.getMethod(methodName, methodDesc); + + if (methodMapping == null) { + continue; + } + processMethod(method, methodMapping); + } + } + + private static void processMethod(MethodNode method, MethodMapping methodMapping) { + MethodOverrideAttr overrideAttr = method.get(AType.METHOD_OVERRIDE); + if (methodMapping.getDstName(0) != null) { + if (overrideAttr == null) { + method.getMethodInfo().setAlias(methodMapping.getDstName(0)); + } else { + for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) { + method.getMethodInfo().setAlias(methodMapping.getDstName(0)); + } + } + } + if (methodMapping.getComment() != null) { + method.addInfoComment(methodMapping.getComment()); + } + // Method args & vars are handled in CodeMappingsVisitor + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/mappings/DalvikToJavaBytecodeUtils.java b/jadx-core/src/main/java/jadx/core/utils/mappings/DalvikToJavaBytecodeUtils.java new file mode 100644 index 000000000..24734ac85 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/mappings/DalvikToJavaBytecodeUtils.java @@ -0,0 +1,151 @@ +package jadx.core.utils.mappings; + +import java.util.ArrayList; +import java.util.List; + +import jadx.api.metadata.annotations.VarNode; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.MethodNode; + +public class DalvikToJavaBytecodeUtils { + + // **************************** + // Local variable index + // **************************** + + // Method args + + public static Integer getMethodArgLvIndex(VarNode methodArg) { + MethodNode mth = methodArg.getMth(); + Integer lvIndex = getMethodArgLvIndexViaSsaVars(methodArg.getReg(), mth); + if (lvIndex != null) { + return lvIndex; + } + List args = mth.collectArgsWithoutLoading(); + for (VarNode arg : args) { + lvIndex = arg.getReg() - args.get(0).getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1); + if (arg.equals(methodArg)) { + break; + } + } + return lvIndex; + } + + public static Integer getMethodArgLvIndex(SSAVar methodArgSsaVar, MethodNode mth) { + return getMethodArgLvIndexViaSsaVars(methodArgSsaVar.getRegNum(), mth); + } + + private static Integer getMethodArgLvIndexViaSsaVars(int regNum, MethodNode mth) { + List ssaVars = mth.getSVars(); + if (!ssaVars.isEmpty()) { + return regNum - ssaVars.get(0).getRegNum(); + } + return null; + } + + // Method vars + + public static Integer getMethodVarLvIndex(VarNode methodVar) { + MethodNode mth = methodVar.getMth(); + Integer lvIndex = getMethodVarLvIndexViaSsaVars(methodVar.getReg(), mth); + if (lvIndex != null) { + return lvIndex; + } + Integer lastArgLvIndex = mth.getAccessFlags().isStatic() ? -1 : 0; + List args = mth.collectArgsWithoutLoading(); + if (!args.isEmpty()) { + lastArgLvIndex = getMethodArgLvIndex(args.get(args.size() - 1)); + } + return lastArgLvIndex + methodVar.getReg() + (mth.getAccessFlags().isStatic() ? 0 : 1); + } + + public static Integer getMethodVarLvIndex(SSAVar methodVarSsaVar, MethodNode mth) { + return getMethodVarLvIndexViaSsaVars(methodVarSsaVar.getRegNum(), mth); + } + + private static Integer getMethodVarLvIndexViaSsaVars(int regNum, MethodNode mth) { + List ssaVars = mth.getSVars(); + if (ssaVars.isEmpty()) { + return null; + } + Integer lastArgLvIndex = mth.getAccessFlags().isStatic() ? -1 : 0; + List args = mth.getArgRegs(); + if (!args.isEmpty()) { + lastArgLvIndex = getMethodArgLvIndexViaSsaVars(args.get(args.size() - 1).getSVar().getRegNum(), mth); + } + return lastArgLvIndex + regNum + (mth.getAccessFlags().isStatic() ? 0 : 1); + } + + // **************************** + // Local variable table index + // **************************** + + // Method args + + public static Integer getMethodArgLvtIndex(VarNode methodArg) { + MethodNode mth = methodArg.getMth(); + int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1; + List args = mth.collectArgsWithoutLoading(); + for (VarNode arg : args) { + if (arg.equals(methodArg)) { + return lvtIndex; + } + lvtIndex++; + } + return null; + } + + public static Integer getMethodArgLvtIndex(SSAVar methodArgSsaVar, MethodNode mth) { + List ssaVars = mth.getSVars(); + if (ssaVars.isEmpty()) { + return null; + } + List args = mth.getArgRegs(); + int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1; + for (RegisterArg arg : args) { + if (arg.getSVar().equals(methodArgSsaVar)) { + return lvtIndex; + } + lvtIndex++; + } + return null; + } + + // Method vars + + // TODO: public static Integer getMethodVarLvtIndex(VarNode methodVar) {} + + public static Integer getMethodVarLvtIndex(SSAVar methodVarSsaVar, MethodNode mth) { + List ssaVars = new ArrayList<>(mth.getSVars()); + if (ssaVars.isEmpty()) { + return null; + } + Integer lvtIndex = getMethodArgLvtIndex(methodVarSsaVar, mth); + if (lvtIndex != null) { + return lvtIndex; + } + + lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1; + lvtIndex += mth.getArgTypes().size(); + + lvtIndex = getMethodArgLvtIndex(methodVarSsaVar, mth) + 1; + ssaVars.subList(0, ssaVars.indexOf(methodVarSsaVar) + 1).clear(); + + int lastRegNum = -1; + for (SSAVar ssaVar : ssaVars) { + if (ssaVar.getRegNum() == lastRegNum) { + // Not present in bytecode + // System.out.println("Duplicate RegNum: " + ssaVar.getRegNum()); + continue; + } + lvtIndex++; + if (ssaVar.equals(methodVarSsaVar)) { + return lvtIndex; + } + lastRegNum = ssaVar.getRegNum(); + } + return null; + } + +} diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 2f3e87600..089cc6024 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -40,7 +40,7 @@ import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JadxInternalAccess; import jadx.api.JavaClass; -import jadx.api.args.DeobfuscationMapFileMode; +import jadx.api.args.GeneratedRenamesMappingFileMode; import jadx.api.metadata.ICodeMetadata; import jadx.api.metadata.annotations.InsnCodeOffset; import jadx.core.dex.attributes.AFlag; @@ -139,7 +139,7 @@ public abstract class IntegrationTest extends TestUtils { args.setFsCaseSensitive(false); // use same value on all systems args.setCommentsLevel(CommentsLevel.DEBUG); args.setDeobfuscationOn(false); - args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE); + args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE); } @AfterEach @@ -570,7 +570,7 @@ public abstract class IntegrationTest extends TestUtils { protected void enableDeobfuscation() { args.setDeobfuscationOn(true); - args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE); + args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE); args.setDeobfuscationMinLength(2); args.setDeobfuscationMaxLength(64); } diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 4c1b0fef9..680816b61 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -35,11 +35,6 @@ dependencies { implementation 'com.android.tools.build:apksig:7.4.1' implementation 'io.github.skylot:jdwp:2.0.0' - // TODO: Switch back to upstream once this PR gets merged: - // https://github.com/FabricMC/mapping-io/pull/19 - // implementation 'net.fabricmc:mapping-io:0.3.0' - implementation files('libs/mapping-io-0.4.0-SNAPSHOT.jar') - testImplementation project(":jadx-core").sourceSets.test.output } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 033759f87..95123a3f8 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java index f76b3ddc1..348f01778 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/MappingExporter.java @@ -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 collectMethodArgs(MethodNode methodNode) { - ICodeInfo codeInfo = methodNode.getTopParentClass().getCode(); - int mthDefPos = methodNode.getDefPosition(); - int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); - List 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> 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 args = collectMethodArgs(mth); + List 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 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(); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java index 69d75bbb2..e3d1af6b8 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 6a8359a5d..762433756 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 09215b751..32bcb087b 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -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 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 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 connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java index 7256dca32..c1fb3050b 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/data/ProjectData.java @@ -18,6 +18,7 @@ public class ProjectData { private JadxCodeData codeData = new JadxCodeData(); private List openTabs = Collections.emptyList(); private int activeTab = -1; + private @Nullable Path mappingsPath; private @Nullable Path cacheDir; private boolean enableLiveReload = false; private List 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; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index d71a10fd6..efb6a245b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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 paths = fileDialog.show(); - if (paths.size() != 1) { + List 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 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 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 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 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(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java index 8658687d9..f932c1eb4 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FridaAction.java @@ -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 argNames = collectMethodArgNames(jMth.getJavaMethod()); + List 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 collectMethodArgNames(JavaMethod javaMethod) { - ICodeInfo codeInfo = javaMethod.getTopParentClass().getCodeInfo(); - int mthDefPos = javaMethod.getDefPos(); - int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos); - List 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()); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java index 81e8370bc..ba9cce5b8 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java @@ -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> updater) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java index 4150298d9..f404738b0 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java @@ -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 namesMap = new ConcurrentHashMap<>(); private final Map 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; } /** diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 3df4c65f8..39120cef9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index b679179f9..72696f543 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 45b463f1f..0508d24c7 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 14e9f6e9f..b43f9a6a1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=여기에 주석을 추가할수 없음 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index d04d0dafa..d757930f6 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index ac8a05d7e..75bbe3fb1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=无法在此添加注释 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index ce15f0b2e..39eb93552 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=無法在此新增註解 diff --git a/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java b/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java index a41af1d65..b16b7fa16 100644 --- a/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java +++ b/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java @@ -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);