cheatengine 5.8.28 → 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 +85 -88
- package/ce_mcp_server.js +2 -3
- package/package.json +1 -1
- package/src/pipe-client.js +45 -22
- package/src/tool-registry.js +10 -29
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
|
@@ -1500,46 +1500,9 @@ Handlers.execute_lua = function(p)
|
|
|
1500
1500
|
local ok, result = pcall(fn)
|
|
1501
1501
|
if not ok then error("Runtime error: " .. tostring(result)) end
|
|
1502
1502
|
return { result = result }
|
|
1503
|
-
end
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
local mv = getMemoryViewForm()
|
|
1507
|
-
if mv then
|
|
1508
|
-
local dv = mv.DisassemblerView
|
|
1509
|
-
if dv and dv.SelectedAddress then
|
|
1510
|
-
return { address = Utils.formatHex(dv.SelectedAddress) }
|
|
1511
|
-
end
|
|
1512
|
-
end
|
|
1513
|
-
error("No address selected in Memory View")
|
|
1514
|
-
end
|
|
1515
|
-
|
|
1516
|
-
-- Handler: get_logs - Retrieve log entries with filtering
|
|
1517
|
-
Handlers.get_logs = function(p)
|
|
1518
|
-
local count = p.count or 100
|
|
1519
|
-
local minLevel = p.min_level or p.minLevel or LogLevel.DEBUG
|
|
1520
|
-
|
|
1521
|
-
-- Convert string level to number if needed
|
|
1522
|
-
if type(minLevel) == "string" then
|
|
1523
|
-
local levelMap = {
|
|
1524
|
-
debug = LogLevel.DEBUG,
|
|
1525
|
-
info = LogLevel.INFO,
|
|
1526
|
-
warn = LogLevel.WARN,
|
|
1527
|
-
error = LogLevel.ERROR
|
|
1528
|
-
}
|
|
1529
|
-
minLevel = levelMap[minLevel:lower()] or LogLevel.DEBUG
|
|
1530
|
-
end
|
|
1531
|
-
|
|
1532
|
-
local entries = Logger.getEntries(count, minLevel)
|
|
1533
|
-
local stats = Logger.getStats()
|
|
1534
|
-
|
|
1535
|
-
return {
|
|
1536
|
-
entries = entries,
|
|
1537
|
-
count = #entries,
|
|
1538
|
-
stats = stats
|
|
1539
|
-
}
|
|
1540
|
-
end
|
|
1541
|
-
|
|
1542
|
-
-- Handler: stats - Return comprehensive execution statistics
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
-- Handler: stats - Return comprehensive execution statistics
|
|
1543
1506
|
Handlers.stats = function(p)
|
|
1544
1507
|
local metrics = Metrics.getSummary()
|
|
1545
1508
|
local logStats = Logger.getStats()
|
|
@@ -1729,10 +1692,11 @@ Handlers.aob_scan = function(p)
|
|
|
1729
1692
|
scanOk = true
|
|
1730
1693
|
end)
|
|
1731
1694
|
|
|
1732
|
-
if not scanOk then
|
|
1733
|
-
pcall(function()
|
|
1734
|
-
|
|
1735
|
-
|
|
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
|
|
1736
1700
|
|
|
1737
1701
|
foundList.initialize()
|
|
1738
1702
|
local totalCount = foundList.Count or 0
|
|
@@ -1836,10 +1800,11 @@ Handlers.value_scan = function(p)
|
|
|
1836
1800
|
memscan.waitTillDone()
|
|
1837
1801
|
end)
|
|
1838
1802
|
|
|
1839
|
-
if not scanOk then
|
|
1840
|
-
pcall(function()
|
|
1841
|
-
|
|
1842
|
-
|
|
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
|
|
1843
1808
|
|
|
1844
1809
|
foundList.initialize()
|
|
1845
1810
|
|
|
@@ -1860,11 +1825,12 @@ Handlers.value_scan = function(p)
|
|
|
1860
1825
|
isInModule = inModule(addrNum)
|
|
1861
1826
|
end)
|
|
1862
1827
|
|
|
1863
|
-
table_insert(results, {
|
|
1864
|
-
address = Utils.formatHex(addrNum),
|
|
1865
|
-
symbol = symbol,
|
|
1866
|
-
isStatic = symbol
|
|
1867
|
-
|
|
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
|
+
})
|
|
1868
1834
|
end
|
|
1869
1835
|
end
|
|
1870
1836
|
|
|
@@ -1986,10 +1952,11 @@ Handlers.scan_new = function(p)
|
|
|
1986
1952
|
memscan.waitTillDone()
|
|
1987
1953
|
end)
|
|
1988
1954
|
|
|
1989
|
-
if not scanOk then
|
|
1990
|
-
pcall(function()
|
|
1991
|
-
|
|
1992
|
-
|
|
1955
|
+
if not scanOk then
|
|
1956
|
+
pcall(function() foundList.deinitialize() end)
|
|
1957
|
+
pcall(function() memscan.destroy() end)
|
|
1958
|
+
error("First scan failed")
|
|
1959
|
+
end
|
|
1993
1960
|
|
|
1994
1961
|
foundList.initialize()
|
|
1995
1962
|
local totalCount = foundList.Count or 0
|
|
@@ -2142,13 +2109,14 @@ Handlers.scan_results = function(p)
|
|
|
2142
2109
|
end
|
|
2143
2110
|
end)
|
|
2144
2111
|
|
|
2145
|
-
table_insert(results, {
|
|
2146
|
-
index = i,
|
|
2147
|
-
address = Utils.formatHex(addrNum),
|
|
2148
|
-
symbol = symbol,
|
|
2149
|
-
value = currentValue,
|
|
2150
|
-
isStatic =
|
|
2151
|
-
|
|
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
|
+
})
|
|
2152
2120
|
end
|
|
2153
2121
|
end
|
|
2154
2122
|
|
|
@@ -2333,10 +2301,10 @@ Handlers.enum_modules = function()
|
|
|
2333
2301
|
if #r == 0 then
|
|
2334
2302
|
usedFallback = true
|
|
2335
2303
|
local mzScan = AOBScan("4D 5A 90 00 03 00 00 00") -- MZ PE header
|
|
2336
|
-
if mzScan and mzScan.Count > 0 then
|
|
2337
|
-
for i = 0, math_min(mzScan.Count - 1, 50) do
|
|
2338
|
-
local addr = tonumber(mzScan[i], 16)
|
|
2339
|
-
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
|
|
2340
2308
|
local peOffset = readInteger(addr + 0x3C)
|
|
2341
2309
|
local moduleSize = 0
|
|
2342
2310
|
local realName = nil
|
|
@@ -2376,11 +2344,13 @@ Handlers.enum_modules = function()
|
|
|
2376
2344
|
path = "",
|
|
2377
2345
|
source = realName and "export_directory" or "aob_fallback"
|
|
2378
2346
|
})
|
|
2379
|
-
end
|
|
2380
|
-
end
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2347
|
+
end
|
|
2348
|
+
end
|
|
2349
|
+
end
|
|
2350
|
+
if mzScan then
|
|
2351
|
+
mzScan.destroy()
|
|
2352
|
+
end
|
|
2353
|
+
end
|
|
2384
2354
|
|
|
2385
2355
|
return {
|
|
2386
2356
|
modules = r,
|
|
@@ -6911,12 +6881,25 @@ Handlers.call_function = function(p)
|
|
|
6911
6881
|
table_insert(resolvedArgs, 0)
|
|
6912
6882
|
end
|
|
6913
6883
|
|
|
6914
|
-
-- Execute
|
|
6915
|
-
--
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
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
|
|
6920
6903
|
|
|
6921
6904
|
if not ok then
|
|
6922
6905
|
return {
|
|
@@ -7473,9 +7456,8 @@ local SYNC_COMMANDS = {
|
|
|
7473
7456
|
register_symbol = true,
|
|
7474
7457
|
unregister_symbol = true,
|
|
7475
7458
|
allocate_memory = true,
|
|
7476
|
-
execute_lua = true,
|
|
7477
|
-
|
|
7478
|
-
-- Hook commands
|
|
7459
|
+
execute_lua = true,
|
|
7460
|
+
-- Hook commands
|
|
7479
7461
|
hook_function = true,
|
|
7480
7462
|
unhook_function = true,
|
|
7481
7463
|
-- Emulation commands
|
|
@@ -7694,12 +7676,27 @@ end
|
|
|
7694
7676
|
-- ============ Server Lifecycle ============
|
|
7695
7677
|
local Server = {}
|
|
7696
7678
|
|
|
7697
|
-
function Server.stop()
|
|
7698
|
-
Utils.debugPrint("Stopping server...")
|
|
7699
|
-
Context.serverRunning = false
|
|
7700
|
-
|
|
7701
|
-
-- Cleanup
|
|
7702
|
-
|
|
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()
|
|
7703
7700
|
|
|
7704
7701
|
-- Clear address cache
|
|
7705
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool Registry for MCP Tools
|
|
3
|
-
* Defines all
|
|
3
|
+
* Defines all 55 tools available in the Cheat Engine MCP Bridge
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const ToolCategory = {
|
|
@@ -108,32 +108,12 @@ class ToolRegistry {
|
|
|
108
108
|
|
|
109
109
|
this._register(new Tool(
|
|
110
110
|
'ce_execute_lua',
|
|
111
|
-
'Execute
|
|
111
|
+
'Execute Lua code with full access to CE APIs. Returns {result}.',
|
|
112
112
|
'execute_lua',
|
|
113
113
|
ToolCategory.SYSTEM,
|
|
114
114
|
[new ToolParam('code', 'string', 'Lua code to execute', true)]
|
|
115
115
|
));
|
|
116
116
|
|
|
117
|
-
this._register(new Tool(
|
|
118
|
-
'ce_get_selected_address',
|
|
119
|
-
'Get the currently selected address in CE Memory View/Disassembler',
|
|
120
|
-
'get_selected_address',
|
|
121
|
-
ToolCategory.SYSTEM
|
|
122
|
-
));
|
|
123
|
-
|
|
124
|
-
this._register(new Tool(
|
|
125
|
-
'ce_get_logs',
|
|
126
|
-
'Get log entries from the Cheat Engine MCP Bridge Lua side. ' +
|
|
127
|
-
'Returns: {entries: [{timestamp, level, category, message, data}], count}. ' +
|
|
128
|
-
'Useful for debugging and monitoring bridge operations.',
|
|
129
|
-
'get_logs',
|
|
130
|
-
ToolCategory.SYSTEM,
|
|
131
|
-
[
|
|
132
|
-
new ToolParam('count', 'integer', 'Maximum number of log entries to return (default: 100)', false, 100),
|
|
133
|
-
new ToolParam('min_level', 'integer', 'Minimum log level to include: 1=DEBUG, 2=INFO, 3=WARN, 4=ERROR (default: 1)', false, 1),
|
|
134
|
-
]
|
|
135
|
-
));
|
|
136
|
-
|
|
137
117
|
this._register(new Tool(
|
|
138
118
|
'ce_list_processes',
|
|
139
119
|
'List running processes. Use before ce_attach_process to find available targets. ' +
|
|
@@ -173,7 +153,7 @@ class ToolRegistry {
|
|
|
173
153
|
|
|
174
154
|
this._register(new Tool(
|
|
175
155
|
'ce_read_memory',
|
|
176
|
-
'Read a single memory value. Returns: {
|
|
156
|
+
'Read a single memory value. Returns: {value}. ' +
|
|
177
157
|
'For reading multiple addresses, use ce_read_memory_batch instead for better performance.',
|
|
178
158
|
'read_memory',
|
|
179
159
|
ToolCategory.MEMORY,
|
|
@@ -189,7 +169,7 @@ class ToolRegistry {
|
|
|
189
169
|
'[PERFORMANCE] Read multiple memory addresses in ONE request. ' +
|
|
190
170
|
'ALWAYS prefer this over multiple ce_read_memory calls for better performance. ' +
|
|
191
171
|
'Example: [{"address": "game.exe+100", "type": "dword", "id": "hp"}, {"address": "game.exe+104", "type": "float", "id": "mp"}]. ' +
|
|
192
|
-
'Returns: {results:
|
|
172
|
+
'Returns: {results: {id_or_address: {value, error?}}}.',
|
|
193
173
|
'read_memory_batch',
|
|
194
174
|
ToolCategory.MEMORY,
|
|
195
175
|
[new ToolParam('requests', 'array', 'Array of {address, type, id?, size?} objects', true)]
|
|
@@ -197,7 +177,7 @@ class ToolRegistry {
|
|
|
197
177
|
|
|
198
178
|
this._register(new Tool(
|
|
199
179
|
'ce_write_memory',
|
|
200
|
-
'Write memory value. Returns: {success
|
|
180
|
+
'Write memory value. Returns: {success, address}.',
|
|
201
181
|
'write_memory',
|
|
202
182
|
ToolCategory.MEMORY,
|
|
203
183
|
[
|
|
@@ -318,7 +298,7 @@ class ToolRegistry {
|
|
|
318
298
|
|
|
319
299
|
this._register(new Tool(
|
|
320
300
|
'ce_enum_modules',
|
|
321
|
-
'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}.',
|
|
322
302
|
'enum_modules',
|
|
323
303
|
ToolCategory.SCANNING
|
|
324
304
|
));
|
|
@@ -395,7 +375,7 @@ class ToolRegistry {
|
|
|
395
375
|
'USE WHEN: You already have base+offsets and want to verify the chain works or read the value. ' +
|
|
396
376
|
'Returns CE notation like \'[[game.exe+123]+10]+20\' for easy copy to CE. ' +
|
|
397
377
|
'NOT FOR: Discovering pointer paths (use ce_find_pointer_path for discovery). ' +
|
|
398
|
-
'Returns: {base, offsets,
|
|
378
|
+
'Returns: {success, base, offsets, finalAddress, ceNotation, chain: [{level, address, symbol, offset, ptrValue?}], partialCENotation?, failedAtLevel?, error?}.',
|
|
399
379
|
'resolve_pointer',
|
|
400
380
|
ToolCategory.SYMBOLS,
|
|
401
381
|
[
|
|
@@ -426,7 +406,7 @@ class ToolRegistry {
|
|
|
426
406
|
|
|
427
407
|
this._register(new Tool(
|
|
428
408
|
'ce_get_instruction_info',
|
|
429
|
-
'Get detailed instruction info. Returns: {address, opcode,
|
|
409
|
+
'Get detailed instruction info. Returns: {address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}.',
|
|
430
410
|
'get_instruction_info',
|
|
431
411
|
ToolCategory.DEBUG,
|
|
432
412
|
[this.ADDR_PARAM]
|
|
@@ -757,7 +737,7 @@ class ToolRegistry {
|
|
|
757
737
|
'Generate unique AOB signature for code at an address. ' +
|
|
758
738
|
'USE WHEN: Creating version-independent scripts, documenting code locations for future game updates. ' +
|
|
759
739
|
'WORKFLOW: Generate signature here -> after game update, use ce_aob_scan to relocate. ' +
|
|
760
|
-
'Returns: {address, signature,
|
|
740
|
+
'Returns: {address, signature, offset_from_start, byte_count, usage_hint}.',
|
|
761
741
|
'generate_signature',
|
|
762
742
|
ToolCategory.ANALYSIS,
|
|
763
743
|
[this.ADDR_PARAM]
|
|
@@ -842,6 +822,7 @@ class ToolRegistry {
|
|
|
842
822
|
[
|
|
843
823
|
this.ADDR_PARAM,
|
|
844
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.'),
|
|
845
826
|
new ToolParam('timeout', 'integer', 'Timeout in milliseconds', false, 5000),
|
|
846
827
|
new ToolParam('return_type', 'string', 'How to interpret return value', false, 'qword',
|
|
847
828
|
['byte', 'word', 'dword', 'qword', 'float', 'double', 'pointer']),
|