未验证 提交 31f0162b 编写于 作者: J Julian Burner 提交者: GitHub

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

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

* Add support for importing method arg mappings

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

* Add GUI for importing mappings

* Also show save file dialog when exporting mappings

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

* Include imported renames when exporting mappings

* Add "close mappings" menu entry

* Don't instantiate MappingTree unless actually needed

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

* Save location of open mapping file into project data

* Correctly reset cache when loading new mappings

* Remove unused import

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

* Fix if statement

* Correctly handle absence of mappings path in project data

* Show overwrite warning for folders only if not empty

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

* Handle wrong mappings namespace count error

* Replace unneeded public with private

* Add option for saving open mappings directly to disk

* Correctly propagate and throw exceptions during decompiler init

* Respect opened mappings' existing namespaces; fix related crash

* Deduplicate code, add `DalvikToJavaBytecodeUtils` class

* Small cleanup; move more functionality to utility class

* Support for importing class, field and method mappings

* Handle mappings in RenameDialog

* Fix checkstyle

* Fix wrong naming order

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

That commit got rid of redundant embedded libraries

* Add null checks

* Check if mapping tree is null before running MappingsVisitor

* Use working mapping-io build

* Handle cache invalidation directly in DiskCodeCache class

* Don't reset UserRenamesMappingsMode if project is just reloaded

