SyncVarAttributeAccessReplacer.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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(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(syncVarAccessLists, td);
  22. }
  23. }
  24. Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
  25. }
  26. static void ProcessClass(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(syncVarAccessLists, md);
  33. }
  34. // processes all nested classes in this class recursively
  35. foreach (TypeDefinition nested in td.NestedTypes)
  36. {
  37. ProcessClass(syncVarAccessLists, nested);
  38. }
  39. }
  40. static void ProcessMethod(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(syncVarAccessLists, md, instr, i);
  61. }
  62. }
  63. }
  64. static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
  65. {
  66. // stfld (sets value of a field)?
  67. if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst)
  68. {
  69. ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
  70. }
  71. // ldfld (load value of a field)?
  72. if (instr.OpCode == OpCodes.Ldfld && instr.Operand is FieldDefinition opFieldld)
  73. {
  74. // this instruction gets the value of a field. cache the field reference.
  75. ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
  76. }
  77. // ldflda (load field address aka reference)
  78. if (instr.OpCode == OpCodes.Ldflda && instr.Operand is FieldDefinition opFieldlda)
  79. {
  80. // watch out for initobj instruction
  81. // see https://github.com/vis2k/Mirror/issues/696
  82. return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
  83. }
  84. // we processed one instruction (instr)
  85. return 1;
  86. }
  87. // replaces syncvar write access with the NetworkXYZ.set property calls
  88. static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
  89. {
  90. // don't replace property call sites in constructors
  91. if (md.Name == ".ctor")
  92. return;
  93. // does it set a field that we replaced?
  94. if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  95. {
  96. //replace with property
  97. //Log.Warning($" replacing {md.Name}:{i}", opField);
  98. i.OpCode = OpCodes.Call;
  99. i.Operand = replacement;
  100. //Log.Warning($" replaced {md.Name}:{i}", opField);
  101. }
  102. }
  103. // replaces syncvar read access with the NetworkXYZ.get property calls
  104. static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
  105. {
  106. // don't replace property call sites in constructors
  107. if (md.Name == ".ctor")
  108. return;
  109. // does it set a field that we replaced?
  110. if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  111. {
  112. //replace with property
  113. //Log.Warning($" replacing {md.Name}:{i}");
  114. i.OpCode = OpCodes.Call;
  115. i.Operand = replacement;
  116. //Log.Warning($" replaced {md.Name}:{i}");
  117. }
  118. }
  119. static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
  120. {
  121. // don't replace property call sites in constructors
  122. if (md.Name == ".ctor")
  123. return 1;
  124. // does it set a field that we replaced?
  125. if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
  126. {
  127. // we have a replacement for this property
  128. // is the next instruction a initobj?
  129. Instruction nextInstr = md.Body.Instructions[iCount + 1];
  130. if (nextInstr.OpCode == OpCodes.Initobj)
  131. {
  132. // we need to replace this code with:
  133. // var tmp = new MyStruct();
  134. // this.set_Networkxxxx(tmp);
  135. ILProcessor worker = md.Body.GetILProcessor();
  136. VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
  137. md.Body.Variables.Add(tmpVariable);
  138. worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
  139. worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
  140. worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
  141. worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
  142. worker.Remove(instr);
  143. worker.Remove(nextInstr);
  144. return 4;
  145. }
  146. }
  147. return 1;
  148. }
  149. }
  150. }