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 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() memscan.destroy() end)
1697
- return { count = 0, results = {}, error = "AOB Scan failed" }
1698
- end
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() memscan.destroy() end)
1804
- return { count = 0, results = {}, error = "Scan failed" }
1805
- end
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 and symbol:find("%.exe%+") ~= nil or symbol:find("%.dll%+") ~= nil
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() memscan.destroy() end)
1954
- error("First scan failed")
1955
- end
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 = symbol and (symbol:find("%.exe%+") ~= nil or symbol:find("%.dll%+") ~= nil)
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
- mzScan.destroy()
2345
- end
2346
- end
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 the function using CE's executeCodeEx
6878
- -- executeCodeEx(timeout, address, param1, param2, ...)
6879
- local ok, result = pcall(function()
6880
- return executeCodeEx(timeout, funcAddr,
6881
- resolvedArgs[1], resolvedArgs[2], resolvedArgs[3], resolvedArgs[4])
6882
- end)
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 all zombie resources (breakpoints, traces, etc.)
7664
- Utils.cleanupZombieState()
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 = result.success === false && !hasPartialResult;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cheatengine",
3
- "version": "5.8.29",
3
+ "version": "5.8.30",
4
4
  "description": "Cheat Engine MCP Server - AI-assisted reverse engineering bridge",
5
5
  "main": "ce_mcp_server.js",
6
6
  "bin": {
@@ -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
- while (!this.stopReconnectFlag) {
136
- if (!this.isValid()) {
137
- this.connectionAttempts++;
138
- this.healthMonitor.recordConnectionAttempt();
139
-
140
- if (await this._tryConnectOnce()) {
141
- backoff = 0.5;
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 = Math.min(backoff * 1.5, maxBackoff);
150
+ backoff = 0.5;
144
151
  }
145
- } else {
146
- backoff = 0.5;
147
- }
148
152
 
149
- await this._sleep(backoff * 1000);
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.reconnectTimer) return;
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
- const closeHandler = () => {
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 (this.socket) {
301
- this.socket.once('close', closeHandler);
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;
@@ -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: {address, type, value}. ' +
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: [{id, address, type, value, error?}]}.',
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: true, address, bytes_written}.',
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, base, size}]}.',
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, final_address, ceNotation, chain: [{level, address, value, symbol}]}.',
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, parameters, bytes, size, isCall, isJump, isRet, isConditionalJump, readsMemory, writesMemory}.',
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, mask, unique}.',
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']),