公司动态

BepInEx框架入门:Unity游戏Mod开发从原理到实战

📅 2026/8/10 9:27:16
BepInEx框架入门:Unity游戏Mod开发从原理到实战
1. 项目概述为什么我们需要BepInEx如果你是一个Unity游戏的深度玩家或者是一个对游戏机制有改造热情的Mod开发者那么你一定遇到过这样的困境面对一个心爱的游戏你想添加一个新功能、修改一个数值或者仅仅是汉化界面却发现游戏本身没有提供任何官方接口。传统的“内存修改器”虽然直接但极不稳定游戏一更新就失效甚至可能触发反作弊导致封号。而“替换游戏文件”的方式更是危险一个操作失误就可能让游戏彻底崩溃。这正是BepInEx诞生的背景。它不是一个简单的“注入器”而是一个成熟、稳定、社区驱动的Unity游戏插件运行时框架。你可以把它理解为一个“操作系统”它为你的插件Mod提供了一个安全、标准化的运行环境。通过BepInEx插件可以像在操作系统上安装软件一样被安全地加载和管理而无需直接“破坏”游戏的原生文件。这解决了Mod开发中最核心的两个痛点兼容性和可维护性。游戏更新后只要其核心Unity版本和BepInEx框架本身兼容你的插件有很大概率无需修改就能继续工作同时多个插件可以和平共处互不干扰。我最初接触BepInEx是为了给一款喜欢的独立游戏添加一个物品筛选功能。在尝试了各种野路子方法经历了无数次游戏闪退和存档损坏后BepInEx的稳定和优雅让我印象深刻。它把Mod开发从“黑客行为”变成了“正规开发”让开发者可以专注于功能实现而不是和游戏底层斗智斗勇。本指南将从一个实战者的角度带你从零开始彻底掌握BepInEx的完整工作流让你也能为自己喜欢的游戏打造高质量的插件。2. BepInEx核心架构与工作原理拆解在动手之前我们必须理解BepInEx是如何“无痕”地融入游戏并管理插件的。知其然更要知其所以然这能帮助你在遇到问题时快速定位甚至进行高级定制。2.1 分层架构从启动器到你的插件BepInEx的架构非常清晰可以划分为四个核心层次启动器层 (Bootstrap)这是最先执行的部分。一个经过特殊处理的、与游戏主程序同名的可执行文件如GameName.exe或一个独立的启动器如UnityDoorstop会在游戏原始主程序启动前先行加载。它的核心任务只有一个将BepInEx的核心库BepInEx.Core.dll注入到游戏进程的地址空间中。这个过程利用了Unity Mono或IL2CPP运行时的特性实现了“寄生”启动。核心层 (Core)这是BepInEx的大脑。它被注入后会立即初始化一个轻量级的插件管理器环境。它的职责包括接管Unity引擎的启动流程通过监听Unity的初始化事件在游戏场景加载前、后等关键节点插入自己的逻辑。管理插件生命周期负责扫描、验证、加载和卸载所有插件。提供基础服务例如日志系统输出到LogOutput.log、配置文件系统、进程间通信IPC等。所有插件都依赖这些服务。插件层 (Plugins)这就是你和我将要编写的.dll文件。每个插件都是一个独立的.NET类库包含一个继承自BaseUnityPlugin的主类。BepInEx核心层会实例化这个类并调用其Awake()、Start()、Update()等与Unity MonoBehaviour生命周期类似的方法从而让你的代码在游戏世界中“活”起来。工具链层 (Utility)包括BepInEx.Packager用于打包插件、BepInEx.ConfigurationManager为插件提供图形化配置界面等辅助工具。它们不是运行时必需的但能极大提升开发和用户体验。注意理解这个分层至关重要。当你遇到“游戏打不开”的问题时问题大概率出在启动器层注入失败遇到“插件没加载”但游戏能进问题可能在核心层配置或插件层的代码兼容性上。2.2 注入原理Doorstop与MonoModBepInEx主要支持两种注入技术针对不同的Unity后端针对Mono后端旧版Unity游戏 - Unity Doorstop这是最常用、最稳定的方式。Doorstop是一个独立的原生库winhttp.dll或version.dll它利用Windows系统的DLL搜索顺序劫持机制。当你重命名游戏目录下的winhttp.dll为winhttp_o.dll并将Doorstop的winhttp.dll放入时系统会优先加载我们的DLL。这个DLL在加载后会修改Unity Mono运行时的环境变量强制其在启动时预加载BepInEx核心库。这种方式对游戏进程的侵入性极低兼容性极佳。针对IL2CPP后端新版Unity游戏 - BepInEx.IL2CPPIL2CPP将C#代码预编译为C安全性更高传统的DLL注入方式失效。BepInEx为此提供了专门的版本。其原理更复杂通常需要对游戏汇编代码进行“打补丁”Patching在IL2CPP初始化流程中插入一个钩子。这个钩子会加载一个名为BepInEx.Unity.IL2CPP.dll的本地插件由它来引导托管代码的BepInEx核心。由于涉及对游戏二进制文件的修改IL2CPP版本的安装通常更复杂有时需要依赖社区提供的特定游戏补丁或安装器。实操心得对于绝大多数单机游戏直接使用BepInEx为Mono后端提供的标准包即可。如何判断游戏后端可以查看游戏目录下是否有GameName_Data/Managed/文件夹存在则为Mono或者存在GameName_Data/il2cpp_data/等文件夹则为IL2CPP。IL2CPP的游戏需要寻找专门适配的BepInEx版本不可混用。3. 环境准备与基础安装实战理论讲完我们开始动手。我将以一个虚构的、使用Unity Mono后端的游戏《幻想冒险者》为例演示完整流程。3.1 工具与资源准备目标游戏确定你的游戏目录。例如D:\Games\FantasyAdventurer。BepInEx发行包前往BepInEx的GitHub Releases页面。对于Mono游戏下载BepInEx_x64_5.4.22.0.zip版本号请以最新稳定版为准。x64对应64位游戏如果你的游戏是32位的则需下载x86版本。文本编辑器推荐VS Code或Notepad用于编辑配置文件。插件开发环境可选后续使用Visual Studio 2022 Community版免费并安装“.NET桌面开发”和“使用Unity的游戏开发”工作负载。3.2 三步安装法从压缩包到可运行框架安装过程其实非常简单但每一步都有需要注意的细节。第一步解压与放置将下载的BepInEx_x64_5.4.22.0.zip解压你会得到如下文件和文件夹BepInEx/ ├── core/ # BepInEx核心运行库勿动 ├── patchers/ # 预处理器插件目录高级功能初期空 ├── plugins/ # **这是你将来放自己插件.dll的地方** ├── changelog.txt ├── doorstop_config.ini # **重要Doorstop注入器配置文件** └── winhttp.dll # **关键Doorstop注入器x64版**将整个BepInEx文件夹以及根目录下的winhttp.dll和doorstop_config.ini文件一并复制到你的游戏根目录即FantasyAdventurer.exe所在目录。复制后目录结构应类似FantasyAdventurer/ ├── FantasyAdventurer.exe ├── FantasyAdventurer_Data/ ├── BepInEx/ # 新增 ├── winhttp.dll # 新增 ├── doorstop_config.ini # 新增 └── ... (其他游戏文件)第二步关键配置修改用文本编辑器打开doorstop_config.ini。我们只需要关注两个关键配置[UnityDoorstop] ; 是否启用Doorstop。必须为 true。 enabled true ; 指向BepInEx核心dll的路径。通常保持默认即可。 targetAssembly BepInEx\core\BepInEx.Preloader.dll [General] ; **游戏主程序的文件名不含路径。这是最容易出错的地方** ; 如果你的游戏主程序是 FantasyAdventurer.exe这里就填 FantasyAdventurer.exe ; 如果是 Game.exe就填 Game.exe。必须完全一致包括大小写在Windows下通常不区分。 doorstop.targetAssembly FantasyAdventurer.exe保存文件。第三步首次运行与验证像往常一样双击FantasyAdventurer.exe启动游戏。如果安装成功游戏启动时在初始界面或后台BepInEx就已经开始工作了。进入游戏主菜单后退出游戏。返回游戏根目录检查BepInEx文件夹内是否新生成了LogOutput.log文件以及config、cache等文件夹。打开LogOutput.log搜索[Info]级别的日志。如果你看到类似Chainloader started和Chainloader finished的日志并且没有大量的[Error]恭喜你BepInEx框架已经成功安装并运行踩坑记录我遇到过最常见的问题是游戏闪退且没有生成日志。这几乎100%是因为doorstop_config.ini中的doorstop.targetAssembly配置错误或者游戏主程序名有特殊字符/空格导致路径解析问题。另一个可能是游戏本身已有反篡改机制需要寻找社区提供的特定绕过补丁。4. 你的第一个BepInEx插件从零开发实战框架搭好了现在我们来创造第一个插件。我们的目标是做一个简单的“欢迎Mod”在游戏启动时在屏幕左上角显示一条“BepInEx Mod Loaded!”的调试信息。4.1 创建插件项目与配置依赖新建项目打开Visual Studio创建新项目选择“类库(.NET Framework)”。项目名称设为FantasyAdventurerWelcomeMod框架选择.NET Framework 4.7.2这是与Unity旧版本Mono兼容的常见选择。引用必要DLL在解决方案资源管理器中右键“引用” - “添加引用”。浏览找到游戏目录下的FantasyAdventurer_Data/Managed/文件夹。添加Assembly-CSharp.dll这是游戏主要逻辑代码所在的程序集。这是插件能与游戏对象交互的关键。浏览找到你刚安装的BepInEx目录下的BepInEx/core/文件夹。添加BepInEx.dll和0Harmony.dllHarmony是BepInEx用于方法补丁的库后续高级功能会用到。浏览找到Unity安装目录下的Editor/Data/Managed/文件夹或从Unity Hub安装的某个版本中添加UnityEngine.dll和UnityEngine.CoreModule.dll如果找不到也可以从游戏Managed文件夹里找通常也有。修改项目属性右键项目 - 属性。在“应用程序”标签页将“程序集名称”和“默认命名空间”改为你喜欢的名字例如WelcomeMod。在“生成”标签页确保“输出路径”是bin\Debug\。4.2 编写核心插件类在项目中将默认的Class1.cs重命名为WelcomePlugin.cs并替换为以下代码using BepInEx; using BepInEx.Logging; using UnityEngine; namespace WelcomeMod { // 最重要的特性告诉BepInEx这是一个插件。 // GUID必须是全球唯一的建议使用“作者名.插件名”的格式。 // 插件名和版本号会显示在BepInEx的插件管理界面。 [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] public class WelcomePlugin : BaseUnityPlugin // 必须继承BaseUnityPlugin { // BepInEx提供的日志器用于输出信息到LogOutput.log和控制台。 internal static ManualLogSource Log; // 一个简单的开关用于控制是否显示GUI。 private static bool showGUI true; // Awake()方法在插件被加载时立即执行一次早于所有游戏对象。 private void Awake() { // 将当前实例的Logger赋值给静态变量方便其他类使用。 Log Logger; // 使用日志器输出信息。推荐使用LogInfo而非Debug.Log因为后者在发布版游戏可能被禁用。 Log.LogInfo($插件 {PluginInfo.PLUGIN_NAME} 已加载); // 订阅Unity的GUI渲染事件。OnGUI每帧都会被调用用于绘制UI。 // 这里我们使用Harmony来打补丁这是一种更规范的方式。简单演示也可以直接在Update里判断但OnGUI是绘制UI的标准位置。 } // Update()方法每帧调用一次。 private void Update() { // 示例按F1键切换GUI显示/隐藏 if (Input.GetKeyDown(KeyCode.F1)) { showGUI !showGUI; Log.LogInfo($显示GUI: {showGUI}); } } // OnGUI()方法用于绘制即时模式GUI。 private void OnGUI() { if (!showGUI) return; // 如果开关关闭则不绘制 // 创建一个在屏幕左上角的标签。 GUI.Label(new Rect(10, 10, 300, 50), BepInEx Mod Loaded! - Press F1 to hide); // 你可以在这里绘制更多GUI元素比如按钮、滑块等。 } } // 一个静态类用于定义插件的元信息保持代码整洁。 public static class PluginInfo { public const string PLUGIN_GUID com.yourname.welcomemod; public const string PLUGIN_NAME 幻想冒险者欢迎Mod; public const string PLUGIN_VERSION 1.0.0; } }4.3 编译、部署与测试编译在Visual Studio中按CtrlShiftB生成解决方案。如果一切顺利会在项目bin\Debug\目录下生成WelcomeMod.dll取决于你设置的程序集名称。部署将这个WelcomeMod.dll文件复制到游戏目录的BepInEx/plugins/文件夹下。这是BepInEx加载插件的默认位置。你可以直接在plugins下创建子文件夹来分类管理你的插件例如BepInEx/plugins/MyMods/WelcomeMod.dllBepInEx同样会递归扫描加载。测试再次启动游戏。如果一切正常你应该能在游戏画面的左上角看到“BepInEx Mod Loaded!”的文字。按F1键文字应该会消失/出现。同时查看BepInEx/LogOutput.log你应该能找到类似[Info] [WelcomeMod] 插件 幻想冒险者欢迎Mod 已加载的日志条目。恭喜你已经成功创建并运行了你的第一个BepInEx插件。这个简单的插件包含了插件声明、日志输出、输入检测和GUI绘制等基本要素是几乎所有复杂Mod的起点。5. 进阶实战使用Harmony进行游戏代码补丁显示GUI只是小试牛刀Mod的真正力量在于修改游戏原有的行为。我们不能直接修改游戏的Assembly-CSharp.dll但可以通过Harmony库在运行时将我们的代码“织入”到游戏原有的方法中。这被称为“补丁”Patching。5.1 Harmony补丁基础前置、后置与绕道假设在游戏《幻想冒险者》中有一个管理玩家生命值的类PlayerHealth其中有一个方法Heal(float amount)。我们想实现一个“超级治疗”Mod使所有治疗量翻倍。首先我们需要通过反编译工具如dnSpy或ILSpy查看游戏程序集Assembly-CSharp.dll找到这个方法的签名。假设我们找到如下代码namespace FantasyAdventurer { public class PlayerHealth { public void Heal(float amount) { currentHealth amount; if (currentHealth maxHealth) currentHealth maxHealth; UpdateHealthUI(); } } }我们的目标是修改传入的amount参数在游戏执行原方法逻辑前将其乘以2。5.2 实现“超级治疗”补丁回到我们的WelcomeMod项目或者新建一个项目。确保已引用Harmony项目应已引用0Harmony.dll。创建补丁类在项目中新增一个C#类文件命名为HealPatch.cs。using HarmonyLib; using BepInEx.Logging; namespace WelcomeMod { // HarmonyPatch特性用于指定要修补的目标类和方法。 // 第一个参数是目标类第二个参数是目标方法名。 // 我们这里使用MethodType.Normal指定是普通实例方法。 [HarmonyPatch(typeof(FantasyAdventurer.PlayerHealth), nameof(FantasyAdventurer.PlayerHealth.Heal), typeof(float))] internal class HealPatch { // HarmonyPrefix是一个前缀补丁在原方法执行**之前**运行。 // 如果返回false可以阻止原方法执行返回true则继续执行。 // 通过ref关键字我们可以修改传入的参数。 static bool Prefix(ref float amount) { // 获取我们插件主类的日志器实例。注意这里需要主类将Logger设为public static。 WelcomePlugin.Log?.LogInfo($原治疗量: {amount}); // 将治疗量翻倍 amount * 2f; WelcomePlugin.Log?.LogInfo($修改后治疗量: {amount}); // 返回true让原方法继续执行但此时amount已经是翻倍后的值了。 return true; } } }在插件主类中应用补丁修改WelcomePlugin.cs的Awake方法。private void Awake() { Log Logger; Log.LogInfo($插件 {PluginInfo.PLUGIN_NAME} 已加载); // 应用Harmony补丁 // 创建一个新的Harmony实例使用我们插件的GUID作为标识。 var harmony new Harmony(PluginInfo.PLUGIN_GUID); harmony.PatchAll(); // 自动扫描当前程序集即我们的dll中所有带有[HarmonyPatch]特性的类并应用补丁。 Log.LogInfo(Harmony补丁已应用。); }重新编译与测试重新编译项目将新的dll覆盖到BepInEx/plugins/目录下启动游戏。在游戏中触发治疗比如使用治疗药水观察游戏内治疗效果是否翻倍同时查看日志文件应该能看到我们打印的“原治疗量”和“修改后治疗量”的信息。核心技巧Harmony补丁非常强大除了Prefix前缀还有Postfix后缀在原方法执行之后运行可以修改返回值或输出参数和Transpiler绕道直接修改方法的IL代码最强大也最复杂。对于绝大多数需求Prefix和Postfix已经足够。使用补丁时务必小心确保你的修改不会破坏游戏逻辑或导致崩溃。务必在补丁方法中进行空值检查和日志记录这是调试的救命稻草。6. 插件配置与用户交互一个专业的Mod应该允许用户自定义配置。BepInEx内置了强大的配置系统并可以通过ConfigurationManager插件提供图形化界面。6.1 使用BepInEx配置系统让我们为“超级治疗”添加一个可配置的倍数而不是写死的2倍。定义配置项在WelcomePlugin类中添加配置属性。using BepInEx.Configuration; namespace WelcomeMod { [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] public class WelcomePlugin : BaseUnityPlugin { internal static ManualLogSource Log; // 声明一个静态的配置项方便补丁类访问 internal static ConfigEntryfloat HealMultiplier; private void Awake() { Log Logger; // 创建配置项 // Config.Bind(分组, 键名, 默认值, 配置描述) HealMultiplier Config.Bind(Gameplay, // 分组会在配置文件中生成[Gameplay]节 HealMultiplier, // 键名 2.0f, // 默认值2倍 治疗效果的倍增系数。设置为1.0为原版效果。); Log.LogInfo($插件 {PluginInfo.PLUGIN_NAME} 已加载治疗倍率: {HealMultiplier.Value}); var harmony new Harmony(PluginInfo.PLUGIN_GUID); harmony.PatchAll(); } } }修改补丁使用配置值更新HealPatch.cs。static bool Prefix(ref float amount) { // 使用配置值而不是硬编码的2.0f float multiplier WelcomePlugin.HealMultiplier?.Value ?? 2.0f; WelcomePlugin.Log?.LogInfo($原治疗量: {amount}, 配置倍率: {multiplier}); amount * multiplier; WelcomePlugin.Log?.LogInfo($修改后治疗量: {amount}); return true; }现在用户的配置会保存在BepInEx/config/com.yourname.welcomemod.cfg文件中。用户可以直接用文本编辑器修改。6.2 集成ConfigurationManager图形化配置手动编辑配置文件对用户不友好。我们可以引导用户安装BepInEx.ConfigurationManager插件。用户安装让用户从BepInEx的GitHub或Thunderstore等Mod站下载ConfigurationManager的dll放入BepInEx/plugins/目录。为配置项添加属性可选但推荐ConfigurationManager可以通过反射读取配置项的元数据但我们可以通过特性提供更友好的信息。HealMultiplier Config.Bind( new ConfigDefinition(Gameplay, HealMultiplier), 2.0f, new ConfigDescription(治疗效果的倍增系数。, new AcceptableValueRangefloat(0.5f, 10.0f), // 允许的范围 new ConfigurationManagerAttributes { Order 1 } // 在界面中的显示顺序 ));注意要使用ConfigurationManagerAttributes需要额外引用ConfigurationManager的dll并添加using语句这对用户不是必须的属于开发增强。安装ConfigurationManager后用户在游戏中按F1键默认热键即可呼出一个悬浮的配置窗口里面会列出所有插件的配置项并允许用户用滑块、输入框等控件实时修改数值无需重启游戏。这极大地提升了Mod的易用性。7. 调试、排查与社区资源开发Mod不可能一帆风顺游戏崩溃、插件不加载、补丁不生效是家常便饭。一套高效的调试和排查流程至关重要。7.1 日志你的第一道防线BepInEx的日志系统非常完善。LogOutput.log是首要查看的文件。[Info]: 常规信息用于跟踪流程。[Debug]: 调试信息默认可能不显示需要在BepInEx/config/BepInEx.cfg中设置[Logging.Console]和[Logging.Disk]下的LogLevels为All来开启。[Warning]: 警告可能有问题但不致命。[Error]: 错误功能可能已失效。[Fatal]: 致命错误通常是导致崩溃的原因。实操心得在你的插件代码的每个关键步骤尤其是补丁方法开始和结束都加上Log.LogDebug。当问题出现时通过日志可以清晰看到执行流在哪里中断或出现了意外值。7.2 常见问题速查表问题现象可能原因排查步骤游戏完全无法启动无日志1. Doorstop注入失败。2.doorstop.targetAssembly配置错误。3. 游戏有强反篡改。1. 检查winhttp.dll版本x86/x64是否与游戏匹配。2. 逐字符核对doorstop_config.ini中的文件名。3. 查看游戏根目录是否有doorstop.log里面有更详细的注入错误信息。4. 寻找游戏社区是否需特定破解或补丁。游戏能启动但插件未加载日志中无插件信息1. 插件dll未放在正确位置。2. 插件依赖的DLL缺失或版本冲突。3. 插件主类未标注[BepInPlugin]或继承错误。1. 确认dll在BepInEx/plugins/或其子目录下。2. 检查LogOutput.log看是否有Failed to load [你的插件dll]的错误错误信息会提示缺失哪个依赖。3. 确保插件项目引用的Unity、BepInEx等dll版本与游戏环境兼容。插件已加载日志可见但功能不生效1. Harmony补丁目标方法签名错误。2. 补丁类未正确应用harmony.PatchAll未调用。3. 游戏代码被混淆方法名/类名非预期。1. 使用dnSpy等工具再次确认目标类、方法名、参数类型的完全正确性包括命名空间。2. 在插件Awake中确认harmony.PatchAll()被调用且无异常。3. 在补丁的Prefix/Postfix方法第一行加日志确认是否被执行。4. 对于混淆游戏可能需要使用Harmony的MethodType.Getter/Setter或通过特征码Signature来定位方法。修改配置后游戏内效果未实时更新配置项未正确绑定或补丁中读取的是旧值。1. 确保在补丁中每次读取的是ConfigEntryT.Value属性而不是缓存的值。2. 对于复杂的配置考虑在配置变更事件Config.SettingChanged中更新内部缓存变量。与其他Mod冲突多个Mod修补了同一个方法且执行顺序或逻辑冲突。1. 使用Harmony的Priority优先级特性调整补丁顺序。2. 查看日志中Harmony的调试输出了解所有应用到同一方法的补丁。3. 尝试禁用其他Mod进行隔离测试。7.3 不可或缺的社区与工具BepInEx官方文档与GitHub遇到问题首先查阅 BepInEx官方文档 和 GitHub Issues 。很多基础问题和错误都有解答。游戏特定的Mod社区在GitHub、Discord、Reddit或专门的Mod网站如Nexus Mods, Thunderstore上寻找你游戏的Mod社区。那里通常有现成的BepInEx安装指南、已知问题解决方案和其他Modder的经验分享。反编译工具dnSpy/dnSpyEx功能强大的.NET反编译、调试和汇编编辑器。是Modder的瑞士军刀用于分析游戏代码结构。注意它已停止维护但dnSpyEx是活跃分支。ILSpy另一个优秀的反编译器与Visual Studio集成更好。调试器Visual Studio可以附加到游戏进程进行源码级调试。这需要将游戏程序集如Assembly-CSharp.dll作为符号服务器引用并开启Unity脚本调试支持设置较为复杂但却是解决疑难杂症的终极手段。开发BepInEx插件是一个融合了逆向工程、软件开发和社区协作的奇妙过程。从让一行文字出现在游戏屏幕上到彻底改变游戏的玩法规则其中的乐趣和成就感是巨大的。希望这份指南能为你打开这扇门。记住耐心阅读日志、善用社区资源、从小功能开始迭代是通往成功Modder之路的不二法门。