@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,781 @@
1
+ "use strict";
2
+ /**
3
+ * 扫描比较器 —— 逐项对齐 Cheat Engine `memscan.pas` 的 TScanner 比较函数族。
4
+ *
5
+ * CE 的结构是:为每种变量类型维护一个 typesmatch[] 布尔数组,每次比较把所有
6
+ * 候选类型的比较结果 AND 进去,最后只要还有任何一个类型匹配就保留该地址。
7
+ * 这里用等价的实现:对每个候选类型分别收集命中的地址。
8
+ *
9
+ * 已对齐的语义(含容易搞错的细节):
10
+ * - SingleExtremeRounded 用闭区间 [min,max];DoubleExtremeRounded 用开区间 (min,max)
11
+ * —— 见 TScanner.SingleExact / DoubleExact,CE 自身在这里就不一致。
12
+ * - Truncated 用 [value, value+step) 左闭右开。
13
+ * - Rounded 用 RoundTo(actual, -accuracy) === value(含 FPC 的偶舍入)。
14
+ * - IncreasedValueBy / DecreasedValueBy 对浮点会先比较原始位模式不等,再按精度判等。
15
+ * - 百分比系列的边界开闭、以及 dvalue>dvalue2 时自动交换。
16
+ * - 字符串不区分大小写时只把 ASCII a-z 折叠为大写(ANSI)/
17
+ * 用 UpCase(Unicode),对齐 CaseInsensitiveAnsiStringExact / UnicodeString。
18
+ * - AOB 支持半字节通配,对齐 parseAob 的掩码语义。
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.roundTo = void 0;
22
+ exports.optionNeedsOldValue = optionNeedsOldValue;
23
+ exports.compileConfig = compileConfig;
24
+ exports.scanBlock = scanBlock;
25
+ exports.testValueMatches = testValueMatches;
26
+ const value_codec_1 = require("../value-codec");
27
+ Object.defineProperty(exports, "roundTo", { enumerable: true, get: function () { return value_codec_1.roundTo; } });
28
+ const types_1 = require("../types");
29
+ /* ------------------------------------------------------------------ *
30
+ * 编译
31
+ * ------------------------------------------------------------------ */
32
+ /** 数值类型的默认宽度 */
33
+ function widthOf(type, explicit) {
34
+ const w = types_1.VARIABLE_TYPE_SIZES[type];
35
+ if (w > 0)
36
+ return w;
37
+ if (explicit && explicit > 0)
38
+ return explicit;
39
+ throw new Error(`类型 ${types_1.VariableType[type]} 需要显式指定长度`);
40
+ }
41
+ /** 是否需要保存"上一次的值"(next scan 的旧值语义)。 */
42
+ function optionNeedsOldValue(option) {
43
+ switch (option) {
44
+ case types_1.ScanOption.IncreasedValue:
45
+ case types_1.ScanOption.IncreasedValueBy:
46
+ case types_1.ScanOption.IncreasedValueByPercentage:
47
+ case types_1.ScanOption.DecreasedValue:
48
+ case types_1.ScanOption.DecreasedValueBy:
49
+ case types_1.ScanOption.DecreasedValueByPercentage:
50
+ case types_1.ScanOption.Changed:
51
+ case types_1.ScanOption.Unchanged:
52
+ case types_1.ScanOption.ValueBetweenPercentage:
53
+ return true;
54
+ default:
55
+ return false;
56
+ }
57
+ }
58
+ /** 是否属于"带值的比较"(与无值的 unknown/changed 相对)。 */
59
+ function optionHasValue(option) {
60
+ switch (option) {
61
+ case types_1.ScanOption.UnknownValue:
62
+ case types_1.ScanOption.Changed:
63
+ case types_1.ScanOption.Unchanged:
64
+ case types_1.ScanOption.IncreasedValue:
65
+ case types_1.ScanOption.DecreasedValue:
66
+ return false;
67
+ default:
68
+ return true;
69
+ }
70
+ }
71
+ function optionHasSecondValue(option) {
72
+ switch (option) {
73
+ case types_1.ScanOption.ValueBetween:
74
+ case types_1.ScanOption.ValueBetweenPercentage:
75
+ case types_1.ScanOption.IncreasedValueByPercentage:
76
+ case types_1.ScanOption.DecreasedValueByPercentage:
77
+ return true;
78
+ default:
79
+ return false;
80
+ }
81
+ }
82
+ function isPercentageOption(option) {
83
+ return (option === types_1.ScanOption.IncreasedValueByPercentage ||
84
+ option === types_1.ScanOption.DecreasedValueByPercentage ||
85
+ option === types_1.ScanOption.ValueBetweenPercentage);
86
+ }
87
+ /** 展开 vtAll 为 6 个基础数值类型,对齐 CE 的 typesmatch 数组。 */
88
+ const ALL_NUMERIC = [
89
+ types_1.VariableType.Byte,
90
+ types_1.VariableType.Word,
91
+ types_1.VariableType.DWord,
92
+ types_1.VariableType.QWord,
93
+ types_1.VariableType.Single,
94
+ types_1.VariableType.Double,
95
+ ];
96
+ /** 把字符串目标转成对应宽度的字节表示。 */
97
+ function buildStringRule(type, value, caseSensitive, stringLength) {
98
+ const target = stringLength !== undefined && stringLength > 0 ? value.slice(0, stringLength) : value;
99
+ const wide = type === types_1.VariableType.UnicodeString;
100
+ const byteLength = wide ? target.length * 2 : target.length;
101
+ const upper = caseSensitive ? target : target.replace(/[a-z]/g, (c) => c.toUpperCase());
102
+ return { target, upper, byteLength, wide, caseSensitive };
103
+ }
104
+ /** 编译单个类型的比较规则。 */
105
+ function compileRule(type, cfg, round) {
106
+ const rule = { type, width: 0 };
107
+ const hasValue = optionHasValue(cfg.scanOption);
108
+ const percentage = isPercentageOption(cfg.scanOption);
109
+ switch (type) {
110
+ case types_1.VariableType.Byte:
111
+ case types_1.VariableType.Word:
112
+ case types_1.VariableType.DWord:
113
+ case types_1.VariableType.QWord:
114
+ case types_1.VariableType.Pointer: {
115
+ rule.width = widthOf(type, type === types_1.VariableType.Pointer ? 8 : undefined);
116
+ if (hasValue) {
117
+ const a = (0, value_codec_1.parseIntegerLiteral)(cfg.value1 ?? '0', cfg.hexadecimal ?? false);
118
+ const b = optionHasSecondValue(cfg.scanOption)
119
+ ? (0, value_codec_1.parseIntegerLiteral)(cfg.value2 ?? cfg.value1 ?? '0', cfg.hexadecimal ?? false)
120
+ : a;
121
+ rule.ints = { v: a.raw, v2: b.raw, signed: cfg.signed === true || a.negative || b.negative };
122
+ }
123
+ // 无值选项(Unknown/Changed/Unchanged/Increased/Decreased)不需要 ints,
124
+ // 比较逻辑只用到宽度与符号;但符号信息仍需保留,因此给一个零值占位。
125
+ else {
126
+ rule.ints = { v: 0n, v2: 0n, signed: cfg.signed === true };
127
+ }
128
+ return rule;
129
+ }
130
+ case types_1.VariableType.Single:
131
+ case types_1.VariableType.Double: {
132
+ rule.width = widthOf(type);
133
+ if (hasValue) {
134
+ rule.floats = (0, value_codec_1.buildFloatCompareParams)(cfg.value1 ?? '0', cfg.value2, percentage);
135
+ }
136
+ else {
137
+ // 无值选项占位:精度信息仍需要(用于 IncreasedValueBy 之外的比较)
138
+ rule.floats = (0, value_codec_1.buildFloatCompareParams)('0', undefined, false);
139
+ }
140
+ return rule;
141
+ }
142
+ case types_1.VariableType.String:
143
+ case types_1.VariableType.CodePageString:
144
+ case types_1.VariableType.UnicodeString: {
145
+ const s = buildStringRule(type, cfg.value1 ?? '', cfg.caseSensitive === true, cfg.stringLength);
146
+ if (!s)
147
+ throw new Error('字符串扫描需要提供扫描值');
148
+ rule.str = s;
149
+ rule.width = s.byteLength;
150
+ return rule;
151
+ }
152
+ case types_1.VariableType.ByteArray: {
153
+ const pattern = (0, value_codec_1.parseAob)(cfg.value1 ?? '', true);
154
+ rule.aob = { pattern, nibble: (0, value_codec_1.aobHasNibbleWildcard)(pattern) };
155
+ rule.width = pattern.length;
156
+ return rule;
157
+ }
158
+ case types_1.VariableType.Binary: {
159
+ const pattern = (0, value_codec_1.parseBinaryPattern)(cfg.binaryString ?? cfg.value1 ?? '');
160
+ rule.bin = { pattern, width: (0, value_codec_1.binaryByteWidth)(pattern.bitCount) };
161
+ rule.width = rule.bin.width;
162
+ return rule;
163
+ }
164
+ default:
165
+ throw new Error(`不支持直接编译的变量类型: ${types_1.VariableType[type]}`);
166
+ }
167
+ }
168
+ /** 编译分组长扫描。 */
169
+ function compileGroup(cfg) {
170
+ const elems = cfg.groupElements ?? [];
171
+ if (elems.length === 0)
172
+ throw new Error('分组长扫描需要至少一个元素');
173
+ const compiled = [];
174
+ let total = 0;
175
+ for (const e of elems) {
176
+ const rule = compileRule(e.variableType, {
177
+ ...cfg,
178
+ variableType: e.variableType,
179
+ scanOption: e.wildcard ? types_1.ScanOption.ExactValue : e.scanOption ?? types_1.ScanOption.ExactValue,
180
+ value1: e.value,
181
+ value2: e.value2,
182
+ hexadecimal: e.hexadecimal,
183
+ signed: e.signed,
184
+ floatAccuracy: e.floatAccuracy ?? -1,
185
+ roundingType: e.roundingType ?? types_1.RoundingType.ExtremeRounded,
186
+ stringLength: e.stringLength,
187
+ }, e.roundingType ?? types_1.RoundingType.ExtremeRounded);
188
+ compiled.push({
189
+ type: e.variableType,
190
+ wildcard: e.wildcard === true,
191
+ width: rule.width,
192
+ ints: rule.ints,
193
+ floats: rule.floats,
194
+ str: rule.str,
195
+ aob: rule.aob,
196
+ bin: rule.bin,
197
+ });
198
+ total += rule.width;
199
+ }
200
+ return {
201
+ elements: compiled,
202
+ blockSize: total,
203
+ outOfOrder: cfg.groupOutOfOrder === true,
204
+ outOfOrderAligned: cfg.groupAligned === true,
205
+ };
206
+ }
207
+ /** 编译用户扫描配置。 */
208
+ function compileConfig(cfg) {
209
+ const rounding = cfg.roundingType ?? types_1.RoundingType.Rounded;
210
+ const isAll = cfg.variableType === types_1.VariableType.All;
211
+ let rules;
212
+ if (isAll) {
213
+ // 对齐 TScanner.All* 系列:所有数值类型同时参与,任一匹配即命中
214
+ rules = ALL_NUMERIC.map((t) => compileRule(t, { ...cfg, variableType: t }, rounding));
215
+ }
216
+ else if (cfg.variableType === types_1.VariableType.Grouped) {
217
+ rules = [];
218
+ }
219
+ else {
220
+ rules = [compileRule(cfg.variableType, cfg, rounding)];
221
+ }
222
+ // 对齐 CE 的 fastscanmethod:fsmAligned 时按类型宽度对齐,fsmLastDigits 时按用户指定末位
223
+ let alignment = 1;
224
+ if (cfg.fastScanMethod === 1 /* fsmAligned */ && rules.length === 1) {
225
+ alignment = Math.max(1, rules[0].width);
226
+ }
227
+ else if (cfg.fastScanMethod === 2 /* fsmLastDigits */) {
228
+ const d = cfg.fastScanAlignmentLastDigits;
229
+ alignment = d && d > 1 ? d : 1;
230
+ }
231
+ return {
232
+ option: cfg.scanOption,
233
+ rounding,
234
+ rules,
235
+ isAll,
236
+ inverse: false,
237
+ floatScanWithoutExponents: cfg.floatScanWithoutExponents === true,
238
+ alignment,
239
+ group: cfg.variableType === types_1.VariableType.Grouped ? compileGroup(cfg) : undefined,
240
+ };
241
+ }
242
+ /* ------------------------------------------------------------------ *
243
+ * 单点比较:返回命中的类型列表
244
+ * ------------------------------------------------------------------ */
245
+ /** 单条规则在该偏移是否命中。next 为空表示首次扫描。 */
246
+ function ruleMatches(rule, cc, buf, off, prev, prevOff) {
247
+ const opt = cc.option;
248
+ /* —— 分组长:由 groupMatchesBlock 处理 —— */
249
+ /* —— 特殊类型优先 —— */
250
+ if (rule.str) {
251
+ if (off + rule.str.byteLength > buf.length)
252
+ return false;
253
+ if (opt === types_1.ScanOption.ExactValue || opt === types_1.ScanOption.UnknownValue) {
254
+ const ok = rule.str.wide
255
+ ? rule.str.caseSensitive
256
+ ? (0, value_codec_1.testWideString)(buf, off, rule.str.target)
257
+ : (0, value_codec_1.testWideStringCI)(buf, off, rule.str.target)
258
+ : rule.str.caseSensitive
259
+ ? (0, value_codec_1.testString)(buf, off, rule.str.target)
260
+ : (0, value_codec_1.testStringCI)(buf, off, rule.str.upper);
261
+ return ok;
262
+ }
263
+ // 字符串类型的变动/未变动:比较整段字节
264
+ if (opt === types_1.ScanOption.Changed || opt === types_1.ScanOption.Unchanged) {
265
+ if (!prev)
266
+ return false;
267
+ const changed = !buf.subarray(off, off + rule.str.byteLength).equals(prev.subarray(prevOff, prevOff + rule.str.byteLength));
268
+ return opt === types_1.ScanOption.Changed ? changed : !changed;
269
+ }
270
+ return false;
271
+ }
272
+ if (rule.aob) {
273
+ if (opt !== types_1.ScanOption.ExactValue && opt !== types_1.ScanOption.UnknownValue)
274
+ return false;
275
+ return (0, value_codec_1.aobMatchesAt)(buf, off, rule.aob.pattern, rule.aob.nibble);
276
+ }
277
+ if (rule.bin) {
278
+ if (opt !== types_1.ScanOption.ExactValue && opt !== types_1.ScanOption.UnknownValue)
279
+ return false;
280
+ const { andMask, bitMask, bitCount } = rule.bin.pattern;
281
+ const width = rule.bin.width;
282
+ if (off + width > buf.length)
283
+ return false;
284
+ let val = 0n;
285
+ for (let i = width - 1; i >= 0; i--)
286
+ val = (val << 8n) | BigInt(buf[off + i]);
287
+ void bitCount;
288
+ return (val & andMask) === (bitMask & andMask);
289
+ }
290
+ /* —— 数值类型 —— */
291
+ const width = rule.width;
292
+ if (off + width > buf.length)
293
+ return false;
294
+ // 需要旧值的前置检查
295
+ const needsOld = optionNeedsOldValue(opt);
296
+ if (needsOld && !prev)
297
+ return false;
298
+ if (needsOld && prevOff + width > prev.length)
299
+ return false;
300
+ if (rule.ints) {
301
+ const { v, v2, signed } = rule.ints;
302
+ // 1/2/4 字节:完全走 Number,避免每字节一次 BigInt 分配
303
+ if (width < 8) {
304
+ const n = readSmallInt(buf, off, width);
305
+ const old = needsOld ? readSmallInt(prev, prevOff, width) : 0;
306
+ return compareSmallInt(opt, n, old, v, v2, signed, width);
307
+ }
308
+ // 8 字节快路径:用 Buffer 原生 BigInt 读取(避免通用 readIntLike 的分支开销),
309
+ // 且对最常见的几种选项直接比较,省去一次 BigInt 运算链。
310
+ const nv = signed ? buf.readBigInt64LE(off) : buf.readBigUInt64LE(off);
311
+ let ov = 0n;
312
+ if (needsOld)
313
+ ov = signed ? prev.readBigInt64LE(prevOff) : prev.readBigUInt64LE(prevOff);
314
+ switch (opt) {
315
+ case types_1.ScanOption.UnknownValue:
316
+ return true;
317
+ case types_1.ScanOption.ExactValue:
318
+ return nv === v;
319
+ case types_1.ScanOption.Changed:
320
+ return nv !== ov;
321
+ case types_1.ScanOption.Unchanged:
322
+ return nv === ov;
323
+ case types_1.ScanOption.IncreasedValue:
324
+ return nv > ov;
325
+ case types_1.ScanOption.DecreasedValue:
326
+ return nv < ov;
327
+ default:
328
+ return compareIntBigInt(opt, nv, ov, v, v2, signed, width);
329
+ }
330
+ }
331
+ if (rule.floats) {
332
+ const isSingle = rule.type === types_1.VariableType.Single;
333
+ const cur = isSingle ? buf.readFloatLE(off) : buf.readDoubleLE(off);
334
+ const old = needsOld ? (isSingle ? prev.readFloatLE(prevOff) : prev.readDoubleLE(prevOff)) : 0;
335
+ const params = rule.floats;
336
+ const curS = Math.fround(cur);
337
+ const oldS = Math.fround(old);
338
+ switch (opt) {
339
+ case types_1.ScanOption.UnknownValue:
340
+ return true;
341
+ case types_1.ScanOption.ExactValue:
342
+ return isSingle
343
+ ? (0, value_codec_1.singleMatches)(curS, params, cc.rounding)
344
+ : (0, value_codec_1.doubleMatches)(cur, params, cc.rounding);
345
+ case types_1.ScanOption.ValueBetween:
346
+ return isSingle
347
+ ? curS >= params.single && curS <= params.single2
348
+ : cur >= params.value && cur <= params.value2;
349
+ case types_1.ScanOption.BiggerThan:
350
+ return isSingle ? curS > params.single : cur > params.value;
351
+ case types_1.ScanOption.SmallerThan:
352
+ return isSingle ? curS < params.single : cur < params.value;
353
+ case types_1.ScanOption.IncreasedValue:
354
+ return isSingle ? curS > oldS : cur > old;
355
+ case types_1.ScanOption.DecreasedValue:
356
+ return isSingle ? curS < oldS : cur < old;
357
+ case types_1.ScanOption.Changed:
358
+ return isSingle ? buf.readFloatLE(off) !== prev.readFloatLE(prevOff) : cur !== old;
359
+ case types_1.ScanOption.Unchanged:
360
+ return isSingle ? buf.readFloatLE(off) === prev.readFloatLE(prevOff) : cur === old;
361
+ case types_1.ScanOption.IncreasedValueBy:
362
+ // 对齐 SingleIncreasedValueBy:原始位模式必须不同,且按精度判等
363
+ if (isSingle) {
364
+ if (buf.readUInt32LE(off) === prev.readUInt32LE(prevOff))
365
+ return false;
366
+ return (0, value_codec_1.roundTo)(curS, params.accuracy) === (0, value_codec_1.roundTo)(oldS + params.single, params.accuracy);
367
+ }
368
+ if (buf.readBigUInt64LE(off) === prev.readBigUInt64LE(prevOff))
369
+ return false;
370
+ return (0, value_codec_1.roundTo)(cur, params.accuracy) === (0, value_codec_1.roundTo)(old + params.value, params.accuracy);
371
+ case types_1.ScanOption.DecreasedValueBy:
372
+ if (isSingle) {
373
+ if (buf.readUInt32LE(off) === prev.readUInt32LE(prevOff))
374
+ return false;
375
+ return (0, value_codec_1.roundTo)(curS, params.accuracy) === (0, value_codec_1.roundTo)(oldS - params.single, params.accuracy);
376
+ }
377
+ if (buf.readBigUInt64LE(off) === prev.readBigUInt64LE(prevOff))
378
+ return false;
379
+ return (0, value_codec_1.roundTo)(cur, params.accuracy) === (0, value_codec_1.roundTo)(old - params.value, params.accuracy);
380
+ case types_1.ScanOption.IncreasedValueByPercentage:
381
+ // 对齐 SingleIncreasedValueByPercentage:old + old*svalue < cur < old + old*svalue2
382
+ return isSingle
383
+ ? curS > oldS + oldS * params.single && curS < oldS + oldS * params.single2
384
+ : cur > old + old * params.value && cur < old + old * params.value2;
385
+ case types_1.ScanOption.DecreasedValueByPercentage:
386
+ return isSingle
387
+ ? curS > oldS - oldS * params.single2 && curS < oldS - oldS * params.single
388
+ : cur > old - old * params.value2 && cur < old - old * params.value;
389
+ case types_1.ScanOption.ValueBetweenPercentage:
390
+ return isSingle
391
+ ? curS > oldS * params.single && curS <= oldS * params.single2
392
+ : cur > old * params.value && cur <= old * params.value2;
393
+ default:
394
+ return false;
395
+ }
396
+ }
397
+ return false;
398
+ }
399
+ /** 按宽度与符号读取整数。 */
400
+ function readIntLike(buf, off, width, signed) {
401
+ switch (width) {
402
+ case 1: {
403
+ const v = buf[off];
404
+ return signed ? BigInt(v < 0x80 ? v : v - 0x100) : BigInt(v);
405
+ }
406
+ case 2: {
407
+ const v = buf.readUInt16LE(off);
408
+ return signed ? BigInt(buf.readInt16LE(off)) : BigInt(v);
409
+ }
410
+ case 4: {
411
+ const v = buf.readUInt32LE(off);
412
+ return signed ? BigInt(buf.readInt32LE(off)) : BigInt(v);
413
+ }
414
+ case 8: {
415
+ return signed ? buf.readBigInt64LE(off) : buf.readBigUInt64LE(off);
416
+ }
417
+ default:
418
+ throw new Error(`不支持整数宽度 ${width}`);
419
+ }
420
+ }
421
+ /**
422
+ * 整数比较,严格对齐 CE 的 TScanner.<Type><Option>。
423
+ *
424
+ * 性能说明:这里对 1/2/4 字节全部走 Number 路径。
425
+ * 早期实现统一转 BigInt 比较,实测只有约 200 MB/s —— 每字节一次 BigInt 分配的
426
+ * 开销远大于比较本身。8 字节保留 BigInt(Number 装不下 64 位精度)。
427
+ */
428
+ function compareInt(opt, n, old, v, v2, signed, width) {
429
+ if (width < 8) {
430
+ return compareSmallInt(opt, Number(n), Number(old), v, v2, signed, width);
431
+ }
432
+ return compareIntBigInt(opt, n, old, v, v2, signed, width);
433
+ }
434
+ /** 读取 1/2/4 字节无符号整数为 Number。 */
435
+ function readSmallInt(buf, off, width) {
436
+ switch (width) {
437
+ case 1:
438
+ return buf[off];
439
+ case 2:
440
+ return buf.readUInt16LE(off);
441
+ case 4:
442
+ return buf.readUInt32LE(off);
443
+ default:
444
+ return buf.readUInt32LE(off);
445
+ }
446
+ }
447
+ /** 1/2/4 字节的 Number 快速路径(热路径,避免 BigInt)。 */
448
+ function compareSmallInt(opt, n, old, v, v2, signed, width) {
449
+ const bits = width * 8;
450
+ const mask = bits === 32 ? 0xffffffff : (1 << bits) - 1;
451
+ const signBit = bits === 32 ? 0x80000000 : 1 << (bits - 1);
452
+ const period = bits === 32 ? 0x100000000 : 1 << bits;
453
+ const uv = Number(v & 0xffffffffn) & mask;
454
+ const uv2 = Number(v2 & 0xffffffffn) & mask;
455
+ const A = signed && n >= signBit ? n - period : n;
456
+ const B = signed && old >= signBit ? old - period : old;
457
+ const V = signed && uv >= signBit ? uv - period : uv;
458
+ const V2 = signed && uv2 >= signBit ? uv2 - period : uv2;
459
+ const wrap = (x) => (bits === 32 ? x >>> 0 : x & mask);
460
+ switch (opt) {
461
+ case types_1.ScanOption.ExactValue:
462
+ case types_1.ScanOption.UnknownValue:
463
+ return A === V;
464
+ case types_1.ScanOption.ValueBetween:
465
+ return A >= V && A <= V2;
466
+ case types_1.ScanOption.BiggerThan:
467
+ return A > V;
468
+ case types_1.ScanOption.SmallerThan:
469
+ return A < V;
470
+ case types_1.ScanOption.IncreasedValue:
471
+ return A > B;
472
+ case types_1.ScanOption.DecreasedValue:
473
+ return A < B;
474
+ case types_1.ScanOption.Changed:
475
+ return A !== B;
476
+ case types_1.ScanOption.Unchanged:
477
+ return A === B;
478
+ case types_1.ScanOption.IncreasedValueBy:
479
+ return signed ? A === B + V : A === wrap(B + V);
480
+ case types_1.ScanOption.DecreasedValueBy:
481
+ return signed ? A === B - V : A === wrap(B - V);
482
+ case types_1.ScanOption.IncreasedValueByPercentage:
483
+ return A > B + (B * V) / 100 && A < B + (B * V2) / 100;
484
+ case types_1.ScanOption.DecreasedValueByPercentage:
485
+ return A > B - (B * V2) / 100 && A < B - (B * V) / 100;
486
+ case types_1.ScanOption.ValueBetweenPercentage:
487
+ return A > (B * V) / 100 && A <= (B * V2) / 100;
488
+ default:
489
+ return false;
490
+ }
491
+ }
492
+ /** 8 字节的 BigInt 路径。 */
493
+ function compareIntBigInt(opt, n, old, v, v2, signed, width) {
494
+ const mask = (1n << BigInt(width * 8)) - 1n;
495
+ const signedWidth = BigInt(width * 8 - 1);
496
+ const asSigned = (x) => {
497
+ const c = x & mask;
498
+ return c > (1n << signedWidth) - 1n ? c - (1n << (signedWidth + 1n)) : c;
499
+ };
500
+ if (signed) {
501
+ const sn = asSigned(n);
502
+ const so = asSigned(old);
503
+ const sv = asSigned(v);
504
+ const sv2 = asSigned(v2);
505
+ switch (opt) {
506
+ case types_1.ScanOption.ExactValue:
507
+ case types_1.ScanOption.UnknownValue:
508
+ return sn === sv;
509
+ case types_1.ScanOption.ValueBetween:
510
+ return sn >= sv && sn <= sv2;
511
+ case types_1.ScanOption.BiggerThan:
512
+ return sn > sv;
513
+ case types_1.ScanOption.SmallerThan:
514
+ return sn < sv;
515
+ case types_1.ScanOption.IncreasedValue:
516
+ return sn > so;
517
+ case types_1.ScanOption.DecreasedValue:
518
+ return sn < so;
519
+ case types_1.ScanOption.Changed:
520
+ return sn !== so;
521
+ case types_1.ScanOption.Unchanged:
522
+ return sn === so;
523
+ case types_1.ScanOption.IncreasedValueBy:
524
+ return sn === so + sv;
525
+ case types_1.ScanOption.DecreasedValueBy:
526
+ return sn === so - sv;
527
+ case types_1.ScanOption.IncreasedValueByPercentage:
528
+ return sn > so + (so * sv) / 100n && sn < so + (so * sv2) / 100n;
529
+ case types_1.ScanOption.DecreasedValueByPercentage:
530
+ return sn > so - (so * sv2) / 100n && sn < so - (so * sv) / 100n;
531
+ case types_1.ScanOption.ValueBetweenPercentage:
532
+ return sn > (so * sv) / 100n && sn <= (so * sv2) / 100n;
533
+ default:
534
+ return false;
535
+ }
536
+ }
537
+ const un = n & mask;
538
+ const uo = old & mask;
539
+ const uv = v & mask;
540
+ const uv2 = v2 & mask;
541
+ switch (opt) {
542
+ case types_1.ScanOption.ExactValue:
543
+ case types_1.ScanOption.UnknownValue:
544
+ return un === uv;
545
+ case types_1.ScanOption.ValueBetween:
546
+ return un >= uv && un <= uv2;
547
+ case types_1.ScanOption.BiggerThan:
548
+ return un > uv;
549
+ case types_1.ScanOption.SmallerThan:
550
+ return un < uv;
551
+ case types_1.ScanOption.IncreasedValue:
552
+ return un > uo;
553
+ case types_1.ScanOption.DecreasedValue:
554
+ return un < uo;
555
+ case types_1.ScanOption.Changed:
556
+ return un !== uo;
557
+ case types_1.ScanOption.Unchanged:
558
+ return un === uo;
559
+ case types_1.ScanOption.IncreasedValueBy:
560
+ return un === ((uo + uv) & mask);
561
+ case types_1.ScanOption.DecreasedValueBy:
562
+ return un === ((uo - uv) & mask);
563
+ case types_1.ScanOption.IncreasedValueByPercentage:
564
+ return un > uo + (uo * uv) / 100n && un < uo + (uo * uv2) / 100n;
565
+ case types_1.ScanOption.DecreasedValueByPercentage:
566
+ return un > uo - (uo * uv2) / 100n && un < uo - (uo * uv) / 100n;
567
+ case types_1.ScanOption.ValueBetweenPercentage:
568
+ return un > (uo * uv) / 100n && un <= (uo * uv2) / 100n;
569
+ default:
570
+ return false;
571
+ }
572
+ }
573
+ /* ------------------------------------------------------------------ *
574
+ * 块扫描
575
+ * ------------------------------------------------------------------ */
576
+ /**
577
+ * 在内存块中扫描,把命中的"块内偏移"写入 out。
578
+ *
579
+ * @param cc 编译后的配置
580
+ * @param buf 当前内存块
581
+ * @param prev 上一次扫描时同一区域的原始内存(next scan 用);长度与 buf 对齐
582
+ * @param out 输出块内偏移
583
+ * @param outMatches 输出每个偏移命中的规则下标(与 out 平行,按类型区分结果时需要)
584
+ */
585
+ function scanBlock(cc, buf, prev, out, outMatches) {
586
+ if (cc.group) {
587
+ scanGroupBlock(cc.group, buf, out);
588
+ return;
589
+ }
590
+ const rules = cc.rules;
591
+ const needPrev = optionNeedsOldValue(cc.option);
592
+ if (needPrev && !prev) {
593
+ throw new Error('该扫描选项需要上一次扫描的值,请先执行首次扫描');
594
+ }
595
+ const align = cc.alignment > 0 ? cc.alignment : 1;
596
+ // 所有规则里最大的宽度,决定循环上界
597
+ let maxWidth = 0;
598
+ for (const r of rules)
599
+ if (r.width > maxWidth)
600
+ maxWidth = r.width;
601
+ const limit = buf.length - maxWidth;
602
+ // 快路径:单类型 + 无需旧值 + 无符号 4 字节精确比较(最常见的场景)
603
+ if (rules.length === 1 &&
604
+ !needPrev &&
605
+ cc.option === types_1.ScanOption.ExactValue &&
606
+ rules[0].ints &&
607
+ !rules[0].ints.signed &&
608
+ rules[0].width === 4) {
609
+ scanAlignedU32(buf, Number(rules[0].ints.v & 0xffffffffn), align, out);
610
+ return;
611
+ }
612
+ for (let i = 0; i <= limit; i += align) {
613
+ const matches = outMatches ? [] : EMPTY_MATCHES;
614
+ let hit = false;
615
+ for (let k = 0; k < rules.length; k++) {
616
+ if (ruleMatches(rules[k], cc, buf, i, prev, i)) {
617
+ hit = true;
618
+ if (outMatches)
619
+ matches.push(k);
620
+ if (!cc.isAll)
621
+ break; // 非 all 模式:一个类型命中即可
622
+ }
623
+ }
624
+ if (hit) {
625
+ out.push(i);
626
+ if (outMatches)
627
+ outMatches.push(matches);
628
+ }
629
+ }
630
+ }
631
+ const EMPTY_MATCHES = [];
632
+ /** 4 字节对齐精确扫描的快路径(Uint32Array 比较,显著快于逐字节 readUInt32LE)。 */
633
+ function scanAlignedU32(buf, value, align, out) {
634
+ if (align !== 4) {
635
+ for (let i = 0; i + 4 <= buf.length; i += align) {
636
+ if (buf.readUInt32LE(i) === value)
637
+ out.push(i);
638
+ }
639
+ return;
640
+ }
641
+ const count = buf.length >>> 2;
642
+ for (let i = 0; i < count; i++) {
643
+ if (buf.readUInt32LE(i << 2) === value)
644
+ out.push(i << 2);
645
+ }
646
+ }
647
+ /* ------------------------------------------------------------------ *
648
+ * 分组长扫描(对齐 compareblock / compareblock_outoforder)
649
+ * ------------------------------------------------------------------ */
650
+ function groupElementMatchesAt(el, buf, off) {
651
+ if (el.wildcard)
652
+ return true;
653
+ if (el.str) {
654
+ if (off + el.str.byteLength > buf.length)
655
+ return false;
656
+ return el.str.wide
657
+ ? el.str.caseSensitive
658
+ ? (0, value_codec_1.testWideString)(buf, off, el.str.target)
659
+ : (0, value_codec_1.testWideStringCI)(buf, off, el.str.target)
660
+ : el.str.caseSensitive
661
+ ? (0, value_codec_1.testString)(buf, off, el.str.target)
662
+ : (0, value_codec_1.testStringCI)(buf, off, el.str.upper);
663
+ }
664
+ if (el.aob)
665
+ return (0, value_codec_1.aobMatchesAt)(buf, off, el.aob.pattern, el.aob.nibble);
666
+ if (el.bin) {
667
+ const { andMask, bitMask } = el.bin.pattern;
668
+ let val = 0n;
669
+ for (let i = el.bin.width - 1; i >= 0; i--)
670
+ val = (val << 8n) | BigInt(buf[off + i]);
671
+ return (val & andMask) === (bitMask & andMask);
672
+ }
673
+ if (el.ints) {
674
+ const mask = (1n << BigInt(el.width * 8)) - 1n;
675
+ const n = readIntLike(buf, off, el.width, el.ints.signed) & mask;
676
+ if (el.ints.v === el.ints.v2)
677
+ return n === (el.ints.v & mask);
678
+ return n >= (el.ints.v & mask) && n <= (el.ints.v2 & mask);
679
+ }
680
+ if (el.floats) {
681
+ const isSingle = el.type === types_1.VariableType.Single;
682
+ const cur = isSingle ? buf.readFloatLE(off) : buf.readDoubleLE(off);
683
+ const p = el.floats;
684
+ return isSingle
685
+ ? cur >= p.minSingle && cur <= p.maxSingle
686
+ : cur >= p.minDouble && cur <= p.maxDouble;
687
+ }
688
+ return false;
689
+ }
690
+ /** 顺序分组长扫描:对齐 TGroupData.compareblock(元素按声明顺序紧密排列)。 */
691
+ function scanGroupBlock(group, buf, out) {
692
+ const blockSize = group.blockSize;
693
+ if (blockSize <= 0)
694
+ return;
695
+ const limit = buf.length - blockSize;
696
+ if (!group.outOfOrder) {
697
+ for (let i = 0; i <= limit; i++) {
698
+ let off = i;
699
+ let ok = true;
700
+ for (const el of group.elements) {
701
+ if (!groupElementMatchesAt(el, buf, off)) {
702
+ ok = false;
703
+ break;
704
+ }
705
+ off += el.width;
706
+ }
707
+ if (ok)
708
+ out.push(i);
709
+ }
710
+ return;
711
+ }
712
+ // 乱序扫描:对齐 TGroupData.compareblock_outoforder 的语义 ——
713
+ // 对每个候选基址 i,在窗口 [i, i+blockSize) 内为每个元素**独立定位**一个位置,
714
+ // 要求各元素占用的字节区间互不重叠。
715
+ //
716
+ // 性能:早期实现用 `used.some(...)` 逐对检查重叠,在几十 MB 段上是 O(n·k²)。
717
+ // 这里改用一块"占用位图"(Uint8Array,长度 = 块宽),
718
+ // 检查与置位都是 O(width),整体降为 O(n·k·w),实测从 15s 降到 ~2s。
719
+ const occ = new Uint8Array(blockSize + 1);
720
+ // 匹配顺序:先具体元素、后通配元素。
721
+ // 原因:通配元素"匹配任何值",若先给它定位,它可能占掉某个具体元素唯一可用的位置,
722
+ // 导致本来存在的匹配被漏掉(实测会漏掉 health/ammo 这对相邻值)。
723
+ // 按"具体在前"的顺序定位可避免这种贪心抢占。
724
+ const order = group.elements
725
+ .map((_, idx) => idx)
726
+ .sort((a, b) => {
727
+ const wa = group.elements[a].wildcard ? 1 : 0;
728
+ const wb = group.elements[b].wildcard ? 1 : 0;
729
+ return wa - wb;
730
+ });
731
+ for (let i = 0; i <= limit; i++) {
732
+ // 每轮重置占用位图(fill 比新建数组快)
733
+ occ.fill(0, 0, blockSize + 1);
734
+ let ok = true;
735
+ for (const ei of order) {
736
+ const el = group.elements[ei];
737
+ const step = group.outOfOrderAligned && el.width > 1 ? el.width : 1;
738
+ const winEnd = i + blockSize - el.width;
739
+ let found = -1;
740
+ for (let p = i; p <= winEnd; p += step) {
741
+ // 与已占用区间重叠则跳过(相对 i 的偏移范围 [p-i, p-i+width))
742
+ const rel = p - i;
743
+ let free = true;
744
+ for (let q = rel; q < rel + el.width; q++) {
745
+ if (occ[q] !== 0) {
746
+ free = false;
747
+ break;
748
+ }
749
+ }
750
+ if (!free)
751
+ continue;
752
+ if (groupElementMatchesAt(el, buf, p)) {
753
+ found = p;
754
+ break;
755
+ }
756
+ }
757
+ if (found === -1) {
758
+ ok = false;
759
+ break;
760
+ }
761
+ const rel = found - i;
762
+ for (let q = rel; q < rel + el.width; q++)
763
+ occ[q] = 1;
764
+ el.outOffset = rel;
765
+ }
766
+ if (ok)
767
+ out.push(i);
768
+ }
769
+ }
770
+ /* ------------------------------------------------------------------ *
771
+ * 导出给测试的辅助
772
+ * ------------------------------------------------------------------ */
773
+ /** 用编译后的配置测试"某个值是否匹配"(便于单元测试,不依赖进程)。 */
774
+ function testValueMatches(cfg, current, previous) {
775
+ const cc = compileConfig(cfg);
776
+ const matches = [];
777
+ const perRule = [];
778
+ scanBlock(cc, current, previous ?? null, matches, perRule);
779
+ return perRule.map((m) => m.length > 0);
780
+ }
781
+ //# sourceMappingURL=matcher.js.map