mirror of
https://github.com/Escartem/AnimeWwise.git
synced 2026-06-05 16:00:27 +08:00
20
README.md
20
README.md
@@ -2,7 +2,7 @@
|
||||
An easy to use tool to extract audio from some anime games, with the original filenames and paths.
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
# 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)
|
||||

|
||||

|
||||
> [!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.
|
||||

|
||||
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
|
||||

|
||||
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
|
||||
|
||||
11
allocator.py
11
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()
|
||||
|
||||
125
app.py
125
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] == "":
|
||||
|
||||
10
bnk.py
10
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()
|
||||
|
||||
30
extract.py
30
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
|
||||
|
||||
@@ -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
|
||||
|
||||
135
mapper.py
135
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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
91
updater.ui
Normal file
91
updater.ui
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UpdaterWindow</class>
|
||||
<widget class="QWidget" name="UpdaterWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>100</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Updater</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>404</width>
|
||||
<height>83</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QLabel" name="title">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>6</x>
|
||||
<y>0</y>
|
||||
<width>391</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
<underline>true</underline>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Updating...</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>7</x>
|
||||
<y>42</y>
|
||||
<width>381</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="status">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>16</x>
|
||||
<y>62</y>
|
||||
<width>361</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": 211,
|
||||
"mapsVersion": 89
|
||||
"version": 220,
|
||||
"mapsVersion": 90
|
||||
}
|
||||
@@ -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])
|
||||
|
||||
20
wwise.py
20
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)
|
||||
|
||||
Reference in New Issue
Block a user