From ebd4ce95f3d6aa28df5dcdef70e575f032bd781e Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 29 Jan 2025 11:22:34 +0100 Subject: [PATCH 1/6] refactor: :construction: WIP: use GDRE --- addons/mod_loader/mod_loader_setup.gd | 143 ++++++++++++-------------- 1 file changed, 64 insertions(+), 79 deletions(-) diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index a7d1b64a..0e9cc5ed 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -97,7 +97,8 @@ func reorder_autoloads() -> void: original_autoloads[name] = value ModLoaderSetupLog.info( - "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t") + "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"), + LOG_NAME ) for autoload in original_autoloads.keys(): @@ -123,7 +124,8 @@ func reorder_autoloads() -> void: new_autoloads[name] = value ModLoaderSetupLog.info( - "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t") + "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"), + LOG_NAME ) @@ -145,6 +147,9 @@ func handle_override_cfg() -> void: # Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file. func handle_injection() -> void: + var is_embedded: bool = not FileAccess.file_exists(path.pck) + var injection_path: String = path.exe if is_embedded else path.pck + ModLoaderSetupLog.debug("Start injection", LOG_NAME) # Create temp dir ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME) @@ -167,83 +172,67 @@ func handle_injection() -> void: DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot")) # Save the global class cache config file combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) - get_pck_version() - # Check if .pck is embedded split it from the .exe - if not FileAccess.file_exists(path.pck): - split_pck() - inject() + inject(injection_path) # Rename vanilla - DirAccess.rename_absolute( - path.pck, - path.pck.trim_suffix("%s.pck" % file_name.pck).path_join("%s-vanilla.pck" % file_name.pck) - ) - ModLoaderSetupLog.debug( - ( - 'Renamed "%s" to "%s"' - % [ - path.pck, - path.pck.trim_suffix("%s.pck" % file_name.pck).path_join( - "%s-vanilla.pck" % file_name.pck - ) - ] - ), - LOG_NAME - ) - # Rename modded - DirAccess.rename_absolute( - path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - "%s.pck" % path.game_base_dir.path_join(file_name.pck) - ) - ModLoaderSetupLog.debug( - ( - 'Renamed "%s" to "%s"' - % [ - path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - "%s.pck" % path.game_base_dir.path_join(file_name.pck) - ] - ), - LOG_NAME - ) - - clean_up() - + if not is_embedded: + DirAccess.rename_absolute( + path.pck, + path.pck.trim_suffix("%s.pck" % file_name.pck).path_join( + "%s-vanilla.pck" % file_name.pck + ) + ) + ModLoaderSetupLog.debug( + ( + 'Renamed "%s" to "%s"' + % [ + path.pck, + path.pck.trim_suffix("%s.pck" % file_name.pck).path_join( + "%s-vanilla.pck" % file_name.pck + ) + ] + ), + LOG_NAME + ) + # Rename modded + DirAccess.rename_absolute( + path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), + "%s.pck" % path.game_base_dir.path_join(file_name.pck) + ) + ModLoaderSetupLog.debug( + ( + 'Renamed "%s" to "%s"' + % [ + path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), + "%s.pck" % path.game_base_dir.path_join(file_name.pck) + ] + ), + LOG_NAME + ) -func get_pck_version() -> String: - var engine_version_info := Engine.get_version_info() - # Godot 4 pck version always starts with a 2 (at least for now). - var pck_version := ( - "2.%s.%s.%s" - % [engine_version_info.major, engine_version_info.minor, engine_version_info.patch] - ) - ModLoaderSetupLog.debug("The pck version is: %s" % pck_version, LOG_NAME) - return pck_version + #clean_up() # Add modified binary to the pck -func inject(pck_version: String = get_pck_version()) -> void: +func inject(injection_path: String) -> void: var arguments := [ - "-pc", - path.pck, - path.temp_dir_path, - path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - pck_version + "--pck-patch=%s" % injection_path, + "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal], + ( + "--patch-file=%s=%s" + % [ + path.temp_global_script_class_cache_path, + path.global_script_class_cache_path_internal + ] + ), + "--output=%s" % path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), ] - ModLoaderSetupLog.debug( - "Injecting temp dir content into .pck: %s %s", [path.pck_explorer, arguments], LOG_NAME - ) - # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. - var _exit_code_inject := OS.execute(path.pck_explorer, arguments) - - -func split_pck() -> void: - var arguments := ["-s", path.exe] - ModLoaderSetupLog.debug( - "Splitting .pck from .exe: %s %s", [path.pck_explorer, arguments], LOG_NAME - ) # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. - var _exit_code_split_pck := OS.execute(path.pck_explorer, arguments) + ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) + var output := [] + var _exit_code_inject := OS.execute(path.gdre, arguments, output) + ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME) # Removes the temp files @@ -268,17 +257,15 @@ func setup_file_data() -> void: path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir() # C:/path/to/game/addons/mod_loader path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/" - path.pck_explorer = ( - path.mod_loader_dir - + get_pck_explorer_path() - ) - # ! pck explorer doesn't like trailing `/` in a path ! + path.gdre = path.mod_loader_dir + get_gdre_path() path.temp_dir_path = path.mod_loader_dir + "setup/temp" path.temp_project_binary_path = path.temp_dir_path + "/project.binary" path.temp_global_script_class_cache_path = ( path.temp_dir_path + "/.godot/global_script_class_cache.cfg" ) + path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg" + path.project_binary_path_internal = "res://project.binary" # can be supplied to override the exe_name file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name") # can be supplied to override the pck_name @@ -347,13 +334,11 @@ func get_combined_global_script_class_cache() -> ConfigFile: return global_script_class_cache_combined -func get_pck_explorer_path() -> String: - var pck_explorer_path := "vendor/GodotPCKExplorer/GodotPCKExplorer.Console" - +func get_gdre_path() -> String: if OS.get_name() == "Windows": - return "%s.exe" % pck_explorer_path + return "vendor/GDRE/gdre_tools.exe" - return pck_explorer_path + return "" func restart() -> void: From 6ef665838051b247b9c5da0b179117bd018315c1 Mon Sep 17 00:00:00 2001 From: Kai Date: Sun, 9 Feb 2025 15:26:28 +0100 Subject: [PATCH 2/6] refactor: :recycle: added new --embed param --- addons/mod_loader/mod_loader_setup.gd | 78 ++++++++++++--------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index 0e9cc5ed..fcef2334 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -149,6 +149,7 @@ func handle_override_cfg() -> void: func handle_injection() -> void: var is_embedded: bool = not FileAccess.file_exists(path.pck) var injection_path: String = path.exe if is_embedded else path.pck + var file_extension := injection_path.get_extension() ModLoaderSetupLog.debug("Start injection", LOG_NAME) # Create temp dir @@ -173,61 +174,52 @@ func handle_injection() -> void: # Save the global class cache config file combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) - inject(injection_path) + inject(injection_path, is_embedded) # Rename vanilla - if not is_embedded: - DirAccess.rename_absolute( - path.pck, - path.pck.trim_suffix("%s.pck" % file_name.pck).path_join( - "%s-vanilla.pck" % file_name.pck - ) - ) - ModLoaderSetupLog.debug( - ( - 'Renamed "%s" to "%s"' - % [ - path.pck, - path.pck.trim_suffix("%s.pck" % file_name.pck).path_join( - "%s-vanilla.pck" % file_name.pck - ) - ] - ), - LOG_NAME - ) - # Rename modded - DirAccess.rename_absolute( - path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - "%s.pck" % path.game_base_dir.path_join(file_name.pck) - ) - ModLoaderSetupLog.debug( - ( - 'Renamed "%s" to "%s"' - % [ - path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - "%s.pck" % path.game_base_dir.path_join(file_name.pck) - ] - ), - LOG_NAME - ) + var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension] + var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension] + + DirAccess.rename_absolute(injection_path, vanilla_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME) - #clean_up() + # Rename modded + DirAccess.rename_absolute(modded_path, injection_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME) + + clean_up() # Add modified binary to the pck -func inject(injection_path: String) -> void: - var arguments := [ - "--pck-patch=%s" % injection_path, - "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal], +func inject(injection_path: String, is_embedded := false) -> void: + var arguments := [] + arguments.push_back("--pck-patch=%s" % injection_path) + if is_embedded: + arguments.push_back("--embed=%s" % injection_path) + arguments.push_back( + "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal] + ) + arguments.push_back( ( "--patch-file=%s=%s" % [ path.temp_global_script_class_cache_path, path.global_script_class_cache_path_internal ] - ), - "--output=%s" % path.game_base_dir.path_join("%s-modded.pck" % file_name.pck), - ] + ) + ) + arguments.push_back( + ( + "--output=%s" + % path.game_base_dir.path_join( + ( + "%s-modded.%s" + % [file_name[injection_path.get_extension()], injection_path.get_extension()] + ) + ) + ) + ) + # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) var output := [] From 95ce66c8534dcadbed408524948067926889c982 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 11 Feb 2025 11:48:09 +0100 Subject: [PATCH 3/6] refactor: :recycle: added GDRE download step Moved the setup to a setup scene. This setup scene is loaded by the `--script`. --- addons/mod_loader/mod_loader_setup.gd | 299 +------------------- addons/mod_loader/setup/setup.gd | 364 +++++++++++++++++++++++++ addons/mod_loader/setup/setup_utils.gd | 18 ++ 3 files changed, 384 insertions(+), 297 deletions(-) create mode 100644 addons/mod_loader/setup/setup.gd diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index fcef2334..38cc0994 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -1,21 +1,13 @@ extends SceneTree -const LOG_NAME := "ModLoader:Setup" -const settings := { - "IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied", - "IS_LOADER_SET_UP": "application/run/is_loader_set_up", - "MOD_LOADER_AUTOLOAD": "autoload/ModLoader", -} +const LOG_NAME := "ModLoader:Setup" # IMPORTANT: use the ModLoaderLog via this variable within this script! # Otherwise, script compilation will break on first load since the class is not defined. var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd") var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd") -var path := {} -var file_name := {} -var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup") var is_setup_create_override_cfg: bool = ModLoaderSetupUtils.is_running_with_command_line_arg( "--setup-create-override-cfg" ) @@ -39,7 +31,7 @@ func _init() -> void: modded_start() return - setup_modloader() + change_scene_to_file("res://addons/mod_loader/setup/setup.tscn") # ModLoader already setup - switch to the main scene @@ -49,290 +41,3 @@ func modded_start() -> void: root.set_title("%s (Modded)" % ProjectSettings.get_setting("application/config/name")) change_scene_to_file.call_deferred(ProjectSettings.get_setting("application/run/main_scene")) - - -# Set up the ModLoader as an autoload and register the other global classes. -func setup_modloader() -> void: - ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME) - - # Setup path and file_name dict with all required paths and file names. - setup_file_data() - - # Add ModLoader autoload (the * marks the path as autoload) - reorder_autoloads() - ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true) - - # The game needs to be restarted first, before the loader is truly set up - # Set this here and check it elsewhere to prompt the user for a restart - ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false) - - if is_setup_create_override_cfg: - handle_override_cfg() - else: - handle_injection() - - # ModLoader is set up. A game restart is required to apply the ProjectSettings. - ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME) - - match true: - # If the --only-setup cli argument is passed, quit with exit code 0 - is_only_setup: - quit(0) - # If no cli argument is passed, show message with OS.alert() and user has to restart the game - _: - OS.alert( - "The Godot ModLoader has been set up. The game needs to be restarted to apply the changes. Confirm to restart." - ) - restart() - - -# Reorders the autoloads in the project settings, to get the ModLoader on top. -func reorder_autoloads() -> void: - # remove and re-add autoloads - var original_autoloads := {} - for prop in ProjectSettings.get_property_list(): - var name: String = prop.name - if name.begins_with("autoload/"): - var value: String = ProjectSettings.get_setting(name) - original_autoloads[name] = value - - ModLoaderSetupLog.info( - "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"), - LOG_NAME - ) - - for autoload in original_autoloads.keys(): - ProjectSettings.set_setting(autoload, null) - - # Add ModLoaderStore autoload (the * marks the path as autoload) - ProjectSettings.set_setting( - "autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd" - ) - - # Add ModLoader autoload (the * marks the path as autoload) - ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd") - - # add all previous autoloads back again - for autoload in original_autoloads.keys(): - ProjectSettings.set_setting(autoload, original_autoloads[autoload]) - - var new_autoloads := {} - for prop in ProjectSettings.get_property_list(): - var name: String = prop.name - if name.begins_with("autoload/"): - var value: String = ProjectSettings.get_setting(name) - new_autoloads[name] = value - - ModLoaderSetupLog.info( - "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"), - LOG_NAME - ) - - -# Saves the ProjectSettings to a override.cfg file in the base game directory. -func handle_override_cfg() -> void: - ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME) - - # Make the '.godot' dir public as 'godot' and copy all files to the public dir. - make_project_data_public() - - # Combine mod_loader and game global classes - var global_script_class_cache_combined := get_combined_global_script_class_cache() - global_script_class_cache_combined.save("res://godot/global_script_class_cache.cfg") - - var _save_custom_error: int = ProjectSettings.save_custom( - ModLoaderSetupUtils.get_override_path() - ) - - -# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file. -func handle_injection() -> void: - var is_embedded: bool = not FileAccess.file_exists(path.pck) - var injection_path: String = path.exe if is_embedded else path.pck - var file_extension := injection_path.get_extension() - - ModLoaderSetupLog.debug("Start injection", LOG_NAME) - # Create temp dir - ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME) - DirAccess.make_dir_recursive_absolute(path.temp_dir_path) - - # Create project.binary - ModLoaderSetupLog.debug( - 'Storing project.binary at "%s"' % path.temp_project_binary_path, LOG_NAME - ) - var _error_save_custom_project_binary = ProjectSettings.save_custom( - path.temp_project_binary_path - ) - # Create combined global class cache cfg - var combined_global_script_class_cache_file := get_combined_global_script_class_cache() - ModLoaderSetupLog.debug( - 'Storing global_script_class_cache at "%s"' % path.temp_global_script_class_cache_path, - LOG_NAME - ) - # Create the .godot dir inside the temp dir - DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot")) - # Save the global class cache config file - combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) - - inject(injection_path, is_embedded) - - # Rename vanilla - var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension] - var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension] - - DirAccess.rename_absolute(injection_path, vanilla_path) - ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME) - - # Rename modded - DirAccess.rename_absolute(modded_path, injection_path) - ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME) - - clean_up() - - -# Add modified binary to the pck -func inject(injection_path: String, is_embedded := false) -> void: - var arguments := [] - arguments.push_back("--pck-patch=%s" % injection_path) - if is_embedded: - arguments.push_back("--embed=%s" % injection_path) - arguments.push_back( - "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal] - ) - arguments.push_back( - ( - "--patch-file=%s=%s" - % [ - path.temp_global_script_class_cache_path, - path.global_script_class_cache_path_internal - ] - ) - ) - arguments.push_back( - ( - "--output=%s" - % path.game_base_dir.path_join( - ( - "%s-modded.%s" - % [file_name[injection_path.get_extension()], injection_path.get_extension()] - ) - ) - ) - ) - - # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. - ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) - var output := [] - var _exit_code_inject := OS.execute(path.gdre, arguments, output) - ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME) - - -# Removes the temp files -func clean_up() -> void: - ModLoaderSetupLog.debug("Start clean up", LOG_NAME) - DirAccess.remove_absolute(path.temp_project_binary_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_project_binary_path, LOG_NAME) - DirAccess.remove_absolute(path.temp_global_script_class_cache_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_global_script_class_cache_path, LOG_NAME) - DirAccess.remove_absolute(path.temp_dir_path.path_join(".godot")) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path.path_join(".godot"), LOG_NAME) - DirAccess.remove_absolute(path.temp_dir_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path, LOG_NAME) - ModLoaderSetupLog.debug("Clean up completed", LOG_NAME) - - -# Initialize the path and file_name dictionary -func setup_file_data() -> void: - # C:/path/to/game/game.exe - path.exe = OS.get_executable_path() - # C:/path/to/game/ - path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir() - # C:/path/to/game/addons/mod_loader - path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/" - path.gdre = path.mod_loader_dir + get_gdre_path() - path.temp_dir_path = path.mod_loader_dir + "setup/temp" - path.temp_project_binary_path = path.temp_dir_path + "/project.binary" - path.temp_global_script_class_cache_path = ( - path.temp_dir_path - + "/.godot/global_script_class_cache.cfg" - ) - path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg" - path.project_binary_path_internal = "res://project.binary" - # can be supplied to override the exe_name - file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name") - # can be supplied to override the pck_name - file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name") - # game - or use the value of cli_arg_exe_name if there is one - file_name.exe = ( - ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) - if file_name.cli_arg_exe == "" - else file_name.cli_arg_exe - ) - # game - or use the value of cli_arg_pck_name if there is one - # using exe_path.get_file() instead of exe_name - # so you don't override the pck_name with the --exe-name cli arg - # the main pack name is the same as the .exe name - # if --main-pack cli arg is not set - file_name.pck = ( - ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) - if file_name.cli_arg_pck == "" - else file_name.cli_arg_pck - ) - # C:/path/to/game/game.pck - path.pck = path.game_base_dir.path_join(file_name.pck + ".pck") - - ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME) - ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME) - - -func make_project_data_public() -> void: - ModLoaderSetupLog.info("Register Global Classes", LOG_NAME) - ProjectSettings.set_setting("application/config/use_hidden_project_data_directory", false) - - var godot_files = ModLoaderSetupUtils.get_flat_view_dict("res://.godot") - - ModLoaderSetupLog.info('Copying all files from "res://.godot" to "res://godot".', LOG_NAME) - - for file in godot_files: - ModLoaderSetupUtils.copy_file( - file, file.trim_prefix("res://.godot").insert(0, "res://godot") - ) - - -func get_combined_global_script_class_cache() -> ConfigFile: - ModLoaderSetupLog.info("Load mod loader class cache", LOG_NAME) - var global_script_class_cache_mod_loader := ConfigFile.new() - global_script_class_cache_mod_loader.load( - "res://addons/mod_loader/setup/global_script_class_cache_mod_loader.cfg" - ) - - ModLoaderSetupLog.info("Load game class cache", LOG_NAME) - var global_script_class_cache_game := ConfigFile.new() - global_script_class_cache_game.load("res://.godot/global_script_class_cache.cfg") - - ModLoaderSetupLog.info("Create new class cache", LOG_NAME) - var global_classes_mod_loader := global_script_class_cache_mod_loader.get_value("", "list") - var global_classes_game := global_script_class_cache_game.get_value("", "list") - - ModLoaderSetupLog.info("Combine class cache", LOG_NAME) - var global_classes_combined := [] - global_classes_combined.append_array(global_classes_mod_loader) - global_classes_combined.append_array(global_classes_game) - - ModLoaderSetupLog.info("Save combined class cache", LOG_NAME) - var global_script_class_cache_combined := ConfigFile.new() - global_script_class_cache_combined.set_value("", "list", global_classes_combined) - - return global_script_class_cache_combined - - -func get_gdre_path() -> String: - if OS.get_name() == "Windows": - return "vendor/GDRE/gdre_tools.exe" - - return "" - - -func restart() -> void: - OS.set_restart_on_exit(true) - quit() diff --git a/addons/mod_loader/setup/setup.gd b/addons/mod_loader/setup/setup.gd new file mode 100644 index 00000000..84661c58 --- /dev/null +++ b/addons/mod_loader/setup/setup.gd @@ -0,0 +1,364 @@ +extends Control + + +signal gdre_download_completed + +const LOG_NAME := "ModLoader:Setup" +const GDRE_DOWNLOAD_WIN := "https://github.com/GDRETools/gdsdecomp/releases/download/v0.9.0-beta.3/GDRE_tools-v0.9.0-beta.3-windows.zip" +const settings := { + "IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied", + "IS_LOADER_SET_UP": "application/run/is_loader_set_up", + "MOD_LOADER_AUTOLOAD": "autoload/ModLoader", +} + +# IMPORTANT: use the ModLoaderLog via this variable within this script! +# Otherwise, script compilation will break on first load since the class is not defined. +var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd") +var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd") + +var path := {} +var file_name := {} +var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup") +var is_setup_create_override_cfg: bool = ModLoaderSetupUtils.is_running_with_command_line_arg( + "--setup-create-override-cfg" +) + +@onready var http_request: HTTPRequest = %HTTPRequest + + +func _init() -> void: + for i in AudioServer.bus_count: + AudioServer.set_bus_mute(i, true) + + +func _ready() -> void: + setup_modloader() + + +# Set up the ModLoader as an autoload and register the other global classes. +func setup_modloader() -> void: + ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME) + + # Setup path and file_name dict with all required paths and file names. + setup_file_data() + + # Add ModLoader autoload (the * marks the path as autoload) + reorder_autoloads() + ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true) + + # The game needs to be restarted first, before the loader is truly set up + # Set this here and check it elsewhere to prompt the user for a restart + ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false) + + if is_setup_create_override_cfg: + handle_override_cfg() + else: + if not FileAccess.file_exists(path.gdre): + var request := gdre_download() + ModLoaderSetupLog.info("GDRE Download started.", LOG_NAME) + await gdre_download_completed + handle_injection() + + # ModLoader is set up. A game restart is required to apply the ProjectSettings. + ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME) + + match true: + # If the --only-setup cli argument is passed, quit with exit code 0 + is_only_setup: + get_tree().quit(0) + # If no cli argument is passed, show message with OS.alert() and user has to restart the game + _: + OS.alert( + "The Godot ModLoader has been set up. The game needs to be restarted to apply the changes. Confirm to restart." + ) + restart() + + +# Reorders the autoloads in the project settings, to get the ModLoader on top. +func reorder_autoloads() -> void: + # remove and re-add autoloads + var original_autoloads := {} + for prop in ProjectSettings.get_property_list(): + var name: String = prop.name + if name.begins_with("autoload/"): + var value: String = ProjectSettings.get_setting(name) + original_autoloads[name] = value + + ModLoaderSetupLog.info( + "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"), + LOG_NAME + ) + + for autoload in original_autoloads.keys(): + ProjectSettings.set_setting(autoload, null) + + # Add ModLoaderStore autoload (the * marks the path as autoload) + ProjectSettings.set_setting( + "autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd" + ) + + # Add ModLoader autoload (the * marks the path as autoload) + ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd") + + # add all previous autoloads back again + for autoload in original_autoloads.keys(): + ProjectSettings.set_setting(autoload, original_autoloads[autoload]) + + var new_autoloads := {} + for prop in ProjectSettings.get_property_list(): + var name: String = prop.name + if name.begins_with("autoload/"): + var value: String = ProjectSettings.get_setting(name) + new_autoloads[name] = value + + ModLoaderSetupLog.info( + "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"), + LOG_NAME + ) + + +# Saves the ProjectSettings to a override.cfg file in the base game directory. +func handle_override_cfg() -> void: + ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME) + + # Make the '.godot' dir public as 'godot' and copy all files to the public dir. + make_project_data_public() + + # Combine mod_loader and game global classes + var global_script_class_cache_combined := get_combined_global_script_class_cache() + global_script_class_cache_combined.save("res://godot/global_script_class_cache.cfg") + + var _save_custom_error: int = ProjectSettings.save_custom( + ModLoaderSetupUtils.get_override_path() + ) + + +# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file. +func handle_injection() -> void: + var is_embedded: bool = not FileAccess.file_exists(path.pck) + var injection_path: String = path.exe if is_embedded else path.pck + var file_extension := injection_path.get_extension() + + ModLoaderSetupLog.debug("Start injection", LOG_NAME) + # Create temp dir + ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME) + DirAccess.make_dir_recursive_absolute(path.temp_dir_path) + + # Create project.binary + ModLoaderSetupLog.debug( + 'Storing project.binary at "%s"' % path.temp_project_binary_path, LOG_NAME + ) + var _error_save_custom_project_binary = ProjectSettings.save_custom( + path.temp_project_binary_path + ) + # Create combined global class cache cfg + var combined_global_script_class_cache_file := get_combined_global_script_class_cache() + ModLoaderSetupLog.debug( + 'Storing global_script_class_cache at "%s"' % path.temp_global_script_class_cache_path, + LOG_NAME + ) + # Create the .godot dir inside the temp dir + DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot")) + # Save the global class cache config file + combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) + + inject(injection_path, is_embedded) + + # Rename vanilla + var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension] + var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension] + + DirAccess.rename_absolute(injection_path, vanilla_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME) + + # Rename modded + DirAccess.rename_absolute(modded_path, injection_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME) + + clean_up() + + +# Add modified binary to the pck +func inject(injection_path: String, is_embedded := false) -> void: + var arguments := [] + arguments.push_back("--pck-patch=%s" % injection_path) + if is_embedded: + arguments.push_back("--embed=%s" % injection_path) + arguments.push_back( + "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal] + ) + arguments.push_back( + ( + "--patch-file=%s=%s" + % [ + path.temp_global_script_class_cache_path, + path.global_script_class_cache_path_internal + ] + ) + ) + arguments.push_back( + ( + "--output=%s" + % path.game_base_dir.path_join( + ( + "%s-modded.%s" + % [file_name[injection_path.get_extension()], injection_path.get_extension()] + ) + ) + ) + ) + + # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. + ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) + var output := [] + var _exit_code_inject := OS.execute(path.gdre, arguments, output) + ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME) + + +# Removes the temp files +func clean_up() -> void: + ModLoaderSetupLog.debug("Start clean up", LOG_NAME) + DirAccess.remove_absolute(path.temp_project_binary_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_project_binary_path, LOG_NAME) + DirAccess.remove_absolute(path.temp_global_script_class_cache_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_global_script_class_cache_path, LOG_NAME) + DirAccess.remove_absolute(path.temp_dir_path.path_join(".godot")) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path.path_join(".godot"), LOG_NAME) + DirAccess.remove_absolute(path.temp_dir_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path, LOG_NAME) + ModLoaderSetupLog.debug("Clean up completed", LOG_NAME) + + +# Initialize the path and file_name dictionary +func setup_file_data() -> void: + # C:/path/to/game/game.exe + path.exe = OS.get_executable_path() + # C:/path/to/game/ + path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir() + # C:/path/to/game/addons/mod_loader + path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/" + path.gdre = path.mod_loader_dir + get_gdre_path() + path.gdre_download = path.mod_loader_dir + get_gdre_path().get_basename() + ".zip" + path.temp_dir_path = path.mod_loader_dir + "setup/temp" + path.temp_project_binary_path = path.temp_dir_path + "/project.binary" + path.temp_global_script_class_cache_path = ( + path.temp_dir_path + + "/.godot/global_script_class_cache.cfg" + ) + path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg" + path.project_binary_path_internal = "res://project.binary" + # can be supplied to override the exe_name + file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name") + # can be supplied to override the pck_name + file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name") + # game - or use the value of cli_arg_exe_name if there is one + file_name.exe = ( + ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) + if file_name.cli_arg_exe == "" + else file_name.cli_arg_exe + ) + # game - or use the value of cli_arg_pck_name if there is one + # using exe_path.get_file() instead of exe_name + # so you don't override the pck_name with the --exe-name cli arg + # the main pack name is the same as the .exe name + # if --main-pack cli arg is not set + file_name.pck = ( + ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) + if file_name.cli_arg_pck == "" + else file_name.cli_arg_pck + ) + # C:/path/to/game/game.pck + path.pck = path.game_base_dir.path_join(file_name.pck + ".pck") + + ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME) + ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME) + + +func make_project_data_public() -> void: + ModLoaderSetupLog.info("Register Global Classes", LOG_NAME) + ProjectSettings.set_setting("application/config/use_hidden_project_data_directory", false) + + var godot_files = ModLoaderSetupUtils.get_flat_view_dict("res://.godot") + + ModLoaderSetupLog.info('Copying all files from "res://.godot" to "res://godot".', LOG_NAME) + + for file in godot_files: + ModLoaderSetupUtils.copy_file( + file, file.trim_prefix("res://.godot").insert(0, "res://godot") + ) + + +func get_combined_global_script_class_cache() -> ConfigFile: + ModLoaderSetupLog.info("Load mod loader class cache", LOG_NAME) + var global_script_class_cache_mod_loader := ConfigFile.new() + global_script_class_cache_mod_loader.load( + "res://addons/mod_loader/setup/global_script_class_cache_mod_loader.cfg" + ) + + ModLoaderSetupLog.info("Load game class cache", LOG_NAME) + var global_script_class_cache_game := ConfigFile.new() + global_script_class_cache_game.load("res://.godot/global_script_class_cache.cfg") + + ModLoaderSetupLog.info("Create new class cache", LOG_NAME) + var global_classes_mod_loader := global_script_class_cache_mod_loader.get_value("", "list") + var global_classes_game := global_script_class_cache_game.get_value("", "list") + + ModLoaderSetupLog.info("Combine class cache", LOG_NAME) + var global_classes_combined := [] + global_classes_combined.append_array(global_classes_mod_loader) + global_classes_combined.append_array(global_classes_game) + + ModLoaderSetupLog.info("Save combined class cache", LOG_NAME) + var global_script_class_cache_combined := ConfigFile.new() + global_script_class_cache_combined.set_value("", "list", global_classes_combined) + + return global_script_class_cache_combined + + +func get_gdre_path() -> String: + if OS.get_name() == "Windows": + return "vendor/GDRE/gdre_tools.exe" + + return "" + + +func gdre_download() -> HTTPRequest: + var error := http_request.request(GDRE_DOWNLOAD_WIN) + + if not error == OK: + ModLoaderSetupLog.error("HTTP request failed with Error: %s" % error_string(error), LOG_NAME) + + return http_request + + +func _gdre_download_completed(result, response_code, headers, body) -> void: + ModLoaderSetupLog.info("GDRE download request completed - saving to disk.", LOG_NAME) + if not response_code == 200: + ModLoaderSetupLog.error("An error occurred in the HTTP request - response code: %s." % response_code, LOG_NAME) + return + + print(path.gdre.get_base_dir()) + + if not DirAccess.dir_exists_absolute(path.gdre.get_base_dir()): + var error := DirAccess.make_dir_recursive_absolute(path.gdre.get_base_dir()) + if not error == OK: + ModLoaderSetupLog.error("Failed to create GDRE directory. Error: \"%s\"." % error_string(error) , LOG_NAME) + + var file := FileAccess.open(path.gdre_download, FileAccess.WRITE) + if not file: + ModLoaderSetupLog.error("Failed to write downloaded file. Error: \"%s\"." % error_string(FileAccess.get_open_error()) , LOG_NAME) + return + + file.store_buffer(body) + file.close() + ModLoaderSetupLog.info("GDRE download completed - saved to %s." % path.gdre_download, LOG_NAME) + + # Unpack zip + ModLoaderSetupUtils.unzip(path.gdre_download) + + gdre_download_completed.emit() + + +func restart() -> void: + OS.set_restart_on_exit(true) + get_tree().quit() diff --git a/addons/mod_loader/setup/setup_utils.gd b/addons/mod_loader/setup/setup_utils.gd index 78fba4fd..260727ce 100644 --- a/addons/mod_loader/setup/setup_utils.gd +++ b/addons/mod_loader/setup/setup_utils.gd @@ -284,3 +284,21 @@ static func copy_file(from: String, to: String) -> void: return file_to.store_buffer(file_from_content) + + +static func unzip(src_path: String, dst_path := src_path.get_base_dir()) -> void: + var reader := ZIPReader.new() + var err := reader.open(src_path) + if err != OK: + ModLoaderSetupLog.error("Failed to open zip. Error: \"%s\"." % error_string(err) , LOG_NAME) + return PackedByteArray() + for zip_file_path in reader.get_files(): + ModLoaderSetupLog.info("Reading file %s." % zip_file_path, LOG_NAME) + var buffer := reader.read_file(zip_file_path) + var file := FileAccess.open(dst_path.path_join(zip_file_path), FileAccess.WRITE) + if not file: + ModLoaderSetupLog.error("Failed to write file. Error: \"%s\"." % error_string(FileAccess.get_open_error()) , LOG_NAME) + return + ModLoaderSetupLog.info("Storing file %s." % dst_path.path_join(zip_file_path), LOG_NAME) + file.store_buffer(buffer) + reader.close() From 70a28362246af28ea7232e26e553d8c0b4f0300a Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 14 Feb 2025 14:57:16 +0100 Subject: [PATCH 4/6] Revert "refactor: :recycle: added GDRE download step" This reverts commit 95ce66c8534dcadbed408524948067926889c982. --- addons/mod_loader/mod_loader_setup.gd | 299 +++++++++++++++++++- addons/mod_loader/setup/setup.gd | 364 ------------------------- addons/mod_loader/setup/setup_utils.gd | 18 -- 3 files changed, 297 insertions(+), 384 deletions(-) delete mode 100644 addons/mod_loader/setup/setup.gd diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index 38cc0994..fcef2334 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -1,13 +1,21 @@ extends SceneTree - const LOG_NAME := "ModLoader:Setup" +const settings := { + "IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied", + "IS_LOADER_SET_UP": "application/run/is_loader_set_up", + "MOD_LOADER_AUTOLOAD": "autoload/ModLoader", +} + # IMPORTANT: use the ModLoaderLog via this variable within this script! # Otherwise, script compilation will break on first load since the class is not defined. var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd") var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd") +var path := {} +var file_name := {} +var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup") var is_setup_create_override_cfg: bool = ModLoaderSetupUtils.is_running_with_command_line_arg( "--setup-create-override-cfg" ) @@ -31,7 +39,7 @@ func _init() -> void: modded_start() return - change_scene_to_file("res://addons/mod_loader/setup/setup.tscn") + setup_modloader() # ModLoader already setup - switch to the main scene @@ -41,3 +49,290 @@ func modded_start() -> void: root.set_title("%s (Modded)" % ProjectSettings.get_setting("application/config/name")) change_scene_to_file.call_deferred(ProjectSettings.get_setting("application/run/main_scene")) + + +# Set up the ModLoader as an autoload and register the other global classes. +func setup_modloader() -> void: + ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME) + + # Setup path and file_name dict with all required paths and file names. + setup_file_data() + + # Add ModLoader autoload (the * marks the path as autoload) + reorder_autoloads() + ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true) + + # The game needs to be restarted first, before the loader is truly set up + # Set this here and check it elsewhere to prompt the user for a restart + ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false) + + if is_setup_create_override_cfg: + handle_override_cfg() + else: + handle_injection() + + # ModLoader is set up. A game restart is required to apply the ProjectSettings. + ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME) + + match true: + # If the --only-setup cli argument is passed, quit with exit code 0 + is_only_setup: + quit(0) + # If no cli argument is passed, show message with OS.alert() and user has to restart the game + _: + OS.alert( + "The Godot ModLoader has been set up. The game needs to be restarted to apply the changes. Confirm to restart." + ) + restart() + + +# Reorders the autoloads in the project settings, to get the ModLoader on top. +func reorder_autoloads() -> void: + # remove and re-add autoloads + var original_autoloads := {} + for prop in ProjectSettings.get_property_list(): + var name: String = prop.name + if name.begins_with("autoload/"): + var value: String = ProjectSettings.get_setting(name) + original_autoloads[name] = value + + ModLoaderSetupLog.info( + "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"), + LOG_NAME + ) + + for autoload in original_autoloads.keys(): + ProjectSettings.set_setting(autoload, null) + + # Add ModLoaderStore autoload (the * marks the path as autoload) + ProjectSettings.set_setting( + "autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd" + ) + + # Add ModLoader autoload (the * marks the path as autoload) + ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd") + + # add all previous autoloads back again + for autoload in original_autoloads.keys(): + ProjectSettings.set_setting(autoload, original_autoloads[autoload]) + + var new_autoloads := {} + for prop in ProjectSettings.get_property_list(): + var name: String = prop.name + if name.begins_with("autoload/"): + var value: String = ProjectSettings.get_setting(name) + new_autoloads[name] = value + + ModLoaderSetupLog.info( + "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"), + LOG_NAME + ) + + +# Saves the ProjectSettings to a override.cfg file in the base game directory. +func handle_override_cfg() -> void: + ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME) + + # Make the '.godot' dir public as 'godot' and copy all files to the public dir. + make_project_data_public() + + # Combine mod_loader and game global classes + var global_script_class_cache_combined := get_combined_global_script_class_cache() + global_script_class_cache_combined.save("res://godot/global_script_class_cache.cfg") + + var _save_custom_error: int = ProjectSettings.save_custom( + ModLoaderSetupUtils.get_override_path() + ) + + +# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file. +func handle_injection() -> void: + var is_embedded: bool = not FileAccess.file_exists(path.pck) + var injection_path: String = path.exe if is_embedded else path.pck + var file_extension := injection_path.get_extension() + + ModLoaderSetupLog.debug("Start injection", LOG_NAME) + # Create temp dir + ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME) + DirAccess.make_dir_recursive_absolute(path.temp_dir_path) + + # Create project.binary + ModLoaderSetupLog.debug( + 'Storing project.binary at "%s"' % path.temp_project_binary_path, LOG_NAME + ) + var _error_save_custom_project_binary = ProjectSettings.save_custom( + path.temp_project_binary_path + ) + # Create combined global class cache cfg + var combined_global_script_class_cache_file := get_combined_global_script_class_cache() + ModLoaderSetupLog.debug( + 'Storing global_script_class_cache at "%s"' % path.temp_global_script_class_cache_path, + LOG_NAME + ) + # Create the .godot dir inside the temp dir + DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot")) + # Save the global class cache config file + combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) + + inject(injection_path, is_embedded) + + # Rename vanilla + var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension] + var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension] + + DirAccess.rename_absolute(injection_path, vanilla_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME) + + # Rename modded + DirAccess.rename_absolute(modded_path, injection_path) + ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME) + + clean_up() + + +# Add modified binary to the pck +func inject(injection_path: String, is_embedded := false) -> void: + var arguments := [] + arguments.push_back("--pck-patch=%s" % injection_path) + if is_embedded: + arguments.push_back("--embed=%s" % injection_path) + arguments.push_back( + "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal] + ) + arguments.push_back( + ( + "--patch-file=%s=%s" + % [ + path.temp_global_script_class_cache_path, + path.global_script_class_cache_path_internal + ] + ) + ) + arguments.push_back( + ( + "--output=%s" + % path.game_base_dir.path_join( + ( + "%s-modded.%s" + % [file_name[injection_path.get_extension()], injection_path.get_extension()] + ) + ) + ) + ) + + # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. + ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) + var output := [] + var _exit_code_inject := OS.execute(path.gdre, arguments, output) + ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME) + + +# Removes the temp files +func clean_up() -> void: + ModLoaderSetupLog.debug("Start clean up", LOG_NAME) + DirAccess.remove_absolute(path.temp_project_binary_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_project_binary_path, LOG_NAME) + DirAccess.remove_absolute(path.temp_global_script_class_cache_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_global_script_class_cache_path, LOG_NAME) + DirAccess.remove_absolute(path.temp_dir_path.path_join(".godot")) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path.path_join(".godot"), LOG_NAME) + DirAccess.remove_absolute(path.temp_dir_path) + ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path, LOG_NAME) + ModLoaderSetupLog.debug("Clean up completed", LOG_NAME) + + +# Initialize the path and file_name dictionary +func setup_file_data() -> void: + # C:/path/to/game/game.exe + path.exe = OS.get_executable_path() + # C:/path/to/game/ + path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir() + # C:/path/to/game/addons/mod_loader + path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/" + path.gdre = path.mod_loader_dir + get_gdre_path() + path.temp_dir_path = path.mod_loader_dir + "setup/temp" + path.temp_project_binary_path = path.temp_dir_path + "/project.binary" + path.temp_global_script_class_cache_path = ( + path.temp_dir_path + + "/.godot/global_script_class_cache.cfg" + ) + path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg" + path.project_binary_path_internal = "res://project.binary" + # can be supplied to override the exe_name + file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name") + # can be supplied to override the pck_name + file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name") + # game - or use the value of cli_arg_exe_name if there is one + file_name.exe = ( + ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) + if file_name.cli_arg_exe == "" + else file_name.cli_arg_exe + ) + # game - or use the value of cli_arg_pck_name if there is one + # using exe_path.get_file() instead of exe_name + # so you don't override the pck_name with the --exe-name cli arg + # the main pack name is the same as the .exe name + # if --main-pack cli arg is not set + file_name.pck = ( + ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) + if file_name.cli_arg_pck == "" + else file_name.cli_arg_pck + ) + # C:/path/to/game/game.pck + path.pck = path.game_base_dir.path_join(file_name.pck + ".pck") + + ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME) + ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME) + + +func make_project_data_public() -> void: + ModLoaderSetupLog.info("Register Global Classes", LOG_NAME) + ProjectSettings.set_setting("application/config/use_hidden_project_data_directory", false) + + var godot_files = ModLoaderSetupUtils.get_flat_view_dict("res://.godot") + + ModLoaderSetupLog.info('Copying all files from "res://.godot" to "res://godot".', LOG_NAME) + + for file in godot_files: + ModLoaderSetupUtils.copy_file( + file, file.trim_prefix("res://.godot").insert(0, "res://godot") + ) + + +func get_combined_global_script_class_cache() -> ConfigFile: + ModLoaderSetupLog.info("Load mod loader class cache", LOG_NAME) + var global_script_class_cache_mod_loader := ConfigFile.new() + global_script_class_cache_mod_loader.load( + "res://addons/mod_loader/setup/global_script_class_cache_mod_loader.cfg" + ) + + ModLoaderSetupLog.info("Load game class cache", LOG_NAME) + var global_script_class_cache_game := ConfigFile.new() + global_script_class_cache_game.load("res://.godot/global_script_class_cache.cfg") + + ModLoaderSetupLog.info("Create new class cache", LOG_NAME) + var global_classes_mod_loader := global_script_class_cache_mod_loader.get_value("", "list") + var global_classes_game := global_script_class_cache_game.get_value("", "list") + + ModLoaderSetupLog.info("Combine class cache", LOG_NAME) + var global_classes_combined := [] + global_classes_combined.append_array(global_classes_mod_loader) + global_classes_combined.append_array(global_classes_game) + + ModLoaderSetupLog.info("Save combined class cache", LOG_NAME) + var global_script_class_cache_combined := ConfigFile.new() + global_script_class_cache_combined.set_value("", "list", global_classes_combined) + + return global_script_class_cache_combined + + +func get_gdre_path() -> String: + if OS.get_name() == "Windows": + return "vendor/GDRE/gdre_tools.exe" + + return "" + + +func restart() -> void: + OS.set_restart_on_exit(true) + quit() diff --git a/addons/mod_loader/setup/setup.gd b/addons/mod_loader/setup/setup.gd deleted file mode 100644 index 84661c58..00000000 --- a/addons/mod_loader/setup/setup.gd +++ /dev/null @@ -1,364 +0,0 @@ -extends Control - - -signal gdre_download_completed - -const LOG_NAME := "ModLoader:Setup" -const GDRE_DOWNLOAD_WIN := "https://github.com/GDRETools/gdsdecomp/releases/download/v0.9.0-beta.3/GDRE_tools-v0.9.0-beta.3-windows.zip" -const settings := { - "IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied", - "IS_LOADER_SET_UP": "application/run/is_loader_set_up", - "MOD_LOADER_AUTOLOAD": "autoload/ModLoader", -} - -# IMPORTANT: use the ModLoaderLog via this variable within this script! -# Otherwise, script compilation will break on first load since the class is not defined. -var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd") -var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd") - -var path := {} -var file_name := {} -var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup") -var is_setup_create_override_cfg: bool = ModLoaderSetupUtils.is_running_with_command_line_arg( - "--setup-create-override-cfg" -) - -@onready var http_request: HTTPRequest = %HTTPRequest - - -func _init() -> void: - for i in AudioServer.bus_count: - AudioServer.set_bus_mute(i, true) - - -func _ready() -> void: - setup_modloader() - - -# Set up the ModLoader as an autoload and register the other global classes. -func setup_modloader() -> void: - ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME) - - # Setup path and file_name dict with all required paths and file names. - setup_file_data() - - # Add ModLoader autoload (the * marks the path as autoload) - reorder_autoloads() - ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true) - - # The game needs to be restarted first, before the loader is truly set up - # Set this here and check it elsewhere to prompt the user for a restart - ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false) - - if is_setup_create_override_cfg: - handle_override_cfg() - else: - if not FileAccess.file_exists(path.gdre): - var request := gdre_download() - ModLoaderSetupLog.info("GDRE Download started.", LOG_NAME) - await gdre_download_completed - handle_injection() - - # ModLoader is set up. A game restart is required to apply the ProjectSettings. - ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME) - - match true: - # If the --only-setup cli argument is passed, quit with exit code 0 - is_only_setup: - get_tree().quit(0) - # If no cli argument is passed, show message with OS.alert() and user has to restart the game - _: - OS.alert( - "The Godot ModLoader has been set up. The game needs to be restarted to apply the changes. Confirm to restart." - ) - restart() - - -# Reorders the autoloads in the project settings, to get the ModLoader on top. -func reorder_autoloads() -> void: - # remove and re-add autoloads - var original_autoloads := {} - for prop in ProjectSettings.get_property_list(): - var name: String = prop.name - if name.begins_with("autoload/"): - var value: String = ProjectSettings.get_setting(name) - original_autoloads[name] = value - - ModLoaderSetupLog.info( - "Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"), - LOG_NAME - ) - - for autoload in original_autoloads.keys(): - ProjectSettings.set_setting(autoload, null) - - # Add ModLoaderStore autoload (the * marks the path as autoload) - ProjectSettings.set_setting( - "autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd" - ) - - # Add ModLoader autoload (the * marks the path as autoload) - ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd") - - # add all previous autoloads back again - for autoload in original_autoloads.keys(): - ProjectSettings.set_setting(autoload, original_autoloads[autoload]) - - var new_autoloads := {} - for prop in ProjectSettings.get_property_list(): - var name: String = prop.name - if name.begins_with("autoload/"): - var value: String = ProjectSettings.get_setting(name) - new_autoloads[name] = value - - ModLoaderSetupLog.info( - "Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"), - LOG_NAME - ) - - -# Saves the ProjectSettings to a override.cfg file in the base game directory. -func handle_override_cfg() -> void: - ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME) - - # Make the '.godot' dir public as 'godot' and copy all files to the public dir. - make_project_data_public() - - # Combine mod_loader and game global classes - var global_script_class_cache_combined := get_combined_global_script_class_cache() - global_script_class_cache_combined.save("res://godot/global_script_class_cache.cfg") - - var _save_custom_error: int = ProjectSettings.save_custom( - ModLoaderSetupUtils.get_override_path() - ) - - -# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file. -func handle_injection() -> void: - var is_embedded: bool = not FileAccess.file_exists(path.pck) - var injection_path: String = path.exe if is_embedded else path.pck - var file_extension := injection_path.get_extension() - - ModLoaderSetupLog.debug("Start injection", LOG_NAME) - # Create temp dir - ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME) - DirAccess.make_dir_recursive_absolute(path.temp_dir_path) - - # Create project.binary - ModLoaderSetupLog.debug( - 'Storing project.binary at "%s"' % path.temp_project_binary_path, LOG_NAME - ) - var _error_save_custom_project_binary = ProjectSettings.save_custom( - path.temp_project_binary_path - ) - # Create combined global class cache cfg - var combined_global_script_class_cache_file := get_combined_global_script_class_cache() - ModLoaderSetupLog.debug( - 'Storing global_script_class_cache at "%s"' % path.temp_global_script_class_cache_path, - LOG_NAME - ) - # Create the .godot dir inside the temp dir - DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot")) - # Save the global class cache config file - combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path) - - inject(injection_path, is_embedded) - - # Rename vanilla - var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension] - var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension] - - DirAccess.rename_absolute(injection_path, vanilla_path) - ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME) - - # Rename modded - DirAccess.rename_absolute(modded_path, injection_path) - ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME) - - clean_up() - - -# Add modified binary to the pck -func inject(injection_path: String, is_embedded := false) -> void: - var arguments := [] - arguments.push_back("--pck-patch=%s" % injection_path) - if is_embedded: - arguments.push_back("--embed=%s" % injection_path) - arguments.push_back( - "--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal] - ) - arguments.push_back( - ( - "--patch-file=%s=%s" - % [ - path.temp_global_script_class_cache_path, - path.global_script_class_cache_path_internal - ] - ) - ) - arguments.push_back( - ( - "--output=%s" - % path.game_base_dir.path_join( - ( - "%s-modded.%s" - % [file_name[injection_path.get_extension()], injection_path.get_extension()] - ) - ) - ) - ) - - # For unknown reasons the output only displays a single "[" - so only the executed arguments are logged. - ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME) - var output := [] - var _exit_code_inject := OS.execute(path.gdre, arguments, output) - ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME) - - -# Removes the temp files -func clean_up() -> void: - ModLoaderSetupLog.debug("Start clean up", LOG_NAME) - DirAccess.remove_absolute(path.temp_project_binary_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_project_binary_path, LOG_NAME) - DirAccess.remove_absolute(path.temp_global_script_class_cache_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_global_script_class_cache_path, LOG_NAME) - DirAccess.remove_absolute(path.temp_dir_path.path_join(".godot")) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path.path_join(".godot"), LOG_NAME) - DirAccess.remove_absolute(path.temp_dir_path) - ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path, LOG_NAME) - ModLoaderSetupLog.debug("Clean up completed", LOG_NAME) - - -# Initialize the path and file_name dictionary -func setup_file_data() -> void: - # C:/path/to/game/game.exe - path.exe = OS.get_executable_path() - # C:/path/to/game/ - path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir() - # C:/path/to/game/addons/mod_loader - path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/" - path.gdre = path.mod_loader_dir + get_gdre_path() - path.gdre_download = path.mod_loader_dir + get_gdre_path().get_basename() + ".zip" - path.temp_dir_path = path.mod_loader_dir + "setup/temp" - path.temp_project_binary_path = path.temp_dir_path + "/project.binary" - path.temp_global_script_class_cache_path = ( - path.temp_dir_path - + "/.godot/global_script_class_cache.cfg" - ) - path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg" - path.project_binary_path_internal = "res://project.binary" - # can be supplied to override the exe_name - file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name") - # can be supplied to override the pck_name - file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name") - # game - or use the value of cli_arg_exe_name if there is one - file_name.exe = ( - ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) - if file_name.cli_arg_exe == "" - else file_name.cli_arg_exe - ) - # game - or use the value of cli_arg_pck_name if there is one - # using exe_path.get_file() instead of exe_name - # so you don't override the pck_name with the --exe-name cli arg - # the main pack name is the same as the .exe name - # if --main-pack cli arg is not set - file_name.pck = ( - ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true) - if file_name.cli_arg_pck == "" - else file_name.cli_arg_pck - ) - # C:/path/to/game/game.pck - path.pck = path.game_base_dir.path_join(file_name.pck + ".pck") - - ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME) - ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME) - - -func make_project_data_public() -> void: - ModLoaderSetupLog.info("Register Global Classes", LOG_NAME) - ProjectSettings.set_setting("application/config/use_hidden_project_data_directory", false) - - var godot_files = ModLoaderSetupUtils.get_flat_view_dict("res://.godot") - - ModLoaderSetupLog.info('Copying all files from "res://.godot" to "res://godot".', LOG_NAME) - - for file in godot_files: - ModLoaderSetupUtils.copy_file( - file, file.trim_prefix("res://.godot").insert(0, "res://godot") - ) - - -func get_combined_global_script_class_cache() -> ConfigFile: - ModLoaderSetupLog.info("Load mod loader class cache", LOG_NAME) - var global_script_class_cache_mod_loader := ConfigFile.new() - global_script_class_cache_mod_loader.load( - "res://addons/mod_loader/setup/global_script_class_cache_mod_loader.cfg" - ) - - ModLoaderSetupLog.info("Load game class cache", LOG_NAME) - var global_script_class_cache_game := ConfigFile.new() - global_script_class_cache_game.load("res://.godot/global_script_class_cache.cfg") - - ModLoaderSetupLog.info("Create new class cache", LOG_NAME) - var global_classes_mod_loader := global_script_class_cache_mod_loader.get_value("", "list") - var global_classes_game := global_script_class_cache_game.get_value("", "list") - - ModLoaderSetupLog.info("Combine class cache", LOG_NAME) - var global_classes_combined := [] - global_classes_combined.append_array(global_classes_mod_loader) - global_classes_combined.append_array(global_classes_game) - - ModLoaderSetupLog.info("Save combined class cache", LOG_NAME) - var global_script_class_cache_combined := ConfigFile.new() - global_script_class_cache_combined.set_value("", "list", global_classes_combined) - - return global_script_class_cache_combined - - -func get_gdre_path() -> String: - if OS.get_name() == "Windows": - return "vendor/GDRE/gdre_tools.exe" - - return "" - - -func gdre_download() -> HTTPRequest: - var error := http_request.request(GDRE_DOWNLOAD_WIN) - - if not error == OK: - ModLoaderSetupLog.error("HTTP request failed with Error: %s" % error_string(error), LOG_NAME) - - return http_request - - -func _gdre_download_completed(result, response_code, headers, body) -> void: - ModLoaderSetupLog.info("GDRE download request completed - saving to disk.", LOG_NAME) - if not response_code == 200: - ModLoaderSetupLog.error("An error occurred in the HTTP request - response code: %s." % response_code, LOG_NAME) - return - - print(path.gdre.get_base_dir()) - - if not DirAccess.dir_exists_absolute(path.gdre.get_base_dir()): - var error := DirAccess.make_dir_recursive_absolute(path.gdre.get_base_dir()) - if not error == OK: - ModLoaderSetupLog.error("Failed to create GDRE directory. Error: \"%s\"." % error_string(error) , LOG_NAME) - - var file := FileAccess.open(path.gdre_download, FileAccess.WRITE) - if not file: - ModLoaderSetupLog.error("Failed to write downloaded file. Error: \"%s\"." % error_string(FileAccess.get_open_error()) , LOG_NAME) - return - - file.store_buffer(body) - file.close() - ModLoaderSetupLog.info("GDRE download completed - saved to %s." % path.gdre_download, LOG_NAME) - - # Unpack zip - ModLoaderSetupUtils.unzip(path.gdre_download) - - gdre_download_completed.emit() - - -func restart() -> void: - OS.set_restart_on_exit(true) - get_tree().quit() diff --git a/addons/mod_loader/setup/setup_utils.gd b/addons/mod_loader/setup/setup_utils.gd index 260727ce..78fba4fd 100644 --- a/addons/mod_loader/setup/setup_utils.gd +++ b/addons/mod_loader/setup/setup_utils.gd @@ -284,21 +284,3 @@ static func copy_file(from: String, to: String) -> void: return file_to.store_buffer(file_from_content) - - -static func unzip(src_path: String, dst_path := src_path.get_base_dir()) -> void: - var reader := ZIPReader.new() - var err := reader.open(src_path) - if err != OK: - ModLoaderSetupLog.error("Failed to open zip. Error: \"%s\"." % error_string(err) , LOG_NAME) - return PackedByteArray() - for zip_file_path in reader.get_files(): - ModLoaderSetupLog.info("Reading file %s." % zip_file_path, LOG_NAME) - var buffer := reader.read_file(zip_file_path) - var file := FileAccess.open(dst_path.path_join(zip_file_path), FileAccess.WRITE) - if not file: - ModLoaderSetupLog.error("Failed to write file. Error: \"%s\"." % error_string(FileAccess.get_open_error()) , LOG_NAME) - return - ModLoaderSetupLog.info("Storing file %s." % dst_path.path_join(zip_file_path), LOG_NAME) - file.store_buffer(buffer) - reader.close() From fb1553ec9d2311c2398911de5822953319d05b30 Mon Sep 17 00:00:00 2001 From: Kai Date: Sun, 16 Feb 2025 14:22:10 +0100 Subject: [PATCH 5/6] refactor: :recycle: `--headless` --- addons/mod_loader/mod_loader_setup.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index fcef2334..9b444f3c 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -193,6 +193,7 @@ func handle_injection() -> void: # Add modified binary to the pck func inject(injection_path: String, is_embedded := false) -> void: var arguments := [] + arguments.push_back("--headless") arguments.push_back("--pck-patch=%s" % injection_path) if is_embedded: arguments.push_back("--embed=%s" % injection_path) From f22df10baa01bebd56c0c6059d88b512ecd083fa Mon Sep 17 00:00:00 2001 From: Kai Date: Sun, 16 Feb 2025 17:11:50 +0100 Subject: [PATCH 6/6] chore: :fire: remove pck explorer license --- .../GodotPCKExplorer.Console-LICENSE | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 addons/mod_loader/vendor/GodotPCKExplorer/GodotPCKExplorer.Console-LICENSE diff --git a/addons/mod_loader/vendor/GodotPCKExplorer/GodotPCKExplorer.Console-LICENSE b/addons/mod_loader/vendor/GodotPCKExplorer/GodotPCKExplorer.Console-LICENSE deleted file mode 100644 index 2d2b3c2b..00000000 --- a/addons/mod_loader/vendor/GodotPCKExplorer/GodotPCKExplorer.Console-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 DmitriySalnikov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the Software), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, andor sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.