#if !FUSION_DEV #region Assets/Photon/Fusion/CodeGen/AssemblyInfo.cs [assembly: Fusion.NetworkAssemblyIgnore] #endregion #region Assets/Photon/Fusion/CodeGen/ForLoopMacro.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using static ILWeaverOpCodes; using MethodBody = Mono.Cecil.Cil.MethodBody; public readonly struct NewArrayWithLengthEqualToOtherArrayOrZero : ILProcessorMacro { public readonly Action GetArray; public readonly TypeReference ArrayElementType; public NewArrayWithLengthEqualToOtherArrayOrZero(TypeReference arrayElementType, Action getArray) { GetArray = getArray; ArrayElementType = arrayElementType; } public void Emit(ILProcessor il) { var brNotNul = Nop(); var brNewArr = Nop(); il.Append(Dup()); il.Append(Brtrue_S(brNotNul)); il.Append(Pop()); il.Append(Ldc_I4(0)); il.Append(Br_S(brNewArr)); il.Append(brNotNul); il.Append(Ldlen()); il.Append(Conv_I4()); il.Append(brNewArr); il.Append(Newarr(ArrayElementType)); } } public readonly struct GetCollectionCountOrZero : ILProcessorMacro { public readonly TypeReference CollectionType; public GetCollectionCountOrZero(TypeReference collectionType) { CollectionType = collectionType; } public void Emit(ILProcessor il) { var brNotNul = Nop(); var done = Nop(); il.Append(Dup()); il.Append(Brtrue_S(brNotNul)); il.Append(Pop()); il.Append(Ldc_I4(0)); il.Append(Br_S(done)); il.Append(brNotNul); il.Append(Call(new MethodReference("get_Count", il.Body.Method.Module.TypeSystem.Int32, CollectionType) { HasThis = true })); il.Append(done); } } public readonly struct ForLoopMacro : ILProcessorMacro { public readonly MethodBody Body; public readonly Action Generator; public readonly Action Start; public readonly Action Stop; public ForLoopMacro(MethodBody body, Action generator, Action start, Action stop) { Body = body; Generator = generator; Start = start; Stop = stop; } public void Emit(ILProcessor il) { var varId = Body.Variables.Count; var indexVariable = new VariableDefinition(Body.Method.Module.TypeSystem.Int32); Body.Variables.Add(indexVariable); Start(il); il.Append(Stloc(Body, varId)); var loopConditionStart = Ldloc(Body, varId); il.Append(Br_S(loopConditionStart)); { var loopBodyBegin = il.AppendReturn(Nop()); Generator(il, indexVariable); il.Append(Ldloc(Body, varId)); il.Append(Ldc_I4(1)); il.Append(Add()); il.Append(Stloc(Body, varId)); il.Append(loopConditionStart); Stop(il); il.Append(Blt_S(loopBodyBegin)); } } } public readonly struct DictionaryForEachMacro : ILProcessorMacro { public readonly MethodBody Body; public readonly Action Generator; public readonly TypeReference EnumerableType; public readonly ModuleDefinition Module; public DictionaryForEachMacro(ModuleDefinition module, MethodBody body, Action generator, TypeReference enumerableType) { Module = module; Body = body; Generator = generator; EnumerableType = enumerableType; } (TypeReference variableType, TypeReference depententType) GetDependentType(Type type, TypeReference provider) { var enumeratorDef = Module.ImportReference(type).Resolve(); var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!0", provider); parameterTypeReference0.SetPosition(0); var parameterTypeReference1 = new Mono.Cecil.GenericParameter($"!1", provider); parameterTypeReference1.SetPosition(1); var returnRef = new GenericInstanceType(Module.ImportReference(enumeratorDef)) { GenericArguments = { parameterTypeReference0, parameterTypeReference1 } }; var variableTypeRef = Module.ImportReference(enumeratorDef) .MakeGenericInstanceType(((GenericInstanceType)EnumerableType).GenericArguments.ToArray()); return (variableTypeRef, returnRef); } public void Emit(ILProcessor il) { var enumeratorType = GetDependentType(typeof(Dictionary<,>.Enumerator), EnumerableType); var enumeratorVariable = new VariableDefinition(enumeratorType.variableType); Body.Variables.Add(enumeratorVariable); var keyValueType = GetDependentType(typeof(KeyValuePair<,>), enumeratorType.variableType); var keyValueVariable = new VariableDefinition(keyValueType.variableType); Body.Variables.Add(keyValueVariable); il.Append(Callvirt(new MethodReference("GetEnumerator", enumeratorType.depententType, EnumerableType) { HasThis = true })); il.Append(Stloc(enumeratorVariable)); var moveNextStart = Ldloca(enumeratorVariable); var getCurrentStart = Ldloca(enumeratorVariable); il.Append(Br(moveNextStart)); il.Append(getCurrentStart); il.Append(Callvirt(new MethodReference("get_Current", keyValueType.depententType, enumeratorType.variableType) { HasThis = true })); il.Append(Stloc(keyValueVariable)); Generator(il, keyValueVariable); il.Append(moveNextStart); il.Append(Callvirt(new MethodReference("MoveNext", Module.TypeSystem.Boolean, enumeratorType.variableType) { HasThis = true })); il.Append(Brtrue_S(getCurrentStart)); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILMacroStruct.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using Mono.Cecil.Cil; internal struct ILMacroStruct : ILProcessorMacro { Action generator; Instruction[] instructions; public static implicit operator ILMacroStruct(Instruction[] instructions) { if (instructions == null) { throw new ArgumentNullException(nameof(instructions)); } return new ILMacroStruct() { instructions = instructions }; } public static implicit operator ILMacroStruct(Action generator) { if (generator == null) { throw new ArgumentNullException(nameof(generator)); } return new ILMacroStruct() { generator = generator }; } public void Emit(ILProcessor il) { if (generator != null) { generator(il); } else { foreach (var instruction in instructions) { il.Append(instruction); } } } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaver.Cache.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Linq; using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using static Fusion.CodeGen.ILWeaverOpCodes; partial class ILWeaver { private TypeReference MakeFixedBuffer(ILWeaverAssembly asm, int wordCount) { FieldDefinition CreateFixedBufferField (TypeDefinition type, string fieldName, TypeReference elementType, int elementCount) { var fixedBufferFieldType = new TypeDefinition("", $"<{fieldName}>e__FixedBuffer", TypeAttributes.SequentialLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic) { BaseType = asm.Import(typeof(ValueType)), PackingSize = 0, ClassSize = elementCount * elementType.GetPrimitiveSize(), }; fixedBufferFieldType.AddAttribute(asm); fixedBufferFieldType.AddAttribute(asm); fixedBufferFieldType.AddAttribute(asm); fixedBufferFieldType.AddTo(type); var elementField = new FieldDefinition("FixedElementField", FieldAttributes.Public, elementType); elementField.AddTo(fixedBufferFieldType); var field = new FieldDefinition(fieldName, FieldAttributes.Public, fixedBufferFieldType); field.AddAttribute(asm, elementType, elementCount); field.AddTo(type); return field; } string typeName = $"FixedStorage@{wordCount}"; var fixedBufferType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", typeName); if (fixedBufferType == null) { // fixed buffers could be included directly in structs, but then again it would be impossible to provide a custom drawer; // that's why there's this proxy struct var storageType = new TypeDefinition("Fusion.CodeGen", typeName, TypeAttributes.ExplicitLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable, asm.ValueType); storageType.AddTo(asm.CecilAssembly); storageType.AddInterface(asm); storageType.AddAttribute(asm, wordCount); storageType.AddAttribute(asm); FieldDefinition bufferField; if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) { bufferField = CreateFixedBufferField(storageType, $"Data", asm.Import(typeof(int)), wordCount); bufferField.Offset = 0; // Unity debugger seems to copy only the first element of a buffer, // the rest is garbage when inspected; let's add some additional // fields to help it for (int i = 1; i < wordCount; ++i) { var unityDebuggerWorkaroundField = new FieldDefinition($"_{i}", FieldAttributes.Private | FieldAttributes.NotSerialized, asm.Import()); unityDebuggerWorkaroundField.Offset = Allocator.REPLICATE_WORD_SIZE * i; unityDebuggerWorkaroundField.AddTo(storageType); } } fixedBufferType = storageType; } return fixedBufferType; } private string TypeNameToIdentifier(TypeReference type, string prefix) { string result = type.FullName; result = result.Replace("`1", ""); result = result.Replace("`2", ""); result = result.Replace("`3", ""); result = result.Replace(".", "_"); result = prefix + result; return result; } private TypeDefinition MakeUnitySurrogate(ILWeaverAssembly asm, PropertyDefinition property) { var type = property.PropertyType; GenericInstanceType baseType; string surrogateName; TypeReference dataType; Instruction initCall = null; if (type.IsNetworkDictionary(out var keyType, out var valueType)) { keyType = asm.Import(keyType); valueType = asm.Import(valueType); var keyReaderWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, keyType); var valueReaderWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, valueType); baseType = asm.Import(typeof(Fusion.Internal.UnityDictionarySurrogate<,,,>)).MakeGenericInstanceType(keyType, keyReaderWriterType, valueType, valueReaderWriterType); surrogateName = "UnityDictionarySurrogate@" + keyReaderWriterType.Name + "@" + valueReaderWriterType.Name; dataType = TypeReferenceRocks.MakeGenericInstanceType(asm.Import(typeof(SerializableDictionary<,>)), keyType, valueType); initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(SerializableDictionary)).Resolve().GetMethodOrThrow("Create"))) { GenericArguments = { keyType, valueType } }); } else if (type.IsNetworkArray(out var elementType)) { elementType = asm.Import(elementType); var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, elementType); baseType = asm.Import(typeof(Fusion.Internal.UnityArraySurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType); surrogateName = "UnityArraySurrogate@" + readerWriterType.Name; dataType = elementType.MakeArrayType(); initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) { GenericArguments = { elementType } }); } else if (type.IsNetworkList(out elementType)) { elementType = asm.Import(elementType); var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, elementType); baseType = asm.Import(typeof(Fusion.Internal.UnityLinkedListSurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType); surrogateName = "UnityLinkedListSurrogate@" + readerWriterType.Name; dataType = elementType.MakeArrayType(); initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) { GenericArguments = { elementType } }); } else { var readerWriterType = MakeElementReaderWriter(asm, property.DeclaringType, property, property.PropertyType); baseType = asm.Import(typeof(Fusion.Internal.UnityValueSurrogate<,>)).MakeGenericInstanceType(property.PropertyType, readerWriterType); surrogateName = "UnityValueSurrogate@" + readerWriterType.Name; dataType = property.PropertyType; } int attributesHash = HashCodeUtilities.InitialHash; VisitPropertyMovableAttributes(property, (ctor, blob) => { attributesHash = HashCodeUtilities.GetHashDeterministic(ctor.FullName, attributesHash); attributesHash = HashCodeUtilities.GetHashCodeDeterministic(blob, attributesHash); }); if (attributesHash != HashCodeUtilities.InitialHash) { surrogateName += $"@Attributes_0x{attributesHash:X8}"; } var surrogateType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", surrogateName); if (surrogateType == null) { surrogateType = new TypeDefinition("Fusion.CodeGen", surrogateName, TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.Serializable | TypeAttributes.BeforeFieldInit, baseType); surrogateType.AddTo(asm.CecilAssembly); var dataProp = new PropertyDefinition("DataProperty", PropertyAttributes.None, dataType); dataProp.AddTo(surrogateType); var dataField = new FieldDefinition("Data", FieldAttributes.Public, dataType); dataField.AddTo(surrogateType); var getMethod = new MethodDefinition($"get_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, dataType); { var il = getMethod.Body.GetILProcessor(); il.Append(Ldarg_0()); il.Append(Ldfld(dataField)); il.Append(Ret()); } getMethod.AddTo(surrogateType); var setMethod = new MethodDefinition($"set_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, asm.Void); setMethod.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, dataType)); { var il = setMethod.Body.GetILProcessor(); il.Append(Ldarg_0()); il.Append(Ldarg_1()); il.Append(Stfld(dataField)); il.Append(Ret()); } setMethod.AddTo(surrogateType); dataProp.GetMethod = getMethod; dataProp.SetMethod = setMethod; MovePropertyAttributesToBackingField(asm, property, dataField, addSerializeField: false); surrogateType.AddDefaultConstructor(il => { if (initCall != null) { il.Append(Ldarg_0()); il.Append(initCall); il.Append(Stfld(dataField)); } }); } return surrogateType; } private TypeDefinition MakeElementReaderWriter(ILWeaverAssembly asm, TypeReference declaringType, ICustomAttributeProvider member, TypeReference elementType) { elementType = asm.Import(elementType); var interfaceType = asm.Import(typeof(IElementReaderWriter<>)).MakeGenericInstanceType(elementType); void AddIElementReaderWriterImplementation(TypeDefinition readerWriterType, int elementWordCount, bool isExplicit = false) { var dataType = asm.Import(typeof(byte*)); var indexType = asm.Import(typeof(int)); readerWriterType.Interfaces.Add(new InterfaceImplementation(interfaceType)); var visibility = isExplicit ? MethodAttributes.Private : MethodAttributes.Public; var namePrefix = isExplicit ? $"CodeGen@ElementReaderWriter<{elementType.FullName}>." : ""; var readMethod = new MethodDefinition($"{namePrefix}Read", visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, elementType); readMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); readMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); readMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); readMethod.AddAttribute(asm); readMethod.AddTo(readerWriterType); var readRefMethod = new MethodDefinition($"{namePrefix}ReadRef", visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, elementType.MakeByReferenceType()); readRefMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); readRefMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); readRefMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); readRefMethod.AddAttribute(asm); readRefMethod.AddTo(readerWriterType); var writeMethod = new MethodDefinition($"{namePrefix}Write", visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, asm.Void); writeMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); writeMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); writeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType)); writeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); writeMethod.AddAttribute(asm); writeMethod.AddTo(readerWriterType); var getElementWordCountMethod = new MethodDefinition($"{namePrefix}GetElementWordCount", visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, asm.Import(typeof(int))); getElementWordCountMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); getElementWordCountMethod.AddAttribute(asm); getElementWordCountMethod.AddTo(readerWriterType); var getElementHashCodeMethod = new MethodDefinition($"{namePrefix}GetElementHashCode", visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, asm.Import(typeof(int))); getElementHashCodeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType)); getElementHashCodeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); getElementHashCodeMethod.AddAttribute(asm); getElementHashCodeMethod.AddTo(readerWriterType); if (isExplicit) { readMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.Read))); readRefMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.ReadRef))); writeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.Write))); getElementWordCountMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.GetElementWordCount))); getElementHashCodeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.GetElementHashCode))); } Action addressGetter = il => { il.Append(Instruction.Create(OpCodes.Ldarg_1)); il.Append(Instruction.Create(OpCodes.Ldarg_2)); il.Append(Instruction.Create(OpCodes.Ldc_I4, elementWordCount * Allocator.REPLICATE_WORD_SIZE)); il.Append(Instruction.Create(OpCodes.Mul)); il.Append(Instruction.Create(OpCodes.Add)); }; EmitRead(asm, readMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, emitRet: true); EmitRead(asm, readRefMethod.Body.GetILProcessor(), readRefMethod.ReturnType, readerWriterType, member, addressGetter, emitRet: true, throwForNonUnmanagedRefs: true); EmitWrite(asm, writeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, OpCodes.Ldarg_3, emitRet: true); EmitGetHashCode(asm, getElementHashCodeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, valueGetter: il => { il.Append(Ldarg_1()); }, valueAddrGetter: il => { il.Append(Ldarga_S(getElementHashCodeMethod.Parameters[0])); }, emitRet: true); { var il = getElementWordCountMethod.Body.GetILProcessor(); il.Append(Ldc_I4(elementWordCount)); il.Append(Ret()); } } var typeInfo = TypeRegistry.GetInfo(elementType); if (!typeInfo.CanBeUsedInStructs) { if (!declaringType.Is()) { throw new ILWeaverException($"{elementType} needs wrapping - such types are only supported as NetworkBehaviour properties."); } // let's add an interface! var behaviour = declaringType.Resolve(); var wordCount = typeInfo.GetMemberWordCount(member, behaviour); if (!behaviour.Interfaces.Any(x => x.InterfaceType.FullName == interfaceType.FullName)) { Log.Debug($"Adding interface {behaviour} {interfaceType}"); AddIElementReaderWriterImplementation(behaviour, wordCount, isExplicit: true); } return behaviour; } else { var readerWriterName = "ReaderWriter@" + elementType.FullName.Replace(".", "_").Replace("/", "__"); if (typeInfo.TryGetCapacity(member, out int capacity)) { readerWriterName += $"@Capacity_{capacity}"; } var readerWriterTypeDef = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", readerWriterName); const string GetInstanceMethodName = "GetInstance"; if (readerWriterTypeDef == null) { readerWriterTypeDef = new TypeDefinition("Fusion.CodeGen", readerWriterName, TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.SequentialLayout | TypeAttributes.BeforeFieldInit, asm.ValueType); // without this, VS debugger will crash readerWriterTypeDef.PackingSize = 0; readerWriterTypeDef.ClassSize = 1; readerWriterTypeDef.AddTo(asm.CecilAssembly); readerWriterTypeDef.AddAttribute(asm); var wordCount = typeInfo.GetMemberWordCount(member, readerWriterTypeDef); AddIElementReaderWriterImplementation(readerWriterTypeDef, wordCount); var instanceField = new FieldDefinition("Instance", FieldAttributes.Public | FieldAttributes.Static, interfaceType); instanceField.AddTo(readerWriterTypeDef); var initializeMethod = new MethodDefinition(GetInstanceMethodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, interfaceType); initializeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); initializeMethod.AddTo(readerWriterTypeDef); { var il = initializeMethod.Body.GetILProcessor(); var loadFld = Ldsfld(instanceField); var tmpVar = new VariableDefinition(readerWriterTypeDef); il.Body.Variables.Add(tmpVar); il.Append(Ldsfld(instanceField)); il.Append(Brtrue_S(loadFld)); il.Append(Ldloca_S(tmpVar)); il.Append(Initobj(readerWriterTypeDef)); il.Append(Ldloc_0()); il.Append(Box(readerWriterTypeDef)); il.Append(Stsfld(instanceField)); il.Append(loadFld); il.Append(Ret()); } } return readerWriterTypeDef; } } private void EmitElementReaderWriterLoad(ILProcessor il, TypeDefinition readerWriterType) { if (readerWriterType.Is()) { il.Append(Ldarg_0()); } else { var getInstanceMethod = readerWriterType.GetMethodOrThrow("GetInstance"); il.Append(Call(getInstanceMethod)); } } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaver.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using Mono.Collections.Generic; using static Fusion.CodeGen.ILWeaverOpCodes; using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; using MethodAttributes = Mono.Cecil.MethodAttributes; using ParameterAttributes = Mono.Cecil.ParameterAttributes; public unsafe partial class ILWeaver { Dictionary _rpcCount = new Dictionary(new MemberReferenceFullNameComparer()); internal readonly ILWeaverLog Log; internal readonly ILWeaverSettings Settings; public ILWeaver(ILWeaverSettings settings, ILWeaverLog log) { if (log == null) { throw new ArgumentNullException(nameof(log)); } Log = log; Settings = settings; } public ILWeaver(ILWeaverSettings settings, ILWeaverLogger logger) : this(settings, new ILWeaverLog(logger)) { } private void EnsureTypeRegistry(ILWeaverAssembly asm) { if (TypeRegistry == null) { TypeRegistry = new NetworkTypeInfoRegistry(asm.CecilAssembly.MainModule, Settings, Log.Logger, typeRef => CalculateStructWordCount(typeRef)); } } void InjectPtrNullCheck(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property) { if (Settings.NullChecksForNetworkedProperties) { var nop = Instruction.Create(OpCodes.Nop); il.Append(Instruction.Create(OpCodes.Ldarg_0)); var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr)); il.Append(Instruction.Create(OpCodes.Ldfld, ptrGetter)); //var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr)); //il.Append(Instruction.Create(OpCodes.Ldfld, ptrField)); il.Append(Instruction.Create(OpCodes.Ldc_I4_0)); il.Append(Instruction.Create(OpCodes.Conv_U)); il.Append(Instruction.Create(OpCodes.Ceq)); il.Append(Instruction.Create(OpCodes.Brfalse, nop)); var ctor = typeof(InvalidOperationException).GetConstructors().First(x => x.GetParameters().Length == 1); var exnCtor = asm.Import(ctor); il.Append(Instruction.Create(OpCodes.Ldstr, $"Error when accessing {property.DeclaringType.Name}.{property.Name}. Networked properties can only be accessed when Spawned() has been called.")); il.Append(Instruction.Create(OpCodes.Newobj, exnCtor)); il.Append(Instruction.Create(OpCodes.Throw)); il.Append(nop); } } void EmitRead(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action addressGetter) { EmitRead(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, true); } void EmitRead(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, bool emitRet = false, bool throwForNonUnmanagedRefs = false) { // for pointer types we can simply just return the address we loaded on the stack if (type.IsPointer || type.IsByReference) { // load address if (throwForNonUnmanagedRefs == false || TypeRegistry.GetInfo(type.GetElementTypeWithGenerics()).IsTriviallyCopyable) { addressGetter(il); } else { il.Append(Ldstr($"Only supported for trivially copyable types. {type.GetElementTypeWithGenerics()} is not trivially copyable.")); il.Append(Newobj(asm.Import(typeof(NotSupportedException).GetConstructor(new[] { typeof(string) })))); il.Append(Throw()); return; } } else { using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter)) { ctx.LoadElementReaderWriterImpl = (il, type, member) => { EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type)); }; TypeRegistry.EmitRead(type, il, ctx, member); } } if (emitRet) { il.Append(Ret()); } } void EmitWrite(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action addressGetter, OpCode valueOpCode) { EmitWrite(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, valueOpCode, true); } void EmitWrite(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, OpCode valueOpCode, bool emitRet = false) { if (type.IsPointer || type.IsByReference) { throw new ILWeaverException($"Pointer and reference members are read-only"); } using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter, valueGetter: (il) => il.Append(Instruction.Create(valueOpCode)))) { ctx.LoadElementReaderWriterImpl = (il, type, member) => { EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type)); }; TypeRegistry.EmitWrite(type, il, ctx, member); if (emitRet) { il.Append(Ret()); } } } void EmitGetHashCode(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, Action valueGetter, Action valueAddrGetter, bool emitRet = false) { using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: null, valueGetter: valueGetter, valueAddrGetter: valueAddrGetter)) { ctx.LoadElementReaderWriterImpl = (il, type, member) => { EmitElementReaderWriterLoad(il, MakeElementReaderWriter(asm, declaringType, member, type)); }; TypeRegistry.EmitGetHashCode(type, il, ctx, member); if (emitRet) { il.Append(Ret()); } } } void ThrowIfPropertyNotEmptyOrCompilerGenerated(PropertyDefinition property) { Collection instructions; int idx; var getter = property.GetMethod; var setter = property.SetMethod; void ExpectNext(params OpCode[] opCodes) { foreach (var opCode in opCodes) { // skip nops for (; idx < instructions.Count && instructions[idx].OpCode.Equals(OpCodes.Nop); ++idx) { } if (idx >= instructions.Count) { throw new InvalidOperationException($"Expected {opCode}, but run out of instructions"); } else if (!instructions[idx].OpCode.Equals(opCode)) { throw new InvalidOperationException($"Expected {opCode}, got {instructions[idx].OpCode} at {idx}. Full IL: {string.Join(", ", instructions)}"); } ++idx; } } if (getter != null && !getter.TryGetAttribute(out _)) { instructions = getter.Body.Instructions; idx = 0; bool expectLocalVariable = false; var returnType = getter.ReturnType; switch (returnType.MetadataType) { case MetadataType.SByte: case MetadataType.Byte: case MetadataType.Int16: case MetadataType.UInt16: case MetadataType.Int32: case MetadataType.UInt32: case MetadataType.Boolean: case MetadataType.Char: ExpectNext(OpCodes.Ldc_I4_0); break; case MetadataType.Int64: case MetadataType.UInt64: ExpectNext(OpCodes.Ldc_I4_0, OpCodes.Conv_I8); break; case MetadataType.Single: ExpectNext(OpCodes.Ldc_R4); break; case MetadataType.Double: ExpectNext(OpCodes.Ldc_R8); break; case MetadataType.String: case MetadataType.Object: ExpectNext(OpCodes.Ldnull); break; default: expectLocalVariable = true; ExpectNext(OpCodes.Ldloca_S, OpCodes.Initobj, OpCodes.Ldloc_0); break; } if (getter.Body.Variables.Count > (expectLocalVariable ? 1 : 0)) { if (expectLocalVariable) { ExpectNext(OpCodes.Stloc_1, OpCodes.Br_S, OpCodes.Ldloc_1); } else { ExpectNext(OpCodes.Stloc_0, OpCodes.Br_S, OpCodes.Ldloc_0); } } ExpectNext(OpCodes.Ret); } if (setter != null && !setter.TryGetAttribute(out _)) { instructions = setter.Body.Instructions; idx = 0; ExpectNext(OpCodes.Ret); } } (MethodDefinition getter, MethodDefinition setter) PreparePropertyForWeaving(PropertyDefinition property) { var getter = property.GetMethod; var setter = property.SetMethod; // clear getter getter.CustomAttributes.Clear(); getter.Body.Instructions.Clear(); // clear setter if it exists setter?.CustomAttributes?.Clear(); setter?.Body?.Instructions?.Clear(); return (getter, setter); } struct WeavablePropertyMeta { public string DefaultFieldName; public FieldDefinition BackingField; public bool ReatainIL; public string OnChanged; } bool IsWeavableProperty(PropertyDefinition property, out WeavablePropertyMeta meta) { if (property.TryGetAttribute(out var attr) == false) { meta = default; return false; } // check getter ... it has to exist var getter = property.GetMethod; if (getter == null) { meta = default; return false; } // check setter ... var setter = property.SetMethod; if (setter == null) { // if it doesn't exist we allow either array or pointer if (property.PropertyType.IsByReference == false && property.PropertyType.IsPointer == false && !property.PropertyType.IsNetworkCollection()) { throw new ILWeaverException($"Simple properties need a setter."); } } if (getter.IsStatic) { throw new ILWeaverException($"Networked properties can't be static."); } // check for backing field ... if (property.TryGetBackingField(out var backing)) { var il = attr.Properties.FirstOrDefault(x => x.Name == "RetainIL"); if (il.Argument.Value is bool retainIL && retainIL) { meta = new WeavablePropertyMeta() { ReatainIL = true }; return true; } } meta = new WeavablePropertyMeta() { BackingField = backing, ReatainIL = false, }; attr.TryGetAttributeProperty(nameof(NetworkedAttribute.Default), out meta.DefaultFieldName); return true; } void ThrowIfNotRpcCompatible(TypeReference type) { NetworkTypeInfo typeInfo; typeInfo = TypeRegistry.GetInfo(type); if (!typeInfo.CanBeUsedInRpc) { throw new ArgumentException($"Can't be used in RPC"); } } MethodReference GetBaseMethodReference(ILWeaverAssembly asm, MethodDefinition overridingDefinition, TypeReference baseType) { var baseMethod = new MethodReference(overridingDefinition.Name, overridingDefinition.ReturnType, baseType) { HasThis = overridingDefinition.HasThis, ExplicitThis = overridingDefinition.ExplicitThis, CallingConvention = overridingDefinition.CallingConvention, }; foreach (var parameter in overridingDefinition.Parameters) { baseMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); } if (baseMethod.DeclaringType is GenericInstanceType genericTypeRef) { baseMethod = baseMethod.GetCallable(genericTypeRef); } return asm.Import(baseMethod); } string InvokerMethodName(string method, Dictionary nameCache) { nameCache.TryGetValue(method, out var count); nameCache[method] = ++count; return $"{method}@Invoker{(count == 1 ? "" : count.ToString())}"; } bool HasRpcPrefixOrSuffix(MethodDefinition def) { return def.Name.StartsWith("rpc", StringComparison.OrdinalIgnoreCase) || def.Name.EndsWith("rpc", StringComparison.OrdinalIgnoreCase); } void WeaveRpcs(ILWeaverAssembly asm, TypeDefinition type, bool allowInstanceRpcs = true) { // rpc list var rpcs = new List<(MethodDefinition, CustomAttribute)>(); bool hasStaticRpc = false; foreach (var rpc in type.Methods) { if (rpc.TryGetAttribute(out var attr)) { if (HasRpcPrefixOrSuffix(rpc) == false) { throw new ILWeaverException($"{rpc}: name needs to start or end with the \"Rpc\" prefix or suffix"); } if (rpc.IsStatic && rpc.Parameters.FirstOrDefault()?.ParameterType.FullName != asm.NetworkRunner.Reference.FullName) { throw new ILWeaverException($"{rpc}: Static RPC needs {nameof(NetworkRunner)} as the first parameter"); } hasStaticRpc |= rpc.IsStatic; if (!allowInstanceRpcs && !rpc.IsStatic) { throw new ILWeaverException($"{rpc}: Instance RPCs not allowed for this type"); } foreach (var parameter in rpc.Parameters) { if (rpc.IsStatic && parameter == rpc.Parameters[0]) { continue; } if (IsInvokeOnlyParameter(parameter)) { continue; } var parameterType = parameter.ParameterType.IsArray ? parameter.ParameterType.GetElementTypeWithGenerics() : parameter.ParameterType; try { ThrowIfNotRpcCompatible(parameterType); } catch (Exception ex) { throw new ILWeaverException($"{rpc}: parameter {parameter.Name} is not Rpc-compatible.", ex); } } if (!rpc.ReturnType.Is(asm.RpcInvokeInfo) && !rpc.ReturnType.IsVoid()) { throw new ILWeaverException($"{rpc}: RPCs can't return a value."); } rpcs.Add((rpc, attr)); } } if (!rpcs.Any()) { return; } int instanceRpcKeys = GetInstanceRpcCount(type.BaseType); Dictionary invokerNameCounter = new Dictionary(); foreach (var (rpc, attr) in rpcs) { int sources; int targets; if (attr.ConstructorArguments.Count == 2) { sources = attr.GetAttributeArgument(0); targets = attr.GetAttributeArgument(1); } else { sources = AuthorityMasks.ALL; targets = AuthorityMasks.ALL; } ParameterDefinition rpcTargetParameter = rpc.Parameters.SingleOrDefault(x => x.HasAttribute()); if (rpcTargetParameter != null && !rpcTargetParameter.ParameterType.Is()) { throw new ILWeaverException($"{rpcTargetParameter}: {nameof(RpcTargetAttribute)} can only be used for {nameof(PlayerRef)} type argument"); } attr.TryGetAttributeProperty(nameof(RpcAttribute.InvokeLocal), out var invokeLocal, defaultValue: true); attr.TryGetAttributeProperty(nameof(RpcAttribute.Channel), out var channel); attr.TryGetAttributeProperty(nameof(RpcAttribute.TickAligned), out var tickAligned, defaultValue: true); attr.TryGetAttributeProperty(nameof(RpcAttribute.HostMode), out var hostMode); // rpc key int instanceRpcKey = -1; var returnsRpcInvokeInfo = rpc.ReturnType.Is(asm.RpcInvokeInfo); using (var ctx = new RpcMethodContext(asm, rpc, rpc.IsStatic)) { // local variables ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType()); ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int))); var message = new VariableDefinition(asm.SimulationMessage.Reference.MakePointerType()); VariableDefinition localAuthorityMask = null; rpc.Body.Variables.Add(ctx.DataVariable); rpc.Body.Variables.Add(ctx.OffsetVariable); rpc.Body.Variables.Add(message); rpc.Body.InitLocals = true; // get il processes and our jump instruction var il = rpc.Body.GetILProcessor(); var jmp = Nop(); var inv = Nop(); var prepareInv = Nop(); Instruction targetedInvokeLocal = null; // instructions for our branch var ins = new List(); if (returnsRpcInvokeInfo) { // find local variable that's used for return(default); ctx.RpcInvokeInfoVariable = new VariableDefinition(asm.RpcInvokeInfo); rpc.Body.Variables.Add(ctx.RpcInvokeInfoVariable); ins.Add(Ldloca(ctx.RpcInvokeInfoVariable)); ins.Add(Initobj(ctx.RpcInvokeInfoVariable.VariableType)); // fix each ret var returns = il.Body.Instructions.Where(x => x.OpCode == OpCodes.Ret).ToList(); foreach (var retInstruction in returns) { // need to pop the original value and load our new one il.InsertBefore(retInstruction, Pop()); il.InsertBefore(retInstruction, Ldloc(ctx.RpcInvokeInfoVariable)); } } if (rpc.IsStatic) { ins.Add(Ldsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc)))); ins.Add(Brfalse(jmp)); ins.Add(Ldc_I4(0)); ins.Add(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc)))); } else { ins.Add(Ldarg_0()); ins.Add(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); ins.Add(Brfalse(jmp)); ins.Add(Ldarg_0()); ins.Add(Ldc_I4(0)); ins.Add(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); } ins.Add(inv); // insert instruction into method body var prev = rpc.Body.Instructions[0]; //.OpCode == OpCodes.Nop ? rpc.Body.Instructions[1] : rpc.Body.Instructions[0]; for (int i = ins.Count - 1; i >= 0; --i) { il.InsertBefore(prev, ins[i]); prev = ins[i]; } // jump target il.Append(jmp); var returnInstructions = returnsRpcInvokeInfo ? new[] { Ldloc(ctx.RpcInvokeInfoVariable), Ret() } : new[] { Ret() }; var ret = returnInstructions.First(); // check if runner's ok if (rpc.IsStatic) { il.AppendMacro(ctx.LoadRunner()); var checkDone = Nop(); il.Append(Brtrue_S(checkDone)); il.Append(Ldstr(rpc.Parameters[0].Name)); il.Append(Newobj(typeof(ArgumentNullException).GetConstructor(asm, 1))); il.Append(Throw()); il.Append(checkDone); } else { il.Append(Ldarg_0()); il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.ThrowIfBehaviourNotInitialized)))); } il.AppendMacro(ctx.SetRpcInvokeInfoStatus(!invokeLocal, RpcLocalInvokeResult.NotInvokableLocally)); // if we shouldn't invoke during resim { var checkDone = Nop(); il.AppendMacro(ctx.LoadRunner()); il.Append(Call(asm.NetworkRunner.GetGetterOrThrow("Stage"))); il.Append(Ldc_I4((int)SimulationStages.Resimulate)); il.Append(Bne_Un_S(checkDone)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.NotInvokableDuringResim)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotInvokableDuringResim)); il.Append(Br(ret)); il.Append(checkDone); } if (!rpc.IsStatic) { localAuthorityMask = new VariableDefinition(asm.Import(typeof(int))); rpc.Body.Variables.Add(localAuthorityMask); il.Append(Ldarg_0()); il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); il.Append(Call(asm.NetworkedObject.GetMethod(nameof(NetworkObject.GetLocalAuthorityMask)))); il.Append(Stloc(localAuthorityMask)); } // check if target is reachable or not if (rpcTargetParameter != null) { il.AppendMacro(ctx.LoadRunner()); il.Append(Ldarg(rpcTargetParameter)); il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.GetRpcTargetStatus)))); il.Append(Dup()); // check for being unreachable { var done = Nop(); il.Append(Ldc_I4((int)RpcTargetStatus.Unreachable)); il.Append(Bne_Un_S(done)); if (!returnsRpcInvokeInfo) { il.Append(Ldarg(rpcTargetParameter)); il.Append(Ldstr(rpc.ToString())); il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcTargetUnreachable)))); } il.Append(Pop()); // pop the GetRpcTargetStatus il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.TargetPlayerIsNotLocal)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerUnreachable)); il.Append(Br(ret)); il.Append(done); } // check for self { il.Append(Ldc_I4((int)RpcTargetStatus.Self)); if (invokeLocal) { // straight to the invoke; this will prohibit any sending Log.Assert(targetedInvokeLocal == null); targetedInvokeLocal = Nop(); il.Append(Beq(targetedInvokeLocal)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.TargetPlayerIsNotLocal)); } else { // will never get called var checkDone = Nop(); il.Append(Bne_Un_S(checkDone)); if (!returnsRpcInvokeInfo && NetworkRunner.BuildType == NetworkRunner.BuildTypes.Debug) { il.Append(Ldarg(rpcTargetParameter)); il.Append(Ldstr(rpc.ToString())); il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalTargetedRpcCulled)))); } il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerIsLocalButRpcIsNotInvokableLocally)); il.Append(Br(ret)); il.Append(checkDone); } } } // check if sender flags make sense if (!rpc.IsStatic) { var checkDone = Nop(); il.Append(Ldloc(localAuthorityMask)); il.Append(Ldc_I4(sources)); il.Append(And()); il.Append(Brtrue_S(checkDone)); if (!returnsRpcInvokeInfo) { // source is not valid, notify il.Append(Ldstr(rpc.ToString())); il.Append(Ldarg_0()); il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); il.Append(Ldc_I4(sources)); il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalSimulationNotAllowedToSendRpc)))); } il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.InsufficientSourceAuthority)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.InsufficientSourceAuthority)); il.Append(Br(ret)); il.Append(checkDone); if (invokeLocal) { // how about the target? does it match only the local client? if (targets != 0 && (targets & AuthorityMasks.PROXY) == 0) { il.Append(Ldloc(localAuthorityMask)); il.Append(Ldc_I4(targets)); il.Append(And()); il.Append(Ldc_I4(targets)); il.Append(Beq(prepareInv)); } } } var messageSizeVar = ctx.CreateVariable(asm.Import()); { il.Append(Ldc_I4(RpcHeader.SIZE)); il.Append(Stloc(messageSizeVar)); for (int i = 0; i < rpc.Parameters.Count; ++i) { var para = rpc.Parameters[i]; if (rpc.IsStatic && i == 0) { Log.Assert(para.ParameterType.IsSame()); continue; } if (IsInvokeOnlyParameter(para)) { continue; } if (para == rpcTargetParameter) { continue; } il.Append(Ldloc(messageSizeVar)); using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) { if (para.ParameterType.IsArray) { // do nothing EmitRpcArrayByteSize(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics()); } else { TypeRegistry.EmitRpcByteCount(para.ParameterType, il, ctx, para, wordAligned: true); } } il.Append(Add()); il.Append(Stloc(messageSizeVar)); } } // check the size var sizeOk = Nop(); il.Append(Ldloc(messageSizeVar)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.CanAllocateUserPayload)))); il.Append(Brtrue_S(sizeOk)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.PayloadSizeExceeded)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.PayloadSizeExceeded)); if (!returnsRpcInvokeInfo) { il.Append(Ldstr(rpc.ToString())); il.Append(Ldloc(messageSizeVar)); il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcPayloadSizeExceeded)))); } il.Append(Br(ret)); il.Append(sizeOk); // check if sending makes sense at all var afterSend = Nop(); // if not targeted (already handled earlier) check if it can be sent at all if (rpcTargetParameter == null) { var checkDone = Nop(); il.AppendMacro(ctx.LoadRunner()); il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.HasAnyActiveConnections)))); il.Append(Brtrue(checkDone)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NoActiveConnections)); il.Append(Br(afterSend)); il.Append(checkDone); } // create simulation message il.AppendMacro(ctx.LoadRunner()); il.Append(Call(asm.NetworkRunner.GetGetterOrThrow(nameof(NetworkRunner.Simulation)))); il.Append(Ldloc(messageSizeVar)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.Allocate), 2))); il.Append(Stloc(message)); // get data for messages il.Append(Ldloc(message)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.GetData), 1))); il.Append(Stloc(ctx.DataVariable)); // create RpcHeader // il.Append(Ldloc(data)); if (rpc.IsStatic) { il.Append(Ldstr(rpc.ToString())); il.Append(Call(asm.Import(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.GetRpcStaticIndexOrThrow))))); il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 1))); } else { il.Append(Ldarg_0()); il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); il.Append(Call(asm.NetworkedObject.GetGetterOrThrow(nameof(NetworkObject.Id)))); il.Append(Ldarg_0()); il.Append(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.ObjectIndex)))); instanceRpcKey = ++instanceRpcKeys; il.Append(Ldc_I4(instanceRpcKey)); il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 3))); } il.Append(Ldloc(ctx.DataVariable)); il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Write)))); // no need to align this //il.AppendMacro(AlignToWordSize()); //il.Append(Stobj(asm.RpcHeader.Reference)); //il.Append(Ldc_I4(RpcHeader.SIZE)); il.Append(Stloc(ctx.OffsetVariable)); // write parameters for (int i = 0; i < rpc.Parameters.Count; ++i) { var para = rpc.Parameters[i]; if (rpc.IsStatic && i == 0) { continue; } if (IsInvokeOnlyParameter(para)) { continue; } if (para == rpcTargetParameter) { continue; } using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) { if (para.ParameterType.IsArray) { //WeaveRpcArrayInput(asm, ctx, il, para); EmitRpcWriteArray(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics()); } else { TypeRegistry.EmitWrite(para.ParameterType, il, ctx, para); } } } // update message offset il.Append(Ldloc(message)); il.Append(Ldflda(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Offset)))); il.Append(Ldloc(ctx.OffsetVariable)); il.Append(Ldc_I4(8)); il.Append(Mul()); il.Append(Stind_I4()); // send message il.AppendMacro(ctx.LoadRunner()); if (rpcTargetParameter != null) { il.Append(Ldloc(message)); il.Append(Ldarg(rpcTargetParameter)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetTarget)))); } if (channel == RpcChannel.Unreliable) { il.Append(Ldloc(message)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetUnreliable)))); } if (!tickAligned) { il.Append(Ldloc(message)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetNotTickAligned)))); } if (rpc.IsStatic) { il.Append(Ldloc(message)); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetStatic)))); } // send the rpc il.Append(Ldloc(message)); if (ctx.RpcInvokeInfoVariable != null) { il.Append(Ldloca(ctx.RpcInvokeInfoVariable)); il.Append(Ldflda(asm.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendResult)))); il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 2))); } else { il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 1))); } il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotCulled)); il.Append(afterSend); // .. hmm if (invokeLocal) { if (targetedInvokeLocal != null) { il.Append(Br(ret)); il.Append(targetedInvokeLocal); } if (!rpc.IsStatic) { var checkDone = Nop(); il.Append(Ldloc(localAuthorityMask)); il.Append(Ldc_I4(targets)); il.Append(And()); il.Append(Brtrue_S(checkDone)); il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.InsufficientTargetAuthority)); il.Append(Br(ret)); il.Append(checkDone); } il.Append(prepareInv); foreach (var param in rpc.Parameters) { if (param.ParameterType.IsSame()) { // need to fill it now il.AppendMacro(ctx.LoadRunner()); il.Append(Ldc_I4((int)channel)); il.Append(Ldc_I4((int)hostMode)); il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromLocal)))); il.Append(Starg_S(param)); } } il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.Invoked)); // invoke il.Append(Br(inv)); } foreach (var instruction in returnInstructions) { il.Append(instruction); } } var invoker = new MethodDefinition(InvokerMethodName(rpc.Name, invokerNameCounter), MethodAttributes.Family | MethodAttributes.Static, asm.Import(typeof(void))); using (var ctx = new RpcMethodContext(asm, invoker, rpc.IsStatic)) { // create invoker delegate if (rpc.IsStatic) { var runner = new ParameterDefinition("runner", ParameterAttributes.None, asm.NetworkRunner.Reference); invoker.Parameters.Add(runner); } else { var behaviour = new ParameterDefinition("behaviour", ParameterAttributes.None, asm.NetworkedBehaviour.Reference); invoker.Parameters.Add(behaviour); } var message = new ParameterDefinition("message", ParameterAttributes.None, asm.SimulationMessage.Reference.MakePointerType()); invoker.Parameters.Add(message); // add attribute if (rpc.IsStatic) { Log.Assert(instanceRpcKey < 0); invoker.AddAttribute(asm, rpc.ToString()); } else { Log.Assert(instanceRpcKey >= 0); invoker.AddAttribute(asm, instanceRpcKey, sources, targets); } #if UNITY_EDITOR invoker.AddAttribute(asm); #endif // put on type invoker.AddTo(type); // local variables ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType()); ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int))); var parameters = new VariableDefinition[rpc.Parameters.Count]; for (int i = 0; i < parameters.Length; ++i) { invoker.Body.Variables.Add(parameters[i] = new VariableDefinition(rpc.Parameters[i].ParameterType)); } invoker.Body.Variables.Add(ctx.DataVariable); invoker.Body.Variables.Add(ctx.OffsetVariable); invoker.Body.InitLocals = true; var il = invoker.Body.GetILProcessor(); // grab data from message and store in local il.Append(Ldarg_1()); il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.GetData), 1))); il.Append(Stloc(ctx.DataVariable)); il.Append(Ldloc(ctx.DataVariable)); il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.ReadSize)))); il.AppendMacro(ctx.AlignToWordSize()); il.Append(Stloc(ctx.OffsetVariable)); for (int i = 0; i < parameters.Length; ++i) { var para = parameters[i]; if (rpc.IsStatic && i == 0) { il.Append(Ldarg_0()); il.Append(Stloc(para)); continue; } if (rpcTargetParameter == rpc.Parameters[i]) { il.Append(Ldarg_1()); il.Append(Ldfld(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Target)))); il.Append(Stloc(para)); } else if (para.VariableType.IsSame()) { il.AppendMacro(ctx.LoadRunner()); il.Append(Ldarg_1()); il.Append(Ldc_I4((int)hostMode)); il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromMessage)))); il.Append(Stloc(para)); } else if (para.VariableType.IsArray) { EmitRpcReadArray(il, ctx, rpc.Parameters[i], para.VariableType.GetElementTypeWithGenerics(), para); } else { using (ctx.TargetVariableAddr(para)) { TypeRegistry.EmitRead(para.VariableType, il, ctx, rpc.Parameters[i]); if (!ctx.TargetAddrUsed) { il.Append(Stloc(para)); } } } } if (rpc.IsStatic) { il.Append(Ldc_I4(1)); il.Append(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); } else { il.Append(Ldarg_0()); il.Append(Ldc_I4(1)); il.Append(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); } var callableRpc = rpc.GetCallable(); if (!rpc.IsStatic) { il.Append(Ldarg_0()); il.Append(Instruction.Create(OpCodes.Castclass, callableRpc.DeclaringType)); } for (int i = 0; i < parameters.Length; ++i) { il.Append(Ldloc(parameters[i])); } il.Append(Call(callableRpc)); if (returnsRpcInvokeInfo) { il.Append(Pop()); } il.Append(Ret()); } } { Log.Assert(_rpcCount.TryGetValue(type, out int count) == false || count == instanceRpcKeys); _rpcCount[type] = instanceRpcKeys; } } private int GetInstanceRpcCount(TypeReference type) { if (_rpcCount.TryGetValue(type, out int result)) { return result; } result = 0; var typeDef = type.Resolve(); if (typeDef.BaseType != null) { result += GetInstanceRpcCount(typeDef.BaseType); } result += typeDef.GetMethods() .Where(x => !x.IsStatic) .Where(x => x.HasAttribute()) .Count(); _rpcCount.Add(type, result); return result; } private bool IsInvokeOnlyParameter(ParameterDefinition para) { if (para.ParameterType.IsSame()) { return true; } return false; } void EmitRpcWriteArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) { // store array length il.AppendMacro(context.LoadAddress()); il.AppendMacro(context.LoadValue()); il.Append(Ldlen()); il.Append(Conv_I4()); il.Append(Stind_I4()); il.AppendMacro(context.AddOffset(sizeof(int))); if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { il.AppendMacro(context.LoadAddress()); il.AppendMacro(context.LoadValue()); var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyFromArray), 2)); memCpy.GenericArguments.Add(elementType); il.Append(Call(memCpy)); il.AppendMacro(context.AddOffset()); } else { il.AppendMacro( context.For( start: il => il.Append(Ldc_I4(0)), stop: il => { il.AppendMacro(context.LoadValue()); il.Append(Ldlen()); il.Append(Conv_I4()); }, body: (il, i) => { using (context.ValueGetter((il, old) => { old(il); il.Append(Ldloc(i)); il.Append(Ldelem(elementType)); })) { TypeRegistry.EmitWrite(elementType, il, context, member); } } ) ); } } void EmitRpcReadArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType, VariableDefinition arrayVar) { // alloc array il.AppendMacro(context.LoadAddress()); il.Append(Ldind_I4()); il.Append(Instruction.Create(OpCodes.Newarr, elementType)); il.Append(Stloc(arrayVar)); il.AppendMacro(context.AddOffset(sizeof(int))); if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { il.Append(Ldloc(arrayVar)); il.AppendMacro(context.LoadAddress()); var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyToArray), 2)); memCpy.GenericArguments.Add(elementType); il.Append(Call(memCpy)); il.AppendMacro(context.AddOffset()); } else { il.AppendMacro( context.For( start: il => il.Append(Ldc_I4(0)), stop: il => { il.Append(Ldloc(arrayVar)); il.Append(Ldlen()); il.Append(Conv_I4()); }, body: (il, i) => { var placeholder = Nop(); il.Append(placeholder); using (context.TargetVariableAddr(arrayVar, i, elementType)) { TypeRegistry.EmitRead(elementType, il, context, member); if (!context.TargetAddrUsed) { il.InsertAfter(placeholder, Ldloc(i)); il.InsertAfter(placeholder, Ldloc(arrayVar)); il.Append(Stelem(elementType)); } } il.Remove(placeholder); } ) ); } } void EmitRpcArrayByteSize(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) { var elementTypeData = TypeRegistry.GetInfo(elementType); if (elementTypeData.HasDynamicRpcSize) { var totalSize = context.CreateVariable(context.Assembly.Import()); il.Append(Ldc_I4(sizeof(Int32))); il.Append(Stloc(totalSize)); il.AppendMacro( context.For( start: il => il.Append(Ldc_I4(0)), stop: il => { il.AppendMacro(context.LoadValue()); il.Append(Ldlen()); il.Append(Conv_I4()); }, (il, counter) => { il.Append(Ldloc(totalSize)); using (context.ValueGetter((il, old) => { old(il); il.Append(Ldloc(counter)); il.Append(Ldelem(elementType)); })) { TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: true); } il.Append(Add()); il.Append(Stloc(totalSize)); } ) ); il.Append(Ldloc(totalSize)); } else { // array length il.AppendMacro(context.LoadValue()); il.Append(Ldlen()); il.Append(Conv_I4()); TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: false); il.Append(Mul()); // store length as well il.Append(Ldc_I4(sizeof(Int32))); il.Append(Add()); // align il.AppendMacro(context.AlignToWordSize()); } } public void WeaveSimulation(ILWeaverAssembly asm, TypeDefinition type) { EnsureTypeRegistry(asm); WeaveRpcs(asm, type, allowInstanceRpcs: false); WeaveUnityMessages(asm, type); } public static bool IsFieldOperand(Instruction instruction, FieldDefinition field, TypeReference declaringType) { var storeField = instruction.Operand as FieldReference; if (storeField == null) { return false; } if (storeField == field) { return true; } if (storeField.Name == field.Name && storeField.DeclaringType.Is(declaringType)) { return true; } return false; } private Instruction[] GetInlineFieldInit(MethodDefinition constructor, FieldDefinition field, TypeDefinition declaringType) { if (field == null) { throw new ArgumentNullException(nameof(field)); } if (constructor == null) { throw new ArgumentNullException(nameof(constructor)); } var instructions = constructor.Body.Instructions; int ldarg0Index = 0; for (int i = 0; i < instructions.Count; ++i) { var instruction = instructions[i]; if (instruction.OpCode == OpCodes.Ldarg_0) { ldarg0Index = i; } else if (instruction.OpCode == OpCodes.Stfld && IsFieldOperand(instruction, field, declaringType)) { // regular init return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray(); } else if (instruction.OpCode == OpCodes.Initobj && instruction.Previous?.OpCode == OpCodes.Ldflda && IsFieldOperand(instruction.Previous, field, declaringType)) { // init with default constructor return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray(); } else if (instruction.IsBaseConstructorCall(constructor.DeclaringType)) { // base constructor init break; } } return Array.Empty(); } private Instruction[] RemoveInlineFieldInit(TypeDefinition type, FieldDefinition field) { var constructors = type.GetConstructors().Where(x => !x.IsStatic); if (!constructors.Any()) { return Array.Empty(); } var firstConstructor = constructors.First(); var firstInlineInit = GetInlineFieldInit(firstConstructor, field, type).ToArray(); if (firstInlineInit.Length != 0) { Log.Debug($"Found {field} inline init: {(string.Join("; ", firstInlineInit.Cast()))}"); } foreach (var constructor in constructors.Skip(1)) { var otherInlineInit = GetInlineFieldInit(constructor, field, type); if (!firstInlineInit.SequenceEqual(otherInlineInit, new InstructionEqualityComparer())) { throw new ILWeaverException($"Expect inline init of {field} to be the same in all constructors," + $" but there's a difference between {firstConstructor} and {constructor}"); } } foreach (var constructor in constructors) { Log.Debug($"Removing inline init of {field} from {constructor}"); var il = constructor.Body.GetILProcessor(); var otherInlineInit = GetInlineFieldInit(constructor, field, type); foreach (var instruction in otherInlineInit.Reverse()) { Log.Debug($"Removing {instruction}"); il.Remove(instruction); } } return firstInlineInit; } private static bool IsMakeInitializerCall(Instruction instruction) { if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) { if (method.DeclaringType.IsSame() && method.Name == nameof(NetworkBehaviour.MakeInitializer)) { return true; } } return false; } private static bool IsMakeRefOrMakePtrCall(Instruction instruction) { if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) { if (method.DeclaringType.IsSame() && ( method.Name == nameof(NetworkBehaviour.MakeRef) || method.Name == nameof(NetworkBehaviour.MakePtr) )) { return true; } } return false; } private void CheckIfMakeInitializerImplicitCast(Instruction instruction) { if (instruction.OpCode == OpCodes.Call && (instruction.Operand as MethodReference)?.Name == "op_Implicit") { // all good } else { throw new ILWeaverException($"Expected an implicit cast, got {instruction}"); } } private void ReplaceBackingFieldInInlineInit(ILWeaverAssembly asm, FieldDefinition backingField, FieldReference field, ILProcessor il, Instruction[] instructions) { bool nextImplicitCast = false; foreach (var instruction in instructions) { if (nextImplicitCast) { CheckIfMakeInitializerImplicitCast(instruction); nextImplicitCast = false; il.Remove(instruction); } else if (IsFieldOperand(instruction, backingField, field.DeclaringType)) { instruction.Operand = field; } else if (IsMakeInitializerCall(instruction)) { // dictionaries need one extra step, if using SerializableDictionary :( if (Settings.UseSerializableDictionary && backingField.FieldType.IsNetworkDictionary(out var keyType, out var valueType)) { var m = new GenericInstanceMethod(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.MakeSerializableDictionary))); m.GenericArguments.Add(keyType); m.GenericArguments.Add(valueType); Log.Debug($"Inline init for {field}, replacing {instruction.Operand} with {m}"); instruction.Operand = m; } else { // remove the op, it will be fine Log.Debug($"Inline init for {field}, removing {instruction}"); il.Remove(instruction); } nextImplicitCast = true; } } } static IEnumerable AllTypeDefs(TypeDefinition definitions) { yield return definitions; if (definitions.HasNestedTypes) { foreach (var nested in definitions.NestedTypes.SelectMany(AllTypeDefs)) { yield return nested; } } } static IEnumerable AllTypeDefs(Collection definitions) { return definitions.SelectMany(AllTypeDefs); } public bool Weave(ILWeaverAssembly asm) { // if we don't have the weaved assembly attribute, we need to do weaving and insert the attribute if (asm.CecilAssembly.HasAttribute() != false) { return false; } using (Log.ScopeAssembly(asm.CecilAssembly)) { // grab main module .. this contains all the types we need var module = asm.CecilAssembly.MainModule; var moduleAllTypes = AllTypeDefs(module.Types).ToArray(); // go through all types and check for network behaviours foreach (var t in moduleAllTypes) { if (t.IsValueType && t.Is()) { try { WeaveStruct(asm, t); } catch (Exception ex) { throw new ILWeaverException($"Failed to weave struct {t}", ex); } } } foreach (var t in moduleAllTypes) { if (t.IsValueType && t.Is()) { try { WeaveInput(asm, t); } catch (Exception ex) { throw new ILWeaverException($"Failed to weave input {t}", ex); } } } foreach (var t in moduleAllTypes) { if (t.IsSubclassOf()) { try { WeaveBehaviour(asm, t); } catch (Exception ex) { throw new ILWeaverException($"Failed to weave behaviour {t}", ex); } } else if (t.IsSubclassOf()) { try { WeaveSimulation(asm, t); } catch (Exception ex) { throw new ILWeaverException($"Failed to weave behaviour {t}", ex); } } } if (Settings.CheckRpcAttributeUsage) { using (Log.Scope("Checking RpcAttribute usage")) { foreach (var t in moduleAllTypes) { if (t.IsSubclassOf()) { continue; } foreach (var method in t.Methods) { if (method.TryGetAttribute(out _)) { Log.Warn(method, $"Incorrect {nameof(RpcAttribute)} usage on {method}: only types derived from {nameof(SimulationBehaviour)} and {nameof(NetworkBehaviour)} are supported"); } } } } } // only if it was modified if (asm.Modified) { // add weaved assembly attribute to this assembly asm.CecilAssembly.CustomAttributes.Add(new CustomAttribute(typeof(NetworkAssemblyWeavedAttribute).GetConstructor(asm))); } return asm.Modified; } } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaver.INetworkedStruct.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using static Fusion.CodeGen.ILWeaverOpCodes; using FieldAttributes = Mono.Cecil.FieldAttributes; unsafe partial class ILWeaver { const int WordSize = Allocator.REPLICATE_WORD_SIZE; NetworkTypeInfoRegistry TypeRegistry; public int CalculateStructWordCount(TypeReference typeRef) { var type = typeRef.Resolve(); Log.Assert(type.TryGetAttribute(out _) == false); var wordCount = 0; foreach (var property in type.Properties) { if (!IsWeavableProperty(property, out var propertyInfo)) { continue; } try { wordCount += TypeRegistry.GetPropertyWordCount(property); } catch (Exception ex) { throw new ILWeaverException($"Failed to get word count of property {property}", ex); } } // figure out word counts for everything foreach (var field in type.Fields) { // skip statics if (field.IsStatic) { continue; } try { // increase block count wordCount += TypeRegistry.GetTypeWordCount(field.FieldType); } catch (Exception ex) { throw new ILWeaverException($"Failed to get word count of field {field}", ex); } } return wordCount; } public bool WeaveInput(ILWeaverAssembly asm, TypeReference typeRef) { ILWeaverException.DebugThrowIf(!typeRef.Is(), $"Not a {nameof(INetworkInput)}"); if (typeRef.Module != asm.CecilAssembly.MainModule) { throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}"); } var type = typeRef.Resolve(); if (type.TryGetAttribute(out _)) { return false; } EnsureTypeRegistry(asm); using (Log.ScopeInput(type)) { int wordCount = WeaveStructInner(asm, type); // add new attribute type.AddAttribute(asm, wordCount); return true; } } public bool WeaveStruct(ILWeaverAssembly asm, TypeReference typeRef) { ILWeaverException.DebugThrowIf(!typeRef.Is(), $"Not a {nameof(INetworkStruct)}"); if (typeRef.Module != asm.CecilAssembly.MainModule) { throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}"); } var type = typeRef.Resolve(); if (type.TryGetAttribute(out _)) { return false; } EnsureTypeRegistry(asm); using (Log.ScopeStruct(type)) { int wordCount = WeaveStructInner(asm, type); // add new attribute type.AddAttribute(asm, wordCount); return true; } } int WeaveStructInner(ILWeaverAssembly asm, TypeDefinition type) { // flag asm as modified asm.Modified = true; // set as explicit layout type.IsExplicitLayout = true; Log.Assert(type.IsValueType); if (!type.Is() && !type.Is()) { throw new ILWeaverException($"Structs need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)}"); } // clear all backing fields foreach (var property in type.Properties) { if (!IsWeavableProperty(property, out var propertyInfo)) { continue; } try { if (TypeRegistry.GetInfo(property.PropertyType).IsTriviallyCopyable) { Log.Warn(property, $"Networked property {property} should be replaced with a regular field. For structs, " + $"[Networked] attribute should to be applied only on collections, booleans, floats and vectors."); } int fieldIndex = type.Fields.Count; if (propertyInfo.BackingField != null) { if (!propertyInfo.BackingField.FieldType.IsValueType) { Log.Warn(property, $"Networked property {property} has a backing field that is not a value type. To keep unmanaged status," + $" the accessor should follow \"{{ get => default; set {{}} }}\" pattern"); } fieldIndex = type.Fields.IndexOf(propertyInfo.BackingField); if (fieldIndex >= 0) { type.Fields.RemoveAt(fieldIndex); } } if (Settings.CheckNetworkedPropertiesBeingEmpty) { try { ThrowIfPropertyNotEmptyOrCompilerGenerated(property); } catch (Exception ex) { Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}"); } } var propertyWordCount = TypeRegistry.GetPropertyWordCount(property); property.GetMethod?.RemoveAttribute(asm); property.SetMethod?.RemoveAttribute(asm); var getIL = property.GetMethod.Body.GetILProcessor(); getIL.Clear(); getIL.Body.Variables.Clear(); var setIL = property.SetMethod?.Body.GetILProcessor(); if (setIL != null) { setIL.Clear(); setIL.Body.Variables.Clear(); } var backingFieldName = $"_{property.Name}"; var fixedBufferInfo = MakeFixedBuffer(asm, propertyWordCount); var surrogateType = MakeUnitySurrogate(asm, property); var storageField = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fixedBufferInfo); TypeRegistry.GetInfo(property.PropertyType).TryGetCapacity(property, out var capacity); #if UNITY_EDITOR var fixedBufferAttribute = storageField.AddAttribute(asm, property.PropertyType, surrogateType, capacity); // this attribute should always be used first; depending on Unity version this means different order fixedBufferAttribute.Properties.Add(new CustomAttributeNamedArgument(nameof(PropertyAttribute.order), new CustomAttributeArgument(asm.Import(), #if UNITY_2021_1_OR_NEWER -int.MaxValue #else int.MaxValue #endif ))); #endif storageField.InsertTo(type, fieldIndex); // move field attributes, if any if (propertyInfo.BackingField != null) { MoveBackingFieldAttributes(asm, propertyInfo.BackingField, storageField); } MovePropertyAttributesToBackingField(asm, property, storageField); Action addressGetter = il => { var m = new GenericInstanceMethod(asm.Native.GetMethod(nameof(Native.ReferenceToPointer))); m.GenericArguments.Add(storageField.FieldType); il.Append(Ldarg_0()); il.Append(Ldflda(storageField)); il.Append(Call(m)); }; EmitRead(asm, getIL, property, addressGetter); if (setIL != null) { EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1); } } catch (Exception ex) { throw new ILWeaverException($"Failed to weave property {property}", ex); } } // figure out word counts for everything var wordCount = 0; foreach (var field in type.Fields) { // skip statics if (field.IsStatic) { continue; } // set offset field.Offset = wordCount * Allocator.REPLICATE_WORD_SIZE; try { // increase block count wordCount += TypeRegistry.GetTypeWordCount(field.FieldType); } catch (Exception ex) { throw new ILWeaverException($"Failed to get word count of field {field}", ex); } } type.PackingSize = 0; type.ClassSize = wordCount * Allocator.REPLICATE_WORD_SIZE; return wordCount; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaver.NetworkBehaviour.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using static Fusion.CodeGen.ILWeaverOpCodes; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using FieldAttributes = Mono.Cecil.FieldAttributes; using MethodAttributes = Mono.Cecil.MethodAttributes; #if UNITY_EDITOR using UnityEngine; using UnityEngine.Scripting; #endif unsafe partial class ILWeaver { FieldDefinition AddNetworkBehaviourBackingField(PropertyDefinition property) { var fieldType = TypeRegistry.GetInfo(property.PropertyType).GetUnityBackingFieldType(true); var field = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fieldType); return field; } private void MoveBackingFieldAttributes(ILWeaverAssembly asm, FieldDefinition backingField, FieldDefinition storageField) { if (backingField.IsNotSerialized) { storageField.IsNotSerialized = true; } foreach (var attrib in backingField.CustomAttributes) { if (attrib.AttributeType.Is() || attrib.AttributeType.Is()) { continue; } storageField.CustomAttributes.Add(attrib); } } private void VisitPropertyMovableAttributes(PropertyDefinition property, Action onAttribute) { foreach (var attribute in property.CustomAttributes) { if (attribute.AttributeType.IsSame() || attribute.AttributeType.IsSame() || attribute.AttributeType.IsSame()) { continue; } var attribDef = attribute.AttributeType.Resolve(); if (attribDef.TryGetAttribute(out var proxy)) { #if UNITY_EDITOR var attribTypeRef = proxy.GetAttributeArgument(0); var attribTypeDef = attribTypeRef.Resolve(); if (attribTypeDef.TryGetMatchingConstructor(attribute.Constructor.Resolve(), out var constructor)) { onAttribute(constructor, attribute.GetBlob()); } else { Log.Warn(property, $"Failed to find matching constructor of {attribTypeDef} for {attribute.Constructor} (property {property})"); } #endif continue; } if (attribDef.TryGetAttribute(out var usage)) { var targets = usage.GetAttributeArgument(0); if ((targets & AttributeTargets.Field) != AttributeTargets.Field) { Log.Debug($"Attribute {attribute.AttributeType} can't be applied on a field, skipping."); continue; } } onAttribute(attribute.Constructor, attribute.GetBlob()); } } private void MovePropertyAttributesToBackingField(ILWeaverAssembly asm, PropertyDefinition property, FieldDefinition field, bool addSerializeField = true) { bool hasNonSerialized = false; VisitPropertyMovableAttributes(property, (ctor, blob) => { Log.Debug($"Adding {ctor.DeclaringType} to {field}"); field.CustomAttributes.Add(new CustomAttribute(asm.Import(ctor), blob)); if (ctor.DeclaringType.IsSame()) { Log.Debug($"{field} marked as NonSerialized, SerializeField will not be applied"); hasNonSerialized = true; } }); #if UNITY_EDITOR if (addSerializeField) { if (!hasNonSerialized && property.GetMethod.IsPublic) { if (field.IsNotSerialized) { // prohibited } else if (field.HasAttribute()) { // already added } else { field.AddAttribute(asm); } } } #endif } // bool TryGetNetworkBehaviourTGenericArgument(ILWeaverAssembly asm, TypeReference type, out TypeReference genericArgument) { // var outerType = type; // while (!type.IsSame()) { // // var typeDef = type.Resolve(); // if (typeDef.IsSame(asm.NetworkedBehaviourT)) { // var genericInstance = (GenericInstanceType)type; // genericArgument = genericInstance.GenericArguments[0]; // if (genericArgument is GenericParameter gp && gp.TryResolve(outerType, out var resolved)) { // genericArgument = resolved; // } // return true; // } // // type = typeDef.BaseType; // } // // genericArgument = null; // return false; // } public int GetBehaviourWordCount(ILWeaverAssembly asm, TypeReference type) { int wordCount = 0; var outerType = type; while (!type.IsSame()) { var typeDef = type.Resolve(); if (typeDef.TryGetAttribute(out var weavedAttribute)) { var result = weavedAttribute.GetAttributeArgument(0); if (result > 0) { wordCount += result; } // else if (TryGetNetworkBehaviourTGenericArgument(asm, outerType, out var genericArgument)) { // if (genericArgument is GenericParameter) { // Log.Assert(wordCount == 0); // return -1; // } else { // var genericTypeDef = genericArgument.Resolve(); // if (genericTypeDef == null) { // throw new ILWeaverException($"Failed to resolve generic argument {genericArgument} of {outerType}"); // } // // wordCount += TypeRegistry.GetTypeWordCount(genericTypeDef); // } // } break; } foreach (var property in typeDef.Properties) { if (!IsWeavableProperty(property, out var propertyInfo)) { continue; } wordCount += TypeRegistry.GetPropertyWordCount(property); } type = typeDef.BaseType; } return wordCount; } public int WeaveBehaviour(ILWeaverAssembly asm, TypeDefinition type) { if (type.IsSame()) { return 0; } if (!type.IsSubclassOf()) { throw new ILWeaverException($"Not a {nameof(NetworkBehaviour)}"); } if (type.Module != asm.CecilAssembly.MainModule) { throw new ILWeaverException($"Type {type} is not in the main module of assembly {asm.CecilAssembly}"); } if (type.TryGetAttribute(out var weavedAttribute)) { return weavedAttribute.GetAttributeArgument(0); } bool isOpenGenericNB = false; // if (TryGetNetworkBehaviourTGenericArgument(asm, type, out var genericArgument)) { // if (genericArgument is GenericParameter) { // isOpenGenericNB = true; // } // } var baseType = type.BaseType.Resolve(); if (baseType.Module == type.Module) { //Log.Warn($"Weaving base ASAP {baseType} {type}"); WeaveBehaviour(asm, baseType); } EnsureTypeRegistry(asm); // flag as modified asm.Modified = true; using (Log.ScopeBehaviour(type)) { // get block count of parent as starting point for ourselves var wordCount = GetBehaviourWordCount(asm, type.BaseType); // this is the data field which holds this behaviours root pointer //var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr)); var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr)); var setDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyBackingFieldsToState)); var getDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyStateToBackingFields)); FieldDefinition lastAddedFieldWithKnownPosition = null; List fieldsWithUncertainPosition = new List(); foreach (var property in type.Properties) { if (!IsWeavableProperty(property, out var propertyInfo)) { continue; } if (isOpenGenericNB) { throw new ILWeaverException($"Open generic {nameof(NetworkBehaviour)} can't have [Networked] properties."); } if (!string.IsNullOrEmpty(propertyInfo.OnChanged)) { WeaveChangedHandler(asm, property, propertyInfo.OnChanged); } if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) { var elementType = property.PropertyType.GetElementTypeWithGenerics(); if (!TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { throw new ILWeaverException($"{property}: type {elementType} can't be used in pointer/reference properties."); } } try { // try to maintain fields order int backingFieldIndex = type.Fields.Count; if (propertyInfo.BackingField != null) { backingFieldIndex = type.Fields.IndexOf(propertyInfo.BackingField); if (backingFieldIndex >= 0) { type.Fields.RemoveAt(backingFieldIndex); } else { Log.Warn(property, $"Unable to find backing field for {property}"); backingFieldIndex = type.Fields.Count; } } var readOnlyInit = GetReadOnlyPropertyInitializer(property); // prepare getter/setter methods if (readOnlyInit == null && Settings.CheckNetworkedPropertiesBeingEmpty) { try { ThrowIfPropertyNotEmptyOrCompilerGenerated(property); } catch (Exception ex){ Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}"); } } var (getter, setter) = PreparePropertyForWeaving(property); var getterRef = getter.GetCallable(); var setterRef = setter?.GetCallable(); // capture word count in case we re-use the lambda that is created later on ... var wordOffset = wordCount; var getIL = getter.Body.GetILProcessor(); var setIL = setter?.Body?.GetILProcessor(); Action addressGetter = il => { il.Append(Ldarg_0()); //il.Append(Ldfld(ptrField)); il.Append(Ldfld(ptrGetter)); il.Append(Ldc_I4(wordOffset * Allocator.REPLICATE_WORD_SIZE)); il.Append(Add()); }; // emit accessors InjectPtrNullCheck(asm, getIL, property); EmitRead(asm, getIL, property, addressGetter); if (setIL != null) { InjectPtrNullCheck(asm, setIL, property); EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1); } var propertyWordCount = TypeRegistry.GetPropertyWordCount(property); // step up wordcount Log.Assert(wordCount >= 0); wordCount += propertyWordCount; var typeInfo = TypeRegistry.GetInfo(property.PropertyType); // inject attribute to poll weaver data during runtime property.AddAttribute(asm, wordOffset, propertyWordCount); if (property.HasAttribute() || propertyInfo.BackingField?.IsNotSerialized == true) { // so the property is not serialized, so there will be no backing field. Instruction[] fieldInit = null; VariableDefinition[] fieldInitLocalVariables = null; if (readOnlyInit?.Instructions.Length > 0) { fieldInit = readOnlyInit.Value.Instructions; fieldInitLocalVariables = readOnlyInit.Value.Variables; } else if (propertyInfo.BackingField != null) { fieldInit = RemoveInlineFieldInit(type, propertyInfo.BackingField); fieldInitLocalVariables = Array.Empty(); } if (fieldInit?.Any() == true) { var storeIndex = Array.FindIndex(fieldInit, x => IsMakeInitializerCall(x) || x.OpCode == OpCodes.Stfld && IsFieldOperand(x, propertyInfo.BackingField, type)); if (storeIndex >= 0) { Log.Assert(fieldInit[0].OpCode == OpCodes.Ldarg_0); fieldInit = fieldInit.Skip(1).Take(storeIndex - 1).ToArray(); } else { // keep as it is } // create initializer method var initializeMethod = new MethodDefinition($"FusionCodeGen@Initialize@{property.Name}", MethodAttributes.Private, typeInfo.GetUnityBackingFieldType(false)); initializeMethod.AddTo(type); initializeMethod.AddAttribute(asm, property.Name, wordOffset, propertyWordCount); { var (initClone, _) = MonoCecilExtensions.CloneAndFixUp(initializeMethod.Body, fieldInit, fieldInitLocalVariables); var il = initializeMethod.Body.GetILProcessor(); foreach (var instruction in initClone) { il.Append(instruction); } il.Append(Ret()); } { // need to patch defaults with this, but only during the initial set var il = setDefaults.Item1.Body.GetILProcessor(); var postInit = Nop(); il.Append(Ldarg_1()); il.Append(Brfalse(postInit)); typeInfo.EmitUnityInit(property, il, null, il => { ; il.Append(Ldarg_0()); il.Append(Call(initializeMethod)); }); il.Append(postInit); } } } else { FieldReference defaultField = null; { FieldDefinition defaultFieldDef; if (string.IsNullOrEmpty(propertyInfo.DefaultFieldName)) { defaultFieldDef = AddNetworkBehaviourBackingField(property); if (propertyInfo.BackingField != null) { defaultFieldDef.InsertTo(type, backingFieldIndex); MoveBackingFieldAttributes(asm, propertyInfo.BackingField, defaultFieldDef); if (lastAddedFieldWithKnownPosition == null) { // fixup fields that have been added without knowing their index foreach (var f in fieldsWithUncertainPosition) { type.Fields.Remove(f); } var index = type.Fields.IndexOf(defaultFieldDef); fieldsWithUncertainPosition.Reverse(); foreach (var f in fieldsWithUncertainPosition) { f.InsertTo(type, index); } } lastAddedFieldWithKnownPosition = defaultFieldDef; } else { if (lastAddedFieldWithKnownPosition == null) { // not sure where to put this... append defaultFieldDef.AddTo(type); fieldsWithUncertainPosition.Add(defaultFieldDef); } else { // add after the previous field var index = type.Fields.IndexOf(lastAddedFieldWithKnownPosition); Log.Assert(index >= 0); defaultFieldDef.InsertTo(type, index + 1); lastAddedFieldWithKnownPosition = defaultFieldDef; } } MovePropertyAttributesToBackingField(asm, property, defaultFieldDef); } else { defaultFieldDef = property.DeclaringType.GetFieldOrThrow(propertyInfo.DefaultFieldName); } defaultFieldDef.AddAttribute(asm, property.Name, wordOffset, propertyWordCount); defaultFieldDef.AddAttribute(asm, nameof(NetworkBehaviour.IsEditorWritable), true, CompareOperator.Equal, DrawIfMode.ReadOnly); defaultField = defaultFieldDef.GetLoadable(); } // in each constructor, replace inline init, if present foreach (var constructor in type.GetConstructors()) { if (readOnlyInit?.Instructions.Length > 0) { var il = constructor.Body.GetILProcessor(); Instruction before = il.Body.Instructions[0]; { // find where to plug in; after last stfld, but before base constructor call for (int i = 0; i < il.Body.Instructions.Count; ++i) { var instruction = il.Body.Instructions[i]; if (instruction.IsBaseConstructorCall(type)) { break; } else if (instruction.OpCode == OpCodes.Stfld) { before = il.Body.Instructions[i + 1]; } } } // clone variables var (instructions, variables) = CloneInstructions(readOnlyInit.Value.Instructions, readOnlyInit.Value.Variables); foreach (var variable in variables) { il.Body.Variables.Add(variable); } il.InsertBefore(before, Ldarg_0()); foreach (var instruction in instructions) { il.InsertBefore(before, instruction); } il.InsertBefore(before, Stfld(defaultField)); } else if (propertyInfo.BackingField != null) { // remove the inline init, if present var init = GetInlineFieldInit(constructor, propertyInfo.BackingField, type); if (init.Length > 0) { ReplaceBackingFieldInInlineInit(asm, propertyInfo.BackingField, defaultField, constructor.Body.GetILProcessor(), init); } } } typeInfo.EmitUnityInit(property, setDefaults.Item1.Body.GetILProcessor(), defaultField.FieldType, il => { il.Append(Ldarg_0()); il.Append(Ldfld(defaultField)); }); typeInfo.EmitUnityStore(property, getDefaults.Item1.Body.GetILProcessor(), defaultField); } } catch (Exception ex) { throw new ILWeaverException($"Failed to weave property {property}", ex); } } { var (method, instruction) = setDefaults; method.Body.GetILProcessor().Append(instruction); } { var (method, instruction) = getDefaults; method.Body.GetILProcessor().Append(instruction); } if (wordCount != GetBehaviourWordCount(asm, type)) { throw new ILWeaverException($"Failed to weave {type} - word count mismatch {wordCount} vs {GetBehaviourWordCount(asm, type)}"); } // add meta attribute type.AddAttribute(asm, wordCount); WeaveRpcs(asm, type); WeaveUnityMessages(asm, type); return wordCount; } } private void WeaveUnityMessages(ILWeaverAssembly asm, TypeDefinition type) { WeaveUnityMessage("OnDestroy", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDestroy))); WeaveUnityMessage("OnEnable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnEnable))); WeaveUnityMessage("OnDisable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDisable))); void WeaveUnityMessage(string methodName, MethodReference internalMethod) { if (!type.TryGetMethod(methodName, out var method)) { // all good, nothing to do return; } var instructions = method.Body.Instructions; bool hasCleanUp = instructions .Where(x => x.OpCode == OpCodes.Call) .Select(x => (MethodReference)x.Operand) .Any(m => m.Name == nameof(internalMethod.Name) && m.DeclaringType.Is(internalMethod.DeclaringType)); if (hasCleanUp) { Log.Debug($"Type {type} has a custom {methodName} method that calls {internalMethod.Name}, skipping"); return; } asm.Modified = true; Log.Debug($"Type {type} has a custom {methodName} method, weaving it"); instructions.Insert(0, Ldarg_0()); instructions.Insert(1, Call(internalMethod)); } } private void WeaveChangedHandler(ILWeaverAssembly asm, PropertyDefinition property, string handlerName) { // find the handler { foreach (var declaringType in property.DeclaringType.GetHierarchy()) { var candidates = declaringType.GetMethods() .Where(x => x.IsStatic) .Where(x => x.Name == handlerName) .Where(x => x.HasParameters && x.Parameters.Count == 1) .Where(x => x.Parameters[0].ParameterType.IsSubclassOf()) .ToList(); if (candidates.Count > 1) { throw new ILWeaverException($"Ambiguous match for OnChanged handler for {property}: {string.Join("; ", candidates)}"); } if (candidates.Count == 1) { var handler = candidates[0]; Log.Debug($"OnChanged handler for {property}: {handler}"); #if UNITY_EDITOR // add preserve attribute, if not added already if (!handler.TryGetAttribute(out _)) { handler.AddAttribute(asm); Log.Debug($"Added {nameof(PreserveAttribute)} to {handler}"); } #endif return; } } } throw new ILWeaverException($"No match found for OnChanged handler for {property}"); } struct ReadOnlyInitializer { public Instruction[] Instructions; public VariableDefinition[] Variables; } private ReadOnlyInitializer? GetReadOnlyPropertyInitializer(PropertyDefinition property) { if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) { // need to check if there's MakeRef/Ptr before getter gets obliterated var instructions = property.GetMethod.Body.Instructions; for (int i = 0; i < instructions.Count; ++i) { var instr = instructions[i]; if (IsMakeRefOrMakePtrCall(instr)) { // found it! return new ReadOnlyInitializer() { Instructions = instructions.Take(i).ToArray(), Variables = property.GetMethod.Body.Variables.ToArray() }; } } } return null; } private (Instruction[], VariableDefinition[]) CloneInstructions(Instruction[] source, VariableDefinition[] sourceVariables) { var constructor = typeof(Instruction).GetConstructor( System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new[] { typeof(OpCode), typeof(object) }, null); // shallow copy var result = source.Select(x => (Instruction)constructor.Invoke(new[] { x.OpCode, x.Operand })) .ToArray(); var variableMapping = new Dictionary(); // now need to resolve local variables and jump targets foreach (var instruction in result) { if (instruction.IsLdlocWithIndex(out var locIndex) || instruction.IsStlocWithIndex(out locIndex)) { var variable = sourceVariables[locIndex]; if (!variableMapping.TryGetValue(variable, out var replacement)) { replacement = new VariableDefinition(variable.VariableType); variableMapping.Add(variable, replacement); } if (instruction.IsLdlocWithIndex(out _)) { instruction.OpCode = OpCodes.Ldloc; } else { instruction.OpCode = OpCodes.Stloc; } instruction.Operand = replacement; } else if (instruction.Operand is VariableDefinition variable) { if (!variableMapping.TryGetValue(variable, out var replacement)) { replacement = new VariableDefinition(variable.VariableType); variableMapping.Add(variable, replacement); } instruction.Operand = replacement; } else if (instruction.Operand is Instruction target) { var targetIndex = Array.IndexOf(source, target); Log.Assert(targetIndex >= 0); instruction.Operand = result[targetIndex]; } else if (instruction.Operand is Instruction[] targets) { instruction.Operand = targets.Select(x => { var targetIndex = Array.IndexOf(source, x); Log.Assert(targetIndex >= 0); return result[targetIndex]; }); } else if (instruction.Operand is ParameterDefinition) { throw new NotSupportedException(); } } return (result, variableMapping.Values.ToArray()); } private (MethodDefinition method, Instruction returnInstruction) CreateOverride(ILWeaverAssembly asm, TypeDefinition type, string name) { var rootMethod = asm.NetworkedBehaviour.GetMethod(name); var result = type.Methods.FirstOrDefault(x => x.Name == name); if (result != null) { // need to find the placeholder method var placeholderMethodName = asm.NetworkedBehaviour.GetMethod(nameof(NetworkBehaviour.InvokeWeavedCode)).FullName; var placeholders = result.Body.Instructions .Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.FullName == placeholderMethodName) .ToList(); if (placeholders.Count != 1) { throw new ILWeaverException($"When overriding {name} in a type with [Networked] properties, make sure to call {placeholderMethodName} exactly once somewhere."); } var baseCalls = result.Body.Instructions .Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.Name == name) .ToList(); if (baseCalls.Count == 0 && !type.BaseType.IsSame()) { throw new ILWeaverException($"When overriding {name} in a type derived from a type derived from {nameof(NetworkBehaviour)}, invoke the base method."); } foreach (var baseCall in baseCalls) { var baseMethod = (MethodReference)baseCall.Operand; if (!baseMethod.DeclaringType.IsSame(type.BaseType)) { Log.Debug($"Changing base declaring type from {baseMethod.DeclaringType} to {type.BaseType}"); baseCall.Operand = GetBaseMethodReference(asm, result, type.BaseType); } } var placeholder = placeholders[0]; var il = result.Body.GetILProcessor(); var jumpTarget = Nop(); var returnTarget = Nop(); // this is where to jump after weaved code's done il.InsertAfter(placeholder, returnTarget); il.InsertAfter(placeholder, Br(jumpTarget)); il.Append(jumpTarget); return (result, Br(returnTarget)); } result = new MethodDefinition(name, MethodAttributes.Public, rootMethod.ReturnType) { IsVirtual = true, IsHideBySig = true, IsReuseSlot = true }; foreach (var parameter in rootMethod.Parameters) { result.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); } result.AddTo(type); // call base method if it exists, unless it is a NB var baseType = type.BaseType; while (!baseType.IsSame()) { var baseMethod = GetBaseMethodReference(asm, result, baseType); var bodyIL = result.Body.GetILProcessor(); bodyIL.Append(Instruction.Create(OpCodes.Ldarg_0)); foreach (var parameter in result.Parameters) { bodyIL.Append(Ldarg(parameter)); } bodyIL.Append(Call(baseMethod)); break; } return (result, Ret()); } private Lazy MakeLazy(Func func) { return new Lazy(func); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverAssembly.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; public class ILWeaverImportedType { public Type ClrType; public ILWeaverAssembly Assembly; public List BaseDefinitions; public TypeReference Reference; Dictionary _fields = new Dictionary(); Dictionary<(string, int?), MethodReference> _methods = new Dictionary<(string, int?), MethodReference>(); Dictionary _propertiesGet = new Dictionary(); Dictionary _propertiesSet = new Dictionary(); public static implicit operator TypeReference(ILWeaverImportedType type) => type.Reference; public ILWeaverImportedType(ILWeaverAssembly asm, Type type) { ClrType = type; Assembly = asm; Type baseType = type; BaseDefinitions = new List(); // Store the type, and each of its base types - so we can later find fields/properties/methods in the base class. while (baseType != null) { BaseDefinitions.Add(asm.CecilAssembly.MainModule.ImportReference(baseType).Resolve()); baseType = baseType.BaseType; } Reference = asm.CecilAssembly.MainModule.ImportReference(BaseDefinitions[0]); } public FieldReference GetFieldOrThrow(string name) { bool found = _fields.TryGetValue(name, out var fieldRef); if (found == false) { for (int i = 0; i < BaseDefinitions.Count; ++i) { FieldDefinition typeDef = BaseDefinitions[i].Fields.FirstOrDefault(x => x.Name == name); if (typeDef != null) { fieldRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef); _fields.Add(name, fieldRef); return fieldRef; } } throw new Exception($"Field {name} not found on type {ClrType}"); } return fieldRef; } public MethodReference GetGetterOrThrow(string name) { bool found = _propertiesGet.TryGetValue(name, out var methRef); if (found == false) { for (int i = 0; i < BaseDefinitions.Count; ++i) { PropertyDefinition typeDef = BaseDefinitions[i].Properties.FirstOrDefault(x => x.Name == name); if (typeDef != null) { methRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef.GetMethod); _propertiesGet.Add(name, methRef); return methRef; } } throw new Exception($"Property {name} not found on type {ClrType}"); } return methRef; } public MethodReference GetMethod(string name, int? argsCount = null, int? genericArgsCount = null) { if (!TryGetMethod(name, out var methRef, argsCount, genericArgsCount)) { throw new InvalidOperationException($"Not found: {name}"); } return methRef; } public bool TryGetMethod(string name, out MethodReference method, int? argsCount = null, int? genericArgsCount = null) { if (_methods.TryGetValue((name, argsCount), out method)) { return method != null; } foreach (var t in BaseDefinitions) { var typeDef = t.Methods.FirstOrDefault( x => x.Name == name && (argsCount.HasValue == false || x.Parameters.Count == argsCount.Value) && (genericArgsCount == null || x.GenericParameters.Count == genericArgsCount.Value)); if (typeDef != null) { method = Assembly.CecilAssembly.MainModule.ImportReference(typeDef); _methods.Add((name, argsCount), method); return true; } } _methods.Add((name, argsCount), null); return false; } public GenericInstanceMethod GetGenericMethod(string name, int? argsCount = null, params TypeReference[] types) { var method = GetMethod(name, argsCount); var generic = new GenericInstanceMethod(method); foreach (var t in types) { generic.GenericArguments.Add(t); } return generic; } } public class ILWeaverAssembly { public bool Modified; public List Errors = new List(); public AssemblyDefinition CecilAssembly; ILWeaverImportedType _networkRunner; ILWeaverImportedType _readWriteUtils; ILWeaverImportedType _nativeUtils; ILWeaverImportedType _rpcInfo; ILWeaverImportedType _rpcInvokeInfo; ILWeaverImportedType _rpcHeader; ILWeaverImportedType _networkBehaviourUtils; ILWeaverImportedType _simulation; ILWeaverImportedType _networkedObject; ILWeaverImportedType _networkedObjectId; ILWeaverImportedType _networkedBehaviour; ILWeaverImportedType _networkedBehaviourT; ILWeaverImportedType _networkedBehaviourId; ILWeaverImportedType _simulationBehaviour; ILWeaverImportedType _simulationMessage; ILWeaverImportedType _object; ILWeaverImportedType _valueType; ILWeaverImportedType _void; ILWeaverImportedType _int; ILWeaverImportedType _float; Dictionary _types = new Dictionary(); private ILWeaverImportedType MakeImportedType(ref ILWeaverImportedType field) { return MakeImportedType(ref field, typeof(T)); } private ILWeaverImportedType MakeImportedType(ref ILWeaverImportedType field, Type type) { if (field == null) { field = new ILWeaverImportedType(this, type); } return field; } public ILWeaverImportedType WordSizedPrimitive => MakeImportedType(ref _int); public ILWeaverImportedType Void => MakeImportedType(ref _void, typeof(void)); public ILWeaverImportedType Object => MakeImportedType(ref _object); public ILWeaverImportedType ValueType => MakeImportedType(ref _valueType); public ILWeaverImportedType Float => MakeImportedType(ref _float); public ILWeaverImportedType NetworkedObject => MakeImportedType(ref _networkedObject); public ILWeaverImportedType Simulation => MakeImportedType(ref _simulation); public ILWeaverImportedType SimulationMessage => MakeImportedType(ref _simulationMessage); public ILWeaverImportedType NetworkedBehaviour => MakeImportedType(ref _networkedBehaviour); public ILWeaverImportedType SimulationBehaviour => MakeImportedType(ref _simulationBehaviour); public ILWeaverImportedType NetworkId => MakeImportedType(ref _networkedObjectId); public ILWeaverImportedType NetworkedBehaviourId => MakeImportedType(ref _networkedBehaviourId); public ILWeaverImportedType NetworkRunner => MakeImportedType(ref _networkRunner); public ILWeaverImportedType ReadWriteUtils => MakeImportedType(ref _readWriteUtils, typeof(ReadWriteUtilsForWeaver)); public ILWeaverImportedType Native => MakeImportedType(ref _nativeUtils, typeof(Native)); public ILWeaverImportedType NetworkBehaviourUtils => MakeImportedType(ref _networkBehaviourUtils, typeof(NetworkBehaviourUtils)); public ILWeaverImportedType RpcHeader => MakeImportedType(ref _rpcHeader); public ILWeaverImportedType RpcInfo => MakeImportedType(ref _rpcInfo); public ILWeaverImportedType RpcInvokeInfo => MakeImportedType(ref _rpcInvokeInfo); public TypeReference Import(TypeReference type) { return CecilAssembly.MainModule.ImportReference(type); } public MethodReference Import(MethodInfo method) { return CecilAssembly.MainModule.ImportReference(method); } public MethodReference Import(MethodReference method) { return CecilAssembly.MainModule.ImportReference(method); } public MethodReference Import(ConstructorInfo method) { return CecilAssembly.MainModule.ImportReference(method); } public TypeReference Import(Type type) { if (_types.TryGetValue(type, out var reference) == false) { _types.Add(type, reference = CecilAssembly.MainModule.ImportReference(type)); } return reference; } public void Dispose() { CecilAssembly?.Dispose(); Modified = false; Errors.Clear(); CecilAssembly = null; } public TypeReference Import() { return Import(typeof(T)); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.ILPostProcessor.cs #if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System.Collections.Generic; using System.IO; using System.Linq; using Mono.Cecil; internal class ILWeaverAssemblyResolver : IAssemblyResolver { private List _lookInDirectories; private Dictionary _assemblyNameToPath; private Dictionary _resolvedAssemblies = new Dictionary(); private string _compiledAssemblyName; private ILWeaverLog _log; public AssemblyDefinition WeavedAssembly; public ILWeaverAssemblyResolver(ILWeaverLog log, string compiledAssemblyName, string[] references, string[] weavedAssemblies) { _log = log; _compiledAssemblyName = compiledAssemblyName; _assemblyNameToPath = new Dictionary(); foreach (var referencePath in references) { var assemblyName = Path.GetFileNameWithoutExtension(referencePath); if (_assemblyNameToPath.TryGetValue(assemblyName, out var existingPath)) { _log.Warn($"Assembly {assemblyName} (full path: {referencePath}) already referenced by {compiledAssemblyName} at {existingPath}"); } else { _log.Debug($"Adding {assemblyName}->{referencePath}"); _assemblyNameToPath.Add(assemblyName, referencePath); } } _lookInDirectories = references.Select(x => Path.GetDirectoryName(x)).Distinct().ToList(); } public void Dispose() { } public AssemblyDefinition Resolve(AssemblyNameReference name) { return Resolve(name, new ReaderParameters(ReadingMode.Deferred)); } public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { { if (name.Name == _compiledAssemblyName) return WeavedAssembly; var path = GetAssemblyPath(name); if (string.IsNullOrEmpty(path)) return null; if (_resolvedAssemblies.TryGetValue(path, out var result)) return result; parameters.AssemblyResolver = this; var pdb = path + ".pdb"; if (File.Exists(pdb)) { parameters.SymbolStream = CreateAssemblyStream(pdb); } var assemblyDefinition = AssemblyDefinition.ReadAssembly(CreateAssemblyStream(path), parameters); _resolvedAssemblies.Add(path, assemblyDefinition); return assemblyDefinition; } } private string GetAssemblyPath(AssemblyNameReference name) { if (_assemblyNameToPath.TryGetValue(name.Name, out var path)) { return path; } // fallback for second-order references foreach (var parentDir in _lookInDirectories) { var fullPath = Path.Combine(parentDir, name.Name + ".dll"); if (File.Exists(fullPath)) { _assemblyNameToPath.Add(name.Name, fullPath); return fullPath; } } return null; } private static MemoryStream CreateAssemblyStream(string fileName) { var bytes = File.ReadAllBytes(fileName); return new MemoryStream(bytes); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.UnityEditor.cs #if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using Mono.Cecil; using CompilerAssembly = UnityEditor.Compilation.Assembly; class ILWeaverAssemblyResolver : BaseAssemblyResolver { Dictionary _assemblies; Dictionary _assembliesByPath; public IEnumerable Assemblies => _assemblies.Values; public ILWeaverAssemblyResolver() { _assemblies = new Dictionary(StringComparer.Ordinal); _assembliesByPath = new Dictionary(); } public sealed override AssemblyDefinition Resolve(AssemblyNameReference name) { if (_assemblies.TryGetValue(name.FullName, out var asm) == false) { asm = new ILWeaverAssembly(); asm.CecilAssembly = base.Resolve(name, ReaderParameters(false, false)); _assemblies.Add(name.FullName, asm); } return asm.CecilAssembly; } public void Clear() { _assemblies.Clear(); } public bool Contains(CompilerAssembly compilerAssembly) { return _assembliesByPath.ContainsKey(compilerAssembly.outputPath); } public ILWeaverAssembly AddAssembly(string path, bool readWrite = true, bool readSymbols = true) { return AddAssembly(AssemblyDefinition.ReadAssembly(path, ReaderParameters(readWrite, readSymbols)), null); } public ILWeaverAssembly AddAssembly(CompilerAssembly compilerAssembly, bool readWrite = true, bool readSymbols = true) { return AddAssembly(AssemblyDefinition.ReadAssembly(compilerAssembly.outputPath, ReaderParameters(readWrite, readSymbols)), compilerAssembly); } public ILWeaverAssembly AddAssembly(AssemblyDefinition assembly, CompilerAssembly compilerAssembly) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } if (_assemblies.TryGetValue(assembly.Name.FullName, out var asm) == false) { asm = new ILWeaverAssembly(); asm.CecilAssembly = assembly; _assemblies.Add(assembly.Name.FullName, asm); if (compilerAssembly != null) { Assert.Always(_assembliesByPath.ContainsKey(compilerAssembly.outputPath) == false); _assembliesByPath.Add(compilerAssembly.outputPath, asm); } } return asm; } protected override void Dispose(bool disposing) { foreach (var asm in _assemblies.Values) { asm.CecilAssembly?.Dispose(); } _assemblies.Clear(); base.Dispose(disposing); } ReaderParameters ReaderParameters(bool readWrite, bool readSymbols) { ReaderParameters p; p = new ReaderParameters(ReadingMode.Immediate); p.ReadWrite = readWrite; p.ReadSymbols = readSymbols; p.AssemblyResolver = this; return p; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.ILPostProcessor.cs #if FUSION_WEAVER_ILPOSTPROCESSOR namespace Fusion.CodeGen { using System; using Unity.CompilationPipeline.Common.ILPostProcessing; #if FUSION_WEAVER using System.Collections.Generic; using Unity.CompilationPipeline.Common.Diagnostics; #if FUSION_HAS_MONO_CECIL using System.IO; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using System.Reflection; using System.Runtime.Serialization.Json; using System.Xml.Linq; class ILWeaverBindings : ILPostProcessor { const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt"; const string MainAssemblyName = "Assembly-CSharp"; const string OverrideMethodName = nameof(ILWeaverSettings) + ".OverrideNetworkProjectConfigPath"; const string UserFileName = "Fusion.CodeGen.User.cs"; enum ConfigPathSource { User, PathFile, Find } Lazy<(string, ConfigPathSource)> _configPath; Lazy _config; public ILWeaverBindings() { _configPath = new Lazy<(string, ConfigPathSource)>(() => { // try the user-provided path var defaultPath = ILWeaverSettings.DefaultConfigPath; if (!string.IsNullOrEmpty(defaultPath) && File.Exists(defaultPath)) { return (defaultPath, ConfigPathSource.User); } // try the editor-provided path if (File.Exists(ConfigPathCachePath)) { var path = File.ReadAllText(ConfigPathCachePath); if (File.Exists(path)) { return (path, ConfigPathSource.PathFile); } } // last resort: grep string[] paths = Directory.GetFiles("Assets", "*.fusion", SearchOption.AllDirectories); if (paths.Length == 0) { throw new InvalidOperationException($"No {nameof(NetworkProjectConfig)} file found (.fusion extension) in {Path.GetFullPath("Assets")}"); } if (paths.Length > 1) { throw new InvalidOperationException($"Multiple config files found: {string.Join(", ", paths)}"); } return (paths[0], ConfigPathSource.Find); }); _config = new Lazy(() => { string configPath = _configPath.Value.Item1; using (var stream = File.OpenRead(configPath)) { var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, new System.Xml.XmlDictionaryReaderQuotas()); return XDocument.Load(jsonReader); } }); } public override ILPostProcessor GetInstance() { return this; } public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { // try to load the config ILWeaverSettings settings; try { settings = ReadSettings(_config.Value); } catch (Exception ex) { string message; DiagnosticType messageType = DiagnosticType.Error; try { var (configPath, source) = _configPath.Value; message = $"Failed to load config from \"{configPath}\". "; if (source == ConfigPathSource.User) { message += $"This is path comes from the default location ({ILWeaverSettings.DefaultConfigPath}). " + $"Implement {OverrideMethodName} in {UserFileName} to override. "; } else if (source == ConfigPathSource.PathFile) { message += $"The path comes from {ConfigPathCachePath} file that is generated by editor scripts each time compilation starts. " + $"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist. " + $"Implement {OverrideMethodName} in {UserFileName} to override. "; } else if (source == ConfigPathSource.Find) { message += $"The path comes searching Assets directory for *.fusion files. " + $"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist and " + $"{ConfigPathCachePath} was not properly generated by editor scripts. "; } message += $"Details: {ex}"; } catch (Exception configPathEx) { message = $"Failed to locate a valid config. " + $"The weaver first checks the default location - {ILWeaverSettings.DefaultConfigPath} (implement {OverrideMethodName} in {UserFileName} to override). " + $"If the file does not exist, editor-generated {ConfigPathCachePath} is checked (there might be a scenario where weaving is triggered without the editor scripts being compiled yet). " + $"If that fails, the weaver searches for *.fusion files in Assets directory. "; message += $"Details: {configPathEx}"; messageType = DiagnosticType.Warning; } return new ILPostProcessResult(null, new List() { { new DiagnosticMessage() { MessageData = message, DiagnosticType = messageType, } } }); } InMemoryAssembly resultAssembly = null; var logger = new ILWeaverLoggerDiagnosticMessages(); var log = new ILWeaverLog(logger); using (log.Scope($"Process {compiledAssembly.Name}")) { { var (configPath, configSource) = _configPath.Value; log.Debug($"Using config at {configPath} (from {configSource})"); if (compiledAssembly.Name == MainAssemblyName && configSource == ConfigPathSource.Find) { log.Warn( $"The weaver had to use Directory.GetFiles to locate the config {configPath}. " + $"This is potentially slow and might happen if you moved config to a non-standard location and editor scripts did not get the chance to run yet. " + $"If you see this message while running in a batch mode, implement {OverrideMethodName} in {UserFileName}."); } } try { ILWeaverAssembly asm; ILWeaver weaver; using (log.Scope("Resolving")) { asm = CreateWeaverAssembly(settings, log, compiledAssembly); } using (log.Scope("Init")) { weaver = new ILWeaver(settings, log); } weaver.Weave(asm); if (asm.Modified) { var pe = new MemoryStream(); var pdb = new MemoryStream(); var writerParameters = new WriterParameters { SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true }; using (log.Scope("Writing")) { asm.CecilAssembly.Write(pe, writerParameters); resultAssembly = new InMemoryAssembly(pe.ToArray(), pdb.ToArray()); } } } catch (Exception ex) { log.Error($"Exception thrown when weaving {compiledAssembly.Name}"); log.Exception(ex); } } logger.FixNewLinesInMessages(); return new ILPostProcessResult(resultAssembly, logger.Messages); } public override bool WillProcess(ICompiledAssembly compiledAssembly) { string[] assembliesToWeave; try { assembliesToWeave = ReadSettings(_config.Value, full: false).AssembliesToWeave; } catch { // need to go to the next stage for some assembly, main is good enough return compiledAssembly.Name == MainAssemblyName; } if (!ILWeaverSettings.IsAssemblyWeavable(assembliesToWeave, compiledAssembly.Name)) { return false; } if (!ILWeaverSettings.ContainsRequiredReferences(compiledAssembly.References)) { return false; } return true; } static ILWeaverAssembly CreateWeaverAssembly(ILWeaverSettings settings, ILWeaverLog log, ICompiledAssembly compiledAssembly) { var resolver = new ILWeaverAssemblyResolver(log, compiledAssembly.Name, compiledAssembly.References, settings.AssembliesToWeave); var readerParameters = new ReaderParameters { SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()), SymbolReaderProvider = new PortablePdbReaderProvider(), AssemblyResolver = resolver, ReadingMode = ReadingMode.Immediate, ReadWrite = true, ReadSymbols = true, ReflectionImporterProvider = new ReflectionImporterProvider(log) }; var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()); var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream, readerParameters); resolver.WeavedAssembly = assemblyDefinition; return new ILWeaverAssembly() { CecilAssembly = assemblyDefinition, }; } static ILWeaverSettings ReadSettings(XDocument config, bool full = true) { void SetIfExists(ref bool field, string name) { var b = (bool?)config.Root.Element(name); if (b != null) { field = b.Value; } } var result = new ILWeaverSettings(); if (full) { SetIfExists(ref result.NullChecksForNetworkedProperties, nameof(NetworkProjectConfig.NullChecksForNetworkedProperties)); SetIfExists(ref result.UseSerializableDictionary, nameof(NetworkProjectConfig.UseSerializableDictionary)); SetIfExists(ref result.CheckRpcAttributeUsage, nameof(NetworkProjectConfig.CheckRpcAttributeUsage)); SetIfExists(ref result.CheckNetworkedPropertiesBeingEmpty, nameof(NetworkProjectConfig.CheckNetworkedPropertiesBeingEmpty)); } result.AssembliesToWeave = config.Root.Element(nameof(NetworkProjectConfig.AssembliesToWeave))? .Elements() .Select(x => x.Value) .ToArray() ?? Array.Empty(); return result; } class ReflectionImporterProvider : IReflectionImporterProvider { private ILWeaverLog _log; public ReflectionImporterProvider(ILWeaverLog log) { _log = log; } public IReflectionImporter GetReflectionImporter(ModuleDefinition module) { return new ReflectionImporter(_log, module); } } class ReflectionImporter : DefaultReflectionImporter { private ILWeaverLog _log; public ReflectionImporter(ILWeaverLog log, ModuleDefinition module) : base(module) { _log = log; } public override AssemblyNameReference ImportReference(AssemblyName name) { if (name.Name == "System.Private.CoreLib") { // seems weaver is run with .net core, but we need to stick to .net framework var candidates = module.AssemblyReferences .Where(x => x.Name == "mscorlib" || x.Name == "netstandard") .OrderBy(x => x.Name) .ThenByDescending(x => x.Version) .ToList(); // in Unity 2020.1 and .NET 4.x mode when building with IL2CPP apparently both mscrolib and netstandard can be loaded if (candidates.Count > 0) { return candidates[0]; } throw new ILWeaverException("Could not locate mscrolib or netstandard assemblies"); } return base.ImportReference(name); } } } #else class ILWeaverBindings : ILPostProcessor { public override ILPostProcessor GetInstance() { return this; } public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { return new ILPostProcessResult(null, new List() { new DiagnosticMessage() { DiagnosticType = DiagnosticType.Warning, MessageData = "Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed." } }); } public override bool WillProcess(ICompiledAssembly compiledAssembly) { return compiledAssembly.Name == "Assembly-CSharp"; } } #endif #else class ILWeaverBindings : ILPostProcessor { public override ILPostProcessor GetInstance() { return this; } public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { throw new NotImplementedException(); } public override bool WillProcess(ICompiledAssembly compiledAssembly) { return false; } } #endif } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.UnityEditor.cs #if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR namespace Fusion.CodeGen { #if FUSION_HAS_MONO_CECIL using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using Mono.Cecil; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.Compilation; using CompilerAssembly = UnityEditor.Compilation.Assembly; class ILWeaverBindings { public static bool IsEditorAssemblyPath(string path) { return path.Contains("-Editor") || path.Contains(".Editor"); } [UnityEditor.InitializeOnLoadMethod] public static void InitializeOnLoad() { CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } static void OnPlayModeStateChanged(PlayModeStateChange state) { var projectConfig = NetworkProjectConfig.Global; // exit edit mode means play mode is about to start ... if (state == PlayModeStateChange.ExitingEditMode) { foreach (var assembly in CompilationPipeline.GetAssemblies()) { var name = Path.GetFileNameWithoutExtension(assembly.outputPath); if (ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) { OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]); } } } } static void OnCompilationFinished(string path, CompilerMessage[] messages) { #if FUSION_DEV Stopwatch sw = Stopwatch.StartNew(); Log.Debug($"OnCompilationFinished({path})"); #endif // never modify editor assemblies if (IsEditorAssemblyPath(path)) { return; } var projectConfig = NetworkProjectConfig.Global; if (projectConfig != null) { // name of assembly on disk var name = Path.GetFileNameWithoutExtension(path); if (!ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) { return; } } // errors means we should exit if (messages.Any(x => x.type == CompilerMessageType.Error)) { #if FUSION_DEV Log.Error($"Can't execute ILWeaver on {path}, compilation errors exist."); #endif return; } // grab compiler pipe assembly var asm = CompilationPipeline.GetAssemblies().First(x => x.outputPath == path); // needs to reference phoenix runtime if (ILWeaverSettings.ContainsRequiredReferences(asm.allReferences) == false) { return; } // perform weaving try { var settings = new ILWeaverSettings() { AccuracyDefaults = projectConfig.AccuracyDefaults, CheckNetworkedPropertiesBeingEmpty = projectConfig.CheckNetworkedPropertiesBeingEmpty, CheckRpcAttributeUsage = projectConfig.CheckRpcAttributeUsage, NullChecksForNetworkedProperties = projectConfig.NullChecksForNetworkedProperties, UseSerializableDictionary = projectConfig.UseSerializableDictionary, AssembliesToWeave = projectConfig.AssembliesToWeave, }; var weaver = new ILWeaver(settings, new ILWeaverLoggerUnityDebug()); Weave(weaver, asm); } catch (Exception ex) { UnityEngine.Debug.LogError(ex); } #if FUSION_DEV UnityEngine.Debug.Log($"OnCompilationFinished took: {sw.Elapsed}"); #endif } static void Weave(ILWeaver weaver, Assembly compilerAssembly) { using (weaver.Log.Scope("Processing")) { using (var resolver = new ILWeaverAssemblyResolver()) { // if we're already weaving this don't do anything if (resolver.Contains(compilerAssembly)) { return; } // make sure we can load all dlls foreach (string path in compilerAssembly.allReferences) { resolver.AddSearchDirectory(Path.GetDirectoryName(path)); } // make sure we have the runtime dll loaded if (!ILWeaverSettings.ContainsRequiredReferences(compilerAssembly.allReferences)) { throw new InvalidOperationException($"Weaving: Could not find required assembly references"); } ILWeaverAssembly asm; using (weaver.Log.Scope("Resolving")) { asm = resolver.AddAssembly(compilerAssembly); } if (weaver.Weave(asm)) { using (weaver.Log.Scope("Writing")) { // write asm to disk asm.CecilAssembly.Write(new WriterParameters { WriteSymbols = true }); } } } } } } #else class ILWeaverBindings { [UnityEditor.InitializeOnLoadMethod] public static void InitializeOnLoad() { UnityEngine.Debug.LogError("Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed."); } } #endif } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverException.cs namespace Fusion.CodeGen { using System; using System.Diagnostics; public class ILWeaverException : Exception { public ILWeaverException(string error) : base(error) { } public ILWeaverException(string error, Exception innerException) : base(error, innerException) { } [Conditional("UNITY_EDITOR")] public static void DebugThrowIf(bool condition, string message) { if (condition) { throw new ILWeaverException(message); } } } } #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverExtensions.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using BindingFlags = System.Reflection.BindingFlags; public static class ILWeaverExtensions { public static bool IsIntegral(this TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: case MetadataType.SByte: case MetadataType.UInt16: case MetadataType.Int16: case MetadataType.UInt32: case MetadataType.Int32: case MetadataType.UInt64: case MetadataType.Int64: return true; default: return false; } } public static int GetPrimitiveSize(this TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: case MetadataType.SByte: return sizeof(byte); case MetadataType.UInt16: case MetadataType.Int16: return sizeof(short); case MetadataType.UInt32: case MetadataType.Int32: return sizeof(int); case MetadataType.UInt64: case MetadataType.Int64: return sizeof(long); case MetadataType.Single: return sizeof(float); case MetadataType.Double: return sizeof(double); default: throw new ArgumentException($"Unknown primitive type: {type}", nameof(type)); } } public static Type GetPrimitiveType(this TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: return typeof(byte); case MetadataType.SByte: return typeof(sbyte); case MetadataType.UInt16: return typeof(ushort); case MetadataType.Int16: return typeof(short); case MetadataType.UInt32: return typeof(uint); case MetadataType.Int32: return typeof(int); case MetadataType.UInt64: return typeof(ulong); case MetadataType.Int64: return typeof(long); case MetadataType.Single: return typeof(float); case MetadataType.Double: return typeof(double); case MetadataType.Boolean: return typeof(bool); case MetadataType.Char: return typeof(char); default: throw new ArgumentException($"Unknown primitive type: {type}", nameof(type)); } } public static bool IsString(this TypeReference type) { return type.MetadataType == MetadataType.String; } public static bool IsFloat(this TypeReference type) { return type.MetadataType == MetadataType.Single; } public static bool IsBool(this TypeReference type) { return type.MetadataType == MetadataType.Boolean; } public static bool IsVector2(this TypeReference type) { return type.FullName == "UnityEngine.Vector2"; } public static bool IsVector3(this TypeReference type) { return type.FullName == "UnityEngine.Vector3"; } public static bool IsVector4(this TypeReference type) { return type.FullName == "UnityEngine.Vector4"; } public static bool IsQuaternion(this TypeReference type) { return type.FullName == "UnityEngine.Quaternion"; } public static bool IsVoid(this TypeReference type) { return type.MetadataType == MetadataType.Void; } public static TypeReference GetElementTypeWithGenerics(this TypeReference type) { if (type.IsPointer) { return ((Mono.Cecil.PointerType)type).ElementType; } else if (type.IsByReference) { return ((Mono.Cecil.ByReferenceType)type).ElementType; } else if (type.IsArray) { return ((Mono.Cecil.ArrayType)type).ElementType; } else { return type.GetElementType(); } } public static bool IsSubclassOf(this TypeReference type) { return !IsSame(type) && Is(type); } public static bool IsNetworkCollection(this TypeReference type) { return type.IsNetworkArray(out _) || type.IsNetworkList(out _) || type.IsNetworkDictionary(out _, out _); } public static bool IsNetworkList(this TypeReference type, out TypeReference elementType) { if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkLinkedList<>).FullName) { elementType = default; return false; } var git = (GenericInstanceType)type; elementType = git.GenericArguments[0]; return true; } public static bool IsNetworkArray(this TypeReference type, out TypeReference elementType) { if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkArray<>).FullName) { elementType = default; return false; } var git = (GenericInstanceType)type; elementType = git.GenericArguments[0]; return true; } public static bool IsNetworkDictionary(this TypeReference type, out TypeReference keyType, out TypeReference valueType) { if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkDictionary<,>).FullName) { keyType = default; valueType = default; return false; } var git = (GenericInstanceType)type; keyType = git.GenericArguments[0]; valueType = git.GenericArguments[1]; return true; } public static bool Is(this TypeReference type) { return Is(type, typeof(T)); } public static bool Is(this TypeReference type, Type t) { if (IsSame(type, t)) { return true; } var resolvedType = type.Resolve(); if (resolvedType == null) { throw new InvalidOperationException($"Failed to resolve {type}"); } if (t.IsInterface) { foreach (var interf in resolvedType.Interfaces) { if (interf.InterfaceType.IsSame(t)) { return true; } } return false; } else { if (resolvedType.BaseType == null) { return false; } return Is(resolvedType.BaseType, t); } } public static bool Is(this TypeReference type, TypeReference t) { if (IsSame(type, t)) { return true; } var resolvedType = type.Resolve(); if (IsSame(resolvedType, t)) { return true; } if (t is GenericParameter genericParameter) { foreach (var constraint in genericParameter.Constraints) { #if FUSION_CECIL_1_11_OR_NEWER if (!Is(type, constraint.ConstraintType)) { #else if (!Is(type, constraint)) { #endif return false; } } return true; } if (t.Resolve().IsInterface == true) { if (resolvedType == null) { return false; } foreach (var interf in resolvedType.Interfaces) { if (interf.InterfaceType.IsSame(t)) { return true; } } return false; } else { if (resolvedType.BaseType == null) { return false; } return Is(resolvedType.BaseType, t); } } public static bool IsSame(this TypeReference type) { return IsSame(type, typeof(T)); } public static bool IsSame(this TypeReference type, Type t) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (type.IsByReference) { type = type.GetElementTypeWithGenerics(); } if (type.IsVoid() && t == typeof(void)) { return true; } if (type.IsValueType != t.IsValueType) { return false; } if (type.IsNested != t.IsNested) { return false; } if (t.IsNested) { if (type.Name != t.Name || !IsSame(type.DeclaringType, t.DeclaringType)) { return false; } } else { if (type.FullName != t.FullName) { return false; } } return true; } public static bool IsSame(this TypeReference type, TypeOrTypeRef t) { if (t.Type != null) { return IsSame(type, t.Type); } else if (t.TypeReference != null) { return IsSame(type, t.TypeReference); } else { throw new InvalidOperationException(); } } public static bool IsSame(this TypeReference type, TypeReference t) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (type.IsByReference) { type = type.GetElementTypeWithGenerics(); } if (type.IsValueType != t.IsValueType) { return false; } if (type.FullName != t.FullName) { return false; } return true; } public static IEnumerable GetHierarchy(this TypeDefinition type, TypeReference stopAtBaseType = null) { if (stopAtBaseType?.IsSame(type) == true) { yield break; } for (; ; ) { yield return type; if (type.BaseType == null || stopAtBaseType?.IsSame(type.BaseType) == true) { break; } type = type.BaseType.Resolve(); } } public static bool Remove(this FieldDefinition field) { return field.DeclaringType.Fields.Remove(field); } public static FieldDefinition GetFieldOrThrow(this TypeDefinition type, string fieldName) { foreach (var field in type.Fields) { if ( field.Name == fieldName ) { return field; } } throw new ArgumentOutOfRangeException(nameof(fieldName), $"Field {fieldName} not found in {type}"); } public static MethodReference GetGenericInstanceMethodOrThrow(this GenericInstanceType type, string name) { var methodRef = type.Resolve().GetMethodOrThrow(name); var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType) { HasThis = methodRef.HasThis, ExplicitThis = methodRef.ExplicitThis, DeclaringType = type, CallingConvention = methodRef.CallingConvention, }; foreach (var parameter in methodRef.Parameters) { newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType)); } foreach (var genericParameter in methodRef.GenericParameters) { newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef)); } return newMethodRef; } public static MethodReference GetCallable(this MethodReference methodRef, GenericInstanceType declaringType = null) { if (declaringType == null) { if (!methodRef.DeclaringType.HasGenericParameters) { return methodRef; } declaringType = new GenericInstanceType(methodRef.DeclaringType); foreach (var parameter in methodRef.DeclaringType.GenericParameters) { declaringType.GenericArguments.Add(parameter); } } var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType, declaringType) { HasThis = methodRef.HasThis, ExplicitThis = methodRef.ExplicitThis, CallingConvention = methodRef.CallingConvention }; foreach (var parameter in methodRef.Parameters) { newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType)); } foreach (var genericParameter in methodRef.GenericParameters) { newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef)); } return newMethodRef; } public static FieldReference GetLoadable(this FieldDefinition field) { if (!field.DeclaringType.HasGenericParameters) { return field; } var declaringType = new GenericInstanceType(field.DeclaringType); foreach (var parameter in field.DeclaringType.GenericParameters) { declaringType.GenericArguments.Add(parameter); } return new FieldReference(field.Name, field.FieldType, declaringType); } public static void AddInterface(this TypeDefinition type, ILWeaverAssembly asm) { type.Interfaces.Add(new InterfaceImplementation(asm.Import(typeof(T)))); } public static bool RemoveAttribute(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute { for (int i = 0; i < member.CustomAttributes.Count; ++i) { var attr = member.CustomAttributes[i]; if ( attr.AttributeType.Is() ) { member.CustomAttributes.RemoveAt(i); return true; } } return false; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute { CustomAttribute attr; attr = new CustomAttribute(typeof(T).GetConstructor(asm)); member.CustomAttributes.Add(attr); return attr; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module = null) where T : Attribute { CustomAttribute attr; attr = new CustomAttribute(typeof(T).GetConstructor(module ?? member.DeclaringType.Module)); member.CustomAttributes.Add(attr); return attr; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0) where T : Attribute { CustomAttribute attr; attr = new CustomAttribute(typeof(T).GetConstructor(asm, 1)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); member.CustomAttributes.Add(attr); return attr; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1) where T : Attribute { CustomAttribute attr; attr = new CustomAttribute(typeof(T).GetConstructor(asm, 2)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); member.CustomAttributes.Add(attr); return attr; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2) where T : Attribute { CustomAttribute attr; attr = new CustomAttribute(typeof(T).GetConstructor(asm, 3)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg2)); member.CustomAttributes.Add(attr); return attr; } public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2, A3 arg3) where T : Attribute { CustomAttribute attr; // TODO: this is inconsistent with other AddAttribute, but needed for DrawIfAttribute to work var constructor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(A0), typeof(A1), typeof(A2), typeof(A3) }, null); attr = new CustomAttribute(asm.CecilAssembly.MainModule.ImportReference(constructor)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg2)); attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg3)); member.CustomAttributes.Add(attr); return attr; } private static TypeReference ImportAttributeType(this ILWeaverAssembly asm) { if (typeof(T) == typeof(TypeReference)) { return asm.Import(); } else { return asm.Import(); } } public static MethodReference GetConstructor(this Type type, ILWeaverAssembly asm, int argCount = 0) { foreach (var ctor in type.GetConstructors()) { if (ctor.GetParameters().Length == argCount) { return asm.CecilAssembly.MainModule.ImportReference(ctor); } } throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}"); } public static MethodReference GetConstructor(this Type type, ModuleDefinition module, int argCount = 0) { foreach (var ctor in type.GetConstructors()) { if (ctor.GetParameters().Length == argCount) { return module.ImportReference(ctor); } } throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}"); } public static void AddTo(this MethodDefinition method, TypeDefinition type) { type.Methods.Add(method); method.AddAttribute(); } public static void AddTo(this PropertyDefinition property, TypeDefinition type) { type.Properties.Add(property); property.AddAttribute(); } public static FieldDefinition AddTo(this FieldDefinition field, TypeDefinition type) { type.Fields.Add(field); field.AddAttribute(); return field; } public static void InsertTo(this FieldDefinition field, TypeDefinition type, int index) { type.Fields.Insert(index, field); field.AddAttribute(); } public static void AddTo(this TypeDefinition type, AssemblyDefinition assembly) { assembly.MainModule.Types.Add(type); type.AddAttribute(assembly.MainModule); } public static void AddTo(this TypeDefinition type, TypeDefinition parentType) { parentType.NestedTypes.Add(type); type.AddAttribute(); } public static Instruction AppendReturn(this ILProcessor il, Instruction instruction) { il.Append(instruction); return instruction; } public static void Clear(this ILProcessor il) { var instructions = il.Body.Instructions; foreach (var instruction in instructions.Reverse()) { il.Remove(instruction); } } public static void AppendMacro(this ILProcessor il, in T macro) where T : struct, ILProcessorMacro { macro.Emit(il); } public static bool GetSingleOrDefaultMethodWithAttribute(this TypeDefinition type, out CustomAttribute attribute, out MethodDefinition method) where T : Attribute { MethodDefinition resultMethod = null; CustomAttribute resultAttribute = null; foreach (var m in type.Methods) { if (m.TryGetAttribute(out var attr)) { if (resultMethod != null) { throw new ILWeaverException($"Only one method with attribute {typeof(T)} allowed per class: {type}"); } else { resultMethod = m; resultAttribute = attr; } } } method = resultMethod; attribute = resultAttribute; return method != null; } public static PropertyDefinition ThrowIfStatic(this PropertyDefinition property) { if (property.GetMethod?.IsStatic == true || property.SetMethod?.IsStatic == true) { throw new ILWeaverException($"Property is static: {property.FullName}"); } return property; } public static PropertyDefinition ThrowIfNoGetter(this PropertyDefinition property) { if (property.GetMethod == null) { throw new ILWeaverException($"Property does not have a getter: {property.FullName}"); } return property; } public static PropertyDefinition ThrowIfNoSetter(this PropertyDefinition property) { if (property.GetMethod == null) { throw new ILWeaverException($"Property does not have a getter: {property.FullName}"); } return property; } public static MethodDefinition ThrowIfStatic(this MethodDefinition method) { if (method.IsStatic) { throw new ILWeaverException($"Method is static: {method.FullName}"); } return method; } public static MethodDefinition ThrowIfNotStatic(this MethodDefinition method) { if (!method.IsStatic) { throw new ILWeaverException($"Method is not static: {method}"); } return method; } public static MethodDefinition ThrowIfNotPublic(this MethodDefinition method) { if (!method.IsPublic) { throw new ILWeaverException($"Method is not public: {method}"); } return method; } public static MethodDefinition ThrowIfReturnType(this MethodDefinition method, TypeOrTypeRef type) { if (!method.ReturnType.IsSame(type)) { throw new ILWeaverException($"Method has an invalid return type (expected {type}): {method}"); } return method; } public static MethodDefinition ThrowIfParameterCount(this MethodDefinition method, int count) { if (method.Parameters.Count != count) { throw new ILWeaverException($"Method has invalid parameter count (expected {count}): {method}"); } return method; } public static MethodDefinition ThrowIfParameterCountLessThan(this MethodDefinition method, int count) { if (method.Parameters.Count < count) { throw new ILWeaverException($"Method has invalid parameter count (expected at leaset {count}): {method}"); } return method; } public static MethodDefinition ThrowIfParameter(this MethodDefinition method, int index, TypeOrTypeRef type = null, bool isByReference = false, bool ignore = false) { if (ignore) { return method; } var p = method.Parameters[index]; if (type != null && !p.ParameterType.IsSame(type)) { throw new ILWeaverException($"Parameter {p} ({index}) has an invalid type (expected {type}): {method}"); } if (p.ParameterType.IsByReference != isByReference) { if (p.IsOut) { throw new ILWeaverException($"Parameter {p} ({index}) is a ref parameter: {method}"); } else { throw new ILWeaverException($"Parameter {p} ({index}) is not a ref parameter: {method}"); } } return method; } public static bool IsBaseConstructorCall(this Instruction instruction, TypeDefinition type) { if (instruction.OpCode == OpCodes.Call) { var m = ((MethodReference)instruction.Operand).Resolve(); if (m.IsConstructor && m.DeclaringType.IsSame(type.BaseType)) { // base constructor init return true; } } return false; } public static bool IsLdloca(this Instruction instruction, out VariableDefinition variable, out bool isShort) { if (instruction.OpCode == OpCodes.Ldloca) { variable = (VariableDefinition)instruction.Operand; isShort = false; return true; } if (instruction.OpCode == OpCodes.Ldloca_S) { variable = (VariableDefinition)instruction.Operand; isShort = true; return true; } variable = default; isShort = default; return false; } public static bool IsLdlocWithIndex(this Instruction instruction, out int index) { if (instruction.OpCode == OpCodes.Ldloc_0) { index = 0; return true; } if (instruction.OpCode == OpCodes.Ldloc_1) { index = 1; return true; } if (instruction.OpCode == OpCodes.Ldloc_2) { index = 2; return true; } if (instruction.OpCode == OpCodes.Ldloc_3) { index = 3; return true; } index = -1; return false; } public static bool IsStlocWithIndex(this Instruction instruction, out int index) { if (instruction.OpCode == OpCodes.Stloc_0) { index = 0; return true; } if (instruction.OpCode == OpCodes.Stloc_1) { index = 1; return true; } if (instruction.OpCode == OpCodes.Stloc_2) { index = 2; return true; } if (instruction.OpCode == OpCodes.Stloc_3) { index = 3; return true; } index = -1; return false; } } public class TypeOrTypeRef { public Type Type { get; } public TypeReference TypeReference { get; } public TypeOrTypeRef(Type type, bool isOut = false) { Type = type; } public TypeOrTypeRef(TypeReference type, bool isOut = false) { TypeReference = type; } public static implicit operator TypeOrTypeRef(Type type) { return new TypeOrTypeRef(type); } public static implicit operator TypeOrTypeRef(TypeReference type) { return new TypeOrTypeRef(type); } public override string ToString() { if (Type != null) { return Type.FullName; } else if (TypeReference != null) { return TypeReference.ToString(); } else { return "AnyType"; } } } public interface ILProcessorMacro { void Emit(ILProcessor il); } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverLog.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Mono.Cecil; public interface ILWeaverLogger { void Log(LogType logType, string message, string filePath, int lineNumber); void Log(Exception ex); } public sealed class ILWeaverLog { private ILWeaverLogger _logger; public ILWeaverLogger Logger => _logger; public ILWeaverLog(ILWeaverLogger logger) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } _logger = logger; } public void AssertMessage(bool condition, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { if (!condition) { _logger.Log(LogType.Error, $"Assert failed: {message}", filePath, lineNumber); throw new AssertException($"{message}{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}"); } } public void Assert(bool condition, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { if (!condition) { _logger.Log(LogType.Error, $"Assert failed", filePath, lineNumber); throw new AssertException($"Assert failed{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}"); } } [Conditional("FUSION_WEAVER_DEBUG")] public void Debug(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { _logger.Log(LogType.Debug, message, filePath, lineNumber); } public void Warn(MethodDefinition method, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { TryOverrideLocation(method, ref filePath, ref lineNumber); _logger.Log(LogType.Warn, message, filePath, lineNumber); } public void Warn(PropertyDefinition property, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { TryOverrideLocation(property.GetMethod, ref filePath, ref lineNumber); _logger.Log(LogType.Warn, message, filePath, lineNumber); } public void Warn(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { _logger.Log(LogType.Warn, message, filePath, lineNumber); } public void Error(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { _logger.Log(LogType.Error, message, filePath, lineNumber); } public void Exception(Exception ex) { _logger.Log(ex); } #if !FUSION_WEAVER_DEBUG public struct LogScope : IDisposable { public void Dispose() { } } public LogScope Scope(string name) { return default; } public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly) { return default; } public LogScope ScopeBehaviour(TypeDefinition type) { return default; } public LogScope ScopeInput(TypeDefinition type) { return default; } public LogScope ScopeStruct(TypeDefinition type) { return default; } #else public LogScope Scope(string name, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { return new LogScope(this, name, filePath, lineNumber); } public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { return new LogScope(this, $"Assembly: {cecilAssembly.FullName}", filePath, lineNumber); } public LogScope ScopeBehaviour(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { return new LogScope(this, $"Behaviour: {type.FullName}", filePath, lineNumber); } public LogScope ScopeInput(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { return new LogScope(this, $"Input: {type.FullName}", filePath, lineNumber); } public LogScope ScopeStruct(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { return new LogScope(this, $"Struct: {type.FullName}", filePath, lineNumber); } #pragma warning disable CS0282 // There is no defined ordering between fields in multiple declarations of partial struct public partial struct LogScope : IDisposable { #pragma warning restore CS0282 // There is no defined ordering between fields in multiple declarations of partial struct public string Message; public TimeSpan Elapsed => _stopwatch.Elapsed; private ILWeaverLog _log; private Stopwatch _stopwatch; public int LineNumber; public string FilePath; public LogScope(ILWeaverLog log, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) : this() { _log = log; _stopwatch = Stopwatch.StartNew(); Message = message; LineNumber = lineNumber; FilePath = filePath; _log.Debug($"{Message} start", FilePath, LineNumber); } public void Dispose() { _stopwatch.Stop(); _log.Debug($"{Message} end {Elapsed}", FilePath, LineNumber); } } #endif private static bool TryOverrideLocation(MethodDefinition method, ref string filePath, ref int lineNumber) { var debugInformation = method?.DebugInformation; if (debugInformation?.HasSequencePoints == true) { var sequencePoint = debugInformation.SequencePoints[0]; filePath = sequencePoint.Document.Url; lineNumber = sequencePoint.StartLine; return true; } return false; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerDiagnosticMessages.cs #if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using Unity.CompilationPipeline.Common.Diagnostics; class ILWeaverLoggerDiagnosticMessages : ILWeaverLogger { public List Messages { get; } = new List(); public void Log(LogType logType, string message, string filePath, int lineNumber) { DiagnosticType diagnosticType; if (logType == LogType.Debug) { // there are no debug diagnostic messages, make pretend warnings message = $"DEBUG: {message}"; diagnosticType = DiagnosticType.Warning; } else if (logType == LogType.Info) { message = $"INFO: {message}"; diagnosticType = DiagnosticType.Warning; } else if (logType == LogType.Trace) { message = $"TRACE: {message}"; diagnosticType = DiagnosticType.Warning; } else if (logType == LogType.Warn) { diagnosticType = DiagnosticType.Warning; } else { diagnosticType = DiagnosticType.Error; } // newlines in messagedata will need to be escaped, but let's not slow things down now Messages.Add(new DiagnosticMessage() { File = filePath, Line = lineNumber, DiagnosticType = diagnosticType, MessageData = message }); } public void Log(Exception ex) { var lines = ex.ToString().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { Log(LogType.Error, line, null, 0); } } public void FixNewLinesInMessages() { // fix the messages foreach (var msg in Messages) { msg.MessageData = msg.MessageData.Replace('\r', ';').Replace('\n', ';'); } } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerUnityDebug.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; public class ILWeaverLoggerUnityDebug : ILWeaverLogger { public void Log(LogType logType, string message, string filePath, int lineNumber) { switch (logType) { case LogType.Debug: case LogType.Trace: case LogType.Info: UnityEngine.Debug.Log(message); break; case LogType.Warn: UnityEngine.Debug.LogWarning(message); break; case LogType.Error: UnityEngine.Debug.LogError(message); break; } } public void Log(Exception ex) { UnityEngine.Debug.unityLogger.LogException(ex); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverMethodContext.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using static Fusion.CodeGen.ILWeaverOpCodes; class MethodContext : IDisposable { public Action LoadElementReaderWriterImpl; private Action _addressGetter; private Dictionary<(string, string), VariableDefinition> _fields = new Dictionary<(string, string), VariableDefinition>(); private bool _runnerIsLdarg0 = false; protected Action _valueGetter; protected Action _valueAddrGetter; private TargetVariableAddrInfo _targetVariable; public MethodContext(ILWeaverAssembly assembly, MethodDefinition method, bool staticRunnerAccessor = false, Action addressGetter = null, Action valueGetter = null, Action valueAddrGetter = null) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } if (method == null) { throw new ArgumentNullException(nameof(method)); } this.Assembly = assembly; this.Method = method; this._runnerIsLdarg0 = staticRunnerAccessor; this._addressGetter = addressGetter; this._valueGetter = valueGetter; this._valueAddrGetter = valueAddrGetter; } public ILWeaverAssembly Assembly { get; private set; } public virtual bool HasOffset => false; public virtual bool IsWriteCompact => false; public MethodDefinition Method { get; private set; } public VariableDefinition AddVariable(TypeReference variableType) { var variable = new VariableDefinition(variableType); Method.Body.Variables.Add(variable); return variable; } public void Dispose() { } public AddOffsetMacro AddOffset() => new AddOffsetMacro(this, null); public AddOffsetMacro AddOffset(int value) => new AddOffsetMacro(this, Ldc_I4(value), isAligned: (value % Allocator.REPLICATE_WORD_SIZE) == 0); public ILMacroStruct AlignToWordSize() => new[] { Ldc_I4(Allocator.REPLICATE_WORD_SIZE - 1), Add(), Ldc_I4(~(Allocator.REPLICATE_WORD_SIZE - 1)), And() }; public ForLoopMacro For(Action start, Action stop, Action body) => new ForLoopMacro(this, body, start, stop); public VariableDefinition GetOrCreateVariable(string id, TypeReference type, ILProcessor il = null) { if (_fields.TryGetValue((id, type.FullName), out var val)) { return val; } var result = CreateVariable(type, il); _fields.Add((id, type.FullName), result); return result; } public VariableDefinition CreateVariable(TypeReference type, ILProcessor il = null, Instruction before = null) { var result = new VariableDefinition(type); Method.Body.Variables.Add(result); if (il != null) { if (before == null) { if (type.IsValueType) { il.Append(Ldloca(result)); il.Append(Initobj(type)); } else { il.Append(Ldnull()); il.Append(Stloc(result)); } } else { if (type.IsValueType) { il.InsertBefore(before, Ldloca(result)); il.InsertBefore(before, Initobj(type)); } else { il.InsertBefore(before, Ldnull()); il.InsertBefore(before, Stloc(result)); } } } return result; } public virtual ILMacroStruct LoadAddress() => _addressGetter; public virtual ILMacroStruct LoadElementReaderWriter(TypeReference type, ICustomAttributeProvider member) => new Action(il => LoadElementReaderWriterImpl(il, type, member)); public ILMacroStruct LoadFixedBufferAddress(FieldDefinition fixedBufferField) => new Action(il => { var elementField = fixedBufferField.FieldType.Resolve().Fields[0]; int pointerLoc = il.Body.Variables.Count; il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakePointerType())); int pinnedRefLoc = il.Body.Variables.Count; il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakeByReferenceType().MakePinnedType())); il.Append(Ldflda(fixedBufferField)); il.Append(Ldflda(elementField)); il.Append(Stloc(il.Body, pinnedRefLoc)); il.Append(Ldloc(il.Body, pinnedRefLoc)); il.Append(Conv_U()); il.Append(Stloc(il.Body, pointerLoc)); il.Append(Ldloc(il.Body, pointerLoc)); }); public ILMacroStruct LoadRunner() { return _runnerIsLdarg0 ? new[] { Ldarg_0() } : new[] { Ldarg_0(), Call(Assembly.SimulationBehaviour.GetGetterOrThrow(nameof(SimulationBehaviour.Runner))) }; } public virtual ILMacroStruct LoadValue() => _valueGetter; public virtual ILMacroStruct LoadValueAddr() => _valueAddrGetter; public bool HasValueGetter => _valueGetter != null; public bool HasValueAddrGetter => _valueAddrGetter != null; public ValueGetterScope ValueGetter(Action valueGetter) => new ValueGetterScope(this, valueGetter); public ValueGetterScope ValueGetter(Action> valueGetter) { var current = _valueGetter; return new ValueGetterScope(this, il => valueGetter(il, current)); } public LoadVariableAddressMacro GetTargetVariableAddrOrTemp(TypeReference type, ILProcessor il, out VariableDefinition variable, Instruction before = null) { if (_targetVariable.Variable == null || !_targetVariable.Type.IsSame(type)) { variable = CreateVariable(type, il, before); return new LoadVariableAddressMacro(variable, null, null); } TargetAddrUsed = true; variable = null; return new LoadVariableAddressMacro(_targetVariable.Variable, _targetVariable.IndexVariable, _targetVariable.Type); } public TargetVariableScope TargetVariableAddr(VariableDefinition variable) => new TargetVariableScope(this, new TargetVariableAddrInfo(variable)); public TargetVariableScope TargetVariableAddr(VariableDefinition arrayVariable, VariableDefinition indexVariable, TypeReference type) => new TargetVariableScope(this, new TargetVariableAddrInfo(arrayVariable, indexVariable, type)); public bool TargetAddrUsed { get; set; } public ILMacroStruct VerifyRawNetworkUnwrap(TypeReference type, int maxByteCount) => new[] { Ldc_I4(maxByteCount), Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkUnwrap), genericArgsCount: 1)) { GenericArguments = { type } }), }; public ILMacroStruct VerifyRawNetworkWrap(TypeReference type, int maxByteCount) => new[] { Ldc_I4(maxByteCount), Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkWrap), genericArgsCount: 1)) { GenericArguments = { type } }), }; protected virtual void EmitAddOffsetAfter(ILProcessor il) { } protected virtual void EmitAddOffsetBefore(ILProcessor il) { } public readonly struct AddOffsetMacro : ILProcessorMacro { public readonly MethodContext Context; public readonly Instruction Instruction; public readonly bool IsAligned; public AddOffsetMacro(MethodContext context, Instruction instruction = null, bool isAligned = false) { Context = context; Instruction = instruction; IsAligned = isAligned; } public void Emit(ILProcessor il) { if (Context.HasOffset) { if (Instruction == null) { if (!IsAligned) { il.AppendMacro(Context.AlignToWordSize()); } } Context.EmitAddOffsetBefore(il); if (Instruction != null) { il.Append(Instruction); if (!IsAligned) { il.AppendMacro(Context.AlignToWordSize()); } } Context.EmitAddOffsetAfter(il); } else { if (Instruction == null) { // means variant with size already pushed has been used, pop it il.Append(Pop()); } } } } public readonly struct TargetVariableAddrInfo { public readonly VariableDefinition Variable; public readonly VariableDefinition IndexVariable; public readonly TypeReference Type; public TargetVariableAddrInfo(VariableDefinition variable) { Variable = variable; IndexVariable = null; Type = variable.VariableType; } public TargetVariableAddrInfo(VariableDefinition variable, VariableDefinition indexVariable, TypeReference elementType) { Variable = variable; IndexVariable = indexVariable; Type = elementType; } } public readonly struct ForLoopMacro : ILProcessorMacro { public readonly MethodContext Context; public readonly Action Generator; public readonly Action Start; public readonly Action Stop; public ForLoopMacro(MethodContext context, Action generator, Action start, Action stop) { Context = context; Generator = generator; Start = start; Stop = stop; } public void Emit(ILProcessor il) { var body = Context.Method.Body; var varId = body.Variables.Count; var indexVariable = new VariableDefinition(Context.Assembly.Import(typeof(int))); body.Variables.Add(indexVariable); Start(il); il.Append(Stloc(body, varId)); var loopConditionStart = Ldloc(body, varId); il.Append(Br_S(loopConditionStart)); { var loopBodyBegin = il.AppendReturn(Nop()); Generator(il, indexVariable); il.Append(Ldloc(body, varId)); il.Append(Ldc_I4(1)); il.Append(Add()); il.Append(Stloc(body, varId)); il.Append(loopConditionStart); Stop(il); il.Append(Blt_S(loopBodyBegin)); } } } public struct ValueGetterScope : IDisposable { MethodContext _context; Action _oldValueGetter; public ValueGetterScope(MethodContext context, Action valueGetter) { _context = context; _oldValueGetter = context._valueGetter; context._valueGetter = valueGetter; } public void Dispose() { _context._valueGetter = _oldValueGetter; } } public struct TargetVariableScope : IDisposable { MethodContext _context; TargetVariableAddrInfo _oldTargetVariable; bool _wasUsed; public TargetVariableScope(MethodContext context, TargetVariableAddrInfo variable) { _context = context; _oldTargetVariable = context._targetVariable; _wasUsed = context.TargetAddrUsed; context._targetVariable = variable; context.TargetAddrUsed = false; } public void Dispose() { _context._targetVariable = _oldTargetVariable; _context.TargetAddrUsed = _wasUsed; } } public struct LoadVariableAddressMacro : ILProcessorMacro { VariableDefinition _variable; VariableDefinition _index; TypeReference _elemType; public LoadVariableAddressMacro(VariableDefinition variable, VariableDefinition index, TypeReference elemType) { _variable = variable; _index = index; _elemType = elemType; } void ILProcessorMacro.Emit(ILProcessor il) { if (_index == null) { il.Append(Ldloca(_variable)); } else { il.Append(Ldloc(_variable)); il.Append(Ldloc(_index)); il.Append(Ldelema(_elemType)); } } } } class RpcMethodContext : MethodContext { public VariableDefinition DataVariable; public VariableDefinition OffsetVariable; public VariableDefinition RpcInvokeInfoVariable; public RpcMethodContext(ILWeaverAssembly asm, MethodDefinition definition, bool staticRunnerAccessor) : base(asm, definition, staticRunnerAccessor) { } public override bool HasOffset => true; public override bool IsWriteCompact => true; public override ILMacroStruct LoadAddress() => new[] { Ldloc(DataVariable), Ldloc(OffsetVariable), Add(), }; public ILMacroStruct SetRpcInvokeInfoStatus(bool emitIf, RpcLocalInvokeResult reason) => RpcInvokeInfoVariable == null || !emitIf ? new Instruction[0] : new[] { Ldloca(RpcInvokeInfoVariable), Ldc_I4((int)reason), Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.LocalInvokeResult))) }; public ILMacroStruct SetRpcInvokeInfoStatus(RpcSendCullResult reason) => RpcInvokeInfoVariable == null ? new Instruction[0] : new[] { Ldloca(RpcInvokeInfoVariable), Ldc_I4((int)reason), Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendCullResult))) }; protected override void EmitAddOffsetAfter(ILProcessor il) { il.Append(Add()); il.Append(Stloc(OffsetVariable)); } protected override void EmitAddOffsetBefore(ILProcessor il) { il.Append(Ldloc(OffsetVariable)); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverOpCodes.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using MethodBody = Mono.Cecil.Cil.MethodBody; static class ILWeaverOpCodes { // utils public static Instruction Nop() => Instruction.Create(OpCodes.Nop); public static Instruction Ret() => Instruction.Create(OpCodes.Ret); public static Instruction Dup() => Instruction.Create(OpCodes.Dup); public static Instruction Pop() => Instruction.Create(OpCodes.Pop); public static Instruction Ldnull() => Instruction.Create(OpCodes.Ldnull); public static Instruction Throw() => Instruction.Create(OpCodes.Throw); public static Instruction Cast(TypeReference type) => Instruction.Create(OpCodes.Castclass, type); // breaks public static Instruction Brfalse(Instruction target) => Instruction.Create(OpCodes.Brfalse, target); public static Instruction Brtrue(Instruction target) => Instruction.Create(OpCodes.Brtrue, target); public static Instruction Brfalse_S(Instruction target) => Instruction.Create(OpCodes.Brfalse_S, target); public static Instruction Brtrue_S(Instruction target) => Instruction.Create(OpCodes.Brtrue_S, target); public static Instruction Br_S(Instruction target) => Instruction.Create(OpCodes.Br_S, target); public static Instruction Br(Instruction target) => Instruction.Create(OpCodes.Br, target); public static Instruction Blt_S(Instruction target) => Instruction.Create(OpCodes.Blt_S, target); public static Instruction Ble_S(Instruction target) => Instruction.Create(OpCodes.Ble_S, target); public static Instruction Beq(Instruction target) => Instruction.Create(OpCodes.Beq, target); public static Instruction Bne_Un_S(Instruction target) => Instruction.Create(OpCodes.Bne_Un_S, target); public static Instruction Beq_S(Instruction target) => Instruction.Create(OpCodes.Beq_S, target); // math public static Instruction Add() => Instruction.Create(OpCodes.Add); public static Instruction Sub() => Instruction.Create(OpCodes.Sub); public static Instruction Mul() => Instruction.Create(OpCodes.Mul); public static Instruction Div() => Instruction.Create(OpCodes.Div); public static Instruction And() => Instruction.Create(OpCodes.And); // obj public static Instruction Ldobj(TypeReference type) => Instruction.Create(OpCodes.Ldobj, type); public static Instruction Stobj(TypeReference type) => Instruction.Create(OpCodes.Stobj, type); public static Instruction Newobj(MethodReference constructor) => Instruction.Create(OpCodes.Newobj, constructor); public static Instruction Newarr(TypeReference type) => Instruction.Create(OpCodes.Newarr, type); public static Instruction Initobj(TypeReference type) => Instruction.Create(OpCodes.Initobj, type); public static Instruction Box(TypeReference type) => Instruction.Create(OpCodes.Box, type); // fields public static Instruction Ldflda(FieldReference field) => Instruction.Create(OpCodes.Ldflda, field); public static Instruction Ldfld(FieldReference field) => Instruction.Create(OpCodes.Ldfld, field); public static Instruction Stfld(FieldReference field) => Instruction.Create(OpCodes.Stfld, field); public static Instruction Ldsfld(FieldReference field) => Instruction.Create(OpCodes.Ldsfld, field); public static Instruction Stsfld(FieldReference field) => Instruction.Create(OpCodes.Stsfld, field); // locals public static Instruction Ldloc_or_const(VariableDefinition var, int val) => var != null ? Ldloc(var) : Ldc_I4(val); public static Instruction Ldloc(VariableDefinition var, MethodDefinition method) => Ldloc(method.Body, method.Body.Variables.IndexOf(var)); public static Instruction Ldloc(VariableDefinition var) => Instruction.Create(OpCodes.Ldloc, var); public static Instruction Ldloca(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca, var); public static Instruction Ldloca_S(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca_S, var); public static Instruction Stloc(VariableDefinition var) => Instruction.Create(OpCodes.Stloc, var); public static Instruction Stloc_0() => Instruction.Create(OpCodes.Stloc_0); public static Instruction Stloc_1() => Instruction.Create(OpCodes.Stloc_1); public static Instruction Stloc_2() => Instruction.Create(OpCodes.Stloc_2); public static Instruction Stloc_3() => Instruction.Create(OpCodes.Stloc_3); public static Instruction Ldloc_0() => Instruction.Create(OpCodes.Ldloc_0); public static Instruction Ldloc_1() => Instruction.Create(OpCodes.Ldloc_1); public static Instruction Ldloc_2() => Instruction.Create(OpCodes.Ldloc_2); public static Instruction Ldloc_3() => Instruction.Create(OpCodes.Ldloc_3); public static Instruction Stloc(MethodBody body, int index) { switch (index) { case 0: return Stloc_0(); case 1: return Stloc_1(); case 2: return Stloc_2(); case 3: return Stloc_3(); default: return Stloc(body.Variables[index]); } } public static Instruction Ldloc(MethodBody body, int index) { switch (index) { case 0: return Ldloc_0(); case 1: return Ldloc_1(); case 2: return Ldloc_2(); case 3: return Ldloc_3(); default: return Ldloc(body.Variables[index]); } } // ldarg public static Instruction Ldarg(ParameterDefinition arg) => Instruction.Create(OpCodes.Ldarg, arg); public static Instruction Ldarg_0() => Instruction.Create(OpCodes.Ldarg_0); public static Instruction Ldarg_1() => Instruction.Create(OpCodes.Ldarg_1); public static Instruction Ldarg_2() => Instruction.Create(OpCodes.Ldarg_2); public static Instruction Ldarg_3() => Instruction.Create(OpCodes.Ldarg_3); public static Instruction Ldarga_S(ParameterDefinition p) => Instruction.Create(OpCodes.Ldarga_S, p); // starg public static Instruction Starg_S(ParameterDefinition arg) => Instruction.Create(OpCodes.Starg_S, arg); // array public static Instruction Ldlen() => Instruction.Create(OpCodes.Ldlen); public static Instruction Ldelem(TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: return Instruction.Create(OpCodes.Ldelem_U1); case MetadataType.SByte: return Instruction.Create(OpCodes.Ldelem_I1); case MetadataType.UInt16: return Instruction.Create(OpCodes.Ldelem_U2); case MetadataType.Int16: return Instruction.Create(OpCodes.Ldelem_I2); case MetadataType.UInt32: return Instruction.Create(OpCodes.Ldelem_U4); case MetadataType.Int32: return Instruction.Create(OpCodes.Ldelem_I4); case MetadataType.UInt64: return Instruction.Create(OpCodes.Ldelem_I8); case MetadataType.Int64: return Instruction.Create(OpCodes.Ldelem_I8); case MetadataType.Single: return Instruction.Create(OpCodes.Ldelem_R4); case MetadataType.Double: return Instruction.Create(OpCodes.Ldelem_R8); default: if (type.IsValueType) { return Instruction.Create(OpCodes.Ldelem_Any, type); } else { return Instruction.Create(OpCodes.Ldelem_Ref); } } } public static Instruction Stelem(TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: case MetadataType.SByte: return Instruction.Create(OpCodes.Stelem_I1); case MetadataType.UInt16: case MetadataType.Int16: return Instruction.Create(OpCodes.Stelem_I2); case MetadataType.UInt32: case MetadataType.Int32: return Instruction.Create(OpCodes.Stelem_I4); case MetadataType.UInt64: case MetadataType.Int64: return Instruction.Create(OpCodes.Stelem_I8); case MetadataType.Single: return Instruction.Create(OpCodes.Stelem_R4); case MetadataType.Double: return Instruction.Create(OpCodes.Stelem_R8); default: if (type.IsValueType) { return Instruction.Create(OpCodes.Stelem_Any, type); } else { return Instruction.Create(OpCodes.Stelem_Ref); } } } public static Instruction Ldelema(TypeReference arg) => Instruction.Create(OpCodes.Ldelema, arg); // conversions public static Instruction Conv_R4() => Instruction.Create(OpCodes.Conv_R4); public static Instruction Conv_I4() => Instruction.Create(OpCodes.Conv_I4); public static Instruction Conv_U() => Instruction.Create(OpCodes.Conv_U); // functions public static Instruction Call(MethodReference method) => Instruction.Create(OpCodes.Call, method); public static Instruction Callvirt(MethodReference method) => Instruction.Create(OpCodes.Callvirt, method); public static Instruction Ldftn(MethodReference method) => Instruction.Create(OpCodes.Ldftn, method); // constants public static Instruction Ldstr(string value) => Instruction.Create(OpCodes.Ldstr, value); public static Instruction Ldc_R4(float value) => Instruction.Create(OpCodes.Ldc_R4, value); public static Instruction Ldc_R8(float value) => Instruction.Create(OpCodes.Ldc_R8, value); public static Instruction Ldc_I4(int value) { switch (value) { case 0: return Instruction.Create(OpCodes.Ldc_I4_0); case 1: return Instruction.Create(OpCodes.Ldc_I4_1); case 2: return Instruction.Create(OpCodes.Ldc_I4_2); case 3: return Instruction.Create(OpCodes.Ldc_I4_3); case 4: return Instruction.Create(OpCodes.Ldc_I4_4); case 5: return Instruction.Create(OpCodes.Ldc_I4_5); case 6: return Instruction.Create(OpCodes.Ldc_I4_6); case 7: return Instruction.Create(OpCodes.Ldc_I4_7); case 8: return Instruction.Create(OpCodes.Ldc_I4_8); default: return Instruction.Create(OpCodes.Ldc_I4, value); } } public static Instruction Stind_I4() => Instruction.Create(OpCodes.Stind_I4); public static Instruction Ldind_I4() => Instruction.Create(OpCodes.Ldind_I4); public static Instruction Stind_R4() => Instruction.Create(OpCodes.Stind_R4); public static Instruction Ldind_R4() => Instruction.Create(OpCodes.Ldind_R4); public static Instruction Stind_or_Stobj(TypeReference type) { if (type.IsPrimitive) { return Stind(type); } else { return Stobj(type); } } public static Instruction Ldind_or_Ldobj(TypeReference type) { if (type.IsPrimitive) { return Ldind(type); } else { return Ldobj(type); } } public static Instruction Stind(TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: case MetadataType.SByte: return Instruction.Create(OpCodes.Stind_I1); case MetadataType.UInt16: case MetadataType.Int16: return Instruction.Create(OpCodes.Stind_I2); case MetadataType.UInt32: case MetadataType.Int32: return Instruction.Create(OpCodes.Stind_I4); case MetadataType.UInt64: case MetadataType.Int64: return Instruction.Create(OpCodes.Stind_I8); case MetadataType.Single: return Instruction.Create(OpCodes.Stind_R4); case MetadataType.Double: return Instruction.Create(OpCodes.Stind_R8); default: throw new ILWeaverException($"Unknown primitive type {type.FullName}"); } } public static Instruction Ldind(TypeReference type) { switch (type.MetadataType) { case MetadataType.Byte: return Instruction.Create(OpCodes.Ldind_U1); case MetadataType.SByte: return Instruction.Create(OpCodes.Ldind_I1); case MetadataType.UInt16: return Instruction.Create(OpCodes.Ldind_U2); case MetadataType.Int16: return Instruction.Create(OpCodes.Ldind_I2); case MetadataType.UInt32: return Instruction.Create(OpCodes.Ldind_U4); case MetadataType.Int32: return Instruction.Create(OpCodes.Ldind_I4); case MetadataType.UInt64: return Instruction.Create(OpCodes.Ldind_I8); case MetadataType.Int64: return Instruction.Create(OpCodes.Ldind_I8); case MetadataType.Single: return Instruction.Create(OpCodes.Ldind_R4); case MetadataType.Double: return Instruction.Create(OpCodes.Ldind_R8); default: throw new ILWeaverException($"Unknown primitive type {type.FullName}"); } } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/ILWeaverSettings.cs #if FUSION_WEAVER namespace Fusion.CodeGen { using System; public partial class ILWeaverSettings { public static string DefaultConfigPath { get { string result = "Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion"; OverrideNetworkProjectConfigPath(ref result); return result; } } static partial void OverrideNetworkProjectConfigPath(ref string path); public static bool IsAssemblyWeavable(string[] assembliesToWeave, string assemblyName) { return Array.FindIndex(assembliesToWeave, x => assemblyName.Equals(x, StringComparison.OrdinalIgnoreCase)) >= 0; } public static bool ContainsRequiredReferences(string[] references) { return Array.FindIndex(references, x => x.Contains("Fusion.Runtime")) >= 0; } public bool NullChecksForNetworkedProperties; public bool UseSerializableDictionary; public bool CheckRpcAttributeUsage; public bool CheckNetworkedPropertiesBeingEmpty; public string[] AssembliesToWeave; } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/InstructionEqualityComparer.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System.Collections.Generic; using Mono.Cecil.Cil; internal class InstructionEqualityComparer : IEqualityComparer { public bool Equals(Instruction x, Instruction y) { if (x.OpCode != y.OpCode) { return false; } if (x.Operand != y.Operand) { if (x.Operand?.GetType() != y?.Operand.GetType()) { return false; } // there needs to be a better way to do this if (x.Operand.ToString() != y.Operand.ToString()) { return false; } } return true; } public int GetHashCode(Instruction obj) { return obj.GetHashCode(); } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/MemberReferenceFullNameComparer.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System.Collections.Generic; using Mono.Cecil; class MemberReferenceFullNameComparer : IEqualityComparer { bool IEqualityComparer.Equals(MemberReference x, MemberReference y) { if ( x == y ) { return true; } if ( x == null || y == null ) { return false; } return GetFullName(x).Equals(GetFullName(y)); } int IEqualityComparer.GetHashCode(MemberReference obj) { if ( obj == null ) { return 0; } return GetFullName(obj).GetHashCode(); } string GetFullName(MemberReference member) { if (member is TypeReference type) { if (type.IsGenericParameter) { return $"{type.FullName} of {GetFullName(type.DeclaringType)}"; } } return member.FullName; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/MonoCecilExtensions.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using UnityEditor; using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; using MethodAttributes = Mono.Cecil.MethodAttributes; using MethodBody = Mono.Cecil.Cil.MethodBody; public static class MonoCecilExtensions { public static MethodDefinition AddDefaultConstructor(this TypeDefinition type, Action initializers = null) { var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; var method = new MethodDefinition(".ctor", methodAttributes, type.Module.ImportReference(typeof(void))); method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); MethodReference baseConstructor = new MethodReference(method.Name, method.ReturnType) { HasThis = method.HasThis, ExplicitThis = method.ExplicitThis, DeclaringType = type.BaseType, CallingConvention = method.CallingConvention, }; if (type.BaseType.IsGenericInstance) { var gi = (GenericInstanceType)type.BaseType; foreach (var genericParameter in gi.GenericParameters) { baseConstructor.GenericParameters.Add(new GenericParameter(genericParameter.Name, baseConstructor)); } } method.AddTo(type); var il = method.Body.GetILProcessor(); if (initializers != null) { initializers(il); } il.Append(Instruction.Create(OpCodes.Call, type.Module.ImportReference(baseConstructor))); il.Append(Instruction.Create(OpCodes.Ret)); return method; } public static T GetAttributeArgument(this CustomAttribute attr, int index) { if (TryGetAttributeArgument(attr, index, out var result)) { return result; } else { throw new ArgumentOutOfRangeException($"Argument {index} not found in {attr}"); } } public static MethodDefinition GetMethodOrThrow(this TypeDefinition type, string methodName, int? argCount = null) { var query = type.Methods.Where(x => x.Name == methodName); if (argCount != null) { query = query.Where(x => x.Parameters.Count == argCount.Value); } var results = query.ToList(); if (results.Count == 0) { throw new ArgumentOutOfRangeException(nameof(methodName), $"Method {methodName} not found in {type}"); } else if (results.Count > 1) { throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}"); } else { return results[0]; } } public static bool TryGetMethod(this TypeDefinition type, string methodName, out MethodDefinition method, int? argCount = null) { var query = type.Methods.Where(x => x.Name == methodName); if (argCount != null) { query = query.Where(x => x.Parameters.Count == argCount.Value); } var results = query.ToList(); if (results.Count == 0) { method = default; return false; } else if (results.Count > 1) { throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}"); } else { method = results[0]; return true; } } public static bool HasAttribute(this ICustomAttributeProvider type) where T : Attribute { return TryGetAttribute(type, out _); } public static bool TryGetBackingField(this PropertyDefinition property, out FieldDefinition field) { const string Prefix = "<"; const string Suffix = ">k__BackingField"; var fieldName = $"{Prefix}{property.Name}{Suffix}"; foreach (var f in property.DeclaringType.Fields) { if (!f.IsPrivate) { continue; } if (f.Name == fieldName) { field = f; return true; } } field = null; return false; } public static bool IsBackingField(this FieldDefinition field, out PropertyDefinition property) { var fieldName = field.Name; const string Prefix = "<"; const string Suffix = ">k__BackingField"; if (!fieldName.StartsWith(Prefix) || !fieldName.EndsWith(Suffix)) { property = default; return false; } var propertyName = fieldName.Substring(Prefix.Length, fieldName.Length - Prefix.Length - Suffix.Length); foreach (var prop in field.DeclaringType.Properties) { if (prop.Name == propertyName) { property = prop; return true; } } throw new InvalidOperationException($"Field {field} matches backing field name, but property {propertyName} is not found"); } public static bool IsEnumType(this TypeReference type, out TypeReference valueType) { var typeDef = type.Resolve(); if (!typeDef.IsEnum) { valueType = default; return false; } foreach (var field in typeDef.Fields) { if (field.Name == "value__" && field.IsSpecialName && field.IsRuntimeSpecialName && !field.IsStatic) { valueType = field.FieldType; return true; } } throw new InvalidOperationException($"Matching value__ field not found on {type}"); } public static bool IsFixedBuffer(this TypeReference type, out int size) { size = default; if (!type.IsValueType) { return false; } if (!type.Name.EndsWith("e__FixedBuffer")) { return false; } var definition = type.Resolve(); // this is a bit of a guesswork if (HasAttribute(definition) && HasAttribute(definition) && definition.ClassSize > 0) { size = definition.ClassSize; return true; } return false; } public static bool TryGetAttribute(this ICustomAttributeProvider type, out CustomAttribute attribute) where T : Attribute { for (int i = 0; i < type.CustomAttributes.Count; ++i) { var attr = type.CustomAttributes[i]; if (attr.AttributeType.Is(typeof(T))) { attribute = attr; return true; } } attribute = null; return false; } public static bool TryGetAttributeArgument(this CustomAttribute attr, int index, out T value, T defaultValue = default) { if (index < attr.ConstructorArguments.Count) { var val = attr.ConstructorArguments[index].Value; if (val is T t) { value = t; return true; } else if ( typeof(T).IsEnum && val.GetType().IsPrimitive ) { value = (T)Enum.ToObject(typeof(T), val); return true; } } value = defaultValue; return false; } public static bool TryGetAttributeProperty(this CustomAttribute attr, string name, out T value, T defaultValue = default) { if (attr.HasProperties) { var prop = attr.Properties.FirstOrDefault(x => x.Name == name); if (prop.Argument.Value != null) { value = (T)prop.Argument.Value; return true; } } value = defaultValue; return false; } public static bool TryGetMatchingConstructor(this TypeDefinition type, MethodDefinition constructor, out MethodDefinition matchingConstructor) { return TryGetMatchingMethod(type.GetConstructors(), constructor.Parameters, out matchingConstructor); } public static bool TryGetMethod(this TypeDefinition type, string methodName, IList parameters, out MethodDefinition method) { var methods = type.Methods.Where(x => x.Name == methodName); if (TryGetMatchingMethod(methods, parameters, out method)) { return true; } //if (type.BaseType != null) { // if (stopAtBaseType == null || !stopAtBaseType.IsSame(type.BaseType)) { // return TryGetMethod(type.BaseType.Resolve(), methodName, parameters, out method, stopAtBaseType); // } //} method = null; return false; } public static VariableDefinition Clone(this VariableDefinition variable) { return new VariableDefinition(variable.VariableType); } public static Instruction Clone(this Instruction instruction) { return (Instruction)Activator.CreateInstance(typeof(Instruction), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { instruction.OpCode, instruction.Operand }, null); } public static (Instruction[], VariableDefinition[]) CloneAndFixUp(MethodBody targetBody, Instruction[] instructions, VariableDefinition[] localVariables) { var resultInstructions = new Instruction[instructions.Length]; for (int i = 0; i < instructions.Length; ++i) { resultInstructions[i] = instructions[i].Clone(); } var resultVariables = new VariableDefinition[localVariables.Length]; for (int i = 0; i < localVariables.Length; ++i) { resultVariables[i] = localVariables[i].Clone(); targetBody.Variables.Add(resultVariables[i]); } for (int i = 0; i < instructions.Length; ++i) { if (instructions[i].Operand is Instruction referencedInstruction) { var referencedIndex = Array.IndexOf(instructions, referencedInstruction); if (referencedIndex >= 0) { resultInstructions[i].Operand = resultInstructions[referencedIndex]; } else { throw new InvalidOperationException(); } } else if (instructions[i].Operand is VariableDefinition referencedVariable) { var referencedIndex = Array.IndexOf(localVariables, referencedVariable); if (referencedIndex >= 0) { resultInstructions[i].Operand = resultVariables[referencedIndex]; } else { throw new InvalidOperationException(); } } else { var opCode = instructions[i].OpCode; int index = -1; if (opCode == OpCodes.Ldloc_0) { index = 0; } else if (opCode == OpCodes.Ldloc_1) { index = 1; } else if (opCode == OpCodes.Ldloc_2) { index = 2; } else if (opCode == OpCodes.Ldloc_3) { index = 3; } if (index >= 0) { var varIndex = Array.FindIndex(localVariables, x => x.Index == index); if (varIndex >= 0) { var replacementOp = resultInstructions[i]; replacementOp.OpCode = OpCodes.Ldloc; replacementOp.Operand = resultVariables[varIndex]; } else { throw new InvalidOperationException($"Using ldloc with index {index} but no variable with that index exists"); } } } } return (resultInstructions, resultVariables); } private static bool TryGetMatchingMethod(IEnumerable methods, IList parameters, out MethodDefinition result) { foreach (var c in methods) { if (c.Parameters.Count != parameters.Count) { continue; } int i; for (i = 0; i < c.Parameters.Count; ++i) { if (!c.Parameters[i].ParameterType.IsSame(parameters[i].ParameterType)) { break; } } if (i == c.Parameters.Count) { result = c; return true; } } result = null; return false; } public static void SetPosition(this GenericParameter parameter, int position) { var positionField = parameter.GetType().GetField("position", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); Debug.Assert(positionField != null, nameof(positionField) + " != null"); positionField.SetValue(parameter, position); } public static TypeReference GetGenericParameter(this TypeReference provider, int position) { var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!{position}", provider); parameterTypeReference0.SetPosition(position); return parameterTypeReference0; } public static MethodReference ImportGetter(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { var body = (System.Linq.Expressions.MemberExpression)methodExpression.Body; var property = (PropertyInfo)body.Member; var method = module.ImportReference(property.GetMethod); MakeCallable(method, genericParameterProvider); return method; } public static FieldReference ImportField(this ModuleDefinition module, System.Linq.Expressions.Expression> fieldExpression, TypeReference genericParameterProvider = null) { var body = (System.Linq.Expressions.MemberExpression)fieldExpression.Body; var member = (FieldInfo)body.Member; var result = module.ImportReference(member); MakeCallable(result, genericParameterProvider); return result; } public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { MethodInfo methodInfo; switch (methodExpression.Body) { case MethodCallExpression mce: methodInfo = mce.Method; break; case UnaryExpression ue: methodInfo = ue.Method; break; default: throw new NotSupportedException($"{methodExpression.Body.GetType()} is not supported"); } var method = module.ImportReference(methodInfo); MakeCallable(method, genericParameterProvider); return method; } public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body; var method = module.ImportReference(body.Method); MakeCallable(method, genericParameterProvider); return method; } public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body; var method = module.ImportReference(body.Method); MakeCallable(method, genericParameterProvider); return method; } private static void MakeCallable(this MethodReference method, TypeReference genericParameterProvider) { if (method.ReturnType.IsGenericParameter || method.Parameters.Any(x => x.ParameterType.IsGenericParameter)) { // needs some bullshit processing if (genericParameterProvider == null) { throw new ArgumentException("Generic parameter provider must be specified when importing generic methods"); } // this is iffy! method.DeclaringType = genericParameterProvider; if (method.ReturnType.IsGenericParameter) { method.ReturnType = genericParameterProvider.GetGenericParameter(((GenericParameter)method.ReturnType).Position); } foreach (var parameter in method.Parameters) { if (parameter.ParameterType.IsGenericParameter) { parameter.ParameterType = genericParameterProvider.GetGenericParameter(((GenericParameter)parameter.ParameterType).Position); } } } else if (genericParameterProvider != null) { // iffy method.DeclaringType = genericParameterProvider; } } private static void MakeCallable(this FieldReference field, TypeReference genericParameterProvider) { if (genericParameterProvider == null) { throw new ArgumentNullException(nameof(genericParameterProvider)); } if (field.FieldType.IsGenericParameter) { field.FieldType = genericParameterProvider.GetGenericParameter(((GenericParameter)field.FieldType).Position); } // iffy field.DeclaringType = genericParameterProvider; } public static TypeReference ImportType(this ModuleDefinition module) { return module.ImportReference(typeof(T)); } public static TypeReference MakeGenericInstance(this TypeReference self, params TypeReference[] arguments) { if (self.GenericParameters.Count != arguments.Length) { throw new ArgumentException(); } var instance = new GenericInstanceType(self); foreach (var argument in arguments) { instance.GenericArguments.Add(argument); } return instance; } public static MethodReference MakeGenericInstance(this MethodReference self, params TypeReference[] arguments) { var reference = new MethodReference(self.Name, self.ReturnType) { DeclaringType = self.DeclaringType.MakeGenericInstance(arguments), HasThis = self.HasThis, ExplicitThis = self.ExplicitThis, CallingConvention = self.CallingConvention, }; foreach (var parameter in self.Parameters) { reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); } foreach (var parameter in self.GenericParameters) { reference.GenericParameters.Add(new GenericParameter(parameter.Name, reference)); } return reference; } public static bool TryResolve(this GenericParameter gp, TypeReference context, out TypeReference typeReference) { var declaringType = gp.DeclaringType; if (declaringType is TypeDefinition declaringTypeDef) { if (!declaringTypeDef.HasGenericParameters) { throw new ArgumentException("Generic parameter must be declared on a generic type"); } var genericArgumentIndex = declaringTypeDef.GenericParameters.IndexOf(gp); if (genericArgumentIndex < 0) { throw new ArgumentException($"Generic parameter not found in declaring type {declaringTypeDef}"); } // find type ref pointing to this var type = context; while (!type.IsSame()) { var typeDef = type.Resolve(); if (typeDef.IsSame(declaringType)) { if (type is TypeDefinition) { // impossible to resolve break; } if (!type.IsGenericInstance) { throw new NotSupportedException($"Expected generic instance of {declaringType}"); } var genericInstance = (GenericInstanceType)type; var genericArg = genericInstance.GenericArguments[genericArgumentIndex]; if (genericArg is GenericParameter newGenericParameter) { return TryResolve(newGenericParameter, context, out typeReference); } else { typeReference = genericArg; return true; } } type = typeDef.BaseType; } typeReference = null; return false; } else { throw new NotSupportedException(); } } } public static class TmpVariable { public static T variable; } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/NetworkTypeInfo.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using static ILWeaverOpCodes; [Flags] public enum NetworkTypeInfoFlags { IsTriviallyCopyable = 1 << 0, CantBeUsedInStructs = 1 << 1, CantBeUsedInRpcs = 1 << 2, HasDynamicRpcSize = 1 << 3, } public class NetworkTypeInfo { internal delegate void EmitDelegate(ICustomAttributeProvider member, ILProcessor processor, MethodContext context); internal delegate int GetMemberWordCountDelegate(ICustomAttributeProvider member, TypeReference declaringType); internal delegate int GetCapacityDelegate(ICustomAttributeProvider member); internal delegate void EmitInitDelegate(PropertyDefinition property, ILProcessor processor, TypeReference initType, Action emitArg); internal delegate void EmitStoreDelegate(PropertyDefinition property, ILProcessor processor, FieldReference field); internal delegate TypeReference GetUnitySerializableTypeDelegate(bool isSerializable); internal static NetworkTypeInfo Create(TypeReference type, GetUnitySerializableTypeDelegate unitySerializableType = null, EmitDelegate read = null, EmitDelegate write = null, EmitDelegate compactByteCount = null, EmitDelegate getHashCode = null, GetMemberWordCountDelegate wordCount = null, int typeByteSize = -1, GetCapacityDelegate capacity = null, EmitInitDelegate unityInit = null, EmitStoreDelegate unityStore = null, NetworkTypeInfoFlags flags = 0) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (typeByteSize <= 0 && wordCount == null) { throw new ArgumentNullException(nameof(wordCount)); } return new NetworkTypeInfo(type, flags) { _emitRead = read, _emitWrite = write, _emitGetHashCode = getHashCode, _unitySerializableType = unitySerializableType, _typeByteSize = typeByteSize, _getMemberWordCount = wordCount, _getCapacity = capacity, _emitRpcByteCount = compactByteCount, _emitUnityInit = unityInit, _emitUnityStore = unityStore, }; } protected NetworkTypeInfo(TypeReference type, NetworkTypeInfoFlags flags) { TypeRef = type; _flags = flags; } public TypeReference TypeRef { get; private set; } public bool HasStaticSize => _typeByteSize > 0; public int StaticByteCount { get { if (_typeByteSize <= 0) { throw new InvalidOperationException($"{TypeRef} does not have a static type size"); } return _typeByteSize; } } public int StaticWordCount => Native.WordCount(StaticByteCount, Allocator.REPLICATE_WORD_SIZE); public bool HasDynamicRpcSize => (_flags & NetworkTypeInfoFlags.HasDynamicRpcSize) != 0; public bool CanBeUsedInRpc => (_flags & NetworkTypeInfoFlags.CantBeUsedInRpcs) == 0; public bool CanBeUsedInStructs => (_flags & NetworkTypeInfoFlags.CantBeUsedInStructs) == 0; public bool IsTriviallyCopyable => (_flags & NetworkTypeInfoFlags.IsTriviallyCopyable) != 0; private NetworkTypeInfoFlags _flags; private int _typeByteSize; private EmitDelegate _emitWrite; private EmitDelegate _emitRead; private EmitDelegate _emitRpcByteCount; private EmitDelegate _emitGetHashCode; private GetMemberWordCountDelegate _getMemberWordCount; private GetCapacityDelegate _getCapacity; private GetUnitySerializableTypeDelegate _unitySerializableType; private EmitInitDelegate _emitUnityInit; private EmitStoreDelegate _emitUnityStore; internal virtual void EmitUnityInit(PropertyDefinition property, ILProcessor il, TypeReference initType, Action emitArg) { if (_emitUnityInit != null) { _emitUnityInit(property, il, initType, emitArg); } else { var setterRef = property.SetMethod.GetCallable(); il.Append(Ldarg_0()); emitArg(il); il.Append(Call(setterRef)); } } internal virtual void EmitUnityStore(PropertyDefinition property, ILProcessor il, FieldReference field) { if (_emitUnityStore != null) { _emitUnityStore(property, il, field); } else { il.Append(Ldarg_0()); il.Append(Ldarg_0()); il.Append(Call(property.GetMethod.GetCallable())); il.Append(Stfld(field)); } } internal virtual int GetMemberWordCount(ICustomAttributeProvider member, TypeReference declaringType) { int result; if (_getMemberWordCount != null) { if (member == null) { throw new InvalidOperationException($"Member is needed to get word count"); } result = _getMemberWordCount(member, declaringType); } else { result = StaticWordCount; } if (result <= 0) { throw new InvalidOperationException($"Expected word count of {member} to be greater than 0"); } return result; } internal virtual bool TryGetCapacity(ICustomAttributeProvider member, out int capacity) { if (_getCapacity != null) { capacity = _getCapacity(member); return true; } else { capacity = 0; return false; } } internal virtual void EmitRpcByteCount(ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) { if (_emitRpcByteCount != null) { _emitRpcByteCount(member, il, context); if (wordAligned) { il.AppendMacro(context.AlignToWordSize()); } } else if (_getMemberWordCount != null) { il.Append(Ldc_I4(_getMemberWordCount(member, context.Method.DeclaringType) * Allocator.REPLICATE_WORD_SIZE)); } else { if (wordAligned) { il.Append(Ldc_I4(Native.RoundToAlignment(StaticByteCount, Allocator.REPLICATE_WORD_ALIGN))); } else { il.Append(Ldc_I4(StaticByteCount)); } } } internal virtual void EmitWrite(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { if (_emitWrite != null) { _emitWrite(member, il, context); } else { il.AppendMacro(context.LoadAddress()); il.AppendMacro(context.LoadValue()); il.Append(Stind_or_Stobj(TypeRef)); il.AppendMacro(context.AddOffset(StaticByteCount)); } } internal virtual void EmitGetHashCode(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { if (_emitGetHashCode != null) { _emitGetHashCode(member, il, context); } else { var tmp = context.AddVariable(TypeRef); if (context.HasValueAddrGetter) { il.AppendMacro(context.LoadValueAddr()); } else { il.AppendMacro(context.LoadValue()); il.Append(Stloc(tmp)); il.Append(Ldloca(tmp)); } if (TypeRef.IsPrimitive) { var getHashCode = context.Assembly.Import(TypeRef.GetPrimitiveType().GetMethod(nameof(object.GetHashCode))); il.Append(Call(getHashCode)); } else { if (!TypeRef.IsValueType) { throw new InvalidOperationException($"Expected {TypeRef} to be a value type"); } var getHashCode = context.Assembly.Object.GetMethod(nameof(object.GetHashCode)); if (TypeRef.IsValueType) { il.Append(Instruction.Create(OpCodes.Constrained, TypeRef)); } il.Append(Callvirt(getHashCode)); } } } internal virtual void EmitRead(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { if (_emitRead != null) { _emitRead(member, il, context); } else { il.AppendMacro(context.LoadAddress()); il.Append(Ldind_or_Ldobj(TypeRef)); il.AppendMacro(context.AddOffset(StaticByteCount)); } } internal virtual TypeReference GetUnityBackingFieldType(bool isSerializable) { return _unitySerializableType?.Invoke(isSerializable) ?? TypeRef; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/NetworkTypeInfoRegistry.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; #if UNITY_EDITOR using UnityEngine; #endif using static ILWeaverOpCodes; using Behaviour = Fusion.Behaviour; using FieldAttributes = Mono.Cecil.FieldAttributes; using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; public class NetworkTypeInfoRegistry { public delegate int CalculateWordCountDelegate(TypeReference type); private Dictionary _types = new Dictionary(new MemberReferenceFullNameComparer()); private ModuleDefinition _module; private CalculateWordCountDelegate _calculateValueTypeWordCount; private ILWeaverSettings _settings; internal ILWeaverLog Log { get; } public NetworkTypeInfoRegistry(ModuleDefinition module, ILWeaverSettings settings, ILWeaverLogger log, CalculateWordCountDelegate getWordCount) { _module = module; _settings = settings; _calculateValueTypeWordCount = getWordCount; Log = new ILWeaverLog(log); AddBuiltInTypes(); } public int GetTypeWordCount(TypeReference type) => GetInfo(type).StaticWordCount; public int GetPropertyWordCount(PropertyDefinition property) => GetMemberWordCount(property.PropertyType, property, property.DeclaringType); public int GetMemberWordCount(TypeReference type, ICustomAttributeProvider member, TypeReference declaringType) => GetInfo(type).GetMemberWordCount(member, declaringType); internal void EmitRead(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitRead(il, context, member); internal void EmitWrite(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitWrite(il, context, member); internal void EmitGetHashCode(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitGetHashCode(il, context, member); internal void EmitRpcByteCount(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) => GetInfo(type).EmitRpcByteCount(il, context, member, wordAligned); public NetworkTypeInfo GetInfo() => GetInfo(typeof(T)); public NetworkTypeInfo GetInfo(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } var t = _module.ImportReference(type); if (t == null) { throw new InvalidOperationException($"Failed to resolve: {type.FullName}"); } return GetInfo(t); } public NetworkTypeInfo GetInfo(TypeReference type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (_types.TryGetValue(type, out var result)) { return result; } return AddType(type); } const int DefaultArrayCapacity = 1; public const int DefaultContainerCapacity = 1; public const int DefaultStringCapacity = 16; private NetworkTypeInfo AddType(TypeReference type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (_types.ContainsKey(type)) { throw new InvalidOperationException($"Type {type} already added"); } var meta = MakeTypeData(type); _types.Add(type, meta); return meta; } private NetworkTypeInfo MakeTypeData(TypeReference type) { var resolved = type.Resolve(); var imported = _module.ImportReference(type); if (type is GenericParameter) { throw new ArgumentException($"Generic parameters are not supported", nameof(type)); } { if (type is Mono.Cecil.PointerType ptrType) { return CreatePointerOrRefMeta(imported, ptrType.ElementType); } else if (type is ByReferenceType refType) { return CreatePointerOrRefMeta(imported, refType.ElementType); } else if (type is TypeSpecification && !(type is GenericInstanceType)) { throw new ArgumentException($"Invalid TypeReference type: {type.FullName} ({type.GetType()})", nameof(type)); } } { if (type.IsNetworkArray(out var elementType)) { return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, false); } if (type.IsNetworkList(out elementType)) { return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, true); } if (type.IsNetworkDictionary(out var keyType, out var valueType)) { return CreateNetworkDictionaryMeta(imported, keyType, valueType); } } if (TryGetNetworkWrapperType(type, out var wrapInfo)) { return CreateWrappedMeta(imported, wrapInfo); } else if (resolved.IsValueType) { if (resolved.IsFixedBuffer(out var byteCount)) { return CreateUnmanagedTypeMeta(imported, byteCount); } else if (resolved.IsEnumType(out var enumValueType)) { return CreateUnmanagedTypeMeta(imported, GetInfo(enumValueType).StaticByteCount); } else if (resolved.Is() || resolved.Is()) { int wordCount; try { wordCount = GetUserValueTypeWordCount(resolved, type); } catch (Exception ex) { throw new ArgumentException($"Failed to get user type word count: {type.FullName}", nameof(type), ex); } return CreateUnmanagedTypeMeta(imported, Math.Max(1, wordCount) * Allocator.REPLICATE_WORD_SIZE); } else { // TODO: unmanaged portable structs throw new ArgumentException($"Value types need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)} interface (type: {type.FullName})", nameof(type)); } } else { // check for wapper? throw new ArgumentException($"Type {type.FullName} is a reference type but does not implement Wrap pattern.", nameof(type)); } } private int GetUserValueTypeWordCount(TypeDefinition type, TypeReference typeRef) { int wordCount; // is the type already weaved? if (type.TryGetAttribute(out var attribute)) { wordCount = attribute.GetAttributeArgument(0); // is this a generic composite type? if (attribute.TryGetAttributeArgument(1, out bool value, false) && value) { Log.Assert(typeRef.IsGenericInstance == true); foreach (var gen in ((GenericInstanceType)typeRef).GenericArguments) { if (gen.IsValueType && gen.Is()) { wordCount += GetTypeWordCount(typeRef); } } } } else { wordCount = _calculateValueTypeWordCount(type); } return wordCount; } private NetworkTypeInfo CreateUnmanagedTypeMeta(TypeReference type, int byteCount, NetworkTypeInfo.EmitDelegate read = null, NetworkTypeInfo.EmitDelegate write = null, bool isTriviallyCopyable = true) { return NetworkTypeInfo.Create(type, typeByteSize: byteCount, flags: isTriviallyCopyable ? NetworkTypeInfoFlags.IsTriviallyCopyable : 0, read: read, write: write ); } private NetworkTypeInfo CreatePointerOrRefMeta(TypeReference type, TypeReference elementType) { var elementInfo = GetInfo(elementType); return NetworkTypeInfo.Create(type, read: (member, il, context) => il.AppendMacro(context.LoadAddress()), write: (member, il, context) => throw new NotSupportedException($"Pointers and references can't have setters"), wordCount: (member, declaringType) => elementInfo.GetMemberWordCount(member, declaringType), unitySerializableType: _ => elementType, unityInit: (prop, il, _, emitArg) => { il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); emitArg(il); il.Append(Stind_or_Stobj(prop.PropertyType.GetElementTypeWithGenerics())); }, unityStore: (prop, il, field) => { il.Append(Ldarg_0()); il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); il.Append(Ldind_or_Ldobj(prop.PropertyType.GetElementType())); il.Append(Stfld(field)); }, flags: NetworkTypeInfoFlags.CantBeUsedInRpcs | NetworkTypeInfoFlags.CantBeUsedInRpcs ); } private NetworkTypeInfo CreateWrappedMeta(TypeReference type, NetworkTypeWrapInfo WrapInfo) { if (WrapInfo == null) { throw new ArgumentNullException(nameof(WrapInfo)); } NetworkTypeInfoFlags flags = 0; if (WrapInfo.MaxByteCount <= 0 && !WrapInfo.WrapperTypeInfo.CanBeUsedInRpc) { flags |= NetworkTypeInfoFlags.CantBeUsedInRpcs; } if (WrapInfo.WrapNeedsRunner || WrapInfo.UnwrapNeedsRunner) { flags |= NetworkTypeInfoFlags.CantBeUsedInStructs; } return NetworkTypeInfo.Create(type, wordCount: (member, declaringType) => { if (WrapInfo.MaxByteCount > 0) { return Native.WordCount(WrapInfo.MaxByteCount, Allocator.REPLICATE_WORD_SIZE); } else { return WrapInfo.WrapperTypeInfo.GetMemberWordCount(member, declaringType); } }, read: (member, il, context) => { if (WrapInfo.IsRaw || WrapInfo.UnwrapByRef) { var nop = il.AppendReturn(Nop()); if (WrapInfo.UnwrapNeedsRunner) { il.AppendMacro(context.LoadRunner()); } il.AppendMacro(context.LoadAddress()); if (!WrapInfo.IsRaw) { il.Append(Ldind_or_Ldobj(WrapInfo.WrapperType)); } il.AppendMacro(context.GetTargetVariableAddrOrTemp(WrapInfo.TargetType, il, out var variable, before: nop)); il.Append(Call(WrapInfo.UnwrapMethod.GetCallable())); if (WrapInfo.IsRaw) { il.AppendMacro(context.VerifyRawNetworkUnwrap(type, WrapInfo.MaxByteCount)); il.AppendMacro(context.AddOffset()); } else { il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount)); } if (variable != null) { il.Append(Ldloc(variable)); if (!WrapInfo.TargetType.Is(type)) { il.Append(Cast(type)); } } il.Remove(nop); } else { if (WrapInfo.UnwrapNeedsRunner) { il.AppendMacro(context.LoadRunner()); } il.AppendMacro(context.LoadAddress()); il.Append(Ldind_or_Ldobj(WrapInfo.WrapperType)); il.Append(Call(WrapInfo.UnwrapMethod.GetCallable())); if (!WrapInfo.TargetType.Is(type)) { il.Append(Cast(type)); } il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount)); } }, write: (member, il, context) => { // this is to do the store later on if (!WrapInfo.IsRaw) { il.AppendMacro(context.LoadAddress()); } // actual args start here if (WrapInfo.WrapNeedsRunner) { il.AppendMacro(context.LoadRunner()); } il.AppendMacro(context.LoadValue()); if (WrapInfo.IsRaw) { il.AppendMacro(context.LoadAddress()); } il.Append(Call(WrapInfo.WrapMethod.GetCallable())); if (WrapInfo.IsRaw) { il.AppendMacro(context.VerifyRawNetworkWrap(type, WrapInfo.MaxByteCount)); il.AppendMacro(context.AddOffset()); } else { il.Append(Stind_or_Stobj(WrapInfo.WrapperType)); il.AppendMacro(context.AddOffset(WrapInfo.WrapperTypeInfo.StaticByteCount)); } }, getHashCode: (member, il, context) => { if (WrapInfo.IsRaw) { if (WrapInfo.WrapNeedsRunner) { il.AppendMacro(context.LoadRunner()); } il.AppendMacro(context.LoadValue()); var bufferVariable = context.AddVariable(context.Assembly.Import(typeof(byte)).MakePointerType()); var bytesWrittenVariable = context.AddVariable(context.Assembly.Import(typeof(int))); il.Append(Ldc_I4(WrapInfo.MaxByteCount)); il.Append(Conv_U()); il.Append(Instruction.Create(OpCodes.Localloc)); il.Append(Stloc(bufferVariable)); il.Append(Ldloc(bufferVariable)); il.Append(Call(WrapInfo.WrapMethod.GetCallable())); il.Append(Stloc(bytesWrittenVariable)); il.Append(Ldloc(bufferVariable)); il.Append(Ldloc(bytesWrittenVariable)); var getArrayHashCode = context.Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.GetByteArrayHashCode), argsCount: 2); il.Append(Call(getArrayHashCode)); } else { using var nestedContext = new MethodContext(context.Assembly, context.Method, valueGetter: il => { if (WrapInfo.WrapNeedsRunner) { il.AppendMacro(context.LoadRunner()); } il.AppendMacro(context.LoadValue()); il.Append(Call(WrapInfo.WrapMethod.GetCallable())); }); WrapInfo.WrapperTypeInfo.EmitGetHashCode(il, nestedContext, member); } }, flags: flags ); } private NetworkTypeInfo CreateNetworkArrayOrNetworkLinkedListMeta(TypeReference type, TypeReference elementType, bool isList = false) { var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters)); ctor.DeclaringType = type; elementType = _module.ImportReference(elementType); var elementInfo = GetInfo(elementType); var unitySerializableType = elementType.MakeArrayType(); return NetworkTypeInfo.Create(type, wordCount: (member, declaringType) => { var capacity = GetCapacity(member, DefaultCollectionCapacity); var elementWordCount = elementInfo.GetMemberWordCount(member, declaringType); if (isList) { return NetworkLinkedList.META_WORDS + capacity * (elementWordCount + NetworkLinkedList.ELEMENT_WORDS); } else { return capacity * elementWordCount; } }, read: (member, il, context) => { var capacity = GetCapacity(member, DefaultContainerCapacity); il.AppendMacro(context.LoadAddress()); il.Append(Ldc_I4(capacity)); il.AppendMacro(context.LoadElementReaderWriter(elementType, member)); il.Append(Newobj(ctor)); }, write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"), capacity: (member) => GetCapacity(member, DefaultContainerCapacity), unitySerializableType: _ => unitySerializableType, unityInit: (prop, il, _, emitArg) => { var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.InitializeNetworkList) : nameof(NetworkBehaviourUtils.InitializeNetworkArray))); var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } }; il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); emitArg(il); il.Append(Ldstr(prop.Name)); il.Append(Call(m)); }, unityStore: (prop, il, field) => { var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.CopyFromNetworkList) : nameof(NetworkBehaviourUtils.CopyFromNetworkArray))); var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } }; il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); il.Append(Ldarg_0()); il.Append(Ldflda(field)); il.Append(Call(m)); }, flags: NetworkTypeInfoFlags.CantBeUsedInRpcs ); } private NetworkTypeInfo CreateNetworkDictionaryMeta(TypeReference type, TypeReference keyType, TypeReference valueType) { var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters)); ctor.DeclaringType = type; keyType = _module.ImportReference(keyType); valueType = _module.ImportReference(valueType); var keyInfo = GetInfo(keyType); var valueInfo = GetInfo(valueType); NetworkTypeInfo.GetUnitySerializableTypeDelegate unitySerializableType = isSerializable => { if (isSerializable && _settings.UseSerializableDictionary) { return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(SerializableDictionary<,>)), keyType, valueType); } else { return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(Dictionary<,>)), keyType, valueType); } }; NetworkTypeInfo.GetCapacityDelegate getCapacity = member => Primes.GetNextPrime(Math.Max(1, GetCapacity(member, DefaultCollectionCapacity))); return NetworkTypeInfo.Create(type, wordCount: (member, declaringType) => { var capacity = getCapacity(member); return // meta data (counts, etc) NetworkDictionary.META_WORD_COUNT + // buckets (capacity) + // entry // next (capacity) + // key (capacity * keyInfo.GetMemberWordCount(member, declaringType)) + // value (capacity * valueInfo.GetMemberWordCount(member, declaringType)); }, read: (member, il, context) => { var capacity = getCapacity(member); il.AppendMacro(context.LoadAddress()); il.Append(Ldc_I4(capacity)); il.AppendMacro(context.LoadElementReaderWriter(keyType, member)); il.AppendMacro(context.LoadElementReaderWriter(valueType, member)); il.Append(Newobj(ctor)); }, write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"), capacity: getCapacity, unitySerializableType: unitySerializableType, unityInit: (prop, il, initType, emitArg) => { if (initType == null) { initType = unitySerializableType(false); } var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.InitializeNetworkDictionary))); var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { initType, keyType, valueType } }; il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); emitArg(il); il.Append(Ldstr(prop.Name)); il.Append(Call(m)); }, unityStore: (prop, il, field) => { var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.CopyFromNetworkDictionary))); var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { field.FieldType, keyType, valueType } }; il.Append(Ldarg_0()); il.Append(Call(prop.GetMethod.GetCallable())); il.Append(Ldarg_0()); il.Append(Ldflda(field)); il.Append(Call(m)); }, flags: NetworkTypeInfoFlags.CantBeUsedInRpcs ); } void AddBuiltInType(NetworkTypeInfo meta) { _types.Add(meta.TypeRef, meta); } unsafe void AddBuiltInType(MethodReference readMethod = null, MethodReference writeMethod = null, bool isTriviallyCopyable = true) where T : unmanaged { var imported = _module.ImportReference(typeof(T)); var size = sizeof(T); int alignedByteCount = Native.WordCount(size, Allocator.REPLICATE_WORD_SIZE) * Allocator.REPLICATE_WORD_SIZE; if (readMethod != null) { readMethod = _module.ImportReference(readMethod); } if (writeMethod != null) { writeMethod = _module.ImportReference(writeMethod); } AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), size, read: readMethod != null ? (member, il, c) => { il.AppendMacro(c.LoadAddress()); il.Append(Call(readMethod)); il.AppendMacro(c.AddOffset(alignedByteCount)); } : null, write: writeMethod != null ? (member, il, c) => { il.AppendMacro(c.LoadAddress()); il.AppendMacro(c.LoadValue()); il.Append(Call(writeMethod)); il.AppendMacro(c.AddOffset(alignedByteCount)); } : null, isTriviallyCopyable: isTriviallyCopyable )); } unsafe void AddUnityType() where T : unmanaged { AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), sizeof(T))); } unsafe void AddBuiltInTypes() { var readWriteUtils = _module.ImportReference(typeof(ReadWriteUtilsForWeaver)).Resolve(); // safe primitive types AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); { var imported = _module.ImportReference(typeof(bool)); var readMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadBoolean), 1)); var writeMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteBoolean), 2)); AddBuiltInType(NetworkTypeInfo.Create(imported, read: (member, il, c) => { il.AppendMacro(c.LoadAddress()); il.Append(Call(readMethod)); il.AppendMacro(c.AddOffset(sizeof(int))); }, write: (member, il, c) => { il.AppendMacro(c.LoadAddress()); il.AppendMacro(c.LoadValue()); il.Append(Call(writeMethod)); il.AppendMacro(c.AddOffset(sizeof(int))); }, typeByteSize: sizeof(int) )); } #if UNITY_EDITOR // Unity types // TODO: restore AccuracyAttribute support for migrating from 1.1 AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddBuiltInType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); AddUnityType(); #endif { AddBuiltInType> (); AddBuiltInType> (); AddBuiltInType> (); AddBuiltInType> (); AddBuiltInType> (); AddBuiltInType> (); AddBuiltInType>(); AddBuiltInType>(); AddBuiltInType>(); } { var getUtf8ByteCount = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetByteCountUtf8NoHash), 1)); var writeUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf8NoHash), 2)); var readUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf8NoHash), 2)); var getHashCode = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetStringHashCode), 2)); var readNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32NoHash), 3)); var writeNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32NoHash), 3)); var readWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32WithHash), 3)); var writeWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32WithHash), 4)); var t = _module.ImportReference(typeof(string)); FieldDefinition GetCacheField(PropertyDefinition prop) { var name = $"cache_{prop.Name}"; var field = prop.DeclaringType.Fields.SingleOrDefault(x => x.Name == name && x.FieldType.IsSame(t)); if (field == null) { field = new FieldDefinition($"cache_{prop.Name}", FieldAttributes.Private, t); field.AddTo(prop.DeclaringType); field.AddAttribute(); } return field; } AddBuiltInType(NetworkTypeInfo.Create(t, wordCount: (member, declaringType) => { return GetCapacity(member, DefaultStringCapacity) + (declaringType.IsValueType ? 1 : 2); }, read: (member, il, context) => { il.AppendMacro(context.LoadAddress()); if (context.IsWriteCompact) { il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable)); il.Append(Call(readUtf8)); il.AppendMacro(context.AddOffset()); if (variable != null) { il.Append(Ldloc(variable)); } } else { il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); if (context.Method.DeclaringType.IsValueType) { il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable)); il.Append(Call(readNoHash)); il.AppendMacro(context.AddOffset()); if (variable != null) { il.Append(Ldloc(variable)); } } else { var field = GetCacheField((PropertyDefinition)member); il.Append(Ldarg_0()); il.Append(Ldflda(field)); il.Append(Call(readWithHash)); il.AppendMacro(context.AddOffset()); il.Append(Ldarg_0()); il.Append(Ldfld(field)); } } }, write: (member, il, context) => { il.AppendMacro(context.LoadAddress()); if (context.IsWriteCompact) { il.AppendMacro(context.LoadValue()); il.Append(Call(writeUtf8)); } else { il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); if (context.Method.DeclaringType.IsValueType) { il.AppendMacro(context.LoadValue()); il.Append(Call(writeNoHash)); } else { il.AppendMacro(context.LoadValue()); il.Append(Ldarg_0()); il.Append(Ldflda(GetCacheField((PropertyDefinition)member))); il.Append(Call(writeWithHash)); } } il.AppendMacro(context.AddOffset()); }, getHashCode: (member, il, context) => { il.AppendMacro(context.LoadValue()); il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); il.Append(Call(getHashCode)); }, compactByteCount: (member, il, context) => { il.AppendMacro(context.LoadValue()); il.Append(Call(getUtf8ByteCount)); }, capacity: member => GetCapacity(member, DefaultStringCapacity), flags: NetworkTypeInfoFlags.HasDynamicRpcSize )); } // System types AddBuiltInType(); // reference types foreach (var t in new [] { typeof(NetworkObject), typeof(NetworkBehaviour) }) { var typeRef = _module.ImportReference(t); TryGetNetworkWrapperType(typeRef, out var wrapInfo); AddBuiltInType(CreateWrappedMeta(typeRef, wrapInfo)); } } const int DefaultCollectionCapacity = 1; internal static int GetCapacity(PropertyDefinition property, int defaultCapacity) { if (property.TryGetAttribute(out var attr)) { return attr.GetAttributeArgument(0); } else { return defaultCapacity; } } bool TryGetNetworkWrapperType(TypeReference type, out NetworkTypeWrapInfo result) { if (type == null) { throw new ArgumentNullException(nameof(type)); } var definition = type.Resolve(); int rawByteCount = 0; bool wrapNeedsRunner = false; if (definition.GetSingleOrDefaultMethodWithAttribute(out var wrapAttribute, out var wrapMethod)) { int argsStart = 0; wrapAttribute.TryGetAttributeProperty(nameof(NetworkSerializeMethodAttribute.MaxSize), out rawByteCount); try { if (wrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is()) { wrapNeedsRunner = true; argsStart = 1; } if (rawByteCount > 0) { wrapMethod.ThrowIfNotStatic() .ThrowIfNotPublic() .ThrowIfParameterCount(argsStart + 2) .ThrowIfParameter(argsStart + 0, type) .ThrowIfParameter(argsStart + 1, typeof(byte*)) .ThrowIfReturnType(typeof(int)); } else { wrapMethod.ThrowIfNotStatic() .ThrowIfNotPublic() .ThrowIfParameterCount(argsStart + 1) .ThrowIfParameter(argsStart + 0, type); } } catch (Exception ex) { throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)} has an invalid signature", ex); } } bool unwrapByRef = false; bool unwrapNeedsRunner = false; if (definition.GetSingleOrDefaultMethodWithAttribute(out var unwrapAttribute, out var unwrapMethod)) { if (wrapMethod == null) { throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)}, but there is no method marked with {nameof(NetworkSerializeMethodAttribute)}: {unwrapMethod}"); } int argsStart = 0; try { if (unwrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is()) { unwrapNeedsRunner = true; argsStart = 1; } if (wrapNeedsRunner) { unwrapMethod.ThrowIfParameterCountLessThan(1) .ThrowIfParameter(0, typeof(NetworkRunner)); } if (rawByteCount > 0) { unwrapMethod.ThrowIfNotStatic() .ThrowIfNotPublic() .ThrowIfReturnType(typeof(int)) .ThrowIfParameterCount(argsStart + 2) .ThrowIfParameter(argsStart + 0, typeof(byte*)) .ThrowIfParameter(argsStart + 1, type, isByReference: true); unwrapByRef = true; } else { unwrapMethod.ThrowIfNotStatic() .ThrowIfNotPublic() .ThrowIfParameter(argsStart + 0, wrapMethod.ReturnType); if (unwrapMethod.Parameters.Count == 2 + argsStart) { unwrapMethod.ThrowIfReturnType(typeof(void)) .ThrowIfParameter(argsStart + 1, type, isByReference: true); unwrapByRef = true; } else { unwrapMethod.ThrowIfParameterCount(argsStart + 1); unwrapMethod.ThrowIfReturnType(type); } } } catch (Exception ex) { throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)} has an invalid signature", ex); } } else if (wrapMethod != null) { throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)}, but there is no method marked with {nameof(NetworkDeserializeMethodAttribute)}: {wrapMethod}"); } if (wrapMethod != null && unwrapMethod != null) { result = new NetworkTypeWrapInfo() { WrapNeedsRunner = wrapNeedsRunner, UnwrapNeedsRunner = unwrapNeedsRunner, UnwrapMethod = _module.ImportReference(unwrapMethod), WrapMethod = _module.ImportReference(wrapMethod), MaxByteCount = rawByteCount, UnwrapByRef = unwrapByRef, WrapperType = rawByteCount > 0 ? null : _module.ImportReference(wrapMethod.ReturnType), WrapperTypeInfo = rawByteCount > 0 ? null : GetInfo(wrapMethod.ReturnType), TargetType = _module.ImportReference(definition), }; return true; } if (definition.BaseType == null) { result = default; return false; } else { return TryGetNetworkWrapperType(definition.BaseType, out result); } } public static int GetCapacity(ICustomAttributeProvider member, int defaultCapacity) { if (member.TryGetAttribute(out var attr)) { if (attr.TryGetAttributeArgument(0, out var result)) { return result; } } return defaultCapacity; } public static bool TryGetCapacity(ICustomAttributeProvider member, out int capacity) { if (member.TryGetAttribute(out var attr)) { if (attr.TryGetAttributeArgument(0, out var result)) { capacity = result; return true; } } capacity = 0; return false; } } } #endif #endregion #region Assets/Photon/Fusion/CodeGen/NetworkTypeWrapInfo.cs #if FUSION_WEAVER && FUSION_HAS_MONO_CECIL namespace Fusion.CodeGen { using Mono.Cecil; public class NetworkTypeWrapInfo { public NetworkTypeInfo WrapperTypeInfo; public TypeReference WrapperType; public TypeReference TargetType; public MethodReference WrapMethod; public MethodReference UnwrapMethod; public bool WrapNeedsRunner; public bool UnwrapNeedsRunner; public bool UnwrapByRef; public int MaxByteCount; public bool IsRaw => MaxByteCount > 0; } } #endif #endregion #endif