5、【游戏MOD】【骑砍2】跨平台MOD构建环境(五)/:元数据查看工具与跨版本适配

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

5、【游戏MOD】【骑砍2】跨平台MOD构建环境(五)/:元数据查看工具与跨版本适配

背景

上篇《跨平台MOD构建环境(四)/:跨平台编译与通用打包》讲清了流水线最后两步:compile.sh 通过三个 MSBuild 全局属性完成跨平台编译-------p:GamePath 注入参考程序集路径、-p:EnableWindowsTargeting=true 允许 Linux 编 net472、-p:ModulePackagePath= 关闭工程自带的 Windows 打包步骤;整解失败自动回退逐工程;pack.sh 按描述符 modules[] 把 DLL 放进 bin/Win64_Shipping_Client/、把资产铺到模块根并压缩成 zip。同时强调了 Directory.Build.props 里的 <Private>false</Private>------它是「旧 DLL 复制进 bin/ 导致假通过」这一跨版本第一坑的解法。

主链路讲完后,本篇换个角度,讲一个专门为「跨游戏版本适配」准备的小工具:元数据查看工具 。MOD 从旧版本移植到新版本时,最常见的失败是某个字段/属性/方法被游戏改名,而这个工具用来在不运行游戏、不加载依赖的前提下,直接从参考程序集里查出类型与成员签名。


骑砍2

🎯 为什么只需读元数据

第二篇说过,参考程序集是只含元数据、不含 IL(Intermediate Language,中间语言)程序体的 DLL。而编译期恰好只需要「有哪些类型、成员签名是什么」,不需要方法体------所以直接用参考程序集分析,就能回答「新版本里这个成员还叫这个名吗、参数变了吗」。

对象 内容 能回答的问题
参考程序集 仅元数据(类型/字段/属性/方法签名) 成员是否改名、签名是否变化
运行程序集 元数据 + IL 方法「怎么做的」(行为)

只读参考程序集还附带一个好处:不需要解析依赖 。读取时用 PEReader(Portable Executable,可移植可执行文件的读取器)配合 MetadataReader,只解析元数据表,不会去加载被引用的程序集,因此即便依赖缺失也能跑。


🧩 两种模式与用法

工具对外只有两个动作:type(打印某类型的字段/属性/方法)与 find(按子串反查成员所在类型)。

sh 复制代码
# 打印某类型的完整签名
./meta.sh TaleWorlds.MountAndBlade.dll type TaleWorlds.MountAndBlade.MBMissile
# 反查含某子串的成员(不知道成员在哪个类型时用)
./meta.sh TaleWorlds.CampaignSystem.dll find PartyLimit

外层脚本 meta.sh 负责繁琐的准备工作:定位参考程序集根(优先留档,其次缓存)、必要时构建工具、再把参数透传给工具本体。

sh 复制代码
# meta.sh:定位 refs + 按需构建工具(示例)
REFS=$(ls -d "$VENDOR_DIR"/*/ 2>/dev/null | head -1)
[ -n "$REFS" ] || REFS=$(ls -d "$REFS_DIR"/*/ 2>/dev/null | head -1)
exec dotnet "$TOOL_DLL" "$REFS" "$1" "$2" "$3"

🧱 处理管线

工具本体的管线很短:定位 DLL → 用 PEReader 打开 → 取 MetadataReader → 遍历 TypeDefinitions → 按模式输出。

其中关键的一步是解码签名 。元数据里的方法签名是压缩编码的,需要实现一个 ISignatureTypeProvider 来把它还原成可读字符串:

csharp 复制代码
// 解码签名(示例)
var md = pe.GetMetadataReader();
var provider = new TypeProvider(md);
// 每个方法:先 DecodeSignature,再打印返回类型与参数类型
var sig = mdef.DecodeSignature(provider, null);
var pars = string.Join(", ", sig.ParameterTypes);
Console.WriteLine($"    {vis} {stat}{sig.ReturnType} {md.GetString(mdef.Name)}({pars})");

type 模式会依次打印三块内容:-- fields --、-- properties --、-- methods --,每行都带可见性(public/private)与静态标记,形如 public static void Foo(Bar),足够用来比对版本间的成员差异。


🔬 find 模式是怎么反查的

find 模式不知道目标成员在哪个类型,所以它遍历每个类型,把字段、属性、方法的名字逐一与查询子串做「包含」比较,命中就输出「类型名:成员列表」。

