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