Beispiel #1
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 #2
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 #3
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 #4
0
 def pack_bnd3(self, writer: BinaryWriter, binder_flags: BinderFlags, bit_big_endian: bool):
     self.flags.pack(writer, bit_big_endian)
     writer.pad(3)
     writer.pack("i", self.compressed_size)
     writer.reserve("data_offset", "q" if binder_flags.has_long_offsets else "I", obj=self)
     if binder_flags.has_ids:
         writer.pack("i", self.id)
     if binder_flags.has_names:
         writer.reserve("path_offset", "i", obj=self)
     if binder_flags.has_compression:
         writer.pack("i", self.uncompressed_size)
Beispiel #5
0
    def pack_header(self, writer: BinaryWriter, index: int,
                    platform: TPFPlatform, tpf_flags: int):
        if platform == TPFPlatform.PC:
            dds = self.get_dds()
            if dds.header.caps_2 & DDSCAPS2.CUBEMAP:
                tex_type = TextureType.Cubemap
            elif dds.header.caps_2 & DDSCAPS2.VOLUME:
                tex_type = TextureType.Volume
            else:
                tex_type = TextureType.Texture
            mipmap_count = dds.header.mipmap_count
        else:
            tex_type = self.texture_type
            mipmap_count = self.mipmaps

        writer.reserve(f"file_data_{index}", "I")
        writer.reserve(f"file_size_{index}", "i")
        writer.pack("b", self.format)
        writer.pack("b", tex_type)
        writer.pack("b", mipmap_count)
        writer.pack("b", self.texture_flags)

        if platform != TPFPlatform.PC:
            writer.pack("h", self.header.width)
            writer.pack("h", self.header.height)
            if platform == TPFPlatform.Xbox360:
                writer.pad(4)
            elif platform == TPFPlatform.PS3:
                writer.pack("i", self.header.unk1)
                if tpf_flags != 0:
                    writer.pack("i", self.header.unk2)
            elif platform in {TPFPlatform.PS4, TPFPlatform.XboxOne}:
                writer.pack("i", self.header.texture_count)
                writer.pack("i", self.header.unk2)

        writer.reserve(f"file_name_{index}", "I")
        writer.pack("i", 0 if self.float_struct is None else 1)

        if platform in {TPFPlatform.PS4, TPFPlatform.XboxOne}:
            writer.pack("i", self.header.dxgi_format)

        if self.float_struct:
            self.float_struct.pack(writer)
Beispiel #6
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 #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)

        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()
Beispiel #8
0
 def pack(self, writer: BinaryWriter):
     for gx_item in self.gx_items:
         gx_item.pack(writer)
     writer.pack("iii", self.terminator_id, 100, self.terminator_null_count + 12)
     writer.pad(self.terminator_null_count)
