Beispiel #1
0
 def pack(self, writer: BinaryWriter):
     writer.pack_struct(
         self.STRUCT,
         self,
         __size=len(self.data) + self.STRUCT.size,
     )
     writer.append(self.data)
Beispiel #2
0
    def pack(self) -> bytes:
        """Pack TPF file to bytes."""
        writer = BinaryWriter(
            big_endian=self.platform in {TPFPlatform.Xbox360, TPFPlatform.PS3})
        writer.append(b"TPF\0")
        writer.reserve("data_size", "i")
        writer.pack("i", len(self.textures))
        writer.pack("b", self.platform)
        writer.pack("b", self.tpf_flags)
        writer.pack("b", self.encoding)
        writer.pad(1)

        for i, texture in enumerate(self.textures):
            texture.pack_header(writer, i, self.platform, self.tpf_flags)
        for i, texture in enumerate(self.textures):
            texture.pack_name(writer, i, self.encoding)

        data_start = writer.position
        for i, texture in enumerate(self.textures):
            # TKGP notes: padding varies wildly across games, so don't worry about it too much.
            if len(texture.data) > 0:
                writer.pad_align(4)
            texture.pack_data(writer, i)
        writer.fill("data_size", writer.position - data_start)
        return writer.finish()
Beispiel #3
0
    def pack(self):
        writer = BinaryWriter(big_endian=self.big_endian)
        writer.pack_struct(self.HEADER_STRUCT, goal_count=len(self.goals))
        goal_struct = self.GOAL_STRUCT_64 if self.use_struct_64 else self.GOAL_STRUCT_32
        packed_strings_offset = writer.position + len(
            self.goals) * goal_struct.size

        packed_goals = b""
        packed_strings = b""
        encoding = self.encoding
        z_term = b"\0\0" if self.use_struct_64 else b"\0"
        for goal in self.goals:
            name_offset = packed_strings_offset + len(packed_strings)
            packed_strings += goal.goal_name.encode(encoding=encoding) + z_term
            goal_kwargs = goal.get_interrupt_details()
            logic_interrupt_name = goal_kwargs.pop("logic_interrupt_name")
            if logic_interrupt_name:
                logic_interrupt_name_offset = packed_strings_offset + len(
                    packed_strings)
                packed_strings += logic_interrupt_name.encode(
                    encoding=encoding) + z_term
            else:
                logic_interrupt_name_offset = 0
            packed_goals += goal_struct.pack(
                goal_id=goal.goal_id,
                name_offset=name_offset,
                logic_interrupt_name_offset=logic_interrupt_name_offset,
                **goal_kwargs,
            )

        writer.append(packed_goals)
        writer.append(packed_strings)

        return writer.finish()
Beispiel #4
0
 def pack_header(self, writer: BinaryWriter):
     writer.append(b"BHF3")
     writer.pack("8s", self.signature.encode("ascii"))
     self.flags.pack(writer, self.bit_big_endian)
     writer.pack("?", self.big_endian)
     writer.pack("?", self.bit_big_endian)
     writer.pad(1)
     writer.pack("i", len(self._entries))
     writer.pad(12)
Beispiel #5
0
    def pack_data(self, writer: BinaryWriter, index: int):
        writer.fill(f"file_data_{index}", writer.position)
        if self.texture_flags in {2, 3}:
            data = zlib.compress(self.data, level=7)
        else:
            data = self.data

        writer.fill(f"file_size_{index}", len(data))
        writer.append(data)
Beispiel #6
0
 def pack_name(self, writer: BinaryWriter, index: int, encoding: int):
     writer.fill(f"file_name_{index}", writer.position)
     if encoding == 1:
         name = self.name.encode(encoding="utf-16-be" if writer.
                                 big_endian else "utf-16-le") + b"\0\0"
     elif encoding in {0, 2}:
         name = self.name.encode(encoding="shift-jis") + b"\0"
     else:
         raise ValueError(
             f"Invalid TPF texture encoding: {encoding}. Must be 0, 1, or 2."
         )
     writer.append(name)
