跳转至

参与 BML+ 开发

本文面向构建和修改 BML+ Loader 本身的贡献者。如果你只想基于已发布的 BML+ SDK 编写 Mod,请阅读原生 Mod 指南脚本 Mod 教程。编写 Mod 不需要构建本仓库。

运行链路

BallancePlayer / Virtools CK2
            |
       BMLPlus.dll
            |
       ModManager              接收 CK 生命周期和引擎回调
            |
       ModContext              发现、依赖排序、所有权和服务
        /         \
   原生 Mod       脚本宿主      IMod 回调 / CKAngelScript 回调
        \         /
       内建服务和生成式 IMC API

ModManager 将 Virtools manager 生命周期连接到 BML+。ModContext 负责 Mod 发现、依赖顺序、回调分发、公共服务和关闭。原生 Mod 通过已安装的 C++ ABI 进入, 脚本 Mod 通过 CKAngelScript 宿主进入。内建生成式 IMC Provider 只投影 Loader 已有 行为,不维护第二份实现。

开发环境

BML+ 是 Virtools CK2 使用的 32 位 Windows 插件。构建 Loader 需要:

  • Visual Studio 2019 或更新版本及 C++ 工具链;
  • CMake 3.14 或更新版本;
  • Python 3.10 或更新版本;
  • Virtools SDK 2.1;
  • 启用脚本支持时使用 CKAngelScript API 6 或更新版本;
  • 本仓库的全部 Git 子模块。

克隆仓库及其子模块:

git clone --recursive https://github.com/doyaGu/BallanceModLoaderPlus.git
cd BallanceModLoaderPlus

如果克隆时没有使用 --recursive,在配置项目前先执行:

git submodule update --init --recursive

配置、构建和测试

必须使用 x86/Win32。64 位二进制无法由 Ballance Player 加载,也不能链接 Virtools SDK 的库。

下列命令面向多配置 Visual Studio 生成器。如果 Visual Studio 不是默认生成器, 请通过 -G 指定本机安装的版本。使用 Ninja 时,应先进入 x86 Native Tools 环境,省略 -A Win32,添加 -DCMAKE_BUILD_TYPE=Debug,并为 Release 另建一个 使用 -DCMAKE_BUILD_TYPE=Release 配置的构建目录。

cmake -S . -B build-dev `
  -A Win32 `
  -DVIRTOOLS_SDK_PATH="<Virtools-SDK-2.1 路径>" `
  -DCKANGELSCRIPT_ROOT="<CKAngelScript 路径>" `
  -DBML_ENABLE_ANGELSCRIPT=ON `
  -DBML_BUILD_TESTS=ON `
  -DCMAKE_INSTALL_PREFIX="<install-dev 的绝对路径>"

cmake --build build-dev --config Debug
ctest --test-dir build-dev -C Debug --output-on-failure

使用 Visual Studio 时,Debug DLL 位于 build-dev/bin/Debug/BMLPlus.dll;单配置 生成器的输出为 build-dev/bin/BMLPlus.dll。发布前还要构建并测试 Release:

cmake --build build-dev --config Release
ctest --test-dir build-dev -C Release --output-on-failure

使用下列命令检查安装后的 SDK 目录和消费端辅助文件:

cmake --build build-dev --config Release --target install

CMake 安装目录包含 BMLConfig.cmake、公共头文件、Mod CMake 辅助函数、IMC 生成器和面向 Mod 作者的公开文档。发布脚本再向该安装树加入原生/脚本模板和编辑器 API 文件。

运行时验证

单元测试和集成测试不会运行 Virtools 或真实 Player。修改 Hook、生命周期顺序、 渲染、输入、CK 对象访问、原生 Mod 装载或脚本宿主后,还必须执行 Player 冒烟测试。

设置 BML_BALLANCE_ROOT,或向脚本传入 -BallanceRoot