Beispiel #9
0
    def pack(self):

        writer = BinaryWriter(big_endian=self.header.endian == b"B\0")
        encoding = ("utf-16-be" if writer.big_endian else
                    "utf-16-le") if self.header.unicode else "shift_jis_2004"

        true_face_count = 0
        total_face_count = 0
        for mesh in self.meshes:
            allow_primitive_restarts = len(
                mesh.vertices) < 2**16 - 1  # max unsigned short value
            for face_set in mesh.face_sets:
                face_set_true_count, face_set_total_count = face_set.get_face_counts(
                    allow_primitive_restarts)
                true_face_count += face_set_true_count
                total_face_count += face_set_total_count

        if self.header.version < Version.Bloodborne_DS3_A:
            # Set header's `vertex_index_size` to the largest size detected across all `FaceSet`s (16 or 32).
            header_vertex_indices_size = 16
            for mesh in self.meshes:
                for face_set in mesh.face_sets:
                    face_set_vertex_index_size = face_set.get_vertex_index_size(
                    )
                    header_vertex_indices_size = max(
                        header_vertex_indices_size, face_set_vertex_index_size)
        else:
            # Vertex size is stored per `VertexBuffer`.
            header_vertex_indices_size = 0

        self.header.pack(
            writer,
            dummy_count=len(self.dummies),
            material_count=len(self.materials),
            bone_count=len(self.bones),
            mesh_count=len(self.meshes),
            vertex_buffer_count=sum(
                len(mesh.vertex_buffers) for mesh in self.meshes),
            face_set_count=sum(len(mesh.face_sets) for mesh in self.meshes),
            buffer_layout_count=len(self.buffer_layouts),
            texture_count=sum(
                len(material.textures) for material in self.materials),
            true_face_count=true_face_count,
            total_face_count=total_face_count,
            vertex_indices_size=header_vertex_indices_size,
        )

        for dummy in self.dummies:
            dummy.pack(writer,
                       color_is_argb=self.header.version == Version.DarkSouls2)

        for material in self.materials:
            material.pack(writer)

        for bone in self.bones:
            bone.pack(writer)

        for mesh in self.meshes:
            mesh.pack(writer)

        for mesh in self.meshes:
            for face_set in mesh.face_sets:
                if header_vertex_indices_size == 0:
                    face_set_vertex_index_size = face_set.get_vertex_index_size(
                    )
                else:
                    face_set_vertex_index_size = header_vertex_indices_size
                face_set.pack(writer, face_set_vertex_index_size)

        for mesh in self.meshes:
            for i, vertex_buffer in enumerate(mesh.vertex_buffers):
                vertex_buffer.pack(
                    writer,
                    self.header.version,
                    mesh_vertex_buffer_index=i,
                    buffer_layouts=self.buffer_layouts,
                    mesh_vertex_count=len(mesh.vertices),
                )

        for i, buffer_layout in enumerate(self.buffer_layouts):
            buffer_layout.pack(writer)

        first_texture_index = 0
        for i, material in enumerate(self.materials):
            material.pack_textures(writer,
                                   first_texture_index=first_texture_index)
            first_texture_index += len(material.textures)

        # TODO: Write unknown Sekiro struct here.

        # Indexed data only after this point, with 16 pad bytes between each data type.

        writer.pad_align(16)
        for i, buffer_layout in enumerate(self.buffer_layouts):
            buffer_layout.pack_members(writer)

        writer.pad_align(16)
        for i, mesh in enumerate(self.meshes):
            mesh.pack_bounding_box(writer)

        writer.pad_align(16)
        bone_indices_start = writer.position
        for i, mesh in enumerate(self.meshes):
            mesh.pack_bone_indices(writer,
                                   bone_indices_start=bone_indices_start)

        writer.pad_align(16)
        first_face_set_index = 0
        for i, mesh in enumerate(self.meshes):
            mesh.pack_face_set_indices(writer, first_face_set_index)
            first_face_set_index += len(mesh.face_sets)

        writer.pad_align(16)
        first_vertex_buffer_index = 0
        for mesh in self.meshes:
            mesh.pack_vertex_buffer_indices(writer, first_vertex_buffer_index)
            first_vertex_buffer_index += len(mesh.vertex_buffers)

        writer.pad_align(16)
        gx_offsets = []
        for gx_list in self.gx_lists:
            gx_offsets.append(writer.position)
            gx_list.pack(writer)
        for material in self.materials:
            material.fill_gx_offset(writer, gx_offsets)

        writer.pad_align(16)
        for material in self.materials:
            material.pack_strings(writer, encoding)
            for texture in material.textures:
                texture.pack_zstring(writer, "path", encoding=encoding)
                texture.pack_zstring(writer, "texture_type", encoding=encoding)

        writer.pad_align(16)
        for bone in self.bones:
            bone.pack_zstring(writer, "name", encoding=encoding)

        alignment = 32 if self.header.version <= 0x2000E else 16
        writer.pad_align(alignment)
        if self.header.version in {Version.DarkSouls2_NT, Version.DarkSouls2}:
            writer.pad(32)

        vertex_data_start = writer.position
        self.header.fill(writer, vertex_data_offset=vertex_data_start)

        for mesh in self.meshes:
            for face_set in mesh.face_sets:
                if header_vertex_indices_size == 0:
                    face_set_vertex_index_size = face_set.get_vertex_index_size(
                    )
                else:
                    face_set_vertex_index_size = header_vertex_indices_size
                writer.pad_align(16)
                face_set.pack_vertex_indices(
                    writer,
                    vertex_index_size=face_set_vertex_index_size,
                    vertex_indices_offset=writer.position - vertex_data_start,
                )

            for vertex in mesh.vertices:
                vertex.prepare_pack()

            for vertex_buffer in mesh.vertex_buffers:
                writer.pad_align(16)
                uv_factor = 2048 if self.header.version >= Version.DarkSouls2_NT else 1024
                vertex_buffer.pack_buffer(
                    writer,
                    buffer_layouts=self.buffer_layouts,
                    vertices=mesh.vertices,
                    buffer_offset=writer.position - vertex_data_start,
                    uv_factor=uv_factor,
                )

            for vertex in mesh.vertices:
                vertex.finish_pack()

        writer.pad_align(16)
        self.header.fill(writer,
                         vertex_data_size=writer.position - vertex_data_start)
        if self.header.version in {Version.DarkSouls2_NT, Version.DarkSouls2}:
            writer.pad(32)

        return writer.finish()