Skip to content

Commit

Permalink
Add a TDXT extractor and partial updater.
Browse files Browse the repository at this point in the history
  • Loading branch information
DragonMinded committed Sep 19, 2023
1 parent 379d746 commit 3a1ebf3
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 1 deletion.
2 changes: 2 additions & 0 deletions bemani/format/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from bemani.format.ifs import IFS
from bemani.format.arc import ARC
from bemani.format.twodx import TwoDX
from bemani.format.tdxt import TDXT
from bemani.format.iidxchart import IIDXChart
from bemani.format.iidxmusicdb import IIDXMusicDB, IIDXSong

Expand All @@ -9,6 +10,7 @@
"IFS",
"ARC",
"TwoDX",
"TDXT",
"IIDXChart",
"IIDXMusicDB",
"IIDXSong",
Expand Down
2 changes: 1 addition & 1 deletion bemani/format/tdxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def fromBytes(raw_data: bytes) -> "TDXT":
f"{endian}4sIIIHHIII",
raw_data[0:32],
)
if raw_length != len(raw_data):
if (raw_length + 64) != len(raw_data):
raise Exception("Invalid texture length!")

# I have only ever observed the following values across two different games.
Expand Down
117 changes: 117 additions & 0 deletions bemani/utils/tdxtutils.py
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions tdxtutils
Original file line number Diff line number Diff line change
@@ -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__")
1 change: 1 addition & 0 deletions verifytyping
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare -a arr=(
"scheduler"
"services"
"struct"
"tdxtutils"
"trafficgen"
"twodxutils"
)
Expand Down

0 comments on commit 3a1ebf3

Please sign in to comment.