Projects STRLCPY jadx Commits 79f7a0a2
🤬
  • ■ ■ ■ ■
    build.gradle
    skipped 89 lines
    90 90   resolutionStrategy {
    91 91   componentSelection { rules ->
    92 92   rules.all { ComponentSelection selection ->
    93  - boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
     93 + boolean rejected = ['alpha', 'beta', 'dev', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
    94 94   selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
    95 95   }
    96 96   if (rejected) {
    skipped 65 lines
  • ■ ■ ■ ■ ■
    gradle.properties
    1 1  org.gradle.warning.mode=all
    2 2  org.gradle.parallel=true
     3 +org.gradle.caching=true
    3 4   
    4 5  # Flags for google-java-format (optimize imports by spotless) for Java >= 16.
    5 6  # Java < 9 will ignore unsupported flags (thanks to -XX:+IgnoreUnrecognizedVMOptions)
    skipped 7 lines
  • ■ ■ ■ ■ ■
    jadx-cli/build.gradle
    skipped 8 lines
    9 9   runtimeOnly(project(':jadx-plugins:jadx-java-input'))
    10 10   runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
    11 11   runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
     12 + runtimeOnly(project(':jadx-plugins:jadx-script:jadx-script-plugin'))
    12 13   
    13 14   implementation 'com.beust:jcommander:1.82'
    14 15   implementation 'ch.qos.logback:logback-classic:1.3.4'
    skipped 17 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JadxDecompiler.java
    skipped 24 lines
    25 25  import org.slf4j.Logger;
    26 26  import org.slf4j.LoggerFactory;
    27 27   
     28 +import jadx.api.core.nodes.IJadxDecompiler;
     29 +import jadx.api.impl.plugins.SimplePluginContext;
    28 30  import jadx.api.metadata.ICodeAnnotation;
    29 31  import jadx.api.metadata.ICodeNodeRef;
    30 32  import jadx.api.metadata.annotations.NodeDeclareRef;
    skipped 1 lines
    32 34  import jadx.api.metadata.annotations.VarRef;
    33 35  import jadx.api.plugins.JadxPlugin;
    34 36  import jadx.api.plugins.JadxPluginManager;
     37 +import jadx.api.plugins.gui.JadxGuiContext;
    35 38  import jadx.api.plugins.input.JadxInputPlugin;
    36 39  import jadx.api.plugins.input.data.ILoadResult;
    37 40  import jadx.api.plugins.options.JadxPluginOptions;
     41 +import jadx.api.plugins.pass.JadxPass;
     42 +import jadx.api.plugins.pass.types.JadxAfterLoadPass;
     43 +import jadx.api.plugins.pass.types.JadxPassType;
    38 44  import jadx.core.Jadx;
    39 45  import jadx.core.dex.attributes.AFlag;
    40 46  import jadx.core.dex.nodes.ClassNode;
    skipped 38 lines
    79 85   * </code>
    80 86   * </pre>
    81 87   */
    82  -public final class JadxDecompiler implements Closeable {
     88 +public final class JadxDecompiler implements IJadxDecompiler, Closeable {
    83 89   private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
    84 90   
    85 91   private final JadxArgs args;
    skipped 14 lines
    100 106   private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
    101 107   
    102 108   private final List<ILoadResult> customLoads = new ArrayList<>();
     109 + private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
     110 + private @Nullable JadxGuiContext guiContext;
    103 111   
    104 112   public JadxDecompiler() {
    105 113   this(new JadxArgs());
    skipped 11 lines
    117 125   loadInputFiles();
    118 126   
    119 127   root = new RootNode(args);
     128 + root.setDecompilerRef(this);
     129 + root.mergePasses(customPasses);
    120 130   root.loadClasses(loadedInputs);
    121 131   root.initClassPath();
    122 132   root.loadResources(getResources());
    123 133   root.runPreDecompileStage();
    124 134   root.initPasses();
     135 + loadFinished();
    125 136   }
    126 137   
    127 138   private void loadInputFiles() {
    skipped 13 lines
    141 152   }
    142 153   }
    143 154   
    144  - public void addCustomLoad(ILoadResult customLoad) {
    145  - customLoads.add(customLoad);
    146  - }
    147  - 
    148  - public List<ILoadResult> getCustomLoads() {
    149  - return customLoads;
    150  - }
    151  - 
    152 155   private void reset() {
    153 156   root = null;
    154 157   classes = null;
    skipped 31 lines
    186 189   LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
    187 190   p -> p.getPluginInfo().getPluginId()));
    188 191   }
     192 + applyPluginOptions(args);
     193 + initPlugins();
     194 + }
     195 + 
     196 + private void applyPluginOptions(JadxArgs args) {
    189 197   Map<String, String> pluginOptions = args.getPluginOptions();
    190 198   if (!pluginOptions.isEmpty()) {
    191 199   LOG.debug("Applying plugin options: {}", pluginOptions);
    skipped 4 lines
    196 204   String pluginId = plugin.getPluginInfo().getPluginId();
    197 205   throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
    198 206   }
     207 + }
     208 + }
     209 + }
     210 + 
     211 + private void initPlugins() {
     212 + customPasses.clear();
     213 + 
     214 + List<JadxPlugin> plugins = pluginManager.getResolvedPlugins();
     215 + SimplePluginContext context = new SimplePluginContext(this);
     216 + context.setGuiContext(guiContext);
     217 + for (JadxPlugin passPlugin : plugins) {
     218 + try {
     219 + passPlugin.init(context);
     220 + } catch (Exception e) {
     221 + String pluginId = passPlugin.getPluginInfo().getPluginId();
     222 + throw new JadxRuntimeException("Failed to pass plugin: " + pluginId, e);
     223 + }
     224 + }
     225 + if (LOG.isDebugEnabled()) {
     226 + List<String> passes = customPasses.values().stream().flatMap(Collection::stream)
     227 + .map(p -> p.getInfo().getName()).collect(Collectors.toList());
     228 + LOG.debug("Loaded custom passes: {} {}", passes.size(), passes);
     229 + }
     230 + }
     231 + 
     232 + private void loadFinished() {
     233 + List<JadxPass> list = customPasses.get(JadxAfterLoadPass.TYPE);
     234 + if (list != null) {
     235 + for (JadxPass pass : list) {
     236 + ((JadxAfterLoadPass) pass).init(this);
    199 237   }
    200 238   }
    201 239   }
    skipped 439 lines
    641 679   
    642 680   public IDecompileScheduler getDecompileScheduler() {
    643 681   return decompileScheduler;
     682 + }
     683 + 
     684 + public void addCustomLoad(ILoadResult customLoad) {
     685 + customLoads.add(customLoad);
     686 + }
     687 + 
     688 + public List<ILoadResult> getCustomLoads() {
     689 + return customLoads;
     690 + }
     691 + 
     692 + public void addCustomPass(JadxPass pass) {
     693 + customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
     694 + }
     695 + 
     696 + public void setJadxGuiContext(JadxGuiContext guiContext) {
     697 + this.guiContext = guiContext;
    644 698   }
    645 699   
    646 700   @Override
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java
     1 +package jadx.api.impl.passes;
     2 + 
     3 +import org.slf4j.Logger;
     4 +import org.slf4j.LoggerFactory;
     5 + 
     6 +import jadx.api.plugins.pass.types.JadxDecompilePass;
     7 +import jadx.core.dex.nodes.ClassNode;
     8 +import jadx.core.dex.nodes.MethodNode;
     9 +import jadx.core.dex.nodes.RootNode;
     10 +import jadx.core.dex.visitors.AbstractVisitor;
     11 +import jadx.core.utils.exceptions.JadxException;
     12 + 
     13 +public class DecompilePassWrapper extends AbstractVisitor {
     14 + private static final Logger LOG = LoggerFactory.getLogger(DecompilePassWrapper.class);
     15 + 
     16 + private final JadxDecompilePass decompilePass;
     17 + 
     18 + public DecompilePassWrapper(JadxDecompilePass decompilePass) {
     19 + this.decompilePass = decompilePass;
     20 + }
     21 + 
     22 + @Override
     23 + public void init(RootNode root) throws JadxException {
     24 + try {
     25 + decompilePass.init(root);
     26 + } catch (Throwable e) {
     27 + LOG.error("Error in decompile pass init: {}", this, e);
     28 + }
     29 + }
     30 + 
     31 + @Override
     32 + public boolean visit(ClassNode cls) throws JadxException {
     33 + try {
     34 + return decompilePass.visit(cls);
     35 + } catch (Throwable e) {
     36 + LOG.error("Error in decompile pass init: {}", this, e);
     37 + return false;
     38 + }
     39 + }
     40 + 
     41 + @Override
     42 + public void visit(MethodNode mth) throws JadxException {
     43 + try {
     44 + decompilePass.visit(mth);
     45 + } catch (Throwable e) {
     46 + LOG.error("Error in decompile pass: {}", this, e);
     47 + }
     48 + }
     49 + 
     50 + @Override
     51 + public String toString() {
     52 + return decompilePass.getInfo().getName();
     53 + }
     54 +}
     55 + 
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/impl/passes/PreparePassWrapper.java
     1 +package jadx.api.impl.passes;
     2 + 
     3 +import org.slf4j.Logger;
     4 +import org.slf4j.LoggerFactory;
     5 + 
     6 +import jadx.api.plugins.pass.types.JadxPreparePass;
     7 +import jadx.core.dex.nodes.RootNode;
     8 +import jadx.core.dex.visitors.AbstractVisitor;
     9 +import jadx.core.utils.exceptions.JadxException;
     10 + 
     11 +public class PreparePassWrapper extends AbstractVisitor {
     12 + private static final Logger LOG = LoggerFactory.getLogger(PreparePassWrapper.class);
     13 + 
     14 + private final JadxPreparePass preparePass;
     15 + 
     16 + public PreparePassWrapper(JadxPreparePass preparePass) {
     17 + this.preparePass = preparePass;
     18 + }
     19 + 
     20 + @Override
     21 + public void init(RootNode root) throws JadxException {
     22 + try {
     23 + preparePass.init(root);
     24 + } catch (Exception e) {
     25 + LOG.error("Error in prepare pass init: {}", this, e);
     26 + }
     27 + }
     28 + 
     29 + @Override
     30 + public String toString() {
     31 + return preparePass.getInfo().getName();
     32 + }
     33 +}
     34 + 
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/impl/plugins/SimplePassContext.java
     1 +package jadx.api.impl.plugins;
     2 + 
     3 +import jadx.api.JadxDecompiler;
     4 +import jadx.api.plugins.pass.JadxPass;
     5 +import jadx.api.plugins.pass.JadxPassContext;
     6 + 
     7 +public class SimplePassContext implements JadxPassContext {
     8 + 
     9 + private final JadxDecompiler jadxDecompiler;
     10 + 
     11 + public SimplePassContext(JadxDecompiler jadxDecompiler) {
     12 + this.jadxDecompiler = jadxDecompiler;
     13 + }
     14 + 
     15 + @Override
     16 + public void addPass(JadxPass pass) {
     17 + jadxDecompiler.addCustomPass(pass);
     18 + }
     19 +}
     20 + 
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/impl/plugins/SimplePluginContext.java
     1 +package jadx.api.impl.plugins;
     2 + 
     3 +import org.jetbrains.annotations.Nullable;
     4 + 
     5 +import jadx.api.JadxDecompiler;
     6 +import jadx.api.plugins.JadxPluginContext;
     7 +import jadx.api.plugins.gui.JadxGuiContext;
     8 +import jadx.api.plugins.pass.JadxPassContext;
     9 + 
     10 +public class SimplePluginContext implements JadxPluginContext {
     11 + 
     12 + private final JadxDecompiler decompiler;
     13 + private final JadxPassContext passContext;
     14 + private @Nullable JadxGuiContext guiContext;
     15 + 
     16 + public SimplePluginContext(JadxDecompiler decompiler) {
     17 + this.decompiler = decompiler;
     18 + this.passContext = new SimplePassContext(decompiler);
     19 + }
     20 + 
     21 + @Override
     22 + public JadxDecompiler getDecompiler() {
     23 + return decompiler;
     24 + }
     25 + 
     26 + @Override
     27 + public JadxPassContext getPassContext() {
     28 + return passContext;
     29 + }
     30 + 
     31 + @Override
     32 + public @Nullable JadxGuiContext getGuiContext() {
     33 + return guiContext;
     34 + }
     35 + 
     36 + public void setGuiContext(JadxGuiContext guiContext) {
     37 + this.guiContext = guiContext;
     38 + }
     39 +}
     40 + 
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
    skipped 20 lines
    21 21  import jadx.api.ICodeInfo;
    22 22  import jadx.api.ICodeWriter;
    23 23  import jadx.api.JadxArgs;
     24 +import jadx.api.core.nodes.IClassNode;
    24 25  import jadx.api.impl.SimpleCodeInfo;
    25 26  import jadx.api.plugins.input.data.IClassData;
    26 27  import jadx.api.plugins.input.data.IFieldData;
    skipped 27 lines
    54 55  import static jadx.core.dex.nodes.ProcessState.LOADED;
    55 56  import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
    56 57   
    57  -public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
     58 +public class ClassNode extends NotificationAttrNode implements IClassNode, ILoadable, ICodeNode, Comparable<ClassNode> {
    58 59   private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
    59 60   
    60 61   private final RootNode root;
    skipped 810 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
    skipped 9 lines
    10 10  import org.slf4j.Logger;
    11 11  import org.slf4j.LoggerFactory;
    12 12   
     13 +import jadx.api.core.nodes.IMethodNode;
    13 14  import jadx.api.plugins.input.data.ICodeReader;
    14 15  import jadx.api.plugins.input.data.IDebugInfo;
    15 16  import jadx.api.plugins.input.data.IMethodData;
    16 17  import jadx.api.plugins.input.data.attributes.JadxAttrType;
    17 18  import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
    18 19  import jadx.core.dex.attributes.AFlag;
     20 +import jadx.core.dex.attributes.AType;
    19 21  import jadx.core.dex.attributes.nodes.LoopInfo;
     22 +import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
    20 23  import jadx.core.dex.attributes.nodes.NotificationAttrNode;
    21 24  import jadx.core.dex.info.AccessInfo;
    22 25  import jadx.core.dex.info.AccessInfo.AFType;
    skipped 12 lines
    35 38   
    36 39  import static jadx.core.utils.Utils.lockList;
    37 40   
    38  -public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
     41 +public class MethodNode extends NotificationAttrNode implements IMethodNode,
     42 + IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
    39 43   private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
    40 44   
    41 45   private final MethodInfo mthInfo;
    skipped 527 lines
    569 573   noCode = true;
    570 574   }
    571 575   
     576 + public void rename(String newName) {
     577 + MethodOverrideAttr overrideAttr = get(AType.METHOD_OVERRIDE);
     578 + if (overrideAttr != null) {
     579 + for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
     580 + relatedMth.getMethodInfo().setAlias(newName);
     581 + }
     582 + } else {
     583 + mthInfo.setAlias(newName);
     584 + }
     585 + }
     586 + 
    572 587   /**
    573  - * Calculate instructions count at currect stage
     588 + * Calculate instructions count at current stage
    574 589   */
    575 590   public long countInsns() {
    576 591   if (instructions != null) {
    skipped 76 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
    skipped 14 lines
    15 15  import jadx.api.ICodeCache;
    16 16  import jadx.api.ICodeWriter;
    17 17  import jadx.api.JadxArgs;
     18 +import jadx.api.JadxDecompiler;
    18 19  import jadx.api.ResourceFile;
    19 20  import jadx.api.ResourceType;
    20 21  import jadx.api.ResourcesLoader;
     22 +import jadx.api.core.nodes.IRootNode;
    21 23  import jadx.api.data.ICodeData;
     24 +import jadx.api.impl.passes.DecompilePassWrapper;
     25 +import jadx.api.impl.passes.PreparePassWrapper;
    22 26  import jadx.api.plugins.input.data.IClassData;
    23 27  import jadx.api.plugins.input.data.ILoadResult;
     28 +import jadx.api.plugins.pass.JadxPass;
     29 +import jadx.api.plugins.pass.types.JadxDecompilePass;
     30 +import jadx.api.plugins.pass.types.JadxPassType;
     31 +import jadx.api.plugins.pass.types.JadxPreparePass;
    24 32  import jadx.core.Jadx;
    25 33  import jadx.core.ProcessClass;
    26 34  import jadx.core.clsp.ClspGraph;
    skipped 11 lines
    38 46  import jadx.core.dex.visitors.typeinference.TypeUpdate;
    39 47  import jadx.core.utils.CacheStorage;
    40 48  import jadx.core.utils.ErrorsCounter;
     49 +import jadx.core.utils.PassMerge;
    41 50  import jadx.core.utils.StringUtils;
    42 51  import jadx.core.utils.Utils;
    43 52  import jadx.core.utils.android.AndroidResourcesUtils;
    skipped 4 lines
    48 57  import jadx.core.xmlgen.entry.ResourceEntry;
    49 58  import jadx.core.xmlgen.entry.ValuesParser;
    50 59   
    51  -public class RootNode {
     60 +public class RootNode implements IRootNode {
    52 61   private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
    53 62   
    54 63   private final JadxArgs args;
    skipped 20 lines
    75 84   private ClassNode appResClass;
    76 85   private boolean isProto;
    77 86   
     87 + /**
     88 + * Optional decompiler reference
     89 + */
     90 + private @Nullable JadxDecompiler decompiler;
     91 + 
    78 92   public RootNode(JadxArgs args) {
    79 93   this.args = args;
    80 94   this.preDecompilePasses = Jadx.getPreDecompilePassesList();
    81  - this.processClasses = new ProcessClass(this.getArgs());
     95 + this.processClasses = new ProcessClass(args);
    82 96   this.stringUtils = new StringUtils(args);
    83 97   this.constValues = new ConstStorage(args);
    84 98   this.typeUpdate = new TypeUpdate(this);
    skipped 181 lines
    266 280   classes.forEach(ClassNode::updateParentClass);
    267 281   }
    268 282   
     283 + public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
     284 + PassMerge.run(preDecompilePasses,
     285 + customPasses.get(JadxPreparePass.TYPE),
     286 + p -> new PreparePassWrapper((JadxPreparePass) p));
     287 + PassMerge.run(processClasses.getPasses(),
     288 + customPasses.get(JadxDecompilePass.TYPE),
     289 + p -> new DecompilePassWrapper((JadxDecompilePass) p));
     290 + }
     291 + 
    269 292   public void runPreDecompileStage() {
    270 293   boolean debugEnabled = LOG.isDebugEnabled();
    271 294   for (IDexTreeVisitor pass : preDecompilePasses) {
    skipped 11 lines
    283 306   DepthTraversal.visit(pass, cls);
    284 307   }
    285 308   if (debugEnabled) {
    286  - LOG.debug("{} time: {}ms", pass.getClass().getSimpleName(), System.currentTimeMillis() - start);
     309 + LOG.debug("Prepare pass: '{}' - {}ms", pass, System.currentTimeMillis() - start);
    287 310   }
    288 311   }
    289 312   }
    skipped 238 lines
    528 551   
    529 552   public JadxArgs getArgs() {
    530 553   return args;
     554 + }
     555 + 
     556 + public void setDecompilerRef(JadxDecompiler jadxDecompiler) {
     557 + this.decompiler = jadxDecompiler;
     558 + }
     559 + 
     560 + public @Nullable JadxDecompiler getDecompiler() {
     561 + return decompiler;
    531 562   }
    532 563   
    533 564   public TypeUpdate getTypeUpdate() {
    skipped 24 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java
    skipped 439 lines
    440 440   k++;
    441 441   }
    442 442   }
     443 + 
     444 + @Override
     445 + public String toString() {
     446 + return "OverrideMethodVisitor";
     447 + }
    443 448  }
    444 449   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java
    skipped 298 lines
    299 299   }
    300 300   return null;
    301 301   }
     302 + 
     303 + @Override
     304 + public String toString() {
     305 + return "ProcessAnonymous";
     306 + }
    302 307  }
    303 308   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/ProcessMethodsForInline.java
    skipped 70 lines
    71 71   }
    72 72   }
    73 73   }
     74 + 
     75 + @Override
     76 + public String toString() {
     77 + return "ProcessMethodsForInline";
     78 + }
    74 79  }
    75 80   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/SignatureProcessor.java
    skipped 230 lines
    231 231   }
    232 232   return validateInnerType(innerType);
    233 233   }
     234 + 
     235 + @Override
     236 + public String toString() {
     237 + return "SignatureProcessor";
     238 + }
    234 239  }
    235 240   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java
    skipped 265 lines
    266 266   }
    267 267   return pkg.substring(0, dotPos);
    268 268   }
     269 + 
     270 + @Override
     271 + public String toString() {
     272 + return "RenameVisitor";
     273 + }
    269 274  }
    270 275   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/rename/UserRenames.java
    skipped 10 lines
    11 11  import jadx.api.data.ICodeRename;
    12 12  import jadx.api.data.IJavaCodeRef;
    13 13  import jadx.api.data.IJavaNodeRef;
    14  -import jadx.core.dex.attributes.AType;
    15  -import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
    16 14  import jadx.core.dex.info.ClassInfo;
    17 15  import jadx.core.dex.info.InfoStorage;
    18 16  import jadx.core.dex.instructions.args.ArgType;
    skipped 53 lines
    72 70   } else {
    73 71   IJavaCodeRef codeRef = rename.getCodeRef();
    74 72   if (codeRef == null) {
    75  - applyMethodRename(mth, rename);
     73 + mth.rename(rename.getNewName());
    76 74   }
    77 75   }
    78 76   break;
    79 77   }
    80  - }
    81  - 
    82  - private static void applyMethodRename(MethodNode mth, ICodeRename rename) {
    83  - MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
    84  - if (overrideAttr != null) {
    85  - for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
    86  - renameMethod(relatedMth, rename);
    87  - }
    88  - } else {
    89  - renameMethod(mth, rename);
    90  - }
    91  - }
    92  - 
    93  - private static void renameMethod(MethodNode mth, ICodeRename rename) {
    94  - mth.getMethodInfo().setAlias(rename.getNewName());
    95 78   }
    96 79   
    97 80   // TODO: Very inefficient!!! Add PackageInfo class to build package hierarchy
    skipped 36 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java
    skipped 144 lines
    145 145   mergeIntoMth.setUseIn(mergedUsage);
    146 146   sourceMth.setUseIn(Collections.emptyList());
    147 147   }
     148 + 
     149 + @Override
     150 + public String toString() {
     151 + return "UsageInfoVisitor";
     152 + }
    148 153  }
    149 154   
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/utils/PassMerge.java
     1 +package jadx.core.utils;
     2 + 
     3 +import java.util.HashMap;
     4 +import java.util.List;
     5 +import java.util.Map;
     6 +import java.util.function.Function;
     7 + 
     8 +import jadx.api.plugins.pass.JadxPass;
     9 +import jadx.api.plugins.pass.JadxPassInfo;
     10 +import jadx.core.dex.visitors.IDexTreeVisitor;
     11 +import jadx.core.utils.exceptions.JadxRuntimeException;
     12 + 
     13 +public class PassMerge {
     14 + 
     15 + public static void run(List<IDexTreeVisitor> passes, List<JadxPass> customPasses, Function<JadxPass, IDexTreeVisitor> wrap) {
     16 + if (Utils.isEmpty(customPasses)) {
     17 + return;
     18 + }
     19 + for (JadxPass customPass : customPasses) {
     20 + IDexTreeVisitor pass = wrap.apply(customPass);
     21 + int pos = searchInsertPos(passes, customPass.getInfo());
     22 + if (pos == -1) {
     23 + passes.add(pass);
     24 + } else {
     25 + passes.add(pos, pass);
     26 + }
     27 + }
     28 + }
     29 + 
     30 + private static int searchInsertPos(List<IDexTreeVisitor> passes, JadxPassInfo info) {
     31 + List<String> runAfter = info.runAfter();
     32 + List<String> runBefore = info.runBefore();
     33 + if (runAfter.isEmpty() && runBefore.isEmpty()) {
     34 + return -1; // last
     35 + }
     36 + if (ListUtils.isSingleElement(runAfter, "start")) {
     37 + return 0;
     38 + }
     39 + if (ListUtils.isSingleElement(runBefore, "end")) {
     40 + return -1;
     41 + }
     42 + Map<String, Integer> namesMap = buildNamesMap(passes);
     43 + int after = 0;
     44 + for (String name : runAfter) {
     45 + Integer pos = namesMap.get(name);
     46 + if (pos != null) {
     47 + after = Math.max(after, pos);
     48 + }
     49 + }
     50 + int before = Integer.MAX_VALUE;
     51 + for (String name : runBefore) {
     52 + Integer pos = namesMap.get(name);
     53 + if (pos != null) {
     54 + before = Math.min(before, pos);
     55 + }
     56 + }
     57 + if (before <= after) {
     58 + throw new JadxRuntimeException("Conflict pass order requirements: " + info.getName()
     59 + + "\n run after: " + runAfter
     60 + + "\n run before: " + runBefore
     61 + + "\n passes: " + ListUtils.map(passes, PassMerge::getPassName));
     62 + }
     63 + if (after == 0) {
     64 + return before;
     65 + }
     66 + int pos = after + 1;
     67 + return pos >= passes.size() ? -1 : pos;
     68 + }
     69 + 
     70 + private static Map<String, Integer> buildNamesMap(List<IDexTreeVisitor> passes) {
     71 + int size = passes.size();
     72 + Map<String, Integer> namesMap = new HashMap<>(size);
     73 + for (int i = 0; i < size; i++) {
     74 + namesMap.put(getPassName(passes.get(i)), i);
     75 + }
     76 + return namesMap;
     77 + }
     78 + 
     79 + private static String getPassName(IDexTreeVisitor pass) {
     80 + return pass.getClass().getSimpleName();
     81 + }
     82 +}
     83 + 
  • ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IClassNode.java
     1 +package jadx.api.core.nodes;
     2 + 
     3 +public interface IClassNode {
     4 +}
     5 + 
  • ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IJadxDecompiler.java
     1 +package jadx.api.core.nodes;
     2 + 
     3 +public interface IJadxDecompiler {
     4 +}
     5 + 
  • ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IMethodNode.java
     1 +package jadx.api.core.nodes;
     2 + 
     3 +public interface IMethodNode {
     4 +}
     5 + 
  • ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/core/nodes/IRootNode.java
     1 +package jadx.api.core.nodes;
     2 + 
     3 +public interface IRootNode {
     4 +}
     5 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java
    skipped 1 lines
    2 2   
    3 3  public interface JadxPlugin {
    4 4   JadxPluginInfo getPluginInfo();
     5 + 
     6 + default void init(JadxPluginContext context) {
     7 + // default to no-op
     8 + }
    5 9  }
    6 10   
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginContext.java
     1 +package jadx.api.plugins;
     2 + 
     3 +import org.jetbrains.annotations.Nullable;
     4 + 
     5 +import jadx.api.core.nodes.IJadxDecompiler;
     6 +import jadx.api.plugins.gui.JadxGuiContext;
     7 +import jadx.api.plugins.pass.JadxPassContext;
     8 + 
     9 +public interface JadxPluginContext {
     10 + 
     11 + IJadxDecompiler getDecompiler();
     12 + 
     13 + JadxPassContext getPassContext();
     14 + 
     15 + @Nullable
     16 + JadxGuiContext getGuiContext();
     17 +}
     18 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java
    skipped 41 lines
    42 42   ServiceLoader<JadxPlugin> jadxPlugins = ServiceLoader.load(JadxPlugin.class);
    43 43   for (JadxPlugin plugin : jadxPlugins) {
    44 44   addPlugin(plugin);
    45  - LOG.debug("Loading plugin: {}", plugin.getPluginInfo().getPluginId());
    46 45   }
    47 46   resolve();
    48 47   }
    skipped 7 lines
    56 55   
    57 56   private PluginData addPlugin(JadxPlugin plugin) {
    58 57   PluginData pluginData = new PluginData(plugin, plugin.getPluginInfo());
     58 + LOG.debug("Loading plugin: {}", pluginData.getPluginId());
    59 59   if (!allPlugins.add(pluginData)) {
    60 60   throw new IllegalArgumentException("Duplicate plugin id: " + pluginData + ", class " + plugin.getClass());
    61 61   }
    skipped 50 lines
    112 112   }
    113 113   
    114 114   public List<JadxInputPlugin> getInputPlugins() {
    115  - return resolvedPlugins.stream()
    116  - .filter(JadxInputPlugin.class::isInstance)
    117  - .map(JadxInputPlugin.class::cast)
    118  - .collect(Collectors.toList());
     115 + return getPluginsWithType(JadxInputPlugin.class);
    119 116   }
    120 117   
    121 118   public List<JadxPluginOptions> getPluginsWithOptions() {
     119 + return getPluginsWithType(JadxPluginOptions.class);
     120 + }
     121 + 
     122 + @SuppressWarnings("unchecked")
     123 + public <T extends JadxPlugin> List<T> getPluginsWithType(Class<T> type) {
    122 124   return resolvedPlugins.stream()
    123  - .filter(JadxPluginOptions.class::isInstance)
    124  - .map(JadxPluginOptions.class::cast)
     125 + .filter(p -> type.isAssignableFrom(p.getClass()))
     126 + .map(p -> (T) p)
    125 127   .collect(Collectors.toList());
    126 128   }
    127 129   
    skipped 74 lines
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java
     1 +package jadx.api.plugins.gui;
     2 + 
     3 +public interface JadxGuiContext {
     4 + 
     5 + /**
     6 + * Run code in UI Thread
     7 + */
     8 + void uiRun(Runnable runnable);
     9 + 
     10 + void addMenuAction(String name, Runnable action);
     11 +}
     12 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/JadxPass.java
     1 +package jadx.api.plugins.pass;
     2 + 
     3 +import jadx.api.plugins.pass.types.JadxPassType;
     4 + 
     5 +public interface JadxPass {
     6 + JadxPassInfo getInfo();
     7 + 
     8 + JadxPassType getPassType();
     9 +}
     10 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/JadxPassContext.java
     1 +package jadx.api.plugins.pass;
     2 + 
     3 +public interface JadxPassContext {
     4 + 
     5 + void addPass(JadxPass pass);
     6 +}
     7 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/JadxPassInfo.java
     1 +package jadx.api.plugins.pass;
     2 + 
     3 +import java.util.List;
     4 + 
     5 +public interface JadxPassInfo {
     6 + 
     7 + String getName();
     8 + 
     9 + String getDescription();
     10 + 
     11 + List<String> runAfter();
     12 + 
     13 + List<String> runBefore();
     14 +}
     15 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/impl/OrderedJadxPassInfo.java
     1 +package jadx.api.plugins.pass.impl;
     2 + 
     3 +import java.util.ArrayList;
     4 +import java.util.List;
     5 + 
     6 +import jadx.api.plugins.pass.JadxPassInfo;
     7 + 
     8 +public class OrderedJadxPassInfo implements JadxPassInfo {
     9 + 
     10 + private final String name;
     11 + private final String desc;
     12 + private final List<String> runAfter;
     13 + private final List<String> runBefore;
     14 + 
     15 + public OrderedJadxPassInfo(String name) {
     16 + this(name, name);
     17 + }
     18 + 
     19 + public OrderedJadxPassInfo(String name, String desc) {
     20 + this(name, desc, new ArrayList<>(), new ArrayList<>());
     21 + }
     22 + 
     23 + public OrderedJadxPassInfo(String name, String desc, List<String> runAfter, List<String> runBefore) {
     24 + this.name = name;
     25 + this.desc = desc;
     26 + this.runAfter = runAfter;
     27 + this.runBefore = runBefore;
     28 + }
     29 + 
     30 + @Override
     31 + public String getName() {
     32 + return name;
     33 + }
     34 + 
     35 + @Override
     36 + public String getDescription() {
     37 + return desc;
     38 + }
     39 + 
     40 + @Override
     41 + public List<String> runAfter() {
     42 + return runAfter;
     43 + }
     44 + 
     45 + @Override
     46 + public List<String> runBefore() {
     47 + return runBefore;
     48 + }
     49 +}
     50 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/impl/SimpleJadxPassInfo.java
     1 +package jadx.api.plugins.pass.impl;
     2 + 
     3 +import java.util.Collections;
     4 +import java.util.List;
     5 + 
     6 +import jadx.api.plugins.pass.JadxPassInfo;
     7 + 
     8 +public class SimpleJadxPassInfo implements JadxPassInfo {
     9 + 
     10 + private final String name;
     11 + private final String desc;
     12 + 
     13 + public SimpleJadxPassInfo(String name) {
     14 + this(name, name);
     15 + }
     16 + 
     17 + public SimpleJadxPassInfo(String name, String desc) {
     18 + this.name = name;
     19 + this.desc = desc;
     20 + }
     21 + 
     22 + @Override
     23 + public String getName() {
     24 + return name;
     25 + }
     26 + 
     27 + @Override
     28 + public String getDescription() {
     29 + return desc;
     30 + }
     31 + 
     32 + @Override
     33 + public List<String> runAfter() {
     34 + return Collections.emptyList();
     35 + }
     36 + 
     37 + @Override
     38 + public List<String> runBefore() {
     39 + return Collections.emptyList();
     40 + }
     41 +}
     42 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/types/JadxAfterLoadPass.java
     1 +package jadx.api.plugins.pass.types;
     2 + 
     3 +import jadx.api.core.nodes.IJadxDecompiler;
     4 +import jadx.api.plugins.pass.JadxPass;
     5 + 
     6 +public interface JadxAfterLoadPass extends JadxPass {
     7 + JadxPassType TYPE = new JadxPassType(JadxAfterLoadPass.class);
     8 + 
     9 + void init(IJadxDecompiler decompiler);
     10 + 
     11 + @Override
     12 + default JadxPassType getPassType() {
     13 + return TYPE;
     14 + }
     15 +}
     16 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/types/JadxDecompilePass.java
     1 +package jadx.api.plugins.pass.types;
     2 + 
     3 +import jadx.api.core.nodes.IClassNode;
     4 +import jadx.api.core.nodes.IMethodNode;
     5 +import jadx.api.core.nodes.IRootNode;
     6 +import jadx.api.plugins.pass.JadxPass;
     7 + 
     8 +public interface JadxDecompilePass extends JadxPass {
     9 + JadxPassType TYPE = new JadxPassType(JadxDecompilePass.class);
     10 + 
     11 + void init(IRootNode root);
     12 + 
     13 + /**
     14 + * Visit class
     15 + *
     16 + * @return false for disable child methods and inner classes traversal
     17 + */
     18 + boolean visit(IClassNode cls);
     19 + 
     20 + /**
     21 + * Visit method
     22 + */
     23 + void visit(IMethodNode mth);
     24 + 
     25 + @Override
     26 + default JadxPassType getPassType() {
     27 + return TYPE;
     28 + }
     29 +}
     30 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/types/JadxPassType.java
     1 +package jadx.api.plugins.pass.types;
     2 + 
     3 +import jadx.api.plugins.pass.JadxPass;
     4 + 
     5 +public class JadxPassType {
     6 + private final String cls;
     7 + 
     8 + public JadxPassType(Class<? extends JadxPass> cls) {
     9 + this.cls = cls.getSimpleName();
     10 + }
     11 + 
     12 + @Override
     13 + public boolean equals(Object o) {
     14 + if (this == o) {
     15 + return true;
     16 + }
     17 + if (!(o instanceof JadxPassType)) {
     18 + return false;
     19 + }
     20 + return cls.equals(((JadxPassType) o).cls);
     21 + }
     22 + 
     23 + @Override
     24 + public int hashCode() {
     25 + return cls.hashCode();
     26 + }
     27 + 
     28 + @Override
     29 + public String toString() {
     30 + return "JadxPassType{" + cls + '}';
     31 + }
     32 +}
     33 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/pass/types/JadxPreparePass.java
     1 +package jadx.api.plugins.pass.types;
     2 + 
     3 +import jadx.api.core.nodes.IRootNode;
     4 +import jadx.api.plugins.pass.JadxPass;
     5 + 
     6 +public interface JadxPreparePass extends JadxPass {
     7 + JadxPassType TYPE = new JadxPassType(JadxPreparePass.class);
     8 + 
     9 + void init(IRootNode root);
     10 + 
     11 + @Override
     12 + default JadxPassType getPassType() {
     13 + return TYPE;
     14 + }
     15 +}
     16 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/build.gradle.kts
     1 +plugins {
     2 + kotlin("jvm") version "1.7.20"
     3 +}
     4 + 
     5 +dependencies {
     6 + implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
     7 + 
     8 + implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
     9 + implementation("org.jetbrains.kotlin:kotlin-script-runtime")
     10 + 
     11 + implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
     12 +}
     13 + 
     14 +sourceSets {
     15 + main {
     16 + java.srcDirs("scripts", "context")
     17 + }
     18 +}
     19 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/context/stubs.kt
     1 +@file:Suppress("MayBeConstant", "unused")
     2 + 
     3 +import jadx.plugins.script.runtime.JadxScriptInstance
     4 +import mu.KotlinLogging
     5 + 
     6 +/**
     7 + * Stubs for JadxScriptBaseClass script super class
     8 + */
     9 + 
     10 +val log = KotlinLogging.logger("JadxScript")
     11 +val scriptName = "script"
     12 + 
     13 +fun getJadxInstance(): JadxScriptInstance {
     14 + throw IllegalStateException("Stub method!")
     15 +}
     16 + 
     17 +/**
     18 + * Annotations for maven imports
     19 + */
     20 +@Target(AnnotationTarget.FILE)
     21 +@Repeatable
     22 +@Retention(AnnotationRetention.SOURCE)
     23 +annotation class DependsOn(vararg val artifactsCoordinates: String, val options: Array<String> = [])
     24 + 
     25 +@Target(AnnotationTarget.FILE)
     26 +@Repeatable
     27 +@Retention(AnnotationRetention.SOURCE)
     28 +annotation class Repository(vararg val repositoriesCoordinates: String, val options: Array<String> = [])
     29 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/scripts/deobf.jadx.kts
     1 +// custom deobfuscator example
     2 + 
     3 +val jadx = getJadxInstance()
     4 +jadx.args.isDeobfuscationOn = false
     5 +jadx.args.renameFlags = emptySet()
     6 + 
     7 +val regex = """[Oo0]+""".toRegex()
     8 +var n = 0
     9 +jadx.rename.all { name, node ->
     10 + when {
     11 + name matches regex -> {
     12 + val newName = "${node.typeName()}${n++}"
     13 + println("renaming ${node.typeName()} '$node' to '$newName'")
     14 + newName
     15 + }
     16 + else -> null
     17 + }
     18 +}
     19 +jadx.afterLoad {
     20 + println("Renames count: $n")
     21 +}
     22 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/scripts/gui.jadx.kts
     1 +// customize jadx-gui
     2 + 
     3 +val jadx = getJadxInstance()
     4 + 
     5 +jadx.gui.ifAvailable {
     6 + addMenuAction("Decompile All") {
     7 + jadx.decompile.all()
     8 + }
     9 +}
     10 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts
     1 +// logger is preferred for output
     2 +log.info { "Hello from jadx script!" }
     3 + 
     4 +// println will also work (will be redirected to logger)
     5 +println("println from script '$scriptName'")
     6 + 
     7 +// get jadx decompiler script instance
     8 +val jadx = getJadxInstance()
     9 + 
     10 +// adjust options if needed
     11 +jadx.args.isDeobfuscationOn = false
     12 + 
     13 +// change names
     14 +jadx.rename.all { name ->
     15 + when (name) {
     16 + "HelloWorld" -> "HelloJadx"
     17 + else -> null
     18 + }
     19 +}
     20 + 
     21 +// run some code after loading is finished
     22 +jadx.afterLoad {
     23 + println("Loaded classes: ${jadx.classes.size}")
     24 + // print first class code
     25 + jadx.classes.firstOrNull()?.let { cls ->
     26 + println("Class: '${cls.name}'")
     27 + println(cls.code)
     28 + }
     29 +}
     30 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/scripts/replace.jadx.kts
     1 +// instructions modification example
     2 + 
     3 +import jadx.core.dex.instructions.ConstStringNode
     4 +import jadx.core.dex.instructions.InvokeNode
     5 +import jadx.core.dex.instructions.args.InsnArg
     6 + 
     7 +val jadx = getJadxInstance()
     8 + 
     9 +jadx.replace.insns { mth, insn ->
     10 + if (insn is InvokeNode) {
     11 + if (insn.callMth.shortId == "println(Ljava/lang/String;)V") {
     12 + val arg = insn.getArg(1)
     13 + val newArg = InsnArg.wrapInsnIntoArg(ConstStringNode("Jadx!"))
     14 + insn.setArg(1, newArg)
     15 + log.info { "Replace '$arg' with '$newArg' in $mth" }
     16 + }
     17 + }
     18 + null
     19 +}
     20 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/scripts/stages.jadx.kts
     1 +// insert processing passes for different decompilation stages
     2 + 
     3 +import jadx.core.dex.instructions.InsnType
     4 +import jadx.core.dex.nodes.IRegion
     5 +import java.lang.Integer.max
     6 + 
     7 +val jadx = getJadxInstance()
     8 + 
     9 +// print raw instructions
     10 +jadx.stages.rawInsns { mth, insns ->
     11 + log.info { "Instructions for method: $mth" }
     12 + for ((offset, insn) in insns.withIndex()) {
     13 + insn?.let {
     14 + log.info { " 0x${offset.hex()}: $insn" }
     15 + }
     16 + }
     17 +}
     18 + 
     19 +// access method basic blocks
     20 +jadx.stages.mthBlocks { mth, blocks ->
     21 + // count invoke instructions
     22 + var invCount = 0
     23 + for (block in blocks) {
     24 + for (insn in block.instructions) {
     25 + if (insn.type == InsnType.INVOKE) {
     26 + invCount++
     27 + }
     28 + }
     29 + }
     30 + log.info { "Invokes count in method $mth = $invCount" }
     31 +}
     32 + 
     33 +// access method regions
     34 +jadx.stages.mthRegions { mth, region ->
     35 + // recursively count max depth of nested regions
     36 + fun countRegionsDepth(region: IRegion): Int {
     37 + val subBlocks = region.subBlocks
     38 + if (subBlocks.isEmpty()) {
     39 + return 0
     40 + }
     41 + var depth = 1
     42 + for (block in subBlocks) {
     43 + if (block is IRegion) {
     44 + depth = max(depth, 1 + countRegionsDepth(block))
     45 + }
     46 + }
     47 + return depth
     48 + }
     49 + 
     50 + val depth = countRegionsDepth(region)
     51 + log.info { "Max region depth in method $mth = $depth" }
     52 + if (depth > 5) {
     53 + jadx.debug.printMethodRegions(mth, printInsns = true)
     54 + }
     55 +}
     56 + 
     57 +jadx.afterLoad {
     58 + /*
     59 + Start full decompilation (optional):
     60 + 1. jadx-cli start decompilation automatically
     61 + 2. jadx-gui start decompilation only on class open or search, so you might need to force it
     62 + */
     63 + // jadx.decompile.all()
     64 +}
     65 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts
     1 +plugins {
     2 + id("jadx-library")
     3 + 
     4 + kotlin("jvm") version "1.7.20"
     5 +}
     6 + 
     7 +dependencies {
     8 + implementation("org.jetbrains.kotlin:kotlin-scripting-common")
     9 + implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
     10 + implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
     11 + 
     12 + implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
     13 + 
     14 + implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
     15 +}
     16 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptPlugin.kt
     1 +package jadx.plugins.script
     2 + 
     3 +import jadx.api.plugins.JadxPlugin
     4 +import jadx.api.plugins.JadxPluginContext
     5 +import jadx.api.plugins.JadxPluginInfo
     6 +import jadx.api.plugins.gui.JadxGuiContext
     7 +import jadx.api.plugins.pass.JadxPassContext
     8 +import jadx.plugins.script.passes.JadxScriptAfterLoadPass
     9 +import jadx.plugins.script.runner.ScriptEval
     10 + 
     11 +class JadxScriptPlugin : JadxPlugin {
     12 + 
     13 + override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
     14 + 
     15 + override fun init(init: JadxPluginContext) {
     16 + val scriptStates = ScriptEval().process(init) ?: return
     17 + init.passContext.addPass(JadxScriptAfterLoadPass(scriptStates))
     18 + }
     19 +}
     20 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/passes/JadxScriptAfterLoadPass.kt
     1 +package jadx.plugins.script.passes
     2 + 
     3 +import jadx.api.core.nodes.IJadxDecompiler
     4 +import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
     5 +import jadx.api.plugins.pass.types.JadxAfterLoadPass
     6 +import jadx.plugins.script.runner.ScriptStates
     7 +import mu.KotlinLogging
     8 + 
     9 +private val LOG = KotlinLogging.logger {}
     10 + 
     11 +class JadxScriptAfterLoadPass(private val scriptStates: ScriptStates) : JadxAfterLoadPass {
     12 + 
     13 + override fun getInfo() = SimpleJadxPassInfo("JadxScriptAfterLoad", "Execute scripts 'afterLoad' block")
     14 + 
     15 + override fun init(decompiler: IJadxDecompiler) {
     16 + for (script in scriptStates.getScripts()) {
     17 + if (script.error) {
     18 + continue
     19 + }
     20 + try {
     21 + for (b in script.scriptData.afterLoad) {
     22 + b.invoke()
     23 + }
     24 + } catch (e: Throwable) {
     25 + script.error = true
     26 + LOG.error(e) { "Error executing 'afterLoad' block in script: ${script.scriptFile.name}" }
     27 + }
     28 + }
     29 + }
     30 +}
     31 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptEval.kt
     1 +package jadx.plugins.script.runner
     2 + 
     3 +import jadx.api.JadxDecompiler
     4 +import jadx.api.plugins.JadxPluginContext
     5 +import jadx.api.plugins.pass.JadxPassContext
     6 +import jadx.plugins.script.runtime.JadxScript
     7 +import jadx.plugins.script.runtime.JadxScriptData
     8 +import mu.KotlinLogging
     9 +import java.io.File
     10 +import kotlin.script.experimental.api.*
     11 +import kotlin.script.experimental.host.toScriptSource
     12 +import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
     13 +import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
     14 +import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
     15 + 
     16 +private val LOG = KotlinLogging.logger {}
     17 + 
     18 +class ScriptEval {
     19 + 
     20 + fun process(init: JadxPluginContext): ScriptStates? {
     21 + val jadx = init.decompiler as JadxDecompiler
     22 + val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
     23 + if (scripts.isEmpty()) {
     24 + return null
     25 + }
     26 + val scriptStates = ScriptStates()
     27 + for (scriptFile in scripts) {
     28 + val scriptData = JadxScriptData(jadx, init, scriptFile)
     29 + load(scriptFile, scriptData)
     30 + scriptStates.add(scriptFile, scriptData)
     31 + }
     32 + return scriptStates
     33 + }
     34 + 
     35 + private fun load(scriptFile: File, scriptData: JadxScriptData) {
     36 + LOG.debug { "Loading script: ${scriptFile.absolutePath}" }
     37 + val result = eval(scriptFile, scriptData)
     38 + processEvalResult(result, scriptFile)
     39 + }
     40 + 
     41 + private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics<EvaluationResult> {
     42 + val compilationConf = createJvmCompilationConfigurationFromTemplate<JadxScript>()
     43 + val evalConf = createJvmEvaluationConfigurationFromTemplate<JadxScript> {
     44 + constructorArgs(scriptData)
     45 + }
     46 + return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConf, evalConf)
     47 + }
     48 + 
     49 + private fun processEvalResult(res: ResultWithDiagnostics<EvaluationResult>, scriptFile: File) {
     50 + when (res) {
     51 + is ResultWithDiagnostics.Success -> {
     52 + val result = res.value.returnValue
     53 + if (result is ResultValue.Error) {
     54 + result.error.printStackTrace()
     55 + }
     56 + }
     57 + is ResultWithDiagnostics.Failure -> {
     58 + LOG.error { "Script execution failed: ${scriptFile.name}" }
     59 + res.reports
     60 + .filter { it.severity >= ScriptDiagnostic.Severity.ERROR }
     61 + .forEach { r ->
     62 + LOG.error(r.exception) { r.render(withSeverity = false) }
     63 + }
     64 + }
     65 + }
     66 + }
     67 +}
     68 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptStates.kt
     1 +package jadx.plugins.script.runner
     2 + 
     3 +import jadx.plugins.script.runtime.JadxScriptData
     4 +import java.io.File
     5 + 
     6 +data class ScriptStateData(
     7 + val scriptFile: File,
     8 + val scriptData: JadxScriptData,
     9 + var error: Boolean = false
     10 +)
     11 + 
     12 +class ScriptStates {
     13 + 
     14 + private val data: MutableList<ScriptStateData> = ArrayList()
     15 + 
     16 + fun add(scriptFile: File, scriptData: JadxScriptData) {
     17 + data.add(ScriptStateData(scriptFile, scriptData))
     18 + }
     19 + 
     20 + fun getScripts() = data
     21 +}
     22 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-plugin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin
     1 +jadx.plugins.script.JadxScriptPlugin
     2 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts
     1 +plugins {
     2 + id("jadx-library")
     3 + 
     4 + kotlin("jvm") version "1.7.20"
     5 +}
     6 + 
     7 +group = "jadx-script-context"
     8 + 
     9 +dependencies {
     10 + implementation("org.jetbrains.kotlin:kotlin-scripting-common")
     11 + implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
     12 + 
     13 + // allow to use maven dependencies in scripts
     14 + implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies")
     15 + implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven")
     16 + 
     17 + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
     18 + implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
     19 + 
     20 + api(project(":jadx-plugins:jadx-plugins-api"))
     21 + api(project(":jadx-core")) // TODO: workaround
     22 + 
     23 + runtimeOnly(project(":jadx-plugins:jadx-dex-input"))
     24 + runtimeOnly(project(":jadx-plugins:jadx-smali-input"))
     25 + runtimeOnly(project(":jadx-plugins:jadx-java-convert"))
     26 + runtimeOnly(project(":jadx-plugins:jadx-java-input"))
     27 + runtimeOnly(project(":jadx-plugins:jadx-raung-input"))
     28 + 
     29 +}
     30 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/debug.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.core.nodes.IMethodNode
     4 +import jadx.core.dex.nodes.MethodNode
     5 +import jadx.core.dex.visitors.DotGraphVisitor
     6 +import jadx.core.utils.DebugUtils
     7 +import jadx.plugins.script.runtime.JadxScriptInstance
     8 +import java.io.File
     9 + 
     10 +class Debug(private val jadx: JadxScriptInstance) {
     11 + 
     12 + fun printMethodRegions(mth: IMethodNode, printInsns: Boolean = false) {
     13 + DebugUtils.printRegions(mth as MethodNode, printInsns)
     14 + }
     15 + 
     16 + fun saveCFG(mth: IMethodNode, file: File = File("dump-mth-raw")) {
     17 + DotGraphVisitor.dumpRaw().save(file, mth as MethodNode)
     18 + }
     19 +}
     20 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/decompile.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.JadxArgs
     4 +import jadx.api.JavaClass
     5 +import jadx.plugins.script.runtime.JadxScriptInstance
     6 +import java.util.concurrent.Executors
     7 + 
     8 +class Decompile(private val jadx: JadxScriptInstance) {
     9 + 
     10 + fun all() {
     11 + jadx.classes.forEach(JavaClass::decompile)
     12 + }
     13 + 
     14 + fun allThreaded(threadsCount: Int = JadxArgs.DEFAULT_THREADS_COUNT) {
     15 + val executor = Executors.newFixedThreadPool(threadsCount)
     16 + val dec = jadx.internalDecompiler
     17 + val batches = dec.decompileScheduler.buildBatches(jadx.classes)
     18 + for (batch in batches) {
     19 + executor.submit {
     20 + batch.forEach(JavaClass::decompile)
     21 + }
     22 + }
     23 + }
     24 +}
     25 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/gui.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.plugins.gui.JadxGuiContext
     4 +import jadx.plugins.script.runtime.JadxScriptInstance
     5 + 
     6 +class Gui(
     7 + private val jadx: JadxScriptInstance,
     8 + private val guiContext: JadxGuiContext?
     9 +) {
     10 + 
     11 + fun isAvailable() = guiContext != null
     12 + 
     13 + fun ifAvailable(block: Gui.() -> Unit) {
     14 + guiContext?.let { this.apply(block) }
     15 + }
     16 + 
     17 + fun ui(block: () -> Unit) {
     18 + guiContext?.uiRun(block)
     19 + }
     20 + 
     21 + fun addMenuAction(name: String, action: () -> Unit) {
     22 + guiContext?.addMenuAction(name, action)
     23 + }
     24 +}
     25 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/rename.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.core.nodes.IRootNode
     4 +import jadx.core.dex.nodes.IDexNode
     5 +import jadx.core.dex.nodes.RootNode
     6 +import jadx.plugins.script.runtime.JadxScriptInstance
     7 + 
     8 +class RenamePass(private val jadx: JadxScriptInstance) {
     9 + 
     10 + fun all(makeNewName: (String) -> String?) {
     11 + all { name, _ -> makeNewName.invoke(name) }
     12 + }
     13 + 
     14 + fun all(makeNewName: (String, IDexNode) -> String?) {
     15 + jadx.addPass(object : ScriptPreparePass(jadx, "RenameAll") {
     16 + override fun init(root: IRootNode) {
     17 + val rootNode = root as RootNode
     18 + for (cls in rootNode.classes) {
     19 + makeNewName.invoke(cls.classInfo.shortName, cls)?.let {
     20 + cls.classInfo.changeShortName(it)
     21 + }
     22 + for (mth in cls.methods) {
     23 + if (mth.isConstructor) {
     24 + continue
     25 + }
     26 + makeNewName.invoke(mth.name, mth)?.let {
     27 + mth.rename(it)
     28 + }
     29 + }
     30 + for (fld in cls.fields) {
     31 + makeNewName.invoke(fld.name, fld)?.let {
     32 + fld.fieldInfo.alias = it
     33 + }
     34 + }
     35 + }
     36 + }
     37 + })
     38 + }
     39 +}
     40 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/replace.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.core.dex.instructions.args.InsnArg
     4 +import jadx.core.dex.instructions.args.InsnWrapArg
     5 +import jadx.core.dex.nodes.InsnNode
     6 +import jadx.core.dex.nodes.MethodNode
     7 +import jadx.core.utils.InsnRemover
     8 +import jadx.plugins.script.runtime.JadxScriptInstance
     9 + 
     10 +class Replace(private val jadx: JadxScriptInstance) {
     11 + 
     12 + fun insns(replace: (MethodNode, InsnNode) -> InsnNode?) {
     13 + jadx.stages.mthBlocks { mth, blocks ->
     14 + for (block in blocks) {
     15 + val insns = block.instructions
     16 + for ((i, insn) in insns.withIndex()) {
     17 + replaceSubInsns(mth, insn, replace)
     18 + replace.invoke(mth, insn)?.let {
     19 + insns[i] = it
     20 + }
     21 + }
     22 + }
     23 + }
     24 + }
     25 + 
     26 + private fun replaceSubInsns(mth: MethodNode, insn: InsnNode, replace: (MethodNode, InsnNode) -> InsnNode?) {
     27 + val argsCount = insn.argsCount
     28 + if (argsCount == 0) {
     29 + return
     30 + }
     31 + for (i in 0 until argsCount) {
     32 + val arg = insn.getArg(i)
     33 + if (arg is InsnWrapArg) {
     34 + val wrapInsn = arg.wrapInsn
     35 + replaceSubInsns(mth, wrapInsn, replace)
     36 + replace.invoke(mth, wrapInsn)?.let {
     37 + InsnRemover.unbindArgUsage(mth, arg)
     38 + insn.setArg(i, InsnArg.wrapInsnIntoArg(it))
     39 + }
     40 + }
     41 + }
     42 + }
     43 +}
     44 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/search.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.core.dex.nodes.ClassNode
     4 +import jadx.plugins.script.runtime.JadxScriptInstance
     5 + 
     6 +class Search(private val jadx: JadxScriptInstance) {
     7 + private val dec = jadx.internalDecompiler
     8 + 
     9 + fun classByFullName(fullName: String): ClassNode? {
     10 + return dec.searchClassNodeByOrigFullName(fullName)
     11 + }
     12 + 
     13 + fun classesByShortName(fullName: String): List<ClassNode> {
     14 + return dec.root.searchClassByShortName(fullName)
     15 + }
     16 +}
     17 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/stages.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.core.nodes.IMethodNode
     4 +import jadx.core.dex.nodes.BlockNode
     5 +import jadx.core.dex.nodes.InsnNode
     6 +import jadx.core.dex.nodes.MethodNode
     7 +import jadx.core.dex.regions.Region
     8 +import jadx.plugins.script.runtime.JadxScriptInstance
     9 + 
     10 +class Stages(private val jadx: JadxScriptInstance) {
     11 + 
     12 + fun rawInsns(block: (MethodNode, Array<InsnNode?>) -> Unit) {
     13 + jadx.addPass(object : ScriptOrderedDecompilePass(
     14 + jadx,
     15 + "StageRawInsns",
     16 + runAfter = listOf("start")
     17 + ) {
     18 + override fun visit(mth: IMethodNode) {
     19 + val mthNode = mth as MethodNode
     20 + mthNode.instructions?.let {
     21 + block.invoke(mthNode, it)
     22 + }
     23 + }
     24 + })
     25 + }
     26 + 
     27 + fun mthEarlyBlocks(block: (MethodNode, List<BlockNode>) -> Unit) {
     28 + mthBlocks(beforePass = "SSATransform", block)
     29 + }
     30 + 
     31 + fun mthBlocks(
     32 + beforePass: String = "RegionMakerVisitor",
     33 + block: (MethodNode, List<BlockNode>) -> Unit
     34 + ) {
     35 + jadx.addPass(object : ScriptOrderedDecompilePass(
     36 + jadx,
     37 + "StageMthBlocks",
     38 + runBefore = listOf(beforePass)
     39 + ) {
     40 + override fun visit(mth: IMethodNode) {
     41 + val mthNode = mth as MethodNode
     42 + mthNode.basicBlocks?.let {
     43 + block.invoke(mthNode, it)
     44 + }
     45 + }
     46 + })
     47 + }
     48 + 
     49 + fun mthRegions(block: (MethodNode, Region) -> Unit) {
     50 + jadx.addPass(object : ScriptOrderedDecompilePass(
     51 + jadx,
     52 + "StageMthRegions",
     53 + runBefore = listOf("PrepareForCodeGen")
     54 + ) {
     55 + override fun visit(mth: IMethodNode) {
     56 + val mthNode = mth as MethodNode
     57 + mthNode.region?.let {
     58 + block.invoke(mthNode, it)
     59 + }
     60 + }
     61 + })
     62 + }
     63 +}
     64 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/wrappers.kt
     1 +package jadx.plugins.script.runtime.data
     2 + 
     3 +import jadx.api.core.nodes.IClassNode
     4 +import jadx.api.core.nodes.IMethodNode
     5 +import jadx.api.core.nodes.IRootNode
     6 +import jadx.api.plugins.pass.JadxPass
     7 +import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
     8 +import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
     9 +import jadx.api.plugins.pass.types.JadxDecompilePass
     10 +import jadx.api.plugins.pass.types.JadxPreparePass
     11 +import jadx.plugins.script.runtime.JadxScriptInstance
     12 + 
     13 +private fun buildScriptName(jadx: JadxScriptInstance, name: String) = "JadxScript${name}(${jadx.scriptName})"
     14 + 
     15 +private fun buildSimplePassInfo(jadx: JadxScriptInstance, name: String) =
     16 + SimpleJadxPassInfo(buildScriptName(jadx, name))
     17 + 
     18 +abstract class ScriptPreparePass(
     19 + private val jadx: JadxScriptInstance, private val name: String
     20 +) : JadxPreparePass {
     21 + override fun getInfo() = buildSimplePassInfo(jadx, name)
     22 +}
     23 + 
     24 +abstract class ScriptDecompilePass(
     25 + private val jadx: JadxScriptInstance, private val name: String
     26 +) : JadxDecompilePass {
     27 + override fun getInfo() = buildSimplePassInfo(jadx, name)
     28 + 
     29 + override fun init(root: IRootNode) {
     30 + }
     31 + 
     32 + override fun visit(cls: IClassNode): Boolean {
     33 + return true
     34 + }
     35 + 
     36 + override fun visit(mth: IMethodNode) {
     37 + }
     38 +}
     39 + 
     40 +abstract class ScriptOrderedPass(
     41 + private val jadx: JadxScriptInstance,
     42 + private val name: String,
     43 + private val runAfter: List<String> = listOf(),
     44 + private val runBefore: List<String> = listOf()
     45 +) : JadxPass {
     46 + override fun getInfo(): OrderedJadxPassInfo {
     47 + val scriptName = buildScriptName(jadx, name)
     48 + return OrderedJadxPassInfo(scriptName, scriptName, runAfter, runBefore)
     49 + }
     50 +}
     51 + 
     52 +abstract class ScriptOrderedPreparePass(
     53 + jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
     54 +) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxPreparePass {}
     55 + 
     56 +abstract class ScriptOrderedDecompilePass(
     57 + jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
     58 +) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxDecompilePass {
     59 + 
     60 + override fun init(root: IRootNode) {
     61 + }
     62 + 
     63 + override fun visit(cls: IClassNode): Boolean {
     64 + return true
     65 + }
     66 + 
     67 + override fun visit(mth: IMethodNode) {
     68 + }
     69 +}
     70 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/runtime.kt
     1 +@file:Suppress("unused", "MemberVisibilityCanBePrivate")
     2 + 
     3 +package jadx.plugins.script.runtime
     4 + 
     5 +import jadx.api.JadxArgs
     6 +import jadx.api.JadxDecompiler
     7 +import jadx.api.JavaClass
     8 +import jadx.api.plugins.JadxPluginContext
     9 +import jadx.api.plugins.pass.JadxPass
     10 +import jadx.plugins.script.runtime.data.*
     11 +import mu.KLogger
     12 +import mu.KotlinLogging
     13 +import java.io.File
     14 + 
     15 + 
     16 +open class JadxScriptBaseClass(private val scriptData: JadxScriptData) {
     17 + val scriptName = scriptData.scriptName
     18 + val log = KotlinLogging.logger("JadxScript:${scriptName}")
     19 + 
     20 + fun getJadxInstance() = JadxScriptInstance(scriptData, log)
     21 + 
     22 + fun println(message: Any?) {
     23 + log.info(message?.toString())
     24 + }
     25 + 
     26 + fun print(message: Any?) {
     27 + log.info(message?.toString())
     28 + }
     29 +}
     30 + 
     31 +class JadxScriptData(
     32 + val jadxInstance: JadxDecompiler,
     33 + val pluginContext: JadxPluginContext,
     34 + val scriptFile: File
     35 +) {
     36 + val afterLoad: MutableList<() -> Unit> = ArrayList()
     37 + 
     38 + val scriptName get() = scriptFile.name.removeSuffix(".jadx.kts")
     39 +}
     40 + 
     41 +class JadxScriptInstance(
     42 + private val scriptData: JadxScriptData,
     43 + val log: KLogger
     44 +) {
     45 + private val decompiler = scriptData.jadxInstance
     46 + 
     47 + val rename: RenamePass by lazy { RenamePass(this) }
     48 + val stages: Stages by lazy { Stages(this) }
     49 + val replace: Replace by lazy { Replace(this) }
     50 + val decompile: Decompile by lazy { Decompile(this) }
     51 + val search: Search by lazy { Search(this) }
     52 + val gui: Gui by lazy { Gui(this, scriptData.pluginContext.guiContext) }
     53 + val debug: Debug by lazy { Debug(this) }
     54 + 
     55 + val args: JadxArgs
     56 + get() = decompiler.args
     57 + 
     58 + val classes: List<JavaClass>
     59 + get() = decompiler.classes
     60 + 
     61 + val scriptFile get() = scriptData.scriptFile
     62 + 
     63 + val scriptName get() = scriptData.scriptName
     64 + 
     65 + fun afterLoad(block: () -> Unit) {
     66 + scriptData.afterLoad.add(block)
     67 + }
     68 + 
     69 + fun addPass(pass: JadxPass) {
     70 + scriptData.pluginContext.passContext.addPass(pass)
     71 + }
     72 + 
     73 + val internalDecompiler: JadxDecompiler
     74 + get() = decompiler
     75 +}
     76 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/script.kt
     1 +package jadx.plugins.script.runtime
     2 + 
     3 +import kotlinx.coroutines.runBlocking
     4 +import kotlin.script.experimental.annotations.KotlinScript
     5 +import kotlin.script.experimental.api.*
     6 +import kotlin.script.experimental.dependencies.*
     7 +import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
     8 +import kotlin.script.experimental.jvm.JvmDependency
     9 +import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
     10 +import kotlin.script.experimental.jvm.jvm
     11 + 
     12 +@KotlinScript(
     13 + fileExtension = "jadx.kts",
     14 + compilationConfiguration = JadxScriptConfiguration::class
     15 +)
     16 +abstract class JadxScript
     17 + 
     18 +object JadxScriptConfiguration : ScriptCompilationConfiguration({
     19 + defaultImports(DependsOn::class, Repository::class)
     20 + 
     21 + jvm {
     22 + dependenciesFromCurrentContext(
     23 + wholeClasspath = true
     24 + )
     25 + }
     26 + 
     27 + baseClass(JadxScriptBaseClass::class)
     28 + 
     29 + refineConfiguration {
     30 + onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
     31 + }
     32 +})
     33 + 
     34 +private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
     35 + 
     36 +fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
     37 + val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)
     38 + ?.takeIf { it.isNotEmpty() }
     39 + ?: return context.compilationConfiguration.asSuccess()
     40 + return runBlocking { resolver.resolveFromScriptSourceAnnotations(annotations) }
     41 + .onSuccess {
     42 + context.compilationConfiguration.with {
     43 + dependencies.append(JvmDependency(it))
     44 + }.asSuccess()
     45 + }
     46 +}
     47 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/utils.kt
     1 +/**
     2 + * Utils for use in scripts.
     3 + * Located in default package to reduce imports.
     4 + */
     5 + 
     6 +import java.io.File
     7 + 
     8 +fun String.asFile(): File = File(this)
     9 + 
     10 +fun Int.hex(): String = Integer.toHexString(this)
     11 + 
  • ■ ■ ■ ■ ■ ■
    jadx-plugins/jadx-script/readme.md
     1 +## JADX scripting support
     2 + 
     3 +NOTE: work still in progress!
     4 + 
     5 +### Examples
     6 + 
     7 +Check script examples in `examples/scripts/` (start with `hello`)
     8 + 
     9 +### Script usage
     10 + 
     11 +#### In jadx-cli
     12 + 
     13 +Just add script file as input
     14 + 
     15 +#### In jadx-gui
     16 + 
     17 +1. Add script file to the project
     18 +2. Script will appear in `Inputs/Scripts` section
     19 +3. After script change you need to reload project (`Reload` button in toolbar or `F5`)
     20 +4. You can enable `Live reload` option in `File` menu to reload project automatically on scripts change
     21 + 
     22 +### Script development
     23 + 
     24 +Jadx-gui for now don't support autocompletion, errors highlighting, docs and code navigation
     25 +so best approach for script editing is to open jadx project in IntelliJ Idea and write your script in `examples/scripts/` folder.
     26 +Also, this will allow to debug your scripts: for that you need to create run configuration for jadx-cli or jadx-gui
     27 +add breakpoints and next run it in debug mode (jadx-gui is preferred because of faster script reload).
     28 + 
     29 +Script logs and compilation errors will appear in `Log viewer` (separate script log will be added later)
     30 + 
  • ■ ■ ■ ■ ■ ■
    settings.gradle
    1  -rootProject.name = 'jadx'
    2  - 
    3  -include 'jadx-core'
    4  -include 'jadx-cli'
    5  -include 'jadx-gui'
    6  -include 'jadx-plugins'
    7  -include 'jadx-plugins:jadx-plugins-api'
    8  -include 'jadx-plugins:jadx-dex-input'
    9  -include 'jadx-plugins:jadx-java-input'
    10  -include 'jadx-plugins:jadx-raung-input'
    11  -include 'jadx-plugins:jadx-smali-input'
    12  -include 'jadx-plugins:jadx-java-convert'
    13  - 
  • ■ ■ ■ ■ ■ ■
    settings.gradle.kts
     1 +rootProject.name = "jadx"
     2 + 
     3 +include("jadx-core")
     4 +include("jadx-cli")
     5 +include("jadx-gui")
     6 + 
     7 +include("jadx-plugins")
     8 +include("jadx-plugins:jadx-plugins-api")
     9 +include("jadx-plugins:jadx-dex-input")
     10 +include("jadx-plugins:jadx-java-input")
     11 +include("jadx-plugins:jadx-raung-input")
     12 +include("jadx-plugins:jadx-smali-input")
     13 +include("jadx-plugins:jadx-java-convert")
     14 + 
     15 +include("jadx-plugins:jadx-script:jadx-script-plugin")
     16 +include("jadx-plugins:jadx-script:jadx-script-runtime")
     17 +include("jadx-plugins:jadx-script:examples")
     18 + 
Please wait...
Page is in error, reload to recover