cheatengine 5.8.29 → 5.8.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/README_CN.md +39 -20
- package/ce_mcp_bridge.lua +80 -45
- package/ce_mcp_server.js +2 -3
- package/package.json +1 -1
- package/src/pipe-client.js +45 -22
- package/src/tool-registry.js +8 -7
package/README.md
CHANGED
|
@@ -139,12 +139,16 @@ Read a single memory value.
|
|
|
139
139
|
- `type` (string, required): `byte`, `word`, `dword`, `qword`, `float`, `double`, `string`, `bytes`
|
|
140
140
|
- `size` (integer, optional): Size for string/bytes type (default: 100)
|
|
141
141
|
|
|
142
|
+
**Returns:** `{value}`
|
|
143
|
+
|
|
142
144
|
#### `ce_read_memory_batch(requests)`
|
|
143
145
|
Read multiple addresses in one call. **Always prefer this over multiple ce_read_memory calls.**
|
|
144
146
|
|
|
145
147
|
**Parameters:**
|
|
146
148
|
- `requests` (array, required): Array of `{address, type, id?, size?}`
|
|
147
149
|
|
|
150
|
+
**Returns:** `{results: {id_or_address: {value, error?}}}`
|
|
151
|
+
|
|
148
152
|
#### `ce_write_memory(address, type, value)`
|
|
149
153
|
Write a value to memory.
|
|
150
154
|
|
|
@@ -153,6 +157,8 @@ Write a value to memory.
|
|
|
153
157
|
- `type` (string, required): Value type
|
|
154
158
|
- `value` (string, required): Value to write
|
|
155
159
|
|
|
160
|
+
**Returns:** `{success, address}`
|
|
161
|
+
|
|
156
162
|
---
|
|
157
163
|
|
|
158
164
|
### Scanning & Search
|
|
@@ -208,6 +214,8 @@ List all active scan sessions.
|
|
|
208
214
|
#### `ce_enum_modules`
|
|
209
215
|
List all loaded modules (DLLs).
|
|
210
216
|
|
|
217
|
+
**Returns:** `{count, modules: [{name, address, size, path, source}], used_fallback}`
|
|
218
|
+
|
|
211
219
|
---
|
|
212
220
|
|
|
213
221
|
### Symbols & Addresses
|
|
@@ -250,6 +258,8 @@ Disassemble instructions.
|
|
|
250
258
|
#### `ce_get_instruction_info(address)`
|
|
251
259
|
Get detailed info about a single instruction.
|
|
252
260
|
|
|
261
|
+
**Returns:** `{address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}`
|
|
262
|
+
|
|
253
263
|
#### `ce_analyze_code(address, count?)`
|
|
254
264
|
Static analysis of code block (calls, jumps, refs).
|
|
255
265
|
|
|
@@ -315,6 +325,8 @@ Detect function start and end by analyzing prologue/epilogue patterns.
|
|
|
315
325
|
#### `ce_generate_signature(address)`
|
|
316
326
|
Generate unique AOB signature for an address. Useful for game updates.
|
|
317
327
|
|
|
328
|
+
**Returns:** `{address, signature, offset_from_start, byte_count, usage_hint}`
|
|
329
|
+
|
|
318
330
|
---
|
|
319
331
|
|
|
320
332
|
### Advanced Analysis
|
|
@@ -346,9 +358,16 @@ Lightweight symbolic execution. Interprets instruction semantics without executi
|
|
|
346
358
|
- `count` (integer, optional): Instructions to trace (default: 30)
|
|
347
359
|
- `initial_state` (object, optional): Initial register symbols, e.g. `{"rcx": "this_ptr", "rdx": "arg1"}`
|
|
348
360
|
|
|
349
|
-
#### `ce_call_function(address, args?, return_type?, timeout?)`
|
|
361
|
+
#### `ce_call_function(address, args?, call_method?, return_type?, timeout?)`
|
|
350
362
|
Call a function in the target process. **WARNING: Executes real code!**
|
|
351
363
|
|
|
364
|
+
**Parameters:**
|
|
365
|
+
- `address` (string, required): Target function address
|
|
366
|
+
- `args` (array, optional): Up to 4 arguments (integers or address expressions)
|
|
367
|
+
- `call_method` (integer, optional): Calling method for `executeCodeEx` (e.g. `0`). If omitted, bridge auto-fallbacks for compatibility
|
|
368
|
+
- `return_type` (string, optional): `byte`, `word`, `dword`, `qword`, `float`, `double`, `pointer` (default: `qword`)
|
|
369
|
+
- `timeout` (integer, optional): Timeout in milliseconds (default: `5000`)
|
|
370
|
+
|
|
352
371
|
---
|
|
353
372
|
|
|
354
373
|
### Function Hooking
|
package/README_CN.md
CHANGED
|
@@ -131,27 +131,33 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
131
131
|
|
|
132
132
|
### 内存读写
|
|
133
133
|
|
|
134
|
-
#### `ce_read_memory(address, type, size?)`
|
|
135
|
-
读取单个内存值。
|
|
134
|
+
#### `ce_read_memory(address, type, size?)`
|
|
135
|
+
读取单个内存值。
|
|
136
136
|
|
|
137
137
|
**参数:**
|
|
138
138
|
- `address` (string, 必需): 地址表达式 (如 `"game.exe+0x1234"`, `"0x140001000"`)
|
|
139
|
-
- `type` (string, 必需): `byte`, `word`, `dword`, `qword`, `float`, `double`, `string`, `bytes`
|
|
140
|
-
- `size` (integer, 可选): string/bytes 类型的大小 (默认: 100)
|
|
139
|
+
- `type` (string, 必需): `byte`, `word`, `dword`, `qword`, `float`, `double`, `string`, `bytes`
|
|
140
|
+
- `size` (integer, 可选): string/bytes 类型的大小 (默认: 100)
|
|
141
|
+
|
|
142
|
+
**返回:** `{value}`
|
|
141
143
|
|
|
142
|
-
#### `ce_read_memory_batch(requests)`
|
|
143
|
-
一次调用读取多个地址。**始终优先使用此方法而非多次调用 ce_read_memory。**
|
|
144
|
+
#### `ce_read_memory_batch(requests)`
|
|
145
|
+
一次调用读取多个地址。**始终优先使用此方法而非多次调用 ce_read_memory。**
|
|
144
146
|
|
|
145
147
|
**参数:**
|
|
146
|
-
- `requests` (array, 必需): `{address, type, id?, size?}` 数组
|
|
148
|
+
- `requests` (array, 必需): `{address, type, id?, size?}` 数组
|
|
149
|
+
|
|
150
|
+
**返回:** `{results: {id_or_address: {value, error?}}}`
|
|
147
151
|
|
|
148
|
-
#### `ce_write_memory(address, type, value)`
|
|
149
|
-
向内存写入值。
|
|
152
|
+
#### `ce_write_memory(address, type, value)`
|
|
153
|
+
向内存写入值。
|
|
150
154
|
|
|
151
155
|
**参数:**
|
|
152
|
-
- `address` (string, 必需): 地址表达式
|
|
153
|
-
- `type` (string, 必需): 值类型
|
|
154
|
-
- `value` (string, 必需): 要写入的值
|
|
156
|
+
- `address` (string, 必需): 地址表达式
|
|
157
|
+
- `type` (string, 必需): 值类型
|
|
158
|
+
- `value` (string, 必需): 要写入的值
|
|
159
|
+
|
|
160
|
+
**返回:** `{success, address}`
|
|
155
161
|
|
|
156
162
|
---
|
|
157
163
|
|
|
@@ -205,8 +211,10 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
205
211
|
#### `ce_scan_list`
|
|
206
212
|
列出所有活动的扫描会话。
|
|
207
213
|
|
|
208
|
-
#### `ce_enum_modules`
|
|
209
|
-
列出所有已加载的模块 (DLL)。
|
|
214
|
+
#### `ce_enum_modules`
|
|
215
|
+
列出所有已加载的模块 (DLL)。
|
|
216
|
+
|
|
217
|
+
**返回:** `{count, modules: [{name, address, size, path, source}], used_fallback}`
|
|
210
218
|
|
|
211
219
|
---
|
|
212
220
|
|
|
@@ -247,8 +255,10 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
247
255
|
- `count` (integer, 可选): 指令数量 (默认: 10)
|
|
248
256
|
- `direction` (string, 可选): `"forward"` 或 `"backward"` (默认: forward)
|
|
249
257
|
|
|
250
|
-
#### `ce_get_instruction_info(address)`
|
|
251
|
-
获取单条指令的详细信息。
|
|
258
|
+
#### `ce_get_instruction_info(address)`
|
|
259
|
+
获取单条指令的详细信息。
|
|
260
|
+
|
|
261
|
+
**返回:** `{address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}`
|
|
252
262
|
|
|
253
263
|
#### `ce_analyze_code(address, count?)`
|
|
254
264
|
代码块的静态分析 (调用、跳转、引用)。
|
|
@@ -312,8 +322,10 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
312
322
|
#### `ce_find_function_boundaries(address, max_search?)`
|
|
313
323
|
通过分析序言/尾声模式检测函数起止。
|
|
314
324
|
|
|
315
|
-
#### `ce_generate_signature(address)`
|
|
316
|
-
为地址生成唯一的 AOB 特征码。用于游戏更新。
|
|
325
|
+
#### `ce_generate_signature(address)`
|
|
326
|
+
为地址生成唯一的 AOB 特征码。用于游戏更新。
|
|
327
|
+
|
|
328
|
+
**返回:** `{address, signature, offset_from_start, byte_count, usage_hint}`
|
|
317
329
|
|
|
318
330
|
---
|
|
319
331
|
|
|
@@ -346,8 +358,15 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
346
358
|
- `count` (integer, 可选): 要跟踪的指令数 (默认: 30)
|
|
347
359
|
- `initial_state` (object, 可选): 初始寄存器符号,如 `{"rcx": "this_ptr", "rdx": "arg1"}`
|
|
348
360
|
|
|
349
|
-
#### `ce_call_function(address, args?, return_type?, timeout?)`
|
|
350
|
-
在目标进程中调用函数。**警告: 执行真实代码!**
|
|
361
|
+
#### `ce_call_function(address, args?, call_method?, return_type?, timeout?)`
|
|
362
|
+
在目标进程中调用函数。**警告: 执行真实代码!**
|
|
363
|
+
|
|
364
|
+
**参数:**
|
|
365
|
+
- `address` (string, 必需): 目标函数地址
|
|
366
|
+
- `args` (array, 可选): 最多 4 个参数(整数或地址表达式)
|
|
367
|
+
- `call_method` (integer, 可选): `executeCodeEx` 调用方式(如 `0`)。省略时 bridge 会自动走兼容回退
|
|
368
|
+
- `return_type` (string, 可选): `byte`, `word`, `dword`, `qword`, `float`, `double`, `pointer`(默认: `qword`)
|
|
369
|
+
- `timeout` (integer, 可选): 超时毫秒数(默认: `5000`)
|
|
351
370
|
|
|
352
371
|
---
|
|
353
372
|
|
package/ce_mcp_bridge.lua
CHANGED
|
@@ -1692,10 +1692,11 @@ Handlers.aob_scan = function(p)
|
|
|
1692
1692
|
scanOk = true
|
|
1693
1693
|
end)
|
|
1694
1694
|
|
|
1695
|
-
if not scanOk then
|
|
1696
|
-
pcall(function()
|
|
1697
|
-
|
|
1698
|
-
|
|
1695
|
+
if not scanOk then
|
|
1696
|
+
pcall(function() foundList.deinitialize() end)
|
|
1697
|
+
pcall(function() memscan.destroy() end)
|
|
1698
|
+
return { count = 0, results = {}, error = "AOB Scan failed" }
|
|
1699
|
+
end
|
|
1699
1700
|
|
|
1700
1701
|
foundList.initialize()
|
|
1701
1702
|
local totalCount = foundList.Count or 0
|
|
@@ -1799,10 +1800,11 @@ Handlers.value_scan = function(p)
|
|
|
1799
1800
|
memscan.waitTillDone()
|
|
1800
1801
|
end)
|
|
1801
1802
|
|
|
1802
|
-
if not scanOk then
|
|
1803
|
-
pcall(function()
|
|
1804
|
-
|
|
1805
|
-
|
|
1803
|
+
if not scanOk then
|
|
1804
|
+
pcall(function() foundList.deinitialize() end)
|
|
1805
|
+
pcall(function() memscan.destroy() end)
|
|
1806
|
+
return { count = 0, results = {}, error = "Scan failed" }
|
|
1807
|
+
end
|
|
1806
1808
|
|
|
1807
1809
|
foundList.initialize()
|
|
1808
1810
|
|
|
@@ -1823,11 +1825,12 @@ Handlers.value_scan = function(p)
|
|
|
1823
1825
|
isInModule = inModule(addrNum)
|
|
1824
1826
|
end)
|
|
1825
1827
|
|
|
1826
|
-
table_insert(results, {
|
|
1827
|
-
address = Utils.formatHex(addrNum),
|
|
1828
|
-
symbol = symbol,
|
|
1829
|
-
isStatic = symbol
|
|
1830
|
-
|
|
1828
|
+
table_insert(results, {
|
|
1829
|
+
address = Utils.formatHex(addrNum),
|
|
1830
|
+
symbol = symbol,
|
|
1831
|
+
isStatic = (symbol ~= nil) and
|
|
1832
|
+
(symbol:find("%.exe%+") ~= nil or symbol:find("%.dll%+") ~= nil)
|
|
1833
|
+
})
|
|
1831
1834
|
end
|
|
1832
1835
|
end
|
|
1833
1836
|
|
|
@@ -1949,10 +1952,11 @@ Handlers.scan_new = function(p)
|
|
|
1949
1952
|
memscan.waitTillDone()
|
|
1950
1953
|
end)
|
|
1951
1954
|
|
|
1952
|
-
if not scanOk then
|
|
1953
|
-
pcall(function()
|
|
1954
|
-
|
|
1955
|
-
|
|
1955
|
+
if not scanOk then
|
|
1956
|
+
pcall(function() foundList.deinitialize() end)
|
|
1957
|
+
pcall(function() memscan.destroy() end)
|
|
1958
|
+
error("First scan failed")
|
|
1959
|
+
end
|
|
1956
1960
|
|
|
1957
1961
|
foundList.initialize()
|
|
1958
1962
|
local totalCount = foundList.Count or 0
|
|
@@ -2105,13 +2109,14 @@ Handlers.scan_results = function(p)
|
|
|
2105
2109
|
end
|
|
2106
2110
|
end)
|
|
2107
2111
|
|
|
2108
|
-
table_insert(results, {
|
|
2109
|
-
index = i,
|
|
2110
|
-
address = Utils.formatHex(addrNum),
|
|
2111
|
-
symbol = symbol,
|
|
2112
|
-
value = currentValue,
|
|
2113
|
-
isStatic =
|
|
2114
|
-
|
|
2112
|
+
table_insert(results, {
|
|
2113
|
+
index = i,
|
|
2114
|
+
address = Utils.formatHex(addrNum),
|
|
2115
|
+
symbol = symbol,
|
|
2116
|
+
value = currentValue,
|
|
2117
|
+
isStatic = (symbol ~= nil) and
|
|
2118
|
+
(symbol:find("%.exe%+") ~= nil or symbol:find("%.dll%+") ~= nil)
|
|
2119
|
+
})
|
|
2115
2120
|
end
|
|
2116
2121
|
end
|
|
2117
2122
|
|
|
@@ -2296,10 +2301,10 @@ Handlers.enum_modules = function()
|
|
|
2296
2301
|
if #r == 0 then
|
|
2297
2302
|
usedFallback = true
|
|
2298
2303
|
local mzScan = AOBScan("4D 5A 90 00 03 00 00 00") -- MZ PE header
|
|
2299
|
-
if mzScan and mzScan.Count > 0 then
|
|
2300
|
-
for i = 0, math_min(mzScan.Count - 1, 50) do
|
|
2301
|
-
local addr = tonumber(mzScan[i], 16)
|
|
2302
|
-
if addr then
|
|
2304
|
+
if mzScan and mzScan.Count > 0 then
|
|
2305
|
+
for i = 0, math_min(mzScan.Count - 1, 50) do
|
|
2306
|
+
local addr = tonumber(mzScan[i], 16)
|
|
2307
|
+
if addr then
|
|
2303
2308
|
local peOffset = readInteger(addr + 0x3C)
|
|
2304
2309
|
local moduleSize = 0
|
|
2305
2310
|
local realName = nil
|
|
@@ -2339,11 +2344,13 @@ Handlers.enum_modules = function()
|
|
|
2339
2344
|
path = "",
|
|
2340
2345
|
source = realName and "export_directory" or "aob_fallback"
|
|
2341
2346
|
})
|
|
2342
|
-
end
|
|
2343
|
-
end
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
+
end
|
|
2348
|
+
end
|
|
2349
|
+
end
|
|
2350
|
+
if mzScan then
|
|
2351
|
+
mzScan.destroy()
|
|
2352
|
+
end
|
|
2353
|
+
end
|
|
2347
2354
|
|
|
2348
2355
|
return {
|
|
2349
2356
|
modules = r,
|
|
@@ -6874,12 +6881,25 @@ Handlers.call_function = function(p)
|
|
|
6874
6881
|
table_insert(resolvedArgs, 0)
|
|
6875
6882
|
end
|
|
6876
6883
|
|
|
6877
|
-
-- Execute
|
|
6878
|
-
--
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6884
|
+
-- Execute function using CE's executeCodeEx.
|
|
6885
|
+
-- If call_method is explicitly provided, use new signature:
|
|
6886
|
+
-- executeCodeEx(callmethod, timeout, address, ...)
|
|
6887
|
+
-- If omitted, try old signature first for backward compatibility:
|
|
6888
|
+
-- executeCodeEx(timeout, address, ...)
|
|
6889
|
+
-- then fallback to new signature.
|
|
6890
|
+
local callMethod = p.call_method
|
|
6891
|
+
local ok, result = false, nil
|
|
6892
|
+
if callMethod ~= nil then
|
|
6893
|
+
ok, result = pcall(executeCodeEx, callMethod, timeout, funcAddr,
|
|
6894
|
+
resolvedArgs[1], resolvedArgs[2], resolvedArgs[3], resolvedArgs[4])
|
|
6895
|
+
else
|
|
6896
|
+
ok, result = pcall(executeCodeEx, timeout, funcAddr,
|
|
6897
|
+
resolvedArgs[1], resolvedArgs[2], resolvedArgs[3], resolvedArgs[4])
|
|
6898
|
+
if not ok then
|
|
6899
|
+
ok, result = pcall(executeCodeEx, 0, timeout, funcAddr,
|
|
6900
|
+
resolvedArgs[1], resolvedArgs[2], resolvedArgs[3], resolvedArgs[4])
|
|
6901
|
+
end
|
|
6902
|
+
end
|
|
6883
6903
|
|
|
6884
6904
|
if not ok then
|
|
6885
6905
|
return {
|
|
@@ -7656,12 +7676,27 @@ end
|
|
|
7656
7676
|
-- ============ Server Lifecycle ============
|
|
7657
7677
|
local Server = {}
|
|
7658
7678
|
|
|
7659
|
-
function Server.stop()
|
|
7660
|
-
Utils.debugPrint("Stopping server...")
|
|
7661
|
-
Context.serverRunning = false
|
|
7662
|
-
|
|
7663
|
-
-- Cleanup
|
|
7664
|
-
|
|
7679
|
+
function Server.stop()
|
|
7680
|
+
Utils.debugPrint("Stopping server...")
|
|
7681
|
+
Context.serverRunning = false
|
|
7682
|
+
|
|
7683
|
+
-- Cleanup UI status timer/label to avoid leaked timers across stop/start cycles
|
|
7684
|
+
if CE_MCP_STATUS_LABEL then
|
|
7685
|
+
pcall(function()
|
|
7686
|
+
if CE_MCP_STATUS_LABEL.timer then
|
|
7687
|
+
CE_MCP_STATUS_LABEL.timer.Enabled = false
|
|
7688
|
+
CE_MCP_STATUS_LABEL.timer.destroy()
|
|
7689
|
+
CE_MCP_STATUS_LABEL.timer = nil
|
|
7690
|
+
end
|
|
7691
|
+
if CE_MCP_STATUS_LABEL.label then
|
|
7692
|
+
CE_MCP_STATUS_LABEL.label.destroy()
|
|
7693
|
+
CE_MCP_STATUS_LABEL.label = nil
|
|
7694
|
+
end
|
|
7695
|
+
end)
|
|
7696
|
+
end
|
|
7697
|
+
|
|
7698
|
+
-- Cleanup all zombie resources (breakpoints, traces, etc.)
|
|
7699
|
+
Utils.cleanupZombieState()
|
|
7665
7700
|
|
|
7666
7701
|
-- Clear address cache
|
|
7667
7702
|
Utils.clearAddressCache()
|
package/ce_mcp_server.js
CHANGED
|
@@ -31,7 +31,6 @@ class CEMCPServer {
|
|
|
31
31
|
// Check 1: Pipe existence
|
|
32
32
|
let pipeExists = false;
|
|
33
33
|
try {
|
|
34
|
-
const fs = require('fs');
|
|
35
34
|
// Try to check if pipe exists (Windows specific)
|
|
36
35
|
const testClient = new PipeClient();
|
|
37
36
|
const connected = await testClient.connect(false, 1000);
|
|
@@ -270,8 +269,8 @@ class CEMCPServer {
|
|
|
270
269
|
// Check if error: has error field, success=false, and no partial results
|
|
271
270
|
let isError = false;
|
|
272
271
|
if (typeof result === 'object' && result !== null && result.error) {
|
|
273
|
-
const hasPartialResult = ['chain', 'path', 'partialCENotation', 'finalAddress'].some(k => k in result);
|
|
274
|
-
isError =
|
|
272
|
+
const hasPartialResult = ['chain', 'path', 'partialCENotation', 'finalAddress', 'final_address', 'ceNotation', 'ce_pointer_notation'].some(k => k in result);
|
|
273
|
+
isError = !hasPartialResult;
|
|
275
274
|
}
|
|
276
275
|
|
|
277
276
|
const text = isError
|
package/package.json
CHANGED
package/src/pipe-client.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const net = require('net');
|
|
7
7
|
const { EventEmitter } = require('events');
|
|
8
|
-
const { Config, HealthMonitor, Logger, log } = require('./base');
|
|
8
|
+
const { Config, HealthMonitor, TimeoutError, Logger, log } = require('./base');
|
|
9
9
|
|
|
10
10
|
// Windows error codes
|
|
11
11
|
const ERROR_FILE_NOT_FOUND = 2;
|
|
@@ -21,6 +21,7 @@ class PipeClient extends EventEmitter {
|
|
|
21
21
|
this.socket = null;
|
|
22
22
|
this.healthMonitor = new HealthMonitor(errorThreshold);
|
|
23
23
|
this.reconnectTimer = null;
|
|
24
|
+
this.reconnectLoopPromise = null;
|
|
24
25
|
this.stopReconnectFlag = false;
|
|
25
26
|
this.connected = false;
|
|
26
27
|
this.connectionAttempts = 0;
|
|
@@ -97,6 +98,8 @@ class PipeClient extends EventEmitter {
|
|
|
97
98
|
this.pendingResponse.reject(new Error(`Invalid response size: ${respLen}`));
|
|
98
99
|
this.pendingResponse = null;
|
|
99
100
|
this.responseBuffer = Buffer.alloc(0);
|
|
101
|
+
this._close();
|
|
102
|
+
this.healthMonitor.recordError(`Invalid response frame length: ${respLen}`);
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -132,21 +135,25 @@ class PipeClient extends EventEmitter {
|
|
|
132
135
|
let backoff = 0.5;
|
|
133
136
|
const maxBackoff = 10.0;
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
try {
|
|
139
|
+
while (!this.stopReconnectFlag) {
|
|
140
|
+
if (!this.isValid()) {
|
|
141
|
+
this.connectionAttempts++;
|
|
142
|
+
this.healthMonitor.recordConnectionAttempt();
|
|
143
|
+
|
|
144
|
+
if (await this._tryConnectOnce()) {
|
|
145
|
+
backoff = 0.5;
|
|
146
|
+
} else {
|
|
147
|
+
backoff = Math.min(backoff * 1.5, maxBackoff);
|
|
148
|
+
}
|
|
142
149
|
} else {
|
|
143
|
-
backoff =
|
|
150
|
+
backoff = 0.5;
|
|
144
151
|
}
|
|
145
|
-
} else {
|
|
146
|
-
backoff = 0.5;
|
|
147
|
-
}
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
await this._sleep(backoff * 1000);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
this.reconnectLoopPromise = null;
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
|
|
@@ -155,9 +162,9 @@ class PipeClient extends EventEmitter {
|
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
startBackgroundReconnect() {
|
|
158
|
-
if (this.
|
|
165
|
+
if (this.reconnectLoopPromise) return;
|
|
159
166
|
this.stopReconnectFlag = false;
|
|
160
|
-
this._reconnectLoop();
|
|
167
|
+
this.reconnectLoopPromise = this._reconnectLoop();
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
async connect(force = false, timeout = 3000) {
|
|
@@ -245,9 +252,8 @@ class PipeClient extends EventEmitter {
|
|
|
245
252
|
const lenBuffer = Buffer.alloc(4);
|
|
246
253
|
lenBuffer.writeUInt32LE(jsonBytes.length, 0);
|
|
247
254
|
|
|
248
|
-
// Send request
|
|
249
|
-
this.socket.write(lenBuffer);
|
|
250
|
-
this.socket.write(jsonBytes);
|
|
255
|
+
// Send request as one framed buffer to avoid split-frame races
|
|
256
|
+
this.socket.write(Buffer.concat([lenBuffer, jsonBytes]));
|
|
251
257
|
|
|
252
258
|
// Wait for response
|
|
253
259
|
const result = await this._waitForResponse();
|
|
@@ -268,7 +274,15 @@ class PipeClient extends EventEmitter {
|
|
|
268
274
|
|
|
269
275
|
_waitForResponse() {
|
|
270
276
|
return new Promise((resolve, reject) => {
|
|
277
|
+
const socket = this.socket;
|
|
278
|
+
let closeHandler = null;
|
|
271
279
|
this.pendingResponse = { resolve, reject };
|
|
280
|
+
|
|
281
|
+
const cleanup = () => {
|
|
282
|
+
if (socket && closeHandler) {
|
|
283
|
+
socket.removeListener('close', closeHandler);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
272
286
|
|
|
273
287
|
// Check if we already have data in buffer (race condition fix)
|
|
274
288
|
if (this.responseBuffer.length >= 4) {
|
|
@@ -281,10 +295,12 @@ class PipeClient extends EventEmitter {
|
|
|
281
295
|
const decoded = this._decodeResponse(respData);
|
|
282
296
|
const result = JSON.parse(decoded);
|
|
283
297
|
this.pendingResponse = null;
|
|
298
|
+
cleanup();
|
|
284
299
|
resolve(result);
|
|
285
300
|
return;
|
|
286
301
|
} catch (err) {
|
|
287
302
|
this.pendingResponse = null;
|
|
303
|
+
cleanup();
|
|
288
304
|
reject(err);
|
|
289
305
|
return;
|
|
290
306
|
}
|
|
@@ -292,13 +308,14 @@ class PipeClient extends EventEmitter {
|
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
// Set up a one-time close handler to reject if socket closes while waiting
|
|
295
|
-
|
|
311
|
+
closeHandler = () => {
|
|
296
312
|
this.pendingResponse = null;
|
|
313
|
+
cleanup();
|
|
297
314
|
reject(new Error('Socket closed while waiting for response'));
|
|
298
315
|
};
|
|
299
316
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
317
|
+
if (socket) {
|
|
318
|
+
socket.once('close', closeHandler);
|
|
302
319
|
}
|
|
303
320
|
});
|
|
304
321
|
}
|
|
@@ -308,8 +325,9 @@ class PipeClient extends EventEmitter {
|
|
|
308
325
|
const startTime = Date.now();
|
|
309
326
|
|
|
310
327
|
// Create timeout promise
|
|
328
|
+
let timeoutHandle = null;
|
|
311
329
|
const timeoutPromise = new Promise((_, reject) => {
|
|
312
|
-
setTimeout(() => {
|
|
330
|
+
timeoutHandle = setTimeout(() => {
|
|
313
331
|
const elapsedTime = (Date.now() - startTime) / 1000;
|
|
314
332
|
const errorMsg = `Tool execution timed out after ${elapsedTime.toFixed(2)}s (timeout: ${timeoutSeconds}s)`;
|
|
315
333
|
log.error(`Timeout: tool=${toolName || 'unknown'}, params=${JSON.stringify(data.params || {})}, elapsed=${elapsedTime.toFixed(2)}s`);
|
|
@@ -337,6 +355,10 @@ class PipeClient extends EventEmitter {
|
|
|
337
355
|
};
|
|
338
356
|
}
|
|
339
357
|
return { error: err.message };
|
|
358
|
+
} finally {
|
|
359
|
+
if (timeoutHandle) {
|
|
360
|
+
clearTimeout(timeoutHandle);
|
|
361
|
+
}
|
|
340
362
|
}
|
|
341
363
|
}
|
|
342
364
|
|
|
@@ -346,6 +368,7 @@ class PipeClient extends EventEmitter {
|
|
|
346
368
|
|
|
347
369
|
stop() {
|
|
348
370
|
this.stopReconnectFlag = true;
|
|
371
|
+
this.reconnectLoopPromise = null;
|
|
349
372
|
if (this.reconnectTimer) {
|
|
350
373
|
clearTimeout(this.reconnectTimer);
|
|
351
374
|
this.reconnectTimer = null;
|
package/src/tool-registry.js
CHANGED
|
@@ -153,7 +153,7 @@ class ToolRegistry {
|
|
|
153
153
|
|
|
154
154
|
this._register(new Tool(
|
|
155
155
|
'ce_read_memory',
|
|
156
|
-
'Read a single memory value. Returns: {
|
|
156
|
+
'Read a single memory value. Returns: {value}. ' +
|
|
157
157
|
'For reading multiple addresses, use ce_read_memory_batch instead for better performance.',
|
|
158
158
|
'read_memory',
|
|
159
159
|
ToolCategory.MEMORY,
|
|
@@ -169,7 +169,7 @@ class ToolRegistry {
|
|
|
169
169
|
'[PERFORMANCE] Read multiple memory addresses in ONE request. ' +
|
|
170
170
|
'ALWAYS prefer this over multiple ce_read_memory calls for better performance. ' +
|
|
171
171
|
'Example: [{"address": "game.exe+100", "type": "dword", "id": "hp"}, {"address": "game.exe+104", "type": "float", "id": "mp"}]. ' +
|
|
172
|
-
'Returns: {results:
|
|
172
|
+
'Returns: {results: {id_or_address: {value, error?}}}.',
|
|
173
173
|
'read_memory_batch',
|
|
174
174
|
ToolCategory.MEMORY,
|
|
175
175
|
[new ToolParam('requests', 'array', 'Array of {address, type, id?, size?} objects', true)]
|
|
@@ -177,7 +177,7 @@ class ToolRegistry {
|
|
|
177
177
|
|
|
178
178
|
this._register(new Tool(
|
|
179
179
|
'ce_write_memory',
|
|
180
|
-
'Write memory value. Returns: {success
|
|
180
|
+
'Write memory value. Returns: {success, address}.',
|
|
181
181
|
'write_memory',
|
|
182
182
|
ToolCategory.MEMORY,
|
|
183
183
|
[
|
|
@@ -298,7 +298,7 @@ class ToolRegistry {
|
|
|
298
298
|
|
|
299
299
|
this._register(new Tool(
|
|
300
300
|
'ce_enum_modules',
|
|
301
|
-
'List all loaded modules (DLLs). Returns: {count, modules: [{name,
|
|
301
|
+
'List all loaded modules (DLLs). Returns: {count, modules: [{name, address, size, path, source}], used_fallback}.',
|
|
302
302
|
'enum_modules',
|
|
303
303
|
ToolCategory.SCANNING
|
|
304
304
|
));
|
|
@@ -375,7 +375,7 @@ class ToolRegistry {
|
|
|
375
375
|
'USE WHEN: You already have base+offsets and want to verify the chain works or read the value. ' +
|
|
376
376
|
'Returns CE notation like \'[[game.exe+123]+10]+20\' for easy copy to CE. ' +
|
|
377
377
|
'NOT FOR: Discovering pointer paths (use ce_find_pointer_path for discovery). ' +
|
|
378
|
-
'Returns: {base, offsets,
|
|
378
|
+
'Returns: {success, base, offsets, finalAddress, ceNotation, chain: [{level, address, symbol, offset, ptrValue?}], partialCENotation?, failedAtLevel?, error?}.',
|
|
379
379
|
'resolve_pointer',
|
|
380
380
|
ToolCategory.SYMBOLS,
|
|
381
381
|
[
|
|
@@ -406,7 +406,7 @@ class ToolRegistry {
|
|
|
406
406
|
|
|
407
407
|
this._register(new Tool(
|
|
408
408
|
'ce_get_instruction_info',
|
|
409
|
-
'Get detailed instruction info. Returns: {address, opcode,
|
|
409
|
+
'Get detailed instruction info. Returns: {address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}.',
|
|
410
410
|
'get_instruction_info',
|
|
411
411
|
ToolCategory.DEBUG,
|
|
412
412
|
[this.ADDR_PARAM]
|
|
@@ -737,7 +737,7 @@ class ToolRegistry {
|
|
|
737
737
|
'Generate unique AOB signature for code at an address. ' +
|
|
738
738
|
'USE WHEN: Creating version-independent scripts, documenting code locations for future game updates. ' +
|
|
739
739
|
'WORKFLOW: Generate signature here -> after game update, use ce_aob_scan to relocate. ' +
|
|
740
|
-
'Returns: {address, signature,
|
|
740
|
+
'Returns: {address, signature, offset_from_start, byte_count, usage_hint}.',
|
|
741
741
|
'generate_signature',
|
|
742
742
|
ToolCategory.ANALYSIS,
|
|
743
743
|
[this.ADDR_PARAM]
|
|
@@ -822,6 +822,7 @@ class ToolRegistry {
|
|
|
822
822
|
[
|
|
823
823
|
this.ADDR_PARAM,
|
|
824
824
|
new ToolParam('args', 'array', 'Function arguments (up to 4 for fastcall). Can be integers or address expressions.', false, []),
|
|
825
|
+
new ToolParam('call_method', 'integer', 'Optional calling method for executeCodeEx (e.g. 0=default/stdcall-like). If omitted, bridge auto-fallbacks for compatibility.'),
|
|
825
826
|
new ToolParam('timeout', 'integer', 'Timeout in milliseconds', false, 5000),
|
|
826
827
|
new ToolParam('return_type', 'string', 'How to interpret return value', false, 'qword',
|
|
827
828
|
['byte', 'word', 'dword', 'qword', 'float', 'double', 'pointer']),
|