1454 lines
68 KiB
C#
1454 lines
68 KiB
C#
using FishNet.CodeGenerating.Extension;
|
|
using FishNet.CodeGenerating.Helping;
|
|
using FishNet.CodeGenerating.Helping.Extension;
|
|
using FishNet.Configuring;
|
|
using FishNet.Object;
|
|
using FishNet.Object.Synchronizing;
|
|
using FishNet.Object.Synchronizing.Internal;
|
|
using FishNet.Serializing;
|
|
using FishNet.Transporting;
|
|
using MonoFN.Cecil;
|
|
using MonoFN.Cecil.Cil;
|
|
using MonoFN.Cecil.Rocks;
|
|
using MonoFN.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace FishNet.CodeGenerating.Processing
|
|
{
|
|
internal class NetworkBehaviourSyncProcessor : CodegenBase
|
|
{
|
|
#region Reflection references.
|
|
private TypeDefinition SyncBase_TypeDef;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Last instruction to read a sync type.
|
|
/// </summary>
|
|
private Instruction _lastReadInstruction;
|
|
/// <summary>
|
|
/// Sync objects, such as get and set, created during this process. Used to skip modifying created methods.
|
|
/// </summary>
|
|
private List<object> _createdSyncTypeMethodDefinitions = new List<object>();
|
|
/// <summary>
|
|
/// ReadSyncVar methods which have had their base call already made.
|
|
/// </summary>
|
|
private HashSet<MethodDefinition> _baseCalledReadSyncVars = new HashSet<MethodDefinition>();
|
|
#endregion
|
|
|
|
#region Const.
|
|
private const string SYNCVAR_PREFIX = "syncVar___";
|
|
private const string ACCESSOR_PREFIX = "sync___";
|
|
private const string SETREGISTERED_METHOD_NAME = "SetRegistered";
|
|
private const string INITIALIZEINSTANCE_METHOD_NAME = "InitializeInstance";
|
|
private const string GETSERIALIZEDTYPE_METHOD_NAME = "GetSerializedType";
|
|
private const string SENDRATE_NAME = "SendRate";
|
|
private const string READPERMISSIONS_NAME = "ReadPermissions";
|
|
#endregion
|
|
|
|
public override bool ImportReferences()
|
|
{
|
|
System.Type syncBaseType = typeof(SyncBase);
|
|
SyncBase_TypeDef = base.ImportReference(syncBaseType).Resolve();
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes SyncVars and Objects.
|
|
/// </summary>
|
|
/// <param name="typeDef"></param>
|
|
/// <param name="diagnostics"></param>
|
|
internal bool Process(TypeDefinition typeDef, List<(SyncType, ProcessedSync)> allProcessedSyncs, ref uint syncTypeStartCount)
|
|
{
|
|
bool modified = false;
|
|
_createdSyncTypeMethodDefinitions.Clear();
|
|
_lastReadInstruction = null;
|
|
|
|
FieldDefinition[] fieldDefs = typeDef.Fields.ToArray();
|
|
foreach (FieldDefinition fd in fieldDefs)
|
|
{
|
|
CustomAttribute syncAttribute;
|
|
SyncType st = GetSyncType(fd, true, out syncAttribute);
|
|
//Not a sync type field.
|
|
if (st == SyncType.Unset)
|
|
continue;
|
|
|
|
if (st == SyncType.Variable)
|
|
{
|
|
if (TryCreateSyncVar(syncTypeStartCount, allProcessedSyncs, typeDef, fd, syncAttribute))
|
|
syncTypeStartCount++;
|
|
}
|
|
else if (st == SyncType.List || st == SyncType.HashSet)
|
|
{
|
|
if (TryCreateSyncList_SyncHashSet(syncTypeStartCount, allProcessedSyncs, typeDef, fd, syncAttribute, st))
|
|
syncTypeStartCount++;
|
|
}
|
|
else if (st == SyncType.Dictionary)
|
|
{
|
|
if (TryCreateSyncDictionary(syncTypeStartCount, allProcessedSyncs, typeDef, fd, syncAttribute))
|
|
syncTypeStartCount++;
|
|
}
|
|
else if (st == SyncType.Custom)
|
|
{
|
|
if (TryCreateCustom(syncTypeStartCount, allProcessedSyncs, typeDef, fd, syncAttribute))
|
|
syncTypeStartCount++;
|
|
}
|
|
|
|
modified = true;
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets number of SyncTypes by checking for SyncVar/Object attributes. This does not perform error checking.
|
|
/// </summary>
|
|
/// <param name="typeDef"></param>
|
|
/// <returns></returns>
|
|
internal uint GetSyncTypeCount(TypeDefinition typeDef)
|
|
{
|
|
uint count = 0;
|
|
foreach (FieldDefinition fd in typeDef.Fields)
|
|
{
|
|
if (HasSyncTypeAttributeUnchecked(fd))
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces GetSets for methods which may use a SyncType.
|
|
/// </summary>
|
|
internal bool ReplaceGetSets(TypeDefinition typeDef, List<(SyncType, ProcessedSync)> allProcessedSyncs)
|
|
{
|
|
bool modified = false;
|
|
|
|
List<MethodDefinition> modifiableMethods = GetModifiableMethods(typeDef);
|
|
modified |= ReplaceGetSetDirties(modifiableMethods, allProcessedSyncs);
|
|
|
|
return modified;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets SyncType fieldDef is.
|
|
/// </summary>
|
|
/// <param name="fieldDef"></param>
|
|
/// <param name="diagnostics"></param>
|
|
/// <returns></returns>
|
|
internal SyncType GetSyncType(FieldDefinition fieldDef, bool validate, out CustomAttribute syncAttribute)
|
|
{
|
|
syncAttribute = null;
|
|
//If the generated field for syncvars ignore it.
|
|
if (fieldDef.Name.StartsWith(SYNCVAR_PREFIX))
|
|
return SyncType.Unset;
|
|
|
|
bool syncObject;
|
|
bool error;
|
|
syncAttribute = GetSyncTypeAttribute(fieldDef, out syncObject, out error);
|
|
//Do not perform further checks if an error occurred.
|
|
if (error)
|
|
return SyncType.Unset;
|
|
/* If if attribute is null the code must progress
|
|
* to throw errors when user creates a sync type
|
|
* without using the attribute. */
|
|
if (!validate)
|
|
{
|
|
return (syncAttribute == null) ? SyncType.Unset : SyncType.Custom;
|
|
}
|
|
else
|
|
{
|
|
/* If no attribute make sure the field does not implement
|
|
* ISyncType. If it does then a SyncObject or SyncVar attribute
|
|
* should exist. */
|
|
if (syncAttribute == null)
|
|
{
|
|
TypeDefinition foundSyncBaseTd = fieldDef.FieldType.CachedResolve(base.Session).GetClassInInheritance(base.Session, SyncBase_TypeDef);
|
|
if (foundSyncBaseTd != null && foundSyncBaseTd.ImplementsInterface<ISyncType>())
|
|
base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} is a SyncType but is missing the [SyncVar] or [SyncObject] attribute.");
|
|
|
|
return SyncType.Unset;
|
|
}
|
|
|
|
/* If the attribute is not [SyncObject] then the attribute
|
|
* is [SyncVar]. Only checks that need to be made is to make sure
|
|
* the user is not using a SyncVar attribute when they should be using a SyncObject attribute. */
|
|
if (syncAttribute != null && !syncObject)
|
|
{
|
|
//Make sure syncvar attribute isnt on a sync object.
|
|
if (GetSyncObjectSyncType(syncAttribute) != SyncType.Unset)
|
|
{
|
|
base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} uses a [SyncVar] attribute but should be using [SyncObject].");
|
|
return SyncType.Unset;
|
|
}
|
|
else
|
|
return SyncType.Variable;
|
|
}
|
|
|
|
/* If here could be syncObject
|
|
* or attribute might be null. */
|
|
if (fieldDef.FieldType.CachedResolve(base.Session).ImplementsInterfaceRecursive<ISyncType>(base.Session))
|
|
return GetSyncObjectSyncType(syncAttribute);
|
|
|
|
SyncType GetSyncObjectSyncType(CustomAttribute sa)
|
|
{
|
|
//If attribute is null then throw error.
|
|
if (sa == null)
|
|
{
|
|
base.LogError($"{fieldDef.Name} within {fieldDef.DeclaringType.Name} is a SyncType but [SyncObject] attribute was not found.");
|
|
return SyncType.Unset;
|
|
}
|
|
|
|
if (fieldDef.FieldType.Name == base.GetClass<ObjectHelper>().SyncList_Name)
|
|
{
|
|
return SyncType.List;
|
|
}
|
|
else if (fieldDef.FieldType.Name == base.GetClass<ObjectHelper>().SyncDictionary_Name)
|
|
{
|
|
return SyncType.Dictionary;
|
|
}
|
|
else if (fieldDef.FieldType.Name == base.GetClass<ObjectHelper>().SyncHashSet_Name)
|
|
{
|
|
return SyncType.HashSet;
|
|
}
|
|
//Custom types must also implement ICustomSync.
|
|
else if (fieldDef.FieldType.CachedResolve(base.Session).ImplementsInterfaceRecursive<ICustomSync>(base.Session))
|
|
{
|
|
return SyncType.Custom;
|
|
}
|
|
else
|
|
{
|
|
return SyncType.Unset;
|
|
}
|
|
}
|
|
|
|
//Fall through.
|
|
if (syncAttribute != null)
|
|
base.LogError($"SyncObject attribute found on {fieldDef.Name} within {fieldDef.DeclaringType.Name} but type {fieldDef.FieldType.Name} does not inherit from SyncBase, or if a custom type does not implement ICustomSync.");
|
|
|
|
return SyncType.Unset;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Tries to create a SyncList.
|
|
/// </summary>
|
|
private bool TryCreateCustom(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute)
|
|
{
|
|
//Get the serialized type.
|
|
MethodDefinition getSerialziedTypeMd = originalFieldDef.FieldType.CachedResolve(base.Session).GetMethod(GETSERIALIZEDTYPE_METHOD_NAME);
|
|
MethodReference getSerialziedTypeMr = base.ImportReference(getSerialziedTypeMd);
|
|
Collection<Instruction> instructions = getSerialziedTypeMr.CachedResolve(base.Session).Body.Instructions;
|
|
|
|
bool canSerialize = false;
|
|
TypeReference serializedDataTypeRef = null;
|
|
/* If the user is returning null then
|
|
* they are indicating a custom serializer does not
|
|
* have to be implemented. */
|
|
if (instructions.Count == 2 && instructions[0].OpCode == OpCodes.Ldnull && instructions[1].OpCode == OpCodes.Ret)
|
|
{
|
|
canSerialize = true;
|
|
}
|
|
//If not returning null then make a serializer for return type.
|
|
else
|
|
{
|
|
foreach (Instruction item in instructions)
|
|
{
|
|
//This token references the type.
|
|
if (item.OpCode == OpCodes.Ldtoken)
|
|
{
|
|
TypeReference importedTr = null;
|
|
if (item.Operand is TypeDefinition td)
|
|
importedTr = base.ImportReference(td);
|
|
else if (item.Operand is TypeReference tr)
|
|
importedTr = base.ImportReference(tr);
|
|
|
|
if (importedTr != null)
|
|
{
|
|
serializedDataTypeRef = importedTr;
|
|
canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(serializedDataTypeRef, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Wasn't able to determine serialized type, or create it.
|
|
if (!canSerialize)
|
|
{
|
|
base.LogError($"Custom SyncObject {originalFieldDef.Name} data type {serializedDataTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
|
return false;
|
|
}
|
|
|
|
bool result = InitializeCustom(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
|
|
if (result)
|
|
allProcessedSyncs.Add((SyncType.Custom, null));
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Tries to create a SyncList.
|
|
/// </summary>
|
|
private bool TryCreateSyncList_SyncHashSet(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute, SyncType syncType)
|
|
{
|
|
//Import fieldType to module.
|
|
TypeReference fieldTypeTr = base.ImportReference(originalFieldDef.FieldType);
|
|
//Make sure type can be serialized.
|
|
GenericInstanceType tmpGenerinstanceType = fieldTypeTr as GenericInstanceType;
|
|
//this returns the correct data type, eg SyncList<int> would return int.
|
|
TypeReference dataTypeRef = base.ImportReference(tmpGenerinstanceType.GenericArguments[0]);
|
|
|
|
bool canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(dataTypeRef, true);
|
|
if (!canSerialize)
|
|
{
|
|
base.LogError($"SyncObject {originalFieldDef.Name} data type {dataTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
|
return false;
|
|
}
|
|
|
|
bool result = InitializeSyncList_SyncHashSet(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
|
|
if (result)
|
|
allProcessedSyncs.Add((syncType, null));
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to create a SyncDictionary.
|
|
/// </summary>
|
|
private bool TryCreateSyncDictionary(uint syncTypeCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncAttribute)
|
|
{
|
|
//Make sure type can be serialized.
|
|
GenericInstanceType tmpGenerinstanceType = originalFieldDef.FieldType as GenericInstanceType;
|
|
//this returns the correct data type, eg SyncList<int> would return int.
|
|
TypeReference keyTypeRef = tmpGenerinstanceType.GenericArguments[0];
|
|
TypeReference valueTypeRef = tmpGenerinstanceType.GenericArguments[1];
|
|
|
|
bool canSerialize;
|
|
//Check key serializer.
|
|
canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(keyTypeRef, true);
|
|
if (!canSerialize)
|
|
{
|
|
base.LogError($"SyncObject {originalFieldDef.Name} key type {keyTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
|
return false;
|
|
}
|
|
//Check value serializer.
|
|
canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(valueTypeRef, true);
|
|
if (!canSerialize)
|
|
{
|
|
base.LogError($"SyncObject {originalFieldDef.Name} value type {valueTypeRef.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
|
return false;
|
|
}
|
|
|
|
bool result = InitializeSyncDictionary(syncTypeCount, typeDef, originalFieldDef, syncAttribute);
|
|
if (result)
|
|
allProcessedSyncs.Add((SyncType.Dictionary, null));
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Tries to create a SyncVar.
|
|
/// </summary>
|
|
private bool TryCreateSyncVar(uint syncCount, List<(SyncType, ProcessedSync)> allProcessedSyncs, TypeDefinition typeDef, FieldDefinition fieldDef, CustomAttribute syncAttribute)
|
|
{
|
|
bool canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(fieldDef.FieldType, true);
|
|
if (!canSerialize)
|
|
{
|
|
base.LogError($"SyncVar {fieldDef.FullName} field type {fieldDef.FieldType.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
|
return false;
|
|
}
|
|
|
|
if (base.Module != typeDef.Module)
|
|
{
|
|
//Only display warning if field is exposed.
|
|
if (!fieldDef.Attributes.HasFlag(FieldAttributes.Private))
|
|
base.Session.DifferentAssemblySyncVars.Add(fieldDef);
|
|
return false;
|
|
}
|
|
|
|
FieldDefinition syncVarFd;
|
|
MethodReference accessorSetValueMr;
|
|
MethodReference accessorGetValueMr;
|
|
|
|
bool created = CreateSyncVar(syncCount, typeDef, fieldDef, syncAttribute, out syncVarFd, out accessorSetValueMr, out accessorGetValueMr);
|
|
if (created)
|
|
{
|
|
FieldReference originalFr = base.ImportReference(fieldDef);
|
|
allProcessedSyncs.Add((SyncType.Variable, new ProcessedSync(originalFr, syncVarFd, accessorSetValueMr, accessorGetValueMr)));
|
|
}
|
|
|
|
return created;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Returns if fieldDef has a SyncType attribute. No error checking is performed.
|
|
/// </summary>
|
|
/// <param name="fieldDef"></param>
|
|
/// <returns></returns>
|
|
private bool HasSyncTypeAttributeUnchecked(FieldDefinition fieldDef)
|
|
{
|
|
foreach (CustomAttribute customAttribute in fieldDef.CustomAttributes)
|
|
{
|
|
if (base.GetClass<AttributeHelper>().IsSyncVarAttribute(customAttribute.AttributeType.FullName))
|
|
return true;
|
|
else if (base.GetClass<AttributeHelper>().IsSyncObjectAttribute(customAttribute.AttributeType.FullName))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the syncvar attribute on a method, if one exist. Otherwise returns null.
|
|
/// </summary>
|
|
/// <param name="fieldDef"></param>
|
|
/// <returns></returns>
|
|
private CustomAttribute GetSyncTypeAttribute(FieldDefinition fieldDef, out bool syncObject, out bool error)
|
|
{
|
|
CustomAttribute foundAttribute = null;
|
|
//Becomes true if an error occurred during this process.
|
|
error = false;
|
|
syncObject = false;
|
|
|
|
foreach (CustomAttribute customAttribute in fieldDef.CustomAttributes)
|
|
{
|
|
if (base.GetClass<AttributeHelper>().IsSyncVarAttribute(customAttribute.AttributeType.FullName))
|
|
syncObject = false;
|
|
else if (base.GetClass<AttributeHelper>().IsSyncObjectAttribute(customAttribute.AttributeType.FullName))
|
|
syncObject = true;
|
|
else
|
|
continue;
|
|
|
|
//A syncvar attribute already exist.
|
|
if (foundAttribute != null)
|
|
{
|
|
base.LogError($"{fieldDef.Name} cannot have multiple SyncType attributes.");
|
|
error = true;
|
|
}
|
|
//Static.
|
|
if (fieldDef.IsStatic)
|
|
{
|
|
base.LogError($"{fieldDef.Name} SyncType cannot be static.");
|
|
error = true;
|
|
}
|
|
//Generic.
|
|
if (fieldDef.FieldType.IsGenericParameter)
|
|
{
|
|
base.LogError($"{fieldDef.Name} SyncType cannot be be generic.");
|
|
error = true;
|
|
}
|
|
//SyncObject readonly check.
|
|
if (syncObject && !fieldDef.Attributes.HasFlag(FieldAttributes.InitOnly))
|
|
{
|
|
/* If missing readonly see if the user specified
|
|
* they want the object to be serialized. */
|
|
bool requireReadOnly = customAttribute.GetField(nameof(SyncObjectAttribute.RequireReadOnly), true);
|
|
if (requireReadOnly)
|
|
base.LogError($"{fieldDef.Name} SyncObject must be readonly.");
|
|
error = true;
|
|
}
|
|
|
|
|
|
//If all checks passed.
|
|
if (!error)
|
|
foundAttribute = customAttribute;
|
|
}
|
|
|
|
//If an error occurred then reset results.
|
|
if (error)
|
|
foundAttribute = null;
|
|
|
|
return foundAttribute;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a syncVar class for the user's syncvar.
|
|
/// </summary>
|
|
/// <param name="originalFieldDef"></param>
|
|
/// <param name="syncTypeAttribute"></param>
|
|
/// <returns></returns>
|
|
private bool CreateSyncVar(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute syncTypeAttribute, out FieldDefinition createdSyncVarFd, out MethodReference accessorSetValueMethodRef, out MethodReference accessorGetValueMethodRef)
|
|
{
|
|
accessorGetValueMethodRef = null;
|
|
accessorSetValueMethodRef = null;
|
|
CreatedSyncVar createdSyncVar;
|
|
createdSyncVarFd = CreateSyncVarFieldDefinition(typeDef, originalFieldDef, out createdSyncVar);
|
|
|
|
if (createdSyncVarFd != null)
|
|
{
|
|
MethodReference hookMr = GetSyncVarHookMethodReference(typeDef, originalFieldDef, syncTypeAttribute);
|
|
createdSyncVar.HookMr = hookMr;
|
|
|
|
//If accessor was made add it's methods to createdSyncTypeObjects.
|
|
if (CreateSyncVarAccessor(originalFieldDef, createdSyncVarFd, createdSyncVar, out accessorGetValueMethodRef,
|
|
out accessorSetValueMethodRef, hookMr) != null)
|
|
{
|
|
_createdSyncTypeMethodDefinitions.Add(accessorGetValueMethodRef.CachedResolve(base.Session));
|
|
_createdSyncTypeMethodDefinitions.Add(accessorSetValueMethodRef.CachedResolve(base.Session));
|
|
}
|
|
|
|
InitializeSyncVar(syncCount, createdSyncVarFd, typeDef, originalFieldDef, syncTypeAttribute, createdSyncVar);
|
|
|
|
MethodDefinition syncVarReadMd = CreateSyncVarRead(typeDef, syncCount, originalFieldDef, accessorSetValueMethodRef);
|
|
if (syncVarReadMd != null)
|
|
_createdSyncTypeMethodDefinitions.Add(syncVarReadMd);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates or gets a SyncType class for originalFieldDef.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private FieldDefinition CreateSyncVarFieldDefinition(TypeDefinition typeDef, FieldDefinition originalFieldDef, out CreatedSyncVar createdSyncVar)
|
|
{
|
|
createdSyncVar = base.GetClass<CreatedSyncVarGenerator>().GetCreatedSyncVar(originalFieldDef, true);
|
|
if (createdSyncVar == null)
|
|
return null;
|
|
|
|
originalFieldDef.Attributes &= ~FieldAttributes.Private;
|
|
originalFieldDef.Attributes |= FieldAttributes.Public;
|
|
|
|
FieldDefinition createdFieldDef = new FieldDefinition($"{SYNCVAR_PREFIX}{originalFieldDef.Name}", originalFieldDef.Attributes, createdSyncVar.SyncVarGit);
|
|
if (createdFieldDef == null)
|
|
{
|
|
base.LogError($"Could not create field for Sync type {originalFieldDef.FieldType.FullName}, name of {originalFieldDef.Name}.");
|
|
return null;
|
|
}
|
|
|
|
typeDef.Fields.Add(createdFieldDef);
|
|
return createdFieldDef;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates and gets the hook MethodReference for a SyncVar if available.
|
|
/// </summary>
|
|
/// <param name="moduleDef"></param>
|
|
/// <param name="typeDef"></param>
|
|
/// <param name="attribute"></param>
|
|
/// <returns></returns>
|
|
private MethodReference GetSyncVarHookMethodReference(TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
|
|
{
|
|
string hook = attribute.GetField("OnChange", string.Empty);
|
|
//No hook is specified.
|
|
if (string.IsNullOrEmpty(hook))
|
|
return null;
|
|
|
|
MethodDefinition md = typeDef.GetMethod(hook);
|
|
|
|
if (md != null)
|
|
{
|
|
string incorrectParametersMsg = $"OnChange method for {originalFieldDef.FullName} must contain 3 parameters in order of {originalFieldDef.FieldType.Name} oldValue, {originalFieldDef.FieldType.Name} newValue, {base.Module.TypeSystem.Boolean} asServer.";
|
|
//Not correct number of parameters.
|
|
if (md.Parameters.Count != 3)
|
|
{
|
|
base.LogError(incorrectParametersMsg);
|
|
return null;
|
|
}
|
|
/* Check if any parameters are not
|
|
* the expected type. */
|
|
if (md.Parameters[0].ParameterType.CachedResolve(base.Session) != originalFieldDef.FieldType.CachedResolve(base.Session) ||
|
|
md.Parameters[1].ParameterType.CachedResolve(base.Session) != originalFieldDef.FieldType.CachedResolve(base.Session) ||
|
|
md.Parameters[2].ParameterType.CachedResolve(base.Session) != base.Module.TypeSystem.Boolean.CachedResolve(base.Session))
|
|
{
|
|
base.LogError(incorrectParametersMsg);
|
|
return null;
|
|
}
|
|
|
|
//If here everything checks out, return a method reference to hook method.
|
|
return base.ImportReference(md);
|
|
}
|
|
//Hook specified but no method found.
|
|
else
|
|
{
|
|
base.LogError($"Could not find method name {hook} for SyncType {originalFieldDef.FullName}.");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates accessor for a SyncVar.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private FieldDefinition CreateSyncVarAccessor(FieldDefinition originalFd, FieldDefinition createdSyncVarFd, CreatedSyncVar createdSyncVar, out MethodReference accessorGetValueMr, out MethodReference accessorSetValueMr, MethodReference hookMr)
|
|
{
|
|
/* Create and add property definition. */
|
|
PropertyDefinition createdPropertyDef = new PropertyDefinition($"SyncAccessor_{originalFd.Name}", PropertyAttributes.None, originalFd.FieldType);
|
|
createdPropertyDef.DeclaringType = originalFd.DeclaringType;
|
|
//add the methods and property to the type.
|
|
originalFd.DeclaringType.Properties.Add(createdPropertyDef);
|
|
|
|
ILProcessor processor;
|
|
|
|
/* Get method for property definition. */
|
|
MethodDefinition createdGetMethodDef = originalFd.DeclaringType.AddMethod($"{ACCESSOR_PREFIX}get_value_{originalFd.Name}", MethodAttributes.Public |
|
|
MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
|
originalFd.FieldType);
|
|
createdGetMethodDef.SemanticsAttributes = MethodSemanticsAttributes.Getter;
|
|
|
|
processor = createdGetMethodDef.Body.GetILProcessor();
|
|
processor.Emit(OpCodes.Ldarg_0); //this.
|
|
processor.Emit(OpCodes.Ldfld, originalFd);
|
|
processor.Emit(OpCodes.Ret);
|
|
accessorGetValueMr = base.ImportReference(createdGetMethodDef);
|
|
//Add getter to properties.
|
|
createdPropertyDef.GetMethod = createdGetMethodDef;
|
|
|
|
/* Set method. */
|
|
//Create the set method
|
|
MethodDefinition createdSetMethodDef = originalFd.DeclaringType.AddMethod($"{ACCESSOR_PREFIX}set_value_{originalFd.Name}", MethodAttributes.Public |
|
|
MethodAttributes.SpecialName |
|
|
MethodAttributes.HideBySig);
|
|
createdSetMethodDef.SemanticsAttributes = MethodSemanticsAttributes.Setter;
|
|
|
|
ParameterDefinition valueParameterDef = base.GetClass<GeneralHelper>().CreateParameter(createdSetMethodDef, originalFd.FieldType, "value");
|
|
ParameterDefinition calledByUserParameterDef = base.GetClass<GeneralHelper>().CreateParameter(createdSetMethodDef, typeof(bool), "asServer");
|
|
processor = createdSetMethodDef.Body.GetILProcessor();
|
|
|
|
/* Assign to new value. Do this first because SyncVar<T> calls hook
|
|
* and value needs to be updated before hook. Only update
|
|
* value if calledByUser(asServer) or (!calledByUser && !base.IsServer).
|
|
* This ensures clientHost will not overwrite server value. */
|
|
|
|
Instruction afterChangeFieldInst = processor.Create(OpCodes.Nop);
|
|
Instruction beforeChangeFieldInst = processor.Create(OpCodes.Nop);
|
|
//if (calledByUser || !base.IsServer)
|
|
processor.Emit(OpCodes.Ldarg, calledByUserParameterDef);
|
|
processor.Emit(OpCodes.Brtrue, beforeChangeFieldInst);
|
|
processor.Emit(OpCodes.Ldarg_0); //this.
|
|
processor.Emit(OpCodes.Call, base.GetClass<NetworkBehaviourHelper>().IsServer_MethodRef);
|
|
processor.Emit(OpCodes.Brtrue, afterChangeFieldInst);
|
|
|
|
// _originalField = value;
|
|
processor.Append(beforeChangeFieldInst);
|
|
processor.Emit(OpCodes.Ldarg_0); //this.
|
|
processor.Emit(OpCodes.Ldarg, valueParameterDef);
|
|
processor.Emit(OpCodes.Stfld, originalFd);
|
|
processor.Append(afterChangeFieldInst);
|
|
|
|
Instruction retInst = processor.Create(OpCodes.Ret);
|
|
|
|
if (!Configuration.Configurations.CodeStripping.IsBuilding)
|
|
{
|
|
processor.Emit(OpCodes.Call, base.GetClass<GeneralHelper>().Application_IsPlaying_MethodRef);
|
|
processor.Emit(OpCodes.Brfalse_S, retInst);
|
|
}
|
|
// SyncVar<>.SetValue(....);
|
|
processor.Emit(OpCodes.Ldarg_0); //this.
|
|
processor.Emit(OpCodes.Ldfld, createdSyncVarFd);
|
|
processor.Emit(OpCodes.Ldarg, valueParameterDef);
|
|
processor.Emit(OpCodes.Ldarg, calledByUserParameterDef);
|
|
processor.Emit(createdSyncVar.SetValueMr.GetCallOpCode(base.Session), createdSyncVar.SetValueMr);
|
|
|
|
processor.Append(retInst);
|
|
accessorSetValueMr = base.ImportReference(createdSetMethodDef);
|
|
//Add setter to properties.
|
|
createdPropertyDef.SetMethod = createdSetMethodDef;
|
|
|
|
return originalFd;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets methods used from SyncBase for typeDef.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal bool SetSyncBaseMethods(TypeDefinition typeDef, out MethodReference setRegisteredMr, out MethodReference initializeInstanceMr)
|
|
{
|
|
setRegisteredMr = null;
|
|
initializeInstanceMr = null;
|
|
//Find the SyncBase class.
|
|
TypeDefinition syncBaseTd = null;
|
|
TypeDefinition copyTd = typeDef;
|
|
do
|
|
{
|
|
if (copyTd.Name == nameof(SyncBase))
|
|
{
|
|
syncBaseTd = copyTd;
|
|
break;
|
|
}
|
|
copyTd = copyTd.GetNextBaseTypeDefinition(base.Session);
|
|
} while (copyTd != null);
|
|
|
|
//If SyncBase isn't found.
|
|
if (syncBaseTd == null)
|
|
{
|
|
base.LogError($"Could not find SyncBase within type {typeDef.FullName}.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
//InitializeInstance.
|
|
initializeInstanceMr = syncBaseTd.GetMethodReference(base.Session, INITIALIZEINSTANCE_METHOD_NAME);
|
|
//SetSyncIndex.
|
|
setRegisteredMr = syncBaseTd.GetMethodReference(base.Session, SETREGISTERED_METHOD_NAME);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a custom SyncObject.
|
|
/// </summary>
|
|
internal bool InitializeCustom(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
|
|
{
|
|
float sendRate = 0.1f;
|
|
WritePermission writePermissions = WritePermission.ServerOnly;
|
|
ReadPermission readPermissions = ReadPermission.Observers;
|
|
Channel channel = Channel.Reliable;
|
|
//If attribute isn't null then override values.
|
|
if (attribute != null)
|
|
{
|
|
sendRate = attribute.GetField(SENDRATE_NAME, -1f);
|
|
writePermissions = WritePermission.ServerOnly;
|
|
readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
|
|
channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
|
|
}
|
|
|
|
//Set needed methods from syncbase.
|
|
MethodReference setSyncIndexMr;
|
|
MethodReference initializeInstanceMr;
|
|
if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setSyncIndexMr, out initializeInstanceMr))
|
|
return false;
|
|
|
|
MethodDefinition injectionMethodDef;
|
|
ILProcessor processor;
|
|
|
|
uint hash = (uint)syncCount;
|
|
List<Instruction> insts = new List<Instruction>();
|
|
|
|
/* Initialize with attribute settings. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
//
|
|
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
|
|
insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
|
|
processor.InsertFirst(insts);
|
|
|
|
insts.Clear();
|
|
/* Set NetworkBehaviour and SyncIndex to use. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
//
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(setSyncIndexMr.GetCallOpCode(base.Session), setSyncIndexMr));
|
|
|
|
processor.InsertLast(insts);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a SyncList.
|
|
/// </summary>
|
|
internal bool InitializeSyncList_SyncHashSet(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
|
|
{
|
|
float sendRate = 0.1f;
|
|
WritePermission writePermissions = WritePermission.ServerOnly;
|
|
ReadPermission readPermissions = ReadPermission.Observers;
|
|
Channel channel = Channel.Reliable;
|
|
//If attribute isn't null then override values.
|
|
if (attribute != null)
|
|
{
|
|
sendRate = attribute.GetField(SENDRATE_NAME, -1f);
|
|
writePermissions = WritePermission.ServerOnly;
|
|
readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
|
|
channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
|
|
}
|
|
|
|
//This import shouldn't be needed but cecil is stingy so rather be safe than sorry.
|
|
base.ImportReference(originalFieldDef);
|
|
|
|
//Set needed methods from syncbase.
|
|
MethodReference setSyncIndexMr;
|
|
MethodReference initializeInstanceMr;
|
|
if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setSyncIndexMr, out initializeInstanceMr))
|
|
return false;
|
|
|
|
MethodDefinition injectionMethodDef;
|
|
ILProcessor processor;
|
|
|
|
uint hash = (uint)syncCount;
|
|
List<Instruction> insts = new List<Instruction>();
|
|
|
|
/* Initialize with attribute settings. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
//InitializeInstance.
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
|
|
insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
|
|
processor.InsertFirst(insts);
|
|
|
|
insts.Clear();
|
|
/* Set NetworkBehaviour and SyncIndex to use. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(setSyncIndexMr.GetCallOpCode(base.Session), setSyncIndexMr));
|
|
|
|
processor.InsertLast(insts);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a SyncDictionary.
|
|
/// </summary>
|
|
internal bool InitializeSyncDictionary(uint syncCount, TypeDefinition typeDef, FieldDefinition originalFieldDef, CustomAttribute attribute)
|
|
{
|
|
float sendRate = 0.1f;
|
|
WritePermission writePermissions = WritePermission.ServerOnly;
|
|
ReadPermission readPermissions = ReadPermission.Observers;
|
|
Channel channel = Channel.Reliable;
|
|
//If attribute isn't null then override values.
|
|
if (attribute != null)
|
|
{
|
|
sendRate = attribute.GetField(SENDRATE_NAME, -1f);
|
|
writePermissions = WritePermission.ServerOnly;
|
|
readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
|
|
channel = Channel.Reliable; //attribute.GetField("Channel", Channel.Reliable);
|
|
}
|
|
|
|
//This import shouldn't be needed but cecil is stingy so rather be safe than sorry.
|
|
base.ImportReference(originalFieldDef);
|
|
|
|
//Set needed methods from syncbase.
|
|
MethodReference setRegisteredMr;
|
|
MethodReference initializeInstanceMr;
|
|
if (!SetSyncBaseMethods(originalFieldDef.FieldType.CachedResolve(base.Session), out setRegisteredMr, out initializeInstanceMr))
|
|
return false;
|
|
|
|
MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
|
ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
uint hash = (uint)syncCount;
|
|
List<Instruction> insts = new List<Instruction>();
|
|
|
|
/* Initialize with attribute settings. */
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4_1)); //true for syncObject.
|
|
insts.Add(processor.Create(OpCodes.Call, initializeInstanceMr));
|
|
processor.InsertFirst(insts);
|
|
|
|
insts.Clear();
|
|
/* Set NetworkBehaviour and SyncIndex to use. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFieldDef));
|
|
insts.Add(processor.Create(setRegisteredMr.GetCallOpCode(base.Session), setRegisteredMr));
|
|
|
|
processor.InsertFirst(insts);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a SyncVar<>.
|
|
/// </summary>
|
|
internal void InitializeSyncVar(uint syncCount, FieldDefinition createdFd, TypeDefinition typeDef, FieldDefinition originalFd, CustomAttribute attribute, CreatedSyncVar createdSyncVar)
|
|
{
|
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|
|
|
//Get all possible attributes.
|
|
float sendRate = attribute.GetField(SENDRATE_NAME, -1f);
|
|
WritePermission writePermissions = WritePermission.ServerOnly;
|
|
ReadPermission readPermissions = attribute.GetField(READPERMISSIONS_NAME, ReadPermission.Observers);
|
|
Channel channel = attribute.GetField("Channel", Channel.Reliable);
|
|
|
|
MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
|
ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
uint hash = (uint)syncCount;
|
|
List<Instruction> insts = new List<Instruction>();
|
|
//Initialize fieldDef with values from attribute.
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this again for NetworkBehaviour.
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)writePermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)readPermissions));
|
|
insts.Add(processor.Create(OpCodes.Ldc_R4, sendRate));
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, (int)channel));
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
|
|
insts.Add(processor.Create(OpCodes.Ldfld, originalFd.MakeHostGenericIfNeeded(base.Session))); //initial value.
|
|
insts.Add(processor.Create(OpCodes.Newobj, createdSyncVar.ConstructorMr));
|
|
insts.Add(processor.Create(OpCodes.Stfld, createdFd.MakeHostGenericIfNeeded(base.Session)));
|
|
|
|
//If there is a hook method.
|
|
if (createdSyncVar.HookMr != null)
|
|
{
|
|
//SyncVar<dataType>.add_OnChanged (event).
|
|
TypeDefinition svTd = base.GetClass<CreatedSyncVarGenerator>().SyncVar_TypeRef.CachedResolve(base.Session);
|
|
GenericInstanceType svGit = svTd.MakeGenericInstanceType(new TypeReference[] { originalFd.FieldType });
|
|
MethodDefinition addMd = svTd.GetMethod("add_OnChange");
|
|
MethodReference syncVarAddMr = addMd.MakeHostInstanceGeneric(base.Session, svGit);
|
|
|
|
//Action<dataType, dataType, bool> constructor.
|
|
GenericInstanceType actionGit = gh.ActionT3_TypeRef.MakeGenericInstanceType(
|
|
originalFd.FieldType, originalFd.FieldType,
|
|
base.GetClass<GeneralHelper>().GetTypeReference(typeof(bool)));
|
|
MethodReference gitActionCtorMr = gh.ActionT3Constructor_MethodRef.MakeHostInstanceGeneric(base.Session, actionGit);
|
|
|
|
// syncVar___field.OnChanged += UserHookMethod;
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0));
|
|
insts.Add(processor.Create(OpCodes.Ldfld, createdFd));
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0));
|
|
|
|
//Load the callback function.
|
|
MethodDefinition hookMd = createdSyncVar.HookMr.CachedResolve(base.Session);
|
|
OpCode ldOpCode;
|
|
if (hookMd.IsVirtual)
|
|
{
|
|
insts.Add(processor.Create(OpCodes.Dup));
|
|
ldOpCode = OpCodes.Ldvirtftn;
|
|
}
|
|
else
|
|
{
|
|
ldOpCode = OpCodes.Ldftn;
|
|
}
|
|
insts.Add(processor.Create(ldOpCode, hookMd));
|
|
|
|
insts.Add(processor.Create(OpCodes.Newobj, gitActionCtorMr));
|
|
insts.Add(processor.Create(syncVarAddMr.GetCallOpCode(base.Session), syncVarAddMr));
|
|
}
|
|
processor.InsertFirst(insts);
|
|
|
|
insts.Clear();
|
|
/* Set NetworkBehaviour and SyncIndex to use. */
|
|
injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
|
processor = injectionMethodDef.Body.GetILProcessor();
|
|
|
|
//Set NB and SyncIndex to SyncVar<>.
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
|
insts.Add(processor.Create(OpCodes.Ldfld, createdFd));
|
|
insts.Add(processor.Create(createdSyncVar.SetSyncIndexMr.GetCallOpCode(base.Session), createdSyncVar.SetSyncIndexMr));
|
|
|
|
processor.InsertFirst(insts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces GetSets for methods which may use a SyncType.
|
|
/// </summary>
|
|
/// <param name="modifiableMethods"></param>
|
|
/// <param name="processedSyncs"></param>
|
|
internal bool ReplaceGetSetDirties(List<MethodDefinition> modifiableMethods, List<(SyncType, ProcessedSync)> processedSyncs)
|
|
{
|
|
//Build processed syncs into dictionary for quicker loookups.
|
|
Dictionary<FieldReference, List<ProcessedSync>> processedLookup = new Dictionary<FieldReference, List<ProcessedSync>>();
|
|
foreach ((SyncType st, ProcessedSync ps) in processedSyncs)
|
|
{
|
|
if (st != SyncType.Variable)
|
|
continue;
|
|
|
|
List<ProcessedSync> result;
|
|
if (!processedLookup.TryGetValue(ps.OriginalFieldRef, out result))
|
|
{
|
|
result = new List<ProcessedSync>() { ps };
|
|
processedLookup.Add(ps.OriginalFieldRef, result);
|
|
}
|
|
|
|
result.Add(ps);
|
|
}
|
|
|
|
bool modified = false;
|
|
foreach (MethodDefinition methodDef in modifiableMethods)
|
|
modified |= ReplaceGetSetDirty(methodDef, processedLookup);
|
|
|
|
return modified;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces GetSets for a method which may use a SyncType.
|
|
/// </summary>
|
|
/// <param name="methodDef"></param>
|
|
/// <param name="processedLookup"></param>
|
|
private bool ReplaceGetSetDirty(MethodDefinition methodDef, Dictionary<FieldReference, List<ProcessedSync>> processedLookup)
|
|
{
|
|
if (methodDef == null)
|
|
{
|
|
base.LogError($"An object expecting value was null. Please try saving your script again.");
|
|
return false;
|
|
}
|
|
if (methodDef.IsAbstract)
|
|
return false;
|
|
if (_createdSyncTypeMethodDefinitions.Contains(methodDef))
|
|
return false;
|
|
if (methodDef.Name == NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME)
|
|
return false;
|
|
|
|
|
|
bool modified = false;
|
|
|
|
for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
|
|
{
|
|
Instruction inst = methodDef.Body.Instructions[i];
|
|
|
|
/* Loading a field. (Getter) */
|
|
if (inst.OpCode == OpCodes.Ldfld && inst.Operand is FieldReference opFieldld)
|
|
{
|
|
FieldReference resolvedOpField = opFieldld.CachedResolve(base.Session);
|
|
if (resolvedOpField == null)
|
|
resolvedOpField = opFieldld.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldld.Name, base.Session);
|
|
|
|
modified |= ProcessGetField(methodDef, i, resolvedOpField, processedLookup);
|
|
}
|
|
/* Load address, reference field. */
|
|
else if (inst.OpCode == OpCodes.Ldflda && inst.Operand is FieldReference opFieldlda)
|
|
{
|
|
FieldReference resolvedOpField = opFieldlda.CachedResolve(base.Session);
|
|
if (resolvedOpField == null)
|
|
resolvedOpField = opFieldlda.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldlda.Name, base.Session);
|
|
|
|
modified |= ProcessAddressField(methodDef, i, resolvedOpField, processedLookup);
|
|
}
|
|
/* Setting a field. (Setter) */
|
|
else if (inst.OpCode == OpCodes.Stfld && inst.Operand is FieldReference opFieldst)
|
|
{
|
|
FieldReference resolvedOpField = opFieldst.CachedResolve(base.Session);
|
|
if (resolvedOpField == null)
|
|
resolvedOpField = opFieldst.DeclaringType.CachedResolve(base.Session).GetFieldReference(opFieldst.Name, base.Session);
|
|
|
|
modified |= ProcessSetField(methodDef, i, resolvedOpField, processedLookup);
|
|
}
|
|
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces Gets for a method which may use a SyncType.
|
|
/// </summary>
|
|
/// <param name="methodDef"></param>
|
|
/// <param name="instructionIndex"></param>
|
|
/// <param name="resolvedOpField"></param>
|
|
/// <param name="processedLookup"></param>
|
|
private bool ProcessGetField(MethodDefinition methodDef, int instructionIndex, FieldReference resolvedOpField, Dictionary<FieldReference, List<ProcessedSync>> processedLookup)
|
|
{
|
|
Instruction inst = methodDef.Body.Instructions[instructionIndex];
|
|
|
|
//If was a replaced field.
|
|
if (processedLookup.TryGetValue(resolvedOpField, out List<ProcessedSync> psLst))
|
|
{
|
|
ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
|
|
if (ps == null)
|
|
return false;
|
|
//Don't modify the accessor method.
|
|
if (ps.GetMethodRef.CachedResolve(base.Session) == methodDef)
|
|
return false;
|
|
|
|
//Generic type.
|
|
if (resolvedOpField.DeclaringType.IsGenericInstance || resolvedOpField.DeclaringType.HasGenericParameters)
|
|
{
|
|
FieldReference newField = inst.Operand as FieldReference;
|
|
GenericInstanceType git = (GenericInstanceType)newField.DeclaringType;
|
|
MethodReference syncvarGetMr = ps.GetMethodRef.MakeHostInstanceGeneric(base.Session, git);
|
|
inst.OpCode = syncvarGetMr.GetCallOpCode(base.Session);
|
|
inst.Operand = syncvarGetMr;
|
|
}
|
|
//Strong type.
|
|
else
|
|
{
|
|
inst.OpCode = OpCodes.Call;
|
|
inst.Operand = ps.GetMethodRef;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Replaces Sets for a method which may use a SyncType.
|
|
/// </summary>
|
|
/// <param name="methodDef"></param>
|
|
/// <param name="instructionIndex"></param>
|
|
/// <param name="resolvedOpField"></param>
|
|
/// <param name="processedLookup"></param>
|
|
private bool ProcessSetField(MethodDefinition methodDef, int instructionIndex, FieldReference resolvedOpField, Dictionary<FieldReference, List<ProcessedSync>> processedLookup)
|
|
{
|
|
Instruction inst = methodDef.Body.Instructions[instructionIndex];
|
|
|
|
/* Find any instructions that are jmp/breaking to the one we are modifying.
|
|
* These need to be modified to call changed instruction. */
|
|
HashSet<Instruction> brInstructions = new HashSet<Instruction>();
|
|
foreach (Instruction item in methodDef.Body.Instructions)
|
|
{
|
|
bool canJmp = (item.OpCode == OpCodes.Br || item.OpCode == OpCodes.Brfalse || item.OpCode == OpCodes.Brfalse_S || item.OpCode == OpCodes.Brtrue || item.OpCode == OpCodes.Brtrue_S || item.OpCode == OpCodes.Br_S);
|
|
if (!canJmp)
|
|
continue;
|
|
if (item.Operand == null)
|
|
continue;
|
|
if (item.Operand is Instruction jmpInst && jmpInst == inst)
|
|
brInstructions.Add(item);
|
|
}
|
|
|
|
//If was a replaced field.
|
|
if (processedLookup.TryGetValue(resolvedOpField, out List<ProcessedSync> psLst))
|
|
{
|
|
ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
|
|
if (ps == null)
|
|
return false;
|
|
//Don't modify the accessor method.
|
|
if (ps.SetMethodRef.CachedResolve(base.Session) == methodDef)
|
|
return false;
|
|
ILProcessor processor = methodDef.Body.GetILProcessor();
|
|
|
|
//Generic type.
|
|
if (resolvedOpField.DeclaringType.IsGenericInstance || resolvedOpField.DeclaringType.HasGenericParameters)
|
|
{
|
|
//Pass in true for as server.
|
|
Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
|
|
methodDef.Body.Instructions.Insert(instructionIndex, boolTrueInst);
|
|
|
|
FieldReference newField = inst.Operand as FieldReference;
|
|
GenericInstanceType git = (GenericInstanceType)newField.DeclaringType;
|
|
inst.OpCode = OpCodes.Call;
|
|
inst.Operand = ps.SetMethodRef.MakeHostInstanceGeneric(base.Session, git);
|
|
}
|
|
//Strong typed.
|
|
else
|
|
{
|
|
|
|
|
|
//Pass in true for as server.
|
|
Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
|
|
methodDef.Body.Instructions.Insert(instructionIndex, boolTrueInst);
|
|
inst.OpCode = OpCodes.Call;
|
|
inst.Operand = ps.SetMethodRef;
|
|
}
|
|
|
|
|
|
/* If any instructions are still pointing
|
|
* to modified value then they need to be
|
|
* redirected to the instruction right above it.
|
|
* This is because the boolTrueInst, to indicate
|
|
* value is being set as server. */
|
|
foreach (Instruction item in brInstructions)
|
|
{
|
|
if (item.Operand is Instruction jmpInst && jmpInst == inst)
|
|
{
|
|
//Use the same index that was passed in, which is now one before modified instruction.
|
|
Instruction newInst = methodDef.Body.Instructions[instructionIndex];
|
|
item.Operand = newInst;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces address Sets for a method which may use a SyncType.
|
|
/// </summary>
|
|
/// <param name="methodDef"></param>
|
|
/// <param name="instructionIndex"></param>
|
|
/// <param name="resolvedOpField"></param>
|
|
/// <param name="processedLookup"></param>
|
|
private bool ProcessAddressField(MethodDefinition methodDef, int instructionIndex, FieldReference resolvedOpField, Dictionary<FieldReference, List<ProcessedSync>> processedLookup)
|
|
{
|
|
Instruction inst = methodDef.Body.Instructions[instructionIndex];
|
|
//Check if next instruction is Initobj, which would be setting a new instance.
|
|
Instruction nextInstr = inst.Next;
|
|
if (nextInstr.OpCode != OpCodes.Initobj)
|
|
return false;
|
|
|
|
//If was a replaced field.
|
|
if (processedLookup.TryGetValue(resolvedOpField, out List<ProcessedSync> psLst))
|
|
{
|
|
ProcessedSync ps = GetProcessedSync(resolvedOpField, psLst);
|
|
if (ps == null)
|
|
return false;
|
|
//Don't modify the accessor method.
|
|
if (ps.GetMethodRef.CachedResolve(base.Session) == methodDef || ps.SetMethodRef.CachedResolve(base.Session) == methodDef)
|
|
return false;
|
|
|
|
ILProcessor processor = methodDef.Body.GetILProcessor();
|
|
|
|
VariableDefinition tmpVariableDef = base.GetClass<GeneralHelper>().CreateVariable(methodDef, resolvedOpField.FieldType);
|
|
processor.InsertBefore(inst, processor.Create(OpCodes.Ldloca, tmpVariableDef));
|
|
processor.InsertBefore(inst, processor.Create(OpCodes.Initobj, resolvedOpField.FieldType));
|
|
processor.InsertBefore(inst, processor.Create(OpCodes.Ldloc, tmpVariableDef));
|
|
Instruction newInstr = processor.Create(OpCodes.Call, ps.SetMethodRef);
|
|
processor.InsertBefore(inst, newInstr);
|
|
|
|
/* Pass in true for as server.
|
|
* The instruction index is 3 past ld. */
|
|
Instruction boolTrueInst = processor.Create(OpCodes.Ldc_I4_1);
|
|
methodDef.Body.Instructions.Insert(instructionIndex + 3, boolTrueInst);
|
|
|
|
processor.Remove(inst);
|
|
processor.Remove(nextInstr);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls ReadSyncVar going up the hierarchy.
|
|
/// </summary>
|
|
/// <param name="firstTypeDef"></param>
|
|
internal void CallBaseReadSyncVar(TypeDefinition firstTypeDef)
|
|
{
|
|
string readSyncVarName = base.GetClass<NetworkBehaviourHelper>().ReadSyncVar_MethodRef.Name;
|
|
//TypeDef which needs to make the base call.
|
|
MethodDefinition callerMd = null;
|
|
TypeDefinition copyTd = firstTypeDef;
|
|
do
|
|
{
|
|
MethodDefinition readMd;
|
|
|
|
readMd = copyTd.GetMethod(readSyncVarName);
|
|
if (readMd != null)
|
|
callerMd = readMd;
|
|
|
|
/* If baseType exist and it's not networkbehaviour
|
|
* look into calling the ReadSyncVar method. */
|
|
if (copyTd.BaseType != null && copyTd.BaseType.FullName != base.GetClass<NetworkBehaviourHelper>().FullName)
|
|
{
|
|
readMd = copyTd.BaseType.CachedResolve(base.Session).GetMethod(readSyncVarName);
|
|
//Not all classes will have syncvars to read.
|
|
if (!_baseCalledReadSyncVars.Contains(callerMd) && readMd != null && callerMd != null)
|
|
{
|
|
MethodReference baseReadMr = copyTd.GetMethodReferenceInBase(base.Session, readSyncVarName);// readMd.GetMethodReferenceInBase (base.Session, base.ImportReference(readMd);
|
|
ILProcessor processor = callerMd.Body.GetILProcessor();
|
|
ParameterDefinition asServerPd = callerMd.Parameters[2];
|
|
/* Calls base.ReadSyncVar and if result is true
|
|
* then exit methods. This is because a true return means the base
|
|
* was able to process the syncvar. */
|
|
List<Instruction> baseCallInsts = new List<Instruction>();
|
|
Instruction skipBaseReturn = processor.Create(OpCodes.Nop);
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ldarg_0)); //This.
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ldarg_1)); //PooledReader.
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ldarg_2)); //Index.
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ldarg, asServerPd)); //AsServer.
|
|
baseCallInsts.Add(processor.Create(OpCodes.Call, baseReadMr));
|
|
baseCallInsts.Add(processor.Create(OpCodes.Brfalse_S, skipBaseReturn));
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ldc_I4_1));
|
|
baseCallInsts.Add(processor.Create(OpCodes.Ret));
|
|
baseCallInsts.Add(skipBaseReturn);
|
|
processor.InsertFirst(baseCallInsts);
|
|
|
|
_baseCalledReadSyncVars.Add(callerMd);
|
|
}
|
|
}
|
|
|
|
copyTd = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(copyTd, base.Session);
|
|
|
|
} while (copyTd != null);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a PooledReader locally then sets value to the SyncVars accessor.
|
|
/// </summary>
|
|
/// <param name="typeDef"></param>
|
|
/// <param name="syncIndex"></param>
|
|
/// <param name="originalFieldDef"></param>
|
|
private MethodDefinition CreateSyncVarRead(TypeDefinition typeDef, uint syncIndex, FieldDefinition originalFieldDef, MethodReference accessorSetMethodRef)
|
|
{
|
|
Instruction jmpGoalInst;
|
|
ILProcessor processor;
|
|
|
|
//Get the read sync method, or create it if not present.
|
|
MethodDefinition readSyncMethodDef = typeDef.GetMethod(base.GetClass<NetworkBehaviourHelper>().ReadSyncVar_MethodRef.Name);
|
|
if (readSyncMethodDef == null)
|
|
{
|
|
readSyncMethodDef = new MethodDefinition(base.GetClass<NetworkBehaviourHelper>().ReadSyncVar_MethodRef.Name,
|
|
(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual),
|
|
typeDef.Module.TypeSystem.Void);
|
|
readSyncMethodDef.ReturnType = base.GetClass<GeneralHelper>().GetTypeReference(typeof(bool));
|
|
|
|
base.GetClass<GeneralHelper>().CreateParameter(readSyncMethodDef, typeof(PooledReader));
|
|
base.GetClass<GeneralHelper>().CreateParameter(readSyncMethodDef, typeof(uint));
|
|
base.GetClass<GeneralHelper>().CreateParameter(readSyncMethodDef, typeof(bool));
|
|
readSyncMethodDef.Body.InitLocals = true;
|
|
|
|
processor = readSyncMethodDef.Body.GetILProcessor();
|
|
//Return false as fall through.
|
|
processor.Emit(OpCodes.Ldc_I4_0);
|
|
processor.Emit(OpCodes.Ret);
|
|
|
|
typeDef.Methods.Add(readSyncMethodDef);
|
|
}
|
|
//Already created.
|
|
else
|
|
{
|
|
processor = readSyncMethodDef.Body.GetILProcessor();
|
|
}
|
|
|
|
ParameterDefinition pooledReaderPd = readSyncMethodDef.Parameters[0];
|
|
ParameterDefinition indexPd = readSyncMethodDef.Parameters[1];
|
|
ParameterDefinition asServerPd = readSyncMethodDef.Parameters[2];
|
|
VariableDefinition nextValueVariableDef;
|
|
List<Instruction> readInsts;
|
|
|
|
/* Create a nop instruction placed at the first index of the method.
|
|
* All instructions will be added before this, then the nop will be
|
|
* removed afterwards. This ensures the newer instructions will
|
|
* be above the previous. This let's the IL jump to a previously
|
|
* created read instruction when the latest one fails conditions. */
|
|
Instruction nopPlaceHolderInst = processor.Create(OpCodes.Nop);
|
|
|
|
readSyncMethodDef.Body.Instructions.Insert(0, nopPlaceHolderInst);
|
|
|
|
/* If there was a previously made read then set jmp goal to the first
|
|
* condition for it. Otherwise set it to the last instruction, which would
|
|
* be a ret. Keep in mind if ret has a value we must go back 2 index
|
|
* rather than one. */
|
|
jmpGoalInst = (_lastReadInstruction != null) ? _lastReadInstruction :
|
|
readSyncMethodDef.Body.Instructions[readSyncMethodDef.Body.Instructions.Count - 2];
|
|
|
|
//Check index first. if (index != syncIndex) return
|
|
Instruction nextLastReadInstruction = processor.Create(OpCodes.Ldarg, indexPd);
|
|
processor.InsertBefore(jmpGoalInst, nextLastReadInstruction);
|
|
|
|
uint hash = (uint)syncIndex;
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4, (int)hash));
|
|
//processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4, syncIndex));
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Bne_Un, jmpGoalInst));
|
|
//PooledReader.ReadXXXX()
|
|
readInsts = base.GetClass<ReaderProcessor>().CreateRead(readSyncMethodDef, pooledReaderPd,
|
|
originalFieldDef.FieldType, out nextValueVariableDef);
|
|
if (readInsts == null)
|
|
return null;
|
|
//Add each instruction from CreateRead.
|
|
foreach (Instruction i in readInsts)
|
|
processor.InsertBefore(jmpGoalInst, i);
|
|
|
|
//Call accessor with new value and passing in asServer.
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldarg_0)); //this.
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldloc, nextValueVariableDef));
|
|
//processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4_0));
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldarg, asServerPd));
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Call, accessorSetMethodRef));
|
|
//Return true when able to process.
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ldc_I4_1));
|
|
processor.InsertBefore(jmpGoalInst, processor.Create(OpCodes.Ret));
|
|
|
|
_lastReadInstruction = nextLastReadInstruction;
|
|
processor.Remove(nopPlaceHolderInst);
|
|
|
|
return readSyncMethodDef;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns methods which may be modified by code generation.
|
|
/// </summary>
|
|
/// <param name="typeDef"></param>
|
|
/// <returns></returns>
|
|
private List<MethodDefinition> GetModifiableMethods(TypeDefinition typeDef)
|
|
{
|
|
List<MethodDefinition> results = new List<MethodDefinition>();
|
|
|
|
CheckTypeDefinition(typeDef);
|
|
//Have to add nested types because this are where courotines are stored.
|
|
foreach (TypeDefinition nestedTd in typeDef.NestedTypes)
|
|
CheckTypeDefinition(nestedTd);
|
|
|
|
void CheckTypeDefinition(TypeDefinition td)
|
|
{
|
|
foreach (MethodDefinition methodDef in td.Methods)
|
|
{
|
|
if (methodDef.Name == ".cctor")
|
|
continue;
|
|
if (methodDef.IsConstructor)
|
|
continue;
|
|
if (methodDef.Body == null)
|
|
continue;
|
|
|
|
results.Add(methodDef);
|
|
}
|
|
|
|
foreach (PropertyDefinition propertyDef in td.Properties)
|
|
{
|
|
if (propertyDef.GetMethod != null)
|
|
results.Add(propertyDef.GetMethod);
|
|
if (propertyDef.SetMethod != null)
|
|
results.Add(propertyDef.SetMethod);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the ProcessedSync entry for resolvedOpField.
|
|
/// </summary>
|
|
/// <param name="resolvedOpField"></param>
|
|
/// <param name="psLst"></param>
|
|
/// <returns></returns>
|
|
private ProcessedSync GetProcessedSync(FieldReference resolvedOpField, List<ProcessedSync> psLst)
|
|
{
|
|
for (int i = 0; i < psLst.Count; i++)
|
|
{
|
|
if (psLst[i].OriginalFieldRef == resolvedOpField)
|
|
return psLst[i];
|
|
}
|
|
|
|
/* Fall through, not found. */
|
|
base.LogError($"Unable to find user referenced field for {resolvedOpField.Name}.");
|
|
return null;
|
|
}
|
|
}
|
|
}
|