InjectFix学习笔记
1.InjectFix 工作流程
- 打新包
- 其他预处理
- 预先配置好需要Patch的类跟函数,提交配置Configure代码
- 打包机打包时,自动reload被Inject的dll,保证当前的dll没有被注入过
- 调用InjectFix提供的注入函数,注入WrappersManagerImpl,ILFixInterfaceBridge,ILFixDynamicMethodWrapper,IDMAP-1跟插桩代码dll
- 执行il2cpp处理
- 执行其他预处理
- Patch阶段
- 修复代码
- 修复函数:使用标签[IFix.Patch]
- 新增类、属性、方法:使用标签[IFix.Interpret]
- 调用InjectFix提供的打Patch函数,生成Patch文件
- 生成Patch文件的AssetsBuddle包
- 上传更新过后的AssetsBuddle包
- 修复代码
ps:在patch过程中,如果没有重新打出新的包体,不要删除Patch标签
2.InjectFix 工作原理
2.1 Inject阶段
2.1.1 [IFix]跟[IFix.Filter]标签
在Inject阶段,预先对有可能需要修复的类跟函数提交配置类,调用Inject处理函数时,会对这些写上标签的函数注入插桩代码,例如有如下代码,其中Add跟Sub目前逻辑是错误的,是需要修复的函数:
namespace IFix.Test
{
public class Calculator
{
public int Add(int a, int b)
{
return a * b;
}
public int Sub(int a, int b)
{
return a / b;
}
public int Mult(int a, int b)
{
return a * b;
}
public int Div(int a, int b)
{
return a / b;
}
}
}
打包时需要配置对应的配置类,具体配置Configure类如下,其中 [Configure] 标签是配置类的标签,用来配置注入阶段需要注入或者过滤的内容,配置类必须放在Editor目录下。
- [IFix] 标签 :用来配置需要注入的类型
- [IFix.Filter] 标签 :用来过滤不需要的字段
[Configure]
public class HelloworldCfg
{
[IFix]
static IEnumerable<Type> hotfix
{
get
{
return new List<Type>()
{
typeof(IFix.Test.Calculator),
};
}
}
[IFix.Filter]
static bool Filter(System.Reflection.MethodInfo methodInfo)
{
return methodInfo.DeclaringType.FullName == "IFix.Test.Calculator"
&& (methodInfo.Name == "Div" || methodInfo.Name == "Mult");
}
}
使用ILSpy工具查看注入后dll,看到生成如下代码,其中Add跟Sub方法增加了注入代码,Mult跟Div函数因为在配置类中被过滤了,没有注入代码。
public class Calculator
{
public int Add(int a, int b)
{
if (IFix.WrappersManagerImpl.IsPatched(6))
{
return IFix.WrappersManagerImpl.GetPatch(6).__Gen_Wrap_1(this, a, b);
}
return a * b;
}
public int Sub(int a, int b)
{
if (IFix.WrappersManagerImpl.IsPatched(7))
{
return IFix.WrappersManagerImpl.GetPatch(7).__Gen_Wrap_1(this, a, b);
}
return a / b;
}
public int Mult(int a, int b)
{
return a * b;
}
public int Div(int a, int b)
{
return a / b;
}
}
除了插桩代码,注入阶段还会注入几个相关的代码,其中IDMAP0是修复函数的索引ID,当索引值超过32760时,会生成下一个IDMAP1,依次类推。
public enum IDMAP0
{
IFix-Test-Calculator-Add0 = 6,
IFix-Test-Calculator-Sub0,
Helloworld-test0 = 5,
Helloworld-Start0 = 0
}
其中的GetPatch(6)中的参数6对应到注入代码中IDMAP0中IFix-Test-Calculator-Add0
注入WrappersManagerImpl类代码
public class WrappersManagerImpl : WrappersManager
{
private VirtualMachine virtualMachine;
public WrappersManagerImpl(VirtualMachine virtualMachine)
{
this.virtualMachine = virtualMachine;
}
public static ILFixDynamicMethodWrapper GetPatch(int id)
{
return ILFixDynamicMethodWrapper.wrapperArray[id];
}
public static bool IsPatched(int id)
{
return id < ILFixDynamicMethodWrapper.wrapperArray.Length && ILFixDynamicMethodWrapper.wrapperArray[id] != null;
}
public System.Delegate CreateDelegate(System.Type type, int id, object anon)
{
ILFixDynamicMethodWrapper iLFixDynamicMethodWrapper = new ILFixDynamicMethodWrapper(this.virtualMachine, id, anon);
return Utils.TryAdapterToDelegate(iLFixDynamicMethodWrapper, type, "__Gen_Wrap_");
}
public object CreateWrapper(int id)
{
return new ILFixDynamicMethodWrapper(this.virtualMachine, id, null);
}
public object InitWrapperArray(int len)
{
ILFixDynamicMethodWrapper.wrapperArray = new ILFixDynamicMethodWrapper[len];
return ILFixDynamicMethodWrapper.wrapperArray;
}
public AnonymousStorey CreateBridge(int fieldNum, int[] fieldTypes, int typeIndex, int[] vTable, int[] slots, VirtualMachine virtualMachine)
{
return new ILFixInterfaceBridge(fieldNum, fieldTypes, typeIndex, vTable, slots, virtualMachine);
}
}
其中使用到的iLFixDynamicMethodWrapper的代码如下,其中 __Gen_Wrap_X 函数会根据注入时函数的参数个数,类型来生成,相同的参数只生成一个对应的 __Gen_Wrap_X 方法,例子中的Add,Sub方法共用了 __Gen_Wrap_1 方法
public class ILFixDynamicMethodWrapper
{
private VirtualMachine virtualMachine;
private int methodId;
private object anonObj;
public static ILFixDynamicMethodWrapper[] wrapperArray;
public ILFixDynamicMethodWrapper(VirtualMachine virtualMachine, int methodId, object anonObj)
{
this.virtualMachine = virtualMachine;
this.methodId = methodId;
this.anonObj = anonObj;
}
public void __Gen_Wrap_0(string P0)
{
Call call = Call.Begin();
if (this.anonObj != null)
{
call.PushObject(this.anonObj);
}
call.PushObject(P0);
this.virtualMachine.Execute(this.methodId, ref call, (this.anonObj != null) ? 2 : 1, 0);
}
public int __Gen_Wrap_1(object P0, int P1, int P2)
{
Call call = Call.Begin();
if (this.anonObj != null)
{
call.PushObject(this.anonObj);
}
call.PushObject(P0);
call.PushInt32(P1);
call.PushInt32(P2);
this.virtualMachine.Execute(this.methodId, ref call, (this.anonObj != null) ? 4 : 3, 0);
return call.GetInt32(0);
}
public int __Gen_Wrap_2(int P0)
{
Call call = Call.Begin();
if (this.anonObj != null)
{
call.PushObject(this.anonObj);
}
call.PushInt32(P0);
this.virtualMachine.Execute(this.methodId, ref call, (this.anonObj != null) ? 2 : 1, 0);
return call.GetInt32(0);
}
public void __Gen_Wrap_3(object P0)
{
Call call = Call.Begin();
if (this.anonObj != null)
{
call.PushObject(this.anonObj);
}
call.PushObject(P0);
this.virtualMachine.Execute(this.methodId, ref call, (this.anonObj != null) ? 2 : 1, 0);
}
static ILFixDynamicMethodWrapper()
{
ILFixDynamicMethodWrapper.wrapperArray = new ILFixDynamicMethodWrapper[0];
}
}
如果Patch阶段,有对Add方法进行修复,生成Patch,则执行逻辑会通过IFix.WrappersManagerImpl.GetPatch(6).__Gen_Wrap_1(this, a, b)调用ILFixDynamicMethodWrapper 中的__Gen_Wrap_1函数,最终调用virtualMachine的函数,通过IFix内部实现的虚拟机,解析执行Patch中的IL指令,执行修复后的代码逻辑,最后然后通过 call.GetInt32(0) 获取计算返回的结果,将正确的值返回。
2.1.2 [IFix.CustomBridge] :interface和delegate桥接
在注入阶段使用,用来把一个在虚拟机上的类适配到原生interface或者把一个虚拟机的函数适配到原生delegate。
- 修复代码赋值一个闭包到一个delegate变量;
- 修复代码的Unity协程用了yield return;
- 新增一个函数,赋值到一个delegate变量;
- 新增一个类,赋值到一个原生interface变量;
- 新增函数,用了yield return;
例如,原生类代码如下:
public interface ISubSystem
{
bool running { get; }
void Print();
}
public class Test
{
public delegate int MyDelegate(int a, int b);
}
配置类代码如下:
[IFix.CustomBridge]
public static class AdditionalBridge
{
static List<Type> bridge = new List<Type>()
{
typeof(ISubSystem),
typeof(IEnumerator),
typeof(Test.MyDelegate)
};
}
新增函数(或者修复代码[IFix.Patch]的Unity协程),用到了 yield return
[IFix.Interpret]
public IEnumerator TestInterface()
{
yield return new WaitForSeconds(1);
UnityEngine.Debug.Log("wait one second");
}
新增函数(或者修复代码[IFix.Patch]),赋值到一个delegate变量
public class Test
{
public delegate int MyDelegate(int a, int b);
[IFix.Interpret]
public MyDelegate TestDelegate()
{
return (a,b) => a + b;
}
}
新增一个类,该类实现了一个接口
[IFix.Interpret]
public class SubSystem : ISubSystem
{
public bool running { get { return true; } }
public void Print()
{
UnityEngine.Debug.Log("SubSystem1.Print");
}
}
注入dll后,使用ILSpy能看到dll中有注入的类ILFixInterfaceBridge
2.2 Patch阶段
Patch阶段针对修复内容使用对应的Tag
- 修复方法 :[IFix.Patch]
- 新增方法、类型 :[IFix.Interpret]
2.2.1 修复方法
需要修复的方法,再Patch阶段加上[Patch]标签,在执行生成Patch时,被修复的方法就会被写到生成的Patch里,例如,将Add方法从原先的 a * b 改成正确的逻辑 a + b:
[Patch]
public int Add(int a, int b)
{
return a + b;
}
不支持修复泛型函数,不支持修复构造函数
2.2.2 新增函数、类型
- 新增一个函数
[IFix.Interpret]
public bool Greater(int a,int b)
{
return a > b;
}
- 新增一个类
[IFix.Interpret]
public class NewClass
{
...
}
- 新增一个属性
private string name;//这个name字段是原生的
public string Name
{
[IFix.Interpret]
set
{
name = value;
}
[IFix.Interpret]
get
{
return name;
}
}
不支持继承原生类,泛型类,不支持在原生类中新增字段