Add a TDXT extractor and partial updater.
This commit is contained in:
parent
379d746edd
commit
3a1ebf3f3e
@ -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
|
||||
|
||||
@ -9,6 +10,7 @@ __all__ = [
|
||||
"IFS",
|
||||
"ARC",
|
||||
"TwoDX",
|
||||
"TDXT",
|
||||
"IIDXChart",
|
||||
"IIDXMusicDB",
|
||||
"IIDXSong",
|
||||
|
@ -78,7 +78,7 @@ class 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.
|
||||
|
117
bemani/utils/tdxtutils.py
Normal file
117
bemani/utils/tdxtutils.py
Normal 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
12
tdxtutils
Executable 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__")
|
@ -22,6 +22,7 @@ declare -a arr=(
|
||||
"scheduler"
|
||||
"services"
|
||||
"struct"
|
||||
"tdxtutils"
|
||||
"trafficgen"
|
||||
"twodxutils"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user