diff --git a/README.md b/README.md index 922d007..6735f7d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An easy to use tool to extract audio from some anime games, with the original filenames and paths. -![image](https://github.com/user-attachments/assets/e66048df-4d71-4bda-8201-1c2c67f44de7) +![image](https://github.com/user-attachments/assets/ce2c8b19-82a2-42fc-a149-ed9ffbb7c54b) # Usage @@ -12,7 +12,7 @@ An easy to use tool to extract audio from some anime games, with the original fi 2. Install dependencies -> `pip install -r requirements.txt` 3. Run the app with `python app.py` 4. Select your input folder containing your `.pck` files, it can be your game audio folder directly (if you decide to use this one, make sure the game is not running) -![image](https://github.com/user-attachments/assets/72cf7983-00d0-4e98-b0d0-8b5547057a56) +![image](https://github.com/user-attachments/assets/e877a57a-a115-4c2e-beac-27d927d1a37e) > [!TIP] > The audio folder can be found in the following locations > - `GenshinImpact_Data\StreamingAssets\AudioAsset\...` @@ -23,10 +23,10 @@ An easy to use tool to extract audio from some anime games, with the original fi > Diff files are `.hdiff` present in the update patches of the games. If you want to extract an hdiff content, you must have the pck file with the *same name before patch* in the input folder, pck's that do not have a corresponding hdiff file will be extracted normally, when they do have a corresponding hdiff file, *only the hdiff file content is extracted* and not the full pck 6. Select a mapping > [!WARNING] -> By default, the files extracted from the game don't have names, the mappings are here to help restore the original filenames and paths so it's easier to search, but not all games are supported, not at every version and the mapping does not guarantee to have every file named +> By default, the files extracted from the game don't have names, the mappings are here to help restore the original filenames and paths so it's easier to search, there are only mappings for hoyo games and their coverage varies 7. After that, you can browse the files you loaded, if you messed up and wanna go back, you can select File > Reset to unload everything and go back to the starting screen. -![image](https://github.com/user-attachments/assets/9714b6ab-527a-49d9-ae98-354d1979a2b9) -8. In the `Extract` tab, you will be able to select what audio you want, choosing the output folder and audio format. You can extract everything or extract the files you selected in the `Browse` tab +![image](https://github.com/user-attachments/assets/73e2ece9-9fa7-4149-8674-762adb1ef50c) +8. In the `Extract` menu, you will be able to select what audio you want, choosing the output folder and audio format. You can extract everything or extract the files you selected > [!NOTE] > The program does not check for existing files in the output folder, it will overwrite them, make sure to check your folder before starting the extraction 9. Extract your files, and enjoy ! @@ -42,3 +42,13 @@ The program has been tested and proved to be very efficient with extraction (not # Contribute Feel free to contribute to this project as much as you want, a share would be very appreciated aswell, I'll be glad to know if this helped anyone <3 + +# Credits + +- [@Razmoth](https://github.com/Razmoth) - help on figuring out keys parsing to recover names for genshin and zzz +- [@Dimbreath](https://github.com/Dimbreath) - AnimeGameData, TurnBasedGameData and ZZZData +- [@Kei-Luna](https://github.com/Kei-Luna) - instructions on recovering names for genshin music +- [@davispuh](https://github.com/davispuh) - star rail keys bruteforce tool +- [@bnnm](https://github.com/bnnm) - wwise audio exploration tool +- @hcs - wwise audio extraction script +- [@vgmstream](https://github.com/vgmstream) and their contributors - wwise headers parsing diff --git a/allocator.py b/allocator.py index 1d9f424..65dfddb 100644 --- a/allocator.py +++ b/allocator.py @@ -6,23 +6,22 @@ class Allocator: def __init__(self): self.files = {} - def load_file(self, path): - filename = os.path.basename(path) + def load_file(self, path, name): with open(path, "r+b") as f: mmap_object = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) - self.files[filename] = mmap_object + self.files[name] = mmap_object def unload_file(self, name): - self.files[os.path.basename(name)].close() + self.files[name].close() def read_at(self, file, offset, size): - mmap_object = self.files[os.path.basename(file)] + mmap_object = self.files[file] mmap_object.seek(offset) data = mmap_object.read(size) return data def free_mem(self): for file in list(self.files.keys()): - self.files[os.path.basename(file)].close() + self.files[file].close() self.files.clear() diff --git a/app.py b/app.py index afcec86..a620467 100644 --- a/app.py +++ b/app.py @@ -2,14 +2,16 @@ import os import sys import json import math +import time import extract import platform +import urllib import webbrowser from PyQt5 import uic from requests import get from PyQt5.QtGui import QTextCursor from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType, Qt -from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem, QAction, QActionGroup +from PyQt5.QtWidgets import QDesktopWidget, QDialog, QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem, QAction, QActionGroup QMetaType.type("QTextCursor") @@ -70,6 +72,111 @@ class BackgroundWorker(QObject): self.extract.extract_files(self.input, self.files, self.output, self.format, progress=self.progress.emit) self.finished.emit({"action": "extract"}) +class UpdaterWorker(QObject): + finished = pyqtSignal(bool) + progress = pyqtSignal(list) + + def __init__(self): + super().__init__() + + def run(self): + try: + # know current and latest maps + self.progress.emit([0, "Fetching index..."]) + ver = lambda s: int(s.replace(".", "")) + + index = open("maps/index.json", "r") + currentMaps = json.loads(index.read()) + index.close() + + latestMaps = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/maps/index.json") + + if latestMaps.status_code == 200: + latestMaps = json.loads(latestMaps.text) + + # do each game + n_games = len(latestMaps["maps"]) + game_size = 95 // n_games + for i in range(n_games): + current = currentMaps["maps"][i] + latest = latestMaps["maps"][i] + name = f"maps/{latest['name']}" + + if (ver(current["version"]) < ver(latest["version"])) or not os.path.isfile(name): + self.progress.emit([5 + game_size * i, f'Updating {latest["game"]} to {latest["version"]}']) + + url = f"https://raw.githubusercontent.com/Escartem/AnimeWwise/master/{name}" + urllib.request.urlretrieve(url, "maps/temp.map") + + if os.path.isfile(name): + os.remove(name) + os.rename("maps/temp.map", name) + + # update index + currentMaps["maps"][i]["version"] = latest["version"] + + # save new index + index = open("maps/index.json", "w+") + index.write(json.dumps(currentMaps, indent=4)) + index.close() + + index_sum = sum([ver(e["version"]) for e in currentMaps["maps"]]) + + with open("version.json", "r+") as f: + data = json.loads(f.read()) + data["mapsVersion"] = index_sum + f.seek(0) + f.write(json.dumps(data, indent=4)) + f.truncate() + f.close() + + # done + self.progress.emit([100, "Update finished ! The program will start shortly..."]) + except Exception as e: + # failure :( + self.progress.emit([100, f"Update failed ! The program will start shortly... | {e}"]) + + time.sleep(3) + self.finished.emit(True) + + +class Updater(QDialog): + def __init__(self): + super(Updater, self).__init__() + uic.loadUi("updater.ui", self) + self.setWindowFlag(Qt.WindowCloseButtonHint, False) + self.center() + self.update() + + def center(self): + qr = self.frameGeometry() + cp = QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) + + def update(self): + self.backgroundThread = QThread() + self.backgroundWorker = UpdaterWorker() + self.backgroundWorker.moveToThread(self.backgroundThread) + self.backgroundThread.started.connect(self.backgroundWorker.run) + self.backgroundWorker.finished.connect(self.updateFinished) + self.backgroundWorker.finished.connect(self.backgroundThread.quit) + self.backgroundWorker.finished.connect(self.backgroundWorker.deleteLater) + self.backgroundThread.finished.connect(self.backgroundThread.deleteLater) + + self.backgroundWorker.progress.connect(self.updateProgress) + self.backgroundThread.start() + + @pyqtSlot(bool) + def updateFinished(self): + self.close() + + @pyqtSlot(list) + def updateProgress(self, data): + self.progressBar.setValue(data[0]) + if len(data) == 2: + self.status.setText(data[1]) + class AnimeWwise(QMainWindow): def __init__(self): super(AnimeWwise, self).__init__() @@ -87,6 +194,8 @@ class AnimeWwise(QMainWindow): sys.stdout = TextEditStream(self.console) self.extract = extract.WwiseExtract() self.checkUpdates() + self.totalProgress.setMaximum(10000) + self.fileProgress.setMaximum(10000) # utils self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder") @@ -102,10 +211,12 @@ class AnimeWwise(QMainWindow): if currentVersion["version"] < latestVersion["version"]: print("Update found !") - QMessageBox.information(None, "Info", "Newer version of the program is availble, please update.", QMessageBox.Ok) + QMessageBox.information(None, "Info", "Newer version of the program is availble, please update it.", QMessageBox.Ok) elif currentVersion["mapsVersion"] < latestVersion["mapsVersion"]: print("Update found !") - QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program.", QMessageBox.Ok) + QMessageBox.information(None, "Info", "Newer version of the mappings are availble, the program will update them now.", QMessageBox.Ok) + self.updaterWindow = Updater() + self.updaterWindow.exec_() else: print("No updates") except: @@ -210,10 +321,13 @@ class AnimeWwise(QMainWindow): # workers @pyqtSlot(list) def progressBarSlot(self, progress): + progress_value = math.ceil(progress[1]*100) if progress[0] == "total": - self.totalProgress.setValue(math.ceil(progress[1])) + self.totalProgress.setValue(progress_value) + self.totalProgress.setFormat("%.02f %%" % (progress_value / 100)) elif progress[0] == "file": - self.fileProgress.setValue(math.ceil(progress[1])) + self.fileProgress.setValue(progress_value) + self.fileProgress.setFormat("%.02f %%" % (progress_value / 100)) @pyqtSlot(dict) def handleFinished(self, data): @@ -250,6 +364,7 @@ class AnimeWwise(QMainWindow): files = [os.path.join(self.folders["input"], f) for f in os.listdir(self.folders["input"]) if f.endswith(".pck")] elif self.loadType == "file": path = QFileDialog.getOpenFileName(self, "Select .pck File", "", "PCK Files (*.pck)", options=QFileDialog.Options()) + self.folders["input"] = os.path.dirname(path[0]) files = [path[0]] if len(files) == 0 or files[0] == "": diff --git a/bnk.py b/bnk.py index ab48c84..520e3ce 100644 --- a/bnk.py +++ b/bnk.py @@ -2,24 +2,27 @@ import io from filereader import FileReader -def bnk2wem(data): +def bnk2wem(data, name): # gets raw data from object - reader = FileReader(io.BytesIO(data), "little") + reader = FileReader(io.BytesIO(data), "little", name=name) bkhd_signature = reader.ReadBytes(4) if bkhd_signature != b"\x42\x4B\x48\x44": - raise Exception("not a valid bnk") + print(f"[WARNING] invalid bkhd signature at {reader.GetName()}") + return [] bkhd_size = reader.ReadUInt32() reader.ReadBytes(bkhd_size) if reader.GetBufferPos() == reader.GetStreamLength(): + print(f"[WARNING] empty bnk file at {reader.GetName()}") return [] # empty bnk didx_signature = reader.ReadBytes(4) if didx_signature != b"\x44\x49\x44\x58": + print(f"[WARNING] invalid didx signature at {reader.GetName()}") return [] # invalid index signature (hirc block instead ?) didx_size = reader.ReadUInt32() @@ -35,6 +38,7 @@ def bnk2wem(data): data_signature = reader.ReadBytes(4) if data_signature != b"\x44\x41\x54\x41": + print(f"[WARNING] invalid data signature at {reader.GetName()}") return [] # invalid data signature (missing sector ?) data_size = reader.ReadUInt32() diff --git a/extract.py b/extract.py index 85e2f57..57b581e 100644 --- a/extract.py +++ b/extract.py @@ -1,5 +1,6 @@ import os import io +import json import wwise import tempfile import wavescan @@ -11,7 +12,12 @@ from filereader import FileReader cwd = os.getcwd() path = lambda *args: os.path.join(*args) -call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + +def call(args): + try: + subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + except Exception as e: + print(f"[WARNING] failed to extract, {e}") class WwiseExtract: def __init__(self): @@ -154,9 +160,22 @@ class WwiseExtract: filename = f"{filename} (hdiff)" files = [*files[0], *files[1]] + # in case of manual use of mapping, use this + # load json here + + # handle = open("banks.json", "r") + # banks = json.loads(handle.read()) + # handle.close() + for file in files: if mapper is not None: key = mapper.get_key(file[0].split(".")[0]) + + # and override the method with a manual dict lookup + + # _id = file[0].split(".")[0] + # if _id in list(banks["banks"].keys()): + # key = [banks["banks"][_id], ""] else: key = None @@ -170,6 +189,9 @@ class WwiseExtract: wem_data = data[file_data["offset"]:file_data["offset"]+file_data["size"]] parsed_wem = wwise.parse_wwise(FileReader(io.BytesIO(wem_data), "little", name=f"{file[3]}:{file[0]}:{file[1]}")) + if not parsed_wem: + continue + file_data["metadata"] = parsed_wem if key is not None: @@ -285,7 +307,7 @@ class WwiseExtract: if os.path.isfile(hdiff_path): load_path = hdiff_path - self.allocator.load_file(load_path) + self.allocator.load_file(load_path, source) # extract every file from this one for file in [file for file in files if file["source"] == source]: @@ -371,8 +393,8 @@ class WwiseExtract: def update_progress(self, current, total, step): base = 100 / self.steps - self.progress(["total", current * base // total + base * (step - 1)]) - self.progress(["file", current * 100 // total]) + self.progress(["total", current * base / total + base * (step - 1)]) + self.progress(["file", current * 100 / total]) def reset(self): self.mapper = None diff --git a/filereader.py b/filereader.py index 507a8ff..905bf69 100644 --- a/filereader.py +++ b/filereader.py @@ -11,8 +11,7 @@ class FileReader: def __init__(self, file, endianness:str, name:str=None): self.stream = file self.endianness = endianness - if name: - self.name = name + self.name = name def _read(self, mode:str, bufferLength:int, endianness:str=None, pos:int=None) -> bytes: # endianness override diff --git a/mapper.py b/mapper.py index c2ff03e..0cdace8 100644 --- a/mapper.py +++ b/mapper.py @@ -15,15 +15,12 @@ class Mapper: reader.ReadBytes(2) map_version = reader.ReadBytes(2) - if map_version == b"\x56\x31": - print(f"Warning: you are using an old version of the mapping that is no longer supported, please use a newer one or download an older version of this tool.") - raise Exception("outdated mapping") - elif map_version == b"\x56\x32": - self.reader = reader - self.process_map() - else: - file.close() - raise Exception("invalid mapping version") + if map_version != b"\x32\x31": + print(f"Warning: you are using an unknown / unsupported version of the mapping that is no longer supported, please use a newer one or download an older version of this tool.") + raise Exception("incompatible mapping") + + self.reader = reader + self.process_map() def process_map(self): reader = self.reader @@ -33,15 +30,14 @@ class Mapper: vl2 = lambda data: int.from_bytes(data, "little") raw = lambda length: rw2(reader.ReadBytes(length)) rw2 = lambda data: data.rstrip(b"\x00").decode("utf-8") - n2p = lambda val: [e[0] for e in enumerate(list(bin(val)[2:][::-1])) if e[1] == "1"] # get map meta reader.ReadBytes(2) games = { - "ys": "Genshin", - "sr": "Star Rail", - "zzz": "Zenless Zone Zero" + "hk4e": "Genshin", + "hkrpg": "Star Rail", + "nap": "Zenless Zone Zero" # more later } @@ -54,18 +50,46 @@ class Mapper: "sfx" ] - header_size = val(1) # header size - block_size = 4 - header_blocks = [reader.ReadBytes(block_size) for _ in range(header_size // block_size)] + # read sectors + sectors_signature = reader.ReadBytes(9) + if sectors_signature != b"\xFF\x53\x45\x43\x54\x4F\x52\x53\xFF": # ff sectors ff + raise Exception("invalid mapping sectors signature") + + n_sectors = val(1) + sectors = {} + + for i in range(n_sectors): + name_length = val(1) + name = raw(name_length) + offset = val(4) + size = val(4) + + sectors[name] = { + "offset": offset, + "size": size + } + + # read config + reader.SetBufferPos(sectors["HEADER"]["offset"]) + + header_sig = reader.ReadBytes(8) # hardcoded but lazy, this value is for this sector only + + n_configs = val(1) + config = {} + for i in range(n_configs): + name = raw(4) + value = raw(5) + config[name] = value infos = { - "game": games[rw2(header_blocks[0])], - "version": list(rw2(header_blocks[1])), - "coverage": int(rw2(header_blocks[2])), - # more later + "game": games[config["game"]], + "version": config["verS"], + # "coverage": config["covR"], + "useBanksSector": config["bnkS"], + # "bankSectorCoverage": config["bCov"] } - print(f"> Loading mapping for {infos['game']} v{infos['version'][0]}.{infos['version'][1]}, this may take a few seconds...") + print(f"> Loading mapping for {infos['game']} v{infos['version']}, this may take a few seconds...") # read prefixes prefixes = {} @@ -77,6 +101,11 @@ class Mapper: marker = reader.ReadBytes(1) prefixes[marker] = prefix + # sector jump here + reader.SetBufferPos(sectors["ITEMS"]["offset"]) + + items_sec_sig = reader.ReadBytes(7) # hardcoded too + # read languages langs_offsets = {} n_langs = reader.ReadUInt8() @@ -90,7 +119,7 @@ class Mapper: # read folders folder_offsets = {} - n_folders = reader.ReadUInt8() + n_folders = reader.ReadUInt16() for i in range(n_folders): offset = reader.GetBufferPos() @@ -128,42 +157,68 @@ class Mapper: self.files_offsets = files_offsets # read keys - # GI 3649050 + # GI 3649050 (outdated value, use items sector size instead) keys_data = {} n_keys = val(3) left = reader.GetRemainingLength() + if infos["useBanksSector"] == "TRUE": + left -= sectors["BANKS"]["size"] + data = bytearray(reader.ReadBytes(left)) keys_data = {rw2(data[i:i+16]): bytes(data[i+16:i+21]) for i in range(0, len(data), 21)} self.keys_data = keys_data + # read banks sector + bank_keys = {} + if infos["useBanksSector"] == "TRUE": + reader.SetBufferPos(sectors["BANKS"]["offset"]) + + banks_sec_sig = reader.ReadBytes(7) # hardcoded + + global_path_size = val(1) + global_path = raw(global_path_size) + + n_bank_keys = val(2) + + for i in range(n_bank_keys): + key_length = val(1) + key = raw(key_length) + value_length = val(1) + value = raw(value_length) + + bank_keys[key] = f"{global_path}\\{value}" + + self.bank_keys = bank_keys + # done print(f"> Finished loading mapping") - print(f": {n_langs} supported languages") + print(f"=-=-= Voicelines sector =-=-=") + print(f": {n_langs} languages") print(f": {n_files} mapped files") - print(f": {n_keys} available keys") - print(f"") - print(f"> Mapping coverage") - coverage = n2p(infos["coverage"]) - for val in coverage: - if val%2 == 0: - print(f": partial {coverages[val//2-1]}") - else: - print(f": {coverages[(val-1)//2]}") + print(f": {n_keys} keys") + if infos["useBanksSector"] == "TRUE": + print(f"=-=-= Music sector =-=-=") + print(f": {n_bank_keys} keys") def get_key(self, key, lang=False): keys_data = self.keys_data - if key not in keys_data.keys(): - return None + banks_data = self.bank_keys - key_data = keys_data[key] - data = [self.files_offsets[int.from_bytes(key_data[2:], "little")]] + if key in keys_data.keys(): + key_data = keys_data[key] + data = [self.files_offsets[int.from_bytes(key_data[2:], "little")]] - if lang: - data.append(self.langs_offsets[int.from_bytes(key_data[:1], "little")]) + if lang: + data.append(self.langs_offsets[int.from_bytes(key_data[:1], "little")]) - return data + return data + + if key in banks_data.keys(): + return [banks_data[str(key)], ""] + + return None def reset(self): self.reader = None diff --git a/maps/index.json b/maps/index.json index 96d61ca..1b1c8fe 100644 --- a/maps/index.json +++ b/maps/index.json @@ -1,17 +1,19 @@ -{"maps": [ - { - "name": "hk4e.map", - "game": "Genshin Impact", - "version": "5.2" - }, - { - "name": "hkrpg.map", - "game": "Star Rail", - "version": "2.6" - }, - { - "name": "nap.map", - "game": "Zenless Zone Zero", - "version": "1.2" - } -]} \ No newline at end of file +{ + "maps": [ + { + "name": "hk4e.map", + "game": "Genshin Impact", + "version": "5.2" + }, + { + "name": "hkrpg.map", + "game": "Honkai: Star Rail", + "version": "2.6" + }, + { + "name": "nap.map", + "game": "Zenless Zone Zero", + "version": "1.2" + } + ] +} \ No newline at end of file diff --git a/updater.ui b/updater.ui new file mode 100644 index 0000000..a8f2f8b --- /dev/null +++ b/updater.ui @@ -0,0 +1,91 @@ + + + UpdaterWindow + + + + 0 + 0 + 400 + 100 + + + + + 400 + 100 + + + + + 400 + 100 + + + + Updater + + + + + 0 + 0 + 404 + 83 + + + + + + 6 + 0 + 391 + 31 + + + + + 16 + 75 + true + true + + + + Updating... + + + + + + 7 + 42 + 381 + 16 + + + + 0 + + + false + + + + + + 16 + 62 + 361 + 21 + + + + Status + + + + + + + diff --git a/version.json b/version.json index a3cbb45..2328d43 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": 211, - "mapsVersion": 89 + "version": 220, + "mapsVersion": 90 } \ No newline at end of file diff --git a/wavescan.py b/wavescan.py index 55e1fa8..af33961 100644 --- a/wavescan.py +++ b/wavescan.py @@ -220,7 +220,7 @@ def extract_sector(section_size, is_sounds, is_externals, ext, endianness, lang_ bnk_data = reader.ReadBytes(size) reader.SetBufferPos(pos) - wems = bnk2wem(bnk_data) + wems = bnk2wem(bnk_data, f"{filename}@{pos}.{size}") for wem in wems: wwise_data.append([f"{os.path.basename(name).split('.')[0]}_{wem[0]}.wem", offset+wem[1], wem[2], filename]) diff --git a/wwise.py b/wwise.py index 0a0dd4b..07e4a90 100644 --- a/wwise.py +++ b/wwise.py @@ -23,7 +23,7 @@ def parse_wwise(reader): if reader.GetStreamLength() == 0: print(f"[WARNING] null stream size at {reader.GetName()}, unreadable block") - return metadata + return None header = reader.ReadBytes(4) @@ -34,7 +34,7 @@ def parse_wwise(reader): reader.endianness = "little" else: print(f"[WARNING] invalid header {header} at {reader.GetName()}, assuming unreadable") - return metadata + return None # additional check reader.SetBufferPos(0x08) @@ -42,7 +42,7 @@ def parse_wwise(reader): if check != b"WAVE" and check != "XWMA": print(f"[WARNING] invalid check mark {check}, assuming unreadable") - return metadata + return None # read chunks reader.SetBufferPos(0x0C) @@ -71,7 +71,7 @@ def parse_wwise(reader): fmt_length = chunks["fmt"]["length"] if fmt_length < 0x10: print(f"[WARNING] invalid fmt chunk length {fmt_length} at {reader.GetName()}, skipping") - return metadata + return None reader.SetBufferPos(chunks["fmt"]["offset"]) @@ -94,7 +94,7 @@ def parse_wwise(reader): if metadata["format"] == 0x0166: print(f"[WARNING] XMA2WAVEFORMATEX in fmt at {reader.GetName()}") - return metadata + return None # parse codec codecs = { @@ -122,7 +122,7 @@ def parse_wwise(reader): if metadata["format"] not in codecs: print(f'[WARNING] unknown codec {metadata["format"]} at {reader.GetName()}') - return metadata + return None codec = codecs[metadata["format"]] @@ -153,23 +153,25 @@ def parse_wwise(reader): elif metadata["codec"] == "VORBIS": if (metadata["blockSize"] != 0 or metadata["bitsPerSample"] != 0): print(f"[WARNING] worbis type at {reader.GetName()}, skipping") - return metadata + return None if "vorb" in chunks: # vorb chunk only in wwise earlier to 2012, therefore impossible for mihoyo games print(f"[WARNING] found vorb chunk at {reader.GetName()}, is this the correct game ?") - return metadata + return None extra_offset = chunks["fmt"]["offset"] + 0x18 if metadata["extraSize"] != 0x30: print(f"[WARNING] unknown extra wwise size at {reader.GetName()}, skipping") - return metadata + return None data_offset = 0x10 blocks_offset = 0x28 # define header to type 2, packet to modified and codebook to aoTuV603, required ? + # this somehow breaks and don't read correctly, why :c + # stream_size * 8 * sample_rate / num_samples = bitrate * 1000 metadata["numSamples"] = reader.ReadInt32(extra_offset) setup_offset = reader.ReadUInt32(extra_offset + data_offset) audio_offset = reader.ReadUInt32(extra_offset + data_offset + 0x04)