using System; using System.IO; using System.Linq; using System.Collections.Generic; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor; using MethodAttributes = Mono.Cecil.MethodAttributes; namespace Unity.Netcode.Editor.CodeGen { internal sealed class INetworkSerializableILPP : ILPPInterface { public override ILPPInterface GetInstance() => this; public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName || compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); private readonly List m_Diagnostics = new List(); public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { if (!WillProcess(compiledAssembly)) { return null; } m_Diagnostics.Clear(); // read var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver); if (assemblyDefinition == null) { m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}"); return null; } // process var mainModule = assemblyDefinition.MainModule; if (mainModule != null) { try { if (ImportReferences(mainModule)) { var types = mainModule.GetTypes() .Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType) .ToList(); // process `INetworkMessage` types if (types.Count == 0) { return null; } CreateModuleInitializer(assemblyDefinition, types); } else { m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}"); } } catch (Exception e) { m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); } } else { m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}"); } mainModule.RemoveRecursiveReferences(); // write var pe = new MemoryStream(); var pdb = new MemoryStream(); var writerParameters = new WriterParameters { SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true }; assemblyDefinition.Write(pe, writerParameters); return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } private MethodReference m_InitializeDelegates_MethodRef; private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates); private bool ImportReferences(ModuleDefinition moduleDefinition) { var helperType = typeof(NetworkVariableHelper); foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { switch (methodInfo.Name) { case k_InitializeMethodName: m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo); break; } } return true; } private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition) { var staticCtorMethodDef = typeDefinition.GetStaticConstructor(); if (staticCtorMethodDef == null) { staticCtorMethodDef = new MethodDefinition( ".cctor", // Static Constructor (constant-constructor) MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, typeDefinition.Module.TypeSystem.Void); staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); typeDefinition.Methods.Add(staticCtorMethodDef); } return staticCtorMethodDef; } // Creates a static module constructor (which is executed when the module is loaded) that registers all the // message types in the assembly with MessagingSystem. // This is the same behavior as annotating a static method with [ModuleInitializer] in standardized // C# (that attribute doesn't exist in Unity, but the static module constructor still works) // https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0 // https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx private void CreateModuleInitializer(AssemblyDefinition assembly, List networkSerializableTypes) { foreach (var typeDefinition in assembly.MainModule.Types) { if (typeDefinition.FullName == "") { var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition); var processor = staticCtorMethodDef.Body.GetILProcessor(); var instructions = new List(); foreach (var type in networkSerializableTypes) { var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef); method.GenericArguments.Add(type); instructions.Add(processor.Create(OpCodes.Call, method)); } instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction)); break; } } } } }