【Unity】Mod形式的Dll及AssetBundle外部加载插件

综述

本插件利用Mono.cecil静态注入模块(BepInEx包含的一个dll)实现在Unity游戏预加载(PreLoader)阶段的Dll修补工作,用以达到通过同版本Unity创建AssetBundle时候,无法打包脚本导致的游戏运行过程中利用Harmony等动态注入模块通过Hook函数或其他方式加载外部AssetBundle中的GameObject出现如下图所示的脚本缺失问题(The referenced script on this Behaviour is missing!)。
The referenced script on this Behaviour is missing!

使用方法

Github源码连接:点击此处查看

目录结构

只给出了与项目中所给例子相匹配的目录结构,具体结构自行结合实际修改。

  • BepInEx
    • config

    • core

    • patches

      • PatchMod.dll
      • PatchModInfo.dll
      • YamlDotNet.dll
    • plugins

      • RankPanel_Trigger.dll
      • BundleLoader
        • BundleLoader.dll
        • PatchModInfo.dll
        • YamlDotNet.dll
  • doorstop_config.ini
  • winhttp.dll
  • PatchMod
    • PatchMod.cfg
    • RankPanel
      • mods.yml
      • Dlls
        • Assembly-CSharp.dll
      • AseetBundles
        • rankpanel.ab
  • 其他文件

构建

PatchMod.dll 放入 BepInEx\patchers 文件夹中,将 BundleLoader.dll 放入 BepInEx\plugins 文件夹中。

对应Mod包的结构参考 PatchMod_Example.zip进行开发,将解压后的 PatchMod文件夹放入游戏根目录中。

PatchMod放置位置

目录中包含 PatchMod.cfg与各个Mod的包文件。

PatchMod.cfg文件内容如下:

[General]
# 是否预先加载进内存,预先加载进去可以防止其他Assembly-csharp加载
preLoad=true
# 是否将修补后的Dll输出到本地,用于调试查看
save2local=false

样板Mod中包含一个排行榜Mod,其打包过程如下:自己根据所要开发插件的游戏的Unity版本,用相同版本开发出组件并编写脚本,将要加入到游戏内的 Object打包为 AssetBundle,并记住其名字,然后插件项目整体进行构建,得到插件项目的 Assembly-csharp.dll,放到文件夹内。

Mod文件夹结构

在这里我的Dll文件放到了 Dlls文件夹下,AssetBundle文件放到了 Resources文件夹下,并在 mod.yml(拓展名为 .yml的文件即可)内编辑Mod相关设置。

# Mod名
name: 排行榜面板
# Dll读取路径
dlls:
    - Dlls/Assembly-CSharp.dll
# AssetBundle读取路径
resources:
    - AseetBundles/rankpanel.ab

进入游戏后在BepInEx控制台内即可以看到相关插件输出内容以及Mod组件加载列表。此后再根据其他插件Hook某些触发调用 Object即可。本项目内自带一个测试本用例的插件,亦可以下载完整版测试用例【金庸群侠传X】进行测试。

具体实现

首先是了解BepInEx插件,这是一个用于Unity/XNA游戏的外挂程序。
我们此次主要涉及三个部分:

  • 预加载时的Dll修补(Mono.cecil实现)
  • 游戏加载完成后的Bundle读取管理(Unity自带的Bundle管理机制)
  • 游戏内触发(Harmony2的动态修补Dll)

预加载修补

此部分具体参考的是IL-Repack项目,这是一个利用C#反射机制来进行Dll合并的项目(前身是IL-Merge,现已启用),IL-Repack的修补工作是通过魔改的一个Mono.cecil实现,本项目采用原版Mono.cecil模仿其实现。

using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PatchMod
{
   
    internal class MergeDll
    {
   
        //修复总函数
        internal static void Fix(string repairDllPath,ref AssemblyDefinition patchAssembly)
        {
   
            //修复用的文件(包含添加进去的内容)
            AssemblyDefinition repairAssembly = AssemblyDefinition.ReadAssembly(repairDllPath);
            //TODO:下面所有方法只修补二者MainModule.
            MergeDll.FixModuleReference(patchAssembly.MainModule, repairAssembly.MainModule);//修复引用
            foreach (TypeDefinition typeDef in repairAssembly.MainModule.Types)
            {
   
                //修复类型
                MergeDll.FixType(patchAssembly.MainModule, typeDef, (module, belongTypeDef, fieldDef) =>
                {
   
                    MergeDll.FixField(module, belongTypeDef, fieldDef);
                }, (module, belongTypeDef, methodDef) =>
                {
   
                    MergeDll.FixMethod(module, belongTypeDef, methodDef);
                });
            }
        }

        //修复Dll引用,将source添加到target中
        internal static void FixModuleReference(ModuleDefinition target, ModuleDefinition source)
        {
   
            foreach (ModuleReference modRef in source.ModuleReferences)
            {
   
                string name = modRef.Name;
                //如果存在重名则跳过修补
                if (!target.ModuleReferences.Any(y => y.Name == name))
                {
   
                    target.ModuleReferences.Add(modRef);
                }
            }
            foreach (AssemblyNameReference asmRef in source.AssemblyReferences)
            {
   
                string name = asmRef.FullName;
                //如果存在重名则跳过修补
                if (!target.AssemblyReferences.Any(y => y.FullName == name))
                {
   
                    target.AssemblyReferences.Add(asmRef);
                }
            }
        }

        //修复自定义类型,将source添加到target中
        //TODO:目前只能添加不同命名空间的类型
        internal static void FixType(ModuleDefinition target, TypeDefinition source, Action<ModuleDefinition, TypeDefinition, FieldDefinition> func_FixFeild, Action<ModuleDefinition, TypeDefinition, MethodDefinition> func_FixMethod)
        {
   
            //不合并同名Type
            //TODO:是否添加合并同名Type判断?
            if (!target.Types.Any(x => x.Name == source.Name))
            {
   
                //新建Type
                //如果是自定义Type直接Add会导致报错,因为属于不同的模块,
                //TODO:暂时没用unity工程的Assembly-csharp测试,不知道直接添加可否成功?
                //只向模块添加类型
                TypeDefinition importTypeDefinition = new(source.Namespace, source.Name, source.Attributes) {
    };
                //修复基类引用关系
                //例如 Component : MonoBehaviour
                if (source.BaseType != null)
                {
   
                    importTypeDefinition.BaseType = source.BaseType;
                }
                target.Types.Add
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值