尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

不加载DLL如何读取版本?Lumafly用Mono.Cecil解析Assembly-CSharp.dll完整原理

不加载DLL如何读取版本?Lumafly用Mono.Cecil解析Assembly-CSharp.dll完整原理 不加载DLL如何读取版本Lumafly用Mono.Cecil解析Assembly-CSharp.dll完整原理【免费下载链接】LumaflyA cross platform mod manager for Hollow Knight written in Avalonia.项目地址: https://gitcode.com/gh_mirrors/lu/LumaflyLumafly 是一款跨平台的《空洞骑士》Hollow KnightMod 管理工具Mod Manager基于 Avalonia 开发。它最核心的设计之一是检测 Modding API 版本时从不把游戏的 Assembly-CSharp.dll 加载进内存执行而是用 Mono.Cecil 以只读模式解析这个 .NET 程序集的元数据从中提取 API 版本号。本文带你用源码走一遍不加载 DLL 也能读版本的完整原理。为什么不能直接加载 Assembly-CSharp.dll 来读版本直觉上读取版本号最简单的做法是Assembly.Load加载 DLL然后反射读字段。但对 Lumafly 来说这条路走不通原因有三依赖缺失会直接崩溃。Assembly-CSharp.dll 是 Unity 游戏的核心程序集它引用了 Unity 引擎的大量运行时库。在 Lumafly 的进程里加载它找不到依赖程序集会直接抛异常。副作用不可控。加载程序集可能触发静态构造函数、注册事件、写文件等隐藏逻辑Mod 管理器不能容忍这种读个版本号反而动了游戏的行为。版本文件需要频繁探测。Lumafly 每次启动、每次切换 Mod 开关时都要判断当前文件是原版还是打入了 API加载方式太重、太危险。 正确的姿势是不执行代码只读元数据。这正是 Mono.Cecil 的用武之地。Mono.Cecil一个只读的 .NET 程序集解析器Mono.Cecil 是 .NET 生态中经典的程序集操作库它可以直接解析 PE 二进制文件也就是 .dll读取其中的类、方法、字段等 IL 元数据——全程不需要 JIT 编译更不会执行任何一行游戏代码。Lumafly 在 Lumafly.csproj 中引用了 Mono.Cecil 0.11.5PackageReference IncludeMono.Cecil Version0.11.5 /它相当于给程序集开了一个透明窗口你可以像查目录一样翻阅 DLL 内部结构但不会唤醒里面的任何代码。⚡四步提取 API 版本号完整解析流程核心逻辑集中在 CheckValidityOfAssembly.cs 的GetAPIVersion方法中整个过程只有四步第一步读取程序集元数据。调用AssemblyDefinition.ReadAssembly(asm)Cecil 打开 DLL 文件并解析出类层次结构此时还没有执行任何游戏代码。第二步定位 Modding.ModHooks 类。通过asmDefinition.MainModule.GetType(Modding.ModHooks)查找 API 的签名类。这个类是 Modding API 打入游戏后留下的标记——原版游戏里根本不存在它。第三步找到 _modVersion 字段。在 ModHooks 的字段列表中筛选出名为_modVersion的字段并用IsLiteral校验它确实是一个常量字段。第四步取出常量值。返回(int) ver.Constant版本号到手。核心代码极其精炼using AssemblyDefinition asmDefinition AssemblyDefinition.ReadAssembly(asm); var modhooks asmDefinition.MainModule.GetType(Modding.ModHooks); var ver modhooks.Fields.FirstOrDefault(x x.Name _modVersion); return (int)ver.Constant; 注意整个方法包在 try-catch 里任何异常都静默返回null。这是一个刻意的防御设计文件损坏、格式不对都不能让管理器崩溃读不到本身就是一种有效信息后面会用到。版本号的妙用三文件切换机制在 Installer.cs 中Lumafly 定义了三个关键常量对应 Managed 目录下的同一份主程序集常量文件名含义CurrentAssembly-CSharp.dll游戏当前实际运行的文件VanillaAssembly-CSharp.dll.v备份的原版文件ModdedAssembly-CSharp.dll.m备份的已注入 API文件切换 Mod 开关的本质就是把Current替换为.v原版或.mMod 版文件。那么管理器怎么知道现在当前是哪种状态答案还是靠GetAPIVersion这个不加载的探测Assembly-CSharp.dll里有ModHooks 类 → 当前是 Mod 模式直接读出版本号没有ModHooks 类 → 当前是原版再去.m文件里读版本号并把启用状态标记为false两个文件都读不到 → API 未安装记录为NotInstalledState。这套判断逻辑在 Installer.cs 的 CheckAPI 方法 中实现读到的版本号会被封装成InstalledState持久化下来界面上的 API 开关状态、版本号显示都源于此。另外还有个巧思CheckVanillaFileValidity 用文件存在且GetAPIVersion 返回 null来判定一份原版备份是否干净——读不到版本号在这里反而变成了校验条件。第二个实战场景自动定位 Mod 配置文件不加载 DLL 的解析能力在 GlobalSettingsFinder.cs 中还有第二个妙用。很多 Mod 的设置界面由设置 Mod 类驱动配置文件保存为类名.GlobalSettings.json。但 Mod 的显示名称和它的类名经常对不上比如显示名没有空格、类名带了额外后缀。Lumafly 的对策是用AssemblyDefinition.ReadAssembly(dll)解析 Mod 自己的 DLL遍历其中所有非抽象类检查基类是否以Modding.Mod、SFCore.Generics.SaveSettingsMod等已知基类开头对每个候选类名去存档目录找是否存在对应的.GlobalSettings.json命中即返回该文件名实现配置文件自动定位。全程只读元数据零执行风险。这个功能的正确性由 MiscServicesTest.cs 中的FindSettingsFile用例覆盖。Lumafly 如何测试读版本功能测试 GetAPIVersion 用例 的做法很聪明仓库里放了一个最小化的桩文件 MockMAPI.dll——它只是一个包含Modding.ModHooks类、且类里有_modVersion常量字段的小 DLLvar version _checkValidityOfAssembly.GetAPIVersion(MockMAPI.dll); Assert.Equal(74, version);测试断言解析出的版本号必须是 74从而验证了ReadAssembly → 找类 → 找字段 → 读常量这条链路在真实文件上完全可用。总结这个技巧为什么值得借鉴Lumafly 的方案回答了不加载 DLL 如何读取版本这个问题核心经验有三点用 Mono.Cecil 做静态元数据解析替代Assembly.Load的动态加载彻底规避依赖缺失与代码执行副作用把解析失败返回 null当成设计契约让同一个探测函数既能读版本又能判断 Mod/Vanilla 状态一石二鸟用最小桩 DLL 做单元测试把对真实二进制文件的解析逻辑纳入自动化测试。这套只读不执行的思路不只适用于 Mod 管理——任何需要探测、校验 .NET 程序集内部信息的工具都可以直接参考 CheckValidityOfAssembly.cs 这个不到 50 行的实现。【免费下载链接】LumaflyA cross platform mod manager for Hollow Knight written in Avalonia.项目地址: https://gitcode.com/gh_mirrors/lu/Lumafly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表