MelonLoader 双架构模组加载机制拆解
2026/8/25 20:47:45 网站建设 项目流程

MelonLoader 双架构模组加载机制拆解

【免费下载链接】MelonLoaderThe World's First Universal Mod Loader for Unity Games compatible with both Il2Cpp and Mono项目地址: https://gitcode.com/gh_mirrors/me/MelonLoader

MelonLoader 是一个同时支持 Il2Cpp 与 Mono 双编译架构的 Unity 游戏模组加载器,也是目前社区中跨平台覆盖最广的 Unity 模组加载方案之一。多数通用模组框架只处理 Mono 游戏,而 MelonLoader 把 Il2Cpp 游戏作为一级公民来对待——这两条路径在实现上几乎没有共享代码,理解它们各自如何"钻进"游戏进程,是读懂这个项目的关键。本文按源码结构,从注入、运行时适配到模组加载逐层拆解。

Unity 两种编译架构,决定了加载器的两条路

Unity 发布游戏时提供两种后端。Mono 架构下,C# 代码编译为 IL 字节码,Managed/目录里是一堆可以正常反射、正常用 Harmony 打补丁的 .dll,加载器要做的事相对直接。Il2Cpp 架构下,IL 先被 AOT 编译成 C++,最终产物是GameAssembly.dll(Linux 下为.so、macOS 下为.dylib)这个纯原生二进制——没有托管元数据、没有可加载的程序集,传统的"往 Mono 域里塞个程序集"思路完全不成立。

这就是 MelonLoader 代码库整体结构的设计依据:MelonLoader.Bootstrap/是原生引导层,负责在不同平台把代码送进游戏进程;其下RuntimeHandlers/按 Mono、Il2Cpp、Dotnet 三条线分别处理运行时差异;MelonLoader/Melons/则是托管域之上的模组管理层。双架构不是两个分支开关,而是两套独立的接入方案。

🔧 注入层:三种平台各走各的路

引导层要解决的第一问题是"如何进入游戏进程"。入口在MelonLoader.Bootstrap/Exports.cs,它先做一步启发式判断:进程目录下是否存在Data/<GameName>_Data/目录,以此确认目标确实是 Unity 游戏,避免误伤普通程序。确认之后分平台走三条路。

Windows:伪装成系统 DLL 的代理库

Windows 下 MelonLoader 的引导库被改名为一个系统常见的 DLL 文件名(默认version.dll,也可以叫winhttp.dlld3d9.dlldsound.dll等 14 种名字),利用游戏启动时对系统库的依赖完成加载。Proxy/ProxyResolver.cs的工作是:找到同名真实模块(本地_original.dll备份优先,其次系统目录),然后把该模块的每一个导出函数重建到代理库的导出表中,逐一转发给真实模块。游戏以为自己在加载 winhttp,实际上DllMain里跑的是 MelonLoader 的初始化代码——对游戏侧完全透明。

Linux 与 macOS:预加载与自清理

Linux 通过LD_PRELOAD(macOS 为DYLD_INSERT_LIBRARIES)在main之前挂钩引导库。这里有一个容易被忽略的细节:游戏运行时会派生子进程(Steam overlay、crash handler 等),如果预加载环境一直挂着,子进程会重复进入引导逻辑。Exports.cs里的RemoveLibraryPreloadEnv会在确认 Unity 即将进入main后,把自己从LD_PRELOAD/DYLD_LIBRARY_PATH中摘除——代码注释直言"hook libc 的 pre-main 天生危险,挂得越短越好"。

Mono 路径:在 JIT 初始化里劫持程序集搜索路径

Mono 游戏的接入点选在mono_jit_init_version——Mono 运行时初始化的必经函数。RuntimeHandlers/Mono/MonoHandler.cs用 Dobby 函数钩子(静态库位于MelonLoader.Bootstrap/Deps/)把它重定向到自己的 detour 中,处理链条是:

  1. 解析运行时版本号,判断是旧版 Mono(≤3)还是新版 MonoBleedingEdge;
  2. 重写程序集搜索路径,把BaseLibs/NetStandardPatches/(旧 Mono)或BaseLibs/MonoBleedingEdgePatches/(新 Mono)里的补丁版 BCL 前置到路径最前面;
  3. 若开启了 Debug 模式,初始化--debugger-agent调试服务器;
  4. 调回原函数,让 Mono 正常完成初始化。

第 2 步是双架构"通用"承诺的底层支撑:Unity 自带的 Mono BCL 存在不少已知缺陷,MelonLoader 不替换整个运行时,而是用逐文件前置覆盖的方式只修坏掉的那几个程序集(System.dllSystem.Xml.dll等),把兼容面控制到最小。同一文件里对mono_image_open_from_data_with_name的 detour 则支持--melonloader.monosearchpathoverride参数,允许用户指定路径整体替换某个程序集的加载来源。

Il2Cpp 路径:进程内启动 .NET 6 并从 C++ 里重建程序集

