#pragma warning disable 184 using System; using System.IO; using ICSharpCode.SharpZipLib.Zip; using TriLibCore.General; using TriLibCore.Mappers; using TriLibCore.Utils; using UnityEngine; namespace TriLibCore { /// Represents an Asset Loader class which works with Zip files. //todo: unify async loading. public static class AssetLoaderZip { /// /// Buffer size used to copy the zip file entries content. /// private const int ZipBufferSize = 4096; /// /// Called when all model resources have been loaded. /// /// The asset loading context, containing callbacks and model loading data. private static void OnMaterialsLoad(AssetLoaderContext assetLoaderContext) { var zipLoadCustomContextData = CustomDataHelper.GetCustomData(assetLoaderContext.CustomData); if (zipLoadCustomContextData != null) { if (zipLoadCustomContextData.Stream != null) { zipLoadCustomContextData.Stream.Close(); } if (zipLoadCustomContextData.OnMaterialsLoad != null) { zipLoadCustomContextData.OnMaterialsLoad(assetLoaderContext); } } } /// /// Called when any loading error occurs. /// /// Thrown error containing an attached context. private static void OnError(IContextualizedError contextualizedError) { if (contextualizedError?.GetContext() is AssetLoaderContext assetLoaderContext) { var zipLoadCustomContextData = CustomDataHelper.GetCustomData(assetLoaderContext.CustomData); if (zipLoadCustomContextData != null) { if (zipLoadCustomContextData.Stream != null) { zipLoadCustomContextData.Stream.Close(); } if (zipLoadCustomContextData.OnError != null) { zipLoadCustomContextData.OnError.Invoke(contextualizedError); } } } } /// Loads a model from the given Zip file path asynchronously. /// The Zip file path. /// The Method to call on the Main Thread when the Model is loaded but resources may still pending. /// The Method to call on the Main Thread when the Model and resources are loaded. /// The Method to call when the Model loading progress changes. /// The Method to call on the Main Thread when any error occurs. /// The Game Object that will be the parent of the loaded Game Object. Can be null. /// The options to use when loading the Model. /// The Custom Data that will be passed along the Context. /// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file. /// Turn on this field to avoid loading the model immediately and chain the Tasks. /// The method to call on the parallel Thread before the Unity objects are created. /// The asset loader context, containing model loading information and the output game object. public static AssetLoaderContext LoadModelFromZipFile(string path, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null, bool haltTask = false, Action onPreLoad = null) { Stream stream = null; var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry); var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData { ZipFile = zipFile, ZipEntry = zipEntry, Stream = stream, OnError = onError, OnMaterialsLoad = onMaterialsLoad }); if (customContextData != null) { CustomDataHelper.SetCustomData(ref customDataDic, customContextData); } return AssetLoader.LoadModelFromStream(memoryStream, path, fileExtension, onLoad, OnMaterialsLoad, onProgress, OnError, wrapperGameObject, assetLoaderOptions, customDataDic, haltTask, onPreLoad, true); } /// Loads a model from the given Zip file Stream asynchronously. /// The Stream containing the Zip data. /// The Method to call on the Main Thread when the Model is loaded but resources may still pending. /// The Method to call on the Main Thread when the Model and resources are loaded. /// The Method to call when the Model loading progress changes. /// The Method to call on the Main Thread when any error occurs. /// The Game Object that will be the parent of the loaded Game Object. Can be null. /// The options to use when loading the Model. /// The Custom Data that will be passed along the Context. /// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file. /// Turn on this field to avoid loading the model immediately and chain the Tasks. /// /// The Zip file path. /// The method to call on the parallel Thread before the Unity objects are created. /// The asset loader context, containing model loading information and the output game object. public static AssetLoaderContext LoadModelFromZipStream(Stream stream, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null, bool haltTask = false, string modelFilename = null, Action onPreLoad = null) { var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry); var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData { ZipFile = zipFile, ZipEntry = zipEntry, Stream = stream, OnError = onError, OnMaterialsLoad = onMaterialsLoad }); if (customContextData != null) { CustomDataHelper.SetCustomData(ref customDataDic, customContextData); } return AssetLoader.LoadModelFromStream(memoryStream, modelFilename, fileExtension, onLoad, OnMaterialsLoad, onProgress, OnError, wrapperGameObject, assetLoaderOptions, customDataDic, haltTask, onPreLoad, true); } /// Loads a model from the given Zip file path synchronously. /// The Zip file path. /// The Method to call on the Main Thread when any error occurs. /// The Game Object that will be the parent of the loaded Game Object. Can be null. /// The options to use when loading the Model. /// The Custom Data that will be passed along the Context. /// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file. /// The asset loader context, containing model loading information and the output game object. public static AssetLoaderContext LoadModelFromZipFileNoThread(string path, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null) { Stream stream = null; var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry); var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData { ZipFile = zipFile, ZipEntry = zipEntry, Stream = stream, OnError = onError }); if (customContextData != null) { CustomDataHelper.SetCustomData(ref customDataDic, customContextData); } var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, path, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, customDataDic, true); stream.Close(); return assetLoaderContext; } /// Loads a model from the given Zip file Stream synchronously. /// The Stream containing the Zip data. /// The Method to call on the Main Thread when any error occurs. /// The Game Object that will be the parent of the loaded Game Object. Can be null. /// The options to use when loading the Model. /// The Custom Data that will be passed along the Context. /// The Model inside the Zip file extension. If null TriLib will try to find a suitable model format inside the Zip file. /// The asset loader context, containing model loading information and the output game object. public static AssetLoaderContext LoadModelFromZipStreamNoThread(Stream stream, Action onError, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null) { var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry); var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData { ZipFile = zipFile, ZipEntry = zipEntry, Stream = stream, OnError = onError }); if (customContextData != null) { CustomDataHelper.SetCustomData(ref customDataDic, customContextData); } var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, null, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, customDataDic, true); stream.Close(); return assetLoaderContext; } /// Configures the Zip Model loading, adding the Zip External Data/Texture Mappers. /// The method to execute when any error occurs. /// The Stream containing the Zip data. /// The Zip file path. /// The options to use when loading the Model. /// The Model inside the Zip file extension. /// The Zip file instance. /// The model Zip entry inside the Zip file. /// The model file data stream. private static Stream SetupZipModelLoading(Action onError, ref Stream stream, string path, ref AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile, out ZipEntry modelZipEntry) { if (assetLoaderOptions == null) { assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions(); } if (!ArrayUtils.ContainsType(assetLoaderOptions.TextureMappers)) { assetLoaderOptions.TextureMappers = new TextureMapper[] { ScriptableObject.CreateInstance() }; } if (!(assetLoaderOptions.ExternalDataMapper is ZipFileExternalDataMapper)) { assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance(); } if (stream == null) { stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); } var validExtensions = Readers.Extensions; zipFile = new ZipFile(stream); Stream memoryStream = null; modelZipEntry = null; foreach (ZipEntry zipEntry in zipFile) { if (!zipEntry.IsFile) { continue; } var checkingFileExtension = FileUtils.GetFileExtension(zipEntry.Name, false); if (fileExtension != null && checkingFileExtension == fileExtension) { memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile); modelZipEntry = zipEntry; } else if (validExtensions.Contains(checkingFileExtension)) { memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile); modelZipEntry = zipEntry; break; } } if (memoryStream == null) { var exception = new Exception("Unable to find a suitable model on the Zip file. Please inform a valid model file extension."); onError?.Invoke(new ContextualizedError(exception, "Error")); } return memoryStream; } /// Copies the contents of a Zip file Entry into a Memory Stream. /// The Model inside the Zip file extension. /// The Zip file Entry. /// The Zip file instance. /// A memory stream with the zip file entry contents. public static Stream ZipFileEntryToStream(out string fileExtension, ZipEntry zipEntry, ZipFile zipFile) { var buffer = new byte[ZipBufferSize]; var zipFileStream = zipFile.GetInputStream(zipEntry); var memoryStream = new MemoryStream(ZipBufferSize); ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipFileStream, memoryStream, buffer); memoryStream.Seek(0, SeekOrigin.Begin); zipFileStream.Close(); fileExtension = FileUtils.GetFileExtension(zipEntry.Name, false); return memoryStream; } } }