diff --git a/pyproject.toml b/pyproject.toml index 11a74f9..10984e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "construct>=2.10.0", "lzokay", "nod>=1.7.0", + "typing-extensions>=4.0.0" ] dynamic = ["version"] diff --git a/src/retro_data_structures/asset_manager.py b/src/retro_data_structures/asset_manager.py index 0933db2..db51337 100644 --- a/src/retro_data_structures/asset_manager.py +++ b/src/retro_data_structures/asset_manager.py @@ -21,7 +21,7 @@ Resource, resolve_asset_id, ) -from retro_data_structures.exceptions import UnknownAssetId +from retro_data_structures.exceptions import DependenciesHandledElsewhere, UnknownAssetId from retro_data_structures.formats import Dgrp, dependency_cheating from retro_data_structures.formats.audio_group import Agsc, Atbl from retro_data_structures.formats.pak import Pak @@ -116,7 +116,7 @@ class AssetManager: _modified_resources: dict[AssetId, RawResource | None] _in_memory_paks: dict[str, Pak] _custom_asset_ids: dict[str, AssetId] - _audio_group_dependency: Dgrp | None = None + _audio_group_dependency: tuple[Dgrp, ...] | None = None _cached_dependencies: dict[AssetId, tuple[Dependency, ...]] _cached_ancs_per_char_dependencies: defaultdict[AssetId, dict[int, tuple[Dependency, ...]]] @@ -364,15 +364,9 @@ def ensure_present(self, pak_name: str, asset_id: NameOrAssetId): if pak_name not in self._paks_for_asset_id[asset_id]: self._ensured_asset_ids[pak_name].add(asset_id) - # Ensure the asset's dependencies are present as well - for dep in self.get_dependencies_for_asset(asset_id): - if dep.id == asset_id: - continue - self.ensure_present(pak_name, dep.id) - def get_pak(self, pak_name: str) -> Pak: if pak_name not in self._ensured_asset_ids: - raise ValueError(f"Unknown pak_name: {pak_name}") + raise ValueError(f"Unknown pak_name: {pak_name}. Known names: {tuple(self._ensured_asset_ids.keys())}") if pak_name not in self._in_memory_paks: logger.info("Reading %s", pak_name) @@ -408,6 +402,10 @@ def _get_dependencies_for_asset(self, asset_id: NameOrAssetId, must_exist: bool, elif formats.has_resource_type(asset_type): if self.get_asset_format(asset_id).has_dependencies(self.target_game): deps = tuple(self.get_parsed_asset(asset_id).dependencies_for()) + deps += tuple(self.target_game.special_ancs_dependencies(asset_id)) + + else: + logger.warning(f"Potential missing assets for {asset_type} {asset_id}") logger.debug(f"Adding {asset_id:#8x} deps to cache...") dep_cache[asset_id] = deps @@ -417,7 +415,11 @@ def _get_dependencies_for_asset(self, asset_id: NameOrAssetId, must_exist: bool, def get_dependencies_for_asset(self, asset_id: NameOrAssetId, *, must_exist: bool = False) -> Iterator[Dependency]: override = asset_id in self.target_game.mlvl_dependencies_to_ignore - for it in self._get_dependencies_for_asset(asset_id, must_exist): + try: + deps = self._get_dependencies_for_asset(asset_id, must_exist) + except DependenciesHandledElsewhere: + return + for it in deps: yield Dependency(it.type, it.id, it.exclude_for_mlvl or override) def get_dependencies_for_ancs(self, asset_id: NameOrAssetId, char_index: int | None = None): @@ -477,12 +479,17 @@ def get_audio_group_dependency(self, sound_id: int) -> Iterator[Dependency]: return if self._audio_group_dependency is None: - # audio_groups_single_player_DGRP - self._audio_group_dependency = self.get_file(0x31CB5ADB) + self._audio_group_dependency = tuple( + self.get_file(asset, Dgrp) + for asset in self.target_game.audio_group_dependencies() + ) dep = Dependency("AGSC", agsc, False) - if dep in self._audio_group_dependency.direct_dependencies: - yield Dependency("AGSC", agsc, True) + if any( + (dep in deps.direct_dependencies) + for deps in self._audio_group_dependency + ): + return else: yield dep diff --git a/src/retro_data_structures/base_resource.py b/src/retro_data_structures/base_resource.py index 272e8a5..23d12ae 100644 --- a/src/retro_data_structures/base_resource.py +++ b/src/retro_data_structures/base_resource.py @@ -1,9 +1,13 @@ from __future__ import annotations +import logging import typing import uuid from construct import Construct, Container +from typing_extensions import Self + +from retro_data_structures.exceptions import DependenciesHandledElsewhere if typing.TYPE_CHECKING: from retro_data_structures.asset_manager import AssetManager @@ -18,6 +22,13 @@ class Dependency(typing.NamedTuple): type: AssetType id: AssetId exclude_for_mlvl: bool = False + can_duplicate: bool = False + + def __repr__(self): + s = f"Dep {self.type} 0x{self.id:08X}" + if self.exclude_for_mlvl: + s += " (non-MLVL)" + return s class BaseResource: @@ -40,7 +51,7 @@ def resource_type(cls) -> AssetType: @classmethod def parse(cls, data: bytes, target_game: Game, - asset_manager: AssetManager | None = None) -> BaseResource: + asset_manager: AssetManager | None = None) -> Self: return cls(cls.construct_class(target_game).parse(data, target_game=target_game), target_game, asset_manager) @@ -58,6 +69,13 @@ def has_dependencies(cls, target_game: Game) -> bool: except (KeyError, AttributeError): return True + except DependenciesHandledElsewhere: + return False + + except NotImplementedError: + logging.warning("Potential missing dependencies for %s", cls.resource_type()) + return False + def dependencies_for(self) -> typing.Iterator[Dependency]: raise NotImplementedError() @@ -66,12 +84,27 @@ def raw(self) -> Container: return self._raw +class AssetId32(int): + def __repr__(self) -> str: + return f"{self:#010x}" + + +class AssetId64(int): + def __repr__(self) -> str: + return f"{self:#018x}" + + def resolve_asset_id(game: Game, value: NameOrAssetId) -> AssetId: if isinstance(value, str): value = game.hash_asset_id(value) - if game.uses_guid_as_asset_id and isinstance(value, int): - return uuid.UUID(int=value) + if isinstance(value, int): + if game.uses_guid_as_asset_id: + return uuid.UUID(int=value) + elif game.uses_asset_id_32: + return AssetId32(value) + elif game.uses_asset_id_64: + return AssetId64(value) return value @@ -79,5 +112,6 @@ def resolve_asset_id(game: Game, value: NameOrAssetId) -> AssetId: class RawResource(typing.NamedTuple): type: AssetType data: bytes + compressed: bool = False Resource = RawResource | BaseResource diff --git a/src/retro_data_structures/exceptions.py b/src/retro_data_structures/exceptions.py index e244de7..63deddc 100644 --- a/src/retro_data_structures/exceptions.py +++ b/src/retro_data_structures/exceptions.py @@ -1,5 +1,9 @@ +from __future__ import annotations -from retro_data_structures.base_resource import AssetId +import typing + +if typing.TYPE_CHECKING: + from retro_data_structures.base_resource import AssetId def format_asset_id(asset_id: AssetId) -> str: @@ -23,3 +27,7 @@ def __init__(self, asset_id, reason: str): super().__init__(f"Unable to decode asset id {format_asset_id(asset_id)}: {reason}") self.asset_id = asset_id self.reason = reason + + +class DependenciesHandledElsewhere(Exception): + pass diff --git a/src/retro_data_structures/formats/__init__.py b/src/retro_data_structures/formats/__init__.py index e477e8d..3a807a7 100644 --- a/src/retro_data_structures/formats/__init__.py +++ b/src/retro_data_structures/formats/__init__.py @@ -31,7 +31,8 @@ ) from retro_data_structures.formats.evnt import EVNT, Evnt from retro_data_structures.formats.mapa import MAPA, Mapa -from retro_data_structures.formats.mapw import Mapw +from retro_data_structures.formats.mapu import MAPU, Mapu +from retro_data_structures.formats.mapw import MAPW, Mapw from retro_data_structures.formats.mlvl import MLVL, Mlvl from retro_data_structures.formats.mrea import MREA, Mrea from retro_data_structures.formats.msbt import Msbt @@ -54,6 +55,8 @@ "DGRP": DGRP, "EVNT": EVNT, "MAPA": MAPA, + "MAPW": MAPW, + "MAPU": MAPU, "MLVL": MLVL, "MREA": MREA, "PART": PART, @@ -84,6 +87,7 @@ "DGRP": Dgrp, "EVNT": Evnt, "MAPA": Mapa, + "MAPU": Mapu, "MAPW": Mapw, "MLVL": Mlvl, "MREA": Mrea, diff --git a/src/retro_data_structures/formats/ancs.py b/src/retro_data_structures/formats/ancs.py index 832c4d5..4588b3c 100644 --- a/src/retro_data_structures/formats/ancs.py +++ b/src/retro_data_structures/formats/ancs.py @@ -213,12 +213,14 @@ def construct_class(cls, target_game: Game) -> construct.Construct: return ANCS def ancs_dependencies_for(self, char_index: int | None) -> typing.Iterator[Dependency]: + def char_anims(char) -> typing.Iterator[tuple[int, str]]: + for anim_name in char.animation_names: + yield next((i, a) for i, a in enumerate(self.raw.animation_set.animations) + if a.name == anim_name.name) def char_deps(char): yield from char_dependencies_for(char, self.asset_manager) - for anim_name in char.animation_names: - anim_index, anim = next((i, a) for i, a in enumerate(self.raw.animation_set.animations) - if a.name == anim_name.name) + for anim_index, anim in char_anims(char): yield from meta_animation.dependencies_for(anim.meta, self.asset_manager) if self.raw.animation_set.animation_resources is not None: diff --git a/src/retro_data_structures/formats/audio_group.py b/src/retro_data_structures/formats/audio_group.py index 443db9e..effe8fa 100644 --- a/src/retro_data_structures/formats/audio_group.py +++ b/src/retro_data_structures/formats/audio_group.py @@ -141,6 +141,9 @@ def construct_class(cls, target_game: Game) -> Construct: def resource_type(cls) -> AssetType: return "ATBL" + def dependencies_for(self) -> typing.Iterator[Dependency]: + yield from [] + class Agsc(BaseResource): @classmethod diff --git a/src/retro_data_structures/formats/dependency_cheating.py b/src/retro_data_structures/formats/dependency_cheating.py index 6692737..5c6b0d0 100644 --- a/src/retro_data_structures/formats/dependency_cheating.py +++ b/src/retro_data_structures/formats/dependency_cheating.py @@ -7,12 +7,13 @@ import construct from retro_data_structures.base_resource import AssetType, Dependency, RawResource -from retro_data_structures.common_types import AssetId32, String +from retro_data_structures.common_types import AssetId32, FourCC, String from retro_data_structures.construct_extensions.alignment import AlignTo from retro_data_structures.data_section import DataSection from retro_data_structures.exceptions import UnknownAssetId from retro_data_structures.formats import Part, effect_script from retro_data_structures.formats.hier import Hier +from retro_data_structures.formats.tree import Tree from retro_data_structures.game_check import Game if typing.TYPE_CHECKING: @@ -42,12 +43,23 @@ def _cheat(stream: bytes, asset_manager: AssetManager) -> typing.Iterator[Depend def csng_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing.Iterator[Dependency]: - yield from asset_manager.get_dependencies_for_asset(_csng.parse(asset)) + yield from asset_manager.get_dependencies_for_asset(_csng.parse(asset.data)) def dumb_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing.Iterator[Dependency]: - hier = Hier.parse(asset.data, asset_manager.target_game, asset_manager) - yield from hier.dependencies_for() + try: + magic = FourCC.parse(asset.data) + except Exception: + raise UnableToCheatError() + + if magic == "HIER": + hier = Hier.parse(asset.data, asset_manager.target_game, asset_manager) + yield from hier.dependencies_for() + elif magic == "TREE": + tree = Tree.parse(asset.data, asset_manager.target_game, asset_manager) + yield from tree.dependencies_for() + else: + raise UnableToCheatError() _frme = construct.FocusedSeq( @@ -55,7 +67,10 @@ def dumb_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing construct.Int32ub, "deps" / construct.PrefixedArray( construct.Int32ub, - construct.Int32ub + construct.Struct( + type=FourCC, + id=construct.Int32ub + ) ) ) @@ -63,7 +78,7 @@ def dumb_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing def frme_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing.Iterator[Dependency]: for dep in _frme.parse(asset.data): try: - yield from asset_manager.get_dependencies_for_asset(dep) + yield from asset_manager.get_dependencies_for_asset(dep.id) except UnknownAssetId: raise UnableToCheatError() @@ -233,6 +248,10 @@ def effect_dependencies(asset: RawResource, asset_manager: AssetManager) -> typi raise UnableToCheatError() +def no_dependencies(asset: RawResource, asset_manager: AssetManager) -> typing.Iterator[Dependency]: + yield from [] + + _FORMATS_TO_CHEAT = { "CSNG": csng_dependencies, "DUMB": dumb_dependencies, @@ -243,6 +262,12 @@ def effect_dependencies(asset: RawResource, asset_manager: AssetManager) -> typi "FONT": font_dependencies, "CMDL": cmdl_dependencies, "PART": effect_dependencies, + "AFSM": no_dependencies, + "DCLN": no_dependencies, + "STLC": no_dependencies, + "PATH": no_dependencies, + "EGMC": no_dependencies, + "PTLA": no_dependencies, } diff --git a/src/retro_data_structures/formats/mapu.py b/src/retro_data_structures/formats/mapu.py index 08d84c5..8439e88 100644 --- a/src/retro_data_structures/formats/mapu.py +++ b/src/retro_data_structures/formats/mapu.py @@ -35,5 +35,3 @@ def construct_class(cls, target_game: Game) -> Construct: def dependencies_for(self) -> typing.Iterator[Dependency]: yield from self.asset_manager.get_dependencies_for_asset(self.raw.hexagon_mapa) - for world in self.raw.worlds: - yield from self.asset_manager.get_dependencies_for_asset(world.mlvl) diff --git a/src/retro_data_structures/formats/mlvl.py b/src/retro_data_structures/formats/mlvl.py index ddfbd50..6057f97 100644 --- a/src/retro_data_structures/formats/mlvl.py +++ b/src/retro_data_structures/formats/mlvl.py @@ -235,7 +235,22 @@ def construct_class(cls, target_game: Game) -> construct.Construct: return MLVL def dependencies_for(self) -> typing.Iterator[Dependency]: - raise NotImplementedError() + for area in self.areas: + area.build_mlvl_dependencies(False) + yield from area.dependencies_for() + + mlvl_deps = [ + self._raw.world_name_id, + self._raw.world_save_info_id, + self._raw.default_skybox_id + ] + if self.asset_manager.target_game == Game.ECHOES: + mlvl_deps.append(self._raw.dark_world_name_id) + if self.asset_manager.target_game <= Game.CORRUPTION: + mlvl_deps.append(self._raw.world_map_id) + + for dep in mlvl_deps: + yield from self.asset_manager.get_dependencies_for_asset(dep) def __repr__(self) -> str: try: diff --git a/src/retro_data_structures/formats/mrea.py b/src/retro_data_structures/formats/mrea.py index 52ba9a3..ad466a4 100644 --- a/src/retro_data_structures/formats/mrea.py +++ b/src/retro_data_structures/formats/mrea.py @@ -31,7 +31,7 @@ from retro_data_structures.construct_extensions.alignment import PrefixedWithPaddingBefore from retro_data_structures.construct_extensions.version import BeforeVersion, WithVersion from retro_data_structures.data_section import DataSection -from retro_data_structures.exceptions import UnknownAssetId +from retro_data_structures.exceptions import DependenciesHandledElsewhere, UnknownAssetId from retro_data_structures.formats.area_collision import AreaCollision from retro_data_structures.formats.arot import AROT from retro_data_structures.formats.cmdl import dependencies_for_material_set @@ -522,7 +522,7 @@ def construct_class(cls, target_game: Game) -> construct.Construct: return MREA def dependencies_for(self) -> typing.Iterator[Dependency]: - raise NotImplementedError() + raise DependenciesHandledElsewhere() def _ensure_decoded_section(self, section_name: str, lazy_load: bool = False): if section_name not in self._raw.sections: @@ -607,9 +607,9 @@ class AreaDependencies: @property def all_dependencies(self) -> Iterator[Dependency]: + yield from self.non_layer for layer in self.layers: yield from layer - yield from self.non_layer def __eq__(self, __value: object) -> bool: if not isinstance(__value, AreaDependencies): @@ -811,10 +811,16 @@ def build_mlvl_dependencies(self, only_modified: bool = False): self.dependencies = AreaDependencies(layer_deps, non_layer) + def dependencies_for(self): + yield from self.dependencies.all_dependencies + yield from self.asset_manager.get_dependencies_for_asset(self.mrea_asset_id) + yield from self.asset_manager.get_dependencies_for_asset(self._raw.area_name_id) + def ensure_dependencies_in_paks(self): asset_manager = self._parent_mlvl.asset_manager - paks = asset_manager.find_paks(self.mrea_asset_id) - for dep in self.dependencies.all_dependencies: + paks = list(asset_manager.find_paks(self.mrea_asset_id)) + + for dep in self.dependencies_for(): for pak in paks: asset_manager.ensure_present(pak, dep.id) diff --git a/src/retro_data_structures/formats/scan.py b/src/retro_data_structures/formats/scan.py index f79508c..a8ac8f7 100644 --- a/src/retro_data_structures/formats/scan.py +++ b/src/retro_data_structures/formats/scan.py @@ -78,8 +78,8 @@ def _internal_dependencies_for(self) -> typing.Iterator[Dependency]: for image in self.raw.scan_images: yield from self.asset_manager.get_dependencies_for_asset(image.texture) else: - for dep in self.raw.dependencies: - yield from self.asset_manager.get_dependencies_for_asset(dep.asset_id) + scan_info = self.scannable_object_info.get_properties() + yield from scan_info.dependencies_for(self.asset_manager) def dependencies_for(self) -> typing.Iterator[Dependency]: for it in self._internal_dependencies_for(): diff --git a/src/retro_data_structures/formats/script_layer.py b/src/retro_data_structures/formats/script_layer.py index f8f4446..50bf357 100644 --- a/src/retro_data_structures/formats/script_layer.py +++ b/src/retro_data_structures/formats/script_layer.py @@ -91,6 +91,22 @@ def new_layer(index: int | None, target_game: Game) -> Container: SCGN = ConstructScriptLayer("SCGN") +def dependencies_for_layer(asset_manager: AssetManager, + instances: typing.Iterable[ScriptInstance] + ) -> typing.Iterator[Dependency]: + deps: list[Dependency] = [] + for instance in instances: + deps.extend(instance.mlvl_dependencies_for(asset_manager)) + + unique_deps: set[Dependency] = set() + for dep in deps: + if dep in unique_deps: + continue + # specifically keep the order of the *first* appearance of the dependency + unique_deps.add(dep) + yield dep + + class ScriptLayer: _parent_area: Area | None = None _index: int @@ -233,19 +249,8 @@ def mark_modified(self): self._modified = True def build_mlvl_dependencies(self, asset_manager: AssetManager) -> typing.Iterator[Dependency]: - logging.debug(f" Layer: {self.name}") - - deps: list[Dependency] = [] - for instance in self.instances: - deps.extend(instance.mlvl_dependencies_for(asset_manager)) - - unique_deps: set[Dependency] = set() - for dep in deps: - if dep in unique_deps: - continue - # specifically keep the order of the *first* appearance of the dependency - unique_deps.add(dep) - yield dep + logging.debug(" Layer: %s", self.name) + yield from dependencies_for_layer(asset_manager, self.instances) @property def dependencies(self) -> typing.Iterator[Dependency]: diff --git a/src/retro_data_structures/formats/strg.py b/src/retro_data_structures/formats/strg.py index 830e5fe..12bd3ab 100644 --- a/src/retro_data_structures/formats/strg.py +++ b/src/retro_data_structures/formats/strg.py @@ -188,7 +188,7 @@ def _compute_corruption_strings_size(ctx): "junk" / GreedyRange(Byte), ) -image_regex = re.compile(r"&image=(?:\S+?,)+?((?:[a-fA-F0-9]+,?)+);") +image_regex = re.compile(r"&image=(?:.+?,)*?((?:[a-fA-F0-9]+,?)+);") font_regex = re.compile(r"&font=([a-fA-F0-9]+?);") class Strg(BaseResource): diff --git a/src/retro_data_structures/formats/tree.py b/src/retro_data_structures/formats/tree.py index fcf4e89..c47938a 100644 --- a/src/retro_data_structures/formats/tree.py +++ b/src/retro_data_structures/formats/tree.py @@ -1,7 +1,13 @@ +import typing + +from construct import Construct from construct.core import Byte, Const, Int32ub, PrefixedArray, Struct +from retro_data_structures.base_resource import AssetType, BaseResource, Dependency from retro_data_structures.common_types import FourCC -from retro_data_structures.formats.script_object import ConstructScriptInstance +from retro_data_structures.formats.script_layer import dependencies_for_layer +from retro_data_structures.formats.script_object import ConstructScriptInstance, ScriptInstance +from retro_data_structures.game_check import Game TREE = Struct( "magic" / Const("TREE", FourCC), @@ -9,3 +15,21 @@ "unknown" / Const(1, Byte), "nodes" / PrefixedArray(Int32ub, ConstructScriptInstance), ) + + +class Tree(BaseResource): + @classmethod + def resource_type(cls) -> AssetType: + return "DUMB" + + @classmethod + def construct_class(cls, target_game: Game) -> Construct: + return TREE + + def dependencies_for(self) -> typing.Iterator[Dependency]: + yield from dependencies_for_layer(self.asset_manager, self.nodes) + + @property + def nodes(self) -> typing.Iterator[ScriptInstance]: + for inst in self.raw.nodes: + yield ScriptInstance(inst, self.target_game) diff --git a/src/retro_data_structures/game_check.py b/src/retro_data_structures/game_check.py index b7f0413..cb276da 100644 --- a/src/retro_data_structures/game_check.py +++ b/src/retro_data_structures/game_check.py @@ -93,14 +93,27 @@ def mlvl_dependencies_to_ignore(self) -> tuple[AssetId]: return (0x7b2ea5b1,) return () + def audio_group_dependencies(self): + if self == Game.ECHOES: + # audio_groups_single_player_DGRP + yield 0x31CB5ADB + # audio_groups_multi_player_DGRP + # yield 0xEE0CC360 # FIXME + def special_ancs_dependencies(self, ancs: AssetId): - if self == Game.ECHOES and ancs == 0xC043D342: - # every gun animation needs these i guess - yield Dependency("TXTR", 0x9e6f9531, False) - yield Dependency("TXTR", 0xcea098fe, False) - yield Dependency("TXTR", 0x607638ea, False) - yield Dependency("TXTR", 0x578e51b8, False) - yield Dependency("TXTR", 0x1e7b6c64, False) + if self == Game.ECHOES: + if ancs == 0xC043D342: + # every gun animation needs these i guess + yield Dependency("TXTR", 0x9e6f9531, False) + yield Dependency("TXTR", 0xcea098fe, False) + yield Dependency("TXTR", 0x607638ea, False) + yield Dependency("TXTR", 0x578e51b8, False) + yield Dependency("TXTR", 0x1e7b6c64, False) + + if ancs == 0x2E980BF2: + # samus ANCS from Hive Chamber A + yield Dependency("ANIM", 0x711A038F, True) + yield Dependency("ANIM", 0x1A9CCDD5, True) def get_current_game(ctx) -> Game: diff --git a/tests/formats/test_mlvl.py b/tests/formats/test_mlvl.py index ab877da..4aeb969 100644 --- a/tests/formats/test_mlvl.py +++ b/tests/formats/test_mlvl.py @@ -104,7 +104,7 @@ def test_mlvl_dependencies(prime2_asset_manager: AssetManager): for area in mlvl.areas: old = area.dependencies_by_layer - old = {layer_name: set((typ, hex(idx)) for typ, idx, _ in layer) for layer_name, layer in old.items()} + old = {layer_name: set((dep.type, hex(dep.id)) for dep in layer) for layer_name, layer in old.items()} start = time.time() area.build_mlvl_dependencies() @@ -112,7 +112,7 @@ def test_mlvl_dependencies(prime2_asset_manager: AssetManager): total_elapsed += elapsed new = area.dependencies_by_layer - new = {layer_name: set((typ, hex(idx)) for typ, idx, _ in layer) for layer_name, layer in new.items()} + new = {layer_name: set((dep.type, hex(dep.id)) for dep in layer) for layer_name, layer in new.items()} missing = { layer_name: old_layer.difference(new_layer) diff --git a/tests/test_properties.py b/tests/test_properties.py index 623f530..1497912 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -7,7 +7,7 @@ from retro_data_structures.formats.script_object import _try_quick_get_name from retro_data_structures.properties.base_property import BaseProperty -_root = Path(__file__).parents[1] +_root = Path(__file__).parents[1].joinpath("src") def perform_module_checks(path: Path):