Unity引擎中InjectFix接入使用与边界全梳理 腾讯开源的 Unity C# 代码热修复框架官方仓库https://github.com/Tencent/InjectFix本文结合官方 README / user_manual / FAQ、以及本项目实际接入代码整理。一、是什么 / 原理定位Unity 业务C# 层热修复不用 Xlua/Lua直接在 C# 工程上改代码即可出补丁。与 XLua 生成器导出 IFix 注入方法导致 Gen 编译报错XLua 与 IFix 注入在 IL 层的冲突案例一起构成某游戏项目的热更体系Lua 走 XLua纯 C# 逻辑走 InjectFix。原理编译期注入 运行时解释执行。注入阶段Inject打包前用IFix.exe基于 Mono.Cecil读[Configure]配置对[IFix]属性里列出的所有类的所有方法在 IL 里注入一段重定向桩函数入口被改成跳转到虚拟机的CallVirtualMachine。这个未打补丁 → 走原逻辑的表叫IDMap会打进主包。补丁阶段Fix/Patch改好 bug 后再次跑IFix.exe对比修改后的程序集 vs 原始注入后程序集把发生变化的函数反编译成自定义 ILIFix.Core.Instruction指令集打包成.patch。运行时加载PatchManager.Load(stream)读补丁把补丁指令挂到虚拟机上被修复函数执行时注入桩发现 IDMap 里有新版本 → 走解释执行不命中 → 走原逻辑。优点老项目无需改代码、支持 Unity 全系列全平台Mono IL2CPP均可、补丁格式私有INSTRUCTION_FORMAT_MAGIC校验。与 Xlua / ILRuntime 区别不是把代码搬到 Lua/虚拟语言而是原生 C# 方法原地被解释执行补丁里是字节码指令而非 Lua 代码热更代码和主工程代码完全同源同一个.cs文件用[IFix.Patch]/[IFix.Interpret]标注。二、接入安装步骤官方Source/UnityProj/对应一个 Unity 工程目录编译 IFix 工具仅 Windowsmac 需自行用 mcs/mono 编译或直接复用现成 exeSource/VSProj/build_for_unity.bat把UNITY_HOME改成 Unity 安装目录运行。拷贝到 Unity 工程IFixToolKit/内含IFix.exe、Mono.Cecil*.dll→ Unity 工程的Assets 同级目录本项目在仓库根IFix/IFixToolKit/。Assets/IFix、Assets/Plugins内含IFix.Core.dll运行时→ 工程Assets/下。本项目运行时库在Assets/Scripts/Hotfix/Plugins/IFix.Core.dllMono 用Assets/Plugins/Android/iOS 平台化版本。写[Configure]注入配置必须放Editor 目录。注入 出包正常出包前跑一次注入IFix.Editor.IFixEditor.InjectAll把注入桩编进主程序集。打补丁改代码 → 给要修的已有方法加[IFix.Patch]/ 给要新增的东西加[IFix.Interpret]→ 菜单生成.patch.bytes→ 走发布管线。运行时加载xxxPatch.Load(...)。Unity 版本要求官方支持 Unity 全系列。Unity2018.3直接用菜单IFix/Inject、IFix/Fix即可Unity 开放了 C# 编译接口patch 可带平台条件宏直接生成2018.3 以下需要手动用 mcs 按平台编译出 Assembly-CSharp.dll 再调IFixEditor.GenPatch见官方 FAQ。三、注入配置与标签使用核心3.1 标签总览官方 user_manual 总结表标签阶段用途用法[Configure]注入配置类只能放单独一个类必须放Editor 目录[IFix]注入列出将来可能修的类集合只能放[Configure]类的静态属性上[Filter]注入过滤掉不想注入的函数只能放[Configure]类的静态方法上[IFix.Patch]补丁修复已有方法只能放方法上[IFix.Interpret]补丁新增字段/属性/方法/类可放字段、属性、方法、类型上[IFix.CustomBridge]注入把 VM 类适配到原生 interface / VM 函数适配到原生 delegate只能放单独静态类不能放 Editor 目录不能内嵌别的类3.2[Configure]注入配置本项目实例项目里生效的配置在xxxHotfix/Editor/// ScriptsCfg.cs —— Scripts 程序集全量注入凡是 namespace ! null 的都注入桩[Configure]publicclassScriptsCfg{[IFix]staticIEnumerableTypehotfix{get{return(fromtypeinAssembly.Load(Scripts).GetTypes()wheretype.Namespace!nullselecttype).ToList();}}}用法建议被[IFix]覆盖的所有方法都会注入跳转桩是有包体和性能开销的官方建议只列可能出问题的类本项目是全量注入游戏逻辑代码量大、需要热更的几乎都能热更代价是主程序集膨胀 注入耗时。3.3[IFix.Patch]—— 修复已有方法前提该方法所在的类必须已被[IFix]注入。加在方法上改完方法体生成的补丁会修正该函数// 修复前publicintAdd(inta,intb){returna*b;}// 有 bug// 修复后打开 [Patch] 注释[IFix.Patch]publicintAdd(inta,intb){returnab;}// 正确3.4[IFix.Interpret]—— 新增代码补丁阶段新增字段/属性/方法/类直接打标即可[IFix.Interpret]publicclassNewClass{...}[IFix.Interpret]publicintintValue0;// 新增字段[IFix.Interpret]publicIEnumeratorTestInterface()// 新增协程有限制见 4.2{yieldreturnnewWaitForSeconds(1);}3.5[IFix.CustomBridge]—— interface / delegate 桥接关键边界什么时候必须加官方列出的场景修复代码给一个 delegate 变量赋值闭包修复代码或新增代码的协程用了yield return新增类赋值到原生 interface变量新增函数用到yield return。要求写成一个独立静态类静态字段bridge放 interface 和 delegate 的Type集合不能放 Editor 目录、不能内嵌其他类。本项目实例xxx/IFix/InjectFixCustomBridge.cs[CustomBridge]publicstaticclassInjectFixCustomBridge{staticListTypebridgenewListType();staticreadonlyListTypeDefaultTypesnewListType{typeof(IEnumerator),typeof(ISubsystem)};staticInjectFixCustomBridge(){UpdateAllReferences();}}注意官方属性名是bridge小写框架按名字反射。注释掉的KongWebView、CharacterTool.Runtime反射段已随对应程序集停用而删除。好处自动收集interface delegate避免手工逐个维护bridge列表代价程序集里任何新 interface/delegate 都会被兜进 bridge注入产物略大。菜单InjectFix/✡ Print Custom Bridges ✡可打印当前收集到的全部桥接类型排查新增类实现原生接口报错时先看这个。四、边界与限制4.1[IFix.Patch]修已有方法边界能力是否支持备注普通方法✅基础用法getter / setter✅对TestProperty { [IFix.Patch] get/set }有效成员变量不支持但访问器可 patch普通协程✅yield return正常方法内调用泛型方法✅在 Patch 方法体内InnerGenericMethodstring(...)可用泛型方法本身❌[IFix.Patch] public void GenericMethodT(T t)→ 编辑器报错带 out 泛型参数同样不生效构造函数❌[IFix.Patch] public Calculator()不行private 构造函数同样无法 patch字段❌不能 Patch 字段原生类中新增字段也不行需用 [Interpret]但见 4.24.2[IFix.Interpret]新增代码边界能力是否支持备注新增普通方法 / 属性 / 类✅新增类继承新增类✅PatchChildClass : PatchBaseClass两者都 [Interpret]新增类实现原生接口✅必须配合[IFix.CustomBridge]把接口加进 bridge新增类继承原生类❌[Interpret] class PatchClassInheritClass : TestClass不支持新增泛型类❌泛型方法❌[Interpret] public void PatchGenericMethodT(T t)不行字段❌官方 user_manual 里写明 [Interpret] 可放字段但 CSDN 实测新增字段不可用issueInjectFix 如何新增字段。⚠️本项目也遵循此边界能改方法体、不能靠补丁加字段。构造函数❌Struct结构体❌新增 struct 类型不支持协程⚠️ 有限制见下协程的坑// 非热更代码里已用过 IEnumerator → 热更代码里才可以用privateIEnumeratorIE_Main(){yieldreturnnewWaitForEndOfFrame();}[IFix.Interpret]privateIEnumeratorIE_Patch()// ✅ 编译 OK{yieldreturnnewWaitForSeconds(1);yieldreturnnewWaitForEndOfFrame();yieldreturn0;yieldreturnnull;yieldbreak;}[IFix.Interpret]publicIEnumeratorIE_Patch2()// ❌ 非热更代码没用过 IEnumerator 时报错{yieldreturn...}原因yield会生成状态机类IE_PatchCompilerGenerated本质是一个新增类而新增类不能用泛型状态机又是IEnumeratorT/泛型结构所以只有非热更代码里已经存在对应的 IEnumerator 类型VM 能复用时才可行。4.3 泛型 / IL 相关深层边界泛型是最大禁区Patch 不支持泛型方法、Interpret 不支持泛型方法/泛型类Stelem_Any等依赖泛型解析的 IL 指令在 VM 里基本不会走到官方注释//case Code.Stelem_Any: //泛型不支持解析。多个 issue报过子类重写时先执行带泛型的方法会出错。new int[]{1,2,3}数组字面量生成ldtoken PrivateImplementationDetails指令官方 warningnot support il[IL_0008: ldtoken ...]。不支持 async / awaitasync Task、.NET 4.x async注入阶段报异常VM 不支持AsyncStateMachine。→ 别把异步方法塞进 IFix 热更。不支持的 IL 指令集合CalliInstruction.cs里被注释掉、Jmp、Cpblk/Initblk块拷贝、Localloc、Tail尾调用等。遇上报not support il的改写成简单写法规避。结构体初始化必须newstruct不使用new初始化直接default/逐字段→ 报错。按下标取 struct 数组解释执行会报错。in修饰 struct 参数的虚函数inject 后执行报错。Enum 相关补丁中Enum.Parse报错foreach遍历含枚举的容器报错。IEnumeratorT的 Current在解释执行下报错见 4.2 协程边界。方法体里不能用typeof()新增类中IFix.Interpret新增类内不能用到typeof。不能 Patch 返回协程衍生类非IEnumerator本身的函数InvokeMethodInfo.Invoke在解释执行下Non-static method requires a target报错TargetException新方法调用新方法同理注意。DateTime.Now.AddSeconds打补丁失败、Debug.unityLogger.logEnabled无法访问纯 Unity API 访问边界。BurstCompile 类/结构体不能注入加载 patch 后报 burst 错误ECS/Jobs 代码不要走 IFix。[IFix]里 ifix 无using System.Linq的 linq 配置会在编辑器初始化报错项目里ScriptsCfg等都using System.Linq规避。4.4 与 IL2CPP / 打包相关支持 IL2CPPAndroid/iOS 都可热更但要注意时机IL2CPP 会把注入桩编译进 native 代码所以主包必须带注入打包时执行 injectIFix.Core的CallVirtualMachine入口才能编进去打包后再注入无效。iOS armv7 注入方法过多链接报错新机基本 arm64影响小。手动编译IFix.Core.dll后导出 xcode 工程报IFix.Core.EvaluationStackOperation::ToObject错 → 用build_for_unity.bat重新构建别手动编。补丁平台模板2018.3 以下平台 patch 需要IFixToolKit里的android.win.tpl/android.osx.tpl/ios.osx.tpl/ios.win.tpl从一次正常平台构建的Temp/UnityTempFile*拷贝改名。报错please put template file for android/ios in IFixToolKit directory即缺模板。本项目 2022.3 不受影响4.5 版本 / 兼容官方 issue 统计里出现过2019.2.15 打包未注入、2019.3.x 找不到gmcsEditor\Data\Mono\bin\gmcs路径差异、2019.3 安卓注入失败等老 Unity 版本的坑本项目 Unity 2022.3 已避开。IFix.Core.dll与IFix.exe要配套同一版本找不到IFix.Core.dll、Instruction.cs多版本不一致Code枚举顺序变了 IDMap 就对不上都是升级时的坑。升级 Unity 或改条件编译宏后平台模板、注入 IDMap 都要重新生成。五、注意事项 / 运维红线补丁只进不出PatchManager有Unload但跨版本/多次加载同一方法会叠加每次发版必须带新版本的 IDMapinstruction magic not match就是新旧不匹配。不能靠先卸载再重载做版本回退回退要整个包回退。注入桩有开销[IFix]覆盖的每个方法入口都跳虚拟机的CallVirtualMachine即使没打补丁全量注入会明显增大主程序集 首次调用慢。取舍只注入可能热更的类或用[Filter]排除热点函数如DamageCalculator这种战斗高频计算其实应该 Filter 掉注入它反而拖累性能。.meta与二进制patch 和IFix.Core.dll是二进制产物git add别-A按路径 stage。热更代码别写泛型方法、泛型类、构造/析构函数、新增字段、struct、async/await、typeof新增类内、Burst/ECS、Enum.Parse、LINQ 容器含枚举的foreach、数组字面量{...}等见第四章。新增字段是最常踩的坑—— 需求说加个字段时改成改方法体/加静态配置别指望补丁加字段。编辑器 vs 真机一致性编辑器模拟SimulateInjectFix和真机加载路径不同验证热更先在编辑器模拟测再出 patch 上真机编辑器不拦截 patch 加载错误真机出错会卡在ShowNotice重试循环。修改IFix.Core源码需用build_for_unity.bat重建且 DLL/exe 配套升级同时重生成 IDMap本项目直接复用官方二进制。七、故障速查现象原因/处理instruction magic not match补丁与主包 IDMap 不匹配 → 换同版本补丁/重新出包Error: the new assembly must not be inject, please reimport the project!拿注入后的 dll 生成 patch → 工程根目录右键 Reimportplease put template file for android/ios in IFixToolKit directory缺平台模板 tpl2018.3 以下才需要not support il[IL_xxxx: ldtoken ...]数组字面量/不支持的指令 → 改写如换成new Listint{...}或逐个赋值patch 泛型方法/类、构造、字段报错越界了改设计编辑器能热更、apk 不行包没带注入桩 / 补丁路径版本不对Non-static method requires a target解释执行下MethodInfo.Invoke等反射调用不支持补丁里调Enum.Parse/ 含枚举 foreach 报错换实现IFix.Core.EvaluationStackOperation::ToObjectIL2CPP 报错IFix.Core 手动编译导致 → 用 build_for_unity.bat 重编找不到 IFix.Core.dllIFix.Core.dll/exe 配套丢失或版本不匹配新增类实现原生接口报错接口没进[IFix.CustomBridge]bridge 列表八、参考官方https://github.com/Tencent/InjectFix README / Doc/user_manual.md / Doc/faq.md