Mirror/Unity-Technologies-networking/Weaver/UNetBehaviourProcessor.cs

2331 lines
96 KiB
C#
Raw Normal View History

using System;
using System.Linq;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Unity.UNetWeaver
{
class NetworkBehaviourProcessor
{
List<FieldDefinition> m_SyncVars = new List<FieldDefinition>();
List<FieldDefinition> m_SyncLists = new List<FieldDefinition>();
List<FieldDefinition> m_SyncVarNetIds = new List<FieldDefinition>();
List<MethodDefinition> m_Cmds = new List<MethodDefinition>();
List<MethodDefinition> m_Rpcs = new List<MethodDefinition>();
List<MethodDefinition> m_TargetRpcs = new List<MethodDefinition>();
List<EventDefinition> m_Events = new List<EventDefinition>();
List<FieldDefinition> m_SyncListStaticFields = new List<FieldDefinition>();
List<MethodDefinition> m_CmdInvocationFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_SyncListInvocationFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_RpcInvocationFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_TargetRpcInvocationFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_EventInvocationFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_CmdCallFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_RpcCallFuncs = new List<MethodDefinition>();
List<MethodDefinition> m_TargetRpcCallFuncs = new List<MethodDefinition>();
const int k_SyncVarLimit = 64; // ulong = 64 bytes
int m_QosChannel;
TypeDefinition m_td;
int m_NetIdFieldCounter;
const string k_CmdPrefix = "InvokeCmd";
const string k_RpcPrefix = "InvokeRpc";
const string k_TargetRpcPrefix = "InvokeTargetRpc";
public NetworkBehaviourProcessor(TypeDefinition td)
{
Weaver.DLog(td, "NetworkBehaviourProcessor");
m_td = td;
}
public void Process()
{
if (m_td.HasGenericParameters)
{
Weaver.fail = true;
Log.Error("NetworkBehaviour " + m_td.Name + " cannot have generic parameters");
return;
}
Weaver.DLog(m_td, "Process Start");
ProcessVersion();
ProcessSyncVars();
Weaver.ResetRecursionCount();
ProcessMethods();
ProcessEvents();
if (Weaver.fail)
{
return;
}
GenerateNetworkSettings();
GenerateConstants();
Weaver.ResetRecursionCount();
GenerateSerialization();
if (Weaver.fail)
{
return;
}
GenerateDeSerialization();
GeneratePreStartClient();
Weaver.DLog(m_td, "Process Done");
}
static void WriteClientActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// client active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on server."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
static void WriteServerActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
{
// server active check
worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
worker.Append(worker.Create(OpCodes.Brtrue, label));
worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on client."));
worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
worker.Append(worker.Create(OpCodes.Ret));
worker.Append(label);
}
static void WriteSetupLocals(ILProcessor worker)
{
worker.Body.InitLocals = true;
worker.Body.Variables.Add(new VariableDefinition(Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType)));
}
static void WriteCreateWriter(ILProcessor worker)
{
// create writer
worker.Append(worker.Create(OpCodes.Newobj, Weaver.NetworkWriterCtor));
worker.Append(worker.Create(OpCodes.Stloc_0));
worker.Append(worker.Create(OpCodes.Ldloc_0));
}
static void WriteMessageSize(ILProcessor worker)
{
//write size
worker.Append(worker.Create(OpCodes.Ldc_I4_0));
worker.Append(worker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWriteInt16));
}
static void WriteMessageId(ILProcessor worker, int msgId)
{
// write msg id
worker.Append(worker.Create(OpCodes.Ldloc_0));
worker.Append(worker.Create(OpCodes.Ldc_I4, msgId));
worker.Append(worker.Create(OpCodes.Conv_U2));
worker.Append(worker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWriteInt16));
}
static bool WriteArguments(ILProcessor worker, MethodDefinition md, string errString, bool skipFirst)
{
// write each argument
short argNum = 1;
foreach (ParameterDefinition pd in md.Parameters)
{
if (argNum == 1 && skipFirst)
{
argNum += 1;
continue;
}
MethodReference writeFunc = Weaver.GetWriteFunc(pd.ParameterType);
if (writeFunc == null)
{
Log.Error("WriteArguments for " + md.Name + " type " + pd.ParameterType + " not supported");
Weaver.fail = true;
return false;
}
// use built-in writer func on writer object
worker.Append(worker.Create(OpCodes.Ldloc_0)); // writer object
worker.Append(worker.Create(OpCodes.Ldarg, argNum)); // argument
worker.Append(worker.Create(OpCodes.Call, writeFunc));// call writer func on writer object
argNum += 1;
}
return true;
}
void ProcessVersion()
{
foreach (MethodDefinition md in m_td.Methods)
{
if (md.Name == "UNetVersion")
{
return;
}
}
MethodDefinition versionMethod = new MethodDefinition("UNetVersion", MethodAttributes.Private, Weaver.voidType);
ILProcessor worker = versionMethod.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ret));
m_td.Methods.Add(versionMethod);
}
void GenerateConstants()
{
if (m_Cmds.Count == 0 && m_Rpcs.Count == 0 && m_TargetRpcs.Count == 0 && m_Events.Count == 0 && m_SyncLists.Count == 0)
return;
Weaver.DLog(m_td, " GenerateConstants ");
// find static constructor
MethodDefinition cctor = null;
bool cctorFound = false;
foreach (MethodDefinition md in m_td.Methods)
{
if (md.Name == ".cctor")
{
cctor = md;
cctorFound = true;
}
}
if (cctor != null)
{
// remove the return opcode from end of function. will add our own later.
if (cctor.Body.Instructions.Count != 0)
{
Instruction ret = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1);
}
else
{
Log.Error("No cctor for " + m_td.Name);
Weaver.fail = true;
return;
}
}
}
else
{
// make one!
cctor = new MethodDefinition(".cctor", MethodAttributes.Private |
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
Weaver.voidType);
}
// find instance constructor
MethodDefinition ctor = null;
foreach (MethodDefinition md in m_td.Methods)
{
if (md.Name == ".ctor")
{
ctor = md;
var ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1);
}
else
{
Weaver.fail = true;
Log.Error("No ctor for " + m_td.Name);
return;
}
break;
}
}
if (ctor == null)
{
Weaver.fail = true;
Log.Error("No ctor for " + m_td.Name);
return;
}
ILProcessor ctorWorker = ctor.Body.GetILProcessor();
ILProcessor cctorWorker = cctor.Body.GetILProcessor();
int cmdIndex = 0;
foreach (MethodDefinition md in m_Cmds)
{
FieldReference cmdConstant = Weaver.ResolveField(m_td, "kCmd" + md.Name);
int cmdHash = GetHashCode(m_td.Name + ":Cmd:" + md.Name);
cctorWorker.Append(cctorWorker.Create(OpCodes.Ldc_I4, cmdHash));
cctorWorker.Append(cctorWorker.Create(OpCodes.Stsfld, cmdConstant));
//Weaver.DLog(m_td, " Constant " + m_td.Name + ":Cmd:" + md.Name);
GenerateCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, m_CmdInvocationFuncs[cmdIndex], cmdConstant);
cmdIndex += 1;
}
int rpcIndex = 0;
foreach (MethodDefinition md in m_Rpcs)
{
FieldReference rpcConstant = Weaver.ResolveField(m_td, "kRpc" + md.Name);
int rpcHash = GetHashCode(m_td.Name + ":Rpc:" + md.Name);
cctorWorker.Append(cctorWorker.Create(OpCodes.Ldc_I4, rpcHash));
cctorWorker.Append(cctorWorker.Create(OpCodes.Stsfld, rpcConstant));
//Weaver.DLog(m_td, " Constant " + m_td.Name + ":Rpc:" + md.Name);
GenerateCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, m_RpcInvocationFuncs[rpcIndex], rpcConstant);
rpcIndex += 1;
}
int targetRpcIndex = 0;
foreach (MethodDefinition md in m_TargetRpcs)
{
FieldReference targetRpcConstant = Weaver.ResolveField(m_td, "kTargetRpc" + md.Name);
int targetRpcHash = GetHashCode(m_td.Name + ":TargetRpc:" + md.Name);
cctorWorker.Append(cctorWorker.Create(OpCodes.Ldc_I4, targetRpcHash));
cctorWorker.Append(cctorWorker.Create(OpCodes.Stsfld, targetRpcConstant));
//Weaver.DLog(m_td, " Constant " + m_td.Name + ":Rpc:" + md.Name);
GenerateCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, m_TargetRpcInvocationFuncs[targetRpcIndex], targetRpcConstant);
targetRpcIndex += 1;
}
int eventIndex = 0;
foreach (EventDefinition ed in m_Events)
{
FieldReference eventConstant = Weaver.ResolveField(m_td, "kEvent" + ed.Name);
int eventHash = GetHashCode(m_td.Name + ":Event:" + ed.Name);
cctorWorker.Append(cctorWorker.Create(OpCodes.Ldc_I4, eventHash));
cctorWorker.Append(cctorWorker.Create(OpCodes.Stsfld, eventConstant));
//Weaver.DLog(m_td, " Constant " + m_td.Name + ":Event:" + ed.Name);
GenerateCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, m_EventInvocationFuncs[eventIndex], eventConstant);
eventIndex += 1;
}
int syncListIndex = 0;
foreach (FieldDefinition fd in m_SyncLists)
{
FieldReference listConstant = Weaver.ResolveField(m_td, "kList" + fd.Name);
int listHash = GetHashCode(m_td.Name + ":List:" + fd.Name);
cctorWorker.Append(cctorWorker.Create(OpCodes.Ldc_I4, listHash));
cctorWorker.Append(cctorWorker.Create(OpCodes.Stsfld, listConstant));
//Weaver.DLog(m_td, " Constant " + m_td.Name + ":List:" + fd.Name);
GenerateSyncListInstanceInitializer(ctorWorker, fd);
GenerateCommandDelegate(cctorWorker, Weaver.registerSyncListDelegateReference, m_SyncListInvocationFuncs[syncListIndex], listConstant);
syncListIndex += 1;
}
cctorWorker.Append(cctorWorker.Create(OpCodes.Ret));
if (!cctorFound)
{
m_td.Methods.Add(cctor);
}
// finish ctor
ctorWorker.Append(ctorWorker.Create(OpCodes.Ret));
// in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late
m_td.Attributes = m_td.Attributes & ~TypeAttributes.BeforeFieldInit;
if (m_SyncLists.Count == 0)
return;
// find constructor
MethodDefinition awake = null;
bool awakeFound = false;
foreach (MethodDefinition md in m_td.Methods)
{
if (md.Name == "Awake")
{
awake = md;
awakeFound = true;
}
}
if (awake != null)
{
// remove the return opcode from end of function. will add our own later.
if (awake.Body.Instructions.Count != 0)
{
Instruction ret = awake.Body.Instructions[awake.Body.Instructions.Count - 1];
if (ret.OpCode == OpCodes.Ret)
{
awake.Body.Instructions.RemoveAt(awake.Body.Instructions.Count - 1);
}
else
{
Log.Error("No awake for " + m_td.Name);
Weaver.fail = true;
return;
}
}
}
else
{
awake = new MethodDefinition("Awake", MethodAttributes.Private, Weaver.voidType);
}
ILProcessor awakeWorker = awake.Body.GetILProcessor();
if (!awakeFound)
{
// if we're not directly inheriting from NetworkBehaviour, we have to
// go up the inheritance chain and check for awake calls that we are overriding by "mistake"
CheckForCustomBaseClassAwakeMethod(awakeWorker);
}
int syncListFieldOffset = 0;
foreach (FieldDefinition fd in m_SyncLists)
{
GenerateSyncListInitializer(awakeWorker, fd, syncListFieldOffset);
syncListFieldOffset += 1;
}
awakeWorker.Append(awakeWorker.Create(OpCodes.Ret));
if (!awakeFound)
{
m_td.Methods.Add(awake);
}
}
void CheckForCustomBaseClassAwakeMethod(ILProcessor awakeWorker)
{
// start from base type
var t = m_td.BaseType;
// as long as basetype is not NetworkBehaviour
while (t.FullName != Weaver.NetworkBehaviourType.FullName)
{
// check for the first Awake() method in the hierarchy
var awake = t.Resolve().Methods.FirstOrDefault<MethodDefinition>(x => x.Name == "Awake" && !x.HasParameters);
if (awake != null)
{
// if found, invoke it so we don't loose the behaviour by implicitly overriding Awake
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldarg_0));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, awake));
return;
}
t = t.Resolve().BaseType;
}
}
void GenerateSyncListInstanceInitializer(ILProcessor ctorWorker, FieldDefinition fd)
{
// check the ctor's instructions for an Stfld op-code for this specific sync list field.
foreach (var ins in ctorWorker.Body.Instructions)
{
if (ins.OpCode.Code == Code.Stfld)
{
var field = (FieldDefinition)ins.Operand;
if (field.DeclaringType == fd.DeclaringType && field.Name == fd.Name)
{
// Already initialized by the user in the field definition, e.g:
// public SyncListInt Foo = new SyncListInt();
return;
}
}
}
// Not initialized by the user in the field definition, e.g:
// public SyncListInt Foo;
var listCtor = Weaver.scriptDef.MainModule.ImportReference(fd.FieldType.Resolve().Methods.First<MethodDefinition>(x => x.Name == ".ctor" && !x.HasParameters));
ctorWorker.Append(ctorWorker.Create(OpCodes.Ldarg_0));
ctorWorker.Append(ctorWorker.Create(OpCodes.Newobj, listCtor));
ctorWorker.Append(ctorWorker.Create(OpCodes.Stfld, fd));
}
/*
// This generates code like:
NetworkBehaviour.RegisterCommandDelegate(base.GetType(), ShipControl.kCmdCmdThrust, new NetworkBehaviour.CmdDelegate(ShipControl.InvokeCmdCmdThrust));
*/
void GenerateCommandDelegate(ILProcessor awakeWorker, MethodReference registerMethod, MethodDefinition func, FieldReference field)
{
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldtoken, m_td));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldsfld, field));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldnull));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldftn, func));
awakeWorker.Append(awakeWorker.Create(OpCodes.Newobj, Weaver.CmdDelegateConstructor));
awakeWorker.Append(awakeWorker.Create(OpCodes.Call, registerMethod));
}
/*
// generates code like:
m_sizes.InitializeBehaviour(this, km_sizesCmdHash);
*/
void GenerateSyncListInitializer(ILProcessor awakeWorker, FieldReference fd, int index)
{
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldarg_0));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldfld, fd));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldarg_0));
awakeWorker.Append(awakeWorker.Create(OpCodes.Ldsfld, m_SyncListStaticFields[index]));
GenericInstanceType syncListGeneric = (GenericInstanceType)fd.FieldType.Resolve().BaseType;
syncListGeneric = (GenericInstanceType)Weaver.scriptDef.MainModule.ImportReference(syncListGeneric);
TypeReference listValueType = syncListGeneric.GenericArguments[0];
MethodReference genericInitBehaviourMethod = Helpers.MakeHostInstanceGeneric(Weaver.SyncListInitBehaviourReference, listValueType);
awakeWorker.Append(awakeWorker.Create(OpCodes.Callvirt, genericInitBehaviourMethod));
Weaver.scriptDef.MainModule.ImportReference(genericInitBehaviourMethod);
}
void GenerateSerialization()
{
Weaver.DLog(m_td, " GenerateSerialization");
foreach (var m in m_td.Methods)
{
if (m.Name == "OnSerialize")
return;
}
MethodDefinition serialize = new MethodDefinition("OnSerialize", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.boolType);
serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType)));
serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
serialize.Body.InitLocals = true;
VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType);
serialize.Body.Variables.Add(dirtyLocal);
// call base class
bool baseClassSerialize = false;
if (m_td.BaseType.FullName != Weaver.NetworkBehaviourType.FullName)
{
MethodReference baseSerialize = Weaver.ResolveMethod(m_td.BaseType, "OnSerialize");
if (baseSerialize != null)
{
VariableDefinition baseResult = new VariableDefinition(Weaver.boolType);
serialize.Body.Variables.Add(baseResult);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
serWorker.Append(serWorker.Create(OpCodes.Stloc_1)); // set baseResult to result of base.OnSerialize()
baseClassSerialize = true;
}
}
if (m_SyncVars.Count == 0)
{
if (baseClassSerialize)
{
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc_1));
serWorker.Append(serWorker.Create(OpCodes.Or));
}
else
{
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(serialize);
return;
}
// Generates: if (initialState);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (FieldDefinition syncVar in m_SyncVars)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Weaver.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Weaver.fail = true;
Log.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types.");
return;
}
}
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (initialState);
serWorker.Append(initialStateLabel);
// Generates: dirty = 0;
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_0));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
// write syncvars
int dirtyBit = Weaver.GetSyncVarStart(m_td.BaseType.FullName); // start at number of syncvars in parent
foreach (FieldDefinition syncVar in m_SyncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); // 8 bytes = long
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
WriteDirtyCheck(serWorker, true);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
MethodReference writeFunc = Weaver.GetWriteFunc(syncVar.FieldType);
if (writeFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
}
else
{
Log.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types.");
Weaver.fail = true;
return;
}
serWorker.Append(varLabel);
dirtyBit += 1;
}
WriteDirtyCheck(serWorker, false);
if (Weaver.generateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + m_td.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
if (baseClassSerialize)
{
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ldloc_1));
serWorker.Append(serWorker.Create(OpCodes.Or));
}
else
{
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(serialize);
}
static void WriteDirtyCheck(ILProcessor serWorker, bool reset)
{
//Weaver.DLog(m_td, " GenerateSerialization dirtyCheck");
// Generates: if (!dirty) { write dirty bits, set dirty bool }
Instruction dirtyLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Brtrue, dirtyLabel));
// write dirty bits
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWritePacked64));
if (reset)
{
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
}
// Generates: end if (!dirty)
serWorker.Append(dirtyLabel);
}
static int GetChannelId(FieldDefinition field)
{
int channel = 0;
foreach (var ca in field.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == "channel")
{
channel = (int)customField.Argument.Value;
break;
}
}
}
}
return channel;
}
// returns false for error, not for no-hook-exists
bool CheckForHookFunction(FieldDefinition syncVar, out MethodDefinition foundMethod)
{
foundMethod = null;
foreach (var ca in syncVar.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
foreach (CustomAttributeNamedArgument customField in ca.Fields)
{
if (customField.Name == "hook")
{
string hookFunctionName = customField.Argument.Value as string;
foreach (var m in m_td.Methods)
{
if (m.Name == hookFunctionName)
{
if (m.Parameters.Count == 1)
{
if (m.Parameters[0].ParameterType != syncVar.FieldType)
{
Log.Error("SyncVar Hook function " + hookFunctionName + " has wrong type signature for " + m_td.Name);
Weaver.fail = true;
return false;
}
foundMethod = m;
return true;
}
Log.Error("SyncVar Hook function " + hookFunctionName + " must have one argument " + m_td.Name);
Weaver.fail = true;
return false;
}
}
Log.Error("SyncVar Hook function " + hookFunctionName + " not found for " + m_td.Name);
Weaver.fail = true;
return false;
}
}
}
}
return true;
}
void GenerateNetworkChannelSetting(int channel)
{
MethodDefinition meth = new MethodDefinition("GetNetworkChannel", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.int32Type);
ILProcessor worker = meth.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldc_I4, channel));
worker.Append(worker.Create(OpCodes.Ret));
m_td.Methods.Add(meth);
}
void GenerateNetworkIntervalSetting(float interval)
{
MethodDefinition meth = new MethodDefinition("GetNetworkSendInterval", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.singleType);
ILProcessor worker = meth.Body.GetILProcessor();
worker.Append(worker.Create(OpCodes.Ldc_R4, interval));
worker.Append(worker.Create(OpCodes.Ret));
m_td.Methods.Add(meth);
}
void GenerateNetworkSettings()
{
// look for custom attribute
foreach (var ca in m_td.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.NetworkSettingsType.FullName)
{
// generate virtual functions
foreach (var field in ca.Fields)
{
if (field.Name == "channel")
{
// 0 is Channels.DefaultChannel
if ((int)field.Argument.Value == 0)
continue;
if (HasMethod("GetNetworkChannel"))
{
Log.Error(
"GetNetworkChannel, is already implemented, please make sure you either use NetworkSettings or GetNetworkChannel");
Weaver.fail = true;
return;
}
m_QosChannel = (int)field.Argument.Value;
GenerateNetworkChannelSetting(m_QosChannel);
}
if (field.Name == "sendInterval")
{
const float stdValue = 0.1f;
const float epsilon = 0.00001f;
if ((Math.Abs((float)field.Argument.Value - stdValue) <= epsilon))
continue;
if (HasMethod("GetNetworkSendInterval"))
{
Log.Error(
"GetNetworkSendInterval, is already implemented, please make sure you either use NetworkSettings or GetNetworkSendInterval");
Weaver.fail = true;
return;
}
GenerateNetworkIntervalSetting((float)field.Argument.Value);
}
}
}
}
}
void GeneratePreStartClient()
{
m_NetIdFieldCounter = 0;
MethodDefinition preStartMethod = null;
ILProcessor serWorker = null;
foreach (var m in m_td.Methods)
{
if (m.Name == "PreStartClient")
return;
}
foreach (FieldDefinition syncVar in m_SyncVars)
{
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
{
if (preStartMethod == null)
{
preStartMethod = new MethodDefinition("PreStartClient", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.voidType);
serWorker = preStartMethod.Body.GetILProcessor();
}
FieldDefinition netIdField = m_SyncVarNetIds[m_NetIdFieldCounter];
m_NetIdFieldCounter += 1;
// Generates: if (!_crateNetId.IsEmpty()) { crate = ClientScene.FindLocalObject(_crateNetId); }
Instruction nullLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldflda, netIdField));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkInstanceIsEmpty));
serWorker.Append(serWorker.Create(OpCodes.Brtrue, nullLabel));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, netIdField));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.FindLocalObjectReference));
// return value of FindLocalObjectReference is on stack, assign it to the syncvar
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
// Generates: end crateNetId != 0
serWorker.Append(nullLabel);
}
}
if (preStartMethod != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(preStartMethod);
}
}
void GenerateDeSerialization()
{
Weaver.DLog(m_td, " GenerateDeSerialization");
m_NetIdFieldCounter = 0;
foreach (var m in m_td.Methods)
{
if (m.Name == "OnDeserialize")
return;
}
MethodDefinition serialize = new MethodDefinition("OnDeserialize", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.voidType);
serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType)));
serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
ILProcessor serWorker = serialize.Body.GetILProcessor();
// call base class
if (m_td.BaseType.FullName != Weaver.NetworkBehaviourType.FullName)
{
MethodReference baseDeserialize = Weaver.ResolveMethod(m_td.BaseType, "OnDeserialize");
if (baseDeserialize != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState
serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
}
}
if (m_SyncVars.Count == 0)
{
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(serialize);
return;
}
// Generates: if (initialState);
Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
foreach (var syncVar in m_SyncVars)
{
MethodReference readByReferenceFunc = Weaver.GetReadByReferenceFunc(syncVar.FieldType);
if (readByReferenceFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
serWorker.Append(serWorker.Create(OpCodes.Call, readByReferenceFunc));
}
else
{
// assign value
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
{
// GameObject SyncVar - assign to generated netId var
FieldDefinition netIdField = m_SyncVarNetIds[m_NetIdFieldCounter];
m_NetIdFieldCounter += 1;
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadNetworkInstanceId));
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
}
else
{
MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType);
if (readFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
}
else
{
Log.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types.");
Weaver.fail = true;
return;
}
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
}
}
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
// Generates: end if (initialState);
serWorker.Append(initialStateLabel);
// setup local for dirty bits
serialize.Body.InitLocals = true;
VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
serialize.Body.Variables.Add(dirtyBitsLocal);
// get dirty bits
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked64));
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
// conditionally read each syncvar
int dirtyBit = Weaver.GetSyncVarStart(m_td.BaseType.FullName); // start at number of syncvars in parent
foreach (FieldDefinition syncVar in m_SyncVars)
{
Instruction varLabel = serWorker.Create(OpCodes.Nop);
// check if dirty bit is set
serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit));
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
MethodReference readByReferenceFunc = Weaver.GetReadByReferenceFunc(syncVar.FieldType);
if (readByReferenceFunc != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
serWorker.Append(serWorker.Create(OpCodes.Call, readByReferenceFunc));
}
else
{
MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType);
if (readFunc == null)
{
Log.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types.");
Weaver.fail = true;
return;
}
// check for Hook function
MethodDefinition foundMethod;
if (!CheckForHookFunction(syncVar, out foundMethod))
{
return;
}
if (foundMethod == null)
{
// just assign value
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
}
else
{
// call hook instead
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
}
serWorker.Append(varLabel);
dirtyBit += 1;
}
if (Weaver.generateLogErrors)
{
serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + m_td.Name));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(serialize);
}
bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst)
{
int count = 0;
// read cmd args from NetworkReader
foreach (ParameterDefinition arg in md.Parameters)
{
if (count++ == 0 && skipFirst)
{
continue;
}
MethodReference readFunc = Weaver.GetReadFunc(arg.ParameterType); //?
if (readFunc != null)
{
worker.Append(worker.Create(OpCodes.Ldarg_1));
worker.Append(worker.Create(OpCodes.Call, readFunc));
// conversion.. is this needed?
if (arg.ParameterType.FullName == Weaver.singleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R4));
}
else if (arg.ParameterType.FullName == Weaver.doubleType.FullName)
{
worker.Append(worker.Create(OpCodes.Conv_R8));
}
}
else
{
Log.Error("ProcessNetworkReaderParameters for " + m_td.Name + ":" + md.Name + " type " + arg.ParameterType + " not supported");
Weaver.fail = true;
return false;
}
}
return true;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
MethodDefinition ProcessCommandInvoke(MethodDefinition md)
{
MethodDefinition cmd = new MethodDefinition(k_CmdPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label = cmdWorker.Create(OpCodes.Nop);
WriteServerActiveCheck(cmdWorker, md.Name, label, "Command");
// setup for reader
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, m_td));
if (!ProcessNetworkReaderParameters(md, cmdWorker, false))
return null;
// invoke actual command function
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
AddInvokeParameters(cmd.Parameters);
return cmd;
}
static void AddInvokeParameters(ICollection<ParameterDefinition> collection)
{
collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, Weaver.NetworkBehaviourType2));
collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType)));
}
/*
// generates code like:
public void CallCmdThrust(float thrusting, int spin)
{
Debug.LogError("Call Command function CmdThrust");
if (!NetworkClient.active)
{
Debug.LogError("Command function CmdThrust called on server.");
return;
}
if (isServer)
{
// we are ON the server, invoke directly
CmdThrust(thrusting, spin);
return;
}
NetworkWriter networkWriter = new NetworkWriter();
networkWriter.Write(0);
networkWriter.Write((ushort)MsgType.SYSTEM_COMMAND);
networkWriter.WritePackedUInt32((uint)ShipControl.kCmdCmdThrust);
networkWriter.WritePackedUInt32((uint)playerId);
networkWriter.Write(thrusting);
networkWriter.WritePackedUInt32((uint)spin);
base.SendCommandInternal(networkWriter);
}
*/
MethodDefinition ProcessCommandCall(MethodDefinition md, CustomAttribute ca)
{
MethodDefinition cmd = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in md.Parameters)
{
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label = cmdWorker.Create(OpCodes.Nop);
WriteSetupLocals(cmdWorker);
if (Weaver.generateLogErrors)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, "Call Command function " + md.Name));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.logErrorReference));
}
WriteClientActiveCheck(cmdWorker, md.Name, label, "Command function");
// local client check
Instruction localClientLabel = cmdWorker.Create(OpCodes.Nop);
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.UBehaviourIsServer));
cmdWorker.Append(cmdWorker.Create(OpCodes.Brfalse, localClientLabel));
// call the cmd function directly.
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
for (int i = 0; i < md.Parameters.Count; i++)
{
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg, i + 1));
}
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, md));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
cmdWorker.Append(localClientLabel);
WriteCreateWriter(cmdWorker);
WriteMessageSize(cmdWorker);
WriteMessageId(cmdWorker, 5); //UNetwork.SYSTEM_COMMAND
// create the command id constant
FieldDefinition cmdConstant = new FieldDefinition("kCmd" + md.Name,
FieldAttributes.Static | FieldAttributes.Private,
Weaver.int32Type);
m_td.Fields.Add(cmdConstant);
// write command constant
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldsfld, cmdConstant));
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWritePacked32));
// write playerId from this NetworkBehaviour
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
// load unetviewfield
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getComponentReference));
// load and write netId field
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, Weaver.getUNetIdReference));
var writeFunc = Weaver.GetWriteFunc(Weaver.NetworkInstanceIdType);
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, writeFunc));
if (!WriteArguments(cmdWorker, md, "Command", false))
return null;
// find channel for Command
int channel = 0;
foreach (var field in ca.Fields)
{
if (field.Name == "channel")
{
channel = (int)field.Argument.Value;
}
}
var cmdName = md.Name;
int index = cmdName.IndexOf(k_CmdPrefix);
if (index > -1)
{
cmdName = cmdName.Substring(k_CmdPrefix.Length);
}
// invoke interal send and return
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldc_I4, channel)); // QoS transport channel (reliable/unreliable)
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, cmdName));
cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.sendCommandInternal));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
return cmd;
}
MethodDefinition ProcessTargetRpcInvoke(MethodDefinition md)
{
MethodDefinition rpc = new MethodDefinition(k_RpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
WriteClientActiveCheck(rpcWorker, md.Name, label, "TargetRPC");
// setup for reader
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, m_td));
//ClientScene.readyconnection
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.ReadyConnectionReference));
if (!ProcessNetworkReaderParameters(md, rpcWorker, true))
return null;
// invoke actual command function
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
AddInvokeParameters(rpc.Parameters);
return rpc;
}
MethodDefinition ProcessRpcInvoke(MethodDefinition md)
{
MethodDefinition rpc = new MethodDefinition(k_RpcPrefix + md.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
WriteClientActiveCheck(rpcWorker, md.Name, label, "RPC");
// setup for reader
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, m_td));
if (!ProcessNetworkReaderParameters(md, rpcWorker, false))
return null;
// invoke actual command function
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
AddInvokeParameters(rpc.Parameters);
return rpc;
}
MethodDefinition ProcessTargetRpcCall(MethodDefinition md, CustomAttribute ca)
{
MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in md.Parameters)
{
rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
WriteSetupLocals(rpcWorker);
WriteServerActiveCheck(rpcWorker, md.Name, label, "TargetRPC Function");
Instruction labelConnectionCheck = rpcWorker.Create(OpCodes.Nop);
// check specifically for ULocalConnectionToServer so a host is not trying to send
// an TargetRPC to the "server" from it's local client.
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1));
rpcWorker.Append(rpcWorker.Create(OpCodes.Isinst, Weaver.ULocalConnectionToServerType));
rpcWorker.Append(rpcWorker.Create(OpCodes.Brfalse, labelConnectionCheck));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, string.Format("TargetRPC Function {0} called on connection to server", md.Name)));
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.logErrorReference));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
rpcWorker.Append(labelConnectionCheck);
WriteCreateWriter(rpcWorker);
WriteMessageSize(rpcWorker);
WriteMessageId(rpcWorker, 2); // UNetwork.SYSTEM_RPC
// create the command id constant
FieldDefinition rpcConstant = new FieldDefinition("kTargetRpc" + md.Name,
FieldAttributes.Static | FieldAttributes.Private,
Weaver.int32Type);
m_td.Fields.Add(rpcConstant);
// write command constant
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldsfld, rpcConstant));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWritePacked32));
// write this.unetView.netId
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
// load unetviewfield
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getComponentReference));
// load and write netId field
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.getUNetIdReference));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWriteNetworkInstanceId));
if (!WriteArguments(rpcWorker, md, "TargetRPC", true))
return null;
// find channel for SyncEvent
int channel = 0;
foreach (var field in ca.Fields)
{
if (field.Name == "channel")
{
channel = (int)field.Argument.Value;
}
}
var rpcName = md.Name;
int index = rpcName.IndexOf(k_TargetRpcPrefix);
if (index > -1)
{
rpcName = rpcName.Substring(k_TargetRpcPrefix.Length);
}
// invoke SendInternal and return
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1)); // connection
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, channel)); // QoS transport channel (reliable/unreliable)
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendTargetRpcInternal));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
return rpc;
}
MethodDefinition ProcessRpcCall(MethodDefinition md, CustomAttribute ca)
{
MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in md.Parameters)
{
rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor rpcWorker = rpc.Body.GetILProcessor();
Instruction label = rpcWorker.Create(OpCodes.Nop);
WriteSetupLocals(rpcWorker);
WriteServerActiveCheck(rpcWorker, md.Name, label, "RPC Function");
WriteCreateWriter(rpcWorker);
WriteMessageSize(rpcWorker);
WriteMessageId(rpcWorker, 2); // UNetwork.SYSTEM_RPC
// create the command id constant
FieldDefinition rpcConstant = new FieldDefinition("kRpc" + md.Name,
FieldAttributes.Static | FieldAttributes.Private,
Weaver.int32Type);
m_td.Fields.Add(rpcConstant);
// write command constant
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldsfld, rpcConstant));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWritePacked32));
// write this.unetView.netId
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
// load unetviewfield
rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getComponentReference));
// load and write netId field
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.getUNetIdReference));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWriteNetworkInstanceId));
if (!WriteArguments(rpcWorker, md, "RPC", false))
return null;
// find channel for SyncEvent
int channel = 0;
foreach (var field in ca.Fields)
{
if (field.Name == "channel")
{
channel = (int)field.Argument.Value;
}
}
var rpcName = md.Name;
int index = rpcName.IndexOf(k_RpcPrefix);
if (index > -1)
{
rpcName = rpcName.Substring(k_RpcPrefix.Length);
}
// invoke SendInternal and return
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, channel)); // QoS transport channel (reliable/unreliable)
rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendRpcInternal));
rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
return rpc;
}
bool ProcessMethodsValidateFunction(MethodReference md, CustomAttribute ca, string actionType)
{
if (md.ReturnType.FullName == Weaver.IEnumeratorType.FullName)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] cannot be a coroutine");
Weaver.fail = true;
return false;
}
if (md.ReturnType.FullName != Weaver.voidType.FullName)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] must have a void return type.");
Weaver.fail = true;
return false;
}
if (md.HasGenericParameters)
{
Log.Error(actionType + " [" + m_td.FullName + ":" + md.Name + "] cannot have generic parameters");
Weaver.fail = true;
return false;
}
return true;
}
bool ProcessMethodsValidateParameters(MethodReference md, CustomAttribute ca, string actionType)
{
for (int i = 0; i < md.Parameters.Count; ++i)
{
var p = md.Parameters[i];
if (p.IsOut)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] cannot have out parameters");
Weaver.fail = true;
return false;
}
if (p.IsOptional)
{
Log.Error(actionType + "function [" + m_td.FullName + ":" + md.Name + "] cannot have optional parameters");
Weaver.fail = true;
return false;
}
if (p.ParameterType.Resolve().IsAbstract)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] cannot have abstract parameters");
Weaver.fail = true;
return false;
}
if (p.ParameterType.IsByReference)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] cannot have ref parameters");
Weaver.fail = true;
return false;
}
// TargetRPC is an exception to this rule and can have a NetworkConnection as first parameter
if (p.ParameterType.FullName == Weaver.NetworkConnectionType.FullName &&
!(ca.AttributeType.FullName == Weaver.TargetRpcType.FullName && i == 0))
{
Log.Error(actionType + " [" + m_td.FullName + ":" + md.Name + "] cannot use a NetworkConnection as a parameter. To access a player object's connection on the server use connectionToClient");
Log.Error("Name: " + ca.AttributeType.FullName + " parameter: " + md.Parameters[0].ParameterType.FullName);
Weaver.fail = true;
return false;
}
if (Weaver.IsDerivedFrom(p.ParameterType.Resolve(), Weaver.ComponentType))
{
if (p.ParameterType.FullName != Weaver.NetworkIdentityType.FullName)
{
Log.Error(actionType + " function [" + m_td.FullName + ":" + md.Name + "] parameter [" + p.Name +
"] is of the type [" +
p.ParameterType.Name +
"] which is a Component. You cannot pass a Component to a remote call. Try passing data from within the component.");
Weaver.fail = true;
return false;
}
}
}
return true;
}
private bool ProcessMethodsValidateCommand(MethodDefinition md, CustomAttribute ca)
{
if (md.Name.Length > 2 && md.Name.Substring(0, 3) != "Cmd")
{
Log.Error("Command function [" + m_td.FullName + ":" + md.Name + "] doesnt have 'Cmd' prefix");
Weaver.fail = true;
return false;
}
if (md.IsStatic)
{
Log.Error("Command function [" + m_td.FullName + ":" + md.Name + "] cant be a static method");
Weaver.fail = true;
return false;
}
if (!ProcessMethodsValidateFunction(md, ca, "Command"))
{
return false;
}
if (!ProcessMethodsValidateParameters(md, ca, "Command"))
{
return false;
}
return true;
}
bool ProcessMethodsValidateTargetRpc(MethodDefinition md, CustomAttribute ca)
{
const string targetPrefix = "Target";
int prefixLen = targetPrefix.Length;
if (md.Name.Length > prefixLen && md.Name.Substring(0, prefixLen) != targetPrefix)
{
Log.Error("Target Rpc function [" + m_td.FullName + ":" + md.Name + "] doesnt have 'Target' prefix");
Weaver.fail = true;
return false;
}
if (md.IsStatic)
{
Log.Error("TargetRpc function [" + m_td.FullName + ":" + md.Name + "] cant be a static method");
Weaver.fail = true;
return false;
}
if (!ProcessMethodsValidateFunction(md, ca, "Target Rpc"))
{
return false;
}
if (md.Parameters.Count < 1)
{
Log.Error("Target Rpc function [" + m_td.FullName + ":" + md.Name + "] must have a NetworkConnection as the first parameter");
Weaver.fail = true;
return false;
}
if (md.Parameters[0].ParameterType.FullName != Weaver.NetworkConnectionType.FullName)
{
Log.Error("Target Rpc function [" + m_td.FullName + ":" + md.Name + "] first parameter must be a NetworkConnection");
Weaver.fail = true;
return false;
}
if (!ProcessMethodsValidateParameters(md, ca, "Target Rpc"))
{
return false;
}
return true;
}
bool ProcessMethodsValidateRpc(MethodDefinition md, CustomAttribute ca)
{
if (md.Name.Length > 2 && md.Name.Substring(0, 3) != "Rpc")
{
Log.Error("Rpc function [" + m_td.FullName + ":" + md.Name + "] doesnt have 'Rpc' prefix");
Weaver.fail = true;
return false;
}
if (md.IsStatic)
{
Log.Error("ClientRpc function [" + m_td.FullName + ":" + md.Name + "] cant be a static method");
Weaver.fail = true;
return false;
}
if (!ProcessMethodsValidateFunction(md, ca, "Rpc"))
{
return false;
}
if (!ProcessMethodsValidateParameters(md, ca, "Rpc"))
{
return false;
}
return true;
}
void ProcessMethods()
{
HashSet<string> names = new HashSet<string>();
// find command and RPC functions
foreach (MethodDefinition md in m_td.Methods)
{
Weaver.ResetRecursionCount();
foreach (var ca in md.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
{
if (!ProcessMethodsValidateCommand(md, ca))
return;
if (names.Contains(md.Name))
{
Log.Error("Duplicate Command name [" + m_td.FullName + ":" + md.Name + "]");
Weaver.fail = true;
return;
}
names.Add(md.Name);
m_Cmds.Add(md);
MethodDefinition cmdFunc = ProcessCommandInvoke(md);
if (cmdFunc != null)
{
m_CmdInvocationFuncs.Add(cmdFunc);
}
MethodDefinition cmdCallFunc = ProcessCommandCall(md, ca);
if (cmdCallFunc != null)
{
m_CmdCallFuncs.Add(cmdCallFunc);
Weaver.lists.replacedMethods.Add(md);
Weaver.lists.replacementMethods.Add(cmdCallFunc);
}
break;
}
if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
{
if (!ProcessMethodsValidateTargetRpc(md, ca))
return;
if (names.Contains(md.Name))
{
Log.Error("Duplicate Target Rpc name [" + m_td.FullName + ":" + md.Name + "]");
Weaver.fail = true;
return;
}
names.Add(md.Name);
m_TargetRpcs.Add(md);
MethodDefinition rpcFunc = ProcessTargetRpcInvoke(md);
if (rpcFunc != null)
{
m_TargetRpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = ProcessTargetRpcCall(md, ca);
if (rpcCallFunc != null)
{
m_TargetRpcCallFuncs.Add(rpcCallFunc);
Weaver.lists.replacedMethods.Add(md);
Weaver.lists.replacementMethods.Add(rpcCallFunc);
}
break;
}
if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
{
if (!ProcessMethodsValidateRpc(md, ca))
return;
if (names.Contains(md.Name))
{
Log.Error("Duplicate ClientRpc name [" + m_td.FullName + ":" + md.Name + "]");
Weaver.fail = true;
return;
}
names.Add(md.Name);
m_Rpcs.Add(md);
MethodDefinition rpcFunc = ProcessRpcInvoke(md);
if (rpcFunc != null)
{
m_RpcInvocationFuncs.Add(rpcFunc);
}
MethodDefinition rpcCallFunc = ProcessRpcCall(md, ca);
if (rpcCallFunc != null)
{
m_RpcCallFuncs.Add(rpcCallFunc);
Weaver.lists.replacedMethods.Add(md);
Weaver.lists.replacementMethods.Add(rpcCallFunc);
}
break;
}
}
}
// cmds
foreach (MethodDefinition md in m_CmdInvocationFuncs)
{
m_td.Methods.Add(md);
}
foreach (MethodDefinition md in m_CmdCallFuncs)
{
m_td.Methods.Add(md);
}
// rpcs
foreach (MethodDefinition md in m_RpcInvocationFuncs)
{
m_td.Methods.Add(md);
}
foreach (MethodDefinition md in m_TargetRpcInvocationFuncs)
{
m_td.Methods.Add(md);
}
foreach (MethodDefinition md in m_RpcCallFuncs)
{
m_td.Methods.Add(md);
}
foreach (MethodDefinition md in m_TargetRpcCallFuncs)
{
m_td.Methods.Add(md);
}
}
MethodDefinition ProcessEventInvoke(EventDefinition ed)
{
// find the field that matches the event
FieldDefinition eventField = null;
foreach (FieldDefinition fd in m_td.Fields)
{
if (fd.FullName == ed.FullName)
{
eventField = fd;
break;
}
}
if (eventField == null)
{
Weaver.DLog(m_td, "ERROR: no event field?!");
Weaver.fail = true;
return null;
}
MethodDefinition cmd = new MethodDefinition("InvokeSyncEvent" + ed.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor cmdWorker = cmd.Body.GetILProcessor();
Instruction label1 = cmdWorker.Create(OpCodes.Nop);
Instruction label2 = cmdWorker.Create(OpCodes.Nop);
WriteClientActiveCheck(cmdWorker, ed.Name, label1, "Event");
// null event check
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, m_td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
cmdWorker.Append(cmdWorker.Create(OpCodes.Brtrue, label2));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
cmdWorker.Append(label2);
// setup reader
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, m_td));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
// read the event arguments
MethodReference invoke = Weaver.ResolveMethod(eventField.FieldType, "Invoke");
if (!ProcessNetworkReaderParameters(invoke.Resolve(), cmdWorker, false))
return null;
// invoke actual event delegate function
cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, invoke));
cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
AddInvokeParameters(cmd.Parameters);
return cmd;
}
MethodDefinition ProcessEventCall(EventDefinition ed, CustomAttribute ca)
{
MethodReference invoke = Weaver.ResolveMethod(ed.EventType, "Invoke");
MethodDefinition evt = new MethodDefinition("Call" + ed.Name, MethodAttributes.Public |
MethodAttributes.HideBySig,
Weaver.voidType);
// add paramters
foreach (ParameterDefinition pd in invoke.Parameters)
{
evt.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
ILProcessor evtWorker = evt.Body.GetILProcessor();
Instruction label = evtWorker.Create(OpCodes.Nop);
WriteSetupLocals(evtWorker);
WriteServerActiveCheck(evtWorker, ed.Name, label, "Event");
WriteCreateWriter(evtWorker);
WriteMessageSize(evtWorker);
WriteMessageId(evtWorker, 7); //UNetwork.SYSTEM_SYNCEVENT
// create the command id constant
FieldDefinition evtConstant = new FieldDefinition("kEvent" + ed.Name,
FieldAttributes.Static | FieldAttributes.Private,
Weaver.int32Type);
m_td.Fields.Add(evtConstant);
// write command constant
evtWorker.Append(evtWorker.Create(OpCodes.Ldloc_0)); // networkWriter
evtWorker.Append(evtWorker.Create(OpCodes.Ldsfld, evtConstant));
evtWorker.Append(evtWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWritePacked32));
// write this.unetView.netId
evtWorker.Append(evtWorker.Create(OpCodes.Ldloc_0)); // networkWriter
evtWorker.Append(evtWorker.Create(OpCodes.Ldarg_0)); // this
evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.getComponentReference)); // unetView
// load and write netId field
evtWorker.Append(evtWorker.Create(OpCodes.Callvirt, Weaver.getUNetIdReference)); // netId
evtWorker.Append(evtWorker.Create(OpCodes.Callvirt, Weaver.NetworkWriterWriteNetworkInstanceId)); // networkWriter.Write(this.unetView.netId)
if (!WriteArguments(evtWorker, invoke.Resolve(), "SyncEvent", false))
return null;
// find channel for ClientRpc
int channel = 0;
foreach (var field in ca.Fields)
{
if (field.Name == "channel")
{
channel = (int)field.Argument.Value;
}
}
// invoke interal send and return
evtWorker.Append(evtWorker.Create(OpCodes.Ldarg_0)); // this
evtWorker.Append(evtWorker.Create(OpCodes.Ldloc_0)); // writer
evtWorker.Append(evtWorker.Create(OpCodes.Ldc_I4, channel)); // QoS transport channel (reliable/unreliable)
evtWorker.Append(evtWorker.Create(OpCodes.Ldstr, ed.Name));
evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.sendEventInternal));
evtWorker.Append(evtWorker.Create(OpCodes.Ret));
return evt;
}
void ProcessEvents()
{
// find events
foreach (EventDefinition ed in m_td.Events)
{
foreach (var ca in ed.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncEventType.FullName)
{
if (ed.Name.Length > 4 && ed.Name.Substring(0, 5) != "Event")
{
Log.Error("Event [" + m_td.FullName + ":" + ed.FullName + "] doesnt have 'Event' prefix");
Weaver.fail = true;
return;
}
if (ed.EventType.Resolve().HasGenericParameters)
{
Log.Error("Event [" + m_td.FullName + ":" + ed.FullName + "] cannot have generic parameters");
Weaver.fail = true;
return;
}
m_Events.Add(ed);
MethodDefinition eventFunc = ProcessEventInvoke(ed);
if (eventFunc == null)
{
return;
}
m_td.Methods.Add(eventFunc);
m_EventInvocationFuncs.Add(eventFunc);
Weaver.DLog(m_td, "ProcessEvent " + ed);
MethodDefinition eventCallFunc = ProcessEventCall(ed, ca);
m_td.Methods.Add(eventCallFunc);
Weaver.lists.replacedEvents.Add(ed);
Weaver.lists.replacementEvents.Add(eventCallFunc);
Weaver.DLog(m_td, " Event: " + ed.Name);
break;
}
}
}
}
static MethodDefinition ProcessSyncVarGet(FieldDefinition fd, string originalName)
{
//Create the get method
MethodDefinition get = new MethodDefinition(
"get_Network" + originalName, MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
fd.FieldType);
ILProcessor getWorker = get.Body.GetILProcessor();
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, fd));
getWorker.Append(getWorker.Create(OpCodes.Ret));
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
}
MethodDefinition ProcessSyncVarSet(FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
{
//Create the set method
MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor setWorker = set.Body.GetILProcessor();
// this
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
// new value to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
// reference to field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, fd));
// dirty bit
setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit)); // 8 byte integer aka long
MethodDefinition hookFunctionMethod;
CheckForHookFunction(fd, out hookFunctionMethod);
if (hookFunctionMethod != null)
{
//if (NetworkServer.localClientActive && !syncVarHookGuard)
Instruction label = setWorker.Create(OpCodes.Nop);
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.NetworkServerGetLocalClientActive));
setWorker.Append(setWorker.Create(OpCodes.Brfalse, label));
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.getSyncVarHookGuard));
setWorker.Append(setWorker.Create(OpCodes.Brtrue, label));
// syncVarHookGuard = true;
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_1));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
// call hook
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
setWorker.Append(setWorker.Create(OpCodes.Call, hookFunctionMethod));
// syncVarHookGuard = false;
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_0));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
setWorker.Append(label);
}
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
{
// reference to netId Field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarGameObjectReference));
}
else
{
// make generic version of SetSyncVar with field type
GenericInstanceMethod gm = new GenericInstanceMethod(Weaver.setSyncVarReference);
gm.GenericArguments.Add(fd.FieldType);
// invoke SetSyncVar
setWorker.Append(setWorker.Create(OpCodes.Call, gm));
}
setWorker.Append(setWorker.Create(OpCodes.Ret));
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
return set;
}
void ProcessSyncVar(FieldDefinition fd, long dirtyBit)
{
string originalName = fd.Name;
Weaver.lists.replacedFields.Add(fd);
Weaver.DLog(m_td, "Sync Var " + fd.Name + " " + fd.FieldType + " " + Weaver.gameObjectType);
// GameObject SyncVars have a new field for netId
FieldDefinition netFieldId = null;
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
{
netFieldId = new FieldDefinition("___" + fd.Name + "NetId",
FieldAttributes.Private,
Weaver.NetworkInstanceIdType);
m_SyncVarNetIds.Add(netFieldId);
Weaver.lists.netIdFields.Add(netFieldId);
}
var get = ProcessSyncVarGet(fd, originalName);
var set = ProcessSyncVarSet(fd, originalName, dirtyBit, netFieldId);
//NOTE: is property even needed? Could just use a setter function?
//create the property
PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
{
GetMethod = get, SetMethod = set
};
//add the methods and property to the type.
m_td.Methods.Add(get);
m_td.Methods.Add(set);
m_td.Properties.Add(propertyDefinition);
Weaver.lists.replacementProperties.Add(set);
}
/*
Generates code like:
// SyncListTestPlayerBehaviour
protected static void InvokeSyncListm_ints(NetworkBehaviour obj, NetworkReader reader)
{
if (!NetworkClient.active)
{
Debug.LogError("SyncList m_ints called on server.");
return;
}
((SyncListTestPlayerBehaviour)obj).m_ints.HandleMsg(reader);
}
*/
static MethodDefinition ProcessSyncListInvoke(FieldDefinition fd)
{
MethodDefinition cmd = new MethodDefinition("InvokeSyncList" + fd.Name, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
Weaver.voidType);
ILProcessor syncList = cmd.Body.GetILProcessor();
Instruction label = syncList.Create(OpCodes.Nop);
WriteClientActiveCheck(syncList, fd.Name, label, "SyncList");
syncList.Append(syncList.Create(OpCodes.Ldarg_0)); //this
syncList.Append(syncList.Create(OpCodes.Castclass, fd.DeclaringType));
syncList.Append(syncList.Create(OpCodes.Ldfld, fd)); // list field
syncList.Append(syncList.Create(OpCodes.Ldarg_1)); // reader
// make specialized version of HandleMsg
GenericInstanceType syncListGeneric = (GenericInstanceType)fd.FieldType.Resolve().BaseType;
syncListGeneric = (GenericInstanceType)Weaver.scriptDef.MainModule.ImportReference(syncListGeneric);
TypeReference listValueType = syncListGeneric.GenericArguments[0];
MethodReference genericHandleMsgMethod = Helpers.MakeHostInstanceGeneric(Weaver.SyncListInitHandleMsg, listValueType);
syncList.Append(syncList.Create(OpCodes.Callvirt, genericHandleMsgMethod));
syncList.Append(syncList.Create(OpCodes.Ret));
AddInvokeParameters(cmd.Parameters);
return cmd;
}
FieldDefinition ProcessSyncList(FieldDefinition fd, long dirtyBit)
{
MethodDefinition syncListFunc = ProcessSyncListInvoke(fd);
m_SyncListInvocationFuncs.Add(syncListFunc);
// create the command id constant
return new FieldDefinition("kList" + fd.Name,
FieldAttributes.Static | FieldAttributes.Private,
Weaver.int32Type);
}
void ProcessSyncVars()
{
int numSyncVars = 0;
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
// start assigning syncvars at the place the base class stopped, if any
int dirtyBitCounter = Weaver.GetSyncVarStart(m_td.BaseType.FullName);
m_SyncVarNetIds.Clear();
List<FieldDefinition> listFields = new List<FieldDefinition>();
// find syncvars
foreach (FieldDefinition fd in m_td.Fields)
{
foreach (var ca in fd.CustomAttributes)
{
if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
{
var resolvedField = fd.FieldType.Resolve();
if (Weaver.IsDerivedFrom(resolvedField, Weaver.NetworkBehaviourType))
{
Log.Error("SyncVar [" + fd.FullName + "] cannot be derived from NetworkBehaviour.");
Weaver.fail = true;
return;
}
if (Weaver.IsDerivedFrom(resolvedField, Weaver.ScriptableObjectType))
{
Log.Error("SyncVar [" + fd.FullName + "] cannot be derived from ScriptableObject.");
Weaver.fail = true;
return;
}
if ((fd.Attributes & FieldAttributes.Static) != 0)
{
Log.Error("SyncVar [" + fd.FullName + "] cannot be static.");
Weaver.fail = true;
return;
}
if (resolvedField.HasGenericParameters)
{
Log.Error("SyncVar [" + fd.FullName + "] cannot have generic parameters.");
Weaver.fail = true;
return;
}
if (resolvedField.IsInterface)
{
Log.Error("SyncVar [" + fd.FullName + "] cannot be an interface.");
Weaver.fail = true;
return;
}
var fieldModuleName = resolvedField.Module.Name;
if (fieldModuleName != Weaver.scriptDef.MainModule.Name &&
fieldModuleName != Weaver.m_UnityAssemblyDefinition.MainModule.Name &&
fieldModuleName != Weaver.m_UNetAssemblyDefinition.MainModule.Name &&
fieldModuleName != Weaver.corLib.Name &&
fieldModuleName != "System.Runtime.dll" // this is only for Metro, built-in types are not in corlib on metro
)
{
Log.Error("SyncVar [" + fd.FullName + "] from " + resolvedField.Module.ToString() + " cannot be a different module.");
Weaver.fail = true;
return;
}
if (fd.FieldType.IsArray)
{
Log.Error("SyncVar [" + fd.FullName + "] cannot be an array. Use a SyncList instead.");
Weaver.fail = true;
return;
}
if (Helpers.InheritsFromSyncList(fd.FieldType))
{
Log.Warning(string.Format("Script class [{0}] has [SyncVar] attribute on SyncList field {1}, SyncLists should not be marked with SyncVar.", m_td.FullName, fd.Name));
break;
}
m_SyncVars.Add(fd);
ProcessSyncVar(fd, 1L << dirtyBitCounter);
dirtyBitCounter += 1;
numSyncVars += 1;
if (dirtyBitCounter == k_SyncVarLimit)
{
Log.Error("Script class [" + m_td.FullName + "] has too many SyncVars (" + k_SyncVarLimit + "). (This could include base classes)");
Weaver.fail = true;
return;
}
break;
}
}
if (fd.FieldType.FullName.Contains("UnityEngine.Networking.SyncListStruct"))
{
Log.Error("SyncListStruct member variable [" + fd.FullName + "] must use a dervied class, like \"class MySyncList : SyncListStruct<MyStruct> {}\".");
Weaver.fail = true;
return;
}
if (Weaver.IsDerivedFrom(fd.FieldType.Resolve(), Weaver.SyncListType))
{
if (fd.IsStatic)
{
Log.Error("SyncList [" + m_td.FullName + ":" + fd.FullName + "] cannot be a static");
Weaver.fail = true;
return;
}
m_SyncVars.Add(fd);
m_SyncLists.Add(fd);
listFields.Add(ProcessSyncList(fd, 1L << dirtyBitCounter));
dirtyBitCounter += 1;
numSyncVars += 1;
if (dirtyBitCounter == k_SyncVarLimit)
{
Log.Error("Script class [" + m_td.FullName + "] has too many SyncVars (" + k_SyncVarLimit + "). (This could include base classes)");
Weaver.fail = true;
return;
}
}
}
foreach (FieldDefinition fd in listFields)
{
m_td.Fields.Add(fd);
m_SyncListStaticFields.Add(fd);
}
foreach (FieldDefinition fd in m_SyncVarNetIds)
{
m_td.Fields.Add(fd);
}
foreach (var func in m_SyncListInvocationFuncs)
{
m_td.Methods.Add(func);
}
Weaver.SetNumSyncVars(m_td.FullName, numSyncVars);
}
// Copy of Mono string.GetHashCode(), so that we generate same hashes regardless of runtime (mono/MS .NET)
private static int GetHashCode(string s)
{
unsafe
{
int length = s.Length;
fixed(char* c = s)
{
char* cc = c;
char* end = cc + length - 1;
int h = 0;
for (; cc < end; cc += 2)
{
h = (h << 5) - h + *cc;
h = (h << 5) - h + cc[1];
}
++end;
if (cc < end)
h = (h << 5) - h + *cc;
return h;
}
}
}
bool HasMethod(string name)
{
foreach (var method in m_td.Methods)
{
if (method.Name == name)
{
return true;
}
}
return false;
}
};
}