mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
* 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 18070eb7a6
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:
parent
cb1f3e9843
commit
cb91c8c41c
@ -1,5 +1,6 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
@ -22,8 +23,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;
|
||||
|
||||
@ -106,6 +108,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;
|
||||
|
||||
@ -115,22 +133,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;
|
||||
@ -273,9 +293,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);
|
||||
@ -380,6 +404,14 @@ public class JadxCLIArgs {
|
||||
return extractFinally;
|
||||
}
|
||||
|
||||
public Path getUserRenamesMappingsPath() {
|
||||
return userRenamesMappingsPath;
|
||||
}
|
||||
|
||||
public UserRenamesMappingsMode getUserRenamesMappingsMode() {
|
||||
return userRenamesMappingsMode;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
@ -392,12 +424,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() {
|
||||
@ -509,9 +543,9 @@ public class JadxCLIArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<DeobfuscationMapFileMode> {
|
||||
public static class DeobfuscationMapFileModeConverter extends BaseEnumConverter<GeneratedRenamesMappingFileMode> {
|
||||
public DeobfuscationMapFileModeConverter() {
|
||||
super(DeobfuscationMapFileMode::valueOf, DeobfuscationMapFileMode::values);
|
||||
super(GeneratedRenamesMappingFileMode::valueOf, 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.10.1'
|
||||
|
||||
// TODO: move resources decoding to separate plugin module
|
||||
|
Binary file not shown.
@ -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);
|
||||
}
|
||||
|
@ -35,11 +35,6 @@ dependencies {
|
||||
implementation 'com.android.tools.build:apksig:7.4.1'
|
||||
implementation 'io.github.skylot:jdwp:2.0.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;
|
||||
@ -337,6 +338,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;
|
||||
}
|
||||
@ -349,8 +358,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) {
|
||||
@ -678,7 +687,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);
|
||||
@ -750,7 +759,7 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 15) {
|
||||
deobfuscationMapFileMode = DeobfuscationMapFileMode.READ;
|
||||
generatedRenamesMappingFileMode = GeneratedRenamesMappingFileMode.getDefault();
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 16) {
|
||||
|
@ -59,7 +59,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;
|
||||
@ -262,12 +262,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();
|
||||
}
|
||||
});
|
||||
@ -277,7 +279,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;
|
||||
|
@ -24,6 +24,7 @@ import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
@ -38,7 +39,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;
|
||||
@ -80,16 +83,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;
|
||||
@ -180,10 +188,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;
|
||||
@ -332,8 +346,7 @@ public class MainWindow extends JFrame {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
closeAll();
|
||||
exportMappingsMenu.setEnabled(false);
|
||||
closeAll(false);
|
||||
updateProject(new JadxProject(this));
|
||||
}
|
||||
|
||||
@ -377,31 +390,126 @@ public class MainWindow extends JFrame {
|
||||
update();
|
||||
}
|
||||
|
||||
private void exportMappings(MappingFormat mappingFormat) {
|
||||
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.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) {
|
||||
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.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) {
|
||||
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.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() {
|
||||
FileDialogWrapper fileDialog = new FileDialogWrapper(this, FileOpenMode.CUSTOM_SAVE);
|
||||
fileDialog.setTitle(NLS.str("file.save"));
|
||||
@ -446,14 +554,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) {
|
||||
@ -479,8 +587,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) {
|
||||
@ -495,17 +603,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"));
|
||||
@ -517,7 +669,6 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
checkLoadedStatus();
|
||||
onOpen();
|
||||
exportMappingsMenu.setEnabled(true);
|
||||
onFinish.run();
|
||||
});
|
||||
}
|
||||
@ -527,16 +678,22 @@ public class MainWindow extends JFrame {
|
||||
BreakpointManager.saveAndExit();
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
private void closeAll(boolean reopening) {
|
||||
notifyLoadListeners(false);
|
||||
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() {
|
||||
@ -561,6 +718,7 @@ 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());
|
||||
@ -597,6 +755,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"),
|
||||
@ -619,7 +781,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;
|
||||
@ -632,6 +799,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();
|
||||
}
|
||||
@ -896,35 +1073,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");
|
||||
|
||||
exportMappingsMenu = new JMenu(NLS.str("file.export_mappings_as"));
|
||||
exportMappingsMenu.add(exportMappingsAsTiny2);
|
||||
exportMappingsMenu.add(exportMappingsAsEnigma);
|
||||
exportMappingsMenu.add(exportMappingsAsEnigmaDir);
|
||||
exportMappingsMenu.setEnabled(false);
|
||||
openMappingsMenu = new JMenu(NLS.str("file.open_mappings"));
|
||||
openMappingsMenu.add(openTiny2Mappings);
|
||||
openMappingsMenu.add(openEnigmaMappings);
|
||||
openMappingsMenu.add(openEnigmaDirMappings);
|
||||
|
||||
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
|
||||
@ -1120,7 +1342,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);
|
||||
@ -1498,7 +1723,7 @@ public class MainWindow extends JFrame {
|
||||
saveSplittersInfo();
|
||||
}
|
||||
heapUsageBar.reset();
|
||||
closeAll();
|
||||
closeAll(false);
|
||||
|
||||
FileUtils.deleteTempRootDir();
|
||||
dispose();
|
||||
|
@ -1,7 +1,6 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@ -12,14 +11,9 @@ 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;
|
||||
@ -94,7 +88,8 @@ public final class FridaAction extends JNodeAction {
|
||||
} else {
|
||||
overload = "";
|
||||
}
|
||||
List<String> argNames = collectMethodArgNames(jMth.getJavaMethod());
|
||||
List<String> argNames = mth.collectArgsWithoutLoading().stream()
|
||||
.map(VarNode::getName).collect(Collectors.toList());
|
||||
String args = String.join(", ", argNames);
|
||||
String logArgs;
|
||||
if (argNames.isEmpty()) {
|
||||
@ -102,7 +97,7 @@ public final class FridaAction extends JNodeAction {
|
||||
} else {
|
||||
logArgs = ": " + argNames.stream().map(arg -> arg + "=${" + arg + "}").collect(Collectors.joining(", "));
|
||||
}
|
||||
String shortClassName = mth.getParentClass().getShortName();
|
||||
String shortClassName = mth.getParentClass().getAlias();
|
||||
String classSnippet = generateClassSnippet(jMth.getJParent());
|
||||
if (methodInfo.isConstructor() || methodInfo.getReturnType() == ArgType.VOID) {
|
||||
// no return value
|
||||
@ -121,33 +116,6 @@ public final class FridaAction extends JNodeAction {
|
||||
+ "};";
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
int argsCount = javaMethod.getMethodNode().getMethodInfo().getArgsCount();
|
||||
if (argNames.size() != argsCount) {
|
||||
LOG.warn("Incorrect args count, expected: {}, got: {}", argsCount, argNames.size());
|
||||
}
|
||||
return argNames;
|
||||
}
|
||||
|
||||
private String generateClassSnippet(JClass jc) {
|
||||
JavaClass javaClass = jc.getCls();
|
||||
String rawClassName = StringEscapeUtils.escapeEcmaScript(javaClass.getRawName());
|
||||
|
@ -31,15 +31,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) {
|
||||
|
@ -30,12 +30,15 @@ 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.ClassNode;
|
||||
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;
|
||||
@ -59,13 +62,14 @@ public class DiskCodeCache implements ICodeCache {
|
||||
private final Map<String, Integer> namesMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> allClsIds;
|
||||
|
||||
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);
|
||||
allClsIds = buildClassIdsMap(root.getClasses());
|
||||
@ -89,7 +93,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());
|
||||
@ -194,11 +198,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,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
|
||||
@ -51,7 +54,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
|
||||
|
||||
@ -188,7 +191,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
|
||||
@ -216,6 +219,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
|
||||
|
||||
|
@ -32,7 +32,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
|
||||
@ -51,7 +54,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
|
||||
|
||||
@ -188,7 +191,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
|
||||
@ -216,6 +219,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
|
||||
|
||||
|
@ -32,7 +32,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
|
||||
@ -51,7 +54,7 @@ tree.resources_title=Recursos
|
||||
tree.loading=Cargando...
|
||||
|
||||
progress.load=Cargando
|
||||
#progress.export_mappings=
|
||||
#progress.save_mappings=
|
||||
progress.decompile=Decompiling
|
||||
#progress.canceling=Canceling
|
||||
|
||||
@ -188,7 +191,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
|
||||
@ -216,6 +219,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
|
||||
|
||||
|
@ -32,7 +32,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 프로젝트로 저장
|
||||
@ -51,7 +54,7 @@ tree.resources_title=리소스
|
||||
tree.loading=로딩중...
|
||||
|
||||
progress.load=로딩중
|
||||
progress.export_mappings=매핑 내보내는 중
|
||||
progress.save_mappings=매핑 내보내는 중
|
||||
progress.decompile=디컴파일 중
|
||||
progress.canceling=취소 중
|
||||
|
||||
@ -188,7 +191,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=소스 파일 이름을 클래스 이름 별칭으로 사용
|
||||
@ -216,6 +219,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=여기에 주석을 추가할수 없음
|
||||
|
||||
|
@ -32,7 +32,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
|
||||
@ -51,7 +54,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
|
||||
|
||||
@ -188,7 +191,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
|
||||
@ -216,6 +219,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
|
||||
|
||||
|
@ -32,7 +32,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 项目
|
||||
@ -51,7 +54,7 @@ tree.resources_title=资源文件
|
||||
tree.loading=加载中…
|
||||
|
||||
progress.load=正在加载
|
||||
progress.export_mappings=导出映射
|
||||
progress.save_mappings=导出映射
|
||||
progress.decompile=反编译中
|
||||
progress.canceling=正在取消
|
||||
|
||||
@ -188,7 +191,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=使用资源名作为类的别名
|
||||
@ -216,6 +219,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=无法在此添加注释
|
||||
|
||||
|
@ -32,7 +32,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 專案
|
||||
@ -51,7 +54,7 @@ tree.resources_title=資源
|
||||
tree.loading=載入中...
|
||||
|
||||
progress.load=載入中
|
||||
progress.export_mappings=正在匯出對應
|
||||
progress.save_mappings=正在匯出對應
|
||||
progress.decompile=正在反編譯
|
||||
progress.canceling=正在取消
|
||||
|
||||
@ -188,7 +191,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=將原始檔案名稱作為類別別名
|
||||
@ -216,6 +219,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);
|
||||
|
Loading…
Reference in New Issue
Block a user