Skip to content

Commit

Permalink
refactor: ♻️ 4.1 support (#508)
Browse files Browse the repository at this point in the history
* Remove type hint for for...in variable

* Remove another typed for...in variable

* Fix production_no_workshop.tres format

* Fix production_workshop.tres format

* Use custom function instead of type_string

* keep type in `load_mods()`

* refactor: ♻️ `fix_method_super()` use `erase` `insert` instead of `sub`

_ModLoaderModHookPreProcessor.fix_method_super() erased everything after the function body, leaving only the first function in a script.

* refactor: ♻️ remove inline type in for

* style: 🔥 remove white space

* refactor: ♻️ engine version check

* fix: ✏️ fix typo

* refactor: ♻️ engine_version as int

* refactor: ♻️ use version hex and link to PR

* fix: 🐛 fixed type

* refactor: ♻️ use RegEx.sub() and add back text

* Update addons/mod_loader/internal/mod_hook_preprocessor.gd

* Update addons/mod_loader/internal/mod_hook_preprocessor.gd

Co-authored-by: steen <[email protected]>

---------

Co-authored-by: Alexejhero <[email protected]>
Co-authored-by: steen <[email protected]>
  • Loading branch information
3 people authored Jan 30, 2025
1 parent c11cc6a commit c29ac0d
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 12 deletions.
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

0 comments on commit c29ac0d

Please sign in to comment.