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:
9
app.py
9
app.py
@@ -359,11 +359,11 @@ class AnimeWwise(QMainWindow):
|
||||
files = []
|
||||
if self.folders["input"]:
|
||||
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:
|
||||
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":
|
||||
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])
|
||||
files = [path[0]]
|
||||
|
||||
@@ -554,7 +554,8 @@ class AnimeWwise(QMainWindow):
|
||||
"path": path[:-1],
|
||||
"source": meta["source"],
|
||||
"offset": meta["offset"],
|
||||
"size": meta["size"]
|
||||
"size": meta["size"],
|
||||
"original_name": meta["original_name"]
|
||||
}
|
||||
|
||||
# misc
|
||||
|
||||
37
extract.py
37
extract.py
@@ -6,6 +6,7 @@ import tempfile
|
||||
import wavescan
|
||||
import platform
|
||||
import subprocess
|
||||
from vfs import decrypt
|
||||
from mapper import Mapper
|
||||
from allocator import Allocator
|
||||
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))
|
||||
|
||||
def get_wems(self, data, filename, hdiff, relpath):
|
||||
reader = FileReader(io.BytesIO(data), "little")
|
||||
files = wavescan.get_data(reader, filename)
|
||||
files = wavescan.get_data(data, filename)
|
||||
|
||||
if hdiff is not None:
|
||||
with open(hdiff, "rb") as f:
|
||||
@@ -143,8 +143,7 @@ class WwiseExtract:
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
reader = FileReader(io.BytesIO(data), "little")
|
||||
files = wavescan.get_data(reader, source_name)
|
||||
files = wavescan.get_data(data, source_name)
|
||||
|
||||
working_dir.cleanup()
|
||||
|
||||
@@ -167,7 +166,7 @@ class WwiseExtract:
|
||||
# banks = json.loads(handle.read())
|
||||
# handle.close()
|
||||
|
||||
for file in files:
|
||||
def process_file(file):
|
||||
if mapper is not None:
|
||||
key = mapper.get_key(file[0].split(".")[0])
|
||||
|
||||
@@ -183,14 +182,15 @@ class WwiseExtract:
|
||||
"source": relpath,
|
||||
"size": file[2],
|
||||
"offset": file[1],
|
||||
"original_name": file[0],
|
||||
"metadata": {}
|
||||
}
|
||||
|
||||
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:
|
||||
continue
|
||||
return
|
||||
|
||||
file_data["metadata"] = parsed_wem
|
||||
|
||||
@@ -228,6 +228,12 @@ class WwiseExtract:
|
||||
if "unmapped" not in temp:
|
||||
temp["unmapped"] = {"folders": {}, "files": []}
|
||||
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
|
||||
|
||||
@@ -316,7 +322,22 @@ class WwiseExtract:
|
||||
|
||||
file["source"] = file["source"].split(" (hdiff)")[0]
|
||||
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"])
|
||||
fullpath = path(output, filepath)
|
||||
os.makedirs(os.path.dirname(fullpath), exist_ok=True)
|
||||
|
||||
@@ -31,7 +31,8 @@ class Mapper:
|
||||
games = {
|
||||
"hk4e": "Genshin",
|
||||
"hkrpg": "Star Rail",
|
||||
"nap": "Zenless Zone Zero"
|
||||
"nap": "Zenless Zone Zero",
|
||||
"beyond": "Arknights Endfield"
|
||||
# more later
|
||||
}
|
||||
|
||||
|
||||
BIN
maps/beyond.map
Normal file
BIN
maps/beyond.map
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 222,
|
||||
"mapsVersion": 122,
|
||||
"mapsVersion": 123,
|
||||
"maps": [
|
||||
{
|
||||
"name": "hk4e.map",
|
||||
@@ -16,6 +16,11 @@
|
||||
"name": "nap.map",
|
||||
"game": "Zenless Zone Zero",
|
||||
"version": "2.2"
|
||||
},
|
||||
{
|
||||
"name": "beyond.map",
|
||||
"game": "Arknights Endfield",
|
||||
"version": "1.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
vfs.py
Normal file
53
vfs.py
Normal 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
|
||||
31
wavescan.py
31
wavescan.py
@@ -1,7 +1,10 @@
|
||||
# Custom rewrite of the Wwise AKPK packages extractor, original by Nicknine and bnnm
|
||||
import os
|
||||
import io
|
||||
import traceback
|
||||
from bnk import bnk2wem
|
||||
from vfs import decrypt
|
||||
from filereader import FileReader
|
||||
|
||||
|
||||
reader = None
|
||||
@@ -10,7 +13,7 @@ wwise_data = []
|
||||
filename = ""
|
||||
|
||||
|
||||
def get_data(_reader, _filename):
|
||||
def get_data(data, _filename):
|
||||
global wwise_data
|
||||
global bank_version
|
||||
global reader
|
||||
@@ -18,10 +21,32 @@ def get_data(_reader, _filename):
|
||||
|
||||
filename = _filename
|
||||
wwise_data = []
|
||||
reader = _reader
|
||||
reader = FileReader(io.BytesIO(data), "little")
|
||||
vfs = False
|
||||
|
||||
# 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()
|
||||
raise Exception("not a valid audio file")
|
||||
|
||||
|
||||
30
wwise.py
30
wwise.py
@@ -1,5 +1,11 @@
|
||||
# wwise riff header parser
|
||||
# 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):
|
||||
# default meta config
|
||||
@@ -27,6 +33,28 @@ def parse_wwise(reader):
|
||||
|
||||
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
|
||||
if header == b"RIFX":
|
||||
reader.endianness = "big"
|
||||
@@ -52,7 +80,7 @@ def parse_wwise(reader):
|
||||
while reader.GetBufferPos() < reader.GetStreamLength():
|
||||
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()}")
|
||||
|
||||
formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "")
|
||||
|
||||
Reference in New Issue
Block a user