IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> InjectFix原理学习(实现修复加法的热更) -> 正文阅读

[游戏开发]InjectFix原理学习(实现修复加法的热更)


前期准备


  • 根据读上面的文章,可以了解到,InjectFix步骤分为以下几步
    插桩: 就是通过标签指定需要修复的函数,通过Mono.Cecil插件,在函数最前方插入IL指令,通过唯一key值判断是否有修复代码,如果有将执行修复函数内
    修复: 修改错误的代码,通过Mono.Cecil插件,检查修复函数的代码,生产修复函数的IL指令并存储到文件内,并和上面插桩代码的唯一key值对应
    执行修复代码的IL指令: 这部分就是解析生成的IL指令,也就是虚拟机,这部分有大量的unsafe代码

  • 首先看一下最终效果图,就是把加法写错了,然后通过修复将原来写错的减法修改成加法
  • 首先下面这张图,函数名为Sum是进行加法的,因为写错了,打印出来的结果是-1
  • 下面是通过修复,原代码依然的错误的,但是打印出来的是正确是数值3

插桩

  • 这块就是插桩的代码,执行后会在Sum函数IL指令最上方,插入PatchLoad.HasPatch(0),检查函数,0是因为我这只有一个函数,要修复直接用0这个硬编码就行,如果有修复需要,将会执行genMethod这个生成的代码
  • genMethod的IL指令生成,部分直接采用了硬编码,具体就是实例化一个Call对象,将参数Push进去,并使用PatchLoad的virtualMachine执行Execute函数
    static void InjectMethod(ModuleDefinition module, MethodDefinition method)
    {
        //TODO 使用InsertBefore插入

        // 生成方法
        var genMethod = GenPatchMethod(module, method);


        // 插桩
        var ilp = method.Body.GetILProcessor();
        var startNopIns = method.Body.Instructions[0];
        var brFalseJumpIns = startNopIns.Next; // 条件不满足时跳转的指令

        var ins_ldstr = ilp.Create(OpCodes.Ldstr, "0"); // 插入patchKey
        ilp.InsertBefore(startNopIns, ins_ldstr); //插到index = 0位置

        var incrIndex = 0;
        var method_HasPatch = module.Types.Single(t => t.Name == "PatchLoad").Methods.Single(m => m.Name == "HasPatch");
        var ins_call = ilp.Create(OpCodes.Call, method_HasPatch);
        IncrAddIns(ilp, ins_call, ref incrIndex);
        var ins_brfalse = ilp.Create(OpCodes.Brfalse, brFalseJumpIns);
        IncrAddIns(ilp, ins_brfalse, ref incrIndex);

        // 用于测试直接返回0
        //var ins_ldc_i4_0 = ilp.Create(OpCodes.Ldc_I4_0);
        //IncrAddIns(ilp, ins_ldc_i4_0, ref incrIndex);

        // 拥有Patch时返回执生成方法,并返回
        if(method.Parameters.Count > 0) {
            //如果有参数 把参数从局部放入栈中
            //for (int i = 0; i < method.Parameters.Count; i++) {
            //    IncrAddIns(ilp, Instruction.Create(OpCodes.Ldarg_S, (byte)i), ref incrIndex);
            //}

            // 知道Sum有2个参数 直接方进入就行了
            IncrAddIns(ilp, Instruction.Create(OpCodes.Ldarg_1), ref incrIndex);
            IncrAddIns(ilp, Instruction.Create(OpCodes.Ldarg_2), ref incrIndex);

        }

        IncrAddIns(ilp, Instruction.Create(OpCodes.Call, genMethod), ref incrIndex);

        var ins_ret = ilp.Create(OpCodes.Ret);
        IncrAddIns(ilp, ins_ret, ref incrIndex);
    }
    
    /// <summary>
    /// 生成补丁方法
    /// </summary>
    /// <returns></returns>
    static MethodDefinition GenPatchMethod(ModuleDefinition module, MethodDefinition method) {
        // PatchLoad
        var patchLoadType = module.Types.Single(t => t.Name == "PatchLoad");
        var vmField = patchLoadType.Fields.Single(f => f.Name == "virtualMachine");
        // Call
        var callType = module.Types.Single(t => t.Name == "Call");
        var callBegin = callType.Methods.Single(m => m.Name == "Begin");
        var callPushInt32 = callType.Methods.Single(m => m.Name == "PushInt32");
        var callGetInt32 = callType.Methods.Single(m => m.Name == "GetInt32");
        // VirtualMachine
        var vmExecute = module.Types.Single(t => t.Name == "VirtualMachine")
            .Methods.Single(m => m.Name == "Execute" && m.Parameters.Count == 4);
        // 基础方法
        // 返回值
        // 参数

        var returnType = method.ReturnType;
        Mono.Cecil.MethodAttributes methodAttributes = Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static;
        MethodDefinition patchMethod = new MethodDefinition(GenPatchMethodPrefix, methodAttributes, returnType);

        // 添加参数
        foreach (var parameter in method.Parameters) {
            patchMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
        }

        // var call
        VariableDefinition callVar = new VariableDefinition(callType);
        patchMethod.Body.Variables.Add(callVar);

        var ilp  = patchMethod.Body.GetILProcessor();

        ilp.Append(Instruction.Create(OpCodes.Nop));
        ilp.Append(Instruction.Create(OpCodes.Call, callBegin));
        ilp.Append(Instruction.Create(OpCodes.Stloc_0));
        // 将位于特定索引处的局部变量的地址加载到计算堆栈上
        ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
        // 将索引为 0 的参数加载到计算堆栈上。
        ilp.Append(Instruction.Create(OpCodes.Ldarg_0));
        ilp.Append(Instruction.Create(OpCodes.Call, callPushInt32));
        ilp.Append(Instruction.Create(OpCodes.Nop));
        ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
        ilp.Append(Instruction.Create(OpCodes.Ldarg_1));
        ilp.Append(Instruction.Create(OpCodes.Call, callPushInt32));
        ilp.Append(Instruction.Create(OpCodes.Nop));
        // 将静态字段的值推送到堆栈
        ilp.Append(Instruction.Create(OpCodes.Ldsfld, vmField));
        ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
        ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
        ilp.Append(Instruction.Create(OpCodes.Ldc_I4_2));
        ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
        ilp.Append(Instruction.Create(OpCodes.Call, vmExecute));
        ilp.Append(Instruction.Create(OpCodes.Nop));

        ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
        ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
        ilp.Append(Instruction.Create(OpCodes.Call, callGetInt32));
        // 将计算堆栈1 存储在局部变量列表中
        //ilp.Append(Instruction.Create(OpCodes.Stloc_1));
        // 将局部变量1 加载到计算堆栈中
        //ilp.Append(Instruction.Create(OpCodes.Ldloc_1));
        ilp.Append(Instruction.Create(OpCodes.Ret));


        // 测试方法
        //ilp.Append(Instruction.Create(OpCodes.Ldstr, "我是生成的方法"));
        //var log_method = module.ImportReference(typeof(Debug).GetMethod("Log", new Type[] { typeof(object) }));
        //ilp.Append(Instruction.Create(OpCodes.Call, log_method));

        // 测试用  直接返回0
        //ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
        //ilp.Append(Instruction.Create(OpCodes.Ret));

        patchLoadType.Methods.Add(patchMethod);

        return patchMethod;
    }
  • 执行完这部分代码,可以使用ildasm看看,Sum函数的IL指令的变化,蓝圈是插入的IL指令,红圈是原本Sum的指令

