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 = []
|
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
|
||||||
|
|||||||
37
extract.py
37
extract.py
@@ -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)
|
||||||
|
|||||||
@@ -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
BIN
maps/beyond.map
Normal file
Binary file not shown.
@@ -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
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
|
# 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")
|
||||||
|
|
||||||
|
|||||||
30
wwise.py
30
wwise.py
@@ -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(" ", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user