* Add new CLI args for mapping files and deprecate args regarding jobf files (will be moved to the cache dir in the future) * Add support for importing method arg mappings Also change `mapping-file` to `mappings-path`, since folders are supported, too * Add GUI for importing mappings * Also show save file dialog when exporting mappings * Fix crash on startup when `--mappings-path` parameter is set * Include imported renames when exporting mappings * Add "close mappings" menu entry * Don't instantiate MappingTree unless actually needed * Terminology: `import` → `open`; `export` → `save` * Save location of open mapping file into project data * Correctly reset cache when loading new mappings * Remove unused import * Save opened mappings' last modified date to reset cache when changed * Fix if statement * Correctly handle absence of mappings path in project data * Show overwrite warning for folders only if not empty * Prevent crash when imported mappings don't have any namespaces * Handle wrong mappings namespace count error * Replace unneeded public with private * Add option for saving open mappings directly to disk * Correctly propagate and throw exceptions during decompiler init * Respect opened mappings' existing namespaces; fix related crash * Deduplicate code, add `DalvikToJavaBytecodeUtils` class * Small cleanup; move more functionality to utility class * Support for importing class, field and method mappings * Handle mappings in RenameDialog * Fix checkstyle * Fix wrong naming order * Use modified mapping-io JAR from https://github.com/skylot/jadx/commit/18070eb7a649db0b0daef38d456316d5b4650072 That commit got rid of redundant embedded libraries * Add null checks * Check if mapping tree is null before running MappingsVisitor * Use working mapping-io build * Handle cache invalidation directly in DiskCodeCache class * Don't reset UserRenamesMappingsMode if project is just reloaded * Fix checkstyle Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@@ -15,8 +16,9 @@ import java.util.function.Predicate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.deobf.IAliasProvider;
|
||||
import jadx.api.deobf.IRenameCondition;
|
||||
@@ -72,12 +74,15 @@ public class JadxArgs {
|
||||
*/
|
||||
private boolean includeDependencies = false;
|
||||
|
||||
private Path userRenamesMappingsPath = null;
|
||||
private UserRenamesMappingsMode userRenamesMappingsMode = UserRenamesMappingsMode.getDefault();
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
private File deobfuscationMapFile = null;
|
||||
|
||||
private DeobfuscationMapFileMode deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||
private File generatedRenamesMappingFile = null;
|
||||
private GeneratedRenamesMappingFileMode generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
private ResourceNameSource resourceNameSource = ResourceNameSource.AUTO;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
@@ -326,6 +331,22 @@ public class JadxArgs {
|
||||
this.classFilter = classFilter;
|
||||
}
|
||||
|
||||
public Path getUserRenamesMappingsPath() {
|
||||
return userRenamesMappingsPath;
|
||||
}
|
||||
|
||||
public void setUserRenamesMappingsPath(Path path) {
|
||||
this.userRenamesMappingsPath = path;
|
||||
}
|
||||
|
||||
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
||||
return userRenamesMappingsMode;
|
||||
}
|
||||
|
||||
public void setUserRenamesMappingsMode(UserRenamesMappingsMode mode) {
|
||||
this.userRenamesMappingsMode = mode;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
@@ -336,22 +357,24 @@ public class JadxArgs {
|
||||
|
||||
@Deprecated
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationMapFileMode == DeobfuscationMapFileMode.OVERWRITE;
|
||||
return generatedRenamesMappingFileMode == GeneratedRenamesMappingFileMode.OVERWRITE;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
if (deobfuscationForceSave) {
|
||||
this.deobfuscationMapFileMode = DeobfuscationMapFileMode.OVERWRITE;
|
||||
this.generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.OVERWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
public DeobfuscationMapFileMode getDeobfuscationMapFileMode() {
|
||||
return deobfuscationMapFileMode;
|
||||
@Deprecated
|
||||
public GeneratedRenamesMappingFileMode getGeneratedRenamesMappingFileMode() {
|
||||
return generatedRenamesMappingFileMode;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMapFileMode(DeobfuscationMapFileMode deobfuscationMapFileMode) {
|
||||
this.deobfuscationMapFileMode = deobfuscationMapFileMode;
|
||||
@Deprecated
|
||||
public void setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode mode) {
|
||||
this.generatedRenamesMappingFileMode = mode;
|
||||
}
|
||||
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
@@ -386,12 +409,14 @@ public class JadxArgs {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public File getDeobfuscationMapFile() {
|
||||
return deobfuscationMapFile;
|
||||
@Deprecated
|
||||
public File getGeneratedRenamesMappingFile() {
|
||||
return generatedRenamesMappingFile;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMapFile(File deobfuscationMapFile) {
|
||||
this.deobfuscationMapFile = deobfuscationMapFile;
|
||||
@Deprecated
|
||||
public void setGeneratedRenamesMappingFile(File file) {
|
||||
this.generatedRenamesMappingFile = file;
|
||||
}
|
||||
|
||||
public ResourceNameSource getResourceNameSource() {
|
||||
@@ -611,9 +636,11 @@ public class JadxArgs {
|
||||
+ ", skipResources=" + skipResources
|
||||
+ ", skipSources=" + skipSources
|
||||
+ ", includeDependencies=" + includeDependencies
|
||||
+ ", userRenamesMappingsPath=" + userRenamesMappingsPath
|
||||
+ ", userRenamesMappingsMode=" + userRenamesMappingsMode
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationMapFile=" + deobfuscationMapFile
|
||||
+ ", deobfuscationMapFileMode=" + deobfuscationMapFileMode
|
||||
+ ", generatedRenamesMappingFile=" + generatedRenamesMappingFile
|
||||
+ ", generatedRenamesMappingFileMode=" + generatedRenamesMappingFileMode
|
||||
+ ", resourceNameSource=" + resourceNameSource
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
|
||||
@@ -673,6 +673,10 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
|
||||
root.notifyCodeDataListeners();
|
||||
}
|
||||
|
||||
public void reloadMappings() {
|
||||
root.notifyMappingsListeners();
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
package jadx.api.args;
|
||||
|
||||
public enum DeobfuscationMapFileMode {
|
||||
@Deprecated
|
||||
public enum GeneratedRenamesMappingFileMode {
|
||||
|
||||
/**
|
||||
* Load if found, don't save (default)
|
||||
@@ -22,6 +23,10 @@ public enum DeobfuscationMapFileMode {
|
||||
*/
|
||||
IGNORE;
|
||||
|
||||
public static GeneratedRenamesMappingFileMode getDefault() {
|
||||
return READ;
|
||||
}
|
||||
|
||||
public boolean shouldRead() {
|
||||
return this == READ || this == READ_OR_SAVE;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package jadx.api.args;
|
||||
|
||||
public enum UserRenamesMappingsMode {
|
||||
|
||||
/**
|
||||
* Just read, user can save manually (default)
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* Read and autosave after every change
|
||||
*/
|
||||
READ_AND_AUTOSAVE_EVERY_CHANGE,
|
||||
|
||||
/**
|
||||
* Read and autosave before exiting the app or closing the project
|
||||
*/
|
||||
READ_AND_AUTOSAVE_BEFORE_CLOSING,
|
||||
|
||||
/**
|
||||
* Don't load and don't save
|
||||
*/
|
||||
IGNORE;
|
||||
|
||||
public static UserRenamesMappingsMode getDefault() {
|
||||
return READ;
|
||||
}
|
||||
|
||||
public boolean shouldRead() {
|
||||
return this != IGNORE;
|
||||
}
|
||||
|
||||
public boolean shouldWrite() {
|
||||
return this == READ_AND_AUTOSAVE_EVERY_CHANGE || this == READ_AND_AUTOSAVE_BEFORE_CLOSING;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
public interface IMappingsUpdateListener {
|
||||
|
||||
void updated(MemoryMappingTree mappingTree);
|
||||
}
|
||||
@@ -10,13 +10,18 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.core.nodes.IMethodNode;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IDebugInfo;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
|
||||
import jadx.api.utils.CodeUtils;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
@@ -249,6 +254,36 @@ public class MethodNode extends NotificationAttrNode implements IMethodNode,
|
||||
return mthInfo.getReturnType().equals(ArgType.VOID);
|
||||
}
|
||||
|
||||
public List<VarNode> collectArgsWithoutLoading() {
|
||||
ICodeInfo codeInfo = getTopParentClass().getCode();
|
||||
int mthDefPos = getDefPosition();
|
||||
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
|
||||
int argsCount = mthInfo.getArgsCount();
|
||||
List<VarNode> args = new ArrayList<>(argsCount);
|
||||
codeInfo.getCodeMetadata().searchDown(mthDefPos, (pos, ann) -> {
|
||||
if (pos > lineEndPos) {
|
||||
// Stop at line end
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (ann instanceof NodeDeclareRef) {
|
||||
ICodeNodeRef declRef = ((NodeDeclareRef) ann).getNode();
|
||||
if (declRef instanceof VarNode) {
|
||||
VarNode varNode = (VarNode) declRef;
|
||||
if (!varNode.getMth().equals(this)) {
|
||||
// Stop if we've gone too far and have entered a different method
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
args.add(varNode);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (args.size() != argsCount) {
|
||||
LOG.warn("Incorrect args count, expected: {}, got: {}", argsCount, args.size());
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public List<RegisterArg> getArgRegs() {
|
||||
if (argsList == null) {
|
||||
throw new JadxRuntimeException("Method arg registers not loaded: " + this
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -13,6 +14,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.mappingio.MappingReader;
|
||||
import net.fabricmc.mappingio.MappingUtil;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
@@ -20,6 +25,7 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.api.core.nodes.IRootNode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.passes.DecompilePassWrapper;
|
||||
@@ -66,11 +72,13 @@ public class RootNode implements IRootNode {
|
||||
private final JadxArgs args;
|
||||
private final List<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;
|
||||
@@ -211,6 +219,26 @@ public class RootNode implements IRootNode {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to parse '.arsc' file", e);
|
||||
}
|
||||
if (args.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE
|
||||
&& args.getUserRenamesMappingsPath() != null) {
|
||||
try {
|
||||
mappingTree = new MemoryMappingTree();
|
||||
MappingReader.read(args.getUserRenamesMappingsPath(), mappingTree);
|
||||
if (mappingTree.getSrcNamespace() == null) {
|
||||
mappingTree.setSrcNamespace(MappingUtil.NS_SOURCE_FALLBACK);
|
||||
}
|
||||
if (mappingTree.getDstNamespaces() == null || mappingTree.getDstNamespaces().isEmpty()) {
|
||||
mappingTree.setDstNamespaces(Arrays.asList(MappingUtil.NS_TARGET_FALLBACK));
|
||||
} else if (mappingTree.getDstNamespaces().size() > 1) {
|
||||
throw new JadxRuntimeException(
|
||||
String.format("JADX only supports mappings with just one destination namespace! The provided ones have %s.",
|
||||
mappingTree.getDstNamespaces().size()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mappingTree = null;
|
||||
throw new JadxRuntimeException("Failed to load mappings", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateManifestAttribMap(IResParser parser) {
|
||||
@@ -565,11 +593,19 @@ public class RootNode implements IRootNode {
|
||||
this.codeDataUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void registerMappingsUpdateListener(IMappingsUpdateListener listener) {
|
||||
this.mappingsUpdateListeners.add(listener);
|
||||
}
|
||||
|
||||
public void notifyCodeDataListeners() {
|
||||
ICodeData codeData = args.getCodeData();
|
||||
codeDataUpdateListeners.forEach(l -> l.updated(codeData));
|
||||
}
|
||||
|
||||
public void notifyMappingsListeners() {
|
||||
mappingsUpdateListeners.forEach(l -> l.updated(mappingTree));
|
||||
}
|
||||
|
||||
public ClspGraph getClsp() {
|
||||
return clsp;
|
||||
}
|
||||
@@ -596,6 +632,14 @@ public class RootNode implements IRootNode {
|
||||
return constValues;
|
||||
}
|
||||
|
||||
public MemoryMappingTree getMappingTree() {
|
||||
return mappingTree;
|
||||
}
|
||||
|
||||
public void setMappingTree(MemoryMappingTree mappingTree) {
|
||||
this.mappingTree = mappingTree;
|
||||
}
|
||||
|
||||
public InfoStorage getInfoStorage() {
|
||||
return infoStorage;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
|
||||
import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping;
|
||||
import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.mappings.DalvikToJavaBytecodeUtils;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ApplyCodeMappings",
|
||||
desc = "Apply mappings to method args and vars",
|
||||
runAfter = {
|
||||
InitCodeVariables.class,
|
||||
DebugInfoApplyVisitor.class
|
||||
}
|
||||
)
|
||||
public class CodeMappingsVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeMappingsVisitor.class);
|
||||
|
||||
private Map<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package jadx.core.dex.visitors.rename;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
|
||||
import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
|
||||
import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "MappingsVisitor",
|
||||
desc = "Apply mappings to classes, fields and methods",
|
||||
runAfter = {
|
||||
RenameVisitor.class
|
||||
}
|
||||
)
|
||||
public class MappingsVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MappingsVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
List<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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package jadx.core.utils.mappings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class DalvikToJavaBytecodeUtils {
|
||||
|
||||
// ****************************
|
||||
// Local variable index
|
||||
// ****************************
|
||||
|
||||
// Method args
|
||||
|
||||
public static Integer getMethodArgLvIndex(VarNode methodArg) {
|
||||
MethodNode mth = methodArg.getMth();
|
||||
Integer lvIndex = getMethodArgLvIndexViaSsaVars(methodArg.getReg(), mth);
|
||||
if (lvIndex != null) {
|
||||
return lvIndex;
|
||||
}
|
||||
List<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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,7 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.args.DeobfuscationMapFileMode;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -139,7 +139,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
args.setFsCaseSensitive(false); // use same value on all systems
|
||||
args.setCommentsLevel(CommentsLevel.DEBUG);
|
||||
args.setDeobfuscationOn(false);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||
args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -570,7 +570,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void enableDeobfuscation() {
|
||||
args.setDeobfuscationOn(true);
|
||||
args.setDeobfuscationMapFileMode(DeobfuscationMapFileMode.IGNORE);
|
||||
args.setGeneratedRenamesMappingFileMode(GeneratedRenamesMappingFileMode.IGNORE);
|
||||
args.setDeobfuscationMinLength(2);
|
||||
args.setDeobfuscationMaxLength(64);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user