From 70d288791b17ef3944ea0fbbe2ceddad92ad286c Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Wed, 7 May 2025 07:14:26 +0200 Subject: [PATCH] another 20 trillion sandboxed classes --- source/funkin/api/discord/DiscordClient.hx | 13 ++ source/funkin/api/newgrounds/Leaderboards.hx | 106 ++++++++++++++++ source/funkin/api/newgrounds/Medals.hx | 119 ++++++++++++++---- .../funkin/api/newgrounds/NewgroundsClient.hx | 18 +++ source/funkin/modding/PolymodHandler.hx | 25 ++-- 5 files changed, 251 insertions(+), 30 deletions(-) diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx index b0cb3180a13..4d6b5187808 100644 --- a/source/funkin/api/discord/DiscordClient.hx +++ b/source/funkin/api/discord/DiscordClient.hx @@ -204,4 +204,17 @@ typedef DiscordClientPresenceParams = */ var ?smallImageKey:String; } + +class DiscordClientSandboxed +{ + public static function setPresence(params:DiscordClientPresenceParams) + { + return DiscordClient.instance.setPresence(params); + } + + public static function shutdown() + { + DiscordClient.instance.shutdown(); + } +} #end diff --git a/source/funkin/api/newgrounds/Leaderboards.hx b/source/funkin/api/newgrounds/Leaderboards.hx index 5c7e5e73e0f..ce661877823 100644 --- a/source/funkin/api/newgrounds/Leaderboards.hx +++ b/source/funkin/api/newgrounds/Leaderboards.hx @@ -2,7 +2,10 @@ package funkin.api.newgrounds; #if FEATURE_NEWGROUNDS import io.newgrounds.Call.CallError; +import io.newgrounds.components.ScoreBoardComponent; +import io.newgrounds.objects.Score; import io.newgrounds.objects.ScoreBoard as LeaderboardData; +import io.newgrounds.objects.User; import io.newgrounds.objects.events.Outcome; import io.newgrounds.utils.ScoreBoardList; @@ -66,6 +69,41 @@ class Leaderboards } } + /** + * Request to receive scores from Newgrounds. + * @param leaderboard The leaderboard to fetch scores from. + * @param params Additional parameters for fetching the score. + */ + public static function requestScores(leaderboard:Leaderboard, params:RequestScoresParams) + { + // Silently reject retrieving scores from unknown leaderboards. + if (leaderboard == Leaderboard.Unknown) return; + + var leaderboardList = NewgroundsClient.instance.leaderboards; + if (leaderboardList == null) return; + + var leaderboardData:Null = leaderboardList.get(leaderboard.getId()); + if (leaderboardData == null) return; + + var user:Null = null; + if ((params?.useCurrentUser ?? false) && NewgroundsClient.instance.isLoggedIn()) user = NewgroundsClient.instance.user; + + leaderboardData.requestScores(params?.limit ?? 10, params?.skip ?? 0, params?.period ?? ALL, params?.social ?? false, params?.tag, user, + function(outcome:Outcome):Void { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Fetched scores!'); + if (params?.onComplete != null) params.onComplete(leaderboardData.scores); + + case FAIL(error): + trace('[NEWGROUNDS] Failed to fetch scores!'); + trace(error); + if (params?.onFail != null) params.onFail(); + } + }); + } + /** * Submit a score for a Story Level to Newgrounds. */ @@ -84,6 +122,74 @@ class Leaderboards Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag); } } + +/** + * Wrapper for `Leaderboards` that prevents submitting scores. + */ +@:nullSafety +class LeaderboardsSandboxed +{ + public static function getLeaderboardBySong(songId:String, difficultyId:String) + { + return Leaderboard.getLeaderboardBySong(songId, difficultyId); + } + + public static function getLeaderboardByLevel(levelId:String) + { + return Leaderboard.getLeaderboardByLevel(levelId); + } + + public function requestScores(leaderboard:Leaderboard, params:RequestScoresParams) + { + Leaderboards.requestScores(leaderboard, params); + } +} + +/** + * Additional parameters for `Leaderboards.requestScores()` + */ +typedef RequestScoresParams = +{ + /** + * How many scores to include in a list. + * @default `10` + */ + var ?limit:Int; + + /** + * How many scores to skip before starting the list. + * @default `0` + */ + var ?skip:Int; + + /** + * The time-frame to pull the scores from. + * @default `Period.ALL` + */ + var ?period:Period; + + /** + * If true, only scores by the user and their friends will be loaded. Ignored if no user is set. + * @default `false` + */ + var ?social:Bool; + + /** + * An optional tag to filter the results by. + * @default `null` + */ + var ?tag:String; + + /** + * If true, only the scores from the currently logged in user will be loaded. + * Additionally, if `social` is set to true, the scores of the user's friend will be loaded. + * @default `false` + */ + var ?useCurrentUser:Bool; + + var ?onComplete:Array->Void; + var ?onFail:Void->Void; +} #end enum abstract Leaderboard(Int) diff --git a/source/funkin/api/newgrounds/Medals.hx b/source/funkin/api/newgrounds/Medals.hx index 0559a004fc9..e64274793e4 100644 --- a/source/funkin/api/newgrounds/Medals.hx +++ b/source/funkin/api/newgrounds/Medals.hx @@ -131,33 +131,79 @@ class Medals } } + public static function fetchMedalData(medal:Medal):Null + { + var medalList = NewgroundsClient.instance.medals; + @:privateAccess + if (medalList == null || medalList._map == null) return null; + + var medalData:Null = medalList.get(medal.getId()); + @:privateAccess + if (medalData == null || medalData._data == null) + { + trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}'); + return null; + } + + return { + id: medalData.id, + name: medalData.name, + description: medalData.description, + icon: medalData.icon, + value: medalData.value, + difficulty: medalData.difficulty, + secret: medalData.secret, + unlocked: medalData.unlocked + } + } + public static function awardStoryLevel(id:String):Void { - switch (id) + var medal:Medal = Medal.getMedalByStoryLevel(id); + if (medal == Medal.Unknown) { - case 'tutorial': - Medals.award(Medal.StoryTutorial); - case 'week1': - Medals.award(Medal.StoryWeek1); - case 'week2': - Medals.award(Medal.StoryWeek2); - case 'week3': - Medals.award(Medal.StoryWeek3); - case 'week4': - Medals.award(Medal.StoryWeek4); - case 'week5': - Medals.award(Medal.StoryWeek5); - case 'week6': - Medals.award(Medal.StoryWeek6); - case 'week7': - Medals.award(Medal.StoryWeek7); - case 'weekend1': - Medals.award(Medal.StoryWeekend1); - default: - trace('[NEWGROUNDS] Story level does not have a medal! (${id}).'); + trace('[NEWGROUNDS] Story level does not have a medal! (${id}).'); + return; } + Medals.award(medal); + } +} + +/** + * Wrapper for `Medals` that prevents awarding medals. + */ +class MedalsSandboxed +{ + public static function fetchMedalData(medal:Medal):Null + { + return Medals.fetchMedalData(medal); + } + + public static function getMedalByStoryLevel(id:String):Medal + { + return Medal.getMedalByStoryLevel(id); + } + + public static function getAllMedals():Array + { + return Medal.getAllMedals(); } } + +/** + * Contains data for a Medal, but excludes functions like `sendUnlock()`. + */ +typedef FetchedMedalData = +{ + var id:Int; + var name:String; + var description:String; + var icon:String; + var value:Int; + var difficulty:Int; + var secret:Bool; + var unlocked:Bool; +} #end /** @@ -324,6 +370,8 @@ enum abstract Medal(Int) from Int to Int { switch (levelId) { + case "tutorial": + return StoryTutorial; case "week1": return StoryWeek1; case "week2": @@ -344,4 +392,33 @@ enum abstract Medal(Int) from Int to Int return Unknown; } } + + /** + * Lists all medals aside from the `Unknown` one. + */ + public static function getAllMedals() + { + return [ + StartGame, + StoryTutorial, + StoryWeek1, + StoryWeek2, + StoryWeek3, + StoryWeek4, + StoryWeek5, + StoryWeek6, + StoryWeek7, + StoryWeekend1, + CharSelect, + FreeplayPicoMix, + FreeplayStressPico, + LossRating, + PerfectRatingHard, + GoldPerfectRatingHard, + ErectDifficulty, + GoldPerfectRatingNightmare, + FridayNight, + Nice + ]; + } } diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index 36ab563abd8..8bd993ff64d 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -331,4 +331,22 @@ class NewgroundsClient return Save.instance.ngSessionId; } } + +/** + * Wrapper for `NewgroundsClient` that prevents submitting cheated data. + */ +class NewgroundsClientSandboxed +{ + public static var user(get, never):Null; + + static function get_user() + { + return NewgroundsClient.instance.user; + } + + public static function isLoggedIn() + { + return NewgroundsClient.instance.isLoggedIn(); + } +} #end diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 4ae1d46bc5a..dcf292360ca 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -258,6 +258,21 @@ class PolymodHandler // `funkin.util.FileUtil` has unrestricted access to the file system. Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed); + #if FEATURE_NEWGROUNDS + // `funkin.api.newgrounds.Leaderboards` allows for submitting cheated scores. + Polymod.addImportAlias('funkin.api.newgrounds.Leaderboards', funkin.api.newgrounds.Leaderboards.LeaderboardsSandboxed); + + // `funkin.api.newgrounds.Medals` allows for unfair granting of medals. + Polymod.addImportAlias('funkin.api.newgrounds.Medals', funkin.api.newgrounds.Medals.MedalsSandboxed); + + // `funkin.api.newgrounds.NewgroundsClientSandboxed` allows for submitting cheated data. + Polymod.addImportAlias('funkin.api.newgrounds.NewgroundsClient', funkin.api.newgrounds.NewgroundsClient.NewgroundsClientSandboxed); + #end + + #if FEATURE_DISCORD_RPC + Polymod.addImportAlias('funkin.api.discord.DiscordClient', funkin.api.discord.DiscordClient.DiscordClientSandboxed); + #end + // Add blacklisting for prohibited classes and packages. // `Sys` @@ -310,6 +325,7 @@ class PolymodHandler { if (cls == null) continue; var className:String = Type.getClassName(cls); + if (polymod.hscript._internal.PolymodScriptClass.importOverrides.exists(className)) continue; Polymod.blacklistImport(className); } @@ -322,15 +338,6 @@ class PolymodHandler Polymod.blacklistImport(className); } - // `funkin.api.newgrounds.*` - // Contains functions which allow for cheating medals and leaderboards. - for (cls in ClassMacro.listClassesInPackage('funkin.api.newgrounds')) - { - if (cls == null) continue; - var className:String = Type.getClassName(cls); - Polymod.blacklistImport(className); - } - // `io.newgrounds.*` // Contains functions which allow for cheating medals and leaderboards. for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))