Skip to content

Commit eb46c53

Browse files
authored
Merge branch '4.x' into refactor_self_setup_GDRE
2 parents ebd4ce9 + 5698a12 commit eb46c53

25 files changed

+414
-252
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

2+
.godot
23
# IDEs
34
.idea
45
.vscode
56

67
# mac thing
7-
**/.DS_Store
8+
.DS_Store

addons/mod_loader/api/log.gd

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ static var logged_messages := {
3131
"info": {},
3232
"success": {},
3333
"debug": {},
34+
"hint": {},
3435
}
3536
}
3637

@@ -42,6 +43,8 @@ static var verbosity: VERBOSITY_LEVEL = VERBOSITY_LEVEL.DEBUG
4243
## Array of mods that should be ignored when logging messages (contains mod IDs as strings)
4344
static var ignored_mods: Array[String] = []
4445

46+
## Highlighting color for hint type log messages
47+
static var hint_color := Color("#70bafa")
4548

4649
## This Sub-Class represents a log entry in ModLoader.
4750
class ModLoaderLogEntry:
@@ -205,6 +208,21 @@ static func debug(message: String, mod_name: String, only_once := false) -> void
205208
_log(message, mod_name, "debug", only_once)
206209

207210

211+
## Logs the message. Prefixed HINT and highligted.[br]
212+
## [br]
213+
## [i]Note: Logged with verbosity level at or above debug (-vvv) and in the editor only. Not written to mod loader log.[/i][br]
214+
## Use this to help other developers debug issues by giving them error-specific hints.[br]
215+
## [br]
216+
## [b]Parameters:[/b][br]
217+
## [param message] ([String]): The message to be logged as a debug.[br]
218+
## [param mod_name] ([String]): The name of the mod or ModLoader class associated with this log entry.[br]
219+
## [param only_once] ([bool]): (Optional) If true, the log entry will only be logged once, even if called multiple times. Default is false.[br]
220+
## [br]
221+
## [b]Returns:[/b] [code]void[/code]
222+
static func hint(message: String, mod_name: String, only_once := false) -> void:
223+
_log(message, mod_name, "hint", only_once)
224+
225+
208226
## Logs the message formatted with [method JSON.print]. Prefixed DEBUG.[br]
209227
## [br]
210228
## [i]Note: Logged with verbosity level at or above debug (-vvv).[/i] [br]
@@ -361,8 +379,6 @@ static func get_all_entries_as_string(log_entries: Array) -> Array:
361379
return log_entry_strings
362380

363381

364-
365-
366382
# Internal log functions
367383
# =============================================================================
368384

