using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Build.Utilities; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEditor.VersionControl; using UnityEngine; namespace UnityEditor.AddressableAssets.Settings { using Object = UnityEngine.Object; internal static class AddressableAssetUtility { internal static bool IsInResources(string path) { return path.Replace('\\', '/').ToLower().Contains("/resources/"); } internal static bool TryGetPathAndGUIDFromTarget(Object target, out string path, out string guid) { guid = string.Empty; path = string.Empty; if (target == null) return false; path = AssetDatabase.GetAssetOrScenePath(target); if (!IsPathValidForEntry(path)) return false; if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(target, out guid, out long id)) return false; return true; } static HashSet excludedExtensions = new HashSet(new string[] { ".cs", ".js", ".boo", ".exe", ".dll", ".meta", ".preset", ".asmdef" }); internal static bool IsPathValidForEntry(string path) { if (string.IsNullOrEmpty(path)) return false; path = path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); if (!path.StartsWith("assets", StringComparison.OrdinalIgnoreCase) && !IsPathValidPackageAsset(path)) return false; if (path == CommonStrings.UnityEditorResourcePath || path == CommonStrings.UnityDefaultResourcePath || path == CommonStrings.UnityBuiltInExtraPath) return false; if (path.EndsWith($"{Path.DirectorySeparatorChar}Editor") || path.Contains($"{Path.DirectorySeparatorChar}Editor{Path.DirectorySeparatorChar}") || path.EndsWith("/Editor") || path.Contains("/Editor/")) return false; if (path == "Assets") return false; var settings = AddressableAssetSettingsDefaultObject.SettingsExists ? AddressableAssetSettingsDefaultObject.Settings : null; if (settings != null && path.StartsWith(settings.ConfigFolder) || path.StartsWith(AddressableAssetSettingsDefaultObject.kDefaultConfigFolder)) return false; return !excludedExtensions.Contains(Path.GetExtension(path)); } internal static bool IsPathValidPackageAsset(string path) { string[] splitPath = path.ToLower().Split(Path.DirectorySeparatorChar); if (splitPath.Length < 3) return false; if (splitPath[0] != "packages") return false; if (splitPath[2] == "package.json") return false; return true; } static HashSet validTypes = new HashSet(); internal static Type MapEditorTypeToRuntimeType(Type t, bool allowFolders) { //type is valid and already seen (most common) if (validTypes.Contains(t)) return t; //removes the need to check this outside of this call if (t == null) return t; //check for editor type, this will get hit once for each new type encountered if (!t.Assembly.IsDefined(typeof(AssemblyIsEditorAssembly), true) && !Build.BuildUtility.IsEditorAssembly(t.Assembly)) { validTypes.Add(t); return t; } if (t == typeof(DefaultAsset)) return typeof(DefaultAsset); //try to remap the editor type to a runtime type return MapEditorTypeToRuntimeTypeInternal(t); } static Type MapEditorTypeToRuntimeTypeInternal(Type t) { if (t == typeof(UnityEditor.Animations.AnimatorController)) return typeof(RuntimeAnimatorController); if (t == typeof(UnityEditor.SceneAsset)) return typeof(UnityEngine.ResourceManagement.ResourceProviders.SceneInstance); if (t.FullName == "UnityEditor.Audio.AudioMixerController") return typeof(UnityEngine.Audio.AudioMixer); if (t.FullName == "UnityEditor.Audio.AudioMixerGroupController") return typeof(UnityEngine.Audio.AudioMixerGroup); return null; } internal static void ConvertAssetBundlesToAddressables() { AssetDatabase.RemoveUnusedAssetBundleNames(); var bundleList = AssetDatabase.GetAllAssetBundleNames(); float fullCount = bundleList.Length; int currCount = 0; var settings = AddressableAssetSettingsDefaultObject.GetSettings(true); foreach (var bundle in bundleList) { if (EditorUtility.DisplayCancelableProgressBar("Converting Legacy Asset Bundles", bundle, currCount / fullCount)) break; currCount++; var group = settings.CreateGroup(bundle, false, false, false, null); var schema = group.AddSchema(); schema.Validate(); schema.BundleMode = BundledAssetGroupSchema.BundlePackingMode.PackTogether; group.AddSchema().StaticContent = true; var assetList = AssetDatabase.GetAssetPathsFromAssetBundle(bundle); foreach (var asset in assetList) { var guid = AssetDatabase.AssetPathToGUID(asset); settings.CreateOrMoveEntry(guid, group, false, false); var imp = AssetImporter.GetAtPath(asset); if (imp != null) imp.SetAssetBundleNameAndVariant(string.Empty, string.Empty); } } if (fullCount > 0) settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true, true); EditorUtility.ClearProgressBar(); AssetDatabase.RemoveUnusedAssetBundleNames(); } /// /// Get all types that can be assigned to type T /// /// The class type to use as the base class or interface for all found types. /// A list of types that are assignable to type T. The results are cached. public static List GetTypes() { return TypeManager.Types; } /// /// Get all types that can be assigned to type rootType. /// /// The class type to use as the base class or interface for all found types. /// A list of types that are assignable to type T. The results are not cached. public static List GetTypes(Type rootType) { return TypeManager.GetManagerTypes(rootType); } class TypeManager { public static List GetManagerTypes(Type rootType) { var types = new List(); try { foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) { if (a.IsDynamic) continue; foreach (var t in a.ExportedTypes) { if (t != rootType && rootType.IsAssignableFrom(t) && !t.IsAbstract) types.Add(t); } } } catch (Exception) { // ignored } return types; } } class TypeManager : TypeManager { // ReSharper disable once StaticMemberInGenericType static List s_Types; public static List Types { get { if (s_Types == null) s_Types = GetManagerTypes(typeof(T)); return s_Types; } } } internal static bool SafeMoveResourcesToGroup(AddressableAssetSettings settings, AddressableAssetGroup targetGroup, List paths, List guids, bool showDialog = true) { if (targetGroup == null) { Debug.LogWarning("No valid group to move Resources to"); return false; } if (paths == null || paths.Count == 0) { Debug.LogWarning("No valid Resources found to move"); return false; } if (guids == null) { guids = new List(); foreach (var p in paths) guids.Add(AssetDatabase.AssetPathToGUID(p)); } Dictionary guidToNewPath = new Dictionary(); var message = "Any assets in Resources that you wish to mark as Addressable must be moved within the project. We will move the files to:\n\n"; for (int i = 0; i < guids.Count; i++) { var newName = paths[i].Replace("\\", "/"); newName = newName.Replace("Resources", "Resources_moved"); newName = newName.Replace("resources", "resources_moved"); if (newName == paths[i]) continue; guidToNewPath.Add(guids[i], newName); message += newName + "\n"; } message += "\nAre you sure you want to proceed?"; if (!showDialog || EditorUtility.DisplayDialog("Move From Resources", message, "Yes", "No")) { settings.MoveAssetsFromResources(guidToNewPath, targetGroup); return true; } return false; } static Dictionary s_CachedDisplayNames = new Dictionary(); internal static string GetCachedTypeDisplayName(Type type) { string result = ""; if (type != null) { if (!s_CachedDisplayNames.TryGetValue(type, out result)) { var displayNameAtr = type.GetCustomAttribute(); if (displayNameAtr != null) { result = (string)displayNameAtr.DisplayName; } else result = type.Name; s_CachedDisplayNames.Add(type, result); } } return result; } struct PackageData { public string version; } private static string m_Version = null; internal static string GetVersionFromPackageData() { if (string.IsNullOrEmpty(m_Version)) { var jsonFile = AssetDatabase.LoadAssetAtPath("Packages/com.unity.addressables/package.json"); var packageData = JsonUtility.FromJson(jsonFile.text); var split = packageData.version.Split('.'); if (split.Length < 2) throw new Exception("Could not get correct version data for Addressables package"); m_Version = $"{split[0]}.{split[1]}"; } return m_Version; } public static string GenerateDocsURL(string page) { return $"https://docs.unity3d.com/Packages/com.unity.addressables@{GetVersionFromPackageData()}/manual/{page}"; } internal static bool IsUsingVCIntegration() { return Provider.isActive && Provider.enabled; } internal static bool IsVCAssetOpenForEdit(string path) { AssetList VCAssets = GetVCAssets(path); foreach (Asset vcAsset in VCAssets) { if (vcAsset.path == path) return Provider.IsOpenForEdit(vcAsset); } return false; } internal static AssetList GetVCAssets(string path) { VersionControl.Task op = Provider.Status(path); op.Wait(); return op.assetList; } private static bool MakeAssetEditable(Asset asset) { if (!AssetDatabase.IsOpenForEdit(asset.path)) return AssetDatabase.MakeEditable(asset.path); return false; } internal static bool OpenAssetIfUsingVCIntegration(Object target, bool exitGUI = false) { if (!IsUsingVCIntegration() || target == null) return false; return OpenAssetIfUsingVCIntegration(AssetDatabase.GetAssetOrScenePath(target), exitGUI); } internal static bool OpenAssetIfUsingVCIntegration(string path, bool exitGUI = false) { if (!IsUsingVCIntegration() || string.IsNullOrEmpty(path)) return false; AssetList assets = GetVCAssets(path); var uneditableAssets = new List(); string message = "Check out file(s)?\n\n"; foreach (Asset asset in assets) { if (!Provider.IsOpenForEdit(asset)) { uneditableAssets.Add(asset); message += $"{asset.path}\n"; } } if (uneditableAssets.Count == 0) return false; bool openedAsset = true; if (EditorUtility.DisplayDialog("Attempting to modify files that are uneditable", message, "Yes", "No")) { foreach (Asset asset in uneditableAssets) { if (!MakeAssetEditable(asset)) openedAsset = false; } } else openedAsset = false; if (exitGUI) GUIUtility.ExitGUI(); return openedAsset; } internal static bool InstallCCDPackage() { #if !ENABLE_CCD var confirm = EditorUtility.DisplayDialog("Install CCD Management SDK Package", "Are you sure you want to install the CCD Management SDK pre-release package and enable experimental CCD features within Addressables?\nTo remove this package and its related features, please use the Package manager. Alternatively, uncheck the Addressable Asset Settings > Cloud Content Delivery > Enable Experimental CCD Features toggle to remove the package.", "Yes", "No"); if (confirm) { Client.Add("com.unity.services.ccd.management@1.0.0-pre.2"); AddressableAssetSettingsDefaultObject.Settings.CCDEnabled = true; } #endif return AddressableAssetSettingsDefaultObject.Settings.CCDEnabled; } internal static bool RemoveCCDPackage() { var confirm = EditorUtility.DisplayDialog("Remove CCD Management SDK Package", "Are you sure you want to remove the CCD Management SDK package?", "Yes", "No"); if (confirm) { #if (ENABLE_CCD && UNITY_2019_4_OR_NEWER) Client.Remove("com.unity.services.ccd.management"); AddressableAssetSettingsDefaultObject.Settings.CCDEnabled = false; #endif } return AddressableAssetSettingsDefaultObject.Settings.CCDEnabled; } internal static string GetMd5Hash(string path) { string hashString; using (var md5 = MD5.Create()) { using (var stream = File.OpenRead(path)) { var hash = md5.ComputeHash(stream); hashString = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } } return hashString; } internal static System.Threading.Tasks.Task ParallelForEachAsync(this IEnumerable source, int dop, Func body) { async System.Threading.Tasks.Task AwaitPartition(IEnumerator partition) { using (partition) { while (partition.MoveNext()) { await body(partition.Current); } } } return System.Threading.Tasks.Task.WhenAll( Partitioner .Create(source) .GetPartitions(dop) .AsParallel() .Select(p => AwaitPartition(p))); } internal class SortedDelegate { struct BufferedValues { public T1 arg1; public T2 arg2; public T3 arg3; public T4 arg4; } List m_Buffer; bool m_IsInvoking; private List<(int, Delegate)> m_RegisterQueue = new List<(int, Delegate)>(); public delegate void Delegate(T1 arg1, T2 arg2, T3 arg3, T4 arg4); private SortedList m_SortedInvocationList = new SortedList(); public void Unregister(Delegate toUnregister) { IList keys = m_SortedInvocationList.Keys; for (int i = 0; i < keys.Count; ++i) { m_SortedInvocationList[keys[i]] -= toUnregister; if (m_SortedInvocationList[keys[i]] == null) { m_SortedInvocationList.Remove(keys[i]); break; } } if (m_IsInvoking) { for (int i = m_RegisterQueue.Count - 1; i >= 0; --i) { if (m_RegisterQueue[i].Item2 == toUnregister) { m_RegisterQueue.RemoveAt(i); break; } } } } public void Register(Delegate toRegister, int order) { if (m_IsInvoking) { m_RegisterQueue.Add((order, toRegister)); return; } Unregister(toRegister); if (m_SortedInvocationList.ContainsKey(order)) m_SortedInvocationList[order] += toRegister; else m_SortedInvocationList.Add(order, toRegister); InvokeBuffer_Internal(); } public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { if (m_IsInvoking) return; m_IsInvoking = true; foreach (var invocationList in m_SortedInvocationList) invocationList.Value?.Invoke(arg1,arg2,arg3,arg4); if (m_RegisterQueue.Count > 0) { m_IsInvoking = false; foreach (var toRegister in m_RegisterQueue) Register(toRegister.Item2, toRegister.Item1); m_RegisterQueue.Clear(); m_IsInvoking = true; } InvokeBuffer_Internal(); m_IsInvoking = false; } void InvokeBuffer_Internal() { if (m_Buffer != null) { foreach (var b in m_Buffer) Invoke(b.arg1, b.arg2, b.arg3, b.arg4); m_Buffer = null; } } public void BufferInvoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { if (m_SortedInvocationList.Count == 0 || m_IsInvoking) { if (m_Buffer == null) m_Buffer = new List(); m_Buffer.Add(new BufferedValues { arg1 = arg1, arg2 = arg2, arg3 = arg3, arg4 = arg4 }); } else { Invoke(arg1, arg2, arg3, arg4); } } public static SortedDelegate operator +(SortedDelegate self, Delegate delegateToAdd) { int lastInOrder = self.m_SortedInvocationList.Keys[self.m_SortedInvocationList.Count - 1]; self.Register(delegateToAdd, lastInOrder + 1); return self; } public static SortedDelegate operator -(SortedDelegate self, Delegate delegateToRemove) { self.Unregister(delegateToRemove); return self; } public static bool operator ==(SortedDelegate obj1, SortedDelegate obj2) { bool aNull = ReferenceEquals(obj1, null); bool bNull = ReferenceEquals(obj2, null); if (aNull && bNull) return true; if (!aNull && bNull) return obj1.m_SortedInvocationList.Count == 0; if (aNull && !bNull) return obj2.m_SortedInvocationList.Count == 0; if (ReferenceEquals(obj1, obj2)) return true; return obj1.Equals(obj2); } public static bool operator !=(SortedDelegate lhs, SortedDelegate rhs) { return !(lhs == rhs); } protected bool Equals(SortedDelegate other) { return Equals(m_SortedInvocationList, other.m_SortedInvocationList); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((SortedDelegate) obj); } public override int GetHashCode() { return (m_SortedInvocationList != null ? m_SortedInvocationList.GetHashCode() : 0); } } } }