feat(script): add code area popup menu action
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
package jadx.api.plugins.gui;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
|
||||
public interface JadxGuiContext {
|
||||
|
||||
/**
|
||||
@@ -7,5 +16,32 @@ public interface JadxGuiContext {
|
||||
*/
|
||||
void uiRun(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Add global menu entry ('Plugins' section)
|
||||
*/
|
||||
void addMenuAction(String name, Runnable action);
|
||||
|
||||
/**
|
||||
* Add code viewer popup menu entry
|
||||
*
|
||||
* @param name entry title
|
||||
* @param enabled check if entry should be enabled, called on popup creation
|
||||
* @param keyBinding optional assigned keybinding {@link KeyStroke#getKeyStroke(String)}
|
||||
*/
|
||||
void addPopupMenuAction(String name,
|
||||
@Nullable Function<ICodeNodeRef, Boolean> enabled,
|
||||
@Nullable String keyBinding,
|
||||
Consumer<ICodeNodeRef> action);
|
||||
|
||||
/**
|
||||
* Attach new key binding to main window
|
||||
*
|
||||
* @param id unique ID string
|
||||
* @param keyBinding keybinding string {@link KeyStroke#getKeyStroke(String)}
|
||||
* @param action runnable action
|
||||
* @return false if already registered
|
||||
*/
|
||||
boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action);
|
||||
|
||||
void copyToClipboard(String str);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public class JadxWrapper {
|
||||
decompiler = null;
|
||||
}
|
||||
if (guiPluginsContext != null) {
|
||||
guiPluginsContext.reset();
|
||||
resetGuiPluginsContext();
|
||||
guiPluginsContext = null;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,14 @@ public class JadxWrapper {
|
||||
decompiler.getPluginsContext().setGuiContext(guiPluginsContext);
|
||||
}
|
||||
|
||||
public GuiPluginsContext getGuiPluginsContext() {
|
||||
return guiPluginsContext;
|
||||
}
|
||||
|
||||
public void resetGuiPluginsContext() {
|
||||
guiPluginsContext.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package jadx.gui.plugins.context;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.ui.codearea.JNodeAction;
|
||||
|
||||
public class CodePopupAction {
|
||||
private final String name;
|
||||
private final Function<ICodeNodeRef, Boolean> enabledCheck;
|
||||
private final String keyBinding;
|
||||
private final Consumer<ICodeNodeRef> action;
|
||||
|
||||
public CodePopupAction(String name, Function<ICodeNodeRef, Boolean> enabled, String keyBinding, Consumer<ICodeNodeRef> action) {
|
||||
this.name = name;
|
||||
this.enabledCheck = enabled;
|
||||
this.keyBinding = keyBinding;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public JNodeAction buildAction(CodeArea codeArea) {
|
||||
return new NodeAction(this, codeArea);
|
||||
}
|
||||
|
||||
private static class NodeAction extends JNodeAction {
|
||||
private final CodePopupAction data;
|
||||
|
||||
public NodeAction(CodePopupAction data, CodeArea codeArea) {
|
||||
super(data.name, codeArea);
|
||||
if (data.keyBinding != null) {
|
||||
KeyStroke key = KeyStroke.getKeyStroke(data.keyBinding);
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Failed to parse key stroke: " + data.keyBinding);
|
||||
}
|
||||
addKeyBinding(key, data.name);
|
||||
}
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActionEnabled(@Nullable JNode node) {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
ICodeNodeRef codeNode = node.getCodeNodeRef();
|
||||
if (codeNode == null) {
|
||||
return false;
|
||||
}
|
||||
return data.enabledCheck.apply(codeNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAction(JNode node) {
|
||||
Runnable r = () -> data.action.accept(node.getCodeNodeRef());
|
||||
getCodeArea().getMainWindow().getBackgroundExecutor().execute(data.name, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,23 @@
|
||||
package jadx.gui.plugins.context;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.ui.codearea.JNodePopupBuilder;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
|
||||
@@ -15,11 +26,14 @@ public class GuiPluginsContext implements JadxGuiContext {
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
|
||||
private final List<CodePopupAction> codePopupActionList = new ArrayList<>();
|
||||
|
||||
public GuiPluginsContext(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
codePopupActionList.clear();
|
||||
JMenu pluginsMenu = mainWindow.getPluginsMenu();
|
||||
pluginsMenu.removeAll();
|
||||
pluginsMenu.setVisible(false);
|
||||
@@ -44,4 +58,40 @@ public class GuiPluginsContext implements JadxGuiContext {
|
||||
pluginsMenu.add(item);
|
||||
pluginsMenu.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPopupMenuAction(String name, @Nullable Function<ICodeNodeRef, Boolean> enabled,
|
||||
@Nullable String keyBinding, Consumer<ICodeNodeRef> action) {
|
||||
codePopupActionList.add(new CodePopupAction(name, enabled, keyBinding, action));
|
||||
}
|
||||
|
||||
public void appendPopupMenus(CodeArea codeArea, JNodePopupBuilder popup) {
|
||||
if (codePopupActionList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
popup.addSeparator();
|
||||
for (CodePopupAction codePopupAction : codePopupActionList) {
|
||||
popup.add(codePopupAction.buildAction(codeArea));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) {
|
||||
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding);
|
||||
if (keyStroke == null) {
|
||||
throw new IllegalArgumentException("Failed to parse key binding: " + keyBinding);
|
||||
}
|
||||
JPanel mainPanel = (JPanel) mainWindow.getContentPane();
|
||||
Object prevBinding = mainPanel.getInputMap().get(keyStroke);
|
||||
if (prevBinding != null) {
|
||||
return false;
|
||||
}
|
||||
UiUtils.addKeyBinding(mainPanel, keyStroke, id, action);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyToClipboard(String str) {
|
||||
UiUtils.copyToClipboard(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package jadx.gui.plugins.script
|
||||
|
||||
import com.pinterest.ktlint.core.KtLint
|
||||
import com.pinterest.ktlint.core.LintError
|
||||
import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties.indentStyleProperty
|
||||
import com.pinterest.ktlint.core.api.EditorConfigOverride
|
||||
import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider
|
||||
import org.ec4j.core.model.PropertyType
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -12,11 +15,18 @@ object KtLintUtils {
|
||||
|
||||
val rules = lazy { StandardRuleSetProvider().getRuleProviders() }
|
||||
|
||||
val configOverride = lazy {
|
||||
EditorConfigOverride.from(
|
||||
indentStyleProperty to PropertyType.IndentStyleValue.tab
|
||||
)
|
||||
}
|
||||
|
||||
fun format(code: String, fileName: String): String {
|
||||
val params = KtLint.ExperimentalParams(
|
||||
text = code,
|
||||
fileName = fileName,
|
||||
ruleProviders = rules.value,
|
||||
editorConfigOverride = configOverride.value,
|
||||
script = true,
|
||||
cb = { e: LintError, corrected ->
|
||||
if (!corrected) {
|
||||
@@ -33,6 +43,7 @@ object KtLintUtils {
|
||||
text = code,
|
||||
fileName = fileName,
|
||||
ruleProviders = rules.value,
|
||||
editorConfigOverride = configOverride.value,
|
||||
script = true,
|
||||
cb = { e: LintError, corrected ->
|
||||
if (!corrected) {
|
||||
|
||||
@@ -63,7 +63,9 @@ public class ScriptCodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
public void updateCode(String newCode) {
|
||||
int caretPos = getCaretPosition();
|
||||
setText(newCode);
|
||||
setCaretPosition(caretPos);
|
||||
scriptNode.setChanged(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.pinterest.ktlint.core.LintError;
|
||||
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic;
|
||||
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.treemodel.JInputScript;
|
||||
@@ -131,7 +132,9 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
MainWindow mainWindow = tabbedPane.getMainWindow();
|
||||
mainWindow.getBackgroundExecutor().execute(NLS.str("script.run"), () -> {
|
||||
try {
|
||||
mainWindow.getWrapper().getDecompiler().reloadPasses();
|
||||
JadxWrapper wrapper = mainWindow.getWrapper();
|
||||
wrapper.resetGuiPluginsContext();
|
||||
wrapper.getDecompiler().reloadPasses();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Passes reload failed", e);
|
||||
}
|
||||
@@ -149,23 +152,24 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
|
||||
ScriptCompiler scriptCompiler = new ScriptCompiler(fileName);
|
||||
ScriptAnalyzeResult result = scriptCompiler.analyze(code, scriptArea.getCaretPosition());
|
||||
List<ScriptDiagnostic> errors = result.getErrors();
|
||||
for (ScriptDiagnostic error : errors) {
|
||||
LOG.warn("Parse error: {}", error);
|
||||
List<ScriptDiagnostic> issues = result.getIssues();
|
||||
for (ScriptDiagnostic issue : issues) {
|
||||
LOG.warn("Compiler issue: {}", issue);
|
||||
}
|
||||
boolean success = issues.stream().map(ScriptDiagnostic::getSeverity)
|
||||
.noneMatch(s -> s == ScriptDiagnostic.Severity.ERROR || s == ScriptDiagnostic.Severity.FATAL);
|
||||
|
||||
List<LintError> lintErrs = Collections.emptyList();
|
||||
if (errors.isEmpty()) {
|
||||
if (success) {
|
||||
lintErrs = getLintIssues(code, fileName);
|
||||
}
|
||||
|
||||
errorService.clearErrors();
|
||||
errorService.addErrors(errors);
|
||||
errorService.addCompilerIssues(issues);
|
||||
errorService.addLintErrors(lintErrs);
|
||||
errorService.apply();
|
||||
boolean success = errors.isEmpty();
|
||||
if (!success) {
|
||||
resultLabel.setText("Parsing errors: " + errors.size());
|
||||
resultLabel.setText("Compiler issues: " + issues.size());
|
||||
} else if (!lintErrs.isEmpty()) {
|
||||
resultLabel.setText("Lint issues: " + lintErrs.size());
|
||||
}
|
||||
|
||||
@@ -62,20 +62,21 @@ public class ScriptErrorService extends AbstractParser {
|
||||
}
|
||||
}
|
||||
|
||||
public void addErrors(List<ScriptDiagnostic> errors) {
|
||||
for (ScriptDiagnostic error : errors) {
|
||||
public void addCompilerIssues(List<ScriptDiagnostic> issues) {
|
||||
for (ScriptDiagnostic issue : issues) {
|
||||
DefaultParserNotice notice;
|
||||
SourceCode.Location loc = error.getLocation();
|
||||
SourceCode.Location loc = issue.getLocation();
|
||||
if (loc == null) {
|
||||
notice = new DefaultParserNotice(this, error.getMessage(), 0);
|
||||
notice = new DefaultParserNotice(this, issue.getMessage(), 0);
|
||||
} else {
|
||||
try {
|
||||
int line = loc.getStart().getLine();
|
||||
int offset = scriptArea.getLineStartOffset(line - 1) + loc.getStart().getCol();
|
||||
int len = loc.getEnd() == null ? -1 : loc.getEnd().getCol() - loc.getStart().getCol();
|
||||
notice = new DefaultParserNotice(this, error.getMessage(), line, offset - 1, len);
|
||||
notice = new DefaultParserNotice(this, issue.getMessage(), line, offset - 1, len);
|
||||
notice.setLevel(convertLevel(issue.getSeverity()));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to convert script error", e);
|
||||
LOG.error("Failed to convert script issue", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -83,6 +84,20 @@ public class ScriptErrorService extends AbstractParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static ParserNotice.Level convertLevel(ScriptDiagnostic.Severity severity) {
|
||||
switch (severity) {
|
||||
case FATAL:
|
||||
case ERROR:
|
||||
return ParserNotice.Level.ERROR;
|
||||
case WARNING:
|
||||
return ParserNotice.Level.WARNING;
|
||||
case INFO:
|
||||
case DEBUG:
|
||||
return ParserNotice.Level.INFO;
|
||||
}
|
||||
return ParserNotice.Level.ERROR;
|
||||
}
|
||||
|
||||
public void addLintErrors(List<LintError> errors) {
|
||||
for (LintError error : errors) {
|
||||
try {
|
||||
|
||||
@@ -21,6 +21,7 @@ import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
@@ -164,6 +165,11 @@ public class JClass extends JLoadableNode implements JRenameNode {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNode getCodeNodeRef() {
|
||||
return cls.getClassNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return jParent;
|
||||
|
||||
@@ -16,6 +16,7 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -47,6 +48,11 @@ public class JField extends JNode implements JRenameNode {
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getCodeNodeRef() {
|
||||
return field.getFieldNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return jParent;
|
||||
|
||||
@@ -17,6 +17,7 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -53,6 +54,11 @@ public class JMethod extends JNode implements JRenameNode {
|
||||
return mth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getCodeNodeRef() {
|
||||
return mth.getMethodNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return jParent;
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
@@ -35,6 +36,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return null;
|
||||
}
|
||||
|
||||
public ICodeNodeRef getCodeNodeRef() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return null;
|
||||
|
||||
@@ -11,6 +11,7 @@ import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
@@ -40,6 +41,11 @@ public class JVariable extends JNode implements JRenameNode {
|
||||
return jMth.getRootClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeNodeRef getCodeNodeRef() {
|
||||
return var.getVarNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return jMth.getJParent();
|
||||
|
||||
@@ -120,6 +120,7 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
popup.addSeparator();
|
||||
popup.add(new FridaAction(this));
|
||||
popup.add(new XposedAction(this));
|
||||
getMainWindow().getWrapper().getGuiPluginsContext().appendPopupMenus(this, popup);
|
||||
|
||||
// move caret on mouse right button click
|
||||
popup.getMenu().addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
|
||||
@@ -11,6 +11,7 @@ dependencies {
|
||||
|
||||
// manual imports (IDE can't import dependencies by scripts annotations)
|
||||
implementation("com.github.javafaker:javafaker:1.0.2")
|
||||
implementation("org.apache.commons:commons-text:1.10.0")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import jadx.api.plugins.input.insns.Opcode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
|
||||
val renamesMap = mapOf(
|
||||
val renamesMap = mapOf(
|
||||
"specificString" to "newMethodName",
|
||||
"AA6" to "aa6Method"
|
||||
)
|
||||
@@ -14,7 +14,7 @@ val jadx = getJadxInstance()
|
||||
|
||||
var n = 0
|
||||
jadx.rename.all { _, node ->
|
||||
var newName : String? = null
|
||||
var newName: String? = null
|
||||
if (node is MethodNode) {
|
||||
// use quick instructions scanner
|
||||
node.codeReader?.visitInstructions { insn ->
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
@file:DependsOn("org.apache.commons:commons-text:1.10.0")
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef
|
||||
import jadx.core.codegen.TypeGen
|
||||
import jadx.core.dex.instructions.args.ArgType
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.core.dex.nodes.FieldNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.gui.ifAvailable {
|
||||
addPopupMenuAction(
|
||||
"Custom Frida snippet (g)",
|
||||
enabled = ::isActionEnabled,
|
||||
keyBinding = "G",
|
||||
action = ::runAction
|
||||
)
|
||||
}
|
||||
|
||||
fun isActionEnabled(node: ICodeNodeRef): Boolean {
|
||||
return node is MethodNode || node is ClassNode || node is FieldNode
|
||||
}
|
||||
|
||||
fun runAction(node: ICodeNodeRef) {
|
||||
try {
|
||||
val fridaSnippet = generateFridaSnippet(node)
|
||||
log.info { "Custom frida snippet:\n$fridaSnippet" }
|
||||
jadx.gui.copyToClipboard(fridaSnippet)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Failed to generate Frida code snippet" }
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFridaSnippet(node: ICodeNodeRef): String {
|
||||
return when (node) {
|
||||
is MethodNode -> generateMethodSnippet(node)
|
||||
is ClassNode -> generateClassSnippet(node)
|
||||
is FieldNode -> generateFieldSnippet(node)
|
||||
else -> throw JadxRuntimeException("Unsupported node type: " + node.javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateClassSnippet(cls: ClassNode): String {
|
||||
return """let ${cls.name} = Java.use("${StringEscapeUtils.escapeEcmaScript(cls.rawName)}");"""
|
||||
}
|
||||
|
||||
fun generateMethodSnippet(mthNode: MethodNode): String {
|
||||
val methodInfo = mthNode.methodInfo
|
||||
val methodName = if (methodInfo.isConstructor) {
|
||||
"\$init"
|
||||
} else {
|
||||
StringEscapeUtils.escapeEcmaScript(methodInfo.name)
|
||||
}
|
||||
val overload = if (isOverloaded(mthNode)) {
|
||||
".overload(${methodInfo.argumentsTypes.joinToString(transform = this::parseArgType)})"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val shortClassName = mthNode.parentClass.name
|
||||
val argNames = mthNode.collectArgsWithoutLoading().map { a -> a.name }
|
||||
val args = argNames.joinToString(separator = ", ")
|
||||
val logArgs = if (argNames.isNotEmpty()) {
|
||||
argNames.joinToString(separator = " + ', ' + ", prefix = " + ', ' + ") { p -> "'$p: ' + $p" }
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val clsSnippet = generateClassSnippet(mthNode.parentClass)
|
||||
return if (methodInfo.isConstructor || methodInfo.returnType == ArgType.VOID) {
|
||||
// no return value
|
||||
"""
|
||||
$clsSnippet
|
||||
$shortClassName["$methodName"]$overload.implementation = function ($args) {
|
||||
console.log('$shortClassName.$methodName is called'$logArgs);
|
||||
this["$methodName"]($args);
|
||||
};
|
||||
""".trimIndent()
|
||||
} else {
|
||||
"""
|
||||
$clsSnippet
|
||||
$shortClassName["$methodName"]$overload.implementation = function ($args) {
|
||||
console.log('$shortClassName.$methodName is called'$logArgs);
|
||||
let ret = this["$methodName"]($args);
|
||||
console.log('$shortClassName.$methodName return: ' + ret);
|
||||
return ret;
|
||||
};
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
fun generateFieldSnippet(fld: FieldNode): String {
|
||||
var rawFieldName = StringEscapeUtils.escapeEcmaScript(fld.name)
|
||||
for (methodNode in fld.parentClass.methods) {
|
||||
if (methodNode.name == rawFieldName) {
|
||||
rawFieldName = "_$rawFieldName"
|
||||
break
|
||||
}
|
||||
}
|
||||
return """
|
||||
${generateClassSnippet(fld.parentClass)}
|
||||
${fld.name} = ${fld.parentClass.name}.$rawFieldName.value;
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
fun isOverloaded(methodNode: MethodNode): Boolean {
|
||||
return methodNode.parentClass.methods.stream().anyMatch { m: MethodNode ->
|
||||
m.name == methodNode.name && methodNode.methodInfo.shortId != m.methodInfo.shortId
|
||||
}
|
||||
}
|
||||
|
||||
fun parseArgType(x: ArgType): String {
|
||||
val typeStr = if (x.isArray) {
|
||||
TypeGen.signature(x).replace("/", ".")
|
||||
} else {
|
||||
x.toString()
|
||||
}
|
||||
return "'$typeStr'"
|
||||
}
|
||||
@@ -23,7 +23,7 @@ data class ScriptCompletionResult(
|
||||
)
|
||||
|
||||
data class ScriptAnalyzeResult(
|
||||
val errors: List<ScriptDiagnostic>,
|
||||
val issues: List<ScriptDiagnostic>,
|
||||
val renderType: String?,
|
||||
val reports: List<ScriptDiagnostic>
|
||||
)
|
||||
@@ -44,7 +44,7 @@ class ScriptCompiler(private val scriptName: String) {
|
||||
val result = analyze(code.toScriptSource(scriptName), cursor)
|
||||
val analyzerResult = result.valueOrNull()
|
||||
return ScriptAnalyzeResult(
|
||||
errors = analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.toList() ?: emptyList(),
|
||||
issues = analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.toList() ?: emptyList(),
|
||||
renderType = analyzerResult?.get(ReplAnalyzerResult.renderedResultType),
|
||||
reports = result.reports
|
||||
)
|
||||
|
||||
+23
-2
@@ -1,5 +1,6 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.metadata.ICodeNodeRef
|
||||
import jadx.api.plugins.gui.JadxGuiContext
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
@@ -15,10 +16,30 @@ class Gui(
|
||||
}
|
||||
|
||||
fun ui(block: () -> Unit) {
|
||||
guiContext?.uiRun(block)
|
||||
context().uiRun(block)
|
||||
}
|
||||
|
||||
fun addMenuAction(name: String, action: () -> Unit) {
|
||||
guiContext?.addMenuAction(name, action)
|
||||
context().addMenuAction(name, action)
|
||||
}
|
||||
|
||||
fun addPopupMenuAction(
|
||||
name: String,
|
||||
enabled: (ICodeNodeRef) -> Boolean = { _ -> true },
|
||||
keyBinding: String? = null,
|
||||
action: (ICodeNodeRef) -> Unit
|
||||
) {
|
||||
context().addPopupMenuAction(name, enabled, keyBinding, action)
|
||||
}
|
||||
|
||||
fun registerGlobalKeyBinding(id: String, keyBinding: String, action: () -> Unit): Boolean {
|
||||
return context().registerGlobalKeyBinding(id, keyBinding, action)
|
||||
}
|
||||
|
||||
fun copyToClipboard(str: String) {
|
||||
context().copyToClipboard(str)
|
||||
}
|
||||
|
||||
private fun context(): JadxGuiContext =
|
||||
guiContext ?: throw IllegalStateException("GUI plugins context not available!")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user