First public commit.

This commit is contained in:
The Dust Council 2026-07-03 19:35:35 -07:00
parent 2a48f52979
commit 4bac9d83ed
288 changed files with 18417 additions and 1076 deletions

53
lenser/apple/dsk.py Normal file
View file

@ -0,0 +1,53 @@
"""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