Skip to content

Commit 95ce66c

Browse files
committed
refactor: ♻️ added GDRE download step
Moved the setup to a setup scene. This setup scene is loaded by the `--script`.
1 parent 6ef6658 commit 95ce66c

File tree

3 files changed

+384
-297
lines changed

3 files changed

+384
-297
lines changed

addons/mod_loader/mod_loader_setup.gd

+2-297
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
extends SceneTree
22

3-
const LOG_NAME := "ModLoader:Setup"
43

5-
const settings := {
6-
"IS_LOADER_SETUP_APPLIED": "application/run/is_loader_setup_applied",
7-
"IS_LOADER_SET_UP": "application/run/is_loader_set_up",
8-
"MOD_LOADER_AUTOLOAD": "autoload/ModLoader",
9-
}
4+
const LOG_NAME := "ModLoader:Setup"
105

116
# IMPORTANT: use the ModLoaderLog via this variable within this script!
127
# Otherwise, script compilation will break on first load since the class is not defined.
138
var ModLoaderSetupLog: Object = load("res://addons/mod_loader/setup/setup_log.gd")
149
var ModLoaderSetupUtils: Object = load("res://addons/mod_loader/setup/setup_utils.gd")
1510

16-
var path := {}
17-
var file_name := {}
18-
var is_only_setup: bool = ModLoaderSetupUtils.is_running_with_command_line_arg("--only-setup")
1911
var is_setup_create_override_cfg: bool = ModLoaderSetupUtils.is_running_with_command_line_arg(
2012
"--setup-create-override-cfg"
2113
)
@@ -39,7 +31,7 @@ func _init() -> void:
3931
modded_start()
4032
return
4133

42-
setup_modloader()
34+
change_scene_to_file("res://addons/mod_loader/setup/setup.tscn")
4335

4436

4537
# ModLoader already setup - switch to the main scene
@@ -49,290 +41,3 @@ func modded_start() -> void:
4941
root.set_title("%s (Modded)" % ProjectSettings.get_setting("application/config/name"))
5042

