@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.
Files changed (138) hide show
  1. package/PORT-SCOPE.md +169 -0
  2. package/README.md +451 -2
  3. package/dist/cjs/addr/expression.js +650 -0
  4. package/dist/cjs/addr/expression.js.map +1 -0
  5. package/dist/cjs/addresslist/memory-record.js +612 -0
  6. package/dist/cjs/addresslist/memory-record.js.map +1 -0
  7. package/dist/cjs/asm/assembler.js +354 -0
  8. package/dist/cjs/asm/assembler.js.map +1 -0
  9. package/dist/cjs/asm/autoassembler-directives.js +362 -0
  10. package/dist/cjs/asm/autoassembler-directives.js.map +1 -0
  11. package/dist/cjs/asm/autoassembler.js +2163 -0
  12. package/dist/cjs/asm/autoassembler.js.map +1 -0
  13. package/dist/cjs/asm/backend.js +394 -0
  14. package/dist/cjs/asm/backend.js.map +1 -0
  15. package/dist/cjs/asm/disassembler.js +715 -0
  16. package/dist/cjs/asm/disassembler.js.map +1 -0
  17. package/dist/cjs/backend/dbk-backend.js +683 -0
  18. package/dist/cjs/backend/dbk-backend.js.map +1 -0
  19. package/dist/cjs/backend/native-ce-backend.js +667 -0
  20. package/dist/cjs/backend/native-ce-backend.js.map +1 -0
  21. package/dist/cjs/backend/self-backend.js +449 -0
  22. package/dist/cjs/backend/self-backend.js.map +1 -0
  23. package/dist/cjs/backend/types.js +48 -0
  24. package/dist/cjs/backend/types.js.map +1 -0
  25. package/dist/cjs/cheattable/ct-format.js +533 -0
  26. package/dist/cjs/cheattable/ct-format.js.map +1 -0
  27. package/dist/cjs/index.js +219 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/cjs/lua/aa.js +230 -0
  30. package/dist/cjs/lua/aa.js.map +1 -0
  31. package/dist/cjs/lua/addressing.js +118 -0
  32. package/dist/cjs/lua/addressing.js.map +1 -0
  33. package/dist/cjs/lua/api.js +736 -0
  34. package/dist/cjs/lua/api.js.map +1 -0
  35. package/dist/cjs/lua/asm.js +220 -0
  36. package/dist/cjs/lua/asm.js.map +1 -0
  37. package/dist/cjs/lua/bridge.js +526 -0
  38. package/dist/cjs/lua/bridge.js.map +1 -0
  39. package/dist/cjs/lua/marshal.js +562 -0
  40. package/dist/cjs/lua/marshal.js.map +1 -0
  41. package/dist/cjs/lua/memory.js +213 -0
  42. package/dist/cjs/lua/memory.js.map +1 -0
  43. package/dist/cjs/lua/objects.js +1070 -0
  44. package/dist/cjs/lua/objects.js.map +1 -0
  45. package/dist/cjs/lua/scan.js +543 -0
  46. package/dist/cjs/lua/scan.js.map +1 -0
  47. package/dist/cjs/lua/types.js +103 -0
  48. package/dist/cjs/lua/types.js.map +1 -0
  49. package/dist/cjs/scanner/matcher.js +781 -0
  50. package/dist/cjs/scanner/matcher.js.map +1 -0
  51. package/dist/cjs/scanner/pointer-scan.js +546 -0
  52. package/dist/cjs/scanner/pointer-scan.js.map +1 -0
  53. package/dist/cjs/scanner/scanner.js +513 -0
  54. package/dist/cjs/scanner/scanner.js.map +1 -0
  55. package/dist/cjs/scanner/shared-layout.js +207 -0
  56. package/dist/cjs/scanner/shared-layout.js.map +1 -0
  57. package/dist/cjs/types.js +190 -0
  58. package/dist/cjs/types.js.map +1 -0
  59. package/dist/cjs/value-codec.js +515 -0
  60. package/dist/cjs/value-codec.js.map +1 -0
  61. package/dist/cjs/win/process.js +475 -0
  62. package/dist/cjs/win/process.js.map +1 -0
  63. package/dist/cjs/win/scan-worker.js +84 -0
  64. package/dist/cjs/win/scan-worker.js.map +1 -0
  65. package/dist/cjs/win/winapi.js +403 -0
  66. package/dist/cjs/win/winapi.js.map +1 -0
  67. package/dist/types/addr/expression.d.ts +98 -0
  68. package/dist/types/addr/expression.d.ts.map +1 -0
  69. package/dist/types/addresslist/memory-record.d.ts +218 -0
  70. package/dist/types/addresslist/memory-record.d.ts.map +1 -0
  71. package/dist/types/asm/assembler.d.ts +106 -0
  72. package/dist/types/asm/assembler.d.ts.map +1 -0
  73. package/dist/types/asm/autoassembler-directives.d.ts +118 -0
  74. package/dist/types/asm/autoassembler-directives.d.ts.map +1 -0
  75. package/dist/types/asm/autoassembler.d.ts +434 -0
  76. package/dist/types/asm/autoassembler.d.ts.map +1 -0
  77. package/dist/types/asm/backend.d.ts +167 -0
  78. package/dist/types/asm/backend.d.ts.map +1 -0
  79. package/dist/types/asm/disassembler.d.ts +194 -0
  80. package/dist/types/asm/disassembler.d.ts.map +1 -0
  81. package/dist/types/backend/dbk-backend.d.ts +353 -0
  82. package/dist/types/backend/dbk-backend.d.ts.map +1 -0
  83. package/dist/types/backend/native-ce-backend.d.ts +213 -0
  84. package/dist/types/backend/native-ce-backend.d.ts.map +1 -0
  85. package/dist/types/backend/self-backend.d.ts +64 -0
  86. package/dist/types/backend/self-backend.d.ts.map +1 -0
  87. package/dist/types/backend/types.d.ts +176 -0
  88. package/dist/types/backend/types.d.ts.map +1 -0
  89. package/dist/types/cheattable/ct-format.d.ts +100 -0
  90. package/dist/types/cheattable/ct-format.d.ts.map +1 -0
  91. package/dist/types/index.d.ts +54 -0
  92. package/dist/types/index.d.ts.map +1 -0
  93. package/dist/types/lua/aa.d.ts +107 -0
  94. package/dist/types/lua/aa.d.ts.map +1 -0
  95. package/dist/types/lua/addressing.d.ts +41 -0
  96. package/dist/types/lua/addressing.d.ts.map +1 -0
  97. package/dist/types/lua/api.d.ts +46 -0
  98. package/dist/types/lua/api.d.ts.map +1 -0
  99. package/dist/types/lua/asm.d.ts +42 -0
  100. package/dist/types/lua/asm.d.ts.map +1 -0
  101. package/dist/types/lua/bridge.d.ts +129 -0
  102. package/dist/types/lua/bridge.d.ts.map +1 -0
  103. package/dist/types/lua/marshal.d.ts +244 -0
  104. package/dist/types/lua/marshal.d.ts.map +1 -0
  105. package/dist/types/lua/memory.d.ts +61 -0
  106. package/dist/types/lua/memory.d.ts.map +1 -0
  107. package/dist/types/lua/objects.d.ts +210 -0
  108. package/dist/types/lua/objects.d.ts.map +1 -0
  109. package/dist/types/lua/scan.d.ts +106 -0
  110. package/dist/types/lua/scan.d.ts.map +1 -0
  111. package/dist/types/lua/types.d.ts +146 -0
  112. package/dist/types/lua/types.d.ts.map +1 -0
  113. package/dist/types/scanner/matcher.d.ts +122 -0
  114. package/dist/types/scanner/matcher.d.ts.map +1 -0
  115. package/dist/types/scanner/pointer-scan.d.ts +187 -0
  116. package/dist/types/scanner/pointer-scan.d.ts.map +1 -0
  117. package/dist/types/scanner/scanner.d.ts +150 -0
  118. package/dist/types/scanner/scanner.d.ts.map +1 -0
  119. package/dist/types/scanner/shared-layout.d.ts +96 -0
  120. package/dist/types/scanner/shared-layout.d.ts.map +1 -0
  121. package/dist/types/types.d.ts +239 -0
  122. package/dist/types/types.d.ts.map +1 -0
  123. package/dist/types/value-codec.d.ts +134 -0
  124. package/dist/types/value-codec.d.ts.map +1 -0
  125. package/dist/types/win/process.d.ts +190 -0
  126. package/dist/types/win/process.d.ts.map +1 -0
  127. package/dist/types/win/scan-worker.d.ts +51 -0
  128. package/dist/types/win/scan-worker.d.ts.map +1 -0
  129. package/dist/types/win/winapi.d.ts +144 -0
  130. package/dist/types/win/winapi.d.ts.map +1 -0
  131. package/docs/assembler.md +371 -0
  132. package/docs/autoassembler.md +423 -0
  133. package/docs/backend-dbk.md +305 -0
  134. package/docs/backends.md +273 -0
  135. package/docs/evidence/atomics-wait-io.js +20 -0
  136. package/docs/evidence/mini-repro.js +98 -0
  137. package/docs/lua-api.md +718 -0
  138. package/package.json +60 -4
