StationObscurum/Assets/FishNet/CodeGenerating/Processing/NetworkBehaviourSyncProcessor.cs

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;
}
}
}