namespace ZooMatch { using System; using System.IO; using UnityEngine; using System.Linq; using System.Text; using UnityEngine.Events; using System.Collections; using System.Collections.Generic; using Object = UnityEngine.Object; using System.Security.Cryptography; public class ZooMatchKit : Singleton, ILoadAsset { private const bool IsMD5Encrypt = false; private string _assetBundleRootPath = ZooMatchFileKit.GetFilePath(); private static readonly Dictionary _cacheAssetBundleInfoDict = new Dictionary(); private static readonly Dictionary _cacheDependencyDict = new Dictionary(); private static readonly List _waitUnloadList = new List(); private static AssetBundleManifest _manifest; private static readonly Dictionary decryptAssetBundleDict = new Dictionary(); private const int UnloadWaitTime = 30; #region 加载AssetBundle的基础方法 public IEnumerator LoadABFromFileAsync(string assetBundlePath, string bundlePassword, UnityAction onCompleted) { yield return LoadDecryptAssetBundle(assetBundlePath, bundlePassword, onCompleted); } private IEnumerator LoadDecryptAssetBundle(string assetBundlePath, string bundlePassword, UnityAction onCompleted) { string decryptPath; if (decryptAssetBundleDict.TryGetValue(assetBundlePath, out var outPath) && File.Exists(outPath)) { decryptPath = outPath; } else { var cachePath1 = Path.Combine(UnityEngine.Application.persistentDataPath, "Jarvis"); if (!Directory.Exists(cachePath1)) { Directory.CreateDirectory(cachePath1); } var cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}"); using (Stream stream1 = File.OpenWrite(cachePath)) { var decryptedData = AESForFileKit.DecryptToBytes(assetBundlePath, bundlePassword, out var dataSize); yield return stream1.WriteAsync(decryptedData, 0, dataSize); } decryptPath = cachePath; decryptAssetBundleDict.TryAdd(assetBundlePath, decryptPath); } var request = AssetBundle.LoadFromFileAsync(decryptPath); yield return request; onCompleted?.Invoke(request.assetBundle); } private AssetBundle LoadDecryptAssetBundleSync(string assetBundlePath, string bundlePassword) { string decryptPath; if (decryptAssetBundleDict.TryGetValue(assetBundlePath, out var outPath) && File.Exists(outPath)) { decryptPath = outPath; } else { var decryptedData = AESForFileKit.DecryptToBytes(assetBundlePath, bundlePassword, out var dataSize); var cachePath1 = Path.Combine(UnityEngine.Application.persistentDataPath, "Jarvis"); if (!Directory.Exists(cachePath1)) { Directory.CreateDirectory(cachePath1); } var cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}"); while (File.Exists(cachePath)) { File.Delete(cachePath); cachePath = Path.Combine(cachePath1, $"decryptedBundle{Guid.NewGuid():N}"); } using (Stream stream = File.OpenWrite(cachePath)) { stream.Write(decryptedData, 0, dataSize); stream.Close(); } decryptPath = cachePath; decryptAssetBundleDict.TryAdd(assetBundlePath, decryptPath); } var assetBundle = AssetBundle.LoadFromFile(decryptPath); return assetBundle; } private IEnumerator LoadAssetFromAssetBundle(AssetBundle assetBundle, string assetName, UnityAction onLoadCompleted) where T : Object { T resultAsset = null; if (typeof(T) == typeof(Sprite)) { var texture2D = assetBundle.LoadAsset(assetName); if (texture2D != null) { var sprite = Sprite.Create(texture2D, new Rect(0, 0, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f)); resultAsset = sprite as T; } else { string[] assetNameArray = assetName.Split('.'); if (assetNameArray.Length > 1) { var resultAsset1 = assetBundle.LoadAssetWithSubAssets(assetNameArray[0]); resultAsset = Array.Find(resultAsset1, item => item.name == assetNameArray[1]); } else { } } } else { var assetLoadRequest = assetBundle.LoadAssetAsync(assetName); yield return assetLoadRequest; var gameObject = assetLoadRequest.asset; if (gameObject == null) { var subAssetLoadRequest = assetBundle.LoadAssetWithSubAssetsAsync(assetName); yield return subAssetLoadRequest; resultAsset = subAssetLoadRequest.asset as T; } else { resultAsset = gameObject as T; } } onLoadCompleted?.Invoke(resultAsset); } private void LoadAssetInternal(string assetBundleName, UnityAction onCompleted) { var assetBundlePath = GenerateAssetBundlePath(assetBundleName); GetAssetBundle(assetBundlePath, assetBundleName, onCompleted); } public AssetBundle GetAssetBundle(string assetBundlePath, string assetBundleName) { assetBundleName = assetBundleName.ToLower(); CrazyAsyKit.StopAction(assetBundleName + "Unload"); UnWaitUnload(assetBundleName); if (IsCacheAssetBundle(assetBundleName)) { CacheDependency(assetBundleName, new[] { assetBundleName }); var LoveLegendInfo = GetCacheAssetBundle(assetBundleName); return LoveLegendInfo?.assetBundle; } else { var LoveLegendInfo = new ZooMatchInfo(assetBundleName, null); CacheAssetBundle(assetBundleName, LoveLegendInfo); var isFileLoadType = File.Exists(assetBundlePath); if (isFileLoadType) { GetAssetBundleDependency(assetBundleName); var assetBundle = LoadDecryptAssetBundleSync(assetBundlePath, ZooMatchConstant.admsie); LoveLegendInfo.assetBundle = assetBundle; return assetBundle; } } return null; } public void GetAssetBundle(string assetBundlePath, string assetBundleName, UnityAction onCompleted) { assetBundleName = assetBundleName.ToLower(); CrazyAsyKit.StopAction(assetBundleName + "Unload"); UnWaitUnload(assetBundleName); if (IsCacheAssetBundle(assetBundleName)) { CacheDependency(assetBundleName, new[] { assetBundleName }); var LoveLegendInfo = GetCacheAssetBundle(assetBundleName); LoveLegendInfo?.callRes(assetBundleName, onCompleted); } else { var LoveLegendInfo = new ZooMatchInfo(assetBundleName, onCompleted); CacheAssetBundle(assetBundleName, LoveLegendInfo); var isFileLoadType = File.Exists(assetBundlePath); CrazyAsyKit.StartCoroutine(GetAssetBundleDependency(assetBundleName, delegate { if (isFileLoadType) { CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath, ZooMatchConstant.admsie, bundle => { if (bundle == null) { Debug.LogError("加载AssetBundle 失败:" + assetBundleName); UnCacheAssetBundle(assetBundleName); } else { ZooMatchInfo abInfo = GetCacheAssetBundle(assetBundleName); #if UNITY_EDITOR FileInfo file = new FileInfo(assetBundlePath); abInfo.SetAssetBundleSize(file.Length); #endif abInfo.onLoaded(assetBundleName, bundle); } })); } else { CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath, ZooMatchConstant.admsie, delegate (AssetBundle bundle) { if (bundle == null) { Debug.LogError("加载AssetBundle 失败:" + assetBundleName); UnCacheAssetBundle(assetBundleName); } else { var abInfoV = GetCacheAssetBundle(assetBundleName); var file = new FileInfo(assetBundlePath); abInfoV.SetAssetBundleSize(file.Length); abInfoV.onLoaded(assetBundleName, bundle); } })); } })); } } #region AssetBundle 包的依赖相关 private IEnumerator GetAssetBundleDependency(string assetBundleName, Action action) { assetBundleName = assetBundleName.ToLower(); var allDependencies = new List() { assetBundleName }; allDependencies.AddRange(GetABDependency(assetBundleName)); var index = 0; CacheDependency(assetBundleName, allDependencies.ToArray()); var allDepends = allDependencies.Where(dependencyAbName => dependencyAbName != assetBundleName).ToList(); foreach (var dependencyAbName in allDepends) { yield return LoadDependencyAssetBundle(assetBundleName, dependencyAbName, delegate { index++; }); } while (index != allDepends.Count) { yield return new WaitForEndOfFrame(); } action?.Invoke(); } private void GetAssetBundleDependency(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); var allDependencies = new List() { assetBundleName }; allDependencies.AddRange(GetABDependency(assetBundleName)); CacheDependency(assetBundleName, allDependencies.ToArray()); var allDepends = allDependencies.Where(dependencyAbName => dependencyAbName != assetBundleName).ToList(); foreach (var dependencyAbName in allDepends) { LoadDependencyAssetBundleSync(assetBundleName, dependencyAbName); } } private IEnumerator LoadDependencyAssetBundle(string parentAssetBundleName, string assetBundleName, UnityAction onCompleted) { assetBundleName = assetBundleName.ToLower(); var assetBundlePath = GenerateAssetBundlePath(assetBundleName, IsMD5Encrypt); CrazyAsyKit.StopAction(assetBundleName + "Unload"); UnWaitUnload(assetBundleName); if (IsCacheAssetBundle(assetBundleName)) { CacheAssetBundle(assetBundleName, null, true); var LoveLegendInfo = GetCacheAssetBundle(assetBundleName); LoveLegendInfo?.callRes(parentAssetBundleName, onCompleted); } else { var LoveLegendInfo = new ZooMatchInfo(assetBundleName, onCompleted); CacheAssetBundle(assetBundleName, LoveLegendInfo); if (File.Exists(assetBundlePath)) { yield return LoadABFromFileAsync(assetBundlePath, ZooMatchConstant.admsie, delegate (AssetBundle bundle) { if (bundle == null) { Debug.LogError("加载AssetBundle 失败:" + assetBundleName); UnCacheAssetBundle(assetBundleName); } else { GetCacheAssetBundle(assetBundleName).onLoaded(parentAssetBundleName, bundle); } }); } else { CrazyAsyKit.StartCoroutine(LoadABFromFileAsync(assetBundlePath, ZooMatchConstant.admsie, delegate (AssetBundle bundle) { if (bundle == null) { Debug.LogError("加载AssetBundle 失败:" + assetBundleName); UnCacheAssetBundle(assetBundleName); } else { GetCacheAssetBundle(assetBundleName).onLoaded(parentAssetBundleName, bundle); } })); } } } private AssetBundle LoadDependencyAssetBundleSync(string parentAssetBundleName, string assetBundleName) { assetBundleName = assetBundleName.ToLower(); var assetBundlePath = GenerateAssetBundlePath(assetBundleName, IsMD5Encrypt); CrazyAsyKit.StopAction(assetBundleName + "Unload"); UnWaitUnload(assetBundleName); if (IsCacheAssetBundle(assetBundleName)) { CacheAssetBundle(assetBundleName, null, true); var assetBundleInfos = GetCacheAssetBundle(assetBundleName); return assetBundleInfos?.assetBundle; } var LoveLegendInfo = new ZooMatchInfo(assetBundleName, null); CacheAssetBundle(assetBundleName, LoveLegendInfo); if (File.Exists(assetBundlePath)) { GetAssetBundleDependency(assetBundleName); var assetBundle = LoadDecryptAssetBundleSync(assetBundlePath, ZooMatchConstant.admsie); LoveLegendInfo.assetBundle = assetBundle; return assetBundle; } return null; } private string[] GetABDependency(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); return GetManifest().GetDirectDependencies(assetBundleName); } #region 依赖关系相关 private bool IsCacheDependency(string assetBundleName) { if (_cacheDependencyDict == null || _cacheDependencyDict.Count == 0) return false; assetBundleName = assetBundleName.ToLower(); var isDependency = _cacheDependencyDict.ContainsKey(assetBundleName); return isDependency; } private bool IsOtherDependency(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); var isDependency = false; if (_cacheDependencyDict == null || _cacheDependencyDict.Count == 0) return false; foreach (var keyValuePair in _cacheDependencyDict) { isDependency = keyValuePair.Value.Contains(assetBundleName); if (isDependency) { break; } } return isDependency; } private void CacheDependency(string assetBundleName, string[] assetBundleNameArray) { assetBundleName = assetBundleName.ToLower(); if (!IsCacheDependency(assetBundleName)) { for (int i = 0; i < assetBundleNameArray.Length; i++) { assetBundleNameArray[i] = assetBundleNameArray[i].ToLower(); } _cacheDependencyDict.Add(assetBundleName, assetBundleNameArray); } } private void UnCacheDependency(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); if (IsCacheDependency(assetBundleName)) { _cacheDependencyDict.Remove(assetBundleName); } } private string[] GetCacheDependency(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); string[] dependencyList = { }; if (IsCacheDependency(assetBundleName)) { dependencyList = _cacheDependencyDict[assetBundleName]; } return dependencyList; } #endregion #endregion #region 管理已加载的AssetBundle文件 private void CacheAssetBundle(string assetBundleName, ZooMatchInfo assetBundleInfoV, bool isDependency = false) { assetBundleName = assetBundleName.ToLower(); if (IsCacheAssetBundle(assetBundleName)) { if (isDependency) { } else { return; } } else { _cacheAssetBundleInfoDict.Add(assetBundleName, assetBundleInfoV); } } private void UnCacheAssetBundle(string assetBundleName) { if (IsCacheAssetBundle(assetBundleName)) { _cacheAssetBundleInfoDict.Remove(assetBundleName.ToLower()); } else { } } private ZooMatchInfo GetCacheAssetBundle(string assetBundleName) { ZooMatchInfo assetBundle = null; if (IsCacheAssetBundle(assetBundleName)) { assetBundle = _cacheAssetBundleInfoDict[assetBundleName.ToLower()]; } else { } return assetBundle; } private bool IsCacheAssetBundle(string assetBundleName) { assetBundleName = assetBundleName.ToLower(); if (_cacheAssetBundleInfoDict == null) { return false; } return _cacheAssetBundleInfoDict.ContainsKey(assetBundleName); } #endregion #region 卸载AssetBundle相关 #region 卸载AssetBundle相关信息管理 private bool WaitUnload(ZooMatchInfo assetBundleInfoV) { var isInWait = IsWaitUnload(assetBundleInfoV.assetBundleName); if (isInWait) return true; assetBundleInfoV.UpdateWaitUnloadCurrentTime(); _waitUnloadList.Add(assetBundleInfoV); return false; } private bool IsWaitUnloadKey(string assetBundleName) { var isWaitUnload = false; if (_waitUnloadList == null || _waitUnloadList.Count <= 0) return false; foreach (var bundleInfo in _waitUnloadList) { if (bundleInfo == null || !string.IsNullOrEmpty(bundleInfo.assetBundleName)) continue; if (!bundleInfo.assetBundleName.Equals(assetBundleName)) continue; isWaitUnload = true; break; } return isWaitUnload; } private bool IsWaitUnload(string assetBundleName) { var isWaitUnload = false; if (_waitUnloadList == null || _waitUnloadList.Count == 0) return false; if (!IsWaitUnloadKey(assetBundleName)) { foreach (var waitUnloadAssetBundle in _waitUnloadList) { if (waitUnloadAssetBundle == null || !string.IsNullOrEmpty(waitUnloadAssetBundle.assetBundleName)) continue; isWaitUnload = GetABDependency(waitUnloadAssetBundle.assetBundleName).Contains(assetBundleName); if (isWaitUnload) { break; } } } else { isWaitUnload = true; } return isWaitUnload; } private bool UnWaitUnload(string assetBundleName) { var isRemove = false; foreach (var bundleInfo in _waitUnloadList) { if (bundleInfo == null) continue; if (!bundleInfo.assetBundleName.Equals(assetBundleName)) continue; isRemove = _waitUnloadList.Remove(bundleInfo); break; } return isRemove; } #endregion public void UnloadAssetBundle(string assetBundleName, bool ignoreDependency = false, int unloadWaitTime = 0, Action onCompleted = null) { if (string.IsNullOrEmpty(assetBundleName)) { onCompleted?.Invoke(false); return; } bool unloadSucceed; if (IsCacheAssetBundle(assetBundleName)) { var abInfo = GetCacheAssetBundle(assetBundleName); if (abInfo == null) { onCompleted?.Invoke(false); return; } if (WaitUnload(abInfo) && !ignoreDependency) { unloadSucceed = false; UnCacheDependency(assetBundleName); onCompleted?.Invoke(unloadSucceed); } else { CrazyAsyKit.StopAction(assetBundleName + "Unload"); CrazyAsyKit.StartAction(assetBundleName + "Unload", delegate { unloadSucceed = IsCacheAssetBundle(assetBundleName) && UnloadAssetBundleInternal(assetBundleName, ignoreDependency); UnWaitUnload(assetBundleName); #if UNITY_EDITOR #endif onCompleted?.Invoke(unloadSucceed); }, unloadWaitTime); } } else { onCompleted?.Invoke(false); } } private bool UnloadAssetBundleInternal(string assetBundleName, bool isIgnoreDependency = false, bool isThorough = false) { var LoveLegendInfo = GetCacheAssetBundle(assetBundleName); if (LoveLegendInfo == null) { return false; } var dependencyArray = GetCacheDependency(assetBundleName); UnCacheDependency(assetBundleName); if (!isIgnoreDependency) { if (IsOtherDependency(assetBundleName)) { return false; } } UnCacheAssetBundle(assetBundleName); LoveLegendInfo.Unload(isThorough); foreach (var dependencyAssetBundleName in dependencyArray) { if (dependencyAssetBundleName == assetBundleName) { continue; } if (!IsOtherDependency(dependencyAssetBundleName)) { UnloadAssetBundleInternal(dependencyAssetBundleName, false, isThorough); } else { } } return true; } #endregion #endregion #region 封装直接加载资源的方法 public T GetAsset(string assetBundleName, string assetName) where T : Object { var assetBundlePath = GenerateAssetBundlePath(assetBundleName); var assetBundle = GetAssetBundle(assetBundlePath, assetBundleName); return assetBundle.LoadAsset(assetName); } public void GetAsset(string assetBundleName, string assetName, UnityAction onCompleted) where T : Object { LoadAssetInternal(assetBundleName, delegate (AssetBundle bundle) { if (bundle == null) { Debug.LogError("加载AssetBundle 失败:" + assetBundleName); } else { CrazyAsyKit.StartCoroutine(LoadAssetFromAssetBundle(bundle, assetName, onCompleted)); } }); } public void InitManifest(string assetBundleManifestName, UnityAction onCompleted) { CrazyAsyKit.StopAction(assetBundleManifestName + "Unload"); if (UnWaitUnload(assetBundleManifestName)) { } var assetBundles = AssetBundle.LoadFromFile($"{ZooMatchFileKit.GetFilePath()}{ZooMatchConstant.undles}"); var manifest = assetBundles.LoadAsset("assetbundlemanifest"); _manifest = manifest; onCompleted?.Invoke(manifest != null); } #endregion public void RecycleAssetBundle(string assetBundleName, UnityAction onCompleted = null) { if (IsCacheAssetBundle(assetBundleName)) { var abInfo = GetCacheAssetBundle(assetBundleName); abInfo.UnReferenced(assetBundleName); RecycleAssetBundleDependency(assetBundleName, UnloadWaitTime); if (abInfo.GetReferenced() <= 0) { UnloadAssetBundle(assetBundleName, false, UnloadWaitTime); } } onCompleted?.Invoke(); } private void RecycleAssetBundleDependency(string abName, int waitTime) { var otherAbName = GetCacheDependency(abName); if (otherAbName.Length <= 0) return; foreach (var dependAbName in otherAbName) { if (dependAbName == abName) continue; var dependAbInfo = GetCacheAssetBundle(dependAbName); if (dependAbInfo == null) continue; dependAbInfo.UnReferenced(abName); if (dependAbInfo.GetReferenced() <= 0) { UnloadAssetBundle(dependAbName, false, waitTime); } } } #region 其他方法 private string GenerateAssetBundlePath(string assetBundleName, bool isMD5 = false, bool isManifest = false, bool isAddABSuffix = true) { var fileName = assetBundleName.ToLower(); if (isMD5) { fileName = MD5String(fileName) + ".data"; } var filePath = _assetBundleRootPath.Replace("file:///", "") + assetBundleName.ToLower(); if (isAddABSuffix) { filePath += ZooMatchConstant.setbun; } if (!isManifest) return filePath; if (File.Exists(filePath)) { return filePath; } return filePath; } private static string MD5String(string str) { using var md5 = MD5.Create(); var hash = md5.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(str)); var tmp = new StringBuilder(); foreach (var i in hash) { tmp.Append(i.ToString("x2")); } return tmp.ToString(); } #endregion public void RecycleAsset(string assetUrl, UnityAction onCompleted) { RecycleAssetBundle(assetUrl, onCompleted); } private AssetBundleManifest GetManifest() { if (_manifest == null) { Debug.LogError("AssetBundleManifest is null"); var assetBundles = AssetBundle.LoadFromFile($"{ZooMatchFileKit.GetFilePath()}{ZooMatchConstant.undles}"); var manifest = assetBundles.LoadAsset("assetbundlemanifest"); _manifest = manifest; } return _manifest; } } }