1. 项目概述为什么是BepInEx如果你是一个Unity开发者或者是一个热衷于修改Unity游戏的玩家那么“插件框架”这个词对你来说一定不陌生。从早期的UnityModManager到后来的MelonLoader社区一直在寻找一种稳定、强大且易于使用的方案来为那些没有官方模组支持的Unity游戏注入新的活力。而BepInEx正是目前这个领域里当之无愧的“瑞士军刀”。它不仅仅是一个加载器更是一个完整的、面向开发者的插件运行时环境。我最初接触BepInEx是因为想给一些单机游戏添加一些便利功能比如修改资源、调整游戏参数或者仅仅是修复一些烦人的Bug。当时市面上工具很多但要么配置复杂要么兼容性差更新一个游戏版本可能整个模组生态就崩溃了。BepInEx的出现很大程度上解决了这个问题。它通过一种非侵入式的方式注入到Unity游戏进程中为插件提供了一个标准化的运行环境。这意味着只要游戏是基于特定版本的Unity引擎尤其是IL2CPP和Mono后端BepInEx就有很大概率能正常工作插件开发者也不用为每个游戏单独适配底层加载逻辑。2024年BepInEx的生态已经非常成熟。它支持从Unity 5.4到最新的Unity 2022 LTS版本对IL2CPP脚本后端的支持更是让它成为了众多使用新版本Unity开发的商业游戏的唯一选择。无论是你想为《英灵神殿》Valheim添加地图传送还是为《潜水员戴夫》Dave the Diver制作物品编辑器BepInEx都是背后的核心支撑。这个实战指南的目的就是带你从零开始彻底搞懂BepInEx的运作原理、安装配置、插件开发到调试发布的完整流程。即使你没有任何C#或Unity插件开发经验跟着步骤走你也能创造出属于自己的游戏修改器。2. 核心架构与工作原理拆解要玩转BepInEx不能只停留在“复制文件到游戏目录”的层面。理解它的核心架构能让你在遇到问题时快速定位甚至在开发复杂插件时做出更优雅的设计。2.1 BepInEx的组成模块BepInEx不是一个单一的执行文件而是一个由多个组件协同工作的套件。典型的BepInEx 5.x或6.x版本目录结构包含以下核心部分BepInEx/core/: 这是框架的心脏。里面包含了BepInEx.Core.dll、BepInEx.IL2CPP.dll或BepInEx.Mono.dll等核心库。它们负责最底层的进程注入、程序集加载、插件管理和日志系统。BepInEx/patchers/: 存放“补丁器”Patcher的目录。这是BepInEx的高级功能。插件通常是在游戏代码加载后运行而补丁器可以在游戏程序集Assembly被加载到内存的第一时间就对其进行修改实现更底层、更强大的功能比如修改游戏核心逻辑。Harmony库用于方法级代码修补通常就在这里发挥作用。BepInEx/plugins/: 这就是我们最熟悉的插件目录。开发者编译好的插件DLL文件以及其可能的依赖项放在这里。BepInEx启动时会自动扫描并加载这个目录下的所有有效插件。BepInEx/config/: 配置文件目录。每个插件都可以在这里生成自己的.cfg配置文件允许用户在不修改代码的情况下调整插件行为。BepInEx自身的全局配置BepInEx.cfg也在这里。doorstop_config.ini和winhttp.dll(Windows) /libdoorstop.so(Linux): 这是BepInEx的“入口点”。它们利用Unity引擎的特定机制如Mono的--doorstop-enable参数或IL2CPP的注入点在游戏主程序.exe启动前抢先加载BepInEx的核心库从而取得控制权。这个过程被称为“Doorstop”。2.2 启动流程从游戏EXE到你的插件理解启动流程对排查“游戏打不开”、“黑屏”等问题至关重要。以Windows下最常见的IL2CPP游戏为例用户点击Game.exe。Doorstop拦截操作系统加载Game.exe但winhttp.dll被重命名为与游戏主程序同名的DLL或通过其他方式注入会先被加载。这个DLL会读取doorstop_config.ini获取BepInEx核心DLL的路径。加载BepInEx核心Doorstop将BepInEx的核心程序集如BepInEx.IL2CPP.dll加载到游戏进程。BepInEx初始化BepInEx核心接管初始化日志系统在BepInEx/LogOutput.log生成日志读取BepInEx/config/BepInEx.cfg全局配置。执行补丁器扫描BepInEx/patchers/目录加载并执行所有补丁器。补丁器利用Harmony等工具对游戏刚刚加载的原生代码或程序集进行预处理和修改。加载插件扫描BepInEx/plugins/目录加载所有有效的插件DLL。每个插件都必须有一个继承自BaseUnityPlugin的主类BepInEx会实例化这个类调用其Awake()、Start()等方法类似于Unity的MonoBehaviour生命周期。交还控制权BepInEx完成所有初始化后将控制权交还给游戏原来的入口点。此时你的插件已经和游戏代码一起在内存中运行了。注意很多新手遇到的“游戏打开无响应、黑屏”问题90%发生在上面的第2-4步。原因可能是BepInEx版本与游戏Unity版本不匹配、Doorstop文件被误杀、或者与其它修改器如Cheat Engine的某些驱动冲突。第一步永远是查看BepInEx/LogOutput.log文件里面的错误信息是唯一的“破案线索”。2.3 Mono vs IL2CPP关键区别Unity有两种脚本后端Mono和IL2CPP。BepInEx对它们的处理方式有根本不同。Mono传统的后端代码被编译成.NET标准的CIL中间语言程序集。BepInEx for Mono可以直接加载和反射这些.NET程序集因此兼容性最好插件开发也相对简单。IL2CPPUnity为了提升性能和安全性引入的后端。它先将C#代码编译成CIL再通过IL2CPP工具链转换成C代码最后编译为本地机器码。游戏发布后原始的.NET程序集已经不存在了取而代之的是本地代码和少量元数据。这就是为什么针对IL2CPP游戏的BepInEx需要更复杂的注入技术如BepInEx.IL2CPP并且插件开发时常需要用到“泛型方法”、“指针操作”等高级特性来与本地代码交互。简单来说为IL2CPP游戏写插件门槛更高。3. 环境准备与安装实战理论说再多不如动手装一遍。我们以一款假设使用Unity 2021.3 LTSIL2CPP后端的Windows游戏为例演示完整的安装流程。3.1 工具与资源获取确定游戏信息首先你需要知道游戏的Unity版本和脚本后端。有几种方法查看游戏目录在游戏根目录寻找UnityPlayer.dllIL2CPP或MonoBleedingEdge文件夹Mono。使用工具像UnityEX或AssetStudio这样的资源提取工具在打开游戏资源文件时通常会显示Unity版本。社区查询在游戏的模组社区如NexusMods, GitHub或Discord里通常已经有人验证了可用的BepInEx版本。下载BepInEx前往BepInEx的GitHub Releases页面。关键选择与游戏Unity版本匹配的BepInEx版本。例如对于Unity 2021.3你应该寻找标注支持该版本的BepInEx v5.x 或 v6.x 的IL2CPP版本。通常文件名会类似BepInEx_unity2021.3_il2cpp_x64.zip。如果不确定下载通用版如BepInEx_x64_VERSION.zip尝试但通用版可能不稳定。准备开发环境如需开发插件IDEVisual Studio 2022 Community免费是首选它对C#和.NET开发支持最好。.NET SDK安装.NET 6.0或.NET Framework 4.7.2 SDK根据BepInEx模板要求。BepInEx模板在VS中安装“BepInEx Pack”项目模板这能极大简化插件项目创建。3.2 分步安装指南假设我们的游戏目录是D:\Games\MyUnityGame。备份游戏复制整个游戏文件夹或至少备份Game.exe、UnityPlayer.dll等核心文件。这是安全操作的第一步。解压BepInEx将下载的ZIP包中的所有文件和文件夹直接解压到游戏根目录D:\Games\MyUnityGame。确保解压后你能在根目录看到BepInEx文件夹、doorstop_config.ini和winhttp.dll。配置Doorstop用文本编辑器打开doorstop_config.ini。你需要关注这几个关键配置[General] ; 是否启用Doorstop。保持为true。 enabledtrue ; BepInEx核心库的路径相对于游戏根目录。通常不需要修改。 targetAssemblyBepInEx/core/BepInEx.Preloader.dll ; Unity的启动参数。如果游戏启动有问题可以尝试在这里添加 --doorstop-managedldr。 ; 例如doorstopMonoDllSearchPathOverride./MyGame_Data/Managed对于绝大多数游戏默认配置即可工作。如果遇到注入失败可以尝试在[Unity]或[General]节下添加doorstopMonoDllSearchPathOverride参数指向游戏的Managed程序集目录。首次运行与日志检查双击Game.exe启动游戏。如果安装成功游戏应该能正常启动。此时立刻去检查BepInEx/LogOutput.log文件。如果日志末尾有[Message: BepInEx] Chainloader startup complete恭喜BepInEx加载成功。如果游戏闪退或黑屏查看日志文件末尾的错误信息。常见错误如“Failed to load [BepInEx.Core.dll]”可能是版本不兼容“Access Denied”可能是杀毒软件拦截了DLL文件。如果根本没有生成LogOutput.log文件说明Doorstop注入完全失败。检查doorstop_config.ini的enabled是否为true检查winhttp.dll是否被重命名有些安装包要求你将其重命名为与Game.exe同名的.dll如Game.dll并关闭所有杀毒软件的实时防护再试。安装第一个插件去模组网站下载一个为你的游戏和BepInEx版本制作的插件通常是一个.dll文件。将其放入BepInEx/plugins/目录下的一个新建文件夹例如BepInEx/plugins/MyFirstMod/。保持插件文件结构清晰是个好习惯。重新启动游戏在日志中你应该能看到你的插件被加载的信息。实操心得对于安装后游戏无响应我最常用的排查“三板斧”是一查日志LogOutput.log二关杀软特别是Windows Defender的实时保护三对版本确认BepInEx版本、Unity版本、游戏位数x64/x86完全匹配。另外有些游戏启动器Launcher会以不同的方式启动游戏主程序可能导致Doorstop失效。这种情况下需要研究如何让启动器直接调用Game.exe或者寻找针对该启动器的特殊BepInEx安装方法。4. 开发你的第一个BepInEx插件现在让我们从“使用者”变为“创造者”。我们将创建一个最简单的插件在游戏运行时在屏幕左上角显示一个“Hello BepInEx!”的文本。4.1 创建项目与配置依赖新建项目打开Visual Studio 2022使用“BepInEx 5 Plugin”模板创建新项目命名为HelloBepInExPlugin。分析项目结构模板会自动生成一个Plugin.cs文件里面包含了一个继承自BaseUnityPlugin的类。这就是插件的入口。同时项目引用了BepInEx.Core和UnityEngine等必要的NuGet包。修改元数据在Plugin类上方有[BepInPlugin]特性Attribute这是插件的“身份证”必须修改。[BepInPlugin(PluginGuid, PluginName, PluginVersion)] public class HelloBepInExPlugin : BaseUnityPlugin { public const string PluginGuid com.yourname.hellobepinex; // 唯一ID通常用反向域名 public const string PluginName Hello BepInEx; // 插件显示名称 public const string PluginVersion 1.0.0; // 版本号 // ... 其余代码 }4.2 实现核心功能GUI文本显示我们将使用Unity的OnGUI方法来绘制简单的UI。注意这不是UGUI而是IMGUI即时模式GUI适合绘制简单的调试信息。添加必要的Using指令在文件顶部确保引用了UnityEngine。创建GUI绘制逻辑在Plugin类中添加一个OnGUI方法。为了让OnGUI被Unity调用我们需要在插件启动时进行一些设置。using BepInEx; using UnityEngine; [BepInPlugin(PluginGuid, PluginName, PluginVersion)] public class HelloBepInExPlugin : BaseUnityPlugin { public const string PluginGuid com.yourname.hellobepinex; public const string PluginName Hello BepInEx; public const string PluginVersion 1.0.0; // Awake在插件被加载时调用一次早于所有游戏对象 private void Awake() { Logger.LogInfo($插件 {PluginName} 已加载); // 为了让OnGUI被调用我们需要启用GUI渲染。 // 一种简单的方式是创建一个不可见的GameObject并添加一个脚本来调用OnGUI。 // 但更直接的方式是使用HarmonyPatch来监听Unity的GUI事件这里我们用简单方法 // 我们直接挂载一个MonoBehaviour到场景中。 GameObject go new GameObject(HelloBepInEx_GUI); go.hideFlags HideFlags.HideAndDontSave; // 隐藏且不保存到场景 go.AddComponentHelloGUI(); // 添加我们自定义的GUI组件 DontDestroyOnLoad(go); // 跨场景不销毁 } } // 单独的类来处理GUI绘制 public class HelloGUI : MonoBehaviour { private void OnGUI() { // 设置一个在屏幕左上角的矩形区域 Rect rect new Rect(10, 10, 200, 50); // 绘制一个带背景色的盒子 GUI.Box(rect, GUIContent.none); // 在盒子内绘制文本 GUI.Label(rect, $Hello BepInEx!\nTime: {Time.time:F2}, new GUIStyle(GUI.skin.label) { fontSize 20, normal { textColor Color.green } }); } }这个实现创建了一个永久的GameObject来承载OnGUI绘制。DontDestroyOnLoad确保这个对象在切换游戏场景时不会被销毁。4.3 编译、部署与测试编译项目在Visual Studio中按CtrlShiftB生成解决方案。在项目的bin/Debug或bin/Release目录下你会找到生成的HelloBepInExPlugin.dll文件。部署插件在游戏的BepInEx/plugins/目录下创建一个新文件夹例如HelloBepInEx。将编译好的HelloBepInExPlugin.dll文件复制到这个文件夹内。运行测试启动游戏。如果一切正常你将在游戏画面的左上角看到绿色的“Hello BepInEx!”文字以及游戏运行时间。同时查看BepInEx/LogOutput.log应该能看到[Info : Hello BepInEx] 插件 Hello BepInEx 已加载的日志信息。注意事项直接在插件主类中使用OnGUI可能不会被Unity调用因为BaseUnityPlugin本身并不是一个MonoBehaviour。因此我们通过创建附加了自定义MonoBehaviour的GameObject来绕过这个限制。这是BepInEx插件开发中一个非常常见的模式。另外IMGUI (OnGUI) 性能开销较大仅适用于显示简单信息。复杂的UI建议使用游戏自带的UI系统如果暴露了接口或者更高级的UI框架如UnityExplorer的UI组件。5. 进阶开发配置、热重载与Harmony补丁一个成熟的插件需要配置、更稳定的功能以及修改游戏原有代码的能力。5.1 使用ConfigurationManager进行配置BepInEx内置了配置系统但手动编辑cfg文件不友好。我们可以使用社区插件ConfigurationManager来提供图形化配置界面。添加依赖通过NuGet为你的插件项目安装BepInEx.Configuration包通常模板已包含。同时用户需要在游戏中安装ConfigurationManager插件。创建可配置项在插件的Awake方法中定义配置绑定。private void Awake() { Logger.LogInfo($插件 {PluginName} 已加载); // 1. 定义配置项 Config.Bind(外观, // 配置节(Section) 文本颜色, // 配置键(Key) Color.green, // 默认值 屏幕上显示的文本颜色); // 描述 Config.Bind(外观, 字体大小, 20, new ConfigDescription(字体大小, new AcceptableValueRangeint(12, 36))); // 带范围限制的描述 Config.Bind(功能, 启用显示, true, 是否启用屏幕文本显示); // 2. 从配置中读取值 bool isEnabled Config[功能, 启用显示].BoxedValue as bool? ?? true; int fontSize (int)(Config[外观, 字体大小].BoxedValue); // 颜色需要序列化/反序列化这里简化处理实际可用ColorUtility // 我们将配置传递给GUI组件 GameObject go new GameObject(HelloBepInEx_GUI); go.hideFlags HideFlags.HideAndDontSave; var guiComp go.AddComponentHelloGUI(); guiComp.IsEnabled isEnabled; guiComp.FontSize fontSize; DontDestroyOnLoad(go); }修改HelloGUI类使其使用这些配置值。public class HelloGUI : MonoBehaviour { public bool IsEnabled true; public int FontSize 20; public Color TextColor Color.green; private void OnGUI() { if (!IsEnabled) return; Rect rect new Rect(10, 10, 200, 50); GUI.Box(rect, GUIContent.none); GUIStyle style new GUIStyle(GUI.skin.label); style.fontSize FontSize; style.normal.textColor TextColor; GUI.Label(rect, $Hello BepInEx!\nTime: {Time.time:F2}, style); } }用户配置用户安装ConfigurationManager后在游戏中按F1默认即可打开配置窗口找到你的插件并实时修改“文本颜色”、“字体大小”等选项修改后立即生效。5.2 实现热重载Hot Reload热重载允许你在不重启游戏的情况下重新加载插件代码极大提升开发效率。这通常需要借助第三方工具如BepInEx.ConfigurationManager也支持简单的配置热重载但对于代码热重载UnityExplorer或BepInEx.Debug工具更强大。这里介绍一种利用FileSystemWatcher监听DLL变化并重新加载的简单思路需谨慎使用可能不稳定// 在Plugin.Awake()中添加 private static FileSystemWatcher _watcher; private void SetupHotReload() { string pluginPath Path.Combine(Paths.PluginPath, HelloBepInEx); string dllPath Path.Combine(pluginPath, HelloBepInExPlugin.dll); if (!File.Exists(dllPath)) return; _watcher new FileSystemWatcher(pluginPath, HelloBepInExPlugin.dll); _watcher.NotifyFilter NotifyFilters.LastWrite; _watcher.Changed OnPluginDllChanged; _watcher.EnableRaisingEvents true; Logger.LogInfo(热重载监听已启用。); } private void OnPluginDllChanged(object sender, FileSystemEventArgs e) { Logger.LogWarning(检测到插件DLL变化尝试热重载...); // 注意直接重新加载程序集非常复杂涉及域隔离、类型卸载等。 // 生产环境不建议使用简单的FileSystemWatcher实现完整热重载。 // 更推荐使用专门的开发工具链如BepInEx的Chainloader调试模式或UnityExplorer的C# REPL。 }更可靠的热重载方案是使用BepInEx 6的PluginReload特性如果目标游戏支持或者使用像SpaceWarp针对《Kerbal Space Program 2》等模组框架提供的热重载机制。5.3 使用Harmony进行代码修补这是BepInEx最强大的功能之一。Harmony库允许你在运行时修改游戏原有的方法。例如我们想修改玩家收到伤害时的逻辑。添加Harmony依赖通过NuGet安装Lib.Harmony包。创建补丁类using HarmonyLib; [HarmonyPatch] // 声明这是一个Harmony补丁类 public static class PlayerDamagePatch { // 假设游戏有一个 PlayerController 类其中有一个 TakeDamage 方法 // 我们需要知道方法的完整签名。这通常需要通过反编译工具如dnSpy, ILSpy分析游戏程序集获得。 // 这里我们假设签名是public void TakeDamage(float damage) [HarmonyPrefix] // 前缀补丁在原方法执行前运行 [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.TakeDamage))] static bool Prefix_TakeDamage(ref float damage) { // 如果你想将受到的伤害减半 damage * 0.5f; Logger.LogInfo($伤害被修改为: {damage}); // 返回 true 表示继续执行原方法返回 false 则会跳过原方法。 return true; } [HarmonyPostfix] // 后缀补丁在原方法执行后运行 [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.TakeDamage))] static void Postfix_TakeDamage(float damage) { Logger.LogInfo($玩家受到了 {damage} 点伤害。); } }在插件启动时应用补丁在插件的Awake方法中创建Harmony实例并应用所有补丁。private Harmony _harmony; private void Awake() { Logger.LogInfo($插件 {PluginName} 已加载); // ... 其他初始化代码 // 应用Harmony补丁 _harmony new Harmony(PluginGuid); // 使用插件的GUID作为Harmony ID _harmony.PatchAll(); // 自动搜索当前程序集中所有[HarmonyPatch]标记的类并应用补丁 Logger.LogInfo(Harmony补丁已应用。); } private void OnDestroy() { // 插件卸载时移除所有补丁可选但建议 _harmony?.UnpatchSelf(); Logger.LogInfo(Harmony补丁已移除。); }重要提示使用Harmony需要精确知道目标类和方法名、参数类型。这通常需要对游戏代码进行逆向工程。错误的方法签名会导致游戏崩溃。务必在开发阶段进行充分测试。6. 调试、打包与发布6.1 调试插件调试是开发中最关键的环节。日志输出Logger.LogInfo、LogWarning、LogError是你的好朋友。所有日志都写入BepInEx/LogOutput.log。对于复杂逻辑可以输出关键变量的值。附加调试器使用Visual Studio的“附加到进程”功能选择游戏进程。在BepInEx配置BepInEx.cfg中可以启用[Logging.Console]下的Enabled这样日志也会输出到一个控制台窗口方便查看。更强大的工具是使用UnityExplorer它提供了一个内置的C#交互式控制台和对象浏览器可以实时查看和修改游戏对象、调用方法是调试神器。处理异常用try-catch块包裹可能出错的代码并在catch中记录详细的异常信息包括ex.ToString()。6.2 插件打包一个规范的插件包应该方便用户安装。标准结构YourAwesomeMod-v1.0.0.zip ├── BepInEx/ │ └── plugins/ │ └── YourAwesomeMod/ 以插件名命名的文件夹 │ ├── YourAwesomeMod.dll 主插件文件 │ ├── manifest.json 可选元数据文件 │ ├── icon.png 可选图标 │ └── README.md 说明文档 ├── CHANGELOG.md 更新日志 └── README.md 总说明清单文件manifest.json虽然不是BepInEx强制要求但许多模组管理器如r2modman和社区网站如Thunderstore需要它来识别插件。{ name: Your Awesome Mod, version_number: 1.0.0, website_url: https://github.com/yourname/yourawesomemod, description: This mod does awesome things!, dependencies: [ BepInEx-BepInExPack-5.4.2100 ] }依赖管理如果你的插件依赖其他BepInEx插件如ConfigurationManager务必在manifest.json的dependencies字段和你的README中明确说明。6.3 发布与维护选择平台NexusMods, Thunderstore, GitHub Releases是常见的发布平台。选择你的游戏社区最活跃的平台。清晰文档在README中写明功能、安装方法、配置说明、常见问题解答FAQ。版本管理使用语义化版本控制如主版本.次版本.修订号。每次更新都更新CHANGELOG。社区互动积极回复用户的Issue和反馈。BepInEx插件生态很大程度上依赖于社区的贡献和维护。从理解原理到动手安装从编写第一个“Hello World”到使用Harmony修改游戏逻辑再到最后的调试发布这条路径涵盖了掌握BepInEx的核心技能。记住耐心和仔细阅读日志是解决所有问题的关键。每个游戏都是一个独特的沙盒探索和改造它的过程本身就是最大的乐趣。