lutzleonhardt/rct-bridge

GitHub: lutzleonhardt/rct-bridge

一个将运行中的 RollerCoaster Tycoon 1 进程内存以受控方式暴露给 LLM Agent 的 MCP 桥接器,通过内存窗口、字段掩码和 Undo Journal 实现可审计的游戏内存读写与回滚。

Stars: 0 | Forks: 0

# rct-bridge — RCT1 LLM 访客桥接器 该桥接器为 LLM(或 Shell 中的你)提供对在 Wine 下运行的 RCT.EXE 内存受控的读写访问权限。它被刻意设计为低级别:Agent 自行计算地址;桥接器仅强制执行内存窗口、字段掩码和 Undo-Journal。技术参考为本仓库中的 `PEEP_HANDBUCH.md`(§2 窗口,§4 掩码,§4.2 名称,§11 M1 配方,§12 冒烟测试)。 ## 演示视频:优化游客幸福感 [优化 RollerCoaster Tycoon 中的游客幸福感](https://www.tella.tv/video/besucher-happiness-in-rollercoaster-tycoon-optimieren-76ky) 展示了桥接器的实际运行情况,其中 Claude Code 作为 Agent。所有步骤均通过 MCP 工具由 Agent 转换的自然语言指令: 1. 将游乐园游客分为两组:红组和蓝组,进行完全染色(衬衫和裤子)。为红组提供雨伞,将蓝组的雨伞收回(`snippets/outfit_split.py`)。 2. 随后 Agent 使天降雨(`snippets/make_rain.py`,通过 `globals_rw` 白名单进行天气调度)。 3. 蓝组游客离开游乐园(`snippets/leave_park.py`)。 4. 衣物变为幸福感热力图:绿色代表非常开心,从黄色、橙色过渡到深红色;通过 Cronjob 每分钟更新一次(`snippets/happiness_heatmap.py`,作为死循环也可使用 `bridge/heatmap_loop.py`)。 5. 趋势评估:游客当前是变得更开心还是不开心? 6. 最后,Agent 每分钟观察一次三口之家的 Mustermann 家庭,优化他们的互动以达到最大幸福感,并记录观察结果和决策——结果作为 `TAGEBUCH_MUSTERMANN.md` 保存在本仓库中。 ## 文件分布 - `bridge/` — Python 包:Daemon、CLI、MCP 服务器、exec 脚本、测试。 - `PEEP_HANDBUCH.md` — 提纯的逆向工程参考(Offsets、规则、配方、冒烟测试)。 - `BNSkripte/` — 逆向工程工作中的 Binary Ninja 脚本,手册即基于此生成。 - `TAGEBUCH_MUSTERMANN.md` — 演示产物:上述演示视频中 Mustermann 家庭的日记。 ## 安装(在游戏目录中创建符号链接) 仓库位于游戏目录之外;游戏目录仅通过符号链接与之关联。`bridge/config.py` 从包路径推导游戏目录,而不解析符号链接——因此直接在游戏目录中运行 `python3 -m bridge.daemon` 也能正常工作。 1. 克隆仓库,例如至 `~/projects/rct-bridge`。 2. 在游戏目录中(在 `RCT.EXE` 旁边)创建两个必需的符号链接: `ln -s ~/projects/rct-bridge/bridge bridge` 和 `ln -s ~/projects/rct-bridge/PEEP_HANDBUCH.md PEEP_HANDBUCH.md` — `handbook` MCP 工具需要手册链接,它会在游戏目录中搜索该文件。 3. 随后,在游戏目录中启动 Daemon、CLI 和 MCP 服务器,如下所述。 此处的参考设置:通过 Bottles 安装的 GOG 版 RollerCoaster Tycoon Deluxe;直接使用系统 Wine 并设置 `WINEPREFIX` 启动,gamescope 负责缩放。 ## 架构 ``` Claude Code ─stdio/JSON-RPC─▶ MCP-Server ─Unix-Socket─▶ Daemon ─/proc//mem─▶ RCT.EXE (Wine) (dünner Adapter) (Maske, Journal, exec-Sandbox) ``` 一个长驻 Daemon 将 `wine RCT.EXE` 作为子进程启动,并保持 `/proc//mem` 文件描述符处于打开状态。这不是个人偏好问题,而是 Ancestor 陷阱:在 `kernel.yama.ptrace_scope=1` 时,只有祖先进程才能打开游戏内存。如果短生命周期的 CLI 调用生成了 Wine 并退出,下一次调用将无法再访问该进程。因此,由 Daemon 执行所有访问(Single Writer);掩码、Journal 和 exec 沙箱仅在那里存在。客户端通过 Unix Socket 使用 JSON 行协议进行通信;CLI 只是对此的纯 Shell 封装,而 MCP 服务器(`bridge/mcp_server.py`)只是另一个轻量级客户端——它将 MCP 工具调用一一映射为此协议。 ## 启动 1. 像手动启动游戏一样准备环境并切换到游戏目录——Daemon 会将其环境变量继承给游戏: export WINEPREFIX="/home/lutz/Downloads/rct_deluxe/RCT-Deluxe" cd "$WINEPREFIX/drive_c/GOG Games/RollerCoaster Tycoon Deluxe" 2. 启动 Daemon。最简单的情况:`python3 -m bridge.daemon`(直接生成 `wine RCT.EXE`)。带 gamescope 的参考设置(3072×2304,Nearest-Neighbor 缩放,针对 GOG 版本的 ddraw 覆盖): python3 -m bridge.daemon --cmd 'gamescope -w 3072 -h 2304 -W 3072 -H 2304 -F nearest -- env WINEDLLOVERRIDES=ddraw=n,b wine RCT.EXE' Daemon 将命令作为子进程启动,并在其**后代**中查找游戏(Daemon → gamescope → env → wine)——Yama Ancestor 规则涵盖任意深度的后代,因此中间的 Launcher 不是问题。一旦后代将 RCT.EXE 映射到 0x400000 处(“MZ”检查),即视为找到游戏;此后,Daemon 将在 Socket 上监听(默认:`$XDG_RUNTIME_DIR/rctbridge.sock`)。 3. 在第二个 Shell 中使用 CLI,例如 `python3 -m bridge.cli read globals ticks`。 实用技巧:`alias rctbridge='python3 -m bridge.cli'`。 4. 使用 `python3 -m bridge.cli shutdown` 退出——这将关闭 Launcher 和游戏(可在 Daemon 启动时使用 `--leave-game` 禁用,但注意:在 ptrace_scope=1 时,新的 Daemon 无法再访问孤儿进程)。 变体:`--fake` 使用 FakeMemory 后端运行 Daemon(用于在没有游戏的情况下测试协议),`--attach PID` 挂载到正在运行的实例(需要 `sudo sysctl kernel.yama.ptrace_scope=0`)。 ## 协议(每个请求和响应对应一行 JSON) | Op | 示例请求 | |---|---| | read (Region) | `{"op":"read","region":"sprites","idx":17,"off":34,"size":2}` | | read (Global) | `{"op":"read","region":"globals","name":"ticks"}` | | write | `{"op":"write","region":"sprites","idx":17,"off":48,"data":"1c"}` | | write (Global) | `{"op":"write","region":"globals","name":"climate_rain_level","data":"01"}` | | exec | `{"op":"exec","code":"...","args":{"idx":17}}` | | rollback | `{"op":"rollback","n":1}` 或 `{"op":"rollback","all":true}` | | status | `{"op":"status"}` | | shutdown | `{"op":"shutdown"}` | 响应为 `{"ok":true, ...}` 或 `{"ok":false,"error":"..."}`。 字节以 Hex 字符串形式传输。`status` 提供自检信息:包含 Base/Stride/Mode 的所有 Region 以及可读 Global 的名称。 ## 窗口与掩码 Region 对应手册 §2:`sprites`(RW 带字段掩码),`strings`(RW,强制执行 Slot 边界),`sprite_lists` 和 `rides`(RO),`globals`(具名地址,除了白名单 `globals_rw` 外均为 RO——目前为天气配方的 Climate 块,手册 §10;仅允许对 Global 的完整宽度进行写入)。字段掩码是一个白名单(§4):仅可写入 Name-ID、颜色、Invalidate 标志、peep_flags、Pathfind-Goal-Reset 和 time_lost——加上 `+0xC5/+0xC6`(Heading/Countdown),这两项仅在有访客占用 Slot(`peep_type == 0`)时可写入,因为员工会将这些字节作为 Order 字段回收利用(§4.1)。按设计原则,Stats、Position、State 和 Money 均不可写入。 ## exec 对于批量操作(逐个 poken 800 名游客会因延迟而死),可发送在 Daemon 中运行的 Python 脚本。它只能访问以下四项内容: 1. 包含 `read(region, idx, off, size)`、`read_global(name)`、`write(region, idx, off, bytes)` 和 `write_global(name, bytes)` 的 `bridge` —— 写入操作受掩码/白名单和 Journal 限制,沙箱不会开后门。 2. 用于处理 u16/u32 位级操作的 `struct`。 3. `args` —— 请求中的 JSON 对象(CLI:`--args '{"idx":17}'`)。 4. 用于中间输出的 `log(...)`;结果存入 `result`。 没有 `import`,没有 `open`,没有 `os`。防护目标是防止意外,而非防御攻击者。示例位于 `snippets/` 中(访客扫描、名称查找、M1-goto、天气调度 `make_rain.py`)。注意:脚本中的无限循环会阻塞 Daemon——v0 刻意不设超时。 因此,重复执行的扫描任务不应作为循环放在脚本中,而应放在客户端:`python3 -m bridge.heatmap_loop` (S11) 每 10 秒发送一次来自 `snippets/happiness_heatmap.py` 的扫描(根据 Happiness 设置红绿灯颜色,仅在 Bucket 更改时写入)——模式为“无 LLM 的反射层”:每次请求建立新连接,Daemon 在两次扫描之间保持空闲。停止方式:Ctrl-C 或 `touch bridge/heatmap.stop`。 ## Journal 与 Rollback 在执行任何写入操作之前,都会将旧字节和新字节写入 JSONL 文件(默认 `bridge/journal.jsonl`,支持跨运行的审计),并推入 Undo Stack。`rollback` 按后进先出 (LIFO) 原则恢复旧字节——该 Stack 仅在当前 Daemon 运行时有效。 ## MCP 服务器 `python3 -m bridge.mcp_server` 通过 stdio 将 Daemon 暴露为 MCP 服务器(JSON-RPC 2.0,每行一条消息;像其他部分一样仅使用标准库实现)。 它是一个没有自身逻辑的纯适配器:每次工具调用都会与 Daemon 建立全新的 Socket 连接——这样,顺序执行的 Daemon 绝不会卡在持久连接中,并在两次请求之间保持空闲(后续在此处运行 Watcher)。因此,Daemon 必须已经在运行;MCP 服务器刻意不启动游戏(规避 Ancestor 陷阱)。 在 Claude Code 中注册(从游戏目录执行): ``` claude mcp add rct-bridge -- python3 -m bridge.mcp_server ``` 使用不同的 Socket:`claude mcp add rct-bridge -- python3 -m bridge.mcp_server --socket /pfad/zur.sock`。 这些工具与协议 Op 一一对应:`read`、`read_global`、`write`、`write_global`、`exec`、`rollback`、`status` —— 加上 `handbook`,用于查阅 `PEEP_HANDBUCH.md`(不带参数则返回目录,带 `section` 则返回标题中包含搜索文本的第一个章节)。 `shutdown` 刻意不作为工具暴露:LLM 客户端不应能够终止游戏;此权限仅限于 CLI。 可观测性展望(已决定,将在 MCP 服务器之后实现):Daemon 中的 `watch`/`poll_events` 工具。在 Claude Code 中,该调用作为后台 Bash 进程启动,一旦事件发生即终止——从而唤醒 Main Agent(刻意针对 Claude Code 的特定设计)。设计重点:在 Daemon 中阻塞的 Wait 会导致顺序执行的 Serve Loop 卡死——要么由脚本本身在循环中轮询,要么 Daemon 设置 accept 超时并在连接之间检查条件。 ## 测试 `python3 -m pytest bridge/tests/ -q` —— 完全脱离 Wine 针对 FakeMemory 运行(地址计算、包含 Gate 的掩码、Journal、Rollback、exec 沙箱、协议、包含 Socket 端到端测试的 MCP 适配器)。针对游戏的实时验证刻意未采用 pytest:它们以带编号的脚本形式存放在 `bridge/SMOKE.md` 中。 ## v0 的限制 没有 Watcher(`register_watch`/`poll_events` 将在后续作为新的 Op 和 MCP 工具加入,见上文),exec 无超时,`rides` 以及 Item-/气球颜色仍为 RO。 ## 许可证 代码采用 MIT 许可证(参见 `LICENSE`)。文档资料 `PEEP_HANDBUCH.md` 和 `TAGEBUCH_MUSTERMANN.md` 采用 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 许可证——只要注明原作者,即允许转载使用。本仓库不包含任何游戏文件;RollerCoaster Tycoon 的版权归其权利所有者所有。
标签:LLM代理, MCP服务器, Python, Wine, 云资产清单, 内存修改, 无后门, 游戏自动化, 逆向工具, 逆向工程