Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

even more legacy compat fixes #24

Merged
merged 6 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@

### Legacy Compat v1.2
- Fixed that some more legacy mods would not auto-enable properly.
- Added more fixups for previously unreported issues in Arcania, BL2Fix, and Reign of Giants.
- Added more fixups for previously unreported issues in Arcania, Better Damage Feedback, BL2Fix,
Exodus, Reign of Giants, and Reward Reroller.

### [Mods Base v1.7](https://github.com/bl-sdk/mods_base/blob/master/Readme.md#v17)
> - The "Update Available" notification should now immediately go away upon updating, instead of
> waiting a day for the next check.
>
> - Changed the functions the keybind implementation should overwrite from `KeybindType.enable` to
> `KeybindType._enable` (+ same for disable). These functions don't need to set `is_enabled`.

Expand Down
17 changes: 16 additions & 1 deletion src/legacy_compat/meta_path_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ def find_spec( # noqa: D102

# Imported by the init script, accept both `sdk_mods` folder in the release and the
# `src` folder for if developing in this repo

# BL2Fix does some redundant random.seed() setting. Py 3.11 removed support for setting
# the seed from an arbitrary type, so just completely get rid of the calls.
case (("src" | "sdk_mods"), "Mods.BL2Fix"):
return spec_with_replacements(
fullname,
path,
target,
# Redundant, removed support for arbitrary types in py 3.11
(rb"random\.seed\(datetime\.datetime\.now\(\)\)", b""),
)

Expand All @@ -173,5 +173,20 @@ def find_spec( # noqa: D102
(rb'@ModMenu\.Hook\("Engine\.GameInfo\.PostCommitMapChange"\)', b""),
)

# This is a use case the new SDK kind of broke. Reward Rreroller passed the
# `MissionStatusPlayerData:ObjectivesProgress` field to `ShouldGrantAlternateReward`.
# While they're both arrays of ints called `ObjectivesProgress`, since they're different
# properties they're no longer compatible. Turn it into a list to make a copy.
case (("src" | "sdk_mods"), "Mods.RewardReroller"):
return spec_with_replacements(
fullname,
path,
target,
(
rb"mission\.ShouldGrantAlternateReward\(progress\)",
b"mission.ShouldGrantAlternateReward(list(progress))",
),
)

case _, _:
return None
75 changes: 46 additions & 29 deletions src/legacy_compat/unrealsdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
UObject,
UObjectProperty,
UProperty,
UScriptStruct,
UStrProperty,
UStruct,
UStructProperty,
Expand Down Expand Up @@ -138,8 +137,11 @@ def ConstructObject(
Error: None = None, # noqa: ARG001
InstanceGraph: None = None, # noqa: ARG001
bAssumeTemplateIsArchetype: int = 0, # noqa: ARG001
) -> UObject:
return construct_object(Class, Outer, Name, SetFlags, Template)
) -> UObject | None:
try:
return construct_object(Class, Outer, Name, SetFlags, Template)
except RuntimeError:
return None


type _SDKHook = Callable[[UObject, UFunction, FStruct], bool | None]
Expand Down Expand Up @@ -295,28 +297,11 @@ def KeepAlive(obj: UObject, /) -> None:
_default_func_call = BoundFunction.__call__


def _create_struct_from_tuples(struct: UScriptStruct, value: tuple[Any, ...]) -> WrappedStruct:
"""
Recursively creates a wrapped struct from it's tuple equivalent.

Args:
struct: The struct type to create:
value: The tuple to create it with.
Returns:
The new struct.
"""
return WrappedStruct(
struct,
*(
_create_struct_from_tuples(prop.Struct, inner_val) # pyright: ignore[reportUnknownArgumentType]
if isinstance(prop, UStructProperty) and isinstance(inner_val, tuple)
else inner_val
for prop, inner_val in zip(struct._properties(), value, strict=False)
),
)


