using System; using System.IO; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace YooAsset { internal static class ManifestTools { /// /// 验证清单文件的二进制数据 /// public static bool VerifyManifestData(byte[] fileData, string hashValue) { if (fileData == null || fileData.Length == 0) return false; if (string.IsNullOrEmpty(hashValue)) return false; // 注意:兼容俩种验证方式 // 注意:计算MD5的哈希值通常为32个字符 string fileHash; if (hashValue.Length == 32) fileHash = HashUtility.BytesMD5(fileData); else fileHash = HashUtility.BytesCRC32(fileData); if (fileHash == hashValue) return true; else return false; } /// /// 序列化(JSON文件) /// public static void SerializeToJson(string savePath, PackageManifest manifest) { string json = JsonUtility.ToJson(manifest, true); FileUtility.WriteAllText(savePath, json); } /// /// 序列化(二进制文件) /// public static void SerializeToBinary(string savePath, PackageManifest manifest) { using (FileStream fs = new FileStream(savePath, FileMode.Create)) { // 创建缓存器 BufferWriter buffer = new BufferWriter(ManifestDefine.FileMaxSize); // 写入文件标记 buffer.WriteUInt32(ManifestDefine.FileSign); // 写入文件版本 buffer.WriteUTF8(manifest.FileVersion); // 写入文件头信息 buffer.WriteBool(manifest.EnableAddressable); buffer.WriteBool(manifest.LocationToLower); buffer.WriteBool(manifest.IncludeAssetGUID); buffer.WriteInt32(manifest.OutputNameStyle); buffer.WriteInt32(manifest.BuildBundleType); buffer.WriteUTF8(manifest.BuildPipeline); buffer.WriteUTF8(manifest.PackageName); buffer.WriteUTF8(manifest.PackageVersion); buffer.WriteUTF8(manifest.PackageNote); // 写入资源列表 buffer.WriteInt32(manifest.AssetList.Count); for (int i = 0; i < manifest.AssetList.Count; i++) { var packageAsset = manifest.AssetList[i]; buffer.WriteUTF8(packageAsset.Address); buffer.WriteUTF8(packageAsset.AssetPath); buffer.WriteUTF8(packageAsset.AssetGUID); buffer.WriteUTF8Array(packageAsset.AssetTags); buffer.WriteInt32(packageAsset.BundleID); buffer.WriteInt32Array(packageAsset.DependBundleIDs); } // 写入资源包列表 buffer.WriteInt32(manifest.BundleList.Count); for (int i = 0; i < manifest.BundleList.Count; i++) { var packageBundle = manifest.BundleList[i]; buffer.WriteUTF8(packageBundle.BundleName); buffer.WriteUInt32(packageBundle.UnityCRC); buffer.WriteUTF8(packageBundle.FileHash); buffer.WriteUTF8(packageBundle.FileCRC); buffer.WriteInt64(packageBundle.FileSize); buffer.WriteBool(packageBundle.Encrypted); buffer.WriteUTF8Array(packageBundle.Tags); buffer.WriteInt32Array(packageBundle.DependBundleIDs); } // 写入文件流 buffer.WriteToStream(fs); fs.Flush(); } } /// /// 反序列化(JSON文件) /// public static PackageManifest DeserializeFromJson(string jsonContent) { return JsonUtility.FromJson(jsonContent); } /// /// 反序列化(二进制文件) /// public static PackageManifest DeserializeFromBinary(byte[] binaryData) { // 创建缓存器 BufferReader buffer = new BufferReader(binaryData); // 读取文件标记 uint fileSign = buffer.ReadUInt32(); if (fileSign != ManifestDefine.FileSign) throw new Exception("Invalid manifest file !"); // 读取文件版本 string fileVersion = buffer.ReadUTF8(); if (fileVersion != ManifestDefine.FileVersion) throw new Exception($"The manifest file version are not compatible : {fileVersion} != {ManifestDefine.FileVersion}"); PackageManifest manifest = new PackageManifest(); { // 读取文件头信息 manifest.FileVersion = fileVersion; manifest.EnableAddressable = buffer.ReadBool(); manifest.LocationToLower = buffer.ReadBool(); manifest.IncludeAssetGUID = buffer.ReadBool(); manifest.OutputNameStyle = buffer.ReadInt32(); manifest.BuildBundleType = buffer.ReadInt32(); manifest.BuildPipeline = buffer.ReadUTF8(); manifest.PackageName = buffer.ReadUTF8(); manifest.PackageVersion = buffer.ReadUTF8(); manifest.PackageNote = buffer.ReadUTF8(); // 检测配置 if (manifest.EnableAddressable && manifest.LocationToLower) throw new Exception("Addressable not support location to lower !"); // 读取资源列表 int packageAssetCount = buffer.ReadInt32(); CreateAssetCollection(manifest, packageAssetCount); for (int i = 0; i < packageAssetCount; i++) { var packageAsset = new PackageAsset(); packageAsset.Address = buffer.ReadUTF8(); packageAsset.AssetPath = buffer.ReadUTF8(); packageAsset.AssetGUID = buffer.ReadUTF8(); packageAsset.AssetTags = buffer.ReadUTF8Array(); packageAsset.BundleID = buffer.ReadInt32(); packageAsset.DependBundleIDs = buffer.ReadInt32Array(); FillAssetCollection(manifest, packageAsset); } // 读取资源包列表 int packageBundleCount = buffer.ReadInt32(); CreateBundleCollection(manifest, packageBundleCount); for (int i = 0; i < packageBundleCount; i++) { var packageBundle = new PackageBundle(); packageBundle.BundleName = buffer.ReadUTF8(); packageBundle.UnityCRC = buffer.ReadUInt32(); packageBundle.FileHash = buffer.ReadUTF8(); packageBundle.FileCRC = buffer.ReadUTF8(); packageBundle.FileSize = buffer.ReadInt64(); packageBundle.Encrypted = buffer.ReadBool(); packageBundle.Tags = buffer.ReadUTF8Array(); packageBundle.DependBundleIDs = buffer.ReadInt32Array(); FillBundleCollection(manifest, packageBundle); } } // 初始化资源清单 InitManifest(manifest); return manifest; } #region 解析资源清单辅助方法 public static void InitManifest(PackageManifest manifest) { // 填充资源包内包含的主资源列表 foreach (var packageAsset in manifest.AssetList) { int bundleID = packageAsset.BundleID; if (bundleID >= 0 && bundleID < manifest.BundleList.Count) { var packageBundle = manifest.BundleList[bundleID]; packageBundle.IncludeMainAssets.Add(packageAsset); } else { throw new Exception($"Invalid bundle id : {bundleID} Asset path : {packageAsset.AssetPath}"); } } // 填充资源包引用关系 for (int index = 0; index < manifest.BundleList.Count; index++) { var sourceBundle = manifest.BundleList[index]; foreach (int dependIndex in sourceBundle.DependBundleIDs) { var dependBundle = manifest.BundleList[dependIndex]; dependBundle.AddReferenceBundleID(index); } } } public static void CreateAssetCollection(PackageManifest manifest, int assetCount) { manifest.AssetList = new List(assetCount); manifest.AssetDic = new Dictionary(assetCount); if (manifest.EnableAddressable) { manifest.AssetPathMapping1 = new Dictionary(assetCount * 3); } else { if (manifest.LocationToLower) manifest.AssetPathMapping1 = new Dictionary(assetCount * 2, StringComparer.OrdinalIgnoreCase); else manifest.AssetPathMapping1 = new Dictionary(assetCount * 2); } if (manifest.IncludeAssetGUID) manifest.AssetPathMapping2 = new Dictionary(assetCount); else manifest.AssetPathMapping2 = new Dictionary(); } public static void FillAssetCollection(PackageManifest manifest, PackageAsset packageAsset) { // 添加到列表集合 manifest.AssetList.Add(packageAsset); // 注意:我们不允许原始路径存在重名 string assetPath = packageAsset.AssetPath; if (manifest.AssetDic.ContainsKey(assetPath)) throw new System.Exception($"AssetPath have existed : {assetPath}"); else manifest.AssetDic.Add(assetPath, packageAsset); // 填充AssetPathMapping1 { string location = packageAsset.AssetPath; // 添加原生路径的映射 if (manifest.AssetPathMapping1.ContainsKey(location)) throw new System.Exception($"Location have existed : {location}"); else manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath); // 添加无后缀名路径的映射 string locationWithoutExtension = Path.ChangeExtension(location, null); if (ReferenceEquals(location, locationWithoutExtension) == false) { if (manifest.AssetPathMapping1.ContainsKey(locationWithoutExtension)) YooLogger.Warning($"Location have existed : {locationWithoutExtension}"); else manifest.AssetPathMapping1.Add(locationWithoutExtension, packageAsset.AssetPath); } } // 添加可寻址地址 if (manifest.EnableAddressable) { string location = packageAsset.Address; if (string.IsNullOrEmpty(location) == false) { if (manifest.AssetPathMapping1.ContainsKey(location)) throw new System.Exception($"Location have existed : {location}"); else manifest.AssetPathMapping1.Add(location, packageAsset.AssetPath); } } // 填充AssetPathMapping2 if (manifest.IncludeAssetGUID) { if (manifest.AssetPathMapping2.ContainsKey(packageAsset.AssetGUID)) throw new System.Exception($"AssetGUID have existed : {packageAsset.AssetGUID}"); else manifest.AssetPathMapping2.Add(packageAsset.AssetGUID, packageAsset.AssetPath); } } public static void CreateBundleCollection(PackageManifest manifest, int bundleCount) { manifest.BundleList = new List(bundleCount); manifest.BundleDic1 = new Dictionary(bundleCount); manifest.BundleDic2 = new Dictionary(bundleCount); manifest.BundleDic3 = new Dictionary(bundleCount); } public static void FillBundleCollection(PackageManifest manifest, PackageBundle packageBundle) { // 初始化资源包 packageBundle.InitBundle(manifest); // 添加到列表集合 manifest.BundleList.Add(packageBundle); manifest.BundleDic1.Add(packageBundle.BundleName, packageBundle); manifest.BundleDic2.Add(packageBundle.FileName, packageBundle); manifest.BundleDic3.Add(packageBundle.BundleGUID, packageBundle); } #endregion /// /// 获取资源文件的后缀名 /// public static string GetRemoteBundleFileExtension(string bundleName) { string fileExtension = Path.GetExtension(bundleName); return fileExtension; } /// /// 获取远端的资源文件名 /// public static string GetRemoteBundleFileName(int nameStyle, string bundleName, string fileExtension, string fileHash) { if (nameStyle == (int)EFileNameStyle.HashName) { return StringUtility.Format("{0}{1}", fileHash, fileExtension); } else if (nameStyle == (int)EFileNameStyle.BundleName) { return bundleName; } else if (nameStyle == (int)EFileNameStyle.BundleName_HashName) { if (string.IsNullOrEmpty(fileExtension)) { return StringUtility.Format("{0}_{1}", bundleName, fileHash); } else { string fileName = bundleName.Remove(bundleName.LastIndexOf('.')); return StringUtility.Format("{0}_{1}{2}", fileName, fileHash, fileExtension); } } else { throw new NotImplementedException($"Invalid name style : {nameStyle}"); } } } }