编译后如何更改 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-CSharp
、Version=0.0.0.0
、Culture=neutral
、PublicKeyToken=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 中,如何让行详细信息的列在跳过多个列后动态更改以在父行下对齐?
如何刷新Rails / Sprockets以在资产之后了解生产服务器上的新清单:预编译