5143
change_scene_to_file.call_deferred(ProjectSettings.get_setting("application/run/main_scene"))
52-
53-
54-
# Set up the ModLoader as an autoload and register the other global classes.
55-
func setup_modloader() -> void:
56-
ModLoaderSetupLog.info("Setting up ModLoader", LOG_NAME)
57-
58-
# Setup path and file_name dict with all required paths and file names.
59-
setup_file_data()
60-
61-
# Add ModLoader autoload (the * marks the path as autoload)
62-
reorder_autoloads()
63-
ProjectSettings.set_setting(settings.IS_LOADER_SET_UP, true)
64-
65-
# The game needs to be restarted first, before the loader is truly set up
66-
# Set this here and check it elsewhere to prompt the user for a restart
67-
ProjectSettings.set_setting(settings.IS_LOADER_SETUP_APPLIED, false)
68-
69-
if is_setup_create_override_cfg:
70-
handle_override_cfg()
71-
else:
72-
handle_injection()
73-
74-
# ModLoader is set up. A game restart is required to apply the ProjectSettings.
75-
ModLoaderSetupLog.info("ModLoader is set up, a game restart is required.", LOG_NAME)
76-
77-
match true:
78-
# If the --only-setup cli argument is passed, quit with exit code 0
79-
is_only_setup:
80-
quit(0)
81-
# If no cli argument is passed, show message with OS.alert() and user has to restart the game
82-
_:
83-
OS.alert(
84-
"The Godot ModLoader has been set up. The game needs to be restarted to apply the changes. Confirm to restart."
85-
)
86-
restart()
87-
88-
89-
# Reorders the autoloads in the project settings, to get the ModLoader on top.
90-
func reorder_autoloads() -> void:
91-
# remove and re-add autoloads
92-
var original_autoloads := {}
93-
for prop in ProjectSettings.get_property_list():
94-
var name: String = prop.name
95-
if name.begins_with("autoload/"):
96-
var value: String = ProjectSettings.get_setting(name)
97-
original_autoloads[name] = value
98-
99-
ModLoaderSetupLog.info(
100-
"Start reorder autoloads current state: %s" % JSON.stringify(original_autoloads, "\t"),
101-
LOG_NAME
102-
)
103-
104-
for autoload in original_autoloads.keys():
105-
ProjectSettings.set_setting(autoload, null)
106-
107-
# Add ModLoaderStore autoload (the * marks the path as autoload)
108-
ProjectSettings.set_setting(
109-
"autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd"
110-
)
111-
112-
# Add ModLoader autoload (the * marks the path as autoload)
113-
ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd")
114-
115-
# add all previous autoloads back again
116-
for autoload in original_autoloads.keys():
117-
ProjectSettings.set_setting(autoload, original_autoloads[autoload])
118-
119-
var new_autoloads := {}
120-
for prop in ProjectSettings.get_property_list():
121-
var name: String = prop.name
122-
if name.begins_with("autoload/"):
123-
var value: String = ProjectSettings.get_setting(name)
124-
new_autoloads[name] = value
125-
126-
ModLoaderSetupLog.info(
127-
"Reorder autoloads completed - new state: %s" % JSON.stringify(new_autoloads, "\t"),
128-
LOG_NAME
129-
)
130-
131-
132-
# Saves the ProjectSettings to a override.cfg file in the base game directory.
133-
func handle_override_cfg() -> void:
134-
ModLoaderSetupLog.debug("using the override.cfg file", LOG_NAME)
135-
136-
# Make the '.godot' dir public as 'godot' and copy all files to the public dir.
137-
make_project_data_public()
138-
139-
# Combine mod_loader and game global classes
140-
var global_script_class_cache_combined := get_combined_global_script_class_cache()
141-
global_script_class_cache_combined.save("res://godot/global_script_class_cache.cfg")
142-
143-
var _save_custom_error: int = ProjectSettings.save_custom(
144-
ModLoaderSetupUtils.get_override_path()
145-
)
146-
147-
148-
# Creates the project.binary file, adds it to the pck and removes the no longer needed project.binary file.
149-
func handle_injection() -> void:
150-
var is_embedded: bool = not FileAccess.file_exists(path.pck)
151-
var injection_path: String = path.exe if is_embedded else path.pck
152-
var file_extension := injection_path.get_extension()
153-
154-
ModLoaderSetupLog.debug("Start injection", LOG_NAME)
155-
# Create temp dir
156-
ModLoaderSetupLog.debug('Creating temp dir at "%s"' % path.temp_dir_path, LOG_NAME)
157-
DirAccess.make_dir_recursive_absolute(path.temp_dir_path)
158-
159-
# Create project.binary
160-
ModLoaderSetupLog.debug(
161-
'Storing project.binary at "%s"' % path.temp_project_binary_path, LOG_NAME
162-
)
163-
var _error_save_custom_project_binary = ProjectSettings.save_custom(
164-
path.temp_project_binary_path
165-
)
166-
# Create combined global class cache cfg
167-
var combined_global_script_class_cache_file := get_combined_global_script_class_cache()
168-
ModLoaderSetupLog.debug(
169-
'Storing global_script_class_cache at "%s"' % path.temp_global_script_class_cache_path,
170-
LOG_NAME
171-
)
172-
# Create the .godot dir inside the temp dir
173-
DirAccess.make_dir_recursive_absolute(path.temp_dir_path.path_join(".godot"))
174-
# Save the global class cache config file
175-
combined_global_script_class_cache_file.save(path.temp_global_script_class_cache_path)
176-
177-
inject(injection_path, is_embedded)
178-
179-
# Rename vanilla
180-
var modded_path := "%s-modded.%s" % [injection_path.get_basename(), file_extension]
181-
var vanilla_path := "%s-vanilla.%s" % [injection_path.get_basename(), file_extension]
182-
183-
DirAccess.rename_absolute(injection_path, vanilla_path)
184-
ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [injection_path, vanilla_path], LOG_NAME)
185-
186-
# Rename modded
187-
DirAccess.rename_absolute(modded_path, injection_path)
188-
ModLoaderSetupLog.debug('Renamed "%s" to "%s"' % [modded_path, injection_path], LOG_NAME)
189-
190-
clean_up()
191-
192-
193-
# Add modified binary to the pck
194-
func inject(injection_path: String, is_embedded := false) -> void:
195-
var arguments := []
196-
arguments.push_back("--pck-patch=%s" % injection_path)
197-
if is_embedded:
198-
arguments.push_back("--embed=%s" % injection_path)
199-
arguments.push_back(
200-
"--patch-file=%s=%s" % [path.temp_project_binary_path, path.project_binary_path_internal]
201-
)
202-
arguments.push_back(
203-
(
204-
"--patch-file=%s=%s"
205-
% [
206-
path.temp_global_script_class_cache_path,
207-
path.global_script_class_cache_path_internal
208-
]
209-
)
210-
)
211-
arguments.push_back(
212-
(
213-
"--output=%s"
214-
% path.game_base_dir.path_join(
215-
(
216-
"%s-modded.%s"
217-
% [file_name[injection_path.get_extension()], injection_path.get_extension()]
218-
)
219-
)
220-
)
221-
)
222-
223-
# For unknown reasons the output only displays a single "[" - so only the executed arguments are logged.
224-
ModLoaderSetupLog.debug("Injection started: %s %s" % [path.gdre, arguments], LOG_NAME)
225-
var output := []
226-
var _exit_code_inject := OS.execute(path.gdre, arguments, output)
227-
ModLoaderSetupLog.debug("Injection completed: %s" % output, LOG_NAME)
228-
229-
230-
# Removes the temp files
231-
func clean_up() -> void:
232-
ModLoaderSetupLog.debug("Start clean up", LOG_NAME)
233-
DirAccess.remove_absolute(path.temp_project_binary_path)
234-
ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_project_binary_path, LOG_NAME)
235-
DirAccess.remove_absolute(path.temp_global_script_class_cache_path)
236-
ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_global_script_class_cache_path, LOG_NAME)
237-
DirAccess.remove_absolute(path.temp_dir_path.path_join(".godot"))
238-
ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path.path_join(".godot"), LOG_NAME)
239-
DirAccess.remove_absolute(path.temp_dir_path)
240-
ModLoaderSetupLog.debug('Removed: "%s"' % path.temp_dir_path, LOG_NAME)
241-
ModLoaderSetupLog.debug("Clean up completed", LOG_NAME)
242-
243-
244-
# Initialize the path and file_name dictionary
245-
func setup_file_data() -> void:
246-
# C:/path/to/game/game.exe
247-
path.exe = OS.get_executable_path()
248-
# C:/path/to/game/
249-
path.game_base_dir = ModLoaderSetupUtils.get_local_folder_dir()
250-
# C:/path/to/game/addons/mod_loader
251-
path.mod_loader_dir = path.game_base_dir + "addons/mod_loader/"
252-
path.gdre = path.mod_loader_dir + get_gdre_path()
253-
path.temp_dir_path = path.mod_loader_dir + "setup/temp"
254-
path.temp_project_binary_path = path.temp_dir_path + "/project.binary"
255-
path.temp_global_script_class_cache_path = (
256-
path.temp_dir_path
257-
+ "/.godot/global_script_class_cache.cfg"
258-
)
259-
path.global_script_class_cache_path_internal = "res://.godot/global_script_class_cache.cfg"
260-
path.project_binary_path_internal = "res://project.binary"
261-
# can be supplied to override the exe_name
262-
file_name.cli_arg_exe = ModLoaderSetupUtils.get_cmd_line_arg_value("--exe-name")
263-
# can be supplied to override the pck_name
264-
file_name.cli_arg_pck = ModLoaderSetupUtils.get_cmd_line_arg_value("--pck-name")
265-
# game - or use the value of cli_arg_exe_name if there is one
266-
file_name.exe = (
267-
ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true)
268-
if file_name.cli_arg_exe == ""
269-
else file_name.cli_arg_exe
270-
)
271-
# game - or use the value of cli_arg_pck_name if there is one
272-
# using exe_path.get_file() instead of exe_name
273-
# so you don't override the pck_name with the --exe-name cli arg
274-
# the main pack name is the same as the .exe name
275-
# if --main-pack cli arg is not set
276-
file_name.pck = (
277-
ModLoaderSetupUtils.get_file_name_from_path(path.exe, false, true)
278-
if file_name.cli_arg_pck == ""
279-
else file_name.cli_arg_pck
280-
)
281-
# C:/path/to/game/game.pck
282-
path.pck = path.game_base_dir.path_join(file_name.pck + ".pck")
283-
284-
ModLoaderSetupLog.debug_json_print("path: ", path, LOG_NAME)
285-
ModLoaderSetupLog.debug_json_print("file_name: ", file_name, LOG_NAME)
286-
287-
288-
func make_project_data_public() -> void:
289-
ModLoaderSetupLog.info("Register Global Classes", LOG_NAME)
290-
ProjectSettings.set_setting("application/config/use_hidden_project_data_directory", false)
291-
292-
var godot_files = ModLoaderSetupUtils.get_flat_view_dict("res://.godot")
293-
294-
ModLoaderSetupLog.info('Copying all files from "res://.godot" to "res://godot".', LOG_NAME)
295-
296-
for file in godot_files:
297-
ModLoaderSetupUtils.copy_file(
298-
file, file.trim_prefix("res://.godot").insert(0, "res://godot")
299-
)
300-
301-
302-
func get_combined_global_script_class_cache() -> ConfigFile:
303-
ModLoaderSetupLog.info("Load mod loader class cache", LOG_NAME)
304-
var global_script_class_cache_mod_loader := ConfigFile.new()
305-
global_script_class_cache_mod_loader.load(
306-
"res://addons/mod_loader/setup/global_script_class_cache_mod_loader.cfg"
307-
)
308-
309-
ModLoaderSetupLog.info("Load game class cache", LOG_NAME)
310-
var global_script_class_cache_game := ConfigFile.new()
311-
global_script_class_cache_game.load("res://.godot/global_script_class_cache.cfg")
312-
313-
ModLoaderSetupLog.info("Create new class cache", LOG_NAME)
314-
var global_classes_mod_loader := global_script_class_cache_mod_loader.get_value("", "list")
315-
var global_classes_game := global_script_class_cache_game.get_value("", "list")
316-
317-
ModLoaderSetupLog.info("Combine class cache", LOG_NAME)
318-
var global_classes_combined := []
319-
global_classes_combined.append_array(global_classes_mod_loader)
320-
global_classes_combined.append_array(global_classes_game)
321-
322-
ModLoaderSetupLog.info("Save combined class cache", LOG_NAME)
323-
var global_script_class_cache_combined := ConfigFile.new()
324-
global_script_class_cache_combined.set_value("", "list", global_classes_combined)
325-
326-
return global_script_class_cache_combined
327-
328-
329-
func get_gdre_path() -> String:
330-
if OS.get_name() == "Windows":
331-
return "vendor/GDRE/gdre_tools.exe"
332-
333-
return ""
334-
335-
336-
func restart() -> void:
337-
OS.set_restart_on_exit(true)
338-
quit()

0 commit comments

Comments
 (0)