using System.Collections.Generic; using UnityEngine; using XGame.Editor.Build.AssetBundles; using XGame.Framework.Json; using XFADefine = XGame.Framework.Asset.Define; namespace XGame.Editor.Build { public struct AbEncryptInfo { public long bundleId; public string originBundleName; public uint offset; } [BuildCommand((uint)BuildCommandPriority.ExportAssets)] class CmdExportAssets : BaseBuildCommand, ICommandExecuter { #region copy assets /// /// 异或加密使用的key值:1023 /// const int encryptKey = 0x03FF; Dictionary CollectAbEncryptInfos() { var abEncryptInfos = new Dictionary(); var abmanifest = AssetBundleBackupManifest.Load(); if (abmanifest != null) { //manifest包的加密信息 var encrypt = new AbEncryptInfo() { bundleId = long.Parse(XFADefine.MANIFEST_BUNDLE_NAME), originBundleName = XFADefine.MANIFEST_BUNDLE_NAME, offset = XFADefine.MANIFEST_BUNDLE_OFFSET }; abEncryptInfos.Add(XFADefine.MANIFEST_BUNDLE_NAME, encrypt); var abInfos = abmanifest.bundleInfos; foreach (var abInfo in abInfos) { encrypt = new AbEncryptInfo() { bundleId = abInfo.bundleId, originBundleName = abInfo.nameOrGuid, offset = abInfo.offset }; abEncryptInfos.Add(encrypt.bundleId.ToString(), encrypt); } } return abEncryptInfos; } private Dictionary CollectBundleCrc() { var bundleCrcMap = new Dictionary(); var manifestPath = Asset.PathDefine.AssetBundleOutputPath + "/BundleCrcInfoManifest.manifest"; if (System.IO.File.Exists(manifestPath)) { var text = System.IO.File.ReadAllText(manifestPath); var manifest = XJson.ToObject(text); if (manifest.bundleInfos != null) { foreach (var info in manifest.bundleInfos) { bundleCrcMap.Add(info.bundleName, info.bundleCRC); } } } return bundleCrcMap; } byte[] ReadAllBytes(string assetPath, Dictionary abEncryptInfos) { byte[] bytes = System.IO.File.ReadAllBytes(assetPath); if (assetPath.Contains(Asset.PathDefine.AssetBundleOutputPath)) { var fileName = System.IO.Path.GetFileNameWithoutExtension(assetPath); if (!abEncryptInfos.TryGetValue(fileName, out var encryptInfo)) return bytes; if (encryptInfo.offset <= 0) return bytes; var encryptData = System.Text.Encoding.UTF8.GetBytes(encryptInfo.originBundleName); for (var i = 0; i < encryptData.Length; i++) // 依次对字符串中各字符进行操作 { var current = encryptData[i]; encryptData[i] = (byte)(current ^ encryptKey); // 将密钥与字符异或 } //var fileData = System.IO.File.ReadAllBytes(assetPath); var length = bytes.Length + encryptInfo.offset; var buffer = new byte[length]; System.Array.Copy(encryptData, 0, buffer, 0, encryptInfo.offset); System.Array.Copy(bytes, 0, buffer, encryptInfo.offset, bytes.Length); return buffer; } return bytes; } void WriteAllBytes(string path, byte[] bytes) { var destDir = Asset.FileUtil.GetDirectoryName(path, true); if (!System.IO.Directory.Exists(destDir)) System.IO.Directory.CreateDirectory(destDir); System.IO.File.WriteAllBytes(path, bytes); } void CopyAssets(string streamingAssetsPath, Dictionary assetMap, Dictionary abEncryptInfos) { foreach (var item in assetMap) { string assetPath = item.Key; if (!System.IO.File.Exists(assetPath)) { BuildLog.Error($"Asset can not found:{assetPath}"); return; } byte[] bytes = ReadAllBytes(assetPath, abEncryptInfos); string toPath = System.IO.Path.Combine(streamingAssetsPath, item.Value); WriteAllBytes(toPath, bytes); } } #endregion //#region split assets //void CollectionExpansionAssetInfos(ProductAssetsContext assetsContext, Dictionary assetMap, out Dictionary assetInfos, out CustomPackageInfo[] customPackageInfos) //{ // assetInfos = new Dictionary(assetMap.Count); // foreach (var item in assetMap) // { // EditorAssetInfo assetInfo = new EditorAssetInfo(); // assetInfo.packageId = ManifestUtilities.MainAssetId; // assetInfo.path = item.Value; // assetInfos.Add(item.Key, assetInfo); // } // if (Context.config.ignoreSplitAssets) // { // customPackageInfos = null; // return; // } // CustomPackageControl customPackageControl = new CustomPackageControl(assetsContext); // customPackageControl.CollectPackages(); // customPackageInfos = customPackageControl.GeneratePackages(); // Dictionary assetPkgMap = new Dictionary(); // if (null != customPackageInfos && customPackageInfos.Length > 0) // { // foreach (var pkgInfo in customPackageInfos) // { // var errors = customPackageControl.GetPackageErrors(pkgInfo.uid); // if (null != errors && errors.Length > 0) // { // foreach (var error in errors) // { // BuildLog.Warn("[GenExpancePackageManifest] discover error asset! uid: {0}, id: {1}, error code:{2}, is ext asset: {3} addressable: {4}\nerror asset path:{5}\nsource:{6}" // , pkgInfo.uid, pkgInfo.packageID, error.code, error.asset.isExtAsset, error.asset.addressable, error.asset.assetPath, error.asset.source); // } // } // foreach (var path in pkgInfo.assetPaths) // { // assetPkgMap.Add(path, pkgInfo.packageID); // } // } // } // foreach (var item in assetPkgMap) // { // if (!assetInfos.TryGetValue(item.Key, out var assetInfo)) // { // BuildLog.Warn($"Expansion path not found in assets: {item.Key}"); // continue; // } // assetInfo.packageId = item.Value; // } //} //void SplitAssets(string outputDir, string streamingAssetsPath, Dictionary assetInfos) //{ // foreach (var assetInfo in assetInfos.Values) // { // bool isMainAsset = assetInfo.packageId == ManifestUtilities.MainAssetId || string.IsNullOrEmpty(assetInfo.packageId); // string toPath = GetOutputPath(outputDir, assetInfo.path, isMainAsset, assetInfo.packageId); // var destDir = KAFileUtil.GetDirectoryName(toPath, true); // if (!System.IO.Directory.Exists(destDir)) System.IO.Directory.CreateDirectory(destDir); // string fromPath = System.IO.Path.Combine(streamingAssetsPath, assetInfo.path); // if (isMainAsset) // System.IO.File.Copy(fromPath, toPath); // else // System.IO.File.Move(fromPath, toPath); // } //} //#endregion //#region generate runtime-configs //ExpansionPackageManifest GenerateUniquePackageManifest(string outputDir, string streamingAssetsPath, CustomPackageInfo[] customPackageInfos, ref Dictionary assetInfos) //{ // if (null == customPackageInfos || customPackageInfos.Length <= 0) return null; // List infos = new List(); // foreach (var item in customPackageInfos) // { // if (item.isShared) continue; // if ((null == item.assetPaths || item.assetPaths.Length <= 0) && (null == item.dependencies || item.dependencies.Length <= 0)) continue; // var info = new ExpansionPackageInfo() // { // downloadMode = item.downloadMode, // id = item.packageID, // priority = item.priority, // name = item.packageName, // updateMode = item.updateMode, // }; // info.dependencies = new string[item.dependencies.Length]; // item.dependencies.CopyTo(info.dependencies, 0); // infos.Add(info); // } // ExpansionPackageManifest manifest = new ExpansionPackageManifest(); // manifest.packageInfos = infos.ToArray(); // string outputPath = System.IO.Path.Combine(outputDir, "packages", "main", "assets"); // ManifestUtilities.SaveUniquePackageManifest(outputPath, manifest); // string fromPath = System.IO.Path.Combine(outputPath, ManifestUtilities.AssetManifestRelativePath, "UniquePackages.manifest"); // string toDir = System.IO.Path.Combine(streamingAssetsPath, ManifestUtilities.AssetManifestRelativePath); // if (!System.IO.Directory.Exists(toDir)) System.IO.Directory.CreateDirectory(toDir); // string toPath = System.IO.Path.Combine(toDir, "UniquePackages.manifest"); // System.IO.File.Copy(fromPath, toPath); // EditorAssetInfo assetInfo = new EditorAssetInfo(); // assetInfo.packageId = ManifestUtilities.MainAssetId; // assetInfo.path = System.IO.Path.Combine(ManifestUtilities.AssetManifestRelativePath, /*ManifestUtilities.UniquePackagesManifestFileName*/"UniquePackages.manifest").Replace('\\', '/'); // assetInfos.Add(assetInfo.path, assetInfo); // Debug.Log($"GenerateUniquePackageManifest. assetInfo.path {assetInfo.path} , assetInfo {assetInfo.packageId}"); // return manifest; //} //void GenerateAssetManifest(string outputDir, string streamingAssetsPath, ref Dictionary assetInfos, CustomPackageInfo[] customPackageInfos) //{ // List list = new List(); // foreach (var assetInfo in assetInfos.Values) // { // bool isMainAsset = assetInfo.packageId == ManifestUtilities.MainAssetId || string.IsNullOrEmpty(assetInfo.packageId); // if (!isMainAsset) continue; // string path = GetOutputPath(outputDir, assetInfo.path, true, null); // AssetInfo info = new AssetInfo(); // info.relativePath = assetInfo.path.Replace('\\', '/'); // info.checkCodeType = ECheckCodeType.MD5; // info.size = new System.IO.FileInfo(path).Length; // info.checkCode = AssetUtilities.GetBinaryCheckCode(path, ECheckCodeType.MD5); // list.Add(info); // } // AssetManifest mainManifest = new AssetManifest(); // mainManifest.assetInfos = list.ToArray(); // string assetDir = System.IO.Path.Combine(outputDir, "packages", "main", "assets"); // ManifestUtilities.SaveMainAssetManifest(assetDir, mainManifest); // string mainManifestFileName = $"{ManifestUtilities.MainAssetId}.{ManifestUtilities.ManifestExtensionName}"; // string fromPath = System.IO.Path.Combine(assetDir, ManifestUtilities.AssetManifestRelativePath, mainManifestFileName); // string toDir = System.IO.Path.Combine(streamingAssetsPath, ManifestUtilities.AssetManifestRelativePath); // if (!System.IO.Directory.Exists(toDir)) System.IO.Directory.CreateDirectory(toDir); // string toPath = System.IO.Path.Combine(toDir, mainManifestFileName); // System.IO.File.Copy(fromPath, toPath); // EditorAssetInfo editorAssetInfo = new EditorAssetInfo(); // editorAssetInfo.packageId = ManifestUtilities.MainAssetId; // editorAssetInfo.path = System.IO.Path.Combine(ManifestUtilities.AssetManifestRelativePath, mainManifestFileName); // assetInfos.Add(editorAssetInfo.path, editorAssetInfo); // if (null == customPackageInfos || customPackageInfos.Length <= 0) return; // foreach (var pkgInfo in customPackageInfos) // { // if ((null == pkgInfo.assetPaths || pkgInfo.assetPaths.Length <= 0) && (null == pkgInfo.dependencies || pkgInfo.dependencies.Length <= 0)) continue; // list.Clear(); // foreach (var path in pkgInfo.assetPaths) // { // if (!assetInfos.TryGetValue(path, out var assetInfo)) // { // BuildLog.Warn("Can't found asset: {0}, package: {1}", path, pkgInfo.packageID); // continue; // } // string outputPath = GetOutputPath(outputDir, assetInfo.path, false, pkgInfo.packageID); // AssetInfo info = new AssetInfo(); // info.relativePath = assetInfo.path.Replace('\\', '/'); // info.checkCodeType = ECheckCodeType.MD5; // info.size = new System.IO.FileInfo(outputPath).Length; // info.checkCode = AssetUtilities.GetBinaryCheckCode(outputPath, ECheckCodeType.MD5); // list.Add(info); // } // AssetManifest manifest = new AssetManifest(); // manifest.assetInfos = list.ToArray(); // assetDir = System.IO.Path.Combine(outputDir, "packages", pkgInfo.packageID, "assets"); // ManifestUtilities.SaveExpansionAssetManifest(assetDir, pkgInfo.packageID, manifest); // editorAssetInfo = new EditorAssetInfo(); // editorAssetInfo.packageId = pkgInfo.packageID; // editorAssetInfo.path = System.IO.Path.Combine(ManifestUtilities.AssetManifestRelativePath, $"{pkgInfo.packageID}.{ManifestUtilities.ManifestExtensionName}"); // assetInfos.Add(editorAssetInfo.path, editorAssetInfo); // } //} //#endregion //#region generate backend-configs //void CollectPlacardAssets(HashSet innerBundles, string dirPath, int platformTag, string streamAsstesDir, ref List list) //{ // var dirs = System.IO.Directory.GetDirectories(dirPath); // foreach (var dir in dirs) // { // var subPlatformTag = platformTag; // var entryPath = dir.Replace('\\', '/'); // if (subPlatformTag != 1) subPlatformTag = KAFileUtil.VerifyPlatformPath(entryPath); // if (subPlatformTag == -1) continue; // CollectPlacardAssets(innerBundles, dir, platformTag, streamAsstesDir, ref list); // } // string[] files = System.IO.Directory.GetFiles(dirPath); // foreach (var file in files) // { // if (AssetUtilities.IsIgnorFile(file)) continue; // if (!(AssetUtilities.IsInnerAsset(file, AssetUtilities.PlacardTag) || innerBundles.Contains(file))) continue; // var entryPath = file.Replace('\\', '/'); // string relativePath; // if (platformTag == 1) // { // //当前平台资源 // var platformName = PlatformUtil.ActivePlatform.ToString(); // relativePath = entryPath.Substring(entryPath.IndexOf(platformName) + platformName.Length + 1); // } // else // { // relativePath = entryPath.Substring(streamAsstesDir.Length + 1); // } // list.Add(relativePath); // } //} //void GeneratePlacardManifest(string backendConfigDir, ProductAssetsContext assetsContext) //{ // var innerBundles = assetsContext.CollectPlacardBundles(); // string streamAsstesDir = Kailash.Framework.Adapter.Driven.FileSystem.FileUtil.WorkPath.Replace('\\', '/'); // List placardAssets = new List(); // CollectPlacardAssets(innerBundles, streamAsstesDir, 0, streamAsstesDir, ref placardAssets); // List placardInfos = new List(); // foreach (var item in placardAssets) // { // placardInfos.Add(new PlacardInfo() { filePath = item }); // } // var manifest = new PlacardManifest(); // manifest.placardInfos = placardInfos.ToArray(); // string json = KailashJson.ToJson(manifest); // string path = System.IO.Path.Combine(backendConfigDir, "PlacardManifest.txt"); // System.IO.File.WriteAllText(path, json); //} //void CalculateHotUpdateCheckCode(string outputDir, Dictionary assetInfos) //{ // var bundleCrcMap = CollectBundleCrc(); // var regex = new Regex(@"CRC: (\d+)", RegexOptions.Multiline); // foreach (var item in assetInfos) // { // string ext = System.IO.Path.GetExtension(item.Key); // if (ext != ".bundle") // { // bool isMainAsset = item.Value.packageId == ManifestUtilities.MainAssetId || string.IsNullOrEmpty(item.Value.packageId); // string filePath = GetOutputPath(outputDir, item.Value.path, isMainAsset, item.Value.packageId); // item.Value.checkCode = KailashNative.GetMD5(filePath); // } // else // { // if (bundleCrcMap.TryGetValue(System.IO.Path.GetFileNameWithoutExtension(item.Key), out var bundleCrc)) // { // item.Value.checkCode = bundleCrc; // } // else // { // string path = string.Format("{0}.manifest", item.Key); // if (System.IO.File.Exists(path)) // { // string content = System.IO.File.ReadAllText(path); // var match = regex.Match(content); // item.Value.checkCode = match.Groups[1].Value; // } // else // { // BuildLog.Error($"CalculateHotUpdateCheckCode:找不到CRC配置信息。Path:{item.Key}"); // } // } // } // } //} //#endregion string GetOutputPath(string outputDir, string relativePath, bool isMainAsset, string packageId) { return isMainAsset ? System.IO.Path.Combine(outputDir, "packages", "main", "assets", relativePath) : System.IO.Path.Combine(outputDir, "packages", packageId, "assets", relativePath); } BuildErrorCode ICommandExecuter.Execute() { var streamingAssetsPath = Application.streamingAssetsPath.Replace('\\', '/'); if (System.IO.Directory.Exists(streamingAssetsPath)) { UnityEditor.FileUtil.DeleteFileOrDirectory(streamingAssetsPath); UnityEditor.AssetDatabase.Refresh(); } var pkgPath = System.IO.Path.Combine(Context.config.project.outputPath, "packages"); if (System.IO.Directory.Exists(pkgPath)) { UnityEditor.FileUtil.DeleteFileOrDirectory(pkgPath); } // 拷贝资源 Dictionary assetMap = AssetUtils.CollectionAllAssets(); Dictionary abEncryptInfos = CollectAbEncryptInfos(); CopyAssets(streamingAssetsPath, assetMap, abEncryptInfos); //if (Context.config.editor.isNative) { //本地打包拷贝完资源即可退出 UnityEditor.AssetDatabase.Refresh(); return BuildErrorCode.CmdCompleted; } //var assetsContext = new ProductAssetsContext(); //assetsContext.Init(); //// 拆分分包资源 //CollectionExpansionAssetInfos(assetsContext, assetMap, out Dictionary assetInfos, out CustomPackageInfo[] customPackageInfos); //SplitAssets(Context.config.project.outputPath, streamingAssetsPath, assetInfos); // 移动或拷贝资源 //// 生成专属包配置清单(Runtime):用于运行时,对分包资源进行操作 //ExpansionPackageManifest manifest = GenerateUniquePackageManifest(Context.config.project.outputPath, streamingAssetsPath, customPackageInfos, ref assetInfos); //// 生成分包资源文件清单(包含主包)(Runtime):用于运行时,对分包资源进行校验 //GenerateAssetManifest(Context.config.project.outputPath, streamingAssetsPath, ref assetInfos, customPackageInfos); //// 生成海报配置(Editor):用于制作更新包时,单独提取海报相关资源 //string backendConfigDir = System.IO.Path.Combine(Context.config.project.outputPath, "backend-configs"); //if (!System.IO.Directory.Exists(backendConfigDir)) System.IO.Directory.CreateDirectory(backendConfigDir); //GeneratePlacardManifest(backendConfigDir, assetsContext); //// 生成专属包配置清单(Editor):用于上传分包时,上传相关的分包信息 //string json = XJson.ToJson(manifest); //string path = System.IO.Path.Combine(backendConfigDir, "UniquePackages.txt"); //System.IO.File.WriteAllText(path, json); //// 生成所有资源清单:用于制作更新包时,对资源进行对比 //CalculateHotUpdateCheckCode(Context.config.project.outputPath, assetInfos); //List infos = new List(); //foreach (var info in assetInfos.Values) //{ // infos.Add(info); //} //EditorAssetInfoManifest editorAssetInfoManifest = new EditorAssetInfoManifest(); //editorAssetInfoManifest.assetInfos = infos.ToArray(); //json = XJson.ToJson(editorAssetInfoManifest); //path = System.IO.Path.Combine(backendConfigDir, "AssetsManifest.txt"); //System.IO.File.WriteAllText(path, json); ////由于分包会将资源从streamingAssetsPath移出,因此在最后才调用Refresh() //UnityEditor.AssetDatabase.Refresh(); //return BuildErrorCode.CmdCompleted; } } }