diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java deleted file mode 100644 index 7f3b11e0d..000000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.java +++ /dev/null @@ -1,201 +0,0 @@ -package jadx.gui.ui.codearea; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.swing.JOptionPane; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.api.JavaClass; -import jadx.api.JavaField; -import jadx.api.JavaMethod; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.gui.settings.XposedCodegenLanguage; -import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JField; -import jadx.gui.treemodel.JMethod; -import jadx.gui.treemodel.JNode; -import jadx.gui.ui.action.ActionModel; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; - -public class XposedAction extends JNodeAction { - private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class); - private static final long serialVersionUID = 2641585141624592578L; - - private static final Map PRIMITIVE_TYPE_MAPPING = Map.of( - "int", "Int", - "byte", "Byte", - "short", "Short", - "long", "Long", - "float", "Float", - "double", "Double", - "char", "Char", - "boolean", "Boolean"); - - public XposedAction(CodeArea codeArea) { - super(ActionModel.XPOSED_COPY, codeArea); - } - - @Override - public void runAction(JNode node) { - try { - String xposedSnippet = generateXposedSnippet(node); - LOG.info("Xposed snippet:\n{}", xposedSnippet); - UiUtils.copyToClipboard(xposedSnippet); - } catch (Exception e) { - LOG.error("Failed to generate Xposed code snippet", e); - JOptionPane.showMessageDialog(getCodeArea().getMainWindow(), e.getLocalizedMessage(), NLS.str("error_dialog.title"), - JOptionPane.ERROR_MESSAGE); - } - } - - @Override - public boolean isActionEnabled(JNode node) { - return node instanceof JMethod || node instanceof JClass || node instanceof JField; - } - - private String generateXposedSnippet(JNode node) { - if (node instanceof JMethod) { - return generateMethodSnippet((JMethod) node); - } - if (node instanceof JClass) { - return generateClassSnippet((JClass) node); - } - if (node instanceof JField) { - return generateFieldSnippet((JField) node); - } - throw new JadxRuntimeException("Unsupported node type: " + (node != null ? node.getClass() : "null")); - } - - private String generateMethodSnippet(JMethod jMth) { - JavaMethod javaMethod = jMth.getJavaMethod(); - MethodNode mth = javaMethod.getMethodNode(); - String methodName; - String xposedMethod; - if (mth.isConstructor()) { - xposedMethod = "findAndHookConstructor"; - methodName = ""; - } else { - xposedMethod = "findAndHookMethod"; - methodName = "\"" + mth.getMethodInfo().getName() + "\", "; - } - String rawClassName = javaMethod.getDeclaringClass().getRawName(); - String javaXposedFormatStr = - "XposedHelpers.%s(\"%s\", classLoader, %snew XC_MethodHook() {\n" - + " @Override\n" - + " protected void beforeHookedMethod(MethodHookParam param) throws Throwable {\n" - + " super.beforeHookedMethod(param);\n" - + " }\n" - + " @Override\n" - + " protected void afterHookedMethod(MethodHookParam param) throws Throwable {\n" - + " super.afterHookedMethod(param);\n" - + " }\n" - + "});"; - String kotlinXposedFormatStr = - "XposedHelpers.%s(\"%s\", classLoader, %sobject : XC_MethodHook() {\n" - + " override fun beforeHookedMethod(param: MethodHookParam) {\n" - + " super.beforeHookedMethod(param)\n" - + " }\n" - + "\n" - + " override fun afterHookedMethod(param: MethodHookParam) {\n" - + " super.afterHookedMethod(param)\n" - + " }\n" - + "})"; - - XposedCodegenLanguage language = getLanguage(); - String xposedFormatStr; - switch (language) { - case JAVA: - xposedFormatStr = javaXposedFormatStr; - break; - case KOTLIN: - xposedFormatStr = kotlinXposedFormatStr; - break; - default: - throw new JadxRuntimeException("Invalid Xposed code generation language: " + language); - } - - List mthArgs = mth.getArgTypes(); - if (mthArgs.isEmpty()) { - return String.format(xposedFormatStr, xposedMethod, rawClassName, methodName); - } - String params = mthArgs.stream() - .map(type -> fixTypeContent(type) + ".class, ") - .collect(Collectors.joining()); - return String.format(xposedFormatStr, xposedMethod, rawClassName, methodName + params); - } - - private String fixTypeContent(ArgType type) { - if (type.isGeneric()) { - return type.getObject(); - } else if (type.isGenericType() && type.isObject() && type.isTypeKnown()) { - return "Object"; - } - return type.toString(); - } - - private String generateClassSnippet(JClass jc) { - JavaClass javaClass = jc.getCls(); - String rawClassName = javaClass.getRawName(); - String shortClassName = javaClass.getName(); - - String javaXposedFormatStr = - "ClassLoader classLoader = lpparam.classLoader;\n" - + "Class %sClass = classLoader.loadClass(\"%s\");"; - String kotlinXposedFormatStr = - "val classLoader = lpparam.classLoader\n" - + "val %sClass = classLoader.loadClass(\"%s\")"; - - XposedCodegenLanguage language = getLanguage(); - String xposedFormatStr; - switch (language) { - case JAVA: - xposedFormatStr = javaXposedFormatStr; - break; - case KOTLIN: - xposedFormatStr = kotlinXposedFormatStr; - break; - default: - throw new JadxRuntimeException("Invalid Xposed code generation language: " + language); - } - - return String.format(xposedFormatStr, shortClassName, rawClassName); - } - - private String generateFieldSnippet(JField jf) { - JavaField javaField = jf.getJavaField(); - String isStatic = javaField.getAccessFlags().isStatic() ? "Static" : ""; - String type = PRIMITIVE_TYPE_MAPPING.getOrDefault(javaField.getFieldNode().getType().toString(), "Object"); - String xposedMethod = "XposedHelpers.get" + isStatic + type + "Field"; - - String javaXposedFormatStr = - "%s(/*runtimeObject*/, \"%s\");"; - String kotlinXposedFormatStr = - "%s(/*runtimeObject*/, \"%s\")"; - - XposedCodegenLanguage language = getLanguage(); - String xposedFormatStr; - switch (language) { - case JAVA: - xposedFormatStr = javaXposedFormatStr; - break; - case KOTLIN: - xposedFormatStr = kotlinXposedFormatStr; - break; - default: - throw new JadxRuntimeException("Invalid Xposed code generation language: " + language); - } - - return String.format(xposedFormatStr, xposedMethod, javaField.getFieldNode().getFieldInfo().getName()); - } - - private XposedCodegenLanguage getLanguage() { - return getCodeArea().getMainWindow().getSettings().getXposedCodegenLanguage(); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.kt b/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.kt new file mode 100644 index 000000000..3b2e12ed0 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/XposedAction.kt @@ -0,0 +1,160 @@ +package jadx.gui.ui.codearea + +import jadx.core.dex.instructions.args.ArgType +import jadx.core.dex.instructions.args.PrimitiveType +import jadx.core.utils.exceptions.JadxRuntimeException +import jadx.gui.settings.XposedCodegenLanguage +import jadx.gui.treemodel.JClass +import jadx.gui.treemodel.JField +import jadx.gui.treemodel.JMethod +import jadx.gui.treemodel.JNode +import jadx.gui.ui.action.ActionModel +import jadx.gui.utils.NLS +import jadx.gui.utils.UiUtils +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import javax.swing.JOptionPane + +class XposedAction(codeArea: CodeArea) : JNodeAction(ActionModel.XPOSED_COPY, codeArea) { + override fun runAction(node: JNode) { + try { + val xposedSnippet = generateXposedSnippet(node) + LOG.info("Xposed snippet:\n{}", xposedSnippet) + UiUtils.copyToClipboard(xposedSnippet) + } catch (e: Exception) { + LOG.error("Failed to generate Xposed code snippet", e) + JOptionPane.showMessageDialog( + getCodeArea().mainWindow, + e.localizedMessage, + NLS.str("error_dialog.title"), + JOptionPane.ERROR_MESSAGE, + ) + } + } + + override fun isActionEnabled(node: JNode?): Boolean { + return node is JMethod || node is JClass || node is JField + } + + private fun generateXposedSnippet(node: JNode): String { + return when (node) { + is JMethod -> generateMethodSnippet(node) + is JClass -> generateClassSnippet(node) + is JField -> generateFieldSnippet(node) + else -> throw JadxRuntimeException("Unsupported node type: " + node.javaClass) + } + } + + private fun generateMethodSnippet(jMethod: JMethod): String { + val javaMethod = jMethod.javaMethod + val methodNode = javaMethod.methodNode + + val xposedMethod: String + var args = methodNode.argTypes.map(::fixTypeContent) + val rawClassName = javaMethod.declaringClass.rawName + + if (methodNode.isConstructor) { + xposedMethod = "findAndHookConstructor" + } else { + xposedMethod = "findAndHookMethod" + args = listOf("\"${methodNode.methodInfo.name}\"") + args + } + + val template = when (language) { + XposedCodegenLanguage.JAVA -> + """XposedHelpers.%s("%s", classLoader, %s, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + } + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + super.afterHookedMethod(param); + } +});""" + XposedCodegenLanguage.KOTLIN -> + """XposedHelpers.%s("%s", classLoader, %s, object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + super.beforeHookedMethod(param) + } + + override fun afterHookedMethod(param: MethodHookParam) { + super.afterHookedMethod(param) + } +})""" + } + + return String.format(template, xposedMethod, rawClassName, args.joinToString(", ")) + } + + private fun fixTypeContent(type: ArgType): String { + return when { + type.isGeneric -> "\"${type.`object`}\"" + type.isGenericType && type.isObject && type.isTypeKnown -> "java.lang.Object" + type.isPrimitive -> when (language) { + XposedCodegenLanguage.JAVA -> "$type.class" + XposedCodegenLanguage.KOTLIN -> when (type.primitiveType) { + PrimitiveType.BOOLEAN -> "Boolean::class.javaPrimitiveType" + PrimitiveType.CHAR -> "Char::class.javaPrimitiveType" + PrimitiveType.BYTE -> "Byte::class.javaPrimitiveType" + PrimitiveType.SHORT -> "Short::class.javaPrimitiveType" + PrimitiveType.INT -> "Int::class.javaPrimitiveType" + PrimitiveType.FLOAT -> "Float::class.javaPrimitiveType" + PrimitiveType.LONG -> "Long::class.javaPrimitiveType" + PrimitiveType.DOUBLE -> "Double::class.javaPrimitiveType" + PrimitiveType.OBJECT -> "Any::class.java" + PrimitiveType.ARRAY -> "Array::class.java" + PrimitiveType.VOID -> "Void::class.javaPrimitiveType" + else -> throw JadxRuntimeException("Unknown or null primitive type: $type") + } + } + else -> "\"$type\"" + } + } + + private fun generateClassSnippet(jClass: JClass): String { + val javaClass = jClass.cls + val rawClassName = javaClass.rawName + val className = javaClass.name + + val template = when (language) { + XposedCodegenLanguage.JAVA -> "Class %sClass = classLoader.loadClass(\"%s\");" + XposedCodegenLanguage.KOTLIN -> "val %sClass = classLoader.loadClass(\"%s\")" + } + + return String.format(template, className, rawClassName) + } + + private fun generateFieldSnippet(jField: JField): String { + val javaField = jField.javaField + val static = if (javaField.accessFlags.isStatic) "Static" else "" + val type = PRIMITIVE_TYPE_MAPPING.getOrDefault(javaField.fieldNode.type.toString(), "Object") + val xposedMethod = "XposedHelpers.get${static}${type}Field" + + val template = when (language) { + XposedCodegenLanguage.JAVA -> "%s(/*runtimeObject*/, \"%s\");" + XposedCodegenLanguage.KOTLIN -> "%s(/*runtimeObject*/, \"%s\")" + } + + return String.format(template, xposedMethod, javaField.fieldNode.fieldInfo.name) + } + + private val language: XposedCodegenLanguage + get() = getCodeArea().mainWindow.settings.xposedCodegenLanguage + + companion object { + private val LOG: Logger = LoggerFactory.getLogger(XposedAction::class.java) + private const val serialVersionUID = 2641585141624592578L + + private val PRIMITIVE_TYPE_MAPPING = mapOf( + "int" to "Int", + "byte" to "Byte", + "short" to "Short", + "long" to "Long", + "float" to "Float", + "double" to "Double", + "char" to "Char", + "boolean" to "Boolean", + ) + } +}