From a26d8f4b64815a1b368adfbb4f24db3c58c4c481 Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Fri, 6 Nov 2020 18:40:41 +0000 Subject: [PATCH] Fix several bugs in DXT5 decompressor, renders fonts much closer to the game now. --- bemani/format/dxt.py | 52 +++++++++++--------------------------- bemani/utils/bishiutils.py | 39 +++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/bemani/format/dxt.py b/bemani/format/dxt.py index f112a8e..6234bf1 100644 --- a/bemani/format/dxt.py +++ b/bemani/format/dxt.py @@ -20,7 +20,7 @@ def unpack(_bytes: bytes) -> int: 4: 'I', 8: 'Q' } - return struct.unpack('<' + STRUCT_SIGNS[len(_bytes)], _bytes)[0] + return struct.unpack('>' + STRUCT_SIGNS[len(_bytes)], _bytes)[0] # This function converts RGB565 format to raw pixels @@ -51,7 +51,6 @@ class DXTBuffer: file = io.BytesIO(filedata) for row in range(self.block_county): for col in range(self.block_countx): - # Get the alpha values a0 = unpack(file.read(1)) a1 = unpack(file.read(1)) @@ -68,10 +67,10 @@ class DXTBuffer: # The 4x4 Lookup table loop for j in range(4): for i in range(4): - alpha = self.getAlpha(j, i, a0, a1, acode0, acode1) + alpha = self.getAlpha(i, j, a0, a1, acode0, acode1) self.getColors( - row * 4, col * 4, + row * 4, i, j, ctable, @@ -97,8 +96,8 @@ class DXTBuffer: for j in range(4): for i in range(4): self.getColors( - row * 4, col * 4, + row * 4, i, j, ctable, @@ -120,7 +119,7 @@ class DXTBuffer: c1: Tuple[int, int, int, int], alpha: int, ) -> None: - code = (ctable >> (2 * (4 * i + j))) & 0x03 # Get the color of the current pixel + code = (ctable >> (2 * ((4 * i) + j))) & 0x03 # Get the color of the current pixel pixel_color = None r0 = c0[0] @@ -131,39 +130,26 @@ class DXTBuffer: g1 = c1[1] b1 = c1[2] - # Main two colors + # Sliding scale between colors. if code == 0: pixel_color = (r0, g0, b0, alpha) if code == 1: pixel_color = (r1, g1, b1, alpha) + if code == 2: + pixel_color = ((2 * r0 + r1) // 3, (2 * g0 + g1) // 3, (2 * b0 + b1) // 3, alpha) + if code == 3: + pixel_color = ((r0 + 2 * r1) // 3, (g0 + 2 * g1) // 3, (b0 + 2 * b1) // 3, alpha) - # Use the lookup table to determine the other two colors - if c0 > c1: - if code == 2: - pixel_color = ((2 * r0 + r1) // 3, (2 * g0 + g1) // 3, (2 * b0 + b1) // 3, alpha) - if code == 3: - pixel_color = ((r0 + 2 * r1) // 3, (g0 + 2 * g1) // 3, (b0 + 2 * b1) // 3, alpha) - else: - if code == 2: - pixel_color = ((r0 + r1) // 2, (g0 + g1) // 2, (b0 + b1) // 2, alpha) - if code == 3: - pixel_color = (0, 0, 0, alpha) - - # While not surpassing the image dimensions, assign pixels the colors - if (x + i) < self.width: + # While not surpassing the image dimensions, assign pixels the colors. + if (x + i) < self.width and (y + j) < self.height: self.decompressed_buffer[(y + j) * self.width + (x + i)] = ( - struct.pack(' int: - # Using the same method as the colors calculate the alpha values - alpha = 255 - alpha_index = 3 * (4 * j + i) + alpha_index = 3 * ((4 * j) + i) alpha_code = None if alpha_index <= 12: @@ -185,14 +171,6 @@ class DXTBuffer: alpha = 0 elif alpha_code == 7: alpha = 255 - elif alpha_code == 5: - alpha = (1 * a0 + 4 * a1) // 5 - elif alpha_code == 4: - alpha = (2 * a0 + 3 * a1) // 5 - elif alpha_code == 3: - alpha = (3 * a0 + 2 * a1) // 5 - elif alpha_code == 2: - alpha = (4 * a0 + 1 * a1) // 5 else: - alpha = 0 # For safety + alpha = ((6 - alpha_code) * a0 + (alpha_code - 1) * a1) // 5 return alpha diff --git a/bemani/utils/bishiutils.py b/bemani/utils/bishiutils.py index ee02bec..d6f63c7 100644 --- a/bemani/utils/bishiutils.py +++ b/bemani/utils/bishiutils.py @@ -106,7 +106,13 @@ def descramble_pman(package_data: bytes, offset: int, endian: str, obfuscated: b return names -def extract(filename: str, output_dir: str, *, write: bool, verbose: bool = False) -> None: +def extract( + filename: str, + output_dir: str, *, + write: bool = True, + verbose: bool = False, + raw: bool = False, +) -> None: with open(filename, "rb") as fp: data = fp.read() @@ -230,10 +236,19 @@ def extract(filename: str, output_dir: str, *, write: bool, verbose: bool = Fals else: # Now, see if we can extract this data. print(f"Writing {filename} texture data...") - magic, _, _, _, width, height, fmt, _, flags2, flags1 = struct.unpack( - f"{endian}4sIIIHHBBBB", + magic, _, _, length, width, height, fmtflags = struct.unpack( + f"{endian}4sIIIHHI", raw_data[0:24], ) + if length != len(raw_data): + raise Exception("Invalid texture length!") + fmt = fmtflags & 0xFF + flags1 = (fmtflags >> 24) & 0xFF + flags2 = (fmtflags >> 16) & 0xFF + unk1 = 3 if (flags1 & 0xF == 1) else 1 + unk2 = 3 if ((flags1 >> 4) & 0xF == 1) else 1 + unk3 = 1 if (flags2 & 0xF == 1) else 2 + unk4 = 1 if ((flags2 >> 4) & 0xF == 1) else 2 if endian == "<" and magic != b"TDXT": raise Exception("Unexpected texture format!") @@ -246,7 +261,6 @@ def extract(filename: str, output_dir: str, *, write: bool, verbose: bool = Fals 'RGB', (width, height), raw_data[64:], 'raw', 'RGB', ) # 0x10 = Seems to be some sort of RGB with color swapping. - # 0x11 = Unknown entirely, PS3 format. Looks to be one byte per pixel. # 0x15 = Looks like RGB but reversed (end and beginning bytes swapped). # 0x16 = DTX1 format, when I encounter this I'll hook it up. elif fmt == 0x1A: @@ -261,7 +275,6 @@ def extract(filename: str, output_dir: str, *, write: bool, verbose: bool = Fals 0, 1, ) - img = ImageOps.flip(img).rotate(-90, expand=True) # 0x1E = I have no idea what format this is. # 0x1F = 16bpp, possibly grayscale? Maybe 555A or 565 color? elif fmt == 0x20: @@ -278,7 +291,7 @@ def extract(filename: str, output_dir: str, *, write: bool, verbose: bool = Fals if img: with open(f"{filename}.png", "wb") as bfp: img.save(bfp, format='PNG') - else: + if not img or raw: with open(f"{filename}.raw", "wb") as bfp: bfp.write(raw_data) with open(f"{filename}.xml", "w") as sfp: @@ -698,9 +711,21 @@ def main() -> int: action="store_true", help="Display verbuse debugging output.", ) + parser.add_argument( + "-r", + "--write-raw", + action="store_true", + help="Always write raw texture files.", + ) args = parser.parse_args() - extract(args.file, args.dir, write=not args.pretend, verbose=args.verbose) + extract( + args.file, + args.dir, + write=not args.pretend, + verbose=args.verbose, + raw=args.write_raw, + ) return 0