Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 116 additions & 4 deletions addons/mod_loader/internal/mod_hook_preprocessor.gd
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@tool
class_name _ModLoaderModHookPreProcessor
extends RefCounted

Expand All @@ -13,15 +14,18 @@ const HASH_COLLISION_ERROR := \
"MODDING HOOKS ERROR: Hash collision between %s and %s. The collision can be resolved by renaming one of the methods or changing their script's path."
const MOD_LOADER_HOOKS_START_STRING := \
"\n# ModLoader Hooks - The following code has been automatically added by the Godot Mod Loader."
const ENGINE_VERSION_HEX_4_2_2 := 0x040202

## \\bfunc\\b\\s+ -> Match the word 'func' and one or more whitespace characters
## \\b%s\\b -> the function name
## (?:.*\\n*)*?\\s*\\( -> Match any character between zero and unlimited times, but be lazy
## and only do this until a '(' is found.
const REGEX_MATCH_FUNC_WITH_WHITESPACE := "\\bfunc\\b\\s+\\b%s\\b(?:.*\\n*)*?\\s*\\("

static var engine_version_hex: int = Engine.get_version_info().hex

## finds function names used as setters and getters (excluding inline definitions)
## group 2 and 4 contain the xetter names
## group 2 and 4 contain the setter/getter names
var regex_getter_setter := RegEx.create_from_string("(.*?[sg]et\\s*=\\s*)(\\w+)(\\g<1>)?(\\g<2>)?")

## finds every instance where super() is called
Expand Down Expand Up @@ -198,7 +202,8 @@ func is_func_async(func_body_text: String) -> bool:
var in_multiline_string := false
var current_multiline_delimiter := ""

for line: String in lines:
for _line in lines:
var line: String = _line
var char_index := 0
while char_index < line.length():
if in_multiline_string:
Expand Down Expand Up @@ -327,13 +332,33 @@ func edit_vanilla_method(
return text


func fix_method_super(method_name: String, func_body: RegExMatch, text: String) -> String:
func fix_method_super(method_name: String, func_body: RegExMatch, text: String) -> String:
if engine_version_hex < ENGINE_VERSION_HEX_4_2_2:
return fix_method_super_before_4_2_2(method_name, func_body, text)

return regex_super_call.sub(
text, "super.%s" % method_name,
true, func_body.get_start(), func_body.get_end()
)


# https://github.com/godotengine/godot/pull/86052
# Quote:
# When the end argument of RegEx.sub was used,
# it would truncate the Subject String before even doing the substitution.
func fix_method_super_before_4_2_2(method_name: String, func_body: RegExMatch, text: String) -> String:
var text_after_func_body_end := text.substr(func_body.get_end())

text = regex_super_call.sub(
text, "super.%s" % method_name,
true, func_body.get_start(), func_body.get_end()
)

text = text + text_after_func_body_end

return text


static func get_func_body_start_index(closing_paren_index: int, source_code: String) -> int:
if closing_paren_index == -1:
return -1
Expand Down Expand Up @@ -495,7 +520,7 @@ static func get_return_type_string(return_data: Dictionary) -> String:
if return_data.has("class_name") and not str(return_data.class_name).is_empty():
type_base = str(return_data.class_name)
else:
type_base = type_string(return_data.type)
type_base = get_type_name(return_data.type)

var type_hint: String = "" if return_data.hint_string.is_empty() else ("[%s]" % return_data.hint_string)

Expand Down Expand Up @@ -535,3 +560,90 @@ static func get_hook_check_else_string(
"METHOD_ARGS": method_arg_string_names_only
}
)


# This function was taken from
# https://github.com/godotengine/godot/blob/7e67b496ff7e35f66b88adcbdd5b252d01739cbb/modules/gdscript/tests/scripts/utils.notest.gd#L69
# It is used instead of type_string because type_string does not exist in Godot 4.1
static func get_type_name(type: Variant.Type) -> String:
match type:
TYPE_NIL:
return "Nil" # `Nil` in core, `null` in GDScript.
TYPE_BOOL:
return "bool"
TYPE_INT:
return "int"
TYPE_FLOAT:
return "float"
TYPE_STRING:
return "String"
TYPE_VECTOR2:
return "Vector2"
TYPE_VECTOR2I:
return "Vector2i"
TYPE_RECT2:
return "Rect2"
TYPE_RECT2I:
return "Rect2i"
TYPE_VECTOR3:
return "Vector3"
TYPE_VECTOR3I:
return "Vector3i"
TYPE_TRANSFORM2D:
return "Transform2D"
TYPE_VECTOR4:
return "Vector4"
TYPE_VECTOR4I:
return "Vector4i"
TYPE_PLANE:
return "Plane"
TYPE_QUATERNION:
return "Quaternion"
TYPE_AABB:
return "AABB"
TYPE_BASIS:
return "Basis"
TYPE_TRANSFORM3D:
return "Transform3D"
TYPE_PROJECTION:
return "Projection"
TYPE_COLOR:
return "Color"
TYPE_STRING_NAME:
return "StringName"
TYPE_NODE_PATH:
return "NodePath"
TYPE_RID:
return "RID"
TYPE_OBJECT:
return "Object"
TYPE_CALLABLE:
return "Callable"
TYPE_SIGNAL:
return "Signal"
TYPE_DICTIONARY:
return "Dictionary"
TYPE_ARRAY:
return "Array"
TYPE_PACKED_BYTE_ARRAY:
return "PackedByteArray"
TYPE_PACKED_INT32_ARRAY:
return "PackedInt32Array"
TYPE_PACKED_INT64_ARRAY:
return "PackedInt64Array"
TYPE_PACKED_FLOAT32_ARRAY:
return "PackedFloat32Array"
TYPE_PACKED_FLOAT64_ARRAY:
return "PackedFloat64Array"
TYPE_PACKED_STRING_ARRAY:
return "PackedStringArray"
TYPE_PACKED_VECTOR2_ARRAY:
return "PackedVector2Array"
TYPE_PACKED_VECTOR3_ARRAY:
return "PackedVector3Array"
TYPE_PACKED_COLOR_ARRAY:
return "PackedColorArray"
38: # TYPE_PACKED_VECTOR4_ARRAY
return "PackedVector4Array"
push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
return "<unknown type %s>" % type
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=4 uid="uid://bodsw0jyh6rn5"]
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://bodsw0jyh6rn5"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1"]

[resource]
script = ExtResource("1")
enable_mods = true
locked_mods = Array[String]([])
locked_mods = []
log_level = 2
disabled_mods = Array[String]([])
disabled_mods = []
allow_modloader_autoloads_anywhere = false
steam_id = 0
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
ignored_mod_names_in_log = []
load_from_steam_workshop = false
load_from_local = true
8 changes: 4 additions & 4 deletions addons/mod_loader/options/profiles/production_workshop.tres
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=4 uid="uid://cg0vv5k4o71rv"]
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://cg0vv5k4o71rv"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1"]

[resource]
script = ExtResource("1")
enable_mods = true
locked_mods = Array[String]([])
locked_mods = []
log_level = 2
disabled_mods = Array[String]([])
disabled_mods = []
allow_modloader_autoloads_anywhere = false
steam_id = 0
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
ignored_mod_names_in_log = []
load_from_steam_workshop = true
load_from_local = true