
如何用 BepInEx 插件开发跑通第一个游戏 mod从成就修改器到框架内部【免费下载链接】BepInExUnity / XNA game patcher and plugin framework项目地址: https://gitcode.com/GitHub_Trending/be/BepInExBepInEx 是一个面向 Unity Mono、Unity IL2CPP 与 .NET 框架游戏的插件 / modding 框架。游戏自身代码不会为改成就进度、调难度曲线留下任何接口本文以成就修改器为线索讲清第一个插件如何从零跑通以及框架里哪些模块值得花时间读一遍。从找不到入口到能挂钩框架定位与能力边界一个已发行的游戏里成就进度往往写在某个 MonoBehaviour 或静态管理器类的深处官方代码不开放任何钩子。难点不在想不想改而在从哪挂进去。BepInEx 的解法是两段式的启动阶段先用预加载器接管游戏进程待托管代码加载完成后再扫描插件目录、反射实例化各插件插件内部则用 Harmony 对运行时方法打补丁。它目前的能力边界如下选型前先看这张表运行时WindowsmacOSLinuxUnity Mono稳定稳定稳定Unity IL2CPP实验性不支持实验性.NET / XNA 系稳定需 Mono需 Mono需要强调截至目前只有 Unity Mono 目标是稳定发布其余两行是能力参考。️ 环境准备与最小可运行插件依赖与工具.NET SDK 6.0 或更高版本构建框架本体同样要求 6.0见 docs/BUILDING.mdVisual Studio 或 Rider 均可插件项目是一个普通的 classlib目标游戏已装好对应版本的 BepInEx编译出的 DLL 放入游戏根目录下的BepInEx/plugins如果想通读框架源码先克隆仓库git clone https://gitcode.com/GitHub_Trending/be/BepInEx最小可运行骨架Unity Mono 目标[BepInPlugin(dev.example.tuner, AchievementTuner, 0.1.0)] public class AchievementTuner : BaseUnityPlugin { private void Awake() { // Logger 与 Config 由 BaseUnityPlugin 构造时自动注入 Logger.LogInfo($插件已就绪宿主进程: {Paths.ProcessName}); } }注意两点插件工程要引用目标游戏BepInEx/core目录里的 BepInEx.Core.dll保证与运行时版本一致Unity Mono 插件的入口生命周期是Awake。 跑通一个成就进度修改器开发主流程四步目标行为是成就进度非零时强制拉满。流程固定为四步定位、打补丁、接配置、验证。定位目标方法先找到维护进度的类型与方法最省事的方式是反射列出候选类型靠日志肉眼筛选// 在 Awake 中执行把候选类型打到日志里 var gameAssembly AppDomain.CurrentDomain.GetAssemblies() .First(a a.GetName().Name Assembly-CSharp); foreach (var type in gameAssembly.GetTypes() .Where(t t.Name.Contains(Achievement))) Logger.LogDebug($候选类型: {type.FullName});确认方法签名后进入下一步。用 Harmony 打补丁用[HarmonyPatch]声明补丁类型再在Awake里一次性应用// 在 Awake 中按插件 GUID 命名 Harmony 实例并批量打补丁 Harmony.CreateAndPatchAll(Info.Metadata.GUID);[HarmonyPatch(typeof(AchievementTracker), UpdateProgress)] static class ProgressOverride { // Prefix 先于原方法执行ref 可把改写后的值传回 static void Prefix(ref int progress) { if (progress 0 progress 100) progress 100; } }配置项接入硬编码 100 不利于复用改用Config.Bind声明配置项值会持久化到BepInEx/config/插件GUID.cfgprivate ConfigEntryint TargetProgress; // Awake 中声明配置第二个参数是默认值 TargetProgress Config.Bind(Behavior, TargetProgress, 100, new ConfigDescription(进度非零时强制写入的目标值));把 Prefix 里的 100 替换为TargetProgress.Value即可。日志验证只在值真正被改写时打日志避免刷屏也便于确认补丁生效的时机static void Prefix(ref int progress) { var before progress; if (progress 0 progress Target) progress Target; if (progress ! before) Logger.LogInfo($成就进度 {before} - {progress}); } 关键模块速览配置、日志、加载与 Harmony配置管理ConfigFile源码BepInEx.Core/Configuration/ConfigFile.cs。核心类ConfigFile与ConfigEntryTTOML 格式的持久化配置全部公开方法线程安全BindT统一承载默认值、描述与取值。日志系统Logger 与监听器源码BepInEx.Core/Logging/Logger.cs。核心类Logger、DiskLogListener、ConsoleLogListener发布-订阅模型同一条日志可同时发往控制台、文件与 Unity 日志等多个监听器。插件加载Chainloader源码BepInEx.Core/Bootstrap/BaseChainloader.cs 与 BepInEx.Core/Bootstrap/TypeLoader.cs。用 Mono.Cecil 静态扫描插件程序集解析BepInPlugin属性元数据、校验 GUID 格式通过后才实例化——这也是很多插件没加载问题的根源所在。Harmony 集成Harmony 本体是外部库 HarmonyX由 BepInEx 项目引用仓库内 BepInEx.Preloader.Core/RuntimeFixes/HarmonyBackendFix.cs 负责修复 .NET 环境下 Harmony 后端的行为。插件侧只需写[HarmonyPatch]注解无需关心底层。 新手高频坑四个常见问题排错现象游戏在 IL2CPP 目标下行为异常甚至无法启动。原因IL2CPP 路径依赖 Il2CppInterop仍属实验阶段稳定发布只有 Unity Mono。解法先在 Mono 游戏上验证插件逻辑IL2CPP 参考Runtimes/Unity/BepInEx.Unity.IL2CPP/目录了解其独立管线。现象日志出现Skipping type [...] because its GUID [...] is of an illegal format。原因GUID 为空或含非法字符加载器只接受字母、数字、.、_、-。解法改用反向域名风格的标识符且同一 GUID 不要被两个插件复用否则配置文件与实例会互相串。现象BepInEx/config下找不到自己的 .cfg 文件。原因ConfigFile是惰性创建的配置项被修改或显式调用Save()之前不会落盘。解法插件启动阶段主动调用一次Config.Save()。现象找不到日志文件。原因日志写在BepInEx/Log/目录如UnityPlayer.log而不是游戏安装目录或 Managed 目录。解法直接打开该路径若控制台输出被游戏吞掉可调整doorstop_config.ini的redirect_output_log选项该配置的模板见 Runtimes/Unity/Doorstop/doorstop_config_mono.ini。延伸方向与下一步构建与发布流程以 docs/BUILDING.md 为准它描述了 CakeBuild 各 target 的用途。安装与使用说明以 BepInEx 官方文档站为准开发过程中的疑问可以直接在官方 Discord 社区提出。IL2CPP 实验实现的完整入口在 Runtimes/Unity/BepInEx.Unity.IL2CPP/Preloader.cs想研究跨运行时差异可以从这里开始。下一步建议成就修改器跑通之后换一个真实目标例如 UI 刷新逻辑或难度计算函数把定位 → 打补丁 → 接配置 → 验证这条流程再走一遍你会发现它对所有可注入的游戏逻辑都适用。【免费下载链接】BepInExUnity / XNA game patcher and plugin framework项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考