Skip to main content
Glama

圣女战旗 Mod 制作器(BotM Mod Studio)

《圣女战旗》(Banner of the Maid) 的 Mod 制作器 / Mod 编辑器:改数值与文案、 换立绘、改存档、造武器 / 道具 / 技能。GUI、CLI、MCP 三条通道共用同一套命令总线, 能力完全对等 —— 给不想敲命令的人、给脚本、也给 AI 助手。


能做什么(全部已实现)

能力

入口

改角色 / 兵种 / 道具 / 装备 / 技能 / 勋章 / 掉落 / 军团 / 台词的名称、描述

GUI · CLI · MCP

改上述各类的数值(HP/攻击/防御/敏捷/速度/幸运…)

GUI · CLI · MCP

改人物名称(六语言同步)

GUI · CLI · MCP

改地形 / 天气 / 关卡信息

GUI · CLI · MCP

改界面 UI 文案(commontext*,六语言包同步)

GUI · CLI · MCP

改剧情台词与说话人(Dialog.bin,六语言,按名字寻址)

GUI · CLI · MCP

改任务名/描述/奖励文案、章节名、成就、图鉴、帮助(ScriptControlData.bin,11 张表)

GUI · CLI · MCP

改关卡/地图里的显示标签(Level/*.level 的地形与单位名)

GUI · CLI · MCP

替换人物立绘 / 头像

GUI · CLI · MCP

换立绘图片本身(头像 / 半身 / 全身;自动裁成游戏尺寸;同一张图的全部拷贝一次换掉)

「立绘图片替换…」· texture.fit · texture.replace_all

读 / 改存档里角色的运行时数据(等级、属性、兵种、技能、装备、立绘)

save://R14/roles/2 · MCP

一个角色改到底:名称/台词(六语言)+ 等级/属性/道具,模板与存档各走各的路

「角色编辑…」面板 (Ctrl+E)

存档备份与恢复:整份存档的字节快照,恢复前自动留一份,恢复后逐字节校验

「存档备份…」面板 (Ctrl+B) · save.snapshot / save.restore

造一把新武器:以现有武器为模板,改耐久/射程/攻击/命中/防御/护甲,再叠加游戏里已有的功能(提升射程、提升士气增长、克制加成…)

「武器工坊…」面板 (Ctrl+W) · weapon-create · MCP

造一件新道具:以现有道具为模板,改名称/描述/图标/品质/售价/堆叠上限,选效果(回复生命或行动力、永久加属性、加点数/勋章)

「道具工坊…」面板 (Ctrl+I) · item-create · MCP

造一个新技能:以现有技能为模板,改触发时机与消耗,并可连带造一个新 Buff(技能的战斗数值全在 Buff 的属性通道上)

「技能工坊…」面板 (Ctrl+K) · skill-create · MCP

把某个角色整体换成另一个角色(表 + 存档,一条命令)

role-transplant · MCP

只换立绘(等级/属性/技能一律不动)

role-icons · MCP

立绘预检:名字能不能显示、自动存档有没有被游戏覆盖、游戏是不是在跑

role-diagnose · MCP

移植残留预检:换角色时哪些字段搬不过去、哪些留在原地(含会听到/看到的那些)

「角色替换…」预检表 · role-residue · MCP

立绘预览(头像 / 半身 / 全身,解析到真实纹理)

「角色替换…」面板 · role.portrait

武器工坊:克隆一件真武器,改数值、叠已有功能

「武器工坊…」(Ctrl+W)· weapon.create

道具工坊:克隆一件真道具,改十个平铺字段与效果

「道具工坊…」(Ctrl+I)· item.create

技能工坊:克隆一个真技能,改触发与消耗,连带造新 Buff

「技能工坊…」(Ctrl+K)· skill.create

仓库:把造出来的武器 / 道具变成玩家拿得到的实物

工坊「放入仓库」页 · warehouse.add

跨表影响分析(同一物品出现在多张表)

refs

批量改名 / 批量调数值

rename · scale

改动预览 → 备份 → 校验 → 回滚

全通道默认

变更报告导出(Markdown + JSON)

report

实机验证:启动游戏 → 截图 → OCR 确认 → 回滚

live/(MCP 11 个工具)

AI 助手直接操作游戏(看屏幕、按文字点击)

MCP

状态:M0–M5 完成,479 项测试(478 通过 / 1 跳过)。

角色的立绘与数值来自存档,不来自 Character.bin(PlayerData.ReadRecord 会用 Role.readRecord 把整个 Role 覆盖回来)。只改表不会让已在玩的存档变样, 所以「换角色」是一条同时改两边的命令 —— 见 docs/SAVE-FORMAT.md。

立绘换了却看不到变化,几乎从来不是数据写错。三个真实原因,GUI 的 「角色替换…」面板会逐个查出来: ① Rah 是营地自动存档,游戏离开营地时用内存里的旧角色覆盖它 —— 只写 玩家槽不写它,下次进游戏就是旧立绘;② 立绘名在资源里不存在时游戏保留上一张 图,看起来和「没生效」一模一样;③ 只改 Character.bin 对已有存档无效。

Level/*.level 与 Map/*.map 曾长期标注为「自定义二进制格式尚未逆向」—— 现已解出,见 docs/DATA-SPEC.md §5.6。


Related MCP server: local-game-mcp

快速开始

$env:PYTHONPATH="$PWD\src"

# 桌面界面
python -m botm_editor.gui.app

# 命令行
python -m botm_editor.ai.cli tables
python -m botm_editor.ai.cli search "奶油糕点"
python -m botm_editor.ai.cli read "bin://Character.bin/items/10001"

# 角色 / 立绘替换
python -m botm_editor.ai.cli role-diagnose --roles 2,4,5      # 先查为什么没生效
python -m botm_editor.ai.cli role-icons --from 503003 --to 2 --apply   # 只换立绘
python -m botm_editor.ai.cli role-transplant --from 503003 --to 2 --apply  # 整体换人

# 让 AI 助手接入(MCP,stdio)
python -m botm_editor.ai.mcp_server --root "D:\...\Banner of the Maid"

可选依赖(按需安装,缺失只会降级对应模块):

python -m pip install -i https://repo.huaweicloud.com/repository/pypi/simple `
  UnityPy Pillow PySide6 fastmcp dnfile

三种用法

1. GUI — 给不想敲命令的人

双击 启动Mod制作器.bat 就行(等价于 python tools/launch_gui.py)。 已经有窗口在开的话,它会把你已有的那扇拉到前台,而不是再开一个。 起不来的话先看 .botm_gui.log(启动器的日志)和 .botm_gui.out(GUI 自己的输出)。

左侧数据表 → 中间条目 → 右侧字段。文本字段按六种语言并排显示,数值字段直接编辑。 点「预览并应用修改」会先弹出 diff,确认后才写入。

工具栏 「角色替换…」(Ctrl+T) 是立绘/角色替换的专用面板:选供体与目标、 看三张立绘预览(头像/半身/全身,解析到真实纹理)、勾选写入哪些存档,然后预检 → 应用。预检会逐个存档告诉你:立绘名能不能显示、这个槽里还是不是旧立绘、 Rah 自动存档有没有被游戏用内存里的旧角色覆盖、以及游戏是不是正在运行。 游戏运行时面板拒绝写入 —— 那次写入是一场游戏必胜的竞态。

工具栏 「立绘图片替换…」(Ctrl+P) 换的是图本身:选角色 → 选槽位(头像/ 半身/全身)→ 选一张你自己的画 → 选适配方式 → 看预览 → 替换。图会自动裁成游戏 要的尺寸(半身 270×300、全身 1024×2048、头像 179×159 左右),并一次换掉这张图的 全部拷贝。改完要重启游戏。

工具栏 「角色编辑…」(Ctrl+E) 改的是一个角色的全部,并且把「数据分在两个 文件里」这件事摆在明面上:名称 / 全名 / 描述(六语言)写 Character.bin,等级 / 属性 / 兵种 / 道具写存档里的运行时 Role。面板并排显示两边,数值可以选择写到 「存档(当前生效)」「模板(新游戏默认)」或两边都写。写入前自动备份、写入后回读 校验,失败自动回滚。

状态:面板、写入路径与 21 条测试都已完成;真实落盘已实跑 (tools/live_role_editor_apply.py 四条分支全 PASS,存档与 Character.bin 逐字节复位)。 _apply() 是事务化的 —— 任一槽失败就逆序回滚全部,与 role.transplant 同一套修法; 模板半失败会在碰存档之前中止。

2. CLI — 给脚本

# 查
botm describe                                    # 文件全貌:所有表、别名、条目数
botm search "步兵晋级勋章"                        # 命中道具/勋章/掉落三张表
botm read "bin://Character.bin/roles/1"           # 波利娜,字段已语义化
botm refs "bin://Character.bin/items/13001"       # 这个物品还被谁引用
botm schema                                       # 导出全部字段名

# 改(默认只预览,加 --apply 才写)
botm set-text "bin://Character.bin/items/13001@name" --lang zh-CN --text "皇家步兵勋章"
botm set-text-all "bin://Character.bin/items/13001@name" --map '{\"zh-CN\":\"近卫勋章\",\"en\":\"Guard Medal\"}'
botm set-field "bin://Character.bin/roles/1@defaultMaxHP" --value 30 --apply
botm rename --match "晋级勋章" --replace "晋升勋章" --scope 13
botm scale --field defaultMaxHP --factor 1.5 --scope 1

# 立绘
botm portraits 波利娜
botm extract "ab://art/img_cha_half_1.assetbundle#Texture2D/img_cha_half_1" --out p.png
botm replace-texture "ab://art/img_cha_half_1.assetbundle#Texture2D/img_cha_half_1" --image 新图.png --apply

# 武器
botm weapon-list                                 # 137 件武器,含解码后的战斗数值
botm weapon-effects                              # 游戏里已有的全部武器功能
botm weapon-create --id 22401 --template 20101 --name "天青石试作枪" \
    --stat attack=20 --stat hit=120 --preset morale_up --preset long_range \
    --durability -1 --tier 4 --price 500 --apply

# 仓库(玩家真正拿得到的东西;新造的武器要存进来才用得上)
botm warehouse-list --slot R14                 # uid / 类型 / 名称 / 数量
botm warehouse-add --slot R14 --item 22401 --count 1 --apply

# 回滚
botm history
botm revert p-1a2b3c4d

3. MCP — 给 AI 助手

49 个工具,命名统一为 botm_*。典型流程:

botm_describe()                        → 全部数据表与别名
botm_search("步兵晋级勋章")             → 定位
botm_refs("bin://Character.bin/items/13001")  → 发现它还出现在表 13、14
botm_set_text(address, lang, text)     → dry_run=True,返回 diff
botm_apply(patch_id)                   → 确认后提交
botm_revert(patch_id)                  → 字节级还原
botm_report()                          → 变更报告

所有写入类工具 dry_run 默认 True——AI 必须先看到 diff 再提交,和人类一样。

4. Live — 让 AI 直接操作游戏(实机验证)

写盘成功 ≠ 游戏读得进去。这一层启动真实游戏、截图、再回滚:

botm_game_launch(boot_wait=20)         → 启动游戏
botm_game_shot() / botm_game_look()    → 截图(look 另带焦点与存活状态)
   ↳ AI 用自己的视觉读那张 PNG,量出目标位置
botm_game_click(fx=0.5, fy=0.77)       → 按**比例坐标**点击
botm_game_state()                      → 存活 / 崩溃记录
botm_verify_mod(address, mapping,      → 改 → 跑两次 → 拍两张图 → 回滚;
                route=[...])             把图交给你判定(不判断、不用 OCR)

MCP 侧不读屏:实机工具一律不用 OCR——botm_game_click_text 与 botm_game_read_screen 已删除,脚本里的 click_until/wait_until/advance 会被明确拒绝,所以不装 rapidocr_onnxruntime 也能全功能运行。判断权归调用方: 把截图交给视觉模型。这样更准——实测 OCR 把 VER 2.2.2.2 读成 VER 2.2.2、 把 L'étendard de la Pucelle 读成 C'etendara,标题动画那一帧干脆什么都读不出, 而视觉模型一眼就能描述。

详见 docs/LIVE-VERIFY.md。两条关键实测结论:

  • 本作忽略合成键鼠输入(SendInput),只接受 PostMessage 窗口消息 —— 好处是不需要前台焦点,AI 可以在后台驱动游戏;

  • 本作不写任何 Player.log,所以判据必须靠截图 + 视觉判定,不能读日志。

  • 战斗场景连 PostMessage 也不吃(菜单层正常)——输入中间件是 InControl, 游戏自己还有 France.Game.GameKey 门面。成因分析与后端选型见 docs/INPUT-BACKEND.md(方案 3 调研)。


地址格式

三段式:表 / 记录 / 字段。表可用编号或别名。

bin://Character.bin/roles/1                    # 表1(角色)1号 → 波利娜
bin://Character.bin/roles/1@name[ja]           # 她的日文名
bin://Character.bin/roles/1@defaultMaxHP       # 初始 HP
bin://Character.bin/roles/1@defaultDEF[0]      # 对物理攻击的防御
bin://Character.bin/items/13001@desc[zh-CN]    # 道具描述
ab://art/img_cha_half_1.assetbundle#Texture2D/img_cha_half_1   # 立绘
text://commontext#900001                       # 界面文案(容器名 = 语言包)
text://commontexten#901772                     # 英文包的"游戏设置"
lv://ah01.level#4                              # 关卡里的显示标签(序号寻址)
bin://Dialog.bin/1/11401@dialogsData[0].text[zh-CN]   # 剧情第 1 段第 1 句台词

别名:roles(角色) classes(兵种) items(道具) weapons(武器) equips(装备) skills(技能) buffs(状态) legions(军团) shops(商店) points(勋章) rewards(掉落) subtitles(台词) texts(界面文案) 等 19 个。

语言包(text:// 专用):commontext=zh-CN commontexttw=zh-TW commontexten=en commontextjp=ja commontextfr=fr commontextkr=ko。用 set_all_langs 一次改六个。

关卡标签(lv:// 专用):容器是文件名(ah01.level),# 后是字符串序号 (不是字节偏移 —— 改了长度偏移会漂移)。只读资源名、只改显示标签。


语义字段名从哪来

不是猜的——是从游戏自己的程序集里挖出来的(tools/probe/dll_class.py):

线索

来源类

Character.bin 表名

France.Resource.CharacterDataManager 成员顺序 ↔ 顶层 field 号

角色字段

France.Resource.CharacterRoleData

兵种字段

France.Resource.CharacterClassData

道具字段

France.Resource.ItemData(值级验证)

技能字段

France.Resource.SkillData(顺序完全对齐)

勋章 / 掉落 / 军团 / 台词

PointData / RewardData / LegionData / VoiceSubtitleData

LevelGlobal.bin 表名

France.Resource.LevelGlobalManager 成员顺序

地形 / 关卡字段

France.Resource.GTerrainData / GLevelInfoData

文本组结构

France.Resource.MultiLanguage {1: defaultValue, 2: multiValues}

defaultDEF 为何有 9 个值

WeaponData 定义了 9 种攻击类型(ATTACK_TYPES_COUNT = 9)

字段名附带置信度标注(high / medium / low),botm schema 可导出查看。

表标签约定(实测规律)

  • 带数值的表(角色/兵种/NG+):1..8 基础字段 → 101–106 数值块(103 是按攻击类型索引的 9 元防御数组)→ 201+ ID 数组 → 301+ 文本/语音 → 1000 附加

  • 无数值的表(技能/勋章/掉落/军团/台词):顺序标签 1..N

  • 主键:多数表是整数,少数(关卡表)是字符串,如 gcore-test


为什么这个游戏好改

实测事实

意义

Unity 5.6.5f1 + Mono(非 IL2CPP)

数据与资源可静态修改

Assembly-CSharp.dll 零混淆

可自由逆向,字段名可挖

核心数据是 Protocol Buffers

结构自描述,可保真编辑

AssetBundle 为 LZ4HC 压缩,但重存保真

立绘/文本可替换(packer="original",payload 逐字节校验)

官方无 mod 支持、无创意工坊

只能走社区自研路线(即本项目)

游戏版本 2.2.2.2

标题画面实测(旧文档写的 2.0.9 已过时)

运行时不写 Player.log

实机验证只能走截图 + 视觉判定,见 docs/LIVE-VERIFY.md

输入只认 PostMessage,忽略 SendInput

可后台驱动,不抢用户焦点


核心设计

AI 原生 = 能力对等,而非"再加个 API"

        ┌──────────────────┐
        │  CommandBus      │  ← 唯一能力实现
        └────────▲─────────┘
      ┌──────────┼──────────┐
┌─────┴────┐ ┌───┴────┐ ┌───┴─────┐
│ MCP(AI)  │ │  CLI   │ │  GUI    │
└──────────┘ └────────┘ └─────────┘

GUI 不拥有任何业务逻辑;CLI 与 MCP 只是把参数翻译成 Command。

低耦合高内聚

  • core/ 契约层,零业务依赖(CI 强制检查);

  • wire/ protobuf 引擎,不依赖契约层;

  • 每个文件格式 = 一个模块,模块之间禁止互相 import;

  • 跨模块需求(如"角色→立绘")由总线编排,不引入直接依赖。

保真编辑

serialize(parse(data)) == data     # 未修改时字节级完全一致

这是不用官方 protobuf 库的原因——后者会丢弃未声明字段,直接损坏游戏数据。 同一思路也用在资源上:重存 AssetBundle 后逐像素校验。

安全链

plan(纯计算)→ validate → diff(预览)→ apply(备份 + 原子写 + 写后校验 + 失败自动回滚)。 Windows 下还会对文件锁做重试。


目录结构

BotMModEditor/
├── docs/
│   ├── DEVELOPMENT.md         总纲:目标 / 需求矩阵 / 架构 / 路线图
│   ├── DATA-SPEC.md           实测数据结构 / 地址规范 / 资源规范
│   ├── AI-PROTOCOL.md         命令总线 / Capability Manifest / MCP 工具
│   ├── LIVE-VERIFY.md         实机验证:AI 如何直接操作游戏检验 mod
│   └── INPUT-BACKEND.md       方案 3:输入后端调研(战斗场景怎么驱动)
├── schema/                    语义档案(表名 + 字段名 + 置信度)
├── mods/
│   └── BotMInput/             实机输入后端:BepInEx 插件 + 文件 IPC(见 INPUT-BACKEND.md)
├── tools/
│   ├── probe/                 逆向侦察工具(只读)
│   └── live/                  实机驱动工具
│       ├── run.py                 按 JSON 脚本走一条路线并截图
│       ├── verify_mod.py          完整闭环:改→跑→OCR 判定→回滚
│       ├── diagnose_input.py      输入/聚焦诊断
│       └── scripts/               导航脚本
├── src/botm_editor/
│   ├── core/                  ✅ 契约层(地址/命令/变更集/总线/工作区)
│   ├── wire/                  ✅ 保真 protobuf 引擎
│   ├── modules/
│   │   ├── protobuf_bin/      ✅ Data/*.bin 读写
│   │   ├── texttable/         ✅ 界面文本表 commontext*(六语言)
│   │   ├── levelbin/          ✅ 关卡/地图 Level/*.level、Map/*.map
│   │   └── assetbundle/       ✅ 立绘提取与替换
│   ├── app/                   ✅ 装配层
│   ├── ai/                    ✅ CLI + MCP Server
│   ├── gui/                   ✅ PySide6 界面
│   └── live/                  ✅ 实机验证(截图/输入/OCR/报告)
└── tests/                     ✅ 185 项:黄金/端到端/契约/MCP/GUI/文本表/关卡/嵌套/任务/实机

免责声明

  • 仅供个人学习与单机修改使用,与游戏开发商 / 发行商无关,非官方作品;

  • 不打包任何游戏美术、音频或完整数据文件。唯一的例外是 tests/fixtures/*.bin(合计 24 KB):那是从一份已安装的游戏里裁出来的 最小测试数据,只保留几条记录,用来让解析器有东西可解析 —— 没有它, 离线测试跑不起来。删掉它们测试会失败,不是跳过。

  • 修改前请务必备份存档;本工具会自动备份游戏文件;

  • 请遵守游戏最终用户许可协议,并先拥有该游戏的合法副本。

许可证

MIT,见 LICENSE。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to play local Windows games through low-level keyboard/mouse input, screen capture, OCR, and per-game profiles for semantic actions.
    -
  • F
    license
    C
    quality
    C
    maintenance
    Enables AI agents to read local Victoria 3 save files, list saves, and analyze country, economy, politics, diplomacy, military, and technology data, while generating construction, law, and diplomatic advice.
    19
    -