using FishNet.CodeGenerating.Helping;
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Serializing;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.CodeGenerating.Processing
{
internal class CustomSerializerProcessor
{
#region Types.
internal enum ExtensionType
{
None,
Write,
Read
}
#endregion
internal bool CreateDelegates(TypeDefinition typeDef)
{
bool modified = false;
/* Find all declared methods and register delegates to them.
* After they are all registered create any custom writers
* needed to complete the declared methods. It's important to
* make generated writers after so that a generated method
* isn't made for a type when the user has already made a declared one. */
foreach (MethodDefinition methodDef in typeDef.Methods)
{
ExtensionType extensionType = GetExtensionType(methodDef);
if (extensionType == ExtensionType.None)
continue;
if (CodegenSession.GeneralHelper.CodegenExclude(methodDef))
continue;
MethodReference methodRef = CodegenSession.ImportReference(methodDef);
if (extensionType == ExtensionType.Write)
{
CodegenSession.WriterHelper.AddWriterMethod(methodRef.Parameters[1].ParameterType, methodRef, false, true);
modified = true;
}
else if (extensionType == ExtensionType.Read)
{
CodegenSession.ReaderHelper.AddReaderMethod(methodRef.ReturnType, methodRef, false, true);
modified = true;
}
}
return modified;
}
///
/// Creates serializers for any custom types for declared methods.
///
///
///
internal bool CreateSerializers(TypeDefinition typeDef)
{
bool modified = false;
List<(MethodDefinition, ExtensionType)> declaredMethods = new List<(MethodDefinition, ExtensionType)>();
/* Go through all custom serializers again and see if
* they use any types that the user didn't make a serializer for
* and that there isn't a built-in type for. Create serializers
* for these types. */
foreach (MethodDefinition methodDef in typeDef.Methods)
{
ExtensionType extensionType = GetExtensionType(methodDef);
if (extensionType == ExtensionType.None)
continue;
if (CodegenSession.GeneralHelper.CodegenExclude(methodDef))
continue;
declaredMethods.Add((methodDef, extensionType));
modified = true;
}
//Now that all declared are loaded see if any of them need generated serializers.
foreach ((MethodDefinition methodDef, ExtensionType extensionType) in declaredMethods)
CreateSerializers(extensionType, methodDef);
return modified;
}
///
/// Creates a custom serializer for any types not handled within users declared.
///
///
///
///
///
private void CreateSerializers(ExtensionType extensionType, MethodDefinition methodDef)
{
for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
CheckToModifyInstructions(extensionType, methodDef, ref i);
}
///
/// Checks if instructions need to be modified and does so.
///
///
///
private void CheckToModifyInstructions(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
{
Instruction instruction = methodDef.Body.Instructions[instructionIndex];
//Fields.
if (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld)
CheckFieldReferenceInstruction(extensionType, methodDef, ref instructionIndex);
//Method calls.
else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt)
CheckCallInstruction(extensionType, methodDef, ref instructionIndex, (MethodReference)instruction.Operand);
}
///
/// Checks if a reader or writer must be generated for a field type.
///
///
///
private void CheckFieldReferenceInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
{
Instruction instruction = methodDef.Body.Instructions[instructionIndex];
FieldReference field = (FieldReference)instruction.Operand;
TypeReference type = field.DeclaringType;
if (type.IsType(typeof(GenericWriter<>)) || type.IsType(typeof(GenericReader<>)) && type.IsGenericInstance)
{
GenericInstanceType typeGenericInst = (GenericInstanceType)type;
TypeReference parameterType = typeGenericInst.GenericArguments[0];
CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
}
}
///
/// Checks if a reader or writer must be generated for a call type.
///
///
///
///
///
///
private void CheckCallInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, MethodReference method)
{
if (!method.IsGenericInstance)
return;
//True if call is to read/write.
bool canCreate = (
method.Is(nameof(Writer.Write)) ||
method.Is(nameof(Reader.Read))
);
if (canCreate)
{
GenericInstanceMethod instanceMethod = (GenericInstanceMethod)method;
TypeReference parameterType = instanceMethod.GenericArguments[0];
if (parameterType.IsGenericParameter)
return;
CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
}
}
///
/// Creates a reader or writer for parameterType.
///
///
///
///
///
private void CreateReaderOrWriter(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, TypeReference parameterType)
{
if (!parameterType.IsGenericParameter && parameterType.CanBeResolved())
{
TypeDefinition typeDefinition = parameterType.CachedResolve();
//If class and not value type check for accessible constructor.
if (typeDefinition.IsClass && !typeDefinition.IsValueType)
{
MethodDefinition constructor = typeDefinition.GetMethod(".ctor");
//Constructor is inaccessible, cannot create serializer for type.
if (!constructor.IsPublic)
{
CodegenSession.LogError($"Unable to generator serializers for {typeDefinition.FullName} because it's constructor is not public.");
return;
}
}
ILProcessor processor = methodDef.Body.GetILProcessor();
//Find already existing read or write method.
MethodReference createdMethodRef = (extensionType == ExtensionType.Write) ?
CodegenSession.WriterHelper.GetFavoredWriteMethodReference(parameterType, true) :
CodegenSession.ReaderHelper.GetFavoredReadMethodReference(parameterType, true);
//If a created method already exist nothing further is required.
if (createdMethodRef != null)
{
//Replace call to generic with already made serializer.
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
methodDef.Body.Instructions[instructionIndex] = newInstruction;
return;
}
else
{
createdMethodRef = (extensionType == ExtensionType.Write) ?
CodegenSession.WriterGenerator.CreateWriter(parameterType) :
CodegenSession.ReaderGenerator.CreateReader(parameterType);
}
//If method was created.
if (createdMethodRef != null)
{
/* If an autopack type then we have to inject the
* autopack above the new instruction. */
if (CodegenSession.WriterHelper.IsAutoPackedType(parameterType))
{
AutoPackType packType = CodegenSession.GeneralHelper.GetDefaultAutoPackType(parameterType);
Instruction autoPack = processor.Create(OpCodes.Ldc_I4, (int)packType);
methodDef.Body.Instructions.Insert(instructionIndex, autoPack);
instructionIndex++;
}
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
methodDef.Body.Instructions[instructionIndex] = newInstruction;
}
}
}
///
/// Returns the RPC attribute on a method, if one exist. Otherwise returns null.
///
///
///
private ExtensionType GetExtensionType(MethodDefinition methodDef)
{
bool hasExtensionAttribute = methodDef.HasCustomAttribute();
if (!hasExtensionAttribute)
return ExtensionType.None;
bool write = (methodDef.ReturnType == methodDef.Module.TypeSystem.Void);
//Return None for Mirror types.
#if MIRROR
if (write)
{
if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkWriter")
return ExtensionType.None;
}
else
{
if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkReader")
return ExtensionType.None;
}
#endif
string prefix = (write) ?
WriterHelper.WRITE_PREFIX : ReaderHelper.READ_PREFIX;
//Does not contain prefix.
if (methodDef.Name.Length < prefix.Length || methodDef.Name.Substring(0, prefix.Length) != prefix)
return ExtensionType.None;
//Make sure first parameter is right.
if (methodDef.Parameters.Count >= 1)
{
TypeReference tr = methodDef.Parameters[0].ParameterType;
if (tr.FullName != CodegenSession.WriterHelper.Writer_TypeRef.FullName &&
tr.FullName != CodegenSession.ReaderHelper.Reader_TypeRef.FullName)
return ExtensionType.None;
}
if (write && methodDef.Parameters.Count < 2)
{
CodegenSession.LogError($"{methodDef.FullName} must have at least two parameters, the first being PooledWriter, and second value to write.");
return ExtensionType.None;
}
else if (!write && methodDef.Parameters.Count < 1)
{
CodegenSession.LogError($"{methodDef.FullName} must have at least one parameters, the first being PooledReader.");
return ExtensionType.None;
}
return (write) ? ExtensionType.Write : ExtensionType.Read;
}
}
}