
1. 项目概述为什么你需要BepInEx如果你是一个Unity游戏的玩家尤其是那些在Steam创意工坊里拥有海量模组的游戏比如《雨中冒险2》、《英灵神殿》或者《星露谷物语》那么你大概率已经和BepInEx打过照面了只是你可能没意识到。它就像一个幕后的舞台经理默默支撑着成千上万个玩家自制插件Mod在游戏里稳定运行。简单来说BepInEx是一个为Unity引擎游戏设计的插件加载与扩展框架它允许开发者或者有热情的玩家在不修改游戏原始文件的前提下向游戏注入自定义代码实现从修改游戏数值、添加新功能到彻底改变游戏玩法的各种可能。我最初接触BepInEx是为了给一个老游戏添加中文补丁和性能优化插件。当时面对一堆.dll文件和混乱的安装说明确实走了不少弯路。后来发现无论是想自己动手写个小Mod还是仅仅想更优雅地管理别人制作的插件深入理解BepInEx的工作机制都至关重要。它不仅仅是把文件丢进一个文件夹那么简单理解了它的加载流程、配置管理和依赖处理你就能解决99%的Mod安装失败、冲突崩溃问题甚至能自己动手修复一些兼容性问题。这篇指南的目标就是帮你从“只会照着教程复制粘贴”的模组使用者升级为“知其然更知其所以然”的框架掌握者无论是安装、调试还是开发都能游刃有余。2. BepInEx核心架构与工作原理拆解要玩转BepInEx死记硬背安装步骤是没用的必须搞清楚它到底是怎么“劫持”并扩展一个Unity游戏的。这能让你在遇到问题时快速定位到是哪个环节出了岔子。2.1 启动流程从游戏EXE到插件加载一个标准Unity游戏的启动流程是玩家双击Game.exe- 系统加载游戏主程序 - Unity运行时初始化 - 加载游戏资源与代码。BepInEx介入后这个流程被巧妙地改变了。BepInEx的核心是一个名为winhttp.dll在Windows上或类似的本地库文件。它利用了操作系统的DLL搜索顺序机制。当你把BepInEx的文件解压到游戏根目录后这个winhttp.dll会被放在和Game.exe同级的位置。系统在启动游戏时会优先加载当前目录下的这个DLL而不是系统目录里的那个。这个被BepInEx替换掉的winhttp.dll就是整个框架的“引导器”。它的工作流程可以概括为引导阶段BepInEx的winhttp.dll首先被加载。它不会真的去处理HTTP请求而是立即启动自己的引导程序。环境准备引导程序检查当前运行环境如.NET Framework或.NET Core版本然后加载BepInEx的核心组件BepInEx.Core.dll。游戏进程接管核心组件会挂接到Unity引擎的底层函数上特别是UnityEngine.Application的启动路径。这相当于在游戏自己的代码开始执行前先拿到了控制权。插件加载BepInEx在游戏场景初始化前扫描BepInEx/plugins目录。对于找到的每一个有效的插件DLL即继承了BaseUnityPlugin的类库它使用.NET的反射机制动态加载程序集创建插件实例并调用其Awake()、Start()等方法其执行时机与Unity自身的MonoBehaviour生命周期紧密关联。控制权交还所有插件初始化完毕后BepInEx将控制权交还给游戏原本的启动流程游戏正常启动但此时你的插件代码已经像“特洛伊木马”一样运行在游戏进程内部了。注意这就是为什么你必须将BepInEx文件放在游戏根目录与Game.exe同级并且可能需要关闭杀毒软件。因为修改系统DLL加载行为和向进程注入代码的行为很容易被安全软件误判为病毒或木马。2.2 核心目录结构解析安装完BepInEx后游戏根目录下会生成一个BepInEx文件夹它的结构清晰各司其职BepInEx/ ├── core/ # BepInEx自身的核心运行库如 BepInEx.Core.dll, 0Harmony.dll等。通常不需要手动修改。 ├── plugins/ # 【最重要】用户插件目录。你下载的或自己开发的插件DLL都应放在这里。每个插件通常有自己的子文件夹。 ├── patchers/ # 预处理器Preloader Patchers目录。用于在游戏程序集加载初期进行更底层、更复杂的修改普通插件开发者较少涉及。 ├── config/ # 配置文件目录。BepInEx核心配置BepInEx.cfg以及每个插件的配置文件如插件名.cfg都存放于此。 └── LogOutput.log # 运行日志文件。排查问题的第一手资料任何启动错误、插件异常都会记录在此。理解这个结构至关重要。例如当你安装一个Mod时如果说明写着“把.dll文件放到plugins文件夹”你就需要看清楚是直接放在plugins根目录还是放在plugins/作者名/插件名/这样的子目录下。很多插件需要自己的子文件夹来存放附属资源如图片、配置文件。2.3 Harmony库运行时“打补丁”的魔法BepInEx的强大功能很大程度上依赖于一个名为Harmony的第三方库。Harmony允许你在运行时修改其他程序集包括游戏本身的代码的方法函数。这被称为“补丁”。假设游戏里有一个计算伤害的函数public class Player { public int CalculateDamage(int baseDamage) { return baseDamage * 2; // 游戏原版伤害翻倍 } }你觉得伤害太高了想改成1.5倍。如果没有Harmony你几乎无法修改这个已经编译好的游戏代码。但有了Harmony你可以在你的插件里写一个“后置补丁”[HarmonyPatch(typeof(Player), nameof(Player.CalculateDamage))] [HarmonyPostfix] public static void CalculateDamage_Postfix(ref int __result) { __result (int)(__result * 0.75f); // 将原结果乘以0.75相当于原伤害*2*0.751.5倍 }游戏运行时Harmony会确保你的这段代码在原函数CalculateDamage执行之后运行并修改其结果。你还可以使用[HarmonyPrefix]在原函数执行之前运行甚至用[HarmonyTranspiler]直接修改函数的IL代码中间语言实现极其复杂的逻辑变更。实操心得Harmony补丁是Mod开发的利器但也极其危险。一个错误的补丁可能导致游戏崩溃或难以预料的Bug。务必确保你的补丁方法签名完全正确并且充分理解原函数的上下文。在开发时善用BepInEx的日志和调试工具来验证补丁是否成功应用。3. 从零开始BepInEx的安装与配置详解网上很多教程只给一个“下载解压到游戏目录”的步骤但其中有很多细节决定了安装的成败。3.1 版本选择与下载首先不要盲目下载最新版。BepInEx的版本需要与你的游戏所采用的.NET运行时版本匹配。访问BepInEx的GitHub发布页。查看游戏需求通常较新的Unity游戏如使用Unity 2019可能基于**.NET Framework 4.x** 或.NET (Core) 6/7/8。而老游戏Unity 5.x, 2017可能基于**.NET Framework 3.5**。BepInEx 5.x 版本通常用于**.NET Framework 4.7.2及以上或.NET Core**环境。BepInEx 4.x 版本则兼容**.NET Framework 3.5**。一个简单的判断方法是用记事本打开游戏目录下的GameName_Data/Managed/Assembly-CSharp.dll如果存在查看其属性或者看游戏根目录是否有unityplayer.dll和GameName_Data文件夹。更可靠的方法是查阅该游戏Mod社区的推荐版本。下载时选择BepInEx_x64_版本号.zip对于64位游戏或BepInEx_x86_版本号.zip对于32位游戏。绝大多数现代游戏都是64位的。3.2 标准安装步骤与避坑指南备份游戏在进行任何Mod操作前备份整个游戏文件夹或至少备份GameName_Data文件夹。Steam用户可以使用“验证游戏文件完整性”来恢复但备份是最稳妥的。解压到根目录将下载的ZIP包内所有文件和文件夹解压到你的游戏安装目录。这个目录应该包含Game.exe或游戏的主可执行文件。首次运行启动游戏。如果安装成功游戏启动时可能会有一个短暂的命令行窗口闪过然后游戏正常启动。启动后检查游戏目录是否生成了BepInEx文件夹及其子目录。检查日志打开BepInEx/LogOutput.log。如果看到类似[Info : BepInEx] BepInEx 5.4.21.0 - ...的日志并且没有大量的[Error]说明BepInEx核心加载成功。常见安装失败原因杀毒软件拦截这是最常见的问题。Windows Defender或其他安全软件可能将winhttp.dll或注入行为视为威胁。你需要将游戏目录添加到杀毒软件的排除列表白名单中并在安装时临时关闭实时保护。版本不匹配使用了错误版本的BepInEx导致与游戏.NET运行时冲突。症状是游戏无法启动或闪退。仔细核对游戏社区推荐的版本。文件位置错误没有把文件解压到真正的游戏根目录。例如Steam库的游戏可能在一个深层路径里而你解压到了上一级文件夹。管理员权限某些游戏或系统路径需要管理员权限才能写入。尝试以管理员身份运行游戏一次。3.3 核心配置文件解读BepInEx/config/BepInEx.cfg文件控制着框架的全局行为。用记事本或任何文本编辑器打开它你会看到类似以下的配置段[Logging] # 日志输出到控制台。开启后游戏启动时会显示一个命令行窗口显示日志便于调试。 Enabled false [Logging.Console] # 日志输出到文件。务必保持为true。 Enabled true [Chainloader] # 插件加载顺序。除非你知道自己在做什么否则不要轻易修改。 DependencyErrors FailLoad对于大多数用户保持默认配置即可。但有两个设置对开发者或高级用户很有用[Logging.Console]下的Enabled设为true后启动游戏会弹出一个控制台窗口实时显示所有日志信息调试插件时极其方便。[Preloader]下的Entrypoint极少数情况下如果游戏的可执行文件不是标准的名字你可能需要在这里指定。每个插件也会在config目录下生成自己的.cfg文件用于配置该插件的各项参数玩家可以在游戏内通过专门的配置管理器插件如ConfigurationManager来图形化地修改这些设置无需手动编辑文本文件。4. 插件Mod的安装、管理与冲突解决BepInEx本身只是一个平台真正的功能由插件Mod提供。管理好插件是享受模组游戏的关键。4.1 插件的安装与组织插件通常以.dll文件形式存在有时会附带一些资源文件如图片、JSON配置模板。基本安装将插件的主DLL文件放入BepInEx/plugins目录。许多插件要求放在以作者或插件名命名的子文件夹内例如BepInEx/plugins/AuthorName/AmazingMod/AmazingMod.dll。务必阅读Mod发布页的安装说明。依赖管理许多插件依赖于其他库例如MMHOOK (MonoMod.RuntimeDetour)用于事件钩子。UnityEngine.UI或UnityEngine.Audio等Unity模块。其他基础Mod库。 这些依赖库通常需要被放置在BepInEx/plugins目录下或者更常见的放在BepInEx/patchers或BepInEx/core目录具体看依赖库的说明。缺失依赖会导致插件加载失败并在日志中报错。使用Mod管理器对于大型模组社区如《星露谷物语》的SMAPI虽不同但理念相似或《英灵神殿》的社区可能会有专门的Mod管理器工具。这些工具能自动处理下载、安装、更新和依赖解决强烈推荐使用。4.2 排查插件加载失败与游戏崩溃游戏闪退、黑屏或功能异常第一步永远是查看BepInEx/LogOutput.log。日志分析实战假设日志末尾出现这样的错误[Error : BepInEx] Could not load [MyCoolMod.dll] because it has missing dependencies: MyCoolMod, Version1.0.0.0, Cultureneutral, PublicKeyTokennull - Assembly-CSharp, Version0.0.0.0, Cultureneutral, PublicKeyTokennull这表示MyCoolMod.dll未能加载因为它依赖Assembly-CSharp程序集但版本对不上。这可能是因为Mod是为游戏的不同版本如v1.0编译的而你现在运行的是v1.1。解决方案是寻找与你游戏版本匹配的Mod更新。再比如[Error : Unity Log] NullReferenceException: Object reference not set to an instance of an object at MyCoolMod.PlayerPatch.Update () [0x00000] in filename unknown:0这是一个运行时错误说明插件MyCoolMod里的PlayerPatch.Update方法试图访问一个空null的对象。这通常是插件自身的Bug或者与你游戏内的其他Mod冲突。系统化排查步骤二分法如果安装了大量Mod后出现问题使用“二分法”排查。将一半的Mod移出plugins文件夹测试游戏。如果问题消失说明问题出在被移出的那一半里如果问题依旧则出在剩下的那一半。重复此过程逐步缩小范围直到定位到导致冲突的具体Mod。检查更新确保BepInEx、所有插件以及其依赖库都是最新版本并且与当前游戏版本兼容。查看社区在Mod的发布页面如GitHub、NexusMods或相关游戏社区查看“Posts”或“Bugs”板块看看其他用户是否遇到相同问题及解决方案。纯净测试临时移除所有插件清空plugins文件夹只保留BepInEx核心看游戏是否能正常启动。这能确定问题是BepInEx本身引起的还是某个插件引起的。4.3 插件配置的修改与热重载许多插件支持运行时配置。配置文件位于BepInEx/config目录以插件名.cfg命名。你可以用文本编辑器修改它们但修改后通常需要重启游戏才能生效。对于开发者或高级用户有一些工具可以实现“热重载”ConfigurationManager这是一个非常流行的BepInEx插件它为所有支持配置的插件提供一个游戏内的图形化设置界面。你可以直接在游戏内修改设置部分设置可以即时生效无需重启。开发环境如果你在Visual Studio等IDE中开发插件并启用了BepInEx的EnableDebugging配置可以结合dnSpy等调试器实现代码的热重载但这属于开发范畴。5. 进阶实战开发你的第一个BepInEx插件理解了原理和管理方法后自己动手写一个插件能让你对这套框架的理解达到新的高度。这里我们创建一个最简单的插件在游戏屏幕上显示一段自定义文字。5.1 开发环境搭建安装Visual Studio推荐使用Visual Studio 2022 Community版免费且功能强大。安装时确保勾选“.NET桌面开发”工作负载。创建类库项目打开VS新建项目 - 选择“类库(.NET Framework)”或“类库(.NET)”具体取决于你的游戏目标框架例如针对.NET Framework 4.7.2。项目名称设为MyFirstPlugin。引用必要的DLL在解决方案资源管理器中右键“引用” - “添加引用”。浏览并添加游戏目录下的BepInEx/core文件夹中的0Harmony.dll(Harmony库)BepInEx.dll(BepInEx核心API)BepInEx.Harmony.dll(可选用于Harmony相关扩展)浏览并添加游戏目录下的GameName_Data/Managed文件夹中的UnityEngine.dll(Unity核心)UnityEngine.CoreModule.dll(通常也需要)UnityEngine.UI.dll(如果插件涉及UI)Assembly-CSharp.dll(游戏自身的逻辑代码用于补丁和引用游戏类)5.2 编写插件主类删除VS自动生成的Class1.cs新建一个名为MyFirstPlugin.cs的文件。using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; // 1. 定义插件元数据 [BepInPlugin(PluginGUID, PluginName, PluginVersion)] public class MyFirstPlugin : BaseUnityPlugin // 2. 必须继承BaseUnityPlugin { // 定义插件的唯一标识符、名称和版本 public const string PluginGUID com.yourname.myfirstplugin; public const string PluginName My First Plugin; public const string PluginVersion 1.0.0; // 3. 获取日志记录器用于输出信息到BepInEx日志 internal static ManualLogSource Log; // 4. Unity的Awake方法插件加载时自动调用 private void Awake() { // 初始化日志源 Log Logger; Log.LogInfo($Plugin {PluginGUID} is loaded!); // 5. 应用Harmony补丁 Harmony.CreateAndPatchAll(typeof(MyFirstPlugin).Assembly, PluginGUID); Log.LogInfo(Harmony patches applied.); } }这段代码做了几件事[BepInPlugin]特性告诉BepInEx这是一个插件并提供了ID、名字和版本。继承BaseUnityPlugin是必须的它提供了配置和日志等基础支持。在Awake()中我们初始化了日志并调用Harmony.CreateAndPatchAll来搜索当前程序集中所有带有[HarmonyPatch]特性的类并自动应用补丁。5.3 使用Harmony添加游戏内文本显示现在我们添加一个Harmony补丁在游戏每帧更新时在屏幕左上角绘制一段文字。我们需要补丁Unity的OnGUI方法这是Unity旧UI系统的即时模式GUI。在同一项目中新建一个类HUDPatch.csusing HarmonyLib; using UnityEngine; [HarmonyPatch] public class HUDPatch { // 指定要补丁的类和方法。这里我们补丁MonoBehaviour的OnGUI方法。 // 但更常见的做法是补丁游戏内具体的HUD管理类。 // 为了简单演示我们补丁一个在所有场景都可能存在的对象上的OnGUI。 // 注意频繁在OnGUI中绘制会影响性能此处仅作演示。 [HarmonyPostfix] [HarmonyPatch(typeof(Player), nameof(Player.OnGUI))] // 假设游戏有一个Player类有OnGUI方法 // 更通用的做法如果没有合适的类可以尝试补丁GameManager或创建一个MonoBehaviour并挂载到游戏对象上。 // 这里我们换一种思路创建一个MonoBehaviour并手动注入。 static void Postfix() { // 在屏幕左上角(10,10)的位置绘制一个标签 GUI.Label(new Rect(10, 10, 500, 50), $size20colorwhite我的第一个BepInEx插件正在运行/color/size); } }实际上直接补丁OnGUI可能不准确因为不是所有类都有这个方法。一个更稳健的方法是创建一个新的MonoBehaviour并将其动态添加到游戏场景中。新建一个PluginBehaviour.csusing UnityEngine; public class PluginBehaviour : MonoBehaviour { private void OnGUI() { GUI.Label(new Rect(10, 10, 500, 50), $size20coloryellow我的第一个BepInEx插件正在运行时间{Time.time:F2}/color/size); } }然后修改MyFirstPlugin.Awake()方法在最后添加private void Awake() { // ... 之前的代码 ... // 创建一个新的GameObject来承载我们的行为组件 GameObject pluginGO new GameObject(MyFirstPlugin_Behaviour); DontDestroyOnLoad(pluginGO); // 确保切换场景时不被销毁 pluginGO.hideFlags HideFlags.HideAndDontSave; // 在场景编辑器中隐藏 pluginGO.AddComponentPluginBehaviour(); // 添加我们的行为组件 Log.LogInfo(Plugin Behaviour GameObject created.); }5.4 编译、部署与调试编译在VS中选择“生成” - “生成解决方案”。如果一切顺利会在项目的bin/Debug或bin/Release文件夹下生成MyFirstPlugin.dll。部署将编译好的MyFirstPlugin.dll文件复制到游戏的BepInEx/plugins目录下。你可以创建一个子文件夹如BepInEx/plugins/MyFirstPlugin/。调试确保BepInEx/config/BepInEx.cfg中[Logging.Console]的Enabled设为true。启动游戏。你应该能看到一个控制台窗口弹出并看到你的插件加载日志。进入游戏后屏幕左上角应该会显示黄色的文本并且时间在不断更新。查看日志如果文本没有显示打开LogOutput.log检查是否有任何错误信息。可能是OnGUI没有被调用如果游戏不使用旧版UI系统或者你的GameObject没有被正确创建。注意事项这个示例使用了旧版IMGUIOnGUI进行绘制性能较差且可能在某些游戏中不显示。现代Unity游戏更多使用uGUI(Canvas)。要操作uGUI你需要获取游戏内现有的Canvas实例或者动态创建Canvas和Text组件这需要更深入的Unity知识和对游戏代码结构的了解。6. 高级主题与性能调优当你开始制作更复杂的插件时会面临性能、兼容性和维护性的挑战。6.1 插件性能优化要点避免每帧操作在Update()或OnGUI()中进行频繁的查找如GameObject.Find、计算或内存分配会严重拖累游戏性能。尽量将结果缓存起来或者使用事件驱动的方式。善用协程对于需要延时或间隔执行的任务使用Unity的StartCoroutine(IEnumerator)而不是在Update里计时代码更清晰性能也更好。对象池如果你的插件需要频繁创建和销毁Unity对象如特效、UI元素务必实现对象池来复用对象避免GC垃圾回收导致的卡顿。Harmony补丁的代价每个Harmony补丁都有微小的性能开销。避免对极其频繁调用的方法如Update使用复杂的Transpiler补丁。优先使用Prefix/Postfix它们更高效。6.2 与其他Mod的兼容性处理命名空间与GUID确保你的插件GUID是全局唯一的通常使用“作者名.插件名”的格式如com.YourName.AwesomeMod。避免与其他插件冲突。依赖声明如果你的插件必须依赖另一个插件才能运行可以在主类上使用[BepInDependency]特性来声明。[BepInDependency(com.other.author.coremod, BepInDependency.DependencyFlags.HardDependency)] [BepInPlugin(PluginGUID, PluginName, PluginVersion)] public class MyPlugin : BaseUnityPlugin { ... }这样如果依赖的插件缺失或版本过低BepInEx会阻止你的插件加载并给出明确错误。补丁冲突当多个Mod尝试补丁同一个游戏方法时可能发生冲突。Harmony提供了优先级机制[HarmonyPriority]特性来定义补丁的执行顺序。但最好的方法是在设计补丁时尽量保持“最小干预”原则只修改你需要的数据并尽量避免与其他知名Mod修改同一处逻辑。6.3 发布与维护你的插件版本管理使用语义化版本控制如主版本.次版本.修订号。每次发布新版本时更新[BepInPlugin]特性中的版本号。提供清晰文档在发布页面如GitHub、NexusMods写明插件的功能、安装方法、配置说明、已知问题和兼容性信息。打包将你的插件DLL、必需的依赖库、配置文件模板和资源文件如图标打包成一个ZIP文件。在plugins目录内使用合理的子文件夹结构。开源将代码托管在GitHub等平台。这不仅能方便其他开发者学习协作也能让用户在遇到问题时自行排查甚至提交修复。7. 常见问题与排查技巧实录即使你完全按照指南操作也难免会遇到各种奇怪的问题。这里记录了一些我踩过的坑和对应的解决方案。问题1游戏启动无反应或者启动后BepInEx文件夹没有生成。排查首先检查杀毒软件日志看是否拦截了winhttp.dll。然后以管理员身份运行一次游戏。最后检查下载的BepInEx压缩包是否完整是否解压到了正确的目录与Game.exe同级。问题2日志中显示插件加载成功但游戏内功能不生效。排查首先确认插件是否真的适用于当前游戏版本。其次查看插件是否有配置文件BepInEx/config/插件名.cfg可能某些功能默认是关闭的。使用ConfigurationManager这类工具在游戏内检查设置。最后查看日志中是否有该插件抛出的任何异常[Error]或[Exception]这通常是功能失效的直接原因。问题3游戏运行一段时间后突然崩溃。排查这种间歇性崩溃最难调试。首先查看崩溃瞬间的日志末尾寻找NullReferenceException、MissingMethodException或StackOverflowException等线索。尝试使用“二分法”禁用一半的Mod来定位问题插件。如果怀疑是内存泄漏可以观察任务管理器中的游戏内存占用是否随时间无限增长。问题4Harmony补丁编译成功但运行时无效。排查检查补丁的目标方法名、参数类型是否100%正确。大小写、参数数量、引用类型与值类型都必须匹配。使用dnSpy等反编译工具仔细查看游戏原版代码。确保你的补丁类是public且方法是public static。确认Harmony.CreateAndPatchAll被成功调用。可以在补丁方法里加一行Log.LogInfo(“Patch applied!”);来验证。如果使用[HarmonyPatch]指定方法名确保游戏代码在编译时没有因为混淆而改变了方法名。有时需要使用[HarmonyPatch(typeof(Class), MethodType.Getter)]这样的方式来补丁属性。问题5如何调试自己的插件代码进阶调试在BepInEx.cfg中启用[Debug]或[Logging]相关选项如EnableDebugging true。在Visual Studio中将调试器附加到游戏进程调试 - 附加到进程 - 选择游戏进程。你需要加载对应插件项目的PDB符号文件。这需要一定的设置但对于查找复杂的逻辑错误非常有效。对于简单的日志输出善用Logger.LogDebug、LogInfo、LogWarning、LogError在不同地方输出信息是更直接的方法。掌握BepInEx的过程就是一个从“黑盒使用者”到“白盒参与者”的转变。它为你打开了一扇修改和增强你所热爱游戏的大门。从简单的功能修改到庞大的内容扩展其可能性只受限于你的想象力与编程能力。最关键的是在这个过程中培养出的问题排查、系统理解和代码实践能力其价值远超一个游戏Mod本身。当你第一次看到自己编写的代码在游戏世界里完美运行时那种成就感是无与伦比的。