72 lines
3.2 KiB
Python
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))
|