一接触到新的东西,总想看看背后的原理是怎样的,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()函数
可以看到已经成功的修改啦。