using CaoCao.XAsset; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; namespace CaoCao.Editor { public class BuildBundles : IBuildJobStep { private static bool _bXor = false; //是否需要加密 private static string[] _xorList = new string[] { "hotfixb_win", "hotfixb_ios", "hotfixb_android" }; public void Start(BuildJob job) { if (job.bundledAssets.Count > 0) if (!BuildBundlesWithBundledAssets(job)) return; if (job.rawAssets.Count == 0) return; BuildBundlesWithRawAssets(job); } //"E:/HxMobileProject/Bundles/Android/hotfix/gamehotfix_game_hotfix_1724706769b660e9bb10348d54791798.bundle" private static void BuildBundlesWithRawAssets(BuildJob job) { var settings = Settings.GetDefaultSettings().bundle; if (settings.saveBundleName) { foreach (var asset in job.rawAssets) { if (string.IsNullOrEmpty(asset.path)) { Logger.E($"RawAsset not found:{asset.path}"); continue; } var file = new FileInfo(asset.path); var hash = XAsset.Utility.ComputeHash(asset.path); var nameWithAppendHash = $"{hash}{Settings.extension}"; var name = asset.bundle.Replace(Settings.extension, string.Empty); nameWithAppendHash = $"{name}_{nameWithAppendHash}"; var bundle = new BuildBundle { group = asset.bundle, hash = hash, file = nameWithAppendHash, assets = new List {asset.path}, size = (ulong) file.Length, raw = true }; var path = Settings.GetDataPath(bundle.file); XAsset.Utility.CreateDirectoryIfNecessary(path); //把"Assets/GameAssets/GameHotfix/game.hotfix" copy 到 bundle目录 if (!File.Exists(path)) file.CopyTo(path, true); job.bundles.Add(bundle); } } else { foreach (var asset in job.rawAssets) { if (string.IsNullOrEmpty(asset.path)) { Logger.E($"RawAsset not found:{asset.path}"); continue; } var file = new FileInfo(asset.path); var hash = XAsset.Utility.ComputeHash(asset.path); var nameWithAppendHash = $"{hash}{Settings.extension}"; var bundle = new BuildBundle { group = asset.bundle, hash = hash, file = nameWithAppendHash, assets = new List {asset.path}, size = (ulong) file.Length, raw = true }; var path = Settings.GetDataPath(bundle.file); XAsset.Utility.CreateDirectoryIfNecessary(path); if (!File.Exists(path)) file.CopyTo(path, true); job.bundles.Add(bundle); } } } private static bool BuildWithEncodeFunc(BuildJob job, IReadOnlyDictionary nameWithBundles, IAssetBundleManifest manifest, Func encodeFunc) { var settings = Settings.GetDefaultSettings().bundle; var assetBundles = manifest.GetAllAssetBundles(); foreach (var assetBundle in assetBundles) if (nameWithBundles.TryGetValue(assetBundle, out var bundle)) { var path = Settings.GetCachePath(assetBundle); bundle.deps = Array.ConvertAll(manifest.GetAllDependencies(assetBundle), input => nameWithBundles[input].id); var info = new FileInfo(path); if (info.Exists) { var hash = XAsset.Utility.ComputeHash(path); var nameWithAppendHash = $"{hash}{Settings.extension}"; //"E:/HxMobileProject/BundlesCache/Android/b9d3a695563b75faf8b40843773a6262.bundle" var buildPath = Settings.GetCachePath(nameWithAppendHash); bundle.size = encodeFunc(path, hash, buildPath); bundle.hash = XAsset.Utility.ComputeHash(buildPath); bundle.file = $"{bundle.hash}{Settings.extension}"; if (settings.saveBundleName) { var name = assetBundle.Replace(Settings.extension, string.Empty); bundle.file = $"{name}_{bundle.file}"; } //"E:/HxMobileProject/Bundles/Android/assetspackb/common_asset_490b592d96dff85f5898c860db836145.bundle" var newPath = Settings.GetDataPath(bundle.file); XAsset.Utility.CreateDirectoryIfNecessary(newPath); if (File.Exists(newPath)) File.Delete(newPath); File.Move(buildPath, newPath); } else { job.TreatError($"File not found: {info}"); return false; } } else { job.TreatError($"Bundle not found: {assetBundle}"); return false; } return true; } private static ulong EncodeWithoutEncryption(string path, string hash, string newPath) { var bytes = File.ReadAllBytes(path); var size = bytes.Length; XAsset.Utility.CreateDirectoryIfNecessary(newPath); using (var writer = new BinaryWriter(File.OpenWrite(newPath))) { writer.Write(File.ReadAllBytes(path)); } return (ulong) size; } private static ulong EncodeWithEncryption(string path, string hash, string newPath) { string pattern = @"\d+_texture"; // 正则表达式模式 //判断是否需要加密 bool bXor = false; foreach(var p in _xorList) { if(path.Contains(p)) { bXor = true; break; } } #if ENABLE_NEWENCRY //@取消贴图加密 //不加密此处无代码 #else if (!bXor) { MatchCollection matches = Regex.Matches(path, pattern); if (matches.Count > 0) bXor = true; } #endif var bytes = File.ReadAllBytes(path); if (bXor) { byte[] hashBytes = System.Text.Encoding.UTF8.GetBytes(hash); XAsset.Utility.GetSelfXorBytes(bytes, hashBytes); UnityEngine.Debug.Log($"加密:{path}"); } ulong size = XAsset.Utility.WriteOffset + (ulong)bytes.Length; XAsset.Utility.CreateDirectoryIfNecessary(newPath); using (var writer = new BinaryWriter(File.OpenWrite(newPath))) { writer.Write(hash); writer.Write(bytes); } return size; } private static IAssetBundleManifest BuildAssetBundles(BuildJob job) { if (Builder.BuildPipeline != null) return Builder.BuildPipeline.BuildAssetBundles(Settings.PlatformCachePath, job.bundles.ConvertAll( input => new AssetBundleBuild { assetNames = input.assets.ToArray(), assetBundleName = input.group }).ToArray(), job.parameters.options, EditorUserBuildSettings.activeBuildTarget); job.TreatError("Pipeline == null"); return null; } private static bool BuildBundlesWithBundledAssets(BuildJob job) { var nameWithBundles = job.bundles.ToDictionary(o => o.group); foreach (var asset in job.bundledAssets) { if (!nameWithBundles.TryGetValue(asset.bundle, out var bundle)) { var id = job.bundles.Count; bundle = new BuildBundle { id = id, group = asset.bundle, assets = new List() }; job.bundles.Add(bundle); nameWithBundles[asset.bundle] = bundle; } bundle.assets.Add(asset.path); } var manifest = BuildAssetBundles(job); if (manifest == null) { job.TreatError($"Failed to build AssetBundles with {job.parameters.name}."); return false; } var settings = Settings.GetDefaultSettings(); if (settings.bundle.encryption) { if (!BuildWithEncodeFunc(job, nameWithBundles, manifest, EncodeWithEncryption)) return false; } else { if (!BuildWithEncodeFunc(job, nameWithBundles, manifest, EncodeWithoutEncryption)) return false; } return true; } } }