From e9c028489abc07f2c3b4e8881e4bfb4b2fc82fce Mon Sep 17 00:00:00 2001 From: Yoshman29 Date: Tue, 1 Nov 2022 17:22:14 +0100 Subject: [PATCH] zip mod loading --- Project.xml | 2 +- buildnumber.txt | 2 +- example_mods/introMod/data/introText.txt | 1 - example_mods/modList.txt | 1 - source/funkin/mods/ModsAssetLibrary.hx | 130 +------------------ source/funkin/mods/ModsFolder.hx | 40 ++++-- source/funkin/mods/ModsFolderLibrary.hx | 155 +++++++++++++++++++++++ source/funkin/mods/ZipFolderLibrary.hx | 154 ++++++++++++++++++++++ source/funkin/ui/Alphabet.hx | 2 +- 9 files changed, 346 insertions(+), 141 deletions(-) delete mode 100644 example_mods/introMod/data/introText.txt delete mode 100644 example_mods/modList.txt create mode 100644 source/funkin/mods/ModsFolderLibrary.hx create mode 100644 source/funkin/mods/ZipFolderLibrary.hx diff --git a/Project.xml b/Project.xml index cf7e86f8..7f35ae53 100644 --- a/Project.xml +++ b/Project.xml @@ -2,7 +2,7 @@ - + diff --git a/buildnumber.txt b/buildnumber.txt index 7e4cc171..61721cab 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -934 \ No newline at end of file +938 \ No newline at end of file diff --git a/example_mods/introMod/data/introText.txt b/example_mods/introMod/data/introText.txt deleted file mode 100644 index 45e0c08a..00000000 --- a/example_mods/introMod/data/introText.txt +++ /dev/null @@ -1 +0,0 @@ -swagshit--moneymoney \ No newline at end of file diff --git a/example_mods/modList.txt b/example_mods/modList.txt deleted file mode 100644 index 3e249259..00000000 --- a/example_mods/modList.txt +++ /dev/null @@ -1 +0,0 @@ -introMod \ No newline at end of file diff --git a/source/funkin/mods/ModsAssetLibrary.hx b/source/funkin/mods/ModsAssetLibrary.hx index 60c15439..64938365 100644 --- a/source/funkin/mods/ModsAssetLibrary.hx +++ b/source/funkin/mods/ModsAssetLibrary.hx @@ -22,134 +22,16 @@ import sys.FileSystem; using StringTools; -class ModsAssetLibrary extends AssetLibrary { - public var folderPath:String; - public var libName:String; - public var useImageCache:Bool = false; - - public function new(folderPath:String, libName:String) { - this.folderPath = folderPath; - this.libName = libName; - super(); - } - +interface ModsAssetLibrary { #if MOD_SUPPORT - private var editedTimes:Map = []; - var _parsedAsset:String = null; + public var _parsedAsset:String; - public function getEditedTime(asset:String):Null { - return editedTimes[asset]; - } + private function getAssetPath():String; - public override function getAudioBuffer(id:String):AudioBuffer { - if (__isCacheValid(cachedAudioBuffers, id)) - return cachedAudioBuffers.get(id); - else { - if (!exists(id, "SOUND")) { - Log.error('ModsAssetLibrary: Audio Buffer at $id does not exist.'); - return null; - } - var path = getAssetPath(); - editedTimes[id] = FileSystem.stat(path).mtime.getTime(); - var e = AudioBuffer.fromFile(path); - cachedAudioBuffers.set(id, e); - return e; - } - } + private function __isCacheValid(cache:Map, asset:String):Bool; - public override function getBytes(id:String):Bytes { - if (__isCacheValid(cachedBytes, id)) - return cachedBytes.get(id); - else { - if (!exists(id, "BINARY")) { - Log.error('ModsAssetLibrary: Bytes at $id does not exist.'); - return null; - } - var path = getAssetPath(); - editedTimes[id] = FileSystem.stat(path).mtime.getTime(); - var e = Bytes.fromFile(path); - cachedBytes.set(id, e); - return e; - } - } + private function __parseAsset(asset:String):Bool; - public override function getFont(id:String):Font { - if (__isCacheValid(cachedFonts, id)) - return cachedFonts.get(id); - else { - if (!exists(id, "FONT")) { - Log.error('ModsAssetLibrary: Font at $id does not exist.'); - return null; - } - var path = getAssetPath(); - editedTimes[id] = FileSystem.stat(path).mtime.getTime(); - var e = Font.fromFile(path); - cachedFonts.set(id, e); - return e; - } - } - - public override function getImage(id:String):Image { - if (useImageCache && __isCacheValid(cachedImages, id)) - return cachedImages.get(id); - else { - if (!exists(id, "IMAGE")) { - Log.error('ModsAssetLibrary: Image at $id does not exist.'); - return null; - } - var path = getAssetPath(); - editedTimes[id] = FileSystem.stat(path).mtime.getTime(); - - var e = Image.fromFile(path); - cachedImages.set(id, e); - return e; - } - } - - public override function getPath(id:String):String { - if (!__parseAsset(id)) return null; - return getAssetPath(); - } - - public function getFiles(folder:String):Array { - if (!folder.endsWith("/")) folder = folder + "/"; - if (!__parseAsset(folder)) return []; - var path = getAssetPath(); - try { - var result:Array = []; - for(e in FileSystem.readDirectory(path)) - if (!FileSystem.isDirectory('$path$e')) - result.push(e); - return result; - } catch(e) { - // woops!! - } - return []; - } - - public override function exists(asset:String, type:String):Bool { - if(!__parseAsset(asset)) return false; - - return FileSystem.exists(getAssetPath()); - } - - private function getAssetPath() { - return '$folderPath/$_parsedAsset'; - } - - private function __isCacheValid(cache:Map, asset:String) { - if (!editedTimes.exists(asset)) - return false; - if (editedTimes[asset] == null) return false; - if (editedTimes[asset] < FileSystem.stat(getPath(asset)).mtime.getTime()) return false; - return cache.exists(asset) && cache[asset] != null; - } - - private function __parseAsset(asset:String):Bool { - var prefix = 'assets/$libName/'; - if (!asset.startsWith(prefix)) return false; - _parsedAsset = asset.substr(prefix.length); - return true; - } + public function getFiles(folder:String):Array; #end } \ No newline at end of file diff --git a/source/funkin/mods/ModsFolder.hx b/source/funkin/mods/ModsFolder.hx index 8a1ec061..410feef8 100644 --- a/source/funkin/mods/ModsFolder.hx +++ b/source/funkin/mods/ModsFolder.hx @@ -49,12 +49,18 @@ class ModsFolder { * @param force Whenever the mod should be reloaded if it has already been loaded */ public static function loadMod(mod:String, force:Bool = false) { - var e = loadLibraryFromFolder('mods/$mod'.toLowerCase(), '${modsPath}$mod', force); - loadedMods.push(mod); - return e; + if (FileSystem.exists('${modsPath}$mod.zip')) { + var e = loadLibraryFromZip('mods/$mod'.toLowerCase(), '${modsPath}$mod.zip', force); + loadedMods.push(mod); + return e; + } else { + var e = loadLibraryFromFolder('mods/$mod'.toLowerCase(), '${modsPath}$mod', force); + loadedMods.push(mod); + return e; + } } - public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false) { + public static function prepareLibrary(libName:String, force:Bool = false) { if (Assets.hasLibrary(libName)) { if (force) Assets.unloadLibrary(libName); @@ -64,15 +70,25 @@ class ModsFolder { var assets:AssetManifest = new AssetManifest(); assets.name = libName; - assets.libraryType = 'funkin.mods.ModsAssetLibrary'; assets.version = 2; assets.libraryArgs = []; - assets.rootPath = folder; assets.assets = []; - var lib = AssetLibrary.fromManifest(assets); + return AssetLibrary.fromManifest(assets); + } + + public static function loadLibraryFromZip(libName:String, zipPath:String, force:Bool = false) { + var lib = prepareLibrary(libName, force); @:privateAccess - lib.__proxy = new ModsAssetLibrary(assets.rootPath, assets.name); + lib.__proxy = new ZipFolderLibrary(zipPath, libName); + Assets.registerLibrary(libName, lib); + return lib; + } + + public static function loadLibraryFromFolder(libName:String, folder:String, force:Bool = false) { + var lib = prepareLibrary(libName, force); + @:privateAccess + lib.__proxy = new ModsFolderLibrary(folder, libName); Assets.registerLibrary(libName, lib); return lib; } @@ -139,8 +155,8 @@ class ModsFolder { if (e.library is ModsAssetLibrary) { var lib = cast(e.library, ModsAssetLibrary); if (!lib.__parseAsset(e.symbolName)) continue; - if (!lib.__isCacheValid(lib.cachedImages, lib._parsedAsset)) { - lib.cachedImages.remove(lib._parsedAsset); + if (!lib.__isCacheValid(e.library.cachedImages, lib._parsedAsset)) { + e.library.cachedImages.remove(lib._parsedAsset); bmapsToRemove.push(bmap); } } @@ -159,9 +175,9 @@ class ModsFolder { if (library is ModsAssetLibrary) { var modLib = cast(library, ModsAssetLibrary); @:privateAccess - modLib.cachedBytes = []; + library.cachedBytes = []; @:privateAccess - for(sound in modLib.cachedAudioBuffers) + for(sound in library.cachedAudioBuffers) sound.dispose(); } diff --git a/source/funkin/mods/ModsFolderLibrary.hx b/source/funkin/mods/ModsFolderLibrary.hx new file mode 100644 index 00000000..c1465d54 --- /dev/null +++ b/source/funkin/mods/ModsFolderLibrary.hx @@ -0,0 +1,155 @@ +package funkin.mods; + +import lime.utils.Log; +import lime.utils.AssetLibrary; +import lime.utils.AssetManifest; + +import haxe.io.Path; +import lime.app.Event; +import lime.app.Future; +import lime.app.Promise; +import lime.media.AudioBuffer; +import lime.graphics.Image; +import lime.net.HTTPRequest; +import lime.text.Font; +import lime.utils.AssetType; +import lime.utils.Bytes; + +#if MOD_SUPPORT +import sys.FileStat; +import sys.FileSystem; +#end + +using StringTools; + +class ModsFolderLibrary extends AssetLibrary implements ModsAssetLibrary { + public var folderPath:String; + public var libName:String; + public var useImageCache:Bool = true; + + public function new(folderPath:String, libName:String) { + this.folderPath = folderPath; + this.libName = libName; + super(); + } + + #if MOD_SUPPORT + private var editedTimes:Map = []; + public var _parsedAsset:String = null; + + public function getEditedTime(asset:String):Null { + return editedTimes[asset]; + } + + public override function getAudioBuffer(id:String):AudioBuffer { + if (__isCacheValid(cachedAudioBuffers, id)) + return cachedAudioBuffers.get(id); + else { + if (!exists(id, "SOUND")) { + Log.error('ModsAssetLibrary: Audio Buffer at $id does not exist.'); + return null; + } + var path = getAssetPath(); + editedTimes[id] = FileSystem.stat(path).mtime.getTime(); + var e = AudioBuffer.fromFile(path); + cachedAudioBuffers.set(id, e); + return e; + } + } + + public override function getBytes(id:String):Bytes { + if (__isCacheValid(cachedBytes, id)) + return cachedBytes.get(id); + else { + if (!exists(id, "BINARY")) { + Log.error('ModsAssetLibrary: Bytes at $id does not exist.'); + return null; + } + var path = getAssetPath(); + editedTimes[id] = FileSystem.stat(path).mtime.getTime(); + var e = Bytes.fromFile(path); + cachedBytes.set(id, e); + return e; + } + } + + public override function getFont(id:String):Font { + if (__isCacheValid(cachedFonts, id)) + return cachedFonts.get(id); + else { + if (!exists(id, "FONT")) { + Log.error('ModsAssetLibrary: Font at $id does not exist.'); + return null; + } + var path = getAssetPath(); + editedTimes[id] = FileSystem.stat(path).mtime.getTime(); + var e = Font.fromFile(path); + cachedFonts.set(id, e); + return e; + } + } + + public override function getImage(id:String):Image { + if (useImageCache && __isCacheValid(cachedImages, id)) + return cachedImages.get(id); + else { + if (!exists(id, "IMAGE")) { + Log.error('ModsAssetLibrary: Image at $id does not exist.'); + return null; + } + var path = getAssetPath(); + editedTimes[id] = FileSystem.stat(path).mtime.getTime(); + + var e = Image.fromFile(path); + if (useImageCache) cachedImages.set(id, e); + return e; + } + } + + public override function getPath(id:String):String { + if (!__parseAsset(id)) return null; + return getAssetPath(); + } + + public function getFiles(folder:String):Array { + if (!folder.endsWith("/")) folder = folder + "/"; + if (!__parseAsset(folder)) return []; + var path = getAssetPath(); + try { + var result:Array = []; + for(e in FileSystem.readDirectory(path)) + if (!FileSystem.isDirectory('$path$e')) + result.push(e); + return result; + } catch(e) { + // woops!! + } + return []; + } + + public override function exists(asset:String, type:String):Bool { + if(!__parseAsset(asset)) return false; + + return FileSystem.exists(getAssetPath()); + } + + private function getAssetPath() { + return '$folderPath/$_parsedAsset'; + } + + private function __isCacheValid(cache:Map, asset:String) { + if (!editedTimes.exists(asset)) + return false; + if (editedTimes[asset] == null) return false; + if (editedTimes[asset] < FileSystem.stat(getPath(asset)).mtime.getTime()) return false; + return cache.exists(asset) && cache[asset] != null; + } + + private function __parseAsset(asset:String):Bool { + var prefix = 'assets/$libName/'; + if (!asset.startsWith(prefix)) return false; + _parsedAsset = asset.substr(prefix.length); + return true; + } + #end +} \ No newline at end of file diff --git a/source/funkin/mods/ZipFolderLibrary.hx b/source/funkin/mods/ZipFolderLibrary.hx new file mode 100644 index 00000000..57021c3d --- /dev/null +++ b/source/funkin/mods/ZipFolderLibrary.hx @@ -0,0 +1,154 @@ +package funkin.mods; + +import lime.utils.Log; +import lime.utils.AssetLibrary; +import lime.utils.AssetManifest; + +import haxe.io.Path; +import lime.app.Event; +import lime.app.Future; +import lime.app.Promise; +import lime.media.AudioBuffer; +import lime.graphics.Image; +import lime.net.HTTPRequest; +import lime.text.Font; +import lime.utils.AssetType; +import lime.utils.Bytes; + + +#if MOD_SUPPORT +import sys.FileStat; +import sys.FileSystem; +import sys.io.File; +import haxe.zip.Reader; +import haxe.zip.Entry; +#end + +class ZipFolderLibrary extends AssetLibrary implements ModsAssetLibrary { + public var zipPath:String; + public var libName:String; + public var useImageCache:Bool = false; + + #if MOD_SUPPORT + public var zip:Reader; + public var assets:Map = []; + #end + + public function new(zipPath:String, libName:String) { + this.zipPath = zipPath; + this.libName = libName; + + #if MOD_SUPPORT + zip = new Reader(File.read(zipPath, true)); + var entries = zip.read(); + for(entry in entries) { + assets[entry.fileName.toLowerCase()] = entry; + } + #end + + super(); + } + + #if MOD_SUPPORT + public var _parsedAsset:String; + + public override function getAudioBuffer(id:String):AudioBuffer { + if (__isCacheValid(cachedAudioBuffers, id)) + return cachedAudioBuffers.get(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])); + cachedAudioBuffers.set(id, e); + return e; + } + } + + public override function getBytes(id:String):Bytes { + if (__isCacheValid(cachedBytes, id)) + 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; + } + } + + + 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(cachedFonts, id)) + return cachedFonts.get(id); + else { + if (!exists(id, "FONT")) { + Log.error('ZipFolderLibrary: Font at $id does not exist.'); + return null; + } + var e = Font.fromBytes(unzip(assets[_parsedAsset])); + cachedFonts.set(id, e); + return e; + } + } + + public override function getImage(id:String):Image { + if (useImageCache && __isCacheValid(cachedImages, id)) + return cachedImages.get(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 function __parseAsset(asset:String):Bool { + var prefix = 'assets/$libName/'; + if (!asset.startsWith(prefix)) return false; + _parsedAsset = asset.substr(prefix.length).toLowerCase(); + return true; + } + + public function __isCacheValid(cache:Map, asset:String) { + if (cache.exists(asset)) return true; + return false; + } + + public override function exists(asset:String, type:String):Bool { + if(!__parseAsset(asset)) return false; + + return assets[_parsedAsset] != null; + } + + private function getAssetPath() { + return _parsedAsset; + } + + public function getFiles(folder:String):Array { + // TODO + return []; + } + #end +} \ No newline at end of file diff --git a/source/funkin/ui/Alphabet.hx b/source/funkin/ui/Alphabet.hx index d7520a81..dc986fc5 100644 --- a/source/funkin/ui/Alphabet.hx +++ b/source/funkin/ui/Alphabet.hx @@ -108,7 +108,7 @@ class Alphabet extends FlxSpriteGroup if (library is ModsAssetLibrary) { var modLib = cast(library, ModsAssetLibrary); @:privateAccess - if (!modLib.__isCacheValid(modLib.cachedBytes, libThing.symbolName)) { + if (!modLib.__isCacheValid(library.cachedBytes, libThing.symbolName)) { refreshAlphabetXML(alphabetPath); } }