AssetStudio Linux命令行版迁移

大类
技术标签
优先级
开始日期
状态
Public
Public
最后更新

背景

AssetStudio只有GUI,没有CLI,且没法跑在linux上
 

大体魔改思路

  • 新加了一个AssetStudioCLI的控制台项目,选.net 5.0或者.net 6.0
  • 引用上AssetStudioGUI项目,然后直接用他的接口
 

问题

  • linux下的dotnet不支持GUI,没有System.Windows.Forms这些库
 

解决方法1:在同一个命名空间里面抢名字

例如TreeNode在BuildAssetData里面被广泛使用,可以自己实现一个TreeNode类,占上原有的类,避免引用Forms库
notion image
 

解决方法2:基于ReferenceAssembly魔改System.Windows.Forms

System.Windows.Forms和Windows深度绑定,而且带着Unmanaged Code,没法单独拿到linux里面用。但是AssetStudioGUI的逻辑同样也和Forms的数据结构深度绑定,非常难以解耦。因此,若能正常引用Forms中的数据结构,同时跳过Forms模块的初始化流程,也就是为Forms模块制作一个轻量级的Stub,就可以避免大量修改AssetStudioGUI的逻辑。
我首先注意到Fakes工具,可以为unittest快速制作stub,但是他对原始的模块做了过多的rewriting,对程序集名称、类名称、方法名称,都做了相应的更改,不满足我们的需求。
随后,发现ReferenceAssembly就是所有带着所有类型和方法的Stub,通过dnSpy右键编辑程序集特性,删除其中的ReferenceAssembly一行,可以强行让runtime使用相应库。
为了让CLI能够正确找到System.Windows.Forms,还需要修改AssetStudioCLI.deps.json,分别加入相应的库的信息,才可以让runtime加载相应库。
"System.Windows.Forms/4.0.0.0": { "dependencies": { }, "runtime": { "System.Windows.Forms.dll": {} } }, "System.Configuration.ConfigurationManager/0.0.0.0": { "dependencies": { }, "runtime": { "System.Configuration.ConfigurationManager.dll": {} } } "System.Windows.Forms/4.0.0.0": { "type": "reference", "serviceable": false, "sha512": "" }, "System.Configuration.ConfigurationManager/0.0.0.0": { "type": "reference", "serviceable": false, "sha512": "" }
加载后,则会出现各种null pointer的异常
notion image
notion image
notion image
这是因为ReferenceAssembly中默认所有的方法的body都是throw null,带来大量空指针异常。只需要在dnSpy中修改相应的IL即可。
 
同时,Forms自己也引用了大量其他模块,这导致虽然Forms可以正常加载,但是一部分依赖模块仍然无法加载。
notion image
 
System.Configuration.ConfigurationManager模块和Forms类似,也带有Unmanaged Code,因此也需要魔改。然而奇怪的是,.net 5.0中的ConfigurationManager模块在ilspy中显示为.NETStandard模块,而正常应为.NETCoreApp类型的模块,这导致.net core无法拒绝加载相应的模块。
notion image
 
同时,由于不明原因,像上面那样ref Assembly来魔改的套路并不成功,会提示BadImageException错误无法加载。经过实验,发现重新导入一次程序集就能神奇地跑起来:
  • 在dnSpy中新建一个程序集
  • 在dnSpy中载入完整的ConfigurationManager模块(不是RefAssembly)
  • 右键,转换程序集为模块
  • 保存转换后的模块
    • 需要转换一次模块的原因是dnSpy拒绝向同名的Assembly合并,会提示这是另一个程序集的依赖(但是实际上完全不是)
    • 通过转换模块就能绕开这个限制
  • 按照完整的ConfigurationManager模块的信息填好信的程序集的信息(主要是程序集名称、公钥、版本号
  • 然后将转换后的模块合并到新的模块中
  • 右键编辑程序集特性,把ConfigurationManager原始的特性c#代码复制上去最后保存