Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5541dd6
well boys I fixed it. you can now have Zipped Mods!!!!!
ItsLJcool Sep 4, 2025
e049116
comment update
ItsLJcool Sep 4, 2025
d776fed
quick fixes + optimizations
ItsLJcool Sep 4, 2025
11a8b21
fixed stream audio bug????
ItsLJcool Sep 4, 2025
99f1156
found the issue ffs
ItsLJcool Sep 4, 2025
77b1c31
removing traces oops
ItsLJcool Sep 4, 2025
e015669
fixed videos from not being able to play. Inspired from VideoCutscene…
ItsLJcool Sep 4, 2025
c9eb19b
oop just in case no special characters that kill themself on other shit
ItsLJcool Sep 4, 2025
f52ec39
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 4, 2025
cdbcbed
Implemented preloading every video (caching) when loading a ZipFolder…
ItsLJcool Sep 4, 2025
04e3d9b
[UNTESTED] Allowed Zip Extensions for allowing any extension in the l…
ItsLJcool Sep 4, 2025
1cb8bbd
After some more testing loading Zip Addons works as well.
ItsLJcool Sep 4, 2025
ab99861
Added the long awaited .cnemod file type!!!!! (it's just a folder wit…
ItsLJcool Sep 5, 2025
1eb0480
Chalking up the Streamed Audio Bug to a mod issue, and also added cap…
ItsLJcool Sep 5, 2025
ba1976f
Added warning to those who try to use the editor with compressed libr…
ItsLJcool Sep 6, 2025
74a792e
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 9, 2025
ed7fb9b
ok instead of making it a .cnemod, the zip name will be `cnemod.zip` …
ItsLJcool Sep 9, 2025
d1c9377
removing unnessesary traces oops
ItsLJcool Sep 11, 2025
728a62b
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 15, 2025
4d28aaf
clean Library accessor ig then
ItsLJcool Sep 15, 2025
7dda777
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 16, 2025
bafb57e
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 16, 2025
adbc90a
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 16, 2025
f4d18ce
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Sep 21, 2025
53ae2b1
Merge branch 'CodenameCrew:main' into fixing-zip-mods
ItsLJcool Oct 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions source/funkin/backend/assets/AssetsLibraryList.hx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ import funkin.backend.assets.IModsAssetLibrary;
import lime.utils.AssetLibrary;

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)
Expand Down Expand Up @@ -141,9 +153,7 @@ class AssetsLibraryList extends AssetLibrary {
public override inline function getAsset(id:String, type:String):Dynamic
return getSpecificAsset(id, type, BOTH);

public override function isLocal(id:String, type:String) {
return true;
}
public override function isLocal(id:String, type:String) return true;

public function new(?base:AssetLibrary) {
super();
Expand Down
30 changes: 10 additions & 20 deletions source/funkin/backend/assets/ModsFolder.hx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ class ModsFolder {
*/
public static function loadModLib(path:String, force:Bool = false, ?modName:String) {
#if MOD_SUPPORT
if (FileSystem.exists('$path.zip'))
return loadLibraryFromZip('$path'.toLowerCase(), '$path.zip', force, modName);
else
return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force, modName);
for (ext in Flags.ALLOWED_ZIP_EXTENSIONS) {
if (!FileSystem.exists('$path.$ext')) continue;
return loadLibraryFromZip('$path'.toLowerCase(), '$path.$ext', force, modName);
}
return loadLibraryFromFolder('$path'.toLowerCase(), '$path', force, modName);

#else
return null;
Expand All @@ -96,27 +97,16 @@ class ModsFolder {
public static function getModsList():Array<String> {
var mods:Array<String> = [];
#if MOD_SUPPORT
if (!FileSystem.exists(modsPath)) {
// Mods directory does not exist yet, create it
FileSystem.createDirectory(modsPath);
}
// Mods directory does not exist yet, create it
if (!FileSystem.exists(modsPath)) FileSystem.createDirectory(modsPath);

final modsList:Array<String> = FileSystem.readDirectory(modsPath);

if (modsList == null || modsList.length <= 0)
return mods;
if (modsList == null || modsList.length <= 0) return mods;

for (modFolder in modsList) {
if (FileSystem.isDirectory(modsPath + modFolder)) {
mods.push(modFolder);
} else {
var ext = Path.extension(modFolder).toLowerCase();
switch(ext) {
case 'zip':
// is a zip mod!!
mods.push(Path.withoutExtension(modFolder));
}
}
if (FileSystem.isDirectory(modsPath + modFolder)) mods.push(modFolder);
else if (Flags.ALLOWED_ZIP_EXTENSIONS.contains(Path.extension(modFolder))) mods.push(Path.withoutExtension(modFolder));
}
#end
return mods;
Expand Down
60 changes: 43 additions & 17 deletions source/funkin/backend/assets/ZipFolderLibrary.hx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package funkin.backend.assets;

import funkin.backend.system.Flags;

import haxe.io.Path;
import lime.graphics.Image;
import lime.media.AudioBuffer;
import lime.text.Font;
import lime.utils.Bytes;
import openfl.utils.AssetLibrary;
import sys.io.File;

#if MOD_SUPPORT
import funkin.backend.utils.SysZip.SysZipEntry;
Expand All @@ -15,37 +18,65 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
public var basePath:String;
public var modName:String;
public var libName:String;
public var useImageCache:Bool = false;
// public var useImageCache:Bool = false;
public var prefix = 'assets/';

public var zip:SysZip;
public var assets:Map<String, SysZipEntry> = [];
public var lowerCaseAssets:Map<String, SysZipEntry> = [];
public var nameMap:Map<String, String> = [];

public function new(basePath:String, libName:String, ?modName:String) {
public function new(basePath:String, libName:String, ?modName:String, ?preloadVideos:Bool = true) {
CoolUtil.debugTimeStamp();
this.libName = libName;

this.basePath = basePath;

this.modName = (modName == null) ? libName : modName;

zip = SysZip.openFromFile(basePath);
zip.read();
for(entry in zip.entries) {
lowerCaseAssets[entry.fileName.toLowerCase()] = assets[entry.fileName.toLowerCase()] = assets[entry.fileName] = entry;
nameMap.set(entry.fileName.toLowerCase(), entry.fileName);
var name = entry.fileName.toLowerCase();
lowerCaseAssets[name] = assets[name] = assets[entry.fileName] = entry;
nameMap.set(name, entry.fileName);
}

super();
isCompressed = true;
precacheVideos();
CoolUtil.debugTimeStamp("ZipFolderLibrary");
}

public function precacheVideos() {
videoCacheRemap = [];
for (entry in zip.entries) {
var name = entry.fileName.toLowerCase();
if (_videoExtensions.contains(Path.extension(name))) getPath(prefix+name);
}
var count = 0;
for (_ in videoCacheRemap.keys()) count++;
trace('Precached $count video${count == 1 ? "" : "s"}');
}

// Now we have supports for videos in ZIP!!
public var _videoExtensions:Array<String> = [Flags.VIDEO_EXT];
public var videoCacheRemap:Map<String, String> = [];
public function getVideoRemap(originalPath:String):String {
if (!_videoExtensions.contains(Path.extension(_parsedAsset))) return originalPath;
if (videoCacheRemap.exists(originalPath)) return videoCacheRemap.get(originalPath);

// We adding the length of the string to counteract folder in folder naming duplicates.
var newPath = './.temp/${assets[_parsedAsset].fileName.length}-zipvideo-${_parsedAsset.split("/").pop()}';
File.saveBytes(newPath, unzip(assets[_parsedAsset]));
videoCacheRemap.set(originalPath, newPath);
return newPath;
}

function toString():String {
return '(ZipFolderLibrary: $libName/$modName)';
return '(ZipFolderLibrary: $libName/$modName | ${zip.entries.length} entries | Detected Video Extensions: ${_videoExtensions.join(", ")})';
}

public var _parsedAsset:String;

public override function getAudioBuffer(id:String):AudioBuffer {
__parseAsset(id);
return AudioBuffer.fromBytes(unzip(assets[_parsedAsset]));
Expand All @@ -68,15 +99,12 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
return getAssetPath();
}



public inline function unzip(f:SysZipEntry)
return f == null ? null : zip.unzipEntry(f);
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;
_parsedAsset = asset.substr(prefix.length);
if(ModsFolder.useLibFile) {
if (ModsFolder.useLibFile) {
var file = new haxe.io.Path(_parsedAsset);
if(file.file.startsWith("LIB_")) {
var library = file.file.substr(4);
Expand All @@ -87,8 +115,7 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
}

_parsedAsset = _parsedAsset.toLowerCase();
if(nameMap.exists(_parsedAsset))
_parsedAsset = nameMap.get(_parsedAsset);
if (nameMap.exists(_parsedAsset)) _parsedAsset = nameMap.get(_parsedAsset);
return true;
}

Expand All @@ -104,8 +131,7 @@ class ZipFolderLibrary extends AssetLibrary implements IModsAssetLibrary {
}

private function getAssetPath() {
trace('[ZIP]$basePath/$_parsedAsset');
return '[ZIP]$basePath/$_parsedAsset';
return getVideoRemap('$basePath/$_parsedAsset');
}

// TODO: rewrite this to 1 function, like ModsFolderLibrary
Expand Down
3 changes: 3 additions & 0 deletions source/funkin/backend/system/Flags.hx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class Flags {
// -- Codename's Addon Config --
@:bypass public static var addonFlags:Map<String, Dynamic> = [];

// -- Codename's ZipFolderLibrary Config --
public static var ALLOWED_ZIP_EXTENSIONS:Array<String> = ["zip"];

// -- Codename's Mod Config --
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
Expand Down
45 changes: 31 additions & 14 deletions source/funkin/backend/system/MainState.hx
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,29 @@ class MainState extends FlxState {
var _highPriorityAddons:Array<AddonInfo> = [];
var _noPriorityAddons:Array<AddonInfo> = [];

var quick_modsPath = ModsFolder.modsPath + ModsFolder.currentModFolder;

var isCneMod = false;
for (ext in Flags.ALLOWED_ZIP_EXTENSIONS) {
if (!FileSystem.exists(quick_modsPath+"/cnemod."+ext)) continue;
isCneMod = true;
break;
}

// handing if the loading mod, before it's properly loaded, is a compressed mod
// we just need to use `Paths.assetsTree.hasCompressedLibrary` to complete valid checks for actual loaded compressed mods
var isZipMod = false;
for (ext in Flags.ALLOWED_ZIP_EXTENSIONS) {
if (!FileSystem.exists(quick_modsPath+"."+ext)) continue;
isZipMod = true;
break;
}

// Now here is a conundrum, if it's a compressed mod you can't really uncompress it yet, since it's not loaded as a library, so we need to skip it.
var addonPaths = [
ModsFolder.addonsPath,
(
ModsFolder.currentModFolder != null ?
ModsFolder.modsPath + ModsFolder.currentModFolder + "/addons/" :
null
( (ModsFolder.currentModFolder != null && !isZipMod) ?
quick_modsPath + "/addons/" : null
)
];

Expand All @@ -72,12 +89,8 @@ class MainState extends FlxState {

for (addon in FileSystem.readDirectory(path)) {
if (!FileSystem.isDirectory(path + addon)) {
switch(Path.extension(addon).toLowerCase()) {
case 'zip':
addon = Path.withoutExtension(addon);
default:
continue;
}
if (Flags.ALLOWED_ZIP_EXTENSIONS.contains(Path.extension(addon).toLowerCase())) addon = Path.withoutExtension(addon);
else continue;
}

var data:AddonInfo = {
Expand All @@ -100,9 +113,13 @@ class MainState extends FlxState {
#if MOD_SUPPORT
for (addon in _lowPriorityAddons)
loadLib(addon.path, ltrim(addon.name, "[LOW]"));

if (ModsFolder.currentModFolder != null)
loadLib(ModsFolder.modsPath + ModsFolder.currentModFolder, ModsFolder.currentModFolder);

if (ModsFolder.currentModFolder != null) {
if (isCneMod)
loadLib(quick_modsPath + "/cnemod", ModsFolder.currentModFolder);
else
loadLib(quick_modsPath, ModsFolder.currentModFolder);
}

for (addon in _noPriorityAddons)
loadLib(addon.path, addon.name);
Expand Down Expand Up @@ -136,7 +153,7 @@ class MainState extends FlxState {

var startState:Class<FlxState> = Flags.DISABLE_WARNING_SCREEN ? TitleState : funkin.menus.WarningState;

if (Options.devMode && Options.allowConfigWarning) {
if (Options.devMode && Options.allowConfigWarning && !Paths.assetsTree.hasCompressedLibrary) { // because it's a zip file, you can't edit a zip file without decompiling it
var lib:ModsFolderLibrary;
for (e in Paths.assetsTree.libraries) if ((lib = cast AssetsLibraryList.getCleanLibrary(e)) is ModsFolderLibrary
&& lib.modName == ModsFolder.currentModFolder)
Expand Down
1 change: 1 addition & 0 deletions source/funkin/backend/system/macros/Macros.hx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class Macros {
final fields:Array<Field> = Context.getBuildFields(), pos:Position = Context.currentPos();

fields.push({name: 'tag', access: [APublic], pos: pos, kind: FVar(macro :funkin.backend.assets.AssetSource)});
fields.push({name: 'isCompressed', access: [APublic], pos: pos, kind: FVar(macro :Bool, macro false)});

return fields;
}
Expand Down
15 changes: 15 additions & 0 deletions source/funkin/backend/utils/CoolUtil.hx
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,21 @@ final class CoolUtil

return toProperty.setValue(fromProperty.getValue());
}

private static var lastTimeStamp:Float = -1;
public static function debugTimeStamp(?customText:String = "Quick Debug") {
if (lastTimeStamp > 0) {
var endTimeStamp = haxe.Timer.stamp();
Logs.traceColored([
Logs.logText("[Haxe Time Stamp] ", YELLOW),
Logs.logText(customText),
Logs.logText(' | Time stamp took ${(endTimeStamp - lastTimeStamp)} milliseconds.'),
], INFO);
lastTimeStamp = -1;
return;
}
lastTimeStamp = haxe.Timer.stamp();
}
}

class PropertyInfo {
Expand Down
Loading