diff --git a/source/funkin/mods/ZipFolderLibrary.hx b/source/funkin/mods/ZipFolderLibrary.hx index 0059c785..468559ae 100644 --- a/source/funkin/mods/ZipFolderLibrary.hx +++ b/source/funkin/mods/ZipFolderLibrary.hx @@ -14,6 +14,7 @@ import lime.text.Font; import lime.utils.AssetType; import lime.utils.Bytes; import lime.utils.Assets as LimeAssets; +import openfl.text.Font as OpenFLFont; #if MOD_SUPPORT @@ -21,108 +22,60 @@ import sys.FileStat; import sys.FileSystem; import sys.io.File; import haxe.zip.Reader; -import haxe.zip.Entry; -#end +import funkin.utils.SysZip; +import funkin.utils.SysZip.SysZipEntry; class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { public var zipPath:String; public var libName:String; public var useImageCache:Bool = false; public var prefix = 'assets/'; - - #if MOD_SUPPORT - public var zip:Reader; - public var assets:Map = []; - #end + + public var zip:SysZip; + public var assets:Map = []; public function new(zipPath:String, libName:String) { this.zipPath = zipPath; this.libName = libName; - this.prefix = 'assets/$libName/'; - #if MOD_SUPPORT - zip = new Reader(File.read(zipPath, true)); - var entries = zip.read(); - for(entry in entries) { + zip = SysZip.openFromFile(zipPath); + zip.read(); + for(entry in zip.entries) assets[entry.fileName.toLowerCase()] = entry; - } - #end super(); } - #if MOD_SUPPORT public var _parsedAsset:String; - + public override function getAudioBuffer(id:String):AudioBuffer { - if (__isCacheValid(LimeAssets.cache.audio, id)) - return LimeAssets.cache.audio.get('$libName:$id'); - else { - if (!exists(id, "SOUND")) { - Log.error('ZipFolderLibrary: Audio Buffer at $id does not exist.'); - return null; - } - var e = AudioBuffer.fromBytes(unzip(assets[_parsedAsset])); - return e; - } + __parseAsset(id); + return AudioBuffer.fromBytes(unzip(assets[_parsedAsset])); } - public override function getBytes(id:String):Bytes { - if (__isCacheValid(cachedBytes, id, true)) - return cachedBytes.get(id); - else { - if (!exists(id, "BINARY")) { - Log.error('ZipFolderLibrary: Bytes at $id does not exist.'); - return null; - } - var e = Bytes.fromBytes(unzip(assets[_parsedAsset])); - cachedBytes.set(id, e); - return e; - } + __parseAsset(id); + return Bytes.fromBytes(unzip(assets[_parsedAsset])); } - - - public static function unzip(f:Entry) { - if (!f.compressed) - return f.data; - var c = new haxe.zip.Uncompress(-15); - var s = haxe.io.Bytes.alloc(f.fileSize); - var r = c.execute(f.data, 0, s, 0); - c.close(); - if (!r.done || r.read != f.data.length || r.write != f.fileSize) - throw "Invalid compressed data for " + f.fileName; - f.compressed = false; - f.dataSize = f.fileSize; - f.data = s; - return f.data; - } - public override function getFont(id:String):Font { - if (__isCacheValid(LimeAssets.cache.font, id)) - return LimeAssets.cache.font.get('$libName:$id'); - else { - if (!exists(id, "FONT")) { - Log.error('ZipFolderLibrary: Font at $id does not exist.'); - return null; - } - var e = Font.fromBytes(unzip(assets[_parsedAsset])); - return e; - } + __parseAsset(id); + var font = Font.fromBytes(unzip(assets[_parsedAsset])); + + var openflFont = new OpenFLFont(); + @:privateAccess + openflFont.__fromLimeFont(font); + OpenFLFont.registerFont(openflFont); + + return font; + } + public override function getImage(id:String):Image { + __parseAsset(id); + return Image.fromBytes(unzip(assets[_parsedAsset])); } - public override function getImage(id:String):Image { - if (useImageCache && __isCacheValid(LimeAssets.cache.image, id)) - return LimeAssets.cache.image.get('$libName:$id'); - else { - if (!exists(id, "IMAGE")) { - Log.error('ZipFolderLibrary: Image at $id does not exist.'); - return null; - } - var e = Image.fromBytes(unzip(assets[_parsedAsset])); - if (useImageCache) cachedImages.set(id, e); - return e; - } - } + + + public inline function unzip(f:SysZipEntry) + return f == null ? null : zip.unzipEntry(f); public function __parseAsset(asset:String):Bool { if (!asset.startsWith(prefix)) return false; @@ -134,8 +87,8 @@ class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { if (cache.exists(isLocal ? '$libName:$asset': asset)) return true; return false; } - - public override function exists(asset:String, type:String):Bool { + + public override function exists(asset:String, type:String):Bool { if(!__parseAsset(asset)) return false; return assets[_parsedAsset] != null; @@ -147,10 +100,10 @@ class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { public function getFiles(folder:String):Array { var content:Array = []; - + if (!folder.endsWith("/")) folder = folder + "/"; if (!__parseAsset(folder)) return []; - + @:privateAccess for(k=>e in assets) { if (k.toLowerCase().startsWith(_parsedAsset)) { @@ -164,10 +117,10 @@ class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { public function getFolders(folder:String):Array { var content:Array = []; - + if (!folder.endsWith("/")) folder = folder + "/"; if (!__parseAsset(folder)) return []; - + @:privateAccess for(k=>e in assets) { if (k.toLowerCase().startsWith(_parsedAsset)) { @@ -178,5 +131,5 @@ class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { } return content; } - #end -} \ No newline at end of file +} +#end \ No newline at end of file diff --git a/source/funkin/system/Paths.hx b/source/funkin/system/Paths.hx index f9c9de58..c4204bc8 100644 --- a/source/funkin/system/Paths.hx +++ b/source/funkin/system/Paths.hx @@ -168,11 +168,7 @@ class Paths inline static public function font(key:String) { - #if sys - return sys.FileSystem.absolutePath(OpenFlAssets.getPath(getPath('fonts/$key', FONT, null))); - #else return getPath('fonts/$key', FONT, null); - #end } inline static public function obj(key:String) { diff --git a/source/funkin/utils/SysZip.hx b/source/funkin/utils/SysZip.hx new file mode 100644 index 00000000..b89f6b36 --- /dev/null +++ b/source/funkin/utils/SysZip.hx @@ -0,0 +1,158 @@ +package funkin.utils; + +#if sys +import haxe.io.Input; +import haxe.zip.Entry; +import haxe.zip.Reader; +import sys.io.File; +import sys.io.FileInput; +import haxe.zip.InflateImpl; + +/** + * Class that extends Reader allowing you to load ZIP entries without blowing your RAM up!! + * Half of the code is taken from haxe libraries btw + */ +class SysZip extends Reader { + var input:Input; + var fileInput:FileInput; + + public var entries:List; + + /** + * Opens a zip from a specified path. + * @param path Path to the zip file. + */ + public static function openFromFile(path:String) { + + return new SysZip(File.read(path, true)); + } + + /** + * Creates a new SysZip from a specified file input. + * @param input File input. + */ + public function new(input:FileInput) { + super(input); + fileInput = input; + } + + /** + * Reads all the data present in a specified entry. + * NOTE: If the entry is compressed, the data won't be decompressed. For decompression, use `unzipEntry`. + * @param e Entry + */ + public function readEntryData(e:SysZipEntry) { + var bytes:haxe.io.Bytes = null; + var buf = null; + var tmp = null; + + fileInput.seek(e.seekPos, SeekBegin); + if (e.crc32 == null) { + if (e.compressed) { + #if neko + // enter progressive mode : we use a different input which has + // a temporary buffer, this is necessary since we have to uncompress + // progressively, and after that we might have pending read data + // that needs to be processed + var bufSize = 65536; + if (buf == null) { + buf = new haxe.io.BufferInput(i, haxe.io.Bytes.alloc(bufSize)); + tmp = haxe.io.Bytes.alloc(bufSize); + i = buf; + } + var out = new haxe.io.BytesBuffer(); + var z = new neko.zip.Uncompress(-15); + z.setFlushMode(neko.zip.Flush.SYNC); + while (true) { + if (buf.available == 0) + buf.refill(); + var p = bufSize - buf.available; + if (p != buf.pos) { + // because of lack of "srcLen" in zip api, we need to always be stuck to the buffer end + buf.buf.blit(p, buf.buf, buf.pos, buf.available); + buf.pos = p; + } + var r = z.execute(buf.buf, buf.pos, tmp, 0); + out.addBytes(tmp, 0, r.write); + buf.pos += r.read; + buf.available -= r.read; + if (r.done) + break; + } + bytes = out.getBytes(); + #else + var bufSize = 65536; + if (tmp == null) + tmp = haxe.io.Bytes.alloc(bufSize); + var out = new haxe.io.BytesBuffer(); + var z = new InflateImpl(i, false, false); + while (true) { + var n = z.readBytes(tmp, 0, bufSize); + out.addBytes(tmp, 0, n); + if (n < bufSize) + break; + } + bytes = out.getBytes(); + #end + } else + bytes = i.read(e.dataSize); + e.crc32 = i.readInt32(); + if (e.crc32 == 0x08074b50) + e.crc32 = i.readInt32(); + e.dataSize = i.readInt32(); + e.fileSize = i.readInt32(); + // set data to uncompressed + e.dataSize = e.fileSize; + e.compressed = false; + } else + bytes = i.read(e.dataSize); + return bytes; + } + + /** + * Unzips and returns all of the data present in an entry. + * @param f Entry to read from. + */ + public function unzipEntry(f:SysZipEntry) { + var data = readEntryData(f); + + if (!f.compressed) + return data; + var c = new haxe.zip.Uncompress(-15); + var s = haxe.io.Bytes.alloc(f.fileSize); + var r = c.execute(data, 0, s, 0); + c.close(); + if (!r.done || r.read != data.length || r.write != f.fileSize) + throw "Invalid compressed data for " + f.fileName; + data = s; + return data; + } + + public override function read():List { + if (entries != null) + return entries; + entries = new List(); + while (true) { + var e = readEntryHeader(); + if (e == null) + break; + + var zipEntry:SysZipEntry = cast e; + zipEntry.seekPos = fileInput.tell(); + entries.add(zipEntry); + fileInput.seek(e.dataSize, SeekCur); + } + return entries; + } + + public function dispose() { + if (input != null) + input.close(); + } +} + +typedef SysZipEntry = { + > Entry, + var seekPos:Int; +} +#end \ No newline at end of file