static void ProcessMethod(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(syncVarAccessLists, md, instr, i); } } }
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log) { this.assembly = assembly; this.weaverTypes = weaverTypes; this.syncVarAccessLists = syncVarAccessLists; this.Log = Log; }
static int ProcessInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount) { // stfld (sets value of a field)? if (instr.OpCode == OpCodes.Stfld && instr.Operand is FieldDefinition opFieldst) { ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst); } // ldfld (load value of a field)? if (instr.OpCode == OpCodes.Ldfld && 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 && 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); }
public NetworkBehaviourProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Writers writers, Readers readers, Logger Log, TypeDefinition td) { this.assembly = assembly; this.weaverTypes = weaverTypes; this.syncVarAccessLists = syncVarAccessLists; this.writers = writers; this.readers = readers; this.Log = Log; syncVarAttributeProcessor = new SyncVarAttributeProcessor(assembly, weaverTypes, syncVarAccessLists, Log); netBehaviourSubclass = td; }
// process the module public static void Process(ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists) { DateTime startTime = DateTime.Now; // process all classes in this module foreach (TypeDefinition td in moduleDef.Types) { if (td.IsClass) { ProcessClass(syncVarAccessLists, td); } } Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}"); }
static void ProcessClass(SyncVarAccessLists syncVarAccessLists, TypeDefinition td) { //Console.WriteLine($" ProcessClass {td}"); // process all methods in this class foreach (MethodDefinition md in td.Methods) { ProcessMethod(syncVarAccessLists, md); } // processes all nested classes in this class recursively foreach (TypeDefinition nested in td.NestedTypes) { ProcessClass(syncVarAccessLists, nested); } }
// 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); }
// Weave takes an AssemblyDefinition to be compatible with both old and // new weavers: // * old takes a filepath, new takes a in-memory byte[] // * old uses DefaultAssemblyResolver with added dependencies paths, // new uses ...? // // => assembly: the one we are currently weaving (MyGame.dll) // => resolver: useful in case we need to resolve any of the assembly's // assembly.MainModule.AssemblyReferences. // -> we can resolve ANY of them given that the resolver // works properly (need custom one for ILPostProcessor) // -> IMPORTANT: .Resolve() takes an AssemblyNameReference. // those from assembly.MainModule.AssemblyReferences are // guaranteed to be resolve-able. // Parsing from a string for Library/.../Mirror.dll // would not be guaranteed to be resolve-able because // for ILPostProcessor we can't assume where Mirror.dll // is etc. public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified) { WeavingFailed = false; modified = false; try { CurrentAssembly = assembly; // fix "No writer found for ..." error // https://github.com/vis2k/Mirror/issues/2579 // -> when restarting Unity, weaver would try to weave a DLL // again // -> resulting in two GeneratedNetworkCode classes (see ILSpy) // -> the second one wouldn't have all the writer types setup if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName)) { //Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved"); return(true); } weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed); // weaverTypes are needed for CreateGeneratedCodeClass CreateGeneratedCodeClass(); // WeaverList depends on WeaverTypes setup because it uses Import syncVarAccessLists = new SyncVarAccessLists(); // initialize readers & writers with this assembly. // we need to do this in every Process() call. // otherwise we would get // "System.ArgumentException: Member ... is declared in another module and needs to be imported" // errors when still using the previous module's reader/writer funcs. writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log); Stopwatch rwstopwatch = Stopwatch.StartNew(); // Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed); rwstopwatch.Stop(); Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds"); ModuleDefinition moduleDefinition = CurrentAssembly.MainModule; Console.WriteLine($"Script Module: {moduleDefinition.Name}"); MirrorWeaver.QSBReaderWriterProcessor.Process(moduleDefinition, writers, readers, ref WeavingFailed); modified |= WeaveModule(moduleDefinition); if (WeavingFailed) { return(false); } if (modified) { SyncVarAttributeAccessReplacer.Process(moduleDefinition, syncVarAccessLists); // add class that holds read/write functions moduleDefinition.Types.Add(GeneratedCodeClass); ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass); // DO NOT WRITE here. // CompilationFinishedHook writes to the file. // ILPostProcessor writes to in-memory assembly. // it depends on the caller. //CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true }); } return(true); } catch (Exception e) { Log.Error($"Exception :{e}"); WeavingFailed = true; return(false); } }