@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,650 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 地址表达式求值 —— 对齐 Cheat Engine `addressparser.pas` 的功能子集。
|
|
4
|
+
*
|
|
5
|
+
* CE 的地址栏支持这些写法(这也是本模块要支持的):
|
|
6
|
+
* - 十六进制 / 十进制字面量:`1234ABCD`、`0x1234`、`$1234`、`1234`
|
|
7
|
+
* - 模块相对地址:`"game.exe"+1234`、`game.exe+1234`
|
|
8
|
+
* - 符号名:已注册的符号,或模块内的导出符号
|
|
9
|
+
* - 解引用:`[[ game.exe+1234 ]+10]`、`[rax]`(寄存器在脚本上下文才有意义)
|
|
10
|
+
* - 算术:`+ - * / % << >> & | ^ ~`,以及括号
|
|
11
|
+
* - 内置函数:`GetModuleBase("x")`、`GetModuleSize("x")`、`GetAddress("expr")`
|
|
12
|
+
* - 寄存器占位:`EAX`/`RAX` 等 —— 仅在提供了寄存器上下文的场景下解析,
|
|
13
|
+
* 否则报错(本移植不含调试器,故没有真实的寄存器上下文)
|
|
14
|
+
*
|
|
15
|
+
* 设计说明:表达式先编译成 AST(可在表中反复复用,避免每次重解析),
|
|
16
|
+
* 求值时再访问模块列表与符号表,这样模块基址变化(重启进程)不需要重编译。
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.CompiledAddress = exports.ExpressionError = void 0;
|
|
20
|
+
exports.compileExpression = compileExpression;
|
|
21
|
+
exports.evaluateExpression = evaluateExpression;
|
|
22
|
+
exports.resolveAddress = resolveAddress;
|
|
23
|
+
const winapi_1 = require("../win/winapi");
|
|
24
|
+
/** 表达式求值错误 */
|
|
25
|
+
class ExpressionError extends Error {
|
|
26
|
+
expression;
|
|
27
|
+
position;
|
|
28
|
+
constructor(message, expression, position) {
|
|
29
|
+
super(position !== undefined ? `${message}(位置 ${position}):"${expression}"` : `${message}:"${expression}"`);
|
|
30
|
+
this.expression = expression;
|
|
31
|
+
this.position = position;
|
|
32
|
+
this.name = 'ExpressionError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.ExpressionError = ExpressionError;
|
|
36
|
+
const PUNCT = [
|
|
37
|
+
'<<',
|
|
38
|
+
'>>',
|
|
39
|
+
'<=',
|
|
40
|
+
'>=',
|
|
41
|
+
'==',
|
|
42
|
+
'!=',
|
|
43
|
+
'&&',
|
|
44
|
+
'||',
|
|
45
|
+
'+',
|
|
46
|
+
'-',
|
|
47
|
+
'*',
|
|
48
|
+
'/',
|
|
49
|
+
'%',
|
|
50
|
+
'&',
|
|
51
|
+
'|',
|
|
52
|
+
'^',
|
|
53
|
+
'~',
|
|
54
|
+
'(',
|
|
55
|
+
')',
|
|
56
|
+
'[',
|
|
57
|
+
']',
|
|
58
|
+
',',
|
|
59
|
+
'<',
|
|
60
|
+
'>',
|
|
61
|
+
];
|
|
62
|
+
function isDigit(c) {
|
|
63
|
+
return c >= '0' && c <= '9';
|
|
64
|
+
}
|
|
65
|
+
function isHexDigit(c) {
|
|
66
|
+
return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
67
|
+
}
|
|
68
|
+
function isIdentStart(c) {
|
|
69
|
+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_' || c === '.' || c === ':' || c === '@' || c === '?';
|
|
70
|
+
}
|
|
71
|
+
function isIdentChar(c) {
|
|
72
|
+
return isIdentStart(c) || isDigit(c) || c === '\\' || c === '/';
|
|
73
|
+
}
|
|
74
|
+
function tokenize(src) {
|
|
75
|
+
const out = [];
|
|
76
|
+
let i = 0;
|
|
77
|
+
const n = src.length;
|
|
78
|
+
while (i < n) {
|
|
79
|
+
const c = src[i];
|
|
80
|
+
if (c === ' ' || c === '\t' || c === '\r' || c === '\n') {
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// 注释:// 到行尾
|
|
85
|
+
if (c === '/' && src[i + 1] === '/') {
|
|
86
|
+
while (i < n && src[i] !== '\n')
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// 引号包裹的模块名或字符串
|
|
91
|
+
if (c === '"' || c === "'") {
|
|
92
|
+
const quote = c;
|
|
93
|
+
const start = i;
|
|
94
|
+
i++;
|
|
95
|
+
let s = '';
|
|
96
|
+
while (i < n && src[i] !== quote) {
|
|
97
|
+
if (src[i] === '\\' && i + 1 < n) {
|
|
98
|
+
s += src[i + 1];
|
|
99
|
+
i += 2;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
s += src[i];
|
|
103
|
+
i++;
|
|
104
|
+
}
|
|
105
|
+
if (i >= n)
|
|
106
|
+
throw new ExpressionError('字符串未闭合', src, start);
|
|
107
|
+
i++; // 跳过收尾引号
|
|
108
|
+
out.push({ type: 'string', text: s, pos: start });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// 十六进制字面量:$1234 或 0x1234
|
|
112
|
+
if (c === '$') {
|
|
113
|
+
const start = i;
|
|
114
|
+
i++;
|
|
115
|
+
while (i < n && isHexDigit(src[i]))
|
|
116
|
+
i++;
|
|
117
|
+
const hex = src.slice(start + 1, i);
|
|
118
|
+
if (hex === '')
|
|
119
|
+
throw new ExpressionError('$ 后缺少十六进制数字', src, start);
|
|
120
|
+
out.push({ type: 'number', text: src.slice(start, i), value: BigInt('0x' + hex), pos: start });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (c === '0' && (src[i + 1] === 'x' || src[i + 1] === 'X')) {
|
|
124
|
+
const start = i;
|
|
125
|
+
i += 2;
|
|
126
|
+
while (i < n && isHexDigit(src[i]))
|
|
127
|
+
i++;
|
|
128
|
+
const hex = src.slice(start + 2, i);
|
|
129
|
+
if (hex === '')
|
|
130
|
+
throw new ExpressionError('0x 后缺少十六进制数字', src, start);
|
|
131
|
+
out.push({ type: 'number', text: src.slice(start, i), value: BigInt('0x' + hex), pos: start });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// 数字:十进制,或"看起来像十六进制"的裸串(含 A-F 且不是表达式运算符)
|
|
135
|
+
if (isDigit(c)) {
|
|
136
|
+
const start = i;
|
|
137
|
+
while (i < n && isHexDigit(src[i]))
|
|
138
|
+
i++;
|
|
139
|
+
const text = src.slice(start, i);
|
|
140
|
+
// 若紧跟着一个标识符字符,说明这是模块名的一部分(例如 1234abc.exe),
|
|
141
|
+
// 但那种情况通常以引号给出;这里按裸十六进制处理
|
|
142
|
+
const hasHexLetter = /[a-fA-F]/.test(text);
|
|
143
|
+
const value = hasHexLetter ? BigInt('0x' + text) : BigInt(text);
|
|
144
|
+
out.push({ type: 'number', text, value, pos: start });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// 标识符(模块名/符号名/函数名/寄存器)
|
|
148
|
+
if (isIdentStart(c)) {
|
|
149
|
+
const start = i;
|
|
150
|
+
while (i < n && isIdentChar(src[i]))
|
|
151
|
+
i++;
|
|
152
|
+
out.push({ type: 'ident', text: src.slice(start, i), pos: start });
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// 运算符
|
|
156
|
+
let matched = false;
|
|
157
|
+
for (const p of PUNCT) {
|
|
158
|
+
if (src.startsWith(p, i)) {
|
|
159
|
+
out.push({ type: 'punct', text: p, pos: i });
|
|
160
|
+
i += p.length;
|
|
161
|
+
matched = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (!matched) {
|
|
166
|
+
throw new ExpressionError(`无法识别的字符 "${c}"`, src, i);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
out.push({ type: 'eof', text: '', pos: n });
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
/* ------------------------------------------------------------------ *
|
|
173
|
+
* 解析器(递归下降,按运算符优先级分层)
|
|
174
|
+
* ------------------------------------------------------------------ */
|
|
175
|
+
class Parser {
|
|
176
|
+
tokens;
|
|
177
|
+
src;
|
|
178
|
+
pos = 0;
|
|
179
|
+
constructor(tokens, src) {
|
|
180
|
+
this.tokens = tokens;
|
|
181
|
+
this.src = src;
|
|
182
|
+
}
|
|
183
|
+
peek() {
|
|
184
|
+
return this.tokens[this.pos];
|
|
185
|
+
}
|
|
186
|
+
next() {
|
|
187
|
+
return this.tokens[this.pos++];
|
|
188
|
+
}
|
|
189
|
+
eat(text) {
|
|
190
|
+
if (this.peek().type === 'punct' && this.peek().text === text) {
|
|
191
|
+
this.pos++;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
expect(text) {
|
|
197
|
+
if (!this.eat(text)) {
|
|
198
|
+
throw new ExpressionError(`缺少 "${text}"`, this.src, this.peek().pos);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
parse() {
|
|
202
|
+
const e = this.parseLogicalOr();
|
|
203
|
+
if (this.peek().type !== 'eof') {
|
|
204
|
+
throw new ExpressionError(`表达式末尾有多余内容 "${this.peek().text}"`, this.src, this.peek().pos);
|
|
205
|
+
}
|
|
206
|
+
return e;
|
|
207
|
+
}
|
|
208
|
+
parseLogicalOr() {
|
|
209
|
+
let left = this.parseLogicalAnd();
|
|
210
|
+
while (this.peek().type === 'punct' && this.peek().text === '||') {
|
|
211
|
+
this.next();
|
|
212
|
+
left = { kind: 'binary', op: '||', left, right: this.parseLogicalAnd() };
|
|
213
|
+
}
|
|
214
|
+
return left;
|
|
215
|
+
}
|
|
216
|
+
parseLogicalAnd() {
|
|
217
|
+
let left = this.parseBitOr();
|
|
218
|
+
while (this.peek().type === 'punct' && this.peek().text === '&&') {
|
|
219
|
+
this.next();
|
|
220
|
+
left = { kind: 'binary', op: '&&', left, right: this.parseBitOr() };
|
|
221
|
+
}
|
|
222
|
+
return left;
|
|
223
|
+
}
|
|
224
|
+
parseBitOr() {
|
|
225
|
+
let left = this.parseBitXor();
|
|
226
|
+
while (this.peek().type === 'punct' && this.peek().text === '|') {
|
|
227
|
+
this.next();
|
|
228
|
+
left = { kind: 'binary', op: '|', left, right: this.parseBitXor() };
|
|
229
|
+
}
|
|
230
|
+
return left;
|
|
231
|
+
}
|
|
232
|
+
parseBitXor() {
|
|
233
|
+
let left = this.parseBitAnd();
|
|
234
|
+
while (this.peek().type === 'punct' && this.peek().text === '^') {
|
|
235
|
+
this.next();
|
|
236
|
+
left = { kind: 'binary', op: '^', left, right: this.parseBitAnd() };
|
|
237
|
+
}
|
|
238
|
+
return left;
|
|
239
|
+
}
|
|
240
|
+
parseBitAnd() {
|
|
241
|
+
let left = this.parseEquality();
|
|
242
|
+
while (this.peek().type === 'punct' && this.peek().text === '&') {
|
|
243
|
+
this.next();
|
|
244
|
+
left = { kind: 'binary', op: '&', left, right: this.parseEquality() };
|
|
245
|
+
}
|
|
246
|
+
return left;
|
|
247
|
+
}
|
|
248
|
+
parseEquality() {
|
|
249
|
+
let left = this.parseRelational();
|
|
250
|
+
for (;;) {
|
|
251
|
+
const t = this.peek();
|
|
252
|
+
if (t.type === 'punct' && (t.text === '==' || t.text === '!=')) {
|
|
253
|
+
this.next();
|
|
254
|
+
left = { kind: 'binary', op: t.text, left, right: this.parseRelational() };
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
return left;
|
|
260
|
+
}
|
|
261
|
+
parseRelational() {
|
|
262
|
+
let left = this.parseShift();
|
|
263
|
+
for (;;) {
|
|
264
|
+
const t = this.peek();
|
|
265
|
+
if (t.type === 'punct' && (t.text === '<' || t.text === '>' || t.text === '<=' || t.text === '>=')) {
|
|
266
|
+
this.next();
|
|
267
|
+
left = { kind: 'binary', op: t.text, left, right: this.parseShift() };
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
return left;
|
|
273
|
+
}
|
|
274
|
+
parseShift() {
|
|
275
|
+
let left = this.parseAdditive();
|
|
276
|
+
for (;;) {
|
|
277
|
+
const t = this.peek();
|
|
278
|
+
if (t.type === 'punct' && (t.text === '<<' || t.text === '>>')) {
|
|
279
|
+
this.next();
|
|
280
|
+
left = { kind: 'binary', op: t.text, left, right: this.parseAdditive() };
|
|
281
|
+
}
|
|
282
|
+
else
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
return left;
|
|
286
|
+
}
|
|
287
|
+
parseAdditive() {
|
|
288
|
+
let left = this.parseMultiplicative();
|
|
289
|
+
for (;;) {
|
|
290
|
+
const t = this.peek();
|
|
291
|
+
if (t.type === 'punct' && (t.text === '+' || t.text === '-')) {
|
|
292
|
+
this.next();
|
|
293
|
+
left = { kind: 'binary', op: t.text, left, right: this.parseMultiplicative() };
|
|
294
|
+
}
|
|
295
|
+
else
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
return left;
|
|
299
|
+
}
|
|
300
|
+
parseMultiplicative() {
|
|
301
|
+
let left = this.parseUnary();
|
|
302
|
+
for (;;) {
|
|
303
|
+
const t = this.peek();
|
|
304
|
+
if (t.type === 'punct' && (t.text === '*' || t.text === '/' || t.text === '%')) {
|
|
305
|
+
this.next();
|
|
306
|
+
left = { kind: 'binary', op: t.text, left, right: this.parseUnary() };
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
return left;
|
|
312
|
+
}
|
|
313
|
+
parseUnary() {
|
|
314
|
+
const t = this.peek();
|
|
315
|
+
if (t.type === 'punct' && (t.text === '-' || t.text === '+' || t.text === '~')) {
|
|
316
|
+
this.next();
|
|
317
|
+
return { kind: 'unary', op: t.text, operand: this.parseUnary() };
|
|
318
|
+
}
|
|
319
|
+
return this.parsePostfix();
|
|
320
|
+
}
|
|
321
|
+
/** 处理 `[expr]` 解引用 与 `ident+expr` 模块偏移 的后续运算 */
|
|
322
|
+
parsePostfix() {
|
|
323
|
+
let node = this.parsePrimary();
|
|
324
|
+
// 模块名 + 偏移:`"game.exe"+1234` —— 在 primary 阶段已把 module 节点建好,
|
|
325
|
+
// 这里的 additive 层会自然处理 "+1234"。但裸标识符后紧跟 + 时要区分
|
|
326
|
+
// "符号 + 偏移" 与 "符号/模块 + 偏移",两者语义相同(都是基址加偏移),
|
|
327
|
+
// 因此在 primary 里把 ident 记为 symbol,模块解析延到求值期:若该名字
|
|
328
|
+
// 命中已加载模块,就当作模块基址。
|
|
329
|
+
return node;
|
|
330
|
+
}
|
|
331
|
+
parsePrimary() {
|
|
332
|
+
const t = this.next();
|
|
333
|
+
if (t.type === 'number') {
|
|
334
|
+
return { kind: 'number', value: t.value };
|
|
335
|
+
}
|
|
336
|
+
if (t.type === 'string') {
|
|
337
|
+
// 字符串作为模块名出现(`"game.exe"+1234` 的左侧)。
|
|
338
|
+
// 注意:模块节点只表示"基址","+1234" 由上层 additive 处理,
|
|
339
|
+
// 因此这里不能把 offset 也塞进去(否则偏移会被加两次)。
|
|
340
|
+
return { kind: 'module', name: t.text, offset: { kind: 'number', value: 0n } };
|
|
341
|
+
}
|
|
342
|
+
if (t.type === 'punct' && t.text === '(') {
|
|
343
|
+
const inner = this.parseLogicalOr();
|
|
344
|
+
this.expect(')');
|
|
345
|
+
return inner;
|
|
346
|
+
}
|
|
347
|
+
if (t.type === 'punct' && t.text === '[') {
|
|
348
|
+
const inner = this.parseLogicalOr();
|
|
349
|
+
this.expect(']');
|
|
350
|
+
return { kind: 'deref', inner };
|
|
351
|
+
}
|
|
352
|
+
if (t.type === 'ident') {
|
|
353
|
+
// 函数调用?
|
|
354
|
+
if (this.peek().type === 'punct' && this.peek().text === '(') {
|
|
355
|
+
this.next();
|
|
356
|
+
const args = [];
|
|
357
|
+
if (!(this.peek().type === 'punct' && this.peek().text === ')')) {
|
|
358
|
+
for (;;) {
|
|
359
|
+
args.push(this.parseLogicalOr());
|
|
360
|
+
if (this.eat(','))
|
|
361
|
+
continue;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
this.expect(')');
|
|
366
|
+
return { kind: 'call', name: t.text, args };
|
|
367
|
+
}
|
|
368
|
+
// 寄存器名?
|
|
369
|
+
if (REGISTER_NAMES.has(t.text.toUpperCase())) {
|
|
370
|
+
return { kind: 'register', name: t.text.toUpperCase() };
|
|
371
|
+
}
|
|
372
|
+
// 其余按"符号名或模块名"处理:求值时先查模块,再查符号表
|
|
373
|
+
return { kind: 'symbol', name: t.text };
|
|
374
|
+
}
|
|
375
|
+
throw new ExpressionError(`意外的记号 "${t.text || '文件末尾'}"`, this.src, t.pos);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/** x86/x64 通用寄存器名(用于识别 `[rax]` 这类写法并给出明确错误) */
|
|
379
|
+
const REGISTER_NAMES = new Set([
|
|
380
|
+
'AL', 'AH', 'AX', 'EAX', 'RAX',
|
|
381
|
+
'BL', 'BH', 'BX', 'EBX', 'RBX',
|
|
382
|
+
'CL', 'CH', 'CX', 'ECX', 'RCX',
|
|
383
|
+
'DL', 'DH', 'DX', 'EDX', 'RDX',
|
|
384
|
+
'SI', 'ESI', 'RSI', 'DI', 'EDI', 'RDI',
|
|
385
|
+
'BP', 'EBP', 'RBP', 'SP', 'ESP', 'RSP',
|
|
386
|
+
'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15',
|
|
387
|
+
'R8D', 'R9D', 'R10D', 'R11D', 'R12D', 'R13D', 'R14D', 'R15D',
|
|
388
|
+
'RIP', 'EIP', 'PC',
|
|
389
|
+
]);
|
|
390
|
+
const MASK64 = 0xffffffffffffffffn;
|
|
391
|
+
/** 编译表达式为 AST(可缓存复用)。 */
|
|
392
|
+
function compileExpression(expression) {
|
|
393
|
+
const trimmed = expression.trim();
|
|
394
|
+
if (trimmed === '')
|
|
395
|
+
throw new ExpressionError('空表达式', expression);
|
|
396
|
+
const tokens = tokenize(trimmed);
|
|
397
|
+
return new Parser(tokens, trimmed).parse();
|
|
398
|
+
}
|
|
399
|
+
/** 模块名匹配:忽略大小写,允许省略路径。 */
|
|
400
|
+
function findModule(modules, name) {
|
|
401
|
+
const needle = name.toLowerCase();
|
|
402
|
+
// 精确名匹配优先
|
|
403
|
+
let hit = modules.find((m) => m.name.toLowerCase() === needle);
|
|
404
|
+
if (hit)
|
|
405
|
+
return hit;
|
|
406
|
+
// 允许带路径的写法
|
|
407
|
+
hit = modules.find((m) => (m.path ?? '').toLowerCase() === needle);
|
|
408
|
+
if (hit)
|
|
409
|
+
return hit;
|
|
410
|
+
// 退化为"去除扩展名比较"
|
|
411
|
+
const bare = needle.replace(/\.(dll|exe|so)$/i, '');
|
|
412
|
+
hit = modules.find((m) => m.name.toLowerCase().replace(/\.(dll|exe|so)$/i, '') === bare);
|
|
413
|
+
if (hit)
|
|
414
|
+
return hit;
|
|
415
|
+
return modules.find((m) => m.name.toLowerCase().includes(needle));
|
|
416
|
+
}
|
|
417
|
+
/** 求值表达式为 64 位地址。 */
|
|
418
|
+
function evaluateExpression(node, ctx, expression = '') {
|
|
419
|
+
const v = evalNode(node, ctx, expression);
|
|
420
|
+
// CE 的地址空间是 64 位,负数按补码回绕
|
|
421
|
+
return v & MASK64;
|
|
422
|
+
}
|
|
423
|
+
function evalNode(node, ctx, expression) {
|
|
424
|
+
switch (node.kind) {
|
|
425
|
+
case 'number':
|
|
426
|
+
return node.value;
|
|
427
|
+
case 'register': {
|
|
428
|
+
const regs = ctx.registers;
|
|
429
|
+
if (!regs || regs[node.name] === undefined) {
|
|
430
|
+
throw new ExpressionError(`表达式引用了寄存器 ${node.name},但当前上下文没有寄存器值(本移植不含调试器)`, expression);
|
|
431
|
+
}
|
|
432
|
+
return regs[node.name];
|
|
433
|
+
}
|
|
434
|
+
case 'symbol': {
|
|
435
|
+
// 1) 已注册符号
|
|
436
|
+
const sym = ctx.symbols?.get(node.name) ?? ctx.symbols?.get(node.name.toLowerCase());
|
|
437
|
+
if (sym !== undefined)
|
|
438
|
+
return sym;
|
|
439
|
+
// 2) 模块名
|
|
440
|
+
const mods = getModules(ctx);
|
|
441
|
+
const m = findModule(mods, node.name);
|
|
442
|
+
if (m)
|
|
443
|
+
return m.baseAddress;
|
|
444
|
+
throw new ExpressionError(`未知的符号或模块 "${node.name}"`, expression);
|
|
445
|
+
}
|
|
446
|
+
case 'deref': {
|
|
447
|
+
if (ctx.mode === 'parseOnly') {
|
|
448
|
+
throw new ExpressionError('parseOnly 模式下不允许解引用', expression);
|
|
449
|
+
}
|
|
450
|
+
const addr = evalNode(node.inner, ctx, expression);
|
|
451
|
+
if (!ctx.process) {
|
|
452
|
+
throw new ExpressionError('解引用需要已打开的目标进程', expression);
|
|
453
|
+
}
|
|
454
|
+
const buf = ctx.process.readMemory(addr & MASK64, 8);
|
|
455
|
+
if (buf.length < 8) {
|
|
456
|
+
throw new ExpressionError(`解引用失败:无法读取地址 0x${(addr & MASK64).toString(16)}(${(0, winapi_1.win32ErrorMessage)(998)})`, expression);
|
|
457
|
+
}
|
|
458
|
+
return buf.readBigUInt64LE(0);
|
|
459
|
+
}
|
|
460
|
+
case 'unary': {
|
|
461
|
+
const x = evalNode(node.operand, ctx, expression);
|
|
462
|
+
switch (node.op) {
|
|
463
|
+
case '-':
|
|
464
|
+
return (-x) & MASK64;
|
|
465
|
+
case '+':
|
|
466
|
+
return x;
|
|
467
|
+
case '~':
|
|
468
|
+
return ~x & MASK64;
|
|
469
|
+
default:
|
|
470
|
+
throw new ExpressionError(`不支持的一元运算符 ${node.op}`, expression);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
case 'binary': {
|
|
474
|
+
// 短路逻辑
|
|
475
|
+
if (node.op === '&&') {
|
|
476
|
+
return evalNode(node.left, ctx, expression) !== 0n && evalNode(node.right, ctx, expression) !== 0n ? 1n : 0n;
|
|
477
|
+
}
|
|
478
|
+
if (node.op === '||') {
|
|
479
|
+
return evalNode(node.left, ctx, expression) !== 0n || evalNode(node.right, ctx, expression) !== 0n ? 1n : 0n;
|
|
480
|
+
}
|
|
481
|
+
const l = evalNode(node.left, ctx, expression);
|
|
482
|
+
const r = evalNode(node.right, ctx, expression);
|
|
483
|
+
switch (node.op) {
|
|
484
|
+
case '+':
|
|
485
|
+
return (l + r) & MASK64;
|
|
486
|
+
case '-':
|
|
487
|
+
return (l - r) & MASK64;
|
|
488
|
+
case '*':
|
|
489
|
+
return (l * r) & MASK64;
|
|
490
|
+
case '/':
|
|
491
|
+
if (r === 0n)
|
|
492
|
+
throw new ExpressionError('除以零', expression);
|
|
493
|
+
return (l / r) & MASK64;
|
|
494
|
+
case '%':
|
|
495
|
+
if (r === 0n)
|
|
496
|
+
throw new ExpressionError('对零取模', expression);
|
|
497
|
+
return (l % r) & MASK64;
|
|
498
|
+
case '&':
|
|
499
|
+
return l & r;
|
|
500
|
+
case '|':
|
|
501
|
+
return l | r;
|
|
502
|
+
case '^':
|
|
503
|
+
return l ^ r;
|
|
504
|
+
case '<<':
|
|
505
|
+
return (l << (r & 63n)) & MASK64;
|
|
506
|
+
case '>>':
|
|
507
|
+
return (l >> (r & 63n)) & MASK64;
|
|
508
|
+
case '==':
|
|
509
|
+
return l === r ? 1n : 0n;
|
|
510
|
+
case '!=':
|
|
511
|
+
return l !== r ? 1n : 0n;
|
|
512
|
+
case '<':
|
|
513
|
+
return l < r ? 1n : 0n;
|
|
514
|
+
case '>':
|
|
515
|
+
return l > r ? 1n : 0n;
|
|
516
|
+
case '<=':
|
|
517
|
+
return l <= r ? 1n : 0n;
|
|
518
|
+
case '>=':
|
|
519
|
+
return l >= r ? 1n : 0n;
|
|
520
|
+
default:
|
|
521
|
+
throw new ExpressionError(`不支持的运算符 ${node.op}`, expression);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
case 'module': {
|
|
525
|
+
const mods = getModules(ctx);
|
|
526
|
+
const m = findModule(mods, node.name);
|
|
527
|
+
if (!m)
|
|
528
|
+
throw new ExpressionError(`找不到模块 "${node.name}"`, expression);
|
|
529
|
+
const off = evalNode(node.offset, ctx, expression);
|
|
530
|
+
return (m.baseAddress + off) & MASK64;
|
|
531
|
+
}
|
|
532
|
+
case 'call': {
|
|
533
|
+
const name = node.name.toLowerCase();
|
|
534
|
+
switch (name) {
|
|
535
|
+
case 'getmodulebase': {
|
|
536
|
+
if (node.args.length !== 1)
|
|
537
|
+
throw new ExpressionError('GetModuleBase 需要 1 个参数', expression);
|
|
538
|
+
const modName = evalStringArg(node.args[0], ctx, expression);
|
|
539
|
+
const m = findModule(getModules(ctx), modName);
|
|
540
|
+
if (!m)
|
|
541
|
+
throw new ExpressionError(`找不到模块 "${modName}"`, expression);
|
|
542
|
+
return m.baseAddress;
|
|
543
|
+
}
|
|
544
|
+
case 'getmodulesize': {
|
|
545
|
+
if (node.args.length !== 1)
|
|
546
|
+
throw new ExpressionError('GetModuleSize 需要 1 个参数', expression);
|
|
547
|
+
const modName = evalStringArg(node.args[0], ctx, expression);
|
|
548
|
+
const m = findModule(getModules(ctx), modName);
|
|
549
|
+
if (!m)
|
|
550
|
+
throw new ExpressionError(`找不到模块 "${modName}"`, expression);
|
|
551
|
+
return BigInt(m.size);
|
|
552
|
+
}
|
|
553
|
+
case 'getaddress': {
|
|
554
|
+
if (node.args.length !== 1)
|
|
555
|
+
throw new ExpressionError('GetAddress 需要 1 个参数', expression);
|
|
556
|
+
const inner = evalStringArg(node.args[0], ctx, expression);
|
|
557
|
+
return evaluateExpression(compileExpression(inner), ctx, inner);
|
|
558
|
+
}
|
|
559
|
+
case 'readpointer': {
|
|
560
|
+
if (node.args.length !== 1)
|
|
561
|
+
throw new ExpressionError('readPointer 需要 1 个参数', expression);
|
|
562
|
+
const addr = evalNode(node.args[0], ctx, expression);
|
|
563
|
+
if (ctx.mode === 'parseOnly' || !ctx.process) {
|
|
564
|
+
throw new ExpressionError('readPointer 需要已打开的目标进程', expression);
|
|
565
|
+
}
|
|
566
|
+
const buf = ctx.process.readMemory(addr & MASK64, 8);
|
|
567
|
+
if (buf.length < 8)
|
|
568
|
+
throw new ExpressionError('readPointer 读取失败', expression);
|
|
569
|
+
return buf.readBigUInt64LE(0);
|
|
570
|
+
}
|
|
571
|
+
default:
|
|
572
|
+
throw new ExpressionError(`不支持的函数 ${node.name}`, expression);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
default:
|
|
576
|
+
throw new ExpressionError('无法求值的表达式节点', expression);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/** 把参数节点当作字符串(支持字符串字面量与符号名)。 */
|
|
580
|
+
function evalStringArg(node, ctx, expression) {
|
|
581
|
+
if (node.kind === 'module')
|
|
582
|
+
return node.name;
|
|
583
|
+
if (node.kind === 'symbol') {
|
|
584
|
+
// 若它不是已注册符号也不是模块名,就当作裸字符串(CE 允许 GetModuleBase(game.exe))
|
|
585
|
+
const sym = ctx.symbols?.get(node.name);
|
|
586
|
+
if (sym !== undefined)
|
|
587
|
+
return node.name;
|
|
588
|
+
return node.name;
|
|
589
|
+
}
|
|
590
|
+
throw new ExpressionError('该参数需要字符串', expression);
|
|
591
|
+
}
|
|
592
|
+
/** 取模块列表(带缓存)。 */
|
|
593
|
+
function getModules(ctx) {
|
|
594
|
+
if (ctx.moduleCache && ctx.moduleCache.length > 0)
|
|
595
|
+
return ctx.moduleCache;
|
|
596
|
+
if (!ctx.process)
|
|
597
|
+
throw new ExpressionError('解析模块需要已打开的目标进程', '');
|
|
598
|
+
const mods = ctx.process.modules().map((m) => ({
|
|
599
|
+
name: m.name,
|
|
600
|
+
baseAddress: m.baseAddress,
|
|
601
|
+
size: m.size,
|
|
602
|
+
}));
|
|
603
|
+
ctx.moduleCache = mods;
|
|
604
|
+
return mods;
|
|
605
|
+
}
|
|
606
|
+
/* ------------------------------------------------------------------ *
|
|
607
|
+
* 便捷入口
|
|
608
|
+
* ------------------------------------------------------------------ */
|
|
609
|
+
/**
|
|
610
|
+
* 直接求值一个地址表达式。
|
|
611
|
+
*
|
|
612
|
+
* ```ts
|
|
613
|
+
* const addr = resolveAddress('"notepad.exe"+1234', { process: proc });
|
|
614
|
+
* const pointee = resolveAddress('[[notepad.exe+10]+8]', { process: proc });
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
function resolveAddress(expression, ctx) {
|
|
618
|
+
const ast = compileExpression(expression);
|
|
619
|
+
return evaluateExpression(ast, ctx, expression);
|
|
620
|
+
}
|
|
621
|
+
/** 已编译的表达式(便于在地址表里反复求值)。 */
|
|
622
|
+
class CompiledAddress {
|
|
623
|
+
source;
|
|
624
|
+
ast;
|
|
625
|
+
error;
|
|
626
|
+
constructor(expression) {
|
|
627
|
+
this.source = expression;
|
|
628
|
+
try {
|
|
629
|
+
this.ast = compileExpression(expression);
|
|
630
|
+
this.error = null;
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
this.ast = null;
|
|
634
|
+
this.error = e;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
get isValid() {
|
|
638
|
+
return this.ast !== null;
|
|
639
|
+
}
|
|
640
|
+
get parseError() {
|
|
641
|
+
return this.error;
|
|
642
|
+
}
|
|
643
|
+
resolve(ctx) {
|
|
644
|
+
if (!this.ast)
|
|
645
|
+
throw this.error ?? new ExpressionError('表达式未编译', this.source);
|
|
646
|
+
return evaluateExpression(this.ast, ctx, this.source);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
exports.CompiledAddress = CompiledAddress;
|
|
650
|
+
//# sourceMappingURL=expression.js.map
|