@bingine/cemem 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PORT-SCOPE.md +169 -0
- package/README.md +451 -2
- package/dist/cjs/addr/expression.js +650 -0
- package/dist/cjs/addr/expression.js.map +1 -0
- package/dist/cjs/addresslist/memory-record.js +612 -0
- package/dist/cjs/addresslist/memory-record.js.map +1 -0
- package/dist/cjs/asm/assembler.js +354 -0
- package/dist/cjs/asm/assembler.js.map +1 -0
- package/dist/cjs/asm/autoassembler-directives.js +362 -0
- package/dist/cjs/asm/autoassembler-directives.js.map +1 -0
- package/dist/cjs/asm/autoassembler.js +2163 -0
- package/dist/cjs/asm/autoassembler.js.map +1 -0
- package/dist/cjs/asm/backend.js +394 -0
- package/dist/cjs/asm/backend.js.map +1 -0
- package/dist/cjs/asm/disassembler.js +715 -0
- package/dist/cjs/asm/disassembler.js.map +1 -0
- package/dist/cjs/backend/dbk-backend.js +683 -0
- package/dist/cjs/backend/dbk-backend.js.map +1 -0
- package/dist/cjs/backend/native-ce-backend.js +667 -0
- package/dist/cjs/backend/native-ce-backend.js.map +1 -0
- package/dist/cjs/backend/self-backend.js +449 -0
- package/dist/cjs/backend/self-backend.js.map +1 -0
- package/dist/cjs/backend/types.js +48 -0
- package/dist/cjs/backend/types.js.map +1 -0
- package/dist/cjs/cheattable/ct-format.js +533 -0
- package/dist/cjs/cheattable/ct-format.js.map +1 -0
- package/dist/cjs/index.js +219 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lua/aa.js +230 -0
- package/dist/cjs/lua/aa.js.map +1 -0
- package/dist/cjs/lua/addressing.js +118 -0
- package/dist/cjs/lua/addressing.js.map +1 -0
- package/dist/cjs/lua/api.js +736 -0
- package/dist/cjs/lua/api.js.map +1 -0
- package/dist/cjs/lua/asm.js +220 -0
- package/dist/cjs/lua/asm.js.map +1 -0
- package/dist/cjs/lua/bridge.js +526 -0
- package/dist/cjs/lua/bridge.js.map +1 -0
- package/dist/cjs/lua/marshal.js +562 -0
- package/dist/cjs/lua/marshal.js.map +1 -0
- package/dist/cjs/lua/memory.js +213 -0
- package/dist/cjs/lua/memory.js.map +1 -0
- package/dist/cjs/lua/objects.js +1070 -0
- package/dist/cjs/lua/objects.js.map +1 -0
- package/dist/cjs/lua/scan.js +543 -0
- package/dist/cjs/lua/scan.js.map +1 -0
- package/dist/cjs/lua/types.js +103 -0
- package/dist/cjs/lua/types.js.map +1 -0
- package/dist/cjs/scanner/matcher.js +781 -0
- package/dist/cjs/scanner/matcher.js.map +1 -0
- package/dist/cjs/scanner/pointer-scan.js +546 -0
- package/dist/cjs/scanner/pointer-scan.js.map +1 -0
- package/dist/cjs/scanner/scanner.js +513 -0
- package/dist/cjs/scanner/scanner.js.map +1 -0
- package/dist/cjs/scanner/shared-layout.js +207 -0
- package/dist/cjs/scanner/shared-layout.js.map +1 -0
- package/dist/cjs/types.js +190 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/value-codec.js +515 -0
- package/dist/cjs/value-codec.js.map +1 -0
- package/dist/cjs/win/process.js +475 -0
- package/dist/cjs/win/process.js.map +1 -0
- package/dist/cjs/win/scan-worker.js +84 -0
- package/dist/cjs/win/scan-worker.js.map +1 -0
- package/dist/cjs/win/winapi.js +403 -0
- package/dist/cjs/win/winapi.js.map +1 -0
- package/dist/types/addr/expression.d.ts +98 -0
- package/dist/types/addr/expression.d.ts.map +1 -0
- package/dist/types/addresslist/memory-record.d.ts +218 -0
- package/dist/types/addresslist/memory-record.d.ts.map +1 -0
- package/dist/types/asm/assembler.d.ts +106 -0
- package/dist/types/asm/assembler.d.ts.map +1 -0
- package/dist/types/asm/autoassembler-directives.d.ts +118 -0
- package/dist/types/asm/autoassembler-directives.d.ts.map +1 -0
- package/dist/types/asm/autoassembler.d.ts +434 -0
- package/dist/types/asm/autoassembler.d.ts.map +1 -0
- package/dist/types/asm/backend.d.ts +167 -0
- package/dist/types/asm/backend.d.ts.map +1 -0
- package/dist/types/asm/disassembler.d.ts +194 -0
- package/dist/types/asm/disassembler.d.ts.map +1 -0
- package/dist/types/backend/dbk-backend.d.ts +353 -0
- package/dist/types/backend/dbk-backend.d.ts.map +1 -0
- package/dist/types/backend/native-ce-backend.d.ts +213 -0
- package/dist/types/backend/native-ce-backend.d.ts.map +1 -0
- package/dist/types/backend/self-backend.d.ts +64 -0
- package/dist/types/backend/self-backend.d.ts.map +1 -0
- package/dist/types/backend/types.d.ts +176 -0
- package/dist/types/backend/types.d.ts.map +1 -0
- package/dist/types/cheattable/ct-format.d.ts +100 -0
- package/dist/types/cheattable/ct-format.d.ts.map +1 -0
- package/dist/types/index.d.ts +54 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lua/aa.d.ts +107 -0
- package/dist/types/lua/aa.d.ts.map +1 -0
- package/dist/types/lua/addressing.d.ts +41 -0
- package/dist/types/lua/addressing.d.ts.map +1 -0
- package/dist/types/lua/api.d.ts +46 -0
- package/dist/types/lua/api.d.ts.map +1 -0
- package/dist/types/lua/asm.d.ts +42 -0
- package/dist/types/lua/asm.d.ts.map +1 -0
- package/dist/types/lua/bridge.d.ts +129 -0
- package/dist/types/lua/bridge.d.ts.map +1 -0
- package/dist/types/lua/marshal.d.ts +244 -0
- package/dist/types/lua/marshal.d.ts.map +1 -0
- package/dist/types/lua/memory.d.ts +61 -0
- package/dist/types/lua/memory.d.ts.map +1 -0
- package/dist/types/lua/objects.d.ts +210 -0
- package/dist/types/lua/objects.d.ts.map +1 -0
- package/dist/types/lua/scan.d.ts +106 -0
- package/dist/types/lua/scan.d.ts.map +1 -0
- package/dist/types/lua/types.d.ts +146 -0
- package/dist/types/lua/types.d.ts.map +1 -0
- package/dist/types/scanner/matcher.d.ts +122 -0
- package/dist/types/scanner/matcher.d.ts.map +1 -0
- package/dist/types/scanner/pointer-scan.d.ts +187 -0
- package/dist/types/scanner/pointer-scan.d.ts.map +1 -0
- package/dist/types/scanner/scanner.d.ts +150 -0
- package/dist/types/scanner/scanner.d.ts.map +1 -0
- package/dist/types/scanner/shared-layout.d.ts +96 -0
- package/dist/types/scanner/shared-layout.d.ts.map +1 -0
- package/dist/types/types.d.ts +239 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/value-codec.d.ts +134 -0
- package/dist/types/value-codec.d.ts.map +1 -0
- package/dist/types/win/process.d.ts +190 -0
- package/dist/types/win/process.d.ts.map +1 -0
- package/dist/types/win/scan-worker.d.ts +51 -0
- package/dist/types/win/scan-worker.d.ts.map +1 -0
- package/dist/types/win/winapi.d.ts +144 -0
- package/dist/types/win/winapi.d.ts.map +1 -0
- package/docs/assembler.md +371 -0
- package/docs/autoassembler.md +423 -0
- package/docs/backend-dbk.md +305 -0
- package/docs/backends.md +273 -0
- package/docs/evidence/atomics-wait-io.js +20 -0
- package/docs/evidence/mini-repro.js +98 -0
- package/docs/lua-api.md +718 -0
- package/package.json +60 -4
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lua userdata 对象桥 + CE 风格的宿主对象。
|
|
4
|
+
*
|
|
5
|
+
* 实现方式对齐 Cheat Engine 的 luaclass:
|
|
6
|
+
* - 每个「类」有一个元表,元表的 `__index` 是一个 C 闭包,先查方法表
|
|
7
|
+
* (方法表里存的是原始 C 函数),命中就直接返回该函数 —— Lua 用冒号调用时
|
|
8
|
+
* `self` 作为第 1 个参数传进来,与 CE 的 luaclass_getClassObject 一致;
|
|
9
|
+
* - 查不到方法就按属性 getter 处理(`list.Count`、`record.Value` 这类活属性);
|
|
10
|
+
* - 对象本身是 4 字节 userdata,里面存 JS 侧句柄(i32),
|
|
11
|
+
* JS 侧用 Map<句柄, 对象> 保存真实对象。
|
|
12
|
+
*
|
|
13
|
+
* 之所以不用普通 Lua table 模拟:table 承载不了「活属性」,
|
|
14
|
+
* 也没有 CE 对象该有的 destroy()/GC 语义;CE 的对象本来就是 userdata。
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.SCAN_SESSION_CLASS = exports.LuaScanSession = exports.MEMORY_RECORD_CLASS = exports.ADDRESS_LIST_CLASS = exports.LuaAddressList = exports.LuaMemoryRecord = exports.STRING_LIST_CLASS = exports.LuaStringList = exports.LuaObjectBridge = void 0;
|
|
18
|
+
exports.formatRecordValue = formatRecordValue;
|
|
19
|
+
exports.encodeRecordValue = encodeRecordValue;
|
|
20
|
+
exports.registerBuiltinClasses = registerBuiltinClasses;
|
|
21
|
+
const types_1 = require("../types");
|
|
22
|
+
const marshal_1 = require("./marshal");
|
|
23
|
+
const scan_1 = require("./scan");
|
|
24
|
+
/** userdata 里存的句柄宽度(wasm32 下是 4 字节,正好放一个 i32 句柄)。 */
|
|
25
|
+
const USERDATA_SIZE = 4;
|
|
26
|
+
/** 默认字符串长度(CE 的 MemoryRecord 有可配置的字符串长度,这里给固定默认值)。 */
|
|
27
|
+
const ANSI_STRING_LENGTH = 64;
|
|
28
|
+
const WIDE_STRING_LENGTH = 32;
|
|
29
|
+
/* ------------------------------------------------------------------ *
|
|
30
|
+
* 对象桥
|
|
31
|
+
* ------------------------------------------------------------------ */
|
|
32
|
+
class LuaObjectBridge {
|
|
33
|
+
cm;
|
|
34
|
+
objects = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* 每个句柄当前有多少个**活的 userdata** 指向它。
|
|
37
|
+
*
|
|
38
|
+
* 只有当计数归零时才能真正从 `objects` 里摘除宿主对象 ——
|
|
39
|
+
* 详见 `closureGc` 与 `handleOf` 的注释。
|
|
40
|
+
*/
|
|
41
|
+
handleRefs = new Map();
|
|
42
|
+
handleOfObject = new WeakMap();
|
|
43
|
+
classes = new Map();
|
|
44
|
+
classIds = new Map();
|
|
45
|
+
metatableRefs = new Map();
|
|
46
|
+
/** 未声明 setter 的属性写入会落到这里(脚本可以往对象上挂自己的字段) */
|
|
47
|
+
extras = new WeakMap();
|
|
48
|
+
fnPtrs = [];
|
|
49
|
+
indexPtr;
|
|
50
|
+
newIndexPtr;
|
|
51
|
+
methodPtr;
|
|
52
|
+
gcPtr;
|
|
53
|
+
nextHandle = 1;
|
|
54
|
+
nextClassId = 1;
|
|
55
|
+
constructor(cm) {
|
|
56
|
+
this.cm = cm;
|
|
57
|
+
this.indexPtr = cm.module.addFunction((state) => this.closureIndex(state), 'ii');
|
|
58
|
+
this.newIndexPtr = cm.module.addFunction((state) => this.closureNewIndex(state), 'ii');
|
|
59
|
+
this.methodPtr = cm.module.addFunction((state) => this.closureMethod(state), 'ii');
|
|
60
|
+
this.gcPtr = cm.module.addFunction((state) => this.closureGc(state), 'ii');
|
|
61
|
+
this.fnPtrs.push(this.indexPtr, this.newIndexPtr, this.methodPtr, this.gcPtr);
|
|
62
|
+
}
|
|
63
|
+
/** 注册一个类(同名只注册一次),返回类 id。 */
|
|
64
|
+
registerClass(cls) {
|
|
65
|
+
const existing = this.classIds.get(cls.name);
|
|
66
|
+
if (existing !== undefined)
|
|
67
|
+
return existing;
|
|
68
|
+
const id = this.nextClassId++;
|
|
69
|
+
this.classIds.set(cls.name, id);
|
|
70
|
+
this.classes.set(id, cls);
|
|
71
|
+
return id;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 对象 → 句柄(同一个存活对象复用同一个句柄)。
|
|
75
|
+
*
|
|
76
|
+
* ## 为什么必须校验 `objects` 里还存在
|
|
77
|
+
*
|
|
78
|
+
* 句柄缓存用的是 `WeakMap<object, number>`,但**同一对象可能被多个 userdata 共用**——
|
|
79
|
+
* 典型例子是 `getAddressList()`:它每次都返回**同一个** `LuaAddressList` 单例,
|
|
80
|
+
* 于是所有拿到的 userdata 都写着同一个 handle。
|
|
81
|
+
*
|
|
82
|
+
* 只要其中**任意一个** userdata 被 Lua GC 回收,`__gc`(`closureGc`)就会执行
|
|
83
|
+
* `objects.delete(handle)`。此时 `WeakMap` 里的缓存还在(宿主对象仍被强引用),
|
|
84
|
+
* 但 `objects` 表里已经没有这个 handle 了。若 `handleOf` 无脑返回缓存值,
|
|
85
|
+
* 新压栈的 userdata 就会指向一个空槽位,之后任何一次属性/方法访问都会得到
|
|
86
|
+
* `readUserdata → found:false`,最终抛出误导性的
|
|
87
|
+
* 「self 不是本引擎的对象(收到 userdata)」——而且是**永久性**的,
|
|
88
|
+
* 该类对象从此再也不能用(`__gc` 删表是单向的,没有任何路径会把它加回来)。
|
|
89
|
+
*
|
|
90
|
+
* 因此这里必须同时检查「WeakMap 命中」**且**「objects 里确实还有这个 handle」,
|
|
91
|
+
* 不一致就重新分配一个新句柄。
|
|
92
|
+
*/
|
|
93
|
+
handleOf(obj) {
|
|
94
|
+
const cached = this.handleOfObject.get(obj);
|
|
95
|
+
// 必须同时校验 `objects` 里确实还有这个 handle:WeakMap 的缓存不会随
|
|
96
|
+
// `objects.delete` 一起失效,若无脑返回缓存值,新压栈的 userdata 会指向空槽位。
|
|
97
|
+
if (cached !== undefined && this.objects.has(cached))
|
|
98
|
+
return cached;
|
|
99
|
+
const h = this.nextHandle++;
|
|
100
|
+
this.handleOfObject.set(obj, h);
|
|
101
|
+
this.objects.set(h, obj);
|
|
102
|
+
// 新句柄的引用数由 pushObject 紧接着 +1,这里先置 0
|
|
103
|
+
this.handleRefs.set(h, 0);
|
|
104
|
+
return h;
|
|
105
|
+
}
|
|
106
|
+
objectOf(handle) {
|
|
107
|
+
return this.objects.get(handle);
|
|
108
|
+
}
|
|
109
|
+
/* ---------------- MarshalObjectAccess ---------------- */
|
|
110
|
+
/** 该索引是不是本桥的 userdata;是则返回宿主对象。 */
|
|
111
|
+
readUserdata(m, index) {
|
|
112
|
+
if (m.type(index) !== marshal_1.LUA_TUSERDATA)
|
|
113
|
+
return { found: false };
|
|
114
|
+
if (this.metatableRefs.size === 0)
|
|
115
|
+
return { found: false };
|
|
116
|
+
const abs = m.abs(index);
|
|
117
|
+
if (m.cm.lua_getmetatable(m.state, abs) === 0)
|
|
118
|
+
return { found: false };
|
|
119
|
+
let matched = false;
|
|
120
|
+
for (const ref of this.metatableRefs.values()) {
|
|
121
|
+
m.cm.lua_rawgeti(m.state, marshal_1.LUA_REGISTRYINDEX, BigInt(ref));
|
|
122
|
+
if (m.cm.lua_rawequal(m.state, -1, -2) !== 0)
|
|
123
|
+
matched = true;
|
|
124
|
+
m.pop(1);
|
|
125
|
+
if (matched)
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
m.pop(1); // 真实元表
|
|
129
|
+
if (!matched)
|
|
130
|
+
return { found: false };
|
|
131
|
+
const ptr = m.cm.lua_touserdata(m.state, abs);
|
|
132
|
+
const handle = m.cm.module.getValue(ptr, 'i32');
|
|
133
|
+
const obj = this.objects.get(handle);
|
|
134
|
+
if (obj === undefined)
|
|
135
|
+
return { found: false };
|
|
136
|
+
return { found: true, value: obj };
|
|
137
|
+
}
|
|
138
|
+
/** 把宿主对象压栈成 userdata。 */
|
|
139
|
+
pushObject(m, obj) {
|
|
140
|
+
const cls = obj.luaClass;
|
|
141
|
+
if (!cls)
|
|
142
|
+
return false;
|
|
143
|
+
this.registerClass(cls);
|
|
144
|
+
// 顺序很重要:先压 userdata,再压元表,
|
|
145
|
+
// 这样 lua_setmetatable(L, -2) 的 -2 才是 userdata(Lua 的约定是弹栈顶当元表)。
|
|
146
|
+
const before = m.top();
|
|
147
|
+
const handle = this.handleOf(obj);
|
|
148
|
+
this.handleRefs.set(handle, (this.handleRefs.get(handle) ?? 0) + 1);
|
|
149
|
+
const ptr = m.cm.lua_newuserdatauv(m.state, USERDATA_SIZE, 0);
|
|
150
|
+
m.cm.module.setValue(ptr, handle, 'i32');
|
|
151
|
+
this.pushMetatable(m, cls);
|
|
152
|
+
m.cm.lua_setmetatable(m.state, -2);
|
|
153
|
+
if (m.top() !== before + 1) {
|
|
154
|
+
throw new Error(`内部错误:把 ${cls.name} 压栈后栈不平衡`);
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
/* ---------------- 元表 ---------------- */
|
|
159
|
+
/** 把类的元表压栈(首次调用时创建并缓存到注册表)。 */
|
|
160
|
+
pushMetatable(m, cls) {
|
|
161
|
+
const cached = this.metatableRefs.get(cls.name);
|
|
162
|
+
if (cached !== undefined) {
|
|
163
|
+
m.cm.lua_rawgeti(m.state, marshal_1.LUA_REGISTRYINDEX, BigInt(cached));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const classId = this.registerClass(cls);
|
|
167
|
+
const base = m.top();
|
|
168
|
+
m.cm.lua_createtable(m.state, 0, 6);
|
|
169
|
+
const mt = m.top();
|
|
170
|
+
// 1) 方法表:名字 → 原始 C 函数
|
|
171
|
+
m.cm.lua_createtable(m.state, 0, Object.keys(cls.methods).length);
|
|
172
|
+
const methodTable = m.top();
|
|
173
|
+
for (const name of Object.keys(cls.methods)) {
|
|
174
|
+
m.pushString(name);
|
|
175
|
+
m.pushClosure(this.methodPtr, [BigInt(classId), name]);
|
|
176
|
+
m.cm.lua_rawset(m.state, methodTable);
|
|
177
|
+
}
|
|
178
|
+
// 2) __index = C 闭包(classId, 方法表)
|
|
179
|
+
m.pushInteger(BigInt(classId));
|
|
180
|
+
m.cm.lua_pushvalue(m.state, methodTable);
|
|
181
|
+
m.cm.lua_pushcclosure(m.state, this.indexPtr, 2);
|
|
182
|
+
m.cm.lua_setfield(m.state, mt, '__index');
|
|
183
|
+
// 3) __newindex = C 闭包(classId)
|
|
184
|
+
m.pushInteger(BigInt(classId));
|
|
185
|
+
m.cm.lua_pushcclosure(m.state, this.newIndexPtr, 1);
|
|
186
|
+
m.cm.lua_setfield(m.state, mt, '__newindex');
|
|
187
|
+
// 4) __gc(Lua 5.4 要求 setmetatable 之前元表里就有 __gc)
|
|
188
|
+
m.pushInteger(BigInt(classId));
|
|
189
|
+
m.cm.lua_pushcclosure(m.state, this.gcPtr, 1);
|
|
190
|
+
m.cm.lua_setfield(m.state, mt, '__gc');
|
|
191
|
+
m.pushString(cls.name);
|
|
192
|
+
m.cm.lua_setfield(m.state, mt, '__name');
|
|
193
|
+
// 与 CE 一样保护元表,避免脚本误改
|
|
194
|
+
m.pushString('protected metatable');
|
|
195
|
+
m.cm.lua_setfield(m.state, mt, '__metatable');
|
|
196
|
+
// 此刻栈:[元表(mt) at mt, 方法表 at mt+1]。
|
|
197
|
+
// luaL_ref 弹出的是**栈顶**,所以必须先把方法表弹掉,
|
|
198
|
+
// 否则缓存到注册表里的会是方法表而不是元表(早期版本的踩坑点)。
|
|
199
|
+
m.pop(1);
|
|
200
|
+
if (m.top() !== mt)
|
|
201
|
+
throw new Error('内部错误:元表不在栈顶');
|
|
202
|
+
const newRef = m.cm.luaL_ref(m.state, marshal_1.LUA_REGISTRYINDEX);
|
|
203
|
+
this.metatableRefs.set(cls.name, newRef);
|
|
204
|
+
m.setTop(base);
|
|
205
|
+
m.cm.lua_rawgeti(m.state, marshal_1.LUA_REGISTRYINDEX, BigInt(newRef));
|
|
206
|
+
}
|
|
207
|
+
/* ---------------- C 闭包实现 ---------------- */
|
|
208
|
+
header(state) {
|
|
209
|
+
return new marshal_1.LuaMarshal(this.cm, state, this);
|
|
210
|
+
}
|
|
211
|
+
classFromUpvalue(m, upIndex) {
|
|
212
|
+
const id = Number(m.toBigInt(m.cm.lua_upvalueindex(upIndex)));
|
|
213
|
+
const cls = this.classes.get(id);
|
|
214
|
+
if (!cls)
|
|
215
|
+
m.raise('内部错误:找不到桥接类');
|
|
216
|
+
return cls;
|
|
217
|
+
}
|
|
218
|
+
objectAt(m, index, methodName) {
|
|
219
|
+
const got = this.readUserdata(m, index);
|
|
220
|
+
if (!got.found) {
|
|
221
|
+
m.raise(`调用 ${methodName} 时 self 不是本引擎的对象(收到 ${m.typeName(index)})。` +
|
|
222
|
+
`对象方法请用冒号调用,例如 obj:${methodName}(...)`);
|
|
223
|
+
}
|
|
224
|
+
return got.value;
|
|
225
|
+
}
|
|
226
|
+
/** __index:先查方法表,再查属性 getter,最后查数字下标 / 附加字段。 */
|
|
227
|
+
closureIndex(state) {
|
|
228
|
+
const m = this.header(state);
|
|
229
|
+
try {
|
|
230
|
+
const cls = this.classFromUpvalue(m, 1);
|
|
231
|
+
const key = m.read(2);
|
|
232
|
+
// 方法表(rawget 避免递归触发 __index)
|
|
233
|
+
m.cm.lua_pushvalue(m.state, 2);
|
|
234
|
+
m.cm.lua_rawget(m.state, m.cm.lua_upvalueindex(2));
|
|
235
|
+
if (!m.isNil(-1))
|
|
236
|
+
return 1;
|
|
237
|
+
m.pop(1);
|
|
238
|
+
const obj = this.objectAt(m, 1, String(key ?? '?'));
|
|
239
|
+
if (typeof key === 'number' || typeof key === 'bigint') {
|
|
240
|
+
if (cls.indexKey) {
|
|
241
|
+
m.push(cls.indexKey(obj, Number(key)));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
m.pushNil();
|
|
245
|
+
}
|
|
246
|
+
return 1;
|
|
247
|
+
}
|
|
248
|
+
const name = String(key);
|
|
249
|
+
const prop = cls.properties[name];
|
|
250
|
+
if (prop && prop.get) {
|
|
251
|
+
m.push(prop.get(obj, m));
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
if (cls.extraIndex) {
|
|
255
|
+
m.push(cls.extraIndex(obj, name));
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
const extra = this.extras.get(obj);
|
|
259
|
+
if (extra && name in extra) {
|
|
260
|
+
m.push(extra[name]);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
m.pushNil();
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
if ((0, marshal_1.isLuaLongjmp)(e))
|
|
268
|
+
throw e;
|
|
269
|
+
m.raise(`读取属性失败:${(0, marshal_1.describeError)(e)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** __newindex:属性 setter / 数字下标 / 附加字段。 */
|
|
273
|
+
closureNewIndex(state) {
|
|
274
|
+
const m = this.header(state);
|
|
275
|
+
try {
|
|
276
|
+
const cls = this.classFromUpvalue(m, 1);
|
|
277
|
+
const key = m.read(2);
|
|
278
|
+
const value = m.read(3);
|
|
279
|
+
const obj = this.objectAt(m, 1, String(key ?? '?'));
|
|
280
|
+
if (typeof key === 'number' || typeof key === 'bigint') {
|
|
281
|
+
if (!cls.newIndexKey)
|
|
282
|
+
m.raise(`${cls.name} 不支持数字下标赋值`);
|
|
283
|
+
cls.newIndexKey(obj, Number(key), value);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
const name = String(key);
|
|
287
|
+
const prop = cls.properties[name];
|
|
288
|
+
if (prop && prop.set) {
|
|
289
|
+
prop.set(obj, value, m);
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
if (prop && prop.get && !prop.set) {
|
|
293
|
+
m.raise(`${cls.name}.${name} 是只读属性,不能赋值`);
|
|
294
|
+
}
|
|
295
|
+
if (cls.extraNewIndex) {
|
|
296
|
+
cls.extraNewIndex(obj, name, value);
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
// 未声明的属性:存进附加字段
|
|
300
|
+
let extra = this.extras.get(obj);
|
|
301
|
+
if (!extra) {
|
|
302
|
+
extra = {};
|
|
303
|
+
this.extras.set(obj, extra);
|
|
304
|
+
}
|
|
305
|
+
extra[name] = value;
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
if ((0, marshal_1.isLuaLongjmp)(e))
|
|
310
|
+
throw e;
|
|
311
|
+
m.raise(`写入属性失败:${(0, marshal_1.describeError)(e)}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** 方法分发:self 是参数 1,其余是方法参数。 */
|
|
315
|
+
closureMethod(state) {
|
|
316
|
+
const m = this.header(state);
|
|
317
|
+
try {
|
|
318
|
+
const cls = this.classFromUpvalue(m, 1);
|
|
319
|
+
const name = m.toString(m.cm.lua_upvalueindex(2));
|
|
320
|
+
const method = cls.methods[name];
|
|
321
|
+
if (!method)
|
|
322
|
+
m.raise(`${cls.name}.${name} 未实现`);
|
|
323
|
+
const obj = this.objectAt(m, 1, name);
|
|
324
|
+
const argc = m.top();
|
|
325
|
+
const args = [];
|
|
326
|
+
for (let i = 2; i <= argc; i++)
|
|
327
|
+
args.push(m.read(i));
|
|
328
|
+
const result = method(obj, args, m);
|
|
329
|
+
return m.pushResult(result);
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
if ((0, marshal_1.isLuaLongjmp)(e))
|
|
333
|
+
throw e;
|
|
334
|
+
m.raise(`调用对象方法失败:${(0, marshal_1.describeError)(e)}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* `__gc`:某个 userdata 被 Lua GC 回收时调用。
|
|
339
|
+
*
|
|
340
|
+
* ## 这里**不能**无条件 `objects.delete(handle)`
|
|
341
|
+
*
|
|
342
|
+
* 同一个宿主对象会有**多个** userdata 指向它(`getAddressList()` 是全局单例,
|
|
343
|
+
* 每调一次就多一个 userdata,但 handle 相同)。若某个 userdata 被回收就把
|
|
344
|
+
* handle 从 `objects` 里摘掉,**其余仍然存活的 userdata 会全部悬垂**:
|
|
345
|
+
* 它们读到的 handle 在表里查不到,`readUserdata` 返回 `found:false`,
|
|
346
|
+
* 于是后续任何属性/方法访问都抛出误导性的
|
|
347
|
+
* 「self 不是本引擎的对象(收到 userdata)」,而且是永久性的 ——
|
|
348
|
+
* 没有任何路径会把条目加回 `objects`。
|
|
349
|
+
*
|
|
350
|
+
* 所以正确做法是**引用计数**:每次 `pushObject` 加一,这里减一,
|
|
351
|
+
* 只有归零(即最后一个 userdata 也没了)才真正回收宿主对象。
|
|
352
|
+
*/
|
|
353
|
+
closureGc(state) {
|
|
354
|
+
const m = this.header(state);
|
|
355
|
+
try {
|
|
356
|
+
const cls = this.classFromUpvalue(m, 1);
|
|
357
|
+
if (m.type(1) !== marshal_1.LUA_TUSERDATA)
|
|
358
|
+
return 0;
|
|
359
|
+
const ptr = m.cm.lua_touserdata(m.state, 1);
|
|
360
|
+
const handle = m.cm.module.getValue(ptr, 'i32');
|
|
361
|
+
const remaining = (this.handleRefs.get(handle) ?? 1) - 1;
|
|
362
|
+
if (remaining > 0) {
|
|
363
|
+
// 还有别的 userdata 指着它:只减计数,宿主对象必须留着
|
|
364
|
+
this.handleRefs.set(handle, remaining);
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
this.handleRefs.delete(handle);
|
|
368
|
+
const obj = this.objects.get(handle);
|
|
369
|
+
if (obj !== undefined) {
|
|
370
|
+
this.objects.delete(handle);
|
|
371
|
+
if (cls.onGc)
|
|
372
|
+
cls.onGc(obj);
|
|
373
|
+
}
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
if ((0, marshal_1.isLuaLongjmp)(e))
|
|
378
|
+
throw e;
|
|
379
|
+
return 0; // __gc 内不能再抛错
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/** 引擎销毁时释放 addFunction 注册的函数指针。 */
|
|
383
|
+
dispose() {
|
|
384
|
+
for (const p of this.fnPtrs) {
|
|
385
|
+
try {
|
|
386
|
+
this.cm.module.removeFunction(p);
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
/* 忽略 */
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
this.fnPtrs.length = 0;
|
|
393
|
+
this.objects.clear();
|
|
394
|
+
this.handleRefs.clear();
|
|
395
|
+
this.metatableRefs.clear();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
exports.LuaObjectBridge = LuaObjectBridge;
|
|
399
|
+
/* ------------------------------------------------------------------ *
|
|
400
|
+
* StringList —— 对齐 CE 的 TStrings Lua 绑定
|
|
401
|
+
* ------------------------------------------------------------------ */
|
|
402
|
+
class LuaStringList {
|
|
403
|
+
luaClass = exports.STRING_LIST_CLASS;
|
|
404
|
+
items = [];
|
|
405
|
+
objects = [];
|
|
406
|
+
destroyed = false;
|
|
407
|
+
constructor(initial = []) {
|
|
408
|
+
this.items = initial.slice();
|
|
409
|
+
this.objects = new Array(this.items.length).fill(0);
|
|
410
|
+
}
|
|
411
|
+
get count() {
|
|
412
|
+
return this.items.length;
|
|
413
|
+
}
|
|
414
|
+
get isDestroyed() {
|
|
415
|
+
return this.destroyed;
|
|
416
|
+
}
|
|
417
|
+
toArray() {
|
|
418
|
+
return this.items.slice();
|
|
419
|
+
}
|
|
420
|
+
add(s, data = 0) {
|
|
421
|
+
this.items.push(s);
|
|
422
|
+
this.objects.push(data);
|
|
423
|
+
return this.items.length - 1;
|
|
424
|
+
}
|
|
425
|
+
addText(text) {
|
|
426
|
+
for (const line of text.split(/\r?\n/))
|
|
427
|
+
this.add(line);
|
|
428
|
+
}
|
|
429
|
+
clear() {
|
|
430
|
+
this.items = [];
|
|
431
|
+
this.objects = [];
|
|
432
|
+
}
|
|
433
|
+
delete(index) {
|
|
434
|
+
if (index >= 0 && index < this.items.length) {
|
|
435
|
+
this.items.splice(index, 1);
|
|
436
|
+
this.objects.splice(index, 1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getString(index) {
|
|
440
|
+
return index >= 0 && index < this.items.length ? this.items[index] : null;
|
|
441
|
+
}
|
|
442
|
+
setString(index, value) {
|
|
443
|
+
if (index >= 0 && index < this.items.length)
|
|
444
|
+
this.items[index] = value;
|
|
445
|
+
}
|
|
446
|
+
indexOf(value) {
|
|
447
|
+
return this.items.indexOf(value);
|
|
448
|
+
}
|
|
449
|
+
insert(index, value) {
|
|
450
|
+
const i = Math.max(0, Math.min(index, this.items.length));
|
|
451
|
+
this.items.splice(i, 0, value);
|
|
452
|
+
this.objects.splice(i, 0, 0);
|
|
453
|
+
}
|
|
454
|
+
remove(value) {
|
|
455
|
+
const i = this.items.indexOf(value);
|
|
456
|
+
if (i !== -1)
|
|
457
|
+
this.delete(i);
|
|
458
|
+
}
|
|
459
|
+
getText() {
|
|
460
|
+
return this.items.join('\n');
|
|
461
|
+
}
|
|
462
|
+
setText(text) {
|
|
463
|
+
this.clear();
|
|
464
|
+
this.addText(text);
|
|
465
|
+
}
|
|
466
|
+
getData(index) {
|
|
467
|
+
return this.objects[index] ?? 0;
|
|
468
|
+
}
|
|
469
|
+
setData(index, value) {
|
|
470
|
+
if (index >= 0 && index < this.items.length)
|
|
471
|
+
this.objects[index] = value;
|
|
472
|
+
}
|
|
473
|
+
destroy() {
|
|
474
|
+
this.clear();
|
|
475
|
+
this.destroyed = true;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
exports.LuaStringList = LuaStringList;
|
|
479
|
+
exports.STRING_LIST_CLASS = {
|
|
480
|
+
name: 'nceStringList',
|
|
481
|
+
methods: {
|
|
482
|
+
add(obj, args, m) {
|
|
483
|
+
const s = m.argToString(args, 1, 'StringList:add');
|
|
484
|
+
const data = args[1] === undefined ? 0 : m.argToInt(args, 2, 'StringList:add', 0);
|
|
485
|
+
return BigInt(obj.add(s, data));
|
|
486
|
+
},
|
|
487
|
+
addText(obj, args, m) {
|
|
488
|
+
obj.addText(m.argToString(args, 1, 'StringList:addText'));
|
|
489
|
+
},
|
|
490
|
+
clear(obj) {
|
|
491
|
+
obj.clear();
|
|
492
|
+
},
|
|
493
|
+
delete(obj, args, m) {
|
|
494
|
+
obj.delete(m.argToInt(args, 1, 'StringList:delete'));
|
|
495
|
+
},
|
|
496
|
+
getString(obj, args, m) {
|
|
497
|
+
return obj.getString(m.argToInt(args, 1, 'StringList:getString'));
|
|
498
|
+
},
|
|
499
|
+
setString(obj, args, m) {
|
|
500
|
+
obj.setString(m.argToInt(args, 1, 'StringList:setString'), m.argToString(args, 2, 'StringList:setString'));
|
|
501
|
+
},
|
|
502
|
+
getCount(obj) {
|
|
503
|
+
return BigInt(obj.count);
|
|
504
|
+
},
|
|
505
|
+
indexOf(obj, args, m) {
|
|
506
|
+
return BigInt(obj.indexOf(m.argToString(args, 1, 'StringList:indexOf')));
|
|
507
|
+
},
|
|
508
|
+
insert(obj, args, m) {
|
|
509
|
+
obj.insert(m.argToInt(args, 1, 'StringList:insert'), m.argToString(args, 2, 'StringList:insert'));
|
|
510
|
+
},
|
|
511
|
+
remove(obj, args, m) {
|
|
512
|
+
obj.remove(m.argToString(args, 1, 'StringList:remove'));
|
|
513
|
+
},
|
|
514
|
+
getText(obj) {
|
|
515
|
+
return obj.getText();
|
|
516
|
+
},
|
|
517
|
+
setText(obj, args, m) {
|
|
518
|
+
obj.setText(m.argToString(args, 1, 'StringList:setText'));
|
|
519
|
+
},
|
|
520
|
+
getData(obj, args, m) {
|
|
521
|
+
return BigInt(obj.getData(m.argToInt(args, 1, 'StringList:getData')));
|
|
522
|
+
},
|
|
523
|
+
setData(obj, args, m) {
|
|
524
|
+
obj.setData(m.argToInt(args, 1, 'StringList:setData'), m.argToInt(args, 2, 'StringList:setData'));
|
|
525
|
+
},
|
|
526
|
+
destroy(obj) {
|
|
527
|
+
obj.destroy();
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
properties: {
|
|
531
|
+
Count: { get: (obj) => BigInt(obj.count) },
|
|
532
|
+
Text: {
|
|
533
|
+
get: (obj) => obj.getText(),
|
|
534
|
+
set: (obj, v) => obj.setText(typeof v === 'string' ? v : String(v ?? '')),
|
|
535
|
+
},
|
|
536
|
+
Destroyed: { get: (obj) => obj.isDestroyed },
|
|
537
|
+
},
|
|
538
|
+
// list[i] —— CE 的默认数组属性是 0 基
|
|
539
|
+
indexKey: (obj, key) => obj.getString(key),
|
|
540
|
+
newIndexKey: (obj, key, value) => obj.setString(key, typeof value === 'string' ? value : String(value ?? '')),
|
|
541
|
+
};
|
|
542
|
+
class LuaMemoryRecord {
|
|
543
|
+
luaClass = exports.MEMORY_RECORD_CLASS;
|
|
544
|
+
address;
|
|
545
|
+
description;
|
|
546
|
+
type;
|
|
547
|
+
active = true;
|
|
548
|
+
selected = false;
|
|
549
|
+
id;
|
|
550
|
+
cachedValue = '';
|
|
551
|
+
deleted = false;
|
|
552
|
+
host;
|
|
553
|
+
constructor(host, address, description, type, id) {
|
|
554
|
+
this.host = host;
|
|
555
|
+
this.address = address;
|
|
556
|
+
this.description = description;
|
|
557
|
+
this.type = type;
|
|
558
|
+
this.id = id;
|
|
559
|
+
}
|
|
560
|
+
/** 解析出当前真实地址(对齐 CE 的 GetRealAddress)。 */
|
|
561
|
+
currentAddress() {
|
|
562
|
+
return this.host.resolve(this.address);
|
|
563
|
+
}
|
|
564
|
+
width() {
|
|
565
|
+
switch (this.type) {
|
|
566
|
+
case types_1.VariableType.String:
|
|
567
|
+
return ANSI_STRING_LENGTH;
|
|
568
|
+
case types_1.VariableType.UnicodeString:
|
|
569
|
+
return WIDE_STRING_LENGTH * 2;
|
|
570
|
+
default:
|
|
571
|
+
return (0, scan_1.variableWidth)(this.type) || 4;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/** 读值并格式化成字符串(CE 的 MemoryRecord.Value 就是字符串)。 */
|
|
575
|
+
readValueString() {
|
|
576
|
+
if (!this.active)
|
|
577
|
+
return this.cachedValue;
|
|
578
|
+
try {
|
|
579
|
+
const raw = this.host.read(this.currentAddress(), this.width());
|
|
580
|
+
const text = formatRecordValue(raw, this.type);
|
|
581
|
+
this.cachedValue = text;
|
|
582
|
+
return text;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
return '';
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/** 写值(对齐 CE:setValue 会真的写进目标内存)。 */
|
|
589
|
+
writeValueString(value) {
|
|
590
|
+
this.cachedValue = value;
|
|
591
|
+
if (!this.active)
|
|
592
|
+
return;
|
|
593
|
+
this.host.write(this.currentAddress(), encodeRecordValue(value, this.type));
|
|
594
|
+
}
|
|
595
|
+
numericalValue() {
|
|
596
|
+
if (!this.active) {
|
|
597
|
+
const parsed = Number(this.cachedValue);
|
|
598
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
599
|
+
}
|
|
600
|
+
try {
|
|
601
|
+
return readNumerical(this.host.read(this.currentAddress(), this.width()), this.type);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
setNumericalValue(value) {
|
|
608
|
+
this.writeValueString(String(value));
|
|
609
|
+
}
|
|
610
|
+
markDeleted() {
|
|
611
|
+
this.deleted = true;
|
|
612
|
+
}
|
|
613
|
+
get isDeleted() {
|
|
614
|
+
return this.deleted;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
exports.LuaMemoryRecord = LuaMemoryRecord;
|
|
618
|
+
/** 把读到的原始字节格式化成 CE 风格的字符串。 */
|
|
619
|
+
function formatRecordValue(raw, type) {
|
|
620
|
+
switch (type) {
|
|
621
|
+
case types_1.VariableType.Byte:
|
|
622
|
+
return String(raw.readUInt8(0));
|
|
623
|
+
case types_1.VariableType.Word:
|
|
624
|
+
return String(raw.readUInt16LE(0));
|
|
625
|
+
case types_1.VariableType.DWord:
|
|
626
|
+
return String(raw.readUInt32LE(0));
|
|
627
|
+
case types_1.VariableType.QWord:
|
|
628
|
+
case types_1.VariableType.Pointer:
|
|
629
|
+
return raw.readBigUInt64LE(0).toString();
|
|
630
|
+
case types_1.VariableType.Single:
|
|
631
|
+
return String(raw.readFloatLE(0));
|
|
632
|
+
case types_1.VariableType.Double:
|
|
633
|
+
return String(raw.readDoubleLE(0));
|
|
634
|
+
case types_1.VariableType.String:
|
|
635
|
+
case types_1.VariableType.CodePageString: {
|
|
636
|
+
const end = raw.indexOf(0);
|
|
637
|
+
return raw.toString('latin1', 0, end === -1 ? raw.length : end);
|
|
638
|
+
}
|
|
639
|
+
case types_1.VariableType.UnicodeString: {
|
|
640
|
+
let end = raw.length;
|
|
641
|
+
for (let i = 0; i + 1 < raw.length; i += 2) {
|
|
642
|
+
if (raw.readUInt16LE(i) === 0) {
|
|
643
|
+
end = i;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return raw.toString('utf16le', 0, end);
|
|
648
|
+
}
|
|
649
|
+
default: {
|
|
650
|
+
const parts = [];
|
|
651
|
+
for (const b of raw.subarray(0, 16))
|
|
652
|
+
parts.push(b.toString(16).toUpperCase().padStart(2, '0'));
|
|
653
|
+
return parts.join(' ');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/** 把 CE 风格的字符串编码成要写入的字节。 */
|
|
658
|
+
function encodeRecordValue(value, type) {
|
|
659
|
+
const text = value.trim();
|
|
660
|
+
switch (type) {
|
|
661
|
+
case types_1.VariableType.Byte: {
|
|
662
|
+
const buf = Buffer.alloc(1);
|
|
663
|
+
buf.writeUInt8(Number(parseIntLiteral(text) & 0xffn), 0);
|
|
664
|
+
return buf;
|
|
665
|
+
}
|
|
666
|
+
case types_1.VariableType.Word: {
|
|
667
|
+
const buf = Buffer.alloc(2);
|
|
668
|
+
buf.writeUInt16LE(Number(parseIntLiteral(text) & 0xffffn), 0);
|
|
669
|
+
return buf;
|
|
670
|
+
}
|
|
671
|
+
case types_1.VariableType.DWord: {
|
|
672
|
+
const buf = Buffer.alloc(4);
|
|
673
|
+
buf.writeUInt32LE(Number(parseIntLiteral(text) & 0xffffffffn), 0);
|
|
674
|
+
return buf;
|
|
675
|
+
}
|
|
676
|
+
case types_1.VariableType.QWord:
|
|
677
|
+
case types_1.VariableType.Pointer: {
|
|
678
|
+
const buf = Buffer.alloc(8);
|
|
679
|
+
buf.writeBigUInt64LE(BigInt.asUintN(64, parseIntLiteral(text)), 0);
|
|
680
|
+
return buf;
|
|
681
|
+
}
|
|
682
|
+
case types_1.VariableType.Single: {
|
|
683
|
+
const buf = Buffer.alloc(4);
|
|
684
|
+
buf.writeFloatLE(Number(text), 0);
|
|
685
|
+
return buf;
|
|
686
|
+
}
|
|
687
|
+
case types_1.VariableType.Double: {
|
|
688
|
+
const buf = Buffer.alloc(8);
|
|
689
|
+
buf.writeDoubleLE(Number(text), 0);
|
|
690
|
+
return buf;
|
|
691
|
+
}
|
|
692
|
+
case types_1.VariableType.String:
|
|
693
|
+
case types_1.VariableType.CodePageString:
|
|
694
|
+
return Buffer.from(value, 'latin1');
|
|
695
|
+
case types_1.VariableType.UnicodeString:
|
|
696
|
+
return Buffer.from(value, 'utf16le');
|
|
697
|
+
default: {
|
|
698
|
+
const hex = text.replace(/[^0-9a-fA-F]/g, '');
|
|
699
|
+
const out = [];
|
|
700
|
+
for (let i = 0; i + 1 < hex.length; i += 2)
|
|
701
|
+
out.push(parseInt(hex.slice(i, i + 2), 16));
|
|
702
|
+
return Buffer.from(out);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/** 解析 CE 风格的整数字面量(十进制 / 0x / $ 前缀 / 纯十六进制)。 */
|
|
707
|
+
function parseIntLiteral(text) {
|
|
708
|
+
const s = text.trim();
|
|
709
|
+
try {
|
|
710
|
+
if (/^\$/.test(s))
|
|
711
|
+
return BigInt('0x' + s.slice(1));
|
|
712
|
+
if (/^0x/i.test(s))
|
|
713
|
+
return BigInt(s);
|
|
714
|
+
if (/^-?\d+$/.test(s))
|
|
715
|
+
return BigInt(s);
|
|
716
|
+
return BigInt('0x' + s);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return 0n;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function readNumerical(raw, type) {
|
|
723
|
+
switch (type) {
|
|
724
|
+
case types_1.VariableType.Byte:
|
|
725
|
+
return raw.readUInt8(0);
|
|
726
|
+
case types_1.VariableType.Word:
|
|
727
|
+
return raw.readUInt16LE(0);
|
|
728
|
+
case types_1.VariableType.DWord:
|
|
729
|
+
return raw.readUInt32LE(0);
|
|
730
|
+
case types_1.VariableType.QWord:
|
|
731
|
+
case types_1.VariableType.Pointer: {
|
|
732
|
+
const v = raw.readBigUInt64LE(0);
|
|
733
|
+
return v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v;
|
|
734
|
+
}
|
|
735
|
+
case types_1.VariableType.Single:
|
|
736
|
+
return raw.readFloatLE(0);
|
|
737
|
+
case types_1.VariableType.Double:
|
|
738
|
+
return raw.readDoubleLE(0);
|
|
739
|
+
default:
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
class LuaAddressList {
|
|
744
|
+
luaClass = exports.ADDRESS_LIST_CLASS;
|
|
745
|
+
records = [];
|
|
746
|
+
selected = null;
|
|
747
|
+
nextId = 1;
|
|
748
|
+
host;
|
|
749
|
+
constructor(host) {
|
|
750
|
+
this.host = host;
|
|
751
|
+
}
|
|
752
|
+
get count() {
|
|
753
|
+
return this.records.length;
|
|
754
|
+
}
|
|
755
|
+
createMemoryRecord(address = '', description = '', type = types_1.VariableType.DWord) {
|
|
756
|
+
const rec = new LuaMemoryRecord(this.host, address, description, type, this.nextId++);
|
|
757
|
+
this.records.push(rec);
|
|
758
|
+
return rec;
|
|
759
|
+
}
|
|
760
|
+
getMemoryRecord(index) {
|
|
761
|
+
return index >= 0 && index < this.records.length ? this.records[index] : null;
|
|
762
|
+
}
|
|
763
|
+
getMemoryRecordByDescription(description) {
|
|
764
|
+
return this.records.find((r) => r.description === description) ?? null;
|
|
765
|
+
}
|
|
766
|
+
getMemoryRecordByID(id) {
|
|
767
|
+
return this.records.find((r) => r.id === id) ?? null;
|
|
768
|
+
}
|
|
769
|
+
select(rec) {
|
|
770
|
+
if (this.selected)
|
|
771
|
+
this.selected.selected = false;
|
|
772
|
+
this.selected = rec;
|
|
773
|
+
if (rec)
|
|
774
|
+
rec.selected = true;
|
|
775
|
+
}
|
|
776
|
+
get selectedRecord() {
|
|
777
|
+
return this.selected;
|
|
778
|
+
}
|
|
779
|
+
remove(rec) {
|
|
780
|
+
const i = this.records.indexOf(rec);
|
|
781
|
+
if (i !== -1)
|
|
782
|
+
this.records.splice(i, 1);
|
|
783
|
+
if (this.selected === rec)
|
|
784
|
+
this.selected = null;
|
|
785
|
+
rec.markDeleted();
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
exports.LuaAddressList = LuaAddressList;
|
|
789
|
+
exports.ADDRESS_LIST_CLASS = {
|
|
790
|
+
name: 'nceAddressList',
|
|
791
|
+
methods: {
|
|
792
|
+
getCount(obj) {
|
|
793
|
+
return BigInt(obj.count);
|
|
794
|
+
},
|
|
795
|
+
getMemoryRecord(obj, args, m) {
|
|
796
|
+
return obj.getMemoryRecord(m.argToInt(args, 1, 'AddressList:getMemoryRecord'));
|
|
797
|
+
},
|
|
798
|
+
getMemoryRecordByDescription(obj, args, m) {
|
|
799
|
+
return obj.getMemoryRecordByDescription(m.argToString(args, 1, 'AddressList:getMemoryRecordByDescription'));
|
|
800
|
+
},
|
|
801
|
+
getMemoryRecordByID(obj, args, m) {
|
|
802
|
+
return obj.getMemoryRecordByID(m.argToInt(args, 1, 'AddressList:getMemoryRecordByID'));
|
|
803
|
+
},
|
|
804
|
+
createMemoryRecord(obj, args, m) {
|
|
805
|
+
const address = args[0] === undefined ? '' : m.argToString(args, 1, 'AddressList:createMemoryRecord');
|
|
806
|
+
const description = args[1] === undefined ? '' : m.argToString(args, 2, 'AddressList:createMemoryRecord');
|
|
807
|
+
const type = args[2] === undefined
|
|
808
|
+
? types_1.VariableType.DWord
|
|
809
|
+
: (0, scan_1.parseVariableType)(m, args[2], 'AddressList:createMemoryRecord');
|
|
810
|
+
return obj.createMemoryRecord(address, description, type);
|
|
811
|
+
},
|
|
812
|
+
getSelectedRecord(obj) {
|
|
813
|
+
return obj.selectedRecord;
|
|
814
|
+
},
|
|
815
|
+
setSelectedRecord(obj, args) {
|
|
816
|
+
obj.select(args[0] ?? null);
|
|
817
|
+
},
|
|
818
|
+
delete(obj, args) {
|
|
819
|
+
const rec = args[0];
|
|
820
|
+
if (rec)
|
|
821
|
+
obj.remove(rec);
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
properties: {
|
|
825
|
+
Count: { get: (obj) => BigInt(obj.count) },
|
|
826
|
+
SelectedRecord: {
|
|
827
|
+
get: (obj) => obj.selectedRecord,
|
|
828
|
+
set: (obj, v) => obj.select(v ?? null),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
// addresslist[0] → getMemoryRecord(0)(CE 的默认数组属性)
|
|
832
|
+
indexKey: (obj, key) => obj.getMemoryRecord(key),
|
|
833
|
+
};
|
|
834
|
+
exports.MEMORY_RECORD_CLASS = {
|
|
835
|
+
name: 'nceMemoryRecord',
|
|
836
|
+
methods: {
|
|
837
|
+
getAddress(obj) {
|
|
838
|
+
return obj.address;
|
|
839
|
+
},
|
|
840
|
+
setAddress(obj, args, m) {
|
|
841
|
+
const v = m.requireArg(args, 1, 'MemoryRecord:setAddress');
|
|
842
|
+
obj.address =
|
|
843
|
+
typeof v === 'number' || typeof v === 'bigint'
|
|
844
|
+
? '0x' + BigInt(v).toString(16)
|
|
845
|
+
: String(v);
|
|
846
|
+
},
|
|
847
|
+
getCurrentAddress(obj) {
|
|
848
|
+
try {
|
|
849
|
+
return obj.currentAddress();
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
getDescription(obj) {
|
|
856
|
+
return obj.description;
|
|
857
|
+
},
|
|
858
|
+
setDescription(obj, args, m) {
|
|
859
|
+
obj.description = m.argToString(args, 1, 'MemoryRecord:setDescription');
|
|
860
|
+
},
|
|
861
|
+
getType(obj) {
|
|
862
|
+
return BigInt(obj.type);
|
|
863
|
+
},
|
|
864
|
+
setType(obj, args, m) {
|
|
865
|
+
obj.type = (0, scan_1.parseVariableType)(m, m.requireArg(args, 1, 'MemoryRecord:setType'), 'MemoryRecord:setType');
|
|
866
|
+
},
|
|
867
|
+
getValue(obj) {
|
|
868
|
+
return obj.readValueString();
|
|
869
|
+
},
|
|
870
|
+
setValue(obj, args, m) {
|
|
871
|
+
const v = m.requireArg(args, 1, 'MemoryRecord:setValue');
|
|
872
|
+
obj.writeValueString(typeof v === 'string' ? v : String(v));
|
|
873
|
+
},
|
|
874
|
+
getNumericalValue(obj) {
|
|
875
|
+
return obj.numericalValue();
|
|
876
|
+
},
|
|
877
|
+
setNumericalValue(obj, args, m) {
|
|
878
|
+
obj.setNumericalValue(m.argToNumber(args, 1, 'MemoryRecord:setNumericalValue'));
|
|
879
|
+
},
|
|
880
|
+
getActive(obj) {
|
|
881
|
+
return obj.active;
|
|
882
|
+
},
|
|
883
|
+
setActive(obj, args) {
|
|
884
|
+
obj.active = args[0] !== false && args[0] !== null && args[0] !== undefined;
|
|
885
|
+
},
|
|
886
|
+
getID(obj) {
|
|
887
|
+
return BigInt(obj.id);
|
|
888
|
+
},
|
|
889
|
+
delete(obj) {
|
|
890
|
+
obj.markDeleted();
|
|
891
|
+
},
|
|
892
|
+
isSelected(obj) {
|
|
893
|
+
return obj.selected;
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
properties: {
|
|
897
|
+
Address: {
|
|
898
|
+
get: (obj) => obj.address,
|
|
899
|
+
set: (obj, v) => (obj.address = typeof v === 'number' || typeof v === 'bigint' ? '0x' + BigInt(v).toString(16) : String(v ?? '')),
|
|
900
|
+
},
|
|
901
|
+
CurrentAddress: {
|
|
902
|
+
get: (obj) => {
|
|
903
|
+
try {
|
|
904
|
+
return obj.currentAddress();
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
Description: {
|
|
912
|
+
get: (obj) => obj.description,
|
|
913
|
+
set: (obj, v) => {
|
|
914
|
+
obj.description = String(v ?? '');
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
Type: {
|
|
918
|
+
get: (obj) => BigInt(obj.type),
|
|
919
|
+
set: (obj, v, m) => {
|
|
920
|
+
obj.type = (0, scan_1.parseVariableType)(m, v, 'MemoryRecord.Type');
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
Value: {
|
|
924
|
+
get: (obj) => obj.readValueString(),
|
|
925
|
+
set: (obj, v) => obj.writeValueString(typeof v === 'string' ? v : String(v ?? '')),
|
|
926
|
+
},
|
|
927
|
+
NumericalValue: {
|
|
928
|
+
get: (obj) => obj.numericalValue(),
|
|
929
|
+
set: (obj, v) => obj.setNumericalValue(typeof v === 'number' || typeof v === 'bigint' ? v : Number(v ?? 0)),
|
|
930
|
+
},
|
|
931
|
+
Active: {
|
|
932
|
+
get: (obj) => obj.active,
|
|
933
|
+
set: (obj, v) => {
|
|
934
|
+
obj.active = v !== false && v !== null && v !== undefined;
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
ID: { get: (obj) => BigInt(obj.id) },
|
|
938
|
+
Selected: { get: (obj) => obj.selected },
|
|
939
|
+
},
|
|
940
|
+
};
|
|
941
|
+
/* ------------------------------------------------------------------ *
|
|
942
|
+
* ScanSession
|
|
943
|
+
* ------------------------------------------------------------------ */
|
|
944
|
+
class LuaScanSession {
|
|
945
|
+
luaClass = exports.SCAN_SESSION_CLASS;
|
|
946
|
+
session;
|
|
947
|
+
proc;
|
|
948
|
+
destroyed = false;
|
|
949
|
+
constructor(proc, facade, options = {}) {
|
|
950
|
+
this.proc = proc;
|
|
951
|
+
this.session = facade.createSession(proc, options);
|
|
952
|
+
}
|
|
953
|
+
assertAlive() {
|
|
954
|
+
if (this.destroyed)
|
|
955
|
+
throw new Error('扫描会话已销毁(destroy)');
|
|
956
|
+
}
|
|
957
|
+
firstScan(cfg) {
|
|
958
|
+
this.assertAlive();
|
|
959
|
+
return this.session.firstScan(cfg).resultCount;
|
|
960
|
+
}
|
|
961
|
+
nextScan(cfg) {
|
|
962
|
+
this.assertAlive();
|
|
963
|
+
return this.session.nextScan(cfg).resultCount;
|
|
964
|
+
}
|
|
965
|
+
get count() {
|
|
966
|
+
return this.session.resultCount;
|
|
967
|
+
}
|
|
968
|
+
result(index) {
|
|
969
|
+
const all = this.session.results;
|
|
970
|
+
return index >= 0 && index < all.length ? all[index] : null;
|
|
971
|
+
}
|
|
972
|
+
all() {
|
|
973
|
+
return this.session.results.slice();
|
|
974
|
+
}
|
|
975
|
+
/** 读取当前结果的值(按类型)。 */
|
|
976
|
+
values(type, max = 4096) {
|
|
977
|
+
this.assertAlive();
|
|
978
|
+
const width = (0, scan_1.variableWidth)(type) || 4;
|
|
979
|
+
const out = [];
|
|
980
|
+
const all = this.session.results;
|
|
981
|
+
for (const addr of all) {
|
|
982
|
+
if (out.length >= max)
|
|
983
|
+
break;
|
|
984
|
+
const buf = this.proc.readMemory(addr, width);
|
|
985
|
+
if (buf.length < width)
|
|
986
|
+
continue;
|
|
987
|
+
out.push(readNumerical(buf, type));
|
|
988
|
+
}
|
|
989
|
+
return out;
|
|
990
|
+
}
|
|
991
|
+
reset() {
|
|
992
|
+
this.session.reset();
|
|
993
|
+
}
|
|
994
|
+
destroy() {
|
|
995
|
+
if (this.destroyed)
|
|
996
|
+
return;
|
|
997
|
+
this.destroyed = true;
|
|
998
|
+
this.session.dispose();
|
|
999
|
+
}
|
|
1000
|
+
get stats() {
|
|
1001
|
+
return this.session.stats;
|
|
1002
|
+
}
|
|
1003
|
+
get processId() {
|
|
1004
|
+
return this.proc.pid;
|
|
1005
|
+
}
|
|
1006
|
+
get hasSnapshot() {
|
|
1007
|
+
return this.session.stats?.hasSnapshot ?? false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
exports.LuaScanSession = LuaScanSession;
|
|
1011
|
+
exports.SCAN_SESSION_CLASS = {
|
|
1012
|
+
name: 'nceScanSession',
|
|
1013
|
+
methods: {
|
|
1014
|
+
firstScan(obj, args, m) {
|
|
1015
|
+
const cfg = (0, scan_1.parseScanConfig)(m, m.requireArg(args, 1, 'ScanSession:firstScan'), 'ScanSession:firstScan');
|
|
1016
|
+
return BigInt(obj.firstScan(cfg));
|
|
1017
|
+
},
|
|
1018
|
+
nextScan(obj, args, m) {
|
|
1019
|
+
const cfg = (0, scan_1.parseScanConfig)(m, m.requireArg(args, 1, 'ScanSession:nextScan'), 'ScanSession:nextScan');
|
|
1020
|
+
return BigInt(obj.nextScan(cfg));
|
|
1021
|
+
},
|
|
1022
|
+
getCount(obj) {
|
|
1023
|
+
return BigInt(obj.count);
|
|
1024
|
+
},
|
|
1025
|
+
getResult(obj, args, m) {
|
|
1026
|
+
return obj.result(m.argToInt(args, 1, 'ScanSession:getResult'));
|
|
1027
|
+
},
|
|
1028
|
+
getResults(obj) {
|
|
1029
|
+
return obj.all();
|
|
1030
|
+
},
|
|
1031
|
+
getValues(obj, args, m) {
|
|
1032
|
+
const type = (0, scan_1.parseVariableType)(m, m.requireArg(args, 1, 'ScanSession:getValues'), 'ScanSession:getValues');
|
|
1033
|
+
const max = args[1] === undefined ? 4096 : m.argToInt(args, 2, 'ScanSession:getValues');
|
|
1034
|
+
return obj.values(type, max);
|
|
1035
|
+
},
|
|
1036
|
+
getStats(obj) {
|
|
1037
|
+
const stats = obj.stats;
|
|
1038
|
+
if (!stats)
|
|
1039
|
+
return null;
|
|
1040
|
+
return {
|
|
1041
|
+
regionsScanned: Number(stats.regionsScanned),
|
|
1042
|
+
bytesScanned: stats.bytesScanned,
|
|
1043
|
+
elapsedMs: Number(stats.elapsedMs),
|
|
1044
|
+
resultCount: Number(stats.resultCount),
|
|
1045
|
+
hasSnapshot: Boolean(stats.hasSnapshot),
|
|
1046
|
+
workersUsed: Number(stats.workersUsed),
|
|
1047
|
+
};
|
|
1048
|
+
},
|
|
1049
|
+
reset(obj) {
|
|
1050
|
+
obj.reset();
|
|
1051
|
+
},
|
|
1052
|
+
destroy(obj) {
|
|
1053
|
+
obj.destroy();
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
properties: {
|
|
1057
|
+
Count: { get: (obj) => BigInt(obj.count) },
|
|
1058
|
+
ProcessID: { get: (obj) => BigInt(obj.processId) },
|
|
1059
|
+
HasSnapshot: { get: (obj) => obj.hasSnapshot },
|
|
1060
|
+
},
|
|
1061
|
+
indexKey: (obj, key) => obj.result(key),
|
|
1062
|
+
};
|
|
1063
|
+
/** 注册全部内置类。 */
|
|
1064
|
+
function registerBuiltinClasses(bridge) {
|
|
1065
|
+
bridge.registerClass(exports.STRING_LIST_CLASS);
|
|
1066
|
+
bridge.registerClass(exports.ADDRESS_LIST_CLASS);
|
|
1067
|
+
bridge.registerClass(exports.MEMORY_RECORD_CLASS);
|
|
1068
|
+
bridge.registerClass(exports.SCAN_SESSION_CLASS);
|
|
1069
|
+
}
|
|
1070
|
+
//# sourceMappingURL=objects.js.map
|