diff --git a/src/flambe/asset/AssetEntry.hx b/src/flambe/asset/AssetEntry.hx index e43d1151..f074edbe 100644 --- a/src/flambe/asset/AssetEntry.hx +++ b/src/flambe/asset/AssetEntry.hx @@ -20,6 +20,9 @@ enum AssetFormat // Raw text/data Data; + + // Archived file format + ZIP; TAR; } /** diff --git a/src/flambe/asset/Manifest.hx b/src/flambe/asset/Manifest.hx index b46aa1b9..91c1c539 100644 --- a/src/flambe/asset/Manifest.hx +++ b/src/flambe/asset/Manifest.hx @@ -196,7 +196,7 @@ class Manifest return basePath; } - private static function inferFormat (url :String) :AssetFormat + public static function inferFormat (url :String) :AssetFormat { var extension = url.getUrlExtension(); if (extension != null) { @@ -216,6 +216,8 @@ class Manifest case "ogg": return OGG; case "opus": return OPUS; case "wav": return WAV; + case "zip": return ZIP; + case "tar": return TAR; } } else { Log.warn("No file extension for asset, it will be loaded as data", ["url", url]); diff --git a/src/flambe/platform/BasicAssetPackLoader.hx b/src/flambe/platform/BasicAssetPackLoader.hx index d2ddacee..c60efe02 100644 --- a/src/flambe/platform/BasicAssetPackLoader.hx +++ b/src/flambe/platform/BasicAssetPackLoader.hx @@ -172,7 +172,7 @@ class BasicAssetPackLoader map = _pack.textures; case MP3, M4A, OPUS, OGG, WAV: map = _pack.sounds; - case Data: + case Data, ZIP, TAR: map = _pack.files; } diff --git a/src/flambe/platform/flash/FlashAssetPackLoader.hx b/src/flambe/platform/flash/FlashAssetPackLoader.hx index 419cb3c6..09a87721 100644 --- a/src/flambe/platform/flash/FlashAssetPackLoader.hx +++ b/src/flambe/platform/flash/FlashAssetPackLoader.hx @@ -23,6 +23,14 @@ import flambe.asset.AssetEntry; import flambe.asset.Manifest; import flambe.util.Assert; +import format.tar.Data; + +import haxe.io.BytesInput; +import haxe.io.Bytes; + +using flambe.util.Strings; +using StringTools; + class FlashAssetPackLoader extends BasicAssetPackLoader { public function new (platform :FlashPlatform, manifest :Manifest) @@ -66,6 +74,146 @@ class FlashAssetPackLoader extends BasicAssetPackLoader var urlLoader = new URLLoader(req); dispatcher = urlLoader; create = function () return new BasicFile(urlLoader.data); + + case ZIP: + var urlLoader = new URLLoader(req); + urlLoader.dataFormat = flash.net.URLLoaderDataFormat.BINARY; + dispatcher = urlLoader; + var events = new EventGroup(); + events.addDisposingListener(dispatcher, Event.COMPLETE, function (_) { + var bytes = Bytes.ofData(urlLoader.data); + var zip = new haxe.zip.Reader(new BytesInput(bytes)); + var entries:List = zip.read(); + + for (entry in entries) { + var extension = entry.fileName.getUrlExtension(); + if(entry.fileName.charAt(0)=="." || entry.fileName.indexOf("/.")>=0) { + extension=""; + } + if(extension=="" || extension==null) { + Log.warn("No extension or weird format for zipped entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + _assetsRemaining += 1; + promise.total += entry.dataSize; + + var format = Manifest.inferFormat(entry.fileName); + var name = entry.fileName.removeFileExtension(); + if(format == Data) { + name = entry.fileName; + } + var entryFlambe = manifest.add(name, entry.fileName, entry.dataSize, format); + var asset; + var canAdd = false; + switch(format) { + case JXR, PNG, JPG, GIF: + var loader = new Loader(); + loader.loadBytes(entry.data.getData()); + var dispatcher:IEventDispatcher = loader.contentLoaderInfo; + var events = new EventGroup(); + events.addDisposingListener(dispatcher, Event.COMPLETE, function (_) { + create = function () { + var bitmap :Bitmap = cast loader.content; + var texture = _platform.getRenderer().createTexture(bitmap.bitmapData); + bitmap.bitmapData.dispose(); + return texture; + }; + asset = create(); + handleLoad(entryFlambe, asset); + }); + case MP3: + var sound = new Sound(); + sound.loadCompressedDataFromByteArray(entry.data.getData(), entry.dataSize); + create = function () return new FlashSound(sound); + canAdd = true; + case Data: + create = function () return new BasicFile(entry.data.toString()); + canAdd = true; + default: + Log.warn("Format not supported for zipped entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + if (canAdd) { + asset = create(); + handleLoad(entryFlambe, asset); + } + } + }); + handleLoad(entry, {}); + return; + + case TAR: + var urlLoader = new URLLoader(req); + urlLoader.dataFormat = flash.net.URLLoaderDataFormat.BINARY; + dispatcher = urlLoader; + var events = new EventGroup(); + events.addDisposingListener(dispatcher, Event.COMPLETE, function (_) { + var bytes = Bytes.ofData(urlLoader.data); + var tar = new format.tar.Reader(new BytesInput(bytes)); + var entries:List = tar.read(); + + for (entry in entries) { + var extension = entry.fileName.getUrlExtension(); + if(entry.fileName.charAt(0)=="." || entry.fileName.indexOf("/.")>=0) { + extension=""; + } + if(extension=="" || extension==null) { + Log.warn("No extension or weird format for tar entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + _assetsRemaining += 1; + promise.total += entry.fileSize; + + var format = Manifest.inferFormat(entry.fileName); + var name = entry.fileName.removeFileExtension(); + if(format == Data) { + name = entry.fileName; + } + var entryFlambe = manifest.add(name, entry.fileName, entry.fileSize, format); + var asset; + var canAdd = false; + switch(format) { + case JXR, PNG, JPG, GIF: + var loader = new Loader(); + loader.loadBytes(entry.data.getData()); + var dispatcher:IEventDispatcher = loader.contentLoaderInfo; + var events = new EventGroup(); + events.addDisposingListener(dispatcher, Event.COMPLETE, function (_) { + create = function () { + var bitmap :Bitmap = cast loader.content; + var texture = _platform.getRenderer().createTexture(bitmap.bitmapData); + bitmap.bitmapData.dispose(); + return texture; + }; + asset = create(); + handleLoad(entryFlambe, asset); + }); + case MP3: + var sound = new Sound(); + sound.loadCompressedDataFromByteArray(entry.data.getData(), entry.fileSize); + create = function () return new FlashSound(sound); + canAdd = true; + case Data: + create = function () return new BasicFile(entry.data.toString()); + canAdd = true; + default: + _assetsRemaining -= 1; + promise.total -= entry.fileSize; + Log.warn("Format not supported for tar entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + if (canAdd) { + asset = create(); + handleLoad(entryFlambe, asset); + } + } + }); + handleLoad(entry, {}); + return; default: // Should never happen @@ -102,6 +250,6 @@ class FlashAssetPackLoader extends BasicAssetPackLoader override private function getAssetFormats (fn :Array -> Void) { - fn([JXR, PNG, JPG, GIF, MP3, Data]); + fn([JXR, PNG, JPG, GIF, MP3, Data, ZIP, TAR]); } } diff --git a/src/flambe/platform/html/HtmlAssetPackLoader.hx b/src/flambe/platform/html/HtmlAssetPackLoader.hx index 70d14cc5..2a81f42f 100644 --- a/src/flambe/platform/html/HtmlAssetPackLoader.hx +++ b/src/flambe/platform/html/HtmlAssetPackLoader.hx @@ -8,6 +8,8 @@ import js.Browser; import js.html.*; import haxe.Http; +import haxe.io.BytesInput; +import haxe.io.Bytes; import flambe.asset.AssetEntry; import flambe.asset.Manifest; @@ -16,6 +18,11 @@ import flambe.util.Promise; import flambe.util.Signal0; import flambe.util.Signal1; +import format.tar.Data; + +using StringTools; +using flambe.util.Strings; + class HtmlAssetPackLoader extends BasicAssetPackLoader { public function new (platform :HtmlPlatform, manifest :Manifest) @@ -138,16 +145,127 @@ class HtmlAssetPackLoader extends BasicAssetPackLoader downloadText(url, entry, function (text) { handleLoad(entry, new BasicFile(text)); }); + + case ZIP: + if (supportsBlob()) { + downloadArrayBuffer(url, entry, function (buffer) { + var bytes = Bytes.ofData(cast new js.html.Uint8Array(buffer)); + var zip = new haxe.zip.Reader(new BytesInput(bytes)); + var entries:List = zip.read(); + + for (entry in entries) { + var extension = entry.fileName.getUrlExtension(); + if(entry.fileName.charAt(0)=="." || entry.fileName.indexOf("/.")>=0) { + extension=""; + } + if(extension=="" || extension==null) { + Log.warn("No extension or weird format for zipped entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + _assetsRemaining += 1; + promise.total += entry.dataSize; + + var type = ""; + var format = Manifest.inferFormat(entry.fileName); + var name = entry.fileName.removeFileExtension(); + switch(format) { + case PNG: type = "image/png"; + case JPG: type = "image/jpeg"; + case JXR: type = "image/vnd.ms-photo"; + case GIF: type = "image/gif"; + case WEBP: type = "image/webp"; + case M4A: type = "audio/mp4"; + case MP3: type = "audio/mpeg"; + case OGG, OPUS: type = "audio/ogg"; + case WAV: type = "audio/wave"; + case Data: name = entry.fileName; + case ZIP: type = "application/zip"; + case DDS, PVR, PKM: type = "application/octet-stream"; + case TAR: type = "application/x-tar"; + } + + var buff = new js.html.Uint8Array(entry.data.getData()); + var blob = new Blob([buff], {type:type}); + if(format == Data) { + blob = new Blob([haxe.zip.Reader.unzip(entry)], {type:type}); + } + var generatedUrl = _URL.createObjectURL(blob); + var entryFlambe = manifest.add(name, entry.fileName, entry.dataSize, format); + loadEntry(generatedUrl, entryFlambe); + } + }); + } else { + Log.warn("Blob not supported, ignoring this asset", ["url", url]); + } + handleLoad(entry, {}); + case TAR: + if (supportsBlob()) { + downloadArrayBuffer(url, entry, function (buffer) { + var bytes = Bytes.ofData(cast new js.html.Uint8Array(buffer)); + var tar = new format.tar.Reader(new BytesInput(bytes)); + var entries:List = tar.read(); + + for (entry in entries) { + var extension = entry.fileName.getUrlExtension(); + if(entry.fileName.charAt(0)=="." || entry.fileName.indexOf("/.")>=0) { + extension=""; + } + if(extension=="" || extension==null) { + Log.warn("No extension or weird format for tar entry, ignoring this asset", ["url", entry.fileName]); + continue; + } + + _assetsRemaining += 1; + promise.total += entry.fileSize; + + loadTarEntry(entry); + }; + }); + } else { + Log.warn("Blob not supported, ignoring this asset", ["url", url]); + } + handleLoad(entry, {}); } } - + + private function loadTarEntry (entry:format.tar.Entry) { + var type = ""; + var format = Manifest.inferFormat(entry.fileName); + var name = entry.fileName.removeFileExtension(); + switch(format) { + case PNG: type = "image/png"; + case JPG: type = "image/jpeg"; + case JXR: type = "image/vnd.ms-photo"; + case GIF: type = "image/gif"; + case WEBP: type = "image/webp"; + case M4A: type = "audio/mp4"; + case MP3: type = "audio/mpeg"; + case OGG, OPUS: type = "audio/ogg"; + case WAV: type = "audio/wave"; + case Data: name = entry.fileName; + case ZIP: type = "application/zip"; + case DDS, PVR, PKM: type = "application/octet-stream"; + case TAR: type = "application/x-tar"; + } + var buff = new js.html.Uint8Array(entry.data.getData()); + var blob = new Blob([buff], {type:type}); + if(format == Data) { + blob = new Blob([entry.data], {type:type}); + } + var generatedUrl = _URL.createObjectURL(blob); + var entryFlambe = manifest.add(name, entry.fileName, entry.fileSize, format); + loadEntry(generatedUrl, entryFlambe); + } + override private function getAssetFormats (fn :Array -> Void) { if (_supportedFormats == null) { _supportedFormats = new Promise(); detectImageFormats(function (imageFormats) { _supportedFormats.result = _platform.getRenderer().getCompressedTextureFormats() - .concat(imageFormats).concat(detectAudioFormats()).concat([Data]); + .concat(imageFormats).concat(detectAudioFormats()).concat(detectArchiveFormats()) + .concat([Data]); }); } _supportedFormats.get(fn); @@ -313,6 +431,13 @@ class HtmlAssetPackLoader extends BasicAssetPackLoader return result; } + private static function detectArchiveFormats () :Array + { + var result = [ZIP, TAR]; + + return result; + } + private static function supportsBlob () :Bool { if (_detectBlobSupport) {