modemwars/tools/extractAssets.py
2026-08-23 02:09:40 -05:00

72 lines
3.2 KiB
Python

#!/usr/bin/env python3
# extractAssets.py - pull human-readable assets out of the disk: Dan Bunten's letter (track 1),
# the hidden EA message (track 1 sector 17) and the loader title picture (as PNG).
import os, struct, zlib
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
EXT = os.path.join(ROOT, "extracted")
DOCS = os.path.join(ROOT, "docs")
os.makedirs(DOCS, exist_ok=True)
def raw(t, s):
return open(f"{EXT}/sectors/t{t:02d}s{s:02d}.bin", "rb").read()
# --- letter: track 1 sectors 1-10, plain ASCII, CR = newline, ends before the padding in sector 10
text = b"".join(raw(1, s) for s in range(1, 11))
end = text.find(b"72203") + 5
letter = text[:end].decode("latin-1").replace("\r", "\n")
with open(f"{DOCS}/track1_DanBuntenLetter.txt", "w") as f:
f.write("Hidden text stored in plain ASCII on track 1, sectors 1-10 of the Modem Wars disk\n")
f.write("(never shown by the game; addressed to anyone who cracks or inspects the disk).\n")
f.write("=" * 78 + "\n\n")
f.write(letter + "\n")
# --- EA message in the drive bootstrap sector
d = raw(1, 17)
msg = d[0x4D:].split(b"\x00")[0].decode("latin-1")
with open(f"{DOCS}/track1_sector17_message.txt", "w") as f:
f.write("Text embedded after the code in the 1541 bootstrap sector (track 1 sector 17):\n\n")
f.write(msg + "\n")
# --- title picture: multicolour bitmap at LOAD $A000, screen RAM $9C00, colour RAM $9800, background $D021=0
load = open(f"{EXT}/files/load.prg", "rb").read()[2:]
bitmap = load[0xA000-0x9800:0xA000-0x9800+8000]
screen = load[0x9C00-0x9800:0x9C00-0x9800+1000]
color = load[0x9800-0x9800:0x9800-0x9800+1000]
PALETTE = [(0,0,0),(255,255,255),(136,0,0),(170,255,238),(204,68,204),(0,204,85),(0,0,170),(238,238,119),
(221,136,85),(102,68,0),(255,119,119),(51,51,51),(119,119,119),(170,255,102),(0,136,255),(187,187,187)]
W, H = 320, 200
pixels = bytearray(W * H * 3)
for cy in range(25):
for cx in range(40):
cell = cy * 40 + cx
c1 = screen[cell] >> 4
c2 = screen[cell] & 15
c3 = color[cell] & 15
for row in range(8):
b = bitmap[cell * 8 + row]
for px in range(4):
bits = (b >> (6 - px * 2)) & 3
col = [0, c1, c2, c3][bits]
r, g, bl = PALETTE[col]
for dup in range(2):
x = cx * 8 + px * 2 + dup
y = cy * 8 + row
i = (y * W + x) * 3
pixels[i:i+3] = bytes((r, g, bl))
def png(path, w, h, rgb):
rows = b"".join(b"\x00" + bytes(rgb[y*w*3:(y+1)*w*3]) for y in range(h))
def chunk(tag, data):
c = struct.pack(">I", len(data)) + tag + data
return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
with open(path, "wb") as f:
f.write(b"\x89PNG\r\n\x1a\n")
f.write(chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)))
f.write(chunk(b"IDAT", zlib.compress(rows, 9)))
f.write(chunk(b"IEND", b""))
# scale 2x vertically too so the aspect is right
scaled = bytearray()
for y in range(H):
row = pixels[y*W*3:(y+1)*W*3]
scaled += row + row
png(f"{DOCS}/titleScreen.png", W, H * 2, scaled)
print("wrote", os.listdir(DOCS))