Files
ItsLJcoolandGitHub a7b040152d Fixed / Refactored SysZip to actually work, and store compressed entries without uncompressing an entire zip (#761)
* well boys I fixed it. you can now have Zipped Mods!!!!!

* comment update

* quick fixes + optimizations

* fixed stream audio bug????

* found the issue ffs

* removing traces oops

* fixed videos from not being able to play. Inspired from VideoCutscene's fix for ZIP videos, and also removed that fix in favor for just fixing the issue entirely

* oop just in case no special characters that kill themself on other shit

* Implemented preloading every video (caching) when loading a ZipFolderLibrary.

Since videos have to be decompressed and be saved as a file anyways this reduces the time to do that, and what not.

* [UNTESTED] Allowed Zip Extensions for allowing any extension in the list to be passed as a `.zip`

* After some more testing loading Zip Addons works as well.

* Added the long awaited .cnemod file type!!!!! (it's just a folder with .cnemod with a .zip inside LMAO)

* Chalking up the Streamed Audio Bug to a mod issue, and also added capabilites to detect if a library is considered compressed

* Added warning to those who try to use the editor with compressed libraries

* ok instead of making it a .cnemod, the zip name will be `cnemod.zip` or what not.

* removing unnessesary traces oops

* clean Library accessor ig then

* fixed small issues and removed an idiot idea

* writing todos and fixing some context that left the room in comments

* Fixed an issue where if you had a file of 0 bytes (nothing) in a zipped library it would FUCKING CRASH the game

* Actually fixed the issue of decompressing 0 byte files

Also stopped saving the data for Folders in Zipped files, as they are 0 bytes anyways.

* Added the features that needed to exist, and removed a stupid line I saw in `ModsFolder` lol

For some reason animation offsets for characters are breaking and I can't tell if its my drive killing itself or cne so uh

* fix an issue that will force the game to crash if first ever mod being loaded is a ZipModLibrary
2026-03-15 14:24:47 +07:00

233 lines
6.0 KiB
Haxe

package funkin.backend.assets;
#if TRANSLATIONS_SUPPORT
import funkin.backend.assets.TranslatedAssetLibrary;
#end
import funkin.backend.assets.IModsAssetLibrary;
import lime.utils.AssetLibrary;
import haxe.ds.Map;
class AssetsLibraryList extends AssetLibrary {
public var libraries:Array<AssetLibrary> = [];
public var cleanLibraries(get, never):Array<AssetLibrary>;
function get_cleanLibraries():Array<AssetLibrary> {
return [for (l in libraries) getCleanLibrary(l)];
}
// is true if any library in `libraries` contains some kind of compressed library.
public var hasCompressedLibrary(get, never):Bool;
function get_hasCompressedLibrary():Bool {
for (l in libraries) if (getCleanLibrary(l).isCompressed) return true;
return false;
}
@:allow(funkin.backend.system.Main)
@:allow(funkin.backend.system.MainState)
private var __defaultLibraries:Array<AssetLibrary> = [];
public var base:AssetLibrary;
#if TRANSLATIONS_SUPPORT
public var transLib:TranslatedAssetLibrary;
#end
public function removeLibrary(lib:AssetLibrary) {
if (lib != null) {
libraries.remove(lib);
#if TRANSLATIONS_SUPPORT
// TODO: improve this code
for(k=>l in libraries) {
if(l == null) continue;
if(l is TranslatedAssetLibrary) {
var tlib = cast(l, TranslatedAssetLibrary);
var lib:Dynamic = lib;
if(tlib.forLibrary == lib) {
libraries.remove(tlib);
break;
}
}
}
#end
}
return lib;
}
public function existsSpecific(id:String, type:String, source:AssetSource = BOTH) {
if (!id.startsWith("assets/") && existsSpecific('assets/$id', type, source))
return true;
for(k=>l in libraries) {
if (shouldSkipLib(l, source)) continue;
if (l.exists(id, type)) {
return true;
}
}
return false;
}
public override inline function exists(id:String, type:String):Bool
return existsSpecific(id, type, BOTH);
public function getSpecificPath(id:String, source:AssetSource = BOTH) {
for(k=>e in libraries) {
if (shouldSkipLib(e, source)) continue;
@:privateAccess
if (e.exists(id, e.types.get(id))) {
var path = e.getPath(id);
if (path != null)
return path;
}
}
return null;
}
public override inline function getPath(id:String)
return getSpecificPath(id, BOTH);
public function getFiles(folder:String, source:AssetSource = BOTH):Array<String> {
var content:Array<String> = [];
for(k=>l in libraries) {
if (shouldSkipLib(l, source)) continue;
l = getCleanLibrary(l);
// TODO: do base folder scanning
#if MOD_SUPPORT
if (l is IModsAssetLibrary) {
var lib = cast(l, IModsAssetLibrary);
for(e in lib.getFiles(folder))
content.pushOnce(e);
}
#end
}
return content;
}
public function getFolders(folder:String, source:AssetSource = BOTH):Array<String> {
var content:Array<String> = [];
for(k=>l in libraries) {
if (shouldSkipLib(l, source)) continue;
l = getCleanLibrary(l);
// TODO: do base folder scanning
#if MOD_SUPPORT
if (l is IModsAssetLibrary) {
var lib = cast(l, IModsAssetLibrary);
for(e in lib.getFolders(folder))
content.pushOnce(e);
}
#end
}
return content;
}
public function getSpecificAsset(id:String, type:String, source:AssetSource = BOTH):Dynamic {
try {
if (!id.startsWith("assets/")) {
var ass = getSpecificAsset('assets/$id', type, source);
if (ass != null) {
return ass;
}
}
for(k=>l in libraries) {
if (shouldSkipLib(l, source)) continue;
@:privateAccess
if (l.exists(id, l.types.get(id))) {
var asset = l.getAsset(id, type);
if (asset != null) {
return asset;
}
}
}
return null;
} catch(e) {
// TODO: trace the error
throw e;
}
return null;
}
private function shouldSkipLib(lib:AssetLibrary, source:AssetSource) {
if (source == BOTH || lib.tag == BOTH) return false;
return source != lib.tag;
}
public override inline function getAsset(id:String, type:String):Dynamic
return getSpecificAsset(id, type, BOTH);
public override function list(type:String):Array<String> {
// idk if there's a more efficient way tbh, correct if u find better
var files:Map<String, Bool> = [];
for(k=>l in libraries) {
for(f in l.list(type))
files.set(f, false);
}
return [for(k=>e in files) k];
}
public override function isLocal(id:String, type:String) {
return true;
}
public function new(?base:AssetLibrary) {
super();
if (base == null) (this.base = Assets.getLibrary("default")).tag = SOURCE;
else this.base = base;
__defaultLibraries.push(this.base);
#if (sys && TEST_BUILD)
Logs.infos("Used cne test / cne build. Switching into source assets.");
#if MOD_SUPPORT
ModsFolder.modsPath = './${Main.pathBack}mods/';
ModsFolder.addonsPath = './${Main.pathBack}addons/';
#end
__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './${Main.pathBack}assets/', true, SOURCE));
#elseif USE_ADAPTED_ASSETS
__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './assets/', true, SOURCE));
#end
for (d in __defaultLibraries) addLibrary(d);
}
public function unloadLibraries() {
for(l in libraries)
if (!__defaultLibraries.contains(l))
l.unload();
}
public function reset() {
unloadLibraries();
libraries = [];
// adds default libraries in again
for (d in __defaultLibraries) addLibrary(d);
}
public function addLibrary(lib:AssetLibrary, ?tag:AssetSource, ?addTransLib:Bool = true) {
libraries.insert(0, lib);
if (tag != null) lib.tag = tag;
else if (lib.tag == null) lib.tag = MODS;
#if TRANSLATIONS_SUPPORT
if(addTransLib) {
var cleanLib = getCleanLibrary(lib);
if(cleanLib != null && (cleanLib is IModsAssetLibrary)) {
var transLib = new TranslatedAssetLibrary(cast(cleanLib, IModsAssetLibrary));
transLib.tag = cleanLib.tag;
libraries.insert(0, transLib);
}
}
#end
return lib;
}
public static function getCleanLibrary(e:AssetLibrary):AssetLibrary {
var l = e;
if (l is openfl.utils.AssetLibrary) {
var al = cast(l, openfl.utils.AssetLibrary);
@:privateAccess
if (al.__proxy != null) l = al.__proxy;
}
return l;
}
}