编译后如何更改 AssemblyName 以在 Unity3d 中作为 mod 加载

Posted

技术标签:

【中文标题】编译后如何更改 AssemblyName 以在 Unity3d 中作为 mod 加载【英文标题】:How to change AssemblyName after compilation to load as a mod in Unity3d 【发布时间】:2015-04-25 02:02:03 【问题描述】:

我一直在研究向 Unity3D 游戏添加 mod 支持。我是 Unity 的新手(但不是 .NET),所以如果我似乎遗漏了一些明显的东西,请告诉我,因为我可能是。

我希望能够直接从编辑器中导出对象,包括自定义脚本。获取与脚本关联的程序集很容易。项目中任何松散的脚本似乎都被编译为 Assembly-CSharp.dll。当我尝试在另一个 Unity 项目中运行时加载程序集时,问题就出现了。调用Assembly.Load(byte[]) 会返回给我当前项目的程序集,而不是字节数组表示的程序集。我假设这是因为来自 mod 项目和宿主项目的程序集的 AssemblyName 都是相同的(Assembly-CSharpVersion=0.0.0.0Culture=neutralPublicKeyToken=null)。

作为测试,我为 mod 项目更新了我的 Visual Studio 项目,将其从 Assembly-CSharp 重命名为不同的名称。这被 Unity 消除了,但我可以在此之前进行构建,并使用 Assembly-CSharp 以外的名称获取我正在寻找的程序集。通过Assembly.Load(byte[]) 加载这个似乎在一个非常简单的“导入脚本以旋转立方体”的场景中起到了作用。虽然这似乎可行,但我真的在寻找 Unity 编辑器中更自动的单步功能。

现在,正题...

如果我理解这个问题,我正在寻找的是一种在编译后更改程序集的AssemblyName 的编程方式。欢迎所有建议和建议。

我已经调查或考虑过的一些可能的路线:

1) 一个神奇的 API 方法来改变 AssemblyName 并保存它。 这将是理想的,但我还没有在这里发现任何有用的东西。

2) 编辑程序集的原始字节以更改名称?不确定这是否可能,但我是否可以安全可靠地覆盖程序集的某些原始字节以更改其名称,那会很膨胀。

3) 创建一个包含原始程序集类型的新动态程序集。 我对动态程序集的经验有限。我可以创建一个,但我不确定该怎么做(如果可能的话)是将原始程序集中定义的类型复制到新的动态程序集中。

4) 使用 CSharpCodeProvider 手动编译程序集。 假设 Unity 支持它,那么我认为这可能有效,但似乎很痛苦。我还没有研究如何找到编译所需的所有脚本和引用。如果可能的话,我想避免它。

5) *ilasm/ildasm.* 的一些魔力假设有统一的等价物..这些工具可以做到吗?

6) 以编程方式更新csproj 文件并重新编译。 看起来很麻烦而且很痛苦。猜想我需要有代码来支持 Visual Studio 和 MonoDevelop。

我宁愿避免使用外部工具和重新编译,所以前三个选项之一是理想的。想法,想法?谢谢!


奖金问题: 我的“导入脚本以旋转立方体”测试似乎有效,但是在加载程序集期间的日志中,我看到以下两条消息。那不是我使用的程序集名称,不确定它来自哪里。我应该担心吗?以后会不会回来咬我?

> Non platform assembly: data-0B9D7940 (this message is harmless)
> Fallback handler could not load library C:/Dev/Test
> Projects/ModTest/Build/ModHost1/ModHost1_Data/Mono/data-0B9D7940.dll

【问题讨论】:

【参考方案1】:

我最终采用的解决方案是使用 Mono.Cecil。幸运的是,Unity 编辑器中提供了 Mono.Cecil 库,无需使用我的 mod 工具部署任何额外的库。 Mono.Cecil 非常适合在编译后重命名程序集。这是我用来实现它的一些代码:

     // Have Mono.Cecil load the assembly
     var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyFile.FullName);

     // Tell Mono.Cecil to actually change the name
     assemblyDefinition.Name.Name = newAssemblyNameNoExtension;
     assemblyDefinition.MainModule.Name = newAssemblyNameNoExtension;

     // We also need to rename any references to project assemblies (first pass assemblies)
     foreach (var reference in assemblyDefinition.MainModule.AssemblyReferences)
     
        if (Utilities.IsProjectAssembly(reference.Name))
        
           reference.Name = Utilities.GetModAssemblyName(reference.Name, this._modName);
        
     

     // Build the new assembly
     byte[] bytes;
     using (var ms = new MemoryStream())
     
        assemblyDefinition.Write(ms, new Mono.Cecil.WriterParameters()  WriteSymbols = true );
        bytes = ms.ToArray();
     

【讨论】:

【参考方案2】:

在找到正确的方法之前,我已经为此苦苦挣扎了很长一段时间。

当然可以从 Unity 生成的名称中更改程序集名称(通过在编译之前在 .csproj 文件中重命名它,或者在使用 Mono.Cecil 编译后重命名它(尽管我自己没有尝试过这种方法) ),请注意这样一个事实,即如果您想要加载一个资产包,该资产包的预制件与相关程序集相关联的脚本,即使您首先加载了您的程序集,您也将无法正确加载该资产包(Unity发出此类警告:“缺少有关此行为的引用脚本”)。原因是 Unity 也将程序集名称与脚本名称一起存储,并要求类型/脚本来自该特定程序集(这是有道理的)。

这归结为您需要在 Unity 中更改程序集名称。为此,您可以在脚本(根)文件夹中创建一个程序集定义资产,并将名称设置为您希望为程序集名称使用的名称。技术细节见:https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html

【讨论】:

问题具体要求在编译后改名。我看不出这个答案如何解决这个问题,甚至看不到问题中提出的更广泛的场景。这似乎不是对这个问题的有用贡献。 @PeterDuniho 这是与问题相关的有用信息。自从我最初提出这个问题以来,Unity 已经发生了很大变化,Attila 绝对有适合现代 Unity 项目的方法。 关于缺少脚本的问题,我通过对对象进行一些预处理来解决这个问题,在构建模块期间序列化自定义脚本。在运行时,从 mod 加载的对象会经过一些后处理,反序列化脚本。这很混乱,但在大多数情况下,这一切似乎都奏效了。但是,添加程序集定义有点使整个方法过时了。我们的下一款游戏依靠程序集定义文件来支持我们的模组,这是一个更好的解决方案。 @Tallek,在了解 Unity 中的装配定义之前,我几乎自己存储/重新附加了脚本 没错,如果 asmdef/ref 文件夹之外没有运行时脚本,Unity 似乎不会在输出中生成 Assembly-CSharp.dll。

以上是关于编译后如何更改 AssemblyName 以在 Unity3d 中作为 mod 加载的主要内容,如果未能解决你的问题,请参考以下文章

在 WPF 和 XAML 中,如何让行详细信息的列在跳过多个列后动态更改以在父行下对齐?

如何更改导航颜色以在网站上滚动?

如何编译程序以在 Mac 上分发

如何刷新Rails / Sprockets以在资产之后了解生产服务器上的新清单:预编译

如何在“共享您的信息”中更改公司名称以在 iTunes 中自动更新订阅?

如何自动将版本号插入 AssemblyName