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, 游戏模组, 逆向工程