using CaoCao.XAsset; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; namespace CaoCao.Editor { public class BuildVersions : IBuildJobStep { public void Start(BuildJob job) { var versions = Settings.GetDefaultVersions(); var version = versions.Get(job.parameters.name); var manifest = XAsset.Utility.LoadFromFile(Settings.GetDataPath(version.file)); if (!UpdateManifest(job, manifest)) return; SaveVersions(job, version, manifest, versions); } //"E:/HxMobileProject/Bundles/Android/hotfix_268c9e49444c768e1402d137a53a9d7e.json" //"E:/HxMobileProject/BundlesCache/Android/version.json" private static void SaveVersions(BuildJob job, XAsset.Version version, Manifest manifest, Versions versions) { if (job.parameters.buildNumber > 0) version.ver = job.parameters.buildNumber; else version.ver++; //这里添加分包 version.subPackage = job.parameters.subPackage; version.name = job.parameters.name; var json = JsonUtility.ToJson(manifest); var bytes = Encoding.UTF8.GetBytes(json); version.hash = XAsset.Utility.ComputeHash(bytes); var file = version.GetFilename(); var path = Settings.GetDataPath(file); File.WriteAllText(path, json); job.changes.Add(file); var settings = Settings.GetDefaultSettings(); // save version var info = new FileInfo(path); version.file = file; version.size = (ulong) info.Length; versions.Set(version); versions.encryption = settings.bundle.encryption; for (var index = 0; index < versions.data.Count; index++) { var item = versions.data[index]; if (File.Exists(Settings.GetDataPath(item.file))) continue; versions.data.RemoveAt(index); index--; } versions.Save(Settings.GetCachePath(Versions.Filename)); } private static bool UpdateManifest(BuildJob job, Manifest manifest) { var getBundles = new Dictionary(); foreach (var bundle in manifest.bundles) getBundles[bundle.name] = bundle; var dirs = new List(); var assets = new List(); for (var index = 0; index < job.bundles.Count; index++) { var bundle = job.bundles[index]; AddAsset(bundle, dirs, index, assets); if (getBundles.TryGetValue(bundle.group, out var value) && value.hash == bundle.hash && value.size == bundle.size) continue; //记录变更的资源,加入到job.changes中 job.changes.Add(bundle.file); } //没有变更过,退出 if (job.changes.Count == 0 && !job.parameters.forceRebuild && job.bundles.Count == getBundles.Count) { job.error = $"{job.parameters.name} : Nothing to build."; return false; } var map = assets.ToDictionary(a => a.path); foreach (var asset in assets) { var dependencies = Settings.GetDependencies(asset.path); var deps = new List(); foreach (var dependency in dependencies) { if (map.TryGetValue(dependency, out var dep)) { deps.Add(dep.id); } } asset.deps = deps.ToArray(); } manifest.Clear(); manifest.saveBundleName = Settings.GetDefaultSettings().bundle.saveBundleName; manifest.bundles = job.bundles.ConvertAll(Converter).ToArray(); manifest.assets = assets.ToArray(); manifest.dirs = dirs.ToArray(); manifest.build = job.parameters.name; BuildAssetPacks(manifest); return true; } private static void AddAsset(BuildBundle bundle, IList dirs, int index, ICollection assets) { foreach (var asset in bundle.assets) { var buildAsset = Settings.GetAsset(asset); if (buildAsset.addressMode == AddressMode.LoadByDependencies) continue; var dir = Path.GetDirectoryName(asset)?.Replace("\\", "/"); var pos = dirs.IndexOf(dir); if (pos == -1) { pos = dirs.Count; dirs.Add(dir); } var manifestAsset = new ManifestAsset { id = assets.Count, name = Path.GetFileName(asset), path = asset, bundle = index, dir = pos, addressMode = buildAsset.addressMode }; assets.Add(manifestAsset); } } private static ManifestBundle Converter(BuildBundle input) { return new ManifestBundle { name = input.group, size = input.size, hash = input.hash, deps = input.deps }; } private static void BuildAssetPacks(Manifest manifest) { manifest.packs = Array.Empty(); manifest.OnAfterDeserialize(); var packs = new List(); var packed = new HashSet(); var assetPacks = new List(Settings.FindAssets()); assetPacks.Sort((a, b) => a.id.CompareTo(b.id)); foreach (var config in assetPacks) { var assets = new HashSet(); Settings.Collect(config, assets); var set = new HashSet(); foreach (var asset in assets) { if (!manifest.TryGetAsset(asset, out var result)) continue; var bundle = manifest.bundles[result.bundle]; set.Add(result.bundle); set.UnionWith(bundle.deps); } set.ExceptWith(packed); packed.UnionWith(set); if (set.Count <= 0) continue; var bundles = new List(set); bundles.Sort(); CreateAssetPacks(manifest, config.name, packs, bundles, config.desc); } var unpacked = new List(); for (var i = 0; i < manifest.bundles.Length; i++) { if (packed.Contains(i)) continue; unpacked.Add(i); } if (unpacked.Count > 0) CreateAssetPacks(manifest, "Unpacked", packs, unpacked, "Unpacked"); var setting = Settings.GetDefaultSettings(); if (setting.bundle.buildAssetPackAssets) { foreach (var item in packs) PackAssets(item); } manifest.packs = packs.ToArray(); } private static void CreateAssetPacks( Manifest manifest, string name, ICollection packs, IEnumerable bundles, string desc) { var maxSize = Settings.GetDefaultSettings().bundle.maxAssetPackSize; var packedSize = 0UL; var index = 0; var pack = new XAsset.AssetPack { name = name, nameIndexed = $"{manifest.build.ToLower()}_{name.ToLower()}_{index}{XAsset.AssetPack.Extension}", desc = desc, manifest = manifest }; packs.Add(pack); foreach (var id in bundles) { var bundle = manifest.bundles[id]; var file = new FileInfo(Settings.GetDataPath(bundle.file)); if (!file.Exists) continue; if (packedSize > maxSize) { index++; packedSize = 0; pack = new XAsset.AssetPack { name = name, nameIndexed = $"{manifest.build.ToLower()}_{name.ToLower()}_{index}{XAsset.AssetPack.Extension}", desc = desc, manifest = manifest }; packs.Add(pack); } packedSize += bundle.size; pack.assets.Add(new AssetLocation {id = id, size = bundle.size}); manifest.bundles[id].pack = packs.Count - 1; pack.size += bundle.size; } } private static string GetDisplayName(ManifestBundle bundle) { return $"{bundle.file}({XAsset.Utility.FormatBytes(bundle.size)})"; } private static void PackAssets(XAsset.AssetPack pack) { var path = Settings.GetCachePath(pack.nameIndexed); using (var writer = new BinaryWriter(File.OpenWrite(path))) { foreach (var asset in pack.assets) { var bundle = pack.manifest.bundles[asset.id]; var bytes = File.ReadAllBytes(Settings.GetDataPath(bundle.file)); writer.Write(bundle.file); writer.Write(bundle.size); asset.offset = (ulong) writer.BaseStream.Position; writer.Write(bytes); } } var info = new FileInfo(path); pack.hash = XAsset.Utility.ComputeHash(path); pack.size = (ulong) info.Length; pack.packed = true; pack.file = pack.GetFilename(); var savePath = Settings.GetDataPath(pack.file); info.CopyTo(savePath, true); var assets = string.Join("\n\t", pack.assets.ConvertAll(input => GetDisplayName(pack.manifest.bundles[input.id]))); XAsset.Logger.I($"Pack {pack.name} to {pack.nameIndexed} with following assets({XAsset.Utility.FormatBytes(pack.size)}) :\n\t{assets}"); } } }