Skip to content

Instantly share code, notes, and snippets.

@Puyodead1
Last active June 21, 2024 00:08
Show Gist options
  • Save Puyodead1/31eac3fd8eb941a0880e909849d25fc2 to your computer and use it in GitHub Desktop.
Save Puyodead1/31eac3fd8eb941a0880e909849d25fc2 to your computer and use it in GitHub Desktop.
Trainz 22 TZarc extractor
# trainz 22 tzarc extractor by Puyodead1
# only works for v1, v2 archives need to be decompressed first and then feed into this script
import argparse
import pathlib
import struct
# pip install binreader
from binreader import BinaryReader
def swap32(i):
return struct.unpack("<I", struct.pack(">I", i))[0]
def serialize_string(reader: BinaryReader):
str_len = reader.read_byte()
return reader.read_string(str_len)
class TZArcFile:
name: str
size: int
data: bytes
def __init__(self):
pass
def extract(reader: BinaryReader, file_path: str, list_only: bool):
file_count = swap32(reader.read_uint32())
print(f"File Count: {file_count}")
files = []
for i in range(file_count):
file = TZArcFile()
file_name = serialize_string(reader)
file.name = file_name
# compression?
unknown = swap32(reader.read_uint32())
file_size = swap32(reader.read_uint32())
file.size = file_size
files.append(file)
# print(f"[DEBUG] File {file_name} size {file_size}")
# crc?
unknown2 = swap32(reader.read_uint32())
# print(f"[DEBUG] Unknown2: {unknown2}")
for i in range(file_count):
file = files[i]
file_name = file.name
file.data = reader.read(file.size)
# print(f"[DEBUG] Read file data for {file.name}, stream pos {reader.tell()}")
if list_only:
for file in files:
print(file.name)
return
# create a directory using the name of the file
dir_name = pathlib.Path(file_path).stem.replace(" ", "_")
for file in files:
fp = pathlib.Path(dir_name, file.name)
fp.parent.mkdir(exist_ok=True, parents=True)
print(f"Writing {fp}")
with open(fp, "wb") as f:
f.write(file.data)
def main(file_path: str, list_only: bool):
with open(file_path, "rb") as f:
reader = BinaryReader(f)
magic = reader.read_uint32()
assert magic == 0x63615A54, "Not a valid TZArc file"
version = swap32(reader.read_uint32())
print(f"File Version: {version}")
if version == 2:
raise AssertionError("Version 2 archives need to be decompressed first!")
assert version == 1, "Unsupported version"
extract(reader, file_path, list_only)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Auran TZArc extractor")
parser.add_argument("file", help="The file to extract")
parser.add_argument("--list", help="List files instead of extracting", action="store_true")
args = parser.parse_args()
main(args.file, args.list)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment