11extends  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.
138var  ModLoaderSetupLog : Object  =  load ("res://addons/mod_loader/setup/setup_log.gd" )
149var  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" )
1911var  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