fix: improve plugins data handling

This commit is contained in:
Skylot
2023-04-01 21:06:05 +01:00
parent a992c93198
commit 7a309ca367
35 changed files with 786 additions and 562 deletions
@@ -1,6 +1,5 @@
package jadx.plugins.input.dex;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -21,11 +20,10 @@ public class DexInputOptions extends BaseOptionsParser {
public List<OptionDescription> getOptionsDescriptions() {
return Collections.singletonList(
new JadxOptionDescription(
JadxOptionDescription.booleanOption(
VERIFY_CHECKSUM_OPT,
"verify dex file checksum before load",
"yes",
Arrays.asList("yes", "no")));
true));
}
public boolean isVerifyChecksum() {
@@ -7,6 +7,7 @@ import java.util.Locale;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.impl.BaseOptionsParser;
import jadx.api.plugins.options.impl.JadxOptionDescription;
import jadx.core.utils.files.FileUtils;
public class JavaConvertOptions extends BaseOptionsParser {
@@ -34,11 +35,10 @@ public class JavaConvertOptions extends BaseOptionsParser {
"convert mode",
"both",
Arrays.asList("dx", "d8", "both")),
new JadxOptionDescription(
JadxOptionDescription.booleanOption(
D8_DESUGAR_OPT,
"use desugar in d8",
"no",
Arrays.asList("yes", "no")));
false));
}
public Mode getMode() {
@@ -48,4 +48,8 @@ public class JavaConvertOptions extends BaseOptionsParser {
public boolean isD8Desugar() {
return d8Desugar;
}
public String getOptionsHash() {
return FileUtils.md5Sum(mode + ":" + d8Desugar);
}
}
@@ -32,6 +32,7 @@ public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
public void init(JadxPluginContext context) {
context.registerOptions(options);
context.addCodeInput(this);
context.registerInputsHashSupplier(options::getOptionsHash);
}
@Override
@@ -0,0 +1,74 @@
package jadx.plugins.mappings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.mappingio.format.MappingFormat;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionDescription.OptionFlag;
import jadx.api.plugins.options.impl.BaseOptionsParser;
import jadx.api.plugins.options.impl.JadxOptionDescription;
import static jadx.plugins.mappings.RenameMappingsPlugin.PLUGIN_ID;
public class RenameMappingsOptions extends BaseOptionsParser {
public static final String INVERT_OPT = PLUGIN_ID + ".invert";
public static final String FORMAT_OPT = PLUGIN_ID + ".format";
private boolean invert = false;
/**
* null value - used for 'auto' option
*/
private @Nullable MappingFormat format = null;
@Override
public void parseOptions() {
format = getOption(FORMAT_OPT, RenameMappingsOptions::parseMappingFormat, null);
invert = getBooleanOption(INVERT_OPT, false);
}
@Override
public List<OptionDescription> getOptionsDescriptions() {
return Arrays.asList(
new JadxOptionDescription(FORMAT_OPT, "mapping format", "auto", getMappingFormats())
.withFlag(OptionFlag.PER_PROJECT),
JadxOptionDescription.booleanOption(INVERT_OPT, "invert mapping", false)
.withFlag(OptionFlag.PER_PROJECT));
}
private static MappingFormat parseMappingFormat(String name) {
String upName = name.toUpperCase(Locale.ROOT);
if (upName.equals("AUTO")) {
return null;
}
return MappingFormat.valueOf(upName);
}
private static List<String> getMappingFormats() {
List<String> list = new ArrayList<>();
list.add("auto");
for (MappingFormat value : MappingFormat.values()) {
list.add(value.name());
}
return list;
}
public MappingFormat getFormat() {
return format;
}
public boolean isInvert() {
return invert;
}
public String getOptionsHashString() {
return format + ":" + invert;
}
}
@@ -1,58 +1,58 @@
package jadx.plugins.mappings;
import java.util.Collections;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import java.nio.file.Files;
import java.nio.file.Path;
import jadx.api.JadxArgs;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
import jadx.api.plugins.JadxPluginInfo;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.plugins.mappings.load.CodeMappingsVisitor;
import jadx.plugins.mappings.load.MappingsVisitor;
import jadx.core.utils.files.FileUtils;
import jadx.plugins.mappings.load.ApplyMappingsPass;
import jadx.plugins.mappings.load.CodeMappingsPass;
import jadx.plugins.mappings.load.LoadMappingsPass;
public class RenameMappingsPlugin implements JadxPlugin {
public static final String PLUGIN_ID = "rename-mappings";
private final RenameMappingsOptions options = new RenameMappingsOptions();
@Override
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo("jadx-rename-mappings", "Rename Mappings", "various mappings support");
return new JadxPluginInfo(PLUGIN_ID, "Rename Mappings", "various mappings support");
}
@Override
public void init(JadxPluginContext context) {
MappingTree mappingTree = openMapping(context.getArgs());
if (mappingTree != null) {
context.addPass(new MappingsVisitor(mappingTree));
context.addPass(new CodeMappingsVisitor(mappingTree));
context.registerOptions(options);
JadxArgs args = context.getArgs();
if (args.getUserRenamesMappingsMode() == UserRenamesMappingsMode.IGNORE) {
return;
}
Path mappingsPath = args.getUserRenamesMappingsPath();
if (mappingsPath == null || !Files.isReadable(mappingsPath)) {
return;
}
LoadMappingsPass loadPass = new LoadMappingsPass(options);
context.addPass(loadPass);
context.addPass(new ApplyMappingsPass(loadPass));
context.addPass(new CodeMappingsPass(loadPass));
// use mapping file time modification to check for changes
context.registerInputsHashSupplier(() -> FileUtils.md5Sum(getInputsHashString(mappingsPath)));
}
public MappingTree openMapping(JadxArgs args) {
if (args.getUserRenamesMappingsMode() != UserRenamesMappingsMode.IGNORE
&& args.getUserRenamesMappingsPath() != null) {
try {
MemoryMappingTree 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(Collections.singletonList(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()));
}
return mappingTree;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load mappings", e);
}
private String getInputsHashString(Path mappingsPath) {
return getFileHashString(mappingsPath) + ':' + options.getOptionsHashString();
}
private static String getFileHashString(Path mappingsPath) {
try {
return mappingsPath.toAbsolutePath().normalize()
+ ":" + Files.getLastModifiedTime(mappingsPath).toMillis();
} catch (Exception e) {
return "";
}
return null;
}
}
@@ -16,31 +16,36 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
public class MappingsVisitor implements JadxPreparePass {
public class ApplyMappingsPass implements JadxPreparePass {
private final MappingTree mappingTree;
private final LoadMappingsPass loadPass;
public MappingsVisitor(MappingTree mappingTree) {
this.mappingTree = mappingTree;
public ApplyMappingsPass(LoadMappingsPass loadPass) {
this.loadPass = loadPass;
}
@Override
public JadxPassInfo getInfo() {
return new OrderedJadxPassInfo(
"MappingVisitor",
"ApplyMappings",
"Apply mappings to classes, fields and methods")
.after("LoadMappings")
.before("RenameVisitor");
}
@Override
public void init(RootNode root) {
process(root);
root.registerCodeDataUpdateListener(codeData -> process(root));
MappingTree mappingTree = loadPass.getMappings();
if (mappingTree == null) {
return;
}
process(root, mappingTree);
root.registerCodeDataUpdateListener(codeData -> process(root, mappingTree));
}
private void process(RootNode root) {
private void process(RootNode root, MappingTree mappingTree) {
for (ClassNode cls : root.getClasses()) {
String clsRawName = cls.getClassInfo().makeRawFullName().replace('.', '/');
String clsRawName = cls.getClassInfo().getRawName().replace('.', '/');
ClassMapping mapping = mappingTree.getClass(clsRawName);
if (mapping != null) {
processClass(cls, mapping);
@@ -95,6 +100,6 @@ public class MappingsVisitor implements JadxPreparePass {
if (comment != null) {
method.addCodeComment(comment);
}
// Method args & vars are handled in CodeMappingsVisitor
// Method args & vars are handled in CodeMappingsPass
}
}
@@ -18,26 +18,30 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.plugins.mappings.utils.DalvikToJavaBytecodeUtils;
public class CodeMappingsVisitor implements JadxDecompilePass {
private final MappingTree mappingTree;
public class CodeMappingsPass implements JadxDecompilePass {
private final LoadMappingsPass loadPass;
private Map<String, ClassMapping> clsRenamesMap;
public CodeMappingsVisitor(MappingTree mappingTree) {
this.mappingTree = mappingTree;
public CodeMappingsPass(LoadMappingsPass loadPass) {
this.loadPass = loadPass;
}
@Override
public JadxPassInfo getInfo() {
return new OrderedJadxPassInfo(
"ApplyCodeMappings",
"CodeMappings",
"Apply mappings to method args and vars")
.before("CodeRenameVisitor");
}
@Override
public void init(RootNode root) {
updateMappingsMap();
root.registerCodeDataUpdateListener(codeData -> updateMappingsMap());
MappingTree mappingTree = loadPass.getMappings();
if (mappingTree == null) {
return;
}
updateMappingsMap(mappingTree);
root.registerCodeDataUpdateListener(codeData -> updateMappingsMap(mappingTree));
}
@Override
@@ -89,9 +93,9 @@ public class CodeMappingsVisitor implements JadxDecompilePass {
return clsRenamesMap.get(classPath);
}
private void updateMappingsMap() {
private void updateMappingsMap(MappingTree mappings) {
clsRenamesMap = new HashMap<>();
for (ClassMapping cls : mappingTree.getClasses()) {
for (ClassMapping cls : mappings.getClasses()) {
for (MethodMapping mth : cls.getMethods()) {
if (!mth.getArgs().isEmpty() || !mth.getVars().isEmpty()) {
clsRenamesMap.put(cls.getSrcName(), cls);
@@ -0,0 +1,71 @@
package jadx.plugins.mappings.load;
import java.nio.file.Path;
import java.util.Collections;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import jadx.api.JadxArgs;
import jadx.api.plugins.pass.JadxPassInfo;
import jadx.api.plugins.pass.impl.SimpleJadxPassInfo;
import jadx.api.plugins.pass.types.JadxPreparePass;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.plugins.mappings.RenameMappingsOptions;
public class LoadMappingsPass implements JadxPreparePass {
private final RenameMappingsOptions options;
private MappingTree mappings;
public LoadMappingsPass(RenameMappingsOptions options) {
this.options = options;
}
@Override
public JadxPassInfo getInfo() {
return new SimpleJadxPassInfo("LoadMappings", "Load mappings file");
}
@Override
public void init(RootNode root) {
mappings = loadMapping(root.getArgs());
}
public @Nullable MappingTree getMappings() {
return mappings;
}
private MappingTree loadMapping(JadxArgs args) {
try {
Path mappingsPath = args.getUserRenamesMappingsPath();
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(mappingsPath, options.getFormat(), mappingTree);
if (mappingTree.getSrcNamespace() == null) {
mappingTree.setSrcNamespace(MappingUtil.NS_SOURCE_FALLBACK);
}
if (mappingTree.getDstNamespaces() == null || mappingTree.getDstNamespaces().isEmpty()) {
mappingTree.setDstNamespaces(Collections.singletonList(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()));
}
if (options.isInvert()) {
MemoryMappingTree invertedMappingTree = new MemoryMappingTree();
String dstNamespace = mappingTree.getDstNamespaces().get(0);
mappingTree.accept(new MappingSourceNsSwitch(invertedMappingTree, dstNamespace));
return invertedMappingTree;
}
return mappingTree;
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load mappings", e);
}
}
}