csharp 复制代码
// find 模式(示例)
foreach (var fh in td.GetFields())
{
    var nm = md.GetString(md.GetFieldDefinition(fh).Name);
    if (nm.Contains(query, StringComparison.OrdinalIgnoreCase)) hits.Add($"field {nm}");
}
// 属性/方法同理:hits.Add($"prop {nm}") / hits.Add($"method {nm}")

这也解释了前面的一条局限:find 匹配的是成员名 (字段/属性/方法),不是方法体内容;要找「某个方法里用了谁」,还得靠实机日志或 strings。


🧮 签名解码要处理的几类语法

元数据里的签名是压缩编码的,ISignatureTypeProvider 的作用就是把它们还原成可读类型名。需要处理的几类比较典型:

签名形态 解码结果 对应方法
基本类型 int/bool/string 等 GetPrimitiveType
数组 T[] / T[,] GetSZArrayType / GetArrayType
泛型 List<T> GetGenericInstantiation
泛型参数 !0(类型参数)/ !!0(方法参数) GetGenericTypeParameter / GetGenericMethodParameter
引用/指针 ref T / T* GetByReferenceType / GetPointerType

正是这个 provider 把 ValueTuple2<bool, bool>这类签名还原成System.ValueTuple2<bool, bool>,移植时才看得懂新版方法的参数到底变成了什么。


⚠️ 已知局限

工具刻意保持极简(metadata-only、不解析依赖),因此有几处约定需要记住:

现象 原因
in/ref 参数都显示成 ref 元数据读取未区分两者,需按 in 先试
嵌套类型只显示短名 如 Mission.Missile 会打印成 Missile,type 查询要用短名
可见性只分 public/private override 常被显示成 private,不能据此判断可访问性
接口/枚举用 type 查询可能为空 改用 find 反查更稳
看不到方法体 元数据无 IL,行为要靠实机日志或 strings 辅助

一句话记忆:它是「API 存在性与签名」的来源;行为以实机为准。


🧪 实战:定位一个被改名的字段

一次真实移植里,旧代码引用了 MissionGauntletFormationMarker 的私有字段 _focusedFormationsCache,新版运行时报 No such field。用 find 反查新版参考程序集即可确认改名:

sh 复制代码
./meta.sh TaleWorlds.MountAndBlade.GauntletUI.dll find focusedFormation
# 输出里能看到新字段名(旧名已不存在)

据此把补丁里的字段名从 _focusedFormationsCache 改成新名,编译即可通过。整个过程没有启动游戏、没有加载任何依赖,只是读了一份元数据。


📌 本篇小结

  • 编译期只需签名,因此元数据查看工具只读参考程序集(metadata-only)即可分析 API。
  • 两种模式:type(打印成员签名)与 find(按子串反查成员)。
  • 管线:定位 DLL → PEReader/MetadataReader → ISignatureTypeProvider 解码签名 → 遍历类型输出。
  • 局限:in/ref 都印成 ref、嵌套短名、可见性只分两类、看不到 IL。
  • 用途:跨游戏版本时,先确认成员是否改名/改签名,再改代码。

到这里,从 SDK 引导、参考程序集、源码同步、编译打包到元数据工具,「跨平台 MOD 构建环境」就讲完了。后续可以继续展开 崩溃日志收集与判读、产物部署到 Windows 游戏、以及一个跨版本移植的完整复盘。


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

【游戏MOD】【骑砍2】跨平台MOD构建环境(六)/:DLL 里到底装了什么------元数据、IL 与 JIT

相关推荐
HIT_Weston14 小时前
4、【游戏MOD】【骑砍2】跨平台MOD构建环境(四)/:跨平台编译与通用打包
mod开发·骑砍2
HIT_Weston2 天前
3、【MOD开发】【骑砍2】跨平台MOD构建环境(三):只读源码同步、子模块与路径大小写
mod开发·骑砍2
HIT_Weston3 天前
1、【MOD开发】【骑砍2】跨平台MOD构建环境(一):总体思路与 SDK 引导
mod开发·骑砍2
霸王•吕布3 年前
骑砍战团MOD开发(39)-RTS塔防保卫卡拉迪亚大陆
游戏开发·骑马与砍杀·骑砍战团·mod开发·rts塔防·rts视角·rts游戏