1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-04 23:40:25 +08:00

add endfield support

This commit is contained in:
Escartem
2026-02-03 18:26:48 -05:00
parent bf00e1538b
commit 18b8bd7703
8 changed files with 152 additions and 18 deletions

9
app.py
View File

@@ -359,11 +359,11 @@ class AnimeWwise(QMainWindow):
files = [] files = []
if self.folders["input"]: if self.folders["input"]:
if self.pckSubFold.isChecked(): if self.pckSubFold.isChecked():
files = [os.path.join(root, f) for root, dirs, files_in_dir in os.walk(self.folders["input"]) for f in files_in_dir if f.endswith(".pck")] files = [os.path.join(root, f) for root, dirs, files_in_dir in os.walk(self.folders["input"]) for f in files_in_dir if f.endswith(".pck") or f.endswith(".chk")]
else: else:
files = [os.path.join(self.folders["input"], f) for f in os.listdir(self.folders["input"]) if f.endswith(".pck")] files = [os.path.join(self.folders["input"], f) for f in os.listdir(self.folders["input"]) if f.endswith(".pck") or f.endswith(".chk")]
elif self.loadType == "file": elif self.loadType == "file":
path = QFileDialog.getOpenFileName(self, "Select .pck File", "", "PCK Files (*.pck)", options=QFileDialog.Options()) path = QFileDialog.getOpenFileName(self, "Select audio file", "", "PCK Files (*.pck);; CHK Files (*.chk)", options=QFileDialog.Options())
self.folders["input"] = os.path.dirname(path[0]) self.folders["input"] = os.path.dirname(path[0])
files = [path[0]] files = [path[0]]
@@ -554,7 +554,8 @@ class AnimeWwise(QMainWindow):
"path": path[:-1], "path": path[:-1],
"source": meta["source"], "source": meta["source"],
"offset": meta["offset"], "offset": meta["offset"],
"size": meta["size"] "size": meta["size"],
"original_name": meta["original_name"]
} }
# misc # misc

View File

@@ -6,6 +6,7 @@ import tempfile
import wavescan import wavescan
import platform import platform
import subprocess import subprocess
from vfs import decrypt
from mapper import Mapper from mapper import Mapper
from allocator import Allocator from allocator import Allocator
from filereader import FileReader from filereader import FileReader
@@ -85,8 +86,7 @@ class WwiseExtract:
self.get_wems(data, os.path.basename(_input), hdiff, os.path.relpath(_input, start=base_path)) self.get_wems(data, os.path.basename(_input), hdiff, os.path.relpath(_input, start=base_path))
def get_wems(self, data, filename, hdiff, relpath): def get_wems(self, data, filename, hdiff, relpath):
reader = FileReader(io.BytesIO(data), "little") files = wavescan.get_data(data, filename)
files = wavescan.get_data(reader, filename)
if hdiff is not None: if hdiff is not None:
with open(hdiff, "rb") as f: with open(hdiff, "rb") as f:
@@ -143,8 +143,7 @@ class WwiseExtract:
f.write(data) f.write(data)
f.close() f.close()
reader = FileReader(io.BytesIO(data), "little") files = wavescan.get_data(data, source_name)
files = wavescan.get_data(reader, source_name)
working_dir.cleanup() working_dir.cleanup()
@@ -167,7 +166,7 @@ class WwiseExtract:
# banks = json.loads(handle.read()) # banks = json.loads(handle.read())
# handle.close() # handle.close()
for file in files: def process_file(file):
if mapper is not None: if mapper is not None:
key = mapper.get_key(file[0].split(".")[0]) key = mapper.get_key(file[0].split(".")[0])
@@ -183,14 +182,15 @@ class WwiseExtract:
"source": relpath, "source": relpath,
"size": file[2], "size": file[2],
"offset": file[1], "offset": file[1],
"original_name": file[0],
"metadata": {} "metadata": {}
} }
wem_data = data[file_data["offset"]:file_data["offset"]+file_data["size"]] 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]}")) parsed_wem = wwise.parse_wwise(wem_data, f"{file[3]}:{file[0]}:{file[1]}", file[0])
if not parsed_wem: if not parsed_wem:
continue return
file_data["metadata"] = parsed_wem file_data["metadata"] = parsed_wem
@@ -228,6 +228,12 @@ class WwiseExtract:
if "unmapped" not in temp: if "unmapped" not in temp:
temp["unmapped"] = {"folders": {}, "files": []} temp["unmapped"] = {"folders": {}, "files": []}
temp["unmapped"]["files"].append([file[0], file_data]) temp["unmapped"]["files"].append([file[0], file_data])
pos = 0
for file in files:
process_file(file)
pos += 1
self.update_progress(pos, len(files), 1)
self.file_structure = base self.file_structure = base
@@ -316,7 +322,22 @@ class WwiseExtract:
file["source"] = file["source"].split(" (hdiff)")[0] file["source"] = file["source"].split(" (hdiff)")[0]
data = self.allocator.read_at(file["source"], file["offset"], file["size"]) data = self.allocator.read_at(file["source"], file["offset"], file["size"])
if data[0:4] not in [b"RIFF", b"RIFX"]:
# file may be vfs encrypted
data = bytearray(data)
wem_id = 0
try:
wem_id = int(file["original_name"][:-4])
except ValueError:
try:
wem_id = int(file["original_name"][:-4], 16)
except ValueError:
continue
decrypt(data, 0, len(data), wem_id, 0)
if data[0:4] not in [b"RIFF", b"RIFX"]:
continue
filepath = path("/".join(file["path"]), file["name"]) filepath = path("/".join(file["path"]), file["name"])
fullpath = path(output, filepath) fullpath = path(output, filepath)
os.makedirs(os.path.dirname(fullpath), exist_ok=True) os.makedirs(os.path.dirname(fullpath), exist_ok=True)

