mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 04:39:46 +00:00
feat: add base scripting support
This commit is contained in:
parent
fdf170529f
commit
e5e64365fc
@ -90,7 +90,7 @@ dependencyUpdates {
|
||||
resolutionStrategy {
|
||||
componentSelection { rules ->
|
||||
rules.all { ComponentSelection selection ->
|
||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
boolean rejected = ['alpha', 'beta', 'dev', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||
}
|
||||
if (rejected) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
org.gradle.warning.mode=all
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# Flags for google-java-format (optimize imports by spotless) for Java >= 16.
|
||||
# Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
|
||||
|
@ -9,6 +9,7 @@ dependencies {
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-script:jadx-script-plugin'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.82'
|
||||
implementation 'ch.qos.logback:logback-classic:1.3.5'
|
||||
|
@ -24,6 +24,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.core.nodes.IJadxDecompiler;
|
||||
import jadx.api.impl.plugins.SimplePluginContext;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
@ -31,9 +33,13 @@ import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.types.JadxAfterLoadPass;
|
||||
import jadx.api.plugins.pass.types.JadxPassType;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@ -78,7 +84,7 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler implements Closeable {
|
||||
public final class JadxDecompiler implements IJadxDecompiler, Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private final JadxArgs args;
|
||||
@ -95,6 +101,8 @@ public final class JadxDecompiler implements Closeable {
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
|
||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
|
||||
private @Nullable JadxGuiContext guiContext;
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
@ -112,11 +120,14 @@ public final class JadxDecompiler implements Closeable {
|
||||
loadInputFiles();
|
||||
|
||||
root = new RootNode(args);
|
||||
root.setDecompilerRef(this);
|
||||
root.mergePasses(customPasses);
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
loadFinished();
|
||||
}
|
||||
|
||||
private void loadInputFiles() {
|
||||
@ -136,14 +147,6 @@ public final class JadxDecompiler implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
public void addCustomLoad(ILoadResult customLoad) {
|
||||
customLoads.add(customLoad);
|
||||
}
|
||||
|
||||
public List<ILoadResult> getCustomLoads() {
|
||||
return customLoads;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
root = null;
|
||||
classes = null;
|
||||
@ -177,6 +180,11 @@ public final class JadxDecompiler implements Closeable {
|
||||
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
||||
p -> p.getPluginInfo().getPluginId()));
|
||||
}
|
||||
applyPluginOptions(args);
|
||||
initPlugins();
|
||||
}
|
||||
|
||||
private void applyPluginOptions(JadxArgs args) {
|
||||
Map<String, String> pluginOptions = args.getPluginOptions();
|
||||
if (!pluginOptions.isEmpty()) {
|
||||
LOG.debug("Applying plugin options: {}", pluginOptions);
|
||||
@ -191,6 +199,36 @@ public final class JadxDecompiler implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void initPlugins() {
|
||||
customPasses.clear();
|
||||
|
||||
List<JadxPlugin> plugins = pluginManager.getResolvedPlugins();
|
||||
SimplePluginContext context = new SimplePluginContext(this);
|
||||
context.setGuiContext(guiContext);
|
||||
for (JadxPlugin passPlugin : plugins) {
|
||||
try {
|
||||
passPlugin.init(context);
|
||||
} catch (Exception e) {
|
||||
String pluginId = passPlugin.getPluginInfo().getPluginId();
|
||||
throw new JadxRuntimeException("Failed to pass plugin: " + pluginId, e);
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> passes = customPasses.values().stream().flatMap(Collection::stream)
|
||||
.map(p -> p.getInfo().getName()).collect(Collectors.toList());
|
||||
LOG.debug("Loaded custom passes: {} {}", passes.size(), passes);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFinished() {
|
||||
List<JadxPass> list = customPasses.get(JadxAfterLoadPass.TYPE);
|
||||
if (list != null) {
|
||||
for (JadxPass pass : list) {
|
||||
((JadxAfterLoadPass) pass).init(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
@ -644,6 +682,22 @@ public final class JadxDecompiler implements Closeable {
|
||||
return decompileScheduler;
|
||||
}
|
||||
|
||||
public void addCustomLoad(ILoadResult customLoad) {
|
||||
customLoads.add(customLoad);
|
||||
}
|
||||
|
||||
public List<ILoadResult> getCustomLoads() {
|
||||
return customLoads;
|
||||
}
|
||||
|
||||
public void addCustomPass(JadxPass pass) {
|
||||
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
||||
}
|
||||
|
||||
public void setJadxGuiContext(JadxGuiContext guiContext) {
|
||||
this.guiContext = guiContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
|
@ -0,0 +1,54 @@
|
||||
package jadx.api.impl.passes;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass;
|
||||
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.utils.exceptions.JadxException;
|
||||
|
||||
public class DecompilePassWrapper extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecompilePassWrapper.class);
|
||||
|
||||
private final JadxDecompilePass decompilePass;
|
||||
|
||||
public DecompilePassWrapper(JadxDecompilePass decompilePass) {
|
||||
this.decompilePass = decompilePass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
try {
|
||||
decompilePass.init(root);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error in decompile pass init: {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
try {
|
||||
return decompilePass.visit(cls);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error in decompile pass init: {}", this, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
try {
|
||||
decompilePass.visit(mth);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error in decompile pass: {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return decompilePass.getInfo().getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package jadx.api.impl.passes;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class PreparePassWrapper extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PreparePassWrapper.class);
|
||||
|
||||
private final JadxPreparePass preparePass;
|
||||
|
||||
public PreparePassWrapper(JadxPreparePass preparePass) {
|
||||
this.preparePass = preparePass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
try {
|
||||
preparePass.init(root);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error in prepare pass init: {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return preparePass.getInfo().getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package jadx.api.impl.plugins;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
|
||||
public class SimplePassContext implements JadxPassContext {
|
||||
|
||||
private final JadxDecompiler jadxDecompiler;
|
||||
|
||||
public SimplePassContext(JadxDecompiler jadxDecompiler) {
|
||||
this.jadxDecompiler = jadxDecompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPass(JadxPass pass) {
|
||||
jadxDecompiler.addCustomPass(pass);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package jadx.api.impl.plugins;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
|
||||
public class SimplePluginContext implements JadxPluginContext {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final JadxPassContext passContext;
|
||||
private @Nullable JadxGuiContext guiContext;
|
||||
|
||||
public SimplePluginContext(JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
this.passContext = new SimplePassContext(decompiler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxPassContext getPassContext() {
|
||||
return passContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JadxGuiContext getGuiContext() {
|
||||
return guiContext;
|
||||
}
|
||||
|
||||
public void setGuiContext(JadxGuiContext guiContext) {
|
||||
this.guiContext = guiContext;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.core.nodes.IClassNode;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
@ -53,7 +54,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
|
||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||
public class ClassNode extends NotificationAttrNode implements IClassNode, ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||
private final RootNode root;
|
||||
private final IClassData clsData;
|
||||
|
||||
|
@ -11,13 +11,16 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.core.nodes.IMethodNode;
|
||||
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.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
@ -36,7 +39,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.Utils.lockList;
|
||||
|
||||
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
|
||||
public class MethodNode extends NotificationAttrNode implements IMethodNode,
|
||||
IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@ -572,8 +576,19 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
noCode = true;
|
||||
}
|
||||
|
||||
public void rename(String newName) {
|
||||
MethodOverrideAttr overrideAttr = get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||
relatedMth.getMethodInfo().setAlias(newName);
|
||||
}
|
||||
} else {
|
||||
mthInfo.setAlias(newName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate instructions count at currect stage
|
||||
* Calculate instructions count at current stage
|
||||
*/
|
||||
public long countInsns() {
|
||||
if (instructions != null) {
|
||||
|
@ -15,12 +15,20 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.core.nodes.IRootNode;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.passes.DecompilePassWrapper;
|
||||
import jadx.api.impl.passes.PreparePassWrapper;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass;
|
||||
import jadx.api.plugins.pass.types.JadxPassType;
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
@ -38,6 +46,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompare;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.CacheStorage;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.PassMerge;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
@ -49,7 +58,7 @@ import jadx.core.xmlgen.ResourceStorage;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
public class RootNode {
|
||||
public class RootNode implements IRootNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
|
||||
|
||||
private final JadxArgs args;
|
||||
@ -76,10 +85,15 @@ public class RootNode {
|
||||
private ClassNode appResClass;
|
||||
private boolean isProto;
|
||||
|
||||
/**
|
||||
* Optional decompiler reference
|
||||
*/
|
||||
private @Nullable JadxDecompiler decompiler;
|
||||
|
||||
public RootNode(JadxArgs args) {
|
||||
this.args = args;
|
||||
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
|
||||
this.processClasses = new ProcessClass(this.getArgs());
|
||||
this.processClasses = new ProcessClass(args);
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
@ -273,6 +287,15 @@ public class RootNode {
|
||||
classes.forEach(ClassNode::updateParentClass);
|
||||
}
|
||||
|
||||
public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
|
||||
PassMerge.run(preDecompilePasses,
|
||||
customPasses.get(JadxPreparePass.TYPE),
|
||||
p -> new PreparePassWrapper((JadxPreparePass) p));
|
||||
PassMerge.run(processClasses.getPasses(),
|
||||
customPasses.get(JadxDecompilePass.TYPE),
|
||||
p -> new DecompilePassWrapper((JadxDecompilePass) p));
|
||||
}
|
||||
|
||||
public void runPreDecompileStage() {
|
||||
boolean debugEnabled = LOG.isDebugEnabled();
|
||||
for (IDexTreeVisitor pass : preDecompilePasses) {
|
||||
@ -290,7 +313,7 @@ public class RootNode {
|
||||
DepthTraversal.visit(pass, cls);
|
||||
}
|
||||
if (debugEnabled) {
|
||||
LOG.debug("{} time: {}ms", pass.getClass().getSimpleName(), System.currentTimeMillis() - start);
|
||||
LOG.debug("Prepare pass: '{}' - {}ms", pass, System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -537,6 +560,14 @@ public class RootNode {
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setDecompilerRef(JadxDecompiler jadxDecompiler) {
|
||||
this.decompiler = jadxDecompiler;
|
||||
}
|
||||
|
||||
public @Nullable JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
public TypeUpdate getTypeUpdate() {
|
||||
return typeUpdate;
|
||||
}
|
||||
|
@ -460,4 +460,9 @@ public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OverrideMethodVisitor";
|
||||
}
|
||||
}
|
||||
|
@ -360,4 +360,9 @@ public class ProcessAnonymous extends AbstractVisitor {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProcessAnonymous";
|
||||
}
|
||||
}
|
||||
|
@ -71,4 +71,9 @@ public class ProcessMethodsForInline extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProcessMethodsForInline";
|
||||
}
|
||||
}
|
||||
|
@ -276,4 +276,9 @@ public class SignatureProcessor extends AbstractVisitor {
|
||||
}
|
||||
return validateInnerType(innerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SignatureProcessor";
|
||||
}
|
||||
}
|
||||
|
@ -266,4 +266,9 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
}
|
||||
return pkg.substring(0, dotPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RenameVisitor";
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,6 @@ import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.IJavaCodeRef;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@ -72,28 +70,13 @@ public class UserRenames {
|
||||
} else {
|
||||
IJavaCodeRef codeRef = rename.getCodeRef();
|
||||
if (codeRef == null) {
|
||||
applyMethodRename(mth, rename);
|
||||
mth.rename(rename.getNewName());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyMethodRename(MethodNode mth, ICodeRename rename) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr != null) {
|
||||
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
|
||||
renameMethod(relatedMth, rename);
|
||||
}
|
||||
} else {
|
||||
renameMethod(mth, rename);
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameMethod(MethodNode mth, ICodeRename rename) {
|
||||
mth.getMethodInfo().setAlias(rename.getNewName());
|
||||
}
|
||||
|
||||
// TODO: Very inefficient!!! Add PackageInfo class to build package hierarchy
|
||||
private static void applyPkgRenames(RootNode root, List<ICodeRename> renames) {
|
||||
List<ClassNode> classes = root.getClasses(false);
|
||||
|
@ -145,4 +145,9 @@ public class UsageInfoVisitor extends AbstractVisitor {
|
||||
mergeIntoMth.setUseIn(mergedUsage);
|
||||
sourceMth.setUseIn(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UsageInfoVisitor";
|
||||
}
|
||||
}
|
||||
|
82
jadx-core/src/main/java/jadx/core/utils/PassMerge.java
Normal file
82
jadx-core/src/main/java/jadx/core/utils/PassMerge.java
Normal file
@ -0,0 +1,82 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.JadxPassInfo;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class PassMerge {
|
||||
|
||||
public static void run(List<IDexTreeVisitor> passes, List<JadxPass> customPasses, Function<JadxPass, IDexTreeVisitor> wrap) {
|
||||
if (Utils.isEmpty(customPasses)) {
|
||||
return;
|
||||
}
|
||||
for (JadxPass customPass : customPasses) {
|
||||
IDexTreeVisitor pass = wrap.apply(customPass);
|
||||
int pos = searchInsertPos(passes, customPass.getInfo());
|
||||
if (pos == -1) {
|
||||
passes.add(pass);
|
||||
} else {
|
||||
passes.add(pos, pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int searchInsertPos(List<IDexTreeVisitor> passes, JadxPassInfo info) {
|
||||
List<String> runAfter = info.runAfter();
|
||||
List<String> runBefore = info.runBefore();
|
||||
if (runAfter.isEmpty() && runBefore.isEmpty()) {
|
||||
return -1; // last
|
||||
}
|
||||
if (ListUtils.isSingleElement(runAfter, "start")) {
|
||||
return 0;
|
||||
}
|
||||
if (ListUtils.isSingleElement(runBefore, "end")) {
|
||||
return -1;
|
||||
}
|
||||
Map<String, Integer> namesMap = buildNamesMap(passes);
|
||||
int after = 0;
|
||||
for (String name : runAfter) {
|
||||
Integer pos = namesMap.get(name);
|
||||
if (pos != null) {
|
||||
after = Math.max(after, pos);
|
||||
}
|
||||
}
|
||||
int before = Integer.MAX_VALUE;
|
||||
for (String name : runBefore) {
|
||||
Integer pos = namesMap.get(name);
|
||||
if (pos != null) {
|
||||
before = Math.min(before, pos);
|
||||
}
|
||||
}
|
||||
if (before <= after) {
|
||||
throw new JadxRuntimeException("Conflict pass order requirements: " + info.getName()
|
||||
+ "\n run after: " + runAfter
|
||||
+ "\n run before: " + runBefore
|
||||
+ "\n passes: " + ListUtils.map(passes, PassMerge::getPassName));
|
||||
}
|
||||
if (after == 0) {
|
||||
return before;
|
||||
}
|
||||
int pos = after + 1;
|
||||
return pos >= passes.size() ? -1 : pos;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> buildNamesMap(List<IDexTreeVisitor> passes) {
|
||||
int size = passes.size();
|
||||
Map<String, Integer> namesMap = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
namesMap.put(getPassName(passes.get(i)), i);
|
||||
}
|
||||
return namesMap;
|
||||
}
|
||||
|
||||
private static String getPassName(IDexTreeVisitor pass) {
|
||||
return pass.getClass().getSimpleName();
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package jadx.api.core.nodes;
|
||||
|
||||
public interface IClassNode {
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package jadx.api.core.nodes;
|
||||
|
||||
public interface IJadxDecompiler {
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package jadx.api.core.nodes;
|
||||
|
||||
public interface IMethodNode {
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package jadx.api.core.nodes;
|
||||
|
||||
public interface IRootNode {
|
||||
}
|
@ -2,4 +2,8 @@ package jadx.api.plugins;
|
||||
|
||||
public interface JadxPlugin {
|
||||
JadxPluginInfo getPluginInfo();
|
||||
|
||||
default void init(JadxPluginContext context) {
|
||||
// default to no-op
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package jadx.api.plugins;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.core.nodes.IJadxDecompiler;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
|
||||
public interface JadxPluginContext {
|
||||
|
||||
IJadxDecompiler getDecompiler();
|
||||
|
||||
JadxPassContext getPassContext();
|
||||
|
||||
@Nullable
|
||||
JadxGuiContext getGuiContext();
|
||||
}
|
@ -42,7 +42,6 @@ public class JadxPluginManager {
|
||||
ServiceLoader<JadxPlugin> jadxPlugins = ServiceLoader.load(JadxPlugin.class);
|
||||
for (JadxPlugin plugin : jadxPlugins) {
|
||||
addPlugin(plugin);
|
||||
LOG.debug("Loading plugin: {}", plugin.getPluginInfo().getPluginId());
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
@ -56,6 +55,7 @@ public class JadxPluginManager {
|
||||
|
||||
private PluginData addPlugin(JadxPlugin plugin) {
|
||||
PluginData pluginData = new PluginData(plugin, plugin.getPluginInfo());
|
||||
LOG.debug("Loading plugin: {}", pluginData.getPluginId());
|
||||
if (!allPlugins.add(pluginData)) {
|
||||
throw new IllegalArgumentException("Duplicate plugin id: " + pluginData + ", class " + plugin.getClass());
|
||||
}
|
||||
@ -112,16 +112,18 @@ public class JadxPluginManager {
|
||||
}
|
||||
|
||||
public List<JadxInputPlugin> getInputPlugins() {
|
||||
return resolvedPlugins.stream()
|
||||
.filter(JadxInputPlugin.class::isInstance)
|
||||
.map(JadxInputPlugin.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
return getPluginsWithType(JadxInputPlugin.class);
|
||||
}
|
||||
|
||||
public List<JadxPluginOptions> getPluginsWithOptions() {
|
||||
return getPluginsWithType(JadxPluginOptions.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends JadxPlugin> List<T> getPluginsWithType(Class<T> type) {
|
||||
return resolvedPlugins.stream()
|
||||
.filter(JadxPluginOptions.class::isInstance)
|
||||
.map(JadxPluginOptions.class::cast)
|
||||
.filter(p -> type.isAssignableFrom(p.getClass()))
|
||||
.map(p -> (T) p)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
package jadx.api.plugins.gui;
|
||||
|
||||
public interface JadxGuiContext {
|
||||
|
||||
/**
|
||||
* Run code in UI Thread
|
||||
*/
|
||||
void uiRun(Runnable runnable);
|
||||
|
||||
void addMenuAction(String name, Runnable action);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package jadx.api.plugins.pass;
|
||||
|
||||
import jadx.api.plugins.pass.types.JadxPassType;
|
||||
|
||||
public interface JadxPass {
|
||||
JadxPassInfo getInfo();
|
||||
|
||||
JadxPassType getPassType();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package jadx.api.plugins.pass;
|
||||
|
||||
public interface JadxPassContext {
|
||||
|
||||
void addPass(JadxPass pass);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package jadx.api.plugins.pass;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface JadxPassInfo {
|
||||
|
||||
String getName();
|
||||
|
||||
String getDescription();
|
||||
|
||||
List<String> runAfter();
|
||||
|
||||
List<String> runBefore();
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package jadx.api.plugins.pass.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.pass.JadxPassInfo;
|
||||
|
||||
public class OrderedJadxPassInfo implements JadxPassInfo {
|
||||
|
||||
private final String name;
|
||||
private final String desc;
|
||||
private final List<String> runAfter;
|
||||
private final List<String> runBefore;
|
||||
|
||||
public OrderedJadxPassInfo(String name) {
|
||||
this(name, name);
|
||||
}
|
||||
|
||||
public OrderedJadxPassInfo(String name, String desc) {
|
||||
this(name, desc, new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
public OrderedJadxPassInfo(String name, String desc, List<String> runAfter, List<String> runBefore) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.runAfter = runAfter;
|
||||
this.runBefore = runBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> runAfter() {
|
||||
return runAfter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> runBefore() {
|
||||
return runBefore;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package jadx.api.plugins.pass.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.pass.JadxPassInfo;
|
||||
|
||||
public class SimpleJadxPassInfo implements JadxPassInfo {
|
||||
|
||||
private final String name;
|
||||
private final String desc;
|
||||
|
||||
public SimpleJadxPassInfo(String name) {
|
||||
this(name, name);
|
||||
}
|
||||
|
||||
public SimpleJadxPassInfo(String name, String desc) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> runAfter() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> runBefore() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package jadx.api.plugins.pass.types;
|
||||
|
||||
import jadx.api.core.nodes.IJadxDecompiler;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
|
||||
public interface JadxAfterLoadPass extends JadxPass {
|
||||
JadxPassType TYPE = new JadxPassType(JadxAfterLoadPass.class);
|
||||
|
||||
void init(IJadxDecompiler decompiler);
|
||||
|
||||
@Override
|
||||
default JadxPassType getPassType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package jadx.api.plugins.pass.types;
|
||||
|
||||
import jadx.api.core.nodes.IClassNode;
|
||||
import jadx.api.core.nodes.IMethodNode;
|
||||
import jadx.api.core.nodes.IRootNode;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
|
||||
public interface JadxDecompilePass extends JadxPass {
|
||||
JadxPassType TYPE = new JadxPassType(JadxDecompilePass.class);
|
||||
|
||||
void init(IRootNode root);
|
||||
|
||||
/**
|
||||
* Visit class
|
||||
*
|
||||
* @return false for disable child methods and inner classes traversal
|
||||
*/
|
||||
boolean visit(IClassNode cls);
|
||||
|
||||
/**
|
||||
* Visit method
|
||||
*/
|
||||
void visit(IMethodNode mth);
|
||||
|
||||
@Override
|
||||
default JadxPassType getPassType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package jadx.api.plugins.pass.types;
|
||||
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
|
||||
public class JadxPassType {
|
||||
private final String cls;
|
||||
|
||||
public JadxPassType(Class<? extends JadxPass> cls) {
|
||||
this.cls = cls.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxPassType)) {
|
||||
return false;
|
||||
}
|
||||
return cls.equals(((JadxPassType) o).cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cls.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxPassType{" + cls + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package jadx.api.plugins.pass.types;
|
||||
|
||||
import jadx.api.core.nodes.IRootNode;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
|
||||
public interface JadxPreparePass extends JadxPass {
|
||||
JadxPassType TYPE = new JadxPassType(JadxPreparePass.class);
|
||||
|
||||
void init(IRootNode root);
|
||||
|
||||
@Override
|
||||
default JadxPassType getPassType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
18
jadx-plugins/jadx-script/examples/build.gradle.kts
Normal file
18
jadx-plugins/jadx-script/examples/build.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.20"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
|
||||
implementation("org.jetbrains.kotlin:kotlin-script-runtime")
|
||||
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs("scripts", "context")
|
||||
}
|
||||
}
|
28
jadx-plugins/jadx-script/examples/context/stubs.kt
Normal file
28
jadx-plugins/jadx-script/examples/context/stubs.kt
Normal file
@ -0,0 +1,28 @@
|
||||
@file:Suppress("MayBeConstant", "unused")
|
||||
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
import mu.KotlinLogging
|
||||
|
||||
/**
|
||||
* Stubs for JadxScriptBaseClass script super class
|
||||
*/
|
||||
|
||||
val log = KotlinLogging.logger("JadxScript")
|
||||
val scriptName = "script"
|
||||
|
||||
fun getJadxInstance(): JadxScriptInstance {
|
||||
throw IllegalStateException("Stub method!")
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotations for maven imports
|
||||
*/
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Repeatable
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class DependsOn(vararg val artifactsCoordinates: String, val options: Array<String> = [])
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Repeatable
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Repository(vararg val repositoriesCoordinates: String, val options: Array<String> = [])
|
21
jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts
Normal file
21
jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts
Normal file
@ -0,0 +1,21 @@
|
||||
// custom deobfuscator example
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
jadx.args.isDeobfuscationOn = false
|
||||
jadx.args.renameFlags = emptySet()
|
||||
|
||||
val regex = """[Oo0]+""".toRegex()
|
||||
var n = 0
|
||||
jadx.rename.all { name, node ->
|
||||
when {
|
||||
name matches regex -> {
|
||||
val newName = "${node.typeName()}${n++}"
|
||||
println("renaming ${node.typeName()} '$node' to '$newName'")
|
||||
newName
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
jadx.afterLoad {
|
||||
println("Renames count: $n")
|
||||
}
|
9
jadx-plugins/jadx-script/examples/scripts/gui.jadx.kts
Normal file
9
jadx-plugins/jadx-script/examples/scripts/gui.jadx.kts
Normal file
@ -0,0 +1,9 @@
|
||||
// customize jadx-gui
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.gui.ifAvailable {
|
||||
addMenuAction("Decompile All") {
|
||||
jadx.decompile.all()
|
||||
}
|
||||
}
|
29
jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts
Normal file
29
jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts
Normal file
@ -0,0 +1,29 @@
|
||||
// logger is preferred for output
|
||||
log.info { "Hello from jadx script!" }
|
||||
|
||||
// println will also work (will be redirected to logger)
|
||||
println("println from script '$scriptName'")
|
||||
|
||||
// get jadx decompiler script instance
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
// adjust options if needed
|
||||
jadx.args.isDeobfuscationOn = false
|
||||
|
||||
// change names
|
||||
jadx.rename.all { name ->
|
||||
when (name) {
|
||||
"HelloWorld" -> "HelloJadx"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// run some code after loading is finished
|
||||
jadx.afterLoad {
|
||||
println("Loaded classes: ${jadx.classes.size}")
|
||||
// print first class code
|
||||
jadx.classes.firstOrNull()?.let { cls ->
|
||||
println("Class: '${cls.name}'")
|
||||
println(cls.code)
|
||||
}
|
||||
}
|
19
jadx-plugins/jadx-script/examples/scripts/replace.jadx.kts
Normal file
19
jadx-plugins/jadx-script/examples/scripts/replace.jadx.kts
Normal file
@ -0,0 +1,19 @@
|
||||
// instructions modification example
|
||||
|
||||
import jadx.core.dex.instructions.ConstStringNode
|
||||
import jadx.core.dex.instructions.InvokeNode
|
||||
import jadx.core.dex.instructions.args.InsnArg
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.replace.insns { mth, insn ->
|
||||
if (insn is InvokeNode) {
|
||||
if (insn.callMth.shortId == "println(Ljava/lang/String;)V") {
|
||||
val arg = insn.getArg(1)
|
||||
val newArg = InsnArg.wrapInsnIntoArg(ConstStringNode("Jadx!"))
|
||||
insn.setArg(1, newArg)
|
||||
log.info { "Replace '$arg' with '$newArg' in $mth" }
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
64
jadx-plugins/jadx-script/examples/scripts/stages.jadx.kts
Normal file
64
jadx-plugins/jadx-script/examples/scripts/stages.jadx.kts
Normal file
@ -0,0 +1,64 @@
|
||||
// insert processing passes for different decompilation stages
|
||||
|
||||
import jadx.core.dex.instructions.InsnType
|
||||
import jadx.core.dex.nodes.IRegion
|
||||
import java.lang.Integer.max
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
// print raw instructions
|
||||
jadx.stages.rawInsns { mth, insns ->
|
||||
log.info { "Instructions for method: $mth" }
|
||||
for ((offset, insn) in insns.withIndex()) {
|
||||
insn?.let {
|
||||
log.info { " 0x${offset.hex()}: $insn" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// access method basic blocks
|
||||
jadx.stages.mthBlocks { mth, blocks ->
|
||||
// count invoke instructions
|
||||
var invCount = 0
|
||||
for (block in blocks) {
|
||||
for (insn in block.instructions) {
|
||||
if (insn.type == InsnType.INVOKE) {
|
||||
invCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info { "Invokes count in method $mth = $invCount" }
|
||||
}
|
||||
|
||||
// access method regions
|
||||
jadx.stages.mthRegions { mth, region ->
|
||||
// recursively count max depth of nested regions
|
||||
fun countRegionsDepth(region: IRegion): Int {
|
||||
val subBlocks = region.subBlocks
|
||||
if (subBlocks.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
var depth = 1
|
||||
for (block in subBlocks) {
|
||||
if (block is IRegion) {
|
||||
depth = max(depth, 1 + countRegionsDepth(block))
|
||||
}
|
||||
}
|
||||
return depth
|
||||
}
|
||||
|
||||
val depth = countRegionsDepth(region)
|
||||
log.info { "Max region depth in method $mth = $depth" }
|
||||
if (depth > 5) {
|
||||
jadx.debug.printMethodRegions(mth, printInsns = true)
|
||||
}
|
||||
}
|
||||
|
||||
jadx.afterLoad {
|
||||
/*
|
||||
Start full decompilation (optional):
|
||||
1. jadx-cli start decompilation automatically
|
||||
2. jadx-gui start decompilation only on class open or search, so you might need to force it
|
||||
*/
|
||||
// jadx.decompile.all()
|
||||
}
|
15
jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts
Normal file
15
jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts
Normal file
@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id("jadx-library")
|
||||
|
||||
kotlin("jvm") version "1.7.20"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
|
||||
|
||||
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
|
||||
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package jadx.plugins.script
|
||||
|
||||
import jadx.api.plugins.JadxPlugin
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.JadxPluginInfo
|
||||
import jadx.api.plugins.gui.JadxGuiContext
|
||||
import jadx.api.plugins.pass.JadxPassContext
|
||||
import jadx.plugins.script.passes.JadxScriptAfterLoadPass
|
||||
import jadx.plugins.script.runner.ScriptEval
|
||||
|
||||
class JadxScriptPlugin : JadxPlugin {
|
||||
|
||||
override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
|
||||
|
||||
override fun init(init: JadxPluginContext) {
|
||||
val scriptStates = ScriptEval().process(init) ?: return
|
||||
init.passContext.addPass(JadxScriptAfterLoadPass(scriptStates))
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package jadx.plugins.script.passes
|
||||
|
||||
import jadx.api.core.nodes.IJadxDecompiler
|
||||
import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxAfterLoadPass
|
||||
import jadx.plugins.script.runner.ScriptStates
|
||||
import mu.KotlinLogging
|
||||
|
||||
private val LOG = KotlinLogging.logger {}
|
||||
|
||||
class JadxScriptAfterLoadPass(private val scriptStates: ScriptStates) : JadxAfterLoadPass {
|
||||
|
||||
override fun getInfo() = SimpleJadxPassInfo("JadxScriptAfterLoad", "Execute scripts 'afterLoad' block")
|
||||
|
||||
override fun init(decompiler: IJadxDecompiler) {
|
||||
for (script in scriptStates.getScripts()) {
|
||||
if (script.error) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
for (b in script.scriptData.afterLoad) {
|
||||
b.invoke()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
script.error = true
|
||||
LOG.error(e) { "Error executing 'afterLoad' block in script: ${script.scriptFile.name}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package jadx.plugins.script.runner
|
||||
|
||||
import jadx.api.JadxDecompiler
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.pass.JadxPassContext
|
||||
import jadx.plugins.script.runtime.JadxScript
|
||||
import jadx.plugins.script.runtime.JadxScriptData
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
|
||||
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
|
||||
|
||||
private val LOG = KotlinLogging.logger {}
|
||||
|
||||
class ScriptEval {
|
||||
|
||||
fun process(init: JadxPluginContext): ScriptStates? {
|
||||
val jadx = init.decompiler as JadxDecompiler
|
||||
val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
|
||||
if (scripts.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val scriptStates = ScriptStates()
|
||||
for (scriptFile in scripts) {
|
||||
val scriptData = JadxScriptData(jadx, init, scriptFile)
|
||||
load(scriptFile, scriptData)
|
||||
scriptStates.add(scriptFile, scriptData)
|
||||
}
|
||||
return scriptStates
|
||||
}
|
||||
|
||||
private fun load(scriptFile: File, scriptData: JadxScriptData) {
|
||||
LOG.debug { "Loading script: ${scriptFile.absolutePath}" }
|
||||
val result = eval(scriptFile, scriptData)
|
||||
processEvalResult(result, scriptFile)
|
||||
}
|
||||
|
||||
private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics<EvaluationResult> {
|
||||
val compilationConf = createJvmCompilationConfigurationFromTemplate<JadxScript>()
|
||||
val evalConf = createJvmEvaluationConfigurationFromTemplate<JadxScript> {
|
||||
constructorArgs(scriptData)
|
||||
}
|
||||
return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConf, evalConf)
|
||||
}
|
||||
|
||||
private fun processEvalResult(res: ResultWithDiagnostics<EvaluationResult>, scriptFile: File) {
|
||||
when (res) {
|
||||
is ResultWithDiagnostics.Success -> {
|
||||
val result = res.value.returnValue
|
||||
if (result is ResultValue.Error) {
|
||||
result.error.printStackTrace()
|
||||
}
|
||||
}
|
||||
is ResultWithDiagnostics.Failure -> {
|
||||
LOG.error { "Script execution failed: ${scriptFile.name}" }
|
||||
res.reports
|
||||
.filter { it.severity >= ScriptDiagnostic.Severity.ERROR }
|
||||
.forEach { r ->
|
||||
LOG.error(r.exception) { r.render(withSeverity = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package jadx.plugins.script.runner
|
||||
|
||||
import jadx.plugins.script.runtime.JadxScriptData
|
||||
import java.io.File
|
||||
|
||||
data class ScriptStateData(
|
||||
val scriptFile: File,
|
||||
val scriptData: JadxScriptData,
|
||||
var error: Boolean = false
|
||||
)
|
||||
|
||||
class ScriptStates {
|
||||
|
||||
private val data: MutableList<ScriptStateData> = ArrayList()
|
||||
|
||||
fun add(scriptFile: File, scriptData: JadxScriptData) {
|
||||
data.add(ScriptStateData(scriptFile, scriptData))
|
||||
}
|
||||
|
||||
fun getScripts() = data
|
||||
}
|
@ -0,0 +1 @@
|
||||
jadx.plugins.script.JadxScriptPlugin
|
@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("jadx-library")
|
||||
|
||||
kotlin("jvm") version "1.7.20"
|
||||
}
|
||||
|
||||
group = "jadx-script-context"
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
|
||||
|
||||
// allow to use maven dependencies in scripts
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies")
|
||||
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
|
||||
|
||||
api(project(":jadx-plugins:jadx-plugins-api"))
|
||||
api(project(":jadx-core")) // TODO: workaround
|
||||
|
||||
runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-smali-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-java-convert"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-java-input"))
|
||||
runtimeOnly(project(":jadx-plugins:jadx-raung-input"))
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.dex.visitors.DotGraphVisitor
|
||||
import jadx.core.utils.DebugUtils
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
import java.io.File
|
||||
|
||||
class Debug(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun printMethodRegions(mth: IMethodNode, printInsns: Boolean = false) {
|
||||
DebugUtils.printRegions(mth as MethodNode, printInsns)
|
||||
}
|
||||
|
||||
fun saveCFG(mth: IMethodNode, file: File = File("dump-mth-raw")) {
|
||||
DotGraphVisitor.dumpRaw().save(file, mth as MethodNode)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.JadxArgs
|
||||
import jadx.api.JavaClass
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class Decompile(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun all() {
|
||||
jadx.classes.forEach(JavaClass::decompile)
|
||||
}
|
||||
|
||||
fun allThreaded(threadsCount: Int = JadxArgs.DEFAULT_THREADS_COUNT) {
|
||||
val executor = Executors.newFixedThreadPool(threadsCount)
|
||||
val dec = jadx.internalDecompiler
|
||||
val batches = dec.decompileScheduler.buildBatches(jadx.classes)
|
||||
for (batch in batches) {
|
||||
executor.submit {
|
||||
batch.forEach(JavaClass::decompile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.plugins.gui.JadxGuiContext
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Gui(
|
||||
private val jadx: JadxScriptInstance,
|
||||
private val guiContext: JadxGuiContext?
|
||||
) {
|
||||
|
||||
fun isAvailable() = guiContext != null
|
||||
|
||||
fun ifAvailable(block: Gui.() -> Unit) {
|
||||
guiContext?.let { this.apply(block) }
|
||||
}
|
||||
|
||||
fun ui(block: () -> Unit) {
|
||||
guiContext?.uiRun(block)
|
||||
}
|
||||
|
||||
fun addMenuAction(name: String, action: () -> Unit) {
|
||||
guiContext?.addMenuAction(name, action)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IRootNode
|
||||
import jadx.core.dex.nodes.IDexNode
|
||||
import jadx.core.dex.nodes.RootNode
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class RenamePass(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun all(makeNewName: (String) -> String?) {
|
||||
all { name, _ -> makeNewName.invoke(name) }
|
||||
}
|
||||
|
||||
fun all(makeNewName: (String, IDexNode) -> String?) {
|
||||
jadx.addPass(object : ScriptPreparePass(jadx, "RenameAll") {
|
||||
override fun init(root: IRootNode) {
|
||||
val rootNode = root as RootNode
|
||||
for (cls in rootNode.classes) {
|
||||
makeNewName.invoke(cls.classInfo.shortName, cls)?.let {
|
||||
cls.classInfo.changeShortName(it)
|
||||
}
|
||||
for (mth in cls.methods) {
|
||||
if (mth.isConstructor) {
|
||||
continue
|
||||
}
|
||||
makeNewName.invoke(mth.name, mth)?.let {
|
||||
mth.rename(it)
|
||||
}
|
||||
}
|
||||
for (fld in cls.fields) {
|
||||
makeNewName.invoke(fld.name, fld)?.let {
|
||||
fld.fieldInfo.alias = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.core.dex.instructions.args.InsnArg
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg
|
||||
import jadx.core.dex.nodes.InsnNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.utils.InsnRemover
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Replace(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun insns(replace: (MethodNode, InsnNode) -> InsnNode?) {
|
||||
jadx.stages.mthBlocks { mth, blocks ->
|
||||
for (block in blocks) {
|
||||
val insns = block.instructions
|
||||
for ((i, insn) in insns.withIndex()) {
|
||||
replaceSubInsns(mth, insn, replace)
|
||||
replace.invoke(mth, insn)?.let {
|
||||
insns[i] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceSubInsns(mth: MethodNode, insn: InsnNode, replace: (MethodNode, InsnNode) -> InsnNode?) {
|
||||
val argsCount = insn.argsCount
|
||||
if (argsCount == 0) {
|
||||
return
|
||||
}
|
||||
for (i in 0 until argsCount) {
|
||||
val arg = insn.getArg(i)
|
||||
if (arg is InsnWrapArg) {
|
||||
val wrapInsn = arg.wrapInsn
|
||||
replaceSubInsns(mth, wrapInsn, replace)
|
||||
replace.invoke(mth, wrapInsn)?.let {
|
||||
InsnRemover.unbindArgUsage(mth, arg)
|
||||
insn.setArg(i, InsnArg.wrapInsnIntoArg(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Search(private val jadx: JadxScriptInstance) {
|
||||
private val dec = jadx.internalDecompiler
|
||||
|
||||
fun classByFullName(fullName: String): ClassNode? {
|
||||
return dec.searchClassNodeByOrigFullName(fullName)
|
||||
}
|
||||
|
||||
fun classesByShortName(fullName: String): List<ClassNode> {
|
||||
return dec.root.searchClassByShortName(fullName)
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.core.dex.nodes.BlockNode
|
||||
import jadx.core.dex.nodes.InsnNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.dex.regions.Region
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Stages(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun rawInsns(block: (MethodNode, Array<InsnNode?>) -> Unit) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageRawInsns",
|
||||
runAfter = listOf("start")
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.instructions?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun mthEarlyBlocks(block: (MethodNode, List<BlockNode>) -> Unit) {
|
||||
mthBlocks(beforePass = "SSATransform", block)
|
||||
}
|
||||
|
||||
fun mthBlocks(
|
||||
beforePass: String = "RegionMakerVisitor",
|
||||
block: (MethodNode, List<BlockNode>) -> Unit
|
||||
) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageMthBlocks",
|
||||
runBefore = listOf(beforePass)
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.basicBlocks?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun mthRegions(block: (MethodNode, Region) -> Unit) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageMthRegions",
|
||||
runBefore = listOf("PrepareForCodeGen")
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.region?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IClassNode
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.api.core.nodes.IRootNode
|
||||
import jadx.api.plugins.pass.JadxPass
|
||||
import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
|
||||
import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
private fun buildScriptName(jadx: JadxScriptInstance, name: String) = "JadxScript${name}(${jadx.scriptName})"
|
||||
|
||||
private fun buildSimplePassInfo(jadx: JadxScriptInstance, name: String) =
|
||||
SimpleJadxPassInfo(buildScriptName(jadx, name))
|
||||
|
||||
abstract class ScriptPreparePass(
|
||||
private val jadx: JadxScriptInstance, private val name: String
|
||||
) : JadxPreparePass {
|
||||
override fun getInfo() = buildSimplePassInfo(jadx, name)
|
||||
}
|
||||
|
||||
abstract class ScriptDecompilePass(
|
||||
private val jadx: JadxScriptInstance, private val name: String
|
||||
) : JadxDecompilePass {
|
||||
override fun getInfo() = buildSimplePassInfo(jadx, name)
|
||||
|
||||
override fun init(root: IRootNode) {
|
||||
}
|
||||
|
||||
override fun visit(cls: IClassNode): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visit(mth: IMethodNode) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScriptOrderedPass(
|
||||
private val jadx: JadxScriptInstance,
|
||||
private val name: String,
|
||||
private val runAfter: List<String> = listOf(),
|
||||
private val runBefore: List<String> = listOf()
|
||||
) : JadxPass {
|
||||
override fun getInfo(): OrderedJadxPassInfo {
|
||||
val scriptName = buildScriptName(jadx, name)
|
||||
return OrderedJadxPassInfo(scriptName, scriptName, runAfter, runBefore)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScriptOrderedPreparePass(
|
||||
jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
|
||||
) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxPreparePass {}
|
||||
|
||||
abstract class ScriptOrderedDecompilePass(
|
||||
jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
|
||||
) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxDecompilePass {
|
||||
|
||||
override fun init(root: IRootNode) {
|
||||
}
|
||||
|
||||
override fun visit(cls: IClassNode): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visit(mth: IMethodNode) {
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package jadx.plugins.script.runtime
|
||||
|
||||
import jadx.api.JadxArgs
|
||||
import jadx.api.JadxDecompiler
|
||||
import jadx.api.JavaClass
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.pass.JadxPass
|
||||
import jadx.plugins.script.runtime.data.*
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class JadxScriptBaseClass(private val scriptData: JadxScriptData) {
|
||||
val scriptName = scriptData.scriptName
|
||||
val log = KotlinLogging.logger("JadxScript:${scriptName}")
|
||||
|
||||
fun getJadxInstance() = JadxScriptInstance(scriptData, log)
|
||||
|
||||
fun println(message: Any?) {
|
||||
log.info(message?.toString())
|
||||
}
|
||||
|
||||
fun print(message: Any?) {
|
||||
log.info(message?.toString())
|
||||
}
|
||||
}
|
||||
|
||||
class JadxScriptData(
|
||||
val jadxInstance: JadxDecompiler,
|
||||
val pluginContext: JadxPluginContext,
|
||||
val scriptFile: File
|
||||
) {
|
||||
val afterLoad: MutableList<() -> Unit> = ArrayList()
|
||||
|
||||
val scriptName get() = scriptFile.name.removeSuffix(".jadx.kts")
|
||||
}
|
||||
|
||||
class JadxScriptInstance(
|
||||
private val scriptData: JadxScriptData,
|
||||
val log: KLogger
|
||||
) {
|
||||
private val decompiler = scriptData.jadxInstance
|
||||
|
||||
val rename: RenamePass by lazy { RenamePass(this) }
|
||||
val stages: Stages by lazy { Stages(this) }
|
||||
val replace: Replace by lazy { Replace(this) }
|
||||
val decompile: Decompile by lazy { Decompile(this) }
|
||||
val search: Search by lazy { Search(this) }
|
||||
val gui: Gui by lazy { Gui(this, scriptData.pluginContext.guiContext) }
|
||||
val debug: Debug by lazy { Debug(this) }
|
||||
|
||||
val args: JadxArgs
|
||||
get() = decompiler.args
|
||||
|
||||
val classes: List<JavaClass>
|
||||
get() = decompiler.classes
|
||||
|
||||
val scriptFile get() = scriptData.scriptFile
|
||||
|
||||
val scriptName get() = scriptData.scriptName
|
||||
|
||||
fun afterLoad(block: () -> Unit) {
|
||||
scriptData.afterLoad.add(block)
|
||||
}
|
||||
|
||||
fun addPass(pass: JadxPass) {
|
||||
scriptData.pluginContext.passContext.addPass(pass)
|
||||
}
|
||||
|
||||
val internalDecompiler: JadxDecompiler
|
||||
get() = decompiler
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package jadx.plugins.script.runtime
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.*
|
||||
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||
import kotlin.script.experimental.jvm.jvm
|
||||
|
||||
@KotlinScript(
|
||||
fileExtension = "jadx.kts",
|
||||
compilationConfiguration = JadxScriptConfiguration::class
|
||||
)
|
||||
abstract class JadxScript
|
||||
|
||||
object JadxScriptConfiguration : ScriptCompilationConfiguration({
|
||||
defaultImports(DependsOn::class, Repository::class)
|
||||
|
||||
jvm {
|
||||
dependenciesFromCurrentContext(
|
||||
wholeClasspath = true
|
||||
)
|
||||
}
|
||||
|
||||
baseClass(JadxScriptBaseClass::class)
|
||||
|
||||
refineConfiguration {
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
})
|
||||
|
||||
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
|
||||
|
||||
fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
|
||||
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: return context.compilationConfiguration.asSuccess()
|
||||
return runBlocking { resolver.resolveFromScriptSourceAnnotations(annotations) }
|
||||
.onSuccess {
|
||||
context.compilationConfiguration.with {
|
||||
dependencies.append(JvmDependency(it))
|
||||
}.asSuccess()
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Utils for use in scripts.
|
||||
* Located in default package to reduce imports.
|
||||
*/
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun String.asFile(): File = File(this)
|
||||
|
||||
fun Int.hex(): String = Integer.toHexString(this)
|
29
jadx-plugins/jadx-script/readme.md
Normal file
29
jadx-plugins/jadx-script/readme.md
Normal file
@ -0,0 +1,29 @@
|
||||
## JADX scripting support
|
||||
|
||||
NOTE: work still in progress!
|
||||
|
||||
### Examples
|
||||
|
||||
Check script examples in `examples/scripts/` (start with `hello`)
|
||||
|
||||
### Script usage
|
||||
|
||||
#### In jadx-cli
|
||||
|
||||
Just add script file as input
|
||||
|
||||
#### In jadx-gui
|
||||
|
||||
1. Add script file to the project
|
||||
2. Script will appear in `Inputs/Scripts` section
|
||||
3. After script change you need to reload project (`Reload` button in toolbar or `F5`)
|
||||
4. You can enable `Live reload` option in `File` menu to reload project automatically on scripts change
|
||||
|
||||
### Script development
|
||||
|
||||
Jadx-gui for now don't support autocompletion, errors highlighting, docs and code navigation
|
||||
so best approach for script editing is to open jadx project in IntelliJ Idea and write your script in `examples/scripts/` folder.
|
||||
Also, this will allow to debug your scripts: for that you need to create run configuration for jadx-cli or jadx-gui
|
||||
add breakpoints and next run it in debug mode (jadx-gui is preferred because of faster script reload).
|
||||
|
||||
Script logs and compilation errors will appear in `Log viewer` (separate script log will be added later)
|
@ -1,12 +0,0 @@
|
||||
rootProject.name = 'jadx'
|
||||
|
||||
include 'jadx-core'
|
||||
include 'jadx-cli'
|
||||
include 'jadx-gui'
|
||||
include 'jadx-plugins'
|
||||
include 'jadx-plugins:jadx-plugins-api'
|
||||
include 'jadx-plugins:jadx-dex-input'
|
||||
include 'jadx-plugins:jadx-java-input'
|
||||
include 'jadx-plugins:jadx-raung-input'
|
||||
include 'jadx-plugins:jadx-smali-input'
|
||||
include 'jadx-plugins:jadx-java-convert'
|
17
settings.gradle.kts
Normal file
17
settings.gradle.kts
Normal file
@ -0,0 +1,17 @@
|
||||
rootProject.name = "jadx"
|
||||
|
||||
include("jadx-core")
|
||||
include("jadx-cli")
|
||||
include("jadx-gui")
|
||||
|
||||
include("jadx-plugins")
|
||||
include("jadx-plugins:jadx-plugins-api")
|
||||
include("jadx-plugins:jadx-dex-input")
|
||||
include("jadx-plugins:jadx-java-input")
|
||||
include("jadx-plugins:jadx-raung-input")
|
||||
include("jadx-plugins:jadx-smali-input")
|
||||
include("jadx-plugins:jadx-java-convert")
|
||||
|
||||
include("jadx-plugins:jadx-script:jadx-script-plugin")
|
||||
include("jadx-plugins:jadx-script:jadx-script-runtime")
|
||||
include("jadx-plugins:jadx-script:examples")
|
Loading…
Reference in New Issue
Block a user