diff --git a/CHANGELOG.md b/CHANGELOG.md index 08012d204..8edab9421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adaptive turns VSync off during FPS drops, which is better if supported by your graphics card. - Doesn't work on Mac, sorry! - Otis now has muzzle flashes when shooting. +- Added a little easter egg to one of Pico's Results Screen animations. - Overhauled the sticker system to allow modders to easily add custom sticker sets. ([cdc468b](https://github.com/FunkinCrew/Funkin/commit/cdc468ba15683b3c0f54015e337673c77ddd7962)) - by @AbnormalPoof in [#4003](https://github.com/FunkinCrew/Funkin/pull/4003) - Implemented a macro to optimize registries and entries. ([1497521](https://github.com/FunkinCrew/Funkin/commit/14975212a918adb1a5d96a349ec9e8e63c9fc64b)) - by @lemz1 in [#3694](https://github.com/FunkinCrew/Funkin/pull/3694) - The macro is now used to retrieve base game assets. ([963e2ec](https://github.com/FunkinCrew/Funkin/commit/963e2ecd630fae24dae4206d315618cf4c4be6db)) - by @lemz1 in [#4707](https://github.com/FunkinCrew/Funkin/pull/4707) @@ -43,6 +44,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed “Auto Pause” preference to “Pause on Unfocus” for clarity. ([52be941](https://github.com/FunkinCrew/Funkin/commit/52be941b4503da0ac76918e2482ab1804866f2cf)) - by @JackXson-Real in [#4346](https://github.com/FunkinCrew/Funkin/pull/4346) - Overhauled `FileUtil`, introducing various fixes, new functions, and sandboxing. ([95ade2a](https://github.com/FunkinCrew/Funkin/commit/95ade2a08b7709e8208ec1b3e123bf5b4308ba10)) - by @cyn0x8 in [#3032](https://github.com/FunkinCrew/Funkin/pull/3032) +### Fixed + +- Story mode medals are now awarded even without scoring a new personal best. +- Highscores are now submitted to Newgrounds even without scoring a new personal best. - The Freeplay menu no longer displays songs without Erect variations when returning from an Erect variation song. - Fixed Freeplay DJ animations for Boyfriend and Pico when idling (properly this time). - Alternate instrumentals for Cocoa, Senpai, Roses, and Stress are now locked until their Pico Mix is beaten. @@ -183,16 +188,6 @@ The Pit Stop 2 update! - Removed an unused class from Polymod blacklist. ([06c12e3](https://github.com/FunkinCrew/Funkin/commit/06c12e36c6bd6df4e2be32a3bec540172e79e162)) - by @AbnormalPoof in [#3729](https://github.com/FunkinCrew/Funkin/pull/3729) - Many additional small bug fixes. -### Fixed - -* @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843) -* @sphis-Sinco made their first contribution in [#3881](https://github.com/FunkinCrew/Funkin/pull/3881) -* @MrMadera made their first contribution in [#3934](https://github.com/FunkinCrew/Funkin/pull/3934) -* @MidyGamy made their first contribution in [#4068](https://github.com/FunkinCrew/Funkin/pull/4068) -* @Lasercar made their first contribution in [#4082](https://github.com/FunkinCrew/Funkin/pull/4082) -* @MrScottyPieey made their first contribution in [#4085](https://github.com/FunkinCrew/Funkin/pull/4085) - - ## New Contributors for 0.6.0 * @PatoFlamejanteTV made their first contribution in [#3843](https://github.com/FunkinCrew/Funkin/pull/3843) diff --git a/source/funkin/api/newgrounds/Medals.hx b/source/funkin/api/newgrounds/Medals.hx index 984362473..0559a004f 100644 --- a/source/funkin/api/newgrounds/Medals.hx +++ b/source/funkin/api/newgrounds/Medals.hx @@ -44,7 +44,8 @@ class Medals if (NewgroundsClient.instance.isLoggedIn()) { var medalList = NewgroundsClient.instance.medals; - if (medalList == null) return; + @:privateAccess + if (medalList == null || medalList._map == null) return; var medalData:Null = medalList.get(medal.getId()); @:privateAccess diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index 5541b2184..2447f5f2c 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -66,6 +66,8 @@ class FlxAtlasSprite extends FlxAnimate throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?'; } + onAnimationComplete.add(cleanupAnimation); + // This defaults the sprite to play the first animation in the atlas, // then pauses it. This ensures symbols are intialized properly. this.anim.play(''); @@ -317,7 +319,6 @@ class FlxAtlasSprite extends FlxAnimate else if (fr != null && anim.curFrame != anim.length - 1) { anim.curFrame--; - cleanupAnimation(currentAnimation ?? ""); _onAnimationComplete(); } } diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 569261427..f26b1e6fe 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -80,8 +80,10 @@ class PolymodHandler */ public static function loadAllMods():Void { + #if sys // Create the mod root if it doesn't exist. createModRoot(); + #end trace('Initializing Polymod (using all mods)...'); loadModsById(getAllModIds()); } @@ -91,9 +93,10 @@ class PolymodHandler */ public static function loadEnabledMods():Void { + #if sys // Create the mod root if it doesn't exist. createModRoot(); - + #end trace('Initializing Polymod (using configured mods)...'); loadModsById(Save.instance.enabledModIds); } @@ -103,9 +106,10 @@ class PolymodHandler */ public static function loadNoMods():Void { + #if sys // Create the mod root if it doesn't exist. createModRoot(); - + #end // We still need to configure the debug print calls etc. trace('Initializing Polymod (using no mods)...'); loadModsById([]); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index feb3dc10e..58525ddef 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1229,7 +1229,7 @@ class PlayState extends MusicBeatSubState if (!isMinimalMode) { if (iconP1 != null) iconP1.updatePosition(); - if (iconP1 != null) iconP2.updatePosition(); + if (iconP2 != null) iconP2.updatePosition(); } // Transition to the game over substate. @@ -1898,14 +1898,15 @@ class PlayState extends MusicBeatSubState // Position the player strumline on the right half of the screen playerStrumline.x = (FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET) + (cutoutSize / 2.0); // Classic style // playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style - playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; + + playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET - noteStyle.getStrumlineOffsets()[1] : Constants.STRUMLINE_Y_OFFSET; playerStrumline.zIndex = 1001; playerStrumline.cameras = [camHUD]; // Position the opponent strumline on the left half of the screen - opponentStrumline.x = Constants.STRUMLINE_X_OFFSET + cutoutSize; - opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; + opponentStrumline.x = Constants.STRUMLINE_X_OFFSET; + opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET - noteStyle.getStrumlineOffsets()[1] : Constants.STRUMLINE_Y_OFFSET; opponentStrumline.zIndex = 1000; opponentStrumline.cameras = [camHUD]; @@ -2889,7 +2890,7 @@ class PlayState extends MusicBeatSubState #end // 9: Toggle the old icon. - if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon(); + if (FlxG.keys.justPressed.NINE && iconP1 != null) iconP1.toggleOldIcon(); #if FEATURE_DEBUG_FUNCTIONS // PAGEUP: Skip forward two sections. diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx index 5b8d21cfb..cb5528e4d 100644 --- a/source/funkin/ui/debug/anim/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -572,7 +572,7 @@ class DebugBoundingState extends FlxState _file.addEventListener(Event.COMPLETE, onSaveComplete); _file.addEventListener(Event.CANCEL, onSaveCancel); _file.addEventListener(IOErrorEvent.IO_ERROR, onSaveError); - _file.save(saveString,); + _file.save(saveString, fileName); } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 6f012d807..3f2b40f81 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3225,7 +3225,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState #if sys // TODO: Is there a way to open a folder and highlight a file in it? var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]); - WindowUtil.openFolder(absoluteBackupsPath); + FileUtil.openFolder(absoluteBackupsPath); return true; #else trace('No file system access, cannot open backups folder.'); @@ -5838,6 +5838,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { uiCamera.revive(); FlxG.cameras.reset(uiCamera); + uiCamera.onResize(); add(this.root); } diff --git a/source/funkin/ui/debug/charting/dialogs/ChartEditorWelcomeDialog.hx b/source/funkin/ui/debug/charting/dialogs/ChartEditorWelcomeDialog.hx index 8d5e8b620..165d4ca5b 100644 --- a/source/funkin/ui/debug/charting/dialogs/ChartEditorWelcomeDialog.hx +++ b/source/funkin/ui/debug/charting/dialogs/ChartEditorWelcomeDialog.hx @@ -91,6 +91,8 @@ class ChartEditorWelcomeDialog extends ChartEditorBaseDialog #end linkRecentChart.onClick = function(_event) { + linkRecentChart.hide(); + this.hideDialog(DialogButton.CANCEL); // Load chart from file diff --git a/source/funkin/ui/debug/stageeditor/StageEditorState.hx b/source/funkin/ui/debug/stageeditor/StageEditorState.hx index aaafc1a52..a1c56bb85 100644 --- a/source/funkin/ui/debug/stageeditor/StageEditorState.hx +++ b/source/funkin/ui/debug/stageeditor/StageEditorState.hx @@ -1267,7 +1267,7 @@ class StageEditorState extends UIState case "open folder": #if sys var absoluteBackupsPath:String = haxe.io.Path.join([Sys.getCwd(), BACKUPS_PATH]); - WindowUtil.openFolder(absoluteBackupsPath); + FileUtil.openFolder(absoluteBackupsPath); #end case "test stage": diff --git a/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx b/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx index 5ab4029ef..e7b4e31e1 100644 --- a/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx +++ b/source/funkin/ui/debug/stageeditor/components/BackupAvailableDialog.hx @@ -5,7 +5,6 @@ import haxe.ui.containers.dialogs.Dialog.DialogButton; import funkin.util.FileUtil; import haxe.io.Path; import funkin.util.DateUtil; -import funkin.util.WindowUtil; using StringTools; @@ -57,7 +56,7 @@ class BackupAvailableDialog extends Dialog // :[ #if sys var absoluteBackupsPath:String = Path.join([Sys.getCwd(), StageEditorState.BACKUPS_PATH]); - WindowUtil.openFolder(absoluteBackupsPath); + FileUtil.openFolder(absoluteBackupsPath); #end } diff --git a/source/funkin/ui/freeplay/FreeplayScore.hx b/source/funkin/ui/freeplay/FreeplayScore.hx index 3f0813bef..7744d67eb 100644 --- a/source/funkin/ui/freeplay/FreeplayScore.hx +++ b/source/funkin/ui/freeplay/FreeplayScore.hx @@ -35,7 +35,7 @@ class FreeplayScore extends FlxTypedSpriteGroup loopNum--; } - while (loopNum > 0) + while (loopNum >= 0) { group.members[loopNum].digit = 0; loopNum--; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 9acee2598..365578421 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -567,13 +567,13 @@ class FreeplayState extends MusicBeatSubState switch (str) { case 'fav': - generateSongList({filterType: FAVORITE}, true); + generateSongList({filterType: FAVORITE}, true, false); case 'ALL': - generateSongList(null, true); + generateSongList(null, true, false); case '#': - generateSongList({filterType: REGEXP, filterData: '0-9'}, true); + generateSongList({filterType: REGEXP, filterData: '0-9'}, true, false); default: - generateSongList({filterType: REGEXP, filterData: str}, true); + generateSongList({filterType: REGEXP, filterData: str}, true, false); } // If the current song is still in the list, or if it was random, we'll land on it @@ -2033,7 +2033,8 @@ class FreeplayState extends MusicBeatSubState var songScore:Null = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation); intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); + intendedCompletion = songScore == null ? 0.0 : Math.max(0, ((songScore.tallies.sick + + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes)); rememberedDifficulty = currentDifficulty; grpCapsules.members[curSelected].refreshDisplay((prepForNewRank == true) ? false : true); } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 6e8de758f..454553eef 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -108,6 +108,7 @@ class MainMenuState extends MusicBeatState createMenuItem('freeplay', 'mainmenu/freeplay', function() { persistentDraw = true; persistentUpdate = false; + rememberedSelectedIndex = menuItems.selectedIndex; // Freeplay has its own custom transition FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; @@ -422,6 +423,7 @@ class MainMenuState extends MusicBeatState { if (menuItems.enabled && !menuItems.busy) { + rememberedSelectedIndex = menuItems.selectedIndex; FlxG.switchState(() -> new TitleState()); FunkinSound.playOnce(Paths.sound('cancelMenu')); } diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 311e021f9..6e3d94021 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -52,24 +52,44 @@ class FileUtil /** * Paths which should not be deleted or modified by scripts. */ - private static final PROTECTED_PATHS:Array = [ - '', - 'assets', - 'manifest', - 'manifest/*', - 'plugins', - 'plugins/*', - 'Funkin.exe', - 'Funkin', - 'libvlc.dll', - 'libvlccore.dll', - 'lime.ndll' - ]; + public static var PROTECTED_PATHS(get, never):Array; + + public static function get_PROTECTED_PATHS():Array + { + final protected:Array = [ + '', + '.', + 'assets', + 'assets/*', + 'backups', + 'backups/*', + 'manifest', + 'manifest/*', + 'plugins', + 'plugins/*', + 'Funkin.exe', + 'Funkin', + 'icon.ico', + 'libvlc.dll', + 'libvlccore.dll', + 'lime.ndll', + 'scores.json' + ]; + + #if sys + for (i in 0...protected.length) + { + protected[i] = sys.FileSystem.fullPath(Path.join([gameDirectory, protected[i]])); + } + #end + + return protected; + } /** - * Invalid filesystem characters. + * Regex for invalid filesystem characters. */ - private static final INVALID_CHARS:Array = [':', '*', '?', '"', '<', '>', '|']; + public static final INVALID_CHARS:EReg = ~/[:*?"<>|\n\r\t]/g; #if sys private static var _gameDirectory:Null = null; @@ -82,7 +102,7 @@ class FileUtil return _gameDirectory; } - return _gameDirectory = Path.directory(Path.normalize(sys.FileSystem.fullPath(Sys.programPath()))); + return _gameDirectory = sys.FileSystem.fullPath(Path.directory(Sys.programPath())); } #end @@ -601,8 +621,6 @@ class FileUtil throw 'Target path is a directory, not a file: "$path"'; } - createDirIfNotExists(Path.directory(path)); - var shouldWrite:Bool = true; switch (mode) { @@ -791,14 +809,14 @@ class FileUtil */ public static function createDirIfNotExists(dir:String):Void { - #if sys if (!directoryExists(dir)) { + #if sys sys.FileSystem.createDirectory(dir); + #else + throw 'Directory creation is not supported on this platform.'; + #end } - #else - throw 'Directory creation is not supported on this platform.'; - #end } /** @@ -1124,8 +1142,64 @@ class FileUtil }; } - public static function openFolder(pathFolder:String) // what is this for?? - WindowUtil.openFolder(pathFolder); + /** + * Runs platform-specific code to open a path in the file explorer. + * + * @param pathFolder The path of the folder to open. + */ + public static function openFolder(pathFolder:String):Void + { + #if sys + pathFolder = pathFolder.trim(); + if (!directoryExists(pathFolder)) + { + throw 'Path is not a directory: "$pathFolder"'; + } + + #if windows + Sys.command('explorer', [pathFolder.replace('/', '\\')]); + #elseif mac + // mac could be fuckie with where the log folder is relative to the game file... + // if this comment is still here... it means it has NOT been verified on mac yet! + // + // FileUtil.hx note: this was originally used to open the logs specifically! + // thats why the above comment is there! + Sys.command('open', [pathFolder]); + #elseif linux + // TODO: implement linux + // some shit with xdg-open :thinking: emoji... + #end + #else + throw 'External folder open is not supported on this platform.'; + #end + } + + /** + * Runs platform-specific code to open a file explorer and select a specific file. + * + * @param path The path of the file to select. + */ + public static function openSelectFile(path:String):Void + { + #if sys + path = path.trim(); + if (!pathExists(path)) + { + throw 'Path does not exist: "$path"'; + } + + #if windows + Sys.command('explorer', ['/select,', path.replace('/', '\\')]); + #elseif mac + Sys.command('open', ['-R', path]); + #elseif linux + // TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file. + Sys.command('open', [path]); + #end + #else + throw 'External file selection is not supported on this platform.'; + #end + } private static function convertTypeFilter(?typeFilter:Array):Null { @@ -1160,7 +1234,8 @@ class FileUtilSandboxed */ public static function sanitizePath(path:String):String { - if (path == null) + path = (path ?? '').trim(); + if (path == '') { #if sys return FileUtil.gameDirectory; @@ -1174,19 +1249,14 @@ class FileUtilSandboxed path = path.substring(path.lastIndexOf(':') + 1); } - path = path.trim().replace('\\', '/'); + path = path.replace('\\', '/'); while (path.contains('//')) { path = path.replace('//', '/'); } - @:privateAccess for (char in FileUtil.INVALID_CHARS) - { - path = path.replace(char, ''); - } - - var parts:Array = path.split('/'); - var sanitized:Array = new Array(); + final parts:Array = FileUtil.INVALID_CHARS.replace(path, '').split('/'); + final sanitized:Array = new Array(); for (part in parts) { switch (part) @@ -1196,25 +1266,31 @@ class FileUtilSandboxed case '..': sanitized.pop(); default: - sanitized.push(part); + sanitized.push(part.trim()); } } - var ret:String = sanitized.join('/'); + if (sanitized.length == 0) + { + #if sys + return FileUtil.gameDirectory; + #else + return ''; + #end + } #if sys - var realPath:String = Path.normalize(sys.FileSystem.fullPath(Path.join([FileUtil.gameDirectory, ret]))); - // ^ TODO: figure out how to get "real" path of symlinked paths - - if (!realPath.startsWith(FileUtil.gameDirectory.toString())) + // TODO: figure out how to get "real" path of symlinked paths + final realPath:String = sys.FileSystem.fullPath(Path.join([FileUtil.gameDirectory, sanitized.join('/')])); + if (!realPath.startsWith(FileUtil.gameDirectory)) { return FileUtil.gameDirectory; } - ret = realPath; + return realPath; + #else + return sanitized.join('/'); #end - - return ret; } /** @@ -1222,9 +1298,9 @@ class FileUtilSandboxed * @param path The path to check. * @return Whether the path is protected. */ - public static function isProtected(path:String):Bool + public static function isProtected(path:String, sanitizeFirst:Bool = true):Bool { - path = sanitizePath(path); + if (sanitizeFirst) path = sanitizePath(path); @:privateAccess for (protected in FileUtil.PROTECTED_PATHS) { if (path == protected || (protected.contains('*') && path.startsWith(protected.substring(0, protected.indexOf('*'))))) @@ -1301,7 +1377,7 @@ class FileUtilSandboxed public static function saveFilesAsZIPToPath(resources:Array, path:String, mode:FileWriteMode = Skip):Bool { - if (isProtected(path = sanitizePath(path))) throw 'Cannot write to protected path: $path'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path'; return FileUtil.saveFilesAsZIPToPath(resources, path, mode); } @@ -1332,32 +1408,32 @@ class FileUtilSandboxed public static function writeStringToPath(path:String, data:String, mode:FileWriteMode = Skip):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot write to protected path: $path'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path'; FileUtil.writeStringToPath(path, data, mode); } public static function writeBytesToPath(path:String, data:Bytes, mode:FileWriteMode = Skip):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot write to protected path: $path'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path'; FileUtil.writeBytesToPath(path, data, mode); } public static function appendStringToPath(path:String, data:String):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot write to protected path: $path'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot write to protected path: $path'; FileUtil.appendStringToPath(path, data); } public static function moveFile(path:String, destination:String):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot move protected path: $path'; - if (isProtected(destination = sanitizePath(destination))) throw 'Cannot move to protected path: $destination'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot move protected path: $path'; + if (isProtected(destination = sanitizePath(destination), false)) throw 'Cannot move to protected path: $destination'; FileUtil.moveFile(path, destination); } public static function deleteFile(path:String):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot delete protected path: $path'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot delete protected path: $path'; FileUtil.deleteFile(path); } @@ -1393,14 +1469,14 @@ class FileUtilSandboxed public static function moveDir(path:String, destination:String, ?ignore:Array, strict:Bool = true):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot move protected path: "$path"'; - if (isProtected(destination = sanitizePath(destination))) throw 'Cannot move to protected path: "$destination"'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot move protected path: "$path"'; + if (isProtected(destination = sanitizePath(destination), false)) throw 'Cannot move to protected path: "$destination"'; FileUtil.moveDir(path, destination, ignore, strict); } public static function deleteDir(path:String, recursive:Bool = false, ?ignore:Array):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot delete protected path: "$path"'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot delete protected path: "$path"'; FileUtil.deleteDir(path, recursive, ignore); } @@ -1416,7 +1492,7 @@ class FileUtilSandboxed public static function rename(path:String, newName:String, keepExtension:Bool = true):Void { - if (isProtected(path = sanitizePath(path))) throw 'Cannot rename protected path: "$path"'; + if (isProtected(path = sanitizePath(path), false)) throw 'Cannot rename protected path: "$path"'; FileUtil.rename(path, sanitizePath(newName), keepExtension); } @@ -1445,9 +1521,14 @@ class FileUtilSandboxed return FileUtil.makeZIPEntryFromBytes(name, data); } - public static function openFolder(pathFolder:String) + public static function openFolder(pathFolder:String):Void { - FileUtil.openFolder(pathFolder); + FileUtil.openFolder(sanitizePath(pathFolder)); + } + + public static function openSelectFile(path:String):Void + { + FileUtil.openSelectFile(sanitizePath(path)); } } diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx index 18fd41472..10b38c03a 100644 --- a/source/funkin/util/MemoryUtil.hx +++ b/source/funkin/util/MemoryUtil.hx @@ -17,10 +17,10 @@ class MemoryUtil { #if cpp var result:String = 'HXCPP-Immix:'; - result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; - result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; - result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; - result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_LARGE)} bytes'; + result += '\n- Memory Used: ${cpp.vm.Gc.memInfo64(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; + result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo64(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; + result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo64(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; + result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo64(cpp.vm.Gc.MEM_INFO_LARGE)} bytes'; result += '\n- HXCPP Debugger: ${#if HXCPP_DEBUGGER 'Enabled' #else 'Disabled' #end}'; result += '\n- HXCPP Exp Generational Mode: ${#if HXCPP_GC_GENERATIONAL 'Enabled' #else 'Disabled' #end}'; result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_MOVING 'Enabled' #else 'Disabled' #end}'; diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 0554c30b2..2c5519e3d 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -16,6 +16,39 @@ using StringTools; #end class WindowUtil { + /** + * A regex to match valid URLs. + */ + public static final URL_REGEX:EReg = ~/^https?:\/?\/?(?:www\.)?[-a-zA-Z0-9@:%_\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/; + + /** + * Sanitizes a URL via a regex. + * + * @param targetUrl The URL to sanitize. + * @return The sanitized URL, or an empty string if the URL is invalid. + */ + public static function sanitizeURL(targetUrl:String):String + { + targetUrl = (targetUrl ?? '').trim(); + if (targetUrl == '') + { + return ''; + } + + final lowerUrl:String = targetUrl.toLowerCase(); + if (!lowerUrl.startsWith('http:') && !lowerUrl.startsWith('https:')) + { + targetUrl = 'http://' + targetUrl; + } + + if (URL_REGEX.match(targetUrl)) + { + return URL_REGEX.matched(0); + } + + return ''; + } + /** * Runs platform-specific code to open a URL in a web browser. * @param targetUrl The URL to open. @@ -28,6 +61,12 @@ class WindowUtil else if (protocol[0] != 'http' && protocol[0] != 'https') throw "openURL can only open http and https links."; #if FEATURE_OPEN_URL + targetUrl = sanitizeURL(targetUrl); + if (targetUrl == '') + { + throw 'Invalid URL: "$targetUrl"'; + } + #if linux Sys.command('/usr/bin/xdg-open $targetUrl &'); #else @@ -63,54 +102,6 @@ class WindowUtil } #end - /** - * Runs platform-specific code to open a path in the file explorer. - * @param targetPath The path to open. - */ - public static function openFolder(targetPath:String):Void - { - #if html5 - throw 'Cannot open URLs on this platform.'; - #else - if (!sys.FileSystem.exists(targetPath) || !sys.FileSystem.isDirectory(targetPath)) throw 'openFolder should only be used to open existing folders.'; - #if FEATURE_OPEN_URL - #if windows - Sys.command('explorer', [targetPath.replace('/', '\\')]); - #elseif mac - Sys.command('open', [targetPath]); - #elseif linux - Sys.command('open', [targetPath]); - #end - #else - throw 'Cannot open URLs on this platform.'; - #end - #end - } - - /** - * Runs platform-specific code to open a file explorer and select a specific file. - * @param targetPath The path of the file to select. - */ - public static function openSelectFile(targetPath:String):Void - { - #if html5 - throw 'Cannot open URLs on this platform.'; - #else - #if FEATURE_OPEN_URL - #if windows - Sys.command('explorer', ['/select,' + targetPath.replace('/', '\\')]); - #elseif mac - Sys.command('open', ['-R', targetPath]); - #elseif linux - // TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file. - Sys.command('open', [targetPath]); - #end - #else - throw 'Cannot open URLs on this platform.'; - #end - #end - } - /** * Dispatched when the game window is closed. */ diff --git a/source/funkin/util/plugins/VolumePlugin.hx b/source/funkin/util/plugins/VolumePlugin.hx index 5dbe60abf..9fdb65558 100644 --- a/source/funkin/util/plugins/VolumePlugin.hx +++ b/source/funkin/util/plugins/VolumePlugin.hx @@ -31,4 +31,11 @@ class VolumePlugin extends FlxBasic else if (PlayerSettings.player1.controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); } } + + override public function destroy():Void + { + if (FlxG.plugins.list.contains(this)) FlxG.plugins.remove(this); + + super.destroy(); + } }