1
1
extends SceneTree
2
2
3
- const LOG_NAME := "ModLoader:Setup"
4
3
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"
10
5
11
6
# IMPORTANT: use the ModLoaderLog via this variable within this script!
12
7
# Otherwise, script compilation will break on first load since the class is not defined.
13
8
var ModLoaderSetupLog : Object = load ("res://addons/mod_loader/setup/setup_log.gd" )
14
9
var ModLoaderSetupUtils : Object = load ("res://addons/mod_loader/setup/setup_utils.gd" )
15
10
16
- var path := {}
17
- var file_name := {}
18
- var is_only_setup : bool = ModLoaderSetupUtils .is_running_with_command_line_arg ("--only-setup" )
19
11
var is_setup_create_override_cfg : bool = ModLoaderSetupUtils .is_running_with_command_line_arg (
20
12
"--setup-create-override-cfg"
21
13
)
@@ -39,7 +31,7 @@ func _init() -> void:
39
31
modded_start ()
40
32
return
41
33
42
- setup_modloader ( )
34
+ change_scene_to_file ( "res://addons/mod_loader/setup/setup.tscn" )
43
35
44
36
45
37
# ModLoader already setup - switch to the main scene
@@ -49,290 +41,3 @@ func modded_start() -> void:
49
41
root .set_title ("%s (Modded)" % ProjectSettings .get_setting ("application/config/name" ))
50
42
51
43
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