@@ -0,0 +1,2163 @@
1
+ "use strict";
2
+ /**
3
+ * Auto Assembler —— Cheat Engine 脚本语言的解析与执行层。
4
+ *
5
+ * 对齐 `Cheat Engine/autoassembler.pas`(3752 行)与 `Assemblerunit.pas` 里
6
+ * 属于 Auto Assembler 的那部分(`db/dw/dd/dq`、`nop N`、`align`)。
7
+ *
8
+ * ## 它解决什么问题
9
+ *
10
+ * CE 的 Auto Assembler 是"把一段脚本变成对目标进程内存的修改"的编译器 + 执行器:
11
+ *
12
+ * ```text
13
+ * [ENABLE]
14
+ * alloc(newmem,2048) // 1. 在目标进程分配内存
15
+ * label(returnhere) // 2. 声明一个脚本内标签
16
+ * aobscan(sig, DE AD BE EF 90 90 ?? 7*) // 3. 在目标进程里搜特征码
17
+ * newmem: // 4. 地址定位(后续指令从这里排布)
18
+ * mov dword ptr [sig], 1337 // 5. 写代码
19
+ * jmp returnhere
20
+ * sig: // 6. 回到 aobscan 找到的地址
21
+ * jmp newmem
22
+ * returnhere:
23
+ * [DISABLE]
24
+ * dealloc(newmem)
25
+ * ```
26
+ *
27
+ * ## 执行流水线(与 CE 一一对应)
28
+ *
29
+ * ```
30
+ * stripComments ← removecomments() 去 // ; {} 注释(字符串感知)
31
+ * expandIncludes ← INCLUDE() 分支 把 include 的文件内容就地展开
32
+ * analyzeSections ← getenableanddisablepos() 找 [ENABLE]/[DISABLE]
33
+ * selectLines ← getEnableOrDisableScript() 取「外层代码 + 选中段」
34
+ * stripCpuSpecific ← stripCPUspecificCode() 处理 [32-BIT]/[64-BIT]
35
+ * preprocess ← autoassemble2 的解析循环 alloc/aobscan/label/define/db/...
36
+ * layout ← AutoassemblerCodePass2() 逐行定地址 + 汇编(迭代到稳定)
37
+ * apply ← 写内存分支 写目标进程并记录原字节
38
+ * ```
39
+ *
40
+ * ## 三个最容易静默出错、因此被重点处理的地方
41
+ *
42
+ * 1. **地址定位**。CE 的脚本不是"从头顺序汇编",而是由**地址定义行**(`name:`)切块:
43
+ * 遇到已知符号(alloc/aobscan/registered)或地址表达式(`"game.exe"+1234:`)时,
44
+ * 当前地址被**重置**到该值;否则标签取当前地址。若不实现这条,
45
+ * `newmem:` 之后那段代码会被写到地址 0。
46
+ * 2. **前向引用的指令长度**。`jmp returnhere` 里的 `returnhere` 定义在后面,
47
+ * 它的地址又取决于前面指令的长度 —— 循环依赖。CE 的做法是用
48
+ * 「当前地址 + 0xFFFFF」当远占位符、用「当前地址」当近占位符各编一次,
49
+ * 取**较长**的那个,保证后续地址不会因为编码变短而漂移。本移植沿用这一策略,
50
+ * 并额外做迭代收敛(见 `MAX_LAYOUT_PASSES`)。
51
+ * 3. **原字节保留**。[ENABLE] 写入前逐字节记录被覆盖的原字节;[DISABLE] 时按记录
52
+ * 逆序精确还原(`restoreMode: 'original'`,默认)。这正是 CE 教程里
53
+ * 「关掉作弊后游戏恢复正常」的底层机制。
54
+ *
55
+ * ## 与 CE 的差异(完整清单见 docs/autoassembler.md)
56
+ *
57
+ * - **数字进制是一致的**(实测):Keystone 的 Intel 语法把裸数字按**十六进制**解析,
58
+ * 与 CE 相同 —— `mov eax, 100` 编出 `B8 00 01 00 00`(即 0x100)。
59
+ * 想写十进制只能写 `0x` 形式的十六进制值(Keystone 不支持 CE 的 `#123`)。
60
+ * - `db/dw/dd/dq`、`nop N`、`align N` 由本层自己实现,同样是十六进制语义。
61
+ * - `alloc`/`readmem` 的**大小**参数按十进制(对齐 CE 的 `StrToInt`),
62
+ * 需要十六进制时写 `0x` 或 `$` 前缀。
63
+ * - `createthread`/`createthreadandwait` 只做参数校验与记录,不创建远程线程
64
+ * (Node 没有 CreateRemoteThread;见 `allowCreateThread`)。
65
+ * - `luaCall`/`reassemble`/`struct`/`kalloc`/`sharedalloc`/`loadlibrary`/
66
+ * `allocnx`/`allocxo`/`aobscanex`/`{$...}` 条件编译未实现,会给出明确的中文错误。
67
+ *
68
+ * ## 用法
69
+ *
70
+ * ```ts
71
+ * await ensureAsmBackend(); // WASM 后端必须先初始化
72
+ * const aa = new AutoAssembler({ process });
73
+ * aa.autoAssemble(script, { enable: true }); // 注入并记录原字节
74
+ * aa.autoAssemble(script, { enable: false }); // 精确还原
75
+ * ```
76
+ */
77
+ Object.defineProperty(exports, "__esModule", { value: true });
78
+ exports.AutoAssembler = exports.AutoAssemblerError = void 0;
79
+ exports.autoAssemble = autoAssemble;
80
+ const node_fs_1 = require("node:fs");
81
+ const node_path_1 = require("node:path");
82
+ const scanner_1 = require("../scanner/scanner");
83
+ const types_1 = require("../types");
84
+ const value_codec_1 = require("../value-codec");
85
+ const expression_1 = require("../addr/expression");
86
+ const assembler_1 = require("./assembler");
87
+ const backend_1 = require("./backend");
88
+ const autoassembler_directives_1 = require("./autoassembler-directives");
89
+ /* ------------------------------------------------------------------ *
90
+ * 常量
91
+ * ------------------------------------------------------------------ */
92
+ /** 递归 include 的最大深度(防止 A include B include A 无限展开) */
93
+ const MAX_INCLUDE_DEPTH = 8;
94
+ /**
95
+ * 前向引用占位符的"远"偏移量。
96
+ *
97
+ * 取 CE `autoassembler.pas` 3742~3747 行同样的值(`currentaddress+$FFFFF`):
98
+ * 足以让 `jmp`/`call` 落在 32 位相对位移范围内(因此是常规的 5 字节编码),
99
+ * 又不会大到让 `mov r64, imm` 之类的指令换用更长的编码。
100
+ */
101
+ const FAR_PLACEHOLDER_DELTA = 0xfffffn;
102
+ /** 地址布局的最大迭代轮数(超过仍未稳定就报错,避免产出错误字节) */
103
+ const MAX_LAYOUT_PASSES = 20;
104
+ /** 默认扫描块大小 */
105
+ const DEFAULT_SCAN_BLOCK_SIZE = 1 << 20;
106
+ /** 本移植明确不支持的指令(给出可操作的中文报错,而不是让 Keystone 抛怪错误) */
107
+ const UNSUPPORTED_DIRECTIVES = [
108
+ 'luacall',
109
+ 'reassemble',
110
+ 'struct',
111
+ 'kalloc',
112
+ 'sharedalloc',
113
+ 'fullaccess',
114
+ 'loadlibrary',
115
+ 'allocnx',
116
+ 'allocxo',
117
+ 'aobscanex',
118
+ 'createthreadandwait',
119
+ ];
120
+ /** Auto Assembler 错误(带脚本行号与列内容,便于定位) */
121
+ class AutoAssemblerError extends Error {
122
+ /** 出错的源码行(已去注释) */
123
+ line;
124
+ /** 出错的源码行号(1-based;include 的文件里是文件内行号) */
125
+ lineNumber;
126
+ /** 出错行所属文件(主脚本为 `<script>`) */
127
+ file;
128
+ /** 阶段 */
129
+ phase;
130
+ constructor(message, options = {}) {
131
+ const phase = options.phase ?? 'parse';
132
+ let where = '';
133
+ if (options.lineNumber !== undefined) {
134
+ const file = options.file && options.file !== '<script>' ? ` @ ${options.file}` : '';
135
+ where = `(第 ${options.lineNumber} 行${file}:"${options.line ?? ''}")`;
136
+ }
137
+ else if (options.file && options.file !== '<script>') {
138
+ where = `(@ ${options.file})`;
139
+ }
140
+ super(`Auto Assembler ${phase} 错误${where}:${message}`);
141
+ this.name = 'AutoAssemblerError';
142
+ this.line = options.line;
143
+ this.lineNumber = options.lineNumber;
144
+ this.file = options.file;
145
+ this.phase = phase;
146
+ }
147
+ }
148
+ exports.AutoAssemblerError = AutoAssemblerError;
149
+ /** 把脚本原文规范成历史记录的 key(容忍 CRLF 与首尾空白差异) */
150
+ function historyKey(script) {
151
+ return script.replace(/\r\n/g, '\n').trim();
152
+ }
153
+ /** 判断 [address, address+length) 是否已被历史完整覆盖 */
154
+ function isCoveredByHistory(history, address, length) {
155
+ const end = address + BigInt(length);
156
+ for (const record of history) {
157
+ const rEnd = record.address + BigInt(record.bytes.length);
158
+ if (record.address <= address && rEnd >= end)
159
+ return true;
160
+ }
161
+ return false;
162
+ }
163
+ /** 把一条记录插入历史(保持地址升序),并与相邻记录合并 */
164
+ function insertHistory(history, address, bytes) {
165
+ if (bytes.length === 0)
166
+ return;
167
+ const record = { address, bytes };
168
+ let index = 0;
169
+ while (index < history.length && history[index].address < address)
170
+ index++;
171
+ history.splice(index, 0, record);
172
+ // 与前一条合并
173
+ if (index > 0) {
174
+ const prev = history[index - 1];
175
+ const cur = history[index];
176
+ if (prev.address + BigInt(prev.bytes.length) === cur.address) {
177
+ prev.bytes = Buffer.concat([prev.bytes, cur.bytes]);
178
+ history.splice(index, 1);
179
+ index--;
180
+ }
181
+ }
182
+ // 与后一条合并
183
+ if (index + 1 < history.length) {
184
+ const cur = history[index];
185
+ const next = history[index + 1];
186
+ if (cur.address + BigInt(cur.bytes.length) === next.address) {
187
+ cur.bytes = Buffer.concat([cur.bytes, next.bytes]);
188
+ history.splice(index + 1, 1);
189
+ }
190
+ }
191
+ }
192
+ /* ------------------------------------------------------------------ *
193
+ * 纯函数工具
194
+ * ------------------------------------------------------------------ */
195
+ /** 是否是合法标识符(CE 用 `val('$'+s,x)` 做同样的判断) */
196
+ function isIdentifier(text) {
197
+ return /^[A-Za-z_.$?@][\w.$?@]*$/.test(text);
198
+ }
199
+ function escapeRegExp(text) {
200
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
201
+ }
202
+ /**
203
+ * 按 token 边界替换(对齐 `autoassembler.pas` 的 `replacetoken`)。
204
+ *
205
+ * 使用零宽断言而不是 `\b`,因为 CE 的标识符字符集包含 `.`、`$`、`?`、`@`。
206
+ */
207
+ function replaceToken(text, token, value) {
208
+ if (token === '')
209
+ return text;
210
+ const re = new RegExp(`(?<![\\w.$?@])${escapeRegExp(token)}(?![\\w.$?@])`, 'gi');
211
+ return text.replace(re, value);
212
+ }
213
+ /**
214
+ * 去注释,对齐 `removecomments()`(autoassembler.pas 733 行)并额外支持 `;`。
215
+ *
216
+ * 行为:
217
+ * - `//` 到行尾
218
+ * - `;` 到行尾(CE 的 Assembleunit 把 `;` 当注释;AA 层原本不处理,这里统一处理)
219
+ * - `/* ... *​/` 跨行块注释
220
+ * - `{ ... }` 跨行注释(CE 的 bracecomment;ARM 上 CE 不支持,本移植都支持)
221
+ * - 引号内的内容不当作注释(`db 'a//b'`)
222
+ * - 制表符→空格
223
+ *
224
+ * 与 CE 的差异:CE 的 `removecomments` 不处理 `;`,且块注释被替换为**等长空格**
225
+ * 以便保持字符位置;本实现按行产出(不保留位置),因为后续使用的是行号而非列号。
226
+ */
227
+ function stripComments(source) {
228
+ const rawLines = source.split(/\r?\n/);
229
+ const out = [];
230
+ let inBlock = false; // /* */
231
+ let inBrace = false; // { }
232
+ for (const rawLine of rawLines) {
233
+ let text = '';
234
+ let inString = false;
235
+ for (let i = 0; i < rawLine.length; i++) {
236
+ const ch = rawLine[i];
237
+ const next = i + 1 < rawLine.length ? rawLine[i + 1] : '';
238
+ if (inBlock) {
239
+ if (ch === '*' && next === '/') {
240
+ inBlock = false;
241
+ i++;
242
+ }
243
+ text += ' ';
244
+ continue;
245
+ }
246
+ if (inBrace) {
247
+ if (ch === '}')
248
+ inBrace = false;
249
+ text += ' ';
250
+ continue;
251
+ }
252
+ if (inString) {
253
+ text += ch;
254
+ if (ch === "'" || ch === '"')
255
+ inString = false;
256
+ continue;
257
+ }
258
+ if (ch === "'" || ch === '"') {
259
+ inString = true;
260
+ text += ch;
261
+ continue;
262
+ }
263
+ if (ch === '/' && next === '/')
264
+ break; // 行注释
265
+ if (ch === ';')
266
+ break; // `;` 行注释
267
+ if (ch === '/' && next === '*') {
268
+ inBlock = true;
269
+ i++;
270
+ text += ' ';
271
+ continue;
272
+ }
273
+ if (ch === '{') {
274
+ inBrace = true;
275
+ text += ' ';
276
+ continue;
277
+ }
278
+ text += ch === '\t' ? ' ' : ch;
279
+ }
280
+ out.push(text.trim());
281
+ }
282
+ return out;
283
+ }
284
+ /**
285
+ * 匹配 `NAME(参数)` 形式的指令。
286
+ *
287
+ * 参数用贪婪匹配到最后那个 `)`,因此 `include(c:\a)b.cea)` 这类怪写法也能工作
288
+ * (CE 用的是 `pos('(')` + `pos(')')`,行为略有不同,但不影响正常脚本)。
289
+ */
290
+ function parseCall(text, name) {
291
+ const m = text.match(new RegExp(`^${escapeRegExp(name)}\\s*\\(([\\s\\S]*)\\)\\s*$`, 'i'));
292
+ return m ? m[1] : null;
293
+ }
294
+ /** 去掉最外层引号(CE 的 AnsiDequotedStr 子集) */
295
+ function unquote(text) {
296
+ const t = text.trim();
297
+ if (t.length >= 2 && (t[0] === '"' || t[0] === "'") && t[t.length - 1] === t[0]) {
298
+ return t.slice(1, -1);
299
+ }
300
+ return t;
301
+ }
302
+ /** 按 `,` 与空白切分符号名列表(CE 的 `Split([',',' '])`) */
303
+ function splitNames(text) {
304
+ return text
305
+ .split(/[,\s]+/)
306
+ .map((s) => s.trim())
307
+ .filter((s) => s !== '');
308
+ }
309
+ /** 解析"数量"参数:CE 里 alloc 大小 / readmem 长度走 StrToInt(十进制),这里额外接受 0x/$ */
310
+ function parseCount(text, context) {
311
+ const t = text.trim();
312
+ let value = null;
313
+ if (/^0x/i.test(t))
314
+ value = safeBigInt(t);
315
+ else if (t[0] === '$')
316
+ value = safeBigInt('0x' + t.slice(1));
317
+ else if (/^\d+$/.test(t))
318
+ value = safeBigInt(t);
319
+ else
320
+ value = (0, autoassembler_directives_1.parseCeInteger)(t, true);
321
+ if (value === null || value < 0n) {
322
+ throw new Error(`${context} 需要一个非负整数,收到 "${text}"`);
323
+ }
324
+ const n = Number(value);
325
+ if (!Number.isSafeInteger(n)) {
326
+ throw new Error(`${context} 的数值 ${value} 过大`);
327
+ }
328
+ return n;
329
+ }
330
+ function safeBigInt(text) {
331
+ try {
332
+ return BigInt(text);
333
+ }
334
+ catch {
335
+ return null;
336
+ }
337
+ }
338
+ /**
339
+ * 把地址表达式里的裸数字改成 `0x` 形式。
340
+ *
341
+ * 为什么需要:CE 的地址表达式(`"game.exe"+1234`)里是**十六进制**,
342
+ * 而本项目的 `addr/expression.ts` 对不含 a-f 字母的裸数字按**十进制**解析
343
+ * (见该文件 152~163 行)。若不转换,`"exe"+1234` 会算成 +4660 而不是 +0x1234。
344
+ *
345
+ * 只处理不被标识符/`$`/`#` 包夹的纯数字串,引号内不动。
346
+ */
347
+ function hexifyBareNumbers(expression) {
348
+ let out = '';
349
+ let i = 0;
350
+ let quote = null;
351
+ while (i < expression.length) {
352
+ const ch = expression[i];
353
+ if (quote !== null) {
354
+ out += ch;
355
+ if (ch === quote)
356
+ quote = null;
357
+ i++;
358
+ continue;
359
+ }
360
+ if (ch === '"' || ch === "'") {
361
+ quote = ch;
362
+ out += ch;
363
+ i++;
364
+ continue;
365
+ }
366
+ // 已是 0x 前缀:原样保留
367
+ if (ch === '0' && (expression[i + 1] === 'x' || expression[i + 1] === 'X')) {
368
+ let j = i + 2;
369
+ while (j < expression.length && /[0-9a-fA-F]/.test(expression[j]))
370
+ j++;
371
+ out += expression.slice(i, j);
372
+ i = j;
373
+ continue;
374
+ }
375
+ if (ch >= '0' && ch <= '9') {
376
+ let j = i;
377
+ while (j < expression.length && /[0-9a-fA-F]/.test(expression[j]))
378
+ j++;
379
+ const token = expression.slice(i, j);
380
+ const prev = out.length > 0 ? out[out.length - 1] : '';
381
+ const next = j < expression.length ? expression[j] : '';
382
+ const partOfIdent = /[A-Za-z0-9_.$@?#\\/]/.test(prev) || /[A-Za-z_.$@?\\/]/.test(next);
383
+ out += partOfIdent ? token : '0x' + token;
384
+ i = j;
385
+ continue;
386
+ }
387
+ out += ch;
388
+ i++;
389
+ }
390
+ return out;
391
+ }
392
+ /* ------------------------------------------------------------------ *
393
+ * AOB 扫描
394
+ * ------------------------------------------------------------------ */
395
+ /** 扫描出全部匹配地址(升序) */
396
+ function scanAobUnrestricted(proc, patternText, blockSize, parallel) {
397
+ const session = new scanner_1.ScanSession(proc, { blockSize, parallel });
398
+ session.firstScan({
399
+ variableType: types_1.VariableType.ByteArray,
400
+ scanOption: types_1.ScanOption.ExactValue,
401
+ roundingType: types_1.RoundingType.Rounded,
402
+ floatAccuracy: 0,
403
+ value1: patternText,
404
+ includeImage: true,
405
+ includeMapped: true,
406
+ includePrivate: true,
407
+ includeGuard: false,
408
+ });
409
+ return [...session.results].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
410
+ }
411
+ /**
412
+ * 在限定区域内扫描 AOB。
413
+ *
414
+ * `ScanSession` 的 `ScanConfig` 没有地址范围字段,因此 module/region 版本自己实现:
415
+ * 逐块读取(每块多读 `pattern.length-1` 字节以覆盖跨块边界),在块内做模式匹配。
416
+ */
417
+ function scanAobInRegions(proc, patternText, regions, blockSize) {
418
+ const pattern = (0, value_codec_1.parseAob)(patternText);
419
+ if (pattern.length === 0)
420
+ throw new Error(`AOB 特征串解析为空:"${patternText}"`);
421
+ const nibble = (0, value_codec_1.aobHasNibbleWildcard)(pattern);
422
+ const overlap = pattern.length - 1;
423
+ const results = [];
424
+ const seen = new Set();
425
+ for (const region of regions) {
426
+ const start = region.baseAddress;
427
+ const end = region.baseAddress + region.memorySize;
428
+ let cursor = start;
429
+ while (cursor < end) {
430
+ const windowSize = Number(end - cursor > BigInt(blockSize) ? BigInt(blockSize) : end - cursor);
431
+ const readSize = Math.min(windowSize + overlap, blockSize + overlap);
432
+ const buf = proc.readMemory(cursor, readSize);
433
+ if (buf.length >= pattern.length) {
434
+ const limit = Math.min(windowSize, buf.length - pattern.length + 1);
435
+ for (let off = 0; off < limit; off++) {
436
+ if ((0, value_codec_1.aobMatchesAt)(buf, off, pattern, nibble)) {
437
+ const address = cursor + BigInt(off);
438
+ const key = address.toString(16);
439
+ if (!seen.has(key)) {
440
+ seen.add(key);
441
+ results.push(address);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ cursor += BigInt(windowSize);
447
+ }
448
+ }
449
+ return results.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
450
+ }
451
+ /** 按名字找模块(忽略大小写,允许省略扩展名、允许包含匹配) */
452
+ function findModule(modules, name) {
453
+ const needle = name.trim().toLowerCase();
454
+ const exact = modules.find((m) => m.name.toLowerCase() === needle);
455
+ if (exact)
456
+ return exact;
457
+ const base = modules.find((m) => m.name.toLowerCase().replace(/\.(dll|exe|so)$/i, '') === needle.replace(/\.(dll|exe|so)$/i, ''));
458
+ if (base)
459
+ return base;
460
+ const byPath = modules.find((m) => m.path.toLowerCase() === needle);
461
+ if (byPath)
462
+ return byPath;
463
+ return modules.find((m) => m.name.toLowerCase().includes(needle)) ?? null;
464
+ }
465
+ /** 找出 [ENABLE] / [DISABLE] 的行号;重复出现时报错(对齐 getenableanddisablepos 的 -2 语义) */
466
+ function analyzeSections(lines) {
467
+ let enableLine = null;
468
+ let disableLine = null;
469
+ for (const line of lines) {
470
+ const upper = line.text.toUpperCase();
471
+ if (upper === '[ENABLE]') {
472
+ if (enableLine !== null) {
473
+ throw new AutoAssemblerError('[ENABLE] 段重复定义', {
474
+ line: line.text,
475
+ lineNumber: line.number,
476
+ file: line.file,
477
+ });
478
+ }
479
+ enableLine = line.number;
480
+ }
481
+ else if (upper === '[DISABLE]') {
482
+ if (disableLine !== null) {
483
+ throw new AutoAssemblerError('[DISABLE] 段重复定义', {
484
+ line: line.text,
485
+ lineNumber: line.number,
486
+ file: line.file,
487
+ });
488
+ }
489
+ disableLine = line.number;
490
+ }
491
+ }
492
+ return { enableLine, disableLine };
493
+ }
494
+ /**
495
+ * 取出「外层代码 + 选中段」,对齐 `getEnableOrDisableScript`。
496
+ *
497
+ * 关键语义:**两个段之外的代码在 enable 与 disable 时都会执行**
498
+ * (`alloc`/`registersymbol` 通常写在段外面)。段内的行按原顺序保留。
499
+ */
500
+ function selectLines(lines, enable) {
501
+ const out = [];
502
+ let insideEnable = false;
503
+ let insideDisable = false;
504
+ for (const line of lines) {
505
+ const upper = line.text.toUpperCase();
506
+ if (upper === '[ENABLE]') {
507
+ insideEnable = true;
508
+ insideDisable = false;
509
+ continue;
510
+ }
511
+ if (upper === '[DISABLE]') {
512
+ insideEnable = false;
513
+ insideDisable = true;
514
+ continue;
515
+ }
516
+ const isOuter = !insideEnable && !insideDisable;
517
+ if (isOuter || (insideEnable && enable) || (insideDisable && !enable))
518
+ out.push(line);
519
+ }
520
+ return out;
521
+ }
522
+ /**
523
+ * 处理 CPU 专属段,对齐 `stripCPUspecificCode`:
524
+ * - `[32-BIT]` … `[/32-BIT]`
525
+ * - `[64-BIT]` … `[/64-BIT]`
526
+ * 目标为 64 位时丢弃 32 位块,反之亦然。标记行本身被移除。
527
+ */
528
+ function stripCpuSpecific(lines, bitness) {
529
+ const strip32 = bitness === 64;
530
+ const out = [];
531
+ let excluded = false;
532
+ for (const line of lines) {
533
+ const upper = line.text.toUpperCase();
534
+ if (upper === '[32-BIT]') {
535
+ if (strip32)
536
+ excluded = true;
537
+ continue;
538
+ }
539
+ if (upper === '[/32-BIT]') {
540
+ if (strip32)
541
+ excluded = false;
542
+ continue;
543
+ }
544
+ if (upper === '[64-BIT]') {
545
+ if (!strip32)
546
+ excluded = true;
547
+ continue;
548
+ }
549
+ if (upper === '[/64-BIT]') {
550
+ if (!strip32)
551
+ excluded = false;
552
+ continue;
553
+ }
554
+ if (!excluded)
555
+ out.push(line);
556
+ }
557
+ return out;
558
+ }
559
+ /* ------------------------------------------------------------------ *
560
+ * AutoAssembler
561
+ * ------------------------------------------------------------------ */
562
+ /**
563
+ * 一个 Auto Assembler 会话。
564
+ *
565
+ * 会话保存跨调用的状态(符号表、alloc、原字节记录),因此
566
+ * `autoAssemble(script, {enable:true})` 之后
567
+ * `autoAssemble(script, {enable:false})` 才能精确还原。
568
+ * 这与 CE 里 `TMemoryRecord` 持有 `TDisableInfo` 是同一个设计。
569
+ */
570
+ class AutoAssembler {
571
+ options;
572
+ /** 跨脚本可见的符号(alloc / aobscan / registersymbol 的结果) */
573
+ persistentSymbols = new Map();
574
+ /** 当前会话的 alloc */
575
+ allocations = new Map();
576
+ /** registersymbol 注册的名字 */
577
+ registeredSymbols = new Set();
578
+ /**
579
+ * 每个脚本 key 对应一份"ENABLE 期间记录的原字节"。
580
+ *
581
+ * 为什么按脚本分开存:CE 的 `TDisableInfo` 是**每个内存记录一份**的,
582
+ * 因此可以同时启用多个脚本、再各自独立关闭。若共用一份历史,
583
+ * 启用脚本 B 会冲掉脚本 A 的记录,导致 A 无法还原。
584
+ */
585
+ histories = new Map();
586
+ /** 模块列表缓存(地址表达式反复求值用) */
587
+ moduleCache = [];
588
+ constructor(options = {}) {
589
+ this.options = options;
590
+ }
591
+ /** 目标进程(可能为 undefined:纯语法检查模式) */
592
+ get process() {
593
+ return this.options.process;
594
+ }
595
+ /** 当前已知符号(只读快照) */
596
+ get symbols() {
597
+ return new Map(this.persistentSymbols);
598
+ }
599
+ /** 当前 alloc(只读快照) */
600
+ get allocationsList() {
601
+ return [...this.allocations.values()];
602
+ }
603
+ /** 释放本会话分配的全部内存并清空符号/历史 */
604
+ cleanup() {
605
+ const proc = this.options.process;
606
+ if (proc && proc.isOpen) {
607
+ for (const alloc of this.allocations.values()) {
608
+ try {
609
+ proc.free(alloc.address);
610
+ }
611
+ catch {
612
+ /* 目标可能已退出 */
613
+ }
614
+ }
615
+ }
616
+ this.allocations.clear();
617
+ this.persistentSymbols.clear();
618
+ this.registeredSymbols.clear();
619
+ this.histories.clear();
620
+ this.moduleCache = [];
621
+ }
622
+ /** 本会话是否记录过该脚本的 ENABLE 原字节 */
623
+ hasHistory(script) {
624
+ const history = this.histories.get(historyKey(script));
625
+ return history !== undefined && history.length > 0;
626
+ }
627
+ /** 取某脚本记录的原字节(只读副本,诊断用) */
628
+ historyOf(script) {
629
+ return (this.histories.get(historyKey(script)) ?? []).map((r) => ({
630
+ address: r.address,
631
+ bytes: Buffer.from(r.bytes),
632
+ }));
633
+ }
634
+ /** 执行 ENABLE 段 */
635
+ enable(script) {
636
+ return this.autoAssemble(script, { enable: true });
637
+ }
638
+ /** 执行 DISABLE 段(按 `restoreMode` 还原) */
639
+ disable(script) {
640
+ return this.autoAssemble(script, { enable: false });
641
+ }
642
+ /** 只做语法检查与汇编(不分配、不写内存、无副作用) */
643
+ check(script) {
644
+ return this.autoAssemble(script, { syntaxCheckOnly: true });
645
+ }
646
+ /**
647
+ * 解析脚本并执行,对齐 CE 的 `autoassemble(code, enable)`。
648
+ *
649
+ * @param script 脚本原文
650
+ * @param opts.enable 执行 ENABLE(默认)还是 DISABLE 段
651
+ * @param opts.syntaxCheckOnly 只检查不执行(没有 process 时自动开启)
652
+ */
653
+ autoAssemble(script, opts = {}) {
654
+ const enable = opts.enable !== false;
655
+ const proc = this.options.process;
656
+ const syntaxCheckOnly = opts.syntaxCheckOnly === true || !proc;
657
+ const bitness = this.resolveBitness();
658
+ // ---- 1) 词法:去注释 + 展开 include -------------------------------
659
+ const sourceLines = this.toSourceLines(script);
660
+ const expanded = this.expandIncludes(sourceLines, 0);
661
+ // ---- 2) 段选择 → CPU 专属段剥离 ----------------------------------
662
+ const sections = analyzeSections(expanded);
663
+ const selected = stripCpuSpecific(selectLines(expanded, enable), bitness);
664
+ // ---- 3) 预处理:指令 ⇒ 汇编行 ------------------------------------
665
+ const run = new RunState(enable, syntaxCheckOnly, bitness, this.resolveArch());
666
+ const createdAllocations = [];
667
+ // 历史记录:ENABLE 时新建一份空的;DISABLE 时取回同一脚本的记录
668
+ const key = historyKey(script);
669
+ if (enable) {
670
+ run.history = [];
671
+ this.histories.set(key, run.history);
672
+ }
673
+ else {
674
+ const existing = this.histories.get(key);
675
+ run.history = existing ?? [];
676
+ if (!existing) {
677
+ run.warnings.push('本会话没有找到该脚本的 ENABLE 原字节记录;将只执行 [DISABLE] 段脚本,' +
678
+ '可能无法完全还原(请确认 enable/disable 使用的是同一个 AutoAssembler 会话与同一份脚本)');
679
+ }
680
+ }
681
+ // 段缺失时给出提示(CE 在这里直接报错;本移植因为 restoreMode 默认 'original'
682
+ // 可以在没有 [DISABLE] 段的情况下依然精确还原,所以降级为警告)
683
+ if ((sections.enableLine === null) !== (sections.disableLine === null)) {
684
+ run.warnings.push(`脚本只定义了 ${sections.enableLine === null ? '[DISABLE]' : '[ENABLE]'} 段;` +
685
+ `CE 会直接报错,本移植降级为警告(缺省段视为空段)`);
686
+ }
687
+ try {
688
+ this.preprocess(selected, run, createdAllocations);
689
+ run.warningBaseLength = run.warnings.length;
690
+ this.layout(run);
691
+ this.commitLabelSymbols(run);
692
+ this.resolvePendingRegisters(run);
693
+ // ---- 4) 写目标进程 ---------------------------------------------
694
+ const { entries, bytesWritten } = this.apply(run, enable);
695
+ return {
696
+ enable: run.enable,
697
+ syntaxCheckOnly,
698
+ entries,
699
+ bytesWritten,
700
+ lines: run.asmLines.map((l) => ({
701
+ text: l.text,
702
+ address: l.address,
703
+ bytes: l.bytes,
704
+ empty: l.bytes.length === 0,
705
+ labels: l.labels.map((x) => x.name),
706
+ lineNumber: l.lineNumber,
707
+ file: l.file,
708
+ })),
709
+ symbols: new Map([...this.persistentSymbols, ...run.symbols]),
710
+ allocations: [...this.allocations.values()],
711
+ registeredSymbols: [...this.registeredSymbols],
712
+ createThreads: [...run.createThreads],
713
+ aobScans: run.aobScans.map((r) => ({
714
+ name: r.name,
715
+ matches: [...r.matches],
716
+ address: r.address,
717
+ scope: r.scope,
718
+ })),
719
+ warnings: [...run.warnings],
720
+ sections: { enableLine: sections.enableLine, disableLine: sections.disableLine },
721
+ };
722
+ }
723
+ catch (e) {
724
+ // 脚本失败时回滚本次新建的 alloc,避免在目标进程里泄漏内存
725
+ if (proc && !syntaxCheckOnly) {
726
+ for (const name of createdAllocations.reverse()) {
727
+ const alloc = this.allocations.get(name);
728
+ if (!alloc)
729
+ continue;
730
+ try {
731
+ proc.free(alloc.address);
732
+ }
733
+ catch {
734
+ /* 尽力而为 */
735
+ }
736
+ this.allocations.delete(name);
737
+ if (this.persistentSymbols.get(name.toLowerCase()) === alloc.address) {
738
+ this.persistentSymbols.delete(name.toLowerCase());
739
+ }
740
+ }
741
+ }
742
+ if (e instanceof AutoAssemblerError)
743
+ throw e;
744
+ throw new AutoAssemblerError(e.message || String(e), { phase: 'parse' });
745
+ }
746
+ }
747
+ /* ---------------------------------------------------------------- *
748
+ * 步骤 1:词法
749
+ * ---------------------------------------------------------------- */
750
+ toSourceLines(script) {
751
+ return stripComments(script).map((text, i) => ({
752
+ text,
753
+ number: i + 1,
754
+ file: '<script>',
755
+ }));
756
+ }
757
+ /** 展开 `include(file.cea)`(就地插入,保留被包含文件自己的行号) */
758
+ expandIncludes(lines, depth) {
759
+ if (depth > MAX_INCLUDE_DEPTH) {
760
+ throw new AutoAssemblerError(`include 递归层数超过 ${MAX_INCLUDE_DEPTH} 层,疑似循环引用`, {
761
+ phase: 'parse',
762
+ });
763
+ }
764
+ const out = [];
765
+ for (const line of lines) {
766
+ const arg = line.text ? parseCall(line.text, 'include') : null;
767
+ if (arg === null) {
768
+ out.push(line);
769
+ continue;
770
+ }
771
+ const requested = unquote(arg);
772
+ const resolved = this.resolveIncludePath(requested, line);
773
+ const content = (0, node_fs_1.readFileSync)(resolved, 'utf8');
774
+ const inner = stripComments(content).map((text, i) => ({
775
+ text,
776
+ number: i + 1,
777
+ file: resolved,
778
+ }));
779
+ out.push(...this.expandIncludes(inner, depth + 1));
780
+ }
781
+ return out;
782
+ }
783
+ resolveIncludePath(requested, line) {
784
+ let candidate = requested;
785
+ const ext = (0, node_path_1.extname)(candidate).toUpperCase();
786
+ if (ext === '')
787
+ candidate = `${candidate}.cea`;
788
+ else if (ext === '.')
789
+ candidate = `${candidate.slice(0, -1)}.cea`;
790
+ const tries = [];
791
+ if ((0, node_path_1.isAbsolute)(candidate)) {
792
+ tries.push(candidate);
793
+ }
794
+ else {
795
+ if (this.options.scriptPath) {
796
+ tries.push((0, node_path_1.resolve)((0, node_path_1.dirname)(this.options.scriptPath), candidate));
797
+ }
798
+ tries.push((0, node_path_1.resolve)(process.cwd(), candidate));
799
+ for (const dir of this.options.includeDirs ?? []) {
800
+ tries.push((0, node_path_1.resolve)(dir, candidate));
801
+ }
802
+ }
803
+ for (const p of tries) {
804
+ if ((0, node_fs_1.existsSync)(p))
805
+ return p;
806
+ }
807
+ throw new AutoAssemblerError(`include 的文件找不到:"${requested}"(尝试过:${tries.join('、')})`, { line: line.text, lineNumber: line.number, file: line.file });
808
+ }
809
+ /* ---------------------------------------------------------------- *
810
+ * 步骤 2/3:预处理
811
+ * ---------------------------------------------------------------- */
812
+ /**
813
+ * 遍历选中段的每一行,把 CE 指令转换成"汇编行"。
814
+ *
815
+ * 处理顺序与 CE 的解析循环一致:先做指令匹配,未匹配的按普通汇编行处理
816
+ * (因此 `db/nop/align` 也走这里)。
817
+ */
818
+ preprocess(lines, run, createdAllocations) {
819
+ for (const line of lines) {
820
+ const text = line.text;
821
+ if (text === '')
822
+ continue;
823
+ // 已处理掉的段标记(保险)
824
+ if (/^\[\/?\d+-?BIT\]$/i.test(text) || /^\[(ENABLE|DISABLE)\]$/i.test(text))
825
+ continue;
826
+ // 条件编译:CE 的 {$...},本移植不支持
827
+ if (/^\{\$/.test(text)) {
828
+ throw this.error(line, 'CE 的条件编译指令 {$...} 未实现;请用 [32-BIT]/[64-BIT] 段或事先裁剪脚本');
829
+ }
830
+ // ---- include 已在上一步展开,这里不应再出现 ----
831
+ if (parseCall(text, 'include') !== null) {
832
+ throw this.error(line, 'include() 应在展开阶段处理(内部错误)');
833
+ }
834
+ // ---- aobscan 系列 ----
835
+ if (this.tryAobScan(text, line, run))
836
+ continue;
837
+ // ---- createthread ----
838
+ if (this.tryCreateThread(text, line, run))
839
+ continue;
840
+ // ---- 分配类 ----
841
+ if (this.tryAlloc(text, line, run, createdAllocations))
842
+ continue;
843
+ // ---- 符号声明 ----
844
+ if (this.trySymbolDirective(text, line, run))
845
+ continue;
846
+ // ---- define ----
847
+ const defineArgs = parseCall(text, 'define');
848
+ if (defineArgs !== null) {
849
+ this.handleDefine(defineArgs, line, run);
850
+ continue;
851
+ }
852
+ // ---- readmem / loadbinary / assert ----
853
+ if (this.tryMemoryUtility(text, line, run))
854
+ continue;
855
+ // ---- 明确不支持的指令:给出可操作的中文错误 ----
856
+ const unsupported = this.matchUnsupported(text);
857
+ if (unsupported) {
858
+ throw this.error(line, `指令 ${unsupported} 本移植未实现(详见 docs/autoassembler.md 的「已知限制」)`);
859
+ }
860
+ // ---- 普通汇编行:先应用 define 文本替换 ----
861
+ const substituted = this.applyDefines(text, run);
862
+ // 地址定义行(`name:` / `00401000:` / `"game.exe"+1234:`)
863
+ const setter = this.parseAddressSetter(substituted, line);
864
+ if (setter) {
865
+ for (const lab of setter.labels)
866
+ run.definedLabelNames.add(lab.name.toLowerCase());
867
+ run.asmLines.push({
868
+ text: setter.rest,
869
+ pseudo: setter.rest ? (0, autoassembler_directives_1.parsePseudoDirective)(setter.rest) : null,
870
+ readmemBytes: null,
871
+ labels: setter.labels,
872
+ raw: line.text,
873
+ lineNumber: line.number,
874
+ file: line.file,
875
+ address: 0n,
876
+ bytes: Buffer.alloc(0),
877
+ });
878
+ continue;
879
+ }
880
+ run.asmLines.push({
881
+ text: substituted,
882
+ pseudo: (0, autoassembler_directives_1.parsePseudoDirective)(substituted),
883
+ readmemBytes: null,
884
+ labels: [],
885
+ raw: line.text,
886
+ lineNumber: line.number,
887
+ file: line.file,
888
+ address: 0n,
889
+ bytes: Buffer.alloc(0),
890
+ });
891
+ }
892
+ // label() 声明了却从未定义:降级为警告(CE 这里直接报错)
893
+ for (const [name, decl] of run.declaredLabels) {
894
+ if (!run.definedLabelNames.has(name)) {
895
+ run.warnings.push(`第 ${decl.lineNumber} 行 label(${name}) 声明了标签,但脚本里没有 "${name}:" 定义行` +
896
+ `(CE 会在此报错,本移植降级为警告)`);
897
+ }
898
+ }
899
+ }
900
+ /** 判断是否为明确不支持的指令;返回指令名(大写)或 null */
901
+ matchUnsupported(text) {
902
+ const m = text.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
903
+ if (!m)
904
+ return null;
905
+ const name = m[1].toLowerCase();
906
+ for (const bad of UNSUPPORTED_DIRECTIVES) {
907
+ if (name === bad)
908
+ return m[1].toUpperCase();
909
+ }
910
+ return null;
911
+ }
912
+ /* ---------------------------------------------------------------- *
913
+ * aobscan
914
+ * ---------------------------------------------------------------- */
915
+ tryAobScan(text, line, run) {
916
+ // aobscanregion(name, start, stop, pattern)
917
+ const regionArgs = parseCall(text, 'aobscanregion');
918
+ if (regionArgs !== null) {
919
+ const parts = this.splitTopLevel(regionArgs, 4);
920
+ if (parts.length < 4) {
921
+ throw this.error(line, 'aobscanregion 语法应为 aobscanregion(name, start, stop, 11 22 33 ** 55)');
922
+ }
923
+ const name = parts[0].trim();
924
+ if (!isIdentifier(name))
925
+ throw this.error(line, `aobscanregion 的名字 "${name}" 不是合法标识符`);
926
+ const start = this.resolveExpression(parts[1].trim(), line, run);
927
+ const stop = this.resolveExpression(parts[2].trim(), line, run);
928
+ const pattern = parts[3].trim();
929
+ this.performAobScan(name, pattern, line, run, { start, stop });
930
+ return true;
931
+ }
932
+ // aobscanmodule(name, module, pattern)
933
+ const moduleArgs = parseCall(text, 'aobscanmodule');
934
+ if (moduleArgs !== null) {
935
+ const parts = this.splitTopLevel(moduleArgs, 3);
936
+ if (parts.length < 3) {
937
+ throw this.error(line, 'aobscanmodule 语法应为 aobscanmodule(name, module, 11 22 33 ** 55)');
938
+ }
939
+ const name = parts[0].trim();
940
+ if (!isIdentifier(name))
941
+ throw this.error(line, `aobscanmodule 的名字 "${name}" 不是合法标识符`);
942
+ const moduleName = unquote(parts[1].trim());
943
+ const pattern = parts[2].trim();
944
+ this.performAobScan(name, pattern, line, run, { module: moduleName });
945
+ return true;
946
+ }
947
+ // aobscanunique(name, pattern) —— 命中多于一处的唯一性要求
948
+ const uniqueArgs = parseCall(text, 'aobscanunique');
949
+ if (uniqueArgs !== null) {
950
+ const parts = this.splitTopLevel(uniqueArgs, 2);
951
+ if (parts.length < 2) {
952
+ throw this.error(line, 'aobscanunique 语法应为 aobscanunique(name, 11 22 33 ** 55)');
953
+ }
954
+ const name = parts[0].trim();
955
+ if (!isIdentifier(name))
956
+ throw this.error(line, `aobscanunique 的名字 "${name}" 不是合法标识符`);
957
+ this.performAobScan(name, parts[1].trim(), line, run, { unique: true });
958
+ return true;
959
+ }
960
+ // aobscan(name, pattern)
961
+ const args = parseCall(text, 'aobscan');
962
+ if (args !== null) {
963
+ const parts = this.splitTopLevel(args, 2);
964
+ if (parts.length < 2) {
965
+ throw this.error(line, 'aobscan 语法应为 aobscan(name, 11 22 33 ** 55)');
966
+ }
967
+ const name = parts[0].trim();
968
+ if (!isIdentifier(name))
969
+ throw this.error(line, `aobscan 的名字 "${name}" 不是合法标识符`);
970
+ this.performAobScan(name, parts[1].trim(), line, run, {});
971
+ return true;
972
+ }
973
+ return false;
974
+ }
975
+ /** 按 `maxParts` 上限在顶层逗号处切分(引号内不切) */
976
+ splitTopLevel(text, maxParts) {
977
+ const out = [];
978
+ let current = '';
979
+ let quote = null;
980
+ for (let i = 0; i < text.length; i++) {
981
+ const ch = text[i];
982
+ if (quote !== null) {
983
+ current += ch;
984
+ if (ch === quote)
985
+ quote = null;
986
+ continue;
987
+ }
988
+ if (ch === '"' || ch === "'") {
989
+ quote = ch;
990
+ current += ch;
991
+ continue;
992
+ }
993
+ if (ch === ',' && out.length < maxParts - 1) {
994
+ out.push(current);
995
+ current = '';
996
+ continue;
997
+ }
998
+ current += ch;
999
+ }
1000
+ out.push(current);
1001
+ return out;
1002
+ }
1003
+ performAobScan(name, pattern, line, run, scope) {
1004
+ if (pattern === '')
1005
+ throw this.error(line, 'AOB 特征串不能为空');
1006
+ const proc = this.options.process;
1007
+ if (run.syntaxCheckOnly || !proc) {
1008
+ // 语法检查模式:拿不到地址,用 0 占位(对齐 CE 把 AOBSCAN 换成 DEFINE(name,0))
1009
+ run.warnings.push(`第 ${line.number} 行 aobscan(${name}) 在语法检查模式下按地址 0 占位`);
1010
+ this.registerSymbol(name, 0n, run, line);
1011
+ return;
1012
+ }
1013
+ const blockSize = this.options.scanBlockSize ?? DEFAULT_SCAN_BLOCK_SIZE;
1014
+ let results;
1015
+ if (scope.module !== undefined) {
1016
+ const mod = findModule(proc.modules(), scope.module);
1017
+ if (!mod) {
1018
+ throw this.error(line, `aobscanmodule 找不到模块 "${scope.module}"`, 'scan');
1019
+ }
1020
+ results = scanAobInRegions(proc, pattern, [
1021
+ {
1022
+ baseAddress: mod.baseAddress,
1023
+ memorySize: BigInt(mod.size),
1024
+ committed: true,
1025
+ protect: 0,
1026
+ type: 0,
1027
+ },
1028
+ ], blockSize);
1029
+ }
1030
+ else if (scope.start !== undefined && scope.stop !== undefined) {
1031
+ const start = scope.start < scope.stop ? scope.start : scope.stop;
1032
+ const stop = scope.start < scope.stop ? scope.stop : scope.start;
1033
+ const regions = proc
1034
+ .scannableRegions({})
1035
+ .filter((r) => r.baseAddress < stop && r.baseAddress + r.memorySize > start)
1036
+ .map((r) => ({
1037
+ ...r,
1038
+ baseAddress: r.baseAddress < start ? start : r.baseAddress,
1039
+ memorySize: (r.baseAddress + r.memorySize > stop ? stop : r.baseAddress + r.memorySize) -
1040
+ (r.baseAddress < start ? start : r.baseAddress),
1041
+ }));
1042
+ results = scanAobInRegions(proc, pattern, regions, blockSize);
1043
+ }
1044
+ else {
1045
+ results = scanAobUnrestricted(proc, pattern, blockSize, this.options.scanParallel === true);
1046
+ }
1047
+ if (results.length === 0) {
1048
+ throw this.error(line, `aobscan(${name}) 在目标进程里找不到特征码 "${pattern}"`, 'scan');
1049
+ }
1050
+ if (scope.unique === true && results.length > 1) {
1051
+ throw this.error(line, `aobscanunique(${name}) 期望唯一命中,实际找到 ${results.length} 处` +
1052
+ `(前几处:${results.slice(0, 4).map((a) => '0x' + a.toString(16)).join('、')})`, 'scan');
1053
+ }
1054
+ const address = results[0];
1055
+ const scanKind = scope.unique === true
1056
+ ? 'unique'
1057
+ : scope.module !== undefined
1058
+ ? 'module'
1059
+ : scope.start !== undefined
1060
+ ? 'region'
1061
+ : 'all';
1062
+ run.aobScans.push({ name, matches: [...results], address, scope: scanKind });
1063
+ if (results.length > 1) {
1064
+ run.warnings.push(`第 ${line.number} 行 aobscan(${name}) 命中 ${results.length} 处,取第一处 ` +
1065
+ `0x${address.toString(16)}(CE 同样取第一处;可用 aobscanunique 强制唯一)`);
1066
+ }
1067
+ this.registerSymbol(name, address, run, line);
1068
+ }
1069
+ /* ---------------------------------------------------------------- *
1070
+ * createthread
1071
+ * ---------------------------------------------------------------- */
1072
+ tryCreateThread(text, line, run) {
1073
+ const args = parseCall(text, 'createthreadandwait') ?? parseCall(text, 'createthread');
1074
+ if (args === null)
1075
+ return false;
1076
+ const isWait = /^createthreadandwait/i.test(text);
1077
+ const parts = this.splitTopLevel(args, isWait ? 2 : 1);
1078
+ if (isWait) {
1079
+ // createthreadandwait(name, timeout):name 是标签,timeout 是毫秒(十进制)
1080
+ const name = parts[0].trim();
1081
+ if (name === '')
1082
+ throw this.error(line, 'createthreadandwait 需要一个标签参数');
1083
+ if (parts.length >= 2 && parts[1].trim() !== '') {
1084
+ const timeout = parseCount(parts[1].trim(), 'createthreadandwait 的超时');
1085
+ void timeout;
1086
+ }
1087
+ if (this.options.allowCreateThread === true) {
1088
+ throw this.error(line, '本移植不支持在目标进程创建远程线程(Node 无 CreateRemoteThread 对应能力);' +
1089
+ '请把 allowCreateThread 保持为 false,此时 createthreadandwait 只做参数校验');
1090
+ }
1091
+ // 校验标签可解析
1092
+ const address = run.symbols.get(name.toLowerCase());
1093
+ if (address === undefined && !run.declaredLabels.has(name.toLowerCase())) {
1094
+ run.warnings.push(`第 ${line.number} 行 createthreadandwait(${name}):标签未在此前定义,运行期才能确定地址`);
1095
+ }
1096
+ run.warnings.push(`第 ${line.number} 行 createthreadandwait(${name}) 仅做参数校验,未创建线程`);
1097
+ return true;
1098
+ }
1099
+ const expr = args.trim();
1100
+ if (expr === '') {
1101
+ throw this.error(line, 'createthread 语法应为 createthread(address)');
1102
+ }
1103
+ const address = this.resolveExpression(expr, line, run);
1104
+ if (this.options.allowCreateThread === true) {
1105
+ throw this.error(line, '本移植不支持在目标进程创建远程线程(Node 无 CreateRemoteThread 对应能力);' +
1106
+ '请把 allowCreateThread 保持为 false,此时 createthread 只做参数校验');
1107
+ }
1108
+ run.createThreads.push(address);
1109
+ run.warnings.push(`第 ${line.number} 行 createthread(0x${address.toString(16)}) 仅做参数校验,未创建线程`);
1110
+ return true;
1111
+ }
1112
+ /* ---------------------------------------------------------------- *
1113
+ * alloc / dealloc / realloc / globalalloc
1114
+ * ---------------------------------------------------------------- */
1115
+ tryAlloc(text, line, run, createdAllocations) {
1116
+ // globalalloc(name, size)
1117
+ const globalArgs = parseCall(text, 'globalalloc');
1118
+ if (globalArgs !== null) {
1119
+ const parts = this.splitTopLevel(globalArgs, 2);
1120
+ if (parts.length < 2)
1121
+ throw this.error(line, 'globalalloc 语法应为 globalalloc(name, size)');
1122
+ const name = parts[0].trim();
1123
+ if (!isIdentifier(name))
1124
+ throw this.error(line, `globalalloc 的名字 "${name}" 不是合法标识符`);
1125
+ const size = parseCount(parts[1].trim(), 'globalalloc 的大小');
1126
+ this.allocate(name, size, run, line, true, 0n, createdAllocations);
1127
+ return true;
1128
+ }
1129
+ // alloc(name, size[, preferred])
1130
+ const allocArgs = parseCall(text, 'alloc');
1131
+ if (allocArgs !== null) {
1132
+ const parts = this.splitTopLevel(allocArgs, 3);
1133
+ if (parts.length < 2)
1134
+ throw this.error(line, 'alloc 语法应为 alloc(name, sizeinbytes)');
1135
+ const name = parts[0].trim();
1136
+ if (!isIdentifier(name))
1137
+ throw this.error(line, `alloc 的名字 "${name}" 不是合法标识符`);
1138
+ const size = parseCount(parts[1].trim(), 'alloc 的大小');
1139
+ const preferred = parts.length >= 3 && parts[2].trim() !== ''
1140
+ ? this.resolveExpression(parts[2].trim(), line, run)
1141
+ : 0n;
1142
+ this.allocate(name, size, run, line, false, preferred, createdAllocations);
1143
+ return true;
1144
+ }
1145
+ // realloc(name, size)
1146
+ const reallocArgs = parseCall(text, 'realloc');
1147
+ if (reallocArgs !== null) {
1148
+ const parts = this.splitTopLevel(reallocArgs, 2);
1149
+ if (parts.length < 2)
1150
+ throw this.error(line, 'realloc 语法应为 realloc(name, size)');
1151
+ const name = parts[0].trim();
1152
+ const size = parseCount(parts[1].trim(), 'realloc 的大小');
1153
+ const existing = this.allocations.get(name);
1154
+ if (!existing) {
1155
+ throw this.error(line, `realloc("${name}") 找不到已分配的块;请先用 alloc 分配`);
1156
+ }
1157
+ const proc = this.options.process;
1158
+ if (proc && !run.syntaxCheckOnly) {
1159
+ try {
1160
+ proc.free(existing.address);
1161
+ }
1162
+ catch (e) {
1163
+ throw this.error(line, `realloc 释放旧块失败:${e.message}`, 'write');
1164
+ }
1165
+ this.allocations.delete(name);
1166
+ this.persistentSymbols.delete(name.toLowerCase());
1167
+ this.dropHistoryRange(run.history, existing.address, existing.size);
1168
+ this.allocate(name, size, run, line, existing.global, 0n, createdAllocations);
1169
+ }
1170
+ else {
1171
+ existing.size = size;
1172
+ run.warnings.push(`第 ${line.number} 行 realloc(${name}) 在语法检查模式下未真正执行`);
1173
+ }
1174
+ return true;
1175
+ }
1176
+ // dealloc(name) / dealloc(*)
1177
+ const deallocArgs = parseCall(text, 'dealloc');
1178
+ if (deallocArgs !== null) {
1179
+ const target = deallocArgs.trim();
1180
+ if (target === '')
1181
+ throw this.error(line, 'dealloc 语法应为 dealloc(name)');
1182
+ if (target === '*') {
1183
+ for (const name of [...this.allocations.keys()])
1184
+ this.deallocate(name, run, line);
1185
+ }
1186
+ else {
1187
+ for (const name of splitNames(target))
1188
+ this.deallocate(name, run, line, false);
1189
+ }
1190
+ return true;
1191
+ }
1192
+ return false;
1193
+ }
1194
+ allocate(name, size, run, line, global, preferred, createdAllocations) {
1195
+ if (size <= 0)
1196
+ throw this.error(line, `alloc(${name}) 的大小必须为正数,收到 ${size}`);
1197
+ const proc = this.options.process;
1198
+ const existingNamed = this.allocations.get(name);
1199
+ const reuseAllowed = this.options.reuseAllocations !== false;
1200
+ if (existingNamed && reuseAllowed) {
1201
+ this.registerSymbol(name, existingNamed.address, run, line);
1202
+ if (global)
1203
+ existingNamed.global = true;
1204
+ run.warnings.push(`第 ${line.number} 行 alloc(${name}):该名字已分配在 0x${existingNamed.address.toString(16)},` +
1205
+ `复用之(如需重新分配请先 dealloc 或设置 reuseAllocations:false)`);
1206
+ return;
1207
+ }
1208
+ if (run.syntaxCheckOnly || !proc) {
1209
+ // 语法检查:用 0 占位
1210
+ this.registerSymbol(name, 0n, run, line);
1211
+ return;
1212
+ }
1213
+ let address;
1214
+ try {
1215
+ address = proc.allocate(size, 0x40, preferred);
1216
+ }
1217
+ catch (e) {
1218
+ // 首选地址不可用时退化为"任意地址"分配(对齐 CE 的 lastChanceAllocPrefered):
1219
+ // 真实场景里首选地址常常已被占用(VirtualAllocEx 返回 ERROR_INVALID_ADDRESS 487),
1220
+ // 这时 CE 也会放弃首选地址而不是让整个脚本失败。
1221
+ if (preferred !== 0n) {
1222
+ try {
1223
+ address = proc.allocate(size, 0x40, 0n);
1224
+ run.warnings.push(`第 ${line.number} 行 alloc(${name}, ${size}, 0x${preferred.toString(16)}):` +
1225
+ `首选地址不可用(${e.message}),已改为任意地址分配`);
1226
+ }
1227
+ catch (e2) {
1228
+ throw this.error(line, `alloc(${name}, ${size}) 在目标进程分配内存失败:${e2.message}`, 'write');
1229
+ }
1230
+ }
1231
+ else {
1232
+ throw this.error(line, `alloc(${name}, ${size}) 在目标进程分配内存失败:${e.message}`, 'write');
1233
+ }
1234
+ }
1235
+ this.allocations.set(name, {
1236
+ name,
1237
+ address,
1238
+ size,
1239
+ protection: 0x40,
1240
+ global,
1241
+ });
1242
+ createdAllocations.push(name);
1243
+ this.registerSymbol(name, address, run, line);
1244
+ }
1245
+ deallocate(name, run, line, quiet = false) {
1246
+ const alloc = this.allocations.get(name);
1247
+ if (!alloc) {
1248
+ if (quiet)
1249
+ return;
1250
+ // CE 的 dealloc 只释放"本脚本分配过的"内存;找不到时静默通过更接近用户预期
1251
+ run.warnings.push(`第 ${line.number} 行 dealloc(${name}):本会话没有名为 "${name}" 的分配,忽略`);
1252
+ return;
1253
+ }
1254
+ const proc = this.options.process;
1255
+ if (proc && !run.syntaxCheckOnly) {
1256
+ try {
1257
+ proc.free(alloc.address);
1258
+ }
1259
+ catch (e) {
1260
+ throw this.error(line, `dealloc(${name}) 释放内存失败:${e.message}`, 'write');
1261
+ }
1262
+ }
1263
+ // 内存已还给系统,对应的"原字节还原"记录必须一起丢掉
1264
+ this.dropHistoryRange(run.history, alloc.address, alloc.size);
1265
+ this.allocations.delete(name);
1266
+ this.persistentSymbols.delete(name.toLowerCase());
1267
+ }
1268
+ /* ---------------------------------------------------------------- *
1269
+ * label / registersymbol / unregistersymbol
1270
+ * ---------------------------------------------------------------- */
1271
+ trySymbolDirective(text, line, run) {
1272
+ const labelArgs = parseCall(text, 'label');
1273
+ if (labelArgs !== null) {
1274
+ const names = splitNames(labelArgs);
1275
+ if (names.length === 0)
1276
+ throw this.error(line, 'label 语法应为 label(name)');
1277
+ for (const name of names) {
1278
+ if (!isIdentifier(name))
1279
+ throw this.error(line, `label("${name}") 不是合法标识符`);
1280
+ const key = name.toLowerCase();
1281
+ if (!run.declaredLabels.has(key)) {
1282
+ run.declaredLabels.set(key, { name, lineNumber: line.number, file: line.file });
1283
+ }
1284
+ }
1285
+ return true;
1286
+ }
1287
+ const registerArgs = parseCall(text, 'registersymbol');
1288
+ if (registerArgs !== null) {
1289
+ const names = splitNames(registerArgs);
1290
+ if (names.length === 0)
1291
+ throw this.error(line, 'registersymbol 语法应为 registersymbol(name)');
1292
+ for (const name of names) {
1293
+ const key = name.toLowerCase();
1294
+ // CE 把 REGISTERSYMBOL 收集成一张表,等整个脚本汇编完(标签地址已知)
1295
+ // 再统一注册(autoassembler.pas 1971~1996 + 4350~4360)。这里同样延后解析。
1296
+ run.pendingRegisters.push({ name, line });
1297
+ const address = run.symbols.get(key) ?? this.persistentSymbols.get(key);
1298
+ if (address !== undefined) {
1299
+ this.registeredSymbols.add(name);
1300
+ this.persistentSymbols.set(key, address);
1301
+ }
1302
+ }
1303
+ return true;
1304
+ }
1305
+ const unregisterArgs = parseCall(text, 'unregistersymbol');
1306
+ if (unregisterArgs !== null) {
1307
+ const names = splitNames(unregisterArgs);
1308
+ if (names.length === 0)
1309
+ throw this.error(line, 'unregistersymbol 语法应为 unregistersymbol(name)');
1310
+ for (const name of names) {
1311
+ if (name === '*') {
1312
+ for (const n of this.allocations.keys())
1313
+ this.persistentSymbols.delete(n.toLowerCase());
1314
+ this.registeredSymbols.clear();
1315
+ continue;
1316
+ }
1317
+ this.registeredSymbols.delete(name);
1318
+ this.persistentSymbols.delete(name.toLowerCase());
1319
+ }
1320
+ return true;
1321
+ }
1322
+ return false;
1323
+ }
1324
+ /* ---------------------------------------------------------------- *
1325
+ * define
1326
+ * ---------------------------------------------------------------- */
1327
+ /**
1328
+ * `define(name, whatever)` —— **纯文本替换**,对齐 CE(autoassembler.pas 2004~2043)。
1329
+ *
1330
+ * 注意 CE 的语义细节:`whatever` 不是"值"而是"文本"。因此
1331
+ * `define(myVal, 1337)` 之后 `mov [x], myVal` 等价于 `mov [x], 1337`。
1332
+ * 值的解析发生在最终汇编/伪指令解析时,所以同一份 define 在指令里与
1333
+ * `db` 里的进制解释可能不同(详见 docs/autoassembler.md 的差异表)。
1334
+ */
1335
+ handleDefine(args, line, run) {
1336
+ const comma = args.indexOf(',');
1337
+ if (comma < 0) {
1338
+ throw this.error(line, 'define 语法应为 define(name,whatever)');
1339
+ }
1340
+ const name = args.slice(0, comma).trim();
1341
+ let value = args.slice(comma + 1).trim();
1342
+ if (name === '' || value === '') {
1343
+ throw this.error(line, 'define 语法应为 define(name,whatever)');
1344
+ }
1345
+ if (!isIdentifier(name))
1346
+ throw this.error(line, `define("${name}") 不是合法标识符`);
1347
+ // 早先的 define 会应用到后面的 define 值上(CE 同样如此)
1348
+ value = this.applyDefines(value, run, name);
1349
+ const key = name.toLowerCase();
1350
+ const existingIndex = run.defineOrder.indexOf(key);
1351
+ if (existingIndex >= 0) {
1352
+ run.defines.set(key, value);
1353
+ return;
1354
+ }
1355
+ run.defineOrder.push(key);
1356
+ run.defines.set(key, value);
1357
+ }
1358
+ /** 应用当前生效的全部 define(按定义顺序;跳过 `skipName` 自身,避免自替换) */
1359
+ applyDefines(text, run, skipName) {
1360
+ let out = text;
1361
+ for (const key of run.defineOrder) {
1362
+ if (skipName !== undefined && key === skipName.toLowerCase())
1363
+ continue;
1364
+ const value = run.defines.get(key);
1365
+ if (value === undefined)
1366
+ continue;
1367
+ out = replaceToken(out, key, value);
1368
+ }
1369
+ return out;
1370
+ }
1371
+ /* ---------------------------------------------------------------- *
1372
+ * readmem / loadbinary / assert
1373
+ * ---------------------------------------------------------------- */
1374
+ tryMemoryUtility(text, line, run) {
1375
+ // readmem(address, size)
1376
+ const readmemArgs = parseCall(text, 'readmem');
1377
+ if (readmemArgs !== null) {
1378
+ const parts = this.splitTopLevel(readmemArgs, 2);
1379
+ if (parts.length < 2)
1380
+ throw this.error(line, 'readmem 语法应为 readmem(address,size)');
1381
+ const address = this.resolveExpression(parts[0].trim(), line, run);
1382
+ const size = parseCount(parts[1].trim(), 'readmem 的长度');
1383
+ if (size <= 0)
1384
+ throw this.error(line, 'readmem 的长度必须为正数');
1385
+ const proc = this.options.process;
1386
+ let bytes;
1387
+ if (run.syntaxCheckOnly || !proc) {
1388
+ bytes = Buffer.alloc(size);
1389
+ run.warnings.push(`第 ${line.number} 行 readmem(0x${address.toString(16)}, ${size}) 在语法检查模式下按 0 填充`);
1390
+ }
1391
+ else {
1392
+ bytes = proc.readMemory(address, size);
1393
+ if (bytes.length !== size) {
1394
+ throw this.error(line, `readmem(0x${address.toString(16)}, ${size}) 未能完整读取(实得 ${bytes.length} 字节)`, 'scan');
1395
+ }
1396
+ }
1397
+ run.asmLines.push({
1398
+ text: `db ${(0, autoassembler_directives_1.bytesToDbArguments)(bytes)}`,
1399
+ pseudo: { kind: 'db', args: (0, autoassembler_directives_1.bytesToDbArguments)(bytes) },
1400
+ readmemBytes: bytes,
1401
+ labels: [],
1402
+ raw: line.text,
1403
+ lineNumber: line.number,
1404
+ file: line.file,
1405
+ address: 0n,
1406
+ bytes: Buffer.alloc(0),
1407
+ });
1408
+ return true;
1409
+ }
1410
+ // loadbinary(address, filename)
1411
+ const loadArgs = parseCall(text, 'loadbinary');
1412
+ if (loadArgs !== null) {
1413
+ const comma = loadArgs.indexOf(',');
1414
+ if (comma < 0)
1415
+ throw this.error(line, 'loadbinary 语法应为 loadbinary(address,filename)');
1416
+ const address = this.resolveExpression(loadArgs.slice(0, comma).trim(), line, run);
1417
+ const filename = unquote(loadArgs.slice(comma + 1).trim());
1418
+ if (filename === '')
1419
+ throw this.error(line, 'loadbinary 需要一个文件名');
1420
+ const path = (0, node_path_1.isAbsolute)(filename)
1421
+ ? filename
1422
+ : (0, node_path_1.resolve)(this.options.scriptPath ? (0, node_path_1.dirname)(this.options.scriptPath) : process.cwd(), filename);
1423
+ if (!(0, node_fs_1.existsSync)(path)) {
1424
+ throw this.error(line, `loadbinary 的文件不存在:"${filename}"(解析为 ${path})`);
1425
+ }
1426
+ const bytes = (0, node_fs_1.readFileSync)(path);
1427
+ run.directWrites.push({
1428
+ address,
1429
+ bytes,
1430
+ raw: line.text,
1431
+ lineNumber: line.number,
1432
+ file: line.file,
1433
+ });
1434
+ return true;
1435
+ }
1436
+ // assert(address, aob)
1437
+ const assertArgs = parseCall(text, 'assert');
1438
+ if (assertArgs !== null) {
1439
+ const parts = this.splitTopLevel(assertArgs, 2);
1440
+ if (parts.length < 2) {
1441
+ throw this.error(line, 'assert 语法应为 assert(address,11 22 33 ** 55 66)');
1442
+ }
1443
+ const address = this.resolveExpression(parts[0].trim(), line, run);
1444
+ const patternText = parts[1].trim();
1445
+ const pattern = (0, value_codec_1.parseAob)(patternText);
1446
+ if (pattern.length === 0) {
1447
+ throw this.error(line, `assert 的特征串 "${patternText}" 无效`);
1448
+ }
1449
+ const proc = this.options.process;
1450
+ if (!run.syntaxCheckOnly && proc) {
1451
+ const actual = proc.readMemory(address, pattern.length);
1452
+ if (actual.length !== pattern.length) {
1453
+ throw this.error(line, `assert:地址 0x${address.toString(16)} 的内存读不出来`, 'scan');
1454
+ }
1455
+ const nibble = (0, value_codec_1.aobHasNibbleWildcard)(pattern);
1456
+ if (!(0, value_codec_1.aobMatchesAt)(actual, 0, pattern, nibble)) {
1457
+ throw this.error(line, `assert 失败:地址 0x${address.toString(16)} 处期望 ${patternText.toUpperCase()},` +
1458
+ `实际 ${(0, autoassembler_directives_1.bytesToDbArguments)(actual).toUpperCase()}(CE 的 rsTheBytesAtAreNotWhatWasExpected)`, 'scan');
1459
+ }
1460
+ }
1461
+ else {
1462
+ run.warnings.push(`第 ${line.number} 行 assert 在语法检查模式下跳过校验`);
1463
+ }
1464
+ return true;
1465
+ }
1466
+ return false;
1467
+ }
1468
+ /* ---------------------------------------------------------------- *
1469
+ * 地址定义行
1470
+ * ---------------------------------------------------------------- */
1471
+ /**
1472
+ * 解析地址定义行。支持三种形态:
1473
+ *
1474
+ * 1. `newmem:` —— 已知符号(alloc/aobscan/registered)→ 重置当前地址
1475
+ * 2. `returnhere:` —— 脚本标签 → 取当前地址
1476
+ * 3. `00401000:` / `"game.exe"+1234:` —— 地址表达式 → 重置当前地址
1477
+ *
1478
+ * 也支持 `name: 指令` 的同行写法(CE 只支持独立定义行,这里是超集)。
1479
+ */
1480
+ parseAddressSetter(text, line) {
1481
+ let rest = text;
1482
+ const labels = [];
1483
+ for (let guard = 0; guard < 4; guard++) {
1484
+ // 情况 A/B:标识符标签(`name:` 或 `name: insn`)
1485
+ const identMatch = rest.match(/^([A-Za-z_.$?@][\w.$?@]*)\s*:\s*([\s\S]*)$/);
1486
+ if (identMatch) {
1487
+ const name = identMatch[1];
1488
+ const known = this.lookupSymbol(name);
1489
+ labels.push({ name, known });
1490
+ rest = identMatch[2].trim();
1491
+ if (rest === '')
1492
+ break;
1493
+ continue;
1494
+ }
1495
+ // 情况 C:整行以 `:` 结尾且前半不是标识符 → 地址表达式
1496
+ const exprMatch = rest.match(/^([\s\S]*?):\s*$/);
1497
+ if (exprMatch && labels.length === 0) {
1498
+ const expression = exprMatch[1].trim();
1499
+ const address = this.resolveExpression(expression, line, undefined);
1500
+ labels.push({ name: expression, known: address });
1501
+ rest = '';
1502
+ break;
1503
+ }
1504
+ break;
1505
+ }
1506
+ if (labels.length === 0)
1507
+ return null;
1508
+ return { labels, rest };
1509
+ }
1510
+ /**
1511
+ * 把布局得到的标签地址登记为符号。
1512
+ *
1513
+ * 对齐 CE:脚本里的 `label(x)` + `x:` 也会进入符号表
1514
+ * (`disableinfo.allsymbols`,autoassembler.pas 4350~4360),
1515
+ * 因此同一个会话里后续脚本 / [DISABLE] 段都能引用它。
1516
+ */
1517
+ commitLabelSymbols(run) {
1518
+ for (const [name, address] of run.labelAddresses) {
1519
+ run.symbols.set(name, address);
1520
+ if (!run.syntaxCheckOnly)
1521
+ this.persistentSymbols.set(name, address);
1522
+ }
1523
+ }
1524
+ /**
1525
+ * 解析延后的 `registersymbol` 请求。
1526
+ *
1527
+ * 必须在布局之后调用:`registersymbol(mylabel)` 里的标签地址只有在
1528
+ * 所有指令长度确定之后才是最终值。
1529
+ */
1530
+ resolvePendingRegisters(run) {
1531
+ for (const pending of run.pendingRegisters) {
1532
+ const key = pending.name.toLowerCase();
1533
+ const address = run.symbols.get(key) ??
1534
+ run.labelAddresses.get(key) ??
1535
+ this.persistentSymbols.get(key);
1536
+ if (address === undefined) {
1537
+ run.warnings.push(`第 ${pending.line.number} 行 registersymbol(${pending.name}):脚本里找不到符号 ` +
1538
+ `"${pending.name}"(CE 会注册一个未解析的符号,本移植记警告并跳过)`);
1539
+ continue;
1540
+ }
1541
+ run.symbols.set(key, address);
1542
+ if (!run.syntaxCheckOnly) {
1543
+ this.registeredSymbols.add(pending.name);
1544
+ this.persistentSymbols.set(key, address);
1545
+ }
1546
+ }
1547
+ }
1548
+ /** 查符号(alloc/aobscan/registered/持久符号),忽略大小写 */
1549
+ lookupSymbol(name) {
1550
+ const key = name.toLowerCase();
1551
+ const fromAlloc = this.allocations.get(name);
1552
+ if (fromAlloc)
1553
+ return fromAlloc.address;
1554
+ const persisted = this.persistentSymbols.get(key);
1555
+ if (persisted !== undefined)
1556
+ return persisted;
1557
+ return null;
1558
+ }
1559
+ /**
1560
+ * 求值地址表达式。
1561
+ *
1562
+ * 优先按 CE 的整数语义解析裸数字(十六进制),否则交给 `resolveAddress`;
1563
+ * 送进去之前会把裸数字改写成 `0x` 形式(`hexifyBareNumbers`),
1564
+ * 因为 `addr/expression.ts` 把无 a-f 字母的裸数字当十进制。
1565
+ */
1566
+ resolveExpression(expression, line, run) {
1567
+ const expr = expression.trim();
1568
+ if (expr === '')
1569
+ throw this.error(line, '地址表达式为空');
1570
+ // 1) 纯数字:CE 十六进制语义
1571
+ const numeric = (0, autoassembler_directives_1.parseCeInteger)(expr, true);
1572
+ if (numeric !== null)
1573
+ return numeric;
1574
+ // 2) 已注册符号
1575
+ const key = expr.toLowerCase();
1576
+ const known = run?.symbols.get(key) ??
1577
+ this.allocations.get(expr)?.address ??
1578
+ this.persistentSymbols.get(key);
1579
+ if (known !== undefined)
1580
+ return known;
1581
+ // 3) 地址表达式(模块+偏移、指针解引用等)
1582
+ const proc = this.options.process;
1583
+ try {
1584
+ if (!proc) {
1585
+ // 没有进程时至少做语法校验
1586
+ void hexifyBareNumbers(expr);
1587
+ throw new Error('语法检查模式无法求值地址表达式(需要目标进程解析模块/指针)');
1588
+ }
1589
+ if (this.moduleCache.length === 0) {
1590
+ this.moduleCache = proc.modules().map((m) => ({
1591
+ name: m.name,
1592
+ baseAddress: m.baseAddress,
1593
+ size: m.size,
1594
+ }));
1595
+ }
1596
+ return (0, expression_1.resolveAddress)(hexifyBareNumbers(expr), {
1597
+ process: proc,
1598
+ symbols: run?.symbols ?? this.persistentSymbols,
1599
+ moduleCache: this.moduleCache,
1600
+ });
1601
+ }
1602
+ catch (e) {
1603
+ throw this.error(line, `无法求值地址表达式 "${expr}":${e.message}`, 'parse');
1604
+ }
1605
+ }
1606
+ /* ---------------------------------------------------------------- *
1607
+ * 符号注册
1608
+ * ---------------------------------------------------------------- */
1609
+ registerSymbol(name, address, run, line) {
1610
+ const key = name.toLowerCase();
1611
+ run.symbols.set(key, address);
1612
+ // 语法检查模式下不污染会话的持久符号表(那里会写入 0 占位地址)
1613
+ if (!run.syntaxCheckOnly)
1614
+ this.persistentSymbols.set(key, address);
1615
+ void line;
1616
+ }
1617
+ /* ---------------------------------------------------------------- *
1618
+ * 布局(迭代到指令长度稳定)
1619
+ * ---------------------------------------------------------------- */
1620
+ layout(run) {
1621
+ const asmLines = run.asmLines;
1622
+ const allLabelNames = new Set();
1623
+ for (const l of asmLines)
1624
+ for (const lab of l.labels)
1625
+ allLabelNames.add(lab.name.toLowerCase());
1626
+ let prevLabelAddrs = new Map();
1627
+ let converged = false;
1628
+ for (let pass = 0; pass < MAX_LAYOUT_PASSES; pass++) {
1629
+ const labelAddrs = new Map();
1630
+ const definedSoFar = new Set();
1631
+ let address = this.options.startAddress ?? 0n;
1632
+ let addressSet = this.options.startAddress !== undefined;
1633
+ run.warnings.length = run.warningBaseLength;
1634
+ for (const asmLine of asmLines) {
1635
+ // 1) 先处理本行的标签定义(地址定义 / 标签取值)
1636
+ for (const lab of asmLine.labels) {
1637
+ const key = lab.name.toLowerCase();
1638
+ if (lab.known !== null) {
1639
+ address = lab.known;
1640
+ addressSet = true;
1641
+ labelAddrs.set(key, address);
1642
+ }
1643
+ else {
1644
+ if (!addressSet) {
1645
+ throw new AutoAssemblerError(`第 ${asmLine.lineNumber} 行定义了标签 "${lab.name}",但此前没有任何地址定义行;` +
1646
+ `脚本开头需要先写 alloc 的块名(如 "newmem:")或地址/模块表达式(如 "game.exe"+1234:)`, {
1647
+ line: asmLine.raw,
1648
+ lineNumber: asmLine.lineNumber,
1649
+ file: asmLine.file,
1650
+ phase: 'assemble',
1651
+ });
1652
+ }
1653
+ labelAddrs.set(key, address);
1654
+ }
1655
+ definedSoFar.add(key);
1656
+ }
1657
+ asmLine.address = address;
1658
+ // 2) 空行(纯标签行)不产生字节
1659
+ if (asmLine.text === '' && asmLine.pseudo === null) {
1660
+ asmLine.bytes = Buffer.alloc(0);
1661
+ continue;
1662
+ }
1663
+ if (!addressSet) {
1664
+ throw new AutoAssemblerError(`第 ${asmLine.lineNumber} 行有代码但尚未指定它应写入的地址;` +
1665
+ `请先用 "allocname:" 或 "module+offset:" 这样的地址定义行定位`, {
1666
+ line: asmLine.raw,
1667
+ lineNumber: asmLine.lineNumber,
1668
+ file: asmLine.file,
1669
+ phase: 'assemble',
1670
+ });
1671
+ }
1672
+ // 3) 编码
1673
+ if (asmLine.pseudo !== null) {
1674
+ asmLine.bytes = this.encodePseudo(asmLine, run, address);
1675
+ }
1676
+ else {
1677
+ asmLine.bytes = this.encodeInstruction(asmLine, run, address, allLabelNames, definedSoFar, prevLabelAddrs);
1678
+ }
1679
+ address += BigInt(asmLine.bytes.length);
1680
+ }
1681
+ // 4) 判断是否收敛
1682
+ run.labelAddresses = labelAddrs;
1683
+ if (this.sameLabelMap(prevLabelAddrs, labelAddrs)) {
1684
+ converged = true;
1685
+ break;
1686
+ }
1687
+ prevLabelAddrs = labelAddrs;
1688
+ }
1689
+ if (!converged) {
1690
+ throw new AutoAssemblerError(`地址布局在 ${MAX_LAYOUT_PASSES} 轮内没有稳定(前向引用的标签导致指令长度反复变化)。` +
1691
+ `请把被引用的标签定义移到引用之前,或改用固定长度的写法。`, { phase: 'assemble' });
1692
+ }
1693
+ }
1694
+ sameLabelMap(a, b) {
1695
+ if (a.size !== b.size)
1696
+ return false;
1697
+ for (const [k, v] of a) {
1698
+ const other = b.get(k);
1699
+ if (other === undefined || other !== v)
1700
+ return false;
1701
+ }
1702
+ return true;
1703
+ }
1704
+ /** 编码 `db/dw/dd/dq`、`nop N`、`align N[,b]` */
1705
+ encodePseudo(asmLine, run, address) {
1706
+ const pseudo = asmLine.pseudo;
1707
+ if (!pseudo)
1708
+ return Buffer.alloc(0);
1709
+ void run;
1710
+ try {
1711
+ if (pseudo.kind === 'db' || pseudo.kind === 'dw' || pseudo.kind === 'dd' || pseudo.kind === 'dq') {
1712
+ const proc = this.options.process;
1713
+ const result = (0, autoassembler_directives_1.encodeDataDirective)(pseudo.kind, pseudo.args, {
1714
+ address,
1715
+ hexDefault: this.options.dataHexDefault !== false,
1716
+ readByte: proc && !run.syntaxCheckOnly
1717
+ ? (addr) => {
1718
+ const b = proc.readMemory(addr, 1);
1719
+ return b.length === 1 ? b[0] : null;
1720
+ }
1721
+ : undefined,
1722
+ });
1723
+ for (const w of result.warnings) {
1724
+ run.warnings.push(`第 ${asmLine.lineNumber} 行 ${w}`);
1725
+ }
1726
+ return result.bytes;
1727
+ }
1728
+ if (pseudo.kind === 'nop') {
1729
+ const count = (0, autoassembler_directives_1.parseCeInteger)(pseudo.args, true);
1730
+ if (count === null)
1731
+ throw new Error(`nop 的数量 "${pseudo.args}" 无法解析`);
1732
+ const n = Number(count);
1733
+ if (!Number.isSafeInteger(n) || n < 0)
1734
+ throw new Error(`nop 的数量 ${count} 不合法`);
1735
+ return (0, autoassembler_directives_1.encodeNop)(n);
1736
+ }
1737
+ if (pseudo.kind === 'align') {
1738
+ const tokens = (0, autoassembler_directives_1.splitDirectiveArguments)(pseudo.args);
1739
+ const alignment = (0, autoassembler_directives_1.parseCeInteger)(tokens[0], true);
1740
+ if (alignment === null || alignment <= 0n) {
1741
+ throw new Error(`align 的对齐值 "${tokens[0]}" 无法解析或不是正数`);
1742
+ }
1743
+ const fill = tokens[1] !== undefined ? (0, autoassembler_directives_1.parseCeInteger)(tokens[1], true) : 0n;
1744
+ if (fill === null)
1745
+ throw new Error(`align 的填充字节 "${tokens[1]}" 无法解析`);
1746
+ return (0, autoassembler_directives_1.encodeAlign)(address, alignment, Number(fill & 0xffn));
1747
+ }
1748
+ }
1749
+ catch (e) {
1750
+ if (e instanceof AutoAssemblerError)
1751
+ throw e;
1752
+ throw new AutoAssemblerError(e.message, {
1753
+ line: asmLine.raw,
1754
+ lineNumber: asmLine.lineNumber,
1755
+ file: asmLine.file,
1756
+ phase: 'assemble',
1757
+ });
1758
+ }
1759
+ throw new AutoAssemblerError(`未处理的伪指令 ${pseudo.kind}`, {
1760
+ line: asmLine.raw,
1761
+ lineNumber: asmLine.lineNumber,
1762
+ file: asmLine.file,
1763
+ phase: 'assemble',
1764
+ });
1765
+ }
1766
+ /**
1767
+ * 编码一条指令。
1768
+ *
1769
+ * 前向引用的标签用「近/远两个占位符各编一次、取较长者」的策略
1770
+ * (对齐 CE autoassembler.pas 3741~3763 行),保证地址不会因为
1771
+ * 编码从长变短而漂移。
1772
+ */
1773
+ encodeInstruction(asmLine, run, address, allLabelNames, definedSoFar, prevLabelAddrs) {
1774
+ const base = new Map();
1775
+ for (const [k, v] of this.persistentSymbols)
1776
+ base.set(k, v);
1777
+ for (const [k, v] of run.symbols)
1778
+ base.set(k, v);
1779
+ for (const [k, v] of definedSoFar) {
1780
+ const own = prevLabelAddrs.get(k);
1781
+ if (own !== undefined)
1782
+ base.set(k, own);
1783
+ }
1784
+ const unresolved = [];
1785
+ for (const name of allLabelNames) {
1786
+ if (base.has(name))
1787
+ continue;
1788
+ const prev = prevLabelAddrs.get(name);
1789
+ if (prev !== undefined)
1790
+ base.set(name, prev);
1791
+ else
1792
+ unresolved.push(name);
1793
+ }
1794
+ const bitness = run.bitness;
1795
+ const arch = run.arch;
1796
+ if (unresolved.length === 0) {
1797
+ return this.assembleLine(asmLine, asmLine.text, address, base, bitness, arch);
1798
+ }
1799
+ const closeSymbols = new Map(base);
1800
+ const farSymbols = new Map(base);
1801
+ for (const name of unresolved) {
1802
+ closeSymbols.set(name, address);
1803
+ farSymbols.set(name, address + FAR_PLACEHOLDER_DELTA);
1804
+ }
1805
+ const close = this.tryAssembleLine(asmLine, asmLine.text, address, closeSymbols, bitness, arch);
1806
+ const far = this.tryAssembleLine(asmLine, asmLine.text, address, farSymbols, bitness, arch);
1807
+ if (close === null && far === null) {
1808
+ // 两种占位符都失败:用近距离方案再跑一次以产生可读的错误
1809
+ return this.assembleLine(asmLine, asmLine.text, address, closeSymbols, bitness, arch);
1810
+ }
1811
+ if (close === null)
1812
+ return far;
1813
+ if (far === null)
1814
+ return close;
1815
+ return far.length >= close.length ? far : close;
1816
+ }
1817
+ tryAssembleLine(asmLine, text, address, symbols, bitness, arch) {
1818
+ try {
1819
+ return (0, assembler_1.assemble)(text, { address, arch, bitness, symbols }).bytes;
1820
+ }
1821
+ catch {
1822
+ void asmLine;
1823
+ return null;
1824
+ }
1825
+ }
1826
+ assembleLine(asmLine, text, address, symbols, bitness, arch) {
1827
+ try {
1828
+ return (0, assembler_1.assemble)(text, { address, arch, bitness, symbols }).bytes;
1829
+ }
1830
+ catch (e) {
1831
+ if (e instanceof assembler_1.AssemblerError) {
1832
+ throw new AutoAssemblerError(e.message.replace(/^汇编失败(第 \d+ 行:"[^"]*"):/, '').replace(/^汇编失败:/, ''), {
1833
+ line: asmLine.raw,
1834
+ lineNumber: asmLine.lineNumber,
1835
+ file: asmLine.file,
1836
+ phase: 'assemble',
1837
+ });
1838
+ }
1839
+ throw new AutoAssemblerError(e.message, {
1840
+ line: asmLine.raw,
1841
+ lineNumber: asmLine.lineNumber,
1842
+ file: asmLine.file,
1843
+ phase: 'assemble',
1844
+ });
1845
+ }
1846
+ }
1847
+ /* ---------------------------------------------------------------- *
1848
+ * 应用(写目标进程)
1849
+ * ---------------------------------------------------------------- */
1850
+ apply(run, enable) {
1851
+ const proc = this.options.process;
1852
+ const entries = [];
1853
+ let bytesWritten = 0;
1854
+ const failures = [];
1855
+ const history = run.history;
1856
+ // ---- DISABLE + restoreMode 'original':先按历史逆序精确还原 ----------
1857
+ if (!enable && (this.options.restoreMode ?? 'original') === 'original') {
1858
+ for (const record of [...history].reverse()) {
1859
+ const entry = {
1860
+ address: record.address,
1861
+ bytes: record.bytes,
1862
+ originalBytes: null,
1863
+ restoredFromOriginal: true,
1864
+ line: '<ENABLE 期间记录的原字节>',
1865
+ lineNumber: 0,
1866
+ file: '<history>',
1867
+ };
1868
+ if (proc && !run.syntaxCheckOnly) {
1869
+ try {
1870
+ this.writeBlock(proc, record.address, record.bytes, entry);
1871
+ }
1872
+ catch (e) {
1873
+ if (e instanceof AutoAssemblerError) {
1874
+ // 单条失败不应阻断其余还原:记录后继续,最后统一抛出
1875
+ failures.push(e.message);
1876
+ continue;
1877
+ }
1878
+ throw e;
1879
+ }
1880
+ bytesWritten += record.bytes.length;
1881
+ }
1882
+ entries.push(entry);
1883
+ }
1884
+ }
1885
+ // ---- 脚本自身产生的写入 -------------------------------------------
1886
+ const lineEntries = [];
1887
+ for (const asmLine of run.asmLines) {
1888
+ if (asmLine.bytes.length === 0)
1889
+ continue;
1890
+ lineEntries.push({
1891
+ address: asmLine.address,
1892
+ bytes: asmLine.bytes,
1893
+ line: asmLine.raw,
1894
+ lineNumber: asmLine.lineNumber,
1895
+ file: asmLine.file,
1896
+ });
1897
+ }
1898
+ for (const dw of run.directWrites) {
1899
+ lineEntries.push({
1900
+ address: dw.address,
1901
+ bytes: dw.bytes,
1902
+ line: dw.raw,
1903
+ lineNumber: dw.lineNumber,
1904
+ file: dw.file,
1905
+ });
1906
+ }
1907
+ for (const item of lineEntries) {
1908
+ // DISABLE + original 模式:与历史重叠的区间已经被精确还原,跳过
1909
+ if (!enable && (this.options.restoreMode ?? 'original') === 'original') {
1910
+ if (isCoveredByHistory(history, item.address, item.bytes.length))
1911
+ continue;
1912
+ }
1913
+ const entry = {
1914
+ address: item.address,
1915
+ bytes: item.bytes,
1916
+ originalBytes: null,
1917
+ restoredFromOriginal: false,
1918
+ line: item.line,
1919
+ lineNumber: item.lineNumber,
1920
+ file: item.file,
1921
+ };
1922
+ if (proc && !run.syntaxCheckOnly) {
1923
+ entry.originalBytes = this.readOriginal(proc, item.address, item.bytes.length);
1924
+ this.writeBlock(proc, item.address, item.bytes, entry);
1925
+ bytesWritten += item.bytes.length;
1926
+ }
1927
+ entries.push(entry);
1928
+ }
1929
+ // ENABLE 期间记录的原字节:DISABLE 时就是靠它们精确还原的
1930
+ if (enable && proc && !run.syntaxCheckOnly) {
1931
+ for (const entry of entries) {
1932
+ if (entry.originalBytes && entry.originalBytes.length > 0) {
1933
+ this.recordOriginal(history, entry.address, entry.originalBytes);
1934
+ }
1935
+ }
1936
+ }
1937
+ if (failures.length > 0) {
1938
+ throw new AutoAssemblerError(`还原原字节时有 ${failures.length} 处写入失败(其余已还原):\n ` + failures.join('\n '), { phase: 'write' });
1939
+ }
1940
+ return { entries, bytesWritten };
1941
+ }
1942
+ readOriginal(proc, address, length) {
1943
+ try {
1944
+ const buf = proc.readMemory(address, length);
1945
+ if (buf.length !== length)
1946
+ return null;
1947
+ return Buffer.from(buf);
1948
+ }
1949
+ catch {
1950
+ return null;
1951
+ }
1952
+ }
1953
+ writeBlock(proc, address, bytes, entry) {
1954
+ try {
1955
+ const written = proc.writeMemory(address, bytes, true);
1956
+ if (written !== bytes.length) {
1957
+ throw new Error(`只写入了 ${written}/${bytes.length} 字节`);
1958
+ }
1959
+ }
1960
+ catch (e) {
1961
+ throw new AutoAssemblerError(`写入目标进程失败(地址 0x${address.toString(16)},${bytes.length} 字节):${e.message}`, {
1962
+ line: entry.line,
1963
+ lineNumber: entry.lineNumber || undefined,
1964
+ file: entry.file,
1965
+ phase: 'write',
1966
+ });
1967
+ }
1968
+ }
1969
+ /* ---------------------------------------------------------------- *
1970
+ * 原字节历史
1971
+ * ---------------------------------------------------------------- */
1972
+ /**
1973
+ * 记录被覆盖的原字节。
1974
+ *
1975
+ * 记录集保持"按地址升序、互不重叠":与已有记录重叠的部分会被裁掉
1976
+ * (保留最早的原始内容),相邻的会合并。
1977
+ */
1978
+ recordOriginal(history, address, bytes) {
1979
+ if (bytes.length === 0)
1980
+ return;
1981
+ let pos = address;
1982
+ let rest = bytes;
1983
+ const pieces = [];
1984
+ for (const record of history) {
1985
+ if (rest.length === 0)
1986
+ break;
1987
+ const rEnd = record.address + BigInt(record.bytes.length);
1988
+ if (rEnd <= pos)
1989
+ continue;
1990
+ if (record.address >= pos + BigInt(rest.length))
1991
+ break;
1992
+ if (record.address > pos) {
1993
+ const cut = Number(record.address - pos);
1994
+ pieces.push({ address: pos, bytes: Buffer.from(rest.subarray(0, cut)) });
1995
+ rest = rest.subarray(cut);
1996
+ pos = record.address;
1997
+ }
1998
+ // 跳过已被记录覆盖的部分
1999
+ const skip = Number(rEnd - pos);
2000
+ if (skip >= rest.length) {
2001
+ rest = rest.subarray(rest.length);
2002
+ break;
2003
+ }
2004
+ rest = rest.subarray(skip);
2005
+ pos = rEnd;
2006
+ }
2007
+ if (rest.length > 0)
2008
+ pieces.push({ address: pos, bytes: Buffer.from(rest) });
2009
+ for (const piece of pieces)
2010
+ insertHistory(history, piece.address, piece.bytes);
2011
+ }
2012
+ /**
2013
+ * 从历史里摘掉一段区间。
2014
+ *
2015
+ * `dealloc`/`realloc` 释放内存后必须调用:那块内存已经还给系统,
2016
+ * 再去"还原"它只会得到 ERROR_INVALID_ADDRESS(实测踩过)。
2017
+ */
2018
+ dropHistoryRange(history, address, size) {
2019
+ if (history.length === 0)
2020
+ return;
2021
+ const start = address;
2022
+ const end = address + BigInt(size);
2023
+ const out = [];
2024
+ for (const record of history) {
2025
+ const recEnd = record.address + BigInt(record.bytes.length);
2026
+ if (recEnd <= start || record.address >= end) {
2027
+ out.push(record);
2028
+ continue;
2029
+ }
2030
+ if (record.address < start) {
2031
+ out.push({
2032
+ address: record.address,
2033
+ bytes: Buffer.from(record.bytes.subarray(0, Number(start - record.address))),
2034
+ });
2035
+ }
2036
+ if (recEnd > end) {
2037
+ out.push({
2038
+ address: end,
2039
+ bytes: Buffer.from(record.bytes.subarray(Number(end - record.address))),
2040
+ });
2041
+ }
2042
+ }
2043
+ history.length = 0;
2044
+ history.push(...out);
2045
+ }
2046
+ /* ---------------------------------------------------------------- *
2047
+ * 杂项
2048
+ * ---------------------------------------------------------------- */
2049
+ resolveBitness() {
2050
+ if (this.options.arch) {
2051
+ // ARM/Thumb 是 32 位目标,ARM64 是 64 位 —— 供 [32-BIT]/[64-BIT] 段剥离使用
2052
+ const a = this.options.arch;
2053
+ return a === 'arm64' || a === 'x64' ? 64 : 32;
2054
+ }
2055
+ if (this.options.bitness)
2056
+ return this.options.bitness;
2057
+ const proc = this.options.process;
2058
+ if (!proc)
2059
+ return 64;
2060
+ try {
2061
+ return proc.isWow64() ? 32 : 64;
2062
+ }
2063
+ catch {
2064
+ return 64;
2065
+ }
2066
+ }
2067
+ /**
2068
+ * 解析汇编用的架构。
2069
+ *
2070
+ * 优先级:显式 `arch` > 由 `bitness` 推断 > 按目标进程位数推断 > `x64`。
2071
+ * 支持 ARM 之后,只给 `bitness` 已不足以表达架构
2072
+ * (`arm` 与 `thumb` 都是 32 位),因此 ARM 目标请显式给 `arch`。
2073
+ */
2074
+ resolveArch() {
2075
+ if (this.options.arch)
2076
+ return this.options.arch;
2077
+ if (this.options.bitness)
2078
+ return (0, backend_1.archFromBitness)(this.options.bitness);
2079
+ const proc = this.options.process;
2080
+ if (!proc)
2081
+ return 'x64';
2082
+ try {
2083
+ return proc.isWow64() ? 'x86' : 'x64';
2084
+ }
2085
+ catch {
2086
+ return 'x64';
2087
+ }
2088
+ }
2089
+ /** 构造带行号/文件/阶段的错误 */
2090
+ error(line, message, phase) {
2091
+ return new AutoAssemblerError(message, {
2092
+ line: line.text,
2093
+ lineNumber: line.number,
2094
+ file: line.file,
2095
+ phase,
2096
+ });
2097
+ }
2098
+ }
2099
+ exports.AutoAssembler = AutoAssembler;
2100
+ /* ------------------------------------------------------------------ *
2101
+ * 运行期状态
2102
+ * ------------------------------------------------------------------ */
2103
+ /**
2104
+ * 一次 `autoAssemble` 调用的状态。
2105
+ *
2106
+ * `warnings.length = run.warningBaseLength` 在每轮布局开始时重置,
2107
+ * 避免同一批警告随迭代次数重复累积;`warningBaseLength` 在预处理结束时确定。
2108
+ */
2109
+ class RunState {
2110
+ enable;
2111
+ syntaxCheckOnly;
2112
+ bitness;
2113
+ arch;
2114
+ /** 当前可见符号(预处理阶段逐步累积) */
2115
+ symbols = new Map();
2116
+ /** define 名字 → 替换文本 */
2117
+ defines = new Map();
2118
+ /** define 的定义顺序 */
2119
+ defineOrder = [];
2120
+ /** label() 声明的标签 */
2121
+ declaredLabels = new Map();
2122
+ /** 实际上出现了定义行的标签名 */
2123
+ definedLabelNames = new Set();
2124
+ /** 待布局的汇编行 */
2125
+ asmLines = [];
2126
+ /** loadbinary 之类的直接写入 */
2127
+ directWrites = [];
2128
+ /** createthread 校验通过的地址 */
2129
+ createThreads = [];
2130
+ /** aobscan 命中情况 */
2131
+ aobScans = [];
2132
+ /** 待注册(等布局完成后才能解析地址)的 registersymbol 请求 */
2133
+ pendingRegisters = [];
2134
+ /** 布局结束后各脚本标签的地址 */
2135
+ labelAddresses = new Map();
2136
+ /** 本次运行对应的原字节记录(ENABLE 时新建、DISABLE 时取回) */
2137
+ history = [];
2138
+ warnings = [];
2139
+ /** 布局迭代时警告回退的位置 */
2140
+ warningBaseLength = 0;
2141
+ constructor(enable, syntaxCheckOnly, bitness, arch) {
2142
+ this.enable = enable;
2143
+ this.syntaxCheckOnly = syntaxCheckOnly;
2144
+ this.bitness = bitness;
2145
+ this.arch = arch;
2146
+ }
2147
+ }
2148
+ /* ------------------------------------------------------------------ *
2149
+ * 便捷入口
2150
+ * ------------------------------------------------------------------ */
2151
+ /**
2152
+ * 一次性执行脚本(ENABLE 或 DISABLE)。
2153
+ *
2154
+ * 注意:**成对往返请用 `new AutoAssembler(...)` 复用同一个会话**,
2155
+ * 因为 [DISABLE] 的精确还原依赖 [ENABLE] 时记录的原字节。
2156
+ * 这个便捷函数适合"一次注入 / 语法检查"这类无状态场景。
2157
+ */
2158
+ function autoAssemble(script, options = {}) {
2159
+ const { enable, syntaxCheckOnly, ...sessionOptions } = options;
2160
+ const session = new AutoAssembler(sessionOptions);
2161
+ return session.autoAssemble(script, { enable, syntaxCheckOnly });
2162
+ }
2163
+ //# sourceMappingURL=autoassembler.js.map