qad3n/Qube-Loader
GitHub: qad3n/Qube-Loader
一款面向 Cube World alpha 版本的 Mod 加载器,通过封装逆向工程的内存布局为开发者提供干净的 C/C++ API,免于手动操作原始内存。
Stars: 1 | Forks: 0
# Cube World Alpha Mod Loader (Qube-Loader)
Qube-Loader 是一款针对 2013 年 Cube World alpha 版本的 mod loader。只需注入 32 位 DLL,
将你的 mod 放入 `mods/` 文件夹,每个 mod 就能使用干净的 C/C++ API,而无需直接操作原始内存。
通过该 API,mod 可以:
- **读取**实时游戏状态(玩家、世界、实体、物品、摄像机等)
- **监听**事件(升级、受到伤害、实体生成等)
- **拦截**游戏函数,以对其进行更改、取消或覆盖
- 使用 **loader 服务**:配置、存档数据、包含依赖关系的 manifest、mod 间的
消息传递、本地化以及 asset 覆盖
该 loader 还运行带有颜色区分的 logger,捕获游戏自身的调试输出,并
安装崩溃处理程序,让你无需附加 debugger 即可进行开发。
## 状态
这是一个早期的概念验证和业余爱好项目,专为 alpha 社区打造。
- 文档仅限于本 README 和 `modloader/sdk/` 中的头文件注释。
除此之外,源代码即是参考文档。
- 部分功能可能不完整或未完全接通。API 覆盖范围会随着时间的推移而扩大。
- ABI 是有版本控制的,但仍处于变动之中,因此针对某个开发版本构建的 mod
可能会在下一个版本中失效。
- 该 loader 是基于逆向工程的数据构建的(见下文),这些数据并非完美无缺,因此
可能会出现错误的值和 bug。
- 开发工作是在 Linux 下的 Wine 环境中进行的,而不是在 Windows 上。在真实的 Windows 机器上,输入、D3D9 和控制台的
行为可能会有所不同。
## 备份你的存档
该 loader 使用逆向工程得到的内存布局来写入游戏内存,因此偏移量错误可能会
损坏角色或世界。在运行它之前,请复制你的存档文件夹。
## 工作原理
设计原则是“轻量 mod,重量级 loader”。所有的偏移量、指针链、struct 布局、
受保护的内存访问以及 hook 都位于 loader 内部。mod 只能看到命名对象和辅助工具。
- mod 通过 `mod.player()` 请求玩家对象。loader 已经解析了
指针链,验证了对象,并读取了字段。
- 实时游戏状态每帧读取一次,并作为普通数据传递给 mod。
- 事件由 loader 检测,并传递给任何订阅了的 mod。
- Hook 允许 mod 在真实的 game function 被调用时,在 game thread 上运行,从而
可以取消调用、更改其参数或覆盖其返回值。
该 loader 是分层的,每一层只与它下面的一层进行通信:
```
mods your DLLs, #include "cube_mod.hpp" only, zero addresses
SDK sdk/cube_mod.hpp (ergonomic C++) over sdk/cube_sdk.h (versioned C ABI)
api the bridge: every mod call lands here, then dispatches to a reader/writer
modloader mod discovery + loading, the host-to-mod event bus, per-frame event sourcing
hooks mechanism only: D3D9 EndScene/Reset, the MinHook detour owner, WndProc, input
game the ONLY layer that knows addresses: offsets, per-domain readers/writers
core infrastructure: guarded memory, logging, crash handler, config
```
## 环境要求
- 2013 alpha 版 `Cube.exe` (32 位) 的副本。该 loader 本质上仅限 Win32 平台;它
hook 了一个 Windows 二进制文件,因此无法在 Linux 或 macOS 上原生运行。在 Linux 上,它
在 Wine 下运行。
- CMake 3.16 或更新版本。
- 32 位 C++17 工具链:
- Windows:Visual Studio 2019 或更新版本(“使用 C++ 的桌面开发”工作负载)。
- Linux:mingw-w64 交叉工具链(`i686-w64-mingw32-g++`)。
- Git(loader 和示例 mod 会拉取 MinHook 和 ImGui 作为 submodule)。
## 路线图
- 扩大 API 覆盖范围:为尚未开放的游戏部分提供更多的读取、写入、事件和内置 hook
(这是 ongoing 工作的主要部分)
- 引入 Lua 脚本层,让 mod 无需 C++ 编译器即可编写(也许吧)
- 服务器端支持(`Server.exe`),建立在 API 中已有的客户端/服务器边界之上
## 目录结构
```
CMakeLists.txt umbrella build (add_subdirectory modloader + example_lib + example_mod)
build.sh build.bat build on Linux (mingw) / Windows (MSVC)
run.sh run.bat build, launch Cube.exe, inject early, tail the log (Linux Wine / Windows)
cmake/ mingw cross toolchain (Linux only)
cube_mod.ini.sample sample loader config
modloader/ the loader DLL (cube_mod.dll) and its injector
src/core/ log, config, crash, guarded memory
src/game/ offsets, per-domain game readers/writers, game-log capture
src/game/gamehooks/ mod-facing game-function hooking (the intercept subsystem)
src/hooks/ D3D9 + MinHook detour + render dispatch
src/loader/ mod loading, registry, event bus, per-frame event polling
src/api/ the CubeApi bridge (one file per domain)
sdk/ the public mod SDK you compile against (split per domain internally)
cube_sdk.h umbrella for the raw versioned C ABI
cube_sdk/ the C ABI split per concern (core, enums, types, apis, api, events_hooks)
cube_mod.hpp umbrella for the ergonomic C++ layer (this is what a mod includes)
cube/ the C++ layer split per domain (player, world, creature, items, view, ...)
imgui/ Dear ImGui (git submodule) - shipped with the SDK; the loader owns the
context + backends, mods build it core-only via cube_imgui.cmake
cube_imgui.cmake one-line helper a mod uses to compile ImGui core for its own build
src/overlay/ the loader-owned ImGui overlay (context, backends, lifecycle, CubeOverlayApi)
injector/inject.cpp inject.exe, a standalone LoadLibrary injector (built by modloader)
include/minhook/ MinHook inline-hook engine (git submodule, loader only)
example_mod/ a full example mod: an ImGui menu exercising the whole API
example_lib/ a minimal headless companion mod: publishes an inter-mod service that
example_mod depends on, resolves, and messages. The smallest mod template.
```
## 你的第一个 mod
MinHook 和 ImGui 是 git 的 submodule,因此简单的 `git clone` 会让它们为空,从而导致
构建失败。请使用 submodule 进行 clone:
```
git clone --recurse-submodules https://github.com/qad3n/Qube-Loader
```
对于已经 clone 的仓库:
```
git submodule update --init --recursive
```
1. 为你的 mod 创建一个文件夹,包含自己的 `CMakeLists.txt`,用于构建 32 位
共享库,并将 `modloader/sdk` 添加到其 include 路径中。示例 mod 就是一个
可用的模板。如果需要菜单,请添加 `cube_add_imgui()`(来自
`modloader/sdk/cube_imgui.cmake`)——只需一行;如果你的 mod 不绘制任何内容,请省略它。
2. 在你的源文件中,只需包含 `cube_mod.hpp` 并编写一个 `CUBE_MOD(...)` 代码块:
#include "cube_mod.hpp"
CUBE_MOD("Hello", "1.0.0", "you")
{
mod.log.info("hello from my first mod");
}
3. 将其构建为 DLL 并将该 DLL 放在 `cube_mod.dll` 旁边的 `mods/` 文件夹中
(示例构建到 `build/mods/`)。loader 在启动时会发现并加载该文件夹中的每一个 DLL。
4. 注入 loader 并观察 `cube_mod.log`。
这就是全部的约定:一个导出了 `CubeMod_Init` 的 DLL,而该函数由 `CUBE_MOD`
宏为你生成。没有偏移量,没有手动内存访问。
## 核心功能与示例
### 读取实时游戏状态
玩家、世界、生物、物品、摄像机等都是可读取的:
```
CUBE_MOD("Reader", "1.0.0", "you")
{
mod.eventListener().onFrame([&]
{
cube::Player player = mod.player();
if (player.isAlive())
mod.log.info("hp %.0f pos %.1f,%.1f,%.1f",
player.getHealth(),
player.getPosition().x, player.getPosition().y, player.getPosition().z);
});
}
```
### 写入游戏状态(受保护)
可以安全更改的已存储值会提供 setter。写入操作由 loader 验证并保护:
```
CUBE_MOD("God Mode", "1.0.0", "you")
{
mod.eventListener().onFrame([&]
{
mod.player().setHealth(1000.0f); // refill every frame
});
}
```
### 观察事件
事件在 loader 检测到更改后触发。只读模式,游戏正常运行:
```
CUBE_MOD("Level Watcher", "1.0.0", "you")
{
mod.eventListener().onLevelUp([&](int newLevel)
{
mod.log.info("reached level %d", newLevel);
});
mod.eventListener().onDamaged([&](float damage)
{
mod.log.info("took %.0f damage", damage);
});
}
```
### 绘制 ImGui 菜单(覆盖层)
loader 拥有整个覆盖层:D3D9 hook、ImGui context、DX9 + Win32
backend、逐帧渲染、切换键、DPI 缩放、设备重置恢复以及
游戏输入冻结。mod 注册一个绘制回调并在其中编写 ImGui。这里
有一个共享的 context,因此任意数量的 mod 都可以各自绘制自己的菜单。
```
#include "cube_mod.hpp" // auto-enables mod.menu() when your build has ImGui (see below)
CUBE_MOD("My Menu", "1.0.0", "you")
{
mod.setCapabilities(cube::Capability::Overlay);
// INSERT toggles it by default; the game is frozen while it is open.
mod.menu().window("My Menu", []
{
ImGui::Text("Hello from my mod");
static bool god = false;
ImGui::Checkbox("God mode", &god);
if (ImGui::Button("Kill all")) { /* ... */ }
});
}
```
`window(title, fn)` 将你的 widget 封装在一个带标题的窗口中。如果需要多个窗口或
自定义布局,请改用 `mod.menu().onDraw(fn)`。可选设置(都具有合理的
默认值):`setToggleKey(VK_F1)`,`setOpen(true)`,`setPassthrough(true)`(一个仅显示的
HUD,让游戏保持可玩状态),`setUiScale(1.25f)`。
`mod.addMenu()` 添加一个具有自己切换键的额外菜单,例如在 F1 上显示 HUD,而 INSERT 上显示
配置面板。只要任何交互式菜单处于打开状态,loader 就会冻结游戏。如果某个菜单的绘制
不断抛出异常,它会被自动禁用,因此有问题的 mod 永远不会影响
其他 mod。
```
cube::Menu& hud = mod.addMenu();
hud.setToggleKey(VK_F1).setPassthrough(true).window("HUD", [] { ImGui::Text("hp: ..."); });
```
在你的 `CMakeLists.txt` 中加上一行代码即可将 ImGui 放入你的构建中。它会针对
loader 附带的相同 submodule 编译 ImGui 核心,因此共享的 context 在布局上是兼容的:
```
include("${CMAKE_SOURCE_DIR}/modloader/sdk/cube_imgui.cmake")
cube_add_imgui(my_mod)
```
### 拦截游戏函数(hook)
Hook 会在调用真实函数时在 game thread 上运行。处理程序可以取消它、
更改其参数或覆盖其返回值。请保持 hook 处理程序尽可能轻量:
```
CUBE_MOD("Always Crit", "1.0.0", "you")
{
// built-in hook: force every crit roll to succeed
mod.eventHook().onCritRoll([](cube::HookCall& c) { c.setReturnInt(1); });
// built-in hook: soften or ignore incoming melee hits (the damage amount is a float)
mod.eventHook().onImpact([](cube::HookCall& c)
{
if (c.damage() > 50.0f)
c.cancel(); // ignore big hits
else
c.setDamage(c.damage() * 0.5f); // halve small ones
});
// raw hook: an address you found yourself
mod.eventHook().raw(0x004889e0, cube::CallConv::Thiscall, 0,
[](cube::HookCall&) { /* ... */ });
}
```
### 受保护的原始内存(逃生舱)
当你确实持有一个原始地址时,请通过 loader 保护的辅助工具读取或写入它。
它们会首先验证页面,并返回安全的回退值,而不是导致崩溃:
```
int hp = mod.read(address); // 0 if the address is unmapped
bool wrote = mod.write(address, 100); // false if blocked or unmapped
unsigned live = mod.rebase(0x00525a30); // static address -> live address
```
## Loader 服务
loader 为每个 mod 提供了一系列服务,因此它永远不需要手动处理文件 I/O、版本控制
或 mod 间的底层通信。所有这些都通过 `mod` 访问。
### Manifest:身份、能力、依赖关系
声明你的 mod 是谁以及它需要什么。loader 以 id 为 key 管理 mod 的状态,拒绝
任何你未声明的权限,并且拒绝启动缺少依赖或依赖版本
超出范围的 mod:
```
CUBE_MOD("My Mod", "1.2.0", "you")
{
mod.setId("you.mymod"); // stable id (keys config/storage/services)
mod.setCapabilities(cube::Capability::Writes | cube::Capability::Overlay);
mod.dependsOn("you.otherlib", "1.0.0"); // require another mod loads first
mod.setPriority(10); // higher dispatches last in every reduce
mod.setUpdateUrl("https://example.com/mymod"); // shown in the load banner (offline)
}
```
未声明任何能力的 mod 是不受限制的(受信任的默认行为);一旦它声明了
任何能力,每个未声明的权限都将被拒绝并记录在案。反复出错的 mod 会
在注册表中被自动禁用,从而停止导致游戏崩溃循环。
### 每个 mod 的配置和存档存储
`mod.config()` 是用户可编辑的设置(一个 ini 文件);`mod.storage()` 是 mod 拥有的二进制
存档数据。两者均在重启后依然存在,且作用域仅限于你的 mod:
```
bool greet = mod.config().getBool("greet_on_load", true);
mod.config().setInt("log_level", 2);
int launches = mod.storage().getValue("launches", 0) + 1;
mod.storage().putValue("launches", launches);
mod.storage().setScope("world_1234"); // optional: namespace by save / world / character
```
### Mod 间服务和消息传递
一个 mod 发布服务,其他 mod 通过名称解析该服务;mod 通过 mod id 发送定向消息。
消费者在 `onReady` 时进行解析,此时所有 mod 都已加载完毕且依赖关系已解决:
```
// provider
mod.services().registerService("mymod.api", 1, &myVtable);
mod.services().onMessage([](cube::Message& m) { m.reply(m.id() + 1); });
// consumer
mod.eventListener().onReady([&]
{
MyApi* api = mod.services().query("mymod.api", 1);
int payload = 41;
int reply = mod.services().sendMessage("you.mymod", 1, &payload, sizeof(payload));
});
```
### 生命周期事件
除了 init 和 shutdown 之外,还可以响应加载完成和进入世界状态:
```
mod.eventListener().onReady([&]{ /* all mods loaded + deps resolved */ });
mod.eventListener().onWorldEnter([&]{ /* entered a world from the title/menu */ });
mod.eventListener().onWorldExit([&]{ /* returned to the title/menu */ });
```
### 本地化
根据每个 mod 的 `lang/.ini` 文件翻译 UI 字符串,支持实时切换 locale;
如果缺少 key,则回退到提供的默认值:
```
std::string title = mod.locale().translate("menu_title", "Menu");
mod.locale().setLocale("de");
```
### Asset 覆盖
通过原始文件名 key 替换游戏 asset(需要 Assets 能力和
兼容的游戏版本)。loader 会将你的字节重新编码为游戏的存储格式:
```
mod.assets().set("aim.png", pngBytes, pngSize);
bool has = mod.assets().has("aim.png");
mod.assets().remove("aim.png");
```
## 构建
Windows(Visual Studio / MSVC),在 Developer Command Prompt 中运行:
```
build.bat
```
Linux(mingw 交叉编译):
```
./build.sh
```
两者都会生成 32 位的 `build/cube_mod.dll`、`build/inject.exe` 以及示例
`build/mods/example_mod.dll`。构建类型默认为 `Release`;设置
`CUBE_MOD_BUILD_TYPE=Debug` 进行更改。
如果你在 Windows 上直接使用 CMake 进行配置,请传递 `-A Win32` 以确保目标为
32 位。不要在那里使用 mingw 工具链文件;它仅适用于 Linux 构建。
## 运行
使用运行脚本。它会进行构建,启动 `Cube.exe`,在游戏
启动的瞬间注入 loader,并实时跟踪日志:
- Linux (Wine):`./run.sh`
- Windows:`run.bat`(双击,或在 Developer Command Prompt 中运行)
**请在启动时注入,不要在游戏过程中注入。** loader 必须在游戏启动时进行 hook,这
正是运行脚本所做的工作。注入到已经处于世界中的游戏可能会
导致行为异常或崩溃。
使用 `GAME_DIR` / `GAME_EXE` 覆盖游戏位置。
在游戏中,`INSERT` 会打开示例 mod 的菜单。
在任一平台上进行手动注入时,请在启动游戏之后、加载
世界之前执行:
```
inject.exe Cube.exe path\to\cube_mod.dll
```
任何 DLL 注入器都可以使用。日志会写入到 DLL 旁边的 `cube_mod.log` 中。在
loader 控制台中输入 `END` 即可卸载它。
Loader 设置首先来自默认值,然后是 DLL 旁边的 `cube_mod.ini`,最后是
环境变量。请参阅 `cube_mod.ini.sample`。
## 相关项目:逆向工程源码
Qube-Loader 是直接参考我维护的另一个配套项目构建的:
https://github.com/qad3n/CubeWorld-Reversal
该仓库专门用于逆向工程 2013 年 Cube World 最新的已弃用 alpha 版本。
loader 使用的每个偏移量、指针链和 struct 布局都
来自于该项目的工作。由于它是反编译和重构的,而非官方的,因此
其中一部分是近似值。如果 loader 暴露的值看起来不对,那么逆向工程代码
就是检查的地方,那里的修正也会同步回 loader 中。
## 许可证
Qube-Loader 采用 GNU General Public License v3.0 进行许可。有关完整文本,请参阅 [LICENSE](LICENSE)。
第三方组件保留其各自的许可证:MinHook (BSD-2-Clause) 和 Dear ImGui
(MIT) 作为 git submodule 打包在它们各自的 `include/` 文件夹下,每个
都带有自己的 `LICENSE.txt`。
标签:Bash脚本, C/C++, Cube World, DLL注入, Linux, 事务性I/O, 云资产清单, 流量审计, 游戏Hook, 游戏模组, 逆向工程