YimMenuV2 上手指南:从零拆解 GTA V Enhanced 模组菜单的内存钩子与 Lua 扩展
【免费下载链接】YimMenuV2Experimental menu for GTA 5: Enhanced项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenuV2
YimMenuV2 是面向 GTA 5 Enhanced(GTA V Enhanced)的免费开源模组菜单框架,用 C++23 写核心、用 Lua 写扩展,靠内存扫描和 Detour 钩子把功能挂进游戏进程。这篇 GTA V 模组开发入门指南按场景带你走一遍:它管什么、怎么编译运行、一个功能从入口到生效的完整链路,外加几个高频报错的排查方法,适合第一次接触游戏模组开发的读者。
加了一行 hook 代码,却找不到该从哪下手
假设你刚 clone 了仓库,想在菜单里加一个小功能。第一反应往往是满目录找"入口",结果src/下几百个文件,每个看起来都可能是。
其实启动链路只有一条,跟着src/main.cpp里的Main()走就行:先初始化文件管理和日志,再ModuleMgr.LoadModules()加载目标模块,接着Pointers.Init()做字节模式扫描拿到关键地址,然后Hooking::Init()装钩子,最后拉起 D3D12 渲染循环。整条链路跑完你会在游戏里看到 "Loaded successfully" 的提示。
也就是说,任何功能的落地路径都逃不出三种手段:内存里找地址、把游戏函数改道到我们的代码、在脚本层调现成 API。下面按这个顺序看。
钩子引擎、Lua 脚本层和内存扫描:先看框架帮你兜底了哪些活
钩子引擎(src/core/hooking/)。src/core/hooking/Hooking.cpp里集中注册了所有 Detour 钩子:脚本线程RunScriptThreads、网络消息ReceiveNetMessage、匹配逻辑MatchmakingUpdate,甚至GetThreadContext这种系统 API 也在名单里。注册完之后Init()一次全部启用。你写新功能时通常只需要挑一个已有钩子接进去,不用自己搭拦截框架。
Lua 脚本层(src/core/scripting/)。每个.lua文件跑在独立的 LuaJIT 沙箱里,memory、pointer、natives这些 API 直接可用,menuAPI 还能给菜单动态加页签。大部分功能扩展不用碰 C++,这也是新手最该先上手的一层,用法细节在 docs/lua-api.md 里写得相当细。
内存模式扫描(src/core/memory/)。PatternScanner拿着字节签名在游戏可执行文件里定位地址,BytePatches则负责那些"改一个字节"的小手术——补丁都记录原始字节,卸载时统一恢复,不会把游戏内存改得一团糟。
三步跑起来
1. clone 仓库,生成 DLL
git clone https://gitcode.com/GitHub_Trending/yi/YimMenuV2依赖(imgui、minhook、LuaJIT 等)由cmake/下的脚本自动拉取,不用手动装。
2. 构建出 YimMenuV2.dll
Windows 上用 Visual Studio 2022 打开根目录,选 Release x64 生成即可。Linux 上直接交叉编译出 Windows DLL:
cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -jcmake/cross-compile.cmake会按顺序找 Zig 或 Clang+MinGW,缺哪个装哪个。
3. 注入游戏,确认菜单弹出
启动游戏前把 BattlEye 关掉(Rockstar 启动器设置里,或加-nobattleye启动参数)。进主菜单后用 Xenos 这类注入器把 YimMenuV2.dll 注进去,然后按INSERT或Ctrl+\。菜单弹出来、设置页能看到 Lua Scripts 列表,就说明加载链路完整。
一个完整用例:从定位入口到验证效果
先写个 Lua 脚本验证"定位入口"这条链路
下面这个脚本用 IDA 格式签名扫描游戏版本字符串。游戏更新后,你可以靠它快速判断框架依赖的签名还灵不灵:
local _, build = util.get_game_version() script.require_game_build("e.g. 1024", build) local addr = memory.scan_pattern("4C 8D 0D ? ? ? ? 48 8D 5C 24 ? 48 89 D9 48 89 FA") log.info("version pattern -> " .. (addr and addr:get_string() or "not found"))把它放进%appdata%\YimMenuV2\scripts\,重启游戏后脚本出现在设置页的 Disabled 列表里,点一下 Enable。日志cout.log里打印出版本号,说明"签名定位 → 内存读取"这条链路你已经在脚本层完整跑通了;返回 nil 则说明游戏更新改掉了这段代码,得回头查src/game/pointers/Pointers.cpp里对应的那条签名。
再看 C++ 侧:一个功能是怎么被"每帧驱动"的
打开src/game/features/self/Godmode.cpp,整个功能不到三十行:
class Godmode : public LoopedCommand { virtual void OnTick() override { if (Self::GetPed() && !Self::GetPed().IsDead()) Self::GetPed().SetInvincible(true); } virtual void OnDisable() override { if (Self::GetPed()) Self::GetPed().SetInvincible(false); } }; static Godmode _Godmode{"godmode", "God Mode", "Blocks all incoming damage"};它继承LoopedCommand(定义在src/core/commands/LoopedCommand.hpp),文件末尾的静态实例在构造时把自己注册进全局命令表,src/game/frontend/submenus/Self.cpp再用BoolCommandItem把它绑到菜单开关上。开关打开后OnTick()每帧跑一次——"修改参数"改的就是OnTick()里这个判定条件。比如把!IsDead()去掉,角色死后无敌状态也会继续挂着,行为就变了,这正是这类命令"持续生效"的来源。
验证效果:重新编译、注入,菜单 Self 页勾选 God Mode,打几枪看角色是否免伤;再取消勾选,OnDisable()会把无敌撤掉。从入口(签名 +PointerCalculator)到参数(OnTick的条件),到验证(日志加实机表现),链路是闭合的。
踩坑速查
| 现象 | 一句话解法 |
|---|---|
日志出现Could not find GTA5_Enhanced.exe | 这个项目只针对 GTA 5 Enhanced,原版 GTA V 用不了;确认游戏目录和注入方式 |
| 游戏更新后某功能失灵或钩子失效 | 多半是字节签名失配,先查cout.log里的 FATAL 记录,再核对src/game/pointers/Pointers.cpp对应模式 |
Linux 上 cmake 报No suitable cross-compilation toolchain found | 装一个 Zig 最快,或备齐 Clang 加 MinGW(ucrt 工具链) |
| 菜单没弹出来但游戏还正常跑 | 读%appdata%\YimMenuV2\cout.log,启动是顺序初始化,卡在哪个Init就看哪一步 |
延伸入口
- docs/lua-api.md:Lua 侧全部 API 的说明文档,写脚本前翻一遍能省不少事
- docs/yimmenu_v2.lua:Lua 语言服务的类型注解,配到编辑器里就有补全和类型检查
- docs/natives.lua:可调用的 native 函数清单
src/types/:游戏内部数据结构的 C++ 定义,读源码卡住时来这里对字段
下一步可以通读 docs/lua-api.md,照"先 Lua 后 C++"的顺序动手:先在脚本层跑通一个功能,再把LoopedCommand和Pointers这两条线读透,之后加新功能基本就是照着 Godmode 这个模板改模板了。
【免费下载链接】YimMenuV2Experimental menu for GTA 5: Enhanced项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenuV2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考