arcaea-apk-extract/andreal.py
2023-09-24 21:31:37 +08:00

140 lines
4.2 KiB
Python

import argparse
import logging
import re
import sys
import zipfile
from pathlib import Path, PurePath
from common import ArcaeaApkParser, ExtractTask, extract
ROOT_OUTPUT_PATH = Path(sys.argv[0]).parent.absolute()
logger = logging.getLogger(__name__)
parser = argparse.ArgumentParser(
prog="arcaea-apk-extract for Andreal",
description="Literal meaning.",
epilog="This program is licensed under the WTFPL. Feel free to fuck anything.",
)
parser.add_argument("apk_file")
parser.add_argument(
"-op",
"--output-path",
action="store",
help="output path, defaults to Path(sys.argv[0]).parent",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="set logging level to logging.DEBUG",
)
class SongParser(ArcaeaApkParser):
@property
def OUTPUT_PATH(self):
return ROOT_OUTPUT_PATH / "Song"
def parse(self):
songs_dir_zf_path = zipfile.Path(self.zf) / "assets" / "songs"
songs_zf_path = [zfp for zfp in songs_dir_zf_path.iterdir() if zfp.is_dir()]
tasks = []
for song_zf_path in songs_zf_path:
for file_zf_path in song_zf_path.iterdir():
if not file_zf_path.is_file():
continue
file_pure_path = PurePath(str(file_zf_path))
if file_pure_path.suffix not in [".jpg", ".png"]:
continue
song_id = re.sub(r"^dl_", "", song_zf_path.name)
if file_pure_path.stem == "base":
new_file_name_stem = song_id
elif file_pure_path.stem == "base_night":
new_file_name_stem = f"{song_id}_night"
elif file_pure_path.stem in ["0", "1", "2", "3"]:
new_file_name_stem = f"{song_id}_{file_pure_path.stem}"
else:
continue
new_file_name = f"{new_file_name_stem}{file_pure_path.suffix}"
new_file_path = self.OUTPUT_PATH / new_file_name
tasks.append(ExtractTask(file_zf_path, new_file_path))
return tasks
class CharParser(ArcaeaApkParser):
CHAR_RE = r"^\d*u?(_icon)?\.png$"
@property
def OUTPUT_PATH_CHAR(self):
return ROOT_OUTPUT_PATH / "Char"
@property
def OUTPUT_PATH_ICON(self):
return ROOT_OUTPUT_PATH / "Icon"
def parse(self):
char_dir_zf_path = zipfile.Path(self.zf) / "assets" / "char"
char_zf_path_list = [zfp for zfp in char_dir_zf_path.iterdir() if zfp.is_file()]
tasks = []
for char_zf_path in char_zf_path_list:
char_pure_path = PurePath(str(char_zf_path))
if not re.match(self.CHAR_RE, char_pure_path.name):
continue
if "_icon" in char_pure_path.stem:
new_file_name = char_pure_path.name.replace("_icon", "")
new_file_path = self.OUTPUT_PATH_ICON / new_file_name
else:
new_file_name = char_pure_path.name
new_file_path = self.OUTPUT_PATH_CHAR / new_file_name
tasks.append(ExtractTask(char_zf_path, new_file_path))
return tasks
if __name__ == "__main__":
args = parser.parse_args(sys.argv[1:])
if args.output_path:
output_path = Path(args.output_path)
assert output_path.exists()
ROOT_OUTPUT_PATH = output_path
logging_level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(
level=logging_level,
style="{",
format="[{levelname}]{name}: {msg}",
stream=sys.stdout,
encoding="utf-8",
)
apk_path = Path(args.apk_file)
assert apk_path.exists()
apk_zf = zipfile.ZipFile(str(apk_path))
sp = SongParser(apk_zf)
cp = CharParser(apk_zf)
sp.OUTPUT_PATH.mkdir(parents=True, exist_ok=True)
cp.OUTPUT_PATH_CHAR.mkdir(parents=True, exist_ok=True)
cp.OUTPUT_PATH_ICON.mkdir(parents=True, exist_ok=True)
sp_tasks = sp.parse()
logger.info(f"{len(sp_tasks)} files from songs")
cp_tasks = cp.parse()
logger.info(f"{len(cp_tasks)} files from char")
tasks = sp_tasks + cp_tasks
logger.info("Extracting, please wait...")
for task in tasks:
extract(task)