diff --git a/filereader.py b/filereader.py new file mode 100644 index 0000000..1ea5943 --- /dev/null +++ b/filereader.py @@ -0,0 +1,57 @@ +import struct + + +class FileReader: + """ + Simplified byte file reader with buffer + Not particularly optimised, contains repetitive functions + In the scope of this project, not everything will be used either + + """ + def __init__(self, file, endianness: str): + self.stream = file + self.endianness = endianness + + def _read(self, mode:str, bufferLength:int, endianness=None) -> bytes: + # endianness override + if endianness is None: + endianness = self.endianness + + endianness = "<" if endianness == "little" else ">" + + return struct.unpack(f"{endianness}{mode}", bytearray(self.stream.read(bufferLength)))[0] + + # read methods + def ReadInt16(self, endianness=None) -> int: + return self._read("h", 2, endianness) + + def ReadUInt16(self, endianness=None) -> int: + return self._read("H", 2, endianness) + + def ReadInt32(self, endianness=None) -> int: + return self._read("i", 4, endianness) + + def ReadUInt32(self, endianness=None) -> int: + return self._read("I", 4, endianness) + + def ReadLong(self, endianness=None) -> int: + return self._read("l", 4, endianness) + + def ReadULong(self, endianness=None) -> int: + return self._read("L", 4, endianness) + + def ReadLongLong(self, endianness=None) -> int: + return self._read("q", 8, endianness) + + def ReadULongLong(self, endianness=None) -> int: + return self._read("Q", 8, endianness) + + def ReadBytes(self, length:int, endianness=None) -> bytes: + return self._read(f"{str(length)}s", int(length), endianness) + + # buffer utils + def GetBufferPos(self) -> int: + return self.stream.tell() + + def SetBufferPos(self, pos:int): + self.stream.seek(pos) diff --git a/wavescan.py b/wavescan.py new file mode 100644 index 0000000..560a349 --- /dev/null +++ b/wavescan.py @@ -0,0 +1,212 @@ +# Custom rewrite of the Wwise AKPK packages extractor, original by Nicknine and bnnm +# TODO: use extracted files id with the mapping table to restore file names +# TODO: make the mapping table :( +from filereader import FileReader +import os + +filename = "External0.pck" +cwd = os.getcwd() +reader = FileReader(open(filename, "rb"), "little") # defaults to little endian +bank_version = 0 + +def main(): + global bank_version + + # check file + if reader.ReadBytes(4) != b"AKPK": + print("Not a valid file") + return + + # check endianness + reader.SetBufferPos(0x08) + endian_check = reader.ReadLong() # this is the same bytes as the flag sector, which seems to be always 1 + + if endian_check == 1: + endianness = 0 # little + elif endian_check == 0x1000000: + endianness = 1 # big + else: + print("uknown endianness, aborting") + return + + # retrieve sectors in header + reader.SetBufferPos(0x04) + + header_size = reader.ReadLong() + flag = reader.ReadLong() + + languages_sector_size = reader.ReadLong() + banks_sector_size = reader.ReadLong() + sounds_sector_size = reader.ReadLong() + externals_sector_size = 0 + + if languages_sector_size + banks_sector_size + sounds_sector_size + 0x10 < header_size: + externals_sector_size = reader.ReadLong() + + sectors = [[True, banks_sector_size, 0, 0, "bnk"], [False, sounds_sector_size, 1, 0, "wem"], [False, externals_sector_size, 1, 1, "wem"]] + + # get langs in the file + lang_array = get_langs(languages_sector_size) + + # extract each sector + for sector in sectors: + extract_sector(*sector[1:], endianness, lang_array, bank_version) + + if sector[0] and bank_version == 0: + if externals_sector_size == 0: + print("can't detect bank version") + bank_version = 62 + +def get_langs(langs_sector_size): + string_offset = reader.GetBufferPos() + lang_array = {} + langs = reader.ReadLong() + + for i in range(langs): + lang_offset = reader.ReadLong() + lang_id = reader.ReadLong() + + lang_offset += string_offset + + current = reader.GetBufferPos() + + reader.SetBufferPos(lang_offset) + + # get dummy bytes to detect encoding + test_byte_1 = reader.ReadBytes(1) + test_byte_2 = reader.ReadBytes(1) + + reader.SetBufferPos(lang_offset) + + if test_byte_1 == 0 or test_byte_2 == 0: + lang_name = reader.ReadBytes(0x20).decode("utf-16le").replace("\x00", "") + else: + lang_name = reader.ReadBytes(0x10).decode("utf-8").replace("\x00", "") + + lang_array[lang_id] = lang_name + + reader.SetBufferPos(current) + + reader.SetBufferPos(string_offset + langs_sector_size) + + return lang_array + +def detect_bank_version(offset): + global bank_version + + current = reader.GetBufferPos() + reader.SetBufferPos(offset) + + # maybe update buffer pos instead + dummy = reader.ReadLong() + dummy = reader.ReadLong() + + bank_version = reader.ReadLong() + + if bank_version > 0x1000: + print("wrong bank version") + bank_version = 62 + + reader.SetBufferPos(current) + +def extract_sector(section_size, is_sounds, is_externals, ext, endianness, lang_array, bank_version, filter_bnk_only=0, filter_wem_only=0): + # check sector validity + if section_size == 0: + return + files = reader.ReadLong() + if files == 0: + return + + entry_size = (section_size - 0x04) / files + + if entry_size == 0x18: + alt_mode = 1 + else: + alt_mode = 0 + + for i in range(files): + # ids must be unsigned here, if signed you need to do id += 2**32 afterwards + if alt_mode == 1 and is_externals == 1: + if endianness == 0: + file_id_2 = reader.ReadULong() + file_id_1 = reader.ReadULong() + else: + file_id_1 = reader.ReadULong() + file_id_2 = reader.ReadULong() + else: + file_id = reader.ReadULong() + + block_size = reader.ReadLong() + + # get file size + if alt_mode == 1 and is_externals == 1: + size = reader.ReadLong() + elif alt_mode == 1: + size = reader.ReadLongLong() + else: + size = reader.ReadLong() + + offset = reader.ReadLong() + lang_id = reader.ReadLong() + + if block_size != 0: + offset *= block_size + + # bank version must be detected at this offset + if is_sounds == 0 and bank_version == 0: + detect_bank_version(offset) + + # update extension for olders banks using differents codecs + if is_sounds == 1 and bank_version < 62: + current = reader.GetBufferPos() + + codec_offset = offset + 0x14 + reader.SetBufferPos(codec_offset) + + codec = reader.ReadInt16() + + if codec == 0x0401 or codec == 0x0166: + ext = "xma" + elif codec == 0xFFFF: + ext = "ogg" + else: + ext = "wav" + + reader.SetBufferPos(current) + + # set file path + if lang_id == 0: + path = "" + else: + path = "".join([f"{e}/" for e in list(lang_array.values())]) + + # se file name + if alt_mode == 1 and is_externals == 1: + name = f"externals/{path}{ID1:08x}{ID2:08x}.{ext}" + else: + name = f"{path}{ID}.{ext}" + + # filtering utilities + if filter_bnk_only == 1 and ext != "bnk": + continue + + if filter_wem_only == 1 and ext != "wem": + continue + + print(f"NAME - {name} | OFFSET - {offset} | SIZE - {size}") + + # save file into disk + current = reader.GetBufferPos() + reader.SetBufferPos(offset) + file_data = reader.ReadBytes(size) + + os.makedirs(os.path.join(cwd, os.path.dirname(name)), exist_ok=True) + + with open(os.path.join(cwd, name), "wb+") as f: + f.write(file_data) + f.close() + + reader.SetBufferPos(current) + +if __name__ == "__main__": + main()