View File

@@ -31,7 +31,8 @@ class Mapper:
games = { games = {
"hk4e": "Genshin", "hk4e": "Genshin",
"hkrpg": "Star Rail", "hkrpg": "Star Rail",
"nap": "Zenless Zone Zero" "nap": "Zenless Zone Zero",
"beyond": "Arknights Endfield"
# more later # more later
} }

BIN
maps/beyond.map Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{ {
"version": 222, "version": 222,
"mapsVersion": 122, "mapsVersion": 123,
"maps": [ "maps": [
{ {
"name": "hk4e.map", "name": "hk4e.map",
@@ -16,6 +16,11 @@
"name": "nap.map", "name": "nap.map",
"game": "Zenless Zone Zero", "game": "Zenless Zone Zero",
"version": "2.2" "version": "2.2"
},
{
"name": "beyond.map",
"game": "Arknights Endfield",
"version": "1.0"
} }
] ]
} }

53
vfs.py Normal file
View File

@@ -0,0 +1,53 @@
# decrypt and parse endfield's .chk files for audio
def decrypt(data, offset, count, seed, fileOffset):
keySeed = seed + (fileOffset >> 2)
dataIndex = offset
remaining = count
alignement = fileOffset & 3
# head
if alignement != 0:
keyValue = key(keySeed)
toAlign = min(4 - alignement, remaining)
for i in range(toAlign):
if dataIndex >= offset + count:
break
bytePos = alignement + i
data[dataIndex] ^= (keyValue >> (bytePos * 8)) & 0xFF
dataIndex += 1
remaining -= toAlign
keySeed += 1
# body
nBlocks = remaining // 4
for i in range(nBlocks):
keyValue = key(keySeed)
dataValue = int.from_bytes(data[dataIndex:dataIndex+4], "little") ^ keyValue
data[dataIndex] = dataValue & 0xFF
data[dataIndex+1] = (dataValue >> 8) & 0xFF
data[dataIndex+2] = (dataValue >> 16) & 0xFF
data[dataIndex+3] = (dataValue >> 24) & 0xFF
dataIndex += 4
keySeed += 1
# tail
trailing = remaining & 3
if trailing > 0:
keyValue = key(keySeed)
for i in range(trailing):
data[dataIndex] ^= (keyValue >> (i * 8)) & 0xFF
dataIndex += 1
def key(seed):
k = ((((seed & 0xFF) ^ 0x9C5A0B29) & 0xFFFFFFFF) * 81861667) & 0xFFFFFFFF
k = ((k ^ ((seed >> 8) & 0xFF)) * 81861667) & 0xFFFFFFFF
k = ((k ^ ((seed >> 16) & 0xFF)) * 81861667) & 0xFFFFFFFF
k = ((k ^ (seed >> 24) & 0xFF) * 81861667) & 0xFFFFFFFF
return k

