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.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",
|
||||||
|
@ -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
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"
|
"scheduler"
|
||||||
"services"
|
"services"
|
||||||
"struct"
|
"struct"
|
||||||
|
"tdxtutils"
|
||||||
"trafficgen"
|
"trafficgen"
|
||||||
"twodxutils"
|
"twodxutils"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user