cheatengine 5.8.26 → 5.8.28
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 +7 -5
- package/README_CN.md +52 -50
- package/ce_mcp_bridge.lua +343 -283
- package/package.json +1 -1
- package/src/tool-registry.js +4 -3
package/README.md
CHANGED
|
@@ -25,13 +25,13 @@ AI <--MCP/JSON-RPC--> ce_mcp_server.js <--Named Pipe--> ce_mcp_bridge.lua (CE)
|
|
|
25
25
|
dofile([[D:\path\to\ce_mcp_bridge.lua]])
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
3. **Configure MCP**
|
|
28
|
+
3. **Configure MCP** :
|
|
29
29
|
```json
|
|
30
30
|
{
|
|
31
31
|
"mcpServers": {
|
|
32
32
|
"cheat-engine": {
|
|
33
33
|
"command": "npx",
|
|
34
|
-
"args": ["
|
|
34
|
+
"args": ["-y", "cheatengine@latest"]
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -157,17 +157,19 @@ Write a value to memory.
|
|
|
157
157
|
|
|
158
158
|
### Scanning & Search
|
|
159
159
|
|
|
160
|
-
#### `ce_aob_scan(aob_string, module?, protection?, max_results?)`
|
|
160
|
+
#### `ce_aob_scan(aob_string, module?, protection?, start?, stop?, max_results?)`
|
|
161
161
|
Scan memory for Array of Bytes pattern. Supports `??` wildcards.
|
|
162
162
|
|
|
163
163
|
**Parameters:**
|
|
164
164
|
- `aob_string` (string, required): Pattern like `"48 89 5C 24 ?? 48 83 EC 20"`
|
|
165
165
|
- `module` (string, optional): Limit scan to module (e.g. `"game.exe"`)
|
|
166
166
|
- `protection` (string, optional): Memory protection flags (default: `"-C+X"`)
|
|
167
|
+
- `start` (string, optional): Start address if module not specified
|
|
168
|
+
- `stop` (string, optional): Stop address if module not specified
|
|
167
169
|
- `max_results` (integer, optional): Maximum results (default: 100)
|
|
168
170
|
|
|
169
171
|
#### `ce_value_scan(value, type, module?, protection?)`
|
|
170
|
-
Scan for a specific value. Useful for pointer tracing. **One-shot scan - for iterative scanning use Scan Sessions.**
|
|
172
|
+
Scan for a specific value. Useful for pointer tracing. Auto-aligns scan based on type for performance. **One-shot scan - for iterative scanning use Scan Sessions.**
|
|
171
173
|
|
|
172
174
|
**Parameters:**
|
|
173
175
|
- `value` (string, required): Value to search (e.g. `"0x255D5E758"` or `"12345"`)
|
|
@@ -182,7 +184,7 @@ Scan for a specific value. Useful for pointer tracing. **One-shot scan - for ite
|
|
|
182
184
|
Implements CE's core "First Scan → Next Scan" workflow with session management. Sessions auto-expire after 5 minutes of inactivity.
|
|
183
185
|
|
|
184
186
|
#### `ce_scan_new(value, type, module?, protection?)`
|
|
185
|
-
Start a new scan session.
|
|
187
|
+
Start a new scan session. Auto-aligns scan based on type for performance (4-byte for dword/float, 8-byte for qword/double).
|
|
186
188
|
|
|
187
189
|
#### `ce_scan_next(session_id, value, scan_type?, value2?)`
|
|
188
190
|
Continue scanning (filter) an existing session.
|
package/README_CN.md
CHANGED
|
@@ -7,7 +7,7 @@ MCP 桥接器,让 AI 助手直接控制 Cheat Engine 进行游戏修改和逆
|
|
|
7
7
|
## 架构
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
AI <--MCP/JSON-RPC--> ce_mcp_server.
|
|
10
|
+
AI <--MCP/JSON-RPC--> ce_mcp_server.js <--命名管道--> ce_mcp_bridge.lua (CE)
|
|
11
11
|
↑
|
|
12
12
|
后台自动重连
|
|
13
13
|
```
|
|
@@ -16,7 +16,7 @@ AI <--MCP/JSON-RPC--> ce_mcp_server.py <--命名管道--> ce_mcp_bridge.lua (CE)
|
|
|
16
16
|
|
|
17
17
|
### 快速开始 (NPX) - 推荐
|
|
18
18
|
|
|
19
|
-
1.
|
|
19
|
+
1. **前提条件**: Node.js 14+ (无需其他依赖项)
|
|
20
20
|
|
|
21
21
|
2. **在 CE 中加载** (二选一):
|
|
22
22
|
- **自动加载**: 将 `ce_mcp_bridge.lua` 复制到 CE 的 `autorun` 文件夹 (如 `D:\Cheat Engine\autorun\`)
|
|
@@ -25,13 +25,13 @@ AI <--MCP/JSON-RPC--> ce_mcp_server.py <--命名管道--> ce_mcp_bridge.lua (CE)
|
|
|
25
25
|
dofile([[D:\path\to\ce_mcp_bridge.lua]])
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
3. **配置 MCP**
|
|
28
|
+
3. **配置 MCP** :
|
|
29
29
|
```json
|
|
30
30
|
{
|
|
31
31
|
"mcpServers": {
|
|
32
32
|
"cheat-engine": {
|
|
33
33
|
"command": "npx",
|
|
34
|
-
"args": ["
|
|
34
|
+
"args": ["-y", "cheatengine@latest"]
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -45,8 +45,8 @@ AI <--MCP/JSON-RPC--> ce_mcp_server.py <--命名管道--> ce_mcp_bridge.lua (CE)
|
|
|
45
45
|
{
|
|
46
46
|
"mcpServers": {
|
|
47
47
|
"cheat-engine": {
|
|
48
|
-
"command": "
|
|
49
|
-
"args": ["D:/path/to/ce_mcp/ce_mcp_server.
|
|
48
|
+
"command": "node",
|
|
49
|
+
"args": ["D:/path/to/ce_mcp/ce_mcp_server.js"]
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -157,17 +157,19 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
157
157
|
|
|
158
158
|
### 扫描与搜索
|
|
159
159
|
|
|
160
|
-
#### `ce_aob_scan(aob_string, module?, protection?, max_results?)`
|
|
160
|
+
#### `ce_aob_scan(aob_string, module?, protection?, start?, stop?, max_results?)`
|
|
161
161
|
扫描内存中的字节数组模式。支持 `??` 通配符。
|
|
162
162
|
|
|
163
163
|
**参数:**
|
|
164
164
|
- `aob_string` (string, 必需): 模式如 `"48 89 5C 24 ?? 48 83 EC 20"`
|
|
165
165
|
- `module` (string, 可选): 限制扫描到模块 (如 `"game.exe"`)
|
|
166
166
|
- `protection` (string, 可选): 内存保护标志 (默认: `"-C+X"`)
|
|
167
|
+
- `start` (string, 可选): 起始地址(未指定 module 时使用)
|
|
168
|
+
- `stop` (string, 可选): 结束地址(未指定 module 时使用)
|
|
167
169
|
- `max_results` (integer, 可选): 最大结果数 (默认: 100)
|
|
168
170
|
|
|
169
171
|
#### `ce_value_scan(value, type, module?, protection?)`
|
|
170
|
-
|
|
172
|
+
扫描特定值。用于指针追踪。根据类型自动对齐扫描以优化性能。**一次性扫描 - 迭代扫描请使用扫描会话。**
|
|
171
173
|
|
|
172
174
|
**参数:**
|
|
173
175
|
- `value` (string, 必需): 要搜索的值 (如 `"0x255D5E758"` 或 `"12345"`)
|
|
@@ -182,7 +184,7 @@ Hook 名称现在会验证以防止 AA 脚本注入:
|
|
|
182
184
|
实现 CE 核心的 "首次扫描 → 再次扫描" 工作流,带会话管理。会话在 5 分钟不活动后自动过期。
|
|
183
185
|
|
|
184
186
|
#### `ce_scan_new(value, type, module?, protection?)`
|
|
185
|
-
|
|
187
|
+
开始新的扫描会话。根据类型自动对齐扫描以优化性能(dword/float 用 4 字节对齐,qword/double 用 8 字节对齐)。
|
|
186
188
|
|
|
187
189
|
#### `ce_scan_next(session_id, value, scan_type?, value2?)`
|
|
188
190
|
继续扫描(过滤)现有会话。
|
|
@@ -383,47 +385,47 @@ Hook 函数以拦截调用并捕获参数。
|
|
|
383
385
|
|
|
384
386
|
## 推荐工作流
|
|
385
387
|
|
|
386
|
-
### 指针追踪
|
|
387
|
-
```json
|
|
388
|
-
// 自动 (首选)
|
|
389
|
-
{"name": "ce_find_pointer_path", "arguments": {"address": "0x255D5E758", "user_prompted": true}}
|
|
390
|
-
// 返回: base_address, offsets, ce_pointer_notation
|
|
391
|
-
|
|
392
|
-
// 手动 (自动失败时)
|
|
393
|
-
// 1. 查找访问该地址的代码
|
|
394
|
-
{"name": "ce_find_what_accesses", "arguments": {"address": "0x255D5E758", "user_prompted": true}}
|
|
395
|
-
// 2. 从结果获取寄存器值 (如 RBX=0x255D5E658)
|
|
396
|
-
// 3. 搜索存储该值的指针
|
|
397
|
-
{"name": "ce_value_scan", "arguments": {"value": "0x255D5E658", "type": "qword"}}
|
|
398
|
-
// 4. 重复直到找到 game.exe+offset
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
### 函数分析
|
|
402
|
-
```json
|
|
403
|
-
// 1. 查找函数边界
|
|
404
|
-
{"name": "ce_find_function_boundaries", "arguments": {"address": "0x14587EDB0"}}
|
|
405
|
-
|
|
406
|
-
// 2. 跟踪执行
|
|
407
|
-
{"name": "ce_break_and_trace", "arguments": {"address": "0x14587EDB0", "max_steps": 100}}
|
|
408
|
-
|
|
409
|
-
// 3. 生成特征码用于更新
|
|
410
|
-
{"name": "ce_generate_signature", "arguments": {"address": "0x14587EDB0"}}
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
### 逆向未知代码
|
|
414
|
-
```json
|
|
415
|
-
// 1. 反汇编
|
|
416
|
-
{"name": "ce_disassemble", "arguments": {"address": "0x14587EDB0", "count": 20}}
|
|
417
|
-
|
|
418
|
-
// 2. 符号跟踪理解逻辑
|
|
419
|
-
{"name": "ce_symbolic_trace", "arguments": {"address": "0x14587EDB0", "initial_state": {"rcx": "this"}}}
|
|
420
|
-
|
|
421
|
-
// 3. 为复杂函数构建 CFG
|
|
422
|
-
{"name": "ce_build_cfg", "arguments": {"address": "0x14587EDB0"}}
|
|
423
|
-
|
|
424
|
-
// 4. 检测模式
|
|
425
|
-
{"name": "ce_detect_patterns", "arguments": {"address": "0x14587EDB0"}}
|
|
426
|
-
```
|
|
388
|
+
### 指针追踪
|
|
389
|
+
```json
|
|
390
|
+
// 自动 (首选)
|
|
391
|
+
{"name": "ce_find_pointer_path", "arguments": {"address": "0x255D5E758", "user_prompted": true}}
|
|
392
|
+
// 返回: base_address, offsets, ce_pointer_notation
|
|
393
|
+
|
|
394
|
+
// 手动 (自动失败时)
|
|
395
|
+
// 1. 查找访问该地址的代码
|
|
396
|
+
{"name": "ce_find_what_accesses", "arguments": {"address": "0x255D5E758", "user_prompted": true}}
|
|
397
|
+
// 2. 从结果获取寄存器值 (如 RBX=0x255D5E658)
|
|
398
|
+
// 3. 搜索存储该值的指针
|
|
399
|
+
{"name": "ce_value_scan", "arguments": {"value": "0x255D5E658", "type": "qword"}}
|
|
400
|
+
// 4. 重复直到找到 game.exe+offset
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### 函数分析
|
|
404
|
+
```json
|
|
405
|
+
// 1. 查找函数边界
|
|
406
|
+
{"name": "ce_find_function_boundaries", "arguments": {"address": "0x14587EDB0"}}
|
|
407
|
+
|
|
408
|
+
// 2. 跟踪执行
|
|
409
|
+
{"name": "ce_break_and_trace", "arguments": {"address": "0x14587EDB0", "max_steps": 100}}
|
|
410
|
+
|
|
411
|
+
// 3. 生成特征码用于更新
|
|
412
|
+
{"name": "ce_generate_signature", "arguments": {"address": "0x14587EDB0"}}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 逆向未知代码
|
|
416
|
+
```json
|
|
417
|
+
// 1. 反汇编
|
|
418
|
+
{"name": "ce_disassemble", "arguments": {"address": "0x14587EDB0", "count": 20}}
|
|
419
|
+
|
|
420
|
+
// 2. 符号跟踪理解逻辑
|
|
421
|
+
{"name": "ce_symbolic_trace", "arguments": {"address": "0x14587EDB0", "initial_state": {"rcx": "this"}}}
|
|
422
|
+
|
|
423
|
+
// 3. 为复杂函数构建 CFG
|
|
424
|
+
{"name": "ce_build_cfg", "arguments": {"address": "0x14587EDB0"}}
|
|
425
|
+
|
|
426
|
+
// 4. 检测模式
|
|
427
|
+
{"name": "ce_detect_patterns", "arguments": {"address": "0x14587EDB0"}}
|
|
428
|
+
```
|
|
427
429
|
|
|
428
430
|
---
|
|
429
431
|
|
package/ce_mcp_bridge.lua
CHANGED
|
@@ -26,14 +26,14 @@ local pairs = pairs
|
|
|
26
26
|
local ipairs = ipairs
|
|
27
27
|
local os_clock = os.clock
|
|
28
28
|
|
|
29
|
-
-- ============ Config ============
|
|
30
|
-
local Config = {
|
|
31
|
-
-- Pipe name: supports environment variable override for anti-detection
|
|
32
|
-
-- Set CE_MCP_PIPE_NAME environment variable to customize
|
|
33
|
-
PIPE_NAME = os.getenv("CE_MCP_PIPE_NAME") or "ce_mcp_bridge",
|
|
34
|
-
-- Authentication token: optional security layer for pipe communication
|
|
35
|
-
-- Set CE_MCP_AUTH_TOKEN environment variable to enable authentication
|
|
36
|
-
AUTH_TOKEN = os.getenv("CE_MCP_AUTH_TOKEN") or nil,
|
|
29
|
+
-- ============ Config ============
|
|
30
|
+
local Config = {
|
|
31
|
+
-- Pipe name: supports environment variable override for anti-detection
|
|
32
|
+
-- Set CE_MCP_PIPE_NAME environment variable to customize
|
|
33
|
+
PIPE_NAME = os.getenv("CE_MCP_PIPE_NAME") or "ce_mcp_bridge",
|
|
34
|
+
-- Authentication token: optional security layer for pipe communication
|
|
35
|
+
-- Set CE_MCP_AUTH_TOKEN environment variable to enable authentication
|
|
36
|
+
AUTH_TOKEN = os.getenv("CE_MCP_AUTH_TOKEN") or nil,
|
|
37
37
|
DEBUG_MODE = false,
|
|
38
38
|
MAX_MESSAGE_SIZE = 10 * 1024 * 1024,
|
|
39
39
|
PIPE_BUFFER_SIZE = 1024 * 1024,
|
|
@@ -1458,16 +1458,16 @@ local function getDebuggerStatus()
|
|
|
1458
1458
|
return status
|
|
1459
1459
|
end
|
|
1460
1460
|
|
|
1461
|
-
Handlers.ping = function()
|
|
1462
|
-
return {
|
|
1463
|
-
status = "ok",
|
|
1464
|
-
timestamp = os.time(),
|
|
1465
|
-
process = process or "N/A",
|
|
1466
|
-
pid = getOpenedProcessID() or 0,
|
|
1467
|
-
connections = Context.connectionCount,
|
|
1468
|
-
debugger = getDebuggerStatus()
|
|
1469
|
-
}
|
|
1470
|
-
end
|
|
1461
|
+
Handlers.ping = function()
|
|
1462
|
+
return {
|
|
1463
|
+
status = "ok",
|
|
1464
|
+
timestamp = os.time(),
|
|
1465
|
+
process = process or "N/A",
|
|
1466
|
+
pid = getOpenedProcessID() or 0,
|
|
1467
|
+
connections = Context.connectionCount,
|
|
1468
|
+
debugger = getDebuggerStatus()
|
|
1469
|
+
}
|
|
1470
|
+
end
|
|
1471
1471
|
|
|
1472
1472
|
Handlers.get_process_info = function()
|
|
1473
1473
|
-- Refresh symbol table using ModuleManager
|
|
@@ -1556,16 +1556,16 @@ Handlers.stats = function(p)
|
|
|
1556
1556
|
})
|
|
1557
1557
|
end
|
|
1558
1558
|
|
|
1559
|
-
return {
|
|
1560
|
-
uptime = metrics.uptime,
|
|
1561
|
-
commands = metrics.commands,
|
|
1562
|
-
summary = metrics.summary,
|
|
1563
|
-
cache = metrics.cache,
|
|
1564
|
-
logging = logStats,
|
|
1565
|
-
scanSessions = {
|
|
1566
|
-
active = scanSessionCount,
|
|
1567
|
-
maxAllowed = Config.MAX_SCAN_SESSIONS,
|
|
1568
|
-
sessions = scanSessions
|
|
1559
|
+
return {
|
|
1560
|
+
uptime = metrics.uptime,
|
|
1561
|
+
commands = metrics.commands,
|
|
1562
|
+
summary = metrics.summary,
|
|
1563
|
+
cache = metrics.cache,
|
|
1564
|
+
logging = logStats,
|
|
1565
|
+
scanSessions = {
|
|
1566
|
+
active = scanSessionCount,
|
|
1567
|
+
maxAllowed = Config.MAX_SCAN_SESSIONS,
|
|
1568
|
+
sessions = scanSessions
|
|
1569
1569
|
},
|
|
1570
1570
|
connections = Context.connectionCount,
|
|
1571
1571
|
debugger = getDebuggerStatus()
|
|
@@ -1707,25 +1707,41 @@ Handlers.aob_scan = function(p)
|
|
|
1707
1707
|
end
|
|
1708
1708
|
|
|
1709
1709
|
-- Execute scan
|
|
1710
|
-
|
|
1711
|
-
local
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1710
|
+
-- Use createMemScan to properly support start/stop range (AOBScan function doesn't support range args correctly)
|
|
1711
|
+
local memscan = createMemScan()
|
|
1712
|
+
local foundList = createFoundList(memscan)
|
|
1713
|
+
local scanOk = false
|
|
1714
|
+
|
|
1715
|
+
local vtByteArray = vtByteArray or 7
|
|
1716
|
+
local rtRounded = rtRounded or 0
|
|
1717
|
+
local fsmNotAligned = fsmNotAligned or 0
|
|
1718
|
+
local soExactValue = soExactValue or 1
|
|
1719
|
+
|
|
1720
|
+
pcall(function()
|
|
1721
|
+
memscan.firstScan(
|
|
1722
|
+
soExactValue, vtByteArray, rtRounded,
|
|
1723
|
+
cleanAob, "",
|
|
1724
|
+
startAddr or 0, stopAddr or 0x7FFFFFFFFFFFFFFF,
|
|
1725
|
+
protection, fsmNotAligned, "",
|
|
1726
|
+
true, false, false, false
|
|
1727
|
+
)
|
|
1728
|
+
memscan.waitTillDone()
|
|
1729
|
+
scanOk = true
|
|
1717
1730
|
end)
|
|
1718
1731
|
|
|
1719
|
-
if not
|
|
1720
|
-
|
|
1732
|
+
if not scanOk then
|
|
1733
|
+
pcall(function() memscan.destroy() end)
|
|
1734
|
+
return { count = 0, results = {}, error = "AOB Scan failed" }
|
|
1721
1735
|
end
|
|
1722
1736
|
|
|
1737
|
+
foundList.initialize()
|
|
1738
|
+
local totalCount = foundList.Count or 0
|
|
1739
|
+
|
|
1723
1740
|
local results = {}
|
|
1724
|
-
local totalCount = scanResult.Count or 0
|
|
1725
1741
|
local count = math_min(totalCount, maxResults)
|
|
1726
1742
|
|
|
1727
1743
|
for i = 0, count - 1 do
|
|
1728
|
-
local addr =
|
|
1744
|
+
local addr = foundList.Address[i]
|
|
1729
1745
|
if addr then
|
|
1730
1746
|
if type(addr) == "string" then
|
|
1731
1747
|
table_insert(results, addr)
|
|
@@ -1735,7 +1751,8 @@ Handlers.aob_scan = function(p)
|
|
|
1735
1751
|
end
|
|
1736
1752
|
end
|
|
1737
1753
|
|
|
1738
|
-
|
|
1754
|
+
foundList.deinitialize()
|
|
1755
|
+
memscan.destroy()
|
|
1739
1756
|
|
|
1740
1757
|
return {
|
|
1741
1758
|
count = totalCount,
|
|
@@ -1807,8 +1824,14 @@ Handlers.value_scan = function(p)
|
|
|
1807
1824
|
|
|
1808
1825
|
-- Execute scan
|
|
1809
1826
|
local scanOk = pcall(function()
|
|
1827
|
+
-- Determine alignment
|
|
1828
|
+
local alignType = fsmAligned
|
|
1829
|
+
local alignParam = "4"
|
|
1830
|
+
if vt == vtQword or vt == vtDouble then alignParam = "8" end
|
|
1831
|
+
if vt == vtByte then alignType = fsmNotAligned end
|
|
1832
|
+
|
|
1810
1833
|
memscan.firstScan(soExactValue, vt, rtRounded, scanValue, "",
|
|
1811
|
-
startAddr, stopAddr, protection,
|
|
1834
|
+
startAddr, stopAddr, protection, alignType, alignParam,
|
|
1812
1835
|
isHex, false, false, false)
|
|
1813
1836
|
memscan.waitTillDone()
|
|
1814
1837
|
end)
|
|
@@ -1951,8 +1974,14 @@ Handlers.scan_new = function(p)
|
|
|
1951
1974
|
|
|
1952
1975
|
-- Execute first scan
|
|
1953
1976
|
local scanOk = pcall(function()
|
|
1977
|
+
-- Determine alignment
|
|
1978
|
+
local alignType = fsmAligned
|
|
1979
|
+
local alignParam = "4"
|
|
1980
|
+
if vt == vtQword or vt == vtDouble then alignParam = "8" end
|
|
1981
|
+
if vt == vtByte then alignType = fsmNotAligned end
|
|
1982
|
+
|
|
1954
1983
|
memscan.firstScan(soExactValue, vt, rtRounded, scanValue, "",
|
|
1955
|
-
startAddr, stopAddr, protection,
|
|
1984
|
+
startAddr, stopAddr, protection, alignType, alignParam,
|
|
1956
1985
|
isHex, false, false, false)
|
|
1957
1986
|
memscan.waitTillDone()
|
|
1958
1987
|
end)
|
|
@@ -4762,55 +4791,75 @@ Handlers.find_call_references = function(p)
|
|
|
4762
4791
|
for _, pattern in ipairs(patterns) do
|
|
4763
4792
|
if #callers >= limit then break end
|
|
4764
4793
|
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4794
|
+
-- Use MemScan for range-limited AOB scan
|
|
4795
|
+
local ms = createMemScan()
|
|
4796
|
+
local fl = createFoundList(ms)
|
|
4797
|
+
local scanOk = false
|
|
4798
|
+
|
|
4799
|
+
pcall(function()
|
|
4800
|
+
-- 0=soExactValue, 7=vtByteArray, 0=rtRounded
|
|
4801
|
+
-- fsmNotAligned=0, ""=alignParam
|
|
4802
|
+
-- true=hex, false=notBinary, false=unicode, false=caseSensitive
|
|
4803
|
+
ms.firstScan(0, 7, 0, pattern, "", segStart, segEnd, "+X",
|
|
4804
|
+
0, "", true, false, false, false)
|
|
4805
|
+
ms.waitTillDone()
|
|
4806
|
+
scanOk = true
|
|
4807
|
+
end)
|
|
4774
4808
|
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
for _, c in ipairs(callers) do
|
|
4795
|
-
if c.address == Utils.formatHex(callAddr) then
|
|
4796
|
-
isDup = true
|
|
4797
|
-
break
|
|
4798
|
-
end
|
|
4809
|
+
if scanOk then
|
|
4810
|
+
fl.initialize()
|
|
4811
|
+
local count = fl.Count or 0
|
|
4812
|
+
if count > 0 then
|
|
4813
|
+
totalMatches = totalMatches + count
|
|
4814
|
+
|
|
4815
|
+
for i = 0, count - 1 do
|
|
4816
|
+
if #callers >= limit then break end
|
|
4817
|
+
|
|
4818
|
+
local callAddrStr = fl.Address[i]
|
|
4819
|
+
-- Address can be string or number depending on CE version
|
|
4820
|
+
local callAddr = type(callAddrStr) == "string" and tonumber(callAddrStr, 16) or callAddrStr
|
|
4821
|
+
|
|
4822
|
+
if callAddr then
|
|
4823
|
+
-- Verify: read actual rel32 and check target
|
|
4824
|
+
local relOffset = readInteger(callAddr + 1)
|
|
4825
|
+
if relOffset then
|
|
4826
|
+
if relOffset > 0x7FFFFFFF then
|
|
4827
|
+
relOffset = relOffset - 0x100000000
|
|
4799
4828
|
end
|
|
4829
|
+
local target = callAddr + 5 + relOffset
|
|
4830
|
+
|
|
4831
|
+
if target == funcAddr then
|
|
4832
|
+
local disasm = ""
|
|
4833
|
+
local symbol = nil
|
|
4834
|
+
pcall(function()
|
|
4835
|
+
disasm = disassemble(callAddr) or "???"
|
|
4836
|
+
symbol = getNameFromAddress(callAddr, true)
|
|
4837
|
+
end)
|
|
4838
|
+
|
|
4839
|
+
-- Avoid duplicates
|
|
4840
|
+
local isDup = false
|
|
4841
|
+
for _, c in ipairs(callers) do
|
|
4842
|
+
if c.address == Utils.formatHex(callAddr) then
|
|
4843
|
+
isDup = true
|
|
4844
|
+
break
|
|
4845
|
+
end
|
|
4846
|
+
end
|
|
4800
4847
|
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4848
|
+
if not isDup then
|
|
4849
|
+
table_insert(callers, {
|
|
4850
|
+
address = Utils.formatHex(callAddr),
|
|
4851
|
+
instruction = disasm,
|
|
4852
|
+
symbol = symbol
|
|
4853
|
+
})
|
|
4854
|
+
end
|
|
4807
4855
|
end
|
|
4808
4856
|
end
|
|
4809
4857
|
end
|
|
4810
4858
|
end
|
|
4811
4859
|
end
|
|
4812
|
-
|
|
4860
|
+
fl.deinitialize()
|
|
4813
4861
|
end
|
|
4862
|
+
ms.destroy()
|
|
4814
4863
|
end
|
|
4815
4864
|
end
|
|
4816
4865
|
|
|
@@ -7485,162 +7534,162 @@ function Pipe.executeRequest(req)
|
|
|
7485
7534
|
end
|
|
7486
7535
|
end
|
|
7487
7536
|
|
|
7488
|
-
-- Helper function to send error response without breaking connection
|
|
7489
|
-
local function sendErrorResponse(errorMsg)
|
|
7490
|
-
local resp = { error = errorMsg }
|
|
7491
|
-
local respStr = JSON.encode(resp)
|
|
7492
|
-
local respLen = #respStr
|
|
7493
|
-
|
|
7494
|
-
pcall(function()
|
|
7495
|
-
Context.pipeServer.lock()
|
|
7496
|
-
pcall(function()
|
|
7497
|
-
Context.pipeServer.writeDword(respLen)
|
|
7498
|
-
Context.pipeServer.writeString(respStr, false)
|
|
7499
|
-
end)
|
|
7500
|
-
Context.pipeServer.unlock()
|
|
7501
|
-
end)
|
|
7502
|
-
end
|
|
7503
|
-
|
|
7504
|
-
function Pipe.workerLoop()
|
|
7505
|
-
Utils.debugPrint("Worker started")
|
|
7506
|
-
local consecutiveErrors = 0
|
|
7507
|
-
|
|
7508
|
-
while Context.serverRunning do
|
|
7509
|
-
local acceptOk, acceptErr = pcall(function()
|
|
7510
|
-
Context.pipeServer.acceptConnection()
|
|
7511
|
-
end)
|
|
7512
|
-
|
|
7513
|
-
if not acceptOk then
|
|
7514
|
-
consecutiveErrors = consecutiveErrors + 1
|
|
7515
|
-
if consecutiveErrors > Config.MAX_CONSECUTIVE_ERRORS then
|
|
7516
|
-
Utils.debugPrint("Too many consecutive errors, restarting pipe...")
|
|
7517
|
-
pcall(function() Context.pipeServer.destroy() end)
|
|
7518
|
-
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7519
|
-
consecutiveErrors = 0
|
|
7520
|
-
end
|
|
7521
|
-
if Context.serverRunning then
|
|
7522
|
-
sleep(Config.HEARTBEAT_INTERVAL)
|
|
7523
|
-
end
|
|
7524
|
-
else
|
|
7525
|
-
consecutiveErrors = 0
|
|
7526
|
-
Context.connectionCount = Context.connectionCount + 1
|
|
7527
|
-
Context.lastActivityTime = os_clock() -- Heartbeat: record connection time
|
|
7528
|
-
Utils.debugPrint("Client connected (#" .. Context.connectionCount .. ")")
|
|
7529
|
-
|
|
7530
|
-
local sessionErrors = 0
|
|
7531
|
-
|
|
7532
|
-
while Context.serverRunning and Context.pipeServer and Context.pipeServer.Valid do
|
|
7533
|
-
Utils.periodicCleanup()
|
|
7534
|
-
|
|
7535
|
-
-- Read size
|
|
7536
|
-
local ok, size = false, nil
|
|
7537
|
-
pcall(function()
|
|
7538
|
-
Context.pipeServer.lock()
|
|
7539
|
-
ok, size = pcall(Context.pipeServer.readDword)
|
|
7540
|
-
Context.pipeServer.unlock()
|
|
7541
|
-
end)
|
|
7542
|
-
|
|
7543
|
-
if not ok or not size or size == 0 then
|
|
7544
|
-
sessionErrors = sessionErrors + 1
|
|
7545
|
-
if sessionErrors >= Config.MAX_SESSION_ERRORS then
|
|
7546
|
-
Utils.debugPrint("Too many session errors, disconnecting client")
|
|
7547
|
-
-- Send error before disconnecting
|
|
7548
|
-
sendErrorResponse("Too many consecutive read errors")
|
|
7549
|
-
break
|
|
7550
|
-
end
|
|
7551
|
-
sleep(10)
|
|
7552
|
-
goto continue
|
|
7553
|
-
end
|
|
7554
|
-
|
|
7555
|
-
if size >= Config.MAX_MESSAGE_SIZE then
|
|
7556
|
-
Utils.debugPrint("Message too large: " .. size)
|
|
7557
|
-
sendErrorResponse("Message too large: " .. size .. " bytes (max: " .. Config.MAX_MESSAGE_SIZE .. ")")
|
|
7558
|
-
break
|
|
7559
|
-
end
|
|
7560
|
-
|
|
7561
|
-
sessionErrors = 0
|
|
7562
|
-
|
|
7563
|
-
-- Read payload
|
|
7564
|
-
local ok2, payload = false, nil
|
|
7565
|
-
pcall(function()
|
|
7566
|
-
Context.pipeServer.lock()
|
|
7567
|
-
ok2, payload = pcall(Context.pipeServer.readString, size)
|
|
7568
|
-
Context.pipeServer.unlock()
|
|
7569
|
-
end)
|
|
7570
|
-
|
|
7571
|
-
if ok2 and payload and #payload == size then
|
|
7572
|
-
local decodeOk, req = pcall(JSON.decode, payload)
|
|
7573
|
-
if not decodeOk or not req then
|
|
7574
|
-
Utils.debugPrint("Failed to decode JSON payload")
|
|
7575
|
-
sendErrorResponse("Invalid JSON payload")
|
|
7576
|
-
goto continue
|
|
7577
|
-
end
|
|
7578
|
-
|
|
7579
|
-
local resp = nil
|
|
7580
|
-
|
|
7581
|
-
-- Use optimized request execution (sync on demand)
|
|
7582
|
-
resp = Pipe.executeRequest(req)
|
|
7583
|
-
|
|
7584
|
-
local respStr = JSON.encode(resp)
|
|
7585
|
-
local respLen = #respStr
|
|
7586
|
-
|
|
7587
|
-
if respLen > Config.MAX_MESSAGE_SIZE then
|
|
7588
|
-
respStr = JSON.encode({ error = "Response too large" })
|
|
7589
|
-
respLen = #respStr
|
|
7590
|
-
end
|
|
7591
|
-
|
|
7592
|
-
local writeOk = false
|
|
7593
|
-
pcall(function()
|
|
7594
|
-
Context.pipeServer.lock()
|
|
7595
|
-
writeOk = pcall(function()
|
|
7596
|
-
Context.pipeServer.writeDword(respLen)
|
|
7597
|
-
Context.pipeServer.writeString(respStr, false)
|
|
7598
|
-
end)
|
|
7599
|
-
Context.pipeServer.unlock()
|
|
7600
|
-
end)
|
|
7601
|
-
|
|
7602
|
-
if writeOk then
|
|
7603
|
-
Context.lastActivityTime = os_clock() -- Heartbeat: update on successful communication
|
|
7604
|
-
else
|
|
7605
|
-
Utils.debugPrint("Failed to write response")
|
|
7606
|
-
break
|
|
7607
|
-
end
|
|
7608
|
-
else
|
|
7609
|
-
Utils.debugPrint("Failed to read payload: expected " .. size .. " bytes")
|
|
7610
|
-
sendErrorResponse("Failed to read payload: expected " .. size .. " bytes, got " .. tostring(#(payload or "")))
|
|
7611
|
-
-- Don't break, allow client to retry
|
|
7612
|
-
sleep(50)
|
|
7613
|
-
end
|
|
7614
|
-
|
|
7615
|
-
::continue::
|
|
7616
|
-
end
|
|
7617
|
-
|
|
7618
|
-
pcall(function()
|
|
7619
|
-
if Context.pipeServer and Context.pipeServer.Valid then
|
|
7620
|
-
Context.pipeServer.disconnect()
|
|
7621
|
-
end
|
|
7622
|
-
end)
|
|
7623
|
-
Context.lastActivityTime = 0 -- Heartbeat: reset on disconnect
|
|
7624
|
-
Utils.debugPrint("Client disconnected, recreating pipe for next connection...")
|
|
7625
|
-
|
|
7626
|
-
-- Recreate pipe instance for next client (Windows named pipes are single-client)
|
|
7627
|
-
pcall(function()
|
|
7628
|
-
if Context.pipeServer then
|
|
7629
|
-
Context.pipeServer.destroy()
|
|
7630
|
-
end
|
|
7631
|
-
end)
|
|
7632
|
-
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7633
|
-
|
|
7634
|
-
if not Context.pipeServer or not Context.pipeServer.Valid then
|
|
7635
|
-
Utils.debugPrint("Failed to recreate pipe, restarting worker loop...")
|
|
7636
|
-
sleep(100)
|
|
7637
|
-
else
|
|
7638
|
-
Utils.debugPrint("Pipe recreated, ready for next connection")
|
|
7639
|
-
end
|
|
7640
|
-
end
|
|
7641
|
-
end
|
|
7642
|
-
Utils.debugPrint("Worker stopped")
|
|
7643
|
-
end
|
|
7537
|
+
-- Helper function to send error response without breaking connection
|
|
7538
|
+
local function sendErrorResponse(errorMsg)
|
|
7539
|
+
local resp = { error = errorMsg }
|
|
7540
|
+
local respStr = JSON.encode(resp)
|
|
7541
|
+
local respLen = #respStr
|
|
7542
|
+
|
|
7543
|
+
pcall(function()
|
|
7544
|
+
Context.pipeServer.lock()
|
|
7545
|
+
pcall(function()
|
|
7546
|
+
Context.pipeServer.writeDword(respLen)
|
|
7547
|
+
Context.pipeServer.writeString(respStr, false)
|
|
7548
|
+
end)
|
|
7549
|
+
Context.pipeServer.unlock()
|
|
7550
|
+
end)
|
|
7551
|
+
end
|
|
7552
|
+
|
|
7553
|
+
function Pipe.workerLoop()
|
|
7554
|
+
Utils.debugPrint("Worker started")
|
|
7555
|
+
local consecutiveErrors = 0
|
|
7556
|
+
|
|
7557
|
+
while Context.serverRunning do
|
|
7558
|
+
local acceptOk, acceptErr = pcall(function()
|
|
7559
|
+
Context.pipeServer.acceptConnection()
|
|
7560
|
+
end)
|
|
7561
|
+
|
|
7562
|
+
if not acceptOk then
|
|
7563
|
+
consecutiveErrors = consecutiveErrors + 1
|
|
7564
|
+
if consecutiveErrors > Config.MAX_CONSECUTIVE_ERRORS then
|
|
7565
|
+
Utils.debugPrint("Too many consecutive errors, restarting pipe...")
|
|
7566
|
+
pcall(function() Context.pipeServer.destroy() end)
|
|
7567
|
+
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7568
|
+
consecutiveErrors = 0
|
|
7569
|
+
end
|
|
7570
|
+
if Context.serverRunning then
|
|
7571
|
+
sleep(Config.HEARTBEAT_INTERVAL)
|
|
7572
|
+
end
|
|
7573
|
+
else
|
|
7574
|
+
consecutiveErrors = 0
|
|
7575
|
+
Context.connectionCount = Context.connectionCount + 1
|
|
7576
|
+
Context.lastActivityTime = os_clock() -- Heartbeat: record connection time
|
|
7577
|
+
Utils.debugPrint("Client connected (#" .. Context.connectionCount .. ")")
|
|
7578
|
+
|
|
7579
|
+
local sessionErrors = 0
|
|
7580
|
+
|
|
7581
|
+
while Context.serverRunning and Context.pipeServer and Context.pipeServer.Valid do
|
|
7582
|
+
Utils.periodicCleanup()
|
|
7583
|
+
|
|
7584
|
+
-- Read size
|
|
7585
|
+
local ok, size = false, nil
|
|
7586
|
+
pcall(function()
|
|
7587
|
+
Context.pipeServer.lock()
|
|
7588
|
+
ok, size = pcall(Context.pipeServer.readDword)
|
|
7589
|
+
Context.pipeServer.unlock()
|
|
7590
|
+
end)
|
|
7591
|
+
|
|
7592
|
+
if not ok or not size or size == 0 then
|
|
7593
|
+
sessionErrors = sessionErrors + 1
|
|
7594
|
+
if sessionErrors >= Config.MAX_SESSION_ERRORS then
|
|
7595
|
+
Utils.debugPrint("Too many session errors, disconnecting client")
|
|
7596
|
+
-- Send error before disconnecting
|
|
7597
|
+
sendErrorResponse("Too many consecutive read errors")
|
|
7598
|
+
break
|
|
7599
|
+
end
|
|
7600
|
+
sleep(10)
|
|
7601
|
+
goto continue
|
|
7602
|
+
end
|
|
7603
|
+
|
|
7604
|
+
if size >= Config.MAX_MESSAGE_SIZE then
|
|
7605
|
+
Utils.debugPrint("Message too large: " .. size)
|
|
7606
|
+
sendErrorResponse("Message too large: " .. size .. " bytes (max: " .. Config.MAX_MESSAGE_SIZE .. ")")
|
|
7607
|
+
break
|
|
7608
|
+
end
|
|
7609
|
+
|
|
7610
|
+
sessionErrors = 0
|
|
7611
|
+
|
|
7612
|
+
-- Read payload
|
|
7613
|
+
local ok2, payload = false, nil
|
|
7614
|
+
pcall(function()
|
|
7615
|
+
Context.pipeServer.lock()
|
|
7616
|
+
ok2, payload = pcall(Context.pipeServer.readString, size)
|
|
7617
|
+
Context.pipeServer.unlock()
|
|
7618
|
+
end)
|
|
7619
|
+
|
|
7620
|
+
if ok2 and payload and #payload == size then
|
|
7621
|
+
local decodeOk, req = pcall(JSON.decode, payload)
|
|
7622
|
+
if not decodeOk or not req then
|
|
7623
|
+
Utils.debugPrint("Failed to decode JSON payload")
|
|
7624
|
+
sendErrorResponse("Invalid JSON payload")
|
|
7625
|
+
goto continue
|
|
7626
|
+
end
|
|
7627
|
+
|
|
7628
|
+
local resp = nil
|
|
7629
|
+
|
|
7630
|
+
-- Use optimized request execution (sync on demand)
|
|
7631
|
+
resp = Pipe.executeRequest(req)
|
|
7632
|
+
|
|
7633
|
+
local respStr = JSON.encode(resp)
|
|
7634
|
+
local respLen = #respStr
|
|
7635
|
+
|
|
7636
|
+
if respLen > Config.MAX_MESSAGE_SIZE then
|
|
7637
|
+
respStr = JSON.encode({ error = "Response too large" })
|
|
7638
|
+
respLen = #respStr
|
|
7639
|
+
end
|
|
7640
|
+
|
|
7641
|
+
local writeOk = false
|
|
7642
|
+
pcall(function()
|
|
7643
|
+
Context.pipeServer.lock()
|
|
7644
|
+
writeOk = pcall(function()
|
|
7645
|
+
Context.pipeServer.writeDword(respLen)
|
|
7646
|
+
Context.pipeServer.writeString(respStr, false)
|
|
7647
|
+
end)
|
|
7648
|
+
Context.pipeServer.unlock()
|
|
7649
|
+
end)
|
|
7650
|
+
|
|
7651
|
+
if writeOk then
|
|
7652
|
+
Context.lastActivityTime = os_clock() -- Heartbeat: update on successful communication
|
|
7653
|
+
else
|
|
7654
|
+
Utils.debugPrint("Failed to write response")
|
|
7655
|
+
break
|
|
7656
|
+
end
|
|
7657
|
+
else
|
|
7658
|
+
Utils.debugPrint("Failed to read payload: expected " .. size .. " bytes")
|
|
7659
|
+
sendErrorResponse("Failed to read payload: expected " .. size .. " bytes, got " .. tostring(#(payload or "")))
|
|
7660
|
+
-- Don't break, allow client to retry
|
|
7661
|
+
sleep(50)
|
|
7662
|
+
end
|
|
7663
|
+
|
|
7664
|
+
::continue::
|
|
7665
|
+
end
|
|
7666
|
+
|
|
7667
|
+
pcall(function()
|
|
7668
|
+
if Context.pipeServer and Context.pipeServer.Valid then
|
|
7669
|
+
Context.pipeServer.disconnect()
|
|
7670
|
+
end
|
|
7671
|
+
end)
|
|
7672
|
+
Context.lastActivityTime = 0 -- Heartbeat: reset on disconnect
|
|
7673
|
+
Utils.debugPrint("Client disconnected, recreating pipe for next connection...")
|
|
7674
|
+
|
|
7675
|
+
-- Recreate pipe instance for next client (Windows named pipes are single-client)
|
|
7676
|
+
pcall(function()
|
|
7677
|
+
if Context.pipeServer then
|
|
7678
|
+
Context.pipeServer.destroy()
|
|
7679
|
+
end
|
|
7680
|
+
end)
|
|
7681
|
+
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7682
|
+
|
|
7683
|
+
if not Context.pipeServer or not Context.pipeServer.Valid then
|
|
7684
|
+
Utils.debugPrint("Failed to recreate pipe, restarting worker loop...")
|
|
7685
|
+
sleep(100)
|
|
7686
|
+
else
|
|
7687
|
+
Utils.debugPrint("Pipe recreated, ready for next connection")
|
|
7688
|
+
end
|
|
7689
|
+
end
|
|
7690
|
+
end
|
|
7691
|
+
Utils.debugPrint("Worker stopped")
|
|
7692
|
+
end
|
|
7644
7693
|
|
|
7645
7694
|
-- ============ Server Lifecycle ============
|
|
7646
7695
|
local Server = {}
|
|
@@ -7669,13 +7718,13 @@ function Server.stop()
|
|
|
7669
7718
|
Utils.debugPrint("Server stopped (handled " .. Context.connectionCount .. " connections)")
|
|
7670
7719
|
end
|
|
7671
7720
|
|
|
7672
|
-
function Server.start()
|
|
7673
|
-
Server.stop() -- This calls cleanupZombieState()
|
|
7674
|
-
|
|
7675
|
-
-- Additional cleanup for fresh start
|
|
7676
|
-
Utils.debugPrint("Starting MCP Bridge")
|
|
7677
|
-
|
|
7678
|
-
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7721
|
+
function Server.start()
|
|
7722
|
+
Server.stop() -- This calls cleanupZombieState()
|
|
7723
|
+
|
|
7724
|
+
-- Additional cleanup for fresh start
|
|
7725
|
+
Utils.debugPrint("Starting MCP Bridge")
|
|
7726
|
+
|
|
7727
|
+
Context.pipeServer = createPipe(Config.PIPE_NAME, Config.PIPE_BUFFER_SIZE, Config.PIPE_BUFFER_SIZE)
|
|
7679
7728
|
if not Context.pipeServer or not Context.pipeServer.Valid then
|
|
7680
7729
|
print("[CE-MCP] Failed to create pipe: " .. Config.PIPE_NAME)
|
|
7681
7730
|
return false
|
|
@@ -7708,14 +7757,14 @@ function Server.stats()
|
|
|
7708
7757
|
bpCount = bpCount + 1
|
|
7709
7758
|
end
|
|
7710
7759
|
|
|
7711
|
-
return {
|
|
7712
|
-
running = Context.serverRunning,
|
|
7713
|
-
connections = Context.connectionCount,
|
|
7714
|
-
cached_symbols = symbolCacheCount,
|
|
7715
|
-
cached_modules = moduleCacheCount,
|
|
7716
|
-
active_breakpoints = bpCount
|
|
7717
|
-
}
|
|
7718
|
-
end
|
|
7760
|
+
return {
|
|
7761
|
+
running = Context.serverRunning,
|
|
7762
|
+
connections = Context.connectionCount,
|
|
7763
|
+
cached_symbols = symbolCacheCount,
|
|
7764
|
+
cached_modules = moduleCacheCount,
|
|
7765
|
+
active_breakpoints = bpCount
|
|
7766
|
+
}
|
|
7767
|
+
end
|
|
7719
7768
|
|
|
7720
7769
|
-- ============ Global API ============
|
|
7721
7770
|
-- Cleanup old instance
|
|
@@ -7734,18 +7783,18 @@ if CE_MCP_STATUS_LABEL then
|
|
|
7734
7783
|
end)
|
|
7735
7784
|
end
|
|
7736
7785
|
|
|
7737
|
-
CE_MCP = {
|
|
7738
|
-
start = Server.start,
|
|
7739
|
-
stop = Server.stop,
|
|
7740
|
-
stats = Server.stats,
|
|
7741
|
-
-- Logger API
|
|
7742
|
-
Logger = Logger,
|
|
7743
|
-
LogLevel = LogLevel,
|
|
7744
|
-
-- Expose for debugging
|
|
7745
|
-
_config = Config,
|
|
7746
|
-
_context = Context,
|
|
7747
|
-
_handlers = Handlers,
|
|
7748
|
-
}
|
|
7786
|
+
CE_MCP = {
|
|
7787
|
+
start = Server.start,
|
|
7788
|
+
stop = Server.stop,
|
|
7789
|
+
stats = Server.stats,
|
|
7790
|
+
-- Logger API
|
|
7791
|
+
Logger = Logger,
|
|
7792
|
+
LogLevel = LogLevel,
|
|
7793
|
+
-- Expose for debugging
|
|
7794
|
+
_config = Config,
|
|
7795
|
+
_context = Context,
|
|
7796
|
+
_handlers = Handlers,
|
|
7797
|
+
}
|
|
7749
7798
|
|
|
7750
7799
|
CE_MCP_BRIDGE_INSTANCE = CE_MCP
|
|
7751
7800
|
|
|
@@ -7770,15 +7819,26 @@ function StatusLabel.create()
|
|
|
7770
7819
|
label.Cursor = crHandPoint
|
|
7771
7820
|
|
|
7772
7821
|
if commentBtn then
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7822
|
+
-- Use pcall to safely set properties, in case of unexpected parent/control issues
|
|
7823
|
+
local ok = pcall(function()
|
|
7824
|
+
label.Parent = commentBtn.Parent
|
|
7825
|
+
-- Anchor to CommentButton's left side
|
|
7826
|
+
label.AnchorSideRight.Control = commentBtn
|
|
7827
|
+
label.AnchorSideRight.Side = asrLeft
|
|
7828
|
+
label.AnchorSideTop.Control = commentBtn
|
|
7829
|
+
label.AnchorSideTop.Side = asrTop
|
|
7830
|
+
label.Anchors = "[akTop,akRight]"
|
|
7831
|
+
label.BorderSpacing.Right = 5
|
|
7832
|
+
label.BorderSpacing.Top = 2
|
|
7833
|
+
end)
|
|
7834
|
+
|
|
7835
|
+
if not ok then
|
|
7836
|
+
-- Fallback if anchoring failed
|
|
7837
|
+
label.Parent = mainForm
|
|
7838
|
+
label.Anchors = "[akRight, akTop]"
|
|
7839
|
+
label.Left = mainForm.ClientWidth - 120
|
|
7840
|
+
label.Top = 5
|
|
7841
|
+
end
|
|
7782
7842
|
else
|
|
7783
7843
|
-- Fallback: top right
|
|
7784
7844
|
label.Parent = mainForm
|
|
@@ -7787,15 +7847,15 @@ function StatusLabel.create()
|
|
|
7787
7847
|
label.Top = 5
|
|
7788
7848
|
end
|
|
7789
7849
|
|
|
7790
|
-
-- Click to show stats
|
|
7791
|
-
label.OnClick = function()
|
|
7792
|
-
local stats = Server.stats()
|
|
7793
|
-
local connected = Context.lastActivityTime > 0 and
|
|
7794
|
-
(os_clock() - Context.lastActivityTime) < Config.HEARTBEAT_TIMEOUT
|
|
7795
|
-
showMessage(string_format("CE MCP\nStatus: %s\nConnections: %d",
|
|
7796
|
-
connected and "Connected" or "Waiting",
|
|
7797
|
-
stats.connections or 0))
|
|
7798
|
-
end
|
|
7850
|
+
-- Click to show stats
|
|
7851
|
+
label.OnClick = function()
|
|
7852
|
+
local stats = Server.stats()
|
|
7853
|
+
local connected = Context.lastActivityTime > 0 and
|
|
7854
|
+
(os_clock() - Context.lastActivityTime) < Config.HEARTBEAT_TIMEOUT
|
|
7855
|
+
showMessage(string_format("CE MCP\nStatus: %s\nConnections: %d",
|
|
7856
|
+
connected and "Connected" or "Waiting",
|
|
7857
|
+
stats.connections or 0))
|
|
7858
|
+
end
|
|
7799
7859
|
|
|
7800
7860
|
StatusLabel.label = label
|
|
7801
7861
|
StatusLabel.updateColor(false)
|
package/package.json
CHANGED
package/src/tool-registry.js
CHANGED
|
@@ -221,8 +221,8 @@ class ToolRegistry {
|
|
|
221
221
|
new ToolParam('aob_string', 'string', "e.g. '48 89 5C 24 ?? 48 83 EC 20'", true),
|
|
222
222
|
new ToolParam('module', 'string', "Limit scan to specific module for better performance (e.g. 'game.exe', 'UnityPlayer.dll')"),
|
|
223
223
|
new ToolParam('protection', 'string', 'Flags like -C+X', false, '-C+X'),
|
|
224
|
-
new ToolParam('start', 'string', 'Start address (optional
|
|
225
|
-
new ToolParam('stop', 'string', 'Stop address (optional)'),
|
|
224
|
+
new ToolParam('start', 'string', 'Start address (optional). If module is not specified, this defines the start of the scan range.'),
|
|
225
|
+
new ToolParam('stop', 'string', 'Stop address (optional). If module is not specified, this defines the end of the scan range.'),
|
|
226
226
|
new ToolParam('max_results', 'integer', 'Maximum results', false, 100),
|
|
227
227
|
]
|
|
228
228
|
));
|
|
@@ -233,6 +233,7 @@ class ToolRegistry {
|
|
|
233
233
|
'USE WHEN: After ce_find_what_accesses returns a register value, search for that value to find pointer storage. ' +
|
|
234
234
|
"Example: RBX=0x12345678 accessed your address -> scan for 0x12345678 (qword) -> find where pointer is stored. " +
|
|
235
235
|
"NOT FOR: Finding game values like health/gold (use ce_scan_new for value hunting). " +
|
|
236
|
+
'Auto-aligns scan based on type for performance (4-byte for dword/float, 8-byte for qword/double). ' +
|
|
236
237
|
'Returns: {count, results: [{address, symbol, isStatic}], value_searched, type, module}. ' +
|
|
237
238
|
'TIP: isStatic=true means the address is in a module (potential static base found!).',
|
|
238
239
|
'value_scan',
|
|
@@ -254,7 +255,7 @@ class ToolRegistry {
|
|
|
254
255
|
'[VALUE HUNTING] Start scan session to find unknown addresses by observing value changes. ' +
|
|
255
256
|
"USE WHEN: You don't know the address but can observe changes (health 100->95). " +
|
|
256
257
|
"WORKFLOW: ce_scan_new -> change value in game -> ce_scan_next('decreased') -> repeat until few results. " +
|
|
257
|
-
'
|
|
258
|
+
'Auto-aligns scan based on type for performance (4-byte for dword/float, 8-byte for qword/double). ' +
|
|
258
259
|
'Returns: {session_id, count}. Use ce_scan_next to filter, ce_scan_results to get addresses.',
|
|
259
260
|
'scan_new',
|
|
260
261
|
ToolCategory.SCANNING,
|