From 0e037e7ec85e31abfa418f67cf80190fb77c264e Mon Sep 17 00:00:00 2001 From: Henrique Gemignani Passos Lima Date: Mon, 27 Jan 2025 00:07:40 +0200 Subject: [PATCH] Add RULE format --- src/retro_data_structures/formats/__init__.py | 3 + src/retro_data_structures/formats/rule.py | 80 +++++++++++++++++++ tests/formats/test_rule.py | 21 +++++ 3 files changed, 104 insertions(+) create mode 100644 src/retro_data_structures/formats/rule.py create mode 100644 tests/formats/test_rule.py diff --git a/src/retro_data_structures/formats/__init__.py b/src/retro_data_structures/formats/__init__.py index a58b6d9e..d9f400b9 100644 --- a/src/retro_data_structures/formats/__init__.py +++ b/src/retro_data_structures/formats/__init__.py @@ -38,6 +38,7 @@ from retro_data_structures.formats.msbt import Msbt from retro_data_structures.formats.pak import Pak from retro_data_structures.formats.room import Room +from retro_data_structures.formats.rule import RULE, RuleSet from retro_data_structures.formats.sand import SAND, Sand from retro_data_structures.formats.savw import SAVW, Savw from retro_data_structures.formats.scan import SCAN, Scan @@ -79,6 +80,7 @@ "SWHC": SWHC, "AGSC": AGSC, "ATBL": ATBL, + "RULE": RULE, } ALL_RESOURCE_TYPES = { @@ -114,6 +116,7 @@ "SWHC": Swhc, "AGSC": Agsc, "ATBL": Atbl, + "RULE": RuleSet, } diff --git a/src/retro_data_structures/formats/rule.py b/src/retro_data_structures/formats/rule.py new file mode 100644 index 00000000..a694e055 --- /dev/null +++ b/src/retro_data_structures/formats/rule.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import enum +import typing + +import construct + +from retro_data_structures.adapters.enum_adapter import EnumAdapter +from retro_data_structures.base_resource import AssetType, BaseResource, Dependency +from retro_data_structures.common_types import AssetId32, FourCC +from retro_data_structures.construct_extensions.alignment import AlignTo +from retro_data_structures.construct_extensions.misc import ErrorWithMessage + +if typing.TYPE_CHECKING: + from retro_data_structures.game_check import Game + + +class ComparisonOperator(enum.IntEnum): + LessThan = 0 + LessThanOrEqualTo = 1 + EqualTo = 2 + GreaterThanOrEqualTo = 3 + GreaterThan = 4 + + +class ValueType(enum.IntEnum): + Bool = 0 + Float = 1 + Int32 = 2 + + +RuleCondition = construct.Struct( + id=FourCC, + operator=EnumAdapter(ComparisonOperator, construct.Int8ub), + value_type=EnumAdapter(ValueType, construct.Int8ub), + value=construct.Switch( + construct.this.value_type, + { + ValueType.Bool: construct.Int32ub, + ValueType.Float: construct.Float32b, + ValueType.Int32: construct.Int32ub, + }, + default=ErrorWithMessage(lambda ctx: f"Unknown type: {ctx.value_type}"), + ), +) + +Action = construct.Struct( + id=FourCC, + properties=construct.PrefixedArray(construct.Int8ub, construct.Int32ub), +) + +RuleSetRule = construct.Struct( + conditions=construct.PrefixedArray(construct.Int16ub, RuleCondition), + actions=construct.PrefixedArray(construct.Int16ub, Action), +) + +RULE = construct.Struct( + magic=construct.Const(b"RULE"), + version=construct.Const(1, construct.Int8ub), + parent_rule=AssetId32, + rules=construct.PrefixedArray( + construct.Int16ub, + RuleSetRule, + ), + _align=AlignTo(32), + end=construct.Terminated, +) + + +class RuleSet(BaseResource): + @classmethod + def resource_type(cls) -> AssetType: + return "SAND" + + @classmethod + def construct_class(cls, target_game: Game) -> construct.Construct: + return RULE + + def dependencies_for(self) -> typing.Iterator[Dependency]: + yield Dependency("RULE", self._raw.parent_rule) diff --git a/tests/formats/test_rule.py b/tests/formats/test_rule.py new file mode 100644 index 00000000..58353094 --- /dev/null +++ b/tests/formats/test_rule.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from tests import test_lib + +from retro_data_structures.formats import RuleSet + +if TYPE_CHECKING: + from retro_data_structures.asset_manager import AssetManager + from retro_data_structures.base_resource import AssetId + + +def test_compare_p2(prime2_asset_manager: AssetManager, rule_asset_id: AssetId) -> None: + resource = prime2_asset_manager.get_raw_asset(rule_asset_id) + decoded = RuleSet.parse(resource.data, target_game=prime2_asset_manager.target_game) + encoded = decoded.build() + + decoded2 = RuleSet.parse(encoded, target_game=prime2_asset_manager.target_game) + + assert test_lib.purge_hidden(decoded2.raw) == test_lib.purge_hidden(decoded.raw)