crossed
A program of Phases.
This program is not meant to build. What the tool says is the lesson.
NGA2414 reference to `levelMap`, which is not in memory in phase `intro` while `introStart` is /Users/waldek/Desktop/NGA/tutorial/04-phases/crossed/intro.asm:13
main.ngp
include "atari/800xl.ngp"
modules { "print.asm" "intro.asm" "level.asm" }
resident { print }
phase intro { needs intro then level entry introStart }
phase level { needs level entry levelStart }
entry intro
container atr
800xl.ngp project
; An Atari 800XL with a double-density disk drive: sixty-four kilobytes and no
; banking, so storage is the diskette's own sectors, which nothing maps into
; memory. There is no unit set and no Window for that reason, and the driver
; shows nothing. The Container is the `.atr`, a bootable diskette whose first
; three sectors the tool writes itself.
target {
cpu "6502"
; A diskette it boots, or a `.xex` a DOS loads. A Project naming any other
; is refused here rather than at the writer.
containers atr, xex
}
; The diskette's sectors, from the fourth: the boot record has the first three
; and the load image whatever is left after the storage a program uses. A unit
; is 256 sectors, which is the one size the `.atr` takes, and three of them
; reach past the 180 KB a double-density diskette holds. It is a ceiling and
; not a reservation: what the image costs is what the program's Payloads came
; to.
storage { units 3 size 65536 }
target {
region ram $0000 .. $06FF ram
; $0700 to $087F is the boot record's. It stands in no Region, so the solver
; never allocates there and the record's own pins are the author's word.
region $0880 .. $CFFF ram
region $D800 .. $FFFF ram
region stack $0100 .. $01FF reserved
region io $D000 .. $D7FF register
; A Project that wants more of the hardware by name declares it, as it
; declares the memory its DOS holds.
register PORTB $D301
}
; The OS, the driver for a diskette and the decoders the tool ships, listed as
; a file of the Project's own would be. Every Transition calls the driver and
; the decoders, so they are resident, and the OS is resident because the driver
; calls into it; a decoder nothing uses is dropped.
modules { "atari/os.asm" "atari/charsets.asm" "atari/disk.asm" "stream/zx0.asm" }
resident { os, charsets, disk, zx0 }
os.asm os
; The Atari OS as a Module: what it occupies while it is in memory, and the
; names a program reaches it by. Sections that hold no bytes, pinned where
; the OS lives, so the solver sees memory that is taken rather than a Region
; it may never allocate from — which is what lets a Phase that switches the
; ROM out have those addresses back, once there is a way to say so. See
; 0038 for why what occupies memory for a while is a Module, and 0051 for
; what this first cut settles and what it leaves.
;
; Every Section here is `root`: the OS and the hardware reach them with no
; Reference in any Chunk, so nothing else would keep them. None holds bytes,
; so none has a Payload and no Transition loads one.
;
; This is the OS as an XL or XE ships it. A machine whose OS lives elsewhere
; declares a Module of its own; the variant is what chooses.
.export RTCLOK, SDMCTL, SDLSTL, SDLSTH, CH, SETVBV, XITVBV
; The OS's half of the zero page. The program's own variables live above it,
; in the half the variant leaves to the solver.
.section zeropage at $0000, root
osZero
.res $12
RTCLOK .res 3 ; $0012: the frame counter, three bytes
.res $6B
.ends
; The OS's variables and buffers. The shadow registers are among them and
; are Labels here rather than registers of the variant: they are the OS's
; memory, which the OS copies to the hardware on every vertical blank, and
; a `register` Region inside this Section would be an address the Section
; may not cover.
.section absolute at $0200, root
osRam
.res $2F
SDMCTL .res 1 ; $022F: shadows DMACTL
SDLSTL .res 1 ; $0230: the display list address, low
SDLSTH .res 1 ; $0231: and high
.res $CA
CH .res 1 ; $02FC: the last key pressed, $FF for none
.res $403
.ends
; The ROM, in the two ranges the hardware registers leave between them, and
; the entry points a program calls in the second. The RAM underneath is what
; a Phase without the OS would be given.
.section absolute at $C000, root
osRomLow
.res $1000
.ends
.section absolute at $D800, root
osRomHigh
.res $C5C
SETVBV .res 3 ; $E45C: A = the stage, X/Y = the routine
.res 3
XITVBV .res 3 ; $E462: the end of a deferred routine
.res $1B9B
.ends
charsets.asm charsets
; The Atari's two codes for the same letters, as Charsets.
;
; `atascii` is what the character I/O takes: the machine's own code, with the
; graphics characters where an ASCII machine keeps its control codes, and the
; letters where ASCII has them — which is why an unprefixed literal of plain
; letters has always worked and why nothing else has. `\n` is `$9B`, the end of
; line, and not `$0A`.
;
; `screen` is what the display reads out of screen memory, which is the same
; letters at other numbers. A program that writes where the display looks
; rather than through the OS wants this one.
;
; The graphics characters are written here as the Unicode the box-drawing and
; block characters have, so a picture drawn in the source is the picture the
; machine draws. A letter in inverse video is one of those too — Unicode squares
; them off in a negative, and `atascii"PRESS 🆂"` is seven bytes with the last
; one inverse. A whole line of inverse text is better asked of a Charset
; derived from this one, `.charset bright : atascii ^ $80`, which is what a
; derivation is for. Two of them have no exact Unicode: `$02` and `$0D` are a
; quarter of a cell where the nearest character is an eighth, and are written
; as `▕` and `▔`. Everything else, the inverse entries included, was read off
; the machine's own font and matches it.
;
; This Module emits nothing: it is two names and two tables.
.export atascii, screen
; tag atascii
.charset atascii
"♥├▕┘┤┐╱╲◢▗◣▝▘▔▂▖♣┌─┼●▄▎┬┴▌└" = $00
"↑↓←→" = $1C
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" = $20
"♦" = $60
"abcdefghijklmnopqrstuvwxyz" = $61
"♠" = $7B
"│" = $7C
"\n" = $9B
; Inverse video is the same glyph with the bits the other way round, which
; for the blocks and the triangles is another character Unicode draws. Every
; one of these was read off the machine's own font and is exact; the rest of
; the inverse half has no glyph to be written as, and is reached by adding
; $80 or by a Charset derived from this one.
"▊" = $82 ; ▕
"◤" = $88 ; ◢
"▛" = $89 ; ▗
"◥" = $8A ; ◣
"▙" = $8B ; ▝
"▟" = $8C ; ▘
"▆" = $8D ; ▔
"▜" = $8F ; ▖
"▀" = $95 ; ▄
"▐" = $99 ; ▌
"█" = $A0 ; the space
; And the letters, which Unicode squares off in a negative.
"🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉" = $C1
.endch
; end atascii
.charset screen
" !\"#$%&'()*+,-./0123456789:;<=>?" = $00
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" = $20
"♥├▕┘┤┐╱╲◢▗◣▝▘▔▂▖♣┌─┼●▄▎┬┴▌└" = $40
"♦" = $60
"abcdefghijklmnopqrstuvwxyz" = $61
"♠" = $7B
"│" = $7C
; The same inverses, at the display's numbers.
"▊" = $C2
"◤" = $C8
"▛" = $C9
"◥" = $CA
"▙" = $CB
"▟" = $CC
"▆" = $CD
"▜" = $CF
"▀" = $D5
"▐" = $D9
"█" = $80
"🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉" = $A1
.endch
disk.asm disk
; The storage driver for a diskette: a unit of storage is 256 sectors of 256
; bytes, and unit zero begins at sector 4, the first after the boot record.
; That size is what makes the three bytes the model addresses storage with —
; a unit and a two-byte offset — the address the hardware takes, so this driver
; does no arithmetic: the unit is the sector number's high byte, the offset's
; high byte is its low one, and the offset's low byte is the position in the
; sector. Reading a sector is the OS's own disk handler, called through
; `DSKINV` with the request in the device control block, which is why this
; driver needs no register of its own and knows nothing about a drive.
;
; The medium maps nothing, so this driver names no Window: `stream`, `show`
; and `showAt` are not declared, and the two Procs that put base memory back
; after a Transition are a `rts`.
;
; Resident, since every Transition calls it; the variant lists it so.
DSKINV = $E453 ; the OS's disk handler: one sector per call
DUNIT = $0301 ; the drive, 1 for D1:
DCOMND = $0302 ; 'R' reads
DBUFLO = $0304
DBUFHI = $0305
DAUX1 = $030A ; the sector, two bytes
DAUX2 = $030B
diskFirst = 4 ; the sector unit zero begins at
.driver open diskOpen
.driver read diskRead
.transform copy diskCopy
.macro diskOpen
jsr diskOpenStream
.endm
.macro diskRead
jsr diskReadByte
.endm
; The stream: which sector the buffer holds, where in it the next byte is, and
; whether the position has run off its end. A plain Section rather than
; Temporaries, because the value has to survive between one call and the next
; while nothing here is running.
.section zeropage
diskAt .res 2
diskPos .res 1
diskNeed .res 1
.ends
; The sector the stream reads through. A Section of reservations alone, so it
; is in no Container and no load writes it. `within 256` keeps it off a page
; boundary: every byte of it is read as `diskBuffer,y`, and an index that
; carries into the next page costs a cycle.
.section absolute within 256
diskBuffer
.res 256
.ends
; A = the unit, X/Y = the offset in it: the stream stands there. An offset past
; the unit's end carries into the units after, since the routine counts on in a
; Frame without knowing where a unit ends — and here it carries by itself, the
; three bytes being one position that the sector number takes the top two of.
.proc diskOpenStream
stx diskPos
sta diskAt+1 ; the unit is the sector number's high byte
tya ; and the offset's high byte is its low one
clc
adc #<diskFirst
sta diskAt
lda diskAt+1
adc #>diskFirst
sta diskAt+1
jmp diskFill
.endp
; A = the next byte of the stream. X and Y are not preserved.
.proc diskReadByte
lda diskNeed
beq @have
inc diskAt
bne @next
inc diskAt+1
@next
jsr diskFill
@have
ldy diskPos
inc diskPos
bne @within
sty diskNeed ; Y is $FF here, and any non-zero will do
@within
lda diskBuffer,y
rts
.endp
; The sector in diskAt into the buffer, asked for again until the handler says
; it has it: a Transition that cannot read cannot go on, and the drive answered
; when the OS read the boot record off this diskette.
.proc diskFill
lda #0
sta diskNeed
lda diskAt
sta DAUX1
lda diskAt+1
sta DAUX2
@again
lda #1
sta DUNIT
lda #$52
sta DCOMND
lda #<diskBuffer
sta DBUFLO
lda #>diskBuffer
sta DBUFHI
jsr DSKINV
bmi @again
rts
.endp
; The decoder of `copy`: X/Y = the destination, the stream at the stored size
; and then the bytes. A byte at a time through the stream, because what this
; costs is the sector reads underneath it — a run copied out of the buffer
; would save a few hundred cycles against the tens of thousands a sector takes
; to arrive.
diskDst .ztemp 2
diskLeft .ztemp 2
.proc diskCopy
stx diskDst
sty diskDst+1
jsr diskReadByte
sta diskLeft
jsr diskReadByte
sta diskLeft+1
@byte
lda diskLeft
ora diskLeft+1
beq @done
jsr diskReadByte
ldy #0
sta (diskDst),y
inc diskDst
bne @counted
inc diskDst+1
@counted
lda diskLeft
bne @low
dec diskLeft+1
@low
dec diskLeft
jmp @byte
@done
rts
.endp
zx0.asm zx0
; The decoder of `zx0` over any driver's stream: Einar Saukas's ZX0, version
; 2's standard forward stream, decoded as the reference dzx0.c does, driven
; by the stream's own end marker. Written here from the format, not ported:
; nothing of the reference's text is in it, and it is licensed as everything
; under lib/ is, see lib/LICENSE. Entered with X/Y = the destination and the
; stream at the stored size, which it reads past. A match is copied a byte
; at a time, forward, from what was written, which is what makes an offset
; shorter than its length — the run — come out right. Resident, and outside
; the driver's window.
;
; zx0Bits the bit buffer, a sentinel one above the bits still unread
; zx0Offset the last offset
; zx0Length the length in hand, or an offset's MSB while one is read
; zx0Invert one while an offset's MSB is read, whose data bits the
; stream carries complemented; zero otherwise
.transform zx0 zx0Decode
zx0Dst .ztemp 2
zx0Src .ztemp 2
zx0Bits .ztemp 1
zx0Offset .ztemp 2
zx0Length .ztemp 2
zx0Invert .ztemp 1
; The next bit of the stream, in A as zero or one and in the Z flag. The
; buffer holds a sentinel above the unread bits, so shifting it to nothing is
; the signal to fetch the next byte and put the sentinel back below it.
.proc zx0Bit
asl zx0Bits
bne @have
nga.read
rol ; the carry the asl left is the sentinel
sta zx0Bits
@have
lda #0
rol
rts
.endp
; An interlaced Elias gamma value into zx0Length: a control bit says whether
; a data bit follows, and the value begins at one. Three Procs chained by
; `then` because the value after an offset is entered with its first control
; bit already read, at zx0EliasData: the first falls through into the
; second, and the second branches into the third, which the chain is what
; allows.
.proc zx0Elias
lda #1
sta zx0Length
lda #0
sta zx0Length+1
.endp then zx0EliasMore
.proc zx0EliasMore
jsr zx0Bit
beq zx0EliasData ; zero: a data bit follows
rts
.endp then zx0EliasData
.proc zx0EliasData
jsr zx0Bit
eor zx0Invert
lsr
rol zx0Length
rol zx0Length+1
jmp zx0EliasMore
.endp
; One byte written: the destination moves on and the length in hand comes
; down, leaving Z set when it reaches zero.
.proc zx0Step
inc zx0Dst
bne @moved
inc zx0Dst+1
@moved
lda zx0Length
bne @low
dec zx0Length+1
@low
dec zx0Length
lda zx0Length
ora zx0Length+1
rts
.endp
; The length in hand copied from zx0Dst less the last offset to zx0Dst.
.proc zx0Copy
sec
lda zx0Dst
sbc zx0Offset
sta zx0Src
lda zx0Dst+1
sbc zx0Offset+1
sta zx0Src+1
@byte
ldy #0
lda (zx0Src),y
sta (zx0Dst),y
inc zx0Src
bne @from
inc zx0Src+1
@from
jsr zx0Step
bne @byte
rts
.endp
.proc zx0Decode
stx zx0Dst
sty zx0Dst+1
nga.read ; the stored size, which the end marker makes unnecessary
nga.read
lda #$80
sta zx0Bits ; an empty buffer: the sentinel alone
lda #1
sta zx0Offset
lda #0
sta zx0Offset+1 ; the last offset begins at one
sta zx0Invert
@literals
jsr zx0Elias
@literal
nga.read
ldy #0
sta (zx0Dst),y
jsr zx0Step
bne @literal
jsr zx0Bit
bne @offset
jsr zx0Elias ; a match at the last offset
jsr zx0Copy
jsr zx0Bit
beq @literals
@offset
lda #1
sta zx0Invert
jsr zx0Elias ; the new offset's MSB, complemented in the stream
lda #0
sta zx0Invert
lda zx0Length
beq @done ; 256 is the end marker, and the one value with a low byte of zero
lsr ; offset = MSB * 128 - LSB / 2
sta zx0Offset+1
lda #0
ror
sta zx0Offset
nga.read
lsr ; the LSB's low bit is the length's first control bit
php
eor #$FF
sec
adc zx0Offset
sta zx0Offset
bcs @subtracted
dec zx0Offset+1
@subtracted
plp
lda #1
sta zx0Length
lda #0
sta zx0Length+1
bcs @counted ; a control bit of one: the value is one
jsr zx0EliasData
@counted
inc zx0Length ; a match at a new offset is one longer than written
bne @copy
inc zx0Length+1
@copy
jsr zx0Copy
jsr zx0Bit
bne @offset
jmp @literals
@done
rts
.endp
print.asm
; The one thing both Phases need: a line on the screen, through the character
; I/O the OS opens on channel 0 before a program starts. `PUTREC` writes a
; record, so it ends the line itself.
CIOV = $E456
ICCOM = $0342
ICBAL = $0344
ICBLL = $0348
PUTREC = 9
; Both Phases call it, so both have to see the name.
.export printLine
; Where the line stands, and how long it is. Declaring where the two arguments
; are is what lets a Module of C call this one as a function.
.proc printLine
.declare arg xy
.declare arg a
stx ICBAL
sty ICBAL+1
sta ICBLL
lda #0
sta ICBLL+1
lda #PUTREC
sta ICCOM
ldx #0
jsr CIOV
rts
.endp
intro.asm
.section
introText
.byte "INTRO"
introTextEnd
.ends
; tag cross
.proc introStart
ldx #<introText
ldy #>introText
lda #introTextEnd - introText
jsr printLine
lda levelMap ; the Phase this belongs to has not loaded it
.transition level
.endp
; end cross
level.asm
.export levelMap
; What the Phase is for: a map of ten rows, which is what waits in storage
; while the program is in `intro`. Thirty-seven characters is what a row of
; the screen holds once the OS's left margin is taken off.
levelWidth = 37
.section
levelMap
.byte "#####################################"
.byte "#...................................#"
.byte "#...................................#"
.byte "#......########.....................#"
.byte "#......#......#.....................#"
.byte "#......#......#.....................#"
.byte "#......########.....................#"
.byte "#...................................#"
.byte "#...................................#"
.byte "#####################################"
levelMapEnd
.ends
; A row at a time. `at` and `left` are Temporaries of this Proc, as chapter
; two's were, and `printLine` keeps neither of them.
.proc levelStart
at .ztemp 2
left .ztemp 1
lda #<levelMap
sta at
lda #>levelMap
sta at+1
lda #( levelMapEnd - levelMap ) / levelWidth
sta left
@row
ldx at
ldy at+1
lda #levelWidth
jsr printLine
clc
lda at
adc #levelWidth
sta at
bcc @counted
inc at+1
@counted
dec left
bne @row
@stop jmp @stop
.endp
transition.asm nga.transition
; The Transition routine. Called by `jsr` from a `.transition`, with the
; statement's list right behind the call: the Phase to enter, and per Phase
; the code may be in, the unit and offset where the edge's Frame waits.
; nga.open and nga.read are the driver's roles, macros of the driver's
; Module expanded here; ngaShowBases is the Proc the tool generates beside
; the dispatcher, which reads from the Frame the base the entered Phase gives
; every Window and shows it, the stream's last; and ngaCurrentPhase is the
; Cell the tool generates. Everything else the edge has to say waits in the
; Frame, in storage. See docs/spec/transition.md.
;
; Its zero page is Temporaries: nothing of it is needed once the routine has
; jumped to the entered Phase's entry, and a `.transition` is a jump into the
; routine, so whatever the statement's Section had on the zero page is dead by
; then too. Two Temporaries never live at once share an address — see
; docs/decisions/0034-trace.md.
.export ngaTransition
ngaPtr .ztemp 2
ngaEntry .ztemp 2
ngaDst .ztemp 2
ngaValue .ztemp 2
ngaOffset .ztemp 2
ngaFramePos .ztemp 2
ngaWanted .ztemp 1
ngaCount .ztemp 1
ngaFrameUnit .ztemp 1
ngaUnit .ztemp 1
.proc ngaTransition
pla
sta ngaPtr
pla
sta ngaPtr+1 ; the return address: one below the statement's list
ldy #1
lda (ngaPtr),y
sta ngaWanted ; the Phase to enter
iny
lda (ngaPtr),y
sta ngaCount ; entries that follow
lda ngaPtr
clc
adc #3
sta ngaPtr
bcc @scan
inc ngaPtr+1
@scan
lda ngaCount
bne @check
brk ; no entry for the current Phase: unreachable while the static rule holds
@check
ldy #0
lda (ngaPtr),y
cmp ngaCurrentPhase
beq @found
lda ngaPtr ; the next entry: four bytes on
clc
adc #4
sta ngaPtr
bcc @skipped
inc ngaPtr+1
@skipped
dec ngaCount
jmp @scan
@found
iny
lda (ngaPtr),y
sta ngaFrameUnit ; the unit the Frame waits in
iny
lda (ngaPtr),y
sta ngaFramePos
iny
lda (ngaPtr),y
sta ngaFramePos+1 ; and where in it
.endp then ngaEnter
; The rest of it, and the way in for a Container that has no current Phase to
; look one up by: ngaWanted holds the Phase to enter and ngaFrameUnit and
; ngaFramePos where its Frame waits. The cold start comes here — see
; docs/decisions/0216-a-car-names-its-format-and-the-cold-start-is-an-edge.md.
.export ngaEnter
.proc ngaEnter
jsr ngaFrameOpen
nga.read
sta ngaEntry
nga.read
sta ngaEntry+1
nga.read
sta ngaCount ; blocks that follow
lda #3
jsr ngaFrameSkip
@block
lda ngaCount
beq @cells
jsr ngaFrameOpen ; back to the Frame: a block's stream replaced it
nga.read
sta ngaUnit
nga.read
sta ngaOffset
nga.read
sta ngaOffset+1
nga.read
sta ngaDst
nga.read
sta ngaDst+1
nga.read
pha ; the decoder's number
lda #6
jsr ngaFrameSkip
lda ngaUnit
ldx ngaOffset
ldy ngaOffset+1
nga.open ; the block's stream: its stored size, then its bytes
pla
ldx ngaDst
ldy ngaDst+1
jsr ngaTransform
dec ngaCount
jmp @block
@cells
jsr ngaFrameOpen
nga.read
sta ngaCount ; Cell writes that follow, read in one stream
@cell
lda ngaCount
beq @enter
nga.read
sta ngaDst
nga.read
sta ngaDst+1
nga.read
sta ngaValue
nga.read
sta ngaValue+1
ldy #0
lda ngaValue
sta (ngaDst),y ; a Cell stands outside the driver's window
iny
lda ngaValue+1
sta (ngaDst),y
dec ngaCount
jmp @cell
@enter
jsr ngaShowBases ; the entered Phase's base in every Window, read from the Frame
lda ngaWanted
sta ngaCurrentPhase ; the Phase the program is in from here on
jmp (ngaEntry)
.endp
; The Frame's stream, from where the routine last left it.
.proc ngaFrameOpen
lda ngaFrameUnit
ldx ngaFramePos
ldy ngaFramePos+1
nga.open
rts
.endp
; A bytes on in the Frame, which is where its stream is opened next.
.proc ngaFrameSkip
clc
adc ngaFramePos
sta ngaFramePos
bcc @done
inc ngaFramePos+1
@done
rts
.endp
cell nga.cell
ngaCurrentPhaseboot.asm nga.boot
; The boot record of an .atr: the first three sectors, which the OS reads to
; $0700 and calls at $0706. Those three hold 128 bytes, because the OS boots
; with DSCTLN at 128; every sector after them holds 256, so the first thing the
; loader does is say so. What it loads is the load image, the stream of segments
; a `.xex` is made of with $FFFF closing it, waiting in the sectors after
; storage. See docs/spec/atr.md.
DSKINV = $E453
DUNIT = $0301
DCOMND = $0302
DBUFLO = $0304
DBUFHI = $0305
DAUX1 = $030A
DAUX2 = $030B
DSCTLN = $02D5 ; what a sector holds, which DSKINV transfers
RUNAD = $02E0
DOSVEC = $000A
bootBuf = $0400 ; the cassette buffer and the spare page after
; it, both free once the record is in: 256
; bytes, which is what a sector holds here
; The zero page the OS leaves once it has booted: RAMLO and CASINI were the
; boot's own scratch and TSTDAT is spare. TRAMSZ, $06, is not among them —
; coldstart reads it after the boot has returned.
bootDst = $0000 ; where the segment being read lands
bootLeft = $0002 ; bytes of it still to come
bootSector = $0004 ; the sector the buffer holds
bootPos = $0007 ; where in the buffer the stream stands, a byte
; that wraps because a sector holds 256
.section absolute at $0700, root
ngaBootRecord
.byte 0 ; the OS ignores this byte
.byte 3 ; sectors it reads, from the first
.word $0700 ; where it puts them
.root ; the OS calls what stands here
.word ngaBootIdle ; DOSINI, called once the load returns and on reset
.ends
; The first sector of the load image, which the tool writes here once it knows
; how much storage the program came to use, and one byte of the loader's own.
; At the end of the record, out of the loader's way; the zero page the OS
; leaves is spoken for, and $0006 is TRAMSZ, which the cold start reads after
; the boot has returned.
.export ngaBootImage
.section absolute at $087D, root
ngaBootImage
.word 0
bootNeed
.res 1 ; set once the position has wrapped: the next
.ends ; byte stands in the sector after
; The OS jsr's here once the three sectors stand at $0700, and a carry set on
; return is a boot it tries again.
.proc ngaBootLoad, absolute at $0706, root
lda #0 ; every sector from here on holds 256 bytes
sta DSCTLN
lda #1
sta DSCTLN+1
lda ngaBootImage
sta bootSector
lda ngaBootImage+1
sta bootSector+1
jsr fill
@segment
jsr byte
sta bootDst
jsr byte
sta bootDst+1
and bootDst ; $FFFF closes the stream, and no segment starts there
cmp #$FF
beq @run
jsr byte ; the end address, inclusive
sta bootLeft
jsr byte
sta bootLeft+1
sec ; the count is end - start + 1
lda bootLeft
sbc bootDst
sta bootLeft
lda bootLeft+1
sbc bootDst+1
sta bootLeft+1
inc bootLeft
bne @bytes
inc bootLeft+1
@bytes
lda bootLeft
ora bootLeft+1
beq @segment
jsr byte
ldy #0
sta (bootDst),y
inc bootDst
bne @counted
inc bootDst+1
@counted
lda bootLeft
bne @low
dec bootLeft+1
@low
dec bootLeft
jmp @bytes
@run
lda RUNAD ; where the program starts, which the OS jumps to
sta DOSVEC
lda RUNAD+1
sta DOSVEC+1
clc ; a boot the OS keeps
rts
; A = the next byte of the image, which runs from one sector into the next.
; The position wraps to zero at the sector's end, and bootNeed says the buffer
; no longer holds what stands there.
byte
lda bootNeed
beq @have
inc bootSector
bne @next
inc bootSector+1
@next
jsr fill
@have
ldy bootPos
inc bootPos
bne @within
sty bootNeed ; Y is $FF here, and any non-zero will do
@within
lda bootBuf,y
rts
; The sector in bootSector into the buffer, tried again until it is read: the
; OS read this record off this diskette, so the drive is there.
fill
lda #0
sta bootPos
sta bootNeed
lda bootSector
sta DAUX1
lda bootSector+1
sta DAUX2
@again
lda #1
sta DUNIT
lda #$52
sta DCOMND
lda #<bootBuf
sta DBUFLO
lda #>bootBuf
sta DBUFHI
jsr DSKINV
bmi @again
rts
.endp
; DOSINI: the OS calls it once the load has returned and on every reset, so it
; is in memory by then and the solver places it as it places any Proc.
.proc ngaBootIdle, root
rts
.endp
transforms.asm nga.transforms
; The dispatcher over the decoders the program declared, in the order the
; tool numbered them. See docs/spec/transition.md.
.export ngaShowBases
.proc ngaShowBases
rts
.endp
.export ngaRestore
.proc ngaRestore
rts
.endp
.export ngaTransform
.proc ngaTransform
cmp #0
bne @not0
jmp diskCopy
@not0
brk
.endp