SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
#!/usr/bin/env python3
"""Extract cast-member assets from the original SuperSonic RC game.dcr.
Dev-only tooling. Uses ProjectorRays' --dump-chunks output (decompressed
per-resource chunks) and writes named assets to ref-out/assets/.
python3 tools/extract/extract_assets.py
Outputs are derived from copyrighted originals: they stay in the gitignored
ref-out/ folder and must never be committed or published.
"""
import glob
import io
import json
import os
import struct
import subprocess
import sys
import wave
from PIL import Image
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
DCR = os.path.join(ROOT, "ref", "SuperSonicRC", "game.dcr")
PR = os.path.join(ROOT, "tools", "vendor", "ProjectorRays", "projectorrays")
DUMP = os.path.join(ROOT, "ref-out", "chunks")
OUT = os.path.join(ROOT, "ref-out", "assets")
MEMBER_TYPES = {1: "bitmap", 3: "field", 4: "palette", 6: "sound", 11: "script", 15: "xtra"}
def dump_chunks():
chunks = os.path.join(DUMP, "game", "chunks")
if not os.path.isdir(chunks):
os.makedirs(DUMP, exist_ok=True)
subprocess.run([PR, "decompile", DCR, "--dump-chunks", "--dump-json", "-o", DUMP], check=True)
return chunks
def read_key_table(chunks):
"""KEY* maps chunk section IDs to the CASt section that owns them."""
k = open(glob.glob(os.path.join(chunks, "KEY_-*.bin"))[0], "rb").read()
used = struct.unpack("<I", k[8:12])[0]
owned = {}
for i in range(used):
sect, cast, tag = struct.unpack("<II4s", k[12 + 12 * i:24 + 12 * i])
owned.setdefault(cast, {})[tag[::-1].decode("latin1")] = sect
return owned
def chunk(chunks, tag, sect):
return open(os.path.join(chunks, f"{tag.replace('*', '_')}-{sect}.bin"), "rb").read()
def unpack_rle(data, size):
"""Director's PackBits variant used by BITD and ALFA."""
out = bytearray()
i = 0
while i < len(data) and len(out) < size:
n = data[i]
i += 1
if n & 0x80:
out += bytes([data[i]]) * (257 - n)
i += 1
else:
out += data[i:i + n + 1]
i += n + 1
return bytes(out[:size]).ljust(size, b"\0")
def bitmap_spec(spec):
pitch = struct.unpack(">H", spec[0:2])[0] & 0x7FFF
top, left, bottom, right = struct.unpack(">hhhh", spec[2:10])
reg_y, reg_x = struct.unpack(">hh", spec[18:22])
bpp = spec[23] if len(spec) > 23 else 8
return dict(pitch=pitch, width=right - left, height=bottom - top, bpp=bpp, reg=(reg_x, reg_y))
def decode_bitd(raw, s):
w, h, pitch, bpp = s["width"], s["height"], s["pitch"], s["bpp"]
data = raw if len(raw) == pitch * h else unpack_rle(raw, pitch * h)
if bpp == 32:
img = Image.new("RGBA", (w, h))
px = []
for y in range(h):
row = data[y * pitch:(y + 1) * pitch]
a, r, g, b = (row[c * w:(c + 1) * w] for c in range(4))
px += [(r[x], g[x], b[x], a[x]) for x in range(w)]
img.putdata(px)
return img
if bpp == 16:
img = Image.new("RGB", (w, h))
px = []
for y in range(h):
row = data[y * pitch:(y + 1) * pitch]
hi, lo = row[:w], row[w:2 * w]
for x in range(w):
v = (hi[x] << 8) | lo[x]
px.append((((v >> 10) & 31) * 255 // 31, ((v >> 5) & 31) * 255 // 31, (v & 31) * 255 // 31))
img.putdata(px)
return img
# 8-bit and lower: no custom palette data in this game, emit grayscale.
return Image.frombytes("L", (w, h), bytes(data[y * pitch + x] for y in range(h) for x in range(w)))
def apply_alpha(img, alfa, w, h):
# ALFA rows are padded to an even width (the 291px buttons store 292).
pitch = (w + 1) & ~1
a = alfa if len(alfa) == pitch * h else unpack_rle(alfa, pitch * h)
a = b"".join(a[y * pitch:y * pitch + w] for y in range(h))
img = img.convert("RGBA")
img.putalpha(Image.frombytes("L", (w, h), a))
return img
def mac_snd_to_wav(raw, path):
"""Format-2 Mac 'snd ' resource with a standard or extended sound header."""
hdr = struct.unpack(">I", raw[10:14])[0]
h = raw[hdr:]
rate = struct.unpack(">I", h[8:12])[0] >> 16
enc = h[20]
if enc == 0x00:
n = struct.unpack(">I", h[4:8])[0]
pcm, ch, width = h[22:22 + n], 1, 1
pcm = bytes(pcm) # 8-bit offset binary is WAV's native 8-bit format
elif enc == 0xFF:
ch = struct.unpack(">I", h[4:8])[0]
frames = struct.unpack(">I", h[22:26])[0]
width = struct.unpack(">H", h[48:50])[0] // 8
pcm = h[64:64 + frames * ch * width]
if width == 2:
pcm = b"".join(pcm[i + 1:i + 2] + pcm[i:i + 1] for i in range(0, len(pcm) - 1, 2))
else:
raise ValueError(f"unsupported snd encoding 0x{enc:02x}")
with wave.open(path, "wb") as w:
w.setnchannels(ch)
w.setsampwidth(width)
w.setframerate(rate)
w.writeframes(pcm)
return dict(rate=rate, channels=ch, bits=width * 8)
def swa_to_mp3(raw):
"""Shockwave Audio is MP3 frames behind a Macromedia header."""
start = struct.unpack(">I", raw[0:4])[0]
i = start
while i < len(raw) - 1 and not (raw[i] == 0xFF and raw[i + 1] & 0xE0 == 0xE0):
i += 1
return raw[i:]
def xmed_kind(data):
if data[:4] == b"3DEM":
return "w3d"
if b"MAX_exported_world" in data[:64]:
return "hke"
if data[:4] == b"FFFF":
return "text-xtra"
return "bin"
def main():
chunks = dump_chunks()
owned = read_key_table(chunks)
manifest = []
for sub in ("bitmaps", "sounds", "3d", "text"):
os.makedirs(os.path.join(OUT, sub), exist_ok=True)
for jf in sorted(glob.glob(os.path.join(chunks, "CASt-*.json")), key=lambda p: int(p.rsplit("-", 1)[1][:-5])):
sect = int(jf.rsplit("-", 1)[1][:-5])
meta = json.load(open(jf))
raw = open(jf[:-5] + ".bin", "rb").read()
name = meta["info"].get("name") or f"member{sect}"
spec = raw[12 + meta["infoLen"]:12 + meta["infoLen"] + meta["specificDataLen"]]
kind = MEMBER_TYPES.get(meta["type"], str(meta["type"]))
# Keep only chunks that were actually published (KEY* can list stale ones).
res = {t: n for t, n in owned.get(sect, {}).items()
if os.path.exists(os.path.join(chunks, f"{t.replace('*', '_')}-{n}.bin"))}
entry = dict(section=sect, name=name, type=kind)
if kind == "bitmap":
s = bitmap_spec(spec)
entry.update(width=s["width"], height=s["height"], bpp=s["bpp"], reg=s["reg"])
if "ediM" in res:
img = Image.open(io.BytesIO(chunk(chunks, "ediM", res["ediM"]))).convert("RGB")
elif "BITD" in res:
img = decode_bitd(chunk(chunks, "BITD", res["BITD"]), s)
else:
continue
if "ALFA" in res:
img = apply_alpha(img, chunk(chunks, "ALFA", res["ALFA"]), s["width"], s["height"])
entry["file"] = f"bitmaps/{name}.png"
img.save(os.path.join(OUT, entry["file"]))
elif kind == "sound":
if "snd " in res:
entry["file"] = f"sounds/{name}.wav"
entry.update(mac_snd_to_wav(chunk(chunks, "snd ", res["snd "]), os.path.join(OUT, entry["file"])))
elif "ediM" in res:
entry["file"] = f"sounds/{name}.mp3"
open(os.path.join(OUT, entry["file"]), "wb").write(swa_to_mp3(chunk(chunks, "ediM", res["ediM"])))
else:
continue
elif kind == "xtra" and "XMED" in res:
data = chunk(chunks, "XMED", res["XMED"])
ext = xmed_kind(data)
entry.update(xtra=ext, file=f"3d/{name or sect}.{ext}")
open(os.path.join(OUT, entry["file"]), "wb").write(data)
elif kind == "field" and "STXT" in res:
data = chunk(chunks, "STXT", res["STXT"])
hdr, tlen = struct.unpack(">II", data[:8])
entry["file"] = f"text/{name}.txt"
open(os.path.join(OUT, entry["file"]), "wb").write(data[hdr:hdr + tlen])
else:
continue
manifest.append(entry)
print(f"{entry['type']:7} {entry['file']}")
json.dump(manifest, open(os.path.join(OUT, "manifest.json"), "w"), indent=2)
print(f"{len(manifest)} assets -> {OUT}", file=sys.stderr)
if __name__ == "__main__":
main()
