53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
"""Write a bootable Apple II .dsk (DOS 3.3 sector order, 143360 bytes).
|
|
|
|
The .dsk stores sectors in DOS *logical* order, but the Disk II boot ROM reads
|
|
*physical* sectors, so we place each chunk of the bitmap at the logical slot that
|
|
maps to the physical sector our loader will read -- making the loader's
|
|
physical-sequential reads come out contiguous in memory.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# DOS 3.3 physical-sector -> logical-sector (the .dsk read interleave).
|
|
PHYS2LOG = [0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15]
|
|
|
|
SECTOR = 256
|
|
SPT = 16
|
|
TRACKS = 35
|
|
DISK_SIZE = TRACKS * SPT * SECTOR # 143360
|
|
|
|
|
|
class DskError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _offset(track: int, phys_sector: int) -> int:
|
|
return (track * SPT + PHYS2LOG[phys_sector]) * SECTOR
|
|
|
|
|
|
def build_boot_dsk(boot: bytes, payload: bytes) -> bytes:
|
|
"""boot = assembled boot0 (origin $0800); payload = data pages the loader reads
|
|
in order: track 0 sectors 1-15, then whole tracks 1, 2, ... (physical order)."""
|
|
if len(payload) % SECTOR:
|
|
raise DskError("payload must be a whole number of 256-byte sectors")
|
|
npages = len(payload) // SECTOR
|
|
disk = bytearray(DISK_SIZE)
|
|
disk[0:SECTOR] = (bytes(boot) + bytes(SECTOR))[:SECTOR] # boot0 at T0 phys 0
|
|
|
|
assigns = [(0, p) for p in range(1, 16)] # track 0 (skip boot)
|
|
track = 1
|
|
while len(assigns) < npages:
|
|
assigns += [(track, p) for p in range(16)]
|
|
track += 1
|
|
if track > TRACKS:
|
|
raise DskError("payload exceeds disk capacity")
|
|
for page, (trk, phys) in enumerate(assigns[:npages]):
|
|
off = _offset(trk, phys)
|
|
disk[off:off + SECTOR] = payload[page * SECTOR:(page + 1) * SECTOR]
|
|
return bytes(disk)
|
|
|
|
|
|
def write_dsk(path: str, data: bytes) -> str:
|
|
with open(path, "wb") as f:
|
|
f.write(data)
|
|
return path
|