SyncVarAttributeAccessReplacer.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // [SyncVar] int health;
  2. // is replaced with:
  3. // public int Networkhealth { get; set; } properties.
  4. // this class processes all access to 'health' and replaces it with 'Networkhealth'
  5. using System;
  6. using Mono.CecilX;
  7. using Mono.CecilX.Cil;
  8. namespace Mirror.Weaver
  9. {
  10. public static class SyncVarAttributeAccessReplacer
  11. {
  12. // process the module
  13. public static void Process(Logger Log, ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
  14. {
  15. DateTime startTime = DateTime.Now;
  16. // process all classes in this module
  17. foreach (TypeDefinition td in moduleDef.Types)
  18. {
  19. if (td.IsClass)
  20. {
  21. ProcessClass(Log, syncVarAccessLists, td);
  22. }
  23. }
  24. Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
  25. }
  26. static void ProcessClass(Logger Log, SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
  27. {
  28. //Console.WriteLine($" ProcessClass {td}");
  29. // process all methods in this class
  30. foreach (MethodDefinition md in td.Methods)
  31. {
  32. ProcessMethod(Log, syncVarAccessLists, md);
  33. }
  34. // processes all nested classes in this class recursively
  35. foreach (TypeDefinition nested in td.NestedTypes)
  36. {
  37. ProcessClass(Log, syncVarAccessLists, nested);
  38. }
  39. }
  40. static void ProcessMethod(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
  41. {
  42. // process all references to replaced members with properties
  43. //Log.Warning($" ProcessSiteMethod {md}");
  44. // skip static constructor, "MirrorProcessed", "InvokeUserCode_"
  45. if (md.Name == ".cctor" ||
  46. md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
  47. md.Name.StartsWith(Weaver.InvokeRpcPrefix))
  48. return;
  49. // skip abstract
  50. if (md.IsAbstract)
  51. {
  52. return;
  53. }
  54. // go through all instructions of this method
  55. if (md.Body != null && md.Body.Instructions != null)
  56. {
  57. for (int i = 0; i < md.Body.Instructions.Count;)
  58. {
  59. Instruction instr = md.Body.Instructions[i];
  60. i += ProcessInstruction(Log, syncVarAccessLists, md, instr, i);
  61. }
  62. }
  63. }
  64. static int ProcessInstruction(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
  65. {
  66. // stfld (sets value of a field)?
  67. if (instr.OpCode == OpCodes.Stfld)
  68. {
  69. // operand is a FieldDefinition in the same assembly?
  70. if (instr.Operand is FieldDefinition opFieldst)
  71. {
  72. ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
  73. }
  74. // operand is a FieldReference in another assembly?
  75. // this is not supported just yet.
  76. // compilation error is better than silently failing SyncVar serialization at runtime.
  77. // https://github.com/MirrorNetworking/Mirror/issues/3525
  78. else if (instr.Operand is FieldReference opFieldstRef)
  79. {
  80. // resolve it from the other assembly
  81. FieldDefinition field = opFieldstRef.Resolve();
  82. // [SyncVar]?
  83. if (field.HasCustomAttribute<SyncVarAttribute>())
  84. {
  85. // ILPostProcessor would need to Process() the assembly's
  86. // references before processing this one.
  87. // we can not control the order.
  88. // instead, Log an error to suggest adding a SetSyncVar(value) function.
  89. // this is a very easy solution for a very rare edge case.
  90. 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.");
  91. }
  92. }
  93. }
  94. // ldfld (load value of a field)?
  95. if (instr.OpCode == OpCodes.Ldfld)
  96. {
  97. // operand is a FieldDefinition in the same assembly?
  98. if (instr.Operand is FieldDefinition opFieldld)
  99. {
  100. // this instruction gets the value of a field. cache the field reference.
  101. ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
  102. }
  103. }
  104. // ldflda (load field address aka reference)
  105. if (instr.OpCode == OpCodes.Ldflda)
  106. {
  107. // operand is a FieldDefinition in the same assembly?
  108. if (instr.Operand is FieldDefinition opFieldlda)
  109. {
  110. // watch out for initobj instruction
  111. // see https://github.com/vis2k/Mirror/issues/696
  112. return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
  113. }
  114. }
  115. // we processed one instruction (instr)
  116. return 1;
  117. }
  118. // replaces syncvar write access with the NetworkXYZ.set property calls
  119. static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
  120. {
  121. // don't replace property call sites in constructors
  122. if (md.Name == ".ctor")
  123. return;
  124. // does it set a field that we replaced?
  125. if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  126. {
  127. //replace with property
  128. //Log.Warning($" replacing {md.Name}:{i}", opField);
  129. i.OpCode = OpCodes.Call;
  130. i.Operand = replacement;
  131. //Log.Warning($" replaced {md.Name}:{i}", opField);
  132. }
  133. }
  134. // replaces syncvar read access with the NetworkXYZ.get property calls
  135. static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
  136. {
  137. // don't replace property call sites in constructors
  138. if (md.Name == ".ctor")
  139. return;
  140. // does it set a field that we replaced?
  141. if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  142. {
  143. //replace with property
  144. //Log.Warning($" replacing {md.Name}:{i}");
  145. i.OpCode = OpCodes.Call;
  146. i.Operand = replacement;
  147. //Log.Warning($" replaced {md.Name}:{i}");
  148. }
  149. }
  150. static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
  151. {
  152. // don't replace property call sites in constructors
  153. if (md.Name == ".ctor")
  154. return 1;
  155. // does it set a field that we replaced?
  156. if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  157. {
  158. // we have a replacement for this property
  159. // is the next instruction a initobj?
  160. Instruction nextInstr = md.Body.Instructions[iCount + 1];
  161. if (nextInstr.OpCode == OpCodes.Initobj)
  162. {
  163. // we need to replace this code with:
  164. // var tmp = new MyStruct();
  165. // this.set_Networkxxxx(tmp);
  166. ILProcessor worker = md.Body.GetILProcessor();
  167. VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
  168. md.Body.Variables.Add(tmpVariable);
  169. worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
  170. worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
  171. worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
  172. worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
  173. worker.Remove(instr);
  174. worker.Remove(nextInstr);
  175. return 4;
  176. }
  177. }
  178. return 1;
  179. }
  180. }
  181. }