fix(gui): fix Xposed args code generation (PR #2212)
* Rename .java to .kt * fix: Fix wrong function arguments when generating kotlin xposed code
This commit is contained in:
@@ -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<String, String> 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<ArgType> 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();
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user