using System; using System.Collections.Generic; using System.IO; using ICSharpCode.SharpZipLib.Zip; using TriLibCore.General; using TriLibCore.Mappers; using TriLibCore.Utils; using UnityEngine; using Object = UnityEngine.Object; 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; /// 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 Meshes and hierarchy are loaded. /// The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process. /// The Custom Data that will be passed along the AssetLoaderContext. /// 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 LoadModelFromZipFile(string path, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null) { Stream stream = null; var memoryStream = SetupZipModelLoading(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile); return AssetLoader.LoadModelFromStream(memoryStream, path, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext) { var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData; zipLoadCustomContextData?.Stream.Close(); onMaterialsLoad?.Invoke(assetLoaderContext); }, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData { ZipFile = zipFile, Stream = stream, CustomData = customContextData }); } /// 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 Meshes and hierarchy are loaded. /// The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process. /// The Custom Data that will be passed along the AssetLoaderContext. /// 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 LoadModelFromZipStream(Stream stream, Action onLoad, Action onMaterialsLoad, Action onProgress, Action onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null) { var memoryStream = SetupZipModelLoading(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile); return AssetLoader.LoadModelFromStream(memoryStream, null, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext) { var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData; zipLoadCustomContextData?.Stream.Close(); onMaterialsLoad?.Invoke(assetLoaderContext); }, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData { ZipFile = zipFile, Stream = stream, CustomData = customContextData }); } /// 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process. /// The Custom Data that will be passed along the AssetLoaderContext. /// 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(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile); var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, path, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData { ZipFile = zipFile, CustomData = customContextData }); 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 Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process. /// The Custom Data that will be passed along the AssetLoaderContext. /// 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(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile); var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, null, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData { ZipFile = zipFile, CustomData = customContextData }); stream.Close(); return assetLoaderContext; } /// /// Called when any loading error occurs. /// /// Thrown error containing an attached context. private static void OnError(IContextualizedError contextualizedError) { var assetLoaderContext = contextualizedError?.GetContext() as AssetLoaderContext; var zipLoadCustomContextData = assetLoaderContext?.CustomData as ZipLoadCustomContextData; zipLoadCustomContextData?.Stream.Close(); assetLoaderContext?.OnError?.Invoke(contextualizedError); } /// Configures the Zip Model loading, adding the Zip External Data/Texture Mappers. /// The Stream containing the Zip data. /// The Zip file path. /// The Method to call on the Main Thread when any error occurs. /// The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process. /// The Model inside the Zip file extension. /// The Zip file instance. /// The model file data stream. private static Stream SetupZipModelLoading(ref Stream stream, string path, Action onError, AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile) { if (assetLoaderOptions == null) { assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions(); } assetLoaderOptions.TextureMapper = ScriptableObject.CreateInstance(); assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance(); assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.TextureMapper); assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.ExternalDataMapper); if (stream == null) { stream = new FileStream(path, FileMode.Open); } var validExtensions = Readers.Extensions; zipFile = new ZipFile(stream); Stream memoryStream = 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); } else if (validExtensions.Contains(checkingFileExtension)) { memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile); 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."); if (onError != null) { var error = exception as IContextualizedError; onError(error ?? new ContextualizedError(exception, assetLoaderOptions)); } else { throw exception; } } 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; } } }