cheatengine
Advanced tools
+2
-3
@@ -34,3 +34,2 @@ #!/usr/bin/env node | ||
| try { | ||
| const fs = require('fs'); | ||
| // Try to check if pipe exists (Windows specific) | ||
@@ -274,4 +273,4 @@ const testClient = new PipeClient(); | ||
| if (typeof result === 'object' && result !== null && result.error) { | ||
| const hasPartialResult = ['chain', 'path', 'partialCENotation', 'finalAddress'].some(k => k in result); | ||
| isError = result.success === false && !hasPartialResult; | ||
| const hasPartialResult = ['chain', 'path', 'partialCENotation', 'finalAddress', 'final_address', 'ceNotation', 'ce_pointer_notation'].some(k => k in result); | ||
| isError = !hasPartialResult; | ||
| } | ||
@@ -278,0 +277,0 @@ |
+1
-1
| { | ||
| "name": "cheatengine", | ||
| "version": "5.8.29", | ||
| "version": "5.8.30", | ||
| "description": "Cheat Engine MCP Server - AI-assisted reverse engineering bridge", | ||
@@ -5,0 +5,0 @@ "main": "ce_mcp_server.js", |
+20
-1
@@ -142,2 +142,4 @@ [English](README.md) | 中文 | ||
| **返回:** `{value}` | ||
| #### `ce_read_memory_batch(requests)` | ||
@@ -149,2 +151,4 @@ 一次调用读取多个地址。**始终优先使用此方法而非多次调用 ce_read_memory。** | ||
| **返回:** `{results: {id_or_address: {value, error?}}}` | ||
| #### `ce_write_memory(address, type, value)` | ||
@@ -158,2 +162,4 @@ 向内存写入值。 | ||
| **返回:** `{success, address}` | ||
| --- | ||
@@ -214,2 +220,4 @@ | ||
| **返回:** `{count, modules: [{name, address, size, path, source}], used_fallback}` | ||
| --- | ||
@@ -257,2 +265,4 @@ | ||
| **返回:** `{address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}` | ||
| #### `ce_analyze_code(address, count?)` | ||
@@ -323,2 +333,4 @@ 代码块的静态分析 (调用、跳转、引用)。 | ||
| **返回:** `{address, signature, offset_from_start, byte_count, usage_hint}` | ||
| --- | ||
@@ -355,5 +367,12 @@ | ||
| #### `ce_call_function(address, args?, return_type?, timeout?)` | ||
| #### `ce_call_function(address, args?, call_method?, return_type?, timeout?)` | ||
| 在目标进程中调用函数。**警告: 执行真实代码!** | ||
| **参数:** | ||
| - `address` (string, 必需): 目标函数地址 | ||
| - `args` (array, 可选): 最多 4 个参数(整数或地址表达式) | ||
| - `call_method` (integer, 可选): `executeCodeEx` 调用方式(如 `0`)。省略时 bridge 会自动走兼容回退 | ||
| - `return_type` (string, 可选): `byte`, `word`, `dword`, `qword`, `float`, `double`, `pointer`(默认: `qword`) | ||
| - `timeout` (integer, 可选): 超时毫秒数(默认: `5000`) | ||
| --- | ||
@@ -360,0 +379,0 @@ |
+20
-1
@@ -142,2 +142,4 @@ English | [中文](README_CN.md) | ||
| **Returns:** `{value}` | ||
| #### `ce_read_memory_batch(requests)` | ||
@@ -149,2 +151,4 @@ Read multiple addresses in one call. **Always prefer this over multiple ce_read_memory calls.** | ||
| **Returns:** `{results: {id_or_address: {value, error?}}}` | ||
| #### `ce_write_memory(address, type, value)` | ||
@@ -158,2 +162,4 @@ Write a value to memory. | ||
| **Returns:** `{success, address}` | ||
| --- | ||
@@ -214,2 +220,4 @@ | ||
| **Returns:** `{count, modules: [{name, address, size, path, source}], used_fallback}` | ||
| --- | ||
@@ -257,2 +265,4 @@ | ||
| **Returns:** `{address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}` | ||
| #### `ce_analyze_code(address, count?)` | ||
@@ -323,2 +333,4 @@ Static analysis of code block (calls, jumps, refs). | ||
| **Returns:** `{address, signature, offset_from_start, byte_count, usage_hint}` | ||
| --- | ||
@@ -355,5 +367,12 @@ | ||
| #### `ce_call_function(address, args?, return_type?, timeout?)` | ||
| #### `ce_call_function(address, args?, call_method?, return_type?, timeout?)` | ||
| Call a function in the target process. **WARNING: Executes real code!** | ||
| **Parameters:** | ||
| - `address` (string, required): Target function address | ||
| - `args` (array, optional): Up to 4 arguments (integers or address expressions) | ||
| - `call_method` (integer, optional): Calling method for `executeCodeEx` (e.g. `0`). If omitted, bridge auto-fallbacks for compatibility | ||
| - `return_type` (string, optional): `byte`, `word`, `dword`, `qword`, `float`, `double`, `pointer` (default: `qword`) | ||
| - `timeout` (integer, optional): Timeout in milliseconds (default: `5000`) | ||
| --- | ||
@@ -360,0 +379,0 @@ |
+44
-21
@@ -8,3 +8,3 @@ /** | ||
| const { EventEmitter } = require('events'); | ||
| const { Config, HealthMonitor, Logger, log } = require('./base'); | ||
| const { Config, HealthMonitor, TimeoutError, Logger, log } = require('./base'); | ||
@@ -25,2 +25,3 @@ // Windows error codes | ||
| this.reconnectTimer = null; | ||
| this.reconnectLoopPromise = null; | ||
| this.stopReconnectFlag = false; | ||
@@ -102,2 +103,4 @@ this.connected = false; | ||
| this.responseBuffer = Buffer.alloc(0); | ||
| this._close(); | ||
| this.healthMonitor.recordError(`Invalid response frame length: ${respLen}`); | ||
| return; | ||
@@ -138,17 +141,21 @@ } | ||
| while (!this.stopReconnectFlag) { | ||
| if (!this.isValid()) { | ||
| this.connectionAttempts++; | ||
| this.healthMonitor.recordConnectionAttempt(); | ||
| try { | ||
| while (!this.stopReconnectFlag) { | ||
| if (!this.isValid()) { | ||
| this.connectionAttempts++; | ||
| this.healthMonitor.recordConnectionAttempt(); | ||
| if (await this._tryConnectOnce()) { | ||
| if (await this._tryConnectOnce()) { | ||
| backoff = 0.5; | ||
| } else { | ||
| backoff = Math.min(backoff * 1.5, maxBackoff); | ||
| } | ||
| } else { | ||
| backoff = 0.5; | ||
| } else { | ||
| backoff = Math.min(backoff * 1.5, maxBackoff); | ||
| } | ||
| } else { | ||
| backoff = 0.5; | ||
| await this._sleep(backoff * 1000); | ||
| } | ||
| await this._sleep(backoff * 1000); | ||
| } finally { | ||
| this.reconnectLoopPromise = null; | ||
| } | ||
@@ -162,5 +169,5 @@ } | ||
| startBackgroundReconnect() { | ||
| if (this.reconnectTimer) return; | ||
| if (this.reconnectLoopPromise) return; | ||
| this.stopReconnectFlag = false; | ||
| this._reconnectLoop(); | ||
| this.reconnectLoopPromise = this._reconnectLoop(); | ||
| } | ||
@@ -253,5 +260,4 @@ | ||
| // Send request | ||
| this.socket.write(lenBuffer); | ||
| this.socket.write(jsonBytes); | ||
| // Send request as one framed buffer to avoid split-frame races | ||
| this.socket.write(Buffer.concat([lenBuffer, jsonBytes])); | ||
@@ -277,3 +283,11 @@ // Wait for response | ||
| return new Promise((resolve, reject) => { | ||
| const socket = this.socket; | ||
| let closeHandler = null; | ||
| this.pendingResponse = { resolve, reject }; | ||
| const cleanup = () => { | ||
| if (socket && closeHandler) { | ||
| socket.removeListener('close', closeHandler); | ||
| } | ||
| }; | ||
@@ -291,2 +305,3 @@ // Check if we already have data in buffer (race condition fix) | ||
| this.pendingResponse = null; | ||
| cleanup(); | ||
| resolve(result); | ||
@@ -296,2 +311,3 @@ return; | ||
| this.pendingResponse = null; | ||
| cleanup(); | ||
| reject(err); | ||
@@ -304,9 +320,10 @@ return; | ||
| // Set up a one-time close handler to reject if socket closes while waiting | ||
| const closeHandler = () => { | ||
| closeHandler = () => { | ||
| this.pendingResponse = null; | ||
| cleanup(); | ||
| reject(new Error('Socket closed while waiting for response')); | ||
| }; | ||
| if (this.socket) { | ||
| this.socket.once('close', closeHandler); | ||
| if (socket) { | ||
| socket.once('close', closeHandler); | ||
| } | ||
@@ -321,4 +338,5 @@ }); | ||
| // Create timeout promise | ||
| let timeoutHandle = null; | ||
| const timeoutPromise = new Promise((_, reject) => { | ||
| setTimeout(() => { | ||
| timeoutHandle = setTimeout(() => { | ||
| const elapsedTime = (Date.now() - startTime) / 1000; | ||
@@ -351,2 +369,6 @@ const errorMsg = `Tool execution timed out after ${elapsedTime.toFixed(2)}s (timeout: ${timeoutSeconds}s)`; | ||
| return { error: err.message }; | ||
| } finally { | ||
| if (timeoutHandle) { | ||
| clearTimeout(timeoutHandle); | ||
| } | ||
| } | ||
@@ -361,2 +383,3 @@ } | ||
| this.stopReconnectFlag = true; | ||
| this.reconnectLoopPromise = null; | ||
| if (this.reconnectTimer) { | ||
@@ -363,0 +386,0 @@ clearTimeout(this.reconnectTimer); |
@@ -156,3 +156,3 @@ /** | ||
| 'ce_read_memory', | ||
| 'Read a single memory value. Returns: {address, type, value}. ' + | ||
| 'Read a single memory value. Returns: {value}. ' + | ||
| 'For reading multiple addresses, use ce_read_memory_batch instead for better performance.', | ||
@@ -173,3 +173,3 @@ 'read_memory', | ||
| 'Example: [{"address": "game.exe+100", "type": "dword", "id": "hp"}, {"address": "game.exe+104", "type": "float", "id": "mp"}]. ' + | ||
| 'Returns: {results: [{id, address, type, value, error?}]}.', | ||
| 'Returns: {results: {id_or_address: {value, error?}}}.', | ||
| 'read_memory_batch', | ||
@@ -182,3 +182,3 @@ ToolCategory.MEMORY, | ||
| 'ce_write_memory', | ||
| 'Write memory value. Returns: {success: true, address, bytes_written}.', | ||
| 'Write memory value. Returns: {success, address}.', | ||
| 'write_memory', | ||
@@ -304,3 +304,3 @@ ToolCategory.MEMORY, | ||
| 'ce_enum_modules', | ||
| 'List all loaded modules (DLLs). Returns: {count, modules: [{name, base, size}]}.', | ||
| 'List all loaded modules (DLLs). Returns: {count, modules: [{name, address, size, path, source}], used_fallback}.', | ||
| 'enum_modules', | ||
@@ -382,3 +382,3 @@ ToolCategory.SCANNING | ||
| 'NOT FOR: Discovering pointer paths (use ce_find_pointer_path for discovery). ' + | ||
| 'Returns: {base, offsets, final_address, ceNotation, chain: [{level, address, value, symbol}]}.', | ||
| 'Returns: {success, base, offsets, finalAddress, ceNotation, chain: [{level, address, symbol, offset, ptrValue?}], partialCENotation?, failedAtLevel?, error?}.', | ||
| 'resolve_pointer', | ||
@@ -414,3 +414,3 @@ ToolCategory.SYMBOLS, | ||
| 'ce_get_instruction_info', | ||
| 'Get detailed instruction info. Returns: {address, opcode, parameters, bytes, size, isCall, isJump, isRet, isConditionalJump, readsMemory, writesMemory}.', | ||
| 'Get detailed instruction info. Returns: {address, opcode, params, bytes, bytesStr, size, isCall, isJump, isRet, isConditionalJump, parameterValue}.', | ||
| 'get_instruction_info', | ||
@@ -746,3 +746,3 @@ ToolCategory.DEBUG, | ||
| 'WORKFLOW: Generate signature here -> after game update, use ce_aob_scan to relocate. ' + | ||
| 'Returns: {address, signature, mask, unique}.', | ||
| 'Returns: {address, signature, offset_from_start, byte_count, usage_hint}.', | ||
| 'generate_signature', | ||
@@ -832,2 +832,3 @@ ToolCategory.ANALYSIS, | ||
| new ToolParam('args', 'array', 'Function arguments (up to 4 for fastcall). Can be integers or address expressions.', false, []), | ||
| new ToolParam('call_method', 'integer', 'Optional calling method for executeCodeEx (e.g. 0=default/stdcall-like). If omitted, bridge auto-fallbacks for compatibility.'), | ||
| new ToolParam('timeout', 'integer', 'Timeout in milliseconds', false, 5000), | ||
@@ -834,0 +835,0 @@ new ToolParam('return_type', 'string', 'How to interpret return value', false, 'qword', |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
386070
1.09%1676
1.33%472
4.19%6
-14.29%