diff --git a/game/src/core/save_data.gd b/game/src/core/save_data.gd index 4107a99..03c8297 100644 --- a/game/src/core/save_data.gd +++ b/game/src/core/save_data.gd @@ -2,3 +2,4 @@ class_name SaveData extends Resource @export_file("*.tscn", "*.scn") var current_world: String +@export var com_hash: int = -1 # computer hash for checking, if this came from another source. diff --git a/game/src/core/world/world.gd b/game/src/core/world/world.gd index 85337ed..6d17fdb 100644 --- a/game/src/core/world/world.gd +++ b/game/src/core/world/world.gd @@ -103,6 +103,8 @@ func update_world_state() -> void: world_state.loaded_level_ids = get_loaded_level_ids() + world_state.com_hash = Game.get_computer_hash() + func get_level_ids_from_streamers(streamers: Array[LevelStreamer]) -> Array[StringName]: var streamer_ids: Array[StringName] = [] diff --git a/game/src/core/world_state.gd b/game/src/core/world_state.gd index 18fd5a8..eec2310 100644 --- a/game/src/core/world_state.gd +++ b/game/src/core/world_state.gd @@ -5,3 +5,4 @@ extends Resource @export_file("*.tscn", "*.scn") var world_path: String @export var loaded_level_ids: Array[StringName] = [] @export var instance_data: Dictionary[String, Variant] = {} +@export var com_hash: int = -1 # computer hash for checking, if this came from another source. diff --git a/game/src/game.gd b/game/src/game.gd index 54cfb56..c51e6d5 100644 --- a/game/src/game.gd +++ b/game/src/game.gd @@ -22,7 +22,7 @@ var main_menu: MainMenu var _os_dialog_window_result: int = -1 -@onready var world_holder: Node = %WorldHolder +@onready var scene_holder: Node = %SceneHolder @onready var loading_screen: LoadingScreen = %LoadingScreen @@ -34,6 +34,19 @@ static func debug_draw(draw_shape: String, args: Array) -> void: const DD3D: StringName = &"DebugDraw3D" if is_debug and Engine.has_singleton(DD3D): Engine.get_singleton(DD3D).callv("draw_" + draw_shape, args) + + +static func get_computer_hash() -> int: + var combined_str: String = str( + OS.get_processor_name(), + OS.get_processor_count(), + OS.get_model_name(), + OS.get_name(), + ) + + return combined_str.hash() + + ## Turns res://example.tscn into example.tres (and .scn -> .res) and uid://1234567 into 1234567.tres. static func convert_path_to_resource_filename(path: String) -> String: path = ResourceUID.path_to_uid(path) @@ -50,12 +63,37 @@ func _ready() -> void: register_commands() +func check_computer_hash(input_hash: int) -> bool: + if input_hash == Game.get_computer_hash(): + return true + + _os_dialog_window_result = -1 + + var _on_dialog_interacted: Callable = func(button_id: int) -> void: + _os_dialog_window_result = button_id + + DisplayServer.dialog_show( + "Save Hash Mismatch", + "The save you are trying to load seems to come from a different computer.\nWe are not responsible for any damages to your computer if the save is malicious.\n\nDo you want to load the save anyway?", + ["Abort", "Continue"], _on_dialog_interacted) + + get_tree().root.process_mode = Node.PROCESS_MODE_DISABLED + + while _os_dialog_window_result == -1: + await get_tree().process_frame + + get_tree().root.process_mode = Node.PROCESS_MODE_PAUSABLE + + return _os_dialog_window_result != 0 + + func load_game(_save_data: SaveData, _save_slot: int) -> void: - if not await check_computer_hash(save_data.com_hash): + if not await check_computer_hash(_save_data.com_hash): return save_data = _save_data.duplicate() save_slot = _save_slot + InputManager.capture_mouse() load_world(save_data.current_world, false) @@ -69,6 +107,8 @@ func save_game() -> Error: if is_instance_valid(world): save_data.current_world = ResourceUID.path_to_uid(world.scene_file_path) + save_data.com_hash = Game.get_computer_hash() + DirAccess.make_dir_recursive_absolute(SAVE_DIR % save_slot) return ResourceSaver.save(save_data, SAVE_PATH % [save_slot, file_name]) @@ -97,7 +137,7 @@ func load_world(world_path: String, save_previous: bool = true, load_from_save: world.change_world_requested.connect(load_world) world.world_unload_requested.connect(_on_unload_world_request) - world_holder.add_child.call_deferred(world) + scene_holder.add_child.call_deferred(world) await world.loaded loading_screen.fade_out() return OK @@ -144,7 +184,7 @@ func load_main_menu() -> void: _main_menu.load_game_request.connect(load_game) _main_menu.quit_request.connect(quit_game) main_menu = _main_menu - world_holder.add_child(main_menu) + scene_holder.add_child(main_menu) func load_startup() -> void: @@ -155,7 +195,7 @@ func load_startup() -> void: load_main_menu() loading_screen.fade_out() ) - add_child(startup) + scene_holder.add_child(startup) func unload_main_menu() -> void: @@ -170,12 +210,14 @@ func quit_game() -> void: func register_commands() -> void: var load_save_func: Callable = func(save_index: int) -> void: + print("Loading Save index ", save_index) var path: String = MainMenu.get_save_path(save_index) if ResourceLoader.exists(path): load_game(load(path), save_index) LimboConsole.register_command(load_save_func, "load_save", "Loads the save with the given save slot index.") LimboConsole.register_command(save_game, "save_game", "Saves the game based on the current save slot.") + #DebugConsole.register_command("load_save", load_save_func, "Loads the save with the given save slot index.") func _on_unload_world_request(do_save: bool) -> void: diff --git a/game/src/game.tscn b/game/src/game.tscn index b0294ad..990846a 100644 --- a/game/src/game.tscn +++ b/game/src/game.tscn @@ -7,7 +7,7 @@ script = ExtResource("1_7uq6d") metadata/_custom_type_script = "uid://cl1u038dbrou2" -[node name="WorldHolder" type="Node" parent="." unique_id=1835125942] +[node name="SceneHolder" type="Node" parent="." unique_id=1835125942] unique_name_in_owner = true [node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=919699501] diff --git a/game/src/ui/main_menu/main_menu.gd b/game/src/ui/main_menu/main_menu.gd index 03b13a6..9a24c6b 100644 --- a/game/src/ui/main_menu/main_menu.gd +++ b/game/src/ui/main_menu/main_menu.gd @@ -49,6 +49,16 @@ static func get_save_path(slot_index: int) -> String: return SAVE_DATA_PATH % slot_index +static func does_save_contents_contain_code(save_path: String) -> bool: + var file := FileAccess.open(save_path, FileAccess.READ) + var contents: String = file.get_as_text() + if contents.containsn("func") or contents.containsn("script/source") or contents.containsn("_init"): + OS.alert("The save you are trying to load contains malicious code.\nAborting...", "Save File Warning") + return true + + return false + + func _ready() -> void: continue_button.pressed.connect(_on_continue_pressed) load_button.pressed.connect(_on_load_pressed) @@ -98,6 +108,9 @@ func _on_continue_pressed() -> void: _on_new_game_pressed() return + if does_save_contents_contain_code(path): + return + config.set_value("save", "last_slot", slot) config.save(Game.USER_SETTINGS_PATH) @@ -112,6 +125,8 @@ func _on_load_pressed() -> void: func _on_new_game_pressed() -> void: var save_data: SaveData = Game.INITIAL_SAVE_DATA.duplicate() + save_data.com_hash = Game.get_computer_hash() + print("Save hash: ", save_data.com_hash, " Computer hash: ", Game.get_computer_hash()) var slot_index: int = get_unique_save_slot_index() load_game_request.emit(save_data, slot_index) @@ -139,6 +154,15 @@ func _on_save_entry_pressed(save_slot: int) -> void: _on_new_game_pressed() return + # TODO: Check for hash before loading the file via 'load' + if does_save_contents_contain_code(save_path): + return + #var file := FileAccess.open(save_path, FileAccess.READ) + #var contents: String = file.get_as_text() + #var index: int = contents.find("com_hash = ") + #var hash_str: String = contents.get_slice("\n", 1) + #print("Hash: ", hash_str, " Index: ", index, "\nContents: ", contents) + var save_data: SaveData = load(save_path) load_game_request.emit(save_data, save_slot)