First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
0
lenser/pet/viewer/__init__.py
Normal file
0
lenser/pet/viewer/__init__.py
Normal file
68
lenser/pet/viewer/assemble.py
Normal file
68
lenser/pet/viewer/assemble.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Assemble the PET viewer with `xa` and build the loadable .prg.
|
||||
|
||||
The PRG loads at the PET BASIC start ($0401): a BASIC stub ``10 SYS1056`` then
|
||||
the 6502 viewer (at $0420 = 1056) then the screen data (at $0500). Running it
|
||||
(or SYS 1056) copies the screen codes to $8000.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
BASIC_START = 0x0401
|
||||
ML_ORG = 0x0420 # 1056
|
||||
DATA_ORG = 0x0500
|
||||
|
||||
# BASIC: 10 SYS1056 (bytes from $0401)
|
||||
_STUB = bytes([0x0B, 0x04, 0x0A, 0x00, 0x9E,
|
||||
0x31, 0x30, 0x35, 0x36, 0x00, 0x00, 0x00])
|
||||
|
||||
|
||||
class AssemblerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def _assemble(pages: int) -> bytes:
|
||||
if not have_xa():
|
||||
raise AssemblerError("The 'xa' (xa65) assembler was not found (apt install xa65).")
|
||||
wrapper = (f"#define DATA ${DATA_ORG:04X}\n"
|
||||
f"#define PAGES {pages}\n"
|
||||
'#include "viewer.s"\n')
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "v.bin")
|
||||
fd, wrap = tempfile.mkstemp(suffix=".s", prefix="_wrap_", dir=VIEWER_DIR)
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(wrapper)
|
||||
proc = subprocess.run(["xa", "-o", out, os.path.basename(wrap)],
|
||||
capture_output=True, text=True, cwd=VIEWER_DIR)
|
||||
if proc.returncode != 0:
|
||||
raise AssemblerError(f"xa failed:\n{proc.stdout}{proc.stderr}")
|
||||
with open(out, "rb") as f:
|
||||
return f.read()
|
||||
finally:
|
||||
os.unlink(wrap)
|
||||
|
||||
|
||||
def build_prg(screen: bytes) -> bytes:
|
||||
"""screen = screen-RAM bytes (1000 for 40-col, 2000 for 80-col)."""
|
||||
pages = (len(screen) + 255) // 256
|
||||
data = bytes(screen) + bytes(pages * 256 - len(screen)) # pad to whole pages
|
||||
code = _assemble(pages)
|
||||
mem = bytearray()
|
||||
mem += _STUB
|
||||
mem += b"\x00" * (ML_ORG - BASIC_START - len(mem))
|
||||
mem += code
|
||||
if len(mem) > DATA_ORG - BASIC_START:
|
||||
raise AssemblerError("viewer code overruns the data area")
|
||||
mem += b"\x00" * (DATA_ORG - BASIC_START - len(mem))
|
||||
mem += data
|
||||
return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem)
|
||||
34
lenser/pet/viewer/viewer.s
Normal file
34
lenser/pet/viewer/viewer.s
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
; Commodore PET screen viewer (6502). Copies PAGES*256 bytes of precomputed
|
||||
; screen codes from DATA into the PET screen RAM at $8000, then loops. The PET
|
||||
; video hardware continuously displays $8000 as text, so the picture stays up.
|
||||
;
|
||||
; #defines from viewer/assemble.py -- DATA = source address, PAGES = 256-byte
|
||||
; pages to copy (4 for 40-col / 1000 bytes, 8 for 80-col / 2000 bytes).
|
||||
|
||||
* = $0420
|
||||
|
||||
src = $fb
|
||||
dst = $fd
|
||||
|
||||
start:
|
||||
lda #<DATA
|
||||
sta src
|
||||
lda #>DATA
|
||||
sta src+1
|
||||
lda #$00
|
||||
sta dst
|
||||
lda #$80
|
||||
sta dst+1 ; dest = $8000 (screen RAM)
|
||||
ldx #PAGES
|
||||
ldy #$00
|
||||
cp:
|
||||
lda (src),y
|
||||
sta (dst),y
|
||||
iny
|
||||
bne cp
|
||||
inc src+1
|
||||
inc dst+1
|
||||
dex
|
||||
bne cp
|
||||
hang:
|
||||
jmp hang
|
||||
Loading…
Add table
Add a link
Reference in a new issue