diff --git a/.gitignore b/.gitignore
index 8b905e164..7401c2217 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ addons/*
dev-libs/
*.sum
.vs/
+source/funkin/backend/utils/GameJoltSecurity.hx
\ No newline at end of file
diff --git a/building/libs.xml b/building/libs.xml
index f78849581..9cc20032b 100644
--- a/building/libs.xml
+++ b/building/libs.xml
@@ -17,6 +17,7 @@
+
diff --git a/project.xml b/project.xml
index 3bcdd92c2..45a14243c 100644
--- a/project.xml
+++ b/project.xml
@@ -66,6 +66,9 @@
+
+
+
@@ -131,6 +134,7 @@
+
diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx
index 0e33f999e..e1db271b7 100644
--- a/source/funkin/backend/assets/ModsFolder.hx
+++ b/source/funkin/backend/assets/ModsFolder.hx
@@ -3,6 +3,7 @@ package funkin.backend.assets;
import flixel.util.FlxSignal.FlxTypedSignal;
import funkin.backend.system.MainState;
import funkin.backend.utils.CoolUtil;
+import funkin.backend.utils.GJUtil;
import haxe.ds.StringMap;
import haxe.io.Path;
import lime.text.Font;
@@ -75,9 +76,17 @@ class ModsFolder {
}
public static function reloadMods() {
- if (!__firstTime)
+ if (!__firstTime) {
+ #if GAMEJOLT_API
+ if (GJUtil.active)
+ GJUtil.logout();
+ #end
+
FlxG.switchState(new MainState());
+ }
__firstTime = false;
+
+
}
/**
diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx
index 2e61eec20..f2a6cef70 100644
--- a/source/funkin/backend/system/Flags.hx
+++ b/source/funkin/backend/system/Flags.hx
@@ -28,6 +28,7 @@ class Flags {
public static var MOD_NAME:String = "";
public static var MOD_DESCRIPTION:String = "";
public static var MOD_AUTHOR:String = "";
+ public static var MOD_VERSION:String = "";
@:lazy public static var MOD_API_VERSION:Null = null;
public static var MOD_DOWNLOAD_LINK:String = "";
public static var MOD_DEPENDENCIES:Array = [];
@@ -43,6 +44,11 @@ class Flags {
public static var MOD_REDIRECT_STATES:Map = [];
+ @:also(funkin.backend.utils.GameJoltSecurity.gameId)
+ public static var MOD_GAMEJOLT_GAME_ID:String = '';
+ @:also(funkin.backend.utils.GameJoltSecurity.encryptedGameToken)
+ public static var MOD_GAMEJOLT_TOKEN:String = '';
+
// -- Codename's Default Flags --
@:lazy public static var SAVE_PATH:String = haxe.macro.Compiler.getDefine("SAVE_PATH");
@:lazy public static var SAVE_NAME:String = haxe.macro.Compiler.getDefine("SAVE_NAME");
diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx
index 14988dddc..205cebcaa 100644
--- a/source/funkin/backend/system/MainState.hx
+++ b/source/funkin/backend/system/MainState.hx
@@ -10,6 +10,7 @@ import funkin.backend.assets.ModsFolderLibrary;
import funkin.backend.assets.ZipFolderLibrary;
import funkin.backend.chart.EventsData;
import funkin.backend.system.framerate.Framerate;
+import funkin.backend.utils.GJUtil;
import funkin.editors.ModConfigWarning;
import funkin.menus.TitleState;
import haxe.io.Path;
@@ -162,6 +163,13 @@ class MainState extends FlxState {
if (cast(lib, ZipFolderLibrary).PRELOAD_VIDEOS) cast(lib, ZipFolderLibrary).precacheVideos();
}
+ #if GAMEJOLT_API
+ if (FlxG.save.data.gameJoltArray != null) {
+ var gjDat:Array = FlxG.save.data.gameJoltArray;
+ GJUtil.attemptLogin(gjDat[0], gjDat[1]);
+ }
+ #end
+
var startState:Class = Flags.DISABLE_WARNING_SCREEN ? TitleState : funkin.menus.WarningState;
// In this case if the mod we just loaded a compressed modpack, we can't edit or modify files without decompressing it.
diff --git a/source/funkin/backend/utils/GJUtil.hx b/source/funkin/backend/utils/GJUtil.hx
new file mode 100644
index 000000000..cc8ee81e3
--- /dev/null
+++ b/source/funkin/backend/utils/GJUtil.hx
@@ -0,0 +1,321 @@
+package funkin.backend.utils;
+
+/**
+ * This is how GameJolt API responses are formatted like.
+ */
+typedef Response = {
+ // General
+ success:Bool,
+ ?message:String,
+ // User Fetching
+ ?users:Array,
+ // Trophies Fetching
+ ?trophies:Array,
+ // Scores Fetching
+ ?scores:Array,
+ ?tables:Array,
+ ?rank:Int,
+ // Friends Fetching
+ ?friends:Array<{friend_id:Int}>,
+ // Data Store Fetching
+ ?keys:Array<{key:String}>,
+ ?data:String,
+ // Time Fetching
+ ?timestamp:Int,
+ ?timezone:String,
+ ?year:Int,
+ ?month:Int,
+ ?day:Int,
+ ?hour:Int,
+ ?minute:Int,
+ ?second:Int,
+ // Batch Reception
+ ?responses:Array
+}
+
+/**
+ * The way the scores are fetched from your game API.
+ *
+ * @param score The display text of the Score.
+ * @param sort The Score value.
+ * @param extra_data If some extra data is attached to this Score, it'll be shown here.
+ * @param user The username of the User who achieved this Score, if it's a registered User.
+ * @param user_id The user ID of the User who achieved this Score, if it's a registered User.
+ * @param guest The name of the user who achieved this Score, if it's a guest user.
+ * @param stored A short description about when the Score was achieved by the User or Guest.
+ * @param stored_timestamp A long time stamp (in seconds) of when the Score was achieved by the User or Guest.
+ */
+typedef Score = {
+ score:String,
+ sort:Int,
+ extra_data:String,
+ user:String,
+ user_id:Int,
+ guest:String,
+ stored:String,
+ stored_timestamp:Int
+}
+
+/**
+ * The way the score tables are fetched from your game API.
+ *
+ * @param id The ID of the Score Table.
+ * @param name The name of the Score Table.
+ * @param description The description of the Score Table.
+ * @param primary Whether if this is the Primary Score Table in your game (1) or not (0).
+ */
+typedef ScoreTable = {
+ id:Int,
+ name:String,
+ description:String,
+ primary:Bool
+}
+
+/**
+ * The way the trophies are fetched from your game API.
+ *
+ * @param id The ID of the Trophy.
+ * @param title The title of the Trophy.
+ * @param description The description of the Trophy.
+ * @param difficulty The difficulty rank of the Trophy.
+ * @param image_url The link of the image that represents the Trophy.
+ * @param achieved Whether this Trophy was achieved or not, it can be a string if it was (with info about how much time ago it was achieved) or bool if not (false).
+ */
+typedef Trophy = {
+ id:Int,
+ title:String,
+ description:String,
+ difficulty:String,
+ image_url:String,
+ achieved:String
+}
+
+/**
+ * The way the user data is fetched from the GameJolt API.
+ *
+ * @param id The ID of the User.
+ * @param type The cathegory the User is cataloged like in GameJolt.
+ * @param username The username of the User. (Also available for guests).
+ * @param avatar_url The link of the avatar of the User.
+ * @param signed_up A short description about how long the User have been in GameJolt.
+ * @param signed_up_timestamp A long time stamp (in seconds) of when the User signed up.
+ * @param last_logged_in A short description about the last time the User was found active in GameJolt.
+ * @param last_logged_in_timestamp A long time stamp (in seconds) of the last time the User logged in GameJolt.
+ * @param status The actual status of the User.
+ * @param developer_name The display name of the User. (Also available for guests).
+ * @param developer_website The website of the User.
+ * @param developer_description The description of the User.
+ */
+typedef User = {
+ id:Int,
+ type:String,
+ username:String,
+ avatar_url:String,
+ signed_up:String,
+ signed_up_timestamp:Int,
+ last_logged_in:String,
+ last_logged_in_timestamp:Int,
+ status:String,
+ developer_name:String,
+ developer_website:String,
+ developer_description:String
+}
+
+/**
+ * An enum class to clasify Data Store update functions.
+ */
+enum DataUpdateType {
+ Add(n:Int);
+ Substract(n:Int);
+ Multiply(n:Int);
+ Divide(n:Int);
+ Append(t:String);
+ Prepend(t:String);
+}
+
+/**
+ * An enum of every single command currently available to request to GameJolt API.
+ */
+enum RequestType {
+ BATCH(parallel:Bool, breakOnError:Bool, requests:Array);
+ DATA_FETCH(key:String, fromUser:Bool);
+ DATA_GETKEYS(fromUser:Bool, ?pattern:String);
+ DATA_REMOVE(key:String, fromUser:Bool);
+ DATA_SET(key:String, data:String, toUser:Bool);
+ DATA_UPDATE(key:String, operation:DataUpdateType, toUser:Bool);
+ FRIENDS;
+ TIME;
+ USER_AUTH;
+ USER_FETCH(userOrID:String);
+ SESSION_OPEN;
+ SESSION_PING(active:Bool);
+ SESSION_CHECK;
+ SESSION_CLOSE;
+ SCORES_ADD(score:String, sort:Int, ?extra_data:String, ?table_id:Int);
+ SCORES_GETRANK(sort:Int, ?table_id:Int);
+ SCORES_FETCH(fromUser:Bool, ?table_id:Int, ?limit:Int, ?betterThan:Int);
+ SCORES_TABLES;
+ TROPHIES_FETCH(?achieved:Bool, ?trophy_id:Int);
+ TROPHIES_ADD(trophy_id:Int);
+ TROPHIES_REMOVE(trophy_id:Int);
+}
+
+/**
+ * GameJolt utility to help with GameJolt functionality. Use this class to determine if your player is logged into GameJolt.
+ * Will not do anything if there is no provided GameJolt token.
+ *
+ * # IMPORTANT
+ * If you wish to use this utility, please run your GameJolt game's security code through the Codename Engine
+ * encryption tool on Codename's website.
+ * Place the output of that into your modpack.ini under the flag `MOD_GAMEJOLT_TOKEN`.
+ *
+ * ## DO NOT PLACE YOUR SECURITY KEY RIGHT INTO THE MODPACK.INI!!!! THAT IS A SECURITY ISSUE!!!!
+ */
+class GJUtil
+{
+ /**
+ * Boolean to determine if our player logged in.
+ */
+ public static var loggedIn:Bool = false;
+
+ /**
+ * The username of the logged in user.
+ */
+ public static var userName(default, set):String;
+
+ /**
+ * Whether or not the GameJolt utility is operational.
+ * This cannot be set other than load operations.
+ */
+ public static var active(default, null):Bool = false;
+
+ /**
+ * Whether or not the utility is executing a call.
+ */
+ static var executing:Bool = false;
+
+ /**
+ * Helper function in case the session is lost in the middle of the game.
+ */
+ public static var onLostSession:NullVoid> = null;
+
+ /**
+ * Helper function to simplify the login process.
+ * @param name Username of user attempting to login.
+ * @param token User token of user attempting to login.
+ * @return Bool Whether the attempt was successfull or not.
+ */
+ public static function attemptLogin(name:String, token:String):Bool
+ {
+ if(Flags.MOD_GAMEJOLT_GAME_ID != '' && Flags.MOD_GAMEJOLT_TOKEN != '')
+ active = true
+ else
+ return false;
+
+ var ret:Bool = false;
+ userName = name;
+ GameJoltSecurity.user_token = token;
+ send(RequestType.SESSION_OPEN, false, function(err) {
+ userName = null;
+ GameJoltSecurity.user_token = null;
+ }, function(resp) {
+ trace('GameJolt logged in as ${userName}');
+ ret = true;
+ openfl.Lib.application.onExit.add(onExitApp);
+ FlxG.signals.postUpdate.add(pingTimer);
+ FlxG.save.data.gameJoltArray = [userName, token];
+ FlxG.save.flush();
+ });
+ return ret;
+ }
+
+ static function onExitApp(i:Int)
+ {
+ logout();
+ }
+
+ static var pingTime:Int = 0;
+ public static function pingTimer()
+ {
+ pingTime += 1;
+ if (pingTime < 10000) return;
+ pingTime -= 10000;
+ pingSession();
+ }
+
+ public static function pingSession()
+ {
+ send(RequestType.SESSION_PING(true), true, (str) -> {
+ trace('GameJolt session lost.');
+ if (onLostSession != null) onLostSession();
+ shutdownFunctions();
+ active = false;
+ });
+ }
+
+ public static function logout()
+ {
+ if (!active)
+ return;
+
+ shutdownFunctions();
+ send(RequestType.SESSION_CLOSE, false, null, function(resp) {
+ trace('GameJolt account ${userName} logged out successfully.');
+ userName = null;
+ GameJoltSecurity.user_token = null;
+ });
+ }
+
+ static function shutdownFunctions()
+ {
+ FlxG.signals.postUpdate.remove(pingTimer);
+ openfl.Lib.application.onExit.remove(onExitApp);
+ onLostSession = null;
+ }
+
+ public static function send(call:RequestType, async:Bool = false, ?onError:String->Void, ?onComplete:Response->Void, ?onProgress:Array->Void)
+ {
+ if (executing || !active)
+ return;
+ executing = true;
+
+ @:privateAccess
+ var resp:Response = GameJoltSecurity.handleRequest(async, call, onProgress);
+ executing = false;
+ if (resp.message != null && onError != null)
+ onError(resp.message);
+ else if (resp.message == null && onComplete != null)
+ onComplete(formatImages(resp));
+ }
+
+ static function formatImages(res:Response):Response {
+ if (res.users != null)
+ for (u in res.users) u.avatar_url = '${u.avatar_url.substring(0, 32)}1000${u.avatar_url.substr(34)}'.replace(".jpg", ".png")
+ .replace(".webp", ".png");
+ if (res.trophies != null) for (t in res.trophies) {
+ var newUrl:String = "";
+ if (t.image_url.startsWith('https://m.'))
+ newUrl = '${t.image_url.substring(0, 37)}1000${t.image_url.substr(40)}'.replace(".jpg", ".png").replace(".webp", ".png");
+ else {
+ newUrl = "https://s.gjcdn.net/assets/";
+ newUrl += switch (t.image_url.substring(24).replace(".jpg", "").replace(".webp", "")) {
+ case "trophy-bronze-1": "9c2c91d0";
+ case "trophy-silver-1": "b46e352e";
+ case "trophy-gold-1": "363ce2dc";
+ case "trophy-platinum-1": "92e5330d";
+ default: "";
+ };
+ newUrl += ".png";
+ }
+ t.image_url = newUrl;
+ };
+ if (res.responses != null) for (res2 in res.responses) res2 = formatImages(res2);
+ return res;
+ }
+
+ static function set_userName(name:String):String
+ {
+ loggedIn = (name != null && name != '');
+ return userName = name;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/utils/GameJoltSecurityPublic.hx b/source/funkin/backend/utils/GameJoltSecurityPublic.hx
new file mode 100644
index 000000000..3a51d6f1f
--- /dev/null
+++ b/source/funkin/backend/utils/GameJoltSecurityPublic.hx
@@ -0,0 +1,341 @@
+package funkin.backend.utils;
+
+import hscript.IHScriptCustomBehaviour;
+import funkin.backend.utils.GJUtil;
+import funkin.backend.utils.GJUtil.RequestType;
+import haxe.crypto.Md5;
+import funkin.backend.utils.GJUtil.*;
+import haxe.Http;
+import haxe.Json;
+import openfl.events.*;
+
+/**
+ * # A BIG MOTHERFUCKING WARNING
+ *
+ * This class handles the raw game keys for GameJolt keys.
+ * If the raw game keys are made public, people can mess with leaderboards, data, achievements,
+ * or whatever else is on the game page.
+ *
+ * As such, Codename Engine requires players to encrypt keys, and the method of
+ * encryption is non-disclosable for security reasons, so the original file
+ * used in distributed CNE builds cannot be shared.
+ * Instead, we provide this public-facing file for hardcoding purposes.
+ *
+ * To use this file, make a copy and rename the copy's filename and class name to
+ * `GameJoltSecurity.hx`. To modify the encryption method, go to the function
+ * `set_encryptedGameToken` and modify the code in the first half of the null
+ * check (`if (tok != null) {}`).
+ *
+ * **WE HIGHLY ENCOURAGE YOU TO COME UP WITH AN ENCRYPTION METHOD FOR GAMEJOLT KEYS.**
+ * HaxeFoundation's crypto package is installed with Codename, you can see the methods
+ * you can use (as well as the documentation) [here](https://github.com/HaxeFoundation/crypto).
+ *
+ * # ***DO NOT LET YOUR PLAYERS PUT THE RAW KEYS IN ANY SOFTCODED FILES!!! WE ARE NOT***
+ * ***RESPONSIBLE IF YOU DON'T MAKE YOUR PLAYERS ENCRYPT THEIR KEYS AND THEIR STUFF***
+ * ***GETS HACKED!!!!***
+ *
+ * Also for security purposes, this class is unattainable via HScript.
+ *
+ * ~ SplatterDash
+ */
+@:noCustomClass
+@:dox(hide)
+abstract class GameJoltSecurityPublic implements IHScriptCustomBehaviour
+{
+ /**
+ * Token for the user if they're logged in.
+ */
+ public static var user_token:String = '';
+
+ /**
+ * ID number for the current mod.
+ */
+ public static var gameId:String = '';
+
+ /**
+ * The encrypted game token. Set using GAMEJOLT_ENCRYPTED_TOKEN in ini file.
+ */
+ public static var encryptedGameToken(default, set):String;
+
+ /**
+ * The unencrypted game token. It's insanely hard to get this variable.
+ */
+ @:noPrivateAccess static var revealedGameToken:String;
+
+ /**
+ * URL sent to GameJolt per request.
+ */
+ @:noPrivateAccess static var url(get, never):String;
+
+ /**
+ * The previous response created by the API client. Usually for just storage purposes.
+ */
+ static var lastResponse:Response = {success: false, message: "No response yet."};
+
+ /**
+ * The current call being processed.
+ */
+ static var curCall:Null = null;
+
+ // hscript - thanks LJ :D
+ public function hget(name:String):Dynamic
+ {
+ return null;
+ }
+
+ public function hset(name:String, val:Dynamic):Dynamic
+ {
+ return null;
+ }
+
+ static function get_url():String
+ {
+ return sign('https://api.gamejolt.com/api/game/v1_2${parseType(curCall)}');
+ }
+
+ static function handleRequest(async:Bool = false, data:RequestType, ?onProgress:Array->Void):Response
+ {
+ if (encryptedGameToken == null || gameId == null) {
+ lastResponse = {success: false, message: 'Missing game token and/or game ID.'};
+ curCall = null;
+ return lastResponse;
+ }
+
+ curCall = data;
+
+ if (async) {
+ var loader = new openfl.net.URLLoader();
+ loader.addEventListener(Event.COMPLETE, function(complete) {
+ lastResponse = Json.parse(cast(loader.data, String)).response;
+ if (lastResponse.message != null) {
+ trace('Response Error: ${lastResponse.message}');
+ }
+
+ });
+ loader.addEventListener(ProgressEvent.PROGRESS, progress -> { if (onProgress != null) onProgress([progress.bytesLoaded, progress.bytesTotal]);});
+ loader.addEventListener(IOErrorEvent.IO_ERROR, function(ioError) {
+ lastResponse = {success: false, message: 'IO Error: ${ioError.text}'};
+ });
+ loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, (securityError) -> {
+ lastResponse = {success: false, message: 'Security Error: ${securityError.text}'};
+ });
+ loader.load(new openfl.net.URLRequest(url));
+ return {success: false, message: "No response yet."};
+ } else {
+ var loader:Http = new Http(url);
+ loader.onData = function(data) {
+ lastResponse = Json.parse(data).response;
+ if (lastResponse.message != null)
+ trace('Response Error: ${lastResponse.message}');
+ };
+ loader.onError = function(error) {
+ lastResponse = {success: false, message: 'Request Error: ${error}'};
+ };
+ loader.request(false);
+ }
+ curCall = null;
+ return lastResponse;
+ }
+
+ static function parseType(request:RequestType, signed:Bool = false):String {
+ var command:String = "";
+ var action:String = "";
+ var params:Array<{name:String, value:String}> = [];
+
+ switch (request) {
+ case BATCH(parallel, breakOnError, requests):
+ command = "batch";
+ params.push({name: "parallel", value: '$parallel'});
+ params.push({name: "break_on_error", value: '$breakOnError'});
+ for (req in requests) params.push({name: "requests[]", value: parseType(req, true)});
+ case DATA_FETCH(key, fromUser):
+ command = "data-store";
+ params.push({name: "key", value: key.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_GETKEYS(fromUser, pattern):
+ command = "data-store";
+ action = "get-keys";
+ if (pattern != null && pattern != "")
+ params.push({name: "pattern", value: pattern.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_REMOVE(key, fromUser):
+ command = "data-store";
+ action = "remove";
+ params.push({name: "key", value: key.urlEncode()});
+ if (fromUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_SET(key, data, toUser):
+ command = "data-store";
+ action = "set";
+ params.push({name: "key", value: key.urlEncode()});
+ params.push({name: "data", value: data.urlEncode()});
+ if (toUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ case DATA_UPDATE(key, operation, toUser):
+ command = "data-store";
+ action = "update";
+ params.push({name: "key", value: key.urlEncode()});
+ if (toUser) {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+ switch (operation) {
+ case Add(n):
+ params.push({name: 'operation', value: 'add'});
+ params.push({name: 'value', value: '$n'});
+ case Substract(n):
+ params.push({name: 'operation', value: 'substract'});
+ params.push({name: 'value', value: '$n'});
+ case Multiply(n):
+ params.push({name: 'operation', value: 'multiply'});
+ params.push({name: 'value', value: '$n'});
+ case Divide(n):
+ params.push({name: 'operation', value: 'divide'});
+ params.push({name: 'value', value: '$n'});
+ case Append(t):
+ params.push({name: 'operation', value: 'append'});
+ params.push({name: 'value', value: t.urlEncode()});
+ case Prepend(t):
+ params.push({name: 'operation', value: 'prepend'});
+ params.push({name: 'value', value: t.urlEncode()});
+ }
+ case FRIENDS:
+ command = "friends";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TIME:
+ command = "time";
+ case USER_AUTH:
+ command = "users";
+ action = "auth";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case USER_FETCH(userOrID):
+ command = "users";
+ var letters:Array = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ_-".split("");
+ if (letters.filter(l -> userOrID.contains(l.toUpperCase()) || userOrID.contains(l.toLowerCase())).length > 0)
+ params.push({name: "username", value: userOrID});
+ else
+ params.push({name: "user_id", value: userOrID.replace(",", "%2C")});
+ case SESSION_OPEN:
+ command = "sessions";
+ action = "open";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_PING(active):
+ command = "sessions";
+ action = "ping";
+ params.push({name: "status", value: active ? "active" : "idle"});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_CHECK:
+ command = "sessions";
+ action = "check";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SESSION_CLOSE:
+ command = "sessions";
+ action = "close";
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case SCORES_ADD(score, sort, extra_data, table_id):
+ command = "scores";
+ action = "add";
+ params.push({name: "score", value: score});
+ params.push({name: "sort", value: '$sort'});
+ if (extra_data != null && extra_data != "")
+ params.push({name: "extra_data", value: extra_data.urlEncode()});
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ if (user_token != "") {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ } else
+ params.push({name: "guest", value: GJUtil.userName});
+ case SCORES_GETRANK(sort, table_id):
+ command = "scores";
+ action = "get-rank";
+ params.push({name: "sort", value: '$sort'});
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ case SCORES_FETCH(fromUser, table_id, limit, betterThan):
+ command = "scores";
+ if (table_id != null)
+ params.push({name: "table_id", value: '$table_id'});
+ if (limit != null)
+ params.push({name: "limit", value: '$limit'});
+ if (betterThan != null)
+ params.push({name: betterThan < 0 ? "worse_than" : "better_than", value: '${Math.abs(betterThan)}'});
+ if (fromUser) {
+ if (user_token != "") {
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ } else
+ params.push({name: "guest", value: GJUtil.userName});
+ }
+ case SCORES_TABLES:
+ command = "scores";
+ action = "tables";
+ case TROPHIES_FETCH(achieved, trophy_id):
+ command = "trophies";
+ if (achieved != null)
+ params.push({name: "achieved", value: '$achieved'});
+ if (trophy_id != null)
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TROPHIES_ADD(trophy_id):
+ command = "trophies";
+ action = "add-achieved";
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ case TROPHIES_REMOVE(trophy_id):
+ command = "trophies";
+ action = "remove-achieved";
+ params.push({name: "trophy_id", value: '$trophy_id'});
+ params.push({name: "username", value: GJUtil.userName});
+ params.push({name: "user_token", value: user_token});
+ }
+
+ var urlSection:String = '/$command${action != "" ? '/$action' : ""}?game_id=${gameId}${[for (p in params) '&${p.name}=${p.value}'].join("")}';
+ if (signed)
+ urlSection = sign(urlSection).urlEncode();
+ return urlSection;
+ }
+
+ /**
+ * Setter function for encrypted game token. Also sets revealed game token.
+ * @param tok
+ */
+ static function set_encryptedGameToken(tok:String)
+ {
+ if (tok != null) {
+ // Encryption method goes here.
+ revealedGameToken = tok;
+ } else {
+ revealedGameToken = null;
+ }
+ return encryptedGameToken = tok;
+ }
+
+ /**
+ * Signs a piece of URL with Md5.
+ * @param daUrl The old URL piece.
+ * @return The new URL piece.
+ */
+ static function sign(daUrl:String):String {
+ var urlToEncode:String = daUrl + revealedGameToken;
+ return '$daUrl&signature=${Md5.encode(urlToEncode)}';
+ }
+}
\ No newline at end of file
diff --git a/source/hscript/Config.hx b/source/hscript/Config.hx
index 3acd554b1..7c8e03f10 100644
--- a/source/hscript/Config.hx
+++ b/source/hscript/Config.hx
@@ -29,7 +29,7 @@ class Config {
// Incase any of your files fail
// These are the module names
public static final DISALLOW_CUSTOM_CLASSES = [
-
+ "funkin.backend.utils.GameJoltSecurity", // don't want people getting those gamejolt keys!
];
public static final DISALLOW_ABSTRACT_AND_ENUM = [