1
0
mirror of synced 2024-09-23 19:08:21 +02:00

Add a TDXT extractor and partial updater.

This commit is contained in:
Jennifer Taylor 2023-09-19 00:08:01 +00:00
parent 379d746edd
commit 3a1ebf3f3e
5 changed files with 133 additions and 1 deletions

View File

@ -1,6 +1,7 @@
from bemani.format.ifs import IFS from bemani.format.ifs import IFS
from bemani.format.arc import ARC from bemani.format.arc import ARC
from bemani.format.twodx import TwoDX from bemani.format.twodx import TwoDX
from bemani.format.tdxt import TDXT
from bemani.format.iidxchart import IIDXChart from bemani.format.iidxchart import IIDXChart
from bemani.format.iidxmusicdb import IIDXMusicDB, IIDXSong from bemani.format.iidxmusicdb import IIDXMusicDB, IIDXSong
@ -9,6 +10,7 @@ __all__ = [
"IFS", "IFS",
"ARC", "ARC",
"TwoDX", "TwoDX",
"TDXT",
"IIDXChart", "IIDXChart",
"IIDXMusicDB", "IIDXMusicDB",
"IIDXSong", "IIDXSong",

View File

@ -78,7 +78,7 @@ class TDXT:
f"{endian}4sIIIHHIII", f"{endian}4sIIIHHIII",
raw_data[0:32], raw_data[0:32],
) )
if raw_length != len(raw_data): if (raw_length + 64) != len(raw_data):
raise Exception("Invalid texture length!") raise Exception("Invalid texture length!")
# I have only ever observed the following values across two different games. # I have only ever observed the following values across two different games.

117
bemani/utils/tdxtutils.py Normal file
View File

@ -0,0 +1,117 @@
#! /usr/bin/env python3
import argparse
import io
import json
import math
import os
import os.path
import sys
import textwrap
from PIL import Image, ImageDraw
from typing import Any, Dict, List, Optional, Tuple, TypeVar
from bemani.format import TDXT
def extract_texture(
fname: str,
output_fname: Optional[str],
) -> int:
with open(fname, "rb") as bfp:
tdxt = TDXT.fromBytes(bfp.read())
if output_fname is None:
output_fname = os.path.splitext(os.path.abspath(fname))[0] + ".png"
if not output_fname.lower().endswith(".png"):
raise Exception("Invalid output file format!")
# Actually place the files down.
output_dir = os.path.dirname(os.path.abspath(output_fname))
os.makedirs(output_dir, exist_ok=True)
print(f"Extracting texture from {os.path.abspath(fname)} to {os.path.abspath(output_fname)}")
with open(output_fname, "wb") as bfp:
tdxt.img.save(bfp, format="PNG")
return 0
def update_texture(
fname: str,
input_fname: str,
) -> int:
with open(fname, "rb") as bfp:
tdxt = TDXT.fromBytes(bfp.read())
if not input_fname.lower().endswith(".png"):
raise Exception("Invalid output file format!")
with open(input_fname, "rb") as bfp:
img = Image.open(io.BytesIO(bfp.read()))
tdxt.img = img
print(f"Updating texture in {os.path.abspath(fname)} from {os.path.abspath(input_fname)}")
with open(fname, "wb") as bfp:
bfp.write(tdxt.toBytes())
return 0
def main() -> int:
parser = argparse.ArgumentParser(
description="Konami TDXT graphic file unpacker/repacker."
)
subparsers = parser.add_subparsers(help="Action to take", dest="action")
unpack_parser = subparsers.add_parser(
"unpack",
help="Unpack texture data into a PNG file.",
)
unpack_parser.add_argument(
"infile",
metavar="INFILE",
help="The TDXT container to unpack the texture from.",
)
unpack_parser.add_argument(
"outfile",
metavar="OUTFILE",
nargs="?",
default=None,
help="The PNG file to unpack the texture to.",
)
update_parser = subparsers.add_parser(
"update",
help="Update texture data from a PNG file.",
)
update_parser.add_argument(
"outfile",
metavar="OUTFILE",
help="The TDXT container to update the texture to, must already exist.",
)
update_parser.add_argument(
"infile",
metavar="INFILE",
help="The PNG file to update the texture from.",
)
args = parser.parse_args()
if args.action == "unpack":
return extract_texture(
args.infile,
args.outfile,
)
elif args.action == "update":
return update_texture(
args.outfile,
args.infile,
)
else:
raise Exception(f"Invalid action {args.action}!")
if __name__ == "__main__":
sys.exit(main())

12
tdxtutils Executable file
View File

@ -0,0 +1,12 @@
#! /usr/bin/env python3
if __name__ == "__main__":
import os
path = os.path.abspath(os.path.dirname(__file__))
name = os.path.basename(__file__)
import sys
sys.path.append(path)
os.environ["SQLALCHEMY_SILENCE_UBER_WARNING"] = "1"
import runpy
runpy.run_module(f"bemani.utils.{name}", run_name="__main__")

View File

@ -22,6 +22,7 @@ declare -a arr=(
"scheduler" "scheduler"
"services" "services"
"struct" "struct"
"tdxtutils"
"trafficgen" "trafficgen"
"twodxutils" "twodxutils"
) )