Weaver.cs 11.1 KB
Newer Older
cann-alberto's avatar
cann-alberto committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;

namespace Mirror.Weaver
{
    // not static, because ILPostProcessor is multithreaded
    internal class Weaver
    {
        // generated code class
        public const string GeneratedCodeNamespace = "Mirror";
        public const string GeneratedCodeClassName = "GeneratedNetworkCode";
        TypeDefinition GeneratedCodeClass;

        // for resolving Mirror.dll in ReaderWriterProcessor, we need to know
        // Mirror.dll name
        public const string MirrorAssemblyName = "Mirror";

        WeaverTypes weaverTypes;
        SyncVarAccessLists syncVarAccessLists;
        AssemblyDefinition CurrentAssembly;
        Writers writers;
        Readers readers;

        // in case of weaver errors, we don't stop immediately.
        // we log all errors and then eventually return false if
        // weaving has failed.
        // this way the user can fix multiple errors at once, instead of having
        // to fix -> recompile -> fix -> recompile for one error at a time.
        bool WeavingFailed;

        // logger functions can be set from the outside.
        // for example, Debug.Log or ILPostProcessor Diagnostics log for
        // multi threaded logging.
        public Logger Log;

        // remote actions now support overloads,
        // -> but IL2CPP doesnt like it when two generated methods
        // -> have the same signature,
        // -> so, append the signature to the generated method name,
        // -> to create a unique name
        // Example:
        // RpcTeleport(Vector3 position) -> InvokeUserCode_RpcTeleport__Vector3()
        // RpcTeleport(Vector3 position, Quaternion rotation) -> InvokeUserCode_RpcTeleport__Vector3Quaternion()
        // fixes https://github.com/vis2k/Mirror/issues/3060
        public static string GenerateMethodName(string initialPrefix, MethodDefinition md)
        {
            initialPrefix += md.Name;

            for (int i = 0; i < md.Parameters.Count; ++i)
            {
                // with __ so it's more obvious that this is the parameter suffix.
                // otherwise RpcTest(int) => RpcTestInt(int) which is not obvious.
                initialPrefix += $"__{md.Parameters[i].ParameterType.Name}";
            }

            return initialPrefix;
        }

        public Weaver(Logger Log)
        {
            this.Log = Log;
        }

        // returns 'true' if modified (=if we did anything)
        bool WeaveNetworkBehavior(TypeDefinition td)
        {
            if (!td.IsClass)
                return false;

            if (!td.IsDerivedFrom<NetworkBehaviour>())
            {
                if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
                    MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
                return false;
            }

            // process this and base classes from parent to child order

            List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();

            TypeDefinition parent = td;
            while (parent != null)
            {
                if (parent.Is<NetworkBehaviour>())
                {
                    break;
                }

                try
                {
                    behaviourClasses.Insert(0, parent);
                    parent = parent.BaseType.Resolve();
                }
                catch (AssemblyResolutionException)
                {
                    // this can happen for plugins.
                    //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
                    break;
                }
            }

            bool modified = false;
            foreach (TypeDefinition behaviour in behaviourClasses)
            {
                modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
            }
            return modified;
        }

        bool WeaveModule(ModuleDefinition moduleDefinition)
        {
            bool modified = false;

            Stopwatch watch = Stopwatch.StartNew();
            watch.Start();

            // ModuleDefinition.Types only finds top level types.
            // GetAllTypes recursively finds all nested types as well.
            // fixes nested types not being weaved, for example:
            //     class Parent {              // ModuleDefinition.Types finds this
            //         class Child {           // .Types.NestedTypes finds this
            //             class GrandChild {} // only GetAllTypes finds this too
            //         }
            //     }
            // note this is not about inheritance, only about type definitions.
            // see test: NetworkBehaviourTests.DeeplyNested()
            foreach (TypeDefinition td in moduleDefinition.GetAllTypes())
            {
                if (td.IsClass && td.BaseType.CanBeResolved())
                {
                    modified |= WeaveNetworkBehavior(td);
                    modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
                }
            }

            watch.Stop();
            Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");

            return modified;
        }

        void CreateGeneratedCodeClass()
        {
            // create "Mirror.GeneratedNetworkCode" class which holds all
            // Readers<T> and Writers<T>
            GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
                TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
                weaverTypes.Import<object>());
        }

        void ToggleWeaverFuse()
        {
            // // find Weaved() function
            MethodDefinition func = weaverTypes.weaverFuseMethod.Resolve();
            // // change return 0 to return 1

            ILProcessor worker = func.Body.GetILProcessor();
            func.Body.Instructions[0] = worker.Create(OpCodes.Ldc_I4_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}");

                modified |= WeaveModule(moduleDefinition);

                if (WeavingFailed)
                {
                    return false;
                }

                if (modified)
                {
                    SyncVarAttributeAccessReplacer.Process(Log, 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 });
                }

                // if weaving succeeded, switch on the Weaver Fuse in Mirror.dll
                if (CurrentAssembly.Name.Name == MirrorAssemblyName)
                {
                    ToggleWeaverFuse();
                }

                return true;
            }
            catch (Exception e)
            {
                Log.Error($"Exception :{e}");
                WeavingFailed = true;
                return false;
            }
        }
    }
}