diff --git a/src/project.godot b/src/project.godot index ed7d6f3..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" -FileManager="*res://scripts/Files.gd" -Util="*res://scripts/Util.gd" -CredentialStore="*res://scripts/credential_store.gd" -AccountServers="*res://scripts/account_servers.gd" +LaunchArguments="*uid://c45jrfmrjtnyn" +GlobalLogger="*uid://dgmfafi41y1nk" +FileManager="*uid://d2s50p717g3n" +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 51d5af3..1c2df24 100644 --- a/src/scenes/managers/app/network_manager.gd +++ b/src/scenes/managers/app/network_manager.gd @@ -1,12 +1,15 @@ extends Node -var n_c = preload("res://scripts/network_compression.gd").new() -var jwt = preload("res://scripts/jwt.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+))?(.*)$") # 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") + +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. @@ -34,40 +37,47 @@ 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.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 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.log_string("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.log_string("Successfully started server.", 1) + GlobalLogger.logs("Successfully started server.", 1) while status.hosting == false: 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. @@ -82,18 +92,20 @@ 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: # 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) - return + GlobalLogger.logs("Can not join server: We are currently hosting a server.", 2) + close_server() + # return var new_peer = ENetMultiplayerPeer.new() new_peer.create_client(ip, port) @@ -101,46 +113,22 @@ 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"): # 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.log_string("Connected to the server as '%s'." % multiplayer.get_unique_id(), 1) - return - -func _on_connection_failed(): - GlobalLogger.log_string("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.log_string("'%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: 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 @@ -155,90 +143,6 @@ func set_networking_config(options: Dictionary) -> void: else: config.use_steam = false -@rpc("authority", "reliable") -func _receive_server_info(server_info: Dictionary): - GlobalLogger.log_string("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.log_string("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_jwt(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 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) - - 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.log_string("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.log_string("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": "", @@ -254,4 +158,17 @@ 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 + +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/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 0609dee..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 = get_tree().current_scene.get_node("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") @@ -14,10 +13,10 @@ 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 + 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 e27c701..0000000 --- a/src/scenes/managers/level/multiplayer_manager.gd +++ /dev/null @@ -1,29 +0,0 @@ -extends Node - -@onready var scene_manager = get_tree().current_scene.get_node("SceneManager") -var n_c = preload("res://scripts/network_compression.gd").new() -var active_session: String = "" - -func _ready(): - return - -@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) - 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 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/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 887edae..0b61580 100644 --- a/src/scenes/players/player.gd +++ b/src/scenes/players/player.gd @@ -1,11 +1,12 @@ 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_compression.gd").new() +var n_c = preload("res://scripts/network/network_compression.gd").new() @onready var body = $"." @onready var head = $Head @@ -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/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/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/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/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/credential_store.gd b/src/scripts/crypto/credential_store.gd similarity index 80% rename from src/scripts/credential_store.gd rename to src/scripts/crypto/credential_store.gd index 977fa46..405bf3e 100644 --- a/src/scripts/credential_store.gd +++ b/src/scripts/crypto/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 = { @@ -13,4 +12,7 @@ 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.") + +# 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/crypto/credential_store.gd.uid similarity index 100% rename from src/scripts/credential_store.gd.uid rename to src/scripts/crypto/credential_store.gd.uid diff --git a/src/scripts/jwt.gd b/src/scripts/crypto/jwt.gd similarity index 58% rename from src/scripts/jwt.gd rename to src/scripts/crypto/jwt.gd index fd78598..2fb531c 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.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.logs("Failed to format the jwt payload when verifying jwt.", 2) + GlobalLogger.logs(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": ""} @@ -59,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] @@ -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/keys.gd b/src/scripts/crypto/keys.gd similarity index 92% rename from src/scripts/keys.gd rename to src/scripts/crypto/keys.gd index d9d02b2..507bb66 100644 --- a/src/scripts/keys.gd +++ b/src/scripts/crypto/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/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/crypto/rsa.gd b/src/scripts/crypto/rsa.gd new file mode 100644 index 0000000..d578d5e --- /dev/null +++ b/src/scripts/crypto/rsa.gd @@ -0,0 +1,49 @@ +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: + # 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_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 +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.logs("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 diff --git a/src/scripts/Logger.gd b/src/scripts/logger.gd similarity index 64% rename from src/scripts/Logger.gd rename to src/scripts/logger.gd index 83c12c1..b2ae399 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,44 @@ 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: + # 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): - 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/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/network/account_servers.gd b/src/scripts/network/account_servers.gd new file mode 100644 index 0000000..a0126e7 --- /dev/null +++ b/src/scripts/network/account_servers.gd @@ -0,0 +1,27 @@ +extends Node + +var _http = preload("res://scripts/network/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/network/account_servers.gd.uid b/src/scripts/network/account_servers.gd.uid new file mode 100644 index 0000000..1fbfc67 --- /dev/null +++ b/src/scripts/network/account_servers.gd.uid @@ -0,0 +1 @@ +uid://bpysjoq7n0ytu 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 96% rename from src/scripts/network_compression.gd rename to src/scripts/network/network_compression.gd index 0fcb805..8335692 100644 --- a/src/scripts/network_compression.gd +++ b/src/scripts/network/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/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/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..4eb90cc --- /dev/null +++ b/src/scripts/rpc/common.gd @@ -0,0 +1,39 @@ +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 = network_manager.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) + network_manager.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/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/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 b9f5b0a..d159931 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: @@ -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) @@ -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,20 +65,20 @@ 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("; ") 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")