@@ -413,6 +429,9 @@ static func _log(message: String, mod_name: String, log_type: String = "info", o
413429
if verbosity >= VERBOSITY_LEVEL.DEBUG:
414430
print(log_entry.get_prefix() + message)
415431
_write_to_log_file(log_entry.get_entry())
432+
"hint":
433+
if OS.has_feature("editor") and verbosity >= VERBOSITY_LEVEL.DEBUG:
434+
print_rich("[color=%s]%s[/color]" % [hint_color.to_html(false), log_entry.get_prefix() + message])
416435

417436

418437
static func _is_mod_name_ignored(mod_name: String) -> bool:

addons/mod_loader/api/mod.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static func install_script_hooks(vanilla_script_path: String, hook_script_path:
100100

101101
var closest_vanilla: String = vanilla_methods.front()
102102
if closest_vanilla.similarity(hook.name) > 0.8:
103-
ModLoaderLog.debug(
103+
ModLoaderLog.hint(
104104
'Did you mean "%s" instead of "%s"?'
105105
% [closest_vanilla, hook.name], LOG_NAME
106106
)

addons/mod_loader/api/mod_manager.gd

Lines changed: 0 additions & 77 deletions
This file was deleted.

addons/mod_loader/internal/hooks.gd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ static func add_hook(mod_callable: Callable, script_path: String, method_name: S
2020
)
2121

2222
if not ModLoaderStore.hooked_script_paths.has(script_path):
23-
ModLoaderStore.hooked_script_paths[script_path] = true
23+
ModLoaderStore.hooked_script_paths[script_path] = [method_name]
24+
elif not ModLoaderStore.hooked_script_paths[script_path].has(method_name):
25+
ModLoaderStore.hooked_script_paths[script_path].append(method_name)
2426

2527

2628
static func call_hooks(vanilla_method: Callable, args: Array, hook_hash: int) -> Variant:

addons/mod_loader/internal/mod_hook_packer.gd

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,27 @@ static func start() -> void:
3232
_ModLoaderCache.remove_data("hooks")
3333
error = zip_writer.open(mod_hook_pack_path)
3434
else:
35-
# If there is a pack already append to it
35+
# If there is a pack already, append to it
3636
error = zip_writer.open(mod_hook_pack_path, ZIPPacker.APPEND_ADDINZIP)
3737
if not error == OK:
38-
ModLoaderLog.error("Error(%s) writing to zip file at path: %s" % [error, mod_hook_pack_path], LOG_NAME)
38+
ModLoaderLog.error("Error (%s) writing to hooks zip, consider deleting this file: %s" % [error, mod_hook_pack_path], LOG_NAME)
3939
return
4040

41+
ModLoaderLog.debug("Scripts requiring hooks: %s" % [ModLoaderStore.hooked_script_paths], LOG_NAME)
42+
4143
var cache := _ModLoaderCache.get_data("hooks")
42-
var script_paths_with_hook: Array = [] if cache.is_empty() else cache.script_paths
43-
var new_hooks_created := false
44+
var cached_script_paths: Dictionary = {} if cache.is_empty() or not cache.has("hooked_script_paths") else cache.hooked_script_paths
45+
if cached_script_paths == ModLoaderStore.hooked_script_paths:
46+
ModLoaderLog.info("Scripts are already processed according to cache, skipping process.", LOG_NAME)
47+
zip_writer.close()
48+
return
4449

50+
var new_hooks_created := false
4551
# Get all scripts that need processing
46-
ModLoaderLog.debug("Scripts requiring hooks: %s" % [ModLoaderStore.hooked_script_paths.keys()], LOG_NAME)
4752
for path in ModLoaderStore.hooked_script_paths.keys():
48-
if path in script_paths_with_hook:
49-
continue
50-
51-
var processed_source_code := hook_pre_processor.process_script_verbose(path)
53+
var method_mask: Array[String] = []
54+
method_mask.assign(ModLoaderStore.hooked_script_paths[path])
55+
var processed_source_code := hook_pre_processor.process_script_verbose(path, false, method_mask)
5256

5357
# Skip writing to the zip if no new hooks were created for this script
5458
if not hook_pre_processor.script_paths_hooked.has(path):
@@ -61,10 +65,9 @@ static func start() -> void:
6165

6266
ModLoaderLog.debug("Hooks created for script: %s" % path, LOG_NAME)
6367
new_hooks_created = true
64-
script_paths_with_hook.push_back(path)
6568

6669
if new_hooks_created:
67-
_ModLoaderCache.update_data("hooks", {"script_paths": script_paths_with_hook})
70+
_ModLoaderCache.update_data("hooks", {"hooked_script_paths": ModLoaderStore.hooked_script_paths})
6871
_ModLoaderCache.save_to_file()
6972
ModLoader.new_hooks_created.emit()
7073

addons/mod_loader/internal/mod_hook_preprocessor.gd

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,21 @@ var script_paths_hooked := {}
5050
func process_begin() -> void:
5151
hashmap.clear()
5252

53-
54-
func process_script_verbose(path: String, enable_hook_check := false) -> String:
53+
## Calls [method process_script] with additional logging
54+
func process_script_verbose(path: String, enable_hook_check := false, method_mask: Array[String] = []) -> String:
5555
var start_time := Time.get_ticks_msec()
5656
ModLoaderLog.debug("Start processing script at path: %s" % path, LOG_NAME)
57-
var processed := process_script(path, enable_hook_check)
57+
var processed := process_script(path, enable_hook_check, method_mask)
5858
ModLoaderLog.debug("Finished processing script at path: %s in %s ms" % [path, Time.get_ticks_msec() - start_time], LOG_NAME)
5959
return processed
6060

6161

62-
func process_script(path: String, enable_hook_check := false) -> String:
62+
## [param path]: File path to the script to be processed.[br]
63+
## [param enable_hook_check]: Adds a check that ModLoaderStore.any_mod_hooked is [code]true[/code] to the processed method, reducing hash checks.[br]
64+
## [param method_mask]: If provided, only methods in this [Array] will be processed.[br]
65+
func process_script(path: String, enable_hook_check := false, method_mask: Array[String] = []) -> String:
6366
var current_script := load(path) as GDScript
64-
6567
var source_code := current_script.source_code
66-
6768
var source_code_additions := ""
6869

6970
# We need to stop all vanilla methods from forming inheritance chains,
@@ -72,32 +73,35 @@ func process_script(path: String, enable_hook_check := false) -> String:
7273
var method_store: Array[String] = []
7374

7475
var getters_setters := collect_getters_and_setters(source_code)
75-
7676
var moddable_methods := current_script.get_script_method_list().filter(
7777
is_func_moddable.bind(source_code, getters_setters)
7878
)
7979

8080
var methods_hooked := {}
81-
8281
for method in moddable_methods:
8382
if method.name in method_store:
8483
continue
8584

86-
var prefix := "%s%s_" % [METHOD_PREFIX, class_prefix]
85+
var full_prefix := "%s%s_" % [METHOD_PREFIX, class_prefix]
8786

8887
# Check if the method name starts with the prefix added by `edit_vanilla_method()`.
8988
# This indicates that the method was previously processed, possibly by the export plugin.
9089
# If so, store the method name (excluding the prefix) in `methods_hooked`.
91-
if method.name.begins_with(prefix):
92-
var method_name_vanilla: String = method.name.trim_prefix(prefix)
90+
if method.name.begins_with(full_prefix):
91+
var method_name_vanilla: String = method.name.trim_prefix(full_prefix)
9392
methods_hooked[method_name_vanilla] = true
9493
continue
95-
9694
# This ensures we avoid creating a hook for the 'imposter' method, which
9795
# is generated by `build_mod_hook_string()` and has the vanilla method name.
9896
if methods_hooked.has(method.name):
9997
continue
10098

99+
# If a mask is provided, only methods with their name in the mask will be converted.
100+
# Can't be filtered before the loop since it removes prefixed methods required by the previous check.
101+
if not method_mask.is_empty():
102+
if not method.name in method_mask:
103+
continue
104+
101105
var type_string := get_return_type_string(method.return)
102106
var is_static := true if method.flags == METHOD_FLAG_STATIC + METHOD_FLAG_NORMAL else false
103107

@@ -152,7 +156,7 @@ func process_script(path: String, enable_hook_check := false) -> String:
152156
is_static,
153157
is_async,
154158
hook_id,
155-
METHOD_PREFIX + class_prefix,
159+
full_prefix,
156160
enable_hook_check
157161
)
158162

@@ -167,7 +171,7 @@ func process_script(path: String, enable_hook_check := false) -> String:
167171
source_code,
168172
func_def,
169173
func_body,
170-
METHOD_PREFIX + class_prefix
174+
full_prefix
171175
)
172176
source_code_additions += "\n%s" % mod_loader_hook_string
173177

@@ -327,15 +331,15 @@ func edit_vanilla_method(
327331
) -> String:
328332
text = fix_method_super(method_name, func_body, text)
329333
text = text.erase(func_def.get_start(), func_def.get_end() - func_def.get_start())
330-
text = text.insert(func_def.get_start(), "func %s_%s(" % [prefix, method_name])
334+
text = text.insert(func_def.get_start(), "func %s%s(" % [prefix, method_name])
331335

332336
return text
333337

334338

335-
func fix_method_super(method_name: String, func_body: RegExMatch, text: String) -> String:
339+
func fix_method_super(method_name: String, func_body: RegExMatch, text: String) -> String:
336340
if engine_version_hex < ENGINE_VERSION_HEX_4_2_2:
337341
return fix_method_super_before_4_2_2(method_name, func_body, text)
338-
342+
339343
return regex_super_call.sub(
340344
text, "super.%s" % method_name,
341345
true, func_body.get_start(), func_body.get_end()
@@ -344,18 +348,18 @@ func fix_method_super(method_name: String, func_body: RegExMatch, text: String)
344348

345349
# https://github.com/godotengine/godot/pull/86052
346350
# Quote:
347-
# When the end argument of RegEx.sub was used,
351+
# When the end argument of RegEx.sub was used,
348352
# it would truncate the Subject String before even doing the substitution.
349353
func fix_method_super_before_4_2_2(method_name: String, func_body: RegExMatch, text: String) -> String:
350354
var text_after_func_body_end := text.substr(func_body.get_end())
351-
355+
352356
text = regex_super_call.sub(
353357
text, "super.%s" % method_name,
354358
true, func_body.get_start(), func_body.get_end()
355359
)
356-
360+
357361
text = text + text_after_func_body_end
358-
362+
359363
return text
360364

361365

@@ -397,10 +401,9 @@ static func build_mod_hook_string(
397401
return_string, await_string, method_prefix, method_name, method_arg_string_names_only
398402
) if enable_hook_check else ""
399403

400-
401404
return """
402405
{STATIC}func {METHOD_NAME}({METHOD_PARAMS}){RETURN_TYPE_STRING}:
403-
{HOOK_CHECK}{RETURN}{AWAIT}_ModLoaderHooks.call_hooks{ASYNC}({METHOD_PREFIX}_{METHOD_NAME}, [{METHOD_ARGS}], {HOOK_ID}){HOOK_CHECK_ELSE}
406+
{HOOK_CHECK}{RETURN}{AWAIT}_ModLoaderHooks.call_hooks{ASYNC}({METHOD_PREFIX}{METHOD_NAME}, [{METHOD_ARGS}], {HOOK_ID}){HOOK_CHECK_ELSE}
404407
""".format({
405408
"METHOD_PREFIX": method_prefix,
406409
"METHOD_NAME": method_name,
@@ -551,7 +554,7 @@ static func get_hook_check_else_string(
551554
method_name: String,
552555
method_arg_string_names_only: String
553556
) -> String:
554-
return "\n\telse:\n\t\t{RETURN}{AWAIT}{METHOD_PREFIX}_{METHOD_NAME}({METHOD_ARGS})".format(
557+
return "\n\telse:\n\t\t{RETURN}{AWAIT}{METHOD_PREFIX}{METHOD_NAME}({METHOD_ARGS})".format(
555558
{
556559
"RETURN": return_string,
557560
"AWAIT": await_string,

addons/mod_loader/internal/path.gd

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,11 @@ static func get_mod_paths_from_all_sources() -> Array[String]:
194194
var mod_paths: Array[String] = []
195195

196196
var mod_dirs := get_dir_paths_in_dir(get_unpacked_mods_dir_path())
197-
mod_paths.append_array(mod_dirs)
197+
198+
if ModLoaderStore.has_feature.editor or ModLoaderStore.ml_options.load_from_unpacked:
199+
mod_paths.append_array(mod_dirs)
200+
else:
201+
ModLoaderLog.info("Loading mods from \"res://mods-unpacked\" is disabled.", LOG_NAME)
198202

199203
if ModLoaderStore.ml_options.load_from_local:
200204
var mods_dir := get_path_to_mods()

0 commit comments

Comments
 (0)