slowpoker/Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeAccessReplacer.cs
2024-10-17 17:23:05 +03:00

207 lines
8.9 KiB
C#

// [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(RemoteCalls.RemoteProcedureCalls.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.DeclaringType.Name}.{opFieldstRef.Name}' in '{field.Module.Name}' is modified by '{md.FullName}' in '{md.Module.Name}'. Modifying a [SyncVar] from another assembly is not supported. Please add a: 'public void Set{opFieldstRef.Name}(value) {{ this.{opFieldstRef.Name} = value; }}' method 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;
}
}
}