diff --git a/Project.xml b/Project.xml index 143b6b18..1c383c35 100644 --- a/Project.xml +++ b/Project.xml @@ -96,7 +96,7 @@ - + @@ -151,7 +151,7 @@ - + @@ -164,6 +164,7 @@ + diff --git a/buildnumber.txt b/buildnumber.txt index ae6f1163..f4a8e832 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -1507 \ No newline at end of file +1513 \ No newline at end of file diff --git a/source/funkin/menus/TitleState.hx b/source/funkin/menus/TitleState.hx index 58b685b5..b27f5d02 100644 --- a/source/funkin/menus/TitleState.hx +++ b/source/funkin/menus/TitleState.hx @@ -175,12 +175,16 @@ class TitleState extends MusicBeatState new FlxTimer().start(2, function(tmr:FlxTimer) { + #if UPDATE_CHECKING var report = funkin.updating.UpdateUtil.checkForUpdates(); if (report.newUpdate) { FlxG.switchState(new funkin.updating.UpdateScreen(report)); } else { FlxG.switchState(new MainMenuState()); } + #else + FlxG.switchState(new MainMenuState()); + #end }); } diff --git a/source/funkin/system/AssetsLibraryList.hx b/source/funkin/system/AssetsLibraryList.hx index 66dec5ae..008ee91a 100644 --- a/source/funkin/system/AssetsLibraryList.hx +++ b/source/funkin/system/AssetsLibraryList.hx @@ -45,11 +45,13 @@ class AssetsLibraryList extends AssetLibrary { } // TODO: do base folder scanning + #if MODS_FOLDER if (l is ModsAssetLibrary) { var lib = cast(l, ModsAssetLibrary); for(e in lib.getFiles(folder)) content.push(e); } + #end } return content; } @@ -95,6 +97,21 @@ class AssetsLibraryList extends AssetLibrary { } } + public override function isLocal(id:String, type:String) { + return true; + // for(l in libraries) { + // var lib:AssetLibrary = l; + // if (lib is openfl.utils.AssetLibrary) { + // var openflLib = cast(lib, openfl.utils.AssetLibrary); + // @:privateAccess + // if (openflLib.__proxy != null) lib = openflLib.__proxy; + // } + // if (lib.exists(id, type) && lib.isLocal(id, type)) + // return true; + // } + // return false; + } + public function new(?base:AssetLibrary) { super(); if (base == null) diff --git a/source/funkin/system/Main.hx b/source/funkin/system/Main.hx index a271746e..70139dab 100644 --- a/source/funkin/system/Main.hx +++ b/source/funkin/system/Main.hx @@ -151,8 +151,6 @@ class Main extends Sprite #if USE_SOURCE_ASSETS trace("Used lime test windows. Switching into source assets."); Paths.assetsTree.addLibrary(ModsFolder.loadLibraryFromFolder('assets', './../../../../assets/', true)); - #else - Assets.registerLibrary('assets', Paths.assetsTree.base); #end var buildNum:Int = Std.parseInt(File.getContent("./../../../../buildnumber.txt")); @@ -161,12 +159,8 @@ class Main extends Sprite } else { #if USE_ADAPTED_ASSETS Paths.assetsTree.addLibrary(ModsFolder.loadLibraryFromFolder('assets', './assets/', true)); - #else - Assets.registerLibrary('assets', Paths.assetsTree.base); #end } - #else - Assets.registerLibrary('assets', Paths.assetsTree.base); #end diff --git a/source/funkin/system/MusicBeatState.hx b/source/funkin/system/MusicBeatState.hx index aa271226..aace2587 100644 --- a/source/funkin/system/MusicBeatState.hx +++ b/source/funkin/system/MusicBeatState.hx @@ -1,21 +1,15 @@ package funkin.system; -import sys.thread.Thread; -import flixel.FlxBasic; import funkin.scripting.DummyScript; import flixel.FlxState; import flixel.FlxSubState; import funkin.scripting.events.*; import funkin.scripting.Script; import funkin.interfaces.IBeatReceiver; -import funkin.system.Conductor.BPMChangeEvent; import funkin.system.Conductor; import flixel.FlxG; -import flixel.addons.transition.FlxTransitionableState; import flixel.addons.ui.FlxUIState; -import flixel.math.FlxRect; import flixel.math.FlxMath; -import flixel.util.FlxTimer; import funkin.options.PlayerSettings; class MusicBeatState extends FlxUIState implements IBeatReceiver diff --git a/source/funkin/system/ZipUtils.hx b/source/funkin/system/ZipUtils.hx new file mode 100644 index 00000000..adebb0ef --- /dev/null +++ b/source/funkin/system/ZipUtils.hx @@ -0,0 +1,266 @@ +package funkin.system; + +#if (!macro && sys) +import openfl.display.BitmapData; +#end + +import haxe.Exception; +import haxe.Json; +import haxe.crypto.Crc32; +import haxe.zip.Writer; +import haxe.zip.Tools; +import haxe.zip.Entry; +import haxe.zip.Uncompress; +import haxe.zip.Reader; +import haxe.zip.Compress; +import sys.FileSystem; +import sys.io.File; +import sys.thread.Thread; + +using StringTools; + +// import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\"); +// import ZipUtils; var e = ZipUtils.createZipFile("gjnsdghs.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close(); + +class ZipUtils { + public static var bannedNames:Array = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"]; + + /** + * [Description] Uncompresses `zip` into the `destFolder` folder + * @param zip + * @param destFolder + */ + public static function uncompressZip(zip:Reader, destFolder:String, ?prefix:String, ?prog:ZipProgress):ZipProgress { + // we never know + FileSystem.createDirectory(destFolder); + + var fields = zip.read(); + + try { + if (prefix != null) { + var f = fields; + fields = new List(); + for(field in f) { + if (field.fileName.startsWith(prefix)) { + fields.push(field); + } + } + } + + if (prog == null) + prog = new ZipProgress(); + prog.fileCount = fields.length; + for(k=>field in fields) { + prog.curFile = k; + var isFolder = field.fileName.endsWith("/") && field.fileSize == 0; + if (isFolder) { + FileSystem.createDirectory('${destFolder}/${field.fileName}'); + } else { + var split = [for(e in field.fileName.split("/")) e.trim()]; + split.pop(); + FileSystem.createDirectory('${destFolder}/${split.join("/")}'); + + var data = unzip(field); + File.saveBytes('${destFolder}/${field.fileName}', data); + } + } + prog.curFile = fields.length; + prog.done = true; + } catch(e) { + prog.done = true; + prog.error = e; + } + return prog; + } + + #if (!macro && sys) + public static function uncompressZipAsync(zip:Reader, destFolder:String, ?prog:ZipProgress, ?prefix:String):ZipProgress { + if (prog == null) + prog = new ZipProgress(); + Thread.create(function() { + uncompressZip(zip, destFolder, prefix, prog); + }); + return prog; + } + #end + + /** + * [Description] Returns a `zip.Reader` instance from path. + * @param zipPath + * @return Reader + */ + public static function openZip(zipPath:String):Reader { + return new ZipReader(File.read(zipPath)); + } + + /** + * [Description] Copy of haxe's Zip unzip function cause lime replaced it. + * @param f Zip entry + */ + 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; + } + + /** + * [Description] Creates a ZIP file at the specified location and returns the Writer. + * @param path + * @return Writer + */ + public static function createZipFile(path:String):ZipWriter { + var output = File.write(path); + return new ZipWriter(output); + } + + /** + [Description] Writes the entirety of a folder to a zip file. + @param zip ZIP file to write to + @param path Folder path + @param prefix (Additional) allows you to set a prefix in the zip itself. + **/ + public static function writeFolderToZip(zip:ZipWriter, path:String, ?prefix:String, ?prog:ZipProgress, ?whitelist:Array):ZipProgress { + if (prefix == null) prefix = ""; + if (whitelist == null) whitelist = []; + if (prog == null) prog = new ZipProgress(); + + try { + var curPath:Array = ['$path']; + var destPath:Array = []; + if (prefix != "") { + prefix = prefix.replace("\\", "/"); + while(prefix.charAt(0) == "/") prefix = prefix.substr(1); + while(prefix.charAt(prefix.length-1) == "/") prefix = prefix.substr(0, prefix.length-1); + destPath.push(prefix); + } + + var files:Array = []; + + var doFolder:Void->Void = null; + (doFolder = function() { + var path = curPath.join("/"); + var zipPath = destPath.join("/"); + for(e in FileSystem.readDirectory(path)) { + if (bannedNames.contains(e.toLowerCase()) && !whitelist.contains(e.toLowerCase())) continue; + if (FileSystem.isDirectory('$path/$e')) { + // is directory, so loop into that function again + for(p in [curPath, destPath]) p.push(e); + doFolder(); + for(p in [curPath, destPath]) p.pop(); + } else { + // is file, put it in the list + var zipPath = '$zipPath/$e'; + while(zipPath.charAt(0) == "/") zipPath = zipPath.substr(1); + files.push(new StrNameLabel('$path/$e', zipPath)); + } + } + })(); + + prog.fileCount = files.length; + for(k=>file in files) { + prog.curFile = k; + + var fileContent = File.getBytes(file.name); + var fileInfo = FileSystem.stat(file.name); + var entry:Entry = { + fileName: file.label, + fileSize: fileInfo.size, + fileTime: Date.now(), + dataSize: 0, + data: fileContent, + crc32: Crc32.make(fileContent), // TODO??? + compressed: false + }; + Tools.compress(entry, 1); + zip.writeFile(entry); + } + zip.writeCDR(); + } catch(e) { + prog.error = e; + } + prog.done = true; + return prog; + } + + public static function writeFolderToZipAsync(zip:ZipWriter, path:String, ?prefix:String):ZipProgress { + var zipProg = new ZipProgress(); + Thread.create(function() { + writeFolderToZip(zip, path, prefix, zipProg); + }); + return zipProg; + } + + /** + * [Description] Converts an `Array` to a `List`. + * @param array + * @return List + */ + public static function arrayToList(array:Array):List { + var list = new List(); + for(e in array) list.push(e); + return list; + } +} + +class ZipProgress { + public var error:Exception = null; + + public var curFile:Int = 0; + public var fileCount:Int = 0; + public var done:Bool = false; + public var percentage(get, null):Float; + + private function get_percentage() { + return fileCount <= 0 ? 0 : curFile / fileCount; + } + + public function new() {} +} + +class ZipReader extends Reader { + public var files:List; + + public override function read() { + if (files != null) return files; + try { + var files = super.read(); + return this.files = files; + } catch(e) { + } + return new List(); + } +} + +class ZipWriter extends Writer { + public function flush() { + o.flush(); + } + + public function writeFile(entry:Entry) { + writeEntryHeader(entry); + o.writeFullBytes(entry.data, 0, entry.data.length); + } + + public function close() { + o.close(); + } +} + +class StrNameLabel { + public var name:String; + public var label:String; + + public function new(name:String, label:String) { + this.name = name; + this.label = label; + } +} \ No newline at end of file diff --git a/source/funkin/updating/AsyncUpdater.hx b/source/funkin/updating/AsyncUpdater.hx index 7d45e04f..68fb73fb 100644 --- a/source/funkin/updating/AsyncUpdater.hx +++ b/source/funkin/updating/AsyncUpdater.hx @@ -1,5 +1,15 @@ package funkin.updating; +import haxe.zip.Reader; +import funkin.system.ZipUtils; +import haxe.io.Path; +import openfl.utils.ByteArray; +import sys.io.File; +import sys.io.FileOutput; +import openfl.events.Event; +import openfl.events.ProgressEvent; +import openfl.net.URLRequest; +import openfl.net.URLLoader; import sys.FileSystem; import funkin.github.GitHubRelease; @@ -15,13 +25,98 @@ class AsyncUpdater { } #end + + #if windows + public var executableGitHubName:String = "update-windows.exe"; + public var executableName:String = "CodenameEngine.exe"; + #end + #if linux + public var executableGitHubName:String = "update-linux"; + public var executableName:String = "CodenameEngine"; + #end + public var releases:Array; public var progress:UpdaterProgress = new UpdaterProgress(); public var path:String; + public var downloadStream:URLLoader; public function installUpdates() { prepareInstallationEnvironment(); + downloadFiles(); + } + public function installFiles(files:Array) { + progress.step = INSTALLING; + for(e in files) { + var path = '$path$e'; + trace('extracting file ${path}'); + var reader = ZipUtils.openZip(path); + + progress.curZipProgress = new ZipProgress(); + ZipUtils.uncompressZip(reader, './', null, progress.curZipProgress); + } + } + + public function downloadFiles() { + var files:Array = []; + var fileNames:Array = []; + var exePath:String = ""; + for(r in releases) { + for(e in r.assets) { + if (e.name.toLowerCase() == "update-assets.zip") { + files.push(e.browser_download_url); + fileNames.push('${Path.withoutExtension(e.name)}-${r.tag_name}.${Path.extension(e.name)}'); + } else if (e.name.toLowerCase() == executableGitHubName) { + exePath = e.browser_download_url; + } + } + } + progress.curFile = 0; + progress.files = files.length; + progress.step = DOWNLOADING_ASSETS; + trace('starting assets download'); + doFile([for(e in files) e], [for(e in fileNames) e], function() { + progress.curFile = 0; + progress.files = 1; + progress.step = DOWNLOADING_EXECUTABLE; + trace('starting exe download'); + // doFile([exePath], [executableName], function() { + trace('done, starting installation'); + installFiles(fileNames); + // }); + }); + } + + public function doFile(files:Array, fileNames:Array, onFinish:Void->Void) { + var f = files.shift(); + if (f == null) { + onFinish(); + return; + } + var fn = fileNames.shift(); + trace('downloading $f ($fn)'); + progress.curFile++; + downloadStream = new URLLoader(); + downloadStream.dataFormat = BINARY; + + downloadStream.addEventListener(ProgressEvent.PROGRESS, function(e) { + progress.bytesLoaded = e.bytesLoaded; + progress.bytesTotal = e.bytesTotal; + trace('${progress.bytesLoaded} / ${progress.bytesTotal}'); + }); + downloadStream.addEventListener(Event.COMPLETE, function(e) { + var fileOutput:FileOutput = File.write('$path$fn', true); + + var data:ByteArray = new ByteArray(); + downloadStream.data.readBytes(data, 0, downloadStream.data.length - downloadStream.data.position); + fileOutput.writeBytes(data, 0, data.length); + fileOutput.flush(); + + fileOutput.close(); + doFile(files, fileNames, onFinish); + }); + + downloadStream.load(new URLRequest(f)); } public function prepareInstallationEnvironment() { @@ -39,6 +134,11 @@ class AsyncUpdater { class UpdaterProgress { public var step:UpdaterStep = PREPARING; + public var curFile:Int = 0; + public var files:Int = 0; + public var bytesLoaded:Float = 0; + public var bytesTotal:Float = 0; + public var curZipProgress:ZipProgress = null; public function new() {} }