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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@ output/
|
|||||||
temp/
|
temp/
|
||||||
*.pck
|
*.pck
|
||||||
*.hdiff
|
*.hdiff
|
||||||
tools.zip
|
tools.zip
|
||||||
|
__pycache__/
|
||||||
@@ -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 ?
|
||||||
|
|
||||||
|
|||||||
407
extract.py
407
extract.py
@@ -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
56
filereader.py
Normal 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)
|
||||||
1
mapping/mappingChinese.json
Normal file
1
mapping/mappingChinese.json
Normal file
File diff suppressed because one or more lines are too long
1
mapping/mappingEnglish.json
Normal file
1
mapping/mappingEnglish.json
Normal file
File diff suppressed because one or more lines are too long
1
mapping/mappingJapanese.json
Normal file
1
mapping/mappingJapanese.json
Normal file
File diff suppressed because one or more lines are too long
1
mapping/mappingKorean.json
Normal file
1
mapping/mappingKorean.json
Normal file
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
@@ -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
216
wavescan.py
Normal 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)
|
||||||
Reference in New Issue
Block a user