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;
}
}
}