From 854de6dde91df7d367ffda17aefeb0068a42ff8a Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 28 Jan 2026 13:08:22 -0600 Subject: [PATCH 1/7] Refactored JWT script. --- src/scenes/managers/app/network_manager.gd | 11 ++-- src/scripts/crypto/aes.gd | 1 + src/scripts/crypto/aes.gd.uid | 1 + src/scripts/crypto/credential_store.gd | 1 + src/scripts/crypto/credential_store.gd.uid | 1 + src/scripts/{ => crypto}/jwt.gd | 72 ++++++++++------------ src/scripts/crypto/jwt.gd.uid | 1 + src/scripts/crypto/rsa.gd | 37 +++++++++++ src/scripts/crypto/rsa.gd.uid | 1 + src/scripts/jwt.gd.uid | 1 - 10 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 src/scripts/crypto/aes.gd create mode 100644 src/scripts/crypto/aes.gd.uid create mode 100644 src/scripts/crypto/credential_store.gd create mode 100644 src/scripts/crypto/credential_store.gd.uid rename src/scripts/{ => crypto}/jwt.gd (60%) create mode 100644 src/scripts/crypto/jwt.gd.uid create mode 100644 src/scripts/crypto/rsa.gd create mode 100644 src/scripts/crypto/rsa.gd.uid delete mode 100644 src/scripts/jwt.gd.uid diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index 51d5af3..7fd4f77 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -1,7 +1,8 @@ extends Node var n_c = preload("res://scripts/network_compression.gd").new() -var jwt = preload("res://scripts/jwt.gd").new() +var jwt = preload("res://scripts/crypto/jwt.gd").new() +var rsa = preload("res://scripts/crypto/rsa.gd").new() var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") # TODO: Bandwidth toggles @@ -179,13 +180,15 @@ func _receive_player_info(player_info: String): # Preform validation of JWT token var player_info_dic = _sanity_check_player_info(player_info, multiplayer.get_remote_sender_id()) - var player_decoded_jwt = jwt.decode_jwt(player_info_dic.jwt) + var player_decoded_jwt = jwt.decode(player_info_dic.jwt) # TODO: util function to break down a url to the key parts. - var url_parts = parse_url(player_decoded_jwt.payload.issuer) + var url_parts = parse_url(player_decoded_jwt.data.payload.issuer) var host_pub_key = await AccountServers._request_server_pem(url_parts.host, url_parts.port) - var jwt_is_valid = jwt.verify(player_info_dic.jwt, host_pub_key) + var host_pub_key_cryptokey: CryptoKey = rsa.pem_to_cryptokey(host_pub_key) + + var jwt_is_valid = jwt.verify(player_info_dic.jwt, host_pub_key_cryptokey) if jwt_is_valid == false: # TODO: Refuse connection diff --git a/src/scripts/crypto/aes.gd b/src/scripts/crypto/aes.gd new file mode 100644 index 0000000..61679fd --- /dev/null +++ b/src/scripts/crypto/aes.gd @@ -0,0 +1 @@ +extends Node \ No newline at end of file diff --git a/src/scripts/crypto/aes.gd.uid b/src/scripts/crypto/aes.gd.uid new file mode 100644 index 0000000..71277cb --- /dev/null +++ b/src/scripts/crypto/aes.gd.uid @@ -0,0 +1 @@ +uid://cnr57wo33psve diff --git a/src/scripts/crypto/credential_store.gd b/src/scripts/crypto/credential_store.gd new file mode 100644 index 0000000..61679fd --- /dev/null +++ b/src/scripts/crypto/credential_store.gd @@ -0,0 +1 @@ +extends Node \ No newline at end of file diff --git a/src/scripts/crypto/credential_store.gd.uid b/src/scripts/crypto/credential_store.gd.uid new file mode 100644 index 0000000..9ab29a8 --- /dev/null +++ b/src/scripts/crypto/credential_store.gd.uid @@ -0,0 +1 @@ +uid://vwsis8q7qovh diff --git a/src/scripts/jwt.gd b/src/scripts/crypto/jwt.gd similarity index 60% rename from src/scripts/jwt.gd rename to src/scripts/crypto/jwt.gd index fd78598..3db391a 100644 --- a/src/scripts/jwt.gd +++ b/src/scripts/crypto/jwt.gd @@ -1,12 +1,32 @@ -# This provides basic JWT features extends Node -func verify(jwt_string: String = "", signature_pem: String = "") -> bool: - # TODO: Error checks +# NOTE: To keep things consistent, please keep the signature always in base64. Only convert it where it will be used. + +func decode(jwt_string: String = "") -> Dictionary: + var return_dict = {"ok": false, "data": {}} + + var jwt_parts = _get_jwt_parts(jwt_string) + + return_dict.data.head = JSON.parse_string(Marshalls.base64_to_utf8(_base64url_to_base64(jwt_parts.head))) + return_dict.data.payload = JSON.parse_string(Marshalls.base64_to_utf8(_base64url_to_base64(jwt_parts.payload))) + # The signature should not be converted from base64 + return_dict.data.signature = _base64url_to_base64(jwt_parts.signature) + return_dict.ok = true + return return_dict + +func verify(jwt_string: String, public_key: CryptoKey) -> bool: var crypto: Crypto = Crypto.new() - var public_key: CryptoKey = _signature_pem_to_cryptokey(signature_pem) var jwt_parts: Dictionary = _get_jwt_parts(jwt_string) + if jwt_parts.ok != true: + GlobalLogger.log_string("Failed to deconstruct jwt when verifying jwt.", 2) + GlobalLogger.log_string(str(jwt_string), 0) + return false + var formatted_payload: Dictionary = _format_jwt_payload(jwt_parts.head, jwt_parts.payload) + if formatted_payload.ok != true: + GlobalLogger.log_string("Failed to format the jwt payload when verifying jwt.", 2) + GlobalLogger.log_string(str(jwt_parts), 0) + return false return crypto.verify( HashingContext.HASH_SHA256, @@ -15,19 +35,20 @@ func verify(jwt_string: String = "", signature_pem: String = "") -> bool: public_key ) -func decode_jwt(jwt_string: String) -> Dictionary: +# Private functions +func _format_jwt_payload(head: String, payload: String) -> Dictionary: # TODO: Error checks - var return_dict = {"head": {}, "payload": {}} + var return_dict = {"ok": false, "payload_bytes": []} - var jwt_parts = _get_jwt_parts(jwt_string) + var formatted_payload = head + "." + payload + var payload_bytes = formatted_payload.to_utf8_buffer() - jwt_parts.head = _base64url_to_base64(jwt_parts.head) - jwt_parts.head = Marshalls.base64_to_utf8(jwt_parts.head) - return_dict.head = JSON.parse_string(jwt_parts.head) + var hasher: HashingContext = HashingContext.new() + hasher.start(HashingContext.HASH_SHA256) + hasher.update(payload_bytes) - jwt_parts.payload = _base64url_to_base64(jwt_parts.payload) - jwt_parts.payload = Marshalls.base64_to_utf8(jwt_parts.payload) - return_dict.payload = JSON.parse_string(jwt_parts.payload) + return_dict.payload_bytes = hasher.finish() + return_dict.ok = true return return_dict @@ -43,15 +64,6 @@ func _base64url_to_base64(base64url: String): return fixed -func _signature_pem_to_cryptokey(signature_pem: String = "") -> CryptoKey: - # TODO: Error checks - var public_key := CryptoKey.new() - if public_key.load_from_string(signature_pem, true) != OK: - GlobalLogger.log_string("Failed to load signature", 3) - return null - - return public_key - func _get_jwt_parts(jwt_string: String = "") -> Dictionary: # TODO: Error checks var return_dict = {"ok": false, "head": "", "payload": "", "signature": ""} @@ -68,19 +80,3 @@ func _get_jwt_parts(jwt_string: String = "") -> Dictionary: return_dict.ok = true return return_dict - -func _format_jwt_payload(head: String, payload: String) -> Dictionary: - # TODO: Error checks - var return_dict = {"ok": false, "payload_bytes": []} - - var formatted_payload = head + "." + payload - var payload_bytes = formatted_payload.to_utf8_buffer() - - var hasher: HashingContext = HashingContext.new() - hasher.start(HashingContext.HASH_SHA256) - hasher.update(payload_bytes) - - return_dict.payload_bytes = hasher.finish() - return_dict.ok = true - - return return_dict \ No newline at end of file diff --git a/src/scripts/crypto/jwt.gd.uid b/src/scripts/crypto/jwt.gd.uid new file mode 100644 index 0000000..7d89294 --- /dev/null +++ b/src/scripts/crypto/jwt.gd.uid @@ -0,0 +1 @@ +uid://buxmy0sbla4sq diff --git a/src/scripts/crypto/rsa.gd b/src/scripts/crypto/rsa.gd new file mode 100644 index 0000000..8bb2c9e --- /dev/null +++ b/src/scripts/crypto/rsa.gd @@ -0,0 +1,37 @@ +extends Node + +## Generates a RSA keypair at a specific bit length +## @returns Dictionary +func generate_keypair(level: int = 0) -> Dictionary: + # TODO: Error checks + var return_dictionary = {"public": "", "private": ""} + var _target_bits = 0 + + match level: + 0: + _target_bits = 2048 + _: + _target_bits = 4096 + + var crypto = Crypto.new() + var generated_keys = crypto.generate_rsa(_target_bits) + + return_dictionary.private = generated_keys.save_to_string(false) + return_dictionary.public = generated_keys.save_to_string(true) + return return_dictionary + +## Verify a signature with a provided public key +## @returns bool +func verify_signature(data: String = "", data_signature: String = "", signature_pem: String = "") -> bool: + return false + +## Turns a pem into a CryptoKey +## @returns CryptoKey +func pem_to_cryptokey(pem: String = "") -> CryptoKey: + # TODO: Error checks + var public_key := CryptoKey.new() + if public_key.load_from_string(pem, true) != OK: + GlobalLogger.log_string("Failed to load public key", 3) + return null + + return public_key \ No newline at end of file diff --git a/src/scripts/crypto/rsa.gd.uid b/src/scripts/crypto/rsa.gd.uid new file mode 100644 index 0000000..eb71e2c --- /dev/null +++ b/src/scripts/crypto/rsa.gd.uid @@ -0,0 +1 @@ +uid://dkgyyc7tlbvln diff --git a/src/scripts/jwt.gd.uid b/src/scripts/jwt.gd.uid deleted file mode 100644 index 5809512..0000000 --- a/src/scripts/jwt.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bt5gufaix02sa From fb281c2f9c58acc1c42ada14da375535e0a50ad7 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 28 Jan 2026 15:15:34 -0600 Subject: [PATCH 2/7] Refactored files.gd. Log to a file. Changed Global log function name. Print_stack on error. --- src/project.godot | 2 +- src/scenes/managers/app/network_manager.gd | 30 ++++----- .../managers/level/multiplayer_manager.gd | 2 +- src/scripts/Files.gd | 41 ------------ src/scripts/Files.gd.uid | 1 - src/scripts/Logger.gd | 29 +++++---- src/scripts/credential_store.gd | 2 +- src/scripts/crypto/jwt.gd | 10 +-- src/scripts/crypto/rsa.gd | 2 +- src/scripts/keys.gd | 4 +- src/scripts/network_compression.gd | 4 +- src/scripts/utils/files.gd | 65 +++++++++++++++++++ src/scripts/utils/files.gd.uid | 1 + src/userinterface/hud.gd | 8 +-- 14 files changed, 113 insertions(+), 88 deletions(-) delete mode 100644 src/scripts/Files.gd delete mode 100644 src/scripts/Files.gd.uid create mode 100644 src/scripts/utils/files.gd create mode 100644 src/scripts/utils/files.gd.uid diff --git a/src/project.godot b/src/project.godot index ed7d6f3..f2a4cda 100644 --- a/src/project.godot +++ b/src/project.godot @@ -26,7 +26,7 @@ boot_splash/minimum_display_time=1000 LaunchArguments="*res://scripts/LaunchArguments.gd" GlobalLogger="*res://scripts/Logger.gd" -FileManager="*res://scripts/Files.gd" +FileManager="*uid://d2s50p717g3n" Util="*res://scripts/Util.gd" CredentialStore="*res://scripts/credential_store.gd" AccountServers="*res://scripts/account_servers.gd" diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index 7fd4f77..97af12e 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -45,7 +45,7 @@ func _ready(): func start_server(port: int = config.port, max_clients: int = config.max_clients) -> void: if status.hosting: # This ideally should not trigger - GlobalLogger.log_string("Can not start server: Server is already running.", 2) + GlobalLogger.logs("Can not start server: Server is already running.", 2) status.hosting = false status.client = false return @@ -56,13 +56,13 @@ func start_server(port: int = config.port, max_clients: int = config.max_clients # FIXME: This client append is happening too early, this is a debug position info.clients.append({"display_name": "Me!", "multiplayer_id": 1}) if err != OK: - GlobalLogger.log_string("Failed to start server.", 3) + GlobalLogger.logs("Failed to start server.", 3) status.hosting = false status.client = false return multiplayer.multiplayer_peer = new_peer - GlobalLogger.log_string("Successfully started server.", 1) + GlobalLogger.logs("Successfully started server.", 1) while status.hosting == false: await get_tree().process_frame @@ -88,12 +88,12 @@ func update_server(): func join_server(ip: String = "", port: int = config.port) -> void: # Client connects to a server. if ip.is_empty(): - GlobalLogger.log_string("No IP to connect to.", 2) + GlobalLogger.logs("No IP to connect to.", 2) return if status.hosting: # This ideally should not trigger - GlobalLogger.log_string("Can not join server: We are currently hosting a server.", 2) + GlobalLogger.logs("Can not join server: We are currently hosting a server.", 2) return var new_peer = ENetMultiplayerPeer.new() @@ -102,7 +102,7 @@ func join_server(ip: String = "", port: int = config.port) -> void: status.hosting = false status.client = true - GlobalLogger.log_string("Connected to the server.", 1) + GlobalLogger.logs("Connected to the server.", 1) return func kick_player(player_id: int, reason: String = "No reason specified"): @@ -115,11 +115,11 @@ func ban_player(): func _on_connected(): # We are connected to the server. - GlobalLogger.log_string("Connected to the server as '%s'." % multiplayer.get_unique_id(), 1) + GlobalLogger.logs("Connected to the server as '%s'." % multiplayer.get_unique_id(), 1) return func _on_connection_failed(): - GlobalLogger.log_string("Connection to server failed.", 1) + GlobalLogger.logs("Connection to server failed.", 1) return func _on_peer_connected(client_id): @@ -131,7 +131,7 @@ func _on_peer_connected(client_id): # TODO: Preform validation to determine if the player is allowed to be here - GlobalLogger.log_string("'%s' connected to us. Sending our server info." % multiplayer.get_unique_id(), 1) + GlobalLogger.logs("'%s' connected to us. Sending our server info." % multiplayer.get_unique_id(), 1) _receive_server_info.rpc_id(client_id, info) return @@ -141,7 +141,7 @@ func _on_peer_disconnected(): func set_networking_config(options: Dictionary) -> void: if !options: - GlobalLogger.log_string("Tried to set networking config without options", 2) + GlobalLogger.logs("Tried to set networking config without options", 2) return # LAN connections @@ -158,7 +158,7 @@ func set_networking_config(options: Dictionary) -> void: @rpc("authority", "reliable") func _receive_server_info(server_info: Dictionary): - GlobalLogger.log_string("Received server information.") + GlobalLogger.logs("Received server information.") # TODO: Do not change scene until connection is finalized. if server_info.level: @@ -173,7 +173,7 @@ func _receive_player_info(player_info: String): # We are a client. We should not process any farther. return - GlobalLogger.log_string("Received '%s' player info." % multiplayer.get_remote_sender_id()) + GlobalLogger.logs("Received '%s' player info." % multiplayer.get_remote_sender_id()) # TODO: Preform validation to determine if the player is allowed to be here # TODO: Preform validation to determine if the player supplied cridentials are good, where they need to be. @@ -210,7 +210,7 @@ func _receive_player_info(player_info: String): send_server_session_info() func _send_player_info(player_info: String): - GlobalLogger.log_string("Starting server handshake: Sending information about ourself.") + GlobalLogger.logs("Starting server handshake: Sending information about ourself.") _receive_player_info.rpc_id(1, player_info) func send_server_session_info() -> void: @@ -218,7 +218,7 @@ func send_server_session_info() -> void: @rpc("authority", "reliable") func received_server_session_info(received_info: Dictionary) -> void: - GlobalLogger.log_string("Session information updated.") + GlobalLogger.logs("Session information updated.") info = received_info return @@ -257,4 +257,4 @@ func parse_url(url: String) -> Dictionary: result["port"] = int(matches.get_string(3)) if matches.get_string(3) != "" else (443 if result["scheme"] == "https" else 80) result["path"] = matches.get_string(4) if matches.get_string(4) != "" else "/" - return result \ No newline at end of file + return result diff --git a/src/scenes/managers/level/multiplayer_manager.gd b/src/scenes/managers/level/multiplayer_manager.gd index e27c701..0b6614f 100644 --- a/src/scenes/managers/level/multiplayer_manager.gd +++ b/src/scenes/managers/level/multiplayer_manager.gd @@ -10,7 +10,7 @@ func _ready(): @rpc("authority", "reliable") func spawn_player(id: int): var player_scene: PackedScene = load("res://scenes/players/player.tscn") - GlobalLogger.log_string("Spawning player %s" % id) + GlobalLogger.logs("Spawning player %s" % id) var new_player = player_scene.instantiate() new_player.name = str(id) new_player.position = Vector3(0, 0, 0) diff --git a/src/scripts/Files.gd b/src/scripts/Files.gd deleted file mode 100644 index 129ac22..0000000 --- a/src/scripts/Files.gd +++ /dev/null @@ -1,41 +0,0 @@ -# This library provides an interface for file handling -# This handles housekeeping related to files including file creation, deletion, and some modifications. - -extends Node - -# TODO: Every 7 days, zip all log files and compress them. Delete the original files. -# TODO: After zip file is a month old, delete it. - -const app_name: String = "Open Wound" - -func log_file_exists() -> bool: - var today_log_filename = get_today_log_file_name() - var docs_path = OS.get_user_data_dir() - var does_log_file_exist = FileAccess.file_exists("%s/logs/%s.%s" % [docs_path, app_name, today_log_filename]) - return does_log_file_exist - -func get_today_log_file_name() -> String: - var current_timestring = Time.get_datetime_string_from_system() - return sanitize_log_file_name(current_timestring) - -func sanitize_log_file_name(file_name: String) -> String: - return file_name.replace("-", "_").replace("T", "-").replace(":", "_") - -func parse_log_file_name(file_name: String) -> Dictionary: - var date = file_name.split(".")[1].split("-") - var year = date[0].split("_")[0] - var month = date[0].split("_")[1] - var day = date[0].split("_")[2] - var hour = date[1].split("_")[0] - var minute = date[1].split("_")[1] - var second = date[1].split("_")[2] - var time_dictionary = Time.get_datetime_dict_from_datetime_string("%s-%s-%sT%s:%s:%s" % [year, month, day, hour, minute, second], true) - return time_dictionary - -func create_log_file() -> String: - var today_log_filename = get_today_log_file_name() - var docs_path = OS.get_user_data_dir() - var path = "%s/logs/%s.%s" % [docs_path, app_name, today_log_filename] - var file = FileAccess.open(path, FileAccess.WRITE) - file.close() - return path diff --git a/src/scripts/Files.gd.uid b/src/scripts/Files.gd.uid deleted file mode 100644 index 6ed3a18..0000000 --- a/src/scripts/Files.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://be7s67g7qwhfe diff --git a/src/scripts/Logger.gd b/src/scripts/Logger.gd index 83c12c1..ae7d865 100644 --- a/src/scripts/Logger.gd +++ b/src/scripts/Logger.gd @@ -26,14 +26,13 @@ func _ready(): _initialize_log_file() set_console_logging(true) set_file_logging(true) - log_string("Logger initialized") + logs("Logger initialized") func _initialize_log_file(): - if not FileManager.log_file_exists(): - log_file_path = FileManager.create_log_file() - log_file = FileAccess.open(log_file_path, FileAccess.WRITE) - log_file_initialized = true - log_string("Opened log file at %s" % log_file_path, 0) + log_file_path = FileManager.create_log_file() + log_file = FileAccess.open(log_file_path, FileAccess.WRITE) + log_file_initialized = true + logs("Opened log file at %s" % log_file_path, 0) ## Logs a message to both file and console (if enabled). ## @param message: The message string to log. If omitted, defaults to an empty string. @@ -43,31 +42,33 @@ func _initialize_log_file(): ## 2 -> Warning ## 3 -> Error ## Defaults to 0 (Debug). -func log_string(message: String = "", level: int = 0): +func logs(message: String = "", level: int = 0): _log_to_file(message, level) if console_logging_enabled: print_rich("[[color=%s]%s[/color]] %s" % [log_level_colors[level], log_level_names[level], message]) + if level == 3: + print_stack() pass func _log_to_file(message: String = "", level: int = 0): - if file_logging_enabled: + if file_logging_enabled && log_file: var formatted_log = "[%s] %s" % [log_level_names[level], message] - # log_file.store_line(formatted_log) - # log_file.flush() + log_file.store_line(formatted_log) + log_file.flush() func set_console_logging(enabled: bool): if enabled: console_logging_enabled = true - log_string("Console logging enabled for this session.", 1) + logs("Console logging enabled for this session.", 1) else: console_logging_enabled = false - log_string("Console logging disabled for this session.", 1) + logs("Console logging disabled for this session.", 1) func set_file_logging(enabled: bool): if enabled: file_logging_enabled = true - log_string("File logging enabled for this session.", 1) + logs("File logging enabled for this session.", 1) else: file_logging_enabled = false - log_string("File logging disabled for this session.", 1) + logs("File logging disabled for this session.", 1) diff --git a/src/scripts/credential_store.gd b/src/scripts/credential_store.gd index 977fa46..5d3de41 100644 --- a/src/scripts/credential_store.gd +++ b/src/scripts/credential_store.gd @@ -13,4 +13,4 @@ var info = { func set_account_credential(credentials: PackedStringArray = []): info.token = credentials[0] info.expire_time = credentials[1] - GlobalLogger.log_string("Saved JWT to memory.") + GlobalLogger.logs("Saved JWT to memory.") diff --git a/src/scripts/crypto/jwt.gd b/src/scripts/crypto/jwt.gd index 3db391a..2fb531c 100644 --- a/src/scripts/crypto/jwt.gd +++ b/src/scripts/crypto/jwt.gd @@ -18,14 +18,14 @@ func verify(jwt_string: String, public_key: CryptoKey) -> bool: var crypto: Crypto = Crypto.new() var jwt_parts: Dictionary = _get_jwt_parts(jwt_string) if jwt_parts.ok != true: - GlobalLogger.log_string("Failed to deconstruct jwt when verifying jwt.", 2) - GlobalLogger.log_string(str(jwt_string), 0) + GlobalLogger.logs("Failed to deconstruct jwt when verifying jwt.", 2) + GlobalLogger.logs(str(jwt_string), 0) return false var formatted_payload: Dictionary = _format_jwt_payload(jwt_parts.head, jwt_parts.payload) if formatted_payload.ok != true: - GlobalLogger.log_string("Failed to format the jwt payload when verifying jwt.", 2) - GlobalLogger.log_string(str(jwt_parts), 0) + GlobalLogger.logs("Failed to format the jwt payload when verifying jwt.", 2) + GlobalLogger.logs(str(jwt_parts), 0) return false return crypto.verify( @@ -71,7 +71,7 @@ func _get_jwt_parts(jwt_string: String = "") -> Dictionary: var jwt_split = jwt_string.split(".") if len(jwt_split) != 3: - GlobalLogger.log_string("JWT token is not formatted correctly.", 2) + GlobalLogger.logs("JWT token is not formatted correctly.", 2) return return_dict return_dict.head = jwt_split[0] diff --git a/src/scripts/crypto/rsa.gd b/src/scripts/crypto/rsa.gd index 8bb2c9e..e7b53ca 100644 --- a/src/scripts/crypto/rsa.gd +++ b/src/scripts/crypto/rsa.gd @@ -31,7 +31,7 @@ func pem_to_cryptokey(pem: String = "") -> CryptoKey: # TODO: Error checks var public_key := CryptoKey.new() if public_key.load_from_string(pem, true) != OK: - GlobalLogger.log_string("Failed to load public key", 3) + GlobalLogger.logs("Failed to load public key", 3) return null return public_key \ No newline at end of file diff --git a/src/scripts/keys.gd b/src/scripts/keys.gd index d9d02b2..507bb66 100644 --- a/src/scripts/keys.gd +++ b/src/scripts/keys.gd @@ -16,13 +16,13 @@ func read_keys_from_disk(username: String) -> PackedStringArray: var keys_exist = FileAccess.file_exists(pubKeyPath) && FileAccess.file_exists(privKeyPath) if keys_exist: - GlobalLogger.log_string("Using saved account key.") + GlobalLogger.logs("Using saved account key.") pubKey = FileAccess.open(pubKeyPath, FileAccess.READ).get_as_text() privKey = FileAccess.open(privKeyPath, FileAccess.READ).get_as_text() return [pubKey, privKey] - GlobalLogger.log_string("No key available. Generating a new one!") + GlobalLogger.logs("No key available. Generating a new one!") _generate_keys() _write_keys_to_disk(username) diff --git a/src/scripts/network_compression.gd b/src/scripts/network_compression.gd index 0fcb805..8335692 100644 --- a/src/scripts/network_compression.gd +++ b/src/scripts/network_compression.gd @@ -18,7 +18,7 @@ func c_32_vec3(provided_data: Vector3) -> PackedByteArray: func d_32_vec3(provided_data: PackedByteArray) -> Vector3: # Validate array size if provided_data.size() < 12: - GlobalLogger.log_string("'%s' contained invalid PackedByteArray size. Can not decode value.", 2) + GlobalLogger.logs("'%s' contained invalid PackedByteArray size. Can not decode value.", 2) return Vector3() var x = _int_to_float(provided_data.decode_s32(0)) @@ -43,7 +43,7 @@ func c_16_vec3(provided_data: Vector3) -> PackedByteArray: func d_16_vec3(provided_data: PackedByteArray) -> Vector3: # Validate array size if provided_data.size() < 6: - GlobalLogger.log_string("'%s' contained invalid PackedByteArray size. Can not decode value.", 2) + GlobalLogger.logs("'%s' contained invalid PackedByteArray size. Can not decode value.", 2) return Vector3() var x = _int_to_float(provided_data.decode_s16(0)) diff --git a/src/scripts/utils/files.gd b/src/scripts/utils/files.gd new file mode 100644 index 0000000..be14e5e --- /dev/null +++ b/src/scripts/utils/files.gd @@ -0,0 +1,65 @@ +extends Node + +## Create a config file at a given relative directory. +## Example: /system/cool.json +## @returns void +func create_config_file(dir: String) -> void: + GlobalLogger.logs("Creating '%s'" % dir, 0) + _maybe_make_directory("user://config/") + # TODO: Sanataize param + # TODO: Error checks + var dir_access = DirAccess.open("user://config/") + dir_access.make_dir_recursive("user://config/%s" % dir) + return + +## Read a config file from a directory. +## Example: /system/cool.json +## @returns String +func read_config_file(dir: String) -> String: + # TODO: Error checks + GlobalLogger.logs("Reading '%s'" % dir, 0) + dir = "user://config/%s" % dir + var file_contents = FileAccess.open(dir, FileAccess.READ).get_as_text() + return file_contents + +## Create a config file at a given relative directory. +## Example: /system/cool.json +## @returns String +func create_log_file() -> String: + GlobalLogger.logs("Creating a log file for this session.", 0) + _maybe_make_directory("user://logs/") + # TODO: Sanataize param + # TODO: Error checks + # TODO: Try to create a different file if one already exists with that name. + var log_file_name = _get_today_log_file_name() + var log_file_path = "user://logs/%s.%s" % [ProjectSettings.get_setting("application/config/name"), log_file_name] + var file = FileAccess.open(log_file_path, FileAccess.WRITE) + file.close() + GlobalLogger.logs("Log file '%s' created." % log_file_path, 0) + return log_file_path + +func create_client_file(_dir: String) -> void: + # Create a file to store in-game user data. This is for in-game data storage! + # IMPORTANT: DO NOT STORE PRIVATE DATA IN THIS DIRECTORY AS IT IS INTENDED TO BE READ AND WRITTEN TO FREELY! + GlobalLogger.logs("Not implemented.", 3) + return + +func _maybe_make_directory(dir: String): + var dir_access = DirAccess.open("user://") + dir_access.make_dir_recursive(dir) + +func _parse_log_file_name(file_name: String) -> Dictionary: + var date = file_name.split(".")[1].split("-") + var year = date[0].split("_")[0] + var month = date[0].split("_")[1] + var day = date[0].split("_")[2] + var hour = date[1].split("_")[0] + var minute = date[1].split("_")[1] + var second = date[1].split("_")[2] + var time_dictionary = Time.get_datetime_dict_from_datetime_string("%s-%s-%sT%s:%s:%s" % [year, month, day, hour, minute, second], true) + return time_dictionary + +func _get_today_log_file_name() -> String: + var current_timestring = Time.get_datetime_string_from_system() + var file_name = current_timestring.replace("-", "_").replace("T", "-").replace(":", "_") + return file_name diff --git a/src/scripts/utils/files.gd.uid b/src/scripts/utils/files.gd.uid new file mode 100644 index 0000000..805a1b2 --- /dev/null +++ b/src/scripts/utils/files.gd.uid @@ -0,0 +1 @@ +uid://d2s50p717g3n diff --git a/src/userinterface/hud.gd b/src/userinterface/hud.gd index b9f5b0a..3ae3d92 100644 --- a/src/userinterface/hud.gd +++ b/src/userinterface/hud.gd @@ -44,7 +44,7 @@ func _on_nav_home_pressed(): func _show_dashboard_page(page_name: String = "Home"): if page_name not in ["Home", "Sessions", "Contacts", "Inventory", "Debug", "Exit"]: - GlobalLogger.log_string("Tried to switch to an invalid dashboard page: '%s'" % page_name) + GlobalLogger.logs("Tried to switch to an invalid dashboard page: '%s'" % page_name) return for page in get_node("MarginContainer/VBoxContainer/PrimaryDashboard/").get_children(): @@ -65,17 +65,17 @@ func _on_login_button_pressed(): var response = await http.req(HTTPClient.Method.METHOD_POST, "http://localhost", "/api/v1/device/auth", 40400, ["Accept: application/json", "Content-Type: application/json"], JSON.stringify(data)) if response["ok"] == false: - GlobalLogger.log_string("Response failed for unknown reason.", 1) + GlobalLogger.logs("Response failed for unknown reason.", 1) return if response["body"] == null: - GlobalLogger.log_string("No body provided for login request.", 3) + GlobalLogger.logs("No body provided for login request.", 3) return var res_body = JSON.parse_string(response["body"]) if "error" in res_body.keys(): - GlobalLogger.log_string("Login request returned an error. '%s'" % res_body["error"], 1) + GlobalLogger.logs("Login request returned an error. '%s'" % res_body["error"], 1) return var token = response["response_headers"]["Set-Cookie"].split("; ") From 12cec36a788b2b9fa8fefab01750005cc25156a4 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 28 Jan 2026 15:40:07 -0600 Subject: [PATCH 3/7] Refactored account_servers.gd --- src/project.godot | 2 +- src/scenes/managers/app/network_manager.gd | 8 +++++++ src/scripts/account_servers.gd | 23 ------------------ src/scripts/account_servers.gd.uid | 1 - src/scripts/utils/account_servers.gd | 27 ++++++++++++++++++++++ src/scripts/utils/account_servers.gd.uid | 1 + 6 files changed, 37 insertions(+), 25 deletions(-) delete mode 100644 src/scripts/account_servers.gd delete mode 100644 src/scripts/account_servers.gd.uid create mode 100644 src/scripts/utils/account_servers.gd create mode 100644 src/scripts/utils/account_servers.gd.uid diff --git a/src/project.godot b/src/project.godot index f2a4cda..88b1b9a 100644 --- a/src/project.godot +++ b/src/project.godot @@ -29,7 +29,7 @@ GlobalLogger="*res://scripts/Logger.gd" FileManager="*uid://d2s50p717g3n" Util="*res://scripts/Util.gd" CredentialStore="*res://scripts/credential_store.gd" -AccountServers="*res://scripts/account_servers.gd" +AccountServers="*uid://bpysjoq7n0ytu" [display] diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index 97af12e..56a483b 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -186,6 +186,14 @@ func _receive_player_info(player_info: String): var url_parts = parse_url(player_decoded_jwt.data.payload.issuer) var host_pub_key = await AccountServers._request_server_pem(url_parts.host, url_parts.port) + if host_pub_key.ok == false: + GlobalLogger.logs("Failed to get the host public key.\n%s" % host_pub_key, 3) + multiplayer.multiplayer_peer.disconnect_peer(multiplayer.get_remote_sender_id()) + return + + host_pub_key = host_pub_key.data + GlobalLogger.logs("Got the host public key.\n%s" % host_pub_key) + var host_pub_key_cryptokey: CryptoKey = rsa.pem_to_cryptokey(host_pub_key) var jwt_is_valid = jwt.verify(player_info_dic.jwt, host_pub_key_cryptokey) diff --git a/src/scripts/account_servers.gd b/src/scripts/account_servers.gd deleted file mode 100644 index c6c141d..0000000 --- a/src/scripts/account_servers.gd +++ /dev/null @@ -1,23 +0,0 @@ -extends Node -var http = preload("res://scripts/http.gd").new() -var database = {} -# TODO: Open metadata file, and keep it opened - -# TODO: Validate RSA PEM key -func get_pem(host: String, port: int) -> String: - # TODO: Check if we have the key saved - return "" - -func _request_server_pem(host: String, port: int = 443) -> String: - var key = await http.req(HTTPClient.METHOD_GET, host, "/public_key", port) - if key.ok == true: - return key.body - return "" - - -func _ready(): - _request_server_pem("http://localhost", 40400) - -func _open_or_create_database(): - var dir = DirAccess.open("user://") - dir.make_dir_recursive("user://account_servers/database.json") \ No newline at end of file diff --git a/src/scripts/account_servers.gd.uid b/src/scripts/account_servers.gd.uid deleted file mode 100644 index 3be5ba9..0000000 --- a/src/scripts/account_servers.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c6heul4elcmbk diff --git a/src/scripts/utils/account_servers.gd b/src/scripts/utils/account_servers.gd new file mode 100644 index 0000000..de655d4 --- /dev/null +++ b/src/scripts/utils/account_servers.gd @@ -0,0 +1,27 @@ +extends Node + +var _http = preload("res://scripts/http.gd").new() +var _database = {} + +# TODO: Save public account server keys to disk + +func get_public_key(host: String, port: int) -> String: + if host in _database: + return _database[host] + + var response: Dictionary = await _request_server_pem(host, port) + if response.ok == true: + _database[host] = response.data + return response.data + + return "" + +func _request_server_pem(host: String, port: int = 443) -> Dictionary: + GlobalLogger.logs("Requesting server '%s:%s'." % [host, port]) + var return_dict = {"ok": false, "data": ""} + var key = await _http.req(HTTPClient.METHOD_GET, host, "/public_key", port) + if key.ok == true: + return_dict.data = key.body + return_dict.ok = true + return return_dict + return return_dict \ No newline at end of file diff --git a/src/scripts/utils/account_servers.gd.uid b/src/scripts/utils/account_servers.gd.uid new file mode 100644 index 0000000..1fbfc67 --- /dev/null +++ b/src/scripts/utils/account_servers.gd.uid @@ -0,0 +1 @@ +uid://bpysjoq7n0ytu From 881538497f5a497c502e67e43432f3aca1a8c927 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 28 Jan 2026 16:28:47 -0600 Subject: [PATCH 4/7] Moved credential_store. --- src/project.godot | 2 +- src/scripts/{ => utils}/credential_store.gd | 4 +++- src/scripts/{ => utils}/credential_store.gd.uid | 0 3 files changed, 4 insertions(+), 2 deletions(-) rename src/scripts/{ => utils}/credential_store.gd (87%) rename src/scripts/{ => utils}/credential_store.gd.uid (100%) diff --git a/src/project.godot b/src/project.godot index 88b1b9a..e95eb53 100644 --- a/src/project.godot +++ b/src/project.godot @@ -28,8 +28,8 @@ LaunchArguments="*res://scripts/LaunchArguments.gd" GlobalLogger="*res://scripts/Logger.gd" FileManager="*uid://d2s50p717g3n" Util="*res://scripts/Util.gd" -CredentialStore="*res://scripts/credential_store.gd" AccountServers="*uid://bpysjoq7n0ytu" +CredentialStore="*uid://cs4c0ctis2flp" [display] diff --git a/src/scripts/credential_store.gd b/src/scripts/utils/credential_store.gd similarity index 87% rename from src/scripts/credential_store.gd rename to src/scripts/utils/credential_store.gd index 5d3de41..405bf3e 100644 --- a/src/scripts/credential_store.gd +++ b/src/scripts/utils/credential_store.gd @@ -2,7 +2,6 @@ # This script is more of a placeholder until a more secure method of storing these credentials becomes available. # Due to security issues revolving around how Godot handles scripts umong other things, any object can be made accessible by anything else. # As a result, there just isn't a safe spot to store this data. - extends Node var info = { @@ -14,3 +13,6 @@ func set_account_credential(credentials: PackedStringArray = []): info.token = credentials[0] info.expire_time = credentials[1] GlobalLogger.logs("Saved JWT to memory.") + +# TODO: Save account credentials to disk. +# TODO: Read account credentials from disk. \ No newline at end of file diff --git a/src/scripts/credential_store.gd.uid b/src/scripts/utils/credential_store.gd.uid similarity index 100% rename from src/scripts/credential_store.gd.uid rename to src/scripts/utils/credential_store.gd.uid From b05ce26c7ef2fdad31c17703e6b6ac30bca41e55 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Wed, 28 Jan 2026 16:49:20 -0600 Subject: [PATCH 5/7] Moved the other files. --- src/project.godot | 6 ++-- src/scenes/managers/app/network_manager.gd | 2 +- src/scenes/managers/app/scene_manager.gd | 2 +- .../managers/level/multiplayer_manager.gd | 2 +- src/scenes/players/player.gd | 2 +- src/scripts/LaunchArguments.gd.uid | 1 - src/scripts/Util.gd.uid | 1 - src/scripts/crypto/credential_store.gd | 19 +++++++++++- src/scripts/crypto/credential_store.gd.uid | 2 +- src/scripts/{ => crypto}/keys.gd | 0 src/scripts/{ => crypto}/keys.gd.uid | 0 src/scripts/{Logger.gd => logger.gd} | 0 src/scripts/{Logger.gd.uid => logger.gd.uid} | 0 .../{utils => network}/account_servers.gd | 2 +- .../{utils => network}/account_servers.gd.uid | 0 src/scripts/{ => network}/http.gd | 30 +++++++++---------- src/scripts/{ => network}/http.gd.uid | 0 .../{ => network}/network_compression.gd | 0 .../{ => network}/network_compression.gd.uid | 0 src/scripts/utils/credential_store.gd | 18 ----------- src/scripts/utils/credential_store.gd.uid | 1 - .../launch_arguments.gd} | 3 ++ src/scripts/utils/launch_arguments.gd.uid | 1 + src/scripts/{Util.gd => utils/random.gd} | 2 +- src/scripts/utils/random.gd.uid | 1 + src/userinterface/hud.gd | 6 ++-- 26 files changed, 50 insertions(+), 51 deletions(-) delete mode 100644 src/scripts/LaunchArguments.gd.uid delete mode 100644 src/scripts/Util.gd.uid rename src/scripts/{ => crypto}/keys.gd (100%) rename src/scripts/{ => crypto}/keys.gd.uid (100%) rename src/scripts/{Logger.gd => logger.gd} (100%) rename src/scripts/{Logger.gd.uid => logger.gd.uid} (100%) rename src/scripts/{utils => network}/account_servers.gd (92%) rename src/scripts/{utils => network}/account_servers.gd.uid (100%) rename src/scripts/{ => network}/http.gd (69%) rename src/scripts/{ => network}/http.gd.uid (100%) rename src/scripts/{ => network}/network_compression.gd (100%) rename src/scripts/{ => network}/network_compression.gd.uid (100%) delete mode 100644 src/scripts/utils/credential_store.gd delete mode 100644 src/scripts/utils/credential_store.gd.uid rename src/scripts/{LaunchArguments.gd => utils/launch_arguments.gd} (91%) create mode 100644 src/scripts/utils/launch_arguments.gd.uid rename src/scripts/{Util.gd => utils/random.gd} (94%) create mode 100644 src/scripts/utils/random.gd.uid diff --git a/src/project.godot b/src/project.godot index e95eb53..b3ab4a7 100644 --- a/src/project.godot +++ b/src/project.godot @@ -24,12 +24,12 @@ boot_splash/minimum_display_time=1000 [autoload] -LaunchArguments="*res://scripts/LaunchArguments.gd" -GlobalLogger="*res://scripts/Logger.gd" +LaunchArguments="*uid://c45jrfmrjtnyn" +GlobalLogger="*uid://dgmfafi41y1nk" FileManager="*uid://d2s50p717g3n" -Util="*res://scripts/Util.gd" AccountServers="*uid://bpysjoq7n0ytu" CredentialStore="*uid://cs4c0ctis2flp" +Random="*uid://1js68qt8w0mv" [display] diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index 56a483b..f0536f7 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -1,6 +1,6 @@ extends Node -var n_c = preload("res://scripts/network_compression.gd").new() +var n_c = preload("res://scripts/network/network_compression.gd").new() var jwt = preload("res://scripts/crypto/jwt.gd").new() var rsa = preload("res://scripts/crypto/rsa.gd").new() var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") diff --git a/src/scenes/managers/app/scene_manager.gd b/src/scenes/managers/app/scene_manager.gd index 0609dee..3e4ba32 100644 --- a/src/scenes/managers/app/scene_manager.gd +++ b/src/scenes/managers/app/scene_manager.gd @@ -14,7 +14,7 @@ var server_init: bool = false func _ready(): await network_manager.start_server() - var session_name = Util.random_string(6) + var session_name = Random.random_string(6) var new_home = player_home_scene.instantiate() new_home.name = session_name multiplayer_manager.active_session = session_name diff --git a/src/scenes/managers/level/multiplayer_manager.gd b/src/scenes/managers/level/multiplayer_manager.gd index 0b6614f..e82564f 100644 --- a/src/scenes/managers/level/multiplayer_manager.gd +++ b/src/scenes/managers/level/multiplayer_manager.gd @@ -1,7 +1,7 @@ extends Node @onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -var n_c = preload("res://scripts/network_compression.gd").new() +var n_c = preload("res://scripts/network/network_compression.gd").new() var active_session: String = "" func _ready(): diff --git a/src/scenes/players/player.gd b/src/scenes/players/player.gd index 887edae..7ea335c 100644 --- a/src/scenes/players/player.gd +++ b/src/scenes/players/player.gd @@ -5,7 +5,7 @@ var speed = 5.0 @onready var multiplayer_manager = get_tree().current_scene.get_node("MultiplayerManager") @onready var hud = get_tree().current_scene.get_node("Hud") -var n_c = preload("res://scripts/network_compression.gd").new() +var n_c = preload("res://scripts/network/network_compression.gd").new() @onready var body = $"." @onready var head = $Head diff --git a/src/scripts/LaunchArguments.gd.uid b/src/scripts/LaunchArguments.gd.uid deleted file mode 100644 index 29d7da6..0000000 --- a/src/scripts/LaunchArguments.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://8oa8u1aicxac diff --git a/src/scripts/Util.gd.uid b/src/scripts/Util.gd.uid deleted file mode 100644 index d2816f6..0000000 --- a/src/scripts/Util.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://blph0tkw30w0i diff --git a/src/scripts/crypto/credential_store.gd b/src/scripts/crypto/credential_store.gd index 61679fd..405bf3e 100644 --- a/src/scripts/crypto/credential_store.gd +++ b/src/scripts/crypto/credential_store.gd @@ -1 +1,18 @@ -extends Node \ No newline at end of file +# FIXME: IMPORTANT! +# This script is more of a placeholder until a more secure method of storing these credentials becomes available. +# Due to security issues revolving around how Godot handles scripts umong other things, any object can be made accessible by anything else. +# As a result, there just isn't a safe spot to store this data. +extends Node + +var info = { + "token": "", + "expire_time": "" +} + +func set_account_credential(credentials: PackedStringArray = []): + info.token = credentials[0] + info.expire_time = credentials[1] + GlobalLogger.logs("Saved JWT to memory.") + +# TODO: Save account credentials to disk. +# TODO: Read account credentials from disk. \ No newline at end of file diff --git a/src/scripts/crypto/credential_store.gd.uid b/src/scripts/crypto/credential_store.gd.uid index 9ab29a8..d1ea891 100644 --- a/src/scripts/crypto/credential_store.gd.uid +++ b/src/scripts/crypto/credential_store.gd.uid @@ -1 +1 @@ -uid://vwsis8q7qovh +uid://cs4c0ctis2flp diff --git a/src/scripts/keys.gd b/src/scripts/crypto/keys.gd similarity index 100% rename from src/scripts/keys.gd rename to src/scripts/crypto/keys.gd diff --git a/src/scripts/keys.gd.uid b/src/scripts/crypto/keys.gd.uid similarity index 100% rename from src/scripts/keys.gd.uid rename to src/scripts/crypto/keys.gd.uid diff --git a/src/scripts/Logger.gd b/src/scripts/logger.gd similarity index 100% rename from src/scripts/Logger.gd rename to src/scripts/logger.gd diff --git a/src/scripts/Logger.gd.uid b/src/scripts/logger.gd.uid similarity index 100% rename from src/scripts/Logger.gd.uid rename to src/scripts/logger.gd.uid diff --git a/src/scripts/utils/account_servers.gd b/src/scripts/network/account_servers.gd similarity index 92% rename from src/scripts/utils/account_servers.gd rename to src/scripts/network/account_servers.gd index de655d4..a0126e7 100644 --- a/src/scripts/utils/account_servers.gd +++ b/src/scripts/network/account_servers.gd @@ -1,6 +1,6 @@ extends Node -var _http = preload("res://scripts/http.gd").new() +var _http = preload("res://scripts/network/http.gd").new() var _database = {} # TODO: Save public account server keys to disk diff --git a/src/scripts/utils/account_servers.gd.uid b/src/scripts/network/account_servers.gd.uid similarity index 100% rename from src/scripts/utils/account_servers.gd.uid rename to src/scripts/network/account_servers.gd.uid diff --git a/src/scripts/http.gd b/src/scripts/network/http.gd similarity index 69% rename from src/scripts/http.gd rename to src/scripts/network/http.gd index f36aa2a..c804400 100644 --- a/src/scripts/http.gd +++ b/src/scripts/network/http.gd @@ -5,8 +5,8 @@ signal _completed(result: Dictionary) # TODO: When the http client fails to connect to server, no error appears. func req(method: HTTPClient.Method, host: String, path: String = "/", port: int = 443, headers: PackedStringArray = [], body: String = "") -> Dictionary: - var thread := Thread.new() - var params := { + var thread: Thread = Thread.new() + var params: Dictionary = { "method": method, "host": host, "path": path, @@ -20,17 +20,15 @@ func req(method: HTTPClient.Method, host: String, path: String = "/", port: int return await _completed func _thread_main(params: Dictionary) -> void: - var client := HTTPClient.new() - var result := { - "ok": false - } + var client: HTTPClient = HTTPClient.new() + var return_dict: Dictionary = {"ok": false, "body": ""} - var err := client.connect_to_host(params.host, params.port) + var err: int = client.connect_to_host(params.host, params.port) # Could not connect to host if err != OK: - result.error = "Connection failed" - _finish(params, result) + return_dict.error = "Connection failed" + _finish(params, return_dict) return # Wait for connection @@ -49,23 +47,23 @@ func _thread_main(params: Dictionary) -> void: client.poll() OS.delay_msec(10) - result.status_code = client.get_response_code() - result.response_headers = client.get_response_headers_as_dictionary() + return_dict.status_code = client.get_response_code() + return_dict.response_headers = client.get_response_headers_as_dictionary() - var response_body := "" + var response_body: String = "" while client.get_status() == HTTPClient.STATUS_BODY: client.poll() - var chunk := client.read_response_body_chunk() + var chunk: PackedByteArray = client.read_response_body_chunk() if chunk.size() > 0: response_body += chunk.get_string_from_utf8() OS.delay_msec(10) client.close() - result.ok = true - result.body = response_body + return_dict.ok = true + return_dict.body = response_body - _finish(params, result) + _finish(params, return_dict) func _finish(params: Dictionary, result: Dictionary) -> void: call_deferred("_emit_completed", params.thread, result) diff --git a/src/scripts/http.gd.uid b/src/scripts/network/http.gd.uid similarity index 100% rename from src/scripts/http.gd.uid rename to src/scripts/network/http.gd.uid diff --git a/src/scripts/network_compression.gd b/src/scripts/network/network_compression.gd similarity index 100% rename from src/scripts/network_compression.gd rename to src/scripts/network/network_compression.gd diff --git a/src/scripts/network_compression.gd.uid b/src/scripts/network/network_compression.gd.uid similarity index 100% rename from src/scripts/network_compression.gd.uid rename to src/scripts/network/network_compression.gd.uid diff --git a/src/scripts/utils/credential_store.gd b/src/scripts/utils/credential_store.gd deleted file mode 100644 index 405bf3e..0000000 --- a/src/scripts/utils/credential_store.gd +++ /dev/null @@ -1,18 +0,0 @@ -# FIXME: IMPORTANT! -# This script is more of a placeholder until a more secure method of storing these credentials becomes available. -# Due to security issues revolving around how Godot handles scripts umong other things, any object can be made accessible by anything else. -# As a result, there just isn't a safe spot to store this data. -extends Node - -var info = { - "token": "", - "expire_time": "" -} - -func set_account_credential(credentials: PackedStringArray = []): - info.token = credentials[0] - info.expire_time = credentials[1] - GlobalLogger.logs("Saved JWT to memory.") - -# TODO: Save account credentials to disk. -# TODO: Read account credentials from disk. \ No newline at end of file diff --git a/src/scripts/utils/credential_store.gd.uid b/src/scripts/utils/credential_store.gd.uid deleted file mode 100644 index d1ea891..0000000 --- a/src/scripts/utils/credential_store.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cs4c0ctis2flp diff --git a/src/scripts/LaunchArguments.gd b/src/scripts/utils/launch_arguments.gd similarity index 91% rename from src/scripts/LaunchArguments.gd rename to src/scripts/utils/launch_arguments.gd index 4771c4e..05235d3 100644 --- a/src/scripts/LaunchArguments.gd +++ b/src/scripts/utils/launch_arguments.gd @@ -12,3 +12,6 @@ func get_command_line_args() -> Dictionary: # with the value set to an empty string. arguments[argument.trim_prefix("--")] = "" return arguments + +func _ready(): + get_command_line_args() \ No newline at end of file diff --git a/src/scripts/utils/launch_arguments.gd.uid b/src/scripts/utils/launch_arguments.gd.uid new file mode 100644 index 0000000..4dda50b --- /dev/null +++ b/src/scripts/utils/launch_arguments.gd.uid @@ -0,0 +1 @@ +uid://c45jrfmrjtnyn diff --git a/src/scripts/Util.gd b/src/scripts/utils/random.gd similarity index 94% rename from src/scripts/Util.gd rename to src/scripts/utils/random.gd index 02868bb..8d2064c 100644 --- a/src/scripts/Util.gd +++ b/src/scripts/utils/random.gd @@ -7,4 +7,4 @@ func random_string(length: int = 6): var out = "" for i in length: out += chars[rng.randi_range(0, chars.length() - 1)] - return out \ No newline at end of file + return out diff --git a/src/scripts/utils/random.gd.uid b/src/scripts/utils/random.gd.uid new file mode 100644 index 0000000..72994f6 --- /dev/null +++ b/src/scripts/utils/random.gd.uid @@ -0,0 +1 @@ +uid://1js68qt8w0mv diff --git a/src/userinterface/hud.gd b/src/userinterface/hud.gd index 3ae3d92..8c6d425 100644 --- a/src/userinterface/hud.gd +++ b/src/userinterface/hud.gd @@ -1,8 +1,8 @@ extends Control @onready var network_manager = get_tree().current_scene.get_node("NetworkManager") -var http = preload("res://scripts/http.gd").new() -var keys = preload("res://scripts/keys.gd").new() +var http = preload("res://scripts/network/http.gd").new() +var keys = preload("res://scripts/crypto/keys.gd").new() func _ready(): while true: @@ -81,4 +81,4 @@ func _on_login_button_pressed(): var token = response["response_headers"]["Set-Cookie"].split("; ") token[0] = token[0].replace("token=", "") CredentialStore.set_account_credential(token) - _show_dashboard_page("Home") \ No newline at end of file + _show_dashboard_page("Home") From 1cb698c02df6e23a8f9600d6fb28c2145a5c2943 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Thu, 29 Jan 2026 14:50:09 -0600 Subject: [PATCH 6/7] RPC Manager. This contains a lot of dev functions. Another round of refactoring is required. --- src/project.godot | 1 + src/scenes/managers/app/network_manager.gd | 158 +++--------------- src/scenes/managers/app/rpc_manager.gd | 16 ++ src/scenes/managers/app/rpc_manager.gd.uid | 1 + src/scenes/managers/app/rpc_manager.tscn | 6 + src/scenes/managers/app/scene_manager.gd | 2 +- .../managers/level/multiplayer_manager.gd | 36 ++-- src/scenes/master.tscn | 4 +- src/scenes/players/player.gd | 7 +- src/scripts/crypto/rsa.gd | 16 +- src/scripts/logger.gd | 13 +- src/scripts/rpc/client.gd | 29 ++++ src/scripts/rpc/client.gd.uid | 1 + src/scripts/rpc/common.gd | 38 +++++ src/scripts/rpc/common.gd.uid | 1 + src/scripts/rpc/server.gd | 90 ++++++++++ src/scripts/rpc/server.gd.uid | 1 + src/userinterface/hud.gd | 4 +- 18 files changed, 256 insertions(+), 168 deletions(-) create mode 100644 src/scenes/managers/app/rpc_manager.gd create mode 100644 src/scenes/managers/app/rpc_manager.gd.uid create mode 100644 src/scenes/managers/app/rpc_manager.tscn create mode 100644 src/scripts/rpc/client.gd create mode 100644 src/scripts/rpc/client.gd.uid create mode 100644 src/scripts/rpc/common.gd create mode 100644 src/scripts/rpc/common.gd.uid create mode 100644 src/scripts/rpc/server.gd create mode 100644 src/scripts/rpc/server.gd.uid diff --git a/src/project.godot b/src/project.godot index b3ab4a7..64ca709 100644 --- a/src/project.godot +++ b/src/project.godot @@ -30,6 +30,7 @@ FileManager="*uid://d2s50p717g3n" AccountServers="*uid://bpysjoq7n0ytu" CredentialStore="*uid://cs4c0ctis2flp" Random="*uid://1js68qt8w0mv" +MultiplayerManager="*uid://qp3jkmif6h85" [display] diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index f0536f7..c83a0b9 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -7,7 +7,8 @@ var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$" # TODO: Bandwidth toggles @onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -@onready var multiplayer_manager = get_tree().current_scene.get_node("MultiplayerManager") +@onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") +@onready var multiplayer_manager = MultiplayerManager # This file contains all of the session management and client communication. # Anything that goes through the network should first route through here at some point. @@ -35,14 +36,8 @@ var info = { "clients": [] } -func _ready(): - multiplayer.peer_connected.connect(_on_peer_connected) - multiplayer.peer_disconnected.connect(_on_peer_disconnected) - - multiplayer.connected_to_server.connect(_on_connected) - multiplayer.connection_failed.connect(_on_connection_failed) - -func start_server(port: int = config.port, max_clients: int = config.max_clients) -> void: +func start_server(port: int = config.port, max_clients: int = config.max_clients, ignore_port: bool = false) -> void: + # TODO: In ignore_port = true, keep trying to make a server until it succeeds. if status.hosting: # This ideally should not trigger GlobalLogger.logs("Can not start server: Server is already running.", 2) @@ -52,15 +47,25 @@ func start_server(port: int = config.port, max_clients: int = config.max_clients var new_peer = ENetMultiplayerPeer.new() # FIXME: Error handling is required here + info.clients.append({"username": "Me!", "multiplayer_id": 1}) var err = new_peer.create_server(port, max_clients) # FIXME: This client append is happening too early, this is a debug position - info.clients.append({"display_name": "Me!", "multiplayer_id": 1}) + + if err == 20: + # Port is in use + GlobalLogger.logs("Failed to start server: Is the port in use?", 1) + # FIXME: HACK: Just try again with the default port + 1. + err = new_peer.create_server(port + 1, max_clients) + status.hosting = false + status.client = false + if err != OK: - GlobalLogger.logs("Failed to start server.", 3) + GlobalLogger.logs("Failed to start server. Error: '%s'" % err, 1) status.hosting = false status.client = false return + multiplayer.multiplayer_peer = new_peer GlobalLogger.logs("Successfully started server.", 1) @@ -68,7 +73,10 @@ func start_server(port: int = config.port, max_clients: int = config.max_clients await get_tree().process_frame status.hosting = true status.client = false - + + # TODO: Hardcoded spawn host value, is there a better way? + rpc_lib.com.on_spawn_player(1) + func close_server(): # Disconnect all players. # Remove listings from all used networking. @@ -83,6 +91,7 @@ func close_server(): func update_server(): # Update our config. # Submit a update to any active networking service. + GlobalLogger.logs("Not implemented.", 3) return func join_server(ip: String = "", port: int = config.port) -> void: @@ -94,7 +103,8 @@ func join_server(ip: String = "", port: int = config.port) -> void: if status.hosting: # This ideally should not trigger GlobalLogger.logs("Can not join server: We are currently hosting a server.", 2) - return + close_server() + # return var new_peer = ENetMultiplayerPeer.new() new_peer.create_client(ip, port) @@ -107,36 +117,12 @@ func join_server(ip: String = "", port: int = config.port) -> void: func kick_player(player_id: int, reason: String = "No reason specified"): # Server kicks a player from the session. + GlobalLogger.logs("Not implemented.", 3) return func ban_player(): # Server permanatly bans a user. - return - -func _on_connected(): - # We are connected to the server. - GlobalLogger.logs("Connected to the server as '%s'." % multiplayer.get_unique_id(), 1) - return - -func _on_connection_failed(): - GlobalLogger.logs("Connection to server failed.", 1) - return - -func _on_peer_connected(client_id): - info.level_node_name = get_tree().current_scene.get_node("Scenes").get_child(0).name - - # A client has been connected to our server. - if multiplayer.is_server() == false: - return - - # TODO: Preform validation to determine if the player is allowed to be here - - GlobalLogger.logs("'%s' connected to us. Sending our server info." % multiplayer.get_unique_id(), 1) - _receive_server_info.rpc_id(client_id, info) - - return - -func _on_peer_disconnected(): + GlobalLogger.logs("Not implemented.", 3) return func set_networking_config(options: Dictionary) -> void: @@ -156,100 +142,6 @@ func set_networking_config(options: Dictionary) -> void: else: config.use_steam = false -@rpc("authority", "reliable") -func _receive_server_info(server_info: Dictionary): - GlobalLogger.logs("Received server information.") - # TODO: Do not change scene until connection is finalized. - - if server_info.level: - await scene_manager.load_multiplayer_scene(server_info.level, server_info.level_node_name) - - _send_player_info(CredentialStore.info.token) - -@rpc("any_peer", "reliable") -func _receive_player_info(player_info: String): - # TODO: Error checks for JWT - if multiplayer.is_server() == false: - # We are a client. We should not process any farther. - return - - GlobalLogger.logs("Received '%s' player info." % multiplayer.get_remote_sender_id()) - - # TODO: Preform validation to determine if the player is allowed to be here - # TODO: Preform validation to determine if the player supplied cridentials are good, where they need to be. - - # Preform validation of JWT token - var player_info_dic = _sanity_check_player_info(player_info, multiplayer.get_remote_sender_id()) - var player_decoded_jwt = jwt.decode(player_info_dic.jwt) - - # TODO: util function to break down a url to the key parts. - var url_parts = parse_url(player_decoded_jwt.data.payload.issuer) - var host_pub_key = await AccountServers._request_server_pem(url_parts.host, url_parts.port) - - if host_pub_key.ok == false: - GlobalLogger.logs("Failed to get the host public key.\n%s" % host_pub_key, 3) - multiplayer.multiplayer_peer.disconnect_peer(multiplayer.get_remote_sender_id()) - return - - host_pub_key = host_pub_key.data - GlobalLogger.logs("Got the host public key.\n%s" % host_pub_key) - - var host_pub_key_cryptokey: CryptoKey = rsa.pem_to_cryptokey(host_pub_key) - - var jwt_is_valid = jwt.verify(player_info_dic.jwt, host_pub_key_cryptokey) - - if jwt_is_valid == false: - # TODO: Refuse connection - multiplayer.multiplayer_peer.disconnect_peer(multiplayer.get_remote_sender_id()) - return - - info.clients.append(player_info_dic) - - # Spawn player - multiplayer_manager.spawn_player(player_info_dic.multiplayer_id) - multiplayer_manager.rpc("spawn_player", player_info_dic.multiplayer_id) - - # Spawn all connected clients on the new client - for client in info.clients: - if client.multiplayer_id == player_info_dic.multiplayer_id: - continue - multiplayer_manager.rpc_id(player_info_dic.multiplayer_id, "spawn_player", client.multiplayer_id) - - send_server_session_info() - -func _send_player_info(player_info: String): - GlobalLogger.logs("Starting server handshake: Sending information about ourself.") - _receive_player_info.rpc_id(1, player_info) - -func send_server_session_info() -> void: - rpc("received_server_session_info", info) - -@rpc("authority", "reliable") -func received_server_session_info(received_info: Dictionary) -> void: - GlobalLogger.logs("Session information updated.") - info = received_info - return - -# TODO: Handle kick from server -# TODO: Handle ban from server -# TODO: Add item to player inventory -# TODO: Remove item from player inventory -# TODO: Check if item exists in player inventory -# TODO: Get player inventory - -func _sanity_check_player_info(player_info: String, multiplayer_id: int) -> Dictionary: - var sane_player_info = { - "jwt": "", - "multiplayer_id": "", - "display_name": "Greetings!" - } - - sane_player_info.jwt = str(player_info) - sane_player_info.multiplayer_id = int(multiplayer_id) - - return sane_player_info - - func parse_url(url: String) -> Dictionary: var result = { "scheme": "", diff --git a/src/scenes/managers/app/rpc_manager.gd b/src/scenes/managers/app/rpc_manager.gd new file mode 100644 index 0000000..b8ee3ca --- /dev/null +++ b/src/scenes/managers/app/rpc_manager.gd @@ -0,0 +1,16 @@ +extends Node + +var c := preload("res://scripts/rpc/client.gd").new() +var s := preload("res://scripts/rpc/server.gd").new() +var com := preload("res://scripts/rpc/common.gd").new() + +func _ready(): + # RPCs can not be called from outside of the scene tree, we are required to add them. + add_child(c) + add_child(s) + add_child(com) + + multiplayer.peer_connected.connect(s.on_peer_connected) + multiplayer.peer_disconnected.connect(s.on_peer_disconnected) + multiplayer.connected_to_server.connect(c.connected_to_server) + multiplayer.connection_failed.connect(c.connection_failed) \ No newline at end of file diff --git a/src/scenes/managers/app/rpc_manager.gd.uid b/src/scenes/managers/app/rpc_manager.gd.uid new file mode 100644 index 0000000..a7c8542 --- /dev/null +++ b/src/scenes/managers/app/rpc_manager.gd.uid @@ -0,0 +1 @@ +uid://x23lqbiivx1q diff --git a/src/scenes/managers/app/rpc_manager.tscn b/src/scenes/managers/app/rpc_manager.tscn new file mode 100644 index 0000000..bcdecb5 --- /dev/null +++ b/src/scenes/managers/app/rpc_manager.tscn @@ -0,0 +1,6 @@ +[gd_scene format=3 uid="uid://by0vghgshvbhd"] + +[ext_resource type="Script" uid="uid://x23lqbiivx1q" path="res://scenes/managers/app/rpc_manager.gd" id="1_6timl"] + +[node name="RpcManager" type="Node" unique_id=1836544617] +script = ExtResource("1_6timl") diff --git a/src/scenes/managers/app/scene_manager.gd b/src/scenes/managers/app/scene_manager.gd index 3e4ba32..3ec5343 100644 --- a/src/scenes/managers/app/scene_manager.gd +++ b/src/scenes/managers/app/scene_manager.gd @@ -5,7 +5,7 @@ extends Node # Game managers @onready var network_manager = get_tree().current_scene.get_node("NetworkManager") -@onready var multiplayer_manager = get_tree().current_scene.get_node("MultiplayerManager") +@onready var multiplayer_manager = MultiplayerManager @onready var scene_work_root = get_tree().current_scene.get_node("Scenes") @onready var player_home_scene: PackedScene = load("res://scenes/levels/home.tscn") diff --git a/src/scenes/managers/level/multiplayer_manager.gd b/src/scenes/managers/level/multiplayer_manager.gd index e82564f..a260218 100644 --- a/src/scenes/managers/level/multiplayer_manager.gd +++ b/src/scenes/managers/level/multiplayer_manager.gd @@ -1,29 +1,17 @@ extends Node @onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -var n_c = preload("res://scripts/network/network_compression.gd").new() -var active_session: String = "" +@onready var active_session: String = "" -func _ready(): - return +func spawn_player(player): + # FIXME: Placeholder for refactor + while scene_manager.get_current_session_node() == null: + await get_tree().process_frame + + scene_manager.get_current_session_node().call_deferred("add_child", player) -@rpc("authority", "reliable") -func spawn_player(id: int): - var player_scene: PackedScene = load("res://scenes/players/player.tscn") - GlobalLogger.logs("Spawning player %s" % id) - var new_player = player_scene.instantiate() - new_player.name = str(id) - new_player.position = Vector3(0, 0, 0) - scene_manager.get_current_session_node().call_deferred("add_child", new_player) - -@rpc("any_peer", "reliable") -func player_position(info: PackedByteArray): - var target_node = scene_manager.get_current_session_node().get_node_or_null(str(multiplayer.get_remote_sender_id())) - - if target_node == null: - return - - # HACK: The rotation data is hacked on here. This needs to be addressed at some point. - target_node.position = n_c.d_16_pos(info) - target_node.rotation = n_c.d_16_vec3(info.slice(12)) - return +func player_exists(name: String) -> Node3D: + # FIXME: Placeholder for refactor + var target_node = scene_manager.get_current_session_node().get_node_or_null(name) + + return target_node diff --git a/src/scenes/master.tscn b/src/scenes/master.tscn index 721382a..a0fe73a 100644 --- a/src/scenes/master.tscn +++ b/src/scenes/master.tscn @@ -1,13 +1,13 @@ [gd_scene format=3 uid="uid://cxk6c0uipjjpo"] -[ext_resource type="PackedScene" uid="uid://g37f2dfffc8o" path="res://scenes/managers/level/multiplayer_manager.tscn" id="1_h2qy3"] +[ext_resource type="PackedScene" uid="uid://by0vghgshvbhd" path="res://scenes/managers/app/rpc_manager.tscn" id="1_h2qy3"] [ext_resource type="PackedScene" uid="uid://5v8rbnp716b0" path="res://scenes/managers/app/network_manager.tscn" id="1_jooxx"] [ext_resource type="PackedScene" uid="uid://cmknpdx5ba15o" path="res://scenes/managers/app/scene_manager.tscn" id="2_h2qy3"] [ext_resource type="PackedScene" uid="uid://bdsc5kvle3jgd" path="res://userinterface/hud.tscn" id="2_rnotf"] [node name="Master" type="Node3D" unique_id=420526444] -[node name="MultiplayerManager" parent="." unique_id=1925994240 instance=ExtResource("1_h2qy3")] +[node name="RpcManager" parent="." unique_id=1836544617 instance=ExtResource("1_h2qy3")] [node name="NetworkManager" parent="." unique_id=1960146969 instance=ExtResource("1_jooxx")] diff --git a/src/scenes/players/player.gd b/src/scenes/players/player.gd index 7ea335c..0b61580 100644 --- a/src/scenes/players/player.gd +++ b/src/scenes/players/player.gd @@ -1,8 +1,9 @@ extends CharacterBody3D +@onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") + var speed = 5.0 -@onready var multiplayer_manager = get_tree().current_scene.get_node("MultiplayerManager") @onready var hud = get_tree().current_scene.get_node("Hud") var n_c = preload("res://scripts/network/network_compression.gd").new() @@ -144,5 +145,5 @@ func _send_player_synchronization_info(): # HACK: We are just appending the rotation bits at the end here. It should probably be more efficient somewhere else. compressed_position.append_array(compressed_rotation) - - multiplayer_manager.rpc("player_position", compressed_position) \ No newline at end of file + + rpc_lib.com.rpc("on_player_transform", compressed_position) diff --git a/src/scripts/crypto/rsa.gd b/src/scripts/crypto/rsa.gd index e7b53ca..d578d5e 100644 --- a/src/scripts/crypto/rsa.gd +++ b/src/scripts/crypto/rsa.gd @@ -1,5 +1,7 @@ extends Node +var jwt = preload("res://scripts/crypto/jwt.gd").new() + ## Generates a RSA keypair at a specific bit length ## @returns Dictionary func generate_keypair(level: int = 0) -> Dictionary: @@ -22,8 +24,18 @@ func generate_keypair(level: int = 0) -> Dictionary: ## Verify a signature with a provided public key ## @returns bool -func verify_signature(data: String = "", data_signature: String = "", signature_pem: String = "") -> bool: - return false +func verify_jwt_signature(jwt_string: String = "", signature_pem: String = "") -> bool: + var crypto: Crypto = Crypto.new() + var sig: CryptoKey = pem_to_cryptokey(signature_pem) + var jwt_parts: Dictionary = jwt._get_jwt_parts(jwt_string) + var formatted_payload = jwt._format_jwt_payload(jwt_parts.head, jwt_parts.payload) + + return crypto.verify( + HashingContext.HASH_SHA256, + formatted_payload.payload_bytes, + Marshalls.base64_to_raw(jwt_parts.signature), + sig + ) ## Turns a pem into a CryptoKey ## @returns CryptoKey diff --git a/src/scripts/logger.gd b/src/scripts/logger.gd index ae7d865..b2ae399 100644 --- a/src/scripts/logger.gd +++ b/src/scripts/logger.gd @@ -48,7 +48,18 @@ func logs(message: String = "", level: int = 0): if console_logging_enabled: print_rich("[[color=%s]%s[/color]] %s" % [log_level_colors[level], log_level_names[level], message]) if level == 3: - print_stack() + # We are skipping the first frame, otherwise this function will be logged. + var stack := get_stack() + + for i in range(1, stack.size()): + var frame = stack[i] + print( + "%s:%d @ %s()" % [ + frame.source, + frame.line, + frame.function + ] + ) pass func _log_to_file(message: String = "", level: int = 0): diff --git a/src/scripts/rpc/client.gd b/src/scripts/rpc/client.gd new file mode 100644 index 0000000..15490b3 --- /dev/null +++ b/src/scripts/rpc/client.gd @@ -0,0 +1,29 @@ +extends Node + +# Join server +# Leave server +# Kicked from server +# Banned from server + +@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") +@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") + +func connected_to_server(): + return + +func connection_failed(): + return + +@rpc("authority", "reliable") +func on_receive_server_info(info): + GlobalLogger.logs("Got server info!") + if info.level: + await scene_manager.load_multiplayer_scene(info.level, info.level_node_name) + get_parent().s.rpc_id(1, "on_receive_player_info", CredentialStore.info.token) + return + +@rpc("authority", "reliable") +func received_server_session_info(received_info: Dictionary) -> void: + GlobalLogger.logs("Session information updated.") + network_manager.info = received_info + return \ No newline at end of file diff --git a/src/scripts/rpc/client.gd.uid b/src/scripts/rpc/client.gd.uid new file mode 100644 index 0000000..44f228a --- /dev/null +++ b/src/scripts/rpc/client.gd.uid @@ -0,0 +1 @@ +uid://dsbv3frxchi71 diff --git a/src/scripts/rpc/common.gd b/src/scripts/rpc/common.gd new file mode 100644 index 0000000..4cfe27a --- /dev/null +++ b/src/scripts/rpc/common.gd @@ -0,0 +1,38 @@ +extends Node + +var n_c = preload("res://scripts/network/network_compression.gd").new() + +@rpc("any_peer", "unreliable") +func on_player_transform(info): + # TODO: Authenticate + var target_node = MultiplayerManager.player_exists(str(multiplayer.get_remote_sender_id())) + + if target_node == null: + return + + # HACK: The rotation data is hacked on here. This needs to be addressed at some point. + target_node.position = n_c.d_16_pos(info) + target_node.rotation = n_c.d_16_vec3(info.slice(12)) + return + + +@rpc("any_peer", "unreliable") +func on_node_transform() -> void: + # Handles changing positions of a node. + # This should only be used when a node is moving, and not to position a node on spawn. + # TODO: Authenticate + return + +@rpc("authority", "reliable") +func on_spawn_player(id) -> void: + var player_scene: PackedScene = load("res://scenes/players/player.tscn") + GlobalLogger.logs("Spawning player %s" % id) + var new_player = player_scene.instantiate() + new_player.name = str(id) + new_player.position = Vector3(0, 0, 0) + MultiplayerManager.spawn_player(new_player) + return + +@rpc("authority", "reliable") +func on_spawn_node() -> void: + return diff --git a/src/scripts/rpc/common.gd.uid b/src/scripts/rpc/common.gd.uid new file mode 100644 index 0000000..896d644 --- /dev/null +++ b/src/scripts/rpc/common.gd.uid @@ -0,0 +1 @@ +uid://8cnojblfb8ly diff --git a/src/scripts/rpc/server.gd b/src/scripts/rpc/server.gd new file mode 100644 index 0000000..1b90f06 --- /dev/null +++ b/src/scripts/rpc/server.gd @@ -0,0 +1,90 @@ +extends Node + +var n_c = preload("res://scripts/network/network_compression.gd").new() +var jwt = preload("res://scripts/crypto/jwt.gd").new() +var rsa = preload("res://scripts/crypto/rsa.gd").new() +var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$") + +@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") + +# Create server +# Update server +# Close server + +# On player connecting +# On player connected +# On player leaving +# On player kicked +# On player banned + +func on_peer_connected(peer_id): + if multiplayer.is_server() == false: + return + + GlobalLogger.logs("[%s] Peer connected: '%s'. Sending server info." % [multiplayer.get_unique_id(), peer_id]) + get_parent().c.rpc_id(peer_id, "on_receive_server_info", network_manager.info) + +func on_peer_disconnected(): + return + +@rpc("any_peer", "reliable") +func on_receive_player_info(info) -> void: + if multiplayer.is_server() == false: + return + var sender_id = multiplayer.get_remote_sender_id() + GlobalLogger.logs("Got client info!") + var player_info = jwt.decode(info) + + if player_info.ok != true: + GlobalLogger.logs("Unknown error decoding player JWT.", 3) + + player_info = player_info.data + var url_parts = _parse_url(player_info.payload.issuer) + var host_pub_key = await AccountServers._request_server_pem(url_parts.host, url_parts.port) + + if host_pub_key.ok != true: + GlobalLogger.logs("Unknown error retrieving account server Public PEM.", 3) + + host_pub_key = host_pub_key.data + var jwt_is_valid = rsa.verify_jwt_signature(info, host_pub_key) + + if jwt_is_valid == false: + GlobalLogger.logs("JWT signature did not match.", 1) + multiplayer.multiplayer_peer.disconnect_peer(sender_id) + # TODO: Send a message before kicking the user. + return + + player_info.payload["multiplayer_id"] = sender_id + + network_manager.info.clients.append(player_info.payload) + + get_parent().com.on_spawn_player(sender_id) + get_parent().com.rpc("on_spawn_player", sender_id) + + for client in network_manager.info.clients: + if client.multiplayer_id == sender_id: + continue + get_parent().com.rpc_id(sender_id, "on_spawn_player", client.multiplayer_id) + + send_server_info() + +func send_server_info(): + get_parent().c.rpc("received_server_session_info", network_manager.info) + return + +func _parse_url(url: String) -> Dictionary: + var result = { + "scheme": "", + "host": "", + "port": 0, + "path": "" + } + + var matches = url_regex.search(url) + if matches: + result["scheme"] = matches.get_string(1).to_lower() + result["host"] = matches.get_string(2) + result["port"] = int(matches.get_string(3)) if matches.get_string(3) != "" else (443 if result["scheme"] == "https" else 80) + result["path"] = matches.get_string(4) if matches.get_string(4) != "" else "/" + + return result diff --git a/src/scripts/rpc/server.gd.uid b/src/scripts/rpc/server.gd.uid new file mode 100644 index 0000000..4976943 --- /dev/null +++ b/src/scripts/rpc/server.gd.uid @@ -0,0 +1 @@ +uid://c6wrk7xh520cr diff --git a/src/userinterface/hud.gd b/src/userinterface/hud.gd index 8c6d425..d159931 100644 --- a/src/userinterface/hud.gd +++ b/src/userinterface/hud.gd @@ -16,9 +16,9 @@ func set_active_state(state: bool = false): visible = state func _update_hud_state(): - var user_list_formatted = network_manager.info.clients.map(func(elem): return elem.display_name) + var user_list_formatted = network_manager.info.clients.map(func(elem): return elem.username) %HostingBool.text = "Host: %s" % network_manager.status.hosting - %SessionHost.text = "Server Host: %s" % network_manager.info.clients[0].display_name + %SessionHost.text = "Server Host: %s" % network_manager.info.clients[0].username %ClientBool.text = "Client: %s" % network_manager.status.client %ConnectedUserCount.text = "Total Users: %s" % len(network_manager.info.clients) %UserList.text = "User List: %s" % ", ".join(user_list_formatted) From ba0894c02407077f81f80b288cf81b6ea27beb9e Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 3 Feb 2026 11:58:01 -0600 Subject: [PATCH 7/7] Removed multiplayer_manager and merged into network_manager. --- src/project.godot | 1 - src/scenes/managers/app/network_manager.gd | 16 +++++++++++++++- src/scenes/managers/app/scene_manager.gd | 9 ++++----- .../managers/level/multiplayer_manager.gd | 17 ----------------- .../managers/level/multiplayer_manager.gd.uid | 1 - .../managers/level/multiplayer_manager.tscn | 6 ------ src/scripts/rpc/common.gd | 5 +++-- 7 files changed, 22 insertions(+), 33 deletions(-) delete mode 100644 src/scenes/managers/level/multiplayer_manager.gd delete mode 100644 src/scenes/managers/level/multiplayer_manager.gd.uid delete mode 100644 src/scenes/managers/level/multiplayer_manager.tscn diff --git a/src/project.godot b/src/project.godot index 64ca709..b3ab4a7 100644 --- a/src/project.godot +++ b/src/project.godot @@ -30,7 +30,6 @@ FileManager="*uid://d2s50p717g3n" AccountServers="*uid://bpysjoq7n0ytu" CredentialStore="*uid://cs4c0ctis2flp" Random="*uid://1js68qt8w0mv" -MultiplayerManager="*uid://qp3jkmif6h85" [display] diff --git a/src/scenes/managers/app/network_manager.gd b/src/scenes/managers/app/network_manager.gd index c83a0b9..1c2df24 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -8,7 +8,8 @@ var url_regex = RegEx.create_from_string("^(https?)://([^/:]+)(?::(\\d+))?(.*)$" # TODO: Bandwidth toggles @onready var scene_manager = get_tree().current_scene.get_node("SceneManager") @onready var rpc_lib = get_tree().current_scene.get_node("RpcManager") -@onready var multiplayer_manager = MultiplayerManager + +var active_session = "" # This file contains all of the session management and client communication. # Anything that goes through the network should first route through here at some point. @@ -158,3 +159,16 @@ func parse_url(url: String) -> Dictionary: result["path"] = matches.get_string(4) if matches.get_string(4) != "" else "/" return result + +func spawn_player(player): + # FIXME: Placeholder for refactor + while scene_manager.get_current_session_node() == null: + await get_tree().process_frame + + scene_manager.get_current_session_node().call_deferred("add_child", player) + +func player_exists(name: String) -> Node3D: + # FIXME: Placeholder for refactor + var target_node = scene_manager.get_current_session_node().get_node_or_null(name) + + return target_node diff --git a/src/scenes/managers/app/scene_manager.gd b/src/scenes/managers/app/scene_manager.gd index 3ec5343..6bfbab2 100644 --- a/src/scenes/managers/app/scene_manager.gd +++ b/src/scenes/managers/app/scene_manager.gd @@ -5,7 +5,6 @@ extends Node # Game managers @onready var network_manager = get_tree().current_scene.get_node("NetworkManager") -@onready var multiplayer_manager = MultiplayerManager @onready var scene_work_root = get_tree().current_scene.get_node("Scenes") @onready var player_home_scene: PackedScene = load("res://scenes/levels/home.tscn") @@ -17,7 +16,7 @@ func _ready(): var session_name = Random.random_string(6) var new_home = player_home_scene.instantiate() new_home.name = session_name - multiplayer_manager.active_session = session_name + network_manager.active_session = session_name scene_work_root.add_child(new_home) _spawn_host_player() @@ -29,12 +28,12 @@ func load_multiplayer_scene(scene_dir: String, scene_name: String): var scene = scene_packed.instantiate() scene.name = scene_name scene_work_root.add_child(scene) - multiplayer_manager.active_session = scene_name + network_manager.active_session = scene_name await get_tree().process_frame return func _clean_scene_work_root(): - multiplayer_manager.active_session = "" + network_manager.active_session = "" var nodes_to_destroy = scene_work_root.get_children() for node in nodes_to_destroy: node.queue_free() @@ -42,7 +41,7 @@ func _clean_scene_work_root(): return func _spawn_host_player(): - multiplayer_manager.spawn_player(1) + network_manager.spawn_player(1) func get_current_session_node(): return get_tree().current_scene.get_node("Scenes").get_child(0) diff --git a/src/scenes/managers/level/multiplayer_manager.gd b/src/scenes/managers/level/multiplayer_manager.gd deleted file mode 100644 index a260218..0000000 --- a/src/scenes/managers/level/multiplayer_manager.gd +++ /dev/null @@ -1,17 +0,0 @@ -extends Node - -@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -@onready var active_session: String = "" - -func spawn_player(player): - # FIXME: Placeholder for refactor - while scene_manager.get_current_session_node() == null: - await get_tree().process_frame - - scene_manager.get_current_session_node().call_deferred("add_child", player) - -func player_exists(name: String) -> Node3D: - # FIXME: Placeholder for refactor - var target_node = scene_manager.get_current_session_node().get_node_or_null(name) - - return target_node diff --git a/src/scenes/managers/level/multiplayer_manager.gd.uid b/src/scenes/managers/level/multiplayer_manager.gd.uid deleted file mode 100644 index 5aee08e..0000000 --- a/src/scenes/managers/level/multiplayer_manager.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://qp3jkmif6h85 diff --git a/src/scenes/managers/level/multiplayer_manager.tscn b/src/scenes/managers/level/multiplayer_manager.tscn deleted file mode 100644 index 394ab67..0000000 --- a/src/scenes/managers/level/multiplayer_manager.tscn +++ /dev/null @@ -1,6 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://g37f2dfffc8o"] - -[ext_resource type="Script" uid="uid://qp3jkmif6h85" path="res://scenes/managers/level/multiplayer_manager.gd" id="1_qo074"] - -[node name="MultiplayerManager" type="Node"] -script = ExtResource("1_qo074") diff --git a/src/scripts/rpc/common.gd b/src/scripts/rpc/common.gd index 4cfe27a..4eb90cc 100644 --- a/src/scripts/rpc/common.gd +++ b/src/scripts/rpc/common.gd @@ -1,11 +1,12 @@ extends Node var n_c = preload("res://scripts/network/network_compression.gd").new() +@onready var network_manager = get_tree().current_scene.get_node("NetworkManager") @rpc("any_peer", "unreliable") func on_player_transform(info): # TODO: Authenticate - var target_node = MultiplayerManager.player_exists(str(multiplayer.get_remote_sender_id())) + var target_node = network_manager.player_exists(str(multiplayer.get_remote_sender_id())) if target_node == null: return @@ -30,7 +31,7 @@ func on_spawn_player(id) -> void: var new_player = player_scene.instantiate() new_player.name = str(id) new_player.position = Vector3(0, 0, 0) - MultiplayerManager.spawn_player(new_player) + network_manager.spawn_player(new_player) return @rpc("authority", "reliable")