Beispiel #7
0
    def pack(self) -> tp.Tuple[bytes, bytes]:
        header_writer = BinaryWriter(
            big_endian=self.big_endian or self.flags.is_big_endian)
        data_writer = BinaryWriter(
            big_endian=self.big_endian or self.flags.is_big_endian)
        self.pack_header(header_writer)

        entries = list(sorted(self._entries, key=lambda e: e.id))
        entry_headers = [entry.get_header(self.flags) for entry in entries]
        for entry_header in entry_headers:
            entry_header.pack_bnd3(header_writer, self.flags,
                                   self.bit_big_endian)

        if self.flags.has_names:
            for entry, entry_header in zip(entries, entry_headers):
                header_writer.fill("path_offset",
                                   header_writer.position,
                                   obj=entry_header)
                # NOTE: BND paths are *not* encoded in `shift_jis_2004`, unlike most other strings, but are `shift-jis`.
                # The relevant difference is that escaped backslashes are encoded as the yen symbol in `shift_jis_2004`.
                header_writer.append(
                    entry.get_packed_path(encoding="shift-jis"))

        # Useless BDT3 header.
        data_writer.append(b"BDF3")
        data_writer.pack("8s", self.signature.encode("ascii"))
        data_writer.pad(4)

        for entry, entry_header in zip(entries, entry_headers):
            header_writer.fill("data_offset",
                               data_writer.position,
                               obj=entry_header)
            data_writer.append(entry.data)

        return header_writer.finish(), data_writer.finish()
Beispiel #8
0
    def pack_header(self, writer: BinaryWriter):
        writer.append(b"BHF4")
        writer.pack("?", self.unknown1)
        writer.pack("?", self.unknown2)
        writer.pad(3)
        writer.pack("?", self.big_endian)
        writer.pack("?", not self.bit_big_endian)  # note reversal
        writer.pad(1)

        writer.pack("i", len(self._entries))
        writer.pack("q", 0x40)  # header size
        writer.pack("8s", self.signature.encode("ascii"))
        writer.pack("q", self.flags.get_bnd_entry_header_size())
        writer.reserve("data_offset", "q")
        writer.pack("?", self.unicode)
        self.flags.pack(writer, self.bit_big_endian)
        writer.pack("B", self.hash_table_type)
        writer.pad(5)
        if self.hash_table_type == 4:
            writer.reserve("hash_table_offset", "q")
        else:
            writer.pad(8)
Beispiel #9
0
    def pack(self) -> bytes:
        writer = BinaryWriter(big_endian=self.big_endian)
        self.pack_header(writer)

        path_encoding = ("utf-16-be" if self.big_endian else
                         "utf-16-le") if self.unicode else "shift-jis"
        rebuild_hash_table = not self._most_recent_hash_table

        if not self._most_recent_hash_table or len(
                self._entries) != self._most_recent_entry_count:
            rebuild_hash_table = True
        else:
            # Check if any entry paths have changed.
            for i, entry in enumerate(self._entries):
                if entry.path != self._most_recent_paths[i]:
                    rebuild_hash_table = True
                    break

        self._most_recent_entry_count = len(self._entries)
        self._most_recent_paths = [entry.path for entry in self._entries]

        entries = list(sorted(self._entries, key=lambda e: e.id))
        entry_headers = [entry.get_header(self.flags) for entry in entries]
        for entry_header in entry_headers:
            entry_header.pack_bnd4(writer, self.flags, self.bit_big_endian)

        if self.flags.has_names:
            for entry, entry_header in zip(entries, entry_headers):
                writer.fill("path_offset", writer.position, obj=entry_header)
                # NOTE: BND paths are *not* encoded in `shift_jis_2004`, unlike most other strings, but are `shift-jis`.
                # The relevant difference is that escaped backslashes are encoded as the yen symbol in `shift_jis_2004`.
                writer.append(entry.get_packed_path(encoding=path_encoding))

        if self.hash_table_type == 4:
            writer.fill("hash_table_offset", writer.position)
            if rebuild_hash_table:
                writer.append(BinderHashTable.build_hash_table(self._entries))
            else:
                writer.append(self._most_recent_hash_table)

        writer.fill("data_offset", writer.position)

        for entry, entry_header in zip(entries, entry_headers):
            writer.fill("data_offset", writer.position, obj=entry_header)
            writer.append(
                entry.data + b"\0" * 10
            )  # ten pad bytes between each entry (for byte-perfect writes)

        return writer.finish()
