I have been investigating adding mod support to a Unity3D game. I am new to Unity (but not .NET), so if it seems like I am missing something obvious, please let me know because I probably am.
I would like to be able to export objects, including custom scripts, directly from the editor. It is easy enough to get the assembly associated with the script. Any loose scripts in the project seem to get compiled down to Assembly-CSharp.dll
. The problem comes in when I try to load the assembly at runtime in another Unity project. Calling Assembly.Load(byte[])
returns to me the assembly of the current project, not the one represented by the byte array. I am assuming this is because the AssemblyName
of the assembly from the mod project and the host project are both identical (Assembly-CSharp
, Version=0.0.0.0
, Culture=neutral
, PublicKeyToken=null
).
As a test, I updated my Visual Studio project for the mod project, renaming it from Assembly-CSharp to something different. This gets wiped out by Unity, but I can do a build before that happens and get the assembly I am looking for with a name other than Assembly-CSharp. Loading this via Assembly.Load(byte[])
seems to do the trick in a very simple 'import a script to rotate a cube' sort of scenario. While this appears to work, I am really looking for something more auto-magic single step from the Unity editor.
Now, on to the question...
If I understand the problem, what I am looking for is a programmatic way to change the AssemblyName
of an assembly after compilation. All suggestions & advice welcome.
Some possible routes I have investigated or considered:
1) A magic API method to change AssemblyName
and save it.
This would be ideal, but I have yet to stumble across anything useful here.
2) Edit raw bytes of an assembly to change the name? Not sure if this is possible, but if I could safely and reliably overwrite some of the raw bytes of an assembly to change its name, that would be swell.
3) Create a new dynamic assembly containing the types in the original assembly. My experience with dynamic assemblies is limited. I can create one, but what I am unsure how to do (if possible) is copy types defined in the original assembly to the new dynamic assembly.
4) Use the CSharpCodeProvider
to manually compile the assembly. Assuming Unity supports it, then I assume this might work, seems like a pain though. I have not yet investigated what it would take to locate all scripts and references needed for compilation. I'd like to avoid it if possible.
5) *Some magic with ilasm/ildasm.*
Assuming there is unity equivalents.. Is this something do-able with theses tools?
6) Programmatically update the csproj
file and recompile. Seems pretty hacky and a pain. Guessing I would need to have code to support both Visual Studio and MonoDevelop.
I would prefer to avoid external tools and recompilation so one of the first three options would be ideal. Thoughts, ideas? Thanks!
Bonus Question: My 'import a script to rotate a cube' test seemed to work, however in the log during the loading of the assembly, I see the two messages below. That is not the assembly name I used, not sure where it came from. Should I be concerned? Is this going to come back to bite me later?
> 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
In the Project window, select the folder in which you want to place the Assembly Definition. See in Glossary > Create > Assembly Definition. Select the new Assembly Definition you created. Set the properties in the Inspector window, as necessary.
Assembly Definitions are a new way Unity allows us to organize the code by creating separate assemblies and specifying dependencies between them. It first appeared as preview in Unity 2017.3 and became more functional in later releases. Assembly Definitions can also be called asmdefs for short.
Definition of assembly 1 : a company of persons gathered for deliberation and legislation, worship, or entertainment an assembly of religious leaders. 2 capitalized : a legislative body specifically : the lower house of a legislature.
An Assembly is a basic building block of . Net Framework applications. It is basically a compiled code that can be executed by the CLR. An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality.
The solution I ended up going with was using Mono.Cecil. Luckily, the Mono.Cecil library is available in the Unity editor, no need to deploy any extra libraries with my mod tools. Mono.Cecil worked great for renaming the assembly after it was compiled. Here is some code I'm using to make it happen:
// 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();
}
I have struggled with this for quite some time before finding the right approach.
While it is certainly possible to change the Assembly Name from the one generated by Unity (either by renaming it in the .csproj file before compilation, or after-compilation with Mono.Cecil (although I have not tried that approach myself)), be aware of the fact that if you want to load an asset bundle that has prefabs with scripts associated from the assembly in question, you will not be able to properly load that asset bundle, even if you have loaded your assembly first (Unity issues warnings of the sort: "the referenced script on this Behaviour is missing"). The reason is that Unity stores the Assembly Name with the script name as well, and requires that the type/script come from that specific Assembly (which makes sense).
What this boils down to is that you need to change the Assembly Name from within Unity. To do that, you can create an Assembly Definition asset in the scripts (root) folder and set the name to the one you wish to have for your Assembly Name. The technical details can be found at: https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With