内置手册全量示例
内置手册全量示例
这页把 JSXHook 应用内置手册中的 75 条示例 全部整理进当前文档站,并按当前文档栏目重新归类。
你可以把它当成两个东西一起用:
- 一份覆盖完整的手册示例总表
- 一份和当前 API / 模块文档互相对照的补充索引
覆盖范围
- 运行时与 Hook:17 条,主文档:运行时与 Hook / 全局函数清单 / 全局对象清单
- 反射与 Java 类操作:7 条,主文档:反射与 Java 类操作
- 加密与编码:11 条,主文档:加密与编码
- HTTP 客户端与服务:9 条,主文档:http HTTP 客户端 / httpServer HTTP 服务 / mcpServer MCP 服务
- 应用、页面与弹窗:2 条,主文档:app 对象 / 全局对象清单
- DexKit 与 APK 分析:8 条,主文档:DexKit 与 APK 分析
- ImGui 调试面板:2 条,主文档:imgui 对象
- 全局对象:1 条,主文档:全局对象清单
- 项目系统与设置脚本:4 条,主文档:项目系统 / 项目结构
- MCP 服务与编码工作流:5 条,主文档:mcpServer 对象 / 内置 MCP 工具总览
- Files / Storages / Plugins / Device:9 条,主文档:files 对象 / storages 对象 / plugins 对象 / device 对象
使用说明
- 想先看概念和行为说明,优先进每个 API 正文页。
- 想直接翻可运行示例,这页会更快。
- 某些内置手册示例偏“速查卡片”风格,因此这里会保留它原本的短小写法。
目录
- 运行时与 Hook
- 反射与 Java 类操作
- 加密与编码
- HTTP 客户端与服务
- 应用、页面与弹窗
- DexKit 与 APK 分析
- ImGui 调试面板
- 全局对象
- 项目系统与设置脚本
- MCP 服务与编码工作流
- Files / Storages / Plugins / Device
运行时与 Hook
对应主文档:运行时与 Hook / 全局函数清单 / 全局对象清单
包名与上下文
log(`package=${'$'}{lpparam.packageName}`);
log(`process=${'$'}{lpparam.processName}`);
log(`loader=${'$'}{lpparam.classLoader}`);
log(`activity=${'$'}{activity}`);
log(`context=${'$'}{context}`);
log(`modulePath=${'$'}{suparam.modulePath}`);
进程过滤
//@process=main
log(`main process=${'$'}{lpparam.processName}`);
// 其他写法:
// //@process=all
// //@process=com.tencent.mobileqq:MSF
// //@process=com.tencent.mobileqq,com.tencent.mobileqq:MSF
Hook 方法
hook({
class: "com.example.target.LoginManager",
classloader: lpparam.classLoader,
method: "login",
params: ["java.lang.String", "java.lang.String"],
before(it) {
log(`before args=${'$'}{JSON.stringify(it.args)}`);
},
after(it) {
log(`after result=${'$'}{it.result}`);
}
});
Hook 全部重载
hookAll({
class: "android.widget.Toast",
classloader: lpparam.classLoader,
method: "makeText",
before(it) {
log(`makeText args=${'$'}{JSON.stringify(it.args)}`);
}
});
Hook 构造函数
hookctor({
class: "com.example.target.UserInfo",
classloader: lpparam.classLoader,
params: ["java.lang.String"],
before(it) {
log(`ctor args=${'$'}{JSON.stringify(it.args)}`);
},
after(it) {
log(`instance=${'$'}{it.thisObject}`);
}
});
// hookcotr 是 hookctor 的兼容别名
替换返回值
replace({
class: "com.example.target.LoginManager",
classloader: lpparam.classLoader,
method: "isVip",
params: [],
replace(it) {
log("force return true");
return true;
}
});
直接 Hook Method 对象
const StringCls = imports("java.lang.String");
const method = StringCls.class.getDeclaredMethod("length");
hook(method, null, function(it) {
log(`String.length() => ${'$'}{it.result}`);
});
字符串入口的一个新变化
现在 hook / hookAll / replace 这类直接吃“类名 / 方法名”的入口,会先把 Rhino 或 Java 包装值解包,再转成真正的字符串去查找。
所以像 DexKit 结果里的 hit.name、hit.declaredClassName,通常可以直接往下传:
const hit = matches[0];
hook(
hit.declaredClassName,
lpparam.classLoader,
hit.name,
hit.paramTypeNames,
function(it) {
log(`hit=${'$'}{hit.name}`);
}
);
直接用 XposedHelpers / XposedBridge
const UserManager = XposedHelpers.findClass(
"com.example.target.UserManager",
lpparam.classLoader
);
const manager = XposedHelpers.callStaticMethod(UserManager, "getInstance");
XposedBridge.log(`manager=${'$'}{manager}`);
Shell 命令
const result = shell("pm list packages");
log(result.stdout);
log(result.stderr);
log(result.mode);
log(result.success);
日志与堆栈
log("Log from JSXHook");
print("print() also works");
console.info("info log");
console.warn("warn log");
console.error("error log");
printStackTrace();
定时器与取消
const timerId = setTimeout(function() {
log("timeout fired");
}, 500);
clearTimeout(timerId);
const intervalId = setInterval(function() {
log("interval fired");
}, 1000);
clearInterval(intervalId);
Task(function() {
log("Task alias fired");
}, 300);
加载外部 Dex
loadDex("/sdcard/Download/helper.dex");
const Helper = imports("com.example.Helper");
log(`helperVersion=${'$'}{Helper.VERSION}`);
项目 require 与路径
const helper = require("lib/helper.js");
helper.run?.();
const path = getProjectDir("lib/helper.js");
log(path);
注入当前 Activity
injectActivity(`
const Toast = imports("android.widget.Toast");
Toast.makeText(activity, "Hello from injected page", 0).show();
`);
injectActivity 页面内可用对象
// injectActivity 脚本里默认可用:
// this / activity / context / imports / importClass
injectActivity(`
const Toast = importClass("android.widget.Toast");
Toast.makeText(context, "Injected", 0).show();
`);
confirm 弹窗
// confirm 和 showGlobalDialog 是同一个宿主弹窗 API
confirm({
title: "脚本提示",
message: "启动后直接在宿主里弹原生对话框,不需要悬浮窗权限。",
confirmText: "继续",
cancelText: "取消",
onConfirm() {
log("user confirmed");
},
onCancel() {
log("user cancelled");
}
});
// 只要一个按钮也可以:
confirm({
title: "单按钮弹窗",
message: "不给 cancelText 就只显示一个确认按钮。",
confirmText: "知道了"
});
Auto.js 风格全局函数
sleep(1000);
setClip("Hello JSXHook");
log(getClip());
log(random());
log(random(0, 2));
if (getClip() === "stop") {
exit();
}
log("script still running");
反射与 Java 类操作
对应主文档:反射与 Java 类操作
导入 Java 类
const Toast = importClass("android.widget.Toast");
const ArrayList = imports("java.util.ArrayList");
const list = new ArrayList();
callMethod(list, "add", "jsxhook");
hook({
class: "android.app.Activity",
method: "onCreate",
params: ["android.os.Bundle"],
after(it) {
Toast.makeText(it.thisObject, `size=${'$'}{callMethod(list, "size")}`, 0).show();
}
});
通配导入与实例调用
imports("java.util.*");
const list = new ArrayList();
const map = new HashMap();
callMethod(list, "add", "demo");
callMethod(map, "put", "lang", getAppLanguage());
log(`size=${'$'}{callMethod(list, "size")}`);
log(`lang=${'$'}{callMethod(map, "get", "lang")}`);
查类与 invoke
const ActivityThread = findClass(
"android.app.ActivityThread",
lpparam.classLoader
);
const application = invoke(ActivityThread, "currentApplication");
log(`application=${'$'}{application}`);
方法名现在也可以直接吃 Java 返回值
callMethod / callStaticMethod / invoke 在处理 methodName 时,现在也会先解包再转字符串。
这意味着像 methodData.name、matches[0].name 这类值,通常可以直接传:
const methodName = matches[0].name;
const result = callMethod(target, methodName);
log(result);
DexKit 结果里的 isXxx 现在按布尔属性读
像下面这些 Kotlin 布尔属性:
MethodData.isConstructorMethodData.isStaticInitializerMethodData.isMethodClassData.isArray
现在在 JSXHook 里可以直接当 true / false 来读:
const hit = methodHits.firstOrNull();
if (hit && hit.isMethod) {
const method = hit.getMethodInstance(lpparam.classLoader);
log(method);
}
这里不要写成 hit.isMethod()。
不过普通 Java 方法,比如 new java.io.File(".").isFile(),还是照常当方法去调用。
字段读写
hook({
class: "com.example.target.ProfileManager",
classloader: lpparam.classLoader,
method: "updateProfile",
params: ["com.example.target.Profile"],
before(it) {
const profile = it.args[0];
log(`name=${'$'}{getField(profile, "name")}`);
setField(profile, "vip", true);
}
});
字段名现在也可以直接吃 Java 返回值
getField / setField / getStaticField / setStaticField 在处理 fieldName 时,现在同样会先解包再转字符串。
const fieldName = fieldData.name;
log(getField(profile, fieldName));
setField(profile, fieldName, "patched-by-jsxhook");
静态字段与辅助调用
log(`sdk=${'$'}{getStaticField("android.os.Build${'$'}VERSION", "SDK_INT")}`);
const ActivityThread = importClass("android.app.ActivityThread");
const application = callStaticMethod(ActivityThread, "currentApplication");
log(`application=${'$'}{application}`);
// 修改静态字段时:
// setStaticField("com.example.target.DebugFlags", "ENABLE_LOG", true);
printFields 输出
const BuildVersion = importClass("android.os.Build${'$'}VERSION");
printFields(BuildVersion, "\n");
hook({
class: "com.example.target.ProfileManager",
classloader: lpparam.classLoader,
method: "getProfile",
params: [],
after(it) {
printFields(it.thisObject, "\n");
}
});
数组与构造对象
const bytes = ByteArray(16);
const ints = newArray("int", 8);
const FileClass = findClass("java.io.File", classLoader);
const StringClass = importClass("java.lang.String").class;
// 通用构造:推荐日常直接用 this["new"](...)
const file1 = this["new"](FileClass, "/sdcard/Download/demo.txt");
// 第一参数是字符串类名时,第二参数可以显式传 ClassLoader
const fileByName = this["new"](
"java.io.File",
classLoader,
"/sdcard/Download/by-name.txt"
);
// 精确构造:已知签名或高频调用时,先拿 Constructor 再 newInstance(...)
const fileCtor = FileClass.getDeclaredConstructor(StringClass, StringClass);
fileCtor.setAccessible(true);
const file2 = fileCtor.newInstance("/sdcard", "Download/demo.txt");
log(`bytes.length=${'$'}{bytes.length}`);
log(`ints.length=${'$'}{ints.length}`);
log(`file1.exists=${'$'}{file1.exists()}`);
log(`fileByName.path=${'$'}{fileByName.getPath()}`);
log(`file2.path=${'$'}{file2.getPath()}`);
这里有个很容易写错的点:
只有当第一参数还是字符串类名时,第二参数才会被识别为“查类用的 ClassLoader”。
如果第一参数已经是 Class 或类代理,那第二个位置就会继续当构造器参数。
加密与编码
对应主文档:加密与编码
crypto 接口速查
// JSXHook 推荐统一使用 crypto.*
// 兼容别名:${'$'}crypto / ${'$'}base64
//
// Base64:
// crypto.base64.encode(text, encoding?)
// crypto.base64.decode(base64Text, encoding?)
//
// 摘要:
// crypto.digest(data, algorithm, options?)
//
// 加密 / 解密:
// crypto.encrypt(data, key, algorithm, options?)
// crypto.decrypt(data, key, algorithm, options?)
//
// 密钥:
// new crypto.Key(data, options?)
// new crypto.KeyPair(publicKey, privateKey, options?)
// crypto.generateKeyPair("RSA", 2048)
//
// options 常用字段:
// input: "string" | "bytes" | "base64" | "hex" | "file"
// output: "string" | "bytes" | "base64" | "hex" | "file"
// encoding: "utf-8"
// iv: "1234567890abcdef" 或 ByteArray
// dest: "/sdcard/Download/out.bin"
Base64 编码与解码
const text = "Hello JSXHook";
const encoded = crypto.base64.encode(text);
const decoded = crypto.base64.decode(encoded);
const gbkEncoded = crypto.base64.encode("中文", "gbk");
log(encoded);
log(decoded);
log(gbkEncoded);
摘要 MD5 / SHA
const md5 = crypto.digest("abc", "MD5");
const sha1 = crypto.digest("abc", "SHA-1");
const sha256Hex = crypto.digest("Hello JSXHook", "SHA-256", {
output: "hex"
});
const sha256Base64 = crypto.digest("Hello JSXHook", "SHA-256", {
output: "base64"
});
const sha256Bytes = crypto.digest("Hello JSXHook", "SHA-256", {
output: "bytes"
});
log(md5);
log(sha1);
log(sha256Hex);
log(sha256Base64);
log(crypto.base64.encode(sha256Bytes));
摘要输入格式与文件摘要
const rawBytes = new crypto.Key("Hello JSXHook").data;
const hexText = "48656c6c6f204a5358486f6f6b";
const base64Text = "SGVsbG8gSlNYSG9vaw==";
const filePath = "/sdcard/Download/crypto-demo.txt";
files.write(filePath, "Hello JSXHook");
log(crypto.digest(rawBytes, "MD5", { input: "bytes" }));
log(crypto.digest(hexText, "SHA-1", { input: "hex", output: "base64" }));
log(crypto.digest(base64Text, "SHA-256", { input: "base64", output: "hex" }));
log(crypto.digest(filePath, "MD5", { input: "file" }));
AES ECB 字符串加解密
const aesKey = new crypto.Key("1234567890abcdef");
const cipherText = crypto.encrypt("Hello AES", aesKey, "AES", {
output: "base64"
});
const plainText = crypto.decrypt(cipherText, aesKey, "AES", {
input: "base64",
output: "string"
});
log(cipherText);
log(plainText);
AES CBC 与 IV
const aesKey = new crypto.Key("1234567890abcdef");
const iv = "fedcba0987654321";
const cipherHex = crypto.encrypt("Hello AES CBC", aesKey, "AES/CBC/PKCS5Padding", {
iv: iv,
output: "hex"
});
const plainText = crypto.decrypt(cipherHex, aesKey, "AES/CBC/PKCS5Padding", {
input: "hex",
output: "string",
iv: iv
});
log(cipherHex);
log(plainText);
DES / 3DES / Blowfish
const desKey = new crypto.Key("12345678");
const desCipher = crypto.encrypt("Hello DES", desKey, "DES", {
output: "base64"
});
log(crypto.decrypt(desCipher, desKey, "DES", {
input: "base64",
output: "string"
}));
const tripleDesKey = new crypto.Key("123456789012345678901234");
const tripleDesCipher = crypto.encrypt("Hello 3DES", tripleDesKey, "DESede", {
output: "base64"
});
log(crypto.decrypt(tripleDesCipher, tripleDesKey, "DESede", {
input: "base64",
output: "string"
}));
const blowfishKey = new crypto.Key("12345678abcdefgh");
const blowfishCipher = crypto.encrypt("Hello Blowfish", blowfishKey, "Blowfish", {
output: "hex"
});
log(crypto.decrypt(blowfishCipher, blowfishKey, "Blowfish", {
input: "hex",
output: "string"
}));
RSA 生成密钥对
const pair = crypto.generateKeyPair("RSA", 2048);
const publicCipher = crypto.encrypt("Hello RSA", pair.publicKey, "RSA/ECB/PKCS1Padding", {
output: "base64"
});
const publicPlain = crypto.decrypt(publicCipher, pair.privateKey, "RSA/ECB/PKCS1Padding", {
input: "base64",
output: "string"
});
const privateCipher = crypto.encrypt("Hello RSA", pair.privateKey, "RSA/ECB/PKCS1Padding", {
output: "base64"
});
const privatePlain = crypto.decrypt(privateCipher, pair.publicKey, "RSA/ECB/PKCS1Padding", {
input: "base64",
output: "string"
});
log(publicPlain);
log(privatePlain);
导入已有密钥与 KeyPair
const sourcePair = crypto.generateKeyPair("RSA", 2048);
const publicKeyBase64 = crypto.base64.encode(sourcePair.publicKey.data);
const privateKeyBase64 = crypto.base64.encode(sourcePair.privateKey.data);
const publicKey = new crypto.Key(publicKeyBase64, {
input: "base64",
algorithm: "RSA"
});
const privateKey = new crypto.Key(privateKeyBase64, {
input: "base64",
algorithm: "RSA"
});
const restoredPair = new crypto.KeyPair(publicKeyBase64, privateKeyBase64, {
input: "base64",
algorithm: "RSA"
});
const cipherText = crypto.encrypt("Hello KeyPair", restoredPair.publicKey, "RSA/ECB/PKCS1Padding", {
output: "base64"
});
const plainText = crypto.decrypt(cipherText, privateKey, "RSA/ECB/PKCS1Padding", {
input: "base64",
output: "string"
});
log(plainText);
log(publicKey.data.length);
log(restoredPair.privateKey.data.length);
文件加密与解密
const sourcePath = "/sdcard/Download/plain.txt";
const encryptedPath = "/sdcard/Download/plain.aes";
const decryptedPath = "/sdcard/Download/plain.dec.txt";
const aesKey = new crypto.Key("1234567890abcdef");
files.write(sourcePath, "file payload from JSXHook");
crypto.encrypt(sourcePath, aesKey, "AES", {
input: "file",
output: "file",
dest: encryptedPath
});
crypto.decrypt(encryptedPath, aesKey, "AES", {
input: "file",
output: "file",
dest: decryptedPath
});
log(files.read(decryptedPath));
兼容别名 ${'$'}crypto / ${'$'}base64
const encoded = ${'$'}base64.encode("compat");
const key = new ${'$'}crypto.Key("1234567890abcdef");
const cipherText = ${'$'}crypto.encrypt("compat", key, "AES", {
output: "base64"
});
const plainText = ${'$'}crypto.decrypt(cipherText, key, "AES", {
input: "base64",
output: "string"
});
log(encoded);
log(plainText);
// 新脚本建议统一写 crypto.*
HTTP 客户端与服务
对应主文档:http HTTP 客户端 / httpServer HTTP 服务 / mcpServer MCP 服务
HTTP 客户端 GET
const r = http.get("www.baidu.com", {
headers: {
"Accept-Language": "zh-cn,zh;q=0.5",
"User-Agent": "JSXHook/5.2.0"
},
timeout: 15000
});
log(`code=${'$'}{r.statusCode}`);
log(`html=${'$'}{r.body.string()}`);
HTTP 客户端 POST
const res = http.post("https://example.com/login", {
username: "demo",
password: "123456"
}, {
headers: {
"X-Trace-Id": "rose-prism"
}
});
log(`code=${'$'}{res.statusCode}`);
log(`body=${'$'}{res.body.string()}`);
HTTP 客户端 JSON 与全局超时
http.setTimeout({
connectTimeout: 10000,
readTimeout: 20000
});
const r = http.postJson("https://example.com/api", {
action: "ping",
now: Date.now()
});
log(JSON.stringify(http.config()));
log(JSON.stringify(r.body.json()));
HTTP 客户端速查
// 请求入口:
// http.get(url, options)
// http.post(url, data, options)
// http.postJson(url, data, options)
// 全局超时:
// http.setTimeout(...)
// http.config()
// 返回对象:
// r.statusCode
// r.statusMessage
// r.headers
// r.header("Content-Type")
// r.body.string()
// r.body.json()
// r.body.bytes()
// 地址没写协议头时会自动补 http://
const r = http.get("www.baidu.com");
log(`url=${'$'}{r.url}`);
HTTP 服务启动
httpServer.start({
port: 18765,
allowLan: true,
token: "rose-token"
});
httpServer.get("/health", function(req) {
return {
ok: true,
pkg: req.packageName,
process: req.processName
};
});
HTTP 请求对象
httpServer.post("/echo", function(req) {
return {
method: req.method,
path: req.path,
query: req.query,
params: req.params,
body: req.body,
rawBody: req.rawBody,
ip: req.ip,
ua: req.header("user-agent"),
token: req.param("token")
};
});
HTTP 自定义响应
httpServer.post("/invoke", { mainThread: true }, function(req) {
const action = req.param("action");
if (action !== "ping") {
return {
status: 404,
body: {
code: -1,
message: "action not found"
}
};
}
return {
status: 200,
headers: {
"X-Server": "JSXHook"
},
body: {
code: 0,
message: "ok",
data: {
now: Date.now()
}
}
};
});
HTTP 路由管理
httpServer.get("/ping", function(req) {
return "pong";
});
log(JSON.stringify(httpServer.state()));
log(JSON.stringify(httpServer.listRoutes()));
httpServer.remove("/ping", "GET");
httpServer.clear();
httpServer.stop();
HTTP 访问与鉴权说明
// 开了 token 后,请求要带:
// Header: X-Module-Token: rose-token
// 或 query: ?token=rose-token
// 在手机里访问:
// http://127.0.0.1:18765/health?token=rose-token
// 在电脑里访问要用手机局域网 IP,不要用电脑自己的 127.0.0.1:
// http://<phone-ip>:18765/health?token=rose-token
// 注意:服务运行在被 hook 的目标进程里,目标 App 进程退出后服务也会消失。
应用、页面与弹窗
app.startActivity 启动 Intent
app.startActivity({
action: "android.intent.action.VIEW",
packageName: "com.ss.android.ugc.aweme",
className: "com.ss.android.ugc.aweme.app.DeepLinkHandlerActivity",
data: "aweme://user/profile/?sec_uid=MS4wLjABAAAA20naDq3xRECJSXqYrUQO7XXsLiHeRBYLL3-vON7P4KuSNjEoDBLgqgPOSV9zWfq6"
});
const result = app.startActivity({
action: "android.intent.action.VIEW",
packageName: "com.ss.android.ugc.aweme",
className: "com.ss.android.ugc.aweme.app.DeepLinkHandlerActivity",
data: "aweme://user/profile/?sec_uid=MS4wLjABAAAA20naDq3xRECJSXqYrUQO7XXsLiHeRBYLL3-vON7P4KuSNjEoDBLgqgPOSV9zWfq6",
root: true
});
log(JSON.stringify(result));
// 其他常用参数:
// type / category / categories / flags / extras / root
toast 和 app 接口
toast("Hello JSXHook");
log(`target package = ${lpparam.packageName}`);
toastLog(`target versionCode = ${app.versionCode}`);
log(`target versionName = ${app.versionName}`);
log(`jsxhook versionCode = ${app.jsxhook.versionCode}`);
log(`jsxhook versionName = ${app.jsxhook.versionName}`);
app.launchApp("微信");
app.launch("com.tencent.mm");
app.launchPackage("com.tencent.mm");
log(app.getPackageName("微信"));
log(app.getAppName("com.tencent.mm"));
app.openAppSetting("com.tencent.mm");
app.openUrl("https://www.example.com");
const apps = app.getInstalledApps({
get: ["meta_data", "permissions"],
match: ["system_only"]
});
log(JSON.stringify(apps.slice(0, 3)));
// app.uninstall("com.tencent.mobileqq");
DexKit 与 APK 分析
对应主文档:DexKit 与 APK 分析
DexKit 基础打开
const bridge = openDexKit();
try {
log(`dexCount=${'$'}{bridge.getDexNum()}`);
} finally {
bridge.close();
}
按 ClassLoader 打开 DexKit
const bridge = openDexKitByClassLoader(false);
try {
log(`bridgeValid=${'$'}{bridge.isValid()}`);
} finally {
bridge.close();
}
DexFinder 桥接
DexFinder.INSTANCE.create(lpparam.appInfo.sourceDir);
const bridge = DexFinder.getDexKitBridge();
try {
log(`bridgeValid=${'$'}{bridge != null && bridge.isValid()}`);
} finally {
DexFinder.INSTANCE.close();
}
DexKit 使用规则
// DexKit 只做静态 APK / DEX 查找,不是 Frida。
// 不要写 java.perform / java.use / Java.perform / Java.use 这类脚本。
// 找类 / 找方法 / 找字段都在静态 APK 上做。
// 多级筛选时优先用 searchInClass / searchInMethod / searchInField。
// 设置页里的 DexKit 调试默认只针对当前缓存的 APK。
// 批量字符串分组适合一组关键词里“命中任意一个都可以”的场景。
// 如果输入是 Lcom/example/Target; 这种描述符,优先用 descriptor() 精确匹配。
// 如果是设置页里的 APK 缓存,直接对缓存 APK 查找即可。
// 设置页还有“清除当前缓存”和“清除全部缓存”两个按钮。
const FindClass = imports("org.luckypray.dexkit.query.FindClass");
const ClassMatcher = imports("org.luckypray.dexkit.query.matchers.ClassMatcher");
const StringMatchType = imports("org.luckypray.dexkit.query.enums.StringMatchType");
const bridge = openDexKit();
try {
const query = FindClass.create()
.matcher(
ClassMatcher.create()
.className("com.example.Target", StringMatchType.Contains)
);
const matches = bridge.findClass(query);
log(`matches=${'$'}{matches.size()}`);
} finally {
bridge.close();
}
DexKit 查方法
const FindMethod = imports("org.luckypray.dexkit.query.FindMethod");
const MethodMatcher = imports("org.luckypray.dexkit.query.matchers.MethodMatcher");
const bridge = openDexKit();
try {
const query = FindMethod.create()
.matcher(
MethodMatcher.create()
.name("onClick")
.paramCount(1)
.addParamType("android.view.View")
);
const matches = bridge.findMethod(query);
log(`matches=${'$'}{matches.size()}`);
} finally {
bridge.close();
}
DexKit 查字段
const FindField = imports("org.luckypray.dexkit.query.FindField");
const FieldMatcher = imports("org.luckypray.dexkit.query.matchers.FieldMatcher");
const StringMatchType = imports("org.luckypray.dexkit.query.enums.StringMatchType");
const bridge = openDexKit();
try {
const query = FindField.create()
.matcher(
FieldMatcher.create()
.declaredClass("com.example.target.Profile", StringMatchType.Contains)
.name("token", StringMatchType.Contains)
.type("java.lang.String", StringMatchType.Equals)
);
const matches = bridge.findField(query);
log(`matches=${'$'}{matches.size()}`);
} finally {
bridge.close();
}
DexKit 多级筛选
// 把上一轮命中的结果继续筛下去。
const FindClass = imports("org.luckypray.dexkit.query.FindClass");
const FindMethod = imports("org.luckypray.dexkit.query.FindMethod");
const FindField = imports("org.luckypray.dexkit.query.FindField");
const ClassMatcher = imports("org.luckypray.dexkit.query.matchers.ClassMatcher");
const MethodMatcher = imports("org.luckypray.dexkit.query.matchers.MethodMatcher");
const FieldMatcher = imports("org.luckypray.dexkit.query.matchers.FieldMatcher");
const StringMatchType = imports("org.luckypray.dexkit.query.enums.StringMatchType");
const bridge = openDexKit();
try {
const classHits = bridge.findClass(
FindClass.create().matcher(
ClassMatcher.create().className("login", StringMatchType.Contains)
)
);
const methodHits = bridge.findMethod(
FindMethod.create()
.searchInClass(classHits)
.matcher(
MethodMatcher.create()
.name("login", StringMatchType.Contains)
.paramCount(1)
)
);
const fieldHits = bridge.findField(
FindField.create()
.searchInClass(classHits)
.matcher(
FieldMatcher.create()
.name("token", StringMatchType.Contains)
)
);
// If you already have fieldHits, you can continue with searchInField(fieldHits).
log(`classes=${'$'}{classHits.size()}, methods=${'$'}{methodHits.size()}, fields=${'$'}{fieldHits.size()}`);
} finally {
bridge.close();
}
DexKit 批量字符串分组
const FindClass = imports("org.luckypray.dexkit.query.FindClass");
const BatchFindMethodUsingStrings = imports("org.luckypray.dexkit.query.BatchFindMethodUsingStrings");
const ClassMatcher = imports("org.luckypray.dexkit.query.matchers.ClassMatcher");
const StringMatchType = imports("org.luckypray.dexkit.query.enums.StringMatchType");
const bridge = openDexKit();
try {
const classHits = bridge.findClass(
FindClass.create().matcher(
ClassMatcher.create().className("login", StringMatchType.Contains)
)
);
const query = BatchFindMethodUsingStrings.create()
.searchInClasses(classHits)
.addSearchGroup("loginFlow", ["login", "signIn", "auth"])
.addSearchGroup("profileFlow", ["profile", "avatar", "nickname"]);
const groups = bridge.batchFindMethodUsingStrings(query);
log(`group count=${'$'}{groups.size()}`);
log(`loginFlow hits=${'$'}{groups.get("loginFlow").size()}`);
} finally {
bridge.close();
}
ImGui 调试面板
对应主文档:imgui 对象
ImGui 调试面板
const themes = imgui.themes();
const defaultThemeIndex = Math.max(0, themes.indexOf("velvet_rose"));
const ui = imgui.window("调试面板", {
width: 560,
height: 820,
x: 48,
y: 160,
fontScale: 1.40,
uiScale: 1.14,
theme: "velvet_rose",
resizable: true,
windowCollapsible: true,
antiCapture: false,
touchPassthrough: false,
minSize: [420, 260]
});
ui
.separatorText("运行信息")
.text("当前包名: " + lpparam.packageName)
.textWrapped("顶部可以拖动移动,右下角可以拖动缩放,现在还支持 switch、table 和方形拾色器。")
.tabs("panel_tabs", function(tabs) {
tabs.tab("基础", function(tab) {
tab.itemWidth(320);
tab.inputText("备注", "remark", "", {
hint: "这里输入备注",
multiline: true
});
tab.switch("启用功能", "enabled", true);
tab.combo("主题", "theme_index", themes, defaultThemeIndex, function(index) {
ui.set("theme_index", index);
ui.theme(themes[index]);
});
tab.colorEdit("强调色", "accent_color", "#FF78B8", {
alpha: false
});
tab.colorPicker("方形拾色器", "square_picker", "#FF8BC6FF", {
alpha: false,
inputs: true,
pickerHueWheel: false
});
});
tabs.tab("布局", function(tab) {
tab.table("info_table", ["字段", "值"], { border: true, rowBg: true }, function(table) {
table.text("包名");
table.nextColumn();
table.text(lpparam.packageName);
table.nextRow();
table.text("主题");
table.nextColumn();
table.text(themes[ui.get("theme_index", defaultThemeIndex)] || themes[0]);
});
});
})
.textDisabled("可选开启 antiCapture 防录屏,touchPassthrough 可让窗口本身触摸穿透。")
.button("打印状态", function(state) {
log(JSON.stringify(state, null, 4));
})
.show();
ImGui 控件与布局总览
const demoId = "imgui_all_widgets_demo";
const helperId = "imgui_helper_panel";
const themes = imgui.themes();
const defaultTheme = themes.indexOf("rose_prism") >= 0 ? "rose_prism" : themes[0];
const defaultThemeIndex = Math.max(0, themes.indexOf(defaultTheme));
const ui = imgui.window(demoId, {
width: 620,
height: 920,
x: 52,
y: 148,
fontScale: 1.40,
uiScale: 1.14,
theme: defaultTheme,
resizable: true,
windowCollapsible: true,
antiCapture: false,
touchPassthrough: false,
minWidth: 460,
minHeight: 320,
maxWidth: 1180,
maxHeight: 1600
});
function mark(text) {
ui.set("last_action", text);
log("[ImGuiDemo] " + text);
}
function applyTheme(index) {
const safeIndex = Math.max(0, Math.min(index, themes.length - 1));
const themeName = themes[safeIndex];
ui.set("theme_index", safeIndex);
ui.set("theme_name", themeName);
ui.theme(themeName);
mark("主题切换为 " + themeName);
}
function showHelperPanel() {
imgui.window(helperId, {
width: 360,
height: 260,
x: 730,
y: 180,
fontScale: 1.24,
uiScale: 1.06,
theme: ui.get("theme_name", defaultTheme),
resizable: true,
windowCollapsible: true,
antiCapture: false,
touchPassthrough: false
})
.clear()
.separatorText("辅助面板")
.text("这个窗口用来演示 imgui.close / imgui.isShown。")
.text("主面板可见: " + imgui.isShown(demoId))
.text("当前主题: " + ui.get("theme_name", defaultTheme))
.button("关闭辅助面板", function() {
imgui.close(helperId);
})
.show();
}
function renderCompact() {
ui
.clear()
.title("ImGui 简版面板")
.size(520, 360)
.position(48, 140)
.separatorText("clear() 已生效")
.text("这是调用 ui.clear() 后重新绘制的简版布局。")
.textWrapped("你可以点击下面的按钮恢复完整示例,或者关闭辅助面板。")
.button("恢复完整 Demo", function() {
renderFull();
})
.sameLine()
.smallButton("关闭辅助面板", function() {
imgui.close(helperId);
})
.show();
}
function renderFull() {
ui
.clear()
.title(ui.get("panel_title", "ImGui 控件与布局总览"))
.size(ui.get("panel_w", 620), ui.get("panel_h", 920))
.minSize(460, 320)
.maxSize(1180, 1600)
.position(ui.get("panel_x", 52), ui.get("panel_y", 148))
.alpha(ui.get("panel_alpha", 0.94))
.fontScale(ui.get("font_scale", 1.40))
.uiScale(ui.get("panel_scale", 1.14))
.theme(ui.get("theme_name", defaultTheme))
.closable(ui.get("closable", true))
.windowCollapsible(ui.get("window_collapsible", true))
.resizable(ui.get("resizable", true))
.antiCapture(ui.get("anti_capture", false))
.touchPassthrough(ui.get("touch_passthrough", false))
.separatorText("别名速记")
.textWrapped("w=width, h=height, pos/moveTo=position, resize/resizeTo=size, wrappedText=textWrapped, coloredText=textColored, disabledText=textDisabled, secure=antiCapture, widgetScale=uiScale, zoom=scale, touchPassthrough=触摸穿透。")
.tabs("all_tabs", function(tabs) {
tabs.tab("窗口", function(tab) {
tab.itemWidth(320);
tab.inputText("窗口标题", "panel_title", ui.get("panel_title", "ImGui 控件与布局总览"), {
hint: "点击按钮应用 title()"
});
tab.inputInt("宽度缓存", "panel_w", ui.get("panel_w", 620), 10, 50);
tab.inputInt("高度缓存", "panel_h", ui.get("panel_h", 920), 10, 50);
tab.sliderInt("X", "panel_x", 0, 1200, ui.get("panel_x", 52));
tab.sliderInt("Y", "panel_y", 0, 2600, ui.get("panel_y", 148));
tab.sliderFloat("透明度", "panel_alpha", 0.35, 1.0, ui.get("panel_alpha", 0.94));
tab.dragFloat("控件缩放", "panel_scale", 0.80, 1.80, ui.get("panel_scale", 1.14), 0.01);
tab.dragFloat("字体缩放", "font_scale", 1.00, 2.20, ui.get("font_scale", 1.40), 0.01);
tab.sliderFloat("整体缩放", "zoom_value", 0.80, 1.80, ui.get("zoom_value", 1.14));
tab.checkbox("允许关闭按钮", "closable", ui.get("closable", true), function(value) {
ui.closable(value);
ui.set("closable", value);
});
tab.checkbox("允许整窗折叠", "window_collapsible", ui.get("window_collapsible", true), function(value) {
ui.windowCollapsible(value);
ui.set("window_collapsible", value);
});
tab.checkbox("允许右下角缩放", "resizable", ui.get("resizable", true), function(value) {
ui.resizable(value);
ui.set("resizable", value);
});
tab.checkbox("防录屏", "anti_capture", ui.get("anti_capture", false), function(value) {
ui.antiCapture(value);
ui.set("anti_capture", value);
});
tab.checkbox("触摸穿透", "touch_passthrough", ui.get("touch_passthrough", false), function(value) {
ui.touchPassthrough(value);
ui.set("touch_passthrough", value);
});
tab.combo("主题", "theme_index", themes, ui.get("theme_index", defaultThemeIndex), function(index) {
applyTheme(index);
});
tab.combo("预设", "preset_index", ["标准", "柔雾", "晶透"], ui.get("preset_index", 0), function(index) {
if (index === 0) {
ui.alpha(0.94);
ui.set("panel_alpha", 0.94);
ui.uiScale(1.14);
ui.set("panel_scale", 1.14);
} else if (index === 1) {
ui.alpha(0.88);
ui.set("panel_alpha", 0.88);
ui.uiScale(1.06);
ui.set("panel_scale", 1.06);
} else {
ui.alpha(0.98);
ui.set("panel_alpha", 0.98);
ui.uiScale(1.22);
ui.set("panel_scale", 1.22);
}
mark("切换预设 #" + index);
});
tab.button("title()", function(state) {
ui.title(state.panel_title || "ImGui 控件与布局总览");
mark("调用 title()");
});
tab.sameLine();
tab.smallButton("size()", function(state) {
ui.size(state.panel_w, state.panel_h);
mark("调用 size()");
});
tab.sameLine();
tab.smallButton("resizeTo()", function(state) {
ui.resizeTo(state.panel_w, state.panel_h);
mark("调用 resizeTo()");
});
tab.button("w/h/x/y 逐项应用", function(state) {
ui.w(state.panel_w);
ui.h(state.panel_h);
ui.x(state.panel_x);
ui.y(state.panel_y);
mark("调用 w/h/x/y");
});
tab.sameLine();
tab.smallButton("pos()", function(state) {
ui.pos(state.panel_x, state.panel_y);
mark("调用 pos()");
});
tab.sameLine(0, 16);
tab.smallButton("moveTo()", function(state) {
ui.moveTo(state.panel_x, state.panel_y);
mark("调用 moveTo()");
});
tab.button("fontScale()", function(state) {
ui.fontScale(state.font_scale);
ui.set("font_scale", state.font_scale);
mark("调用 fontScale()");
});
tab.sameLine();
tab.smallButton("widgetScale()", function(state) {
ui.widgetScale(state.panel_scale);
ui.set("panel_scale", state.panel_scale);
mark("调用 widgetScale()");
});
tab.sameLine();
tab.smallButton("zoom()", function(state) {
ui.zoom(state.zoom_value);
ui.set("zoom_value", state.zoom_value);
mark("调用 zoom()");
});
tab.button("minSize/maxSize", function() {
ui.minSize(460, 320);
ui.maxSize(1180, 1600);
ui.minWidth(460);
ui.minHeight(320);
ui.maxWidth(1180);
ui.maxHeight(1600);
mark("调用 min/max 尺寸相关方法");
});
tab.sameLine();
tab.smallButton("打开辅助面板", function() {
showHelperPanel();
});
tab.text("imgui.isShown(demoId): " + imgui.isShown(demoId));
tab.text("ui.isShown(): " + ui.isShown());
});
tabs.tab("文本布局", function(tab) {
tab.separatorText("文本输出");
tab.text("text: 普通文本");
tab.textWrapped("textWrapped: 这是一段自动换行的长文本,适合放说明、日志摘要或调试提示。");
tab.wrappedText("wrappedText: 它就是 textWrapped 的别名。");
tab.textColored("textColored: 粉晶强调文本", "#FFFF74B8");
tab.coloredText("coloredText: 也是 textColored 的别名。", "#FF7AA8FF");
tab.textDisabled("textDisabled: 适合弱提示。");
tab.disabledText("disabledText: 同样是别名写法。");
tab.bullet("bullet: 第一条");
tab.bullet("bullet: 第二条");
tab.separator();
tab.separatorText("同一行与间距");
tab.text("左侧");
tab.sameLine();
tab.smallButton("sameLine()", function() {
mark("点击了 sameLine 按钮");
});
tab.sameLine(0, 16);
tab.text("右侧");
tab.spacing(1);
tab.newLine();
tab.text("dummy 上方");
tab.dummy(0, 18);
tab.text("dummy 下方");
tab.separatorText("缩进与分组");
tab.indent(18);
tab.text("indent(18) 之后的文本");
tab.unindent(18);
tab.group(function(group) {
group.text("group(callback)");
group.smallButton("组内按钮 A", function() {
mark("点击了组内按钮 A");
});
group.sameLine();
group.smallButton("组内按钮 B", function() {
mark("点击了组内按钮 B");
});
});
tab.beginGroup();
tab.text("beginGroup / endGroup");
tab.smallButton("手动 Group", function() {
mark("点击了手动 Group 按钮");
});
tab.endGroup();
tab.separatorText("游标与 next item width");
tab.child("定位演示", "cursor_demo", { height: 170, border: true }, function(box) {
box.text("默认流式布局");
box.cursorPos(18, 52);
box.smallButton("cursorPos(18, 52)", function() {
mark("点击了 cursorPos 按钮");
});
box.cursorX(220);
box.smallButton("cursorX(220)", function() {
mark("点击了 cursorX 按钮");
});
box.cursorY(112);
box.textColored("cursorY(112) 放到这里", "#FFFF9BC5");
box.newLine();
box.itemWidth(220);
box.inputFloat("itemWidth 生效", "child_width_input", 12.5, 0.1, 1.0);
});
tab.separatorText("禁用区域");
tab.disabled(function(disabledBox) {
disabledBox.button("disabled(callback)", 220, 42, function() {
});
});
tab.beginDisabled(true);
tab.smallButton("beginDisabled / endDisabled", function() {
});
tab.endDisabled();
});
tabs.tab("输入数值", function(tab) {
tab.itemWidth(330);
tab.inputText("多行备注", "remark", "", {
hint: "multiline=true",
multiline: true,
onChange: function(text) {
ui.set("draft_text", text);
}
});
tab.inputText("回车提交", "submit_text", "", {
hint: "submitEnter=true",
submitEnter: true,
onSubmit: function(text) {
mark("onSubmit -> " + text);
}
});
tab.inputText("密码框", "password_text", "", {
hint: "password=true",
password: true
});
tab.inputText("数字文本", "decimal_text", "12.34", {
decimal: true
});
tab.inputText("只读内容", "readonly_preview", ui.get("readonly_preview", "点击状态页按钮刷新"), {
readOnly: true
});
tab.inputInt("inputInt", "input_int", 3, 1, 10, function(value) {
mark("inputInt -> " + value);
});
tab.inputFloat("inputFloat", "input_float", 0.25, 0.05, 0.25, function(value) {
mark("inputFloat -> " + value);
});
tab.sliderInt("sliderInt", "slider_int", 0, 100, 32, function(value) {
ui.set("slider_int", value);
});
tab.sliderFloat("sliderFloat", "slider_float", 0.0, 1.0, 0.68, function(value) {
ui.set("slider_float", value);
});
tab.sliderAngle("sliderAngle", "angle_value", -180, 180, 24, function(value) {
ui.set("angle_value", value);
});
tab.dragInt("dragInt", "drag_int", 0, 240, 48, 1, function(value) {
ui.set("drag_int", value);
});
tab.dragFloat("dragFloat", "drag_float", 0.0, 1.0, 0.52, 0.005, function(value) {
ui.set("drag_float", value);
});
tab.progress(0.36, "progress()");
tab.progressBar(0.72, "progressBar()");
});
tabs.tab("选择颜色", function(tab) {
tab.button("button(220, 46)", 220, 46, function() {
mark("点击了普通按钮");
});
tab.sameLine();
tab.smallButton("smallButton()", function() {
mark("点击了 smallButton");
});
tab.checkbox("checkbox", "flag_enabled", true, function(value) {
mark("checkbox -> " + value);
});
tab.toggle("toggle", "flag_toggle", false, function(value) {
mark("toggle -> " + value);
});
tab.switch("switch", "flag_switch", true, function(value) {
mark("switch -> " + value);
});
tab.selectable("selectable", "flag_selectable", true, function(value) {
mark("selectable -> " + value);
});
tab.combo("combo", "combo_index", ["晨雾", "粉晶", "月光"], 1, function(index) {
mark("combo -> " + index);
});
tab.listBox("listBox", "list_index", ["苹果", "桃子", "草莓", "樱花"], 2, { visibleItems: 4 });
tab.radioGroup("radioGroup", "radio_index", ["普通", "强调", "静默"], 0, function(index) {
mark("radioGroup -> " + index);
});
tab.colorEdit("colorEdit", "accent_color", "#FFFF79B8", {
alpha: false,
onChange: function(hex) {
mark("colorEdit -> " + hex);
}
});
tab.colorPicker("colorPicker(wheel)", "glow_color", "#CCFFE2F4", {
alpha: true,
alphaBar: true,
inputs: true,
pickerHueWheel: true,
onChange: function(hex) {
mark("colorPicker -> " + hex);
}
});
tab.colorPicker("colorPicker(square)", "square_color", "#FF7AD2C8", {
alpha: false,
inputs: true,
pickerHueWheel: false,
onChange: function(hex) {
mark("squarePicker -> " + hex);
}
});
});
tabs.tab("容器导航", function(tab) {
tab.collapsible("collapsible(callback)", { defaultOpen: true }, function(section) {
section.text("这里是 collapsible(callback)");
});
tab.beginCollapsible("beginCollapsible", { defaultOpen: true });
tab.text("这里是 beginCollapsible / endCollapsible");
tab.endCollapsible();
tab.tree("tree(callback)", { defaultOpen: true }, function(section) {
section.text("tree(callback) 与 collapsible 类似");
});
tab.beginTree("beginTree", { defaultOpen: true });
tab.text("这里是 beginTree / endTree");
tab.endTree();
tab.treeNode("treeNode(alias)", { defaultOpen: true }, function(node) {
node.text("treeNode 是 tree 的别名风格。");
});
tab.collapsingHeader("collapsingHeader", { defaultOpen: true }, function(header) {
header.text("这是带头部样式的折叠区域。");
});
tab.child("child(callback)", "callback_child", { height: 130, border: true }, function(box) {
box.text("child(callback) 区域");
box.button("子区域按钮", function() {
mark("点击了 child(callback) 按钮");
});
});
tab.beginChild("beginChild", "manual_child", 0, 110, { border: true });
tab.text("这里是 beginChild / endChild");
tab.smallButton("manual child", function() {
mark("点击了 manual child");
});
tab.endChild();
tab.separatorText("columns(callback)");
tab.columns("callback_columns", 2, { border: true }, function(col) {
col.text("第一列");
col.nextColumn();
col.textColored("第二列", "#FFFF86C6");
});
tab.separatorText("beginColumns / nextColumn / endColumns");
tab.beginColumns("manual_columns", 3, { border: true });
tab.text("列 1");
tab.nextColumn();
tab.text("列 2");
tab.nextColumn();
tab.text("列 3");
tab.endColumns();
tab.separatorText("table(callback)");
tab.table("demo_table", ["名称", "模式", "值"], { border: true, rowBg: true }, function(table) {
table.text("Alpha");
table.nextColumn();
table.text("Switch");
table.nextColumn();
table.text(ui.get("flag_switch", true) ? "开启" : "关闭");
table.nextRow();
table.text("Theme");
table.nextColumn();
table.text("Combo");
table.nextColumn();
table.text(ui.get("theme_name", defaultTheme));
});
tab.separatorText("beginTable / tableNextRow / endTable");
tab.beginTable("manual_table", ["键", "当前值"], { border: true, rowBg: true });
tab.text("透明度");
tab.nextColumn();
tab.text("" + ui.get("panel_alpha", 0.94));
tab.tableNextRow();
tab.text("当前主题");
tab.nextColumn();
tab.text(ui.get("theme_name", defaultTheme));
tab.endTable();
tab.separatorText("beginTabBar / beginTab / endTab");
tab.beginTabBar("manual_tabbar");
tab.beginTab("Tab A");
tab.text("这是手动 tab bar 的第一页。");
tab.endTab();
tab.beginTab("Tab B");
tab.text("这是手动 tab bar 的第二页。");
tab.endTab();
tab.endTabBar();
});
tabs.tab("图表状态", function(tab) {
tab.plotLines("plotLines", [0.15, 0.28, 0.42, 0.37, 0.66, 0.58, 0.82], {
overlay: "trend",
height: 110
});
tab.plotHistogram("plotHistogram", [1, 4, 2, 6, 3, 5, 7], {
overlay: "hist",
height: 110
});
tab.separatorText("ui.set / ui.get / ui.state");
tab.text("last_action: " + ui.get("last_action", "初始化完成"));
tab.text("theme_name: " + ui.get("theme_name", defaultTheme));
tab.text("辅助面板可见: " + imgui.isShown(helperId));
tab.button("remark -> readonly_preview", function() {
ui.set("readonly_preview", ui.get("remark", ""));
ui.set("last_action", "已同步 readonly_preview");
renderFull();
});
tab.sameLine();
tab.smallButton("打印 ui.state()", function() {
log(JSON.stringify(ui.state(), null, 4));
});
tab.button("打开辅助面板", function() {
showHelperPanel();
});
tab.sameLine();
tab.smallButton("关闭辅助面板", function() {
imgui.close(helperId);
});
tab.button("切到简版布局(clear)", function() {
renderCompact();
});
tab.sameLine();
tab.smallButton("ui.close()", function() {
ui.close();
});
tab.sameLine(0, 16);
tab.smallButton("imgui.closeAll()", function() {
imgui.closeAll();
});
});
})
.show();
}
ui.onClose(function(state) {
log("[ImGuiDemo] 面板关闭,备注=" + (state.remark || ""));
});
ui.set("theme_index", defaultThemeIndex);
ui.set("theme_name", defaultTheme);
ui.set("panel_title", "ImGui 控件与布局总览");
ui.set("panel_w", 620);
ui.set("panel_h", 920);
ui.set("panel_x", 52);
ui.set("panel_y", 148);
ui.set("panel_alpha", 0.94);
ui.set("panel_scale", 1.14);
ui.set("font_scale", 1.40);
ui.set("zoom_value", 1.14);
ui.set("closable", true);
ui.set("window_collapsible", true);
ui.set("resizable", true);
ui.set("anti_capture", false);
ui.set("readonly_preview", "点击状态页按钮刷新");
ui.set("last_action", "初始化完成");
renderFull();
全局对象
对应主文档:全局对象清单
内置全局对象
log(`modulePath=${'$'}{suparam.modulePath}`);
log(`context=${'$'}{context}`);
log(`activity=${'$'}{activity}`);
log(`app=${'$'}{app}`);
log(`self=${'$'}{this}`);
log(`httpClient=${'$'}{http}`);
log(`httpServer=${'$'}{httpServer}`);
log(`application=${'$'}{XpHelper.context}`);
log(`bridge=${'$'}{XposedBridge}`);
log(`helpers=${'$'}{XposedHelpers}`);
log(`dexkit=${'$'}{DexKitBridge}`);
log(`classLoader=${'$'}{classLoader}`);
项目系统与设置脚本
init.js 与 main.js
// init.js:项目元信息
project = {
name: "示例项目",
description: "JSXHook sample project",
scope: ["com.example.app"]
};
// main.js:项目入口
log(`project=${'$'}{project.name}`);
设置脚本 @set
//@set
function buildSettings() {
log("settings page script");
print("print works here too");
}
设置脚本可用 API
// 设置脚本页默认可用:
// this / activity / context / enableEdgeToEdge / log / print / shell / imports
//@set
function buildSettings() {
enableEdgeToEdge();
const Build = imports("android.os.Build");
log(`model=${'$'}{Build.MODEL}`);
}
语言与快速索引
log(`lang=${'$'}{getAppLanguage()}`);
// Hook: hook / hookAll / hookctor / hookcotr / replace
// Java: imports / importClass / findClass / new / newArray / ByteArray
// Reflection: getField / setField / getStaticField / setStaticField / printFields / Class.getDeclaredConstructor
// Calls: invoke / callMethod / callStaticMethod
// Project: require / getProjectDir / injectActivity
// App: app.versionCode / app.versionName (target package version) / app.jsxhook.* (JSXHook version) / app.launch* / app.getInstalledApps / app.startActivity
// Storage: storages.create / storages.remove / storage.get / put / remove / contains / clear
// Device: device.width / device.height / device.brand / device.model / device.getAndroidId / device.getBattery / device.keepScreenOn
// ImGui: imgui.window / imgui.close / imgui.closeAll / imgui.themes / ui.windowCollapsible / ui.antiCapture / ui.minSize / ui.maxSize / ui.tabs / ui.tree / ui.columns / ui.beginTable / ui.table / ui.nextRow / ui.child / ui.disabled / ui.cursorPos / ui.cursorX / ui.cursorY / ui.itemWidth / ui.sliderAngle / ui.dragFloat / ui.combo / ui.switch / ui.toggle / ui.colorEdit / ui.colorPicker / ui.listBox / ui.plotLines / ui.plotHistogram
// Plugins: plugins.load / plugins.unload / plugins.unloadAll (Auto.js 风格纯 Java/JNI 插件兼容)
// Dialog: confirm / showGlobalDialog / showHostDialog
// HTTP Client: http.get / post / postJson / setTimeout / config / r.statusCode / r.body.string / r.body.json / r.body.bytes
// HTTP Server: httpServer.start / stop / get / post / put / delete / patch / any / state / listRoutes / remove / clear
// MCP Server: mcpServer.start / stop / state / quickTool / tool / resource / resourceTemplate / prompt / listTools / listResources / listPrompts / clearAll
// Dex: loadDex / openDexKit / openDexKitByClassLoader / DexFinder / DexKitBridge (static APK search, not Frida)
// Runtime: log / print / toast / toastLog / console / printStackTrace / shell / setTimeout / clearTimeout / setInterval / clearInterval / Task / getAppLanguage
// Globals: lpparam / classLoader / suparam / activity / context / app / files / storages / device / plugins / confirm / showGlobalDialog / showHostDialog / toast / toastLog / this / http / httpServer / mcpServer / XpHelper / XposedHelpers / XposedBridge
MCP 服务与编码工作流
对应主文档:mcpServer 对象 / 内置 MCP 工具总览
MCP 服务启动
const state = mcpServer.start({
port: 5698,
allowLan: true,
token: "",
endpoint: "/mcp",
name: "jsxhook-tools",
version: "1.0.0",
instructions: "这是一个由 JSXHook 脚本提供的 MCP 服务"
});
log(state.url);
log(JSON.stringify(mcpServer.state()));
// 常用控制:
// mcpServer.stop();
// mcpServer.clearAll();
MCP 工具注册
mcpServer.quickTool(
"run",
"运行指定脚本",
[
{ name: "path", type: "string", required: true, description: "脚本路径" }
],
function(args, ctx) {
return {
structuredContent: {
ok: true,
path: args.path,
tool: ctx.toolName
},
content: [
{ type: "text", text: "执行完成" }
]
};
}
);
mcpServer.tool(
"getLog",
{
description: "读取某个 path 的日志",
inputSchema: {
type: "object",
properties: {
path: { type: "string" }
},
required: ["path"]
}
},
function(args) {
return {
structuredContent: {
ok: true,
path: args.path,
result: readLog(args.path)
},
content: [
{ type: "text", text: "日志已返回" }
]
};
}
);
MCP 资源与 Prompt
mcpServer.resource(
"jsxhook://status",
{
name: "status",
mimeType: "application/json; charset=utf-8"
},
function() {
return {
text: JSON.stringify({
packageName: lpparam.packageName,
processName: lpparam.processName
}, null, 2)
};
}
);
mcpServer.resourceTemplate(
"jsxhook://logs/{path}",
{
name: "logs-by-path",
mimeType: "text/plain; charset=utf-8"
},
function(args) {
return {
text: readLog(args.params.path)
};
}
);
mcpServer.prompt(
"run-with-context",
{
description: "生成运行脚本前的上下文",
arguments: [
{ name: "path", required: true },
{ name: "goal", required: false }
]
},
function(args) {
return {
description: "运行提示",
messages: [
{
role: "user",
content: {
type: "text",
text:
"宿主包名: " + lpparam.packageName +
"\n进程名: " + lpparam.processName +
"\n脚本路径: " + args.path +
(args.goal ? ("\n目标: " + args.goal) : "")
}
}
]
};
}
);
MCP 客户端请求示例
// initialize
// POST /mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": { "name": "demo", "version": "1.0.0" }
}
}
// tools/call
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "run",
"arguments": { "path": "main.js" }
}
}
// resources/read / prompts/get 同理:
// method = "resources/read" + params.uri
// method = "prompts/get" + params.name + params.arguments
Auto.js MCP 编码示例
const File = importClass("java.io.File");
const Files = importClass("java.nio.file.Files");
const JString = importClass("java.lang.String");
const FileOutputStream = importClass("java.io.FileOutputStream");
const OutputStreamWriter = importClass("java.io.OutputStreamWriter");
const Base64 = importClass("android.util.Base64");
const DEFAULT_LOG_PATH = "/storage/emulated/0/Android/data/org.autojs.autojspro/files/logs/log.txt";
const DEFAULT_SCRIPT_ROOT = "/storage/emulated/0/脚本";
var moduleName = (typeof project !== "undefined" && project && project.name)
? project.name
: "Auto.js MCP";
var c6665 = null;
function nowText() {
return new Date().toISOString();
}
function logCall(name, detail) {
console.info(
"MCP客户端调用了 " +
String(name || "") +
(detail ? ("," + detail) : "")
);
}
function okResult(text, payload) {
var structured = { ok: true };
if (payload) {
for (var key in payload) {
structured[key] = payload[key];
}
}
if (!structured.message) {
structured.message = String(text || "ok");
}
return {
structuredContent: structured,
content: [
{
type: "text",
text: JSON.stringify(structured, null, 2)
}
]
};
}
function errorResult(text, payload) {
var structured = { ok: false };
if (payload) {
for (var key in payload) {
structured[key] = payload[key];
}
}
if (!structured.message) {
structured.message = String(text || "error");
}
return {
isError: true,
structuredContent: structured,
content: [
{
type: "text",
text: JSON.stringify(structured, null, 2)
}
]
};
}
function normalizePath(path, fallback) {
var value = String(path == null ? "" : path).trim();
if (value) {
return value;
}
return String(fallback == null ? "" : fallback);
}
function toFile(path, fallback) {
return new File(normalizePath(path, fallback));
}
function ensureParent(file) {
var parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
}
function readText(path, fallback) {
var file = toFile(path, fallback);
var bytes = Files.readAllBytes(file.toPath());
return new JString(bytes, "UTF-8").toString();
}
function writeText(path, text, append) {
var file = toFile(path);
ensureParent(file);
if (!file.exists()) {
file.createNewFile();
}
var fos = new FileOutputStream(file, append === true);
var writer = new OutputStreamWriter(fos, "UTF-8");
try {
writer.write(String(text == null ? "" : text));
writer.flush();
} finally {
writer.close();
}
return true;
}
function decodeBase64Text(base64Text) {
var bytes = Base64.decode(String(base64Text == null ? "" : base64Text), Base64.DEFAULT);
return new JString(bytes, "UTF-8").toString();
}
function normalizeWriteContent(args) {
if (args == null) {
throw new Error("缺少写入参数");
}
if (args.text != null) {
return String(args.text);
}
if (args.content != null) {
return String(args.content);
}
if (args.lines != null && typeof args.lines.length === "number") {
var lines = [];
for (var i = 0; i < args.lines.length; i++) {
lines.push(String(args.lines[i] == null ? "" : args.lines[i]));
}
return lines.join("\n");
}
throw new Error("需要传 text、content 或 lines");
}
function createFile(path, overwrite) {
var file = toFile(path);
ensureParent(file);
if (!file.exists()) {
return file.createNewFile();
}
if (overwrite === true) {
var fos = new FileOutputStream(file, false);
fos.close();
return true;
}
return false;
}
function createDirectory(path) {
var dir = toFile(path);
return dir.exists() ? dir.isDirectory() : dir.mkdirs();
}
function deleteDirectoryRecursive(file) {
if (file == null || !file.exists()) {
return false;
}
var children = file.listFiles();
if (children != null) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.isDirectory()) {
deleteDirectoryRecursive(child);
} else {
child.delete();
}
}
}
return file.delete();
}
function deleteFile(path) {
var file = toFile(path);
if (!file.exists()) {
return false;
}
if (file.isDirectory()) {
throw new Error("目标是文件夹,请改用 deleteDirectory");
}
return file.delete();
}
function deleteDirectory(path) {
var dir = toFile(path);
if (!dir.exists()) {
return false;
}
if (!dir.isDirectory()) {
throw new Error("目标不是文件夹");
}
return deleteDirectoryRecursive(dir);
}
function listDirectory(path) {
var dir = toFile(path, DEFAULT_SCRIPT_ROOT);
if (!dir.exists()) {
throw new Error("目录不存在: " + dir.getAbsolutePath());
}
if (!dir.isDirectory()) {
throw new Error("目标不是文件夹: " + dir.getAbsolutePath());
}
var files = dir.listFiles();
var result = [];
if (files == null) {
return result;
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
result.push({
name: file.getName(),
path: file.getAbsolutePath(),
isDirectory: file.isDirectory(),
isFile: file.isFile(),
size: file.isFile() ? file.length() : 0,
lastModified: file.lastModified()
});
}
return result;
}
function statPath(path) {
var file = toFile(path);
var exists = file.exists();
return {
exists: exists,
name: file.getName(),
path: file.getAbsolutePath(),
parent: file.getParent(),
isDirectory: exists && file.isDirectory(),
isFile: exists && file.isFile(),
size: exists && file.isFile() ? file.length() : 0,
lastModified: exists ? file.lastModified() : 0
};
}
function existsPath(path) {
return toFile(path).exists();
}
function renamePath(fromPath, toPath) {
var fromFile = toFile(fromPath);
if (!fromFile.exists()) {
throw new Error("源路径不存在: " + fromFile.getAbsolutePath());
}
var toFileObj = toFile(toPath);
ensureParent(toFileObj);
return fromFile.renameTo(toFileObj);
}
function readLog(path) {
return readText(path, DEFAULT_LOG_PATH);
}
function clearLog(path) {
var file = toFile(path, DEFAULT_LOG_PATH);
if (!file.exists()) {
return false;
}
var fos = new FileOutputStream(file, false);
fos.close();
return true;
}
function run(scriptPath) {
if (c6665 == null) {
log("关键类还没有初始化!");
return false;
}
var ScriptServiceClass = XposedHelpers.findClass(
"com.stardust.autojs.ScriptService",
classLoader
);
var C0494 = XposedHelpers.getStaticObjectField(ScriptServiceClass, "ၰ");
var od4Class = XposedHelpers.findClass("okhttp3.internal.io.od4", classLoader);
var dc4Class = XposedHelpers.findClass("okhttp3.internal.io.dc4", classLoader);
var dc4Var = this["new"](dc4Class, scriptPath);
var od4Var = this["new"](od4Class, dc4Var, false);
XposedHelpers.callMethod(C0494, "ԫ", od4Var, c6665);
return true;
}
hook({
class: "org.autojs.autojs.flutter.FlutterScriptsChannel",
classloader: lpparam.classLoader,
method: "ԫ",
params: [
"int",
classLoader.loadClass("okhttp3.internal.io.sh2"),
classLoader.loadClass("okhttp3.internal.io.bi2${'$'}Ԭ"),
classLoader.loadClass("okhttp3.internal.io.ৡ")
],
after(it) {
var interfaceC5481 = it.args[3];
if (c6665 == null && interfaceC5481.getClass().getName() == "okhttp3.internal.io.eb0") {
var C6665Class = XposedHelpers.findClass(
"org.autojs.autojs.flutter.FlutterScriptsChannel${'$'}Ԩ",
lpparam.classLoader
);
c6665 = this["new"](C6665Class, it.thisObject, interfaceC5481);
log("关键类初始化成功");
}
}
});
mcpServer.clearAll();
mcpServer.tool(
"status",
{
description: "查看当前模块、宿主和关键类初始化状态",
inputSchema: {
type: "object",
properties: {}
}
},
function(args, ctx) {
logCall(ctx.toolName);
return okResult("状态已返回", {
result: {
moduleName: moduleName,
packageName: lpparam.packageName,
processName: lpparam.processName,
autoJsReady: c6665 != null,
defaultLogPath: DEFAULT_LOG_PATH,
defaultScriptRoot: DEFAULT_SCRIPT_ROOT,
time: nowText()
},
moduleName: moduleName,
packageName: lpparam.packageName,
processName: lpparam.processName,
autoJsReady: c6665 != null,
defaultLogPath: DEFAULT_LOG_PATH,
defaultScriptRoot: DEFAULT_SCRIPT_ROOT,
time: nowText()
});
}
);
mcpServer.quickTool(
"run",
"运行指定脚本",
[
{ name: "path", type: "string", required: true, description: "脚本路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
return okResult("运行请求已发送", {
path: path,
result: run(path)
});
}
);
mcpServer.quickTool(
"getLog",
"读取日志文件",
[
{ name: "path", type: "string", required: false, description: "日志路径,不传则使用默认日志" }
],
function(args, ctx) {
var path = normalizePath(args.path, DEFAULT_LOG_PATH);
logCall(ctx.toolName, "path=" + path);
var text = readLog(path);
return okResult("日志已返回", {
path: path,
result: text,
text: text,
length: text.length,
exists: true,
content: text
});
}
);
mcpServer.quickTool(
"clearLog",
"清空日志文件",
[
{ name: "path", type: "string", required: false, description: "日志路径,不传则使用默认日志" }
],
function(args, ctx) {
var path = normalizePath(args.path, DEFAULT_LOG_PATH);
logCall(ctx.toolName, "path=" + path);
var cleared = clearLog(path);
return okResult("日志已清空", {
path: path,
result: cleared,
cleared: cleared
});
}
);
mcpServer.quickTool(
"readFile",
"读取单个文件",
[
{ name: "path", type: "string", required: true, description: "文件路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var text = readText(path);
return okResult("文件已读取", {
path: path,
result: text,
text: text
});
}
);
mcpServer.tool(
"readFiles",
{
description: "批量读取多个文件,适合让 AI 一次拿到上下文",
inputSchema: {
type: "object",
properties: {
paths: {
type: "array",
items: { type: "string" }
}
},
required: ["paths"]
}
},
function(args, ctx) {
var paths = args.paths;
logCall(ctx.toolName, "count=" + (paths ? paths.length : 0));
if (!paths || typeof paths.length !== "number") {
return errorResult("paths 必须是数组");
}
var result = [];
for (var i = 0; i < paths.length; i++) {
var currentPath = normalizePath(paths[i]);
result.push({
path: currentPath,
text: readText(currentPath)
});
}
return okResult("批量读取完成", {
result: result,
files: result
});
}
);
mcpServer.tool(
"writeFile",
{
description: "覆盖写入文件,不存在会自动创建。只适合短文本;长代码优先用 writeFileLines 或 writeFileBase64",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
text: {
type: "string",
description: "短文本内容"
},
content: {
type: "string",
description: "短文本内容,和 text 二选一"
},
lines: {
type: "array",
description: "多行文本,推荐长代码使用",
items: { type: "string" }
}
},
required: ["path"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, normalizeWriteContent(args), false);
var info = statPath(path);
return okResult("文件已写入", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"appendFile",
{
description: "追加写入文件,不存在会自动创建。只适合短文本;长代码优先用 appendFileLines 或 appendFileBase64",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
text: {
type: "string",
description: "短文本内容"
},
content: {
type: "string",
description: "短文本内容,和 text 二选一"
},
lines: {
type: "array",
description: "多行文本,推荐长代码使用",
items: { type: "string" }
}
},
required: ["path"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, normalizeWriteContent(args), true);
var info = statPath(path);
return okResult("内容已追加", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"writeFileLines",
{
description: "用数组按行覆盖写入文件,适合 AI 生成多行代码",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
lines: {
type: "array",
description: "每行一个字符串元素,元素里不要再带换行符",
items: { type: "string" }
}
},
required: ["path", "lines"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, normalizeWriteContent({ lines: args.lines }), false);
var info = statPath(path);
return okResult("文件已按行写入", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"appendFileLines",
{
description: "用数组按行追加写入文件,适合 AI 分段补代码",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
lines: {
type: "array",
description: "每行一个字符串元素,元素里不要再带换行符",
items: { type: "string" }
}
},
required: ["path", "lines"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, normalizeWriteContent({ lines: args.lines }), true);
var info = statPath(path);
return okResult("内容已按行追加", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"writeFileBase64",
{
description: "用 UTF-8 Base64 覆盖写入文件,最适合大段代码或复杂引号内容",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
base64: {
type: "string",
description: "文件内容的 UTF-8 Base64 编码"
}
},
required: ["path", "base64"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, decodeBase64Text(args.base64), false);
var info = statPath(path);
return okResult("Base64 文件已写入", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"appendFileBase64",
{
description: "用 UTF-8 Base64 追加写入文件,适合分块追加长代码",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
base64: {
type: "string",
description: "追加内容的 UTF-8 Base64 编码"
}
},
required: ["path", "base64"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
writeText(path, decodeBase64Text(args.base64), true);
var info = statPath(path);
return okResult("Base64 内容已追加", {
path: path,
result: true,
exists: info.exists,
size: info.size
});
}
);
mcpServer.tool(
"replaceInFile",
{
description: "在文件中替换第一次出现的文本,适合做小范围修改",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "目标文件路径"
},
search: {
type: "string",
description: "要查找的原文本"
},
replace: {
type: "string",
description: "替换成的新文本"
}
},
required: ["path", "search", "replace"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var oldText = readText(path);
var search = String(args.search);
var replace = String(args.replace);
var index = oldText.indexOf(search);
if (index < 0) {
return errorResult("未找到要替换的内容", {
path: path,
result: false
});
}
var newText = oldText.substring(0, index) + replace + oldText.substring(index + search.length);
writeText(path, newText, false);
return okResult("文件替换完成", {
path: path,
result: true
});
}
);
mcpServer.tool(
"createFile",
{
description: "创建空文件,可选是否覆盖已存在文件",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
overwrite: { type: "boolean" }
},
required: ["path"]
}
},
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var created = createFile(path, args.overwrite === true);
return okResult("文件创建完成", {
path: path,
result: created,
created: created
});
}
);
mcpServer.quickTool(
"createDirectory",
"创建文件夹",
[
{ name: "path", type: "string", required: true, description: "文件夹路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var created = createDirectory(path);
return okResult("文件夹已创建", {
path: path,
result: created,
created: created
});
}
);
mcpServer.quickTool(
"deleteFile",
"删除单个文件",
[
{ name: "path", type: "string", required: true, description: "文件路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var deleted = deleteFile(path);
return okResult("文件删除完成", {
path: path,
result: deleted,
deleted: deleted
});
}
);
mcpServer.quickTool(
"deleteDirectory",
"递归删除文件夹",
[
{ name: "path", type: "string", required: true, description: "文件夹路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var deleted = deleteDirectory(path);
return okResult("文件夹删除完成", {
path: path,
result: deleted,
deleted: deleted
});
}
);
mcpServer.quickTool(
"listDirectory",
"列出文件夹内容",
[
{ name: "path", type: "string", required: false, description: "文件夹路径,不传默认脚本目录" }
],
function(args, ctx) {
var path = normalizePath(args.path, DEFAULT_SCRIPT_ROOT);
logCall(ctx.toolName, "path=" + path);
var items = listDirectory(path);
return okResult("目录内容已返回", {
path: path,
result: items,
items: items
});
}
);
mcpServer.quickTool(
"existsPath",
"判断路径是否存在",
[
{ name: "path", type: "string", required: true, description: "目标路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var exists = existsPath(path);
return okResult("路径状态已返回", {
path: path,
result: exists,
exists: exists
});
}
);
mcpServer.quickTool(
"statPath",
"获取路径信息",
[
{ name: "path", type: "string", required: true, description: "目标路径" }
],
function(args, ctx) {
var path = normalizePath(args.path);
logCall(ctx.toolName, "path=" + path);
var info = statPath(path);
return okResult("路径信息已返回", {
result: info,
exists: info.exists,
name: info.name,
path: info.path,
parent: info.parent,
isDirectory: info.isDirectory,
isFile: info.isFile,
size: info.size,
lastModified: info.lastModified
});
}
);
mcpServer.tool(
"renamePath",
{
description: "重命名或移动文件/文件夹",
inputSchema: {
type: "object",
properties: {
fromPath: { type: "string" },
toPath: { type: "string" }
},
required: ["fromPath", "toPath"]
}
},
function(args, ctx) {
var fromPath = normalizePath(args.fromPath);
var toPath = normalizePath(args.toPath);
logCall(ctx.toolName, "from=" + fromPath + ", to=" + toPath);
var renamed = renamePath(fromPath, toPath);
return okResult("路径重命名完成", {
fromPath: fromPath,
toPath: toPath,
result: renamed,
renamed: renamed
});
}
);
mcpServer.prompt(
"code-task-context",
{
description: "给模型描述当前 Auto.js 编码环境和目标",
arguments: [
{
name: "goal",
description: "这次希望模型完成什么任务",
required: true
},
{
name: "focusPath",
description: "这次重点处理的文件或目录",
required: false
}
]
},
function(args) {
var focusText = args.focusPath ? ("\n重点路径: " + args.focusPath) : "";
return {
description: "Auto.js 编码任务上下文",
messages: [
{
role: "user",
content: {
type: "text",
text:
"你正在操作模块 " + moduleName +
"\n宿主包名: " + lpparam.packageName +
"\n进程名: " + lpparam.processName +
"\n默认脚本目录: " + DEFAULT_SCRIPT_ROOT +
"\n目标: " + args.goal +
focusText +
"\n可用工具: readFile / readFiles / writeFile / writeFileLines / writeFileBase64 / appendFile / appendFileLines / appendFileBase64 / replaceInFile / createFile / createDirectory / deleteFile / deleteDirectory / listDirectory / statPath / renamePath / run" +
"\n写长代码时优先使用 writeFileBase64、writeFileLines、appendFileBase64 或 appendFileLines,避免多行字符串转义失败"
}
}
]
};
}
);
mcpServer.start({
port: 5698,
allowLan: true,
token: "",
endpoint: "/mcp",
name: "autojs-coding-tools",
version: "1.0.0",
instructions: "这是一个为 Auto.js 设计的 MCP 服务,可读写文件、管理目录、运行脚本、读取日志。"
});
Files / Storages / Plugins / Device
对应主文档:files 对象 / storages 对象 / plugins 对象 / device 对象
Auto.js files API
// 全局直接用 files
const cwd = files.cwd();
const sdcard = files.getSdcardPath();
log("cwd=" + cwd);
log("sdcard=" + sdcard);
// 路径判断
log(files.isFile(files.path("./demo/hello.txt")));
log(files.isDir(files.join(sdcard, "Download")));
log(files.isEmptyDir(files.join(sdcard, "Download/EmptyDir")));
log(files.exists(files.join(cwd, "demo")));
// 路径拼接与相对路径
const demoDir = files.join(cwd, "demo");
const demoFile = files.path("./demo/hello.txt");
// 创建文件 / 文件夹
files.create(files.join(cwd, "demo-folder/"));
files.createWithDirs(demoFile);
files.ensureDir(files.join(cwd, "demo/nested/1.txt"));
// 读写文本与字节
files.write(demoFile, "hello");
files.append(demoFile, "\nworld");
log(files.read(demoFile));
const bytes = files.readBytes(demoFile);
files.writeBytes(files.join(cwd, "demo/hello.bin"), bytes);
files.appendBytes(files.join(cwd, "demo/hello.bin"), bytes);
// 文件名相关
log(files.getName(demoFile));
log(files.getNameWithoutExtension(demoFile));
log(files.getExtension(demoFile));
// 复制 / 移动 / 重命名
files.copy(demoFile, files.join(cwd, "copy/hello.txt"));
files.move(files.join(cwd, "copy/hello.txt"), files.join(cwd, "moved/hello.txt"));
files.rename(files.join(cwd, "moved/hello.txt"), "renamed.txt");
files.renameWithoutExtension(files.join(cwd, "moved/renamed.txt"), "renamed2");
// 列目录,filter 收到的是文件名
log(files.listDir(demoDir));
log(files.listDir(demoDir, function(name) {
return name.endsWith(".txt") && files.isFile(files.join(demoDir, name));
}));
// 删除文件 / 文件夹
files.remove(files.join(cwd, "moved/renamed2.txt"));
files.removeDir(files.join(cwd, "demo/nested"));
Auto.js storages API
const store = storages.create("demo-config");
const sameStore = storages.create("demo-config");
// 写入 number / string / boolean / null / object / array
store.put("count", 1);
store.put("name", "rose");
store.put("enabled", true);
store.put("empty", null);
store.put("profile", {
nickname: "JSXHook",
tags: ["autojs", "storages"],
level: 5
});
// 读取与默认值
log(store.get("count"));
log(store.get("name", "default"));
log(typeof store.get("missing"));
log(store.get("missing", "fallback"));
log(JSON.stringify(sameStore.get("profile"), null, 4));
// contains / remove
log(store.contains("enabled"));
store.remove("enabled");
log(store.contains("enabled"));
// clear 只清空当前 storage 的全部键值
store.clear();
log(typeof store.get("name"));
// remove 删除整个命名 storage
log(storages.remove("demo-config"));
log(storages.remove("demo-config"));
Auto.js 插件兼容
// 目标:兼容这类“已安装的 Auto.js 纯 Java / JNI 插件”
// 当前优先支持:
// 1. manifest 声明 org.autojs.plugin.sdk.registry
// 2. registry 注册默认插件
// 3. 插件实现 getAssetsScriptDir(),并在 assets/<dir>/index.js 导出 JS 胶水层
// 4. public API 主要是普通 Java / JNI 方法
//
// 如果插件带 JNI,请确保插件 APK 里的 so 架构和当前进程 ABI 一致。
// 导出独立模块时,会自动扫描脚本里的 plugins.load("包名"),
// 优先把当前设备已安装的对应插件 APK 一起打进 assets。
// 运行时会先加载内置插件;如果没有内置,再回退到已安装插件。
const aj2048 = plugins.load("com.daowuya.aj2048");
aj2048.SetMaxDepth(8);
const board = [
[0, 2, 0, 2],
[4, 0, 0, 0],
[0, 8, 0, 0],
[0, 0, 16, 0]
];
const bestMove = aj2048.GetBestMove(board);
const bestMoveNo44 = aj2048.GetBestMove_no44(
board,
200,
200,
270,
100,
false
);
log("bestMove=" + bestMove);
log("bestMoveNo44=" + bestMoveNo44);
// 如果插件注册的不是默认插件,也可以传第二个参数:
// const p = plugins.load("com.example.plugin", "customName");
//
// 如果插件内部有 JNI / 单例 / native 状态,调试完也可以主动卸载:
// plugins.unload("com.daowuya.aj2048");
// plugins.unloadAll();
device 基础信息
log("width=" + device.width);
log("height=" + device.height);
log("buildId=" + device.buildId);
log("broad=" + device.broad);
log("brand=" + device.brand);
log("device=" + device.device);
log("model=" + device.model);
log("product=" + device.product);
log("bootloader=" + device.bootloader);
log("hardware=" + device.hardware);
log("fingerprint=" + device.fingerprint);
log("serial=" + device.serial);
log("sdkInt=" + device.sdkInt);
log("incremental=" + device.incremental);
log("release=" + device.release);
log("baseOS=" + device.baseOS);
log("securityPatch=" + device.securityPatch);
log("codename=" + device.codename);
device 标识信息
// 新系统或目标进程权限不够时,IMEI / MAC 可能拿不到并返回 null
log("androidId=" + device.getAndroidId());
log("imei=" + device.getIMEI());
log("mac=" + device.getMacAddress());
device 亮度控制
log("brightness=" + device.getBrightness());
log("mode=" + device.getBrightnessMode());
// mode: 0 = 手动, 1 = 自动
// 某些系统会先弹出“修改系统设置”授权页
device.setBrightnessMode(0);
device.setBrightness(180);
device 音量控制
log("music=" + device.getMusicVolume() + "/" + device.getMusicMaxVolume());
log("notification=" + device.getNotificationVolume() + "/" + device.getNotificationMaxVolume());
log("alarm=" + device.getAlarmVolume() + "/" + device.getAlarmMaxVolume());
device.setMusicVolume(5);
device.setNotificationVolume(4);
device.setAlarmVolume(3);
device 电量与内存
log("battery=" + device.getBattery());
log("charging=" + device.isCharging());
log("totalMem=" + device.getTotalMem());
log("availMem=" + device.getAvailMem());
log("screenOn=" + device.isScreenOn());
device 唤醒与震动
if (!device.isScreenOn()) {
device.wakeUp();
}
device.wakeUpIfNeeded();
device.keepScreenOn(60 * 1000);
device.keepScreenDim(15 * 1000);
setTimeout(function() {
device.cancelKeepingAwake();
}, 5000);
device.vibrate(300);
setTimeout(function() {
device.cancelVibration();
}, 800);
