//----------------------------------------------------------------------- // // Copyright (c) 2018 Sirenix IVS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //----------------------------------------------------------------------- #if UNITY_EDITOR namespace VRC.Udon.Serialization.OdinSerializer.Editor { using VRC.Udon.Serialization.OdinSerializer.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Scripting; public static class AOTSupportUtilities { /// /// Scans the project's build scenes and resources, plus their dependencies, for serialized types to support. Progress bars are shown during the scan. /// /// The serialized types to support. /// Whether to scan the project's build scenes. /// Whether to scan all the project's asset bundles. /// Whether to scan the project's preloaded assets. /// Whether to scan the project's resources. /// An optional list of the resource paths to scan. Only has an effect if the scanResources argument is true. All the resources will be scanned if null. /// true if the scan succeeded, false if the scan failed or was cancelled public static bool ScanProjectForSerializedTypes(out List serializedTypes, bool scanBuildScenes = true, bool scanAllAssetBundles = true, bool scanPreloadedAssets = true, bool scanResources = true, List resourcesToScan = null, bool scanAddressables = true) { using (var scanner = new AOTSupportScanner()) { scanner.BeginScan(); if (scanBuildScenes && !scanner.ScanBuildScenes(includeSceneDependencies: true, showProgressBar: true)) { Debug.Log("Project scan canceled while scanning scenes and their dependencies."); serializedTypes = null; return false; } if (scanResources && !scanner.ScanAllResources(includeResourceDependencies: true, showProgressBar: true, resourcesPaths: resourcesToScan)) { Debug.Log("Project scan canceled while scanning resources and their dependencies."); serializedTypes = null; return false; } if (scanAllAssetBundles && !scanner.ScanAllAssetBundles(showProgressBar: true)) { Debug.Log("Project scan canceled while scanning asset bundles and their dependencies."); serializedTypes = null; return false; } if (scanPreloadedAssets && !scanner.ScanPreloadedAssets(showProgressBar: true)) { Debug.Log("Project scan canceled while scanning preloaded assets and their dependencies."); serializedTypes = null; return false; } if (scanAddressables && !scanner.ScanAllAddressables(includeAssetDependencies: true, showProgressBar: true)) { Debug.Log("Project scan canceled while scanning addressable assets and their dependencies."); serializedTypes = null; return false; } serializedTypes = scanner.EndScan(); } return true; } /// /// Generates an AOT DLL, using the given parameters. /// public static void GenerateDLL(string dirPath, string assemblyName, List supportSerializedTypes, bool generateLinkXml = true) { #if UNITY_EDITOR && NET_4_6 if (!dirPath.EndsWith("/")) dirPath += "/"; var newDllPath = dirPath + assemblyName; var fullDllPath = newDllPath + ".dll"; var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName() { Name = assemblyName }, AssemblyBuilderAccess.Save, dirPath); var module = assembly.DefineDynamicModule(assemblyName); assembly.SetCustomAttribute(new CustomAttributeBuilder(typeof(EmittedAssemblyAttribute).GetConstructor(new Type[0]), new object[0])); // VRChat Edit: Add the UnityAPICompatibilityVersion Attribute for the current version of Unity to skip API Updating. #if UNITY_2019 assembly.SetCustomAttribute(new CustomAttributeBuilder( typeof(UnityAPICompatibilityVersionAttribute).GetConstructor(new[]{typeof(string), typeof(bool)}), new object[]{Application.unityVersion, true}) ); #else assembly.SetCustomAttribute(new CustomAttributeBuilder( typeof(UnityAPICompatibilityVersionAttribute).GetConstructor(new[]{typeof(string)}), new object[]{Application.unityVersion}) ); #endif // The following is a fix for Unity's crappy Mono runtime that doesn't know how to do this sort // of stuff properly // // We must manually remove the "Default Dynamic Assembly" module that is automatically defined, // otherwise a reference to that non-existent module will be saved into the assembly's IL, and // that will cause a multitude of issues. // // We do this by forcing there to be only one module - the one we just defined, and we set the // manifest module to be that module as well. { var modulesField = assembly.GetType().GetField("modules", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var manifestModuleField = assembly.GetType().GetField("manifest_module", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (modulesField != null) { modulesField.SetValue(assembly, new ModuleBuilder[] { module }); } if (manifestModuleField != null) { manifestModuleField.SetValue(assembly, module); } } var type = module.DefineType(assemblyName + ".PreventCodeStrippingViaReferences", TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.NotPublic); CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(typeof(PreserveAttribute).GetConstructor(Type.EmptyTypes), new object[0]); type.SetCustomAttribute(attributeBuilder); var staticConstructor = type.DefineTypeInitializer(); var il = staticConstructor.GetILGenerator(); var falseLocal = il.DeclareLocal(typeof(bool)); il.Emit(OpCodes.Ldc_I4_0); // Load false il.Emit(OpCodes.Stloc, falseLocal); // Set to falseLocal HashSet seenTypes = new HashSet(); if (UnityVersion.Major == 2019 && UnityVersion.Minor == 2) { // This here is a hack that fixes Unity's assembly updater triggering faultily in Unity 2019.2 // (and in early 2019.3 alphas/betas, but we're not handling those). When it triggers, it edits // the generated AOT assembly such that it immediately causes Unity to hard crash. Having this // type reference present in the assembly prevents that from happening. (Any concrete type in // the serialization assembly would work, this one is just a random pick.) // // Unity should have fixed this in 2019.3, but said that a backport to 2019.2 is not guaranteed // to occur, though it might. supportSerializedTypes.Add(typeof(DateTimeFormatter)); } //var endPoint = il.DefineLabel(); //il.Emit(OpCodes.Br, endPoint); foreach (var serializedType in supportSerializedTypes) { if (serializedType == null) continue; bool isAbstract = serializedType.IsAbstract || serializedType.IsInterface; if (serializedType.IsGenericType && (serializedType.IsGenericTypeDefinition || !serializedType.IsFullyConstructedGenericType())) { Debug.LogError("Skipping type '" + serializedType.GetNiceFullName() + "'! Type is a generic type definition, or its arguments contain generic parameters; type must be a fully constructed generic type."); continue; } if (seenTypes.Contains(serializedType)) continue; seenTypes.Add(serializedType); // Reference serialized type { if (serializedType.IsValueType) { var local = il.DeclareLocal(serializedType); il.Emit(OpCodes.Ldloca, local); il.Emit(OpCodes.Initobj, serializedType); } else if (!isAbstract) { var constructor = serializedType.GetConstructor(Type.EmptyTypes); if (constructor != null) { il.Emit(OpCodes.Newobj, constructor); il.Emit(OpCodes.Pop); } } } // Reference and/or create formatter type if (!FormatterUtilities.IsPrimitiveType(serializedType) && !typeof(UnityEngine.Object).IsAssignableFrom(serializedType) && !isAbstract) { var actualFormatter = FormatterLocator.GetFormatter(serializedType, SerializationPolicies.Unity); if (actualFormatter.GetType().IsDefined()) { //TODO: Make emitted formatter code compatible with IL2CPP //// Emit an actual AOT formatter into the generated assembly //if (this.emitAOTFormatters) //{ // var emittedFormatter = FormatterEmitter.EmitAOTFormatter(typeEntry.Type, module, SerializationPolicies.Unity); // var emittedFormatterConstructor = emittedFormatter.GetConstructor(Type.EmptyTypes); // il.Emit(OpCodes.Newobj, emittedFormatterConstructor); // il.Emit(OpCodes.Pop); //} } var formatters = FormatterLocator.GetAllCompatiblePredefinedFormatters(serializedType, SerializationPolicies.Unity); foreach (var formatter in formatters) { // Reference the pre-existing formatter var formatterConstructor = formatter.GetType().GetConstructor(Type.EmptyTypes); if (formatterConstructor != null) { il.Emit(OpCodes.Newobj, formatterConstructor); il.Emit(OpCodes.Pop); } } //// Make sure we have a proper reflection formatter variant if all else goes wrong //il.Emit(OpCodes.Newobj, typeof(ReflectionFormatter<>).MakeGenericType(serializedType).GetConstructor(Type.EmptyTypes)); //il.Emit(OpCodes.Pop); } ConstructorInfo serializerConstructor; // Reference serializer variant if (serializedType.IsValueType) { serializerConstructor = Serializer.Get(serializedType).GetType().GetConstructor(Type.EmptyTypes); il.Emit(OpCodes.Newobj, serializerConstructor); // The following section is a fix for an issue on IL2CPP for PS4, where sometimes bytecode isn't // generated for methods in base types of needed types - FX, Serializer.ReadValueWeak() // may be missing. This only seems to happen in a relevant way for value types. { var endLabel = il.DefineLabel(); // Load a false local value, then jump to the end of this segment of code due to that // false value. This is an attempt to trick any potential code flow analysis made // by IL2CPP that checks whether this segment of code is actually run. // // We don't run the code because if we did, that would actually throw a bunch of // exceptions from invalid calls to ReadValueWeak and WriteValueWeak. il.Emit(OpCodes.Ldloc, falseLocal); il.Emit(OpCodes.Brfalse, endLabel); var baseSerializerType = typeof(Serializer<>).MakeGenericType(serializedType); var readValueWeakMethod = baseSerializerType.GetMethod("ReadValueWeak", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(IDataReader) }, null); var writeValueWeakMethod = baseSerializerType.GetMethod("WriteValueWeak", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(string), typeof(object), typeof(IDataWriter) }, null); il.Emit(OpCodes.Dup); // Duplicate serializer instance il.Emit(OpCodes.Ldnull); // Load null argument for IDataReader reader il.Emit(OpCodes.Callvirt, readValueWeakMethod); // Call ReadValueWeak on serializer instance il.Emit(OpCodes.Pop); // Pop result of ReadValueWeak il.Emit(OpCodes.Dup); // Duplicate serializer instance il.Emit(OpCodes.Ldnull); // Load null argument for string name il.Emit(OpCodes.Ldnull); // Load null argument for object value il.Emit(OpCodes.Ldnull); // Load null argument for IDataWriter writer il.Emit(OpCodes.Callvirt, writeValueWeakMethod); // Call WriteValueWeak on serializer instance il.MarkLabel(endLabel); // This is where the code always jumps to, skipping the above } il.Emit(OpCodes.Pop); // Pop the serializer instance } else { serializerConstructor = typeof(ComplexTypeSerializer<>).MakeGenericType(serializedType).GetConstructor(Type.EmptyTypes); il.Emit(OpCodes.Newobj, serializerConstructor); il.Emit(OpCodes.Pop); } } //il.MarkLabel(endPoint); il.Emit(OpCodes.Ret); type.CreateType(); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } if (File.Exists(fullDllPath)) { File.Delete(fullDllPath); } if (File.Exists(fullDllPath + ".meta")) { File.Delete(fullDllPath + ".meta"); } try { AssetDatabase.Refresh(); } catch (Exception) { // Sigh, Unity 5.3.0 } assembly.Save(assemblyName); File.Move(newDllPath, fullDllPath); if (generateLinkXml) { File.WriteAllText(dirPath + "link.xml", @" "); } try { AssetDatabase.Refresh(); } catch (Exception) { // Sigh, Unity 5.3.0 } var pluginImporter = PluginImporter.GetAtPath(fullDllPath) as PluginImporter; if (pluginImporter != null) { //pluginImporter.ClearSettings(); pluginImporter.SetCompatibleWithEditor(false); pluginImporter.SetCompatibleWithAnyPlatform(true); // Disable for all standalones pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneLinux64, false); if (!UnityVersion.IsVersionOrGreater(2019, 2)) { pluginImporter.SetCompatibleWithPlatform((BuildTarget)17, false); // StandaloneLinux pluginImporter.SetCompatibleWithPlatform((BuildTarget)25, false); // StandaloneLinuxUniversal } // StandaloneOSXUniversal (<= 2017.2) / StandaloneOSX (>= 2017.3) pluginImporter.SetCompatibleWithPlatform((BuildTarget)2, false); if (!UnityVersion.IsVersionOrGreater(2017, 3)) { pluginImporter.SetCompatibleWithPlatform((BuildTarget)4, false); // StandaloneOSXIntel pluginImporter.SetCompatibleWithPlatform((BuildTarget)27, false); // StandaloneOSXIntel64 } pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneWindows, false); //pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneWindows64, false); //pluginImporter.SetCompatibleWithPlatform(BuildTarget.Android, false); pluginImporter.SaveAndReimport(); } AssetDatabase.SaveAssets(); #endif } } } #endif