Beispiel #1
0
class MSBPart(
    BaseMSBPart,
    MSB_Scale,
    MSB_ModelName,
    MSB_DrawParent,
    MSB_DrawGroups,
    MSB_DisplayGroups,
    MSB_BackreadGroups,
    abc.ABC,
):
    PART_HEADER_STRUCT = BinaryStruct(
        ("__description_offset", "q"),
        ("__name_offset", "q"),
        ("_instance_index", "i"),  # TK says "Unknown; appears to count up with each instance of a model added."
        ("__part_type", "i"),
        ("_part_type_index", "i"),
        ("_model_index", "i"),
        ("__sib_path_offset", "q"),
        ("translate", "3f"),
        ("rotate", "3f"),
        ("scale", "3f"),
        ("__draw_groups", "8I"),
        ("__display_groups", "8I"),
        ("__backread_groups", "8I"),
        "4x",
        ("__base_data_offset", "q"),
        ("__type_data_offset", "q"),
        ("__gparam_data_offset", "q"),
        ("__scene_gparam_data_offset", "q"),
    )
    PART_BASE_DATA_STRUCT = BinaryStruct(
        ("entity_id", "i"),
        ("base_unk_x04_x05", "b"),
        ("base_unk_x05_x06", "b"),
        ("base_unk_x06_x07", "b"),
        ("base_unk_x07_x08", "b"),
        "4x",
        ("lantern_id", "b"),
        ("lod_id", "b"),
        ("base_unk_x0e_x0f", "b"),
        ("base_unk_x0f_x10", "b"),
    )
    NAME_ENCODING = "utf-16-le"
    DRAW_GROUP_COUNT = 256
    DISPLAY_GROUP_COUNT = 256
    BACKREAD_GROUP_COUNT = 256

    FIELD_INFO = BaseMSBPart.FIELD_INFO | {
        "sib_path": MapFieldInfo(
            "SIB Path",
            str,
            "",
            "Internal path to SIB placeholder file for part.",
        ),
        "scale": MapFieldInfo(
            "Scale",
            Vector3,
            Vector3.ones(),
            "Scale of part. Only works for Map Pieces and Objects.",
        ),
        "draw_groups": MapFieldInfo(
            "Draw Groups",
            list,
            set(range(DRAW_GROUP_COUNT)),
            "Draw groups of part. This part will be drawn when the corresponding display group is active.",
        ),
        "display_groups": MapFieldInfo(
            "Display Groups",
            list,
            set(range(DISPLAY_GROUP_COUNT)),
            "Display groups are present in all MSB Parts, but only function for collisions.",
        ),
        "backread_groups": MapFieldInfo(
            "Backread Groups",
            list,
            set(range(BACKREAD_GROUP_COUNT)),
            "Backread groups are present in all MSB Parts, but only function for collisions (probably).",
        ),
        "base_unk_x04_x05": MapFieldInfo(
            "Unknown Base [04-05]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x05_x06": MapFieldInfo(
            "Unknown Base [05-06]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x06_x07": MapFieldInfo(
            "Unknown Base [06-07]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "base_unk_x07_x08": MapFieldInfo(
            "Unknown Base [07-08]",
            int,
            -1,
            "Unknown base data integer.",
        ),
        "lantern_id": MapFieldInfo(
            "Lantern ID",
            int,
            0,
            "Lantern param ID.",
        ),
        "lod_id": MapFieldInfo(
            "LoD ID",
            int,
            -1,
            "LoD (level of detail) param ID.",
        ),
        "base_unk_x0e_x0f": MapFieldInfo(
            "Unknown Base [0e-0f]",
            int,
            20,
            "Unknown base data integer.",
        ),
        "base_unk_x0f_x10": MapFieldInfo(
            "Unknown Base [0f-10]",
            int,
            0,
            "Unknown base data integer.",
        ),
    }

    FIELD_ORDER = (
        "base_unk_x04_x05",
        "base_unk_x05_x06",
        "base_unk_x06_x07",
        "base_unk_x07_x08",
        "lantern_id",
        "lod_id",
        "base_unk_x0e_x0f",
        "base_unk_x0f_x10",
    )

    sib_path: str
    base_unk_x04_x05: int
    base_unk_x05_x06: int
    base_unk_x06_x07: int
    base_unk_x07_x08: int
    lantern_id: int
    lod_id: int
    base_unk_x0e_x0f: int
    base_unk_x0f_x10: int

    def __init__(self, source=None, **kwargs):
        self._instance_index = 0
        super().__init__(source, **kwargs)

    def unpack(self, msb_reader: BinaryReader):
        part_offset = msb_reader.position

        header = msb_reader.unpack_struct(self.PART_HEADER_STRUCT)
        if header["__part_type"] != self.ENTRY_SUBTYPE:
            raise ValueError(f"Unexpected part type enum {header['part_type']} for class {self.__class__.__name__}.")
        self._instance_index = header["_instance_index"]
        self._model_index = header["_model_index"]
        self._part_type_index = header["_part_type_index"]
        for transform in ("translate", "rotate", "scale"):
            setattr(self, transform, Vector3(header[transform]))
        self._draw_groups = int_group_to_bit_set(header["__draw_groups"], assert_size=8)
        self._display_groups = int_group_to_bit_set(header["__display_groups"], assert_size=8)
        self._backread_groups = int_group_to_bit_set(header["__backread_groups"], assert_size=8)
        self.description = msb_reader.unpack_string(
            offset=part_offset + header["__description_offset"], encoding="utf-16-le",
        )
        self.name = msb_reader.unpack_string(
            offset=part_offset + header["__name_offset"], encoding="utf-16-le",
        )
        self.sib_path = msb_reader.unpack_string(
            offset=part_offset + header["__sib_path_offset"], encoding="utf-16-le",
        )

        msb_reader.seek(part_offset + header["__base_data_offset"])
        base_data = msb_reader.unpack_struct(self.PART_BASE_DATA_STRUCT)
        self.set(**base_data)

        msb_reader.seek(part_offset + header["__type_data_offset"])
        self.unpack_type_data(msb_reader)

        self._unpack_gparam_data(msb_reader, part_offset, header)
        self._unpack_scene_gparam_data(msb_reader, part_offset, header)

    def pack(self) -> bytes:
        """Pack to bytes, presumably as part of a full `MSB` pack."""

        # Validate draw/display/backread groups before doing any real work.
        draw_groups = bit_set_to_int_group(self._draw_groups, group_size=8)
        display_groups = bit_set_to_int_group(self._display_groups, group_size=8)
        backread_groups = bit_set_to_int_group(self._backread_groups, group_size=8)

        description_offset = self.PART_HEADER_STRUCT.size
        packed_description = self.description.encode("utf-16-le") + b"\0\0"
        name_offset = description_offset + len(packed_description)
        packed_name = self.get_name_to_pack().encode("utf-16-le") + b"\0\0"
        sib_path_offset = name_offset + len(packed_name)
        packed_sib_path = self.sib_path.encode("utf-16-le") + b"\0\0" if self.sib_path else b"\0\0"
        strings_size = len(packed_description + packed_name + packed_sib_path)
        if strings_size <= 0x38:
            packed_sib_path += b"\0" * (0x3c - strings_size)
        else:
            packed_sib_path += b"\0" * 8
        while len(packed_description + packed_name + packed_sib_path) % 4 != 0:
            packed_sib_path += b"\0"  # Not done in SoulsFormats, but makes sense to me.

        base_data_offset = sib_path_offset + len(packed_sib_path)
        packed_base_data = self.PART_BASE_DATA_STRUCT.pack(self)
        type_data_offset = base_data_offset + len(packed_base_data)
        packed_type_data = self.pack_type_data()
        gparam_data_offset = type_data_offset + len(packed_type_data)
        packed_gparam_data = self._pack_gparam_data()
        scene_gparam_data_offset = gparam_data_offset + len(packed_gparam_data)
        packed_scene_gparam_data = self._pack_scene_gparam_data()
        try:
            packed_header = self.PART_HEADER_STRUCT.pack(
                __description_offset=description_offset,
                __name_offset=name_offset,
                _instance_index=self._instance_index,
                __part_type=self.ENTRY_SUBTYPE,
                _part_type_index=self._part_type_index,
                _model_index=self._model_index,
                __sib_path_offset=sib_path_offset,
                translate=list(self.translate),
                rotate=list(self.rotate),
                scale=list(self.scale),
                __draw_groups=draw_groups,
                __display_groups=display_groups,
                __backread_groups=backread_groups,
                __base_data_offset=base_data_offset,
                __type_data_offset=type_data_offset,
                __gparam_data_offset=gparam_data_offset,
                __scene_gparam_data_offset=scene_gparam_data_offset,
            )
        except struct.error:
            raise MapError(f"Could not pack header data of MSB part '{self.name}'. See traceback.")
        return (
            packed_header + packed_description + packed_name + packed_sib_path
            + packed_base_data + packed_type_data + packed_gparam_data + packed_scene_gparam_data
        )

    def _unpack_gparam_data(self, msb_reader: BinaryReader, part_offset, header):
        pass

    def _pack_gparam_data(self):
        return b""

    def _unpack_scene_gparam_data(self, msb_reader: BinaryReader, part_offset, header):
        pass

    def _pack_scene_gparam_data(self):
        return b""
Beispiel #2
0
class MSBPart(MSBEntryEntityCoordinates, abc.ABC):

    ENTRY_SUBTYPE: MSBPartSubtype = None
    PART_HEADER_STRUCT: BinaryStruct = None
    PART_BASE_DATA_STRUCT: BinaryStruct = None
    PART_TYPE_DATA_STRUCT: BinaryStruct = None
    NAME_ENCODING = ""
    FLAG_SET_SIZE = 128

    FIELD_INFO = MSBEntryEntityCoordinates.FIELD_INFO | {
        "sib_path":
        MapFieldInfo(
            "SIB Path",
            str,
            "",
            "Internal path to SIB placeholder file for part.",
        ),
        "scale":
        MapFieldInfo(
            "Scale",
            Vector3,
            Vector3.ones(),
            "Scale of part. Only works for Map Pieces.",  # TODO: and maybe Objects?
        ),
        # Every concrete subclass defines 'model_name', 'draw_groups', and 'display_groups'.
    }

    sib_path: str
    scale: Vector3
    model_name: tp.Optional[str]

    def __init__(self, source=None, **kwargs):
        self._part_type_index = -1
        self._model_index = -1
        self._draw_groups = set()
        self._display_groups = set()
        super().__init__(source=source, **kwargs)

    def unpack_type_data(self, msb_buffer):
        """This unpacks simple attributes by default, but some Parts need to process these values more."""
        self.set(**self.PART_TYPE_DATA_STRUCT.unpack(msb_buffer,
                                                     exclude_asserted=True))

    def pack_type_data(self):
        try:
            return self.PART_TYPE_DATA_STRUCT.pack(self)
        except struct.error:
            raise SoulstructError(
                f"Could not pack type data of MSB part '{self.name}'. See traceback."
            )

    def set_indices(
        self,
        part_type_index,
        model_indices,
        local_environment_indices,
        region_indices,
        part_indices,
        local_collision_indices,
    ):
        self._part_type_index = part_type_index
        try:
            self._model_index = model_indices[
                self.model_name] if self.model_name else -1
        except KeyError:
            raise KeyError(
                f"Invalid model name for {self.ENTRY_SUBTYPE.name} {self.name} (entity ID {self.entity_id}): "
                f"{self.model_name}")

    def set_names(
        self,
        model_names,
        region_names,
        environment_names,
        part_names,
        collision_names,
    ):
        if self._model_index != -1:
            try:
                self.model_name = model_names[self._model_index]
            except KeyError:
                raise KeyError(
                    f"Invalid model index for {self.ENTRY_SUBTYPE.name} {self.name} (entity ID {self.entity_id}): "
                    f"{self._model_index}")
        else:
            self.model_name = None

    @property
    def draw_groups(self):
        return self._draw_groups

    @draw_groups.setter
    def draw_groups(self, value):
        """Converts value to a `set()` (possibly empty) and validates index range."""
        if value is None or isinstance(value, str) and value in {"None", ""}:
            self._draw_groups = set()
            return
        try:
            draw_groups = set(value)
        except (TypeError, ValueError):
            raise TypeError(
                "Draw groups must be a set, sequence, `None`, 'None', or ''. Or use `set` methods like `.add()`."
            )
        for i in draw_groups:
            if not isinstance(i, int) and 0 <= i < self.FLAG_SET_SIZE:
                raise InvalidFieldValueError(
                    f"Invalid draw group: {i}. Must be 0 <= i < {self.FLAG_SET_SIZE}."
                )
        self._draw_groups = draw_groups

    @property
    def display_groups(self):
        return self._display_groups

    @display_groups.setter
    def display_groups(self, value):
        """Converts value to a `set()` (possibly empty) and validates index range."""
        if value is None or isinstance(value, str) and value in {"None", ""}:
            self._display_groups = set()
            return
        try:
            display_groups = set(value)
        except (TypeError, ValueError):
            raise TypeError(
                "Display groups must be a set, sequence, `None`, 'None', or ''. Or use `set` methods like `.add()`."
            )
        for i in display_groups:
            if not isinstance(i, int) and 0 <= i < self.FLAG_SET_SIZE:
                raise ValueError(
                    f"Invalid display group: {i}. Must be 0 <= i < {self.FLAG_SET_SIZE}."
                )
        self._display_groups = display_groups