powershell -ExecutionPolicy Bypass `
  -File tests/smoke/Validate-BMLBallance.ps1 `
  -BallanceRoot "<Ballance 根目录>" `
  -BuildDll "build-dev/bin/Debug/BMLPlus.dll"

替换已加载的 DLL 前先关闭 Player。测试脚本会备份现有 Loader、安装冒烟测试 资源、启动 Player、检查日志,并在结束后恢复原安装;只有显式传入 -KeepInstalled 才会保留测试文件。

找到修改的负责区域

修改目标 负责区域 最小定向验证
插件入口、Hook Block 注册或引擎拦截 src/BML.cppsrc/Behavior/HookBlock.*src/Hooks/ Win32 构建,以及覆盖该回调或 Hook 的真实 Player 场景
Building Block Prototype 配置、执行或行为图插入 src/Behavior/Runtime.*、对应的 src/Behavior/<BuildingBlock>.* 模块、src/Api/ExecuteBB.cpp 依赖测试、导出 ABI 测试、Win32 构建和受影响的 Player 测试
CK 生命周期和回调时序 src/Loader/ModManager.* 定向生命周期测试和 Player 冒烟测试
Mod 发现、依赖顺序、服务或关闭 src/Loader/ModContext.* 对应 Loader/依赖测试和原生/脚本冒烟覆盖
HUD、菜单、命令栏或内建行为 src/Mods/BMLMod.*src/HUD/src/Console/src/CustomMaps/src/Gameplay/src/UI/ 定向 UI/服务测试和 Player 画面/输入冒烟测试
旧式原生 SDK 或 CMake 消费端行为 include/BML/cmake/ ABI/编译测试、模板配置构建和安装后 SDK 检查
IMC 运行时 src/Imc/ImcApi.cppsrc/Imc/ImcRuntime.* IMC 运行时/兼容性测试和原生 IMC 冒烟测试
内建 interface struct 或其背后的读取实现 include/BML/Interface.hsrc/Api/Interfaces.cppsrc/Api/BuiltinCapabilities.* 定向 interface 测试、C ABI 编译测试和原生冒烟测试
C/脚本 seam 的不透明 CK 对象引用 include/BML/Types.hsrc/Api/ObjectRefs.* ObjectRefsTest、C ABI/IMC 编译测试;删除时序改变时再做 Player 生命周期测试
IMC 代码生成器或其示例接口 tools/imc_codegen.pytests/imc/ 生成器检查、兼容性测试,并一起审查接口、lock 和头文件
脚本发现、绑定、执行或重载 src/AngelScript/docs/api/ 定向脚本测试、API stub 检查和脚本版 Player 冒烟测试
公开文档或发布目录 docs/src/CMakeLists.txtscripts/Package-BMLRelease.ps1 中英文严格文档构建、CMake install 和 SDK stage 校验

阅读代码时先看 ModManager.cpp,再看 IMod.hIMessageReceiver.hIBML.hModContext.h 的声明。进入 ModContext.cpp 时按函数搜索,不建议 从第一行顺读整个文件。

公共接口规则

IBMLIModIMessageReceiver 等旧式原生 C++ 接口跨 DLL 边界,当前 发布线冻结其 ABI。不要修改虚函数签名或顺序、对象布局、所有权规则和跨边界 传递的类型。

BML_* C API 和 IMC 使用显式 handle、状态码及分配函数。修改时必须保持文档 规定的所有权和兼容性。新增 Loader 能力应放进经由 BML_GetInterface 取得的 带版本 interface struct;某个 Mod 向其他 Mod 提供的服务应使用生成式 IMC,而 不是增加新的临时 C++ ABI。

脚本 API 是公开的源码接口。修改绑定时,必须在同一次变更中更新脚本 API 参考、作者文档和运行时冒烟测试覆盖。

生成式接口

Loader 自己不发布任何 .imc 接口,生成器是给发布接口的 Mod 用的编写工具。 本仓库里有两个 .imc 文件,都属于测试。tests/imc/test.sample.imc 让生成器、 lock 格式和已提交的输出都保持在测试覆盖之下。tests/smoke/smoke.native.imc 属于原生冒烟 Mod,它在 Player 中既发布该接口又反过来消费它,让 Loader 的 IMC 导出在运行期持续被覆盖;bml_target_imc_api 把它的生成头写进构建树,因此仓库 里只提交 .imc 和它的 lock。

不要手工修改生成头。应修改对应的 .imc 文件并运行生成器。对示例接口即:

python tools/imc_codegen.py `
  --update-lock `
  --out-dir tests/imc/generated `
  --input tests/imc/test.sample.imc

.imc.imc.lock 和生成头应一起审查和提交。lock 保存稳定的字段与端点标识, 不要手工调整其中的数字。BML+ 的正常构建会对该示例以检查模式运行生成器,并在 已提交的绑定过期时失败。冒烟接口的生成头由构建本身重新生成,其 lock 与 .imc 不再匹配时构建会失败,并给出应运行的 bml_update_imc_locks 目标。

面向 Mod 作者的公开文档由 CMake 安装到 share/BML/docs/<语言>,发布脚本复制 该安装树。不要在打包脚本中增加第二套源码文档复制规则。编辑器 API stub 仍位于 docs/api,因为它们是工具输入,不是阅读文档。

修改约束

  • 保持游戏线程回调短小。不要在 OnProcessOnRender、引擎 Hook 或游戏线程 IMC 处理函数中阻塞。
  • CK 借用对象不转移所有权。对象删除、关卡变化和 CK reset 后要重新验证保存的 对象引用。
  • Mod 回调仍在执行时不能卸载其 DLL。
  • 使用生成式 IMC 绑定,不要手写消息载荷编解码。
  • 测试可观察行为,不要只搜索源文件中是否存在某段实现文本。
  • 同一项公开行为同时有中英文文档时,两种语言应在同一次变更中更新。

Commit 标题使用简短的英文祈使句,例如:

Fix native mod instance cleanup
Document native mod build requirements
Reject malformed event payloads

能够独立审查的行为、文档、生成物和清理工作应分别提交,不要混入无关修改。

提交前检查

  • 使用 Win32 构建并测试 Debug 和 Release。
  • 运行相关的定向测试和完整测试套件。
  • 运行时相关修改通过 Player 冒烟测试。
  • 修改接口后检查生成式 IMC 头和 .imc.lock
  • 以严格模式构建中英文文档站点。
  • 确认没有意外修改旧式原生头文件和 DLL 导出。
  • 暂存前检查最终 diff,排除无关的本地文件。