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

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

* Add support for importing method arg mappings

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

* Add GUI for importing mappings

* Also show save file dialog when exporting mappings

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

* Include imported renames when exporting mappings

* Add "close mappings" menu entry

* Don't instantiate MappingTree unless actually needed

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

* Save location of open mapping file into project data

* Correctly reset cache when loading new mappings

* Remove unused import

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

* Fix if statement

* Correctly handle absence of mappings path in project data

* Show overwrite warning for folders only if not empty

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

* Handle wrong mappings namespace count error

* Replace unneeded public with private

* Add option for saving open mappings directly to disk

* Correctly propagate and throw exceptions during decompiler init

* Respect opened mappings' existing namespaces; fix related crash

* Deduplicate code, add `DalvikToJavaBytecodeUtils` class

* Small cleanup; move more functionality to utility class

* Support for importing class, field and method mappings

* Handle mappings in RenameDialog

* Fix checkstyle

* Fix wrong naming order

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

That commit got rid of redundant embedded libraries

* Add null checks

* Check if mapping tree is null before running MappingsVisitor

* Use working mapping-io build

* Handle cache invalidation directly in DiskCodeCache class

* Don't reset UserRenamesMappingsMode if project is just reloaded

* Fix checkstyle

Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
Julian Burner
2022-08-10 16:37:55 +02:00
committed by Skylot
parent cb1f3e9843
commit cb91c8c41c
37 changed files with 1156 additions and 198 deletions
+42 -15
View File
@@ -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;
}
@@ -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);
}