* Fix checkstyle
Co-authored-by: NSkylot <skylot@gmail.com>
上级 e07bc7f6
package jadx.cli; package jadx.cli;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
...@@ -20,8 +21,9 @@ import jadx.api.JadxArgs; ...@@ -20,8 +21,9 @@ import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource; import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
...@@ -101,6 +103,22 @@ public class JadxCLIArgs { ...@@ -101,6 +103,22 @@ public class JadxCLIArgs {
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers") @Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = false; 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") @Parameter(names = { "--deobf" }, description = "activate deobfuscation")
protected boolean deobfuscationOn = false; protected boolean deobfuscationOn = false;
...@@ -110,22 +128,24 @@ public class JadxCLIArgs { ...@@ -110,22 +128,24 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer") @Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
protected int deobfuscationMaxLength = 64; protected int deobfuscationMaxLength = 64;
@Deprecated
@Parameter( @Parameter(
names = { "--deobf-cfg-file" }, 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( @Parameter(
names = { "--deobf-cfg-file-mode" }, 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' - read if found, don't save (default)"
+ "\n 'read-or-save' - read if found, save otherwise (don't overwrite)" + "\n 'read-or-save' - read if found, save otherwise (don't overwrite)"
+ "\n 'overwrite' - don't read, always save" + "\n 'overwrite' - don't read, always save"
+ "\n 'ignore' - don't read and don't save", + "\n 'ignore' - don't read and don't save",
converter = DeobfuscationMapFileModeConverter.class 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") @Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false; protected boolean deobfuscationUseSourceNameAsAlias = false;
...@@ -268,9 +288,13 @@ public class JadxCLIArgs { ...@@ -268,9 +288,13 @@ public class JadxCLIArgs {
args.setCfgOutput(cfgOutput); args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput); args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts); args.setReplaceConsts(replaceConsts);
if (userRenamesMappingsPath != null) {
args.setUserRenamesMappingsPath(userRenamesMappingsPath);
}
args.setUserRenamesMappingsMode(userRenamesMappingsMode);
args.setDeobfuscationOn(deobfuscationOn); args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationMapFile(FileUtils.toFile(deobfuscationMapFile)); args.setGeneratedRenamesMappingFile(FileUtils.toFile(generatedRenamesMappingFile));
args.setDeobfuscationMapFileMode(deobfuscationMapFileMode); args.setGeneratedRenamesMappingFileMode(generatedRenamesMappingFileMode);
args.setDeobfuscationMinLength(deobfuscationMinLength); args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
...@@ -370,6 +394,14 @@ public class JadxCLIArgs { ...@@ -370,6 +394,14 @@ public class JadxCLIArgs {
return extractFinally; return extractFinally;
} }
public Path getUserRenamesMappingsPath() {
return userRenamesMappingsPath;
}
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
return userRenamesMappingsMode;
}
public boolean isDeobfuscationOn() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
...@@ -382,12 +414,14 @@ public class JadxCLIArgs { ...@@ -382,12 +414,14 @@ public class JadxCLIArgs {
return deobfuscationMaxLength; return deobfuscationMaxLength;
} }
public String getDeobfuscationMapFile() { @Deprecated
return deobfuscationMapFile; public String getGeneratedRenamesMappingFile() {
return generatedRenamesMappingFile;
} }
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() { @Deprecated
return deobfuscationMapFileMode; public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
return generatedRenamesMappingFileMode;
} }
public boolean isDeobfuscationUseSourceNameAsAlias() { public boolean isDeobfuscationUseSourceNameAsAlias() {
...@@ -513,15 +547,15 @@ public class JadxCLIArgs { ...@@ -513,15 +547,15 @@ public class JadxCLIArgs {
} }
} }
public static class DeobfuscationMapFileModeConverter implements IStringConverter<DeobfuscationMapFileMode> { public static class DeobfuscationMapFileModeConverter implements IStringConverter<GeneratedRenamesMappingFileMode> {
@Override @Override
public DeobfuscationMapFileMode convert(String value) { public GeneratedRenamesMappingFileMode convert(String value) {
try { try {
return DeobfuscationMapFileMode.valueOf(value.toUpperCase()); return GeneratedRenamesMappingFileMode.valueOf(value.toUpperCase());
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: " '\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(DeobfuscationMapFileMode.values())); + JadxCLIArgs.enumValuesString(GeneratedRenamesMappingFileMode.values()));
} }
} }
} }
......
...@@ -5,6 +5,13 @@ plugins { ...@@ -5,6 +5,13 @@ plugins {
dependencies { dependencies {
api(project(':jadx-plugins:jadx-plugins-api')) 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.9.0' implementation 'com.google.code.gson:gson:2.9.0'
// TODO: move resources decoding to separate plugin module // TODO: move resources decoding to separate plugin module
......
...@@ -2,6 +2,7 @@ package jadx.api; ...@@ -2,6 +2,7 @@ package jadx.api;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
...@@ -15,8 +16,9 @@ import java.util.function.Predicate; ...@@ -15,8 +16,9 @@ import java.util.function.Predicate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource; import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.data.ICodeData; import jadx.api.data.ICodeData;
import jadx.api.deobf.IAliasProvider; import jadx.api.deobf.IAliasProvider;
import jadx.api.deobf.IRenameCondition; import jadx.api.deobf.IRenameCondition;
...@@ -71,12 +73,15 @@ public class JadxArgs { ...@@ -71,12 +73,15 @@ public class JadxArgs {
*/ */
private boolean includeDependencies = false; private boolean includeDependencies = false;
private Path userRenamesMappingsPath = null;
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
private boolean deobfuscationOn = false; private boolean deobfuscationOn = false;
private boolean useSourceNameAsClassAlias = false; private boolean useSourceNameAsClassAlias = false;
private boolean parseKotlinMetadata = 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 ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
private int deobfuscationMinLength = 0; private int deobfuscationMinLength = 0;
...@@ -317,6 +322,22 @@ public class JadxArgs { ...@@ -317,6 +322,22 @@ public class JadxArgs {
this.classFilter = classFilter; 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() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
...@@ -327,22 +348,24 @@ public class JadxArgs { ...@@ -327,22 +348,24 @@ public class JadxArgs {
@Deprecated @Deprecated
public boolean isDeobfuscationForceSave() { public boolean isDeobfuscationForceSave() {
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE; return generatedRenamesMappingFileMode == GeneratedRenamesMappingFileMode.OVERWRITE;
} }
@Deprecated @Deprecated
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) { public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
if (deobfuscationForceSave) { if (deobfuscationForceSave) {
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE; this.generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.OVERWRITE;
} }
} }
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() { @Deprecated
return deobfuscationMapFileMode; public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
return generatedRenamesMappingFileMode;
} }
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) { @Deprecated
this.deobfuscationMapFileMode = deobfuscationMapFileMode; public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) {
this.generatedRenamesMappingFileMode = mode;
} }
public boolean isUseSourceNameAsClassAlias() { public boolean isUseSourceNameAsClassAlias() {
...@@ -377,12 +400,14 @@ public class JadxArgs { ...@@ -377,12 +400,14 @@ public class JadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public File getDeobfuscationMapFile() { @Deprecated
return deobfuscationMapFile; public File getGeneratedRenamesMappingFile() {
return generatedRenamesMappingFile;
} }
public void setDeobfuscationMapFile(File deobfuscationMapFile) { @Deprecated
this.deobfuscationMapFile = deobfuscationMapFile; public void setGeneratedRenamesMappingFile(File file) {
this.generatedRenamesMappingFile = file;
} }
public ResourceNameSource getResourceNameSource() { public ResourceNameSource getResourceNameSource() {
...@@ -602,9 +627,11 @@ public class JadxArgs { ...@@ -602,9 +627,11 @@ public class JadxArgs {
+ ", skipResources=" + skipResources + ", skipResources=" + skipResources
+ ", skipSources=" + skipSources + ", skipSources=" + skipSources
+ ", includeDependencies=" + includeDependencies + ", includeDependencies=" + includeDependencies
+ ", userRenamesMappingsPath=" + userRenamesMappingsPath
+ ", userRenamesMappingsMode=" + userRenamesMappingsMode
+ ", deobfuscationOn=" + deobfuscationOn + ", deobfuscationOn=" + deobfuscationOn
+ ", deobfuscationMapFile=" + deobfuscationMapFile + ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode + ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
+ ", resourceNameSource=" + resourceNameSource + ", resourceNameSource=" + resourceNameSource
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
......
...@@ -673,6 +673,10 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable { ...@@ -673,6 +673,10 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
root.notifyCodeDataListeners(); root.notifyCodeDataListeners();
} }
public void reloadMappings() {
root.notifyMappingsListeners();
}
public JadxArgs getArgs() { public JadxArgs getArgs() {
return args; return args;
} }
......
package jadx.api.args; package jadx.api.args;
public enum DeobfuscationMapFileMode { @Deprecated
public enum GeneratedRenamesMappingFileMode {
/** /**
* Load if found, don't save (default) * Load if found, don't save (default)
...@@ -22,6 +23,10 @@ public enum DeobfuscationMapFileMode { ...@@ -22,6 +23,10 @@ public enum DeobfuscationMapFileMode {
*/ */
IGNORE; IGNORE;
public static GeneratedRenamesMappingFileMode getDefault() {
return READ;
}
public boolean shouldRead() { public boolean shouldRead() {
return this == READ || this == READ_OR_SAVE; return this == READ || this == READ_OR_SAVE;
} }
......
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;
}
}
...@@ -60,7 +60,9 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor; ...@@ -60,7 +60,9 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables; 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.CodeRenameVisitor;
import jadx.core.dex.visitors.rename.MappingsVisitor;
import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.dex.visitors.rename.RenameVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
...@@ -97,6 +99,7 @@ public class Jadx { ...@@ -97,6 +99,7 @@ public class Jadx {
// rename and deobfuscation // rename and deobfuscation
passes.add(new DeobfuscatorVisitor()); passes.add(new DeobfuscatorVisitor());
passes.add(new RenameVisitor()); passes.add(new RenameVisitor());
passes.add(new MappingsVisitor());
passes.add(new SaveDeobfMapping()); passes.add(new SaveDeobfMapping());
passes.add(new UsageInfoVisitor()); passes.add(new UsageInfoVisitor());
...@@ -143,6 +146,7 @@ public class Jadx { ...@@ -143,6 +146,7 @@ public class Jadx {
passes.add(new ProcessKotlinInternals()); passes.add(new ProcessKotlinInternals());
} }
passes.add(new CodeRenameVisitor()); passes.add(new CodeRenameVisitor());
passes.add(new CodeMappingsVisitor());
if (args.isInlineMethods()) { if (args.isInlineMethods()) {
passes.add(new InlineMethods()); passes.add(new InlineMethods());
} }
...@@ -214,6 +218,7 @@ public class Jadx { ...@@ -214,6 +218,7 @@ public class Jadx {
} }
passes.add(new FinishTypeInference()); passes.add(new FinishTypeInference());
passes.add(new CodeRenameVisitor()); passes.add(new CodeRenameVisitor());
passes.add(new CodeMappingsVisitor());
passes.add(new DeboxingVisitor()); passes.add(new DeboxingVisitor());
passes.add(new ModVisitor()); passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor()); passes.add(new CodeShrinkVisitor());
......
...@@ -16,7 +16,7 @@ import org.slf4j.Logger; ...@@ -16,7 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.deobf.IAliasProvider; import jadx.api.deobf.IAliasProvider;
import jadx.api.deobf.impl.AlwaysRename; import jadx.api.deobf.impl.AlwaysRename;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
...@@ -45,7 +45,7 @@ public class DeobfPresets { ...@@ -45,7 +45,7 @@ public class DeobfPresets {
public static DeobfPresets build(RootNode root) { public static DeobfPresets build(RootNode root) {
Path deobfMapPath = getPathDeobfMapPath(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); LOG.debug("Deobfuscation map file set to: {}", deobfMapPath);
} }
return new DeobfPresets(deobfMapPath); return new DeobfPresets(deobfMapPath);
...@@ -53,7 +53,7 @@ public class DeobfPresets { ...@@ -53,7 +53,7 @@ public class DeobfPresets {
private static Path getPathDeobfMapPath(RootNode root) { private static Path getPathDeobfMapPath(RootNode root) {
JadxArgs jadxArgs = root.getArgs(); JadxArgs jadxArgs = root.getArgs();
File deobfMapFile = jadxArgs.getDeobfuscationMapFile(); File deobfMapFile = jadxArgs.getGeneratedRenamesMappingFile();
if (deobfMapFile != null) { if (deobfMapFile != null) {
return deobfMapFile.toPath(); return deobfMapFile.toPath();
} }
......
...@@ -20,7 +20,7 @@ public class DeobfuscatorVisitor extends AbstractVisitor { ...@@ -20,7 +20,7 @@ public class DeobfuscatorVisitor extends AbstractVisitor {
return; return;
} }
DeobfPresets mapping = DeobfPresets.build(root); DeobfPresets mapping = DeobfPresets.build(root);
if (args.getDeobfuscationMapFileMode().shouldRead()) { if (args.getGeneratedRenamesMappingFileMode().shouldRead()) {
if (mapping.load()) { if (mapping.load()) {
mapping.apply(root); mapping.apply(root);
} }
......
...@@ -7,7 +7,7 @@ import org.slf4j.Logger; ...@@ -7,7 +7,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.core.codegen.json.JsonMappingGen; import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
...@@ -28,13 +28,13 @@ public class SaveDeobfMapping extends AbstractVisitor { ...@@ -28,13 +28,13 @@ public class SaveDeobfMapping extends AbstractVisitor {
} }
private void saveMappings(RootNode root) { private void saveMappings(RootNode root) {
DeobfuscationMapFileMode mode = root.getArgs().getDeobfuscationMapFileMode(); GeneratedRenamesMappingFileMode mode = root.getArgs().getGeneratedRenamesMappingFileMode();
if (!mode.shouldWrite()) { if (!mode.shouldWrite()) {
return; return;
} }
DeobfPresets mapping = DeobfPresets.build(root); DeobfPresets mapping = DeobfPresets.build(root);
Path deobfMapFile = mapping.getDeobfMapFile(); Path deobfMapFile = mapping.getDeobfMapFile();
if (mode == DeobfuscationMapFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) { if (mode == GeneratedRenamesMappingFileMode.READ_OR_SAVE && Files.exists(deobfMapFile)) {
return; return;
} }
try { try {
......
package jadx.core.dex.nodes;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public interface IMappingsUpdateListener {
void updated(MemoryMappingTree mappingTree);
}
...@@ -10,12 +10,17 @@ import org.jetbrains.annotations.Nullable; ...@@ -10,12 +10,17 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.core.nodes.IMethodNode; 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.ICodeReader;
import jadx.api.plugins.input.data.IDebugInfo; import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.IMethodData; import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr; 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.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
...@@ -245,6 +250,32 @@ public class MethodNode extends NotificationAttrNode implements IMethodNode, ...@@ -245,6 +250,32 @@ public class MethodNode extends NotificationAttrNode implements IMethodNode,
return mthInfo.getReturnType().equals(ArgType.VOID); return mthInfo.getReturnType().equals(ArgType.VOID);
} }
public List<VarNode> collectArgsWithoutLoading() {
ICodeInfo codeInfo = getTopParentClass().getCode();
int mthDefPos = getDefPosition();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
List<VarNode> args = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
if (pos > lineEndPos) {
// Stop at line end
return Boolean.TRUE;
}
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
if (declRef instanceof VarNode) {
VarNode varNode = (VarNode) declRef;
if (!varNode.getMth().equals(this)) {
// Stop if we've gone too far and have entered a different method
return Boolean.TRUE;
}
args.add(varNode);
}
}
return null;
});
return args;
}
public List<RegisterArg> getArgRegs() { public List<RegisterArg> getArgRegs() {
if (argsList == null) { if (argsList == null) {
throw new JadxRuntimeException("Method arg registers not loaded: " + this throw new JadxRuntimeException("Method arg registers not loaded: " + this
......
package jadx.core.dex.nodes; package jadx.core.dex.nodes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
...@@ -13,6 +14,10 @@ import org.jetbrains.annotations.Nullable; ...@@ -13,6 +14,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.ICodeCache;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
...@@ -20,6 +25,7 @@ import jadx.api.JadxDecompiler; ...@@ -20,6 +25,7 @@ import jadx.api.JadxDecompiler;
import jadx.api.ResourceFile; import jadx.api.ResourceFile;
import jadx.api.ResourceType; import jadx.api.ResourceType;
import jadx.api.ResourcesLoader; import jadx.api.ResourcesLoader;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.core.nodes.IRootNode; import jadx.api.core.nodes.IRootNode;
import jadx.api.data.ICodeData; import jadx.api.data.ICodeData;
import jadx.api.impl.passes.DecompilePassWrapper; import jadx.api.impl.passes.DecompilePassWrapper;
...@@ -65,11 +71,13 @@ public class RootNode implements IRootNode { ...@@ -65,11 +71,13 @@ public class RootNode implements IRootNode {
private final JadxArgs args; private final JadxArgs args;
private final List<IDexTreeVisitor> preDecompilePasses; private final List<IDexTreeVisitor> preDecompilePasses;
private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>(); private final List<ICodeDataUpdateListener> codeDataUpdateListeners = new ArrayList<>();
private final List<IMappingsUpdateListener> mappingsUpdateListeners = new ArrayList<>();
private final ProcessClass processClasses; private final ProcessClass processClasses;
private final ErrorsCounter errorsCounter = new ErrorsCounter(); private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final StringUtils stringUtils; private final StringUtils stringUtils;
private final ConstStorage constValues; private final ConstStorage constValues;
private MemoryMappingTree mappingTree;
private final InfoStorage infoStorage = new InfoStorage(); private final InfoStorage infoStorage = new InfoStorage();
private final CacheStorage cacheStorage = new CacheStorage(); private final CacheStorage cacheStorage = new CacheStorage();
private final TypeUpdate typeUpdate; private final TypeUpdate typeUpdate;
...@@ -209,6 +217,26 @@ public class RootNode implements IRootNode { ...@@ -209,6 +217,26 @@ public class RootNode implements IRootNode {
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", 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 @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) { private @Nullable ResourceFile getResourceFile(List<ResourceFile> resources) {
...@@ -558,11 +586,19 @@ public class RootNode implements IRootNode { ...@@ -558,11 +586,19 @@ public class RootNode implements IRootNode {
this.codeDataUpdateListeners.add(listener); this.codeDataUpdateListeners.add(listener);
} }
public void registerMappingsUpdateListener(IMappingsUpdateListener listener) {
this.mappingsUpdateListeners.add(listener);
}
public void notifyCodeDataListeners() { public void notifyCodeDataListeners() {
ICodeData codeData = args.getCodeData(); ICodeData codeData = args.getCodeData();
codeDataUpdateListeners.forEach(l -> l.updated(codeData)); codeDataUpdateListeners.forEach(l -> l.updated(codeData));
} }
public void notifyMappingsListeners() {
mappingsUpdateListeners.forEach(l -> l.updated(mappingTree));
}
public ClspGraph getClsp() { public ClspGraph getClsp() {
return clsp; return clsp;
} }
...@@ -589,6 +625,14 @@ public class RootNode implements IRootNode { ...@@ -589,6 +625,14 @@ public class RootNode implements IRootNode {
return constValues; return constValues;
} }
public MemoryMappingTree getMappingTree() {
return mappingTree;
}
public void setMappingTree(MemoryMappingTree mappingTree) {
this.mappingTree = mappingTree;
}
public InfoStorage getInfoStorage() { public InfoStorage getInfoStorage() {
return infoStorage; return infoStorage;
} }
......
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<String, ClassMapping> 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<SSAVar> 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;
}
}
}
}
}
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<File> 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
}
}
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<VarNode> 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<SSAVar> 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<VarNode> 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<SSAVar> ssaVars = mth.getSVars();
if (ssaVars.isEmpty()) {
return null;
}
Integer lastArgLvIndex = mth.getAccessFlags().isStatic() ? -1 : 0;
List<RegisterArg> 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<VarNode> 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<SSAVar> ssaVars = mth.getSVars();
if (ssaVars.isEmpty()) {
return null;
}
List<RegisterArg> 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<SSAVar> 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;
}
}
...@@ -39,7 +39,7 @@ import jadx.api.JadxArgs; ...@@ -39,7 +39,7 @@ import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess; import jadx.api.JadxInternalAccess;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.metadata.ICodeMetadata; import jadx.api.metadata.ICodeMetadata;
import jadx.api.metadata.annotations.InsnCodeOffset; import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
...@@ -132,7 +132,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -132,7 +132,7 @@ public abstract class IntegrationTest extends TestUtils {
args.setFsCaseSensitive(false); // use same value on all systems args.setFsCaseSensitive(false); // use same value on all systems
args.setCommentsLevel(CommentsLevel.DEBUG); args.setCommentsLevel(CommentsLevel.DEBUG);
args.setDeobfuscationOn(false); args.setDeobfuscationOn(false);
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE); args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE);
} }
@AfterEach @AfterEach
...@@ -555,7 +555,7 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -555,7 +555,7 @@ public abstract class IntegrationTest extends TestUtils {
protected void enableDeobfuscation() { protected void enableDeobfuscation() {
args.setDeobfuscationOn(true); args.setDeobfuscationOn(true);
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE); args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE);
args.setDeobfuscationMinLength(2); args.setDeobfuscationMinLength(2);
args.setDeobfuscationMaxLength(64); args.setDeobfuscationMaxLength(64);
} }
......
...@@ -35,11 +35,6 @@ dependencies { ...@@ -35,11 +35,6 @@ dependencies {
implementation 'com.android.tools.build:apksig:7.2.1' implementation 'com.android.tools.build:apksig:7.2.1'
implementation 'io.github.hqktech:jdwp:1.0' implementation 'io.github.hqktech:jdwp:1.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 testImplementation project(":jadx-core").sourceSets.test.output
} }
......
...@@ -50,6 +50,7 @@ public class JadxWrapper { ...@@ -50,6 +50,7 @@ public class JadxWrapper {
private final MainWindow mainWindow; private final MainWindow mainWindow;
private volatile @Nullable JadxDecompiler decompiler; private volatile @Nullable JadxDecompiler decompiler;
private PluginsContext pluginsContext; private PluginsContext pluginsContext;
private boolean resetDiskCacheOnNextReload = false;
public JadxWrapper(MainWindow mainWindow) { public JadxWrapper(MainWindow mainWindow) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
...@@ -71,8 +72,8 @@ public class JadxWrapper { ...@@ -71,8 +72,8 @@ public class JadxWrapper {
initCodeCache(); initCodeCache();
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Jadx decompiler wrapper init error", e);
close(); close();
throw new JadxRuntimeException("Jadx decompiler wrapper init error", e);
} }
} }
...@@ -118,8 +119,15 @@ public class JadxWrapper { ...@@ -118,8 +119,15 @@ public class JadxWrapper {
} }
} }
public void resetDiskCacheOnNextReload() {
resetDiskCacheOnNextReload = true;
}
private BufferCodeCache buildBufferedDiskCache() { 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); return new BufferCodeCache(diskCache);
} }
...@@ -231,6 +239,12 @@ public class JadxWrapper { ...@@ -231,6 +239,12 @@ public class JadxWrapper {
public void reloadCodeData() { public void reloadCodeData() {
getDecompiler().reloadCodeData(); getDecompiler().reloadCodeData();
mainWindow.renamesChanged();
}
public void reloadMappings() {
getDecompiler().reloadMappings();
mainWindow.renamesChanged();
} }
public JavaNode getJavaNodeByRef(ICodeNodeRef nodeRef) { public JavaNode getJavaNodeByRef(ICodeNodeRef nodeRef) {
......
...@@ -16,6 +16,7 @@ import org.slf4j.Logger; ...@@ -16,6 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.MappingWriter; import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat; import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree;
...@@ -41,6 +42,7 @@ import jadx.core.dex.nodes.FieldNode; ...@@ -41,6 +42,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.core.utils.mappings.DalvikToJavaBytecodeUtils;
public class MappingExporter { public class MappingExporter {
private static final Logger LOG = LoggerFactory.getLogger(MappingExporter.class); private static final Logger LOG = LoggerFactory.getLogger(MappingExporter.class);
...@@ -50,32 +52,6 @@ public class MappingExporter { ...@@ -50,32 +52,6 @@ public class MappingExporter {
this.root = rootNode; this.root = rootNode;
} }
private List<VarNode> collectMethodArgs(MethodNode methodNode) {
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
int mthDefPos = methodNode.getDefPosition();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
List<VarNode> args = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
if (pos > lineEndPos) {
// Stop at line end
return Boolean.TRUE;
}
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
if (declRef instanceof VarNode) {
VarNode varNode = (VarNode) declRef;
if (!varNode.getMth().equals(methodNode)) {
// Stop if we've gone too far and have entered a different method
return Boolean.TRUE;
}
args.add(varNode);
}
}
return null;
});
return args;
}
private List<SimpleEntry<VarNode, Integer>> collectMethodVars(MethodNode methodNode) { private List<SimpleEntry<VarNode, Integer>> collectMethodVars(MethodNode methodNode) {
ICodeInfo codeInfo = methodNode.getTopParentClass().getCode(); ICodeInfo codeInfo = methodNode.getTopParentClass().getCode();
int mthDefPos = methodNode.getDefPosition(); int mthDefPos = methodNode.getDefPosition();
...@@ -160,8 +136,14 @@ public class MappingExporter { ...@@ -160,8 +136,14 @@ public class MappingExporter {
FileUtils.makeDirs(path); 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.visitHeader();
mappingTree.visitNamespaces("official", Arrays.asList("named")); mappingTree.visitNamespaces(srcNamespace, Arrays.asList(dstNamespace));
mappingTree.visitContent(); mappingTree.visitContent();
for (ClassNode cls : root.getClasses()) { for (ClassNode cls : root.getClasses()) {
...@@ -215,10 +197,12 @@ public class MappingExporter { ...@@ -215,10 +197,12 @@ public class MappingExporter {
} }
// Method args // Method args
int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1; int lvtIndex = mth.getAccessFlags().isStatic() ? 0 : 1;
int lastArgLvIndex = lvtIndex - 1; List<VarNode> args = mth.collectArgsWithoutLoading();
List<VarNode> args = collectMethodArgs(mth);
for (VarNode arg : args) { 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() String key = rawClassName + methodInfo.getShortId()
+ JadxCodeRef.forVar(arg.getReg(), arg.getSsa()); + JadxCodeRef.forVar(arg.getReg(), arg.getSsa());
if (mappedMethodArgsAndVars.containsKey(key)) { if (mappedMethodArgsAndVars.containsKey(key)) {
...@@ -226,7 +210,6 @@ public class MappingExporter { ...@@ -226,7 +210,6 @@ public class MappingExporter {
mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, mappedMethodArgsAndVars.get(key)); mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, mappedMethodArgsAndVars.get(key));
mappedMethodArgsAndVars.remove(key); mappedMethodArgsAndVars.remove(key);
} }
lastArgLvIndex = lvIndex;
lvtIndex++; lvtIndex++;
// Not checking for comments since method args can't have any // Not checking for comments since method args can't have any
} }
...@@ -235,7 +218,10 @@ public class MappingExporter { ...@@ -235,7 +218,10 @@ public class MappingExporter {
for (SimpleEntry<VarNode, Integer> entry : vars) { for (SimpleEntry<VarNode, Integer> entry : vars) {
VarNode var = entry.getKey(); VarNode var = entry.getKey();
int offset = entry.getValue(); 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() String key = rawClassName + methodInfo.getShortId()
+ JadxCodeRef.forVar(var.getReg(), var.getSsa()); + JadxCodeRef.forVar(var.getReg(), var.getSsa());
if (mappedMethodArgsAndVars.containsKey(key)) { if (mappedMethodArgsAndVars.containsKey(key)) {
...@@ -251,7 +237,11 @@ public class MappingExporter { ...@@ -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); MappingWriter writer = MappingWriter.create(path, mappingFormat);
mappingTree.accept(writer); mappingTree.accept(writer);
mappingTree.visitEnd(); mappingTree.visitEnd();
......
...@@ -159,6 +159,21 @@ public class JadxProject { ...@@ -159,6 +159,21 @@ public class JadxProject {
return data.getActiveTab(); 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() { public @NotNull Path getCacheDir() {
Path cacheDir = data.getCacheDir(); Path cacheDir = data.getCacheDir();
if (cacheDir != null) { if (cacheDir != null) {
......
...@@ -29,8 +29,9 @@ import com.beust.jcommander.Parameter; ...@@ -29,8 +29,9 @@ import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode; import jadx.api.DecompilationMode;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource; import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.cli.JadxCLIArgs; import jadx.cli.JadxCLIArgs;
import jadx.cli.LogHelper; import jadx.cli.LogHelper;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
...@@ -327,6 +328,14 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -327,6 +328,14 @@ public class JadxSettings extends JadxCLIArgs {
this.debugInfo = useDebugInfo; this.debugInfo = useDebugInfo;
} }
public void setUserRenamesMappingsPath(Path path) {
this.userRenamesMappingsPath = path;
}
public void setUserRenamesMappingsMode(UserRenamesMappingsMode mode) {
this.userRenamesMappingsMode = mode;
}
public void setDeobfuscationOn(boolean deobfuscationOn) { public void setDeobfuscationOn(boolean deobfuscationOn) {
this.deobfuscationOn = deobfuscationOn; this.deobfuscationOn = deobfuscationOn;
} }
...@@ -339,8 +348,8 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -339,8 +348,8 @@ public class JadxSettings extends JadxCLIArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength; this.deobfuscationMaxLength = deobfuscationMaxLength;
} }
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode mode) { public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) {
this.deobfuscationMapFileMode = mode; this.generatedRenamesMappingFileMode = mode;
} }
public void setDeobfuscationUseSourceNameAsAlias(boolean deobfuscationUseSourceNameAsAlias) { public void setDeobfuscationUseSourceNameAsAlias(boolean deobfuscationUseSourceNameAsAlias) {
...@@ -656,7 +665,7 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -656,7 +665,7 @@ public class JadxSettings extends JadxCLIArgs {
setDeobfuscationMaxLength(64); setDeobfuscationMaxLength(64);
setDeobfuscationUseSourceNameAsAlias(true); setDeobfuscationUseSourceNameAsAlias(true);
setDeobfuscationParseKotlinMetadata(true); setDeobfuscationParseKotlinMetadata(true);
setDeobfuscationMapFileMode(DeobfuscationMapFileMode.READ); setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.getDefault());
setThreadsCount(JadxArgs.DEFAULT_THREADS_COUNT); setThreadsCount(JadxArgs.DEFAULT_THREADS_COUNT);
setReplaceConsts(true); setReplaceConsts(true);
setSkipResources(false); setSkipResources(false);
...@@ -728,7 +737,7 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -728,7 +737,7 @@ public class JadxSettings extends JadxCLIArgs {
fromVersion++; fromVersion++;
} }
if (fromVersion == 15) { if (fromVersion == 15) {
deobfuscationMapFileMode = DeobfuscationMapFileMode.READ; generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
fromVersion++; fromVersion++;
} }
if (fromVersion == 16) { if (fromVersion == 16) {
......
...@@ -63,7 +63,7 @@ import jadx.api.CommentsLevel; ...@@ -63,7 +63,7 @@ import jadx.api.CommentsLevel;
import jadx.api.DecompilationMode; import jadx.api.DecompilationMode;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames; import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.args.DeobfuscationMapFileMode; import jadx.api.args.GeneratedRenamesMappingFileMode;
import jadx.api.args.ResourceNameSource; import jadx.api.args.ResourceNameSource;
import jadx.api.plugins.JadxPlugin; import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo; import jadx.api.plugins.JadxPluginInfo;
...@@ -266,12 +266,14 @@ public class JadxSettingsWindow extends JDialog { ...@@ -266,12 +266,14 @@ public class JadxSettingsWindow extends JDialog {
needReload(); needReload();
}); });
JComboBox<DeobfuscationMapFileMode> deobfMapFileModeCB = new JComboBox<>(DeobfuscationMapFileMode.values()); JComboBox<GeneratedRenamesMappingFileMode> generatedRenamesMappingFileModeCB =
deobfMapFileModeCB.setSelectedItem(settings.getDeobfuscationMapFileMode()); new JComboBox<>(GeneratedRenamesMappingFileMode.values());
deobfMapFileModeCB.addActionListener(e -> { generatedRenamesMappingFileModeCB.setSelectedItem(settings.getGeneratedRenamesMappingFileMode());
DeobfuscationMapFileMode newValue = (DeobfuscationMapFileMode) deobfMapFileModeCB.getSelectedItem(); generatedRenamesMappingFileModeCB.addActionListener(e -> {
if (newValue != settings.getDeobfuscationMapFileMode()) { GeneratedRenamesMappingFileMode newValue =
settings.setDeobfuscationMapFileMode(newValue); (GeneratedRenamesMappingFileMode) generatedRenamesMappingFileModeCB.getSelectedItem();
if (newValue != settings.getGeneratedRenamesMappingFileMode()) {
settings.setGeneratedRenamesMappingFileMode(newValue);
needReload(); needReload();
} }
}); });
...@@ -281,7 +283,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -281,7 +283,7 @@ public class JadxSettingsWindow extends JDialog {
deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner); deobfGroup.addRow(NLS.str("preferences.deobfuscation_min_len"), minLenSpinner);
deobfGroup.addRow(NLS.str("preferences.deobfuscation_max_len"), maxLenSpinner); 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_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(); deobfGroup.end();
Collection<JComponent> connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner); Collection<JComponent> connectedComponents = Arrays.asList(minLenSpinner, maxLenSpinner);
......
...@@ -18,6 +18,7 @@ public class ProjectData { ...@@ -18,6 +18,7 @@ public class ProjectData {
private JadxCodeData codeData = new JadxCodeData(); private JadxCodeData codeData = new JadxCodeData();
private List<TabViewState> openTabs = Collections.emptyList(); private List<TabViewState> openTabs = Collections.emptyList();
private int activeTab = -1; private int activeTab = -1;
private @Nullable Path mappingsPath;
private @Nullable Path cacheDir; private @Nullable Path cacheDir;
private boolean enableLiveReload = false; private boolean enableLiveReload = false;
private List<String> searchHistory = new ArrayList<>(); private List<String> searchHistory = new ArrayList<>();
...@@ -88,6 +89,15 @@ public class ProjectData { ...@@ -88,6 +89,15 @@ public class ProjectData {
return true; return true;
} }
@Nullable
public Path getMappingsPath() {
return mappingsPath;
}
public void setMappingsPath(Path mappingsPath) {
this.mappingsPath = mappingsPath;
}
@Nullable @Nullable
public Path getCacheDir() { public Path getCacheDir() {
return cacheDir; return cacheDir;
......
...@@ -23,6 +23,7 @@ import java.awt.event.MouseAdapter; ...@@ -23,6 +23,7 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
...@@ -37,7 +38,9 @@ import java.util.Locale; ...@@ -37,7 +38,9 @@ import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
...@@ -79,16 +82,21 @@ import org.slf4j.Logger; ...@@ -79,16 +82,21 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level; 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.format.MappingFormat;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JavaNode; import jadx.api.JavaNode;
import jadx.api.ResourceFile; import jadx.api.ResourceFile;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.plugins.utils.CommonFileUtils; import jadx.api.plugins.utils.CommonFileUtils;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.export.TemplateFile; import jadx.core.export.TemplateFile;
import jadx.core.utils.ListUtils; import jadx.core.utils.ListUtils;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.gui.JadxWrapper; import jadx.gui.JadxWrapper;
import jadx.gui.device.debugger.BreakpointManager; import jadx.gui.device.debugger.BreakpointManager;
...@@ -176,10 +184,16 @@ public class MainWindow extends JFrame { ...@@ -176,10 +184,16 @@ public class MainWindow extends JFrame {
private final transient BackgroundExecutor backgroundExecutor; private final transient BackgroundExecutor backgroundExecutor;
private transient @NotNull JadxProject project; private transient @NotNull JadxProject project;
private boolean projectOpen = false;
private transient Action newProjectAction; private transient Action newProjectAction;
private transient Action saveProjectAction; 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 JPanel mainPanel;
private JSplitPane splitPane; private JSplitPane splitPane;
...@@ -325,8 +339,7 @@ public class MainWindow extends JFrame { ...@@ -325,8 +339,7 @@ public class MainWindow extends JFrame {
if (!ensureProjectIsSaved()) { if (!ensureProjectIsSaved()) {
return; return;
} }
closeAll(); closeAll(false);
exportMappingsMenu.setEnabled(false);
updateProject(new JadxProject(this)); updateProject(new JadxProject(this));
} }
...@@ -370,31 +383,126 @@ public class MainWindow extends JFrame { ...@@ -370,31 +383,126 @@ public class MainWindow extends JFrame {
update(); update();
} }
private void exportMappings(MappingFormat mappingFormat) { private void openMappings(MappingFormat mappingFormat) {
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_SAVE); FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_OPEN);
fileDialog.setTitle(NLS.str("file.export_mappings_as")); fileDialog.setTitle(NLS.str("file.open_mappings"));
Path workingDir = project.getWorkingDir();
Path baseDir = workingDir != null ? workingDir : settings.getLastSaveFilePath();
if (mappingFormat.hasSingleFile()) { if (mappingFormat.hasSingleFile()) {
fileDialog.setSelectedFile(baseDir.resolve("mappings." + mappingFormat.fileExt));
fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt)); fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt));
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY); fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
} else { } else {
fileDialog.setCurrentDir(baseDir);
fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY);
} }
List<Path> paths = fileDialog.show(); List<Path> selectedPaths = fileDialog.show();
if (paths.size() != 1) { if (selectedPaths.size() != 1) {
return; return;
} }
Path savePath = paths.get(0); settings.setLastOpenFilePath(fileDialog.getCurrentDir());
LOG.info("Export mappings to: {}", savePath.toAbsolutePath()); Path filePath = selectedPaths.get(0);
backgroundExecutor.execute(NLS.str("progress.export_mappings"), LOG.info("Loading mappings from: {}", filePath.toAbsolutePath());
() -> new MappingExporter(wrapper.getDecompiler().getRoot())
.exportMappings(savePath, project.getCodeData(), mappingFormat), 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()); s -> update());
} }
private void saveMappingsAs(MappingFormat mappingFormat) {
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_SAVE);
fileDialog.setTitle(NLS.str("file.save_mappings_as"));
if (mappingFormat.hasSingleFile()) {
fileDialog.setSelectedFile(fileDialog.getCurrentDir().resolve("mappings." + mappingFormat.fileExt));
fileDialog.setFileExtList(Collections.singletonList(mappingFormat.fileExt));
fileDialog.setSelectionMode(JFileChooser.FILES_ONLY);
} else {
fileDialog.setSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
List<Path> selectedPaths = fileDialog.show();
if (selectedPaths.size() != 1) {
return;
}
settings.setLastSaveFilePath(fileDialog.getCurrentDir());
Path savePath = selectedPaths.get(0);
// Append file extension if missing
if (mappingFormat.hasSingleFile() && !savePath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(mappingFormat.fileExt)) {
savePath = savePath.resolveSibling(savePath.getFileName() + "." + mappingFormat.fileExt);
}
// If the target file already exists (and it's not an empty directory), show an overwrite
// confirmation
if (Files.exists(savePath)) {
boolean emptyDir = false;
try (Stream<Path> entries = Files.list(savePath)) {
emptyDir = !entries.findFirst().isPresent();
} catch (IOException ignored) {
}
if (!emptyDir) {
int res = JOptionPane.showConfirmDialog(
this,
NLS.str("confirm.save_as_message", savePath.getFileName()),
NLS.str("confirm.save_as_title"),
JOptionPane.YES_NO_OPTION);
if (res == JOptionPane.NO_OPTION) {
return;
}
}
}
LOG.info("Saving mappings to: {}", savePath.toAbsolutePath());
project.setMappingsPath(savePath);
currentMappingFormat = mappingFormat;
saveMappings();
}
public void addNewScript() { public void addNewScript() {
FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_SAVE); FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.CUSTOM_SAVE);
fileDialog.setTitle(NLS.str("file.save")); fileDialog.setTitle(NLS.str("file.save"));
...@@ -439,14 +547,14 @@ public class MainWindow extends JFrame { ...@@ -439,14 +547,14 @@ public class MainWindow extends JFrame {
private void open(List<Path> paths, Runnable onFinish) { private void open(List<Path> paths, Runnable onFinish) {
saveAll(); saveAll();
closeAll(); closeAll(false);
if (paths.size() == 1 && openSingleFile(paths.get(0), onFinish)) { if (paths.size() == 1 && openSingleFile(paths.get(0), onFinish)) {
return; return;
} }
// start new project // start new project
project = new JadxProject(this); project = new JadxProject(this);
project.setFilePaths(paths); project.setFilePaths(paths);
loadFiles(onFinish); loadFiles(false, onFinish);
} }
private boolean openSingleFile(Path singleFile, Runnable onFinish) { private boolean openSingleFile(Path singleFile, Runnable onFinish) {
...@@ -472,8 +580,8 @@ public class MainWindow extends JFrame { ...@@ -472,8 +580,8 @@ public class MainWindow extends JFrame {
public synchronized void reopen() { public synchronized void reopen() {
saveAll(); saveAll();
closeAll(); closeAll(true);
loadFiles(EMPTY_RUNNABLE); loadFiles(true, EMPTY_RUNNABLE);
} }
private void openProject(Path path, Runnable onFinish) { private void openProject(Path path, Runnable onFinish) {
...@@ -488,17 +596,61 @@ public class MainWindow extends JFrame { ...@@ -488,17 +596,61 @@ public class MainWindow extends JFrame {
} }
settings.addRecentProject(path); settings.addRecentProject(path);
project = jadxProject; project = jadxProject;
loadFiles(onFinish); loadFiles(false, onFinish);
} }
private void loadFiles(Runnable onFinish) { private void loadFiles(boolean reopening, Runnable onFinish) {
exportMappingsMenu.setEnabled(false);
if (project.getFilePaths().isEmpty()) { if (project.getFilePaths().isEmpty()) {
return; return;
} }
JadxSettings settings = wrapper.getSettings();
if (settings.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE) {
// Use CLI specified mappings path if present
if (settings.getUserRenamesMappingsPath() != null && settings.getUserRenamesMappingsPath().toFile().exists()) {
project.setMappingsPath(settings.getUserRenamesMappingsPath());
} else {
if (settings.getUserRenamesMappingsPath() != null) {
LOG.error("The specified mappings path doesn't exist, falling back to the project's previously loaded ones");
}
MappingFormat mappingFormat = null;
try {
mappingFormat = MappingReader.detectFormat(project.getMappingsPath());
} catch (Exception ignored) {
}
// Use the project's last opened mappings, if present
if (mappingFormat != null) {
settings.setUserRenamesMappingsPath(project.getMappingsPath());
currentMappingFormat = mappingFormat;
} else {
if (project.getMappingsPath() != null
|| (project.getMappingsPath() == null && settings.getUserRenamesMappingsPath() != null)) {
LOG.error("The project's last opened mappings path is corrupted, resetting");
}
// None of the mapping paths exist, so remove them from the settings
settings.setUserRenamesMappingsPath(null);
project.setMappingsPath(null);
}
}
}
AtomicReference<Exception> wrapperException = new AtomicReference<>();
backgroundExecutor.execute(NLS.str("progress.load"), backgroundExecutor.execute(NLS.str("progress.load"),
wrapper::open, () -> {
try {
wrapper.open();
} catch (Exception e) {
wrapperException.set(e);
}
},
status -> { 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) { if (status == TaskStatus.CANCEL_BY_MEMORY) {
showHeapUsageBar(); showHeapUsageBar();
UiUtils.errorMessage(this, NLS.str("message.memoryLow")); UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
...@@ -510,7 +662,6 @@ public class MainWindow extends JFrame { ...@@ -510,7 +662,6 @@ public class MainWindow extends JFrame {
} }
checkLoadedStatus(); checkLoadedStatus();
onOpen(); onOpen();
exportMappingsMenu.setEnabled(true);
onFinish.run(); onFinish.run();
}); });
} }
...@@ -520,15 +671,21 @@ public class MainWindow extends JFrame { ...@@ -520,15 +671,21 @@ public class MainWindow extends JFrame {
BreakpointManager.saveAndExit(); BreakpointManager.saveAndExit();
} }
private void closeAll() { private void closeAll(boolean reopening) {
cancelBackgroundJobs(); cancelBackgroundJobs();
clearTree(); clearTree();
if (projectOpen) {
closeMappings(!reopening);
}
resetCache(); resetCache();
LogCollector.getInstance().reset(); LogCollector.getInstance().reset();
wrapper.close(); wrapper.close();
tabbedPane.closeAllTabs(); tabbedPane.closeAllTabs();
UiUtils.resetClipboardOwner(); UiUtils.resetClipboardOwner();
System.gc(); System.gc();
projectOpen = false;
renamesChanged = false;
update();
} }
private void checkLoadedStatus() { private void checkLoadedStatus() {
...@@ -553,10 +710,10 @@ public class MainWindow extends JFrame { ...@@ -553,10 +710,10 @@ public class MainWindow extends JFrame {
private void onOpen() { private void onOpen() {
deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree(); initTree();
projectOpen = true;
update(); update();
updateLiveReload(project.isEnableLiveReload()); updateLiveReload(project.isEnableLiveReload());
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent()); BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
backgroundExecutor.execute(NLS.str("progress.load"), backgroundExecutor.execute(NLS.str("progress.load"),
this::restoreOpenTabs, this::restoreOpenTabs,
status -> runInitialBackgroundJobs()); status -> runInitialBackgroundJobs());
...@@ -584,6 +741,10 @@ public class MainWindow extends JFrame { ...@@ -584,6 +741,10 @@ public class MainWindow extends JFrame {
private boolean ensureProjectIsSaved() { private boolean ensureProjectIsSaved() {
if (!project.isSaved() && !project.isInitial()) { if (!project.isSaved() && !project.isInitial()) {
if (wrapper.getRootNode().getMappingTree() != null
&& wrapper.getSettings().getUserRenamesMappingsMode() == UserRenamesMappingsMode.READ_AND_AUTOSAVE_BEFORE_CLOSING) {
saveMappings();
}
int res = JOptionPane.showConfirmDialog( int res = JOptionPane.showConfirmDialog(
this, this,
NLS.str("confirm.not_saved_message"), NLS.str("confirm.not_saved_message"),
...@@ -606,7 +767,12 @@ public class MainWindow extends JFrame { ...@@ -606,7 +767,12 @@ public class MainWindow extends JFrame {
private void update() { private void update() {
newProjectAction.setEnabled(!project.isInitial()); 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(); Path projectPath = project.getProjectPath();
String pathString; String pathString;
...@@ -619,6 +785,16 @@ public class MainWindow extends JFrame { ...@@ -619,6 +785,16 @@ public class MainWindow extends JFrame {
+ project.getName() + pathString + " - " + DEFAULT_TITLE); + 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() { protected void resetCache() {
cacheObject.reset(); cacheObject.reset();
cacheObject.setJRoot(treeRoot); cacheObject.setJRoot(treeRoot);
...@@ -924,35 +1100,80 @@ public class MainWindow extends JFrame { ...@@ -924,35 +1100,80 @@ public class MainWindow extends JFrame {
liveReloadMenuItem = new JCheckBoxMenuItem(liveReload); liveReloadMenuItem = new JCheckBoxMenuItem(liveReload);
liveReloadMenuItem.setState(project.isEnableLiveReload()); liveReloadMenuItem.setState(project.isEnableLiveReload());
Action exportMappingsAsTiny2 = new AbstractAction("Tiny v2 file") { Action openTiny2Mappings = new AbstractAction("Tiny v2 file") {
@Override @Override
public void actionPerformed(ActionEvent e) { 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 @Override
public void actionPerformed(ActionEvent e) { 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 @Override
public void actionPerformed(ActionEvent e) { 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");
openMappingsMenu = new JMenu(NLS.str("file.open_mappings"));
openMappingsMenu.add(openTiny2Mappings);
openMappingsMenu.add(openEnigmaMappings);
openMappingsMenu.add(openEnigmaDirMappings);
exportMappingsMenu = new JMenu(NLS.str("file.export_mappings_as")); saveMappingsAction = new AbstractAction(NLS.str("file.save_mappings")) {
exportMappingsMenu.add(exportMappingsAsTiny2); @Override
exportMappingsMenu.add(exportMappingsAsEnigma); public void actionPerformed(ActionEvent e) {
exportMappingsMenu.add(exportMappingsAsEnigmaDir); saveMappings();
exportMappingsMenu.setEnabled(false); }
};
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) { Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), ICON_SAVE_ALL) {
@Override @Override
...@@ -1144,7 +1365,10 @@ public class MainWindow extends JFrame { ...@@ -1144,7 +1365,10 @@ public class MainWindow extends JFrame {
file.add(reload); file.add(reload);
file.add(liveReloadMenuItem); file.add(liveReloadMenuItem);
file.addSeparator(); file.addSeparator();
file.add(exportMappingsMenu); file.add(openMappingsMenu);
file.add(saveMappingsAction);
file.add(saveMappingsAsMenu);
file.add(closeMappingsAction);
file.addSeparator(); file.addSeparator();
file.add(saveAllAction); file.add(saveAllAction);
file.add(exportAction); file.add(exportAction);
...@@ -1502,7 +1726,7 @@ public class MainWindow extends JFrame { ...@@ -1502,7 +1726,7 @@ public class MainWindow extends JFrame {
saveSplittersInfo(); saveSplittersInfo();
} }
heapUsageBar.reset(); heapUsageBar.reset();
closeAll(); closeAll(false);
FileUtils.deleteTempRootDir(); FileUtils.deleteTempRootDir();
dispose(); dispose();
......
...@@ -12,14 +12,10 @@ import org.apache.commons.text.StringEscapeUtils; ...@@ -12,14 +12,10 @@ import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.api.JavaField; import jadx.api.JavaField;
import jadx.api.JavaMethod; import jadx.api.JavaMethod;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode; import jadx.api.metadata.annotations.VarNode;
import jadx.api.utils.CodeUtils;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
...@@ -96,7 +92,10 @@ public final class FridaAction extends JNodeAction { ...@@ -96,7 +92,10 @@ public final class FridaAction extends JNodeAction {
functionUntilImplementation = String.format("%s[\"%s\"].implementation", shortClassName, methodName); functionUntilImplementation = String.format("%s[\"%s\"].implementation", shortClassName, methodName);
} }
List<String> methodArgNames = collectMethodArgNames(javaMethod); List<String> methodArgNames = new ArrayList<>();
for (VarNode arg : javaMethod.getMethodNode().collectArgsWithoutLoading()) {
methodArgNames.add(arg.getName());
}
String functionParametersString = String.join(", ", methodArgNames); String functionParametersString = String.join(", ", methodArgNames);
String logParametersString = String logParametersString =
...@@ -117,29 +116,6 @@ public final class FridaAction extends JNodeAction { ...@@ -117,29 +116,6 @@ public final class FridaAction extends JNodeAction {
return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody; return generateClassSnippet(jMth.getJParent()) + "\n" + functionParameterAndBody;
} }
private List<String> collectMethodArgNames(JavaMethod javaMethod) {
ICodeInfo codeInfo = javaMethod.getTopParentClass().getCodeInfo();
int mthDefPos = javaMethod.getDefPos();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
List<String> argNames = new ArrayList<>();
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
if (pos > lineEndPos) {
return Boolean.TRUE; // stop at line end
}
if (ann instanceof NodeDeclareRef) {
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
if (declRef instanceof VarNode) {
VarNode varNode = (VarNode) declRef;
if (varNode.getMth().equals(javaMethod.getMethodNode())) {
argNames.add(varNode.getName());
}
}
}
return null;
});
return argNames;
}
private String generateClassSnippet(JClass jc) { private String generateClassSnippet(JClass jc) {
JavaClass javaClass = jc.getCls(); JavaClass javaClass = jc.getCls();
String rawClassName = StringEscapeUtils.escapeEcmaScript(javaClass.getRawName()); String rawClassName = StringEscapeUtils.escapeEcmaScript(javaClass.getRawName());
......
...@@ -136,10 +136,12 @@ public class FileDialog { ...@@ -136,10 +136,12 @@ public class FileDialog {
break; break;
case CUSTOM_SAVE: case CUSTOM_SAVE:
currentDir = mainWindow.getSettings().getLastSaveFilePath();
isOpen = false; isOpen = false;
break; break;
case CUSTOM_OPEN: case CUSTOM_OPEN:
currentDir = mainWindow.getSettings().getLastOpenFilePath();
isOpen = true; isOpen = true;
break; break;
} }
......
...@@ -32,15 +32,29 @@ import org.jetbrains.annotations.Nullable; ...@@ -32,15 +32,29 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.JavaNode;
import jadx.api.data.ICodeRename; import jadx.api.data.ICodeRename;
import jadx.api.data.impl.JadxCodeData; import jadx.api.data.impl.JadxCodeData;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.gui.jobs.TaskStatus; import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JRenameNode; import jadx.gui.treemodel.JRenameNode;
import jadx.gui.treemodel.JVariable;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane; import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.ClassCodeContentPanel;
...@@ -129,6 +143,97 @@ public class RenameDialog extends JDialog { ...@@ -129,6 +143,97 @@ public class RenameDialog extends JDialog {
if (!newName.isEmpty()) { if (!newName.isEmpty()) {
renames.add(rename); renames.add(rename);
} }
MemoryMappingTree mappingTree = mainWindow.getWrapper().getRootNode().getMappingTree();
if (mappingTree == null) {
return;
}
if (newName.isEmpty() || (javaNode != null && newName.equals(javaNode.getName()))) {
newName = null;
}
if (node instanceof JMethod) {
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
String classPath = javaMethod.getDeclaringClass().getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
String methodName = javaMethod.getMethodNode().getMethodInfo().getName();
String methodDesc = javaMethod.getMethodNode().getMethodInfo().getShortId().substring(methodName.length());
if (newName == null) {
MethodMapping mapping = mappingTree.getMethod(classPath, methodName, methodDesc);
if (mapping == null || deleteMappingIfEmpty(mapping, methodName, methodDesc)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitMethod(methodName, methodDesc);
mappingTree.visitDstName(MappedElementKind.METHOD, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JField) {
JavaField javaField = ((JField) node).getJavaField();
String classPath = javaField.getDeclaringClass().getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
String fieldName = javaField.getFieldNode().getFieldInfo().getName();
String fieldDesc = TypeGen.signature(javaField.getFieldNode().getFieldInfo().getType());
if (newName == null) {
FieldMapping mapping = mappingTree.getField(classPath, fieldName, fieldDesc);
if (mapping == null || deleteMappingIfEmpty(mapping, fieldName, fieldDesc)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitField(fieldName, fieldDesc);
mappingTree.visitDstName(MappedElementKind.FIELD, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JClass) {
JavaClass javaClass = ((JClass) node).getCls();
String classPath = javaClass.getClassNode().getClassInfo().makeRawFullName().replace('.', '/');
if (newName == null) {
ClassMapping mapping = mappingTree.getClass(classPath);
if (mapping == null || deleteMappingIfEmpty(mapping)) {
return;
}
}
mappingTree.visitClass(classPath);
mappingTree.visitDstName(MappedElementKind.CLASS, 0, newName);
mappingTree.visitEnd();
} else if (node instanceof JPackage) {
JPackage jPackage = (JPackage) node;
String origPackageName = jPackage.getFullName().replace('.', '/');
for (ClassMapping cls : mappingTree.getClasses()) {
if (!cls.getSrcName().startsWith(origPackageName)) {
continue;
}
if (newName == null) {
newName = "";
}
String newDstName = newName.replace('.', '/') + cls.getDstName(0).substring(newName.length() + 1);
cls.setDstName(newDstName, 0);
}
} else if (node instanceof JVariable) {
// TODO
}
}
private boolean deleteMappingIfEmpty(ClassMapping mapping) {
if (mapping.getFields().isEmpty() && mapping.getMethods().isEmpty()) {
mapping.getTree().removeClass(mapping.getSrcName());
return true;
}
return false;
}
private boolean deleteMappingIfEmpty(MethodMapping mapping, String methodName, String methodDesc) {
if (mapping.getArgs().isEmpty() && mapping.getVars().isEmpty()) {
mapping.getOwner().removeMethod(methodName, methodDesc);
deleteMappingIfEmpty(mapping.getOwner());
return true;
}
return false;
}
private boolean deleteMappingIfEmpty(FieldMapping mapping, String fieldName, String fieldDesc) {
mapping.getOwner().removeMethod(fieldName, fieldDesc);
if (mapping.getOwner().getFields().isEmpty() && mapping.getOwner().getMethods().isEmpty()) {
mapping.getTree().removeClass(mapping.getOwner().getSrcName());
return true;
}
return false;
} }
private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) { private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
......
...@@ -29,11 +29,14 @@ import org.slf4j.LoggerFactory; ...@@ -29,11 +29,14 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeCache; import jadx.api.ICodeCache;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; 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.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
...@@ -56,13 +59,14 @@ public class DiskCodeCache implements ICodeCache { ...@@ -56,13 +59,14 @@ public class DiskCodeCache implements ICodeCache {
private final Map<String, ICodeInfo> writeOps = new ConcurrentHashMap<>(); private final Map<String, ICodeInfo> writeOps = new ConcurrentHashMap<>();
private final Map<String, Integer> namesMap = new ConcurrentHashMap<>(); private final Map<String, Integer> namesMap = new ConcurrentHashMap<>();
public DiskCodeCache(RootNode root, Path baseDir) { public DiskCodeCache(RootNode root, JadxProject project, JadxSettings settings) {
Path baseDir = project.getCacheDir();
srcDir = baseDir.resolve("sources"); srcDir = baseDir.resolve("sources");
metaDir = baseDir.resolve("metadata"); metaDir = baseDir.resolve("metadata");
codeVersionFile = baseDir.resolve("code-version"); codeVersionFile = baseDir.resolve("code-version");
namesMapFile = baseDir.resolve("names-map"); namesMapFile = baseDir.resolve("names-map");
JadxArgs args = root.getArgs(); JadxArgs args = root.getArgs();
codeVersion = buildCodeVersion(args); codeVersion = buildCodeVersion(args, project, settings);
writePool = Executors.newFixedThreadPool(args.getThreadsCount()); writePool = Executors.newFixedThreadPool(args.getThreadsCount());
codeMetadataAdapter = new CodeMetadataAdapter(root); codeMetadataAdapter = new CodeMetadataAdapter(root);
if (checkCodeVersion()) { if (checkCodeVersion()) {
...@@ -85,7 +89,7 @@ public class DiskCodeCache implements ICodeCache { ...@@ -85,7 +89,7 @@ public class DiskCodeCache implements ICodeCache {
} }
} }
private void reset() { public void reset() {
try { try {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
LOG.info("Resetting disk code cache, base dir: {}", srcDir.getParent().toAbsolutePath()); LOG.info("Resetting disk code cache, base dir: {}", srcDir.getParent().toAbsolutePath());
...@@ -189,11 +193,19 @@ public class DiskCodeCache implements ICodeCache { ...@@ -189,11 +193,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 return DATA_FORMAT_VERSION
+ ":" + Jadx.getVersion() + ":" + Jadx.getVersion()
+ ":" + args.makeCodeArgsHash() + ":" + args.makeCodeArgsHash()
+ ":" + buildInputsHash(args.getInputFiles()); + ":" + buildInputsHash(args.getInputFiles())
+ ":" + mappingsLastModified;
} }
/** /**
......
...@@ -31,7 +31,10 @@ file.save_project_as=Projekt speichern als… ...@@ -31,7 +31,10 @@ file.save_project_as=Projekt speichern als…
file.reload=Dateien neu laden file.reload=Dateien neu laden
file.live_reload=Live nachladen file.live_reload=Live nachladen
file.live_reload_desc=Dateien bei Änderungen autom. neuladen 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_all=Alles speichern
#file.save=Save #file.save=Save
file.export_gradle=Als Gradle-Projekt speichern file.export_gradle=Als Gradle-Projekt speichern
...@@ -50,7 +53,7 @@ tree.resources_title=Ressourcen ...@@ -50,7 +53,7 @@ tree.resources_title=Ressourcen
tree.loading=Laden… tree.loading=Laden…
progress.load=Laden progress.load=Laden
progress.export_mappings=Zuordnungen exportieren progress.save_mappings=Zuordnungen exportieren
progress.decompile=Dekompilieren progress.decompile=Dekompilieren
progress.canceling=Breche ab progress.canceling=Breche ab
...@@ -182,7 +185,7 @@ preferences.start_jobs=Autom. Hintergrunddekompilierung starten ...@@ -182,7 +185,7 @@ preferences.start_jobs=Autom. Hintergrunddekompilierung starten
preferences.select_font=Ändern preferences.select_font=Ändern
preferences.select_smali_font=Ändern preferences.select_smali_font=Ändern
preferences.deobfuscation_on=Deobfuskierung aktivieren 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_min_len=Minimale Namenlänge
preferences.deobfuscation_max_len=Maximale Namenlänge preferences.deobfuscation_max_len=Maximale Namenlänge
preferences.deobfuscation_source_alias=Quelldateiname als Klassennamen-Alias verwenden preferences.deobfuscation_source_alias=Quelldateiname als Klassennamen-Alias verwenden
...@@ -209,6 +212,8 @@ msg.language_changed_title=Sprache speichern ...@@ -209,6 +212,8 @@ msg.language_changed_title=Sprache speichern
msg.language_changed=Die neue Sprache wird beim nächsten Start der Anwendung angezeigt. msg.language_changed=Die neue Sprache wird beim nächsten Start der Anwendung angezeigt.
msg.project_error_title=Fehler msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden 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.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 msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen
......
...@@ -31,7 +31,10 @@ file.save_project_as=Save project as... ...@@ -31,7 +31,10 @@ file.save_project_as=Save project as...
file.reload=Reload files file.reload=Reload files
file.live_reload=Live reload file.live_reload=Live reload
file.live_reload_desc=Auto reload files on changes 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_all=Save all
file.save=Save file.save=Save
file.export_gradle=Save as gradle project file.export_gradle=Save as gradle project
...@@ -50,7 +53,7 @@ tree.resources_title=Resources ...@@ -50,7 +53,7 @@ tree.resources_title=Resources
tree.loading=Loading... tree.loading=Loading...
progress.load=Loading progress.load=Loading
progress.export_mappings=Exporting mappings progress.save_mappings=Saving mappings
progress.decompile=Decompiling progress.decompile=Decompiling
progress.canceling=Canceling progress.canceling=Canceling
...@@ -182,7 +185,7 @@ preferences.start_jobs=Auto start background decompilation ...@@ -182,7 +185,7 @@ preferences.start_jobs=Auto start background decompilation
preferences.select_font=Change preferences.select_font=Change
preferences.select_smali_font=Change preferences.select_smali_font=Change
preferences.deobfuscation_on=Enable deobfuscation 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_min_len=Minimum name length
preferences.deobfuscation_max_len=Maximum name length preferences.deobfuscation_max_len=Maximum name length
preferences.deobfuscation_source_alias=Use source file name as class name alias preferences.deobfuscation_source_alias=Use source file name as class name alias
...@@ -209,6 +212,8 @@ msg.language_changed_title=Language changed ...@@ -209,6 +212,8 @@ msg.language_changed_title=Language changed
msg.language_changed=New language will be displayed the next time application starts. msg.language_changed=New language will be displayed the next time application starts.
msg.project_error_title=Error msg.project_error_title=Error
msg.project_error=Project could not be loaded 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.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 msg.cant_add_comment=Can't add comment here
......
...@@ -31,7 +31,10 @@ file.open_title=Abrir archivo ...@@ -31,7 +31,10 @@ file.open_title=Abrir archivo
#file.reload=Reload files #file.reload=Reload files
#file.live_reload=Live reload #file.live_reload=Live reload
#file.live_reload_desc=Auto reload files on changes #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_all=Guardar todo
#file.save=Save #file.save=Save
file.export_gradle=Guardar como proyecto Gradle file.export_gradle=Guardar como proyecto Gradle
...@@ -50,7 +53,7 @@ tree.resources_title=Recursos ...@@ -50,7 +53,7 @@ tree.resources_title=Recursos
tree.loading=Cargando... tree.loading=Cargando...
progress.load=Cargando progress.load=Cargando
#progress.export_mappings= #progress.save_mappings=
progress.decompile=Decompiling progress.decompile=Decompiling
#progress.canceling=Canceling #progress.canceling=Canceling
...@@ -182,7 +185,7 @@ preferences.start_jobs=Inicio autom. descompilación de fondo ...@@ -182,7 +185,7 @@ preferences.start_jobs=Inicio autom. descompilación de fondo
preferences.select_font=Seleccionar preferences.select_font=Seleccionar
#preferences.select_smali_font= #preferences.select_smali_font=
preferences.deobfuscation_on=Activar desobfuscación 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_min_len=Longitud mínima del nombre
preferences.deobfuscation_max_len=Longitud máxima 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 preferences.deobfuscation_source_alias=Usar el nombre del source como alias para la clase
...@@ -209,6 +212,8 @@ msg.language_changed_title=Idioma cambiado ...@@ -209,6 +212,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.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie.
#msg.project_error_title= #msg.project_error_title=
#msg.project_error= #msg.project_error=
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
#msg.cmd_select_class_error= #msg.cmd_select_class_error=
#msg.cant_add_comment=Can't add comment here #msg.cant_add_comment=Can't add comment here
......
...@@ -31,7 +31,10 @@ file.save_project_as=다른 이름으로 프로젝트 저장... ...@@ -31,7 +31,10 @@ file.save_project_as=다른 이름으로 프로젝트 저장...
#file.reload=Reload files #file.reload=Reload files
#file.live_reload=Live reload #file.live_reload=Live reload
#file.live_reload_desc=Auto reload files on changes #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=모두 저장 file.save_all=모두 저장
#file.save=Save #file.save=Save
file.export_gradle=Gradle 프로젝트로 저장 file.export_gradle=Gradle 프로젝트로 저장
...@@ -50,7 +53,7 @@ tree.resources_title=리소스 ...@@ -50,7 +53,7 @@ tree.resources_title=리소스
tree.loading=로딩중... tree.loading=로딩중...
progress.load=로딩중 progress.load=로딩중
#progress.export_mappings= #progress.save_mappings=
progress.decompile=디컴파일 중 progress.decompile=디컴파일 중
#progress.canceling=Canceling #progress.canceling=Canceling
...@@ -182,7 +185,7 @@ preferences.start_jobs=백그라운드에서 디컴파일 자동 시작 ...@@ -182,7 +185,7 @@ preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경 preferences.select_font=변경
preferences.select_smali_font=변경 preferences.select_smali_font=변경
preferences.deobfuscation_on=난독 해제 활성화 preferences.deobfuscation_on=난독 해제 활성화
#preferences.deobfuscation_map_file_mode=Map file handle mode #preferences.generated_renames_mapping_file_mode=Map file handle mode
preferences.deobfuscation_min_len=최소 이름 길이 preferences.deobfuscation_min_len=최소 이름 길이
preferences.deobfuscation_max_len=최대 이름 길이 preferences.deobfuscation_max_len=최대 이름 길이
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용 preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
...@@ -209,6 +212,8 @@ msg.language_changed_title=언어 변경됨 ...@@ -209,6 +212,8 @@ msg.language_changed_title=언어 변경됨
msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다. msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다.
msg.project_error_title=오류 msg.project_error_title=오류
msg.project_error=프로젝트를 로드 할 수 없습니다. msg.project_error=프로젝트를 로드 할 수 없습니다.
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다. msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
msg.cant_add_comment=여기에 주석을 추가할수 없음 msg.cant_add_comment=여기에 주석을 추가할수 없음
......
...@@ -31,7 +31,10 @@ file.save_project_as=Salvar projeto como... ...@@ -31,7 +31,10 @@ file.save_project_as=Salvar projeto como...
file.reload=Recarregar arquivos file.reload=Recarregar arquivos
file.live_reload=Recarregar em tempo real file.live_reload=Recarregar em tempo real
file.live_reload_desc=Recarregar arquivos automaticamente ao serem alterados 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_all=Salvar tudo
#file.save=Save #file.save=Save
file.export_gradle=Salvar como um projeto gradle file.export_gradle=Salvar como um projeto gradle
...@@ -50,7 +53,7 @@ tree.resources_title=Recursos ...@@ -50,7 +53,7 @@ tree.resources_title=Recursos
tree.loading=Carregando... tree.loading=Carregando...
progress.load=Carregando progress.load=Carregando
progress.export_mappings=Exportando mappings #progress.save_mappings=Saving mappings
progress.decompile=Descompilando progress.decompile=Descompilando
progress.canceling=Cancelando progress.canceling=Cancelando
...@@ -182,7 +185,7 @@ preferences.start_jobs=Inicializar descompilação automaticamente em segundo-pl ...@@ -182,7 +185,7 @@ preferences.start_jobs=Inicializar descompilação automaticamente em segundo-pl
preferences.select_font=Alterar preferences.select_font=Alterar
preferences.select_smali_font=Alterar preferences.select_smali_font=Alterar
preferences.deobfuscation_on=Ativar desofuscação 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_min_len=Tamanho mínimo do nome
preferences.deobfuscation_max_len=Tamanho máximo do nome preferences.deobfuscation_max_len=Tamanho máximo do nome
preferences.deobfuscation_source_alias=Utilizar nome do arquivo como apelido da classe preferences.deobfuscation_source_alias=Utilizar nome do arquivo como apelido da classe
...@@ -209,6 +212,8 @@ msg.language_changed_title=Idioma alterado ...@@ -209,6 +212,8 @@ msg.language_changed_title=Idioma alterado
msg.language_changed=Novo idioma será mostrado na próxima inicialização. msg.language_changed=Novo idioma será mostrado na próxima inicialização.
msg.project_error_title=Erro msg.project_error_title=Erro
msg.project_error=Projeto não pôde ser carregado 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.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 msg.cant_add_comment=Não é possível adicionar comentários aqui
......
...@@ -31,7 +31,10 @@ file.save_project_as=另存项目为... ...@@ -31,7 +31,10 @@ file.save_project_as=另存项目为...
file.reload=重新加载文件 file.reload=重新加载文件
file.live_reload=实时重加载 file.live_reload=实时重加载
file.live_reload_desc=文件变动时自动重载 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_all=全部保存
#file.save=Save #file.save=Save
file.export_gradle=另存为 Gradle 项目 file.export_gradle=另存为 Gradle 项目
...@@ -50,7 +53,7 @@ tree.resources_title=资源文件 ...@@ -50,7 +53,7 @@ tree.resources_title=资源文件
tree.loading=加载中... tree.loading=加载中...
progress.load=正在加载 progress.load=正在加载
#progress.export_mappings= #progress.save_mappings=
progress.decompile=反编译中 progress.decompile=反编译中
progress.canceling=正在取消 progress.canceling=正在取消
...@@ -182,7 +185,7 @@ preferences.start_jobs=自动进行后台反编译 ...@@ -182,7 +185,7 @@ preferences.start_jobs=自动进行后台反编译
preferences.select_font=修改 preferences.select_font=修改
preferences.select_smali_font=修改 preferences.select_smali_font=修改
preferences.deobfuscation_on=启用反混淆 preferences.deobfuscation_on=启用反混淆
preferences.deobfuscation_map_file_mode=映射文件句柄模式 preferences.generated_renames_mapping_file_mode=映射文件句柄模式
preferences.deobfuscation_min_len=最小命名长度 preferences.deobfuscation_min_len=最小命名长度
preferences.deobfuscation_max_len=最大命名长度 preferences.deobfuscation_max_len=最大命名长度
preferences.deobfuscation_source_alias=使用资源名作为类的别名 preferences.deobfuscation_source_alias=使用资源名作为类的别名
...@@ -209,6 +212,8 @@ msg.language_changed_title=语言已更改 ...@@ -209,6 +212,8 @@ msg.language_changed_title=语言已更改
msg.language_changed=新的语言将在下次应用程序启动时显示。 msg.language_changed=新的语言将在下次应用程序启动时显示。
msg.project_error_title=错误 msg.project_error_title=错误
msg.project_error=项目无法加载 msg.project_error=项目无法加载
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。 msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
msg.cant_add_comment=无法在此添加注释 msg.cant_add_comment=无法在此添加注释
......
...@@ -31,7 +31,10 @@ file.save_project_as=另存專案... ...@@ -31,7 +31,10 @@ file.save_project_as=另存專案...
file.reload=重新載入檔案 file.reload=重新載入檔案
file.live_reload=實時重新載入 file.live_reload=實時重新載入
file.live_reload_desc=更動後自動重新載入檔案 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_all=全部儲存
#file.save=Save #file.save=Save
file.export_gradle=另存為 gradle 專案 file.export_gradle=另存為 gradle 專案
...@@ -50,7 +53,7 @@ tree.resources_title=資源 ...@@ -50,7 +53,7 @@ tree.resources_title=資源
tree.loading=載入中... tree.loading=載入中...
progress.load=載入中 progress.load=載入中
progress.export_mappings=正在匯出對應 progress.save_mappings=正在匯出對應
progress.decompile=正在反編譯 progress.decompile=正在反編譯
progress.canceling=正在取消 progress.canceling=正在取消
...@@ -182,7 +185,7 @@ preferences.start_jobs=自動開始背景反編譯 ...@@ -182,7 +185,7 @@ preferences.start_jobs=自動開始背景反編譯
preferences.select_font=變更 preferences.select_font=變更
preferences.select_smali_font=變更 preferences.select_smali_font=變更
preferences.deobfuscation_on=啟用去模糊化 preferences.deobfuscation_on=啟用去模糊化
preferences.deobfuscation_map_file_mode=Map 檔案處理模式 preferences.generated_renames_mapping_file_mode=Map 檔案處理模式
preferences.deobfuscation_min_len=最小名稱長度 preferences.deobfuscation_min_len=最小名稱長度
preferences.deobfuscation_max_len=最大名稱長度 preferences.deobfuscation_max_len=最大名稱長度
preferences.deobfuscation_source_alias=將原始檔案名稱作為類別別名 preferences.deobfuscation_source_alias=將原始檔案名稱作為類別別名
...@@ -209,6 +212,8 @@ msg.language_changed_title=已更改語言 ...@@ -209,6 +212,8 @@ msg.language_changed_title=已更改語言
msg.language_changed=新語言將於下次應用程式啟動時套用。 msg.language_changed=新語言將於下次應用程式啟動時套用。
msg.project_error_title=錯誤 msg.project_error_title=錯誤
msg.project_error=無法載入專案 msg.project_error=無法載入專案
#msg.mapping_namespace_count_error_title=
#msg.mapping_namespace_count_error=
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。 msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
msg.cant_add_comment=無法在此新增註解 msg.cant_add_comment=無法在此新增註解
......
...@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory; ...@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.api.impl.NoOpCodeCache; import jadx.api.impl.NoOpCodeCache;
import jadx.core.dex.nodes.ClassNode; 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.gui.utils.codecache.disk.DiskCodeCache;
import jadx.tests.api.IntegrationTest; import jadx.tests.api.IntegrationTest;
...@@ -29,7 +32,10 @@ class DiskCodeCacheTest extends IntegrationTest { ...@@ -29,7 +32,10 @@ class DiskCodeCacheTest extends IntegrationTest {
ClassNode clsNode = getClassNode(DiskCodeCacheTest.class); ClassNode clsNode = getClassNode(DiskCodeCacheTest.class);
ICodeInfo codeInfo = clsNode.getCode(); 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(); String clsKey = clsNode.getFullName();
cache.add(clsKey, codeInfo); cache.add(clsKey, codeInfo);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册