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

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ temp/
*.pck
*.hdiff
tools.zip
__pycache__/

View File

@@ -1,5 +1,7 @@
# 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
@@ -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 :
- export files with their original names
- more export formats support
- reverse search ?

View File

@@ -1,38 +1,44 @@
import os
import sys
import json
import shutil
import zipfile
import filecmp
import wavescan
import subprocess
from halo import Halo
from progress.bar import PixelBar
# don't question how optimised this is
cwd = os.getcwd()
path = lambda path: os.path.join(cwd, path)
call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
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():
# 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
if os.path.exists("temp"):
if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp")
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")
os.system("pause >nul")
shutil.rmtree("output")
print("The output folder needs to be cleared, continue ? [Y/N]")
select = input(">")
if select.lower() == "y":
shutil.rmtree("output")
else:
print("Aborting")
exit()
# 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")]
@@ -54,242 +60,259 @@ def main():
filename = f"{file.split('.')[0]}.hdiff.pck"
print(f"--- {filename} ({iteration}/{len(files)}) ---")
alone, steps, curr = False, 7, 1
alone, steps, curr = False, 8, 1
if file in alone_files:
alone, steps = True, 4
alone, steps = True, 5
######################################
### 1 - Extract original .pck file ###
######################################
# update files
if os.path.exists("temp"):
shutil.rmtree("temp")
os.makedirs(path("temp"), exist_ok=True)
shutil.copy(f"audio/{file}", f"temp/{file}")
if skips[0] != "1":
# update files
if os.path.exists("temp"):
shutil.rmtree("temp")
os.makedirs(path("temp"), exist_ok=True)
shutil.copy(f"audio/{file}", f"temp/{file}")
output_path = "original_decoded"
if alone:
output_path = "wem"
output_path = "original_decoded"
if alone:
output_path = "wem"
# prepare args
args = [
path("tools/quickbms/quickbms.exe"),
"-o",
"-Y",
path("tools/quickbms/wavescan.bms"),
path(f"temp/{file}"),
path(f"temp/{output_path}")
]
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Extracting"
spinner.start()
wavescan.extract(path(f"temp/{file}"), path(f"temp/{output_path}"))
spinner.stop()
print(f"[{curr}/{steps}] Extracting")
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Extracting"
spinner.start()
call(args)
spinner.stop()
print(f"[{curr}/{steps}] Extracting")
if alone:
all_files = os.listdir(path("temp/wem"))
if alone:
all_files = os.listdir(path("temp/wem"))
######################################
### 2 - Patch the .pck with .hdiff ###
######################################
if not alone:
curr += 1
if skips[1] != "1":
if not alone:
curr += 1
# update files
shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff")
shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck")
# update files
shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff")
shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck")
# prepare args
args = [
path("tools/hpatchz/hpatchz.exe"),
"-f",
path(f"temp/{file.split('.')[0]}.original.pck"),
path(f"temp/{file}.hdiff"),
path(f"temp/{file}")
]
# prepare args
args = [
path("tools/hpatchz/hpatchz.exe"),
"-f",
path(f"temp/{file.split('.')[0]}.original.pck"),
path(f"temp/{file}.hdiff"),
path(f"temp/{file}")
]
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Patching"
spinner.start()
call(args)
spinner.stop()
print(f"[{curr}/{steps}] Patching")
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Patching"
spinner.start()
call(args)
spinner.stop()
print(f"[{curr}/{steps}] Patching")
#####################################
### 3 - Extract patched .pck file ###
#####################################
if not alone:
curr += 1
if skips[2] != "1":
if not alone:
curr += 1
# prepare args
args = [
path("tools/quickbms/quickbms.exe"),
"-o",
"-Y",
path("tools/quickbms/wavescan.bms"),
path(f"temp/{file}"),
path(f"temp/patched_decoded")
]
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Extracting patch"
spinner.start()
wavescan.extract(path(f"temp/{file}"), path(f"temp/patched_decoded"))
spinner.stop()
print(f"[{curr}/{steps}] Extracting patch")
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Extracting patch"
spinner.start()
call(args)
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")
# 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 ###
####################################
if not alone:
curr += 1
if skips[3] != "1":
if not alone:
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Filtering files"
spinner.start()
# update spinner
spinner.text = f"[{curr}/{steps}] Filtering files"
spinner.start()
# compare folders
diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded"))
new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files]
# compare folders
diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded"))
new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files]
# merge files
os.makedirs(path("temp/wem"), exist_ok=True)
# merge files
os.makedirs(path("temp/wem"), exist_ok=True)
for file in all_files:
shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}")
for file in all_files:
shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}")
# cleanup useless folders to save storage
shutil.rmtree("temp/original_decoded")
shutil.rmtree("temp/patched_decoded")
# cleanup useless folders to save storage
shutil.rmtree("temp/original_decoded")
shutil.rmtree("temp/patched_decoded")
spinner.stop()
print(f"[{curr}/{steps}] Filtering files")
spinner.stop()
print(f"[{curr}/{steps}] Filtering files")
######################################
### 5 - Convert .wem files to .wav ###
######################################
curr += 1
if skips[4] != "1":
curr += 1
# updates folders and progress bar
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')
# updates folders and progress bar
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')
# convert each file one by one
for file in all_files:
args = [
path("tools/vgmstream/vgmstream-cli.exe"),
"-o",
path(f"temp/wav/{file.split('.')[0]}.wav"),
path(f"temp/wem/{file}")
]
# convert each file one by one
for file in all_files:
args = [
path("tools/vgmstream/vgmstream-cli.exe"),
"-o",
path(f"temp/wav/{file.split('.')[0]}.wav"),
path(f"temp/wem/{file}")
]
call(args)
bar.next()
bar.finish()
call(args)
bar.next()
bar.finish()
# cleanup
shutil.rmtree("temp/wem")
# cleanup
shutil.rmtree("temp/wem")
######################################
### 6 - Convert .wav files to .mp3 ###
######################################
curr += 1
if skips[5] != "1":
curr += 1
# updates folders and progress bar
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')
# updates folders and progress bar
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')
# update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
# update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
# convert each file one by one
for file in all_files:
args = [
path("tools/ffmpeg/ffmpeg.exe"),
"-i",
path(f"temp/wav/{file}"),
"-acodec",
"libmp3lame",
"-b:a",
"192k",
path(f"temp/mp3/{file.split('.')[0]}.mp3"),
]
# convert each file one by one
for file in all_files:
args = [
path("tools/ffmpeg/ffmpeg.exe"),
"-i",
path(f"temp/wav/{file}"),
"-acodec",
"libmp3lame",
"-b:a",
"192k",
path(f"temp/mp3/{file.split('.')[0]}.mp3"),
]
call(args)
bar.next()
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")
call(args)
bar.next()
bar.finish()
# cleanup
shutil.rmtree("temp/mp3")
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)")
shutil.rmtree("temp/wav")
# 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:
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
if os.path.exists("temp"):
if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp")
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)