#! /usr/bin/env python3 import argparse import io import os import os.path import sys from PIL import Image from typing import Optional from bemani.format import TDXT def extract_texture( fname: str, output_fname: Optional[str], invert_channels: bool = False, ) -> int: with open(fname, "rb") as bfp: tdxt = TDXT.fromBytes(bfp.read(), invert_channels=invert_channels) 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, invert_channels: bool = False, ) -> int: with open(fname, "rb") as bfp: tdxt = TDXT.fromBytes(bfp.read(), invert_channels=invert_channels) 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.", ) unpack_parser.add_argument( "--invert-channels", action="store_true", help="Swap the order of R/G/B channels in image.", ) 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.", ) update_parser.add_argument( "--invert-channels", action="store_true", help="Swap the order of R/G/B channels in image.", ) args = parser.parse_args() if args.action == "unpack": return extract_texture( args.infile, args.outfile, invert_channels=args.invert_channels, ) elif args.action == "update": return update_texture( args.outfile, args.infile, invert_channels=args.invert_channels, ) else: raise Exception(f"Invalid action {args.action}!") if __name__ == "__main__": sys.exit(main())