View File

@@ -1,7 +1,10 @@
# Custom rewrite of the Wwise AKPK packages extractor, original by Nicknine and bnnm # Custom rewrite of the Wwise AKPK packages extractor, original by Nicknine and bnnm
import os import os
import io
import traceback import traceback
from bnk import bnk2wem from bnk import bnk2wem
from vfs import decrypt
from filereader import FileReader
reader = None reader = None
@@ -10,7 +13,7 @@ wwise_data = []
filename = "" filename = ""
def get_data(_reader, _filename): def get_data(data, _filename):
global wwise_data global wwise_data
global bank_version global bank_version
global reader global reader
@@ -18,10 +21,32 @@ def get_data(_reader, _filename):
filename = _filename filename = _filename
wwise_data = [] wwise_data = []
reader = _reader reader = FileReader(io.BytesIO(data), "little")
vfs = False
# check file # check file
if reader.ReadBytes(4) != b"AKPK": magic = reader.ReadBytes(4)
if magic == b":)xD":
# file is endfield VFS
print("file is VFS !!")
vfs = True
reader.SetBufferPos(4)
header_size = reader.ReadUInt32()
print(header_size)
reader.SetBufferPos(0)
header = bytearray(reader.ReadBytes(header_size+8))
decrypt(header, 12, header_size - 4, header_size, 0)
# recreate file
dec_data = bytearray()
dec_data += header
dec_data += data[header_size+8:]
dec_data[0:4] = b"AKPK"
dec_data[8:12] = (1).to_bytes(4, "little")
data = dec_data
reader = FileReader(io.BytesIO(data), "little") # reset reader
magic = reader.ReadBytes(4)
if magic != b"AKPK":
# file.close() # file.close()
raise Exception("not a valid audio file") raise Exception("not a valid audio file")

View File

@@ -1,5 +1,11 @@
# wwise riff header parser # wwise riff header parser
# thanks to hcs and bnnm work # thanks to hcs and bnnm work
import io
from vfs import decrypt
from filereader import FileReader
def parse_wwise(data, name, fid):
reader = FileReader(io.BytesIO(data), "little", name=name)
def parse_wwise(reader): def parse_wwise(reader):
# default meta config # default meta config
@@ -27,6 +33,28 @@ def parse_wwise(reader):
header = reader.ReadBytes(4) header = reader.ReadBytes(4)
if header not in ["RIFF", "RIFX"]:
# file may be vfs encrypted, however this is painfully slow so by default it will skip this and return no metadata
if False: # set to true to parse metadata
data = bytearray(data)
wem_id = 0
try:
wem_id = int(fid[:-4])
except ValueError:
try:
wem_id = int(fid[:-4], 16)
except ValueError:
return None
decrypt(data, 0, len(data), wem_id, 0)
if data[0:4] not in [b"RIFF", b"RIFX"]:
print(f"[WARNING] invalid header {header} at {reader.GetName()}, assuming unreadable")
return None
reader = FileReader(io.BytesIO(data), "little", name=name) # reset reader
header = reader.ReadBytes(4)
else:
return metadata
# endian check header # endian check header
if header == b"RIFX": if header == b"RIFX":
reader.endianness = "big" reader.endianness = "big"
@@ -52,7 +80,7 @@ def parse_wwise(reader):
while reader.GetBufferPos() < reader.GetStreamLength(): while reader.GetBufferPos() < reader.GetStreamLength():
chunk_type = reader.ReadBytes(4) chunk_type = reader.ReadBytes(4)
if chunk_type not in [b"fmt ", b"JUNK", b"data", b"akd ", b"cue ", b"LIST", b"smpl"]: if chunk_type not in [b"fmt ", b"JUNK", b"data", b"akd ", b"cue ", b"LIST", b"smpl", b"hash", b"seek"]:
print(f"[WARNING] unexpected chunk {chunk_type} at {reader.GetName()}") print(f"[WARNING] unexpected chunk {chunk_type} at {reader.GetName()}")
formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "") formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "")