Files
ZooMatch-GP/Assets/ZooMatch/ToolKit/ZooMatchKit.cs
T

853 lines
30 KiB
C#

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<ZooMatchKit>, ILoadAsset
{
private const bool IsMD5Encrypt = false;
private string _assetBundleRootPath = ZooMatchFileKit.GetFilePath();
private static readonly Dictionary<string, ZooMatchInfo> _cacheAssetBundleInfoDict =
new Dictionary<string, ZooMatchInfo>();
private static readonly Dictionary<string, string[]> _cacheDependencyDict = new Dictionary<string, string[]>();
private static readonly List<ZooMatchInfo> _waitUnloadList = new List<ZooMatchInfo>();
private static AssetBundleManifest _manifest;
private static readonly Dictionary<string, string> decryptAssetBundleDict = new Dictionary<string, string>();
private const int UnloadWaitTime = 30;
#region 加载AssetBundle的基础方法
public IEnumerator LoadABFromFileAsync(string assetBundlePath, string bundlePassword,
UnityAction<AssetBundle> onCompleted)
{
yield return LoadDecryptAssetBundle(assetBundlePath, bundlePassword, onCompleted);
}
private IEnumerator LoadDecryptAssetBundle(string assetBundlePath, string bundlePassword,
UnityAction<AssetBundle> 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<T>(AssetBundle assetBundle, string assetName,
UnityAction<T> onLoadCompleted) where T : Object
{
T resultAsset = null;
if (typeof(T) == typeof(Sprite))
{
var texture2D = assetBundle.LoadAsset<Texture2D>(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<T>(assetNameArray[0]);
resultAsset = Array.Find(resultAsset1, item => item.name == assetNameArray[1]);
}
else
{
}
}
}
else
{
var assetLoadRequest = assetBundle.LoadAssetAsync<T>(assetName);
yield return assetLoadRequest;
var gameObject = assetLoadRequest.asset;
if (gameObject == null)
{
var subAssetLoadRequest = assetBundle.LoadAssetWithSubAssetsAsync<T>(assetName);
yield return subAssetLoadRequest;
resultAsset = subAssetLoadRequest.asset as T;
}
else
{
resultAsset = gameObject as T;
}
}
onLoadCompleted?.Invoke(resultAsset);
}
private void LoadAssetInternal(string assetBundleName, UnityAction<AssetBundle> 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<AssetBundle> 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<string>() { 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<string>() { 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<AssetBundle> 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<bool> 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<T>(string assetBundleName, string assetName) where T : Object
{
var assetBundlePath = GenerateAssetBundlePath(assetBundleName);
var assetBundle = GetAssetBundle(assetBundlePath, assetBundleName);
return assetBundle.LoadAsset<T>(assetName);
}
public void GetAsset<T>(string assetBundleName, string assetName, UnityAction<T> 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<bool> onCompleted)
{
CrazyAsyKit.StopAction(assetBundleManifestName + "Unload");
if (UnWaitUnload(assetBundleManifestName))
{
}
var assetBundles =
AssetBundle.LoadFromFile($"{ZooMatchFileKit.GetFilePath()}{ZooMatchConstant.undles}");
var manifest = assetBundles.LoadAsset<AssetBundleManifest>("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>("assetbundlemanifest");
_manifest = manifest;
}
return _manifest;
}
}
}