2022-03-04 10:52:26 +00:00
using MirrorWeaver ;
2021-12-28 06:30:22 +00:00
using System ;
using System.Collections.Generic ;
2021-12-28 07:03:26 +00:00
using Mono.Cecil ;
using Mono.Cecil.Cil ;
// to use Mono.Cecil.Rocks here, we need to 'override references' in the
2021-12-28 06:30:22 +00:00
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
// otherwise we get an unknown import exception.
2021-12-28 07:03:26 +00:00
using Mono.Cecil.Rocks ;
2021-12-28 06:30:22 +00:00
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public class Readers
{
// Readers are only for this assembly.
// can't be used from another assembly, otherwise we will get:
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
AssemblyDefinition assembly ;
WeaverTypes weaverTypes ;
TypeDefinition GeneratedCodeClass ;
2023-04-26 22:06:17 +00:00
// CHANGED
2022-03-04 10:52:26 +00:00
internal Logger Log ;
2021-12-28 06:30:22 +00:00
Dictionary < TypeReference , MethodReference > readFuncs =
new Dictionary < TypeReference , MethodReference > ( new TypeReferenceComparer ( ) ) ;
public Readers ( AssemblyDefinition assembly , WeaverTypes weaverTypes , TypeDefinition GeneratedCodeClass , Logger Log )
{
this . assembly = assembly ;
this . weaverTypes = weaverTypes ;
this . GeneratedCodeClass = GeneratedCodeClass ;
this . Log = Log ;
}
internal void Register ( TypeReference dataType , MethodReference methodReference )
{
if ( readFuncs . ContainsKey ( dataType ) )
{
// TODO enable this again later.
// Reader has some obsolete functions that were renamed.
// Don't want weaver warnings for all of them.
//Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Readers so import here in case it is used anywhere else
TypeReference imported = assembly . MainModule . ImportReference ( dataType ) ;
readFuncs [ imported ] = methodReference ;
}
void RegisterReadFunc ( TypeReference typeReference , MethodDefinition newReaderFunc )
{
Register ( typeReference , newReaderFunc ) ;
GeneratedCodeClass . Methods . Add ( newReaderFunc ) ;
}
// Finds existing reader for type, if non exists trys to create one
public MethodReference GetReadFunc ( TypeReference variable , ref bool WeavingFailed )
{
if ( readFuncs . TryGetValue ( variable , out MethodReference foundFunc ) )
return foundFunc ;
TypeReference importedVariable = assembly . MainModule . ImportReference ( variable ) ;
return GenerateReader ( importedVariable , ref WeavingFailed ) ;
}
MethodReference GenerateReader ( TypeReference variableReference , ref bool WeavingFailed )
{
// Arrays are special, if we resolve them, we get the element type,
// so the following ifs might choke on it for scriptable objects
// or other objects that require a custom serializer
// thus check if it is an array and skip all the checks.
if ( variableReference . IsArray )
{
if ( variableReference . IsMultidimensionalArray ( ) )
{
Log . Error ( $"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
return GenerateReadCollection ( variableReference , variableReference . GetElementType ( ) , nameof ( NetworkReaderExtensions . ReadArray ) , ref WeavingFailed ) ;
}
TypeDefinition variableDefinition = variableReference . Resolve ( ) ;
// check if the type is completely invalid
if ( variableDefinition = = null )
{
Log . Error ( $"{variableReference.Name} is not a supported type" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
else if ( variableReference . IsByReference )
{
// error??
Log . Error ( $"Cannot pass type {variableReference.Name} by reference" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
// use existing func for known types
if ( variableDefinition . IsEnum )
{
return GenerateEnumReadFunc ( variableReference , ref WeavingFailed ) ;
}
else if ( variableDefinition . Is ( typeof ( ArraySegment < > ) ) )
{
return GenerateArraySegmentReadFunc ( variableReference , ref WeavingFailed ) ;
}
else if ( variableDefinition . Is ( typeof ( List < > ) ) )
{
GenericInstanceType genericInstance = ( GenericInstanceType ) variableReference ;
TypeReference elementType = genericInstance . GenericArguments [ 0 ] ;
return GenerateReadCollection ( variableReference , elementType , nameof ( NetworkReaderExtensions . ReadList ) , ref WeavingFailed ) ;
}
2023-04-26 22:06:17 +00:00
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if ( variableReference . IsDerivedFrom < NetworkBehaviour > ( ) | | variableReference . Is < NetworkBehaviour > ( ) )
2021-12-28 06:30:22 +00:00
{
return GetNetworkBehaviourReader ( variableReference ) ;
}
// check if reader generation is applicable on this type
if ( variableDefinition . IsDerivedFrom < UnityEngine . Component > ( ) )
{
Log . Error ( $"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
if ( variableReference . Is < UnityEngine . Object > ( ) )
{
Log . Error ( $"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
if ( variableReference . Is < UnityEngine . ScriptableObject > ( ) )
{
Log . Error ( $"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
2023-04-26 22:06:17 +00:00
// CHANGED
2022-03-03 03:28:03 +00:00
/ *
2021-12-28 06:30:22 +00:00
if ( variableDefinition . HasGenericParameters )
{
Log . Error ( $"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
2022-03-03 03:28:03 +00:00
* /
2021-12-28 06:30:22 +00:00
if ( variableDefinition . IsInterface )
{
Log . Error ( $"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
if ( variableDefinition . IsAbstract )
{
Log . Error ( $"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader" , variableReference ) ;
WeavingFailed = true ;
return null ;
}
return GenerateClassOrStructReadFunction ( variableReference , ref WeavingFailed ) ;
}
MethodReference GetNetworkBehaviourReader ( TypeReference variableReference )
{
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
MethodReference generic = weaverTypes . readNetworkBehaviourGeneric ;
MethodReference readFunc = generic . MakeGeneric ( assembly . MainModule , variableReference ) ;
// register function so it is added to Reader<T>
// use Register instead of RegisterWriteFunc because this is not a generated function
Register ( variableReference , readFunc ) ;
return readFunc ;
}
MethodDefinition GenerateEnumReadFunc ( TypeReference variable , ref bool WeavingFailed )
{
MethodDefinition readerFunc = GenerateReaderFunction ( variable ) ;
ILProcessor worker = readerFunc . Body . GetILProcessor ( ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ;
TypeReference underlyingType = variable . Resolve ( ) . GetEnumUnderlyingType ( ) ;
MethodReference underlyingFunc = GetReadFunc ( underlyingType , ref WeavingFailed ) ;
worker . Emit ( OpCodes . Call , underlyingFunc ) ;
worker . Emit ( OpCodes . Ret ) ;
return readerFunc ;
}
MethodDefinition GenerateArraySegmentReadFunc ( TypeReference variable , ref bool WeavingFailed )
{
GenericInstanceType genericInstance = ( GenericInstanceType ) variable ;
TypeReference elementType = genericInstance . GenericArguments [ 0 ] ;
MethodDefinition readerFunc = GenerateReaderFunction ( variable ) ;
ILProcessor worker = readerFunc . Body . GetILProcessor ( ) ;
// $array = reader.Read<[T]>()
ArrayType arrayType = elementType . MakeArrayType ( ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Call , GetReadFunc ( arrayType , ref WeavingFailed ) ) ;
// return new ArraySegment<T>($array);
worker . Emit ( OpCodes . Newobj , weaverTypes . ArraySegmentConstructorReference . MakeHostInstanceGeneric ( assembly . MainModule , genericInstance ) ) ;
worker . Emit ( OpCodes . Ret ) ;
return readerFunc ;
}
MethodDefinition GenerateReaderFunction ( TypeReference variable )
{
string functionName = $"_Read_{variable.FullName}" ;
// create new reader for this type
MethodDefinition readerFunc = new MethodDefinition ( functionName ,
MethodAttributes . Public |
MethodAttributes . Static |
MethodAttributes . HideBySig ,
variable ) ;
readerFunc . Parameters . Add ( new ParameterDefinition ( "reader" , ParameterAttributes . None , weaverTypes . Import < NetworkReader > ( ) ) ) ;
readerFunc . Body . InitLocals = true ;
RegisterReadFunc ( variable , readerFunc ) ;
return readerFunc ;
}
MethodDefinition GenerateReadCollection ( TypeReference variable , TypeReference elementType , string readerFunction , ref bool WeavingFailed )
{
MethodDefinition readerFunc = GenerateReaderFunction ( variable ) ;
// generate readers for the element
GetReadFunc ( elementType , ref WeavingFailed ) ;
ModuleDefinition module = assembly . MainModule ;
TypeReference readerExtensions = module . ImportReference ( typeof ( NetworkReaderExtensions ) ) ;
MethodReference listReader = Resolvers . ResolveMethod ( readerExtensions , assembly , Log , readerFunction , ref WeavingFailed ) ;
GenericInstanceMethod methodRef = new GenericInstanceMethod ( listReader ) ;
methodRef . GenericArguments . Add ( elementType ) ;
// generates
// return reader.ReadList<T>();
ILProcessor worker = readerFunc . Body . GetILProcessor ( ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ; // reader
worker . Emit ( OpCodes . Call , methodRef ) ; // Read
worker . Emit ( OpCodes . Ret ) ;
return readerFunc ;
}
MethodDefinition GenerateClassOrStructReadFunction ( TypeReference variable , ref bool WeavingFailed )
{
MethodDefinition readerFunc = GenerateReaderFunction ( variable ) ;
// create local for return value
readerFunc . Body . Variables . Add ( new VariableDefinition ( variable ) ) ;
ILProcessor worker = readerFunc . Body . GetILProcessor ( ) ;
TypeDefinition td = variable . Resolve ( ) ;
if ( ! td . IsValueType )
GenerateNullCheck ( worker , ref WeavingFailed ) ;
CreateNew ( variable , worker , td , ref WeavingFailed ) ;
2023-04-26 22:06:17 +00:00
// CHANGED
2022-03-04 10:52:26 +00:00
this . ReadAllFieldsGeneric ( variable , worker , ref WeavingFailed ) ;
2021-12-28 06:30:22 +00:00
worker . Emit ( OpCodes . Ldloc_0 ) ;
worker . Emit ( OpCodes . Ret ) ;
return readerFunc ;
}
void GenerateNullCheck ( ILProcessor worker , ref bool WeavingFailed )
{
// if (!reader.ReadBoolean()) {
// return null;
// }
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Call , GetReadFunc ( weaverTypes . Import < bool > ( ) , ref WeavingFailed ) ) ;
Instruction labelEmptyArray = worker . Create ( OpCodes . Nop ) ;
worker . Emit ( OpCodes . Brtrue , labelEmptyArray ) ;
// return null
worker . Emit ( OpCodes . Ldnull ) ;
worker . Emit ( OpCodes . Ret ) ;
worker . Append ( labelEmptyArray ) ;
}
// Initialize the local variable with a new instance
void CreateNew ( TypeReference variable , ILProcessor worker , TypeDefinition td , ref bool WeavingFailed )
{
if ( variable . IsValueType )
{
// structs are created with Initobj
worker . Emit ( OpCodes . Ldloca , 0 ) ;
worker . Emit ( OpCodes . Initobj , variable ) ;
}
else if ( td . IsDerivedFrom < UnityEngine . ScriptableObject > ( ) )
{
GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod ( weaverTypes . ScriptableObjectCreateInstanceMethod ) ;
genericInstanceMethod . GenericArguments . Add ( variable ) ;
worker . Emit ( OpCodes . Call , genericInstanceMethod ) ;
worker . Emit ( OpCodes . Stloc_0 ) ;
}
else
{
// classes are created with their constructor
MethodDefinition ctor = Resolvers . ResolveDefaultPublicCtor ( variable ) ;
if ( ctor = = null )
{
2022-01-14 10:39:37 +00:00
Log . Error ( $"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc." , variable ) ;
WeavingFailed = true ;
return ;
2021-12-28 06:30:22 +00:00
}
MethodReference ctorRef = assembly . MainModule . ImportReference ( ctor ) ;
worker . Emit ( OpCodes . Newobj , ctorRef ) ;
worker . Emit ( OpCodes . Stloc_0 ) ;
}
}
void ReadAllFields ( TypeReference variable , ILProcessor worker , ref bool WeavingFailed )
{
2022-03-04 10:13:47 +00:00
foreach ( FieldDefinition field in variable . FindAllPublicFields ( ) )
2021-12-28 06:30:22 +00:00
{
// mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
OpCode opcode = variable . IsValueType ? OpCodes . Ldloca : OpCodes . Ldloc ;
worker . Emit ( opcode , 0 ) ;
MethodReference readFunc = GetReadFunc ( field . FieldType , ref WeavingFailed ) ;
if ( readFunc ! = null )
{
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Call , readFunc ) ;
}
else
{
Log . Error ( $"{field.Name} has an unsupported type" , field ) ;
WeavingFailed = true ;
}
FieldReference fieldRef = assembly . MainModule . ImportReference ( field ) ;
worker . Emit ( OpCodes . Stfld , fieldRef ) ;
}
}
// Save a delegate for each one of the readers into Reader<T>.read
internal void InitializeReaders ( ILProcessor worker )
{
ModuleDefinition module = assembly . MainModule ;
TypeReference genericReaderClassRef = module . ImportReference ( typeof ( Reader < > ) ) ;
System . Reflection . FieldInfo fieldInfo = typeof ( Reader < > ) . GetField ( nameof ( Reader < object > . read ) ) ;
FieldReference fieldRef = module . ImportReference ( fieldInfo ) ;
TypeReference networkReaderRef = module . ImportReference ( typeof ( NetworkReader ) ) ;
TypeReference funcRef = module . ImportReference ( typeof ( Func < , > ) ) ;
MethodReference funcConstructorRef = module . ImportReference ( typeof ( Func < , > ) . GetConstructors ( ) [ 0 ] ) ;
foreach ( KeyValuePair < TypeReference , MethodReference > kvp in readFuncs )
{
TypeReference targetType = kvp . Key ;
MethodReference readFunc = kvp . Value ;
// create a Func<NetworkReader, T> delegate
worker . Emit ( OpCodes . Ldnull ) ;
worker . Emit ( OpCodes . Ldftn , readFunc ) ;
GenericInstanceType funcGenericInstance = funcRef . MakeGenericInstanceType ( networkReaderRef , targetType ) ;
MethodReference funcConstructorInstance = funcConstructorRef . MakeHostInstanceGeneric ( assembly . MainModule , funcGenericInstance ) ;
worker . Emit ( OpCodes . Newobj , funcConstructorInstance ) ;
// save it in Reader<T>.read
GenericInstanceType genericInstance = genericReaderClassRef . MakeGenericInstanceType ( targetType ) ;
FieldReference specializedField = fieldRef . SpecializeField ( assembly . MainModule , genericInstance ) ;
worker . Emit ( OpCodes . Stsfld , specializedField ) ;
}
}
}
}