Skip to content

Instantly share code, notes, and snippets.

@nitori
Last active April 21, 2024 11:16
Show Gist options
  • Save nitori/75b4e110b01f6658c3460c08f683f792 to your computer and use it in GitHub Desktop.
Save nitori/75b4e110b01f6658c3460c08f683f792 to your computer and use it in GitHub Desktop.
from dataclasses import dataclass, field
import struct
from typing import BinaryIO
import zlib
import typing
"""
TFG1 Data files desired to be extracted (data directory)
https://gitea.xanax.lol/root/tfg-client
TFG4 Source Code (partially. important parts seem to be missing)
https://gitea.xanax.lol/root/tfg-source
"""
@dataclass
class Bitmap:
width: int
height: int
bitmap_width: int
bitmap_height: int
left_border: int
upper_border: int
transparent_color: tuple[int, int, int]
bytes_per_pixel: int
group: int
id: int
name: bytes
compression_method: int
compression_params: int
data_position: int
data_size: int
crc32: str
data: bytes = field(repr=False)
def read_string(f: BinaryIO):
length = f.read(1)[0]
return f.read(length)
class Stream:
def __init__(self, file_or_fp: str | typing.BinaryIO):
if isinstance(file_or_fp, str):
self._f = open(file_or_fp, 'rb')
self.filename = file_or_fp
else:
self._f = file_or_fp
self.filename = file_or_fp.name
self.number_of_bitmaps = None
self.header_size = None
self.max_tell = 0
self.min_tell = float('inf')
def tell(self) -> int:
return self._f.tell()
def __enter__(self):
assert self._f.read(4) == b'GFXC'
chump = self._f.read(5)
self.number_of_bitmaps, a, b, c = struct.unpack('<HBBB', chump)
self.header_size = (c << 16) | (b << 8) | a
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._f.close()
def __iter__(self):
for i in range(self.number_of_bitmaps):
bitmap = self.unserialize()
yield bitmap
def unserialize(self) -> Bitmap:
f = self._f
# C0 00 00 01 3B 00 21 00 5E 00 7B 00 43 79 99 20 13 00 3C 0C 00 01 01 88 C6 DA 0C AF 04 00 00 9E 02 00 00
# it seems to be little endian in all cases
# C0 00 00 01
width, height = struct.unpack('<HH', f.read(4))
# 3B 00 21 00
bitmap_width, bitmap_height = struct.unpack('<HH', f.read(4))
# 5E 00 7B 00
left_border, upper_border = struct.unpack('<HH', f.read(4))
# 43 79 99
r, g, b = struct.unpack('BBB', f.read(3))
# 20
bytes_per_pixel = struct.unpack('B', f.read(1))[0]
assert bytes_per_pixel % 8 == 0, 'bytes_per_pixel must be a multiple of 8'
bytes_per_pixel = bytes_per_pixel // 8
# 13 00 3C 0C
group, id_ = struct.unpack('<HH', f.read(4))
# 00
name = read_string(f)
# 01 01
compression_method, compression_params = struct.unpack('BB', f.read(2))
# 88 C6 DA 0C AF 04 00 00 9E 02 00 00
crc32_checksum, data_position, data_size = struct.unpack('<III', f.read(12))
pos = f.tell()
f.seek(data_position)
data = f.read(data_size)
self.max_tell = max(f.tell(), self.max_tell)
self.min_tell = min(data_position, self.min_tell)
f.seek(pos)
verify = zlib.crc32(data)
assert verify == crc32_checksum
assert len(data) == data_size
return Bitmap(
width, height, bitmap_width, bitmap_height,
left_border, upper_border, (r, g, b),
bytes_per_pixel, group, id_,
name, compression_method, compression_params,
data_position, data_size,
f'{crc32_checksum:08x}',
data
)
def main() -> None:
import os
os.makedirs('outputs', exist_ok=True)
for fn in os.listdir('outputs'):
os.unlink(os.path.join('outputs', fn))
saved = set()
duplicates = {}
filename = 'tfg-client/data/data'
file_size = os.stat(filename).st_size
with Stream(filename) as stream:
print(f'File size: {file_size}')
print(f'Number of bitmaps: {stream.number_of_bitmaps}')
print(f'Header size: {stream.header_size}')
print()
for i, bitmap in enumerate(stream):
duplicates.setdefault(bitmap.crc32, 0)
duplicates[bitmap.crc32] += 1
if bitmap.crc32 in saved:
continue
saved.add(bitmap.crc32)
# if len(bitmap.data) > 100:
# continue
if bitmap.crc32 == 'ceb1b544':
print(bitmap)
# key = f'{bitmap.group:06d}_{bitmap.id:06d}_{bitmap.crc32}'
# if bitmap.name:
# key = f'{key}_{bitmap.name.decode()}'
# if key in maps:
# assert maps[key] == bitmap, 'Bitmap already exists with different data!'
# else:
# maps[key] = bitmap
prefix = f'{bitmap.bitmap_width}x{bitmap.bitmap_height}x{bitmap.bytes_per_pixel}'
# with open(f'outputs/bitmap_{bitmap.name.decode()}_{bitmap.crc32}_{prefix}.raw', 'wb') as f:
# f.write(bitmap.data)
print()
print('Unique files:', len([v for v in duplicates.values() if v == 1]))
print('Files that appear twice:', len([v for v in duplicates.values() if v == 2]))
print('Files that appear 3 times:', len([v for v in duplicates.values() if v == 3]))
print('Files that appear 4 times:', len([v for v in duplicates.values() if v == 4]))
print('Files that appear 5 times:', len([v for v in duplicates.values() if v == 5]))
print('Files that appear more than 5 times:', len([v for v in duplicates.values() if v >= 6]))
print('Most common file:', max(duplicates.items(), key=lambda item: item[1]))
print('Min .tell():', stream.min_tell, '(should match Header size)')
print('Max .tell():', stream.max_tell)
with open(filename, 'rb') as f:
f.seek(stream.max_tell)
with open('outputs/rest.raw', 'wb') as fo:
fo.write(f.read())
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment