def unpack(self, reader: BinaryReader, **kwargs): reader.unpack_value("4s", asserted=b"TPF\0") self.platform = TPFPlatform(reader.unpack_value("B", offset=0xC)) reader.byte_order = ">" if self.platform in { TPFPlatform.Xbox360, TPFPlatform.PS3 } else "<" reader.unpack_value("i") # data length file_count = reader.unpack_value("i") reader.unpack_value("B") # platform self.tpf_flags = reader.unpack_value("B") if self.tpf_flags not in {0, 1, 2, 3}: raise ValueError( f"`TPF.tpf_flags` was {self.tpf_flags}, but expected 0, 1, 2, or 3." ) self.encoding = reader.unpack_value("B") if self.encoding not in {0, 1, 2}: raise ValueError( f"`TPF.encoding` was {self.encoding}, but expected 0, 1, or 2." ) reader.assert_pad(1) encoding = reader.get_utf_16_encoding( ) if self.encoding == 1 else "shift_jis_2004" self.textures = [ TPFTexture.unpack_from(reader, self.platform, self.tpf_flags, encoding) for _ in range(file_count) ]
def unpack_header(self, reader: BinaryReader) -> int: self.big_endian = reader.unpack_value("?", offset=0xD) reader.byte_order = ">" if self.big_endian else "<" self.bit_big_endian = reader.unpack_value("?", offset=0xE) reader.unpack_value("4s", asserted=b"BND3") self.signature = reader.unpack_value("8s").decode("ascii").rstrip("\0") self.flags = BinderFlags.read(reader, self.bit_big_endian) reader.byte_order = ">" if self.big_endian or self.flags.is_big_endian else "<" reader.seek(2, 1) # skip peeked endian bytes reader.assert_pad(1) entry_count = reader.unpack_value("i") reader.seek(12, 1) # skip file size return entry_count
def from_bnd3_reader(cls, reader: BinaryReader, binder_flags: BinderFlags, bit_big_endian: bool): flags = BinderEntryFlags.read(reader, bit_big_endian) reader.assert_pad(3) compressed_size = reader.unpack_value("i") data_offset = reader.unpack_value("q" if binder_flags.has_long_offsets else "I") entry_id = reader.unpack_value("i") if binder_flags.has_ids else None if binder_flags.has_names: path_offset = reader.unpack_value("i") path = reader.unpack_string(path_offset, encoding="shift-jis") # NOT `shift_jis_2004` else: path = None uncompressed_size = reader.unpack_value("i") if binder_flags.has_compression else None return cls( flags=flags, compressed_size=compressed_size, entry_id=entry_id, path=path, uncompressed_size=uncompressed_size, data_offset=data_offset, )
def from_bnd4_reader(cls, reader: BinaryReader, binder_flags: BinderFlags, bit_big_endian: bool, unicode: bool): flags = BinderEntryFlags.read(reader, bit_big_endian) reader.assert_pad(3) assert reader.unpack_value("i") == -1 compressed_size = reader.unpack_value("q") uncompressed_size = reader.unpack_value("q") if binder_flags.has_compression else None data_offset = reader.unpack_value("q" if binder_flags.has_long_offsets else "I") entry_id = reader.unpack_value("i") if binder_flags.has_ids else None if binder_flags.has_names: path_offset = reader.unpack_value("I") path = reader.unpack_string(path_offset, encoding="utf-16-le" if unicode else "shift-jis") else: path = None return cls( flags=flags, compressed_size=compressed_size, entry_id=entry_id, path=path, uncompressed_size=uncompressed_size, data_offset=data_offset, )
def unpack_header(self, reader: BinaryReader): reader.unpack_value("4s", asserted=b"BND4") self.unknown1 = reader.unpack_value("?") self.unknown2 = reader.unpack_value("?") reader.assert_pad(3) self.big_endian = reader.unpack_value("?") self.bit_big_endian = not reader.unpack_value("?") # note reversal reader.assert_pad(1) reader.byte_order = ">" if self.big_endian else "<" # no need to check flags for an override in BND4 entry_count = reader.unpack_value("i") reader.unpack_value("q", asserted=0x40) # header size self.signature = reader.unpack_value("8s").decode("ascii").rstrip("\0") entry_header_size = reader.unpack_value("q") data_offset = reader.unpack_value( "q") # end of all headers, including hash table self.unicode = reader.unpack_value("?") self.flags = BinderFlags.read(reader, self.bit_big_endian) self.hash_table_type = reader.unpack_value("B") reader.assert_pad(5) hash_table_offset = reader.unpack_value("q") flags_header_size = self.flags.get_bnd_entry_header_size() if entry_header_size != flags_header_size: raise ValueError( f"Expected BND entry header size {flags_header_size} based on flags\n" f"{self.flags:08b}, but BND header says {entry_header_size}.") if self.hash_table_type != 4 and hash_table_offset != 0: _LOGGER.warning( f"Found non-zero hash table offset {hash_table_offset}, but header says this BHD has no hash " f"table.") entry_headers = [ BinderEntryHeader.from_bnd4_reader(reader, self.flags, self.bit_big_endian, self.unicode) for _ in range(entry_count) ] if self.hash_table_type == 4: # Save the initial hash table. reader.seek(hash_table_offset) self._most_recent_hash_table = reader.read(data_offset - hash_table_offset) return entry_headers
def unpack_header(self, reader: BinaryReader): reader.unpack_value("4s", asserted=b"BND4") self.unknown1 = reader.unpack_value("?") self.unknown2 = reader.unpack_value("?") reader.assert_pad(3) self.big_endian = reader.unpack_value("?") self.bit_big_endian = not reader.unpack_value("?") # note reversal reader.assert_pad(1) reader.byte_order = ">" if self.big_endian else "<" # no need to check flags for an override in BND4 entry_count = reader.unpack_value("i") reader.unpack_value("q", asserted=0x40) # header size self.signature = reader.unpack_value("8s").decode("ascii").rstrip("\0") entry_header_size = reader.unpack_value("q") data_offset = reader.unpack_value( "q") # end of all headers, including hash table self.unicode = reader.unpack_value("?") self.flags = BinderFlags.read(reader, self.bit_big_endian) self.hash_table_type = reader.unpack_value("B") reader.assert_pad(5) hash_table_offset = reader.unpack_value("q") return entry_count, entry_header_size, hash_table_offset, data_offset
def unpack_from( cls, reader: BinaryReader, platform: TPFPlatform, tpf_flags: int, encoding: str, tpf_path: tp.Union[None, str, Path] = None, ): self = cls() self.tpf_path = tpf_path file_offset = reader.unpack_value("I") file_size = reader.unpack_value("i") self.format = reader.unpack_value("B") self.texture_type = TextureType(reader.unpack_value("B")) self.mipmaps = reader.unpack_value("B") self.texture_flags = reader.unpack_value("B") if self.texture_flags not in {0, 1, 2, 3}: raise ValueError( f"`TPFTexture.flags1` was {self.texture_flags}, but expected 0, 1, 2, or 3." ) if platform != TPFPlatform.PC: self.header = TextureHeader self.header.width = reader.unpack_value("h") self.header.height = reader.unpack_value("h") if platform == TPFPlatform.Xbox360: reader.assert_pad(4) elif platform == TPFPlatform.PS3: self.header.unk1 = reader.unpack_value("i") if tpf_flags != 0: self.header.unk2 = reader.unpack_value("i") if self.header.unk2 not in {0, 0x68E0, 0xAAE4}: raise ValueError( f"`TextureHeader.unk2` was {self.header.unk2}, but expected 0, 0x68E0, or 0xAAE4." ) elif platform in {TPFPlatform.PS4, TPFPlatform.XboxOne}: self.header.texture_count = reader.unpack_value("i") if self.header.texture_count not in {1, 6}: f"`TextureHeader.texture_count` was {self.header.texture_count}, but expected 1 or 6." self.header.unk2 = reader.unpack_value("i") if self.header.unk2 != 0xD: f"`TextureHeader.unk2` was {self.header.unk2}, but expected 0xD." name_offset = reader.unpack_value("I") has_float_struct = reader.unpack_value("i") == 1 if platform in {TPFPlatform.PS4, TPFPlatform.XboxOne}: self.header.dxgi_format = reader.unpack_value("i") if has_float_struct: self.float_struct = FloatStruct.unpack_from(reader) with reader.temp_offset(file_offset): self.data = reader.read(file_size) if self.texture_flags in {2, 3}: # Data is DCX-compressed. # TODO: should enforce DCX type as 'DCP_EDGE'? self.data = decompress(self.data) self.name = reader.unpack_string(offset=name_offset, encoding=encoding) return self