123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- // [SyncVar] int health;
- // is replaced with:
- // public int Networkhealth { get; set; } properties.
- // this class processes all access to 'health' and replaces it with 'Networkhealth'
- using System;
- using Mono.CecilX;
- using Mono.CecilX.Cil;
- namespace Mirror.Weaver
- {
- public static class SyncVarAttributeAccessReplacer
- {
- // process the module
- public static void Process(Logger Log, ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
- {
- DateTime startTime = DateTime.Now;
- // process all classes in this module
- foreach (TypeDefinition td in moduleDef.Types)
- {
- if (td.IsClass)
- {
- ProcessClass(Log, syncVarAccessLists, td);
- }
- }
- Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
- }
- static void ProcessClass(Logger Log, SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
- {
- //Console.WriteLine($" ProcessClass {td}");
- // process all methods in this class
- foreach (MethodDefinition md in td.Methods)
- {
- ProcessMethod(Log, syncVarAccessLists, md);
- }
- // processes all nested classes in this class recursively
- foreach (TypeDefinition nested in td.NestedTypes)
- {
- ProcessClass(Log, syncVarAccessLists, nested);
- }
- }
- static void ProcessMethod(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
- {
- // process all references to replaced members with properties
- //Log.Warning($" ProcessSiteMethod {md}");
- // skip static constructor, "MirrorProcessed", "InvokeUserCode_"
- if (md.Name == ".cctor" ||
- md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
- md.Name.StartsWith(Weaver.InvokeRpcPrefix))
- return;
- // skip abstract
- if (md.IsAbstract)
- {
- return;
- }
- // go through all instructions of this method
- if (md.Body != null && md.Body.Instructions != null)
- {
- for (int i = 0; i < md.Body.Instructions.Count;)
- {
- Instruction instr = md.Body.Instructions[i];
- i += ProcessInstruction(Log, syncVarAccessLists, md, instr, i);
- }
- }
- }
- static int ProcessInstruction(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
- {
- // stfld (sets value of a field)?
- if (instr.OpCode == OpCodes.Stfld)
- {
- // operand is a FieldDefinition in the same assembly?
- if (instr.Operand is FieldDefinition opFieldst)
- {
- ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
- }
- // operand is a FieldReference in another assembly?
- // this is not supported just yet.
- // compilation error is better than silently failing SyncVar serialization at runtime.
- // https://github.com/MirrorNetworking/Mirror/issues/3525
- else if (instr.Operand is FieldReference opFieldstRef)
- {
- // resolve it from the other assembly
- FieldDefinition field = opFieldstRef.Resolve();
- // [SyncVar]?
- if (field.HasCustomAttribute<SyncVarAttribute>())
- {
- // ILPostProcessor would need to Process() the assembly's
- // references before processing this one.
- // we can not control the order.
- // instead, Log an error to suggest adding a SetSyncVar(value) function.
- // this is a very easy solution for a very rare edge case.
- Log.Error($"'[SyncVar] {opFieldstRef.Name}' in '{md.Module.Name}' is modified by '{md.FullName}' in '{field.Module.Name}'. Modifying a [SyncVar] from another assembly is not supported. Please add a: 'public void Set{opFieldstRef.Name}(value) {{ this.{opFieldstRef.Name} = value; }}' function in '{opFieldstRef.DeclaringType.Name}' and call this function from '{md.FullName}' instead.");
- }
- }
- }
- // ldfld (load value of a field)?
- if (instr.OpCode == OpCodes.Ldfld)
- {
- // operand is a FieldDefinition in the same assembly?
- if (instr.Operand is FieldDefinition opFieldld)
- {
- // this instruction gets the value of a field. cache the field reference.
- ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
- }
- }
- // ldflda (load field address aka reference)
- if (instr.OpCode == OpCodes.Ldflda)
- {
- // operand is a FieldDefinition in the same assembly?
- if (instr.Operand is FieldDefinition opFieldlda)
- {
- // watch out for initobj instruction
- // see https://github.com/vis2k/Mirror/issues/696
- return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
- }
- }
- // we processed one instruction (instr)
- return 1;
- }
- // replaces syncvar write access with the NetworkXYZ.set property calls
- static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
- {
- // don't replace property call sites in constructors
- if (md.Name == ".ctor")
- return;
- // does it set a field that we replaced?
- if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
- {
- //replace with property
- //Log.Warning($" replacing {md.Name}:{i}", opField);
- i.OpCode = OpCodes.Call;
- i.Operand = replacement;
- //Log.Warning($" replaced {md.Name}:{i}", opField);
- }
- }
- // replaces syncvar read access with the NetworkXYZ.get property calls
- static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
- {
- // don't replace property call sites in constructors
- if (md.Name == ".ctor")
- return;
- // does it set a field that we replaced?
- if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
- {
- //replace with property
- //Log.Warning($" replacing {md.Name}:{i}");
- i.OpCode = OpCodes.Call;
- i.Operand = replacement;
- //Log.Warning($" replaced {md.Name}:{i}");
- }
- }
- static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
- {
- // don't replace property call sites in constructors
- if (md.Name == ".ctor")
- return 1;
- // does it set a field that we replaced?
- if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
- {
- // we have a replacement for this property
- // is the next instruction a initobj?
- Instruction nextInstr = md.Body.Instructions[iCount + 1];
- if (nextInstr.OpCode == OpCodes.Initobj)
- {
- // we need to replace this code with:
- // var tmp = new MyStruct();
- // this.set_Networkxxxx(tmp);
- ILProcessor worker = md.Body.GetILProcessor();
- VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
- md.Body.Variables.Add(tmpVariable);
- worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
- worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
- worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
- worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
- worker.Remove(instr);
- worker.Remove(nextInstr);
- return 4;
- }
- }
- return 1;
- }
- }
- }
|