using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEditor; using UnityEngine; namespace Unity.VisualScripting { public static class ScriptUtility { private static HashSet guids; private static Dictionary guidTimestamps; private static Dictionary> typesToGuids; private static Dictionary> guidsToTypes; private static bool analyzed; private static void EnsureAnalyzed() { if (!analyzed) { Analyze(); } } private static void Analyze() { UnityAPI.AwaitForever ( () => { guids = new HashSet(); guidTimestamps = new Dictionary(); typesToGuids = new Dictionary>(); guidsToTypes = new Dictionary>(); foreach (var script in Resources.FindObjectsOfTypeAll()) { if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(script, out var guid, out long localId)) { Debug.LogWarning($"Failed to get GUID for script {script.GetClass()}: {script}"); continue; } guids.Add(guid); var type = script.GetClass(); if (!guidsToTypes.ContainsKey(guid)) { guidsToTypes.Add(guid, new HashSet()); } if (!guidTimestamps.ContainsKey(guid)) { var path = AssetDatabase.GetAssetPath(script); if (path.StartsWith("Assets")) { path = Path.Combine(Paths.project, path); } var timestamp = File.GetLastWriteTimeUtc(path); guidTimestamps.Add(guid, timestamp); } if (type != null) { if (!typesToGuids.ContainsKey(type)) { typesToGuids.Add(type, new HashSet()); } typesToGuids[type].Add(guid); guidsToTypes[guid].Add(type); } } analyzed = true; } ); } public static string GetScriptGuid(Type type) { return GetScriptGuids(type).SingleOrDefault(); } public static IEnumerable GetScriptGuids(Type type) { EnsureAnalyzed(); using (var recursion = Recursion.New(1)) { return GetScriptGuids(recursion, type).ToArray(); // No delayed execution for recursion disposal } } private static IEnumerable GetScriptGuids(Recursion recursion, Type type) { if (!recursion?.TryEnter(type) ?? false) { yield break; } if (typesToGuids.ContainsKey(type)) { foreach (var guid in typesToGuids[type]) { yield return guid; } } // Recurse inside the type. // For example, a List or an Enemy[] type should return the script GUID for Enemy. if (type.IsGenericType) { foreach (var genericArgument in type.GetGenericArguments()) { foreach (var genericGuid in GetScriptGuids(recursion, genericArgument)) { yield return genericGuid; } } } else if (type.HasElementType) { foreach (var genericGuid in GetScriptGuids(recursion, type.GetElementType())) { yield return genericGuid; } } recursion?.Exit(type); } public static IEnumerable GetScriptTypes(string guid) { EnsureAnalyzed(); if (guidsToTypes.ContainsKey(guid)) { return guidsToTypes[guid]; } else { return Enumerable.Empty(); } } public static IEnumerable GetAllScriptGuids() { EnsureAnalyzed(); return guids; } public static IEnumerable GetModifiedScriptGuids(DateTime sinceUtc) { EnsureAnalyzed(); foreach (var guidTimestamp in guidTimestamps) { if (guidTimestamp.Value > sinceUtc) { yield return guidTimestamp.Key; } } } // The fileID for a loose script imported directly from a .cs file, // which is the class ID for MonoScript multiplied by 100,000. public const int CsFileID = 11500000; public static int GetFileID(Type type) { var guid = GetScriptGuid(type); var assetExtension = Path.GetExtension(AssetDatabase.GUIDToAssetPath(guid)); switch (assetExtension.ToLowerInvariant()) { case ".cs": return CsFileID; case ".dll": return GetDllFileID(type); default: throw new NotSupportedException($"Unknown type declarer asset extension: '{assetExtension}'."); } } public static int GetDllFileID(Type type) { Ensure.That(nameof(type)).IsNotNull(type); return GetDllFileID(type.Namespace, type.Name); } public static int GetDllFileID(string @namespace, string name) { // Gets the fileID of a given type inside a DLL asset. // "Given a type t, the fileID is equal to the first four bytes of the MD4 of the string `"s\0\0\0" + t.Namespace + t.Name' as a little endian 32-byte integer." // https://forum.unity.com/threads/yaml-fileid-hash-function-for-dll-scripts.252075/#post-1695479 var toBeHashed = "s\0\0\0" + @namespace + name; using (var hash = new MD4()) { var hashed = hash.ComputeHash(Encoding.UTF8.GetBytes(toBeHashed)); var result = 0; for (var i = 3; i >= 0; --i) { result <<= 8; result |= hashed[i]; } return result; } } } }