使用xlua 进行Unity3D 热更新-2
一接触到新的东西,总想看看背后的原理是怎样的,xlua也不例外。于是试着写了一下,算是了解底层的实现原理,以后不用xlua也能有借鉴的地方。
xlua的热修复原理实际上是在 C# 编译成中间语言的时候,进行代码的插入这部分用到了 Mono.Ceil 库来操作,当然还有其他很多的库也可以实现。 因为是在IL的部分插入,因此直接支持IL2CPP
直接进入主题
已知有一个类
1 2 3 4 5 6 7 8 9 10 |
public class InputTest{ void Start(){ Hello(); } private void Hello(){ Debug.Log("hello"); Debug.Log("666"): } } |
这个类在被Unity调用的时候会输出 “Hello”
那么如果我们想修改Hello函数该怎么做呢
1 2 3 4 |
string injectPath = @"./Library\ScriptAssemblies\Assembly-CSharp.dll"; AssemblyDefinition assemblyDefinition = null; var readerParameters = new ReaderParameters { ReadSymbols = true }; assemblyDefinition = AssemblyDefinition.ReadAssembly(injectPath, readerParameters); |
第一步 是要将当前代码的 Assembly 读出来, U3d有3个Assembly。 一个是项目代码叫 Assembly-CSharp.dll 一个是编辑器代码 Assembly-Editor-CSharp.dll.
还有一个是插件 Assembly-Plugin-CSharp.dll. 因为 InputTest是项目代码部分,所以读取 Assembly-CSharp.dll即可
读取成功后,所有的数据都在 assemblyDefinition 中,只需要遍历一下找到要修改的类即可
1 2 3 4 5 6 7 8 9 10 |
foreach (Mono.Cecil.TypeDefinition item in assemblyDefinition.MainModule.Types) { if(item.FullName == "InputTest") { foreach (MethodDefinition method in item.Methods) { if (method.Name.Equals("Hello")) { } } } } |
第二步 通过遍历类型定义找到我们的类 “InputTest” 然后在 类定义中遍历所有的函数定义,找到我们要修改的 “Hello”函数
找到函数后,就可以正式做函数修改了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var ins = method.Body.Instructions.First(); var worker = method.Body.GetILProcessor(); var logRef = assemblyDefinition.MainModule.Import(typeof(Debug).GetMethod("Log", new Type[] { typeof(string) })); worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, "Fuck Off")); worker.InsertBefore(ins, worker.Create(OpCodes.Call, logRef)); worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, "Fuck On")); worker.InsertBefore(ins, worker.Create(OpCodes.Call, logRef)); Type type = typeof(InjectTest); if (null != type) { MethodInfo subMethod = type.GetMethod("SayFuck"); if (null != subMethod) { Debug.Log("Find Method: " + subMethod); var sayRef = assemblyDefinition.MainModule.Import(subMethod); worker.InsertBefore(ins, worker.Create(OpCodes.Call, sayRef)); } } var writerParameters = new WriterParameters { WriteSymbols = true }; assemblyDefinition.Write(injectPath, new WriterParameters()); |
第三步 做了3件事情, 绑定了2个UnityEngine的Log函数,打印了 “Fuck Off”, “Fuck On” 之后再绑定一个类 “InjectTest”中的静态函数 SayFuck()
这样原本的 Hello()函数就会在 打印”Hello”之前先打印 “Fuck Off”, “Fuck On” 调用 InjectTest.SayFuck().
最后就是将执行的修改进行保存 assemblyDefinition.Write
最后的最后用C#反编译软件打开 Assembly-CSharp.dll 看看修改后的Hello()函数
可以看到已经成功的修改啦。