def _convert_struct_tuple_if_required(prop: UProperty, value: Any) -> Any:
def _convert_struct_tuple_if_required(
prop: UProperty,
value: Any,
_ignore_array_dim: bool = False,
) -> Any:
"""
Convert any tuple-based structs in the given value into Wrapped Structs.

Expand All @@ -327,16 +312,29 @@ def _convert_struct_tuple_if_required(prop: UProperty, value: Any) -> Any:
The possibly converted value.
"""

# If it's a fixed array of structs, need to convert each inner value
if not _ignore_array_dim and prop.ArrayDim > 1 and isinstance(prop, UStructProperty):
return tuple(
_convert_struct_tuple_if_required(prop, inner_val, _ignore_array_dim=True)
for inner_val in value # type: ignore
)

# If it's a struct being set as a tuple directly
if isinstance(prop, UStructProperty) and isinstance(value, tuple):
return _create_struct_from_tuples(prop.Struct, value) # pyright: ignore[reportUnknownArgumentType]
return WrappedStruct(
prop.Struct,
*(
_convert_struct_tuple_if_required(inner_prop, inner_val)
for inner_prop, inner_val in zip(prop.Struct._properties(), value, strict=False) # type: ignore
),
)

# If it's an array of structs, need to convert each value
if isinstance(prop, UArrayProperty) and isinstance(prop.Inner, UStructProperty):
seq_value: Sequence[Any] = value

return tuple(
_create_struct_from_tuples(prop.Inner.Struct, inner_val) # pyright: ignore[reportUnknownArgumentType]
_convert_struct_tuple_if_required(prop.Inner, inner_val)
if isinstance(inner_val, tuple)
else inner_val
for inner_val in seq_value
Expand Down Expand Up @@ -432,6 +430,9 @@ def _uobject_setattr(self: UObject, name: str, value: Any) -> None:

@wraps(UObject.__repr__)
def _uobject_repr(self: UObject) -> str:
if self is None or self.Class is None: # type: ignore
return "(null)"

current = self
output = f"{self.Name}"
while current := current.Outer:
Expand Down Expand Up @@ -614,10 +615,24 @@ def _ustructproperty_get_struct(self: UStructProperty) -> UStruct:


@staticmethod
def uobject_path_name(obj: UObject, /) -> str:
def _uobject_path_name(obj: UObject, /) -> str:
return obj._path_name()


def _wrapped_struct_structType_getter(self: WrappedStruct) -> UStruct:
return self._type


def _wrapped_struct_structType_setter(self: WrappedStruct, val: UStruct) -> None:
self._type = val


_wrapped_struct_structType = property( # noqa: N816
_wrapped_struct_structType_getter,
_wrapped_struct_structType_setter,
)


@contextmanager
def _unreal_method_compat_handler() -> Iterator[None]:
UObject.__getattr__ = _uobject_getattr
Expand All @@ -634,8 +649,9 @@ def _unreal_method_compat_handler() -> Iterator[None]:

UObject.FindObjectsContaining = _uobject_find_objects_containing # type: ignore
UStructProperty.GetStruct = _ustructproperty_get_struct # type: ignore
UObject.PathName = uobject_path_name # type: ignore
UObject.PathName = _uobject_path_name # type: ignore
UObject.GetFullName = _uobject_repr # type: ignore
WrappedStruct.structType = _wrapped_struct_structType # type: ignore

try:
yield
Expand All @@ -656,6 +672,7 @@ def _unreal_method_compat_handler() -> Iterator[None]:
del UStructProperty.GetStruct # type: ignore
del UObject.PathName # type: ignore
del UObject.GetFullName # type: ignore
del WrappedStruct.structType # type: ignore


compat_handlers.append(_unreal_method_compat_handler)
2 changes: 1 addition & 1 deletion src/mods_base
Submodule mods_base updated 2 files
+4 −1 Readme.md
+35 −8 mod_list.py
Loading