Il2Cpp 游戏里没有可用的托管环境,MelonLoader 的策略是"在原生进程里再开一个 .NET 运行时"。RuntimeHandlers/Il2Cpp/Il2CppHandler.cs钩住两个函数:il2cpp_init(运行时初始化)和il2cpp_runtime_invoke(托管方法调用入口)。

从 GameAssembly 重建托管程序集

C# 元数据虽然被 AOT 掉了,但 C++ 产物中保留了类型与方法的符号结构。Dependencies/Il2CppAssemblyGenerator/Core.cs(下称 AGF)的工作流程:计算GameAssembly的 SHA-512 哈希与上次缓存比对,哈希变化才触发重新生成;然后依次执行 Cpp2IL 反编译、Il2CppInterop 程序集重建、Unity 依赖库下载与去混淆正则映射,产出可以正常反射的托管程序集。生成结果有本地缓存,且支持--melonloader.agfoffline强制离线运行、--melonloader.agfregenerate强制重建——游戏更新后首次启动通常最慢,卡在这里属于预期行为而非故障。

在原生进程里拉起 .NET 运行时

il2cpp_init的 detour 调完原函数后交给RuntimeHandlers/Dotnet/DotnetHandler.cs。这里的输入是一段级联搜索逻辑:依次尝试配置文件指定的便携运行时、游戏目录下的dotnet/文件夹、引导库自带目录、系统安装的 hostfxr、Windows 自动安装(安装包就存在仓库Dependencies/Dotnet/里)、最后才走在线下载。找到 hostfxr 后,用MelonLoader/net6/MelonLoader.runtimeconfig.json初始化一个独立 .NET 域,加载MelonLoader.NativeHost.dll作为托管入口。

值得注意的时机设计:真正的模组启动被推迟到il2cpp_runtime_invoke第一次观测到Internal_ActiveSceneChanged方法调用时——这标志着游戏已完成场景激活、运行时状态稳定。太早跑托管代码是 Il2Cpp 游戏崩溃的高频原因,用这个"活体信号"做闸门是这套方案能稳定的关键。

托管域之上:模组的发现、排序与兼容层

托管域起来之后,MelonLoader/Melons/目录接管剩余流程。MelonHandler.Setup创建Plugins/(旧式插件)与Mods/两个目录并扫描其中的程序集;每个带MelonInfoAttribute的类型经MelonTypeBase注册后按MelonPriorityAttribute排序实例化,加载顺序因此可预测、可控制。Harmony 补丁系统有独立封装(Fixes/Harmony/),日志级别由--melonloader.harmonyloglevel控制,可打到 IL 级别。

配置走 TOML:首次运行后生成UserData/Loader.cfg,关键项都有对应启动参数:

[loader] debug_mode = true harmony_log_level = "Warn" disable_start_screen = false

另一层设计是Dependencies/CompatibilityLayers/:IPA、Muse.Dash、StressLevelZero 等流行框架的适配层都放在这里,老模组无需改动就能在新加载器上跑。这是双架构之外,MelonLoader 维持生态延续的第二个抓手。

边界、限制与下一步

把已知限制摆清楚,比罗列功能更有实用价值:

  • Il2Cpp 游戏强依赖 .NET 6 运行时。级联搜索虽然兜底能力强(最坏情况在线下载),但企业网络环境下需要预先把Dependencies/Dotnet/里的便携运行时放到游戏目录;
  • Windows 下代理 DLL 文件名可能冲突。游戏如果自带同名 DLL(如自实现的version.dll),或启用了严格的 DLM 反作弊,version.dll默认名可能失效,需要按 README 的列表换名重试;
  • 首次运行 Il2Cpp 游戏需要网络,用于 AGF 远程 API 与运行时下载,离线环境需提前用--melonloader.agfoffline准备缓存;
  • Linux 的 LD_PRELOAD 方式依赖游戏以原生方式启动,通过 Proton/Wine 跑 Steam 游戏时行为会有差异,仓库MelonLoader.Bootstrap/OSXEntry/Utils/WineUtils.cs针对这些场景做了识别,但并非所有 Wine 配置都能自动生效。

如果你想深入某个环节,建议的切入路径很明确:先跑git clone https://gitcode.com/gh_mirrors/me/MelonLoader拿到源码,读MelonLoader.Bootstrap/RuntimeHandlers/下三个 Handler 的 detour 函数(每个都只有几十行核心逻辑),再顺着Dependencies/Il2CppAssemblyGenerator/Core.csRun()方法走一遍 Il2Cpp 的生成管线——这条主线走完,整个加载器"进程怎么被拿到、程序集怎么被造出来"的问题就都闭环了。

【免费下载链接】MelonLoaderThe World's First Universal Mod Loader for Unity Games compatible with both Il2Cpp and Mono项目地址: https://gitcode.com/gh_mirrors/me/MelonLoader

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询