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 Writers
{
// Writers 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 ;
Logger Log ;
Dictionary < TypeReference , MethodReference > writeFuncs =
new Dictionary < TypeReference , MethodReference > ( new TypeReferenceComparer ( ) ) ;
public Writers ( AssemblyDefinition assembly , WeaverTypes weaverTypes , TypeDefinition GeneratedCodeClass , Logger Log )
{
this . assembly = assembly ;
this . weaverTypes = weaverTypes ;
this . GeneratedCodeClass = GeneratedCodeClass ;
this . Log = Log ;
}
public void Register ( TypeReference dataType , MethodReference methodReference )
{
if ( writeFuncs . ContainsKey ( dataType ) )
{
// TODO enable this again later.
// Writer has some obsolete functions that were renamed.
// Don't want weaver warnings for all of them.
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Writers so import here in case it is used anywhere else
TypeReference imported = assembly . MainModule . ImportReference ( dataType ) ;
writeFuncs [ imported ] = methodReference ;
}
void RegisterWriteFunc ( TypeReference typeReference , MethodDefinition newWriterFunc )
{
Register ( typeReference , newWriterFunc ) ;
GeneratedCodeClass . Methods . Add ( newWriterFunc ) ;
}
// Finds existing writer for type, if non exists trys to create one
public MethodReference GetWriteFunc ( TypeReference variable , ref bool WeavingFailed )
{
if ( writeFuncs . TryGetValue ( variable , out MethodReference foundFunc ) )
return foundFunc ;
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
try
{
TypeReference importedVariable = assembly . MainModule . ImportReference ( variable ) ;
return GenerateWriter ( importedVariable , ref WeavingFailed ) ;
}
catch ( GenerateWriterException e )
{
Log . Error ( e . Message , e . MemberReference ) ;
WeavingFailed = true ;
return null ;
}
}
//Throws GenerateWriterException when writer could not be generated for type
MethodReference GenerateWriter ( TypeReference variableReference , ref bool WeavingFailed )
{
if ( variableReference . IsByReference )
{
throw new GenerateWriterException ( $"Cannot pass {variableReference.Name} by reference" , variableReference ) ;
}
// Arrays are special, if we resolve them, we get the element type,
// e.g. int[] resolves to int
// therefore process this before checks below
if ( variableReference . IsArray )
{
if ( variableReference . IsMultidimensionalArray ( ) )
{
throw new GenerateWriterException ( $"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported" , variableReference ) ;
}
TypeReference elementType = variableReference . GetElementType ( ) ;
return GenerateCollectionWriter ( variableReference , elementType , nameof ( NetworkWriterExtensions . WriteArray ) , ref WeavingFailed ) ;
}
if ( variableReference . Resolve ( ) ? . IsEnum ? ? false )
{
// serialize enum as their base type
return GenerateEnumWriteFunc ( variableReference , ref WeavingFailed ) ;
}
// check for collections
if ( variableReference . Is ( typeof ( ArraySegment < > ) ) )
{
GenericInstanceType genericInstance = ( GenericInstanceType ) variableReference ;
TypeReference elementType = genericInstance . GenericArguments [ 0 ] ;
return GenerateCollectionWriter ( variableReference , elementType , nameof ( NetworkWriterExtensions . WriteArraySegment ) , ref WeavingFailed ) ;
}
if ( variableReference . Is ( typeof ( List < > ) ) )
{
GenericInstanceType genericInstance = ( GenericInstanceType ) variableReference ;
TypeReference elementType = genericInstance . GenericArguments [ 0 ] ;
return GenerateCollectionWriter ( variableReference , elementType , nameof ( NetworkWriterExtensions . WriteList ) , ref WeavingFailed ) ;
}
if ( variableReference . IsDerivedFrom < NetworkBehaviour > ( ) )
{
return GetNetworkBehaviourWriter ( variableReference ) ;
}
// check for invalid types
TypeDefinition variableDefinition = variableReference . Resolve ( ) ;
if ( variableDefinition = = null )
{
throw new GenerateWriterException ( $"{variableReference.Name} is not a supported type. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableDefinition . IsDerivedFrom < UnityEngine . Component > ( ) )
{
throw new GenerateWriterException ( $"Cannot generate writer for component type {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableReference . Is < UnityEngine . Object > ( ) )
{
throw new GenerateWriterException ( $"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableReference . Is < UnityEngine . ScriptableObject > ( ) )
{
throw new GenerateWriterException ( $"Cannot generate writer for {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableDefinition . HasGenericParameters )
{
throw new GenerateWriterException ( $"Cannot generate writer for generic type {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableDefinition . IsInterface )
{
throw new GenerateWriterException ( $"Cannot generate writer for interface {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
if ( variableDefinition . IsAbstract )
{
throw new GenerateWriterException ( $"Cannot generate writer for abstract class {variableReference.Name}. Use a supported type or provide a custom writer" , variableReference ) ;
}
// generate writer for class/struct
return GenerateClassOrStructWriterFunction ( variableReference , ref WeavingFailed ) ;
}
MethodReference GetNetworkBehaviourWriter ( TypeReference variableReference )
{
// all NetworkBehaviours can use the same write function
if ( writeFuncs . TryGetValue ( weaverTypes . Import < NetworkBehaviour > ( ) , out MethodReference func ) )
{
// register function so it is added to writer<T>
// use Register instead of RegisterWriteFunc because this is not a generated function
Register ( variableReference , func ) ;
return func ;
}
else
{
// this exception only happens if mirror is missing the WriteNetworkBehaviour method
throw new MissingMethodException ( $"Could not find writer for NetworkBehaviour" ) ;
}
}
MethodDefinition GenerateEnumWriteFunc ( TypeReference variable , ref bool WeavingFailed )
{
MethodDefinition writerFunc = GenerateWriterFunc ( variable ) ;
ILProcessor worker = writerFunc . Body . GetILProcessor ( ) ;
MethodReference underlyingWriter = GetWriteFunc ( variable . Resolve ( ) . GetEnumUnderlyingType ( ) , ref WeavingFailed ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Ldarg_1 ) ;
worker . Emit ( OpCodes . Call , underlyingWriter ) ;
worker . Emit ( OpCodes . Ret ) ;
return writerFunc ;
}
MethodDefinition GenerateWriterFunc ( TypeReference variable )
{
string functionName = $"_Write_{variable.FullName}" ;
// create new writer for this type
MethodDefinition writerFunc = new MethodDefinition ( functionName ,
MethodAttributes . Public |
MethodAttributes . Static |
MethodAttributes . HideBySig ,
weaverTypes . Import ( typeof ( void ) ) ) ;
writerFunc . Parameters . Add ( new ParameterDefinition ( "writer" , ParameterAttributes . None , weaverTypes . Import < NetworkWriter > ( ) ) ) ;
writerFunc . Parameters . Add ( new ParameterDefinition ( "value" , ParameterAttributes . None , variable ) ) ;
writerFunc . Body . InitLocals = true ;
RegisterWriteFunc ( variable , writerFunc ) ;
return writerFunc ;
}
MethodDefinition GenerateClassOrStructWriterFunction ( TypeReference variable , ref bool WeavingFailed )
{
MethodDefinition writerFunc = GenerateWriterFunc ( variable ) ;
ILProcessor worker = writerFunc . Body . GetILProcessor ( ) ;
if ( ! variable . Resolve ( ) . IsValueType )
WriteNullCheck ( worker , ref WeavingFailed ) ;
2022-01-14 06:38:53 +00:00
if ( ! WriteFromSerialize ( variable . Resolve ( ) , worker ) )
if ( ! WriteAllFields ( variable , worker , ref WeavingFailed ) )
return null ;
2021-12-28 06:30:22 +00:00
worker . Emit ( OpCodes . Ret ) ;
return writerFunc ;
}
void WriteNullCheck ( ILProcessor worker , ref bool WeavingFailed )
{
// if (value == null)
// {
// writer.WriteBoolean(false);
// return;
// }
//
Instruction labelNotNull = worker . Create ( OpCodes . Nop ) ;
worker . Emit ( OpCodes . Ldarg_1 ) ;
worker . Emit ( OpCodes . Brtrue , labelNotNull ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Ldc_I4_0 ) ;
worker . Emit ( OpCodes . Call , GetWriteFunc ( weaverTypes . Import < bool > ( ) , ref WeavingFailed ) ) ;
worker . Emit ( OpCodes . Ret ) ;
worker . Append ( labelNotNull ) ;
// write.WriteBoolean(true);
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Ldc_I4_1 ) ;
worker . Emit ( OpCodes . Call , GetWriteFunc ( weaverTypes . Import < bool > ( ) , ref WeavingFailed ) ) ;
}
2022-01-14 06:38:53 +00:00
// try to use Serialize if this is a message
bool WriteFromSerialize ( TypeDefinition klass , ILProcessor worker )
{
2022-01-14 08:28:49 +00:00
if ( ! klass . IsQSBMessageType ( ) )
2022-01-14 06:38:53 +00:00
return false ;
2022-01-14 07:38:45 +00:00
var toSearch = klass ;
while ( toSearch ! = null ) {
if ( toSearch . HasMethods )
{
foreach ( var method in toSearch . Methods )
{
if ( method . Name ! = "Serialize" )
continue ;
if ( method . Parameters . Count ! = 1 )
continue ;
if ( ! method . Parameters [ 0 ] . ParameterType . Is < NetworkWriter > ( ) )
continue ;
if ( ! method . ReturnType . Is ( typeof ( void ) ) )
continue ;
if ( method . HasGenericParameters )
continue ;
// todo does this even work?
2022-01-14 08:28:49 +00:00
// Log.Error($"! write using {method}", klass);
2022-01-14 07:38:45 +00:00
worker . Emit ( OpCodes . Ldarg_1 ) ; // the klass
worker . Emit ( OpCodes . Ldarg_0 ) ; // the writer
worker . Emit ( OpCodes . Callvirt , method ) ;
return true ;
}
}
2022-01-14 06:38:53 +00:00
2022-01-14 07:38:45 +00:00
// Could not find the method in this class, try the parent
toSearch = toSearch . BaseType ? . Resolve ( ) ;
2022-01-14 06:38:53 +00:00
}
return false ;
}
2021-12-28 06:30:22 +00:00
// Find all fields in type and write them
bool WriteAllFields ( TypeReference variable , ILProcessor worker , ref bool WeavingFailed )
{
uint fields = 0 ;
foreach ( FieldDefinition field in variable . FindAllPublicFields ( ) )
{
MethodReference writeFunc = GetWriteFunc ( field . FieldType , ref WeavingFailed ) ;
// need this null check till later PR when GetWriteFunc throws exception instead
if ( writeFunc = = null ) { return false ; }
FieldReference fieldRef = assembly . MainModule . ImportReference ( field ) ;
fields + + ;
worker . Emit ( OpCodes . Ldarg_0 ) ;
worker . Emit ( OpCodes . Ldarg_1 ) ;
worker . Emit ( OpCodes . Ldfld , fieldRef ) ;
2022-01-14 08:28:49 +00:00
// Log.Error($"! write field {field}", variable);
2021-12-28 06:30:22 +00:00
worker . Emit ( OpCodes . Call , writeFunc ) ;
}
return true ;
}
MethodDefinition GenerateCollectionWriter ( TypeReference variable , TypeReference elementType , string writerFunction , ref bool WeavingFailed )
{
MethodDefinition writerFunc = GenerateWriterFunc ( variable ) ;
MethodReference elementWriteFunc = GetWriteFunc ( elementType , ref WeavingFailed ) ;
MethodReference intWriterFunc = GetWriteFunc ( weaverTypes . Import < int > ( ) , ref WeavingFailed ) ;
// need this null check till later PR when GetWriteFunc throws exception instead
if ( elementWriteFunc = = null )
{
Log . Error ( $"Cannot generate writer for {variable}. Use a supported type or provide a custom writer" , variable ) ;
WeavingFailed = true ;
return writerFunc ;
}
ModuleDefinition module = assembly . MainModule ;
TypeReference readerExtensions = module . ImportReference ( typeof ( NetworkWriterExtensions ) ) ;
MethodReference collectionWriter = Resolvers . ResolveMethod ( readerExtensions , assembly , Log , writerFunction , ref WeavingFailed ) ;
GenericInstanceMethod methodRef = new GenericInstanceMethod ( collectionWriter ) ;
methodRef . GenericArguments . Add ( elementType ) ;
// generates
// reader.WriteArray<T>(array);
ILProcessor worker = writerFunc . Body . GetILProcessor ( ) ;
worker . Emit ( OpCodes . Ldarg_0 ) ; // writer
worker . Emit ( OpCodes . Ldarg_1 ) ; // collection
worker . Emit ( OpCodes . Call , methodRef ) ; // WriteArray
worker . Emit ( OpCodes . Ret ) ;
return writerFunc ;
}
// Save a delegate for each one of the writers into Writer{T}.write
internal void InitializeWriters ( ILProcessor worker )
{
ModuleDefinition module = assembly . MainModule ;
TypeReference genericWriterClassRef = module . ImportReference ( typeof ( Writer < > ) ) ;
System . Reflection . FieldInfo fieldInfo = typeof ( Writer < > ) . GetField ( nameof ( Writer < object > . write ) ) ;
FieldReference fieldRef = module . ImportReference ( fieldInfo ) ;
TypeReference networkWriterRef = module . ImportReference ( typeof ( NetworkWriter ) ) ;
TypeReference actionRef = module . ImportReference ( typeof ( Action < , > ) ) ;
MethodReference actionConstructorRef = module . ImportReference ( typeof ( Action < , > ) . GetConstructors ( ) [ 0 ] ) ;
foreach ( KeyValuePair < TypeReference , MethodReference > kvp in writeFuncs )
{
TypeReference targetType = kvp . Key ;
MethodReference writeFunc = kvp . Value ;
// create a Action<NetworkWriter, T> delegate
worker . Emit ( OpCodes . Ldnull ) ;
worker . Emit ( OpCodes . Ldftn , writeFunc ) ;
GenericInstanceType actionGenericInstance = actionRef . MakeGenericInstanceType ( networkWriterRef , targetType ) ;
MethodReference actionRefInstance = actionConstructorRef . MakeHostInstanceGeneric ( assembly . MainModule , actionGenericInstance ) ;
worker . Emit ( OpCodes . Newobj , actionRefInstance ) ;
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericWriterClassRef . MakeGenericInstanceType ( targetType ) ;
FieldReference specializedField = fieldRef . SpecializeField ( assembly . MainModule , genericInstance ) ;
worker . Emit ( OpCodes . Stsfld , specializedField ) ;
}
}
}
}