#!/usr/bin/env python3 """ svt.py -- reader for DIVISION / VWE ".SVT" textures used by the DPL3 renderer. Format (from DPL3/DPL_LOAD.C : load_svt): there is NO header. The file is a raw array of 32-bit texels, always square. The edge length is inferred from the file size alone: 16384 bytes -> 64 x 64 65536 bytes -> 128 x 128 262144 bytes -> 256 x 256 Each texel is 4 bytes. The channel order within the 32-bit word is not stated in the loader (the hardware consumed it directly), so this tool lets you pick it and writes a standard PNG for viewing. Determined empirically: see spec/SVT_FORMAT.md. Usage: python svt.py info python svt.py png [out.png] [order] # order default = rgba """ import struct import sys import zlib SIZE_TO_EDGE = {16384: 64, 65536: 128, 262144: 256} def load(path): with open(path, "rb") as fp: data = fp.read() edge = SIZE_TO_EDGE.get(len(data)) if edge is None: raise ValueError("Bad texture file size %d (not 64/128/256 square RGBA32)" % len(data)) return data, edge def _png_chunk(tag, payload): return (struct.pack(">I", len(payload)) + tag + payload + struct.pack(">I", zlib.crc32(tag + payload) & 0xffffffff)) def write_png(path, width, height, rgba: bytes): """rgba = width*height*4 bytes, row-major, top-to-bottom.""" raw = bytearray() stride = width * 4 for y in range(height): raw.append(0) # filter: none raw += rgba[y * stride:(y + 1) * stride] out = b"\x89PNG\r\n\x1a\n" out += _png_chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0)) out += _png_chunk(b"IDAT", zlib.compress(bytes(raw), 9)) out += _png_chunk(b"IEND", b"") with open(path, "wb") as fp: fp.write(out) def to_rgba(data: bytes, order: str) -> bytes: """Reorder 4-byte texels (file byte positions) into R,G,B,A for PNG. `order` names which file byte maps to each channel, e.g. 'rgba','bgra'. Use 'x' for an unused/pad byte; alpha is then forced opaque (255). """ idx = {c: i for i, c in enumerate(order)} r, g, b = idx["r"], idx["g"], idx["b"] a = idx.get("a") # may be None when a pad byte ('x') is present mv = memoryview(data) out = bytearray(len(data)) for p in range(0, len(data), 4): out[p + 0] = mv[p + r] out[p + 1] = mv[p + g] out[p + 2] = mv[p + b] out[p + 3] = mv[p + a] if a is not None else 255 return bytes(out) def cmd_info(path): data, edge = load(path) print("file :", path) print("size :", len(data), "bytes") print("dims : %d x %d, 32bpp (%d texels)" % (edge, edge, edge * edge)) # quick stats on the raw bytes to hint at channel usage b = memoryview(data) for ch in range(4): vals = b[ch::4] lo = min(vals); hi = max(vals) print(" byte[%d]: min=%3d max=%3d" % (ch, lo, hi)) def cmd_png(path, out=None, order="rgba"): data, edge = load(path) out = out or (path.rsplit(".", 1)[0] + ".png") write_png(out, edge, edge, to_rgba(data, order)) print("wrote %s : %dx%d order=%s" % (out, edge, edge, order)) if __name__ == "__main__": if len(sys.argv) < 3: print(__doc__); sys.exit(1) cmd = sys.argv[1] if cmd == "info": cmd_info(sys.argv[2]) elif cmd == "png": cmd_png(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None, sys.argv[4] if len(sys.argv) > 4 else "xbgr") else: print("unknown command", cmd); sys.exit(1)