1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-05 07:50:23 +08:00

Merge pull request #2 from Escartem/dev

This commit is contained in:
Escartem
2023-10-28 13:15:55 +02:00
committed by GitHub
12 changed files with 496 additions and 4938 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ output/
temp/ temp/
*.pck *.pck
*.hdiff *.hdiff
tools.zip tools.zip
__pycache__/

View File

@@ -1,5 +1,7 @@
# AnimeWwise # AnimeWwise
Extract audio from .pck and .hdiff to mp3 with this tool. It can in theory extract any pck or hdiff file from any game even though it was made for Genshin Impact. There are others tools that do the same but none of them were working so I just made my own. Extract audio from .pck and .hdiff to mp3 including original filenames with this tool. It can in theory extract any pck or hdiff file from any game even though it was made for Genshin Impact. There are others tools that do the same but none of them were working so I just made my own.
⚠️ Only audio from genshin will be exported with original filenames, and the coverage is very low, don't except every file to have a name
# Usage # Usage
@@ -14,7 +16,6 @@ Extract audio from .pck and .hdiff to mp3 with this tool. It can in theory extra
--- ---
## There is a separate branch called `dev` that may includes new features, you can check it out if you want to get them early. Those features are, as of right now : ## There is a separate branch called `dev` that may includes new features, you can check it out if you want to get them early. Those features are, as of right now :
- export files with their original names
- more export formats support - more export formats support
- reverse search ? - reverse search ?

View File

@@ -1,38 +1,44 @@
import os import os
import sys
import json
import shutil import shutil
import zipfile import zipfile
import filecmp import filecmp
import wavescan
import subprocess import subprocess
from halo import Halo from halo import Halo
from progress.bar import PixelBar from progress.bar import PixelBar
# don't question how optimised this is
cwd = os.getcwd() cwd = os.getcwd()
path = lambda path: os.path.join(cwd, path) path = lambda path: os.path.join(cwd, path)
call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
spinner = Halo(text="spinner", spinner={'interval': 100, 'frames': ['', '', '', '', '', '']}, placement="right") spinner = Halo(text="spinner", spinner={'interval': 100, 'frames': ['', '', '', '', '', '']}, placement="right")
skips = "000000000" # used for debugging
# 1 - original extract
# 2 - patch
# 3 - patch extract
# 4 - filter files
# 5 - wem to wav
# 6 - wav to mp3
# 7 - map names
# 8 - clean up
# 9 - temp clean up
def main(): def main():
# Extract tools on first launch
if not os.path.exists("tools"):
if not os.path.exists("tools.zip"):
print("Please place tools.zip in the project directory and run this program again, you can get the file on the project page")
exit()
spinner.text = "Extracting tools for first launch"
spinner.start()
with zipfile.ZipFile(path("tools.zip"), "r") as zip:
zip.extractall(cwd)
os.remove(path("tools.zip"))
spinner.stop()
# Initial cleanup # Initial cleanup
if os.path.exists("temp"): if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp") shutil.rmtree("temp")
if os.path.exists("output") and len(os.listdir("output")) > 0: if os.path.exists("output") and len(os.listdir("output")) > 0:
print("The output folder will be cleared when the program runs, press a key to continue or close this window") print("The output folder needs to be cleared, continue ? [Y/N]")
os.system("pause >nul") select = input(">")
shutil.rmtree("output") if select.lower() == "y":
shutil.rmtree("output")
else:
print("Aborting")
exit()
# Get all files to process # Get all files to process
hdiff_files = [f for f in os.listdir("audio") if f.endswith(".pck") and os.path.exists(f"patch/{f}.hdiff")] hdiff_files = [f for f in os.listdir("audio") if f.endswith(".pck") and os.path.exists(f"patch/{f}.hdiff")]
@@ -54,242 +60,259 @@ def main():
filename = f"{file.split('.')[0]}.hdiff.pck" filename = f"{file.split('.')[0]}.hdiff.pck"
print(f"--- {filename} ({iteration}/{len(files)}) ---") print(f"--- {filename} ({iteration}/{len(files)}) ---")
alone, steps, curr = False, 7, 1 alone, steps, curr = False, 8, 1
if file in alone_files: if file in alone_files:
alone, steps = True, 4 alone, steps = True, 5
###################################### ######################################
### 1 - Extract original .pck file ### ### 1 - Extract original .pck file ###
###################################### ######################################
# update files if skips[0] != "1":
if os.path.exists("temp"): # update files
shutil.rmtree("temp") if os.path.exists("temp"):
os.makedirs(path("temp"), exist_ok=True) shutil.rmtree("temp")
shutil.copy(f"audio/{file}", f"temp/{file}") os.makedirs(path("temp"), exist_ok=True)
shutil.copy(f"audio/{file}", f"temp/{file}")
output_path = "original_decoded" output_path = "original_decoded"
if alone: if alone:
output_path = "wem" output_path = "wem"
# prepare args # update spinner and call program
args = [ spinner.text = f"[{curr}/{steps}] Extracting"
path("tools/quickbms/quickbms.exe"), spinner.start()
"-o", wavescan.extract(path(f"temp/{file}"), path(f"temp/{output_path}"))
"-Y", spinner.stop()
path("tools/quickbms/wavescan.bms"), print(f"[{curr}/{steps}] Extracting")
path(f"temp/{file}"),
path(f"temp/{output_path}")
]
# update spinner and call program if alone:
spinner.text = f"[{curr}/{steps}] Extracting" all_files = os.listdir(path("temp/wem"))
spinner.start()
call(args)
spinner.stop()
print(f"[{curr}/{steps}] Extracting")
if alone:
all_files = os.listdir(path("temp/wem"))
###################################### ######################################
### 2 - Patch the .pck with .hdiff ### ### 2 - Patch the .pck with .hdiff ###
###################################### ######################################
if not alone: if skips[1] != "1":
curr += 1 if not alone:
curr += 1
# update files # update files
shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff") shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff")
shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck") shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck")
# prepare args # prepare args
args = [ args = [
path("tools/hpatchz/hpatchz.exe"), path("tools/hpatchz/hpatchz.exe"),
"-f", "-f",
path(f"temp/{file.split('.')[0]}.original.pck"), path(f"temp/{file.split('.')[0]}.original.pck"),
path(f"temp/{file}.hdiff"), path(f"temp/{file}.hdiff"),
path(f"temp/{file}") path(f"temp/{file}")
] ]
# update spinner and call program # update spinner and call program
spinner.text = f"[{curr}/{steps}] Patching" spinner.text = f"[{curr}/{steps}] Patching"
spinner.start() spinner.start()
call(args) call(args)
spinner.stop() spinner.stop()
print(f"[{curr}/{steps}] Patching") print(f"[{curr}/{steps}] Patching")
##################################### #####################################
### 3 - Extract patched .pck file ### ### 3 - Extract patched .pck file ###
##################################### #####################################
if not alone: if skips[2] != "1":
curr += 1 if not alone:
curr += 1
# prepare args # update spinner and call program
args = [ spinner.text = f"[{curr}/{steps}] Extracting patch"
path("tools/quickbms/quickbms.exe"), spinner.start()
"-o", wavescan.extract(path(f"temp/{file}"), path(f"temp/patched_decoded"))
"-Y", spinner.stop()
path("tools/quickbms/wavescan.bms"), print(f"[{curr}/{steps}] Extracting patch")
path(f"temp/{file}"),
path(f"temp/patched_decoded")
]
# update spinner and call program # cleanup useless files to save storage
spinner.text = f"[{curr}/{steps}] Extracting patch" os.remove(f"temp/{file}")
spinner.start() os.remove(f"temp/{file}.hdiff")
call(args) os.remove(f"temp/{file.split('.')[0]}.original.pck")
spinner.stop()
print(f"[{curr}/{steps}] Extracting patch")
# cleanup useless files to save storage
os.remove(f"temp/{file}")
os.remove(f"temp/{file}.hdiff")
os.remove(f"temp/{file.split('.')[0]}.original.pck")
#################################### ####################################
### 4 - Search new/changed files ### ### 4 - Search new/changed files ###
#################################### ####################################
if not alone: if skips[3] != "1":
curr += 1 if not alone:
curr += 1
# update spinner # update spinner
spinner.text = f"[{curr}/{steps}] Filtering files" spinner.text = f"[{curr}/{steps}] Filtering files"
spinner.start() spinner.start()
# compare folders # compare folders
diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded")) diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded"))
new_files, changed_files = diff.right_only, diff.diff_files new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files] all_files = [*new_files, *changed_files]
# merge files # merge files
os.makedirs(path("temp/wem"), exist_ok=True) os.makedirs(path("temp/wem"), exist_ok=True)
for file in all_files: for file in all_files:
shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}") shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}")
# cleanup useless folders to save storage # cleanup useless folders to save storage
shutil.rmtree("temp/original_decoded") shutil.rmtree("temp/original_decoded")
shutil.rmtree("temp/patched_decoded") shutil.rmtree("temp/patched_decoded")
spinner.stop() spinner.stop()
print(f"[{curr}/{steps}] Filtering files") print(f"[{curr}/{steps}] Filtering files")
###################################### ######################################
### 5 - Convert .wem files to .wav ### ### 5 - Convert .wem files to .wav ###
###################################### ######################################
curr += 1 if skips[4] != "1":
curr += 1
# updates folders and progress bar # updates folders and progress bar
os.makedirs(path("temp/wav"), exist_ok=True) os.makedirs(path("temp/wav"), exist_ok=True)
bar = PixelBar(f"[{curr}/{steps}] Converting to wav ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left') bar = PixelBar(f"[{curr}/{steps}] Converting to wav ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left')
# convert each file one by one # convert each file one by one
for file in all_files: for file in all_files:
args = [ args = [
path("tools/vgmstream/vgmstream-cli.exe"), path("tools/vgmstream/vgmstream-cli.exe"),
"-o", "-o",
path(f"temp/wav/{file.split('.')[0]}.wav"), path(f"temp/wav/{file.split('.')[0]}.wav"),
path(f"temp/wem/{file}") path(f"temp/wem/{file}")
] ]
call(args) call(args)
bar.next() bar.next()
bar.finish() bar.finish()
# cleanup # cleanup
shutil.rmtree("temp/wem") shutil.rmtree("temp/wem")
###################################### ######################################
### 6 - Convert .wav files to .mp3 ### ### 6 - Convert .wav files to .mp3 ###
###################################### ######################################
curr += 1 if skips[5] != "1":
curr += 1
# updates folders and progress bar # updates folders and progress bar
os.makedirs(path("temp/mp3"), exist_ok=True) os.makedirs(path("temp/mp3"), exist_ok=True)
bar = PixelBar(f"[{curr}/{steps}] Converting to mp3 ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left') bar = PixelBar(f"[{curr}/{steps}] Converting to mp3 ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left')
# update file list # update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files] all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
# convert each file one by one # convert each file one by one
for file in all_files: for file in all_files:
args = [ args = [
path("tools/ffmpeg/ffmpeg.exe"), path("tools/ffmpeg/ffmpeg.exe"),
"-i", "-i",
path(f"temp/wav/{file}"), path(f"temp/wav/{file}"),
"-acodec", "-acodec",
"libmp3lame", "libmp3lame",
"-b:a", "-b:a",
"192k", "192k",
path(f"temp/mp3/{file.split('.')[0]}.mp3"), path(f"temp/mp3/{file.split('.')[0]}.mp3"),
] ]
call(args) call(args)
bar.next() bar.next()
bar.finish() bar.finish()
# cleanup
shutil.rmtree("temp/wav")
######################################################
### 7 - Clean everything and move result to output ###
######################################################
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Cleaning up"
spinner.start()
filename = filename.split('.')[0]
if not alone:
# update files list
new_files = [f"{f.split('.')[0]}.mp3" for f in new_files]
changed_files = [f"{f.split('.')[0]}.mp3" for f in changed_files]
# prepare folders
os.makedirs(path(f"output/{filename}"), exist_ok=True)
if len(new_files) > 0:
os.makedirs(path("temp/new_files"), exist_ok=True)
if len(changed_files) > 0:
os.makedirs(path("temp/changed_files"), exist_ok=True)
# split files into corresponding folder
for file in new_files:
shutil.move(f"temp/mp3/{file}", f"temp/new_files/{file}")
for file in changed_files:
shutil.move(f"temp/mp3/{file}", f"temp/changed_files/{file}")
# move them to output
final_path = f"output/{filename}"
if len(new_files) > 0:
shutil.move("temp/new_files", f"{final_path}/new_files")
if len(changed_files) > 0:
shutil.move("temp/changed_files", f"{final_path}/changed_files")
# cleanup # cleanup
shutil.rmtree("temp/mp3") shutil.rmtree("temp/wav")
else:
# for no hdiff files
os.makedirs(path(f"output/{filename} (no hdiff)"), exist_ok=True)
shutil.move("temp/mp3", f"output/{filename} (no hdiff)")
# update files list
all_files = [f"{f.split('.')[0]}.mp3" for f in all_files]
if not alone:
new_files = [f"{f.split('.')[0]}.mp3" for f in new_files]
changed_files = [f"{f.split('.')[0]}.mp3" for f in changed_files]
#########################
### 7 - Map filenames ###
#########################
if skips[6] != "1":
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Mapping names"
spinner.start()
languages = ["english", "japanese", "chinese", "korean"]
mapFiles = [f"{path('mapping/mapping')}{f.capitalize()}.json" for f in languages]
namesTable = []
for language in mapFiles:
with open(language, "r") as f:
namesTable.append(json.loads(f.read()))
f.close()
if alone:
os.makedirs(path(f"temp/map/unmapped"), exist_ok=True)
else:
if len(new_files) > 0:
os.makedirs(path(f"temp/map/new_files/unmapped"), exist_ok=True)
if len(changed_files) > 0:
os.makedirs(path(f"temp/map/changed_files/unmapped"), exist_ok=True)
lang = None
for file in all_files:
for language in namesTable:
if lang is None or namesTable.index(language) == lang:
file_name = file.split(".")[0]
base_path = "temp/map"
if not alone:
if file in new_files:
base_path = "temp/map/new_files"
elif file in changed_files:
base_path = "temp/map/changed_files"
if file_name in language:
# lang detected, stick to it
lang = namesTable.index(language)
dir_path = path(f"{base_path}/{language[file_name]['path']}/{language[file_name]['name']}.mp3")
os.makedirs(os.path.dirname(dir_path), exist_ok=True)
shutil.copy(path(f"temp/mp3/{file}"), dir_path)
else:
shutil.copy(path(f"temp/mp3/{file}"), path(f"{base_path}/unmapped/{file}"))
# stop spinner
spinner.stop()
print(f"[{curr}/{steps}] Mapping names")
######################################################
### 8 - Clean everything and move result to output ###
######################################################
if skips[7] != "1":
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Cleaning up"
spinner.start()
filename = filename.split('.')[0]
os.rename("temp/map", f"temp/{filename}")
shutil.move(f"temp/{filename}", f"output/{filename}")
spinner.stop()
print(f"[{curr}/{steps}] Cleaning up")
spinner.stop()
print(f"[{curr}/{steps}] Cleaning up")
except Exception as e: except Exception as e:
print("An error occured while processing this file ! Skipping to the next one, details of the error bellow :") print("An error occured while processing this file ! Skipping to the next one, details of the error bellow :")
print(e) print(f"Line {sys.exc_info()[-1].tb_lineno}, {e}")
# all files processed # all files processed
if os.path.exists("temp"): if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp") shutil.rmtree("temp")
print("Done extracting everything !") print("Done extracting everything !")

56
filereader.py Normal file
View File

@@ -0,0 +1,56 @@
import struct
class FileReader:
"""
Simplified byte file reader with buffer, it's not particularly optimised but good enough
In the scope of this project, not everything will be used in here
"""
def __init__(self, file, endianness:str):
self.stream = file
self.endianness = endianness
def _read(self, mode:str, bufferLength:int, endianness:str=None) -> bytes:
# endianness override
if endianness is None:
endianness = self.endianness
endianness = "<" if endianness == "little" else ">"
return struct.unpack(f"{endianness}{mode}", bytearray(self.stream.read(bufferLength)))[0]
# read methods
def ReadInt16(self, endianness:str=None) -> int:
return self._read("h", 2, endianness)
def ReadUInt16(self, endianness:str=None) -> int:
return self._read("H", 2, endianness)
def ReadInt32(self, endianness:str=None) -> int:
return self._read("i", 4, endianness)
def ReadUInt32(self, endianness:str=None) -> int:
return self._read("I", 4, endianness)
def ReadLong(self, endianness:str=None) -> int:
return self._read("l", 4, endianness)
def ReadULong(self, endianness:str=None) -> int:
return self._read("L", 4, endianness)
def ReadLongLong(self, endianness:str=None) -> int:
return self._read("q", 8, endianness)
def ReadULongLong(self, endianness:str=None) -> int:
return self._read("Q", 8, endianness)
def ReadBytes(self, length:int, endianness:str=None) -> bytes:
return self._read(f"{str(length)}s", int(length), endianness)
# buffer utils
def GetBufferPos(self) -> int:
return self.stream.tell()
def SetBufferPos(self, pos:int):
self.stream.seek(pos)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +0,0 @@
# modified version to ignore filenames (and wav)
#
# scan data for wave files
# RIFF and RIFX header supported
# note: There are wave files with a wrong file size after RIFF/RIFX
# This script takes the stream size, adds the header size and writes the correct size after RIFF/RIFX
# (c) 2012-06-26 by AlphaTwentyThree
#
# future update plans:
# - option to also write data between found wave files to disk
# - option to automatically transform the file to a playable or at least decodable format
for i = 1 # run through loop with count variable i
FindLoc OFFSET string "WAVE" 0 "" # search for "WAVE", save position as variable OFFSET
if OFFSET == "" # when nothing is found
cleanexit # the script exits (e.g. at end of file)
endif
math OFFSET -= 8 # jump to possible
goto OFFSET # RIFF/RIFX file start
getDstring IDENT 4 # read string of 4 bytes, save variable as IDENT
if IDENT == "RIFX" # differentiate between header possibilities
endian big # set endianness to big, if file has RIFX identifier
callfunction write 1 # see function section below
elif IDENT == "RIFF" # endianness stays little
callfunction write 1 # also run function
else # string "WAVE" found, but doesn't belong to wave file
set SIZE 0xc # do as if something with 0xc bytes was found to continue search from the right position
endif
set SEARCH OFFSET # set marker to position from where to search next
math SEARCH += SIZE # (that would be after the file that was found)
if SEARCH == FSIZE # in case the last found file ends with the main file, we exit
cleanexit
endif
goto SEARCH # if we haven't exited the script above, we set out cursor to after the last found file
next i
startfunction write # function "write" starts here, is called when a wave file is found above
get NAME basename # save name without extension under variable NAME
string NAME += "_" # add underscore to the name
string NAME += i # add the loop variable to the name
goto OFFSET # set cursor to the beginning of the found file
get DUMMY long # RIFF/RIFX identifier, not needed
get DUMMY long # riff size, not needed
get DUMMY long # "WAVE", not needed, we arrive at the "fmt " section
for # loop search for the "data" section at the start of the stream (get the stream size from there)
getDstring AREA_NAME 4 # name of area in header
get AREA_SIZE long # size of area in header
savepos MYOFF # save position we are at
if AREA_NAME == "data" # when we arrive at the needed "data" area:
break # we exit the loop
else # otherwise:
math MYOFF += AREA_SIZE # not reached "data" area -> adjust cursor position...
goto MYOFF # ... and go there
endif
next # remember: the cursor is now directly at the stream start
set STREAMSIZE AREA_SIZE # the last AREA_SIZE is the size we need (size of the audio stream)
set HEADERSIZE MYOFF #
math HEADERSIZE -= OFFSET # calculate the size of the stream header (offset - offset = size)
set SIZE HEADERSIZE #
math SIZE += STREAMSIZE # calculate complete file size (header + stream = file)
log MEMORY_FILE OFFSET SIZE # write file to memory
math SIZE -= 8 # subtract 8 bytes to get the riff size
putVarChr MEMORY_FILE 4 SIZE long # write the correct riff size to the header inside the memory
string NAME += ".wem" # add extension to the name (the name could contain the name of the first marker if the file has markers)
math SIZE += 8 # add the subtracted 8 bytes again
log NAME 0 SIZE MEMORY_FILE # write file in memory to disk
endfunction # remember that we continue with our next i now!

216
wavescan.py Normal file
View File

@@ -0,0 +1,216 @@
# Custom rewrite of the Wwise AKPK packages extractor, original by Nicknine and bnnm
# TODO: use extracted files id with the mapping table to restore file names
from filereader import FileReader
import os
reader = None
bank_version = 0
def extract(input_file, output_folder):
global bank_version
global reader
file = open(input_file, "rb")
reader = FileReader(file, "little") # defaults to little endian
# check file
if reader.ReadBytes(4) != b"AKPK":
print("Not a valid file")
return
# check endianness
reader.SetBufferPos(0x08)
endian_check = reader.ReadLong() # this is the same bytes as the flag sector, which seems to be always 1
if endian_check == 1:
endianness = 0 # little
elif endian_check == 0x1000000:
endianness = 1 # big
else:
print("uknown endianness, aborting")
return
# retrieve sectors in header
reader.SetBufferPos(0x04)
header_size = reader.ReadLong()
flag = reader.ReadLong()
languages_sector_size = reader.ReadLong()
banks_sector_size = reader.ReadLong()
sounds_sector_size = reader.ReadLong()
externals_sector_size = 0
if languages_sector_size + banks_sector_size + sounds_sector_size + 0x10 < header_size:
externals_sector_size = reader.ReadLong()
sectors = [[True, banks_sector_size, 0, 0, "bnk"], [False, sounds_sector_size, 1, 0, "wem"], [False, externals_sector_size, 1, 1, "wem"]]
# get langs in the file
lang_array = get_langs(languages_sector_size)
# extract each sector
for sector in sectors:
extract_sector(*sector[1:], endianness, lang_array, bank_version, output_folder)
if sector[0] and bank_version == 0:
if externals_sector_size == 0:
print("can't detect bank version")
bank_version = 62
# close
file.close()
def get_langs(langs_sector_size):
string_offset = reader.GetBufferPos()
lang_array = {}
langs = reader.ReadLong()
for i in range(langs):
lang_offset = reader.ReadLong()
lang_id = reader.ReadLong()
lang_offset += string_offset
current = reader.GetBufferPos()
reader.SetBufferPos(lang_offset)
# get dummy bytes to detect encoding
test_byte_1 = reader.ReadBytes(1)
test_byte_2 = reader.ReadBytes(1)
reader.SetBufferPos(lang_offset)
if test_byte_1 == 0 or test_byte_2 == 0:
lang_name = reader.ReadBytes(0x20).decode("utf-16le").replace("\x00", "")
else:
lang_name = reader.ReadBytes(0x10).decode("utf-8").replace("\x00", "")
lang_array[lang_id] = lang_name
reader.SetBufferPos(current)
reader.SetBufferPos(string_offset + langs_sector_size)
return lang_array
def detect_bank_version(offset):
global bank_version
current = reader.GetBufferPos()
reader.SetBufferPos(offset)
# maybe update buffer pos instead
dummy = reader.ReadLong()
dummy = reader.ReadLong()
bank_version = reader.ReadLong()
if bank_version > 0x1000:
print("wrong bank version")
bank_version = 62
reader.SetBufferPos(current)
def extract_sector(section_size, is_sounds, is_externals, ext, endianness, lang_array, bank_version, output_folder, filter_bnk_only=0, filter_wem_only=0):
# check sector validity
if section_size == 0:
return
files = reader.ReadLong()
if files == 0:
return
entry_size = (section_size - 0x04) / files
if entry_size == 0x18:
alt_mode = 1
else:
alt_mode = 0
for i in range(files):
# ids must be unsigned here, if signed you need to do id += 2**32 afterwards
if alt_mode == 1 and is_externals == 1:
if endianness == 0:
file_id_2 = reader.ReadULong()
file_id_1 = reader.ReadULong()
else:
file_id_1 = reader.ReadULong()
file_id_2 = reader.ReadULong()
else:
file_id = reader.ReadULong()
block_size = reader.ReadLong()
# get file size
if alt_mode == 1 and is_externals == 1:
size = reader.ReadLong()
elif alt_mode == 1:
size = reader.ReadLongLong()
else:
size = reader.ReadLong()
offset = reader.ReadLong()
lang_id = reader.ReadLong()
if block_size != 0:
offset *= block_size
# bank version must be detected at this offset
if is_sounds == 0 and bank_version == 0:
detect_bank_version(offset)
# update extension for olders banks using differents codecs
if is_sounds == 1 and bank_version < 62:
current = reader.GetBufferPos()
codec_offset = offset + 0x14
reader.SetBufferPos(codec_offset)
codec = reader.ReadInt16()
if codec == 0x0401 or codec == 0x0166:
ext = "xma"
elif codec == 0xFFFF:
ext = "ogg"
else:
ext = "wav"
reader.SetBufferPos(current)
# set file path
if lang_id == 0:
path = ""
else:
path = "".join([f"{e}/" for e in list(lang_array.values())])
# se file name
if alt_mode == 1 and is_externals == 1:
name = f"externals/{path}{file_id_1:08x}{file_id_2:08x}.{ext}"
else:
name = f"{path}{file_id}.{ext}"
# filtering utilities
if filter_bnk_only == 1 and ext != "bnk":
continue
if filter_wem_only == 1 and ext != "wem":
continue
# file infos
# print(f"NAME - {name} | OFFSET - {offset} | SIZE - {size}")
# save file into disk
current = reader.GetBufferPos()
reader.SetBufferPos(offset)
file_data = reader.ReadBytes(size)
os.makedirs(output_folder, exist_ok=True)
with open(os.path.join(output_folder, os.path.basename(name)), "wb+") as f:
f.write(file_data)
f.close()
reader.SetBufferPos(current)