Beispiel #10
0
    def pack(self) -> bytes:
        writer = BinaryWriter(
            big_endian=self.big_endian or self.flags.is_big_endian)
        self.pack_header(writer)

        entries = list(sorted(self._entries, key=lambda e: e.id))
        entry_headers = [entry.get_header(self.flags) for entry in entries]
        for entry_header in entry_headers:
            entry_header.pack_bnd3(writer, self.flags, self.bit_big_endian)

        if self.flags.has_names:
            for entry, entry_header in zip(entries, entry_headers):
                writer.fill("path_offset", writer.position, obj=entry_header)
                # NOTE: BND paths are *not* encoded in `shift_jis_2004`, unlike most other strings, but are `shift-jis`.
                # The relevant difference is that escaped backslashes are encoded as the yen symbol in `shift_jis_2004`.
                writer.append(entry.get_packed_path(encoding="shift-jis"))

        for entry, entry_header in zip(entries, entry_headers):
            writer.fill("data_offset", writer.position, obj=entry_header)
            writer.append(entry.data)

        writer.fill("file_size", writer.position)

        return writer.finish()
Beispiel #11
0
    def pack(self) -> tp.Tuple[bytes, bytes]:
        header_writer = BinaryWriter(
            big_endian=self.big_endian or self.flags.is_big_endian)
        data_writer = BinaryWriter(
            big_endian=self.big_endian or self.flags.is_big_endian)
        self.pack_header(header_writer)

        path_encoding = ("utf-16-be" if self.big_endian else
                         "utf-16-le") if self.unicode else "shift-jis"
        entries = list(sorted(self._entries, key=lambda e: e.id))

        rebuild_hash_table = not self._most_recent_hash_table
        if not self._most_recent_hash_table or len(
                entries) != self._most_recent_entry_count:
            rebuild_hash_table = True
        else:
            # Check if any entry paths have changed.
            for i, entry in enumerate(entries):
                if entry.path != self._most_recent_paths[i]:
                    rebuild_hash_table = True
                    break
        self._most_recent_entry_count = len(entries)
        self._most_recent_paths = [entry.path for entry in entries]

        entry_headers = [entry.get_header(self.flags) for entry in entries]
        for entry_header in entry_headers:
            entry_header.pack_bnd4(header_writer, self.flags,
                                   self.bit_big_endian)

        if self.flags.has_names:
            for entry, entry_header in zip(entries, entry_headers):
                header_writer.fill("path_offset",
                                   header_writer.position,
                                   obj=entry_header)
                # NOTE: BND paths are *not* encoded in `shift_jis_2004`, unlike most other strings, but are `shift-jis`.
                # The relevant difference is that escaped backslashes are encoded as the yen symbol in `shift_jis_2004`.
                header_writer.append(
                    entry.get_packed_path(encoding=path_encoding))

        if self.hash_table_type == 4:
            header_writer.fill("hash_table_offset", header_writer.position)
            if rebuild_hash_table:
                header_writer.append(
                    BinderHashTable.build_hash_table(self._entries))
            else:
                header_writer.append(self._most_recent_hash_table)

        header_writer.fill("data_offset", header_writer.position)

        # Useless BDT4 header.
        data_writer.append(b"BDF4")
        data_writer.pack("?", self.unknown1)
        data_writer.pack("?", self.unknown2)
        data_writer.pad(3)
        data_writer.pack("?", self.big_endian)
        data_writer.pack("?", not self.bit_big_endian)
        data_writer.pad(5)
        data_writer.pack("q", 0x30)  # header size
        data_writer.pack("8s", self.signature.encode("ascii"))
        data_writer.pad(16)

        for entry, entry_header in zip(entries, entry_headers):
            header_writer.fill("data_offset",
                               data_writer.position,
                               obj=entry_header)
            data_writer.append(
                entry.data + b"\0" * 10
            )  # ten pad bytes between each entry (for byte-perfect writes)

        return header_writer.finish(), data_writer.finish()