修复

  • 修复就是通过遍历Body.Instructions中所有IL指令,解析并转成VMInstruction格式的指令,我这边图方便,直接序列化Json通过EditorPrefs.SetString存储在Unity编辑器下了,InjectFix就很复杂了最后存储在一个文件里面,感兴趣可以去读InjectFix源码
    static void Fix(ModuleDefinition module, MethodDefinition method) {
        //{
        //  .custom instance void PatchAttribute::.ctor() = (01 00 00 00 ) 
        //  // 代码大小       9 (0x9)
        //  .maxstack  2
        //  .locals init(int32 V_0)
        //  IL_0000: nop
        // IL_0001:  ldarg.1
        //  IL_0002: ldarg.2
        //  IL_0003: add
        // IL_0004:  stloc.0
        //  IL_0005: br.s IL_0007
        //  IL_0007: ldloc.0
        //  IL_0008: ret
        //}
        var ils = method.Body.Instructions;
        var parseIls = new List<VMInstruction>();
        for (int i = 0; i < ils.Count; i++) {
            var il = ils[i];
            var ilStr = ils[i].OpCode.Code.ToString();
            switch (il.OpCode.Code) {
                case Mono.Cecil.Cil.Code.Nop:
                    parseIls.Add(new VMInstruction {
                        Code = Code.StackSpace,
                        Operand = (method.Body.Variables.Count << 16) | method.Body.MaxStackSize
                    }); // local | maxstack
                    break;
                case Mono.Cecil.Cil.Code.Ldarg_0:
                case Mono.Cecil.Cil.Code.Ldarg_1:
                case Mono.Cecil.Cil.Code.Ldarg_2:
                case Mono.Cecil.Cil.Code.Ldarg_3:
                    parseIls.Add(new VMInstruction() {
                        Code = Code.Ldarg,
                        Operand = int.Parse(ilStr.Substring(ilStr.Length - 1)) - 1
                    });
                    break;
                case Mono.Cecil.Cil.Code.Add:
                    parseIls.Add(new VMInstruction {
                        Code = (Code)Enum.Parse(typeof(Code), ilStr),
                        Operand = 0
                    });
                    break;
                case Mono.Cecil.Cil.Code.Stloc_0:
                case Mono.Cecil.Cil.Code.Stloc_1:
                case Mono.Cecil.Cil.Code.Stloc_2:
                case Mono.Cecil.Cil.Code.Stloc_3:
                    parseIls.Add(new VMInstruction {
                        Code = Code.Stloc,
                        Operand = int.Parse(ilStr.Substring(ilStr.Length - 1)),
                    });
                    break;
                case Mono.Cecil.Cil.Code.Ret:
                    parseIls.Add(new VMInstruction {
                        Code = Code.Ret,
                        Operand = method.ReturnType.ToString() == "System.Void" ? 0 : 1,
                    });
                    break;
            }
        }

        foreach (var vmil in parseIls) {
            Debug.LogError(vmil);
        }

        var json = JsonMapper.ToJson(parseIls);
        // Debug.LogError(json);
        EditorPrefs.SetString(FixJson, json);
    }

虚拟机

  • 这个基本就是InjectFix虚拟机实现,今天有点困,改天再写吧

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-07-21 21:49:23  更:2022-07-21 21:50:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 16:03:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码