; Generated by 6502bench SourceGen v1.10.0
; https://6502bench.com/
;
; This project is https://github.com/micahcowan/fbdasm
;
; This file was generated from
; git commit df27caef7aa2641f2bcc809858dd3b4de59410d8
; from Wed Jan 7 08:15:17 2026 -0800
;
; You may use this disassembly for any purpose.
;
; If you distribute this work (whether original or modified), you must
; credit me: micah@addictivecode.org
;
; In this disassembly I use the term "actor" to refer to what the Family BASIC
; manual mostly calls an "animated character" and the Family BASIC language
; itself calls a "sprite". "Sprite" is ambiguous because in BASIC means a 2x2-
; tile animated character, while NES/Famicom hardware and associated
; documentation considers a "sprite" to be just a single one of those 2x2 tiles.
; "animated character" is far too long for variable names (as is AnimChar), and
; "char" is far too easily confused with its text-related meaning.
;
; Unfortunately I am not consistent with this terminology, and frequently fer to
; "actors" as "sprites". Meanwhile, actual Famicom sprites, I try to refer to as
; "sprite tile" or "tile", to make it clear I'm talking about an 8x8-pixel
; hardware sprite, and not the FB notion of a sprite (animated character).
DTU__ON .eq 0 {const}
EH_NO_HANDLER .eq $00 {const}
ENV_USED .eq $00 {const} ; value used with zpEnvelope to indicate envelope in use for given voice
err_NF .eq $00 {const} ; "NF" - NEXT without FOR
NC_NoCmd .eq 0 {const}
SHARP_UNUSED .eq $00 {const}
DTU__KEYBOARD_WO_SETSUZOKU .eq 1 {const}
EH_HANDLER_DEFINED .eq $01 {const}
err_SN .eq $01 {const} ; "SN" - Syntax Error
NC_FlushOutp .eq 1 {const}
CAS_HDR_PROG .eq $02 {const} ; Identity byte in header for prog saves to cassette tape.
DTU__DENGEN_WO .eq 2 {const}
EH_IN_HANDLER .eq $02 {const}
err_RG .eq $02 {const} ; "RG" - RETURN without GOSUB
HMARGIN .eq 2 {const} ; horizontal margin size (in tiles/chars)
NC_BufferCurLine .eq 2 {const}
SHARP_USED .eq $02 {const}
TYP_INT .eq $02 {const} ; For zpArgTyp. Integer word.
CAS_HDR_SCREEN .eq $03 {const} ; Identity byte in header for screen saves to cassette tape.
DTU__BACKUP_SWITCH_WO .eq 3 {const}
err_OD .eq $03 {const} ; "OD" - Out of Data
key_Stop .eq $03 {const} ; Control-C, (but ^C not always equiv to STOP)
NC_CopyCharsLeft .eq 3 {const}
TYP_STR .eq $03 {const} ; For zpArgTyp. String.
VMARGIN .eq 3 {const} ; certical margin size (in tiles/chars)
CHAN_MASK_TRI .eq 4 {const}
DTU__OFF_NI_SHITE_KUDASAI .eq 4 {const}
err_IL .eq $04 {const} ; "IL" - Illegal value
NC_ClearToEndOfRow .eq 4 {const}
NUM_PALETTES .eq 4 {const} ; number of palettes in a palette set
BGT_MODESEL_ITEMS .eq 5 {const} ; how many menu items in BGTOOL mode-select menu
DTU__NI_SHITE_KUDASAI .eq 5 {const}
err_OV .eq $05 {const} ; "OV" - Overflow
NC_GetCharUnderCursor .eq 5 {const}
DTU__SHITE_KUDASAI .eq 6 {const}
err_OM .eq $06 {const} ; "OM" - Out of Memory
NC_SetBgPalette .eq 6 {const}
err_UL .eq $07 {const} ; "UL" - Undefined Line number
key_Del .eq $08 {const} ; Control-H / Backspace
NC_ShowCursor .eq 8 {const}
NUM_ACTORS .eq 8 {const} ; number of animated characters ("actors") in Family BASIC
SpAtt_DEFMOVE .eq $08 {const} ; bit 3. SET: actor has a MOVE defined
TILE_PIXELS .eq 8 {const} ; pixels in a tile (either dimension)
err_DD .eq $09 {const} ; "DD" - Duplicate Definition
NC_RestoreCharAtCursor .eq 9 {const}
err_DZ .eq $0a {const} ; "DZ" - Division by Zero
NC_PlayMusic .eq 10 {const}
err_TM .eq $0b {const} ; "TM" - Type Mismatch
key_Home .eq $0b {const} ; Control-K
NC_PpuMaskUpdate .eq 11 {const}
tok_LNUM_AFTER .eq $0b {const} ; indicates a line number (word) follows this byte
err_ST .eq $0c {const} ; "ST" - String Too long
key_Cls .eq $0c {const} ; Control-L
NC_PpuMaskClear .eq 12 {const}
SELMD_FRST_TILEPOS .eq 12 {const}
tok_LLINK_AFTER .eq $0c {const} ; indicates a direct link to a line follows - only used during a RENUM
BGT_SELECT_NUM_TROWS .eq 13 {const} ; how many tile rows in BGTOOL SELECT mode's tile menu
key_Enter .eq $0d {const} ; Control-M
NC_InsertChar .eq 13 {const}
err_CC .eq $0e {const} ; "CC" - Cannot Continue
NC_CopyRowDown .eq 14 {const}
DEFAULT_PPUCTRL .eq $10 {const}
SpAtt_ANIMATE .eq $10 {const} ; bit 4. SET: actor is animated
err_TP .eq $11 {const} ; "TP" - Tape read error
tok_HEX_AFTER .eq $11 {const} ; indicates a number (word) follows this byte, originally hexadecimal
err_NR .eq $12 {const} ; "NR" - No Resume
key_Insert .eq $12 {const} ; Control-R
tok_NUM_AFTER .eq $12 {const} ; indicates a number (word) follows this byte, originally decimal
err_RE .eq $13 {const} ; "RE" - RESUME without ERROR
err_NB .eq $14 {const} ; "NB" - No BG Data
BGT_ROWS .eq 21 {const} ; number of rows in BGTOOL (excluding bottom status area)
err_UP .eq $15 {const} ; "UP" - Unprintable Error
NOTE_VAL_OCTAVE .eq 24 {const}
ROWS .eq 24 {const}
key_CtrlZ .eq $1a {const} ; Control-Z (clear to end of screen)
SELMD_LAST_TILEPOS .eq 26 {const}
key_Escape .eq $1b {const} ; Control-[
COLS .eq 28 {const}
key_CursorRight .eq $1c {const} ; Control-\
key_CursorLeft .eq $1d {const} ; Control-]
key_CursorUp .eq $1e {const} ; Control-^
MAP_ROWS .eq 30 {const} ; number of rows in a PPU nametable (240px / 8px tiles)
key_CursorDown .eq $1f {const} ; Control-_
key_Space .eq $20 {const}
SpAtt_BEHIND .eq $20 {const} ; bit 5. SET: behind bg; RESET: in front
STRLEN_MAX .eq 32 {const}
PLAY_INDEX_V1 .eq 33 {const}
ENV_UNUSED .eq $30 {const} ; value used with zpEnvelope to indicate envelope not used for given voice
PLAY_INDEX_V2 .eq 66 {const}
BU_AVAIL_MARK .eq $4c {const}
BU_RESTORED_MARK .eq $59 {const}
CURS_FLASH_TM .eq 128 {const} ; how long to hide/show the cursor when flashing
TOKEN_START .eq $80 {const}
TYP_ARRAY_FLAG .eq $80 {const} ; Flag bit indicating array type.
tok_THEN .eq $85 {const}
tok_LIST .eq $86 {const}
tok_SYSTEM .eq $87 {const}
tok_TO .eq $88 {const}
tok_STEP .eq $89 {const}
tok_SPRITE .eq $8a {const}
tok_PRINT .eq $8b {const}
tok_NEXT .eq $8d {const}
tok_DATA .eq $91 {const}
tok_REM .eq $95 {const}
tok_ON .eq $9a {const}
tok_OFF .eq $9b {const}
tok_MOVE .eq $a1 {const}
TileId_emptyBox .eq $b0 {const}
tok_AUTO .eq $b5 {const}
tok_DELETE .eq $b6 {const}
tok_RENUM .eq $b7 {const}
tok_ERROR .eq $bc {const}
tok_RESUME .eq $bd {const}
tok_ABS .eq $ca {const}
SPR_NullTile .eq $ef {const}
tok_OP_XOR .eq $ef {const}
tok_OP_OR .eq $f0 {const}
VPIXELS .eq 240 {const} ; number of pixels in a PPU nametable, vertically
tok_OP_AND .eq $f1 {const}
tok_OP_NOT .eq $f2 {const}
tok_OP_NE .eq $f3 {const}
tok_OP_EQ .eq $f6 {const}
tok_OP_PLUS .eq $f9 {const}
tok_OP_MINUS .eq $fa {const}
tok_OP_MOD .eq $fb {const}
tok_OP_DIV .eq $fc {const}
FOR_STK_MARKER .eq $fd {const} ; special CPU stack marker for FOR-loop data
TileId_whiteBox .eq $fd {const}
tok_OP_MUL .eq $fd {const}
GOSUB_STK_MARKER .eq $ff {const} ; special CPU stack marker for RETURN data
VRAM_screen0 .eq $2000 {const}
VRAM_screen1 .eq $2400 {const}
PPU_PALETTE_RAM .eq $3f00 {const}
zpFRETOP .eq $03 {addr/2} ; Pointer to just past the end of free space.
zpTXTTAB .eq $05 {addr/2} ; Pointer to the start of the BASIC program code. Always set to $6006.
zpTXTEND .eq $07 {addr/2} ; Ponter to just past the program end (zpTXTEND - zpTXTTAB = prog len)
zpTXTPTR .eq $09 {addr/2} ; Points at the next char of BASIC program to be processed.
zpRunLineNum .eq $0b {addr/2} ; When BASIC running, it's the current line number, or #$FFFF if it's direct from the command prompt. When inserting or deleting lines, this var instead points at the next line of BASIC program after the one to be ins/del
zpRunTxtNxtLnk .eq $0d {addr/2} ; When BASIC running, it's a pointer at the next line (starting at its "offset" byte)
zpInsLineNum .eq $0f {addr/2} ; Line number to give newly-inserted line
zpContRunLineNum .eq $11 {addr/2} ; Saved zpRunLineNum for CONT restore
zpContRunTxtNxtL .eq $13 {addr/2} ; Saved zpRunTxtNxtLnk for CONT restore
zpContTxtThisCmd .eq $15 {addr/2} ; Saved zpTxtThisCmd for CONT restore
zpSavedStack .eq $17 ; Saved SP
zpModifierKeyDn .eq $18 ; Non-zero when modifier key is pressed
zpMisc .eq $19 {addr/4} ; Used for a wide variety of things, mainly temporary pointers
zpVARTAB .eq $1d {addr/2} ; Start of variable storage
zpVAREND .eq $1f {addr/2} ; Just past end of variable storage
zpGpStrIdx .eq $21 ; Index for temp string storage in genPurposeBuf
zp22 .eq $22 ; Written to once, in NEW/_Reset. Never used.
zpStoppedFlag .eq $24 ; Non-zero if STOPped; zero otherwise.
zpPokeAddr .eq $25 {addr/2} ; stores addr to POKE to
zpArgTyp .eq $27 ; Designates the type of data in zpWAccum.
zpWAccum .eq $28 {addr/4} ; Like a 16-bit Accumulator. Forms both the first operand, and the result, of various 16-bit integer operations. Or: byte Len followed by word ptr, for strings.
zpWParam .eq $2c {addr/3} ; Like a 16-bit Data register. forms the second operand of various binary operators
zpExprStkIdx .eq $30 ; Current position in exprParseStk
zpPpuCtrlVal .eq $32
zpPpuMaskVal .eq $33
zpMulScratch .eq $34 {addr/8} ; Used for multiplication scratch space. Only the first four are ever actually used!
zpTmp3C .eq $3c
zpNumStrTmp .eq $3d {addr/5} ; Holds str output from HexStrFromWord and DecStrFromWord
zpFindLNum .eq $44 {addr/2}
zpTmp46 .eq $46 {addr/1} ; various miscellaneous uses
zpLineStartRow .eq $47 ; the screen row at which current typed line starts (only set AFTER typing is finished)
zpCH .eq $48 ; Current cursor column number
zpCV .eq $49 ; Current cursor row number
zpCursTileAddr .eq $4a {addr/2} ; PPU address of tile under cursor
zpInsertMode .eq $4c ; Zero = overwrite, non-zero = insert
zpTokenizePtr .eq $4d {addr/2}
zpPpuScreenBasePage .eq $4f ; The VRAM page used for the current screen we're UPDATING (may not be the one displayed)!
zpKanaHeld .eq $50 ; Non-zero (#$FF) if KANA key is still being held down from when it was first detected. Used to prevent rapid toggling.
zpListOrDelete .eq $51 ; Non-zero (#$FF) if we're in DELETE; zero if we're in LIST
zpOutputStr .eq $52 {addr/2} ; Pointer to null-term'd string to be queued for output
zpTokTblScn .eq $54 {addr/2} ; Used to scan thru tbl_KeywordTokens
zpRandom .eq $56 {addr/2} ; Used for RAND
zpTxtThisCmd .eq $58 {addr/2} ; The start of the currently-running command. Restored to zpTXTPTR on RESUME or CONT.
zpNewLineOffset .eq $5a ; Offset to next line for not-yet-placed, tokenized line
zpTxtLink .eq $5b {addr/2} ; Points to the start of the current program line (including the 3-byte preamble)
zpNmiAnimActCtr .eq $5d ; Counts how many actors are not currently animated, and turns off zpNmiAnimate if it reaches zero
zpUnusedStopFlg .eq $5e ; Causes STOP to be returned by ReadChar when set, but is never actually set by anything AFAICT
zpSavedRts .eq $5f {addr/2} ; FOR, GOSUB, other cmds pull return from stack, save here
zpCursorChar .eq $61 ; The character code of the current cursor
zpOutTxtCurPos .eq $62
zpNMICMD .eq $63 ; Which command the NMI handler should execute. 1 + the command's index in jtbl_NMICMD.
zpOutTxtStartPos .eq $64
zpPaletBgOrSpr .eq $65 ; zero when we're setting bg palette colors, non-zero when setting sprite palette
zpRkimMidMacro .eq $66 ; Non-zero (#$FF) if ReaKeyInsMacros ($B945) should insert next char from a fkey definition (next char is at $480 + ($67)) rather than read from keyboard.
zpFnKeyDfnOff .eq $67 ; Offset from $480 into cur fn key definition string
zpFnKeyHeld .eq $68 ; Non-zero ($FF) if a function key is still being held down from when it was first detected. Used to prevent continuous handling while pressed.
zpFnKeyCode .eq $6a ; Pressed function key (#$F0-$F7, for keys F1-F8).
zpSavedCH .eq $6b
zpSavedCV .eq $6c
zpKeyWhichKey .eq $6d ; Within ReadKeyboard, which key of the row in $6E was found pressed. Outside ReadKeyboard, when the mapped char code is > #$F0, its value will often be stored here while further code determines if a function key was pressed.
zpKeyWhichRow .eq $6e ; which key matrix row/byte of key has a non-modifier key down
zpCtrlKeyDn .eq $6f ; Non-zero (#$FF) when control key is pressed
zpShiftKeyDn .eq $70 ; Non-zero (#$FF) when (either) shift key is pressed
zpGrphKeyDn .eq $71 ; Non-zero (#$FF) when GRPH key is pressed
zpKanaFlag .eq $72 ; Non-zero (#$FF) when kana mode is active
zpCharUnderCurs .eq $73 ; The character currently under cursor
zpCursFlashCtr .eq $74 ; Delay count-down for when cursor should show/hide
zpHeldKeyChar .eq $75 ; Holds the char value of a key being held down
zpLineBufIdx .eq $76 ; Current write position into lineBuffer ($500)
zpKyRptLongCtr .eq $77 ; Key-repeat: delay count-down to first key repeat on long-press
zpKyNotRpt .eq $78 ; Key-repeat: #$01 = waiting on first key repeat, #$00 = on subsequent repeats
zpKyRptShtCtr .eq $79 ; Key-repeat: delay count-down for repeating key past the first, longer pause.
zpKeyRepeating .eq $7b ; Key-repeat: #$FF = key is actively repeating, #$00 = key not repeating
zpBgGotFlag .eq $7c ; zero-page copy of bgGotFlag from battery RAM
zp7D .eq $7d
zp7E .eq $7e
zpForLimitVal .eq $80 {addr/2} ; the limiting value that will cause an exit from FOR statement
zpForStepVal .eq $82 {addr/2} ; the step value by which to increment the index variable for a FOR statement
zpForNxtLnLnk .eq $84 {addr/2} ; link to the line of BASIC after the current FOR loop
zpLNumStart .eq $86 {addr/2} ; Starting line number of a line-number range
zpLNumEnd .eq $88 {addr/2} ; Ending line number of a line-number range
zpVarName .eq $8a {addr/2} ; Two-letter variable name
zpVarData .eq $8c {addr/2} ; pointer to selected variable data (deep, if var is array)
zpVARPTR .eq $8e {addr/2} ; pointer to start of selected variable
zpVarType .eq $90 ; Often indicates type of a found variable. Other misc uses also occur.
zpCasCksum .eq $91 {addr/2} ; Checksum for cassette data, also used for misc counting
zpTraceOnFlag .eq $97 ; Flag for whether to trace line numbers (TRON)
zpSavedTxtChar .eq $98
zpToken .eq $99
zpSavedTXTPTR .eq $9a {addr/2} ; Saved val of zpTXTPTR
zpPaletteNum .eq $9c
zpBackupRstrd .eq $9e ; Set to #$59 if BASIC was restored from battery backup
zpLoadQFlag .eq $9f ; True if we're in "LOAD?" rather than "LOAD"
zpNxtAutoLNum .eq $a0 {addr/2} ; Next line number to use (for AUTO)
zpAutoLNumIncr .eq $a2 {addr/2} ; How much to auto-increment each line number (from AUTO)
zpActorNum .eq $a4 ; Current work actor
zpDefMovIdx .eq $a5 ; cur actor # times 8, for indexing defMovS* vars
zpNmiDefMovIdx .eq $a6 ; (NMI) cur actor # times 8, for indexing defMovS* vars
zpNmiCurMTbl .eq $a7 {addr/2} ; (NMI) address of the mbtl for current actor
zpNmiActorNum .eq $a9 ; Current work actor, used by NMI
zpNmiAnimNxtFr .eq $ab ; While zpNmiAnimate is non-zero, this flag toggles every frame. Z = Handle NMI subcmds; NZ = Handle MOVEs
zpNmiAnimate .eq $ac ; This flag indicates that animated characters are active (via MOVE)
zpCasBufPtr .eq $ad {addr/2} ; Start of data to send to cassette
zpCasEndPtr .eq $af {addr/2} ; End of data to send to cassette
zpVerfFlag .eq $b1 ; If set, VERIFIES rather than LOADs a program. Used for "LOAD?".
zpNextDataPtr .eq $b2 {addr/2} ; Pointer to first content byte of line to start search for next DATA to READ
zpNextDataIdx .eq $b4 ; Char index into line at zpNextDataPtr to start from
zpErrCode .eq $b5
zpUnusedErrVar .eq $b6 ; This var is only ever used in one place, where it's set to zero.
zpErrLineNum .eq $b7 {addr/2} ; Line number where an error happened
zpTempo .eq $b9 {addr/3} ; tempo setting for each of the three voices
zpVolume .eq $bc {addr/3} ; volume level for each of the three voices
zpOctave .eq $bf {addr/3} ; octave # for each of the three voices
zpSharpVal .eq $c2 ; whether to sharp next note. 2 = yes, 0 = no
zpNoteLen .eq $c3 {addr/3} ; note length for each voice
zpPlayVoxIdx .eq $c6 {addr/3} ; start index into genPurposeBuf for each voice
zpPlayStatus .eq $c9 ; <0: error. >0: active voices mask. ==0: done playing.
zpCurVoice .eq $ca ; the voice the NMI music player is working on
zpNoteRealLen .eq $cb {addr/3} ; tempo-adjusted note length for each of the three voices
zpCurVoxMask .eq $ce ; SND_CHN mask for the current sound channel
zpEnvelope .eq $cf {addr/3} ; envelope setting for each voice. ENV_UNUSED (#$30) when no envelope, zero when using volume as envelope
zpDuty .eq $d2 {addr/3} ; cycle duty for each voice.
zpNmiCurFrCnts .eq $d5 {addr/2} ; (NMI) Pointer to vector being used for current actor's anim frame counts, for each facing
zpNmiNumFrames .eq $d7 ; (NMI) Holds the number of anim frames for the actor's cur facing (in high nybble)
zpNmiCurSprSht .eq $d9 {addr/2} ; (NMI) Pointer to sheet of sprites for each facing
zpNmiSprTileMp .eq $db {addr/2} ; (NMI) Pointer to the 2x2 tilemap to display for current actor
zpNmiSprShtOff .eq $dd ; (NMI) Value to add to each tile ID in a sprite sheet, to get IDs for this actor.
zpKanaCursChr .eq $e0 ; character/tile ID to use for cursor when kana active
zpNonKCursChr .eq $e1 ; character/tile ID to use for cursor when kana NOT active
zpAutoEnabled .eq $e2 ; non-zero (=10) when AUTO active; zero means inactive
zpKeyClickFlg .eq $e3 ; non-zero = key clicks on
zpVScroll0 .eq $e4 ; Vertical scroll used when SCREEN 0 is displayed
zpVScroll1 .eq $e5 ; Vertical scroll used when SCREEN 1 is displayed; updated when $F0 is set
zpErrHandStat .eq $e7 ; #$00 - no error, #$01 - error, not resumable, #$02 - error, resumable
zpErrHandLNum .eq $e8 {addr/2} ; line number to jump to when error occurs
zpHaveFNameArg .eq $ea ; true when LOAD was given a filename arg
zpNmiTrampoline .eq $ed {addr/3} ; Contains a JMP instruction to the real NMI handler. It's set to: JMP $8971
zpCurScreenNum .eq $f0 ; the _displayed_ SCREEN (0 or 1). Kept in sync with zpPpuScreenBasePage
zpGameNum .eq $f3 ; Game number (GAME cmd)
zpRsmTxtThisCmd .eq $f6 {addr/2} ; Saved zpTxtThisCmd for RESUME restore
zpRsmRunTxtNxtL .eq $f8 {addr/2} ; Saved zpRunTxtNxtLnk for RESUME restore
zpRsmRunLineNum .eq $fa {addr/2} ; Saved zpRunLineNum for RESUME restore
oamBuffer .eq $0200 {addr/256} ; Region for PPU sprite data DMA
oamSprYPos .eq $0200
oamSprTileId .eq $0201
oamSprAttr .eq $0202
oamSprXPos .eq $0203
tokenizeBuffer .eq $0300 {addr/256}
exprParseStk .eq $0400 {addr/128} ; expression evaluation stack
fnKey1_DfnBuf .eq $0480 {addr/16} ; Current definitions of the FN-key shortcuts, each 16 chars
lineBuffer .eq $0500 {addr/256} ; NMICMD_BufferCurLine copies the full current, potentially multi-row line to here
bgtCH .eq $0505 ; horiz. pos of tile cursor, in BGTOOL mode
bgtCV .eq $0506 ; vert. pos of tile cursor, in BGTOOL mode
bgtMenuTilesBuf .eq $0520 {addr/90} ; saved tiles from behind BGTOOL mode-select menu
isRowWrappedArray0 .eq $0601 {addr/24} ; Marks which visible screen rows are wrapped from previous line, if SCREEN 0.
isRowWrappedArray1 .eq $061b {addr/24} ; Marks which visible screen rows are wrapped from previous line, if SCREEN 1.
kbdDataAry .eq $0634 {addr/9} ; Key down bitmap. See loc $B9A1 for content details
sprTwoByTwoFlags .eq $063d {addr/8} ; 1x1-tile (#$00) vs 2x2 (#$01), for each actor.
sprAttrFlags .eq $0645 {addr/8} ; Sprite tile attributes for the 8 actors. See SpAtt_* for flag values.
sprTileMaps .eq $0680 {addr/32} ; Sprite tile maps for the 8 actors (4 tiles/actor)
defMovSFacing .eq $06c0 ; Actor MOVE direction (swapped with type, relative to DEF MOVE arg order)
defMovSActorFrm .eq $06c1 ; Low nybble: MOVE character type (Mario, Lady, Star Killer, etc)A; high nybble: which frame of animation frame we're on
defMovSSpdCtr .eq $06c2 ; Actor MOVE speed counter
defMovSMotionCtr .eq $06c3 ; Actor MOVE total movement remaining
defMovSSpeed .eq $06c4 ; Actor MOVE total movement remaining
defMovSMotion .eq $06c5 ; Actor MOVE total movement remaining
defMovSPosX .eq $06c6 ; Actor MOVE, current X position
defMovSPosY .eq $06c7 ; Actor MOVE, current Y position
genPurposeBuf .eq $0700 {addr/256} ; user directive messages (at least) get writen out to here
PPUCTRL .eq $2000 ;W VPHB SINN various
PPUMASK .eq $2001 ;W BGRs bMmG various
PPUSTATUS .eq $2002 ;R VSO- ---- various; read resets $2005/2006
OAMADDR .eq $2003 ;W OAM read/write address
PPUSCROLL .eq $2005 ;WW fine scroll position (two writes: X,Y)
PPUADDR .eq $2006 ;WW PPU read/write address (two writes: MSB, LSB)
PPUDATA .eq $2007 ;RW PPU data read/write
SQ1_VOL .eq $4000 ;DDLC VVVV
SQ1_SWEEP .eq $4001 ;EPPP NSSS
SQ1_LO .eq $4002 ;TTTT TTTT
SQ1_HI .eq $4003 ;LLLL LTTT
SQ2_SWEEP .eq $4005 ;EPPP NSSS
DMC_FREQ .eq $4010 ;IL-- RRRR
OAMDMA .eq $4014 ;AAAA AAAA OAM DMA high address
SND_CHN .eq $4015 ;W:---D NT21 R:IF-D NT21
JOY1 .eq $4016 ;joystick 1 data (R) and joystick strobe (W)
JOY2 .eq $4017 ;joystick 2 data (R) and frame counter (W)
bbBackupAvail .eq $6001 ; non-zero if program saved to battery backup
bbTXTEND .eq $6002 ; backed-up TXTEND (end of program text)
bgGotFlag .eq $6004 ; non-zero if bg data is saved at $6C00
bgGetVScroll .eq $6005 ; saved vscroll for saved screen data
basicProgram .eq $6006 ; The start of the BASIC program in memory
bgGetRam .eq $6c00 {addr/1024} ; region to save bg data with BGGET
memoryTop .eq $6fff ; What FRETOP is set to by default
; ROM Header
.addrs NA ;(auto-generated)
0000: 4e 45 53 1a+ .bulk $4e,$45,$53,$1a,$02,$01,$03,$00,$00,$00,$00,$00,$00,$00,$00,$00
.adrend ↑ NA
; Code start
.addrs $8000
8000: 4c ba 80 jmp _Reset
8003: 41 65 tbl_GameTXTENDs .dd2 $6541 ;End of "GAME 0" in mem (prog len + $6006)
8005: 8a 6a .dd2 $6a8a ;End of "GAME 1"
8007: 8c 6c .dd2 $6c8c ;End of "GAME 2"
8009: f8 6c .dd2 $6cf8 ;End of "GAME 3"
800b: 00 d4 tbl_GameTexts .dd2 txtGame0
800d: fe db .dd2 txtGame1
800f: 82 e6 .dd2 txtGame2
8011: 08 f3 .dd2 txtGame3
tbl_NoteValToPdLen
8013: ae 0e .dd2 $0eae
8015: 4e 0e .dd2 $0e4e
8017: f3 0d .dd2 $0df3
8019: 9e 0d .dd2 $0d9e
801b: 4d 0d .dd2 $0d4d
801d: 01 0d .dd2 $0d01
801f: b9 0c .dd2 $0cb9
8021: 75 0c .dd2 $0c75
8023: 35 0c .dd2 $0c35
8025: f8 0b .dd2 $0bf8
8027: bf 0b .dd2 $0bbf
8029: 89 0b .dd2 $0b89
802b: 57 0b .dd2 $0b57
802d: 27 0b .dd2 $0b27
802f: f9 0a .dd2 $0af9
8031: cf 0a .dd2 $0acf
8033: a6 0a .dd2 $0aa6
8035: 80 0a .dd2 $0a80
8037: 5c 0a .dd2 $0a5c
8039: 3a 0a .dd2 $0a3a
803b: 1a 0a .dd2 $0a1a
803d: fc 09 .dd2 $09fc
803f: df 09 .dd2 $09df
8041: c4 09 .dd2 $09c4
8043: ab 09 .dd2 $09ab
8045: 93 09 .dd2 $0993
8047: 7c 09 .dd2 $097c
8049: 67 09 .dd2 $0967
804b: 52 09 .dd2 $0952
804d: 3f 09 .dd2 $093f
804f: 2d 09 .dd2 $092d
8051: 1c 09 .dd2 $091c
8053: 0c 09 .dd2 $090c
8055: fd 08 .dd2 $08fd
8057: ee 08 .dd2 $08ee
8059: e1 08 .dd2 $08e1
805b: d4 08 .dd2 $08d4
805d: c8 08 .dd2 $08c8
805f: bd 08 .dd2 $08bd
8061: b2 08 .dd2 $08b2
8063: a8 08 .dd2 $08a8
8065: 9f 08 .dd2 $089f
8067: 96 08 .dd2 $0896
8069: 8d 08 .dd2 $088d
806b: 85 08 .dd2 $0885
806d: 7e 08 .dd2 $087e
806f: 76 08 .dd2 $0876
8071: 70 08 .dd2 $0870
8073: 69 08 .dd2 $0869
8075: 63 08 .dd2 $0863
8077: 5e 08 .dd2 $085e
8079: 58 08 .dd2 $0858
807b: 53 08 .dd2 $0853
807d: 4f 08 .dd2 $084f
807f: 4a 08 .dd2 $084a
8081: 46 08 .dd2 $0846
8083: 42 08 .dd2 $0842
8085: 3e 08 .dd2 $083e
8087: 3a 08 .dd2 $083a
8089: 37 08 .dd2 $0837
808b: 34 08 .dd2 $0834
808d: 31 08 .dd2 $0831
808f: 2e 08 .dd2 $082e
8091: 2b 08 .dd2 $082b
8093: 29 08 .dd2 $0829
8095: 27 08 .dd2 $0827
8097: 24 08 .dd2 $0824
8099: 22 08 .dd2 $0822
809b: 20 08 .dd2 $0820
809d: 1e 08 .dd2 $081e
809f: 1c 08 .dd2 $081c
80a1: 1b 08 .dd2 $081b
80a3: 01 02 03 04+ tbl_NoteLengths .bulk $01,$02,$03,$04,$06,$08,$0c,$10
80ab: 18 20 .bulk $18,$20
; (note names begin with 'A', but values start at 'C' and wrap around)
tbl_NoteNameToValue
80ad: 12 .dd1 18
80ae: 16 .dd1 22
80af: 00 .dd1 0
80b0: 04 .dd1 4
80b1: 08 .dd1 8
80b2: 0a .dd1 10
80b3: 0e .dd1 14
tbl_OctaveBaseNoteVals
80b4: 00 .dd1 0
80b5: 18 .dd1 24
80b6: 30 .dd1 48
80b7: 48 .dd1 72
80b8: 60 .dd1 96
80b9: 78 .dd1 120
vZpKeyRdCtr .var $28 {addr/1}
80ba: a9 00 _Reset lda #$00
80bc: 8d 00 20 sta PPUCTRL
80bf: 8d 01 20 sta PPUMASK ;turn off rendering
80c2: 20 2b b4 jsr WaitForVBlank
80c5: 20 2b b4 jsr WaitForVBlank
80c8: a9 4c lda #$4c ;JMP opcode
80ca: 85 ed sta zpNmiTrampoline
80cc: a9 71 lda #<NMI_DefaultHandler
80ce: 85 ee sta zpNmiTrampoline+1
80d0: a9 89 lda #>NMI_DefaultHandler
80d2: 85 ef sta zpNmiTrampoline+2 ;set default NMI handler to NMI_DefaultHandler
80d4: 20 ab b3 jsr InitPpuApu ;init various PPU, APU stuff
80d7: 20 a7 b4 jsr DoCLS_1 ;clear bg screen 1
80da: a9 20 lda #>VRAM_screen0
80dc: 85 4f sta zpPpuScreenBasePage ;send screen updates to screen 0
80de: a9 1e lda #30
80e0: 85 28 sta vZpKeyRdCtr
80e2: 20 45 b9 @keyRdLp jsr ReadKeyInsMacros ;read keyboard
80e5: f0 1b beq @haveKbd ;null char returned? -> done checking
80e7: c6 28 dec vZpKeyRdCtr ;else, either key being held, or kbd not connected
80e9: d0 f7 bne @keyRdLp ;-> loop until key-down reported for 30 checks
80eb: a9 05 lda #$05
80ed: 85 48 sta zpCH
80ef: a9 04 lda #$04
80f1: 85 49 sta zpCV ;LOCATE 5,4
80f3: a9 01 lda #DTU__KEYBOARD_WO_SETSUZOKU
80f5: 20 0d 82 jsr DirectTheUser
80f8: a9 06 lda #DTU__SHITE_KUDASAI
80fa: 20 0d 82 jsr DirectTheUser ;ask the user to please connect the keyboard
80fd: 20 45 b9 @waitKbdConn jsr ReadKeyInsMacros ;read keyboard
8100: d0 fb bne @waitKbdConn ;-> loop until null character returned (kbd connected)
8102: 20 f3 81 @haveKbd jsr CheckBackupSwitch ;is the backup switch on?
8105: 90 1a bcc @buSwOff ;off -> good to go!
8107: 20 ab b4 jsr DoCLS ;on: this locks the battery-backed RAM, which we don't want
810a: a9 01 lda #$01
810c: 85 48 sta zpCH
810e: a9 04 lda #$04
8110: 85 49 sta zpCV ;LOCATE 1,4
8112: a9 03 lda #DTU__BACKUP_SWITCH_WO
8114: 20 0d 82 jsr DirectTheUser
8117: a9 04 lda #DTU__OFF_NI_SHITE_KUDASAI
8119: 20 0d 82 jsr DirectTheUser ;ask the user to turn off the cart's backup switch
811c: 20 f3 81 @waitBuSwOff jsr CheckBackupSwitch ;test backup switch
811f: b0 fb bcs @waitBuSwOff ;-> loop until it's off
8121: a9 00 @buSwOff lda #$00
8123: 85 7c sta zpBgGotFlag ;clear zpBgGotFlag
8125: a5 9e lda zpBackupRstrd
8127: c9 59 cmp #BU_RESTORED_MARK ;did we already restore program from battery backup?
8129: f0 2f beq HotStart ;yes -> skip restoration, then
812b: ad 01 60 lda bbBackupAvail
812e: c9 4c cmp #BU_AVAIL_MARK ;does battery-backed RAM indicate a program is backed-up?
8130: d0 4e bne RstNoBackupReady ;no ->
8132: 20 a7 b4 jsr DoCLS_1 ;clear screen 1
8135: ad 04 60 lda bgGotFlag
8138: 85 7c sta zpBgGotFlag ;note whether we have BG data backed-up
813a: ad 02 60 lda bbTXTEND ;copy battery-backed TXTEND to the real one
813d: 85 07 sta zpTXTEND
813f: ad 03 60 lda bbTXTEND+1
8142: 85 08 sta zpTXTEND+1
8144: a9 06 lda #<basicProgram ;copy (hard-coded) basicProgram start to TXTTAB
8146: 85 05 sta zpTXTTAB
8148: a9 60 lda #>basicProgram
814a: 85 06 sta zpTXTTAB+1
814c: a9 00 lda #$00
814e: aa tax
814f: 9d 00 60 @delBuVarsLp sta $6000,x ;delete the backup vars, now that they're restored
8152: e8 inx ;(avoids them getting out of date, forces user to run BACKUP cmd again)
8153: e0 05 cpx #$05
8155: d0 f8 bne @delBuVarsLp ;-> loop until backup vars deleted (5 bytes)
8157: 20 a6 97 jsr Clear ;clear BASIC vars
815a: 20 62 86 HotStart jsr InitVars ;init zero-page vars
815d: 20 27 b3 jsr PrintHotStart ;print the "HOT START" message
8160: 20 6b b5 jsr EnsureSOL ;CRLF
8163: a5 7c lda zpBgGotFlag ;have we got bg data backed-up, too?
8165: f0 11 beq @basic ;no -> start up BASIC
8167: a9 7b lda #<str_BG ;yes, bg backed up: indicate it
8169: 85 52 sta zpOutputStr
816b: a9 81 lda #>str_BG
816d: 85 53 sta zpOutputStr+1
816f: 20 5a 89 jsr PrintString ;print "[BG]"
8172: 20 6b b5 jsr EnsureSOL ;CRLF
8175: 20 06 b2 jsr RestoreBackupBgData ;restore the backed-up bg data
8178: 4c 6a 82 @basic jmp CmdFn_END ;enter BASIC
817b: 5b 42 47 5d+ str_BG .zstr “[BG]”
• Clear variables
RstNoBackupReady
8180: 20 a7 b4 jsr DoCLS_1 ;clear screen 1
8183: a9 59 lda #BU_RESTORED_MARK
8185: 85 9e sta zpBackupRstrd ;no backup avail, so mark it "restored" to avoid future check
8187: a9 00 lda #$00
8189: 8d 04 60 sta bgGotFlag ;if no backup, no backed-up BG data
818c: 4c 5e 82 jmp RstNoBackupStartBasic
; SYSTEM command; also BACKUP.
CmdFn_SYSTEMorBACKUP
818f: a5 33 lda zpPpuMaskVal
8191: 29 ef and #$ef
8193: 85 33 sta zpPpuMaskVal ;disable sprites
8195: 8d 01 20 sta PPUMASK
8198: a9 4c lda #$4c ;JMP opcode?
819a: 8d 01 60 sta bbBackupAvail ;idk why we're JMPing to the null byte
819d: a5 07 lda zpTXTEND ; at the end of the BASIC prog, but anyway
819f: 8d 02 60 sta bbTXTEND
81a2: a5 08 lda zpTXTEND+1
81a4: 8d 03 60 sta $6003
81a7: 20 ab b4 jsr DoCLS ;clear the screen
81aa: a9 02 lda #$02
81ac: 85 48 sta zpCH
81ae: a9 04 lda #$04
81b0: 85 49 sta zpCV ;LOCATE 2,4
81b2: a9 03 lda #DTU__BACKUP_SWITCH_WO
81b4: 20 0d 82 jsr DirectTheUser
81b7: a9 00 lda #DTU__ON
81b9: 20 0d 82 jsr DirectTheUser
81bc: a9 05 lda #DTU__NI_SHITE_KUDASAI
81be: 20 0d 82 jsr DirectTheUser ;print "Please turn on the backup switch" (in Japanese)
81c1: 20 45 b9 @whileBuSwOff jsr ReadKeyInsMacros
81c4: c9 03 cmp #$03 ;check if STOP key is pressed
81c6: f0 92 beq HotStart ;pressed? -> BASIC hot start
81c8: 20 f3 81 jsr CheckBackupSwitch ;is backup switch on?
81cb: 90 f4 bcc @whileBuSwOff ;no -> loop until it is, or until STOP key pressed
81cd: 20 ab b4 jsr DoCLS ;clear screen
81d0: a9 05 lda #$05
81d2: 85 48 sta zpCH
81d4: a9 04 lda #$04
81d6: 85 49 sta zpCV ;LOCATE 5,4
81d8: a9 02 lda #DTU__DENGEN_WO
81da: 20 0d 82 jsr DirectTheUser
81dd: a9 04 lda #DTU__OFF_NI_SHITE_KUDASAI
81df: 20 0d 82 jsr DirectTheUser ;print "Please turn off power" (in Japanese)
81e2: 20 45 b9 @stopLp jsr ReadKeyInsMacros
81e5: c9 03 cmp #$03 ;STOP key pressed?
81e7: d0 f9 bne @stopLp ;no -> just keep checking for it
81e9: 4c ba 80 jmp _Reset ;yes -> cold-start BASIC
; Sets the current VRAM address from the A (high) and X (low) registers.
81ec: 8d 06 20 PpuGoToAX sta PPUADDR
81ef: 8e 06 20 stx PPUADDR
81f2: 60 rts
; Checks whether the physical battery-backup switch on the back of the Family
; BASIC cartridge is on or off. Carry is unset while switch is off, set if on
CheckBackupSwitch
81f3: ad 00 60 lda $6000
81f6: 48 pha ;save whatever's at location $6000 (cartridge RAM start)
81f7: a9 19 lda #$19
81f9: 8d 00 60 sta $6000 ;try writing to it
81fc: cd 00 60 cmp $6000 ;do we read back the value we just wrote?
81ff: f0 06 beq @swOff ;yes -> backup switch is off
8201: 68 pla ;no: backup switch is on
8202: 8d 00 60 sta $6000 ;restore original val at $6000
8205: 38 sec ;set carry (switch on)
8206: 60 rts ;and return
8207: 68 @swOff pla
8208: 8d 00 60 sta $6000 ;restore original val at $6000
820b: 18 clc ;clear carry (switch is off)
820c: 60 rts ;and return
; Tell the user to do something
; (turn backup switch on/off, power off, connect keyboard)
820d: 0a DirectTheUser asl A ;multiply message index by 2, for table indexing
820e: aa tax
820f: bd 50 82 lda tbl_DirectTheUserStrs,x ;load the message string
8212: 85 52 sta zpOutputStr
8214: bd 51 82 lda tbl_DirectTheUserStrs+1,x
8217: 85 53 sta zpOutputStr+1
8219: 4c 5a 89 jmp PrintString ;and output it
821c: 20 4f 4e 00 strON .zstr “ ON”
strKEYBOARD_WO_SETSUZOKU
8220: 66 2d aa 2d+ .bulk $66,$2d,$aa,$2d,$a5,$20,$8d,$20,$6d,$71,$a0,$67,$00
822d: a4 8c 9a 8c+ strDENGEN_WO .bulk $a4,$8c,$9a,$8c,$20,$8d,$00
strBACKUP_SWITCH_WO
8234: a6 96 67 60+ .bulk $a6,$96,$67,$60,$96,$ad,$20,$6c,$61,$96,$70,$20,$8d,$00
strOFF_NI_SHITE_KUDASAI
8242: 20 .dd1 $20 ;Falls thru to next string start
8243: 4f .dd1 $4f
8244: 46 .dd1 $46
8245: 46 .dd1 $46
strNI_SHITE_KUDASAI
8246: 20 75 .bulk $20,$75 ;Falls thru to next string start
strSHITE_KUDASAI
8248: 20 6b 72 67+ .bulk $20,$6b,$72,$67,$a1,$6a,$61,$00
tbl_DirectTheUserStrs
8250: 1c 82 .dd2 strON
8252: 20 82 .dd2 strKEYBOARD_WO_SETSUZOKU
8254: 2d 82 .dd2 strDENGEN_WO
8256: 34 82 .dd2 strBACKUP_SWITCH_WO
8258: 42 82 .dd2 strOFF_NI_SHITE_KUDASAI
825a: 46 82 .dd2 strNI_SHITE_KUDASAI
825c: 48 82 .dd2 strSHITE_KUDASAI
RstNoBackupStartBasic
825e: a2 ff ldx #$ff
8260: 9a txs ;initialize stack pointer register
8261: 20 62 86 jsr InitVars ;clear zero-page vars
8264: 20 bc 84 jsr ResetTxtVarPtrs ;initialize basic start/end (empty)
8267: 20 f7 b2 jsr PrintStartupText ;print BASIC startup text
826a: 20 c0 88 CmdFn_END jsr ClearStoppedFlag ;clear "stopped" flag
826d: 85 e7 sta zpErrHandStat ;store zero (EH_NO_HANDLER)
ResetAndEnterDirectMode
826f: a9 00 lda #$00
8271: 85 e2 sta zpAutoEnabled ;turn off auto-increment and erase its data
8273: 85 a2 sta zpAutoLNumIncr
8275: 85 a3 sta zpAutoLNumIncr+1
8277: a2 ff ldx #$ff ;clear the CPU stack
8279: 9a txs
827a: 8a txa ;and push #$FFFF (as a limit to NEXT/RETURN stack searches?)
827b: 48 pha
827c: 48 pha
827d: 20 66 b7 jsr DeactivateKanaMode ;ensure we're in ASCII typing mode
SaveStackAndDirectMode
8280: ba tsx
8281: 86 17 stx zpSavedStack
8283: 58 cli
; Loop over user input lines, obeying commands and entering numbered program
; code lines.
8284: a6 17 DirectModeLoop ldx zpSavedStack ;restore stack ptr from zpSavedStack
8286: 9a txs
8287: 20 6b b5 jsr EnsureSOL
828a: 20 ed b2 jsr PrintOK
828d: a5 24 lda zpStoppedFlag ;STOPped?
828f: f0 05 beq @skipPd ;no -> skip period
8291: a9 2e lda #‘.’ ;yes: print period after "OK", to indicate it was a STOP
8293: 20 75 b5 jsr PrintOneChar ; (and can be CONTinued)
8296: 20 6b b5 @skipPd jsr EnsureSOL ;another CRLF
8299: 20 cc 82 DML_skipOK jsr DirectMode_handleAuto ;auto-prefix line number, if active
829c: 20 04 bd jsr ReadLine ;read a line of the user's input
829f: c9 03 cmp #$03 ;STOP typed during line input?
82a1: f0 1e beq DML_cancelAuto ;yes -> ignore line and reprompt
82a3: 20 a4 8b jsr InitLineBufferScanning ;point TXTPTR at lineBuffer
82a6: 20 b2 90 jsr GetTxtChar
82a9: 20 bf 90 jsr TxtSkipSpaces
82ac: 29 ff and #$ff ;empty line?
82ae: f0 e9 beq DML_skipOK ;yes -> reprompt
82b0: 20 c8 90 jsr IsDigit ;start with a digit?
82b3: b0 09 bcs @directExec ;no -> directly execute commands
82b5: 20 e5 84 jsr ProcessNumberedLine ;yes -> handle the numbered line
82b8: 20 a6 97 jsr Clear ;clear variables (we may have just trampled variable space)
82bb: 4c 99 82 jmp DML_skipOK
82be: 4c 34 83 @directExec jmp ExecuteDirectModeLine
82c1: a5 e2 DML_cancelAuto lda zpAutoEnabled
82c3: f0 d4 beq DML_skipOK
82c5: a9 00 lda #$00
82c7: 85 e2 sta zpAutoEnabled
82c9: 4c 84 82 jmp DirectModeLoop
DirectMode_handleAuto
82cc: a5 e2 lda zpAutoEnabled ;AUTO active?
82ce: f0 55 beq rts_8325 ;no -> nothing to do; return
82d0: a5 a0 lda zpNxtAutoLNum ;copy the next auto linenum to zpFindLNum
82d2: 85 44 sta zpFindLNum
82d4: a5 a1 lda zpNxtAutoLNum+1
82d6: 85 45 sta zpFindLNum+1
82d8: 20 40 85 jsr FindLineNum ;and search for that line number
82db: d0 20 bne DML_noSuchLine ;no such line? -> don't need to handle it then
82dd: 20 30 94 jsr SaveTXTPTR ;save TXTPTR
82e0: a5 5b lda zpTxtLink
82e2: 85 09 sta zpTXTPTR ;set found line as new TXTPTR
82e4: a5 5c lda zpTxtLink+1
82e6: 85 0a sta zpTXTPTR+1
82e8: 20 ee 87 jsr PrintLineNumAndDetok ;print the line we found
82eb: 20 39 94 jsr RestoreTXTPTR ;restore saved TXTPTR
82ee: a6 49 ldx zpCV ;get CV (which is one past the just-printed line)
82f0: ca dex ;decrement (on the printed line)
82f1: ca dex ;decrement again (just above the printed line)
82f2: 20 37 b6 @findLineStart jsr DoesRowWrap ;is this a row that wrapped to next line?
82f5: f0 03 beq @found ;no -> found end of prev line
82f7: ca dex ;yes, wraps. Back up one and see if *that* wraps
82f8: 10 f8 bpl @findLineStart
82fa: e8 @found inx ;incr, from end of prev line, to start of our printed line
82fb: 86 49 stx zpCV ;and save as current row
82fd: a5 a0 DML_noSuchLine lda zpNxtAutoLNum ;zCH is guaranteed to be 0 already,
82ff: 85 28 sta zpWAccum ; since printed line ended with CRLF
8301: a5 a1 lda zpNxtAutoLNum+1
8303: 85 29 sta zpWAccum+1 ;copy auto linenum to WAccum
8305: 20 3e 91 jsr PosDecStrFromWord ;convert to decimal
8308: 20 d6 90 jsr FindFirstNonzeroDigit ;skip leading zeroes
830b: 20 e0 87 jsr QueueOutpNumStrTmp ;queue it for output
830e: a9 20 lda #$20 ;plus a SPACE character
8310: 20 4d 89 jsr QueueCharForOutput
8313: 20 5d 89 jsr PrintOutBuf ;print out linenum + space.
IncrementAutoLNum
8316: 18 clc ;finally, increment auto linenum for next time
8317: a5 a0 lda zpNxtAutoLNum
8319: 65 a2 adc zpAutoLNumIncr
831b: 85 a0 sta zpNxtAutoLNum
831d: a5 a1 lda zpNxtAutoLNum+1
831f: 65 a3 adc zpAutoLNumIncr+1
8321: b0 03 bcs DeactivateAutoAndError ;overflowed? -> deactivate AUTO and error.
8323: 85 a1 sta zpNxtAutoLNum+1
8325: 60 rts_8325 rts
DeactivateAutoAndError
8326: a9 00 lda #$00
8328: 85 e2 sta zpAutoEnabled
832a: 4c f2 8e jmp ErrorOverflow
; This routine used to flag that we are in direct mode, and there is no line
; number for the executing BASIC code.
StrikeRunLineNum
832d: a9 ff lda #$ff
832f: 85 0b sta zpRunLineNum
8331: 85 0c sta zpRunLineNum+1
8333: 60 rts
ExecuteDirectModeLine
8334: 20 5b 92 jsr Tokenize ; tokenize the line
8337: a9 89 lda #<dmFakeNextLine
8339: 85 0d sta zpRunTxtNxtLnk ;Set "next line link" to an arbitrary location that starts with zero (reads as end of program)
833b: a9 bb lda #>dmFakeNextLine
833d: 85 0e sta zpRunTxtNxtLnk+1
833f: a9 00 lda #<tokenizeBuffer ;point TXTPTR at tokenizeBuffer
8341: 85 09 sta zpTXTPTR ; so we execute from there
8343: a9 03 lda #>tokenizeBuffer
8345: 85 0a sta zpTXTPTR+1
8347: 20 2d 83 jsr StrikeRunLineNum
; From here on down, this routine is used both by ExecuteDirectModeLine
; (directly above), and also by a running basic program (for each command)
834a: 20 55 88 ExecuteCommand jsr StopIfStopKey
834d: 20 cd 8b jsr ExecCheckMem
8350: 20 bf 90 jsr TxtSkipSpaces
8353: 48 pha
8354: a5 09 lda zpTXTPTR ;save this spot as start of the current command
8356: 85 58 sta zpTxtThisCmd
8358: a5 0a lda zpTXTPTR+1
835a: 85 59 sta zpTxtThisCmd+1
835c: 20 8c 90 jsr TxtPtrIncr
835f: 68 pla ;got null char (end of line)?
8360: f0 52 beq skipToNextLine_83B4 ;yes -> skip to next
8362: c9 3a cmp #‘:’ ;got colon?
8364: f0 e4 beq ExecuteCommand ;yes -> reset zpTxtThisCmd to here
8366: 48 pha
8367: 20 fd 83 jsr MaybePrintTrace ;print line number trace, if TRON is active
836a: 68 pla
836b: c9 27 cmp #‘'’ ;got apostrophe (REM)?
836d: f0 45 beq skipToNextLine_83B4 ;yes -> skip line
836f: c9 80 cmp #$80 ;got a command token?
8371: b0 44 bcs execCmdByTok_83B7 ;yes -> execute it
8373: 20 c4 a7 jsr IsAlpha ;got an alphabetic character?
8376: b0 39 bcs errSyntax_83B1 ;no -> error
8378: 48 pha
8379: 20 a6 90 jsr TxtPtrDecr ;backup TXTPTR to the start of the var name
837c: 68 pla
837d: 20 76 a5 jsr EvalVarNameOrSynErr ;find or create the variable for this name
8380: a5 90 lda zpVarType ;push the var type
8382: 48 pha
8383: a5 8c lda zpVarData ;push the var data ptr
8385: 48 pha
8386: a5 8d lda zpVarData+1
8388: 48 pha
8389: 20 d9 86 jsr SkipEqualsOrErr ;demand a '=' here
838c: 20 83 a2 jsr FrmEval ;evaluate the value
838f: 68 pla
8390: 85 8d sta zpVarData+1 ;pull var data ptr and restore
8392: 68 pla
8393: 85 8c sta zpVarData
8395: 68 pla
8396: 85 90 sta zpVarType ;pull var type and restore
8398: a0 00 ldy #$00
839a: 29 0f and #$0f
839c: c9 03 cmp #TYP_STR ;is var base type a string?
839e: f0 1f beq ExecCmd_SetStringVar ;yes -> handle as string
83a0: 20 0d aa jsr ErrorUnlessInteger ;ensure value for int is int!
83a3: ad 00 04 lda exprParseStk ;copy expression result to var data
83a6: 91 8c sta (zpVarData),y
83a8: ad 01 04 lda exprParseStk+1
83ab: c8 iny
83ac: 91 8c sta (zpVarData),y
83ae: 4c 4a 83 jmp ExecuteCommand ;continue executing successive commands
83b1: 4c 91 84 errSyntax_83B1 jmp ErrorSyntax
skipToNextLine_83B4
83b4: 4c 69 84 jmp ExecSkipToNextLine
execCmdByTok_83B7
83b7: 4c 29 84 jmp ExecuteCommandByToken
ErrorStringTooLong
83ba: a9 0c lda #err_ST
83bc: 4c 37 b2 jmp PrintErrorAndStop
ExecCmd_SetStringVar
83bf: 20 10 aa jsr ErrorUnlessString ;ensure value for string var is string!
83c2: 20 cb 83 jsr CpyExprStrToStrVar ;copy the string value to our variable data space
83c5: 4c 4a 83 jmp ExecuteCommand ;continue executing successive commands
83c8: 91 8c @setEmptyAndRts sta (zpVarData),y
83ca: 60 rts
CpyExprStrToStrVar
83cb: a0 00 ldy #$00
83cd: ad 00 04 lda exprParseStk ;examine string length
83d0: f0 f6 beq @setEmptyAndRts ;zero? -> just copy the length only
83d2: c9 20 cmp #32 ;len >= 32?
83d4: b0 e4 bcs ErrorStringTooLong ;yes -> error, too long
83d6: 91 8c sta (zpVarData),y ;save length to variable
83d8: ad 01 04 lda exprParseStk+1 ;copy str content ptr into zero-page pointers for indexing
83db: 85 19 sta zpMisc
83dd: ad 02 04 lda exprParseStk+2
83e0: 85 1a sta zpMisc+1
83e2: c8 iny
83e3: 98 tya
83e4: 18 clc
83e5: 65 8c adc zpVarData
83e7: 85 8c sta zpVarData
83e9: a9 00 lda #$00
83eb: 65 8d adc zpVarData+1
83ed: 85 8d sta zpVarData+1 ;set copy dest ptr to one past zpVarData (i.e. contents, not strlen)
83ef: ae 00 04 ldx exprParseStk
83f2: a0 00 ldy #$00
83f4: b1 19 @cpyLp lda (zpMisc),y
83f6: 91 8c sta (zpVarData),y
83f8: c8 iny
83f9: ca dex
83fa: d0 f8 bne @cpyLp
83fc: 60 rts_83FC rts
; Print line number trace, if active
83fd: a5 97 MaybePrintTrace lda zpTraceOnFlag ;return if trace is off, or if we're in direct mode
83ff: f0 fb beq rts_83FC
8401: a9 ff lda #$ff
8403: c5 0b cmp zpRunLineNum
8405: d0 04 bne @doTrace
8407: c5 0c cmp zpRunLineNum+1
8409: f0 f1 beq rts_83FC
840b: a9 23 @doTrace lda #‘#’
840d: 20 4d 89 jsr QueueCharForOutput ;queue a '#'
8410: a5 0b lda zpRunLineNum
8412: 85 28 sta zpWAccum
8414: a5 0c lda zpRunLineNum+1
8416: 85 29 sta zpWAccum+1
8418: 20 3e 91 jsr PosDecStrFromWord ;queue the line number
841b: 20 d6 90 jsr FindFirstNonzeroDigit
841e: 20 e0 87 jsr QueueOutpNumStrTmp
8421: a9 20 lda #$20 ;queue a SPACE
8423: 20 4d 89 jsr QueueCharForOutput
8426: 4c 5d 89 jmp PrintOutBuf ;now print the queue
ExecuteCommandByToken
8429: 38 sec
842a: e9 80 sbc #$80 ;remove high bit from token
842c: c9 44 cmp #$44 ;was it a function token, rather than command token?
842e: b0 61 bcs ErrorSyntax ;yes -> syntax error
8430: 0a asl A ;double the value (for jump table index)
8431: aa tax
8432: bd bf ce lda jtbl_Commands,x ;put address of command handler into zpMisc
8435: 85 19 sta zpMisc
8437: bd c0 ce lda jtbl_Commands+1,x
843a: 85 1a sta zpMisc+1
843c: ba tsx
843d: 86 17 stx zpSavedStack ;save stack pointer
843f: 20 9c 84 jsr IsEndOfCmd ;load next non-space char in A, and set Z iff end of command
8442: 20 3e a3 jsr JmpIndirectZpMisc ;execute the command
8445: 4c 4a 83 jmp ExecuteCommand ;continue executing successive commands
; Enters DirectModeLoop. First checks if we have a resumable error AND were
; executing a direct command, and gives a "NR" (No Resume) error if so.
8448: a5 e7 FinishExecution lda zpErrHandStat
844a: c9 02 cmp #EH_IN_HANDLER
844c: f0 03 beq @errHandNeverResmd
@enterDirectMode
844e: 4c 80 82 jmp SaveStackAndDirectMode
@errHandNeverResmd
8451: 20 59 84 jsr @chkDirect ;are we in direct mode (no line num)?
8454: b0 f8 bcs @enterDirectMode ;yes -> just go to direct mode prompt
8456: 4c 1b 95 jmp ErrorNoResume ;no: error, bc we handled an error but didn't resume after
8459: a9 ff @chkDirect lda #$ff
845b: c5 0b cmp zpRunLineNum
845d: d0 06 bne @notDirect
845f: c5 0c cmp zpRunLineNum+1
8461: d0 02 bne @notDirect
8463: 38 sec
8464: 60 rts
8465: 18 @notDirect clc
8466: 60 rts
8467: 68 CmdFn_DATAorREM pla ;pull return addr (we'll jump directly)
8468: 68 pla
; Also used by apostrophe ('), and to skip the remainder of a false IF statement
ExecSkipToNextLine
8469: a5 0d lda zpRunTxtNxtLnk ;replace zpTXTPTR with ptr to start of next line
846b: 85 09 sta zpTXTPTR
846d: a5 0e lda zpRunTxtNxtLnk+1
846f: 85 0a sta zpTXTPTR+1
; Also used by RUN. Update line linkage info before running a line
8471: 20 b2 90 ExecuteLine jsr GetTxtChar ;get offset from here to start of next line
8474: f0 d2 beq FinishExecution ;zero (last line of prog)? -> completed execution
8476: 18 clc
8477: 65 09 adc zpTXTPTR ;add offset to current TXTPTR
8479: 85 0d sta zpRunTxtNxtLnk ;and store in zpRunTxtNxtLnk as ptr to start of next line
847b: a9 00 lda #$00
847d: 65 0a adc zpTXTPTR+1
847f: 85 0e sta zpRunTxtNxtLnk+1
8481: c8 iny
8482: b1 09 lda (zpTXTPTR),y ;save current line's line number
8484: 85 0b sta zpRunLineNum
8486: c8 iny
8487: b1 09 lda (zpTXTPTR),y
8489: 85 0c sta zpRunLineNum+1
848b: 20 10 9f jsr TxtPtrAdvThree ;skip TXTPTR past offset and line number
848e: 4c 4a 83 jmp ExecuteCommand ;and execute the first command
; Emit a syntax error (code [SN]) and stop execution. In addition to being
; called directly from various places, this function is also in the jump table
; for the keywords THEN, TO, STEP, and OFF, since these are all marker tokens
; and not real commands. (ON can start legit commands, so it does not go here.)
8491: a9 01 ErrorSyntax lda #err_SN
8493: ae ldx ▼ $04a9
ErrorIllegalValue
8494: a9 04 lda #err_IL
8496: 4c 37 b2 jmp PrintErrorAndStop
AdvThenIsEndOfCmd
8499: 20 8c 90 jsr TxtPtrIncr
; Z is set, if and only if we're at the end of the command (no more args)
849c: 20 bf 90 IsEndOfCmd jsr TxtSkipSpaces
849f: 29 ff and #$ff
84a1: f0 02 beq @rts
84a3: c9 3a cmp #‘:’
84a5: 60 @rts rts
ClearStoppedTraceAndError
84a6: 20 c0 88 jsr ClearStoppedFlag
84a9: 85 97 sta zpTraceOnFlag ;storing zero
84ab: 85 e7 ClearErrorFlags sta zpErrHandStat ;clear error/resume flags
84ad: 85 b5 sta zpErrCode
84af: 85 b6 sta zpUnusedErrVar
84b1: 85 b7 sta zpErrLineNum
84b3: 85 b8 sta zpErrLineNum+1
84b5: 60 rts
84b6: 20 bc 84 CmdFn_NEW jsr ResetTxtVarPtrs ;reset pointers to BASIC text and vars
84b9: 4c 6f 82 jmp ResetAndEnterDirectMode ;reset auto-incr and enter direct-mode prompt
84bc: 20 a6 84 ResetTxtVarPtrs jsr ClearStoppedTraceAndError
84bf: 8d 06 60 sta basicProgram ;clear offset and line number
84c2: 8d 07 60 sta basicProgram+1
84c5: 8d 08 60 sta basicProgram+2
84c8: a2 06 ldx #$06
84ca: 86 05 stx zpTXTTAB ;set TXTTAB to $6006
84cc: e8 inx
84cd: 86 07 stx zpTXTEND ;set TXTEND one byte past that ($6007)
84cf: e8 inx
84d0: 86 1d stx zpVARTAB ;set VARTAB one past that ($6008)
84d2: e8 inx
84d3: 86 1f stx zpVAREND ;and VAREND one past VARTAB ($6009)
84d5: e8 inx
84d6: 86 21 stx zpGpStrIdx ;;set $21,$22 to byte after that (NEVER USED!)
84d8: a9 60 lda #$60
84da: 85 06 sta zpTXTTAB+1 ;set high bytes (#$60) for all of the above
84dc: 85 08 sta zpTXTEND+1
84de: 85 1e sta zpVARTAB+1
84e0: 85 20 sta zpVAREND+1
84e2: 85 22 sta zp22
84e4: 60 rts
; Handles a numbered line. If the line consists only of a number, then delete
; the indicated line; otherwise, tokenize the line and insert it where it
; belongs.
ProcessNumberedLine
84e5: 20 c0 88 jsr ClearStoppedFlag ;we're about to mess with line ordering; disallow CONT
84e8: 20 2d 83 jsr StrikeRunLineNum
84eb: 20 fd 90 jsr ParseDecimalWordFromTxt ;get the typed line number
84ee: a9 ff lda #$ff
84f0: c5 28 cmp zpWAccum ;line num = 65535?
84f2: d0 06 bne @proceed ;no
84f4: c5 29 cmp zpWAccum+1 ;maybe... check high byte too
84f6: d0 02 bne @proceed ;less than 65535
84f8: f0 9a beq ErrorIllegalValue ; = 65535, not allowed
84fa: a5 28 @proceed lda zpWAccum
84fc: 85 a0 sta zpNxtAutoLNum ;record line number for AUTO
84fe: a5 29 lda zpWAccum+1
8500: 85 a1 sta zpNxtAutoLNum+1
8502: 20 16 83 jsr IncrementAutoLNum
8505: 20 b2 90 jsr GetTxtChar ;get a char
8508: c9 20 cmp #$20 ;is it a space?
850a: d0 03 bne @notSp ;no -> stick with this char
850c: 20 98 90 jsr TxtPtrIncrAndGetChar ;yes: get the next char
850f: 29 ff @notSp and #$ff ;just a line number, no content?
8511: f0 61 beq TxtDeleteLineWAccum ;yes -> just delete the line if it exists
8513: a5 28 lda zpWAccum ;no: put line number in zpFindLNum
8515: 85 44 sta zpFindLNum
8517: a5 29 lda zpWAccum+1
8519: 85 45 sta zpFindLNum+1
851b: 20 5b 92 jsr Tokenize ;and tokenize the contents via zpTokenizePtr into tokenizeBuffer
; Record tokenized length in zpWAccum
851e: 38 sec
851f: a5 4d lda zpTokenizePtr
8521: e9 00 sbc #<tokenizeBuffer ;subtracting #$00... silly!
8523: 85 28 sta zpWAccum
8525: a5 4e lda zpTokenizePtr+1
8527: e9 03 sbc #>tokenizeBuffer
8529: 85 29 sta zpWAccum+1
;
852b: a9 02 lda #$02
852d: 18 clc
852e: 65 28 adc zpWAccum
8530: 85 5a sta zpNewLineOffset
8532: a5 44 lda zpFindLNum
8534: 85 0f sta zpInsLineNum
8536: a5 45 lda zpFindLNum+1
8538: 85 10 sta zpInsLineNum+1
853a: 20 7c 85 jsr TxtDeleteLine ;delete any existing line w same #
853d: 4c be 85 jmp TxtInsertLine ;then insert ours
; Find the position for the line number zpFindLNum within the BASIC program,
; leaving zpTxtLink pointing at the closest less-than-or-equal numbered line.
; Z flag is set iff an exact line-number match was found.
vZpInsDelNxtLnk .var $0b {addr/2} ;link to next line after the one being inserted/deleted
8540: a5 05 FindLineNum lda zpTXTTAB ;Point zpTxtLink at program start
8542: 85 5b sta zpTxtLink
8544: a5 06 lda zpTXTTAB+1
8546: 85 5c sta zpTxtLink+1
8548: a0 00 FLN_nextLine ldy #$00
854a: b1 5b lda (zpTxtLink),y ;read offset to next ln. != 0?
854c: d0 03 bne @notDone ;not zero -> continue to process
854e: a9 ff lda #$ff ;zero: out of lines. Indicate not exact, and return.
8550: 60 rts_8550 rts
8551: a0 02 @notDone ldy #$02
8553: a5 45 lda zpFindLNum+1
8555: d1 5b cmp (zpTxtLink),y ;compare line num high bytes
8557: d0 07 bne @hiDiff ;high bytes differ -> check how
8559: 88 dey ;high bytes same, check low
855a: a5 44 lda zpFindLNum
855c: d1 5b cmp (zpTxtLink),y
855e: f0 f0 beq rts_8550 ;line numbers equal -> return
8560: 90 ee @hiDiff bcc rts_8550 ;sought line # < cur line #? -> also return
; Fast-forward to next BASIC line
8562: a0 00 ldy #$00
8564: 18 clc
8565: b1 5b lda (zpTxtLink),y ;get offset to next line
8567: 65 5b adc zpTxtLink ;add to pointer for current line
8569: 85 5b sta zpTxtLink ;save as new pointer
856b: a5 5c lda zpTxtLink+1 ;(handle high bytes too)
856d: 69 00 adc #$00
856f: 85 5c sta zpTxtLink+1
8571: 4c 48 85 jmp FLN_nextLine ;-> loop back around and check the new line pointer
; Delete designated line of BASIC code
TxtDeleteLineWAccum
8574: a5 28 lda zpWAccum
8576: 85 44 sta zpFindLNum
8578: a5 29 lda zpWAccum+1
857a: 85 45 sta zpFindLNum+1
857c: 20 40 85 TxtDeleteLine jsr FindLineNum ;found line number?
857f: d0 cf bne rts_8550 ;no -> nothing to do
TxtDeleteFoundLine
8581: a0 00 ldy #$00
8583: b1 5b lda (zpTxtLink),y ;get offset to following line
8585: 85 3c sta zpTmp3C ;record length of line to delete
8587: 18 clc
8588: 65 5b adc zpTxtLink ;add to line link to get start of next line
858a: 85 0b sta vZpInsDelNxtLnk
858c: a5 5c lda zpTxtLink+1
858e: 69 00 adc #$00
8590: 85 0c sta vZpInsDelNxtLnk+1
; Starting from the next line, copy back over the deleted line, including
; offsets, moving all forward lines back by the amount of space this line used
; to occupy.
; Not a terribly fast copy: checks source pointer against program end on _every_
; byte written.
8592: a0 00 CopyLinesBack ldy #$00
8594: b1 0b lda (vZpInsDelNxtLnk),y
8596: 91 5b sta (zpTxtLink),y
8598: e6 0b inc vZpInsDelNxtLnk ;increment copy source
859a: d0 02 bne @noHi
859c: e6 0c inc vZpInsDelNxtLnk+1
859e: e6 5b @noHi inc zpTxtLink ;increment copy dest
85a0: d0 02 bne @noHi2
85a2: e6 5c inc zpTxtLink+1
85a4: a5 5c @noHi2 lda zpTxtLink+1
85a6: c5 08 cmp zpTXTEND+1 ;have we reached the program end?
85a8: d0 e8 bne CopyLinesBack ;no -> copy next byte
85aa: a5 5b lda zpTxtLink ;maybe: check low byte, too
85ac: c5 07 cmp zpTXTEND
85ae: d0 e2 bne CopyLinesBack ;not the end-> copy next byte
85b0: 38 sec ;source ptr reached end; move end back to dest ptr
85b1: a5 07 lda zpTXTEND
85b3: e5 3c sbc zpTmp3C ;subtract length of deleted line from prog end
85b5: 85 07 sta zpTXTEND
85b7: a5 08 lda zpTXTEND+1
85b9: e9 00 sbc #$00
85bb: 85 08 sta zpTXTEND+1
85bd: 60 rts
; Make room for a newly-tokenized line to be inserted into the program... then
; insert it!
85be: a5 07 TxtInsertLine lda zpTXTEND ;get the current program end (low byte)
85c0: 18 clc
85c1: 65 5a adc zpNewLineOffset ;add the length of the new line (including header)
85c3: 85 28 sta zpWAccum ;save in WAccum
85c5: a5 08 lda zpTXTEND+1 ;add any carry to high byte
85c7: 69 00 adc #$00
85c9: 85 29 sta zpWAccum+1 ;save that too
85cb: 20 b6 8b jsr CheckFreeSpaceOrError ;ensure we have space for it
85ce: 20 40 85 jsr FindLineNum ;find where this line goes
85d1: a5 07 lda zpTXTEND ;get current program end (low byte)
85d3: 85 0b sta vZpInsDelNxtLnk ;save it
85d5: 18 clc
85d6: 65 5a adc zpNewLineOffset ;add length of the new line to it
85d8: 85 19 sta zpMisc ;and save in zpMisc
85da: a5 08 lda zpTXTEND+1 ;get program end (high byte)
85dc: 85 0c sta vZpInsDelNxtLnk+1 ;save it
85de: 69 00 adc #$00 ;add the carry from adding new line's len
85e0: 85 1a sta zpMisc+1 ;save in zpMisc high byte
85e2: a0 00 @backCpyLp ldy #$00 ;starting with the byte past the end of final line
85e4: b1 0b lda (vZpInsDelNxtLnk),y ; and its terminator, copy that byte from the old location,
85e6: 91 19 sta (zpMisc),y ; new-line-length bytes forward to new loc
85e8: 20 4a 86 jsr TIL_DecrOldLocPtr ;--oldPtr
85eb: 20 56 86 jsr TIL_DecrNewLocPtr ;--newPtr
85ee: a5 5b lda zpTxtLink
85f0: c5 0b cmp vZpInsDelNxtLnk ;have we reached the spot we'll insert the line at?
85f2: d0 ee bne @backCpyLp ;no -> loop, copy, decrement, until we reach it
85f4: a5 5c lda zpTxtLink+1 ;check the high byte too
85f6: c5 0c cmp vZpInsDelNxtLnk+1 ;high bytes match?
85f8: d0 e8 bne @backCpyLp ;no -> not quite there yet, keep copying
85fa: b1 0b lda (vZpInsDelNxtLnk),y ;yes, made it. Copy this last byte forward
85fc: 91 19 sta (zpMisc),y
85fe: a0 00 ldy #$00
8600: a5 5a lda zpNewLineOffset
8602: 91 5b sta (zpTxtLink),y ;now copy the new line's length as the offset to next line
8604: c8 iny
8605: a5 0f lda zpInsLineNum
8607: 91 5b sta (zpTxtLink),y ;and copy its line number
8609: c8 iny
860a: a5 10 lda zpInsLineNum+1
860c: 91 5b sta (zpTxtLink),y ;(high byte, too)
860e: c8 iny
860f: 18 clc
8610: a9 03 lda #$03
8612: 65 5b adc zpTxtLink
8614: 85 5b sta zpTxtLink ;advance write ptr by 3 (skipping next-line offset, and line #)
8616: a9 00 lda #$00
8618: 65 5c adc zpTxtLink+1
861a: 85 5c sta zpTxtLink+1 ;and handle any carry for the ptr's high byte
861c: a5 5a lda zpNewLineOffset
861e: 38 sec
861f: e9 03 sbc #$03 ;adjust the offset value from here to next line, by same amount
8621: aa tax ;and store in X register
8622: a0 00 ldy #$00
8624: a9 00 lda #<tokenizeBuffer
8626: 85 4d sta zpTokenizePtr ;point zpTokenizePtr at the tokenizeBuffer
8628: a9 03 lda #>tokenizeBuffer
862a: 85 4e sta zpTokenizePtr+1
862c: b1 4d @lineCpyLp lda (zpTokenizePtr),y ;copy from tokenizeBuffer (new line)
862e: 91 5b sta (zpTxtLink),y ; to new line location
8630: c8 iny ;incr copy index
8631: ca dex ;decr remaining chars
8632: d0 f8 bne @lineCpyLp ;chars left? -> keep looping/copying
8634: 18 clc
8635: a5 07 lda zpTXTEND
8637: 65 5a adc zpNewLineOffset ;add the new line length to program end,
8639: 85 07 sta zpTXTEND ; and save new value
863b: a9 00 lda #$00
863d: 65 08 adc zpTXTEND+1
863f: 85 08 sta zpTXTEND+1 ;handle the carry, too
8641: a0 00 ldy #$00
8643: 98 tya
8644: 91 07 sta (zpTXTEND),y ;write final null byte after program end. And another after that.
8646: c8 iny
8647: 91 07 sta (zpTXTEND),y
8649: 60 rts ;-> done
TIL_DecrOldLocPtr
864a: a5 0b lda vZpInsDelNxtLnk ;get low byte of old-loc ptr
864c: f0 03 beq @oldLowZero ;is it zero? -> decrement both high and low bytes
864e: c6 0b dec vZpInsDelNxtLnk ;no, decrement it
8650: 60 rts ;-> and return
8651: c6 0b @oldLowZero dec vZpInsDelNxtLnk ;decr low
8653: c6 0c dec vZpInsDelNxtLnk+1 ;decr high
8655: 60 rts ;-> return
TIL_DecrNewLocPtr
8656: a5 19 lda zpMisc ;get low byte of new-loc ptr
8658: f0 03 beq @newLowZero ;zero? -> decrement bth high and low bytes
865a: c6 19 dec zpMisc ;else just decr low byte
865c: 60 rts ;-> and return
865d: c6 19 @newLowZero dec zpMisc ;decr low
865f: c6 1a dec zpMisc+1 ;decr high
8661: 60 rts ;-> return
8662: a9 04 InitVars lda #$04
8664: 85 b9 sta zpTempo ;initialize tempo for each musical voice
8666: 85 ba sta zpTempo+1
8668: 85 bb sta zpTempo+2
866a: a9 08 lda #$08
866c: 85 c3 sta zpNoteLen ;set a default note length for each musical voice
866e: 85 c4 sta zpNoteLen+1
8670: 85 c5 sta zpNoteLen+2
8672: a9 03 lda #$03
8674: 85 bf sta zpOctave ;set a default octave for each musical voice
8676: 85 c0 sta zpOctave+1 ; (dotted half-note, according to manual)
8678: 85 c1 sta zpOctave+2
867a: a9 0f lda #$0f
867c: 85 bc sta zpVolume ;set default volume (max) for each musical voice
867e: 85 bd sta zpVolume+1
8680: 85 be sta zpVolume+2
8682: a9 30 lda #$30
8684: 85 cf sta zpEnvelope ;turn off envelope for each musical voice
8686: 85 d0 sta zpEnvelope+1
8688: 85 d1 sta zpEnvelope+2
868a: a9 02 lda #$02 ;set duty cycle (50%) for each musical voice
868c: 85 d2 sta zpDuty
868e: 85 d3 sta zpDuty+1
8690: 85 d4 sta zpDuty+2
8692: 20 a6 84 jsr ClearStoppedTraceAndError ;clear stop/trace/error flags
8695: 85 4c sta zpInsertMode ;clear auto-insert mode
8697: 85 57 sta zpRandom+1
8699: a9 ff lda #<memoryTop
869b: 85 03 sta zpFRETOP ;reset FRETOP
869d: a9 03 lda #$03
869f: 85 56 sta zpRandom ;reset RAND PRNG to start at #$0003
86a1: 85 e3 sta zpKeyClickFlg ;turn keyclicks on
86a3: a9 6f lda #>memoryTop
86a5: 85 04 sta zpFRETOP+1
86a7: 20 e8 ae jsr ClearOam
86aa: 20 94 b1 jsr ClearSpriteData
86ad: 4c ab b3 jmp InitPpuApu
• Clear variables
86b0: 20 a6 97 CmdFn_RUN jsr Clear ;flush away any variables
86b3: 20 c0 88 jsr ClearStoppedFlag ;clear various flags
86b6: 20 ab 84 jsr ClearErrorFlags
86b9: 20 94 b1 jsr ClearSpriteData
86bc: 20 e8 ae jsr ClearOam
86bf: 20 9c 84 jsr IsEndOfCmd ;do we have an arg?
86c2: d0 12 bne gotoCmd_86D6 ;yes -> GOTO that line
86c4: a2 00 ldx #$00 ;reset the stack pointer
86c6: 9a txs
86c7: a9 ff lda #$ff
86c9: 48 pha ;indicate limit of NEXT, RETURN stack searches?
86ca: 48 pha
86cb: a5 05 lda zpTXTTAB ;set TXTPTR to start of program
86cd: 85 09 sta zpTXTPTR
86cf: a5 06 lda zpTXTTAB+1
86d1: 85 0a sta zpTXTPTR+1
86d3: 4c 71 84 jmp ExecuteLine ;-> and execute first line
86d6: 4c c6 96 gotoCmd_86D6 jmp CmdFn_GOTO
86d9: 20 bf 90 SkipEqualsOrErr jsr TxtSkipSpaces
86dc: c9 f6 cmp #tok_OP_EQ
86de: d0 03 bne errSyn_86E3
86e0: 4c 8c 90 jmp TxtPtrIncr
86e3: 4c 91 84 errSyn_86E3 jmp ErrorSyntax
; The two BASIC commands that accept line number ranges as arguments - LIST, and
; DELETE - use this routine to read their parameters
ReadLineNumRange
86e6: 08 php ;push processor flags
86e7: a2 00 ldx #$00
86e9: 86 86 stx zpLNumStart
86eb: 86 87 stx zpLNumStart+1
86ed: ca dex
86ee: 86 88 stx zpLNumEnd
86f0: 86 89 stx zpLNumEnd+1
86f2: 28 plp ;pop processor flags
86f3: f0 62 beq rts_8757 ;no args? -> nothing to do, exit
86f5: c9 2c cmp #‘,’
86f7: f0 32 beq RdLNRng_secondArg
86f9: c9 2d cmp #‘-’
86fb: f0 2e beq RdLNRng_secondArg ;comma or dash? -> handle second arg
86fd: c9 2e cmp #‘.’ ;period?
86ff: d0 0b bne RdLNRng_firstArg ;no -> handle first arg
8701: a5 11 lda zpContRunLineNum ;period found: use STOPped line
8703: 85 86 sta zpLNumStart ; as the start
8705: 85 88 sta zpLNumEnd ; and end (for now), of the range
8707: a5 12 lda zpContRunLineNum+1 ;high byte
8709: 4c 1a 87 jmp RdLNRng_period ;-> set high bytes, and accept possible 2nd arg
RdLNRng_firstArg
870c: c9 0b cmp #tok_LNUM_AFTER ;line num prefix token?
870e: d0 d3 bne errSyn_86E3 ;no -> error
8710: 20 98 90 jsr TxtPtrIncrAndGetChar ;read first byte of line number
8713: 85 86 sta zpLNumStart ;use as both start and end of line range
8715: 85 88 sta zpLNumEnd
8717: 20 98 90 jsr TxtPtrIncrAndGetChar ;get high byte of line-num first argument
871a: 85 87 RdLNRng_period sta zpLNumStart+1 ;and set start+end high bytes too
871c: 85 89 sta zpLNumEnd+1
871e: 20 99 84 jsr AdvThenIsEndOfCmd ;get next byte. Is it the end of the command?
8721: f0 34 beq rts_8757 ;yes -> done parsing args
8723: c9 2c cmp #‘,’ ;else, got a comma or dash?
8725: f0 04 beq RdLNRng_secondArg ;yes -> process 2nd arg
8727: c9 2d cmp #‘-’
8729: d0 b8 bne errSyn_86E3 ;else -> error
RdLNRng_secondArg
872b: 20 99 84 jsr AdvThenIsEndOfCmd ;end of cmd after comma-or-dash?
872e: f0 21 beq RdLNRng_progEndIsEnd ;yes -> set end of range to end of program
8730: c9 2e cmp #‘.’ ;else, got a period as 2nd arg?
8732: d0 09 bne @lnum ;no -> expect line number
8734: a5 11 lda zpContRunLineNum ;got a period. Set CONT line as end of range.
8736: 85 88 sta zpLNumEnd
8738: a5 12 lda zpContRunLineNum+1
873a: 4c 49 87 jmp @hiByt ;-> and finish up args processing
873d: c9 0b @lnum cmp #tok_LNUM_AFTER ;got the line-num token prefix?
873f: d0 a2 bne errSyn_86E3 ;no -> error
8741: 20 98 90 jsr TxtPtrIncrAndGetChar ;get line-number 2nd arg
8744: 85 88 sta zpLNumEnd ;and set as end-of-range
8746: 20 98 90 jsr TxtPtrIncrAndGetChar
8749: 85 89 @hiByt sta zpLNumEnd+1
874b: 20 8c 90 jsr TxtPtrIncr
874e: 4c 57 87 jmp rts_8757 ;done processing args
RdLNRng_progEndIsEnd
8751: a9 ff lda #$ff ;set end of line-num range to maximum value (end of program)
8753: 85 88 sta zpLNumEnd
8755: 85 89 sta zpLNumEnd+1
8757: 60 rts_8757 rts ;done processing args
8758: f0 89 CmdFn_DELETE beq errSyn_86E3 ;no arg? -> error
875a: a2 ff ldx #$ff
875c: 86 51 stx zpListOrDelete ;set DELETE mode
875e: 20 e6 86 jsr ReadLineNumRange
8761: 20 6e 87 jsr DoListOrDelete ;Perform the DELETE
8764: 4c a6 97 jmp Clear ;-> Clear variables, and end command.
8767: 20 e6 86 CmdFn_LIST jsr ReadLineNumRange
876a: a9 00 lda #$00
876c: 85 51 sta zpListOrDelete ;set LIST mode
; The two commands that both operate on line-number ranges - LIST, and DELETE -
; both use this routine to perform their actions. The value of the variable
; zpListOrDelete ($51) is used to determine what actual action to perform on
; each line within the range.
876e: 20 30 94 DoListOrDelete jsr SaveTXTPTR ;save the original TXTPTR away
8771: a5 86 lda zpLNumStart ;save "start" line num as line to find
8773: 85 44 sta zpFindLNum
8775: a5 87 lda zpLNumStart+1
8777: 85 45 sta zpFindLNum+1
8779: 20 40 85 jsr FindLineNum ;...and then find it.
877c: a0 00 ldy #$00
877e: b1 5b lda (zpTxtLink),y ;read offset to next line num. Are we at end of prog?
8780: f0 5b beq LoD_RestoreTxtPtrAndReturn ;yes -> restore TXTPTR and exit LIST or DELETE.
8782: a5 5b lda zpTxtLink
8784: 85 09 sta zpTXTPTR ;set TXTPTR start of found line linkage;
8786: a5 5c lda zpTxtLink+1 ; we'll use it to scan through the range
8788: 85 0a sta zpTXTPTR+1
878a: a0 00 LoD_mainLp ldy #$00
878c: b1 09 lda (zpTXTPTR),y ;end of program?
878e: f0 4d beq LoD_RestoreTxtPtrAndReturn ;yes -> restore TXTPTR and exit LIST or DELETE.
8790: c8 iny
8791: b1 09 lda (zpTXTPTR),y ;read current line num
8793: 85 28 sta zpWAccum ;save in WAccum
8795: c8 iny
8796: b1 09 lda (zpTXTPTR),y
8798: 85 29 sta zpWAccum+1
879a: a5 88 lda zpLNumEnd ;set WParam to end-of-range line num
879c: 85 2c sta zpWParam
879e: a5 89 lda zpLNumEnd+1
87a0: 85 2d sta zpWParam+1
87a2: 20 e2 8f jsr WordCompare ;compare current line num, with range's end line num
87a5: f0 02 beq @notPastEnd ;cur > end?
87a7: b0 34 bcs LoD_RestoreTxtPtrAndReturn ;yes -> restore TXTPTR and exit LIST or DELETE.
87a9: a5 51 @notPastEnd lda zpListOrDelete ;are we doing LIST?
87ab: f0 0e beq ListCurLine ;yes -> LIST the current line
; Delete one line of the range
87ad: a5 09 lda zpTXTPTR ;no: we'll DELETE it
87af: 85 5b sta zpTxtLink ;Copy current line linkage to zpTxtLink
87b1: a5 0a lda zpTXTPTR+1
87b3: 85 5c sta zpTxtLink+1
87b5: 20 81 85 jsr TxtDeleteFoundLine ;and delete the line
87b8: 4c 8a 87 jmp LoD_mainLp ;iterate again for next line in range
87bb: 20 ee 87 ListCurLine jsr PrintLineNumAndDetok
87be: 20 c4 87 jsr PauseOnEsc
87c1: 4c 8a 87 jmp LoD_mainLp ;iterate again for next line in range
; Pause listing (within LIST or FIND) if the Escape key is pressed.
; Honor STOP key if that's pressed.
87c4: 20 d7 87 PauseOnEsc jsr ReadKeyMaybeStop ;read from keyboard
87c7: f0 0d beq @rts ;no key pressed? -> return to list next line
87c9: c9 1b cmp #$1b ;ESCAPE key?
87cb: d0 09 bne @rts ;no -> return to list next line
87cd: 20 d7 87 @escPause jsr ReadKeyMaybeStop ;pause LISTing until some key other than ESCAPE is pressed
87d0: f0 fb beq @escPause
87d2: c9 1b cmp #$1b
87d4: f0 f7 beq @escPause
87d6: 60 @rts rts ;return to iterating over lines to LIST
ReadKeyMaybeStop
87d7: 20 55 88 jsr StopIfStopKey
87da: 4c 45 b9 jmp ReadKeyInsMacros
LoD_RestoreTxtPtrAndReturn
87dd: 4c 39 94 jmp RestoreTXTPTR
QueueOutpNumStrTmp
87e0: a9 3d lda #zpNumStrTmp ;point zpOutputStr at zpNumStrTmp, as source
87e2: 18 clc ; for output queuing
87e3: 65 46 adc zpTmp46 ;add index to start position
87e5: 85 52 sta zpOutputStr ;(carry isn't needed, the string is up to 6 chars max)
87e7: a9 00 lda #>zpNumStrTmp
87e9: 85 53 sta zpOutputStr+1
87eb: 4c 3c 89 jmp QueueOutputStr ;queue the output string
; Print line number and the detokenized contents of the line. Used by
; DirectModeLoop when AUTO is active, and by LIST, and FIND.
PrintLineNumAndDetok
87ee: a9 00 lda #$00
87f0: 85 62 sta zpOutTxtCurPos
87f2: a0 01 ldy #$01
87f4: b1 09 lda (zpTXTPTR),y ;put the line number at zpTXTPTR into WAccum
87f6: 85 28 sta zpWAccum
87f8: c8 iny
87f9: b1 09 lda (zpTXTPTR),y
87fb: 85 29 sta zpWAccum+1
87fd: 20 3e 91 jsr PosDecStrFromWord
8800: 20 d6 90 jsr FindFirstNonzeroDigit
8803: 20 e0 87 jsr QueueOutpNumStrTmp ;queue up the line number to be printed
8806: a9 20 lda #$20
8808: 20 4d 89 jsr QueueCharForOutput ;and add a SPACE character afterward
880b: a9 03 lda #$03
880d: 18 clc
880e: 65 09 adc zpTXTPTR ;set up zpTokenizePtr to detokenize from the line's contents
8810: 85 4d sta zpTokenizePtr
8812: a9 00 lda #$00
8814: 65 0a adc zpTXTPTR+1
8816: 85 4e sta zpTokenizePtr+1
8818: 20 42 94 jsr DetokenizeLine ;detokenize from zpTokenizePtr, into lineBuffer
881b: 20 ad 8b jsr PointOutStrAtLineBufStart ;point zpOutputStr at lineBuffer
881e: 20 5a 89 jsr PrintString ;print our queued line number plus detokenized stuff in lineBuffer
8821: a5 4d lda zpTokenizePtr
8823: 85 09 sta zpTXTPTR ;(useless setting of zpTXTPTR; we throw it away a few lines below)
8825: a5 4e lda zpTokenizePtr+1
8827: 85 0a sta zpTXTPTR+1
8829: 20 6f b5 jsr DoCRLF ;carriage return + new line. (AUTO will have to back us back up.)
882c: a9 01 lda #$01
882e: 18 clc
882f: 65 4d adc zpTokenizePtr ;zpTokenizePtr now points at null terminator at end of line
8831: 85 09 sta zpTXTPTR ;set zpTXTPTR to next byte after that
8833: a9 00 lda #$00 ; (the "line offset" byte of next line)
8835: 65 4e adc zpTokenizePtr+1
8837: 85 0a sta zpTXTPTR+1
8839: 60 rts
883a: a9 05 CheckForStopKey lda #$05
883c: 8d 16 40 sta JOY1 ;write $05 to keyboard: reset row/col scanning
883f: 48 pha ;stall 21 cycles (~12 ns)
8840: 68 pla
8841: 48 pha
8842: 68 pla
8843: 48 pha
8844: 68 pla
8845: a9 04 lda #$04
8847: 8d 16 40 sta JOY1 ;write $04: prep read for column 0 of a new row
884a: a9 06 lda #$06
884c: 8d 16 40 sta JOY1 ;welect second col, first row
884f: ad 17 40 lda JOY2 ;read key column state
8852: 29 10 and #$10 ;mask out all but STOP key
8854: 60 rts
8855: 20 3a 88 StopIfStopKey jsr CheckForStopKey
8858: f0 03 beq Stop
885a: 60 rts
885b: 68 CmdFn_STOP pla ;pull return address off of stack -
885c: 68 pla ; we're not returning!
885d: a9 00 Stop lda #NC_NoCmd
885f: 85 63 sta zpNMICMD ;reset NMI sub-command (stopping music, etc)
8861: 20 45 b9 jsr ReadKeyInsMacros
8864: d0 f7 bne Stop ;wait until no key is actively being pressed
8866: 20 14 be jsr SetPpuCtrlAndEnableNmi
8869: 20 83 88 jsr Stop_saveContext
886c: a9 ff lda #$ff ;set stopped flag
886e: 85 24 sta zpStoppedFlag
8870: c5 0b cmp zpRunLineNum ;are we executing in direct mode?
8872: d0 07 bne @skipClear ;no, low byte doesn't match -> skip flag clear
8874: c5 0c cmp zpRunLineNum+1
8876: d0 03 bne @skipClear ;no, high byte doesn't match -> skip flag clear
8878: 20 c0 88 jsr ClearStoppedFlag ;nevermind, re-clear the stopped flag
887b: 4c d8 b2 @skipClear jmp Stop_PrintBreakBeepExit
ErrorCannotContinue
887e: a9 0e lda #err_CC
8880: 4c 37 b2 jmp PrintErrorAndStop
Stop_saveContext
8883: a5 0b lda zpRunLineNum ;save current running context for later restoration via CONT cmd
8885: 85 11 sta zpContRunLineNum
8887: a5 0c lda zpRunLineNum+1
8889: 85 12 sta zpContRunLineNum+1
888b: a5 0d lda zpRunTxtNxtLnk
888d: 85 13 sta zpContRunTxtNxtL
888f: a5 0e lda zpRunTxtNxtLnk+1
8891: 85 14 sta zpContRunTxtNxtL+1
8893: a5 58 lda zpTxtThisCmd
8895: 85 15 sta zpContTxtThisCmd
8897: a5 59 lda zpTxtThisCmd+1
8899: 85 16 sta zpContTxtThisCmd+1
889b: 60 rts
889c: a5 24 CmdFn_CONT lda zpStoppedFlag ;have context saved from STOP?
889e: f0 de beq ErrorCannotContinue ;no -> error
88a0: a5 15 lda zpContTxtThisCmd ;yes -> restore running context from previous STOP
88a2: 85 09 sta zpTXTPTR
88a4: a5 16 lda zpContTxtThisCmd+1
88a6: 85 0a sta zpTXTPTR+1
88a8: 20 f1 9e @skipLp jsr SkipToken
88ab: 20 9c 84 jsr IsEndOfCmd
88ae: d0 f8 bne @skipLp ;skip tokens until we're at end of command (colon or EOL)
88b0: a5 13 lda zpContRunTxtNxtL
88b2: 85 0d sta zpRunTxtNxtLnk
88b4: a5 14 lda zpContRunTxtNxtL+1
88b6: 85 0e sta zpRunTxtNxtLnk+1
88b8: a5 11 lda zpContRunLineNum
88ba: 85 0b sta zpRunLineNum
88bc: a5 12 lda zpContRunLineNum+1
88be: 85 0c sta zpRunLineNum+1
ClearStoppedFlag
88c0: a9 00 lda #$00
88c2: 85 24 sta zpStoppedFlag
88c4: 60 rts
88c5: f0 72 CmdFn_PRINT beq doCRLF_8939 ;no args? -> CRLF and return
88c7: 20 83 a2 jsr FrmEval ;evaluate argument
88ca: a5 27 lda zpArgTyp
88cc: c9 03 cmp #$03 ;is it string?
88ce: f0 16 beq @strArg ;yes -> handle string arg
88d0: ad 00 04 lda exprParseStk
88d3: 85 28 sta zpWAccum ;else, copy integer value to WAccum
88d5: ad 01 04 lda exprParseStk+1
88d8: 85 29 sta zpWAccum+1
88da: 20 98 91 jsr DecStrFromWord ;convert to decimal string
88dd: 20 e0 87 jsr QueueOutpNumStrTmp ;queue it for output
PRINT_outputThenNextArg
88e0: 20 5d 89 jsr PrintOutBuf ;tell NMI handler to output the buffer
88e3: 4c 19 89 jmp PRINT_afterArg ;-> and handle any more args
88e6: ad 00 04 @strArg lda exprParseStk ;get string length
88e9: f0 2e beq PRINT_afterArg ;empty string? -> already done with string then!
88eb: a0 00 ldy #$00
88ed: ad 01 04 lda exprParseStk+1 ;point zpMisc at string
88f0: 85 19 sta zpMisc
88f2: ad 02 04 lda exprParseStk+2
88f5: 85 1a sta zpMisc+1
88f7: b1 19 @strCpyLp lda (zpMisc),y ;get a char from string
88f9: c9 20 cmp #$20 ;is it a printable character?
88fb: b0 02 bcs @prAble
88fd: a9 00 lda #$00 ;if not, replace with null char
88ff: 99 00 05 @prAble sta lineBuffer,y ;copy the char into lineBuffer
8902: c8 iny ;move to next character
8903: ce 00 04 dec exprParseStk ;decrement string length
8906: d0 ef bne @strCpyLp ;strlen != 0? -> loop around to copy next character
8908: a9 00 lda #$00
890a: 99 00 05 sta lineBuffer,y ;null-terminate lineBuffer
890d: 20 ad 8b jsr PointOutStrAtLineBufStart
8910: 20 3c 89 jsr QueueOutputStr ;queue contents of lineBuffer to be printed
8913: 4c e0 88 jmp PRINT_outputThenNextArg ;-> print queued output, handle next arg
; dead/unreachable code
8916: 20 8c 90 jsr TxtPtrIncr
; end unreachable
8919: 20 9c 84 PRINT_afterArg jsr IsEndOfCmd ;no args remain?
891c: f0 1b beq doCRLF_8939 ;yes -> CRLF then return
891e: c9 3b cmp #‘;’ ;else, got semicolon?
8920: f0 09 beq @semicolon ;yes ->
8922: c9 2c cmp #‘,’ ;else, got comma?
8924: d0 0d bne @notComma ;no -> just print as another arg (if any)
8926: a9 09 lda #$09 ;(TAB)
8928: 20 75 b5 jsr PrintOneChar ;print TAB
892b: 20 99 84 @semicolon jsr AdvThenIsEndOfCmd ;move past semicolon
892e: f0 08 beq rts_8938 ;out of args? just end (no CRLF on semicolon or comma)
8930: 4c 19 89 jmp PRINT_afterArg ;else -> handle whatever else there is
8933: 20 9c 84 @notComma jsr IsEndOfCmd
8936: d0 8d bne CmdFn_PRINT ;(always) -> handle more args
8938: 60 rts_8938 rts
8939: 4c 6f b5 doCRLF_8939 jmp DoCRLF
; Copies the null-terminated string pointed at by zpOutputStr ($52)
; to genPurposeBuf ($700), queueing for output
893c: a0 00 QueueOutputStr ldy #$00
893e: a6 62 ldx zpOutTxtCurPos
8940: b1 52 @lp lda (zpOutputStr),y
8942: f0 f4 beq rts_8938 ;-> rts
8944: 20 4d 89 jsr QueueCharForOutput
8947: c8 iny
8948: 4c 40 89 jmp @lp
QueueNullForOutput
894b: a9 00 lda #$00
QueueCharForOutput
894d: a6 62 ldx zpOutTxtCurPos
894f: 9d 00 07 sta genPurposeBuf,x
8952: e6 62 inc zpOutTxtCurPos
8954: f0 01 beq @skip
8956: 60 rts
8957: c6 62 @skip dec zpOutTxtCurPos ;zpOutTxtCurPos <- #$FF
8959: 60 rts
; Copy a string to the output buffer, set zpNMICMD to NC_FlushOutp, and wait for
; NMI handler to finish copying to the screen.
895a: 20 3c 89 PrintString jsr QueueOutputStr
; NUL-terminate the output buffer, and wait for NMI to print it.
; On exit, Z is guaranteed to be set.
895d: 20 4b 89 PrintOutBuf jsr QueueNullForOutput
8960: a5 62 lda zpOutTxtCurPos
8962: f0 0c beq @rts ;-> rts
8964: a9 01 lda #NC_FlushOutp ; -> NMICMD_FlushQueuedOutput
8966: a2 00 ldx #$00
8968: 86 64 stx zpOutTxtStartPos ;reset output to go to start of buffer (after we've printed it) (fall through!)
; Set NMICMD and wait for NMI handler to finish processing it.
WaitForNmiSubcommand
896a: 85 63 sta zpNMICMD
896c: a5 63 @lp lda zpNMICMD
896e: d0 fc bne @lp
8970: 60 @rts rts
NMI_DefaultHandler
8971: 48 pha
8972: a9 00 lda #<oamBuffer
8974: 8d 03 20 sta OAMADDR
8977: a9 02 lda #>oamBuffer ;Trigger DMA transfer of CPU page $02 to VRAM page $00
8979: 8d 14 40 sta OAMDMA
897c: a5 ab lda zpNmiAnimNxtFr ;are we running animations this frame?
897e: d0 0e bne animAndTog_898E ;yes -> go animate
8980: a5 ac lda zpNmiAnimate ;no: Are animations active?
8982: f0 04 beq @ckCmd ;no -> just handle any active NMI subcommand
8984: a9 ff lda #$ff ;yes: we'll handle animations NEXT frame.
8986: 85 ab sta zpNmiAnimNxtFr ;set the flag, then handle NMI subcmds for this frame
8988: a5 63 @ckCmd lda zpNMICMD ;active command?
898a: d0 05 bne NmiHandleCmd ;yes -> go handle it
898c: 68 pla
898d: 40 rti
898e: 4c a3 c8 animAndTog_898E jmp AnimateAndToggleNextFrame
; Set up sub-command address, then jump to it
8991: aa NmiHandleCmd tax
8992: ca dex ;NC constants are one greater than cmd index
8993: 8a txa
8994: 0a asl A ;mul by 2 for jtbl index
8995: aa tax
8996: bd a3 89 lda jtbl_NMICMD,x
8999: 85 1b sta zpMisc+2 ;store handler pointer
899b: bd a4 89 lda jtbl_NMICMD+1,x
899e: 85 1c sta zpMisc+3
89a0: 6c 1b 00 jmp (zpMisc+2) ;and jump to it
89a3: 0c 8a jtbl_NMICMD .dd2 NMICMD_FlushQueuedOutput
89a5: 87 8a .dd2 NMICMD_BufferCurLine
89a7: c3 8a .dd2 NMICMD_CopyCharsLeft
89a9: fa 89 .dd2 NMICMD_ClearToEndOfRow
89ab: d2 89 .dd2 NMICMD_GetCharUnderCursor
89ad: f2 8a .dd2 NMICMD_SetBgPalette
89af: 30 8b .dd2 NMICMD_SetSprPalette
89b1: e4 89 .dd2 NMICMD_ShowCursor
89b3: f2 89 .dd2 NMICMD_RestoreCharAtCursor
89b5: 91 a0 .dd2 NMICMD_PlayMusic
89b7: c4 89 .dd2 NMICMD_PpuMaskUpdate
89b9: c1 89 .dd2 NMICMD_PpuMaskClear
89bb: 3e 8b .dd2 NMICMD_InsertChar
89bd: 74 8b .dd2 NMICMD_CopyRowDown_ph0
89bf: 8c 8b .dd2 NMICMD_CopyRowDown_ph1
NMICMD_PpuMaskClear
89c1: a9 00 lda #$00
89c3: ae ldx ▼ $33a5
NMICMD_PpuMaskUpdate
89c4: a5 33 lda zpPpuMaskVal
89c6: 8d 01 20 sta PPUMASK
NMICMD_DeactivateSubcmd
89c9: a9 00 lda #NC_NoCmd
89cb: 85 63 sta zpNMICMD
89cd: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
89d0: 68 pla
89d1: 40 rti
NMICMD_GetCharUnderCursor
89d2: 20 d8 89 jsr NmiGetCharUnderCursor
89d5: 4c c9 89 jmp NMICMD_DeactivateSubcmd
NmiGetCharUnderCursor
89d8: 20 2e b5 jsr PpuGoToCursor
89db: ad 07 20 lda PPUDATA
89de: ad 07 20 lda PPUDATA
89e1: 85 73 sta zpCharUnderCurs
89e3: 60 rts
NMICMD_ShowCursor
89e4: 20 d8 89 jsr NmiGetCharUnderCursor
89e7: 20 2e b5 jsr PpuGoToCursor
89ea: a5 61 lda zpCursorChar
89ec: 8d 07 20 @setCurs sta PPUDATA
89ef: 4c c9 89 jmp NMICMD_DeactivateSubcmd
NMICMD_RestoreCharAtCursor
89f2: 20 2e b5 jsr PpuGoToCursor
89f5: a5 73 lda zpCharUnderCurs
89f7: 4c ec 89 jmp @setCurs
; Erases from cursor to end of current row
NMICMD_ClearToEndOfRow
89fa: 20 2e b5 jsr PpuGoToCursor
89fd: a6 48 ldx zpCH ;set X reg to current cursor x pos
89ff: a9 20 lda #key_Space
8a01: 8d 07 20 @blankLp sta PPUDATA ;write a space to location in VRAM
8a04: e8 inx ;next char position
8a05: e0 1c cpx #COLS ;did we reach the end of the line?
8a07: d0 f8 bne @blankLp ;no -> loop until we do
8a09: 4c ba 8a jmp NmiEndCmdCleanupAndExit ;-> finish up
NMICMD_FlushQueuedOutput
8a0c: 20 f4 b4 jsr CalcCursorTilemapPos
8a0f: a6 64 ldx zpOutTxtStartPos
8a11: 20 2e b5 jsr PpuGoToCursor
8a14: bd 00 07 @outLp lda genPurposeBuf,x ;grab a char from out buf
8a17: f0 66 beq QueueOutpDeactivate ;NUL -> buf is done
8a19: 8d 07 20 sta PPUDATA ;not NUL; copy char to screen
8a1c: e6 48 inc zpCH ;advance the cursor
8a1e: a9 1c lda #COLS
8a20: c5 48 cmp zpCH ;have we reached the row end?
8a22: f0 0f beq @wrap ;yes -> handle wraparound to next row
8a24: a9 0e lda #$0e
8a26: c5 48 cmp zpCH ;are we at 15th column?
8a28: f0 03 beq @susp ;yes -> print rest next NMI
8a2a: e8 inx
8a2b: d0 e7 bne @outLp ;(always) loop back to output next char
; Done printing for this frame.
; Save our progress, and continue next NMI
8a2d: e8 @susp inx
8a2e: 86 64 stx zpOutTxtStartPos
8a30: 4c be 8a jmp NmiSameCmdCleanupAndExit
; We've reached the end of the screen line: wrap around to next column
8a33: a9 00 @wrap lda #$00
8a35: 85 48 sta zpCH
8a37: 8a txa
8a38: 48 pha
8a39: a6 49 ldx zpCV
8a3b: a9 ff lda #$ff
8a3d: 20 45 b6 jsr MarkWrappedRow
8a40: 68 pla
8a41: aa tax
8a42: e6 49 inc zpCV
8a44: a9 18 lda #ROWS
8a46: c5 49 cmp zpCV
8a48: f0 06 beq @scroll
8a4a: e8 inx
8a4b: 86 64 stx zpOutTxtStartPos
8a4d: 4c be 8a jmp NmiSameCmdCleanupAndExit
; Output cursor has gone to a row below the displayed screen, so
; scroll the screen.
8a50: e8 @scroll inx
8a51: 86 64 stx zpOutTxtStartPos
8a53: 20 48 b7 jsr CursorHome
8a56: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
8a59: a5 32 lda zpPpuCtrlVal
8a5b: 8d 00 20 sta PPUCTRL
8a5e: 20 f4 b4 jsr CalcCursorTilemapPos
8a61: 20 2b b4 jsr WaitForVBlank
8a64: 20 2e b5 jsr PpuGoToCursor
8a67: a2 1c ldx #COLS
8a69: a9 20 lda #$20 ;SPACE character
8a6b: 8d 07 20 @lp sta PPUDATA
8a6e: ca dex
8a6f: d0 fa bne @lp
8a71: 86 63 stx zpNMICMD ;deactivate command (#NC_NoCmd)
8a73: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
8a76: 20 d4 b5 jsr ScrollScreenOneRowNoErase
8a79: a9 01 lda #NC_FlushOutp
8a7b: 85 63 sta zpNMICMD
8a7d: 68 pla ;pull accum (pushed by NMI_DefaultHandler)
8a7e: 40 rti
; Indicate end of screen outputting, reset output buffer, cleanup, and exit.
QueueOutpDeactivate
8a7f: 85 63 sta zpNMICMD
8a81: 85 62 sta zpOutTxtCurPos
8a83: 85 64 sta zpOutTxtStartPos
8a85: f0 37 beq NmiSameCmdCleanupAndExit
; Copies the current row, through to the end of the line, into lineBuffer.
; (Copies multiple rows if the line is wrapped.)
; This command is invoked when handling CR on input, to get a copy of the line
; for input processing.
NMICMD_BufferCurLine
8a87: a9 00 lda #$00
8a89: 85 48 sta zpCH ;move to start of current row
8a8b: 20 f4 b4 jsr CalcCursorTilemapPos ;calculate VRAM addr for cursor
8a8e: 20 2e b5 jsr PpuGoToCursor ;tell PPU to start reading from there
8a91: a0 1c ldy #COLS ;set Y to # of chars in a row
8a93: ad 07 20 lda PPUDATA ;fake read from PPU (prime the pump!)
8a96: a6 76 ldx zpLineBufIdx ;get current index into line buffer
8a98: ad 07 20 @bufCpyLp lda PPUDATA ;read a byte from the screen
8a9b: 9d 00 05 sta lineBuffer,x ;store in the lineBuffer
8a9e: e8 inx ;incr lineBuffer idx
8a9f: e0 ff cpx #$ff ;line buffer full?
8aa1: f0 10 beq @done ;yes -> finished buffering
8aa3: 88 dey ;one less char to copy from row
8aa4: d0 f2 bne @bufCpyLp ;more to copy? -> loop back for next char
8aa6: 86 76 stx zpLineBufIdx ;update lineBuffer's index past where we copied to
8aa8: a6 49 ldx zpCV
8aaa: 20 37 b6 jsr DoesRowWrap ;does the current row line-wrap to the next?
8aad: f0 04 beq @done ;line doesn't wrap -> done; terminate active NMICMD
8aaf: e6 49 inc zpCV ;line wraps: copy next row on next NMI
8ab1: d0 0b bne NmiSameCmdCleanupAndExit ;(always) -> finish up until then
8ab3: a9 ff @done lda #$ff
8ab5: ae a9 00 ldx a:$00a9 ;(NOOP: disabled LDA #$00, but nothing jumps to it.)
8ab8: 85 64 sta zpOutTxtStartPos
NmiEndCmdCleanupAndExit
8aba: a9 00 lda #NC_NoCmd
NmiSetCmdCleanupAndExit
8abc: 85 63 sta zpNMICMD ;turn our command indicator off
; Many NMICMD handlers jump to here when done with the current frame
NmiSameCmdCleanupAndExit
8abe: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
8ac1: 68 pla ;pull pushed A-reg value
8ac2: 40 rti ;-> and return from interrupt
; Shifts the contents of a row on screen, left.
;
; Parameters:
; zpCursTileAddr ($4a) {addr/2}: start of copy region (end is end of the row)
; zpMisc ($19) {addr/2}: destination to copy to
;
; Only ever used by Backspace, to shift line contents past the cursor over to
; the left by one column. Thus zpMisc will hold the address of the current
; cursor position, and zpCursTileAddr will hold the address of one past that.
NMICMD_CopyCharsLeft
8ac3: 20 2e b5 jsr PpuGoToCursor ;go to (one past) current location in VRAM
8ac6: ad 07 20 lda PPUDATA ;initial, fake read
8ac9: a2 00 ldx #$00
8acb: a4 48 ldy zpCH
8acd: ad 07 20 @cpyLp lda PPUDATA ;copy from cursor to end of row, to lineBuffer
8ad0: 9d 00 05 sta lineBuffer,x
8ad3: e8 inx
8ad4: c8 iny
8ad5: c0 1c cpy #COLS
8ad7: d0 f4 bne @cpyLp
8ad9: a5 1a lda zpMisc+1 ;go to (true) current position
8adb: a6 19 ldx zpMisc
8add: 20 ec 81 jsr PpuGoToAX
8ae0: a2 00 ldx #$00
8ae2: a4 48 ldy zpCH
8ae4: bd 00 05 @cpyBackLp lda lineBuffer,x ;copy the lineBuffer back out to PPUDATA.
8ae7: 8d 07 20 sta PPUDATA
8aea: e8 inx
8aeb: c8 iny
8aec: c0 1c cpy #COLS
8aee: d0 f4 bne @cpyBackLp
8af0: f0 c8 beq NmiEndCmdCleanupAndExit ;done!
; Sets background palette number at genPurposeBuf ($200),
; to values at $201-$204.
NMICMD_SetBgPalette
8af2: a9 3f lda #>PPU_PALETTE_RAM
8af4: 8d 06 20 sta PPUADDR
8af7: ad 00 07 lda genPurposeBuf ;set addr to PPU palette RAM + ((palette num from genPurposeBuf) * 4)
8afa: 0a asl A
8afb: 0a asl A
8afc: 8d 06 20 NmiWritePalette sta PPUADDR
8aff: ad 01 07 lda genPurposeBuf+1 ;write the palette color values
8b02: 8d 07 20 sta PPUDATA
8b05: ad 02 07 lda genPurposeBuf+2
8b08: 8d 07 20 sta PPUDATA
8b0b: ad 03 07 lda genPurposeBuf+3
8b0e: 8d 07 20 sta PPUDATA
8b11: ad 04 07 lda genPurposeBuf+4
8b14: 8d 07 20 sta PPUDATA
8b17: a9 3f lda #>PPU_PALETTE_RAM ;set addr to PPU palette RAM start
8b19: 8d 06 20 sta PPUADDR
8b1c: a9 00 lda #<PPU_PALETTE_RAM
8b1e: 8d 06 20 sta PPUADDR
8b21: 8d 06 20 sta PPUADDR ;then to #$0000?
8b24: 8d 06 20 sta PPUADDR
8b27: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
8b2a: 20 14 be jsr SetPpuCtrlAndEnableNmi
8b2d: 4c c9 89 jmp NMICMD_DeactivateSubcmd
; Sets sprite palette number at genPurposeBuf ($200),
; to values at $201-$204.
NMICMD_SetSprPalette
8b30: a9 3f lda #>PPU_PALETTE_RAM
8b32: 8d 06 20 sta PPUADDR ;set addr to PPU palette RAM + 16 (for sprite palettes) + ((palette num from genPurposeBuf) * 4)
8b35: ad 00 07 lda genPurposeBuf
8b38: 0a asl A
8b39: 0a asl A
8b3a: 09 10 ora #$10
8b3c: d0 be bne NmiWritePalette
; Inserts designated character into the current row, shifting all chars past the
; cursor, to the right by one. The char that was previously at the end of the
; row but shifted off, is saved for subsequent calls.
vZpInsChar .var $b0 {addr/1} ;the character to insert
NMICMD_InsertChar
8b3e: 20 2e b5 jsr PpuGoToCursor ;go to cursor
8b41: a4 48 ldy zpCH
8b43: a2 01 ldx #$01
8b45: a5 b0 lda vZpInsChar ;put char-to-be-inserted at start of lineBuffer
8b47: 8d 00 05 sta lineBuffer ; (set by caller)
8b4a: ad 07 20 lda PPUDATA ;initial fake read
8b4d: ad 07 20 @cpyLp lda PPUDATA ;copy char to lineBuffer
8b50: 9d 00 05 sta lineBuffer,x
8b53: e8 inx
8b54: c8 iny
8b55: c0 1c cpy #COLS ;loop until we reach end of row
8b57: d0 f4 bne @cpyLp
8b59: 85 b0 sta vZpInsChar ;last char will be re-inserted in following line (in subsequent call)
8b5b: 20 2e b5 jsr PpuGoToCursor ;return to cursor
8b5e: a2 00 ldx #$00
8b60: a4 48 ldy zpCH
8b62: bd 00 05 @insCpyLp lda lineBuffer,x ;copy chars back out to screen,
8b65: 8d 07 20 sta PPUDATA ; with inserted char at start
8b68: e8 inx ; (everything else shifts right)
8b69: c8 iny
8b6a: c0 1c cpy #COLS
8b6c: d0 f4 bne @insCpyLp ;loop until we reach end of row
NmiSetVScrollAndDeactivate
8b6e: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
8b71: 4c c9 89 jmp NMICMD_DeactivateSubcmd ;done!
• Clear variables
NMICMD_CopyRowDown_ph0
8b74: 20 2e b5 jsr PpuGoToCursor ;go to cursor position in VRAM
8b77: a2 00 ldx #$00 ;(I think CH must be 0?)
8b79: ad 07 20 lda PPUDATA ;fake read
8b7c: ad 07 20 @cpyLp lda PPUDATA ;get char from screen
8b7f: 9d 00 05 sta lineBuffer,x ;copy to line buffer
8b82: e8 inx
8b83: e0 1c cpx #COLS ;loop until we reach the same position on next row
8b85: d0 f5 bne @cpyLp
8b87: a9 0f lda #$0f ;next phase: copy back out to next row, on next frame
8b89: 4c bc 8a jmp NmiSetCmdCleanupAndExit
NMICMD_CopyRowDown_ph1
8b8c: e6 49 inc zpCV ;move cursor down one row
8b8e: 20 f4 b4 jsr CalcCursorTilemapPos ;calculate addr for new position
8b91: 20 2e b5 jsr PpuGoToCursor ;and go there
8b94: a2 00 ldx #$00
8b96: bd 00 05 @cpyLp lda lineBuffer,x ;copy from line buffer to screen
8b99: 8d 07 20 sta PPUDATA
8b9c: e8 inx
8b9d: e0 1c cpx #COLS
8b9f: d0 f5 bne @cpyLp ;copy 28 chars out (a row's worth)
8ba1: 4c 6e 8b jmp NmiSetVScrollAndDeactivate ;done!
; Point zpTxtPtr at lineBuffer ($500)
InitLineBufferScanning
8ba4: a9 00 lda #<lineBuffer
8ba6: 85 09 sta zpTXTPTR
8ba8: a9 05 lda #>lineBuffer
8baa: 85 0a sta zpTXTPTR+1
8bac: 60 rts
PointOutStrAtLineBufStart
8bad: a9 00 lda #<lineBuffer
8baf: 85 52 sta zpOutputStr
8bb1: a9 05 lda #>lineBuffer
8bb3: 85 53 sta zpOutputStr+1
8bb5: 60 rts_8BB5 rts
CheckFreeSpaceOrError
8bb6: a5 03 lda zpFRETOP
8bb8: 38 sec
8bb9: e9 04 sbc #$04 ;subtract 4 from end of free space
8bbb: 85 2c sta zpWParam
8bbd: a5 04 lda zpFRETOP+1
8bbf: e9 00 sbc #$00
8bc1: 85 2d sta zpWParam+1
8bc3: 20 e2 8f jsr WordCompare ;and compare what's in WAccum to that
8bc6: 90 ed bcc rts_8BB5 ;less than zpFRETOP - 4? -> we're fine. Return.
ErrorOutOfMemory
8bc8: a9 06 lda #err_OM ;ERROR: insufficient memory!
8bca: 4c 37 b2 jmp PrintErrorAndStop
; Check that we have enough stack space (20 bytes) to execute a line of BASIC.
; Error if we do not.
8bcd: ba ExecCheckMem tsx
8bce: e0 14 cpx #$14
8bd0: 90 f6 bcc ErrorOutOfMemory
8bd2: 60 rts_8BD2 rts
8bd3: a9 00 AUTO_errSyn lda #$00
8bd5: 85 e2 sta zpAutoEnabled
8bd7: 4c 91 84 jmp ErrorSyntax
8bda: 08 CmdFn_AUTO php ;push processor flags
8bdb: 48 pha ;push accum
8bdc: a9 0a lda #10
8bde: 85 e2 sta zpAutoEnabled ;enable auto-increment
8be0: 85 a0 sta zpNxtAutoLNum ;set next linenum to auto-prefix as 10, by default
8be2: 85 a2 sta zpAutoLNumIncr ;increment by 10 by default
8be4: a9 00 lda #$00
8be6: 85 a1 sta zpNxtAutoLNum+1 ;set high bytes line num, increment
8be8: 85 a3 sta zpAutoLNumIncr+1
8bea: 68 pla ;pull original accum
8beb: 28 plp ;pull proc flags (from arg test)
8bec: f0 e4 beq rts_8BD2 ;no args? Done! (defaults above were enough)
8bee: c9 2c cmp #‘,’ ;is next char a comma?
8bf0: f0 16 beq @argTwo ;yes -> skip first arg, handle next
8bf2: c9 0b cmp #$0b ;got line-number arg indicator byte?
8bf4: d0 dd bne AUTO_errSyn ;no -> error out
8bf6: 20 8c 90 jsr TxtPtrIncr ;move past line-number prefix token
8bf9: 20 88 90 jsr TxtPtrGetCharAndIncr ;get first byte of line num
8bfc: 85 a0 sta zpNxtAutoLNum ;set it as first line num to prefix
8bfe: 20 88 90 jsr TxtPtrGetCharAndIncr ;get 2nd byte
8c01: 85 a1 sta zpNxtAutoLNum+1 ;set as high byte for next line num
8c03: 20 9c 84 jsr IsEndOfCmd ;any more args?
8c06: f0 ca beq rts_8BD2 ;no -> done!
8c08: 20 14 ac @argTwo jsr SkipCommaOrSynErr ;yes: skip expected comma
8c0b: 20 bf 90 jsr TxtSkipSpaces ;and any spaces
8c0e: c9 0b cmp #$0b ;got line-number arg indicator byte?
8c10: d0 c1 bne AUTO_errSyn ;no -> error out
8c12: 20 8c 90 jsr TxtPtrIncr ;copy "line number" as auto-increment amount
8c15: 20 88 90 jsr TxtPtrGetCharAndIncr
8c18: 85 a2 sta zpAutoLNumIncr
8c1a: 20 88 90 jsr TxtPtrGetCharAndIncr
8c1d: 85 a3 sta zpAutoLNumIncr+1
8c1f: 60 rts ;done!
RENUM_SetCharAndIncr
8c20: a0 00 ldy #$00
8c22: 91 09 sta (zpTXTPTR),y
8c24: 4c 8c 90 jmp TxtPtrIncr
8c27: 4c 91 84 errSyn_8C27 jmp ErrorSyntax
restoreTxtPtr_8C2A
8c2a: 4c 39 94 jmp RestoreTXTPTR
8c2d: 4c 94 84 errIllVal_8C2D jmp ErrorIllegalValue
vZpNewStartLNum .var $7c {addr/2} ;new starting line number
vZpOldStartLNum .var $7e {addr/2} ;old starting line number
vZpRenumIncrAmt .var $80 {addr/2} ;amount to increment each new line number by.
vZpRenumNextLNum .var $84 {addr/2} ;next line number to write
8c30: 08 CmdFn_RENUM php ;push processor flags
8c31: 48 pha ;push accum (char at TXTPTR)
8c32: a9 0a lda #10
8c34: 85 7c sta vZpNewStartLNum
8c36: 85 80 sta vZpRenumIncrAmt
8c38: a9 00 lda #$00
8c3a: 85 7d sta vZpNewStartLNum+1
8c3c: 85 7e sta vZpOldStartLNum
8c3e: 85 7f sta vZpOldStartLNum+1
8c40: 85 81 sta vZpRenumIncrAmt+1
8c42: 68 pla ;pull accum
8c43: 28 plp ;pull flags
8c44: f0 57 beq RENUM_checkNewGeOld ;no args? -> stick to the defaults
8c46: c9 2c cmp #‘,’ ;got comma?
8c48: f0 16 beq @arg2 ;yes -> skip 1st arg, check 2nd
8c4a: c9 0b cmp #tok_LNUM_AFTER ;got line-num prefix token?
8c4c: d0 d9 bne errSyn_8C27 ;no? -> error
8c4e: 20 8c 90 jsr TxtPtrIncr ;yes. Skip the prefix token.
8c51: 20 88 90 jsr TxtPtrGetCharAndIncr ;get new-start line num low byte
8c54: 85 7c sta vZpNewStartLNum ;and store
8c56: 20 88 90 jsr TxtPtrGetCharAndIncr ;low byte
8c59: 85 7d sta vZpNewStartLNum+1
8c5b: 20 9c 84 jsr IsEndOfCmd ;more args?
8c5e: f0 3d beq RENUM_checkNewGeOld ;no -> everything else uses defaults
8c60: 20 14 ac @arg2 jsr SkipCommaOrSynErr ;skip comma
8c63: 20 bf 90 jsr TxtSkipSpaces ;and spaces
8c66: c9 2c cmp #‘,’ ;another comma?
8c68: f0 16 beq @arg3 ;yes -> skip 2nd arg
8c6a: c9 0b cmp #tok_LNUM_AFTER ;else, line-num prefix token?
8c6c: d0 b9 bne errSyn_8C27 ;no -> error
8c6e: 20 8c 90 jsr TxtPtrIncr ;move past token
8c71: 20 88 90 jsr TxtPtrGetCharAndIncr ;get byte
8c74: 85 7e sta vZpOldStartLNum ;set low byte of starting (old) line number
8c76: 20 88 90 jsr TxtPtrGetCharAndIncr ;'nother byte
8c79: 85 7f sta vZpOldStartLNum+1 ;set high byte of starting (old) line number
8c7b: 20 9c 84 jsr IsEndOfCmd ;end of command?
8c7e: f0 1d beq RENUM_checkNewGeOld ;yes -> leave increment amt at default of 10
8c80: 20 14 ac @arg3 jsr SkipCommaOrSynErr ;skip expected comma
8c83: 20 bf 90 jsr TxtSkipSpaces ;and spaces
8c86: c9 0b cmp #tok_LNUM_AFTER ;got line-num prefix token?
8c88: d0 9d bne errSyn_8C27 ;no -> error
8c8a: 20 8c 90 jsr TxtPtrIncr ;move past token byte
8c8d: 20 88 90 jsr TxtPtrGetCharAndIncr ;get byte
8c90: 85 80 sta vZpRenumIncrAmt ;set as low byte of increment amt
8c92: 20 88 90 jsr TxtPtrGetCharAndIncr ;'nother byte
8c95: 85 81 sta vZpRenumIncrAmt+1 ;set as high byte of increment amt
8c97: d0 04 bne RENUM_checkNewGeOld ;high byte nonzero? Yes -> continue
8c99: a5 80 lda vZpRenumIncrAmt ;high byte zero. Low byte zero?
8c9b: f0 90 beq errIllVal_8C2D ;yes -> error. Can't have a zero increment!
RENUM_checkNewGeOld
8c9d: a5 7d lda vZpNewStartLNum+1
8c9f: c5 7f cmp vZpOldStartLNum+1
8ca1: f0 02 beq @cmpLo ;is new start lnum (hi byte) > old start lnum (hi byte)?
8ca3: b0 0c bcs RENUM_argsDone ;yes -> everything's great
8ca5: a5 7c @cmpLo lda vZpNewStartLNum ;else, is new start lnum (lo byte) >= old start lnum (lo byte)?
8ca7: c5 7e cmp vZpOldStartLNum ;BUG! Permits high byte lower, as long as low byte is highger!
8ca9: b0 06 bcs RENUM_argsDone ; This can result in repeated line numbers, and/or lower coming after higher
8cab: 4c 94 84 jmp ErrorIllegalValue
restoreTxtPtr_8CAE
8cae: 4c 2a 8c jmp restoreTxtPtr_8C2A
8cb1: 20 30 94 RENUM_argsDone jsr SaveTXTPTR ;save away curent TXTPTR
8cb4: 20 b1 8d jsr ErrorIfNewLNumsOverflow ;go check whether new line numbers
8cb7: a5 05 lda zpTXTTAB ; will overflow, and error if so
8cb9: 85 09 sta zpTXTPTR
8cbb: a5 06 lda zpTXTTAB+1 ;point TXTPTR at first line of BASIC
8cbd: 85 0a sta zpTXTPTR+1
8cbf: 20 b2 90 jsr GetTxtChar ;get offset to 2nd line in program
8cc2: f0 ea beq restoreTxtPtr_8CAE ;BASIC program empty? -> nothing to do; return
8cc4: 20 10 9f jsr TxtPtrAdvThree ;skip over offset and line num
@RENUM_scanCodeForLNums
8cc7: 20 b2 90 jsr GetTxtChar ;get a byte of line content
8cca: f0 0a beq @advLine ;end of line? -> advance to next
8ccc: c9 0b cmp #tok_LNUM_AFTER ;got a line num prefix token?
8cce: f0 11 beq @lnumTokFound
8cd0: 20 f1 9e jsr SkipToken ;else skip this byte
8cd3: 4c c7 8c jmp @RENUM_scanCodeForLNums ; and check next
8cd6: 20 98 90 @advLine jsr TxtPtrIncrAndGetChar ;are we at prog end?
8cd9: f0 3c beq RENUM_doRenum ;yes -> done scanning for line numbers in content
8cdb: 20 10 9f jsr TxtPtrAdvThree
8cde: 4c c7 8c jmp @RENUM_scanCodeForLNums
8ce1: a5 09 @lnumTokFound lda zpTXTPTR ;push TXTPTR lo, hi
8ce3: 48 pha
8ce4: a5 0a lda zpTXTPTR+1
8ce6: 48 pha
8ce7: 20 8c 90 jsr TxtPtrIncr ;skip line-num prefix token byte
8cea: 20 88 90 jsr TxtPtrGetCharAndIncr ;save following line num as line-to-find
8ced: 85 44 sta zpFindLNum
8cef: 20 88 90 jsr TxtPtrGetCharAndIncr
8cf2: 85 45 sta zpFindLNum+1
8cf4: 20 40 85 jsr FindLineNum ;find the indicated line num
8cf7: f0 06 beq @match ;exact match? -> leave zpTxtLink as FindLineNum set it
8cf9: a9 ff lda #$ff ;else (not found): invalidate zpTxtLink
8cfb: 85 5b sta zpTxtLink
8cfd: 85 5c sta zpTxtLink+1
8cff: 68 @match pla ;restore TXTPTR from stack
8d00: 85 0a sta zpTXTPTR+1
8d02: 68 pla
8d03: 85 09 sta zpTXTPTR
8d05: a9 0c lda #tok_LLINK_AFTER ;save over line-number token with line-link token,
8d07: 20 20 8c jsr RENUM_SetCharAndIncr ; so we can scan over them again
8d0a: a5 5b lda zpTxtLink ; and replace with updated line number
8d0c: 20 20 8c jsr RENUM_SetCharAndIncr
8d0f: a5 5c lda zpTxtLink+1
8d11: 20 20 8c jsr RENUM_SetCharAndIncr
8d14: 4c c7 8c jmp @RENUM_scanCodeForLNums ;return to scanning for more line numbers
8d17: a5 7e RENUM_doRenum lda vZpOldStartLNum ;set the (original) first line of renumber range
8d19: 85 44 sta zpFindLNum ; as the line to search for
8d1b: a5 7f lda vZpOldStartLNum+1
8d1d: 85 45 sta zpFindLNum+1
8d1f: 20 40 85 jsr FindLineNum ;and find it
8d22: a5 5b lda zpTxtLink
8d24: 85 09 sta zpTXTPTR ;point TXTPTR at the start of the found line
8d26: a5 5c lda zpTxtLink+1
8d28: 85 0a sta zpTXTPTR+1
8d2a: 20 b2 90 RENUM_renumLoop jsr GetTxtChar ;read the line's offset byte
8d2d: f0 2d beq RENUM_replaceLineLinks ;end of prog? -> replace line links with updated line numbers
8d2f: 48 pha ;push offset to next line
8d30: 20 8c 90 jsr TxtPtrIncr
8d33: a5 7c lda vZpNewStartLNum ;write renumbered line number over old one
8d35: 20 20 8c jsr RENUM_SetCharAndIncr
8d38: a5 7d lda vZpNewStartLNum+1
8d3a: 20 20 8c jsr RENUM_SetCharAndIncr
8d3d: 68 pla ;pull offset to next line
8d3e: 38 sec
8d3f: e9 03 sbc #$03 ;subtract 3 (bc we just advanced by 3)
8d41: 18 clc
8d42: 65 09 adc zpTXTPTR ;advance TXTPTR to start of next line
8d44: 85 09 sta zpTXTPTR
8d46: a9 00 lda #$00
8d48: 65 0a adc zpTXTPTR+1
8d4a: 85 0a sta zpTXTPTR+1
8d4c: 18 clc
8d4d: a5 80 lda vZpRenumIncrAmt ;advance next line number by increment
8d4f: 65 7c adc vZpNewStartLNum
8d51: 85 7c sta vZpNewStartLNum
8d53: a5 81 lda vZpRenumIncrAmt+1
8d55: 65 7d adc vZpNewStartLNum+1
8d57: 85 7d sta vZpNewStartLNum+1
8d59: 4c 2a 8d jmp RENUM_renumLoop ;loop around to renumber this next line
RENUM_replaceLineLinks
8d5c: a5 05 lda zpTXTTAB ;point TXTPTR at start of BASIC program once again
8d5e: 85 09 sta zpTXTPTR
8d60: a5 06 lda zpTXTTAB+1
8d62: 85 0a sta zpTXTPTR+1
8d64: 20 10 9f jsr TxtPtrAdvThree ;skip offset, line number
RENUM_linkReplLoop
8d67: 20 b2 90 jsr GetTxtChar ;get a byte from line content
8d6a: f0 0d beq @advLine ;end of line? -> scan next line
8d6c: c9 0c cmp #tok_LLINK_AFTER ;got a line-link prefix byte (that we wrote earlier)?
8d6e: f0 14 beq @linkFound ;yes -> replace with updated line num
8d70: 20 f1 9e jsr SkipToken ;else, skip this byte
8d73: 4c 67 8d jmp RENUM_linkReplLoop ;and check the next
restoreTxtPtr_8D76
8d76: 4c 39 94 jmp RestoreTXTPTR
8d79: 20 98 90 @advLine jsr TxtPtrIncrAndGetChar ;are we at prog end?
8d7c: f0 f8 beq restoreTxtPtr_8D76 ;yes -> restore TXTPTR, then all done
8d7e: 20 10 9f jsr TxtPtrAdvThree ;skip offset and line-num bytes
8d81: 4c 67 8d jmp RENUM_linkReplLoop ;and return to scanning for line links to replace
8d84: a0 00 @linkFound ldy #$00
8d86: a9 0b lda #tok_LNUM_AFTER
8d88: 91 09 sta (zpTXTPTR),y ;replace line-link token byte with line-num token
8d8a: c8 iny
8d8b: b1 09 lda (zpTXTPTR),y
8d8d: 85 5b sta zpTxtLink ;read the line link into zpTxtLink
8d8f: c8 iny
8d90: b1 09 lda (zpTXTPTR),y
8d92: 85 5c sta zpTxtLink+1
8d94: c9 ff cmp #$ff
8d96: d0 0c bne @writeNewLNum
8d98: a5 5b lda zpTxtLink
8d9a: c9 ff cmp #$ff ;invalid link (orig line num invalid)?
8d9c: d0 06 bne @writeNewLNum ;no -> safe to use real line num
@skipLNumAndIterate
8d9e: 20 10 9f jsr TxtPtrAdvThree ;skip over line link
8da1: 4c 67 8d jmp RENUM_linkReplLoop ;and iterate to find/replace more
8da4: 88 @writeNewLNum dey ;back up a byte
8da5: b1 5b lda (zpTxtLink),y ;overwrite line link with its new number
8da7: 91 09 sta (zpTXTPTR),y
8da9: c8 iny
8daa: b1 5b lda (zpTxtLink),y
8dac: 91 09 sta (zpTXTPTR),y
8dae: 4c 9e 8d jmp @skipLNumAndIterate ;-> go back to finding/replacing line links
ErrorIfNewLNumsOverflow
8db1: a5 7c lda vZpNewStartLNum ;save new first line number, to "next" line number
8db3: 85 84 sta vZpRenumNextLNum
8db5: a5 7d lda vZpNewStartLNum+1
8db7: 85 85 sta vZpRenumNextLNum+1
8db9: a5 7e lda vZpOldStartLNum ;save old first line num of range, as line to find
8dbb: 85 44 sta zpFindLNum
8dbd: a5 7f lda vZpOldStartLNum+1
8dbf: 85 45 sta zpFindLNum+1
8dc1: 20 40 85 jsr FindLineNum ;find it!
8dc4: a5 5b lda zpTxtLink ;point TXTPTR at first byte of found line (the offset to next)
8dc6: 85 09 sta zpTXTPTR
8dc8: a5 5c lda zpTxtLink+1
8dca: 85 0a sta zpTXTPTR+1
8dcc: 20 b2 90 @lnumLp jsr GetTxtChar ;get the offset byte. At prog end?
8dcf: f0 1d beq rts_8DEE ;yes -> return
8dd1: 18 clc
8dd2: 65 09 adc zpTXTPTR ;advance TXTPTR to start of following line
8dd4: 85 09 sta zpTXTPTR
8dd6: a9 00 lda #$00
8dd8: 65 0a adc zpTXTPTR+1
8dda: 85 0a sta zpTXTPTR+1
8ddc: 18 clc
8ddd: a5 80 lda vZpRenumIncrAmt ;advance "next line num" by increment amount
8ddf: 65 84 adc vZpRenumNextLNum
8de1: 85 84 sta vZpRenumNextLNum
8de3: a5 81 lda vZpRenumIncrAmt+1
8de5: 65 85 adc vZpRenumNextLNum+1
8de7: 85 85 sta vZpRenumNextLNum+1 ;did we overflow a new line number?
8de9: b0 04 bcs errIllVal_8DEF ;yes -> error
8deb: 4c cc 8d jmp @lnumLp ;else, loop back and check another line-num increment
8dee: 60 rts_8DEE rts
8def: 4c 94 84 errIllVal_8DEF jmp ErrorIllegalValue
; Sets Z flag iff zpWAccum contains (+/-)32768 (#$8000)
; Sets C if and only if zpWAccum is negative
• Clear variables
8df2: a5 29 Cmp32768 lda zpWAccum+1
8df4: c9 80 cmp #$80
8df6: f0 01 beq @ckLo
8df8: 60 rts
8df9: a5 28 @ckLo lda zpWAccum
8dfb: 38 sec
8dfc: 60 rts
8dfd: 38 WParamNegate sec
8dfe: a9 00 lda #$00
8e00: e5 2c sbc zpWParam
8e02: 85 2c sta zpWParam
8e04: a9 00 lda #$00
8e06: e5 2d sbc zpWParam+1
8e08: 85 2d sta zpWParam+1
8e0a: 60 rts
SetIntAndSubtract
8e0b: a9 02 lda #$02
8e0d: 85 27 sta zpArgTyp
; This routine, together with the Add routine it wraps, is the most ridiculously
; overcoded and overcomplicated "subtract two words" implementation I have ever
; laid eyes on. Could save a ton of code space by just using WordSubtract and
; checking for overflow afterward. Always negates the first operand (except when
; it's -32,768 and therefore can't be) before passing to the Add sub, and
; therefore always negates the result as well. Add, itself, may well negate the
; first operand and the result, meaning that both WAccum and the result from adc
; or sbc may undergo multiple negations (and even if they only undergo it once,
; it's unnecessary and ineffecient).
8e0f: 20 0d aa Subtract jsr ErrorUnlessInteger
8e12: 20 f2 8d jsr Cmp32768
8e15: f0 29 beq Sub_WAccumIs32768 ;zpWAccum = -32768? -> ensure zpWParam is 0
8e17: 20 32 8e jsr WAccumNegate
8e1a: a5 29 lda zpWAccum+1
8e1c: 30 11 bmi @haveNegArg
8e1e: a5 2d lda zpWParam+1
8e20: 30 0d bmi @haveNegArg
8e22: 20 f7 8e jsr WordAdd
8e25: 20 f2 8d jsr Cmp32768
8e28: f0 15 beq rts_8E3F
8e2a: 90 06 bcc WAccumNegate
8e2c: 4c f2 8e jmp ErrorOverflow
8e2f: 20 ac 8e @haveNegArg jsr Add
8e32: 38 WAccumNegate sec
8e33: a9 00 lda #$00
8e35: e5 28 sbc zpWAccum
8e37: 85 28 sta zpWAccum
8e39: a9 00 lda #$00
8e3b: e5 29 sbc zpWAccum+1
8e3d: 85 29 sta zpWAccum+1
8e3f: 60 rts_8E3F rts
Sub_WAccumIs32768
8e40: a5 2d lda zpWParam+1
8e42: 10 06 bpl @errOverfl ;BUG: produces OV ERROR even if WParam is zero lol
8e44: 20 fd 8d jsr WParamNegate
8e47: 4c ac 8e jmp Add
8e4a: 4c f2 8e @errOverfl jmp ErrorOverflow
CopyWParamToWAccum
8e4d: a5 2c lda zpWParam
8e4f: 85 28 sta zpWAccum
8e51: a5 2d lda zpWParam+1
8e53: 85 29 sta zpWAccum+1
8e55: a5 2e lda zpWParam+2
8e57: 85 2a sta zpWAccum+2
8e59: 60 rts_8E59 rts
ConcatenateStrings
8e5a: a6 21 ldx zpGpStrIdx
8e5c: 86 4a stx zpCursTileAddr
8e5e: a5 28 lda zpWAccum ;strlen == 0?
8e60: f0 eb beq CopyWParamToWAccum ;yes -> just use other operand then
8e62: 18 clc
8e63: a4 2c ldy zpWParam ;param len == 0?
8e65: f0 f2 beq rts_8E59 ;yes -> nothing to do, return
8e67: 65 2c adc zpWParam ;add the two strlens
8e69: c9 20 cmp #$20 ; >= 32?
8e6b: b0 dd bcs @errOverfl ;yes -> too big to fit in a string var
8e6d: 85 4b sta zpCursTileAddr+1
8e6f: a5 29 lda zpWAccum+1 ;copy strptr to $19.1A
8e71: 85 19 sta zpMisc
8e73: a5 2a lda zpWAccum+2
8e75: 85 1a sta zpMisc+1
8e77: a0 00 ldy #$00
8e79: b1 19 @cpyWaccumOutb lda (zpMisc),y ;copy zpWAccum str to outbuf
8e7b: 9d 00 07 sta genPurposeBuf,x
8e7e: e8 inx
8e7f: c8 iny
8e80: c6 28 dec zpWAccum
8e82: d0 f5 bne @cpyWaccumOutb
8e84: a5 2d lda zpWParam+1 ;copy PARAM str to outbuf (after ACC)
8e86: 85 19 sta zpMisc
8e88: a5 2e lda zpWParam+2
8e8a: 85 1a sta zpMisc+1
8e8c: a0 00 ldy #$00
8e8e: b1 19 @cpyParamOutb lda (zpMisc),y
8e90: 9d 00 07 sta genPurposeBuf,x
8e93: c8 iny
8e94: e8 inx
8e95: c6 2c dec zpWParam
8e97: d0 f5 bne @cpyParamOutb
8e99: 86 21 stx zpGpStrIdx
8e9b: a5 4b lda zpCursTileAddr+1
8e9d: 85 28 sta zpWAccum
8e9f: a5 4a lda zpCursTileAddr
8ea1: 85 29 sta zpWAccum+1
8ea3: a9 07 lda #$07
8ea5: 85 2a sta zpWAccum+2
8ea7: 60 rts
8ea8: a9 02 SetIntAndAdd lda #TYP_INT
8eaa: 85 27 sta zpArgTyp
; This routine is the most ridiculously overcoded and overcomplicated "add two
; words" implementation I have ever laid eyes on. Could save a ton of code space
; by just using WordAdd and checking for overflow afterward. It branches on
; positivity for each argument, maneuvering with negation to ensure it performs
; an operation on two positive operands. John Brooks (jbrooks@blueshiftinc.com)
; points out that this is a common strategy for multiplication and division, but
; quite useless for addition/subtraction. Such a waste!
8eac: a5 27 Add lda zpArgTyp
8eae: c9 03 cmp #TYP_STR ;are we working with strs?
8eb0: f0 a8 beq ConcatenateStrings ;yes -> concatenate them
8eb2: a5 29 lda zpWAccum+1 ;no: we are working with integer words
8eb4: 30 1b bmi AddInt_WAccumIsNeg ;WAccum neg -> handle that case
8eb6: a5 2d lda zpWParam+1 ;WParam neg?
8eb8: 30 06 bmi ParamNegateThenSubtract ;yes -> handle
8eba: 20 f7 8e jsr WordAdd ;both args positive, just add
8ebd: 30 33 bmi ErrorOverflow ;and error if the result is neg
8ebf: 60 rts
ParamNegateThenSubtract
8ec0: 20 fd 8d jsr WParamNegate
8ec3: 38 WordSubtract sec
8ec4: a5 28 lda zpWAccum
8ec6: e5 2c sbc zpWParam
8ec8: 85 28 sta zpWAccum
8eca: a5 29 lda zpWAccum+1
8ecc: e5 2d sbc zpWParam+1
8ece: 85 29 sta zpWAccum+1
8ed0: 60 rts
AddInt_WAccumIsNeg
8ed1: a5 2d lda zpWParam+1
8ed3: 30 09 bmi AddInt_BothNeg
8ed5: 20 32 8e jsr WAccumNegate
8ed8: 20 c3 8e jsr WordSubtract
8edb: 4c 32 8e jmp WAccumNegate
8ede: 20 32 8e AddInt_BothNeg jsr WAccumNegate
8ee1: 20 fd 8d jsr WParamNegate
8ee4: 20 f7 8e jsr WordAdd
8ee7: 20 f2 8d jsr Cmp32768
8eea: f0 05 beq @rts
8eec: b0 04 bcs ErrorOverflow
8eee: 4c 32 8e jmp WAccumNegate
8ef1: 60 @rts rts
8ef2: a9 05 ErrorOverflow lda #err_OV
8ef4: 4c 37 b2 jmp PrintErrorAndStop
; Adds $2C.2D to $28.29, leaves in $28.29.
8ef7: 18 WordAdd clc
8ef8: a5 2c lda zpWParam
8efa: 65 28 adc zpWAccum
8efc: 85 28 sta zpWAccum
8efe: a5 2d lda zpWParam+1
8f00: 65 29 adc zpWAccum+1
8f02: 85 29 sta zpWAccum+1
8f04: 60 rts
; Clears the scratch space used by WordMultiply. The first byte ($34) is never
; actually cleared (not necessary), and the last four ($38 thru $3B) are never
; actually used by anything.
ClearMultiplyScratch
8f05: a9 00 lda #$00
8f07: a2 08 ldx #$08
8f09: 95 34 @clearLp sta zpMulScratch,x
8f0b: ca dex
8f0c: d0 fb bne @clearLp ;-> write eight "zero" bytes
8f0e: 60 rts ;-> done
MultiplyAndCheckPosNeg
8f0f: a5 29 lda zpWAccum+1 ;is WAccum negative?
8f11: 30 11 bmi @wAccNeg ;yes -> check WPararm
8f13: a5 2d lda zpWParam+1 ;else is WAccum negative?
8f15: 30 11 bmi @mulExpectNeg ;yes -> expect negative result
8f17: 20 35 8f @mulExpectPos jsr WordMultiplyAsWord
8f1a: 20 3d 8f jsr IsWAccumZero ;WAccum == 0?
8f1d: f0 24 beq rts_8F43 ;yes? -> done
8f1f: a5 29 lda zpWAccum+1 ;is result negative?
8f21: 30 cf bmi ErrorOverflow ;yes -> overflow error
8f23: 60 rts
8f24: a5 2d @wAccNeg lda zpWParam+1 ;WAccum is neg. Is Wparam also negative?
8f26: 30 ef bmi @mulExpectPos ;yes -> expect pos result
8f28: 20 35 8f @mulExpectNeg jsr WordMultiplyAsWord
8f2b: 20 3d 8f jsr IsWAccumZero ;WAccum == 0?
8f2e: f0 13 beq rts_8F43 ;yes -> done
8f30: a5 29 lda zpWAccum+1 ;is result positive?
8f32: 10 be bpl ErrorOverflow ;yes -> overflow error
8f34: 60 rts
; Multiply, and cause an overflow error (OV) if it doesn't fit in a word.
; Multiplies the contents of $28.29 by $2C.2D, leaving the results in $28.29.
;
; BUG: won't cause overflow if (result MOD (2 ** 24)) fits in a word. for
; example, multiplying 4095 * 4095 produces overflow, but multiplying 4097
; * 4097 does NOT. This is because it checks for overflow in $36, but not
; in $37.
WordMultiplyAsWord
8f35: 20 44 8f jsr WordMultiplyWithTypeCheck
8f38: a5 36 lda zpMulScratch+2
8f3a: d0 b6 bne ErrorOverflow
8f3c: 60 rts
8f3d: a5 28 IsWAccumZero lda zpWAccum
8f3f: d0 02 bne rts_8F43
8f41: a5 29 lda zpWAccum+1
8f43: 60 rts_8F43 rts
; Multiplies the contents of $28.29 by $2C.2D leaving the results in $28.29.
WordMultiplyWithTypeCheck
8f44: 20 0d aa jsr ErrorUnlessInteger
8f47: 20 05 8f WordMultiply jsr ClearMultiplyScratch ;clear scratch-space values
8f4a: 20 7d 8f jsr EnsureWAccParmPositive ;make sure WAccum, WParam hold positive values
8f4d: 48 pha ;push whether we need to negate after bc of sign changes
8f4e: a2 10 ldx #$10 ;set X reg to sixteen (bits)
8f50: 18 clc
8f51: 66 29 @multLoop ror zpWAccum+1 ;shift WAccum right by one
8f53: 66 28 ror zpWAccum ;was the bit set?
8f55: 90 0d bcc @zeroBit ;no -> skip adding param
8f57: 18 clc ;yes, bit was set: must add param here
8f58: a5 36 lda zpMulScratch+2
8f5a: 65 2c adc zpWParam
8f5c: 85 36 sta zpMulScratch+2 ;add WParam to 2 bytes (16 bits) left of result space
8f5e: a5 37 lda zpMulScratch+3
8f60: 65 2d adc zpWParam+1
8f62: 85 37 sta zpMulScratch+3 ;add the carry to a byte left of that
8f64: 66 37 @zeroBit ror zpMulScratch+3 ;now shift all result bits right one (this will happen 16 times in all)
8f66: 66 36 ror zpMulScratch+2
8f68: 66 35 ror zpMulScratch+1
8f6a: 66 34 ror zpMulScratch
8f6c: ca dex ;one shift done. More remain?
8f6d: d0 e2 bne @multLoop ;-> loop for a total of 16 shifts
8f6f: a5 34 lda zpMulScratch ;result is in zpMulScratch+0,+1
8f71: 85 28 sta zpWAccum ;copy to WParam as the result
8f73: a5 35 lda zpMulScratch+1
8f75: 85 29 sta zpWAccum+1
WMul_PullAndObeyNegFlag
8f77: 68 pla ;pull negation flag. Do we need to negate?
8f78: f0 22 beq rts_8F9C ;no -> we're done
8f7a: 4c 32 8e jmp WAccumNegate ;yes: negate accum and return
vZpNegateResult .var $34 {addr/1}
EnsureWAccParmPositive
8f7d: a9 00 lda #$00
8f7f: 85 34 sta vZpNegateResult ;start with assumption we won't negate the result
8f81: a5 29 lda zpWAccum+1 ;is WAccum negative?
8f83: 10 07 bpl @wpPos ;no -> move on to WParam
8f85: 20 32 8e jsr WAccumNegate ;yes: make it positive
8f88: a9 ff lda #$ff
8f8a: 85 34 sta vZpNegateResult ;mark that we should negate the result
8f8c: a5 2d @wpPos lda zpWParam+1 ;is WParam negative?
8f8e: 10 0a bpl @ldaAndRts ;no -> we're done!
8f90: 20 fd 8d jsr WParamNegate ;yes: negate WParam
8f93: a9 ff lda #$ff
8f95: 38 sec
8f96: e5 34 sbc vZpNegateResult ;toggle result negation/reversesetting
8f98: 85 34 sta vZpNegateResult
8f9a: a5 34 @ldaAndRts lda vZpNegateResult ;this var will be destroyed after; load into accum so caller can push to stack
8f9c: 60 rts_8F9C rts
; Divide WAccum by WParam. Waccum+2,3 will hold remainder/modulus
• Clear variables
8f9d: 20 0d aa WordDivide jsr ErrorUnlessInteger
8fa0: 20 7d 8f jsr EnsureWAccParmPositive
8fa3: 48 pha ;push on stack, whether we should negate result after
8fa4: a9 00 lda #$00
8fa6: 85 2a sta zpWAccum+2
8fa8: 85 2b sta zpWAccum+3
8faa: a2 10 ldx #16 ;loop over 16 bits
8fac: 06 28 @divLoop asl zpWAccum
8fae: 26 29 rol zpWAccum+1
8fb0: 26 2a rol zpWAccum+2
8fb2: 26 2b rol zpWAccum+3
8fb4: a5 2a lda zpWAccum+2
8fb6: 38 sec ;LONG DIVISION in binary!
8fb7: e5 2c sbc zpWParam ;perform a throwaway subtraction of WParam
8fb9: a5 2b lda zpWAccum+3 ; from the shortest initial portion of WAccum
8fbb: e5 2d sbc zpWParam+1 ; that we can subtract from
8fbd: 90 0f bcc @noSbc ;carry clear (borrow set)? -> can't subtract, try a longer portion
8fbf: a5 2a lda zpWAccum+2 ;no borrow: perform subtract for real!
8fc1: 38 sec
8fc2: e5 2c sbc zpWParam
8fc4: 85 2a sta zpWAccum+2
8fc6: a5 2b lda zpWAccum+3
8fc8: e5 2d sbc zpWParam+1
8fca: 85 2b sta zpWAccum+3
8fcc: e6 28 inc zpWAccum
8fce: ca @noSbc dex
8fcf: d0 db bne @divLoop
8fd1: 4c 77 8f jmp WMul_PullAndObeyNegFlag
; Exactly the same as WordSubtract. Useless duplicate routine.
WordSubtractClone
8fd4: 38 sec
8fd5: a5 28 lda zpWAccum
8fd7: e5 2c sbc zpWParam
8fd9: 85 28 sta zpWAccum
8fdb: a5 29 lda zpWAccum+1
8fdd: e5 2d sbc zpWParam+1
8fdf: 85 29 sta zpWAccum+1
8fe1: 60 rts
; Compare WAccum to WParam
8fe2: a5 29 WordCompare lda zpWAccum+1
8fe4: c5 2d cmp zpWParam+1
8fe6: f0 01 beq @cmpLow
8fe8: 60 rts
8fe9: a5 28 @cmpLow lda zpWAccum
8feb: c5 2c cmp zpWParam
8fed: 60 rts
; Compare two operands, in WAccum and WParam. We'll refer to contents of WAccum
; as "A", and WParam as "B".
vZpStrLenA .var $4a {addr/1}
vZpStrLenB .var $4b {addr/1}
8fee: a5 27 Compare lda zpArgTyp
8ff0: c9 03 cmp #TYP_STR ;args string type?
8ff2: f0 41 beq CompareStrings ;yes -> compare strings
8ff4: a5 29 lda zpWAccum+1 ;is A negative?
8ff6: 30 23 bmi Cmp_AIsNegative ;yes -> check B
8ff8: a5 2d lda zpWParam+1 ;is B negative?
8ffa: 30 0c bmi Cmp_AGreaterThanB ;yes -> A isn't, so A > B
8ffc: a5 29 lda zpWAccum+1 ;no: both are pos
8ffe: c5 2d cmp zpWParam+1 ;cmp A, B high bytes
9000: f0 0a beq Cmp_LoBytes1
9002: b0 04 bcs Cmp_AGreaterThanB
9004: 18 Cmp_ALessThanB clc ;clear carry for "less than"
9005: a9 ff lda #$ff ;indicate unequal
9007: 60 rts
Cmp_AGreaterThanB
9008: 38 sec ;set carry for "greater than or equal"
9009: a9 ff lda #$ff ;indicate unequal
900b: 60 rts
900c: a5 28 Cmp_LoBytes1 lda zpWAccum ;this routine exactly identical to Cmp_LoBytes
900e: c5 2c cmp zpWParam
9010: f0 05 beq Cmp_Equal
9012: b0 f4 bcs Cmp_AGreaterThanB
9014: 4c 04 90 jmp Cmp_ALessThanB
9017: 38 Cmp_Equal sec ;set carry for "greater than or equal"
9018: a9 00 lda #$00 ;and load zero to signify equality
901a: 60 rts
901b: a5 2d Cmp_AIsNegative lda zpWParam+1 ;is B positive?
901d: 10 e5 bpl Cmp_ALessThanB ;yes -> A < B
901f: a5 29 lda zpWAccum+1 ;compare A, B high bytes
9021: c5 2d cmp zpWParam+1
9023: f0 05 beq Cmp_LoBytes ;equal? -> compare low bytes
9025: b0 e1 bcs Cmp_AGreaterThanB ;A high byte > B high byte? -> A > B
9027: 4c 04 90 jmp Cmp_ALessThanB ;else -> A < B
902a: a5 28 Cmp_LoBytes lda zpWAccum
902c: c5 2c cmp zpWParam ;compare A, B low bytes
902e: f0 e7 beq Cmp_Equal ;equal? -> A == B
9030: b0 d6 bcs Cmp_AGreaterThanB ;A low byte > B low byte? -> A > B
9032: 4c 04 90 jmp Cmp_ALessThanB ;else -> A < B
; Compare two strings. Comments in this area refer to first operand as `a$`, 2nd
; as `b$`. This does not actually refer to real variable names.
9035: a5 29 CompareStrings lda zpWAccum+1
9037: 85 19 sta zpMisc
9039: a5 2a lda zpWAccum+2
903b: 85 1a sta zpMisc+1
903d: a5 2d lda zpWParam+1
903f: 85 1b sta zpMisc+2
9041: a5 2e lda zpWParam+2
9043: 85 1c sta zpMisc+3
9045: a5 28 lda zpWAccum ;str len 1st operand
9047: f0 28 beq CmpStr_LenAZero ;len(a$) == 0? -> check len(b$)
9049: 85 4a sta vZpStrLenA ;store len(a$)
904b: f0 bb beq Cmp_AGreaterThanB ;NEVER branches - they forgot to compare len(b$) first!
904d: a5 2c lda zpWParam ;get len(b$) - should've come before instr above!
904f: 85 4b sta vZpStrLenB ;store it
9051: a0 00 ldy #$00
9053: b1 19 @strCmpLp lda (zpMisc),y ;get char from a$
9055: d1 1b cmp (zpMisc+2),y ;cmp char from b$
9057: 90 ab bcc Cmp_ALessThanB ;a$[i] < b$[i]? -> a$ < b$
9059: f0 03 beq @charEq ;(this and next instr should've been single bne)
905b: 4c 08 90 jmp Cmp_AGreaterThanB ;not less, not eq -> a$ > b$
905e: c8 @charEq iny ;bump next char to check
905f: c6 4a dec vZpStrLenA ;decrement len(a$)
9061: f0 07 beq @lenAZero ;len(a$) == 0? -> check len(b$)
9063: c6 4b dec vZpStrLenB ;decrement len(b$)
9065: f0 a1 beq Cmp_AGreaterThanB ;len(b$) == 0? -> a$ > b$
9067: 4c 53 90 jmp @strCmpLp ;neither are zero; loop back around to check next char
906a: c6 4b @lenAZero dec vZpStrLenB ;decrement len(b$)
906c: f0 a9 beq Cmp_Equal ;zero, too? -> strs equal
906e: 4c 04 90 jmp Cmp_ALessThanB ;;else -> a$ < b$
9071: a5 2c CmpStr_LenAZero lda zpWParam ;str len 2nd operand
9073: f0 a2 beq Cmp_Equal ;len(b$) == 0? -> they're both equal
9075: 4c 04 90 jmp Cmp_ALessThanB
; stores A at zpTXTPTR, then increments zpTXTPTR.
; If, after incrementing, zpTXTPTR has wraooed to next page, tehn it is
; decremented to point at the last byte of preceding page, and null-terminated.
• Clear variables
TxtPtrStoreAndAdv
9078: a0 00 ldy #$00
907a: 91 09 sta (zpTXTPTR),y
907c: e6 09 inc zpTXTPTR
907e: f0 01 beq @overfl
9080: 60 rts
9081: c6 09 @overfl dec zpTXTPTR
9083: a9 00 lda #$00
9085: 91 09 sta (zpTXTPTR),y
9087: 60 rts
TxtPtrGetCharAndIncr
9088: a0 00 ldy #$00
908a: b1 09 lda (zpTXTPTR),y
908c: e6 09 TxtPtrIncr inc zpTXTPTR
908e: f0 03 beq @hi
9090: 29 ff and #$ff
9092: 60 rts
9093: e6 0a @hi inc zpTXTPTR+1
9095: 29 ff and #$ff
9097: 60 rts
TxtPtrIncrAndGetChar
9098: a0 00 ldy #$00
909a: e6 09 inc zpTXTPTR
909c: f0 03 beq @hi
909e: b1 09 lda (zpTXTPTR),y
90a0: 60 rts
90a1: e6 0a @hi inc zpTXTPTR+1
90a3: b1 09 lda (zpTXTPTR),y
90a5: 60 rts
90a6: a5 09 TxtPtrDecr lda zpTXTPTR ;is low byte zero?
90a8: f0 03 beq @lowZero ;yes -> decr both bytes
90aa: c6 09 dec zpTXTPTR ;no: decr just low byte
90ac: 60 rts ;and return
90ad: c6 09 @lowZero dec zpTXTPTR ;decr lo
90af: c6 0a dec zpTXTPTR+1 ;decr hi
90b1: 60 rts ;-> return
; Get the next program character (doesn't increment)
90b2: a0 00 GetTxtChar ldy #$00
90b4: b1 09 lda (zpTXTPTR),y
90b6: 60 rts
; dead/unreachable code
90b7: a0 00 SetTxtCharDEAD ldy #$00
90b9: 91 09 sta (zpTXTPTR),y
90bb: 60 rts
TxtIncAndSkipSpaces
90bc: 20 8c 90 jsr TxtPtrIncr
90bf: a0 00 TxtSkipSpaces ldy #$00
90c1: b1 09 lda (zpTXTPTR),y
90c3: c9 20 cmp #$20
90c5: f0 f5 beq TxtIncAndSkipSpaces
90c7: 60 rts
; Clears carry if A is a digit, sets otherwise
90c8: c9 30 IsDigit cmp #‘0’
90ca: 90 06 bcc @secRts
90cc: c9 3a cmp #‘:’ ;'9' + 1
90ce: b0 02 bcs @secRts ;better: bcs $90D3
90d0: 18 clc
90d1: 60 rts
90d2: 38 @secRts sec
90d3: 60 rts_90D3 rts
90d4: e6 46 @nxtDig inc zpTmp46
FindFirstNonzeroDigit
90d6: a6 46 ldx zpTmp46
90d8: b5 3d lda zpNumStrTmp,x ;did we get null terminator byte?
90da: f0 05 beq @null ;yes -> skipped too many, back up one so we have '0'
90dc: c9 30 cmp #$30 ;no; do we have a '0' digit?
90de: f0 f4 beq @nxtDig ;yes -> move past it and check next
90e0: 60 rts ;no: we found first non-zero digit then. Return.
90e1: c6 46 @null dec zpTmp46 ;back up one so we have '0'
90e3: 60 rts
ParsePosOrNegDecWFromTxt
90e4: 20 bf 90 jsr TxtSkipSpaces
90e7: c9 2d cmp #‘-’ ;Is cur char a '-'?
90e9: d0 42 bne ParsePosDecWFromTxt ;no -> then it better be a string of digits. Parse them.
90eb: 20 8c 90 jsr TxtPtrIncr ;yes, then advance TXTPTR
90ee: 20 e4 90 jsr ParsePosOrNegDecWFromTxt ;and call ourselves recursively to get a (possibly re-negated) number
90f1: 4c 32 8e jmp WAccumNegate ;negating afterward.
; dead/unreachable code
90f4: 20 2d 91 jsr ParsePosDecWFromTxt
90f7: 20 f2 8d jsr Cmp32768
90fa: f0 3f beq errOverfl
90fc: 60 rts
; Parses a decimal number at zpTxtPtr into zpWAccum, leaving zpTXTPTR after the
; parsed number.
ParseDecimalWordFromTxt
90fd: a9 02 lda #TYP_INT
90ff: 85 27 sta zpArgTyp
9101: a9 00 lda #$00
9103: 85 28 sta zpWAccum ;start with zero in WAccum
9105: 85 29 sta zpWAccum+1
9107: a9 0a @digitsLoop lda #$0a
9109: 85 2c sta zpWParam
910b: a9 00 lda #$00
910d: 85 2d sta zpWParam+1
910f: a0 00 ldy #$00
9111: b1 09 lda (zpTXTPTR),y
9113: 20 c8 90 jsr IsDigit
9116: b0 bb bcs rts_90D3 ;not a digit? -> done
9118: 48 pha ;push the digit
9119: 20 35 8f jsr WordMultiplyAsWord ;multiply WAccum by 10 (will be zero the first time!)
911c: 68 pla ;pull digit
911d: 38 sec
911e: e9 30 sbc #‘0’ ;subtract value of '0' char, now 0-9
9120: 85 2c sta zpWParam ;store in WParam
9122: 20 f7 8e jsr WordAdd ;and perform add
9125: b0 14 bcs errOverfl ;overflow? -> error
9127: 20 8c 90 jsr TxtPtrIncr ;advance to next digit
912a: 4c 07 91 jmp @digitsLoop ;and loop around
; Parse a decimal word at TXTPTR, and throw an overflow error if it overflowed
; to negative. As a special exception, a value of 32,768 is allowed (so that
; later code can check if it was preceded with '-').
ParsePosDecWFromTxt
912d: 20 fd 90 jsr ParseDecimalWordFromTxt
9130: a5 29 lda zpWAccum+1
9132: 30 01 bmi @ck32768
9134: 60 rts
9135: 20 f2 8d @ck32768 jsr Cmp32768
9138: d0 01 bne errOverfl
913a: 60 rts
913b: 4c f2 8e errOverfl jmp ErrorOverflow
; Converts a (POSITIVE) word (at WAccum) into a null-terminated string of five
; decimal digits in ASCII, stored at zpNumStrTmp.
• Clear variables
vZpDigitIdx .var $46 {addr/1} ;index into zpNumStrTmp to store next digit
vZpDigitChar .var $47 {addr/1} ;character holding the digit value for current decimal place
PosDecStrFromWord
913e: a9 00 lda #$00
9140: 85 46 sta vZpDigitIdx
9142: a5 46 PosDecStrMidStr lda vZpDigitIdx
9144: 48 pha
9145: a9 10 lda #$10 ;put 10,000 into zpWParam
9147: 85 2c sta zpWParam
9149: a9 27 lda #$27
914b: 85 2d sta zpWParam+1
914d: 20 8a 91 jsr DecStr_findPlaceValue ;find digit for the ten-thousands place
9150: a9 e8 lda #$e8 ;put 1,000 into zpWParam
9152: 85 2c sta zpWParam
9154: a9 03 lda #$03
9156: 85 2d sta zpWParam+1
9158: 20 8a 91 jsr DecStr_findPlaceValue ;find digit for the thousands place
915b: a9 64 lda #$64 ;put 100 into zpWParam
915d: 85 2c sta zpWParam
915f: a9 00 lda #$00
9161: 85 2d sta zpWParam+1
9163: 20 8a 91 jsr DecStr_findPlaceValue ;find digit for the hundreds place
9166: a9 0a lda #$0a ;put 10 into zpWParam
9168: 85 2c sta zpWParam
916a: 20 8a 91 jsr DecStr_findPlaceValue ;find digit for the tens place
916d: a5 28 lda zpWAccum
916f: 18 clc
9170: 69 30 adc #$30
9172: 20 83 91 jsr DecStr_putAndAdv ;finally, store the remaining ones digit
9175: a9 00 lda #$00
9177: 20 83 91 jsr DecStr_putAndAdv ;and null-terminate the string
917a: 68 pla
917b: 85 46 sta vZpDigitIdx ;restore vZpDigitIdx to what it was at start (usually #$00)
917d: 60 rts
917e: 20 f7 8e DecStr_putDigit jsr WordAdd ;we subtracted too far; add it back in
9181: a5 47 lda vZpDigitChar ;put the digit, and advance to next
DecStr_putAndAdv
9183: a6 46 ldx vZpDigitIdx
9185: 95 3d sta zpNumStrTmp,x
9187: e6 46 inc vZpDigitIdx
9189: 60 rts
DecStr_findPlaceValue
918a: a9 30 lda #‘0’ ;start with the character '0'
918c: 85 47 sta vZpDigitChar
918e: 20 d4 8f @divLp jsr WordSubtractClone ;did current place value "go into" WAccum? (division)
9191: 90 eb bcc DecStr_putDigit ;no -> put char and return for next place value
9193: e6 47 inc vZpDigitChar ;yes: increment digit
9195: 4c 8e 91 jmp @divLp ;and see if it "goes into" WAccum again
; Converts a (possibly negative) word (at WAccum) into a null-terminated string
; of five decimal digits in ASCII, stored at zpNumStrTmp. Will be preceded
; either by a space (positive), or '-' (negative)
9198: a9 00 DecStrFromWord lda #$00
919a: 85 46 sta vZpDigitIdx
919c: a0 20 ldy #$20 ;SPACE char
919e: a5 29 lda zpWAccum+1 ;is WAccum positive (or zero)?
91a0: 10 05 bpl @pos ;yes -> keep prefix as SPACE char
91a2: 20 32 8e jsr WAccumNegate ;no: negate WAccum
91a5: a0 2d ldy #‘-’ ; and prefix with '-' instead
91a7: 98 @pos tya
91a8: 48 pha ;push the prefix
91a9: 20 42 91 jsr PosDecStrMidStr ;convert the positive decimal
91ac: 20 d6 90 jsr FindFirstNonzeroDigit ;advance to first non-'0' digit
91af: c6 46 dec vZpDigitIdx ;then backup one from there
91b1: 68 pla ;pull prefix char
91b2: 20 83 91 jsr DecStr_putAndAdv ;and store it as the prefix
91b5: c6 46 dec vZpDigitIdx ;then back up so it's at the start
91b7: 60 rts
; Converts a value from $0-$F to a hex digit in ASCII.
• Clear variables
91b8: c9 0a HexFromNybble cmp #10 ;A reg >= 10?
91ba: b0 03 bcs @needLet ;yes -> need a hex letter
91bc: 69 30 adc #‘0’ ;no: decimal digit is fine
91be: 60 rts ;-> return
91bf: 18 @needLet clc ;(convert to hex letter-digit)
91c0: 69 37 adc #$37 ;#$0A + #$37 = #$41 ('A'); #$0F + #$37 = #$46 ('F')
91c2: 60 rts ;-> return
; Converts a byte into two hex digits in ASCII, storing them at $3D + x.
91c3: 48 HexFromByte pha ;push the byte
91c4: 29 f0 and #$f0 ;grab the high nybble, and shift it into low
91c6: 4a lsr A
91c7: 4a lsr A
91c8: 4a lsr A
91c9: 4a lsr A
91ca: 20 b8 91 jsr HexFromNybble ;convert to hex
91cd: 95 3d sta zpNumStrTmp,x ;store in the tmp string
91cf: e8 inx ;incr out-string index
91d0: 68 pla ;pull original byte
91d1: 29 0f and #$0f ;grab the low nybble
91d3: 20 b8 91 jsr HexFromNybble ;convert to hex
91d6: 95 3d sta zpNumStrTmp,x ;store in string
91d8: e8 inx ;incr out-string index to next char
91d9: 60 rts ;-> return
; Converts a word (at WAccum) into a null-terminated string of four hexadecimal
; digits in ASCII, stored at zpNumStrTmp.
91da: a2 00 HexStrFromWord ldx #$00
91dc: 86 46 stx zpTmp46
91de: a5 29 lda zpWAccum+1
91e0: 20 c3 91 jsr HexFromByte ;convert WAccum high byte, store in string
91e3: a5 28 lda zpWAccum
91e5: 20 c3 91 jsr HexFromByte ;convert WAccum low byte
91e8: a9 00 lda #$00
91ea: 95 3d sta zpNumStrTmp,x ;add null terminating char to number string
91ec: 60 rts_91EC rts ;-> return
; Converts a hex string at zpTXTPTR, to a binary word in zpWAccum.
91ed: a9 02 WordFromHex lda #TYP_INT
91ef: 85 27 sta zpArgTyp ;exprssion result type will be integer
91f1: a2 00 ldx #$00
91f3: a9 00 lda #$00
91f5: 85 28 sta zpWAccum ;zero out WAccum
91f7: 85 29 sta zpWAccum+1
91f9: 20 b2 90 @chrLp jsr GetTxtChar ;get hex string char
91fc: 38 sec
91fd: e9 30 sbc #‘0’ ;subtract value of ASCII code for '0'
91ff: 90 eb bcc rts_91EC ;char val was < '0'? -> return; not a hex str
9201: c9 0a cmp #10
9203: 90 0b bcc @decDig ;result < 10? -> is a decimal digit, handle that
9205: 38 sec ;else, is a hex letter-digit (A-F)
9206: e9 07 sbc #$07 ;subtract 7 more from ASCII code
9208: c9 0a cmp #10 ;result < 10?
920a: 90 e0 bcc rts_91EC ;yes -> return; char val was not hex
920c: c9 10 cmp #16 ;result >= 16?
920e: b0 dc bcs rts_91EC ;yes -> return; char val was not hex
9210: 48 @decDig pha ;push the value to CPU stack
9211: 20 24 92 jsr @shiftResultLeft ;multiply running result by 16
9214: 68 pla ;pull translated hex digit's value
9215: 85 2c sta zpWParam ;set WParam to it
9217: a9 00 lda #$00
9219: 85 2d sta zpWParam+1
921b: 20 f7 8e jsr WordAdd ;add to running value in WAccum
921e: 20 8c 90 jsr TxtPtrIncr ;advance to next char in string
9221: 4c f9 91 jmp @chrLp ;-> loop back to handle next character
@shiftResultLeft
9224: 8a txa
9225: 48 pha ;push X reg to CPU stack
9226: a9 10 lda #$10
9228: 85 2c sta zpWParam
922a: a9 00 lda #$00
922c: 85 2d sta zpWParam+1
922e: 20 35 8f jsr WordMultiplyAsWord ;multiply running result by 16 (shifting existing digits over one base16 place)
9231: 68 pla
9232: aa tax ;pull and restore X reg
9233: 60 rts ;->return
TokAdvAndgetChar
9234: 20 40 92 jsr TokAdvance
9237: a0 00 TokGetChar ldy #$00
9239: b1 4d lda (zpTokenizePtr),y
923b: 60 rts
TokGetCharAndAdv
923c: a0 00 ldy #$00
923e: b1 4d lda (zpTokenizePtr),y
9240: e6 4d TokAdvance inc zpTokenizePtr
9242: d0 11 bne @flagsRts
9244: e6 4e inc zpTokenizePtr+1
9246: d0 0d bne @flagsRts
; Store two NULs in a row at the end of tokenizeBuffer
9248: a9 00 TokBufTerminate lda #$00
924a: 20 4d 92 jsr TokStoreCharAndAdv
TokStoreCharAndAdv
924d: a0 00 ldy #$00
924f: 91 4d sta (zpTokenizePtr),y
9251: e6 4d inc zpTokenizePtr
9253: f0 03 beq errOverfl_9258 ;exceeded our buffer? -> error
9255: 29 ff @flagsRts and #$ff
9257: 60 rts
9258: 4c f2 8e errOverfl_9258 jmp ErrorOverflow
; Tokenize the line pointed at by zpTXTPTR ($09.$0A), to tokenizeBuffer ($300)
925b: a9 00 Tokenize lda #<tokenizeBuffer
925d: 85 4d sta zpTokenizePtr
925f: a9 03 lda #>tokenizeBuffer
9261: 85 4e sta zpTokenizePtr+1
9263: 20 1e 93 TokenizeMain jsr TokCpySpacesFromTxt
9266: 29 ff and #$ff
9268: f0 de tokMainNoCpySpc beq TokBufTerminate ;NUL? -> finish up
926a: c9 3f cmp #‘?’ ;shorthand for PRINT
926c: f0 41 beq @tokQMark
926e: c9 22 cmp #‘"’
9270: f0 46 beq TokStorAndCopyUntilQuo
9272: c9 27 cmp #‘'’ ;shorthand for (non-buggy) REM
9274: f0 3f beq TokStorAndCopyRest
9276: c9 2e cmp #‘.’
9278: f0 0e beq TokOther
927a: c9 26 cmp #‘&’
927c: f0 7b beq TokenizeHexNum
927e: 20 c8 90 jsr IsDigit
9281: 90 4b bcc TokenizeNumber
9283: 20 e6 93 jsr TokenizeKeyword
9286: 90 2a bcc storeTokHndlArgs
; We didn't recognize anything special.
; Skip high-bit-set bytes, skip control chars, and store everything
; else verbatim.
9288: 20 8c 90 TokOther jsr TxtPtrIncr
928b: c9 80 cmp #$80
928d: b0 d4 bcs TokenizeMain ;skip high bytes without storing
928f: c9 20 cmp #$20
9291: 90 d0 bcc TokenizeMain ;skip control chars w/o store
9293: c9 40 cmp #$40
9295: 90 12 bcc @storePunct
; Copy presumed var name out to tokenizeBuffer, verbatim
9297: 20 4d 92 @lp jsr TokStoreCharAndAdv
929a: a0 00 ldy #$00
929c: b1 09 lda (zpTXTPTR),y
929e: 20 bc a7 jsr IsAlNum
92a1: b0 c0 bcs TokenizeMain
92a3: 20 8c 90 jsr TxtPtrIncr
92a6: 4c 97 92 jmp @lp
92a9: 20 4d 92 @storePunct jsr TokStoreCharAndAdv
92ac: 4c 63 92 jmp TokenizeMain
92af: 4c 4c 93 @tokQMark jmp TokQMark
storeTokHndlArgs
92b2: 4c 53 93 jmp StoreTokenAndHandleArgs
TokStorAndCopyRest
92b5: a0 00 ldy #$00
92b7: ae ldx ▼ $22a0
TokStorAndCopyUntilQuo
92b8: a0 22 ldy #$22
92ba: 84 99 sty zpToken
TokStorAndCopyUntilTok
92bc: 20 4d 92 jsr TokStoreCharAndAdv
92bf: 20 8c 90 jsr TxtPtrIncr
92c2: 20 b2 90 TokCopyUntilTok jsr GetTxtChar
92c5: f0 a1 beq tokMainNoCpySpc ; -> TokBufTerminate
92c7: c5 99 cmp zpToken
92c9: d0 f1 bne TokStorAndCopyUntilTok
92cb: 4c 88 92 jmp TokOther
92ce: a9 12 TokenizeNumber lda #tok_NUM_AFTER
92d0: 48 pha
92d1: 20 2d 91 jsr ParsePosDecWFromTxt
92d4: a5 29 lda zpWAccum+1 ;did num fit in a single byte?
92d6: d0 0d bne tokNumWord ;no -> process word
92d8: a5 28 lda zpWAccum
92da: c9 0a cmp #$0a ;low byte > 9?
92dc: b0 07 bcs tokNumWord ;yes -> process as word
92de: aa tax ;no: store as single byte
92df: 68 pla ;pop the num tok, won't use it
92e0: e8 inx ;inc so we can't get #$00
92e1: 8a txa
92e2: 4c 17 93 jmp tokStorAndCont
92e5: 20 f2 8d tokNumWord jsr Cmp32768 ; = 32768?
92e8: f0 03 beq @is32768
92ea: 4c 0c 93 jmp @storeWordAndCont
92ed: a5 99 @is32768 lda zpToken
92ef: c9 fa cmp #tok_OP_MINUS ;this num preceded by '-'?
92f1: d0 03 bne @errOverfl
92f3: 4c 0c 93 jmp @storeWordAndCont
92f6: 4c f2 8e @errOverfl jmp ErrorOverflow
92f9: 20 8c 90 TokenizeHexNum jsr TxtPtrIncr ;skip the '&'
92fc: 20 b2 90 jsr GetTxtChar
92ff: c9 48 cmp #‘H’ ;got an (optional) 'H'?
9301: d0 03 bne @noH
9303: 20 8c 90 jsr TxtPtrIncr ;yes -> skip it
9306: a9 11 @noH lda #tok_HEX_AFTER
9308: 48 pha ;push hex-num prefix token
9309: 20 ed 91 jsr WordFromHex ;convert hex string to binary number
@storeWordAndCont
930c: 68 pla ;pull token byte
930d: 20 4d 92 jsr TokStoreCharAndAdv ;store it in tokenized line
9310: a5 28 lda zpWAccum
9312: 20 4d 92 jsr TokStoreCharAndAdv ;store low byte of converted num
9315: a5 29 lda zpWAccum+1
9317: 20 4d 92 tokStorAndCont jsr TokStoreCharAndAdv ;and high byte
931a: 4c 63 92 jmp TokenizeMain ;-> return to tokenize loop
931d: 60 rts_931D rts
TokCpySpacesFromTxt
931e: 20 b2 90 jsr GetTxtChar
9321: c9 20 cmp #$20 ;space?
9323: d0 f8 bne rts_931D ;no -> rts
9325: 20 4d 92 jsr TokStoreCharAndAdv ;yes: store and check next
9328: 20 8c 90 jsr TxtPtrIncr
932b: 4c 1e 93 jmp TokCpySpacesFromTxt
; Entered when we just processed a REM keyword into a token.
; Copies the rest of the line verbatim, to the tokenizeBuffer.
;
; This has a BUG! If the katakana "small yo" (ョ) character appears anywhere
; within the REM statement, then the verbatim copying _ends_, and BASIC returns
; to trying to convert keywords into tokens, corrupting the REM statement's
; contents. This happens because this character has the same value as the token
; used for REM, and a typo means the copier is told to stop at the next REM
; character, instead of at NUL (#$00).
;
; Many Japanese words contain this character. I kind of can't believe searching
; for this bug didn't come up with any results, as users back in the day must
; have run across it. Maybe they didn't understand how the REM line was getting
; corrupted, as it would only show up when you think to LIST it back out again.
; Or maybe hardly anyone used REM directly, but used the single-quot (`)
; instead.
;
; Note that the single-quot (') shorthand for REM, handled separately at $9272,
; does _not_ exhibit this bug.
932e: a0 00 TokRemCopyToEnd ldy #$00 ;BUG: this should be lda
9330: 85 99 sta zpToken ;...or else this should be sty
9332: 4c c2 92 jmp TokCopyUntilTok
TokenizeLineRanges
9335: 20 1e 93 jsr TokCpySpacesFromTxt
9338: 20 c8 90 jsr IsDigit
933b: 90 43 bcc TokenizeLineNumber
933d: c9 2c cmp #‘,’
933f: f0 5c beq tokAndNum
9341: c9 2d cmp #‘-’
9343: f0 58 beq tokAndNum
9345: c9 2e cmp #‘.’
9347: f0 54 beq tokAndNum
9349: 4c 63 92 tokenizeMain jmp TokenizeMain
934c: a9 8b TokQMark lda #tok_PRINT
934e: 85 99 sta zpToken
9350: 20 8c 90 jsr TxtPtrIncr ;weird that it falls through here. Exits eventually at $9376.
StoreTokenAndHandleArgs
9353: a5 99 lda zpToken
9355: 20 4d 92 jsr TokStoreCharAndAdv
9358: c9 95 cmp #tok_REM
935a: f0 d2 beq TokRemCopyToEnd
935c: c9 86 cmp #tok_LIST
935e: f0 d5 beq TokenizeLineRanges
9360: c9 b6 cmp #tok_DELETE
9362: f0 d1 beq TokenizeLineRanges
9364: c9 b5 cmp #tok_AUTO
9366: f0 cd beq TokenizeLineRanges
9368: c9 b7 cmp #tok_RENUM
936a: f0 c9 beq TokenizeLineRanges
936c: c9 bd cmp #tok_RESUME
936e: f0 08 beq @numOrCont
9370: c9 91 cmp #tok_DATA
9372: f0 3a beq TokDataArgs
9374: c9 87 cmp #tok_SYSTEM ;is it a command that takes a line number arg?
9376: b0 d1 bcs tokenizeMain ;no -> return to normal tokenizing
9378: 20 1e 93 @numOrCont jsr TokCpySpacesFromTxt ;yes -> parse line number if it's there
937b: 20 c8 90 jsr IsDigit
937e: b0 c9 bcs tokenizeMain
TokenizeLineNumber
9380: 20 fd 90 jsr ParseDecimalWordFromTxt
9383: a9 0b lda #$0b
9385: 20 4d 92 jsr TokStoreCharAndAdv
9388: a5 28 lda zpWAccum
938a: 20 4d 92 jsr TokStoreCharAndAdv
938d: a5 29 lda zpWAccum+1
938f: 20 4d 92 jsr TokStoreCharAndAdv
9392: 20 1e 93 jsr TokCpySpacesFromTxt
9395: c9 2c @delimAndNum cmp #‘,’
9397: f0 04 beq tokAndNum
9399: c9 2d cmp #‘-’
939b: d0 ac bne tokenizeMain
939d: 20 4d 92 tokAndNum jsr TokStoreCharAndAdv
93a0: 20 8c 90 jsr TxtPtrIncr
93a3: 20 1e 93 jsr TokCpySpacesFromTxt
93a6: 20 c8 90 jsr IsDigit
93a9: 90 d5 bcc TokenizeLineNumber
93ab: 4c 95 93 jmp @delimAndNum
93ae: 20 1e 93 TokDataArgs jsr TokCpySpacesFromTxt
93b1: c9 3a cmp #‘:’
93b3: f0 94 beq tokenizeMain
93b5: 29 ff and #$ff
93b7: f0 90 @txtEnd beq tokenizeMain
93b9: c9 22 cmp #‘"’
93bb: f0 09 beq @dquo
93bd: 20 4d 92 @dquoOut jsr TokStoreCharAndAdv
93c0: 20 8c 90 jsr TxtPtrIncr
93c3: 4c ae 93 jmp TokDataArgs
93c6: 20 4d 92 @dquo jsr TokStoreCharAndAdv
93c9: 20 8c 90 jsr TxtPtrIncr
93cc: 20 1e 93 jsr TokCpySpacesFromTxt
93cf: c9 22 cmp #‘"’
93d1: f0 ea beq @dquoOut
93d3: 29 ff and #$ff
93d5: f0 e0 beq @txtEnd
93d7: 4c c6 93 jmp @dquo
93da: a0 00 TokTblGetChar ldy #$00
93dc: b1 54 lda (zpTokTblScn),y
93de: 60 rts_93DE rts
93df: e6 54 IncrTokTblScn inc zpTokTblScn
93e1: d0 fb bne rts_93DE ; -> rts
93e3: e6 55 inc zpTokTblScn+1
93e5: 60 rts
; Returns with carry clear if keyword matched; carry set otherwise
93e6: a9 ab TokenizeKeyword lda #<tbl_KeywordTokens
93e8: 85 54 sta zpTokTblScn
93ea: a9 cc lda #>tbl_KeywordTokens
93ec: 85 55 sta zpTokTblScn+1
93ee: 20 da 93 @nxtKwd jsr TokTblGetChar
93f1: aa tax
93f2: e8 inx ;did we get #$ff (end-of-table byte)?
93f3: f0 36 beq kwdNotFnd ;yes -> return false (load txt char, set carry, rts)
93f5: 85 99 sta zpToken
93f7: 20 df 93 jsr IncrTokTblScn
93fa: 20 30 94 jsr SaveTXTPTR
; Loop over keyword from tbl_KeywordTokens, and word at zpTXTPTR, to see if
; they're a match
93fd: 20 b2 90 @cmpKwd jsr GetTxtChar
9400: 85 98 sta zpSavedTxtChar
9402: 20 da 93 jsr TokTblGetChar
9405: c5 98 cmp zpSavedTxtChar
9407: d0 0d bne @ckForAbbrv
9409: 20 8c 90 jsr TxtPtrIncr ;chars matched, incr and check next
940c: 20 df 93 jsr IncrTokTblScn
940f: 20 da 93 jsr TokTblGetChar
9412: 10 e9 bpl @cmpKwd
9414: 18 @retTrue clc ;reached token byte of next kwd, so keyword matched!
9415: 60 rts ;return true (zpToken has the token)
9416: 20 88 90 @ckForAbbrv jsr TxtPtrGetCharAndIncr
9419: c9 2e cmp #‘.’ ;did user abbreviate the keyword?
941b: f0 f7 beq @retTrue ;yes -> we matched, return true
941d: 20 39 94 jsr RestoreTXTPTR
9420: 20 da 93 @skipToNextKwd jsr TokTblGetChar
9423: 30 c9 bmi @nxtKwd
9425: 20 df 93 jsr IncrTokTblScn
9428: 4c 20 94 jmp @skipToNextKwd
942b: 20 b2 90 kwdNotFnd jsr GetTxtChar
942e: 38 sec
942f: 60 rts
9430: a5 09 SaveTXTPTR lda zpTXTPTR
9432: 85 9a sta zpSavedTXTPTR
9434: a5 0a lda zpTXTPTR+1
9436: 85 9b sta zpSavedTXTPTR+1
9438: 60 rts
9439: a5 9a RestoreTXTPTR lda zpSavedTXTPTR
943b: 85 09 sta zpTXTPTR
943d: a5 9b lda zpSavedTXTPTR+1
943f: 85 0a sta zpTXTPTR+1
9441: 60 rts
; Detokenizes from zpTokenizePtr to lineBuffer (via zpTXTPTR)
; Caller must take care to save/restore previous value of zpTXTPTR
9442: 20 a4 8b DetokenizeLine jsr InitLineBufferScanning ;point zpTXTPTR at start of lineBuffer
DetokenizeLineCont
9445: 20 37 92 jsr TokGetChar ;get the char pointed at by zpTokenizePtr
9448: f0 17 beq Detok_finish ;null terminator? -> copy that to zpTXTPTR and return
944a: c9 20 cmp #$20 ;possible leading byte for number token?
944c: 90 18 bcc DetokNumber ;yes -> translate it
944e: c9 80 cmp #$80 ;non-token, low-value character?
9450: 90 5c bcc Detok_checkHandleDQuot ;yes -> handle possible situations for that
9452: 48 pha ;no, must be keyword token. Push token byte
9453: 20 de 94 jsr Detok_keyword ;expand the token to a keyword
9456: 20 40 92 jsr TokAdvance
9459: 68 pla
945a: c9 95 cmp #tok_REM ;was it the token for REM?
945c: f0 72 beq Detok_copyRemaining ;yes -> copy all remaining chars verbatim
945e: 4c 45 94 jmp DetokenizeLineCont ;no -> continue detokenizing
9461: a9 00 Detok_finish lda #$00 ;unnecessary: we only arrive here if A is already #$00
9463: 4c 78 90 jmp TxtPtrStoreAndAdv
9466: c9 0b DetokNumber cmp #11 ;token >= 11?
9468: b0 0e bcs DetokNumberWord ;yes -> not a single-byte token
946a: 85 28 sta zpWAccum ;no: single-byte token; translate to decimal 0-9
946c: c6 28 dec zpWAccum ;decrement byte to get real value
946e: a9 00 lda #$00
9470: 85 29 sta zpWAccum+1 ;expand byte to word
9472: 20 40 92 jsr TokAdvance ;advance past our single-byte number token
9475: 4c 9b 94 jmp DetokDecimalNum ;and detokenize it
9478: 48 DetokNumberWord pha ;push token byte
9479: 20 34 92 jsr TokAdvAndgetChar ;move number contents into WAccum, and move zpTokenizePtr past them
947c: 85 28 sta zpWAccum
947e: 20 34 92 jsr TokAdvAndgetChar
9481: 85 29 sta zpWAccum+1
9483: 20 40 92 jsr TokAdvance
9486: 68 pla ;pull token byte
9487: c9 11 cmp #tok_HEX_AFTER ;is it for a hex number?
9489: d0 10 bne DetokDecimalNum ;no -> detokenize as decimal
948b: a9 26 lda #‘&’ ;yes: store "&H"
948d: 20 78 90 jsr TxtPtrStoreAndAdv
9490: a9 48 lda #‘H’
9492: 20 78 90 jsr TxtPtrStoreAndAdv
9495: 20 da 91 jsr HexStrFromWord ;and then output the number as hex
9498: 4c 9e 94 jmp CopyNumToTxtPtr
949b: 20 3e 91 DetokDecimalNum jsr PosDecStrFromWord ;get decimal for word at WAccum
949e: 20 d6 90 CopyNumToTxtPtr jsr FindFirstNonzeroDigit ;skip to first non-zero digit
94a1: a6 46 ldx zpTmp46
94a3: b5 3d @cpyNumLp lda zpNumStrTmp,x ;get digit
94a5: f0 9e beq DetokenizeLineCont ;end of number? ->
94a7: 20 78 90 jsr TxtPtrStoreAndAdv ;copy out to zpTXTPTR and advance to next
94aa: e8 inx
94ab: 4c a3 94 jmp @cpyNumLp ;loop around to copy next digit
Detok_checkHandleDQuot
94ae: c9 22 cmp #‘"’ ;double-quote char?
94b0: d0 17 bne Detok_checkHandleApos ;no -> maybe it's an apostrophe
94b2: 20 d8 94 @cpy jsr Detok_copyOneChar ;yes, is quote. Copy until we see another one
94b5: 20 37 92 jsr TokGetChar ;get char
94b8: c9 22 cmp #$22 ;is terminating double-quote?
94ba: f0 07 beq Detok_copyCharContinue ;yes -> copy it and continue normal detokenizing
94bc: 29 ff and #$ff ;terminating null '\0'?
94be: d0 f2 bne @cpy ;no -> loop around, keep copying
94c0: 4c 45 94 jmp DetokenizeLineCont ;yes -> finish up detok
Detok_copyCharContinue
94c3: 20 d8 94 jsr Detok_copyOneChar
94c6: 4c 45 94 jmp DetokenizeLineCont
Detok_checkHandleApos
94c9: c9 27 cmp #‘'’ ;apostrophe (REM shorthand)?
94cb: d0 f6 bne Detok_copyCharContinue ;no -> copy the single character and continue
94cd: 20 d8 94 @cpyLp jsr Detok_copyOneChar ;copy chars until '\0'
Detok_copyRemaining
94d0: 20 37 92 jsr TokGetChar
94d3: d0 f8 bne @cpyLp
94d5: 4c 45 94 jmp DetokenizeLineCont ;finish out detok
Detok_copyOneChar
94d8: 20 3c 92 jsr TokGetCharAndAdv
94db: 4c 78 90 jmp TxtPtrStoreAndAdv
94de: 85 99 Detok_keyword sta zpToken
94e0: a9 ab lda #<tbl_KeywordTokens ;set zpTokTblScan to the start of the tokens table
94e2: 85 54 sta zpTokTblScn
94e4: a9 cc lda #>tbl_KeywordTokens
94e6: 85 55 sta zpTokTblScn+1
94e8: 20 da 93 jsr TokTblGetChar ;get char from table
94eb: c9 ff @chkToken cmp #$ff ;did we reach end of tokens table?
94ed: f0 20 beq rts_950F ;don't replace token with anything
94ef: c5 99 cmp zpToken ;is this the token we're looking for?
94f1: f0 0b beq Detok_copyKeyword ;yes -> copy the keyword out
94f3: 20 df 93 @notOurKwd jsr IncrTokTblScn ;no, not our keyword
94f6: 20 da 93 jsr TokTblGetChar ;skip chars until next token byte
94f9: 10 f8 bpl @notOurKwd
94fb: 4c eb 94 jmp @chkToken ;got token byte -> check if it's ours
Detok_copyKeyword
94fe: 20 df 93 jsr IncrTokTblScn ;move past token byte
9501: 20 da 93 @cpyKwd jsr TokTblGetChar ;get a keyword character
9504: 30 09 bmi rts_950F ;reached next token byte? -> done
9506: 20 78 90 jsr TxtPtrStoreAndAdv ;copy out to zpTXTPTR
9509: 20 df 93 jsr IncrTokTblScn ;move to next keyword character
950c: 4c 01 95 jmp @cpyKwd ;loop until keyword has been copied
950f: 60 rts_950F rts
9510: 20 6e a2 CmdFn_ERROR jsr EvalByteInteger
9513: 4c 37 b2 jmp PrintErrorAndStop
ErrorResumeWithoutErrHandl
9516: a9 13 lda #err_RE
9518: 4c 37 b2 jmp PrintErrorAndStop
951b: a9 00 ErrorNoResume lda #EH_NO_HANDLER
951d: 85 e7 sta zpErrHandStat
951f: a9 12 lda #err_NR
9521: 4c 37 b2 jmp PrintErrorAndStop
9524: 4c 91 84 errSyntax_9524 jmp ErrorSyntax
9527: 08 CmdFn_RESUME php ;push CPU flags
9528: a6 e7 ldx zpErrHandStat ;get err handler status
952a: f0 ea beq ErrorResumeWithoutErrHandl ;no error handler installed? -> error about that
952c: e0 01 cpx #EH_HANDLER_DEFINED ;handler installed, but not called?
952e: f0 eb beq ErrorNoResume ;yes -> error about that
9530: 28 plp ;else, pull flags. Did we get an arg?
9531: f0 20 beq RESUME_restoreContext ;no -> restore and exit
9533: c9 0b cmp #tok_LNUM_AFTER ;else, is arg a line num?
9535: f0 12 beq RESUME_goToLineArg ;yes -> handle
9537: c9 8d cmp #tok_NEXT ;is it the NEXT token?
9539: d0 e9 bne errSyntax_9524 ;no -> no supported args given: ERROR
953b: 20 53 95 jsr RESUME_restoreContext ;yes: restore context (and return to next instr)
953e: 20 9c 84 @skipCmd jsr IsEndOfCmd ;now skip the original offending command (skip to the NEXT one)
9541: f0 2c beq rts_956F ;no -> done
9543: 20 f1 9e jsr SkipToken ;skip the arg, whatever it is
9546: 4c 3e 95 jmp @skipCmd
RESUME_goToLineArg
9549: 20 f0 96 jsr FindLineFromArg ;we're exiting the handler, so go back to "installed" state
954c: a9 01 lda #EH_HANDLER_DEFINED
954e: 85 e7 sta zpErrHandStat ;and go to the asked-for line
9550: 4c c9 96 jmp GoTo
RESUME_restoreContext
9553: a9 01 lda #EH_HANDLER_DEFINED ;we're exiting the handler, so go back to "installed" state
9555: 85 e7 sta zpErrHandStat
9557: a5 f6 lda zpRsmTxtThisCmd
9559: 85 09 sta zpTXTPTR
955b: a5 f7 lda zpRsmTxtThisCmd+1
955d: 85 0a sta zpTXTPTR+1
955f: a5 f8 lda zpRsmRunTxtNxtL
9561: 85 0d sta zpRunTxtNxtLnk
9563: a5 f9 lda zpRsmRunTxtNxtL+1
9565: 85 0e sta zpRunTxtNxtLnk+1
9567: a5 fa lda zpRsmRunLineNum
9569: 85 0b sta zpRunLineNum
956b: a5 fb lda zpRsmRunLineNum+1
956d: 85 0c sta zpRunLineNum+1
956f: 60 rts_956F rts
restoreTxtPtr_9570
9570: 4c 39 94 jmp RestoreTXTPTR
9573: 20 7d a2 CmdFn_FIND jsr EvalString ;evaluate string argument (to expr parse stack)
9576: 20 30 94 jsr SaveTXTPTR ;save away current TXTPTR
9579: a5 05 lda zpTXTTAB
957b: 85 09 sta zpTXTPTR ;reset TXTPTR to start of program
957d: a5 06 lda zpTXTTAB+1
957f: 85 0a sta zpTXTPTR+1
9581: 20 b2 90 FIND_linesLoop jsr GetTxtChar ;are we at program end?
9584: f0 ea beq restoreTxtPtr_9570 ;restore original TXTPTR and exit
9586: 18 clc
9587: a5 09 lda zpTXTPTR
9589: 69 03 adc #$03 ;set zpTokenizePtr to TXTPTR + 3 (skipping next-line offset, line num bytes)
958b: 85 4d sta zpTokenizePtr
958d: a9 00 lda #$00
958f: 65 0a adc zpTXTPTR+1
9591: 85 4e sta zpTokenizePtr+1
9593: a5 09 lda zpTXTPTR
9595: 48 pha ;push value of TXTPTR to CPU stack
9596: a5 0a lda zpTXTPTR+1
9598: 48 pha
9599: 20 42 94 jsr DetokenizeLine ;detokenize this line into lineBuffer
959c: 68 pla ; (wouldn't it be more efficient to tokenize FIND contents and search for that instead?)
959d: 85 0a sta zpTXTPTR+1 ;pull TXTPTR back off stack (detokenize probably overwrote)
959f: 68 pla
95a0: 85 09 sta zpTXTPTR
95a2: ad 00 04 lda exprParseStk ;put FIND arg string info into WParam
95a5: 85 2c sta zpWParam
95a7: ad 01 04 lda exprParseStk+1
95aa: 85 2d sta zpWParam+1
95ac: ad 02 04 lda exprParseStk+2
95af: 85 2e sta zpWParam+2
95b1: a9 00 lda #<lineBuffer ;put detokenized current line info into WAccum+1
95b3: 85 29 sta zpWAccum+1
95b5: a9 05 lda #>lineBuffer
95b7: 85 2a sta zpWAccum+2
95b9: a0 00 ldy #$00 ;init Y reg
95bb: b1 29 @countLp lda (zpWAccum+1),y ;determine length of detokenized line,
95bd: f0 03 beq @lineEnd ; by counting chars in Y reg, until we reach a terminating null
95bf: c8 iny ;increment Y
95c0: d0 f9 bne @countLp ;(always) -> loop, keep counting chars
95c2: 84 28 @lineEnd sty zpWAccum ;save Y-reg (character count) as strlen for WAccum
95c4: 20 e6 95 jsr CheckInStr ;check whether user str is contained within detokenized line
95c7: f0 09 beq @nextLine ;no match? -> advance and check next line
95c9: 20 ee 87 jsr PrintLineNumAndDetok ;match; print the line # and (re-)detokenized contents
95cc: 20 c4 87 jsr PauseOnEsc ;pause if Esc is being pressed
95cf: 4c 81 95 jmp FIND_linesLoop ;-> check for matches in next line
95d2: 20 b2 90 @nextLine jsr GetTxtChar ;get offset to next line of BASIC
95d5: 18 clc
95d6: 65 09 adc zpTXTPTR ;add to TXTPTR (point it at next line)
95d8: 85 09 sta zpTXTPTR
95da: a9 00 lda #$00
95dc: 65 0a adc zpTXTPTR+1
95de: 85 0a sta zpTXTPTR+1
95e0: 4c 81 95 jmp FIND_linesLoop ;-> and loop back around to check for matches
CIS_matchFailedRetZero
95e3: a9 00 lda #$00
95e5: 60 rts
; Checks if the string identified by WParam is contained within the string
; identified by WAccum. Returns the index to the first occurrance, if found, or
; zero if not found.
vZpHayStkRemn .var $19 {addr/1}
vZpNeedleRemn .var $1b {addr/1} ;how many chars left to haystack we can index
vZpHayStkIdx .var $6b {addr/1}
vZpNeedleIdx .var $6c {addr/1}
95e6: a9 00 CheckInStr lda #$00
95e8: 85 6b sta vZpHayStkIdx ;initialize indices into haystack and needle strings
95ea: 85 6c sta vZpNeedleIdx
95ec: a5 28 lda zpWAccum ;get haystack string length
95ee: 85 19 sta vZpHayStkRemn ;save as how many characters left in haystack, to match
95f0: c5 2c cmp zpWParam ;are there fewer chars remaining to haystack than are in needle?
95f2: 90 ef bcc CIS_matchFailedRetZero ;yes -> search failed, match impossible now
CIS_tryNextHayStkChar
95f4: a5 2c lda zpWParam ;get needle string length
95f6: 85 1b sta vZpNeedleRemn ;save as how many chars left in needle, to compare
95f8: a5 6b lda vZpHayStkIdx ;get current index into needle
95fa: 48 pha ; and push to CPU stack
95fb: a4 6b @matchLp ldy vZpHayStkIdx
95fd: b1 29 lda (zpWAccum+1),y ;get char from haystack
95ff: a4 6c ldy vZpNeedleIdx
9601: d1 2d cmp (zpWParam+1),y ;does it match char in needle?
9603: d0 10 bne @restartNextHayStkChar ;no -> reset search at next haystack char
9605: c6 1b dec vZpNeedleRemn ;decrement # chars remaining in check in needle
9607: f0 07 beq @matchSucc ;out of needle chars? -> return the index!
9609: e6 6b inc vZpHayStkIdx ;increment the character indices
960b: e6 6c inc vZpNeedleIdx
960d: 4c fb 95 jmp @matchLp ;loop back around to check next needle/haystack chars
9610: 68 @matchSucc pla ;pull index to start of match within haystack
9611: 18 clc
9612: 69 01 adc #$01 ;add one to it (cvt from 0-based to 1-based indexing)
9614: 60 rts ;return
@restartNextHayStkChar
9615: 68 pla ;pull (aborted) start of match in haystack
9616: 85 6b sta vZpHayStkIdx ;set as haystack index again
9618: e6 6b inc vZpHayStkIdx ;and increment to start searching again at next char
961a: a9 00 lda #$00
961c: 85 6c sta vZpNeedleIdx ;reset needle index to zero
961e: c6 19 dec vZpHayStkRemn ;one less haystack char remaining to be checked
9620: f0 c1 beq CIS_matchFailedRetZero ; == 0? -> no more matches possible; indicate match failure
9622: 4c f4 95 jmp CIS_tryNextHayStkChar ;else, restart matching at the new haystack position
; Turn keyclicks on or off. Pulls some funny tricks to reuse the same ON/OFF
; check logic as CmdFn_TR (just below)
• Clear variables
vZpAmClick .var $74 {addr/1} ;flag to indicate whether we're handling "CLICK ON/OFF" or "TR ON/OFF"
9625: a0 ff CmdFn_CLICK ldy #$ff ;indicate we're CLICK
9627: 85 74 sta vZpAmClick
9629: 30 04 bmi OnOff ;check arg for ON/OFF
; For TRON, TROFF commands. Pulls some funny tricks to let CmdFn_CLICK (above)
; reuse the same ON/OFF check logic
962b: a0 00 CmdFn_TR ldy #$00 ;indicate we're TR--
962d: 84 74 sty vZpAmClick
962f: c9 9a OnOff cmp #tok_ON ;check arg for ON/OFF
9631: f0 07 beq @on ;ON? -> load #$FF
9633: c9 9b cmp #tok_OFF ;OFF?
9635: d0 6c bne errSyn_96A3 ;neither ON nor OFF -> error
9637: a9 00 lda #$00 ;OFF. Load #$00
9639: 2c bit ▼ $ffa9 ;skip next instr
963a: a9 ff @on lda #$ff
963c: a4 74 ldy vZpAmClick ;are we TR--?
963e: f0 03 beq @tr ;yes -> store in the tr flag
9640: 85 e3 sta zpKeyClickFlg ;otherwise, store in the key-click flag
9642: 2c bit ▼ __ ;skip next instr
9643: 85 97 @tr sta zpTraceOnFlag
9645: 4c 8c 90 jmp TxtPtrIncr
• Clear variables
9648: aa CmdFn_GOSUB tax ;move acc value to X-reg for a bit
9649: 68 pla
964a: 85 5f sta zpSavedRts ;pull return address and save it
964c: 68 pla
964d: 85 60 sta zpSavedRts+1
964f: 8a txa
9650: 20 f0 96 jsr FindLineFromArg ;find the line number for the argument
9653: 20 56 9b jsr SkipLineNumArgs ;and skip TXTPTR past it
9656: a5 09 lda zpTXTPTR
9658: 48 pha ;push this TXTPTR addr
9659: a5 0a lda zpTXTPTR+1
965b: 48 pha
965c: a5 0b lda zpRunLineNum
965e: 48 pha ;push the current line number
965f: a5 0c lda zpRunLineNum+1
9661: 48 pha
9662: a5 0d lda zpRunTxtNxtLnk
9664: 48 pha ;push the link to the following line
9665: a5 0e lda zpRunTxtNxtLnk+1
9667: 48 pha
9668: a9 00 lda #>GOSUB_STK_MARKER
966a: 48 pha ;push special #$FF00 marker indicating a GOSUB
966b: a9 ff lda #GOSUB_STK_MARKER
966d: 48 pha
966e: a5 60 lda zpSavedRts+1
9670: 48 pha ;now re-push our return address, so the return still works
9671: a5 5f lda zpSavedRts
9673: 48 pha
9674: 4c c9 96 jmp GoTo ;and jump to the line in argument
ErrorReturnWithoutGosub
9677: a9 02 lda #err_RG
9679: 4c 37 b2 jmp PrintErrorAndStop
967c: 68 RETURN_noArg pla ;pull up our return address and save aside
967d: 85 5f sta zpSavedRts
967f: 68 pla
9680: 85 60 sta zpSavedRts+1
9682: 68 pla ;pull up next byte
9683: c9 ff cmp #GOSUB_STK_MARKER ;does it match our marker byte?
9685: d0 f0 bne ErrorReturnWithoutGosub ;no -> error. Most recent stack-y thing wasn't GOSUB.
9687: 68 pla ;next marker byte match?
9688: d0 ed bne ErrorReturnWithoutGosub ;no -> error
968a: 68 pla ;yes: we can RETURN
968b: 85 0e sta zpRunTxtNxtLnk+1 ;pull up link to next BASIC line, at time of GOSUB
968d: 68 pla ; and set it
968e: 85 0d sta zpRunTxtNxtLnk
9690: 68 pla ;pull up line number we GOSUB'd from
9691: 85 0c sta zpRunLineNum+1 ;and set that
9693: 68 pla
9694: 85 0b sta zpRunLineNum
9696: 68 pla ;pull BASIC program text location, and set
9697: 85 0a sta zpTXTPTR+1
9699: 68 pla
969a: 85 09 sta zpTXTPTR
RestoreReturnAddrAndRts
969c: a5 60 lda zpSavedRts+1
969e: 48 pha ;finally, plop this routine's return address back down
969f: a5 5f lda zpSavedRts
96a1: 48 pha
96a2: 60 rts ;and return to it
96a3: 4c 91 84 errSyn_96A3 jmp ErrorSyntax
96a6: f0 d4 CmdFn_RETURN beq RETURN_noArg ;no args? -> normal return
96a8: 68 pla ;have a linenum arg. Pull stack items, but don't restore
96a9: 85 5f sta zpSavedRts ;first, pull up our own return addr
96ab: 68 pla
96ac: 85 60 sta zpSavedRts+1
96ae: 68 pla
96af: c9 ff cmp #$ff ;did we get the GOSUB mark?
96b1: d0 c4 bne ErrorReturnWithoutGosub ;no -> error
96b3: 68 pla ;2nd byte of GOSUB mark?
96b4: d0 c1 bne ErrorReturnWithoutGosub ;no -> error
96b6: ba tsx
96b7: 8a txa
96b8: 18 clc
96b9: 69 06 adc #$06
96bb: aa tax
96bc: 9a txs ;pop 6 state items off the stack, ignoring them
96bd: a5 60 lda zpSavedRts+1
96bf: 48 pha ;plop our return addr back down
96c0: a5 5f lda zpSavedRts
96c2: 48 pha
96c3: 20 9c 84 jsr IsEndOfCmd ;and fall through to GOTO.
96c6: 20 f0 96 CmdFn_GOTO jsr FindLineFromArg
96c9: a0 00 GoTo ldy #$00
96cb: b1 5b lda (zpTxtLink),y ;get offset to next line
96cd: 18 clc
96ce: 65 5b adc zpTxtLink ;add to current line ptr
96d0: 85 0d sta zpRunTxtNxtLnk
96d2: a9 00 lda #$00
96d4: 65 5c adc zpTxtLink+1
96d6: 85 0e sta zpRunTxtNxtLnk+1 ;and store in zpRunTxtNxtLnk
96d8: c8 iny
96d9: b1 5b lda (zpTxtLink),y ;save line number as current
96db: 85 0b sta zpRunLineNum
96dd: c8 iny
96de: b1 5b lda (zpTxtLink),y
96e0: 85 0c sta zpRunLineNum+1
96e2: 18 clc
96e3: a9 03 lda #$03 ;add 3 to start of the line (past offset, line num), and set that as TXTPTR
96e5: 65 5b adc zpTxtLink
96e7: 85 09 sta zpTXTPTR
96e9: a9 00 lda #$00
96eb: 65 5c adc zpTxtLink+1
96ed: 85 0a sta zpTXTPTR+1
96ef: 60 rts ;get to executin'!
; Verifies that token at TXTPTR is a line number, and parses it.
; Does not consume it (leaves TXTPTR where it is).
96f0: c9 0b FindLineFromArg cmp #tok_LNUM_AFTER ;is arg designated as line number?
96f2: d0 af bne errSyn_96A3 ;no -> error
96f4: a0 01 ldy #$01
96f6: b1 09 lda (zpTXTPTR),y ;set zpFindLNum = arg
96f8: 85 44 sta zpFindLNum
96fa: c8 iny
96fb: b1 09 lda (zpTXTPTR),y
96fd: 85 45 sta zpFindLNum+1
96ff: 20 40 85 jsr FindLineNum ;and find the indicated line
9702: d0 01 bne ErrorUndefLineNum ;error if it doesn't exist
9704: 60 rts_9704 rts
ErrorUndefLineNum
9705: a9 07 lda #err_UL
9707: 4c 37 b2 jmp PrintErrorAndStop
970a: 20 83 a2 CmdFn_CALL jsr FrmEval ;evaluate argument
970d: 6c 00 04 jmp (exprParseStk) ;then, just jump thru the zp ptr to the expr parse stack!
9710: 4c 94 84 errIll_9710 jmp ErrorIllegalValue
9713: 4c 20 bc readChar_9713 jmp ReadChar
vZpPauseCtr .var $74 {addr/1}
9716: f0 fb CmdFn_PAUSE beq readChar_9713 ;no arg? -> just pause until char press
9718: a9 00 lda #$00
971a: 85 74 sta vZpPauseCtr
971c: 20 83 a2 jsr FrmEval ;eval integer argument
971f: 20 0d aa jsr ErrorUnlessInteger
9722: ad 01 04 lda exprParseStk+1
9725: 30 e9 bmi errIll_9710 ;negative number? -> error
9727: d0 05 bne @notZero ;do pause
9729: ad 00 04 lda exprParseStk ;zero. Is low byte also zero?
972c: f0 d6 beq rts_9704 ;yes -> no pause then
972e: ee 01 04 @notZero inc exprParseStk+1 ;incr high byte
9731: 20 55 88 @pauseLp jsr StopIfStopKey ;check for and handle STOP key during PAUSE
9734: c6 74 dec vZpPauseCtr
9736: d0 f9 bne @pauseLp ;loop around STOP check 256 times
9738: ce 00 04 dec exprParseStk ;decrement low byte user arg,
973b: d0 f4 bne @pauseLp ;loop for another 256
973d: ad 01 04 lda exprParseStk+1 ;low byte zero
9740: f0 05 beq @rts ;(NEVER) all zero -> exit PAUSE
9742: ce 01 04 dec exprParseStk+1 ;high byte not zero, decrement and do 65k more iterations
9745: d0 ea bne @pauseLp ; unless dec went to zero, in which case return
9747: 60 @rts rts
• Clear variables
9748: 20 6e a2 CmdFn_LOCATE jsr EvalByteInteger ;eval first arg
974b: c9 1c cmp #COLS ; >= num cols?
974d: b0 c1 bcs errIll_9710 ;yes -> too big
974f: 85 6b sta zpSavedCH ;save
9751: 20 6b a2 jsr EvalCommaThenByteInt ;comma, then second arg
9754: c9 18 cmp #ROWS ; >= num rows
9756: b0 b8 bcs errIll_9710 ;yes -> too big
9758: 85 6c sta zpSavedCV ;save
975a: 4c 5f b6 jmp RestoreCursorPos ;"restore" saved pos to actual cursor pos
975d: 20 83 a2 CmdFn_POKE jsr FrmEval ;eval arg (addr)
9760: ad 00 04 lda exprParseStk
9763: 85 25 sta zpPokeAddr ;and save it to zero page
9765: ad 01 04 lda exprParseStk+1
9768: 85 26 sta zpPokeAddr+1
976a: a0 00 ldy #$00
976c: 84 46 sty zpTmp46 ;init write index to 0
976e: 20 6b a2 @wrLoop jsr EvalCommaThenByteInt ;comma, then write val arg
9771: a4 46 ldy zpTmp46
9773: 91 25 sta (zpPokeAddr),y ;write val to requested addr
9775: e6 46 inc zpTmp46 ;increment addr to next byte
9777: 20 9c 84 jsr IsEndOfCmd ;another arg?
977a: d0 f2 bne @wrLoop ;yes -> write arg to next byte
977c: 60 rts ;no: done
977d: 68 IF_noEval pla ;pull up return addr from CPU stack
977e: 68 pla
977f: 4c 69 84 jmp ExecSkipToNextLine ;and skip to next line (skips colon stmts too)
9782: 20 83 a2 CmdFn_IF jsr FrmEval ;evaluate condition expression
9785: ad 00 04 __ lda exprParseStk
9788: 0d 01 04 ora exprParseStk+1
978b: f0 f0 beq IF_noEval ;cond == zero? -> don't evaluate THEN
978d: 20 9c 84 jsr IsEndOfCmd ;does the cmd end after the condition?
9790: f0 61 beq errSyn_97F3 ;yes -> syntax error
9792: c9 85 cmp #tok_THEN ;got a THEN token?
9794: f0 04 beq @gotThen ;yes -> handle
9796: 60 rts
9797: 4c c6 96 goto_9797 jmp CmdFn_GOTO
979a: 20 99 84 @gotThen jsr AdvThenIsEndOfCmd ;after the THEN, does the IF cmd end?
979d: f0 be beq CmdFn_POKE ;yes -> a very roundabout way to a syntax error...
979f: c9 0b cmp #$0b ;else, did we get a line number?
97a1: f0 f4 beq goto_9797 ;yes -> GOTO it
97a3: 60 rts ;else, return and execute following command (same as no THEN)
97a4: d0 25 CmdFn_CLEAR bne ClearAndSet ;got arg? -> handle
97a6: a5 08 Clear lda zpTXTEND+1 ;set zpVARTAB = zpTXTEND + 1
97a8: 85 1e sta zpVARTAB+1
97aa: a6 07 ldx zpTXTEND
97ac: e8 inx
97ad: d0 02 bne @vtNoHi
97af: e6 1e inc zpVARTAB+1
97b1: 86 1d @vtNoHi stx zpVARTAB
97b3: a5 1e lda zpVARTAB+1 ;set zpVAREND = zpVARTAB + 1
97b5: 85 20 sta zpVAREND+1
97b7: a6 1d ldx zpVARTAB
97b9: e8 inx
97ba: d0 02 bne @veNoHi
97bc: e6 20 inc zpVAREND+1
97be: 86 1f @veNoHi stx zpVAREND
97c0: a0 00 ldy #$00
97c2: 98 tya
97c3: 91 1d sta (zpVARTAB),y ;set bytes at both those locations to 0
97c5: c8 iny
97c6: 91 1d sta (zpVARTAB),y
97c8: 4c e3 9e jmp RestoreFromStart
97cb: 20 a6 97 ClearAndSet jsr Clear ;do the normal CLEAR stuff first, then handle arg
97ce: 20 83 a2 jsr FrmEval ;got back from main CLEAR. Evaluate the arg.
97d1: 20 0d aa jsr ErrorUnlessInteger ;make sure it's an int
97d4: ad 01 04 lda exprParseStk+1
97d7: c9 70 cmp #$70 ;arg >= #$7000?
97d9: b0 1b bcs errIllVal_97F6 ;yes -> error. That's past available RAM.
97db: c5 20 cmp zpVAREND+1 ;arg < zpVAREND? (in the high bytes)
97dd: 90 17 bcc errIllVal_97F6 ;yes -> error. We're already using more space than that.
97df: d0 07 bne @veNotEq ;arg > zpVAREND? -> skip next check
97e1: ad 00 04 lda exprParseStk ;high bytes were exactly equal
97e4: c5 1f cmp zpVAREND ;check low bytes
97e6: 90 0e bcc errIllVal_97F6 ;arg < zpVAREND? -> error. Below already-used space
97e8: ad 00 04 @veNotEq lda exprParseStk ;we're good: set the end of BASIC "Free space" to the arg.
97eb: 85 03 sta zpFRETOP
97ed: ad 01 04 lda exprParseStk+1
97f0: 85 04 sta zpFRETOP+1
97f2: 60 rts
97f3: 4c 91 84 errSyn_97F3 jmp ErrorSyntax
97f6: 4c 94 84 errIllVal_97F6 jmp ErrorIllegalValue
errTypeMism_97F9
97f9: 4c ff a9 jmp ErrorTypeMismatch
vZpForIdxVarPtr .var $7c {addr/2} ;pointer to the value of the index variable
vZpForLineNum .var $86 {addr/2}
vZpForSvTXTPTR .var $88 {addr/2}
97fc: 68 CmdFn_FOR pla
97fd: 85 5f sta zpSavedRts
97ff: 68 pla
9800: 85 60 sta zpSavedRts+1 ;pull up return address, save it aside
9802: 20 bf 90 jsr TxtSkipSpaces
9805: 20 76 a5 jsr EvalVarNameOrSynErr ;evaluate index var
9808: a5 90 lda zpVarType
980a: c9 02 cmp #TYP_INT ;index var is int?
980c: d0 eb bne errTypeMism_97F9 ;no -> error! type mismatch
980e: a5 8c lda zpVarData
9810: 85 7c sta vZpForIdxVarPtr ;save pointer to index var data
9812: 48 pha ;and push it (two bytes) onto CPU stack
9813: a5 8d lda zpVarData+1
9815: 85 7d sta vZpForIdxVarPtr+1
9817: 48 pha
9818: 20 d9 86 jsr SkipEqualsOrErr ;skip '=' that follows index var
981b: 20 83 a2 jsr FrmEval ;evaluate initial value for index (BUG: not type checked!)
981e: 68 pla ;pull index var data poiner from stack, restore it
981f: 85 8d sta zpVarData+1
9821: 68 pla
9822: 85 8c sta zpVarData
9824: a0 00 ldy #$00
9826: ad 00 04 lda exprParseStk ;copy initializer value to index var
9829: 91 8c sta (zpVarData),y
982b: ad 01 04 lda exprParseStk+1
982e: c8 iny
982f: 91 8c sta (zpVarData),y
9831: 20 bf 90 jsr TxtSkipSpaces
9834: c9 88 cmp #tok_TO ;do we have TO token next?
9836: d0 bb bne errSyn_97F3 ;no -> syntax error
9838: 20 8c 90 jsr TxtPtrIncr
983b: 20 83 a2 jsr FrmEval ;evaluate limiting expression (BUG: not type checked!)
983e: ad 00 04 lda exprParseStk
9841: 85 80 sta zpForLimitVal ;save the limiting value
9843: ad 01 04 lda exprParseStk+1
9846: 85 81 sta zpForLimitVal+1
9848: a9 01 lda #$01 ;store default STEP value (1) at expr stack top
984a: 8d 00 04 sta exprParseStk
984d: a9 00 lda #$00
984f: 8d 01 04 sta exprParseStk+1
9852: 20 9c 84 jsr IsEndOfCmd ;end of FOR command?
9855: f0 0a beq @noStep ;yes -> stick with default STEP value
9857: c9 89 cmp #tok_STEP ;got a STEP token?
9859: d0 98 bne errSyn_97F3 ;no -> syntax error
985b: 20 8c 90 jsr TxtPtrIncr
985e: 20 83 a2 jsr FrmEval ;evaluate STEP value
9861: ad 00 04 @noStep lda exprParseStk
9864: 85 82 sta zpForStepVal ;save STEP value (whether given or default)
9866: ad 01 04 lda exprParseStk+1
9869: 85 83 sta zpForStepVal+1
986b: a5 09 lda zpTXTPTR ;save TXTPTR for pushing to stack
986d: 85 88 sta vZpForSvTXTPTR
986f: a5 0a lda zpTXTPTR+1
9871: 85 89 sta vZpForSvTXTPTR+1
9873: a5 0d lda zpRunTxtNxtLnk
9875: 48 pha ;push link to next line of BASIC (2 bytes)
9876: a5 0e lda zpRunTxtNxtLnk+1
9878: 48 pha
9879: a5 0b lda zpRunLineNum ;push current line number (2 bytes)
987b: 48 pha
987c: a5 0c lda zpRunLineNum+1
987e: 48 pha
987f: 4c df 98 jmp FOR_NEXT_PushStuffAndRts ;push more context, restore return value, and return to it
ErrorNextWithoutFor
9882: a9 00 lda #err_NF
9884: 4c 37 b2 jmp PrintErrorAndStop
9887: 68 CmdFn_NEXT pla ;pull up caller routine's return address and save it aside
9888: 85 5f sta zpSavedRts
988a: 68 pla
988b: 85 60 sta zpSavedRts+1
988d: 68 pla ;pull up next byte
988e: c9 fd cmp #FOR_STK_MARKER ;is it a FOR marker byte?
9890: d0 f0 bne ErrorNextWithoutFor ;no -> error
9892: 68 pla ;pull up second marker byte
9893: d0 ed bne ErrorNextWithoutFor ;not match? -> error
9895: 68 pla
9896: 85 89 sta vZpForSvTXTPTR+1 ;pull up TXTPTR value for just-after the FOR statement
9898: 68 pla
9899: 85 88 sta vZpForSvTXTPTR
989b: 68 pla
989c: 85 83 sta zpForStepVal+1 ;pull up STEP increment value
989e: 68 pla
989f: 85 82 sta zpForStepVal
98a1: 68 pla
98a2: 85 81 sta zpForLimitVal+1 ;pull up FOR limit expression value
98a4: 68 pla
98a5: 85 80 sta zpForLimitVal
98a7: 68 pla
98a8: 85 7d sta vZpForIdxVarPtr+1 ;pull up pointer to the index var's value
98aa: 68 pla
98ab: 85 7c sta vZpForIdxVarPtr
98ad: 68 pla
98ae: 85 87 sta vZpForLineNum+1 ;pull line number
98b0: 68 pla
98b1: 85 86 sta vZpForLineNum
98b3: 68 pla
98b4: 85 85 sta zpForNxtLnLnk+1 ;pull link to the next line of BASIC after FOR statement
98b6: 68 pla
98b7: 85 84 sta zpForNxtLnLnk
98b9: a0 00 ldy #$00
98bb: b1 7c lda (vZpForIdxVarPtr),y ;copy index var's value into WAccum
98bd: 85 28 sta zpWAccum
98bf: c8 iny
98c0: b1 7c lda (vZpForIdxVarPtr),y
98c2: 85 29 sta zpWAccum+1
98c4: 20 04 99 jsr NEXT_doStep
98c7: f0 02 beq @eq ;limit expr less than index var?
98c9: 90 36 bcc restoreRtnAddrAndRts_9901 ;yes -> FOR loop is done!
98cb: a5 84 @eq lda zpForNxtLnLnk ;push link to line of BASIC after current FOR statement
98cd: 48 pha
98ce: 85 0d sta zpRunTxtNxtLnk ;and set it as current next line
98d0: a5 85 lda zpForNxtLnLnk+1
98d2: 48 pha
98d3: 85 0e sta zpRunTxtNxtLnk+1
98d5: a5 86 lda vZpForLineNum ;push line number of FOR statement
98d7: 48 pha
98d8: 85 0b sta zpRunLineNum ;and set as current line number
98da: a5 87 lda vZpForLineNum+1
98dc: 48 pha
98dd: 85 0c sta zpRunLineNum+1
FOR_NEXT_PushStuffAndRts
98df: a5 7c lda vZpForIdxVarPtr ;push the pointer to the contents of the index variable (2 bytes)
98e1: 48 pha
98e2: a5 7d lda vZpForIdxVarPtr+1
98e4: 48 pha
98e5: a5 80 lda zpForLimitVal ;push the FOR limitnig value (2 bytes)
98e7: 48 pha
98e8: a5 81 lda zpForLimitVal+1
98ea: 48 pha
98eb: a5 82 lda zpForStepVal ;push the STEP increment value (2 bytes)
98ed: 48 pha
98ee: a5 83 lda zpForStepVal+1
98f0: 48 pha
98f1: a5 88 lda vZpForSvTXTPTR ;push the TXTPTR value for just-after the FOR statement (2 bytes)
98f3: 48 pha
98f4: 85 09 sta zpTXTPTR ;and also restore it as current TXTPTR value
98f6: a5 89 lda vZpForSvTXTPTR+1
98f8: 48 pha
98f9: 85 0a sta zpTXTPTR+1
98fb: a9 00 lda #>FOR_STK_MARKER ;push the FOR marker bytes (2)
98fd: 48 pha
98fe: a9 fd lda #FOR_STK_MARKER
9900: 48 pha
restoreRtnAddrAndRts_9901
9901: 4c 9c 96 jmp RestoreReturnAddrAndRts ;-> now restore the saved return address, and return to it
9904: a5 83 NEXT_doStep lda zpForStepVal+1 ;is the STEP value negative?
9906: 30 0c bmi @negStep ;yes -> handle negative STEP (no reversal)
9908: 20 1a 99 jsr @doStep ;evaluate STEP
990b: 20 ee 8f jsr Compare ;compare updated index var to limit
990e: b0 02 bcs @clcRts ;reverse the carry bit/result of comparison
9910: 38 sec
9911: 60 rts ;and return
9912: 18 @clcRts clc
9913: 60 rts
9914: 20 1a 99 @negStep jsr @doStep ;advance index var by STEP val
9917: 4c ee 8f jmp Compare ;-> compare new index var to limit value, and return
991a: a5 82 @doStep lda zpForStepVal ;copy step value to WParam
991c: 85 2c sta zpWParam
991e: a5 83 lda zpForStepVal+1
9920: 85 2d sta zpWParam+1
9922: 20 a8 8e jsr SetIntAndAdd ;add step to index var
9925: a0 00 ldy #$00
9927: a5 28 lda zpWAccum
9929: 91 7c sta (vZpForIdxVarPtr),y ;set that as new value for index var (I += STEP)
992b: c8 iny
992c: a5 29 lda zpWAccum+1
992e: 91 7c sta (vZpForIdxVarPtr),y
9930: a5 80 lda zpForLimitVal ;load limit value into WParam (for Compare)
9932: 85 2c sta zpWParam
9934: a5 81 lda zpForLimitVal+1
9936: 85 2d sta zpWParam+1
9938: 60 rts_9938 rts ;-> return (Compare will be next)
9939: 4c 91 84 errSyntasx_9939 jmp ErrorSyntax
• Clear variables
vZpStrLen .var $1b {addr/1}
vZpSkipChars .var $52 {addr/1}
vZpStartCV .var $7e {addr/1}
vZpStartCH .var $80 {addr/1}
993c: f0 fb CmdFn_INPUT beq errSyntasx_9939 ;end of cmd? -> error
993e: c9 22 cmp #‘"’ ;got a double-quote?
9940: d0 0b bne @prQMark ;no -> skip handling it then
9942: 20 51 9a jsr INPUT_printStr ;else, print the string
9945: c9 2c cmp #‘,’ ;got following comma?
9947: f0 09 beq @skipQMark ;yes -> skip printing question mark
9949: c9 3b cmp #‘;’ ;else got semicolon?
994b: d0 ec bne errSyntasx_9939 ;no -> error
994d: a9 3f @prQMark lda #‘?’
994f: 20 75 b5 jsr PrintOneChar ;print '?'
9952: a5 49 @skipQMark lda zpCV ;save current cursor position at start of input
9954: 85 7e sta vZpStartCV
9956: a5 48 lda zpCH
9958: 85 80 sta vZpStartCH
995a: 20 6c bd jsr INPUT_readLine ;read the input line from user
995d: 20 55 88 jsr StopIfStopKey ;honor stop key if it's pressed
9960: a9 00 lda #$00
9962: 85 52 sta vZpSkipChars
9964: a5 7e @adjForScrlLp lda vZpStartCV
9966: c5 47 cmp zpLineStartRow ;does our notion of starting row match the input buffer routine's?
9968: f0 0b beq @atStartRow ;yes -> only have to deal with the one line
996a: c6 7e dec vZpStartCV ;no: screen scrolled since we started typing
996c: 18 clc
996d: a9 1c lda #COLS
996f: 65 52 adc vZpSkipChars ;vZpSkipChars += COLS
9971: 85 52 sta vZpSkipChars ; (for each line we scrolled)
9973: d0 ef bne @adjForScrlLp ;(always) -> check if we scrolled another line, too
9975: a5 80 @atStartRow lda vZpStartCH
9977: 18 clc
9978: 65 52 adc vZpSkipChars
997a: 85 52 sta vZpSkipChars ;also skip the number of chars up until
997c: 20 bf 90 INPUT_getVar jsr TxtSkipSpaces ; the column input started at.
997f: 20 76 a5 jsr EvalVarNameOrSynErr ;find (or create) the variable named next (after optional string literal)
9982: 20 30 94 jsr SaveTXTPTR ;save current TXTPTR
9985: a5 52 lda vZpSkipChars
9987: 85 09 sta zpTXTPTR ;skip the determined # of chars at start of lineBuffer
9989: a9 05 lda #>lineBuffer
998b: 85 0a sta zpTXTPTR+1 ;and point TXTPTR at it
998d: a5 90 lda zpVarType ;get the type of the variable we parsed
998f: 29 0f and #$0f ;(basic type, if array)
9991: c9 03 cmp #TYP_STR ;string type?
9993: f0 40 beq INPUT_stringVar ;yes -> handle string
9995: 20 2d ab jsr READ_parseNumber ;no, parse a number
9998: a5 09 lda zpTXTPTR ;save current TXTPTR
999a: 85 52 sta vZpSkipChars ; so it gets restored when we loop back around
999c: a0 00 ldy #$00
999e: a5 28 lda zpWAccum
99a0: 91 8c sta (zpVarData),y ;save parsed number into variable
99a2: c8 iny
99a3: a5 29 lda zpWAccum+1
99a5: 91 8c sta (zpVarData),y
INPUT_checkNextArg
99a7: 20 39 94 jsr RestoreTXTPTR ;restore TXTPTR to point at INPUT args again
99aa: 20 bf 90 jsr TxtSkipSpaces
99ad: c9 2c cmp #‘,’ ;got a comma?
99af: d0 87 bne rts_9938 ;no -> done with INPUT
99b1: 20 8c 90 jsr TxtPtrIncr ; (doesn't check if there's leftover data in the string!)
99b4: 20 30 94 jsr SaveTXTPTR ;yes. Save TXTPTR to just-past the comma
99b7: a5 52 lda vZpSkipChars ;point TXTPTR back into the input string again
99b9: 85 09 sta zpTXTPTR
99bb: a9 05 lda #>lineBuffer
99bd: 85 0a sta zpTXTPTR+1
99bf: a0 00 ldy #$00
99c1: b1 09 lda (zpTXTPTR),y
99c3: c9 2c cmp #‘,’ ;is next char of string a comma?
99c5: d0 08 bne @restoreAndPrQMark ;no -> print question mark and return to evaluating BASIC
99c7: e6 52 inc vZpSkipChars ;move past the string's comma
99c9: 20 39 94 jsr RestoreTXTPTR ;return TXTPTR to BASIC so we can parse var name
99cc: 4c 7c 99 jmp INPUT_getVar ;-> and loop back around to assign stuff to that next var
@restoreAndPrQMark
99cf: 20 39 94 jsr RestoreTXTPTR ;restore TXTPTR to point into BASIC program again
99d2: 4c 4d 99 jmp @prQMark ;print question mark, and return
99d5: 20 bf 90 INPUT_stringVar jsr TxtSkipSpaces ;skip leading spaces in input string
99d8: a9 00 lda #$00
99da: 85 1b sta vZpStrLen ;initialize str len for str var arg
99dc: a8 tay ;init Y reg
99dd: aa tax ;init X reg
99de: b1 09 @cpyGenPBufLp lda (zpTXTPTR),y ;read a char from input string
99e0: f0 10 beq @cpyStrVar ;end of string? -> copy to var
99e2: c9 2c cmp #‘,’
99e4: f0 0c beq @cpyStrVar ;comma? -> also copy out var
99e6: 9d 00 07 sta genPurposeBuf,x ;else store in genPurposeBuf
99e9: e6 1b inc vZpStrLen ;advance string length
99eb: e8 inx ;advance to next char in input str
99ec: 20 8c 90 jsr TxtPtrIncr ;and in genPurposeBuf
99ef: 4c de 99 jmp @cpyGenPBufLp ;loop for next character in input line
99f2: a5 1b @cpyStrVar lda vZpStrLen ;set copied string in genPurposeBuf
99f4: 8d 00 04 sta exprParseStk ; as top of expr stack
99f7: a9 00 lda #<genPurposeBuf
99f9: 8d 01 04 sta exprParseStk+1
99fc: a9 07 lda #>genPurposeBuf
99fe: 8d 02 04 sta exprParseStk+2
9a01: 20 cb 83 jsr CpyExprStrToStrVar ;...and then copy the string on stack, into the INPUT var
9a04: a5 09 lda zpTXTPTR
9a06: 85 52 sta vZpSkipChars ;move input string past string we processed
9a08: 4c a7 99 jmp INPUT_checkNextArg ;-> see if there are more INPUT args
• Clear variables
9a0b: 4c 91 84 errSyntax_9A0B jmp ErrorSyntax
9a0e: 20 76 a5 CmdFn_SWAP jsr EvalVarNameOrSynErr ;evaluate variable name
9a11: a5 90 lda zpVarType
9a13: 29 0f and #$0f ;push base type of var
9a15: 48 pha
9a16: a5 8c lda zpVarData ;push pointer to variable data
9a18: 48 pha
9a19: a5 8d lda zpVarData+1
9a1b: 48 pha
9a1c: 20 14 ac jsr SkipCommaOrSynErr ;comma comes next
9a1f: 20 bf 90 jsr TxtSkipSpaces
9a22: 20 76 a5 jsr EvalVarNameOrSynErr ;parse second var name
9a25: 68 pla
9a26: 85 1a sta zpMisc+1 ;save first var's data ptr to zpMisc
9a28: 68 pla
9a29: 85 19 sta zpMisc
9a2b: a5 90 lda zpVarType
9a2d: 29 0f and #$0f
9a2f: 85 90 sta zpVarType ;strip off any "array" bit on 2nd var's type
9a31: 68 pla ;pull first var type
9a32: c5 90 cmp zpVarType ;compare to 2nd. Match?
9a34: d0 18 bne errTypeMismatch_9A4E ;no -> error
9a36: aa tax
9a37: c9 02 cmp #TYP_INT ;int? use "2" as size of data to swap
9a39: f0 02 beq @int
9a3b: a2 21 ldx #33 ;else use 33 as str data length
9a3d: a0 00 @int ldy #$00
9a3f: b1 8c @cpyLp lda (zpVarData),y ;get a byte from var 2
9a41: 48 pha ;push
9a42: b1 19 lda (zpMisc),y ;get a byte from var 1
9a44: 91 8c sta (zpVarData),y ;copy to var 2
9a46: 68 pla ;pull byte from var 1
9a47: 91 19 sta (zpMisc),y ;write to var 2
9a49: c8 iny ;next byte
9a4a: ca dex ;done copying?
9a4b: d0 f2 bne @cpyLp ;no -> copy next byte
9a4d: 60 rts ;yes. Done!
errTypeMismatch_9A4E
9a4e: 4c ff a9 jmp ErrorTypeMismatch
9a51: 20 8c 90 INPUT_printStr jsr TxtPtrIncr
9a54: a0 00 ldy #$00
9a56: b1 09 lda (zpTXTPTR),y ;get char
9a58: f0 b1 beq errSyntax_9A0B ;terminating null? -> missing var after, so syntax err
9a5a: c9 22 cmp #‘"’ ;got terminating double-quote?
9a5c: f0 06 beq @strDone ;yes -> done
9a5e: 20 75 b5 jsr PrintOneChar ;else, print the char
9a61: 4c 51 9a jmp INPUT_printStr ;and loop around for next char in str
9a64: 20 99 84 @strDone jsr AdvThenIsEndOfCmd ;is cmd end?
9a67: f0 a2 beq errSyntax_9A0B ;yes -> missing var after, so syntax err
9a69: 4c 8c 90 jmp TxtPtrIncr ;-> move past closing dquot, and return to INPUT/LINPUT
9a6c: f0 9d CmdFn_LINPUT beq errSyntax_9A0B ;cmd end? -> syntax error
9a6e: c9 22 cmp #‘"’ ;got a double-quote?
9a70: d0 0b @noStr bne @noStrOrDone
9a72: 20 51 9a jsr INPUT_printStr
9a75: c9 2c cmp #‘,’ ;require following ',' or ';' (and then var)
9a77: f0 04 beq @noStrOrDone
9a79: c9 3b cmp #‘;’
9a7b: d0 8e bne errSyntax_9A0B
9a7d: 20 04 bd @noStrOrDone jsr ReadLine ;read the line (into lineBuffer)
9a80: 20 55 88 jsr StopIfStopKey ;check for and honor STOP key occurrance during input
9a83: 20 bf 90 jsr TxtSkipSpaces
9a86: 20 76 a5 jsr EvalVarNameOrSynErr ;process the following string var name
9a89: a5 90 lda zpVarType ;get base type of var
9a8b: 29 0f and #$0f
9a8d: c9 03 cmp #TYP_STR ;is it string?
9a8f: d0 bd bne errTypeMismatch_9A4E ;no -> error
9a91: a2 00 ldx #$00
9a93: a0 00 ldy #$00
9a95: bd 00 05 lda lineBuffer,x ;get first char of input line
9a98: c9 20 cmp #$20 ;space char?
9a9a: d0 07 bne @countLen ;no ->
9a9c: e8 inx ;yes, space
9a9d: bd 00 05 lda lineBuffer,x ;just a single space is the whole string?
9aa0: f0 0e beq @cpyVar ;yes -> copy empty str to var
9aa2: ca dex ;no -> move back to include space char in copying
9aa3: bd 00 05 @countLen lda lineBuffer,x ;get char from input line
9aa6: f0 08 beq @cpyVar ;end of line/str? -> len is in Y reg; copy to var
9aa8: 9d 00 07 sta genPurposeBuf,x ;copy into genPurposeBuf (but never use??)
9aab: e8 inx
9aac: c8 iny
9aad: 4c a3 9a jmp @countLen ;-> loop for next char in input line
9ab0: 8c 00 04 @cpyVar sty exprParseStk ;set Y as str len
9ab3: a9 00 lda #<lineBuffer
9ab5: 8d 01 04 sta exprParseStk+1 ;point str-to-copy at lineBuffer
9ab8: a9 05 lda #>lineBuffer
9aba: 8d 02 04 sta exprParseStk+2
9abd: 4c cb 83 jmp CpyExprStrToStrVar ;-> perform the copy
; Not actually a CmdFn. This is just CmdFn_ON followed by an ERROR token.
9ac0: 20 99 84 CmdFn_ON_ERROR jsr AdvThenIsEndOfCmd
9ac3: c9 80 cmp #$80 ;followed by GOTO token?
9ac5: d0 33 bne errSyntax_9AFA ;no -> syntax error
9ac7: 20 99 84 jsr AdvThenIsEndOfCmd ;skip past GOTO, and any spaces
9aca: a0 00 ldy #$00
9acc: b1 09 lda (zpTXTPTR),y ;do we have a line number?
9ace: c9 0b cmp #$0b
9ad0: d0 28 bne errSyntax_9AFA ;no -> syntax error
9ad2: c8 iny
9ad3: b1 09 lda (zpTXTPTR),y ;is line num low byte zero?
9ad5: d0 0a bne @handleLNum ;no -> handle line num
9ad7: c8 iny
9ad8: b1 09 lda (zpTXTPTR),y ;get lnum high byte
9ada: 85 e9 sta zpErrHandLNum+1 ;save as handler addr
9adc: d0 03 bne @handleLNum ;lnum high byte zero? no -> handle line num
9ade: 85 e7 sta zpErrHandStat ;storing 0 (EH_NO_HANDLER)
9ae0: 60 rts
9ae1: 20 9c 84 @handleLNum jsr IsEndOfCmd ;skip spaces
9ae4: 20 f0 96 jsr FindLineFromArg ;find the designated line number (or error)
9ae7: a0 01 ldy #$01
9ae9: b1 09 lda (zpTXTPTR),y ;store the line num arg as error handler line num
9aeb: 85 e8 sta zpErrHandLNum
9aed: c8 iny
9aee: b1 09 lda (zpTXTPTR),y
9af0: 85 e9 sta zpErrHandLNum+1
9af2: 20 56 9b jsr SkipLineNumArgs
9af5: a9 01 lda #EH_HANDLER_DEFINED ;indicate we've got an error handler installed
9af7: 85 e7 sta zpErrHandStat
9af9: 60 rts ;and done!
9afa: 4c 91 84 errSyntax_9AFA jmp ErrorSyntax
9afd: c9 bc CmdFn_ON cmp #tok_ERROR ;followed by an ERROR token?
9aff: f0 bf beq CmdFn_ON_ERROR ;yes -> do ON ERROR
9b01: 20 6e a2 jsr EvalByteInteger ;else, BASIC-style switch statement. Evaluate the int expr
9b04: 20 bf 90 jsr TxtSkipSpaces
9b07: 48 pha ;and push next token to CPU stack
9b08: 20 8c 90 jsr TxtPtrIncr
9b0b: ae 00 04 ldx exprParseStk ;was the expr zero?
9b0e: f0 3e beq ON_skipArgsThenPlaAndRet ;yes -> just skip the list of line numbers, "GOTO" following cmd.
9b10: 20 9c 84 @argLp jsr IsEndOfCmd ;out of line num args?
9b13: f0 3c beq pla_rts_9B51 ;yes -> pull token and return
9b15: c9 0b cmp #tok_LNUM_AFTER ;did we get a line num token?
9b17: d0 e1 bne errSyntax_9AFA ;no -> error
9b19: ca dex ;decrement expr value (= how many args to skip)
9b1a: f0 0e beq @hit ;decremented expr at zero? -> go to this line
9b1c: 20 10 9f jsr TxtPtrAdvThree ;else we skip this arg
9b1f: 20 9c 84 jsr IsEndOfCmd ;end of cmd?
9b22: f0 2d beq pla_rts_9B51 ;yes -> done. Pull token and rts
9b24: 20 14 ac jsr SkipCommaOrSynErr ;parse following comma
9b27: 4c 10 9b jmp @argLp ;-> loop to next line-num arg
9b2a: 68 @hit pla
9b2b: c9 bd cmp #tok_RESUME ;got "RESUME" token?
9b2d: f0 19 beq ON_rsm ;yes -> exec RESUME cmd
9b2f: 38 sec
9b30: e9 80 sbc #TOKEN_START ;else, subtract token start
9b32: c9 05 cmp #$05 ; <= tok_THEN? (GOTO, GOSUB, RUN, RETURN, RESTORE, THEN)
9b34: b0 c4 bcs errSyntax_9AFA ;no -> error (other commands disallowed)
9b36: 0a asl A ;token value (- #$80) * 2 -> index into commands table
9b37: aa tax
9b38: bd bf ce lda jtbl_Commands,x
9b3b: 85 19 sta zpMisc ;copy command addr to zpMisc
9b3d: bd c0 ce lda jtbl_Commands+1,x
9b40: 85 1a sta zpMisc+1
9b42: 20 bf 90 jsr TxtSkipSpaces
9b45: 6c 19 00 jmp (zpMisc) ;-> execute (line number at TXTPTR will be the arg)
9b48: 20 9c 84 ON_rsm jsr IsEndOfCmd ;set CPU flags for whether we're at end of cmd
9b4b: 4c 27 95 jmp CmdFn_RESUME ;-> RESUME
ON_skipArgsThenPlaAndRet
9b4e: 20 56 9b jsr SkipLineNumArgs ;skip remaining line-num args
9b51: 68 pla_rts_9B51 pla ;pull token
9b52: 60 rts_9B52 rts ;and return
; Skips TXTPTR past a range of line numbers, whether comma-separated,
; or ranged with '-'
SkipKnownLineNumArgs
9b53: 20 10 9f jsr TxtPtrAdvThree ;we know we're at line num arg: skip it
9b56: 20 9c 84 SkipLineNumArgs jsr IsEndOfCmd ;check for more line num args
9b59: f0 f7 beq rts_9B52 ;end of cmd? -> rts
9b5b: c9 0b cmp #$0b ;another line num arg?
9b5d: f0 f4 beq SkipKnownLineNumArgs ;yes -> skip that
9b5f: 20 14 ac jsr SkipCommaOrSynErr ;no -> must be comma. Skip that, then arg.
9b62: 4c 56 9b jmp SkipLineNumArgs
9b65: 4c 94 84 errIll_9B65 jmp ErrorIllegalValue
vZpStrPtr .var $19 {addr/2}
vZpStrAvail .var $90 {addr/1}
9b68: c9 86 CmdFn_KEY cmp #$86 ;LIST token?
9b6a: f0 45 beq CmdFn_KEYLIST
9b6c: 20 6e a2 jsr EvalByteInteger ;get numeric arg
9b6f: f0 f4 beq errIll_9B65 ;zero? -> ill val error
9b71: c9 09 cmp #$09
9b73: b0 f0 bcs errIll_9B65 ; >= 9? -> ill val error
9b75: 48 pha ;push arg (fkey num)
9b76: 20 14 ac jsr SkipCommaOrSynErr ;skip required comma
9b79: 20 7d a2 jsr EvalString ;evaluate string
9b7c: 68 pla ;pull fkey num
9b7d: 38 sec
9b7e: e9 01 sbc #$01 ;decrement (F1 -> 0 index)
9b80: 0a asl A
9b81: 0a asl A
9b82: 0a asl A
9b83: 0a asl A ;mul by 16 for indexing macro defs
9b84: aa tax
9b85: ad 00 04 lda exprParseStk ;did user define empty string?
9b88: f0 21 beq KEY_nullTerminate ;yes -> just null terminate and return
9b8a: a0 00 ldy #$00
9b8c: ad 01 04 lda exprParseStk+1 ;copy ptr to string contents
9b8f: 85 19 sta vZpStrPtr
9b91: ad 02 04 lda exprParseStk+2
9b94: 85 1a sta vZpStrPtr+1
9b96: a9 0f lda #$0f ;max str len
9b98: 85 90 sta vZpStrAvail
9b9a: b1 19 @cpyLp lda (vZpStrPtr),y ;get a char from user string
9b9c: 9d 80 04 sta fnKey1_DfnBuf,x ;copy to macro definition
9b9f: e8 inx ;incr src and dst
9ba0: c8 iny
9ba1: ce 00 04 dec exprParseStk ;one less byte of str available
9ba4: f0 05 beq KEY_nullTerminate ;out of room? -> finish up
9ba6: c6 90 dec vZpStrAvail
9ba8: d0 f0 bne @cpyLp ;otherwise loop to copy next char
9baa: 60 rts
KEY_nullTerminate
9bab: a9 00 lda #$00
9bad: 9d 80 04 sta fnKey1_DfnBuf,x
9bb0: 60 rts
; Not actually found in jtbl_Commands. It's CmdFn_KEY when followed by
; LIST token.
• Clear variables
vZpFKeyNum .var $74 {addr/1}
vZpFKeyDigit .var $90 {addr/1}
9bb1: 20 8c 90 CmdFn_KEYLIST jsr TxtPtrIncr ;move ptr past LIST token
9bb4: a9 30 lda #‘0’
9bb6: 85 90 sta vZpFKeyDigit
9bb8: a9 00 lda #$00
9bba: 85 74 sta vZpFKeyNum
9bbc: a9 46 @eachFKeyLp lda #‘F’
9bbe: 20 4d 89 jsr QueueCharForOutput ;queue output: 'F'
9bc1: e6 90 inc vZpFKeyDigit
9bc3: a5 90 lda vZpFKeyDigit
9bc5: 20 4d 89 jsr QueueCharForOutput ;queue output: fn key number
9bc8: a9 20 lda #$20
9bca: 20 4d 89 jsr QueueCharForOutput ;queue output: SPACE char
9bcd: a5 74 lda vZpFKeyNum
9bcf: 0a asl A
9bd0: 0a asl A
9bd1: 0a asl A
9bd2: 0a asl A ;multiply fkey num by 16
9bd3: a8 tay
9bd4: b9 80 04 @prKeyDefLp lda fnKey1_DfnBuf,y ;get char from fkey macro string
9bd7: f0 21 beq @cpyDone ;null char -> done printing
9bd9: c9 20 cmp #$20 ;is it a printable character?
9bdb: 90 07 bcc @escCtrl ;no -> escape control. Example: Ctrl-M -> "(M)"
9bdd: 20 4d 89 jsr QueueCharForOutput ;yes: queue character for printing
9be0: c8 iny
9be1: 4c d4 9b jmp @prKeyDefLp ;loop until null or control
9be4: 48 @escCtrl pha ;push the control char
9be5: a9 28 lda #‘(’
9be7: 20 4d 89 jsr QueueCharForOutput ;queue up paren
9bea: 68 pla ;pull control char
9beb: 18 clc
9bec: 69 40 adc #$40 ;shift it into the printable region
9bee: 20 4d 89 jsr QueueCharForOutput ;and queue to print
9bf1: a9 29 lda #‘)’ ;closing paren
9bf3: 20 4d 89 jsr QueueCharForOutput
9bf6: c8 iny
9bf7: 4c d4 9b jmp @prKeyDefLp ;go back to printing chars
9bfa: 20 5d 89 @cpyDone jsr PrintOutBuf ;print queued chars
9bfd: 20 6f b5 jsr DoCRLF ;CRLF
9c00: e6 74 inc vZpFKeyNum
9c02: a5 74 lda vZpFKeyNum
9c04: c9 08 cmp #$08 ;done 8 fkeys?
9c06: 90 b4 bcc @eachFKeyLp ;no -> do next fkey
9c08: 60 rts ;yes: done
• Clear variables
9c09: 4c 91 84 errSyn_9C09 jmp ErrorSyntax
vZpNewVarName .var $7c {addr/2}
vZpNewVarMaxIdx .var $7e {addr/2}
vZpNewVarAryDimens .var $82 {addr/1} ;number of dimens in new array var
vZpNewVarType .var $84 {addr/1}
9c0c: 20 c4 a7 CmdFn_DIM jsr IsAlpha ;is next char alphabetic?
9c0f: b0 f8 bcs errSyn_9C09 ;no, can't be a varname then -> error
9c11: 85 7c sta vZpNewVarName
9c13: a9 02 lda #TYP_INT
9c15: 85 84 sta vZpNewVarType
9c17: a9 00 lda #$00
9c19: 85 7d sta vZpNewVarName+1
9c1b: 20 98 90 jsr TxtPtrIncrAndGetChar ;get next char
9c1e: 20 bc a7 jsr IsAlNum ;alphanumeric?
9c21: b0 05 bcs @notAlNum ;no -> not part of name
9c23: 85 7d sta vZpNewVarName+1 ;yes, it's the 2nd char of var name; store it
9c25: 20 8c 90 jsr TxtPtrIncr ;(note: DIM can't accept oversized names,
9c28: 20 bf 90 @notAlNum jsr TxtSkipSpaces ; unlike normal assignments)
9c2b: c9 24 cmp #‘$’ ;next char == '$'
9c2d: d0 05 bne @notStr ;no -> don't update type
9c2f: e6 84 inc vZpNewVarType ;mark the new type as str
9c31: 20 99 84 jsr AdvThenIsEndOfCmd
9c34: c9 28 @notStr cmp #‘(’ ;next char == '('?
9c36: d0 d1 bne errSyn_9C09 ;no -> error (DIM in FB can only declare arrays)
9c38: a9 00 lda #$00
9c3a: 85 82 sta vZpNewVarAryDimens ;set # dimens to 1 (value: 0), until we hear otherwise
9c3c: 20 65 a2 jsr AdvThenEvalByteInt ;eval specified max index
9c3f: 85 7e sta vZpNewVarMaxIdx ;and save it
9c41: 20 bf 90 jsr TxtSkipSpaces
9c44: c9 29 cmp #‘)’ ;closed paren?
9c46: f0 0e beq @closeP ;yes -> no second index
9c48: e6 82 inc vZpNewVarAryDimens ;no, it's 2-dimensional. Increment # dimens
9c4a: 20 6b a2 jsr EvalCommaThenByteInt ;parse comma and then another index
9c4d: 85 7f sta vZpNewVarMaxIdx+1 ;and store it
9c4f: 20 9c 84 jsr IsEndOfCmd ;check if we're at end of command (and then ignore result??)
9c52: c9 29 cmp #‘)’ ;NOW closed paren?
9c54: d0 b3 bne errSyn_9C09 ;no -> error
9c56: 20 8c 90 @closeP jsr TxtPtrIncr
9c59: 20 70 9c jsr DIM_ReserveArrayMem ;reserve space for the new array
9c5c: 20 9c 84 jsr IsEndOfCmd ;end of cmd?
9c5f: f0 09 beq @rts ;yes -> exit
9c61: 20 14 ac jsr SkipCommaOrSynErr ;parse a separating comma
9c64: 20 bf 90 jsr TxtSkipSpaces ;and any spaces
9c67: 4c 0c 9c jmp CmdFn_DIM ;then process the next declaration
9c6a: 60 @rts rts
ErrorDuplicateDefinitions
9c6b: a9 09 lda #err_DD
9c6d: 4c 37 b2 jmp PrintErrorAndStop
DIM_ReserveArrayMem
9c70: 20 50 9d jsr FindVar_7C_7D_84 ;can we find a matching var?
9c73: b0 f6 bcs ErrorDuplicateDefinitions ;yes -> error duplicate var
9c75: 20 d6 9c jsr DisallowBothIdxAtMax
9c78: a9 01 lda #$01 ;set multiplicand to 1
9c7a: 85 2c sta zpWParam
9c7c: 20 ce a7 jsr ClearOperandHiBytes
9c7f: a5 7e lda vZpNewVarMaxIdx
9c81: 85 28 sta zpWAccum ;set multiplier to size of dimen 1 (max idx + 1)
9c83: e6 28 inc zpWAccum
9c85: d0 02 bne @waNoOverfl
9c87: e6 29 inc zpWAccum+1 ;(incr to get # elems from max idx)
9c89: a5 82 @waNoOverfl lda vZpNewVarAryDimens ;have only one dimen?
9c8b: f0 0a beq @oneDim ;yes -> leave multiplicand where it is
9c8d: a5 7f lda vZpNewVarMaxIdx+1 ;no, multiply the # elems for both
9c8f: 85 2c sta zpWParam
9c91: e6 2c inc zpWParam ;(increment to get # elems)
9c93: d0 02 bne @oneDim
9c95: e6 2d inc zpWParam+1
9c97: 20 47 8f @oneDim jsr WordMultiply
9c9a: 20 04 a7 jsr GetSizeOfElementType
9c9d: 85 2c sta zpWParam
9c9f: a9 00 lda #$00
9ca1: 85 2d sta zpWParam+1
9ca3: 20 47 8f jsr WordMultiply ;then multiply the result by the size of the array's element type
9ca6: a9 05 lda #$05
9ca8: 85 2c sta zpWParam ;add space for var name and type info
9caa: a5 82 lda vZpNewVarAryDimens ;only one dimen?
9cac: f0 02 beq @oneDim1 ;yes -> we're good
9cae: e6 2c inc zpWParam ;no, we need one more byte of space for type info (for 2nd max idx)
9cb0: 20 f7 8e @oneDim1 jsr WordAdd ;(do the add)
9cb3: b0 1e bcs errOutOfMem_9CD3 ;(OOM if so indicated)
9cb5: a5 8c lda zpVarData
9cb7: 85 19 sta zpMisc
9cb9: 85 2c sta zpWParam
9cbb: a5 8d lda zpVarData+1
9cbd: 85 1a sta zpMisc+1
9cbf: 85 2d sta zpWParam+1
9cc1: 20 f7 8e jsr WordAdd ;now add that to the variable location (end of var table)
9cc4: b0 0d bcs errOutOfMem_9CD3 ;overflow past #$FFFF? -> OOM error
9cc6: a5 04 lda zpFRETOP+1
9cc8: 85 2d sta zpWParam+1
9cca: a5 03 lda zpFRETOP
9ccc: 85 2c sta zpWParam
9cce: 20 e2 8f jsr WordCompare ;did we overflow past zpFRETOP?
9cd1: 90 0e bcc DIM_InitializeArrayVar
errOutOfMem_9CD3
9cd3: 4c c8 8b jmp ErrorOutOfMemory
DisallowBothIdxAtMax
9cd6: a9 ff lda #$ff
9cd8: c5 7e cmp vZpNewVarMaxIdx
9cda: d0 04 bne @rts
9cdc: c5 7f cmp vZpNewVarMaxIdx+1
9cde: f0 f3 beq errOutOfMem_9CD3
9ce0: 60 @rts rts
DIM_InitializeArrayVar
9ce1: a0 00 ldy #$00
9ce3: a5 90 lda zpVarType ;store var type (including array flag)
9ce5: 91 8c sta (zpVarData),y
9ce7: c8 iny
9ce8: a5 8a lda zpVarName ;store var name first char
9cea: 91 8c sta (zpVarData),y
9cec: c8 iny
9ced: a5 8b lda zpVarName+1 ;store var name 2nd char
9cef: 91 8c sta (zpVarData),y
9cf1: c8 iny
9cf2: a5 82 lda vZpNewVarAryDimens ;store # dimens
9cf4: 91 8c sta (zpVarData),y
9cf6: c8 iny
9cf7: a5 7e lda vZpNewVarMaxIdx ;store first max idx
9cf9: 91 8c sta (zpVarData),y
9cfb: a5 82 lda vZpNewVarAryDimens ;just one dimen?
9cfd: f0 05 beq @oneDim2 ;yes -> skip storing a 2nd idx
9cff: c8 iny ;no, store 2nd max idx
9d00: a5 7f lda vZpNewVarMaxIdx+1
9d02: 91 8c sta (zpVarData),y
9d04: a5 28 @oneDim2 lda zpWAccum ;set the new end of the vars table
9d06: 85 1f sta zpVAREND
9d08: a5 29 lda zpWAccum+1
9d0a: 85 20 sta zpVAREND+1
9d0c: 18 clc
9d0d: a2 05 ldx #$05 ;5 bytes past var start: that's where the contents begin
9d0f: a5 82 lda vZpNewVarAryDimens ;is it one-dimensional?
9d11: f0 01 beq @oneDim3 ;yes -> stick with 5
9d13: e8 inx ;no: actually we need 6 to include the 2nd max idx
9d14: 8a @oneDim3 txa
9d15: 65 19 adc zpMisc ;add the offset
9d17: 85 19 sta zpMisc
9d19: a9 00 lda #$00
9d1b: 65 1a adc zpMisc+1
9d1d: 85 1a sta zpMisc+1
9d1f: a0 00 @ClearElemsLp ldy #$00 ;Initialize all the array contents to zeroes
9d21: a9 00 lda #$00
9d23: 91 19 sta (zpMisc),y ;set zero
9d25: e6 19 inc zpMisc ;increment scanning ptr
9d27: d0 02 bne @noCarry
9d29: e6 1a inc zpMisc+1
9d2b: a5 1a @noCarry lda zpMisc+1
9d2d: c5 20 cmp zpVAREND+1 ;at var table end?
9d2f: d0 ee bne @ClearElemsLp ;no -> next loop iteration
9d31: a5 19 lda zpMisc
9d33: c5 1f cmp zpVAREND ;check high byte too
9d35: d0 e8 bne @ClearElemsLp
9d37: a0 00 ldy #$00
9d39: a9 00 lda #$00
9d3b: 91 19 sta (zpMisc),y ;set next 3 bytes after vars end to 0, too
9d3d: c8 iny
9d3e: 91 19 sta (zpMisc),y
9d40: c8 iny
9d41: 91 19 sta (zpMisc),y
9d43: 60 rts ;and return
; Not in jtbl_Commands: in reality, this is just "SAVE S", not "SAVES".
• Clear variables
9d44: 20 8c 90 CmdFn_SAVES jsr TxtPtrIncr
9d47: 20 52 9e jsr GetFileNameArg
9d4a: 20 9d 9e jsr FileNameCpyToLineBuffer
9d4d: 4c 0d c5 jmp SaveBackgroundMap
; Transfer vars from $7C, $7D, and $84 to the spots that FindVar uses for the
; search key, then call it
FindVar_7C_7D_84
9d50: a5 7c lda zpBgGotFlag
9d52: 85 8a sta zpVarName
9d54: a5 7d lda zp7D
9d56: 85 8b sta zpVarName+1
9d58: a5 84 lda zpForNxtLnLnk
9d5a: 09 80 ora #$80
9d5c: 85 90 sta zpVarType
9d5e: 20 0f a7 jsr FindVar
9d61: 4c 08 a6 jmp CpyVarPtrToVarData
; BASIC PROGRAM HEADER FORMAT:
;
; byte 0: #$02
; bytes 1 - 17: null-term'd filename (no len byte)
; bytes 18 -19: program size in bytes
; bytes 20 - 21: pointer to prog start (zpTXTTAB)
; bytes 22-127: (unused)
9d64: c9 53 CmdFn_SAVE cmp #‘S’
9d66: f0 dc beq CmdFn_SAVES ;divert to save screen, not prog
9d68: 20 52 9e jsr GetFileNameArg
9d6b: 20 9d 9e jsr FileNameCpyToLineBuffer
9d6e: a9 02 lda #CAS_HDR_PROG
9d70: 8d 00 05 sta lineBuffer
9d73: a5 05 lda zpTXTTAB
9d75: 8d 14 05 sta lineBuffer+20
9d78: 85 2c sta zpWParam
9d7a: a5 06 lda zpTXTTAB+1
9d7c: 8d 15 05 sta lineBuffer+21
9d7f: 85 2d sta zpWParam+1
9d81: a5 07 lda zpTXTEND
9d83: 85 28 sta zpWAccum
9d85: a5 08 lda zpTXTEND+1
9d87: 85 29 sta zpWAccum+1
9d89: 20 0b 8e jsr SetIntAndSubtract ;get prog len from prog start and end
9d8c: a5 28 lda zpWAccum
9d8e: 8d 12 05 sta lineBuffer+18
9d91: a5 29 lda zpWAccum+1
9d93: 8d 13 05 sta lineBuffer+19
9d96: 20 9f 9d jsr Print_WRITING_fileName
9d99: 20 1f bf jsr CasSendHeader
9d9c: 4c 30 bf jmp CasSendData
Print_WRITING_fileName
9d9f: a9 67 lda #<strWRITING
9da1: 85 52 sta zpOutputStr
9da3: a9 b3 lda #>strWRITING
9da5: 85 53 sta zpOutputStr+1
9da7: 20 5a 89 jsr PrintString
9daa: f0 18 beq PrintFileName ;always branches
Print_LOADING_fileName
9dac: a9 70 lda #<strLOADING
9dae: 85 52 sta zpOutputStr
9db0: a9 b3 lda #>strLOADING
9db2: 85 53 sta zpOutputStr+1
9db4: 20 5a 89 jsr PrintString
9db7: f0 0b beq PrintFileName ;always branches
Print_SKIP_fileName
9db9: a9 79 lda #<strSKIP
9dbb: 85 52 sta zpOutputStr
9dbd: a9 b3 lda #>strSKIP
9dbf: 85 53 sta zpOutputStr+1
9dc1: 20 5a 89 jsr PrintString
9dc4: a9 01 PrintFileName lda #<lineBuffer+1
9dc6: 85 52 sta zpOutputStr
9dc8: a9 05 lda #>lineBuffer
9dca: 85 53 sta zpOutputStr+1
9dcc: 20 5a 89 jsr PrintString
9dcf: 4c 6f b5 jmp DoCRLF
9dd2: 20 b9 9d LoadSkipFile jsr Print_SKIP_fileName
9dd5: 4c f5 9d jmp LD_skipped
; Not in jtbl_Commands: in reality, this is just "LOAD S", not "LOADS".
9dd8: 20 8c 90 CmdFn_LOADS jsr TxtPtrIncr
9ddb: 20 52 9e jsr GetFileNameArg
9dde: 4c 65 c5 jmp LoadBackgroundMap
9de1: a2 00 CmdFn_LOAD ldx #$00
9de3: 86 9f stx zpLoadQFlag
9de5: c9 53 cmp #‘S’
9de7: f0 ef beq CmdFn_LOADS
9de9: c9 8b cmp #$8b ;"LOAD?"?... actually literally the PRINT token
9deb: d0 05 bne @notLOADQ ;no -> leave flag at zero
9ded: c6 9f dec zpLoadQFlag ; = #$ff
9def: 20 8c 90 jsr TxtPtrIncr
9df2: 20 52 9e @notLOADQ jsr GetFileNameArg
9df5: 20 53 be LD_skipped jsr CasRcvHeader
9df8: b0 53 bcs ErrorTapeRead
9dfa: 20 b1 9e jsr LoadCheckFilename ;does filename match our arg (if any)?
9dfd: b0 d3 bcs LoadSkipFile ;no -> skip this file and try the next
9dff: ad 00 05 lda lineBuffer
9e02: c9 02 cmp #CAS_HDR_PROG ;is it a Program header?
9e04: d0 cc bne LoadSkipFile ;no; BG header or other. Skip that, too.
9e06: 20 ac 9d jsr Print_LOADING_fileName ;yes, this is it! Tell user the good news
9e09: a5 9f lda zpLoadQFlag ;"LOAD?"?
9e0b: f0 06 beq LoadProgram ;no -> load program
9e0d: 20 1c be jsr CasVrfyProgram
9e10: b0 3b bcs ErrorTapeRead
9e12: 60 rts
9e13: a5 05 LoadProgram lda zpTXTTAB ;don't trust header's load destination; override with TXTTAB
9e15: 8d 14 05 sta lineBuffer+20
9e18: a5 06 lda zpTXTTAB+1
9e1a: 8d 15 05 sta lineBuffer+21
9e1d: 20 24 be jsr CasRcvFileData
9e20: b0 2b bcs ErrorTapeRead
9e22: a5 ad lda zpCasBufPtr ;Wherever we finished loading, wll, that's the program
9e24: 85 07 sta zpTXTEND ; end. We should mark it.
9e26: a5 ae lda zpCasBufPtr+1
9e28: 85 08 sta zpTXTEND+1
9e2a: 20 46 9e jsr @nullByteToCasBuf ;put NUL byte there
9e2d: 20 ce bd jsr IncrCasBufPtr
9e30: a5 ad lda zpCasBufPtr
9e32: 85 1d sta zpVARTAB ;next byte is start of vars
9e34: a5 ae lda zpCasBufPtr+1
9e36: 85 1e sta zpVARTAB+1
9e38: 20 46 9e jsr @nullByteToCasBuf ;NUL there
9e3b: 20 ce bd jsr IncrCasBufPtr
9e3e: a5 ad lda zpCasBufPtr
9e40: 85 1f sta zpVAREND ;next byte end of vars
9e42: a5 ae lda zpCasBufPtr+1
9e44: 85 20 sta zpVAREND+1
@nullByteToCasBuf
9e46: a0 00 ldy #$00 ;NUL there
9e48: a9 00 lda #$00
9e4a: 91 ad sta (zpCasBufPtr),y
9e4c: 60 rts
9e4d: a9 11 ErrorTapeRead lda #err_TP
9e4f: 4c 37 b2 jmp PrintErrorAndStop
; Used by SAVE, LOAD, etc. Checks if there's a string argument, and if so,
; copies it (temporarily) to upper half of lineBuffer. Otherwise, sets that area
; to 16 blanks (space characters). (It gets copied to (almost) the start of
; lineBuffer, later)
9e52: 20 8a 9e GetFileNameArg jsr FileNameInit
9e55: a9 00 lda #$00
9e57: 85 ea sta zpHaveFNameArg
9e59: 20 9c 84 jsr IsEndOfCmd ;is there an arg?
9e5c: f0 2b beq @rts ;no -> nothing to do; return
9e5e: 20 7d a2 jsr EvalString ;yes: expect prog name str
9e61: ad 01 04 lda exprParseStk+1
9e64: 85 19 sta zpMisc
9e66: ad 02 04 lda exprParseStk+2
9e69: 85 1a sta zpMisc+1
9e6b: ad 00 04 lda exprParseStk ;is the string empty?
9e6e: f0 19 beq @rts ;yes -> return
9e70: a0 00 ldy #$00
9e72: a2 00 ldx #$00
9e74: b1 19 @cpyName lda (zpMisc),y ;copy eval'd str as filename
9e76: 9d 80 05 sta lineBuffer+128,x
9e79: e8 inx
9e7a: c8 iny
9e7b: ce 00 04 dec exprParseStk
9e7e: d0 f4 bne @cpyName
9e80: a9 00 lda #$00
9e82: 9d 80 05 sta lineBuffer+128,x
9e85: a9 01 lda #$01
9e87: 85 ea sta zpHaveFNameArg
9e89: 60 @rts rts
; Initialize the temporary file name area of lineBuffer (+128) to 16 spaces,
; followed by the null terminator
9e8a: a2 00 FileNameInit ldx #$00
9e8c: a0 10 ldy #$10
9e8e: a9 20 lda #$20
9e90: 9d 80 05 @setSpcLp sta lineBuffer+128,x
9e93: e8 inx
9e94: 88 dey
9e95: d0 f9 bne @setSpcLp
9e97: a9 00 lda #$00
9e99: 9d 80 05 sta lineBuffer+128,x
9e9c: 60 rts
; Copy file name (stored from lineBuffer + 128), to LineBuffer + 1
FileNameCpyToLineBuffer
9e9d: a2 00 ldx #$00
9e9f: a0 10 ldy #$10
9ea1: bd 80 05 @namCpy lda lineBuffer+128,x
9ea4: 9d 01 05 sta lineBuffer+1,x
9ea7: e8 inx
9ea8: 88 dey
9ea9: d0 f6 bne @namCpy
9eab: a9 00 lda #$00
9ead: 9d 01 05 sta lineBuffer+1,x
9eb0: 60 rts
LoadCheckFilename
9eb1: a5 ea lda zpHaveFNameArg ;did LOAD get a filename arg?
9eb3: f0 10 beq @success ;no -> just report true
9eb5: a2 00 ldx #$00 ;yes: compare
9eb7: a0 10 ldy #$10 ;16 char max filename
9eb9: bd 01 05 @cmpLp lda lineBuffer+1,x
9ebc: dd 80 05 cmp lineBuffer+128,x
9ebf: d0 06 bne @failure
9ec1: e8 inx
9ec2: 88 dey
9ec3: d0 f4 bne @cmpLp
9ec5: 18 @success clc
9ec6: 60 rts
9ec7: 38 @failure sec
9ec8: 60 rts
9ec9: f0 18 CmdFn_RESTORE beq RestoreFromStart ;no arg? restore from start of prog
9ecb: 20 f0 96 jsr FindLineFromArg
9ece: 20 56 9b jsr SkipLineNumArgs
9ed1: a9 03 lda #$03
9ed3: 18 clc
9ed4: 65 5b adc zpTxtLink ;add 3 to found line pointer (skipping line linkage and number)
9ed6: 85 b2 sta zpNextDataPtr ;and set that as next place to start future DATA searches
9ed8: a9 00 lda #$00
9eda: 65 5c adc zpTxtLink+1 ;add any carry
9edc: 85 b3 ResFinish sta zpNextDataPtr+1 ;set high byte of DATA link
9ede: a9 00 lda #$00
9ee0: 85 b4 sta zpNextDataIdx ;clear char index
9ee2: 60 rts
; Reset DATA pointer to start of first BASIC line.
RestoreFromStart
9ee3: a5 05 lda zpTXTTAB
9ee5: 18 clc
9ee6: 69 03 adc #$03 ;init DATA search to begin from first line of program (past initial line)link
9ee8: 85 b2 sta zpNextDataPtr
9eea: a9 00 lda #$00
9eec: 65 06 adc zpTXTTAB+1
9eee: 4c dc 9e jmp ResFinish ;(just above)
; Skips the next token.
;
; BUG: does NOT skip entire comments. The DATA token, for instance, will be
; recognized if it occurs within a comment (as a Japanese "small e" ェ)
9ef1: 20 b2 90 SkipToken jsr GetTxtChar
9ef4: c9 80 cmp #$80 ;have a token?
9ef6: b0 15 bcs @incrTxtPtrAndReturn ;just increment to skip it
9ef8: c9 12 cmp #tok_NUM_AFTER ;have a decimal number?
9efa: f0 14 beq TxtPtrAdvThree ;skip it
9efc: c9 11 cmp #tok_HEX_AFTER ;have a hex number?
9efe: f0 10 beq TxtPtrAdvThree ;skip it
9f00: c9 0b cmp #tok_LNUM_AFTER ;have a line number?
9f02: f0 0c beq TxtPtrAdvThree ;skip it
9f04: c9 22 cmp #‘"’ ;double-quote?
9f06: f0 15 beq READ_skipStrLit ;skip the string literal
9f08: 20 c4 a7 jsr IsAlpha ;is it an alphabetic character?
9f0b: 90 1e bcc SkipVarName ;skip a var name
@incrTxtPtrAndReturn
9f0d: 4c 8c 90 jmp TxtPtrIncr ;everything else, skip one char
; Advance zpTXTPTR by 3
9f10: 20 8c 90 TxtPtrAdvThree jsr TxtPtrIncr
9f13: 20 8c 90 jsr TxtPtrIncr
9f16: 4c 8c 90 jmp TxtPtrIncr
9f19: 68 @eosFnd pla ;restore A
9f1a: 4c 8c 90 jmp TxtPtrIncr
9f1d: 48 READ_skipStrLit pha
9f1e: 20 98 90 @skipStrLp jsr TxtPtrIncrAndGetChar
9f21: c9 22 cmp #‘"’ ;have double-quote?
9f23: f0 f4 beq @eosFnd ;skipt it then return (we're done!)
9f25: 29 ff and #$ff ;have end-of-line?
9f27: d0 f5 bne @skipStrLp ;no -> skip char, check next
9f29: 68 pla ;yes: restore A and return
9f2a: 60 rts ;we've successfully skipped string
; Skips over a variable name. This does not include any trailing '$', just
; alphanumeric strings.
9f2b: 48 SkipVarName pha
9f2c: 20 98 90 @skipAlNumLp jsr TxtPtrIncrAndGetChar
9f2f: 20 bc a7 jsr IsAlNum
9f32: 90 f8 bcc @skipAlNumLp
9f34: 68 pla
9f35: 60 rts
vZpStrLen .var $1b {addr/1}
vZpEOsMark .var $3c {addr/1}
9f36: 20 76 a5 CmdFn_READ jsr EvalVarNameOrSynErr
9f39: 20 30 94 jsr SaveTXTPTR ;save away current text position
9f3c: a5 b2 lda zpNextDataPtr ;load up TXTPTR with where we left off from previous READs
9f3e: 85 09 sta zpTXTPTR
9f40: a5 b3 lda zpNextDataPtr+1
9f42: 85 0a sta zpTXTPTR+1
9f44: a5 b4 lda zpNextDataIdx ;were we in the middle of a line?
9f46: d0 26 bne READ_midDataStmt ;yes -> handle
9f48: 20 b2 90 @dataCmdSeekLp jsr GetTxtChar ;loop until found DATA statement, or end of program
9f4b: f0 0a beq @foundEol ;end of line? set up scanning for next one
9f4d: c9 91 cmp #tok_DATA
9f4f: f0 16 beq READ_foundDataCmd ;got DATA? -> read from it
9f51: 20 f1 9e jsr SkipToken
9f54: 4c 48 9f jmp @dataCmdSeekLp
9f57: 20 98 90 @foundEol jsr TxtPtrIncrAndGetChar ;adv and get char
9f5a: f0 06 beq ErrorOutOfData ;a second '\0'? -> we're at the prog end and out of data; error out.
9f5c: 20 10 9f jsr TxtPtrAdvThree ;not '\0'. Skip past "next" offset, and line number.
9f5f: 4c 48 9f jmp @dataCmdSeekLp ;go back to looking for a DATA cmd
9f62: a9 03 ErrorOutOfData lda #err_OD
9f64: 4c 37 b2 jmp PrintErrorAndStop
READ_foundDataCmd
9f67: a9 ff lda #$ff ;not really sure what the point of this is.
9f69: 85 b4 sta zpNextDataIdx ;just to invalidate curent value I guess?
9f6b: 20 f1 9e jsr SkipToken ;skip the DATA token we found
READ_midDataStmt
9f6e: a5 90 lda zpVarType ;get the type of the var we're looking for
9f70: 29 0f and #$0f ;strip the array bit, if present
9f72: c9 03 cmp #TYP_STR ;is it a string?
9f74: f0 52 beq READ_seekStrings ;yes -> handle that
9f76: 20 9c 84 jsr IsEndOfCmd ;no -> we're seeking ints then
9f79: f0 e7 beq ErrorOutOfData ;did we run out of data? -> error out
9f7b: 20 2d ab jsr READ_parseNumber
9f7e: a0 00 ldy #$00
9f80: a5 28 lda zpWAccum
9f82: 91 8c sta (zpVarData),y ;copy number from WAccum into the variable's data
9f84: a5 29 lda zpWAccum+1
9f86: c8 iny
9f87: 91 8c sta (zpVarData),y
9f89: 20 9c 84 jsr IsEndOfCmd ;are we at the end of the DATA command?
9f8c: f0 15 beq READ_setupNextDataCmdSeek ;yes -> go find next DATA stmt
9f8e: 20 bf 90 jsr TxtSkipSpaces
9f91: c9 2c cmp #‘,’ ;no: check if next char (after spaces) is a comma
9f93: f0 03 beq READ_gotDataComma ;comma? -> see if we can start from here next time
9f95: 4c ff a9 jmp ErrorTypeMismatch ;not comma: error!
READ_gotDataComma
9f98: 20 8c 90 jsr TxtPtrIncr
9f9b: 20 9c 84 jsr IsEndOfCmd ;are we at the end of the DATA cmd, after a comma (empty field)?
9f9e: f0 c2 beq ErrorOutOfData ;yes -> error!
9fa0: 4c a7 9f jmp READ_setupNextReadLoc ;no: we found next READ's field to use
READ_setupNextDataCmdSeek
9fa3: a9 00 lda #$00 ;Next READ, search for a DATA statement starting from here.
9fa5: 85 b4 sta zpNextDataIdx
READ_setupNextReadLoc
9fa7: a5 09 lda zpTXTPTR ;Next READ, use the value we're at now.
9fa9: 85 b2 sta zpNextDataPtr ;save current TXTPTR to zpNextDataPtr
9fab: a5 0a lda zpTXTPTR+1
9fad: 85 b3 sta zpNextDataPtr+1
9faf: 20 39 94 jsr RestoreTXTPTR ;and restore TXTPTR to just after the READ variable
9fb2: 20 bf 90 jsr TxtSkipSpaces
9fb5: c9 2c cmp #‘,’ ;comma? -> handle next var
9fb7: f0 01 beq READ_gotReadComma
9fb9: 60 rts_9FB9 rts
READ_gotReadComma
9fba: 20 8c 90 jsr TxtPtrIncr
9fbd: 20 bf 90 jsr TxtSkipSpaces
9fc0: 20 c4 a7 jsr IsAlpha ;got alphabetic char?
9fc3: b0 f4 bcs rts_9FB9 ;no -> allow the spurious trailing comma, and finish cmd
9fc5: 4c 36 9f jmp CmdFn_READ ;yes -> start over again for new variable
READ_seekStrings
9fc8: a9 00 lda #$00
9fca: 85 1b sta vZpStrLen ;set string length to zero
9fcc: aa tax
9fcd: a9 2c lda #‘,’ ;set comma as end-of-string to seek
9fcf: 85 3c sta vZpEOsMark
9fd1: 20 9c 84 jsr IsEndOfCmd ;we're not at the end of the command, are we?
9fd4: f0 8c beq ErrorOutOfData
9fd6: c9 22 cmp #‘"’ ;do we have a double-quote?
9fd8: d0 05 bne @seekEosLp ;no -> skip skipping it
9fda: 85 3c sta vZpEOsMark ;set dbl quot as end-of-string to seek
9fdc: 20 8c 90 jsr TxtPtrIncr
9fdf: b1 09 @seekEosLp lda (zpTXTPTR),y ;are we at end of the line?
9fe1: f0 22 beq READ_eocStoreStrVar ;yes -> store the string var
9fe3: c5 3c cmp vZpEOsMark ;EOS mark (comma or dquot)?
9fe5: f0 0c beq @eosFnd ;yes -> store the string var, and possibly skip a comma
9fe7: 9d 00 07 sta genPurposeBuf,x ;otherwise, copy char into tmp buffer
9fea: e6 1b inc vZpStrLen ;incr string len
9fec: e8 inx
9fed: 20 8c 90 jsr TxtPtrIncr ;incr TXTPTR
9ff0: 4c df 9f jmp @seekEosLp ;and loop back to check that
9ff3: 20 8c 90 @eosFnd jsr TxtPtrIncr
9ff6: 20 9c 84 jsr IsEndOfCmd ;end of the command?
9ff9: f0 0a beq READ_eocStoreStrVar ;yes -> go and store string var
9ffb: c9 2c cmp #‘,’ ;comma?
9ffd: d0 0a bne READ_storeStrVar ;no -> go and store string var
9fff: 20 8c 90 jsr TxtPtrIncr ;yes: skip TXTPTR past comma character
a002: 4c 09 a0 jmp READ_storeStrVar ;-> go and store string var
READ_eocStoreStrVar
a005: a9 00 lda #$00 ;mark that we're not in the middle of a line for next READ
a007: 85 b4 sta zpNextDataIdx
READ_storeStrVar
a009: a5 1b lda vZpStrLen ;move str info onto expression stack
a00b: 8d 00 04 sta exprParseStk
a00e: a9 00 lda #<genPurposeBuf
a010: 8d 01 04 sta exprParseStk+1
a013: a9 07 lda #>genPurposeBuf
a015: 8d 02 04 sta exprParseStk+2
a018: 20 cb 83 jsr CpyExprStrToStrVar ;and call the fn that will copy that to the var
a01b: 4c a7 9f jmp READ_setupNextReadLoc
• Clear variables
vZpPlayPtr .var $91 {addr/2}
a01e: 20 7d a2 CmdFn_PLAY jsr EvalString ;parse following string
a021: ad 00 04 lda exprParseStk ;is the string empty?
a024: f0 60 beq rts_A086 ;yes -> nothing to do, then
a026: a5 09 lda zpTXTPTR
a028: 48 pha ;push TXTPTR to CPU stack
a029: a5 0a lda zpTXTPTR+1
a02b: 48 pha
a02c: ad 01 04 lda exprParseStk+1 ;point TXTPTR at the arg string
a02f: 85 09 sta zpTXTPTR
a031: ad 02 04 lda exprParseStk+2
a034: 85 0a sta zpTXTPTR+1
a036: a9 07 lda #>genPurposeBuf ;point vZpPlayPtr at the general-purpose buffer
a038: 85 92 sta vZpPlayPtr+1
a03a: a9 00 lda #<genPurposeBuf
a03c: 85 91 sta vZpPlayPtr
a03e: 85 c6 sta zpPlayVoxIdx
a040: 20 41 a2 jsr PLAY_copyVoiceToGenPurpBuf ;copy one voice from the arg string into genPurposeBuf
a043: a9 21 lda #PLAY_INDEX_V1
a045: 85 91 sta vZpPlayPtr ;advance buffer pointer to start of next voice
a047: 85 c7 sta zpPlayVoxIdx+1
a049: 20 41 a2 jsr PLAY_copyVoiceToGenPurpBuf ;copy next voice from the arg string into genPurposeBuf
a04c: a9 42 lda #PLAY_INDEX_V2
a04e: 85 91 sta vZpPlayPtr ;advance buffer pointer to start of next voice
a050: 85 c8 sta zpPlayVoxIdx+2
a052: 20 41 a2 jsr PLAY_copyVoiceToGenPurpBuf ;copy final voice from the arg string into genPurposeBuf
a055: a9 01 lda #$01
a057: 85 cb sta zpNoteRealLen ;initialize note lengths (1, bc will be decremented before check)
a059: 85 cc sta zpNoteRealLen+1
a05b: 85 cd sta zpNoteRealLen+2
a05d: a9 7f lda #$7f
a05f: 8d 01 40 sta SQ1_SWEEP ;initialize (and disable) the APU sweep registers
a062: 8d 05 40 sta SQ2_SWEEP
a065: a9 07 lda #$07
a067: 85 c9 sta zpPlayStatus ;record all three voices as active
a069: 8d 15 40 sta SND_CHN ;disable DMA and noise channels, enable triangle and the square waves
a06c: a9 0a lda #NC_PlayMusic
a06e: 85 63 sta zpNMICMD ;tell NMI handler to start playing the music
a070: 20 55 88 @playLp jsr StopIfStopKey ;Stop if STOP is pressed
a073: a5 c9 lda zpPlayStatus ;check status of player
a075: 30 10 bmi @playError ; < 0? -> error occurred, handle
a077: d0 f7 bne @playLp ; > 0? -> still playing, keep looping
a079: a9 00 lda #NC_NoCmd ;else, done playing
a07b: 85 63 sta zpNMICMD ;disable NMI command
a07d: 8d 15 40 sta SND_CHN ;disable all sound channels
a080: 68 pla ;restore TXTPTR to point at BASIC
a081: 85 0a sta zpTXTPTR+1
a083: 68 pla
a084: 85 09 sta zpTXTPTR
a086: 60 rts_A086 rts ;-> done!
a087: a9 00 @playError lda #NC_NoCmd ;reset NMI handler command
a089: 85 63 sta zpNMICMD
a08b: 8d 15 40 sta SND_CHN ;turn off all sound channels
a08e: 4c 91 84 jmp ErrorSyntax ; -> syntax error
NMICMD_PlayMusic
a091: 8a txa ;push X (value is 18)
a092: 48 pha
a093: 98 tya ;push Y
a094: 48 pha
a095: a9 c0 lda #$c0
a097: 8d 17 40 sta JOY2 ;synchronize APU frame counter
a09a: 20 a3 a0 jsr PM_FeedTheVoices
a09d: 68 pla ;pull Y
a09e: a8 tay
a09f: 68 pla ;pull X
a0a0: aa tax
a0a1: 68 pla ;pull A (pushed at start of NMI_DefaultHandler)
a0a2: 40 rti ;-> exit NMI handling
PM_FeedTheVoices
a0a3: a9 02 lda #2
a0a5: 85 ca sta zpCurVoice ;start with third voice (voice 2)
a0a7: a9 04 lda #CHAN_MASK_TRI ;(the triangle channel)
a0a9: 85 ce sta zpCurVoxMask
a0ab: a5 c9 @voiceLp lda zpPlayStatus ;did a syntax error occur?
a0ad: 30 09 bmi rts_A0B8 ;yes -> exit NMI so it can be handled by PLAY command
a0af: 20 b9 a0 jsr PM_FeedCurVoice ;update this voice's state for where we are in the music
a0b2: 46 ce lsr zpCurVoxMask
a0b4: c6 ca dec zpCurVoice ;next (lower) voice number
a0b6: 10 f3 bpl @voiceLp ;-> loop back to handle voice, until all three handled
a0b8: 60 rts_A0B8 rts ;-> return and exit NMI handler
a0b9: a5 c9 PM_FeedCurVoice lda zpPlayStatus
a0bb: 25 ce and zpCurVoxMask ;is the current voice still playing?
a0bd: f0 f9 beq rts_A0B8 ;no -> return, handle next voice
a0bf: a6 ca ldx zpCurVoice
a0c1: d6 cb dec zpNoteRealLen,x ;is this voice currently playing a note or rest?
a0c3: d0 f3 bne rts_A0B8 ;yes -> return, nothing to process
a0c5: b5 c6 lda zpPlayVoxIdx,x
a0c7: 85 91 sta vZpPlayPtr ;set play ptr to next PLAY char for this voice
a0c9: a9 00 lda #SHARP_UNUSED
a0cb: 85 c2 sta zpSharpVal ;deactivate sharp by default
PM_DoCurPlayChar
a0cd: a0 00 ldy #$00
a0cf: b1 91 lda (vZpPlayPtr),y ;get cur PLAY char. End of PLAY string for cur voice?
a0d1: d0 11 bne PM_ProcessCurPlayChar ;not end -> handle the char
a0d3: a5 c9 lda zpPlayStatus
a0d5: 38 sec
a0d6: e5 ce sbc zpCurVoxMask
a0d8: 85 c9 sta zpPlayStatus
a0da: 8d 15 40 sta SND_CHN ;remove this channel's mask from zpPlayStatus
a0dd: a6 ca ldx zpCurVoice
a0df: a5 91 lda vZpPlayPtr
a0e1: 95 c6 sta zpPlayVoxIdx,x ;update this voice's buffer index (so it stays pointing at null char)
a0e3: 60 rts ;-> and return, handle next voice
PM_ProcessCurPlayChar
a0e4: e6 91 inc vZpPlayPtr ;increment PLAY ptr (next char)
a0e6: c9 20 cmp #$20 ;was the play character a SPACE?
a0e8: f0 e3 beq PM_DoCurPlayChar ;yes -> do nothing, handle next char
a0ea: c9 4d PM_isCharM cmp #‘M’ ;got 'M' (envelope)?
a0ec: d0 18 bne PM_isCharO ;no -> check next
a0ee: b1 91 lda (vZpPlayPtr),y ;get M arg
a0f0: f0 79 beq PM_SetSyntaxError ;no arg? -> error
a0f2: a2 30 ldx #ENV_UNUSED ;default (unused) value, assuming arg is '0'
a0f4: c9 30 cmp #‘0’
a0f6: f0 06 beq @setEnv
a0f8: c9 31 cmp #‘1’
a0fa: d0 6f bne PM_SetSyntaxError ;neither 0 nor 1? -> error
a0fc: a2 00 ldx #ENV_USED ;mark envelope as used
a0fe: e6 91 @setEnv inc vZpPlayPtr ;advance to next PLAY char
a100: a4 ca ldy zpCurVoice
a102: 96 cf stx zpEnvelope,y ;set env use/don't use for this voice
a104: 10 c7 bpl PM_DoCurPlayChar ;-> handle next PLAY char
a106: c9 4f PM_isCharO cmp #‘O’ ;got 'O' (octave)?
a108: d0 15 bne PM_isCharT ;no -> check next
a10a: b1 91 lda (vZpPlayPtr),y ;get O arg
a10c: f0 5d beq PM_SetSyntaxError ;no arg? -> error
a10e: e6 91 inc vZpPlayPtr ;advance to next char
a110: c9 36 cmp #‘6’ ; >= '6'?
a112: b0 57 bcs PM_SetSyntaxError ;yes -> error
a114: 38 sec
a115: e9 30 sbc #‘0’ ;convert from ASCII digit, to binary number
a117: 90 52 bcc PM_SetSyntaxError ; < 0? -> error
a119: a6 ca ldx zpCurVoice
a11b: 95 bf sta zpOctave,x ;set the octave value for cur voice
a11d: 10 ae bpl PM_DoCurPlayChar ;-> handle next PLAY char
a11f: c9 54 PM_isCharT cmp #‘T’ ;got 'T' (tempo)?
a121: d0 17 bne PM_isCharV ;no -> check next
a123: b1 91 lda (vZpPlayPtr),y ;get T arg
a125: f0 44 beq PM_SetSyntaxError ;no arg? -> error
a127: e6 91 inc vZpPlayPtr ;advance to next PLAY char
a129: c9 39 cmp #‘9’ ;arg char too high to be a digit?
a12b: b0 3e bcs PM_SetSyntaxError ;yes -> error
a12d: 38 sec
a12e: e9 30 sbc #‘0’ ;convert from ASCII digit, to binary number
a130: f0 39 beq PM_SetSyntaxError ; <= 0? -> error
a132: 90 37 bcc PM_SetSyntaxError
a134: a6 ca ldx zpCurVoice
a136: 95 b9 sta zpTempo,x ;set tempo for current voice
pmDoCurPlayChar_A138
a138: 10 93 bpl PM_DoCurPlayChar ;-> handle next PLAY char
a13a: c9 56 PM_isCharV cmp #‘V’ ;got 'V' (volume, or envelope length)?
a13c: d0 32 bne PM_isCharY ;no -> check next
a13e: b1 91 lda (vZpPlayPtr),y ;get first char of V arg
a140: f0 29 beq PM_SetSyntaxError ;no arg? -> error
a142: e6 91 inc vZpPlayPtr ;advance next PLAY char
a144: c9 3a cmp #‘:’
a146: b0 23 bcs PM_SetSyntaxError ; > '9'? -> error
a148: 38 sec
a149: e9 30 sbc #$30 ;convert from ASCII digit, to binary number
a14b: 90 1e bcc PM_SetSyntaxError ; < 0? -> error
a14d: c9 01 cmp #1 ;was first char a '1'?
a14f: d0 14 bne @setVol ;no -> just a single-digit arg for V, then
a151: b1 91 lda (vZpPlayPtr),y ;else, check for a 2nd digit
a153: c9 36 cmp #‘6’ ;char value >= '6'?
a155: b0 0c bcs @setVolOne ;yes -> not part of our arg; so, just '1'
a157: 38 sec
a158: e9 30 sbc #$30 ;convert from ASCII code, to binary number of digit
a15a: 90 07 bcc @setVolOne ;char < '0'? -> not part of arg, so just '1'
a15c: 18 clc
a15d: 69 0a adc #$0a ;the first '1' was a tens digit. Add ten to value from 2nd digit.
a15f: e6 91 inc vZpPlayPtr ;advance to next PLAY char
a161: d0 02 bne @setVol ;-> set the volume
a163: a9 01 @setVolOne lda #$01
a165: a6 ca @setVol ldx zpCurVoice
a167: 95 bc sta zpVolume,x ;set volume (or envelope) to user-specified level
a169: 10 cd bpl pmDoCurPlayChar_A138 ;-> handle next PLAY char
PM_SetSyntaxError
a16b: a9 80 lda #$80
a16d: 85 c9 sta zpPlayStatus
a16f: 60 rts
a170: c9 59 PM_isCharY cmp #‘Y’ ;got 'Y' (duty)?
a172: d0 16 bne PM_isCharSharp ;no -> check next
a174: b1 91 lda (vZpPlayPtr),y ;get Y arg
a176: c9 34 cmp #‘4’ ; >= '4'?
a178: b0 f1 bcs PM_SetSyntaxError ;yes -> error
a17a: 38 sec
a17b: e9 30 sbc #‘0’ ;convert from ASCII code, to binary number
a17d: 90 ec bcc PM_SetSyntaxError ; <= 0? -> error
a17f: e6 91 inc vZpPlayPtr ;advance to next PLAY char
a181: 4a lsr A
a182: 6a ror A
a183: 6a ror A ;shift left 3 places, for use as value in APU volume register
a184: a6 ca ldx zpCurVoice
a186: 95 d2 sta zpDuty,x ;save as "duty" value
a188: 10 ae bpl pmDoCurPlayChar_A138 ;-> handle next PLAY char
a18a: c9 23 PM_isCharSharp cmp #‘#’ ;got '#'?
a18c: d0 06 bne PM_isCharR ;no -> check next
a18e: a9 02 lda #SHARP_USED
a190: 85 c2 sta zpSharpVal ;mark next note name as sharp
a192: 10 a4 bpl pmDoCurPlayChar_A138 ;-> handle next PLAY char
a194: c9 52 PM_isCharR cmp #‘R’ ;got 'R'? (rest)
a196: d0 14 bne PM_isCharNoteName ;no -> check next
a198: 20 0f a2 jsr PM_calcNoteLen
a19b: a5 ca lda zpCurVoice ;take the current voice #
a19d: 0a asl A ;multiply by 4, to get an APU register index
a19e: 0a asl A
a19f: a8 tay ;set as the Y-reg value
a1a0: a9 90 lda #$90 ;silence value for square channels
a1a2: c0 08 cpy #$08 ;are we on the triangle channel?
a1a4: d0 02 bne @setVol ;no -> keep square silence value
a1a6: a9 00 lda #$00 ;yes -> use silence value for triangle channel
a1a8: 99 00 40 @setVol sta SQ1_VOL,y ;set silence for current voice's sound channel
a1ab: 60 rts ;-> return
PM_isCharNoteName
a1ac: c9 41 cmp #‘A’ ;char >= 'A'?
a1ae: 90 bb bcc PM_SetSyntaxError ;no -> syntax error (unrecognized char)
a1b0: c9 48 cmp #‘H’ ;char > 'G'?
a1b2: b0 b7 bcs PM_SetSyntaxError ;yes -> syntax error
a1b4: 48 pha ;push char value
a1b5: 20 0f a2 jsr PM_calcNoteLen ;get the note length, adjusted by tempo
a1b8: a6 ca ldx zpCurVoice ;get the current voice number
a1ba: 8a txa
a1bb: 0a asl A ;multiply by 4, for APU register index
a1bc: 0a asl A
a1bd: a8 tay
a1be: c9 08 cmp #$08 ;is this the triangle channel?
a1c0: d0 17 bne @square ;no -> handle for squarewave channel
a1c2: b5 cb lda zpNoteRealLen,x ;get the tempo-adjusted note length
a1c4: 38 sec
a1c5: e9 01 sbc #$01 ;subtract 1
a1c7: c9 0e cmp #14 ;less than 14?
a1c9: 90 02 bcc @notMax ;yes -> just use the value
a1cb: a9 0e lda #14 ;else, use max of 14
a1cd: 0a @notMax asl A ;shift left 2 places
a1ce: 0a asl A
a1cf: b4 cf ldy zpEnvelope,x ;are we using "envelope" for the triangle channel?
a1d1: f0 02 beq @keepEnv ;yes, using -> use our note-len-based value
a1d3: a9 81 lda #$81 ;no, not using: set control flag, and set counter reload to 1
a1d5: a0 08 @keepEnv ldy #$08
a1d7: d0 06 bne @setVol
a1d9: b5 bc @square lda zpVolume,x ;merge the duty, envelope, and volume values
a1db: 15 d2 ora zpDuty,x
a1dd: 15 cf ora zpEnvelope,x
a1df: 99 00 40 @setVol sta SQ1_VOL,y ;write duty, envelope, and volume to APU register
a1e2: 68 pla ;pull the note name ('A' to 'G')
a1e3: 38 sec
a1e4: e9 41 sbc #‘A’ ;convert A-G -> 0-6
a1e6: a8 tay ;set as Y-reg value
a1e7: b5 bf lda zpOctave,x ;get voice's current octave
a1e9: aa tax
a1ea: bd b4 80 lda tbl_OctaveBaseNoteVals,x ;get base note value for the current octave
a1ed: 18 clc
a1ee: 79 ad 80 adc tbl_NoteNameToValue,y ;add to that, the value for the note name
a1f1: 65 c2 adc zpSharpVal ;add 2 if it's sharped
a1f3: aa tax ;save note value in X reg
a1f4: a5 ca lda zpCurVoice ;get current voice
a1f6: 0a asl A ;times 4 for APU register index
a1f7: 0a asl A
a1f8: a8 tay ;save in Y reg
a1f9: c9 08 cmp #$08 ;triangle channel?
a1fb: d0 05 bne @sq ;no -> use note value as-is for square waves
a1fd: 8a txa ;yes, triangle: add one octave to get the actual note we'll use
a1fe: 18 clc
a1ff: 69 18 adc #NOTE_VAL_OCTAVE
a201: aa tax
a202: bd 13 80 @sq lda tbl_NoteValToPdLen,x ;get the period length for the note value,
a205: 99 02 40 sta SQ1_LO,y ; and set the appropriate APU registers to trigger
a208: bd 14 80 lda tbl_NoteValToPdLen+1,x ; our note's pitch
a20b: 99 03 40 sta SQ1_HI,y
a20e: 60 rts ;-> return
a20f: b1 91 PM_calcNoteLen lda (vZpPlayPtr),y ;load the next char after note name (or 'R')
a211: f0 13 beq @parseDone ;no char after? -> done parsing
a213: c9 3a cmp #‘:’ ; > '9'?
a215: b0 0f bcs @parseDone ;yes -> not a note length, done parsing
a217: 38 sec
a218: e9 30 sbc #‘0’ ;convert from ASCII digit code, to binary number
a21a: 90 0a bcc @parseDone ; < 0? -> not a note length, done parsing
a21c: aa tax
a21d: bd a3 80 lda tbl_NoteLengths,x ;convert number to a note length (from table)
a220: a6 ca ldx zpCurVoice
a222: 95 c3 sta zpNoteLen,x ;set note length for this voice
a224: e6 91 inc vZpPlayPtr ;advance to next PLAY char
a226: 20 ce a7 @parseDone jsr ClearOperandHiBytes
a229: a6 ca ldx zpCurVoice
a22b: b5 c3 lda zpNoteLen,x ;stick note length into WAccum (single byte)
a22d: 85 28 sta zpWAccum
a22f: b5 b9 lda zpTempo,x
a231: 85 2c sta zpWParam ;stick voice's tempo mark into WParam (single byte)
a233: 20 47 8f jsr WordMultiply ;multiply these
a236: a5 28 lda zpWAccum
a238: a6 ca ldx zpCurVoice
a23a: 95 cb sta zpNoteRealLen,x ;set it as the "real" note length
a23c: a5 91 lda vZpPlayPtr
a23e: 95 c6 sta zpPlayVoxIdx,x ;update the PLAY buffer index for this voice
a240: 60 rts ;-> and return
PLAY_copyVoiceToGenPurpBuf
a241: ad 00 04 lda exprParseStk ;end of string?
a244: f0 12 beq @nullTerm ;yes -> null-terminate and return
a246: ce 00 04 dec exprParseStk ;decrement string length
a249: 20 88 90 jsr TxtPtrGetCharAndIncr ;get a char from the string (and increment pointer)
a24c: c9 3a cmp #‘:’ ;colon? -> null-terminate and return
a24e: f0 08 beq @nullTerm
a250: a0 00 ldy #$00
a252: 91 91 sta (vZpPlayPtr),y ;copy it into general-purpose buffer
a254: e6 91 inc vZpPlayPtr ;advance genPurposeBuf pointer
a256: d0 e9 bne PLAY_copyVoiceToGenPurpBuf ;(always) -> loop around to copy next character
a258: a0 00 @nullTerm ldy #$00
a25a: a9 00 lda #$00
a25c: 91 91 sta (vZpPlayPtr),y ;terminate with a null char
a25e: 60 rts
• Clear variables
a25f: 4c 91 84 errSyn_A25F jmp ErrorSyntax
a262: 4c 94 84 errIllVal_A262 jmp ErrorIllegalValue
AdvThenEvalByteInt
a265: 20 8c 90 jsr TxtPtrIncr
a268: 4c 6e a2 jmp EvalByteInteger
EvalCommaThenByteInt
a26b: 20 14 ac jsr SkipCommaOrSynErr
a26e: 20 83 a2 EvalByteInteger jsr FrmEval
a271: 20 0d aa jsr ErrorUnlessInteger
a274: ad 01 04 lda exprParseStk+1 ;anything in the high byte?
a277: d0 e9 bne errIllVal_A262 ;We're looking for values that fit in a byte
a279: ad 00 04 lda exprParseStk
a27c: 60 rts
a27d: 20 83 a2 EvalString jsr FrmEval
a280: 4c 10 aa jmp ErrorUnlessString
; Evaluate the formula at the current location in the BASIC program tokenized
; text.
a283: a9 00 FrmEval lda #$00
a285: 85 27 sta zpArgTyp
a287: 85 30 sta zpExprStkIdx
a289: a9 80 lda #$80
a28b: 85 21 sta zpGpStrIdx
a28d: 20 9b a2 jsr Expression
a290: a5 27 lda zpArgTyp
a292: f0 cb beq errSyn_A25F
a294: 60 rts
EnsureIntAndSkipToken
a295: 20 0d aa jsr ErrorUnlessInteger
a298: 4c 8c 90 jmp TxtPtrIncr
; The formula evaluator does a recursive parser thing, instead of operator
; precedence. Most of these parsers are in page $A2. One first calls Expression
; (this fn), which calls ExprOR, which calls ExprAND, etc, until it gets about 9
; calls deep and finally hits ExprUnaryOrLiteral, which processes unary ops,
; vars, and literals, and then if it finds a binary op it sets the accumulator
; to that op's token code, and falls back up the chain, letting one of the
; recursive handlers deal with it. This technique is easier to code, but not
; terribly efficient.
;
; Expression itself (this fn) directly handles XOR expressions (the lowest-
; precedence binary op), delegating to ExprOr (and below) for any higher-
; precedence op than XOR.
a29b: 20 be a2 Expression jsr ExprOr ;process more tightly-bound operations
a29e: c9 ef AgainXor cmp #tok_OP_XOR ;we at an XOR token?
a2a0: f0 01 beq @xor ;yes -> handle
a2a2: 60 rts ;no: all done evaluating expressions!
a2a3: 20 95 a2 @xor jsr EnsureIntAndSkipToken ;expect int (boolean)
a2a6: 20 be a2 jsr ExprOr ;eval 2nd operand
a2a9: 20 c1 a3 jsr IntPullWParamGetWAccum ;expect that to be an int too, and prep args
a2ac: a5 28 lda zpWAccum
a2ae: 45 2c eor zpWParam ;XOR the low bytes
a2b0: 85 28 sta zpWAccum
a2b2: a5 29 lda zpWAccum+1
a2b4: 45 2d eor zpWParam+1 ;XOR the high bytes
a2b6: 85 29 sta zpWAccum+1
a2b8: 20 d8 a7 jsr ReplaceExprStkTopWAccum ;replace top stack item
a2bb: 4c 9e a2 jmp AgainXor ;and go evaluate any further expressions
; Parse an OR expression, or an expression of higher precedence.
a2be: 20 e1 a2 ExprOr jsr ExprAnd ;process more tightly-bound operations
a2c1: c9 f0 AgainOr cmp #tok_OP_OR ;we at an OR token?
a2c3: f0 01 beq @or ;yes -> handle
a2c5: 60 rts ;no OR; wind our way up to lower-precedence ops
a2c6: 20 95 a2 @or jsr EnsureIntAndSkipToken ;expect int (boolean)
a2c9: 20 e1 a2 jsr ExprAnd ;eval 2nd operand
a2cc: 20 c1 a3 jsr IntPullWParamGetWAccum ;expect that to be an int too, and prep args
a2cf: a5 28 lda zpWAccum
a2d1: 05 2c ora zpWParam ;OR the low bytes
a2d3: 85 28 sta zpWAccum
a2d5: a5 29 lda zpWAccum+1
a2d7: 05 2d ora zpWParam+1 ;OR the high bytes
a2d9: 85 29 sta zpWAccum+1
a2db: 20 d8 a7 jsr ReplaceExprStkTopWAccum ;replace top stack item
a2de: 4c c1 a2 jmp AgainOr ;and go check for a proceeding OR expression
; Parse an AND expression, or an expression of higher precedence.
a2e1: 20 04 a3 ExprAnd jsr ExprRelOp ;process more tightly-bound operations
a2e4: c9 f1 AgainAnd cmp #tok_OP_AND ;we at an AND token?
a2e6: f0 01 beq @and ;yes -> handle
a2e8: 60 rts ;no AND; wind our way up to lower-precedence ops
a2e9: 20 95 a2 @and jsr EnsureIntAndSkipToken ;expect int (boolean)
a2ec: 20 04 a3 jsr ExprRelOp ;eval 2nd operand
a2ef: 20 c1 a3 jsr IntPullWParamGetWAccum ;expect that to be an int too, and prep args
a2f2: a5 28 lda zpWAccum
a2f4: 25 2c and zpWParam ;AND the low bytes
a2f6: 85 28 sta zpWAccum
a2f8: a5 29 lda zpWAccum+1
a2fa: 25 2d and zpWParam+1 ;AND the high bytes
a2fc: 85 29 sta zpWAccum+1
a2fe: 20 d8 a7 jsr ReplaceExprStkTopWAccum ;replace top stack item
a301: 4c e4 a2 jmp AgainAnd ;and go check for a proceeding AND expression
; Parse a relational expression (<>, >=, <=, =, >, <) or an expression
; of higher precedence.
a304: 20 41 a3 ExprRelOp jsr ExprPlusMinus ;process more tightly-bound operations
a307: c9 f3 AgainRelOp cmp #tok_OP_NE ;is the next byte a token greater or equal to '<>'?
a309: b0 01 bcs @maybeRel ;yes, check more
a30b: 60 rts_A30B rts ;no rel op; wind our way up to lower-precedence ops
a30c: c9 f9 @maybeRel cmp #tok_OP_PLUS ;is it a token >= '+'?
a30e: b0 fb bcs rts_A30B ;yes -> not a rel op
a310: 38 sec
a311: e9 f3 sbc #tok_OP_NE ;subtract '<>' tok val and multiply by 2
a313: 0a asl A ; to get an index
a314: aa tax
a315: bd 6e a8 lda jtbl_RelOps,x ;load and push ptr to this op's handling routine
a318: 48 pha
a319: bd 6f a8 lda jtbl_RelOps+1,x
a31c: 48 pha
a31d: a5 27 lda zpArgTyp ;also push type of 1st operand
a31f: 48 pha
a320: 20 8c 90 jsr TxtPtrIncr ;move past operator token
a323: 20 41 a3 jsr ExprPlusMinus ;and eval 2nd operand (as any more tightly-bound expr)
a326: 68 pla ;pull and compare 1st operand type
a327: c5 27 cmp zpArgTyp ;diff from 2nd operand's type?
a329: d0 45 bne errTypeMism_A370 ;yes -> error
a32b: 20 c4 a3 jsr PullWParamGetWAccum ;get args into WAccum, WParam
a32e: 68 pla
a32f: 85 1a sta zpMisc+1 ;pull handler routine and place in zpMisc
a331: 68 pla
a332: 85 19 sta zpMisc
a334: 20 3e a3 jsr JmpIndirectZpMisc ;execute!
a337: a2 02 ldx #TYP_INT ;no matter what type args had, result is int (0 or -1)
a339: 86 27 stx zpArgTyp
a33b: 4c 07 a3 jmp AgainRelOp ;check for any more rel ops to eval
JmpIndirectZpMisc
a33e: 6c 19 00 jmp (zpMisc)
; Parse an addition or subtraction expression, or an expression of higher
; precedence.
a341: 20 73 a3 ExprPlusMinus jsr ExprMod ;process more tightly-bound operations
a344: c9 f9 AgainPlusMinus cmp #tok_OP_PLUS ;next token == '+'?
a346: f0 05 beq @add ;yes -> handle add
a348: c9 fa cmp #tok_OP_MINUS ;else, next token == '-'?
a34a: f0 0a beq @sub ;yes -> handle subtract
a34c: 60 rts ;no add or subtract; wind our way up to lower-precedence ops
a34d: 20 5f a3 @add jsr @prepArgs ;get args from stack into WAccum, WParam
a350: 20 d5 a7 jsr AddAndReplaceExpr ;do the add
a353: 4c 44 a3 jmp AgainPlusMinus ;and any other add/subtract operations that immediately follow
a356: 20 5f a3 @sub jsr @prepArgs ;get args from stack into WAccum, WParam
a359: 20 ef a7 jsr SubtractAndReplaceExpr ;do the subtract
a35c: 4c 44 a3 jmp AgainPlusMinus ;and any other add/subtract operations that immediately follow
a35f: a5 27 @prepArgs lda zpArgTyp ;get/push arg type before evaluating 2nd arg expr
a361: 48 pha
a362: 20 8c 90 jsr TxtPtrIncr ;move past '+' or '-' operator
a365: 20 73 a3 jsr ExprMod ;eval 2nd operand (as any more tightly-bound expr)
a368: 68 pla ;pull type of preceding operand
a369: c5 27 cmp zpArgTyp ;do operand types differ?
a36b: d0 03 bne errTypeMism_A370 ;yes -> error
a36d: 4c c4 a3 jmp PullWParamGetWAccum ;get operands into WAccum, WParam, and return
errTypeMism_A370
a370: 4c ff a9 jmp ErrorTypeMismatch
; Parse a modulo expression, or an expression of higher precedence.
a373: 20 92 a3 ExprMod jsr ExprMulDiv ;process more tightly-bound operations
a376: c9 fb AgainMod cmp #tok_OP_MOD ;is the next token a MOD?
a378: f0 01 beq @mod ;yes -> perform modulo op
a37a: 60 rts ;no mod; wind our way up to lower-precedence ops
a37b: a5 27 @mod lda zpArgTyp ;get/push arg type before evaluating 2nd arg expr
a37d: 48 pha
a37e: 20 8c 90 jsr TxtPtrIncr ;move past MOD operator
a381: 20 92 a3 jsr ExprMulDiv ;eval 2nd operand (as any more tightly-bound expr)
a384: 68 pla ;pull type of preceding operand
a385: c5 27 cmp zpArgTyp ;do operand types differ?
a387: d0 e7 bne errTypeMism_A370 ;yes -> error
a389: 20 c4 a3 jsr PullWParamGetWAccum ;get operands into WAccum, WParam
a38c: 20 0e a8 jsr ModAndReplaceExpr ;perform modulo
a38f: 4c 76 a3 jmp AgainMod ;and any other modulo operations that immediately follow
; Parse a multiplication or division expression, or an expression of higher
; precedence.
a392: 20 90 a4 ExprMulDiv jsr ExprLiteralUnaryOrFn ;parse literal, unary, or function expr
a395: c9 fc AgainMulDiv cmp #tok_OP_DIV ;is the next token a '/'?
a397: f0 05 beq @div ;yes -> perform division
a399: c9 fd cmp #tok_OP_MUL ;or maybe it's a '*'?
a39b: f0 0a beq @mul ;yes -> perform multiplication
a39d: 60 rts ;no mul or div; wind our way up to lower-precedence ops
a39e: 20 b0 a3 @div jsr @prepArgs ;get args from stack into WAccum, WParam
a3a1: 20 fb a7 jsr DivAndReplaceExpr ;perform the division
a3a4: 4c 95 a3 jmp AgainMulDiv ;perform any immediately following mul or div
a3a7: 20 b0 a3 @mul jsr @prepArgs ;get args from stack into WAccum, WParam
a3aa: 20 f5 a7 jsr MulAndReplaceExpr ;perform the multiplication
a3ad: 4c 95 a3 jmp AgainMulDiv ;perform any immediately following mul or div
a3b0: a5 27 @prepArgs lda zpArgTyp ;get/push arg type before evaluating 2nd arg expr
a3b2: 48 pha
a3b3: 20 8c 90 jsr TxtPtrIncr ;move past '/' operator
a3b6: 20 90 a4 jsr ExprLiteralUnaryOrFn ;eval 2nd operand
a3b9: 68 pla ;pull 1st arg's type and compare
a3ba: c5 27 cmp zpArgTyp ;do operand types differ?
a3bc: d0 b2 bne errTypeMism_A370 ;yes -> error
a3be: 4c c4 a3 jmp PullWParamGetWAccum ;get operands into WAccum, WParam
IntPullWParamGetWAccum
a3c1: 20 0d aa jsr ErrorUnlessInteger
PullWParamGetWAccum
a3c4: a6 30 ldx zpExprStkIdx ;get parse stack position
a3c6: e0 7d cpx #$7d ;less than 3 from the end?
a3c8: b0 3d bcs errIll_A407 ;yes -> error... shouldn't this be out of memory,
a3ca: ca dex ; instead of illegal? also we're literally about to
a3cb: ca dex ; reduce the stack size by 3, so why do we even
a3cc: ca dex ; need this check?
a3cd: 86 30 stx zpExprStkIdx ;pull last result off of stack, and place in WParam
a3cf: bd 00 04 lda exprParseStk,x
a3d2: 85 2c sta zpWParam
a3d4: bd 01 04 lda exprParseStk+1,x
a3d7: 85 2d sta zpWParam+1
a3d9: bd 02 04 lda exprParseStk+2,x
a3dc: 85 2e sta zpWParam+2
a3de: ca dex
a3df: ca dex
a3e0: ca dex
a3e1: bd 00 04 lda exprParseStk,x ;get preceding result off expr stack, place in WAccum
a3e4: 85 28 sta zpWAccum ;doesn't pull - caller will typically replace
a3e6: bd 01 04 lda exprParseStk+1,x ; with its result
a3e9: 85 29 sta zpWAccum+1
a3eb: bd 02 04 lda exprParseStk+2,x
a3ee: 85 2a sta zpWAccum+2
a3f0: 60 rts
; Evaluate the following unary or literal expression, and then copy the
; evaluated result into WAccum
EvalUnaryIntoWAccum
a3f1: 20 8c 90 jsr TxtPtrIncr ;advance to next token char
a3f4: 20 90 a4 jsr ExprLiteralUnaryOrFn ;parse whatever's there
a3f7: a6 30 ldx zpExprStkIdx
a3f9: ca dex
a3fa: ca dex
a3fb: ca dex
a3fc: bd 00 04 lda exprParseStk,x
a3ff: 85 28 sta zpWAccum ;move evaluation result into zpWAccum
a401: bd 01 04 lda exprParseStk+1,x ;(result still remains on expression stack)
a404: 85 29 sta zpWAccum+1
a406: 60 rts
a407: 4c 94 84 errIll_A407 jmp ErrorIllegalValue
a40a: 20 f1 a3 DoExprNegate jsr EvalUnaryIntoWAccum ;get following atomic expression into WAccum
a40d: 20 32 8e jsr WAccumNegate ;and negate it
a410: a5 28 lda zpWAccum ;copy result to parsed-expression stack
a412: 9d 00 04 sta exprParseStk,x
a415: a5 29 lda zpWAccum+1
a417: 9d 01 04 sta exprParseStk+1,x
a41a: 4c bf 90 jmp TxtSkipSpaces
a41d: 20 f1 a3 DoExprNot jsr EvalUnaryIntoWAccum
a420: a9 ff lda #$ff
a422: 38 sec
a423: fd 00 04 sbc exprParseStk,x
a426: 9d 00 04 sta exprParseStk,x
a429: a9 ff lda #$ff
a42b: 38 sec
a42c: fd 01 04 sbc exprParseStk+1,x
a42f: 9d 01 04 sta exprParseStk+1,x
a432: 4c 0d a5 jmp SetIntTypAndSkipSpc
vZpSavX .var $19 {addr/1}
vZpSavY .var $90 {addr/1}
a435: 20 8c 90 ParseStrLit jsr TxtPtrIncr
a438: a0 00 ldy #$00
a43a: a6 21 ldx zpGpStrIdx
a43c: b1 09 @cpyLp lda (zpTXTPTR),y ;copy literal contents into genPurposeBuf as temp str
a43e: f0 13 beq @eol ;end of line? -> done copying
a440: c9 22 cmp #‘"’ ;end of string literal found?
a442: f0 0c beq @afterCopy ;yes -> package up for exprParseStk
a444: 9d 00 07 sta genPurposeBuf,x
a447: e8 inx
a448: c8 iny
a449: c0 20 cpy #$20 ;32 chars reached?
a44b: f0 ba beq errIll_A407 ;yes -> illegal
a44d: 4c 3c a4 jmp @cpyLp
a450: 20 8c 90 @afterCopy jsr TxtPtrIncr
a453: 86 19 @eol stx vZpSavX
a455: 84 90 sty vZpSavY
a457: 98 tya
a458: 18 clc
a459: 65 09 adc zpTXTPTR
a45b: 85 09 sta zpTXTPTR ;advance zpTXTPTR past the end of string literal
a45d: a9 00 lda #$00
a45f: 65 0a adc zpTXTPTR+1
a461: 85 0a sta zpTXTPTR+1
a463: a6 30 ldx zpExprStkIdx
a465: a5 90 lda vZpSavY ;push str len to parse stack
a467: 9d 00 04 sta exprParseStk,x
a46a: a5 21 lda zpGpStrIdx ;push pointer to tmp string on parse stack
a46c: 9d 01 04 sta exprParseStk+1,x
a46f: a9 07 lda #>genPurposeBuf
a471: 9d 02 04 sta exprParseStk+2,x
a474: e8 inx
a475: e8 inx
a476: e8 inx
a477: 86 30 stx zpExprStkIdx ;update stack pointer past newly-pushed stuff
a479: a5 19 lda vZpSavX
a47b: 85 21 sta zpGpStrIdx ;update temp string index past what we copied in
a47d: a9 03 lda #TYP_STR ;indicate cur expr is string
a47f: 85 27 sta zpArgTyp
a481: 4c bf 90 jmp TxtSkipSpaces
• Clear variables
a484: 4c 91 84 errSyn_A484 jmp ErrorSyntax
a487: 4c 1d a4 doExprNot_A487 jmp DoExprNot
doExprNegate_A48A
a48a: 4c 0a a4 jmp DoExprNegate
a48d: 20 8c 90 txtPtrIncr_A48D jsr TxtPtrIncr
; Handles literals, unary operators (-, +, NOT, etc), and literals.
ExprLiteralUnaryOrFn
a490: 20 9c 84 jsr IsEndOfCmd ;is this the end of the command ('\0' or ':')?
a493: f0 ef beq errSyn_A484 ;yes -> syntax error (we're expecting an op or atom!)
a495: c9 0b cmp #11 ;have a single-digit number?
a497: 90 7b bcc ParseSingleDigitNum
a499: c9 fa cmp #tok_OP_MINUS ;unary '-'?
a49b: f0 ed beq doExprNegate_A48A
a49d: c9 f2 cmp #tok_OP_NOT ;NOT unary op?
a49f: f0 e6 beq doExprNot_A487
a4a1: c9 12 cmp #tok_NUM_AFTER ;decimal number?
a4a3: f0 51 beq ParseNumLiteral
a4a5: c9 11 cmp #tok_HEX_AFTER ;hex number?
a4a7: f0 4d beq ParseNumLiteral
a4a9: c9 f9 cmp #tok_OP_PLUS ;unary '+'?
a4ab: f0 e0 beq txtPtrIncr_A48D ;yes -> (no effect - just incr zpTXTPTR!)
a4ad: c9 28 cmp #‘(’ ;parenthesized expression?
a4af: f0 39 beq DoExprParens
a4b1: c9 22 cmp #‘"’ ;string literal?
a4b3: f0 80 beq ParseStrLit
a4b5: c9 41 cmp #‘A’ ;punctuation or raw digits?
a4b7: 90 2b bcc errSyn_A4E4 ;matched -> syntax error
a4b9: c9 5b cmp #‘[’ ;'A' thru 'Z'?
a4bb: 90 6f bcc DoExprVarName ;yes -> handle (presumed) var name
a4bd: c9 a1 cmp #tok_MOVE ;MOVE fn?
a4bf: f0 26 beq exprMove_A4E7 ;-> eval that
a4c1: c9 ca cmp #tok_ABS
a4c3: 90 1f bcc errSyn_A4E4 ;error unless we have a token >= ABS but < XOR
a4c5: c9 ef cmp #tok_OP_XOR ;(BASIC built-in functions)
a4c7: b0 1b bcs errSyn_A4E4
a4c9: 38 sec
a4ca: e9 ca sbc #tok_ABS ;subtract value of tok_ABS
a4cc: 0a asl A ;and double for pointer index
a4cd: aa tax
a4ce: bd 41 cf lda jtbl_Functions,x ;lookup the function, evaluate it, and return
a4d1: 85 19 sta zpMisc
a4d3: bd 42 cf lda jtbl_Functions+1,x
a4d6: 85 1a sta zpMisc+1
a4d8: 20 8c 90 jsr TxtPtrIncr ;move TXTPTR past function token
a4db: 20 bf 90 jsr TxtSkipSpaces
a4de: 20 3e a3 jsr JmpIndirectZpMisc
a4e1: 4c bf 90 jmp TxtSkipSpaces
a4e4: 4c 91 84 errSyn_A4E4 jmp ErrorSyntax
a4e7: 4c 8e a8 exprMove_A4E7 jmp ExprMove
a4ea: 20 8c 90 DoExprParens jsr TxtPtrIncr
a4ed: 20 9b a2 jsr Expression
a4f0: 20 7d a8 jsr SkipRParenOrSynErr
a4f3: 4c bf 90 jmp TxtSkipSpaces
a4f6: a6 30 ParseNumLiteral ldx zpExprStkIdx
a4f8: a0 01 ldy #$01
a4fa: b1 09 lda (zpTXTPTR),y
a4fc: 9d 00 04 sta exprParseStk,x ;move numeric literal directly onto parse stack
a4ff: c8 iny ;(not including leading token byte)
a500: e8 inx
a501: b1 09 lda (zpTXTPTR),y
a503: 9d 00 04 sta exprParseStk,x
a506: e8 inx ;increment index ("push" the literal onto the expr stack)
a507: e8 inx
a508: 86 30 stx zpExprStkIdx
a50a: 20 10 9f jsr TxtPtrAdvThree ;advance zpTXTPTR past the literal
SetIntTypAndSkipSpc
a50d: a9 02 lda #TYP_INT
a50f: 85 27 sta zpArgTyp
a511: 4c bf 90 jmp TxtSkipSpaces
ParseSingleDigitNum
a514: a6 30 ldx zpExprStkIdx
a516: 9d 00 04 sta exprParseStk,x ;store the digit into the parse buffer
a519: de 00 04 dec exprParseStk,x ;and decrement to the true number value
a51c: a9 00 lda #$00
a51e: 9d 01 04 sta exprParseStk+1,x ;store the high byte (#$00)
a521: e8 inx
a522: e8 inx
a523: e8 inx
a524: 86 30 stx zpExprStkIdx ;incr buffer index past number, and terminating null?
a526: 20 8c 90 jsr TxtPtrIncr ;advance to next byte of tokenized line
a529: 4c 0d a5 jmp SetIntTypAndSkipSpc
a52c: 20 7f a5 DoExprVarName jsr EvalVarName
a52f: a6 30 ldx zpExprStkIdx
a531: a5 90 lda zpVarType ;get the variable data's type
a533: 29 0f and #$0f ;(ignore whether or not it's from an array)
a535: c9 03 cmp #TYP_STR ;is it a string?
a537: f0 21 beq DEVN_exprStr ;yes -> do string stuff
a539: a0 00 ldy #$00 ;no: we have an int
a53b: b1 8c lda (zpVarData),y ;copy int onto parse stack
a53d: 9d 00 04 sta exprParseStk,x
a540: c8 iny
a541: b1 8c lda (zpVarData),y
a543: 9d 01 04 sta exprParseStk+1,x
a546: c8 iny
a547: b1 8c lda (zpVarData),y ;and the... type ID byte of the var that follows it?
a549: 9d 02 04 sta exprParseStk+2,x ;I suppose it's just a padding byte to make the size come out right
DEVN_advExprAndRts
a54c: e8 inx
a54d: e8 inx
a54e: e8 inx
a54f: 86 30 stx zpExprStkIdx ;advance parse stack index past the evaluated var data
a551: a5 90 lda zpVarType ;and set var type as current expr value's type
a553: 29 0f and #$0f
a555: 85 27 sta zpArgTyp
a557: 4c bf 90 jmp TxtSkipSpaces ;skip spaces and return
a55a: a0 00 DEVN_exprStr ldy #$00
a55c: b1 8c lda (zpVarData),y
a55e: 9d 00 04 sta exprParseStk,x ;strlen -> parse stack
a561: a9 01 lda #$01
a563: 18 clc
a564: 65 8c adc zpVarData ;then pointer at str contents
a566: 9d 01 04 sta exprParseStk+1,x
a569: a9 00 lda #$00
a56b: 65 8d adc zpVarData+1
a56d: 9d 02 04 sta exprParseStk+2,x
a570: 4c 4c a5 jmp DEVN_advExprAndRts ;advance the parse stack index and return
a573: 4c 91 84 errSyn_A573 jmp ErrorSyntax
; Given a var name at zpTXTPTR, looks it up and points zpVarData at its
; contents.
zpArrayVarIdx .var $19 {addr/2}
vZpMaxIdx .var $4a {addr/2} ;The max indices for an array var's dimensions
vZpSkipVarBaseType .var $73 {addr/1} ;underlying (non-array) base type for the array var we're skipping
vZpAryNumDimens .var $74 {addr/1} ;dimensionality of array var, -1 (0 or 1)
EvalVarNameOrSynErr
a576: 20 c4 a7 jsr IsAlpha
a579: b0 f8 bcs errSyn_A573
a57b: a2 00 ldx #$00
a57d: 86 30 stx zpExprStkIdx ;empty the parse stack
a57f: a2 00 EvalVarName ldx #$00
a581: 86 8b stx zpVarName+1 ;clear variable name second byte
a583: 85 8a sta zpVarName ;store var name first char (we already got)
a585: 20 98 90 jsr TxtPtrIncrAndGetChar ;get next char from line
a588: 20 bc a7 jsr IsAlNum ;is it alphanumeric?
a58b: b0 0a bcs @nonAlNum ;no -> look into that
a58d: 85 8b sta zpVarName+1 ;yes, store as var name 2nd char
a58f: 20 98 90 @alNumLp jsr TxtPtrIncrAndGetChar ;loop over any remaining alphanumeric chars, ignoring
a592: 20 bc a7 jsr IsAlNum
a595: 90 f8 bcc @alNumLp
a597: a9 02 @nonAlNum lda #TYP_INT
a599: 85 90 sta zpVarType
a59b: 20 bf 90 jsr TxtSkipSpaces
a59e: c9 24 cmp #‘$’
a5a0: d0 07 bne @notStr
a5a2: a9 03 lda #TYP_STR
a5a4: 85 90 sta zpVarType
a5a6: 20 8c 90 jsr TxtPtrIncr
a5a9: 20 bf 90 @notStr jsr TxtSkipSpaces
a5ac: c9 28 cmp #‘(’ ;array index?
a5ae: f0 70 beq EvalArrayVar ;yes -> handle array var index
a5b0: 20 0f a7 jsr FindVar ;find the var (string or int)
a5b3: b0 45 bcs EVN_varFound ;found -> get its data
a5b5: a0 00 ldy #$00 ;not found. Make one!
a5b7: a5 90 lda zpVarType ;(we're at end of vars tab now)
a5b9: 91 8e sta (zpVARPTR),y ;store variable type
a5bb: c8 iny
a5bc: a5 8a lda zpVarName ;store variable name
a5be: 91 8e sta (zpVARPTR),y
a5c0: c8 iny
a5c1: a5 8b lda zpVarName+1
a5c3: 91 8e sta (zpVARPTR),y
a5c5: c8 iny
a5c6: a9 00 lda #$00
a5c8: 91 8e sta (zpVARPTR),y ;store zeroes to next three bytes
a5ca: c8 iny ;(appropriate for both int AND str!)
a5cb: 91 8e sta (zpVARPTR),y
a5cd: c8 iny
a5ce: 91 8e sta (zpVARPTR),y ;(third byte indicates vars tab end, if INT)
a5d0: a5 90 lda zpVarType
a5d2: c9 02 cmp #TYP_INT
a5d4: f0 0c beq @int ;int? ->
a5d6: a9 00 lda #$00 ;string. Clear its contents to zero.
a5d8: a2 21 ldx #$21
a5da: c8 @clearStrLp iny
a5db: 91 8e sta (zpVARPTR),y
a5dd: ca dex
a5de: d0 fa bne @clearStrLp
a5e0: a9 21 lda #$21
a5e2: 18 @int clc
a5e3: 69 03 adc #$03 ;add size of var type, name, and data to VARPTR
a5e5: 65 8e adc zpVARPTR
a5e7: 85 28 sta zpWAccum
a5e9: a9 00 lda #$00
a5eb: 65 8f adc zpVARPTR+1
a5ed: 85 29 sta zpWAccum+1
a5ef: 20 b6 8b jsr CheckFreeSpaceOrError ;error if we have insufficient free space for new var
a5f2: a5 28 lda zpWAccum
a5f4: 85 1f sta zpVAREND ;record the new vars table end location
a5f6: a5 29 lda zpWAccum+1
a5f8: 85 20 sta zpVAREND+1
a5fa: 18 EVN_varFound clc
a5fb: a9 03 lda #$03
a5fd: 65 8e adc zpVARPTR ;add 3 to VARPTR to skip type and name
a5ff: 85 8c sta zpVarData ;and store result in zpVarData
a601: a9 00 lda #$00
a603: 65 8f adc zpVARPTR+1
a605: 85 8d sta zpVarData+1
a607: 60 rts
CpyVarPtrToVarData
a608: a5 8e lda zpVARPTR
a60a: 85 8c sta zpVarData
a60c: a5 8f lda zpVARPTR+1
a60e: 85 8d sta zpVarData+1
a610: 60 rts_A610 rts
ParseIntExprOrTMErr
a611: 20 9b a2 jsr Expression
a614: a9 02 lda #$02
a616: c5 27 cmp zpArgTyp
a618: f0 f6 beq rts_A610
a61a: 4c ff a9 errTypMism_A61A jmp ErrorTypeMismatch
a61d: 4c bd a6 singleIdxAry jmp EvalSingleIdxAryVar
a620: a5 8a EvalArrayVar lda zpVarName
a622: 48 pha
a623: a5 8b lda zpVarName+1
a625: 48 pha
a626: a5 90 lda zpVarType
a628: 09 80 ora #TYP_ARRAY_FLAG ;high bit = "array" flag?
a62a: 48 pha
a62b: 20 8c 90 jsr TxtPtrIncr
a62e: 20 11 a6 jsr ParseIntExprOrTMErr ;evaluate int expression for array index
a631: a6 30 ldx zpExprStkIdx
a633: ca dex
a634: ca dex
a635: ca dex
a636: bd 00 04 lda exprParseStk,x ;save away first byte of idx
a639: 48 pha
a63a: 20 f2 a6 jsr ParsStkPopIdx ;verify idx fits in 1 byte, and pop from parse stack
a63d: 90 de bcc singleIdxAry ;only one index? -> handle
; If we reach here, there's a second index provided for this array
a63f: 20 14 ac jsr SkipCommaOrSynErr
a642: 20 11 a6 jsr ParseIntExprOrTMErr ;evaluate int expr for 2nd index
a645: a6 30 ldx zpExprStkIdx
a647: ca dex
a648: ca dex
a649: ca dex
a64a: bd 00 04 lda exprParseStk,x ;save away first byte of 2nd idx
a64d: 48 pha
a64e: 20 f2 a6 jsr ParsStkPopIdx ;verify idx fits in 1 byte, and pop from parse stack
a651: b0 c7 bcs errTypMism_A61A ;didn't end with ')'? -> error!
a653: 20 8c 90 jsr TxtPtrIncr ;advance TXTPTR past closing parenthesis
a656: 68 pla
a657: 85 1b sta zpMisc+2 ;restore 2nd index (to $1B)
a659: 68 pla
a65a: 85 19 sta zpArrayVarIdx ;restore 1st index (to $19)
a65c: 68 pla
a65d: 85 90 sta zpVarType ;restore var type
a65f: 68 pla
a660: 85 8b sta zpVarName+1 ;restore 2nd byte of variable name
a662: 68 pla
a663: 85 8a sta zpVarName ;restore 1st byte of var name
a665: 20 0f a7 jsr FindVar ;find the matching variable
a668: 90 50 bcc errIllVal_A6BA ;didn't find? -> error IL (arrays must be DIM'd before use)
a66a: a0 03 ldy #$03
a66c: b1 8e lda (zpVARPTR),y ;is the found array var 2-dimensional?
a66e: f0 4a beq errIllVal_A6BA ;no -> error (we're looking for 2-dimensional)
a670: c8 iny
a671: b1 8e lda (zpVARPTR),y ;get max idx for first dimen
a673: c5 19 cmp zpArrayVarIdx ;less than the specified 1st index?
a675: 90 43 bcc errIllVal_A6BA ;yes -> error
a677: aa tax ;save max idx to x
a678: c8 iny
a679: b1 8e lda (zpVARPTR),y ;get max idx for second dimen
a67b: c5 1b cmp zpMisc+2 ;less than the specified 2nd index?
a67d: 90 3b bcc errIllVal_A6BA ;yes -> error
a67f: 20 ce a7 jsr ClearOperandHiBytes
a682: e8 inx ;max idx, +1 -> num elements in 1st dim
a683: d0 02 bne @not256
a685: e6 29 inc zpWAccum+1
a687: 86 28 @not256 stx zpWAccum
a689: a5 1b lda zpMisc+2
a68b: 85 2c sta zpWParam
a68d: 20 47 8f jsr WordMultiply ;times value for 2nd index
a690: a5 19 lda zpArrayVarIdx
a692: 85 2c sta zpWParam
a694: 20 a8 8e jsr SetIntAndAdd ;and add the value of the 1st index
a697: 20 04 a7 jsr GetSizeOfElementType
a69a: 85 2c sta zpWParam
a69c: 20 47 8f jsr WordMultiply ;multiply by size of element type
a69f: a9 06 lda #$06 ;add size of two-dimension array variable preamble
EAV_getArrayElem
a6a1: 85 2c sta zpWParam
a6a3: 20 a8 8e jsr SetIntAndAdd ;do the preamble add
a6a6: a5 8e lda zpVARPTR
a6a8: 85 2c sta zpWParam
a6aa: a5 8f lda zpVARPTR+1
a6ac: 85 2d sta zpWParam+1
a6ae: 20 a8 8e jsr SetIntAndAdd ;add to the current pointer into var tab (pointing at found var)
a6b1: a5 28 lda zpWAccum ;save result as ptr to the element value
a6b3: 85 8c sta zpVarData
a6b5: a5 29 lda zpWAccum+1
a6b7: 85 8d sta zpVarData+1
a6b9: 60 rts
a6ba: 4c 94 84 errIllVal_A6BA jmp ErrorIllegalValue
EvalSingleIdxAryVar
a6bd: 20 8c 90 jsr TxtPtrIncr
a6c0: 68 pla
a6c1: 85 19 sta zpArrayVarIdx ;restore the index value
a6c3: 68 pla
a6c4: 85 90 sta zpVarType ;this will have high-bit set now!
a6c6: 68 pla
a6c7: 85 8b sta zpVarName+1 ;restore var name
a6c9: 68 pla
a6ca: 85 8a sta zpVarName
a6cc: 20 0f a7 jsr FindVar
a6cf: 90 e9 bcc errIllVal_A6BA ;didn't find -> error IL (arrays must be DIM'd before use)
a6d1: a0 03 ldy #$03
a6d3: b1 8e lda (zpVARPTR),y ;is the found array var 2-dimensional?
a6d5: d0 e3 bne errIllVal_A6BA ;yes -> error (we're looking for single!)
a6d7: c8 iny
a6d8: b1 8e lda (zpVARPTR),y ;get max array index
a6da: c5 19 cmp zpArrayVarIdx ;is it less than the index used in the expression?
a6dc: 90 dc bcc errIllVal_A6BA ;yes -> error
a6de: a5 19 lda zpArrayVarIdx
a6e0: 85 28 sta zpWAccum
a6e2: 20 04 a7 jsr GetSizeOfElementType
a6e5: 85 2c sta zpWParam
a6e7: 20 ce a7 jsr ClearOperandHiBytes
a6ea: 20 47 8f jsr WordMultiply ;multiply index by size of elements
a6ed: a9 05 lda #$05 ;add size of single-dimension array variable preamble
a6ef: 4c a1 a6 jmp EAV_getArrayElem
; Pops the evaluated array index off the parse stack, erroring if the index
; value exceeds one byte (too big to be an index!). Returns with carry clear if
; and only if next token is a closing parenthesis (')')
a6f2: bd 01 04 ParsStkPopIdx lda exprParseStk+1,x ;second index byte is #$00?
a6f5: d0 c3 bne errIllVal_A6BA ;no -> too big! error
a6f7: 86 30 stx zpExprStkIdx ;save our backed-up stack index
a6f9: 20 bf 90 jsr TxtSkipSpaces
a6fc: c9 29 cmp #‘)’ ;next char == ')'?
a6fe: d0 02 bne @retFalse ;no -> return false
a700: 18 clc ;yes, return true
a701: 60 rts
a702: 38 @retFalse sec
a703: 60 rts
GetSizeOfElementType
a704: a5 90 lda zpVarType
a706: 29 0f and #$0f
a708: c9 02 GetSizeOfType cmp #TYP_INT
a70a: f0 02 beq @rts ;INT? type #$02, has size #$02... -> just return!
a70c: a9 21 lda #33 ;strings are 33 bytes total. 1 len, 31 content, 1 terminating null
a70e: 60 @rts rts
; Finds variable record from zpVarName ($8A.8B) and zpVarType ($90, temp use).
; Unlike most other routines in Family BASIC, exiting with carry set indicates
; success, reset indicates failure.
a70f: a5 1d FindVar lda zpVARTAB
a711: 85 8e sta zpVARPTR
a713: a5 1e lda zpVARTAB+1
a715: 85 8f sta zpVARPTR+1
a717: a0 00 @findVarLoop ldy #$00
a719: b1 8e lda (zpVARPTR),y ;end of variables table?
a71b: f0 36 beq @retFail ;yes -> FAIL
a71d: c5 90 cmp zpVarType ;matches our sought variable type?
a71f: d0 10 bne @skipVar ;no -> skip to next var
a721: c8 iny
a722: b1 8e lda (zpVARPTR),y
a724: c5 8a cmp zpVarName ;first letter of var name matches?
a726: d0 09 bne @skipVar ;no -> skip to next var
a728: c8 iny
a729: b1 8e lda (zpVARPTR),y
a72b: c5 8b cmp zpVarName+1 ;second char of var name matches?
a72d: d0 02 bne @skipVar ;no -> skip to next var
a72f: 38 sec ;var matched- return success (carry set)!
a730: 60 rts
a731: a0 00 @skipVar ldy #$00 ;skipVar
a733: b1 8e lda (zpVARPTR),y ;is it an array type?
a735: 30 1e bmi @skipAryVar ;yes -> handle skipping array
a737: 20 08 a7 jsr GetSizeOfType ;no, get basic type size
a73a: 18 clc
a73b: 69 03 adc #$03 ;add the 3 bytes for type and name
a73d: 65 8e adc zpVARPTR ;and add to variables table index to skip the var
a73f: 85 8e sta zpVARPTR
a741: a9 00 lda #$00
a743: 65 8f adc zpVARPTR+1
a745: 85 8f sta zpVARPTR+1
@contIfNotVarsEnd
a747: a5 1f lda zpVAREND ;did we reach end of var table?
a749: c5 8e cmp zpVARPTR
a74b: d0 ca bne @findVarLoop ;no -> check next var for a match
a74d: a5 20 lda zpVAREND+1 ;low byte matched.
a74f: c5 8f cmp zpVARPTR+1 ;does high byte?
a751: d0 c4 bne @findVarLoop ;no -> check next var for a match
a753: 18 @retFail clc ;yes: end of vars with no match. Return failure (carry clear)
a754: 60 rts
a755: 29 0f @skipAryVar and #$0f
a757: 85 73 sta vZpSkipVarBaseType ;record array's element type (stripping array flag)
a759: a0 03 ldy #$03
a75b: b1 8e lda (zpVARPTR),y
a75d: 85 74 sta vZpAryNumDimens ;number of array dimensions, minus one.
a75f: c8 iny
a760: b1 8e lda (zpVARPTR),y
a762: 85 4a sta vZpMaxIdx ;max index of array
a764: c8 iny
a765: b1 8e lda (zpVARPTR),y ;max index of 2nd dimension of array (if 2 dimension)
a767: 85 4b sta vZpMaxIdx+1 ;(otherwise, this is first byte of first element!)
a769: 20 ce a7 jsr ClearOperandHiBytes ;clear WParam+1, WAccum+1
a76c: 85 2c sta zpWParam ;clear low byte of WParam as well
a76e: a5 4a lda vZpMaxIdx
a770: 85 28 sta zpWAccum ;1st dimension max index goes into WAccum
a772: a5 74 lda vZpAryNumDimens ;one-dimensional array, or two?
a774: f0 04 beq @oneDim ;one -> leave WParam zero (ought to skip the first multiply, but doesn't)
a776: a5 4b lda vZpMaxIdx+1 ;two: max idx of 2nd dimension -> WParam
a778: 85 2c sta zpWParam
a77a: e6 28 @oneDim inc zpWAccum ;increment WParam (max idx -> number-of-elements)
a77c: d0 02 bne @waHi
a77e: e6 29 inc zpWAccum+1
a780: e6 2c @waHi inc zpWParam ;increment WAccum (max idx -> number-of-elements)
a782: d0 02 bne @wpHi
a784: e6 2d inc zpWParam+1
a786: 20 47 8f @wpHi jsr WordMultiply ;multiply them (now have total number of elements)
a789: a5 73 lda vZpSkipVarBaseType
a78b: 20 08 a7 jsr GetSizeOfType ;get element type size
a78e: 85 2c sta zpWParam ;store in WParam
a790: a9 00 lda #$00
a792: 85 2d sta zpWParam+1
a794: 20 47 8f jsr WordMultiply ;multiply
a797: a9 05 lda #$05 ;total number of bytes for the elements
a799: 85 2c sta zpWParam ; plus 5 for array type and name, and max index value
a79b: a5 74 lda vZpAryNumDimens
a79d: f0 02 beq @oneDim2
a79f: e6 2c inc zpWParam ;...make that 6, for the two max index entries
a7a1: 20 a8 8e @oneDim2 jsr SetIntAndAdd ;(do that add)
a7a4: a5 8e lda zpVARPTR
a7a6: 85 2c sta zpWParam
a7a8: a5 8f lda zpVARPTR+1
a7aa: 85 2d sta zpWParam+1
a7ac: 20 a8 8e jsr SetIntAndAdd ;now add to our index into the variables table
a7af: a5 28 lda zpWAccum ;and put result back into index
a7b1: 85 8e sta zpVARPTR
a7b3: a5 29 lda zpWAccum+1
a7b5: 85 8f sta zpVARPTR+1
a7b7: 4c 47 a7 jmp @contIfNotVarsEnd ;we succesfully skipped the array var! Check the next.
a7ba: 38 @secAndRts sec
a7bb: 60 rts
; clears carry if A is alphanumeric, sets otherwise
• Clear variables
a7bc: c9 30 IsAlNum cmp #$30 ; < '0' (punctuation, or control chars)?
a7be: 90 fa bcc @secAndRts
a7c0: c9 3a cmp #$3a ; digit?
a7c2: 90 08 bcc @clcAndRts
; clears carry if A is alphabetic, sets otherwise
a7c4: c9 41 IsAlpha cmp #$41 ; additional punctuation?
a7c6: 90 f2 bcc @secAndRts
a7c8: c9 5b cmp #$5b ; another set of punctuation?
a7ca: b0 ee bcs @secAndRts
a7cc: 18 @clcAndRts clc
a7cd: 60 rts
ClearOperandHiBytes
a7ce: a9 00 lda #$00
a7d0: 85 2d sta zpWParam+1
a7d2: 85 29 sta zpWAccum+1
a7d4: 60 rts
AddAndReplaceExpr
a7d5: 20 ac 8e jsr Add
; Copies WAccum to the expression parse stack, replacing the previous top
; exprssion on the stack
ReplaceExprStkTopWAccum
a7d8: a6 30 ldx zpExprStkIdx
a7da: ca dex
a7db: ca dex
a7dc: ca dex
a7dd: a5 28 lda zpWAccum
a7df: 9d 00 04 sta exprParseStk,x
a7e2: a5 29 lda zpWAccum+1
a7e4: 9d 01 04 sta exprParseStk+1,x
a7e7: a5 2a lda zpWAccum+2
a7e9: 9d 02 04 sta exprParseStk+2,x
a7ec: 4c bf 90 jmp TxtSkipSpaces
SubtractAndReplaceExpr
a7ef: 20 0f 8e jsr Subtract
a7f2: 4c d8 a7 jmp ReplaceExprStkTopWAccum
MulAndReplaceExpr
a7f5: 20 0f 8f jsr MultiplyAndCheckPosNeg
a7f8: 4c d8 a7 jmp ReplaceExprStkTopWAccum
DivAndReplaceExpr
a7fb: a5 2c lda zpWParam ;check for division by zero
a7fd: d0 04 bne @notZero
a7ff: a5 2d lda zpWParam+1
a801: f0 06 beq ErrorDivisionByZero
a803: 20 9d 8f @notZero jsr WordDivide
a806: 4c d8 a7 jmp ReplaceExprStkTopWAccum
ErrorDivisionByZero
a809: a9 0a lda #err_DZ
a80b: 4c 37 b2 jmp PrintErrorAndStop
ModAndReplaceExpr
a80e: a5 29 lda zpWAccum+1
a810: 48 pha
a811: 20 fb a7 jsr DivAndReplaceExpr ;perform division
a814: a5 2a lda zpWAccum+2 ;move remainder to WAccum
a816: 85 28 sta zpWAccum
a818: a5 2b lda zpWAccum+3
a81a: 85 29 sta zpWAccum+1
a81c: 68 pla ;was the divisor positive?
a81d: 10 0a bpl @chkRslt ;yes -> check result
a81f: a5 29 lda zpWAccum+1
a821: 30 03 bmi @replStkTop
a823: 20 32 8e @negate jsr WAccumNegate
a826: 4c d8 a7 @replStkTop jmp ReplaceExprStkTopWAccum
a829: a5 29 @chkRslt lda zpWAccum+1 ;is result positive?
a82b: 10 f9 bpl @replStkTop ;yes -> leave result alone
a82d: 30 f4 bmi @negate ;no: negate then close up
a82f: 20 ee 8f OpFn_NotEq jsr Compare ;compare A and B
a832: d0 09 bne RelOp_ReturnTrue ;unequal? -> return true
RelOp_ReturnFalse
a834: a9 00 lda #$00 ;load 0 (false)
RelOp_RetTrueOrFalse
a836: 85 28 sta zpWAccum ;store acc to both bytes of WAccum
a838: 85 29 sta zpWAccum+1
a83a: 4c d8 a7 jmp ReplaceExprStkTopWAccum ;and replace as result at top of expr parse stack
RelOp_ReturnTrue
a83d: a9 ff lda #$ff ;load -1 (true)
a83f: 4c 36 a8 jmp RelOp_RetTrueOrFalse
a842: 20 ee 8f OpFn_LtEq jsr Compare ;compare A and B
a845: f0 f6 beq RelOp_ReturnTrue ;equal? -> true
a847: 90 f4 bcc RelOp_ReturnTrue ;A < B? -> also true
a849: 4c 34 a8 jmp RelOp_ReturnFalse ;else -> false
a84c: 20 ee 8f OpFn_GrEq jsr Compare ;compare A and B
a84f: b0 ec bcs RelOp_ReturnTrue ;A >= B? true
a851: 4c 34 a8 jmp RelOp_ReturnFalse ;else -> false
a854: 20 ee 8f OpFn_Equal jsr Compare ;compare A and B
a857: f0 e4 beq RelOp_ReturnTrue ;A == B? -> true
a859: 4c 34 a8 jmp RelOp_ReturnFalse ;else -> false
OpFn_GreaterThan
a85c: 20 ee 8f jsr Compare ;compare A and B
a85f: f0 d3 beq RelOp_ReturnFalse ;A == B? -> false
a861: b0 da bcs RelOp_ReturnTrue ;else A >= B? -> true
a863: 4c 34 a8 jmp RelOp_ReturnFalse ;otherwise -> false
a866: 20 ee 8f OpFn_LessThan jsr Compare ;compare A and B
a869: 90 d2 bcc RelOp_ReturnTrue ;A < B? -> true
a86b: 4c 34 a8 jmp RelOp_ReturnFalse ;else -> false
; Jump table for relative/comparative operators
a86e: 2f a8 jtbl_RelOps .dd2 OpFn_NotEq ; <>
a870: 4c a8 .dd2 OpFn_GrEq ; >=
a872: 42 a8 .dd2 OpFn_LtEq ; <=
a874: 54 a8 .dd2 OpFn_Equal ; =
a876: 5c a8 .dd2 OpFn_GreaterThan ; >
a878: 66 a8 .dd2 OpFn_LessThan ; <
SkipLParenOrSynErr
a87a: a2 28 ldx #‘(’
a87c: 2c bit ▼ $29a2
SkipRParenOrSynErr
a87d: a2 29 ldx #‘)’
a87f: 86 75 stx zpHeldKeyChar
a881: 20 bf 90 jsr TxtSkipSpaces
a884: c5 75 cmp zpHeldKeyChar
a886: d0 49 bne errSyn_A8D1
a888: 4c 8c 90 jmp TxtPtrIncr
a88b: 4c 94 84 errIllVal_A88B jmp ErrorIllegalValue
a88e: 20 8c 90 ExprMove jsr TxtPtrIncr
a891: 20 04 aa jsr EvalParenByteOrError ;evaluate MOVE arg
a894: a4 28 ldy zpWAccum
a896: c0 08 cpy #$08 ;arg >= 8?
a898: b0 f1 bcs errIllVal_A88B ;yes -> error
a89a: b9 45 06 lda sprAttrFlags,y ;get DEF MOVE data on specified sprite
a89d: 29 10 and #$10 ;select whether movement is active
a89f: f0 02 beq @zero ;zero? -> keep that value
a8a1: a9 ff lda #$ff ;non-zero, convert to value (-1)
a8a3: 85 28 @zero sta zpWAccum ;save that value to WAccum (as word)
a8a5: 85 29 sta zpWAccum+1
a8a7: 20 7e a9 jsr SkipRParenSetParseStkInt ;and save it as the parse stack top
a8aa: 4c bf 90 jmp TxtSkipSpaces
a8ad: a9 00 ZeroWAccum lda #$00
a8af: 85 28 sta zpWAccum
; (Only "truncates" bc caller always sets A-reg to zero)
TruncWAccumToByte
a8b1: 85 29 sta zpWAccum+1
a8b3: 85 2a sta zpWAccum+2
a8b5: 60 rts
; Skips over a parentheses at TXTPTR, or errors if no paren.
; Evaluates the parenthesized expression, and copies the three bytes to WAccum
EvalParenExprOrError
a8b6: 20 7a a8 jsr SkipLParenOrSynErr
a8b9: 20 9b a2 jsr Expression
a8bc: a6 30 ldx zpExprStkIdx
a8be: ca dex
a8bf: bd 00 04 lda exprParseStk,x
a8c2: 85 2a sta zpWAccum+2
a8c4: ca dex
a8c5: bd 00 04 lda exprParseStk,x
a8c8: 85 29 sta zpWAccum+1
a8ca: ca dex
a8cb: bd 00 04 lda exprParseStk,x
a8ce: 85 28 sta zpWAccum
a8d0: 60 rts
a8d1: 4c 91 84 errSyn_A8D1 jmp ErrorSyntax
; dead/unreachable code
a8d4: a6 30 ldx zpExprStkIdx
a8d6: a5 28 lda zpWAccum
a8d8: 9d 00 04 sta exprParseStk,x
a8db: e8 inx
a8dc: a5 29 lda zpWAccum+1
a8de: 9d 00 04 sta exprParseStk,x
a8e1: e8 inx
a8e2: e8 inx
a8e3: 86 30 stx zpExprStkIdx
a8e5: 60 rts
a8e6: a5 49 FnFn_CSRLIN lda zpCV
a8e8: 85 28 sta zpWAccum ;load cursor y pos into low byte of WAccum
PushWAccByteToParseStk
a8ea: a9 00 lda #$00
a8ec: 85 29 sta zpWAccum+1 ;zero out high byte of WAccum
PushWAccIntToParseStk
a8ee: e6 30 inc zpExprStkIdx ;create space on parse stack for a "pushed" return value
a8f0: e6 30 inc zpExprStkIdx
a8f2: e6 30 inc zpExprStkIdx
SetParseStkTopToWAccInt
a8f4: a9 02 lda #TYP_INT
a8f6: 85 27 sta zpArgTyp ;set current type as int
SetParseStkTopToWAcc
a8f8: a6 30 ldx zpExprStkIdx
a8fa: ca dex
a8fb: ca dex
a8fc: ca dex
a8fd: a5 28 lda zpWAccum ;copy 3 bytes from WAcccum over the top item of the parse stack
a8ff: 9d 00 04 sta exprParseStk,x
a902: a5 29 lda zpWAccum+1
a904: 9d 01 04 sta exprParseStk+1,x
a907: a5 2a lda zpWAccum+2
a909: 9d 02 04 sta exprParseStk+2,x
a90c: 60 rts ;done
a90d: a5 b5 FnFn_ERR lda zpErrCode
a90f: 85 28 sta zpWAccum ;load last error's code into WAccum
a911: 4c ea a8 jmp PushWAccByteToParseStk ;and return it via expr parse stack
a914: a5 b7 FnFn_ERL lda zpErrLineNum
a916: 85 28 sta zpWAccum ;get line num from last err into WAccum
a918: a5 b8 lda zpErrLineNum+1
a91a: 85 29 sta zpWAccum+1
a91c: 4c ee a8 jmp PushWAccIntToParseStk ;and return it via the expr parse stack
a91f: 20 25 a9 FnFn_FRE jsr AmtFreeMemToWAccum ;get the amount of free memory into WAccum
a922: 4c ee a8 jmp PushWAccIntToParseStk ;and push that to parse stack
AmtFreeMemToWAccum
a925: 38 sec
a926: a5 03 lda zpFRETOP ;save (FRETOP - VAREND) to WAccum
a928: e5 1f sbc zpVAREND
a92a: 85 28 sta zpWAccum
a92c: a5 04 lda zpFRETOP+1
a92e: e5 20 sbc zpVAREND+1
a930: 85 29 sta zpWAccum+1
a932: 60 rts
a933: 20 0a aa FnFn_POS jsr EvalParenIntOrError ;read (and discard) dummy integer argument
a936: a5 48 lda zpCH ;copy cursor x pos to low byte of WAccum
SetWacByteSkipRPSetParseStkInt
a938: 85 28 sta zpWAccum ;set Waccum low byte to what's in A reg
a93a: a9 00 lda #$00
a93c: 85 29 sta zpWAccum+1 ;zero out the high byte of WAccum
a93e: 4c 7e a9 jmp SkipRParenSetParseStkInt ;skip right paren, and return WAccum in expr stack
GetActorDefMoveIdxFromArg
a941: 20 04 aa jsr EvalParenByteOrError ;get actor # arg into WAccum (as byte)
GetActorDefMoveIdx
a944: a5 28 lda zpWAccum ;read actor # from WAccum (byte)
a946: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure it's a value < 8
a949: 0a asl A ;multiply by 8, for MOVE dfn indexing
a94a: 0a asl A
a94b: 0a asl A
a94c: aa tax
a94d: 60 rts ;and return in X-reg
; Parses an open paren (or errors), then parses an integer and errors unless its
; value is in the range 0 thru 7 (inclusive - sprite actor number). The parsed
; value is in both A reg and X reg upon (successful) exit.
EvalParenZeroThruSevenOrError
a94e: 20 04 aa jsr EvalParenByteOrError
a951: a5 28 lda zpWAccum
a953: 20 44 af jsr ErrUnlessZeroThruSeven
a956: aa tax
a957: 60 rts
a958: 20 41 a9 FnFn_XPOS jsr GetActorDefMoveIdxFromArg ;get index from actor-num arg
a95b: bd c6 06 lda defMovSPosX,x ;grab X pos
a95e: 4c 38 a9 jmp SetWacByteSkipRPSetParseStkInt ;-> and return it
a961: 20 41 a9 FnFn_YPOS jsr GetActorDefMoveIdxFromArg ;get index from actor-num arg
a964: bd c7 06 lda defMovSPosY,x ;grab Y pos
a967: 4c 38 a9 jmp SetWacByteSkipRPSetParseStkInt ;-> and return it
a96a: 20 41 a9 FnFn_VCT jsr GetActorDefMoveIdxFromArg ;get index from actor-num arg
a96d: bd c0 06 lda defMovSFacing,x ;grab facing
a970: 4c 38 a9 jmp SetWacByteSkipRPSetParseStkInt ;-> and return it
a973: 20 0a aa FnFn_PEEK jsr EvalParenIntOrError ;eval parenthesized int arg
a976: a0 00 ldy #$00
a978: b1 28 lda (zpWAccum),y ;peek at specified addr
a97a: 85 28 sta zpWAccum
a97c: 84 29 sty zpWAccum+1 ;save result to WAccum
SkipRParenSetParseStkInt
a97e: 20 7d a8 jsr SkipRParenOrSynErr
a981: 4c f4 a8 jmp SetParseStkTopToWAccInt
a984: 20 0a aa FnFn_ABS jsr EvalParenIntOrError ;eval parenthesized arg
a987: 20 f2 8d jsr Cmp32768 ;is the value -32,768?
a98a: f0 0d beq errOverfl_A999 ;yes -> can't negate, won't fit
a98c: a5 29 lda zpWAccum+1
a98e: 30 03 bmi @neg ;negative? -> negate it to positive
a990: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> else just set expr value as-is
a993: 20 32 8e @neg jsr WAccumNegate ;negate
a996: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> then set as our value on parse stack
a999: 4c f2 8e errOverfl_A999 jmp ErrorOverflow
a99c: 20 0a aa FnFn_SGN jsr EvalParenIntOrError ;parse parenthesized int argument
a99f: a5 29 lda zpWAccum+1 ;is it negative?
a9a1: 30 09 bmi @neg ;yes -> handle; else, is it positive?
a9a3: d0 10 bne @pos ;yes -> handle
a9a5: a5 28 lda zpWAccum ;(check low byte pos too, if high byte is zero)
a9a7: d0 0c bne @pos
a9a9: a9 00 lda #$00 ;zero: set Waccum to zero
a9ab: 2c bit ▼ $ffa9
a9ac: a9 ff @neg lda #$ff ;neg: set Waccum to -1
a9ae: 85 28 sta zpWAccum
@wrHiBhyteAndFinish
a9b0: 85 29 sta zpWAccum+1
a9b2: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> skip closing parenthesis, and return WAccum
a9b5: a9 01 @pos lda #$01 ;pos: set Waccum to 1
a9b7: 85 28 sta zpWAccum
a9b9: a9 00 lda #$00 ;load zero for high byte
a9bb: f0 f3 beq @wrHiBhyteAndFinish
a9bd: 20 0a aa FnFn_RND jsr EvalParenIntOrError ;get parenthesized int arg (max ret val)
a9c0: a5 28 lda zpWAccum
a9c2: 48 pha
a9c3: a5 29 lda zpWAccum+1
a9c5: 48 pha ;push the max val to CPU stack
a9c6: a5 56 lda zpRandom
a9c8: 85 28 sta zpWAccum
a9ca: a5 57 lda zpRandom+1 ;take previous PRNG result
a9cc: 85 29 sta zpWAccum+1 ;and place in WAccum
a9ce: a9 83 lda #$83
a9d0: 85 2c sta zpWParam
a9d2: a9 03 lda #$03
a9d4: 85 2d sta zpWParam+1 ;multiply by 899
a9d6: 20 44 8f jsr WordMultiplyWithTypeCheck
a9d9: a9 7f lda #$7f
a9db: 25 29 and zpWAccum+1 ;clear away sign bit (max 32,767)
a9dd: 85 29 sta zpWAccum+1
a9df: 85 57 sta zpRandom+1 ;store the result as the new value for zpRandom
a9e1: a5 28 lda zpWAccum
a9e3: 85 56 sta zpRandom
a9e5: 68 pla ;pull the user's arg, and stick into WParam
a9e6: 85 2d sta zpWParam+1
a9e8: 68 pla
a9e9: 85 2c sta zpWParam
a9eb: 20 44 8f jsr WordMultiplyWithTypeCheck ;multiply by the (7-bit) random number we got
a9ee: 06 29 asl zpWAccum+1 ;then shift the result (including overflow) one bit left
a9f0: 26 36 rol zpMulScratch+2
a9f2: 26 37 rol zpMulScratch+3
a9f4: a5 36 lda zpMulScratch+2
a9f6: 85 28 sta zpWAccum ;store the 16-bit overflow
a9f8: a5 37 lda zpMulScratch+3 ; in WAccum (it's guaranteed to be
a9fa: 85 29 sta zpWAccum+1 ; less than the user's number)
a9fc: 4c 7e a9 jmp SkipRParenSetParseStkInt ;read past the closing paren, set top of parse stack to our return value
ErrorTypeMismatch
a9ff: a9 0b lda #err_TM
aa01: 4c 37 b2 jmp PrintErrorAndStop
; Same as EvalParenIntOrError, below, but additionally error if the int value
; doesn't fit in a single byte.
EvalParenByteOrError
aa04: 20 0a aa jsr EvalParenIntOrError
aa07: 4c 80 ab jmp ErrIfWAccumIsWord
; If we're at a parenthesized integer expression, evaluate it and place in
; WAccum. If not, ErrorTypeMismatch.
EvalParenIntOrError
aa0a: 20 b6 a8 jsr EvalParenExprOrError
ErrorUnlessInteger
aa0d: a9 02 lda #TYP_INT
aa0f: ae ldx ▼ $03a9
ErrorUnlessString
aa10: a9 03 lda #TYP_STR
aa12: c5 27 cmp zpArgTyp
aa14: d0 e9 bne ErrorTypeMismatch
aa16: 60 rts
EvalParenStrOrError
aa17: 20 b6 a8 jsr EvalParenExprOrError
aa1a: 4c 10 aa jmp ErrorUnlessString
aa1d: 20 17 aa FnFn_LEN jsr EvalParenStrOrError ;get parenthesized string
TruncWAccumParseRParen
aa20: a9 00 lda #$00
aa22: 20 b1 a8 jsr TruncWAccumToByte ;clear WAccum bytes except lowest
aa25: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> finish up
aa28: 20 17 aa FnFn_ASC jsr EvalParenStrOrError ;get parenthesized string arg
aa2b: a5 28 lda zpWAccum ;empty string?
aa2d: f0 f1 beq TruncWAccumParseRParen ;yes -> return zero
aa2f: a0 00 ldy #$00
aa31: b1 29 lda (zpWAccum+1),y ;read first char of string
aa33: 85 28 sta zpWAccum ;save its value in zpWAccum
aa35: 4c 20 aa jmp TruncWAccumParseRParen ;-> clear other bytes of WAccum and read closing paren. Done!
aa38: 4c 94 84 errIllVal_AA38 jmp ErrorIllegalValue
aa3b: 20 56 b6 FnFn_SCR_STR jsr SaveCursorPos ;save CH/CV
aa3e: 20 04 aa jsr EvalParenByteOrError ;read first arg; x pos
aa41: a5 28 lda zpWAccum
aa43: c9 1c cmp #COLS ; >= # columns?
aa45: b0 f1 bcs errIllVal_AA38 ;yes -> error
aa47: 85 48 sta zpCH ;else, save as cursor x pos (for now)
aa49: 20 3b ac jsr CommaThenIntExprToWAccum ;read 2nd arg; y pos
aa4c: 20 80 ab jsr ErrIfWAccumIsWord ;ensure only uses one byte
aa4f: a5 28 lda zpWAccum
aa51: c9 18 cmp #ROWS ; >= # rows?
aa53: b0 e3 bcs errIllVal_AA38 ;yes -> error
aa55: 85 49 sta zpCV ;else, save as cursor y pos (for now)
aa57: 20 bf 90 jsr TxtSkipSpaces
aa5a: c9 29 cmp #‘)’ ;do we have closing paren?
aa5c: f0 12 beq @getTileId ;yes -> we're getting the tile ID/character at that pos
aa5e: 20 3b ac jsr CommaThenIntExprToWAccum ;else get 3rd arg
aa61: a5 28 lda zpWAccum ; == 0?
aa63: f0 0b beq @getTileId ;yes -> same as omitted. We're getting tile ID/character
aa65: 20 8d b0 jsr ErrUnlessZeroOrOne ;if it's not zero, ensure it's one
aa68: 20 b0 c4 jsr GetTileQuadPaletteNum ;go and fetch the palette number for this location
aa6b: a5 9c lda zpPaletteNum
aa6d: 4c 7a aa jmp @finish ;-> restore cursor pos and return the value
aa70: 20 f4 b4 @getTileId jsr CalcCursorTilemapPos ;calculate address in VRAM of tile at x, y
aa73: a9 05 lda #NC_GetCharUnderCursor
aa75: 20 6a 89 jsr WaitForNmiSubcommand ;get the char/tile ID for that
aa78: a5 73 lda zpCharUnderCurs
aa7a: 85 28 @finish sta zpWAccum ;save value to WAccum
aa7c: 20 5f b6 jsr RestoreCursorPos ;restore original CH/CV
aa7f: 4c 85 aa jmp MakeSingleCharStrFromWAcc ;make a string out of the char found, and return that via expr parse stack
aa82: 20 04 aa FnFn_CHR_STR jsr EvalParenByteOrError ;get parenthesized byte-sized int arg
MakeSingleCharStrFromWAcc
aa85: a5 28 lda zpWAccum ;get its value into A reg
ReturnNewSingleCharStrFromAReg
aa87: 20 94 aa jsr MakeSingleCharStrFromAReg
SetParseStkTopToWAccStr
aa8a: a9 03 lda #TYP_STR
aa8c: 85 27 sta zpArgTyp ;set var type = string
aa8e: 20 7d a8 jsr SkipRParenOrSynErr ;read closing paren
aa91: 4c f8 a8 jmp SetParseStkTopToWAcc ;return our value by setting the top value of the expression stack
MakeSingleCharStrFromAReg
aa94: a6 21 ldx zpGpStrIdx
aa96: 9d 00 07 sta genPurposeBuf,x ;store value in A-reg to genPurposeBuf
aa99: e6 21 inc zpGpStrIdx ;increment index to reflect our consumption of a single byte
aa9b: a9 01 lda #$01
aa9d: 85 28 sta zpWAccum ;set first byte of WAccum to 1 (string length)
aa9f: 86 29 stx zpWAccum+1 ;point the next two bytes at the space
aaa1: a9 07 lda #>genPurposeBuf ; in genPurposeBuf we just wrote to
aaa3: 85 2a sta zpWAccum+2
aaa5: 60 rts ;return
vZpStrLen .var $90 {addr/1}
aaa6: 20 0a aa FnFn_STR_STR jsr EvalParenIntOrError ;eval parenthesized int
aaa9: 20 98 91 jsr DecStrFromWord ;convert int to decimal string
aaac: 20 d6 90 CopyDecOrHexStr jsr FindFirstNonzeroDigit ;skip leading zeroes
aaaf: a9 00 lda #$00
aab1: 85 90 sta vZpStrLen ;start with strlen of 0
aab3: a4 21 ldy zpGpStrIdx
aab5: a6 46 ldx zpTmp46 ;get the position of first non-zero digit into X
aab7: b5 3d @cpyLp lda zpNumStrTmp,x ;get char from decimal str
aab9: f0 0a beq @copyDone ;null terminator? -> done copying
aabb: 99 00 07 sta genPurposeBuf,y ;else write char to genPurposeBuf
aabe: e8 inx
aabf: c8 iny
aac0: e6 90 inc vZpStrLen ;increment strlen
aac2: 4c b7 aa jmp @cpyLp ;-> copy next character
aac5: a5 21 @copyDone lda zpGpStrIdx ;load original index into genPurposeBuf (str start)
aac7: 84 21 sty zpGpStrIdx ;save new index
aac9: 85 29 sta zpWAccum+1 ;save str start low byte
aacb: a9 07 lda #>genPurposeBuf
aacd: 85 2a sta zpWAccum+2 ;save str start high byte (genPurposeBuf)
aacf: a5 90 lda vZpStrLen
aad1: 85 28 sta zpWAccum ;save strlen
aad3: 4c 8a aa jmp SetParseStkTopToWAccStr ;-> wrap things up
aad6: 20 0a aa FnFn_HEX_STR jsr EvalParenIntOrError ;eval parenthesized int
aad9: 20 da 91 jsr HexStrFromWord ;convert int to hex string
aadc: 4c ac aa jmp CopyDecOrHexStr ;proceed as for STR$
• Clear variables
vZpStrLen .var $74 {addr/1}
aadf: 20 17 aa FnFn_VAL jsr EvalParenStrOrError ;parse parenthesized string arg
aae2: a5 28 lda zpWAccum ;empty string?
aae4: f0 27 beq VAL_returnZero ;yes -> return as zero
aae6: 20 30 94 jsr SaveTXTPTR ;save TXTPTR
aae9: a5 28 lda zpWAccum
aaeb: 85 74 sta vZpStrLen ;use string length as a copy countdown
aaed: a2 00 ldx #$00
aaef: a0 00 ldy #$00
aaf1: b1 29 @cpyLp lda (zpWAccum+1),y ;copy string contents into LineBuffer
aaf3: 9d 00 05 sta lineBuffer,x
aaf6: e8 inx
aaf7: c8 iny
aaf8: c6 74 dec vZpStrLen ;decr chars to copy. == 0?
aafa: d0 f5 bne @cpyLp ;no -> loop and copy next character
aafc: a9 00 lda #$00
aafe: 9d 00 05 sta lineBuffer,x ;null-terminate the string
ab01: 20 a4 8b jsr InitLineBufferScanning ;point TXTPTR at LineBuffer
ab04: 20 2d ab jsr READ_parseNumber ;parse a number from the string into WAccum
ab07: 20 39 94 jsr RestoreTXTPTR ;restore TXTPTR to past the argument
ab0a: 4c 7e a9 jmp SkipRParenSetParseStkInt ;skip past right-paren, and return the value in WAccum onto parse stack
ab0d: 20 ad a8 VAL_returnZero jsr ZeroWAccum
ab10: 4c 7e a9 jmp SkipRParenSetParseStkInt
• Clear variables
ParseAndNegateDecWord
ab13: 20 8c 90 jsr TxtPtrIncr ;advance txtptr
ab16: 20 bf 90 jsr TxtSkipSpaces ;and skip any spaces
ab19: 20 c8 90 jsr IsDigit ;cur char a digit?
ab1c: b0 06 bcs @recurse ;no -> look for another '-', '+', or (especially) '&'
ab1e: 20 e4 90 jsr ParsePosOrNegDecWFromTxt ;parse a (possibly re-negated) number
ab21: 4c 32 8e jmp WAccumNegate ;and negate it, returning.
ab24: 20 2d ab @recurse jsr READ_parseNumber
ab27: 4c 32 8e jmp WAccumNegate
ab2a: 20 8c 90 @advAndRedo jsr TxtPtrIncr
READ_parseNumber
ab2d: a0 00 ldy #$00
ab2f: b1 09 lda (zpTXTPTR),y
ab31: c9 2d cmp #‘-’ ;cur char == '-'?
ab33: f0 de beq ParseAndNegateDecWord ;yes -> parse and negate a number
ab35: c9 2b cmp #‘+’ ;cur char == '+'?
ab37: f0 f1 beq @advAndRedo ;yes -> ignore, advance, and restart these checks
ab39: c9 26 cmp #‘&’ ;cur char == '&'?
ab3b: f0 0c beq READ_parseHexNum
ab3d: 20 e4 90 jsr ParsePosOrNegDecWFromTxt ;<- it'll be positive, bc we already checked for '-'
ab40: 20 f2 8d jsr Cmp32768 ;and therefore, if we get 32768, it's an overflow
ab43: f0 01 beq errOverfl_AB46 ;(overflow? -> error)
ab45: 60 rts
ab46: 4c f2 8e errOverfl_AB46 jmp ErrorOverflow
READ_parseHexNum
ab49: 20 8c 90 jsr TxtPtrIncr
ab4c: b1 09 lda (zpTXTPTR),y
ab4e: c9 48 cmp #‘H’
ab50: d0 03 bne @noH ;skip optional 'H', and parse a hex number
ab52: 20 8c 90 jsr TxtPtrIncr
ab55: 4c ed 91 @noH jmp WordFromHex
ab58: 20 9c 84 FnFn_INKEY_STR jsr IsEndOfCmd ;any chars after INKEY$?
ab5b: f0 0d beq @argOmitted ;no -> omitted arg, return active keyboard char
ab5d: c9 28 cmp #‘(’ ;have a left paren?
ab5f: d0 09 bne @argOmitted ;no -> omitted arg, return active keyboard char
ab61: 20 b6 a8 jsr EvalParenExprOrError ;yes, it's a left parenthesis. Move past it, and a dummy expression.
ab64: 20 20 bc jsr ReadChar ;read char from keyboard, waiting for keypress
ab67: 4c 87 aa jmp ReturnNewSingleCharStrFromAReg ;return that value as (single-char) string
ab6a: 20 45 b9 @argOmitted jsr ReadKeyInsMacros ;read any curently-pressed keys
ab6d: f0 0b beq @retEmpty ;nothing pressed? -> return empty string
ab6f: 20 94 aa jsr MakeSingleCharStrFromAReg ;else make single-char string from the keypress
ab72: 20 ee a8 @pushStr jsr PushWAccIntToParseStk ;push the string in WAccum to expr parse stack
ab75: a9 03 lda #TYP_STR ;and set the type accordingly
ab77: 85 27 sta zpArgTyp
ab79: 60 rts
ab7a: 20 ad a8 @retEmpty jsr ZeroWAccum
ab7d: 4c 72 ab jmp @pushStr
; Illegal Value error if WAccum's high byte is non-zero
ErrIfWAccumIsWord
ab80: a5 29 lda zpWAccum+1
ab82: d0 06 bne errIllVal_AB8A
ab84: 60 rts
EnsureLtStrLenMax
ab85: c9 20 cmp #STRLEN_MAX
ab87: b0 01 bcs errIllVal_AB8A
ab89: 60 rts
ab8a: 4c 94 84 errIllVal_AB8A jmp ErrorIllegalValue
ReadLEFTorRIGHTstrArgs
ab8d: 20 17 aa jsr EvalParenStrOrError ;eval left paren, string argument
ab90: a5 28 lda zpWAccum
ab92: 48 pha ;push strlen
ab93: a5 29 lda zpWAccum+1
ab95: 48 pha ;and two pointer bytes (to the string data)
ab96: a5 2a lda zpWAccum+2
ab98: 48 pha
ab99: 20 3b ac jsr CommaThenIntExprToWAccum ;eval comma, integer substr len
ab9c: 20 80 ab jsr ErrIfWAccumIsWord ;ensure it's byte-sized
ab9f: a5 28 lda zpWAccum
aba1: 20 85 ab jsr EnsureLtStrLenMax ;and also < max strlen
aba4: 68 pla
aba5: 85 1a sta zpMisc+1 ;pull the str ptr bytes,
aba7: 68 pla ; and stuff them into zpMisc
aba8: 85 19 sta zpMisc
abaa: 68 pla ;pull orig string length (leaving in A-reg)
abab: 60 rts ;return (strptr in zpMisc, strlen in A-reg, and substr len in WAccum)
; dead/unreachable code
abac: 4c 91 84 jmp ErrorSyntax
abaf: 20 17 aa FnFn_INSTR jsr EvalParenStrOrError ;parse first string arg (haystack)
abb2: a6 28 ldx zpWAccum ;load strlen into X reg
abb4: a0 00 ldy #$00
abb6: b1 29 @cpyLp lda (zpWAccum+1),y ;get a char from string
abb8: 99 00 05 sta lineBuffer,y ;copy into lineBuffer
abbb: c8 iny
abbc: ca dex ;copied whole string?
abbd: d0 f7 bne @cpyLp ;no -> loop back, copy next char
abbf: a5 28 lda zpWAccum
abc1: 48 pha ;push the string length
abc2: 20 1e ac jsr CommaThenExprToWAccum ;parse comma, then 2nd string arg (needle)
abc5: 20 10 aa jsr ErrorUnlessString
abc8: a5 28 lda zpWAccum
abca: 85 2c sta zpWParam ;copy string info from WAccum to WParam
abcc: a5 29 lda zpWAccum+1
abce: 85 2d sta zpWParam+1
abd0: a5 2a lda zpWAccum+2
abd2: 85 2e sta zpWParam+2
abd4: 68 pla ;pull string length
abd5: 85 28 sta zpWAccum ;store in WAccum byte 0
abd7: a9 00 lda #<lineBuffer
abd9: 85 29 sta zpWAccum+1 ;copy ptr to lineBuffer (holding haystack string) into WAccum+1,2
abdb: a9 05 lda #>lineBuffer
abdd: 85 2a sta zpWAccum+2
abdf: 20 e6 95 jsr CheckInStr ;perform the matching
abe2: 85 28 sta zpWAccum ;save index result into zpWAccum
abe4: a9 00 lda #$00 ;(clearing high byte)
abe6: 85 29 sta zpWAccum+1 ;(and ancilliary bytes)
abe8: 85 2a sta zpWAccum+2
abea: 4c 7e a9 jmp SkipRParenSetParseStkInt ;skip closing paren, and return our value via expr parse stack
• Clear variables
vZpStrLen .var $4a {addr/1}
vZpCpyCountDwn .var $90 {addr/1}
abed: 20 8d ab FnFn_LEFT_STR jsr ReadLEFTorRIGHTstrArgs ;read args
abf0: 85 4a sta vZpStrLen ;save orig str len
abf2: a5 28 lda zpWAccum ;substr len == 0?
abf4: f0 16 beq LEFT_RIGHT_returnEmptyStr ;yes -> return empty str
abf6: c5 4a cmp vZpStrLen ;substr len >= orig str len?
abf8: b0 0b bcs LEFT_RIGHT_returnOrigStr ;yes -> just return orig str
@retFromSetOrigStr
abfa: a5 19 lda zpMisc
abfc: 85 29 sta zpWAccum+1 ;just set return string pointer to orig:
abfe: a5 1a lda zpMisc+1 ; this is LEFT$ so we can just adjust the returned string's length
LEFT_RIGHT_finishUp
ac00: 85 2a sta zpWAccum+2 ; (conveniently already correct, and at the right addr)
ac02: 4c 8a aa jmp SetParseStkTopToWAccStr ;return WAccum via expr parse stack
LEFT_RIGHT_returnOrigStr
ac05: a5 4a lda vZpStrLen
ac07: 85 28 sta zpWAccum ;set orig str len as substr len
ac09: 4c fa ab jmp @retFromSetOrigStr ;just return orig str, substr with same len is just orig str
LEFT_RIGHT_returnEmptyStr
ac0c: 85 29 sta zpWAccum+1 ;clear WAccum+1 (+2 will clear after jmp below)
ac0e: 4c 00 ac jmp LEFT_RIGHT_finishUp ;clear WAccum+2 and return on expr parse stack
ac11: 4c 91 84 SC_errSyn jmp ErrorSyntax
SkipCommaOrSynErr
ac14: 20 bf 90 jsr TxtSkipSpaces
ac17: c9 2c cmp #‘,’
ac19: d0 f6 bne SC_errSyn
ac1b: 4c 8c 90 jmp TxtPtrIncr
CommaThenExprToWAccum
ac1e: 20 14 ac jsr SkipCommaOrSynErr ;demand a comma here
ac21: 20 9b a2 jsr Expression ;parse an expression
ac24: a6 30 ldx zpExprStkIdx ;pull the expression value, and
ac26: ca dex ; save it to zpWAccum
ac27: bd 00 04 lda exprParseStk,x
ac2a: 85 2a sta zpWAccum+2
ac2c: ca dex
ac2d: bd 00 04 lda exprParseStk,x
ac30: 85 29 sta zpWAccum+1
ac32: ca dex
ac33: bd 00 04 lda exprParseStk,x
ac36: 85 28 sta zpWAccum
ac38: 86 30 stx zpExprStkIdx ;save new reduced index (effecting the "pull" from expr stack)
ac3a: 60 rts ;done
CommaThenIntExprToWAccum
ac3b: 20 1e ac jsr CommaThenExprToWAccum
ac3e: 4c 0d aa jmp ErrorUnlessInteger
ac41: 20 8d ab FnFn_RIGHT_STR jsr ReadLEFTorRIGHTstrArgs ;read args
ac44: 85 4a sta vZpStrLen ;save str arg's length
ac46: a5 28 lda zpWAccum ;get substr length
ac48: f0 c2 beq LEFT_RIGHT_returnEmptyStr ; == 0? -> return empty str
ac4a: c5 4a cmp vZpStrLen ; substr len >= orig str len?
ac4c: b0 b7 bcs LEFT_RIGHT_returnOrigStr ;yes -> just return the contents of original string
ac4e: 38 sec
ac4f: a5 4a lda vZpStrLen ;else, subtract substr len from full str len
ac51: e5 28 sbc zpWAccum
ac53: 18 clc
ac54: 65 19 adc zpMisc ;add to str ptr
ac56: 85 19 sta zpMisc
ac58: a9 00 lda #$00
ac5a: 65 1a adc zpMisc+1 ;ptr high byte, too
ac5c: 85 1a sta zpMisc+1
ac5e: a5 28 lda zpWAccum
ac60: 85 90 sta vZpCpyCountDwn ;copy substr len to countdown var
ac62: a6 21 ldx zpGpStrIdx
ac64: a0 00 ldy #$00
ac66: b1 19 @cpyLp lda (zpMisc),y ;read char from orig str (at shifted position)
ac68: 9d 00 07 sta genPurposeBuf,x ;copy into general-purpose buffer
ac6b: e8 inx
ac6c: c8 iny
ac6d: c6 90 dec vZpCpyCountDwn ;decrement # chars to copy. == 0?
ac6f: d0 f5 bne @cpyLp ;no -> loop around, copy next char
ac71: a5 21 lda zpGpStrIdx ;point WAccum at general-purpose buffer
ac73: 85 29 sta zpWAccum+1 ; location we copied to
ac75: 86 21 stx zpGpStrIdx
ac77: a9 07 lda #>genPurposeBuf
ac79: 85 2a sta zpWAccum+2
ac7b: 4c 8a aa jmp SetParseStkTopToWAccStr ;-> return the copied string via expr parse stack
ac7e: 4c 94 84 errIllVal_AC7E jmp ErrorIllegalValue
• Clear variables
vZpStrPtr .var $19 {addr/2}
vZpSubStrStartIdx .var $1b {addr/1}
vZpSubStrLen .var $1c {addr/1}
vZpCpyCountDwn .var $90 {addr/1}
ac81: 20 17 aa FnFn_MID_STR jsr EvalParenStrOrError ;read paren, string arg
ac84: a5 28 lda zpWAccum
ac86: 48 pha ;push the string info
ac87: a5 29 lda zpWAccum+1
ac89: 48 pha
ac8a: a5 2a lda zpWAccum+2
ac8c: 48 pha
ac8d: 20 3b ac jsr CommaThenIntExprToWAccum ;next read start index arg
ac90: 20 80 ab jsr ErrIfWAccumIsWord ;ensure it's < 256
ac93: a5 28 lda zpWAccum ; == 0?
ac95: f0 e7 beq errIllVal_AC7E ;yes -> invalid index
ac97: 20 85 ab jsr EnsureLtStrLenMax ; >= 32? also invalid index
ac9a: 38 sec
ac9b: e9 01 sbc #$01 ;sub 1 (cvt from 1-based index to 0-based)
ac9d: 48 pha ;push the index
ac9e: 20 3b ac jsr CommaThenIntExprToWAccum ;read substr length arg
aca1: 20 80 ab jsr ErrIfWAccumIsWord ;ensure < 256
aca4: a5 28 lda zpWAccum ; == 0?
aca6: f0 50 beq MID_pullArgsRetEmptyStr ;yes -> pull up args, return empty string
aca8: 20 85 ab jsr EnsureLtStrLenMax ;ensure substr len < max strlen
acab: 85 1c sta vZpSubStrLen ;save the substr len
acad: 68 pla ;pull the start index
acae: 85 1b sta vZpSubStrStartIdx ;and save that too
acb0: 68 pla ;also the original string's ptr
acb1: 85 1a sta vZpStrPtr+1
acb3: 68 pla
acb4: 85 19 sta vZpStrPtr
acb6: 68 pla ;pull orig str's len
acb7: c5 1b cmp vZpSubStrStartIdx ; <= start index?
acb9: f0 41 beq MID_retEmptyStr ;yes -> return empty string
acbb: 90 3f bcc MID_retEmptyStr
acbd: 38 sec
acbe: e5 1b sbc vZpSubStrStartIdx ;orig str len - substr start idx
acc0: c5 1c cmp vZpSubStrLen ; < substr len?
acc2: 90 2f bcc MID_setSubStrLen ;yes -> set remaining len as the new substr len
MID_setLenJmpBack
acc4: a5 1b lda vZpSubStrStartIdx ; (and return back here)
acc6: 18 clc
acc7: 65 19 adc vZpStrPtr ;add start idx to str ptr
acc9: 85 19 sta vZpStrPtr ; and save
accb: a9 00 lda #$00
accd: 65 1a adc vZpStrPtr+1 ;(high byte, too)
accf: 85 1a sta vZpStrPtr+1
acd1: a5 1c lda vZpSubStrLen
acd3: 85 28 sta zpWAccum ;save substr len to WAccum
acd5: 85 90 sta vZpCpyCountDwn ;and use as # chars to copy
acd7: a6 21 ldx zpGpStrIdx
acd9: a0 00 ldy #$00
acdb: b1 19 @cpyLp lda (vZpStrPtr),y ;get a byte from str
acdd: 9d 00 07 sta genPurposeBuf,x ;copy to gen-purpose str buf
ace0: e8 inx ;incr X-reg (genPurpose idx)
ace1: c8 iny ;incr Y-reg (source str idx)
ace2: c6 90 dec vZpCpyCountDwn ;decrement chars-to-copy. Done copying?
ace4: d0 f5 bne @cpyLp ;not yet -> iterate over remaining chars
ace6: a5 21 lda zpGpStrIdx ;done copying.
ace8: 85 29 sta zpWAccum+1 ;save ptr into genPurposeBuf as str ptr, to WAccum
acea: 86 21 stx zpGpStrIdx ;update index to free space in genPurposeBuf
acec: a9 07 lda #>genPurposeBuf
acee: 85 2a sta zpWAccum+2
acf0: 4c 8a aa jmp SetParseStkTopToWAccStr ;-> return new substr on expression stack
MID_setSubStrLen
acf3: 85 1c sta vZpSubStrLen
acf5: 4c c4 ac jmp MID_setLenJmpBack ;-> "return"
MID_pullArgsRetEmptyStr
acf8: 68 pla ;pull args off stack
acf9: 68 pla
acfa: 68 pla
acfb: 68 pla
acfc: 20 ad a8 MID_retEmptyStr jsr ZeroWAccum ;zero the WAccum
acff: 4c 8a aa jmp SetParseStkTopToWAccStr ;-> return empty string
• Clear variables
vZpPadBtns .var $90 {addr/1} ;Holds state of gamepad buttons
ad02: 4c 94 84 errIllVal_AD02 jmp ErrorIllegalValue
EvalLParenPadNumAndGetBtns
ad05: 20 04 aa jsr EvalParenByteOrError ;read left paren, then byte-size int arg
; Get the state of buttons (including d-pad) as a set of 8 boolean bits, into
; $90. WAccum indicates which gamepad to read.
ad08: a5 28 GetButtons lda zpWAccum ;get byte-size val from WAccum
ad0a: f0 07 beq @zero ; == 0? -> skip ahead a bit
ad0c: c9 01 cmp #$01 ;else, eq 1?
ad0e: d0 f2 bne errIllVal_AD02 ;neither 0 nor 1 -> error
ad10: a9 17 lda #<JOY2 ;we'll read from JOY2
ad12: ae ldx ▼ $16a9 ;(skip next instr)
ad13: a9 16 @zero lda #<JOY1 ;we'll read from JOY1
ad15: 85 19 sta zpMisc
ad17: a9 40 lda #>JOY1
ad19: 85 1a sta zpMisc+1
ad1b: a9 05 lda #$05
ad1d: 8d 16 40 sta JOY1 ;tell controllers to load shfit registers
ad20: ea nop ;wait a bit
ad21: ea nop
ad22: a9 04 lda #$04
ad24: 8d 16 40 sta JOY1 ;lock shift registers
ad27: ea nop ;wait a bit
ad28: ea nop
ad29: a2 08 ldx #$08 ;set X-reg as counter for 8 single-bit reads
ad2b: a0 00 ldy #$00
ad2d: b1 19 @joyRdLp lda (zpMisc),y ;read from gamepad
ad2f: 4a lsr A ;shift into carry
ad30: 26 90 rol vZpPadBtns ;shift from carry onto gamepad buttons-state var
ad32: ea nop ;wait a bit before next gamepad read
ad33: ea nop
ad34: ca dex ;decr bit counter. Did we record 8 bits/buttons?
ad35: d0 f6 bne @joyRdLp ;no -> loop around for next button state
ad37: 60 rts ;yes: done
ad38: 20 05 ad FnFn_STICK jsr EvalLParenPadNumAndGetBtns ;parse gamepad-num arg, and get buttons state for it
ad3b: 20 08 ad jsr GetButtons ;re-read the buttons (why?!)
ad3e: a5 90 lda vZpPadBtns
ad40: 29 0f and #$0f ;mask out everything but d-pad buttons
ST_setWAccumAndSaveExpr
ad42: 85 28 sta zpWAccum ;save this val to WAccum
ad44: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> and return as our result
ad47: 20 05 ad FnFn_STRIG jsr EvalLParenPadNumAndGetBtns ;parse gamepad-num arg, and get buttons state for it
ad4a: a5 90 lda vZpPadBtns
ad4c: 4a lsr A ;shift off the d-pad state, leaving
ad4d: 4a lsr A ; other buttons in the lower four bits
ad4e: 4a lsr A
ad4f: 4a lsr A
ad50: 4c 42 ad jmp ST_setWAccumAndSaveExpr ;-> and return the result
; shifts A left 4 times, effectively multiplying it by 16
• Clear variables
ad53: 0a MulABy16 asl A
ad54: 0a asl A
ad55: 0a asl A
ad56: 0a asl A
ad57: 60 rts
ad58: 4c 94 84 errIllVal_AD58 jmp ErrorIllegalValue
ad5b: c9 2c CmdFn_GAME cmp #‘,’ ;start with comma (2nd arg)?
ad5d: f0 2b beq GAME_mapOnly ;yes -> go handle
ad5f: 20 6e a2 jsr EvalByteInteger ;else parse first arg
ad62: c9 04 cmp #$04 ; >= 4?
ad64: b0 f2 bcs errIllVal_AD58 ;yes -> error
ad66: 85 f3 sta zpGameNum ;save arg as "game number"
ad68: 20 fe ad jsr CmdFn_CLS_Scr1 ;clear SCREEN 1
ad6b: 20 9c 84 jsr IsEndOfCmd ;no more args?
ad6e: f0 0f beq @oneArg ;correct -> skip handling arg 2
ad70: 20 14 ac jsr SkipCommaOrSynErr ;read expected comma
ad73: 20 6e a2 jsr EvalByteInteger ;parse arg 2. Des it equal zero?
ad76: f0 07 beq @oneArg ;yes -> same as no 2nd arg
ad78: c9 01 cmp #$01
ad7a: d0 dc bne errIllVal_AD58
ad7c: 20 d1 ad jsr GAME_loadBgMap ;load up the background map
ad7f: 20 62 86 @oneArg jsr InitVars
ad82: 20 96 ad jsr GAME_loadGame
ad85: a9 00 lda #$00
ad87: 4c b0 86 jmp CmdFn_RUN
ad8a: 20 14 ac GAME_mapOnly jsr SkipCommaOrSynErr ;skip over comma (can't fail: we know it's there)
ad8d: 20 6e a2 jsr EvalByteInteger ;parse "second" arg
ad90: f0 03 beq @rts ;"GAME ,0"? -> done (nothing to do).
ad92: 20 d1 ad jsr GAME_loadBgMap ;just load the background map
ad95: 60 @rts rts ; then exit (no game)
ad96: a5 05 GAME_loadGame lda zpTXTTAB
ad98: 85 19 sta zpMisc ;init write pointer to start of TXTTAB
ad9a: a5 06 lda zpTXTTAB+1
ad9c: 85 1a sta zpMisc+1
ad9e: a5 f3 lda zpGameNum ;recall game num
ada0: 0a asl A ;multiply by 2 for indexing
ada1: aa tax
ada2: bd 03 80 lda tbl_GameTXTENDs,x ;load TXTEND value for this game
ada5: 85 07 sta zpTXTEND ;and store it as the current one
ada7: bd 04 80 lda tbl_GameTXTENDs+1,x
adaa: 85 08 sta zpTXTEND+1
adac: bd 0b 80 lda tbl_GameTexts,x ;init read ptr to start of the selected game
adaf: 85 1b sta zpMisc+2
adb1: bd 0c 80 lda tbl_GameTexts+1,x
adb4: 85 1c sta zpMisc+3
adb6: a0 00 ldy #$00
adb8: b1 1b @cpyLp lda (zpMisc+2),y ;load a byte from in-ROM game
adba: 91 19 sta (zpMisc),y ;store it in TXTTAB
adbc: e6 1b inc zpMisc+2 ;increment source ptr
adbe: d0 02 bne @noHiSrc
adc0: e6 1c inc zpMisc+3
adc2: e6 19 @noHiSrc inc zpMisc ;increment dest ptr
adc4: d0 02 bne @noHiDst
adc6: e6 1a inc zpMisc+1
adc8: a9 70 @noHiDst lda #(>memoryTop)+1
adca: c5 1a cmp zpMisc+1 ;copy to end of RAM at $7000 (not to TXTEND!)
adcc: d0 ea bne @cpyLp
adce: 4c a6 97 jmp Clear
add1: a9 00 GAME_loadBgMap lda #<gameBackgroundMap ;prep background map for reading
add3: 85 19 sta zpMisc
add5: a9 d0 lda #>gameBackgroundMap
add7: 85 1a sta zpMisc+1
add9: 20 59 b5 jsr AwaitVBlankAndClearPpuMask ;wait until safe to do PPU ops. Disable rendering.
addc: a9 24 lda #>VRAM_screen1
adde: 8d 06 20 sta PPUADDR
ade1: a9 00 lda #<VRAM_screen1
ade3: 8d 06 20 sta PPUADDR
ade6: a0 00 ldy #$00
ade8: b1 19 @cpyLp lda (zpMisc),y ;read a byte from built-in map
adea: 8d 07 20 sta PPUDATA ;write byte to screen 1
aded: c8 iny ;advance read idx. Finished reading one page?
adee: d0 f8 bne @cpyLp ;no -> loop around copy next byte
adf0: e6 1a inc zpMisc+1 ;increment source page num
adf2: a9 d4 lda #$d4
adf4: c5 1a cmp zpMisc+1 ;are we at RAM page $D4?
adf6: d0 f0 bne @cpyLp ;no -> copy another page to VRAM
adf8: 20 3c b5 jsr NmiSetVScrollAndEnableNmi ;yes: ensure NMI enabled
adfb: 4c 62 b5 jmp AwaitVBlankAndUpdatePpuMask ;and re-enable rendering. Return
adfe: a9 01 CmdFn_CLS_Scr1 lda #$01
ae00: 85 28 sta zpWAccum
ae02: 4c 13 ae jmp cls_fakedArg
@cls_noScrSwNeeded
ae05: 68 pla
ae06: 68 pla
ae07: 4c ab b4 doCls jmp DoCLS
ae0a: f0 fb CmdFn_CLS beq doCls ;no args ->
ae0c: 20 6e a2 jsr EvalByteInteger ;get int arg
ae0f: c9 02 cmp #$02 ; < 2?
ae11: b0 32 bcs errIllVal_ae45 ;no -> err
ae13: a5 49 cls_fakedArg lda zpCV
ae15: 48 pha ;push orig cursor Y pos
ae16: a5 4f lda zpPpuScreenBasePage
ae18: 48 pha ;push orig upd scr
ae19: a5 28 lda zpWAccum
ae1b: f0 0e beq @swScr0 ;arg is 0? -> switch to that
ae1d: a9 24 lda #>VRAM_screen1 ;no: switch to scr 1
ae1f: c5 4f cmp zpPpuScreenBasePage ;already updating scr 1?
ae21: f0 e2 beq @cls_noScrSwNeeded ;yes -> leave alone, discard saved pos
ae23: 85 4f sta zpPpuScreenBasePage ;no -> do the switch
ae25: a9 00 lda #$00
ae27: 85 e5 sta zpVScroll1 ;and clear scroll pos
ae29: f0 0c beq @doCls
ae2b: a9 20 @swScr0 lda #>VRAM_screen0
ae2d: c5 4f cmp zpPpuScreenBasePage ;already updating scr 0?
ae2f: f0 d4 beq @cls_noScrSwNeeded ;yes -> leave alone, discard saved pos
ae31: 85 4f sta zpPpuScreenBasePage ;no -> do the switch
ae33: a9 00 lda #$00
ae35: 85 e4 sta zpVScroll0 ;and clear scroll pos
ae37: 20 ab b4 @doCls jsr DoCLS
ae3a: 68 pla
ae3b: 85 4f sta zpPpuScreenBasePage ;restore original update screen
ae3d: 68 pla
ae3e: 85 49 sta zpCV ;and original cursor Y pos
ae40: 60 rts
ae41: 20 3c b5 nmiOnAndRts jsr NmiSetVScrollAndEnableNmi
ae44: 60 rts
ae45: 4c 94 84 errIllVal_ae45 jmp ErrorIllegalValue
ae48: 20 6e a2 CmdFn_SCREEN jsr EvalByteInteger ;get int arg.
ae4b: f0 04 beq @got0or1 ;== 0? ->
ae4d: c9 01 cmp #$01 ;== 1?
ae4f: d0 f4 bne errIllVal_ae45
ae51: 29 ff @got0or1 and #$ff
ae53: 85 f0 sta zpCurScreenNum
ae55: f0 31 beq @useScr0 ;SCREEN 0? ->
ae57: a9 24 lda #>VRAM_screen1 ;SCREEN 1. Save base addr high byte.
ae59: 85 4f sta zpPpuScreenBasePage
ae5b: a5 32 lda zpPpuCtrlVal
ae5d: 09 01 ora #$01 ;Tell PPU to switch active screen to 1
@setPpuCtrlAndCont
ae5f: 85 32 sta zpPpuCtrlVal
ae61: 20 2b b4 jsr WaitForVBlank
ae64: 20 14 be jsr SetPpuCtrlAndEnableNmi
ae67: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
ae6a: 20 48 b7 jsr CursorHome ;LOCATE 0,0
ae6d: 20 9c 84 jsr IsEndOfCmd ;done with args?
ae70: f0 cf beq nmiOnAndRts ;yes -> finish up
ae72: 20 14 ac jsr SkipCommaOrSynErr
ae75: 20 6e a2 jsr EvalByteInteger ;get next int arg
ae78: f0 09 beq @secondSrc0 ;== 0? ->
ae7a: c9 01 cmp #$01 ;or == 1?
ae7c: d0 c7 bne errIllVal_ae45 ;neither? -> illegal!
ae7e: a9 24 lda #>VRAM_screen1 ;== 1. send updates, only, to 1.
ae80: 85 4f sta zpPpuScreenBasePage
ae82: 60 rts
ae83: a9 20 @secondSrc0 lda #>VRAM_screen0 ;send updates, only, to scr 0.
ae85: 85 4f sta zpPpuScreenBasePage
ae87: 60 rts
ae88: a9 20 @useScr0 lda #>VRAM_screen0 ;SCREEN 0. Save base addr high byte.
ae8a: 85 4f sta zpPpuScreenBasePage
ae8c: a5 32 lda zpPpuCtrlVal
ae8e: 29 fe and #$fe ;Tell PPU to switch active screen to 0
ae90: 4c 5f ae jmp @setPpuCtrlAndCont
ae93: 4c 94 84 errIllVal_AE93 jmp ErrorIllegalValue
BgtFile_GoToScreen0
ae96: a9 00 lda #$00
ae98: 85 f0 sta zpCurScreenNum ;set SCREEN 0
ae9a: a9 20 lda #>VRAM_screen0
ae9c: 85 4f sta zpPpuScreenBasePage
ae9e: a5 32 lda zpPpuCtrlVal
aea0: 29 fe and #$fe ;mask out bg base addr, so SCREEN 0 is displayed by PPU
aea2: 85 32 sta zpPpuCtrlVal
aea4: 4c 14 be jmp SetPpuCtrlAndEnableNmi ;-> enact updated PPUCTRL, then return
; dead/unreachable code
aea7: a9 01 lda #$01
aea9: 85 f0 sta zpCurScreenNum
aeab: a9 24 lda #>VRAM_screen1
aead: 85 4f sta zpPpuScreenBasePage
aeaf: a5 32 lda zpPpuCtrlVal
aeb1: 09 01 ora #$01
aeb3: 85 32 sta zpPpuCtrlVal
aeb5: 4c 14 be jmp SetPpuCtrlAndEnableNmi
aeb8: 20 6e a2 CmdFn_FILTER jsr EvalByteInteger ;read arg
aebb: c9 08 cmp #$08 ; >= 8?
aebd: b0 d4 bcs errIllVal_AE93 ;yes -> error
aebf: 20 53 ad jsr MulABy16 ;shift the arg val left 5 places
aec2: 0a asl A
aec3: 85 75 sta zpHeldKeyChar ;store in some handy var
aec5: a5 33 lda zpPpuMaskVal ;load PPUMASK value
aec7: 29 1f and #$1f ;mask out the filter bits
aec9: 05 75 ora zpHeldKeyChar ;merge with our shifted arg
aecb: 85 33 sta zpPpuMaskVal ;save PPUMASK value
aecd: 8d 01 20 sta PPUMASK ;and update it in the PPU
aed0: 60 rts ;done!
aed1: 20 6e a2 CmdFn_CGEN jsr EvalByteInteger ;get argument
aed4: c9 04 cmp #$04 ; >= 4?
aed6: b0 bb bcs errIllVal_AE93 ;yes -> error
aed8: 0a asl A ;shift the value left 3 places:
aed9: 0a asl A ; from bits 0 and 1 to bits 3 and 4.
aeda: 0a asl A
aedb: 85 74 sta zpCursFlashCtr ;save away for a tick
aedd: a5 32 lda zpPpuCtrlVal ;get the PPUCTRL value
aedf: 29 e7 and #$e7 ;mask away everything but bits 3 and 4
aee1: 05 74 ora zpCursFlashCtr ;now combine with the value we got form user arg
aee3: 85 32 sta zpPpuCtrlVal ;save
aee5: 4c 14 be jmp SetPpuCtrlAndEnableNmi ;and update
; Clear all sprite tiles and attrs, move positions off-screen
aee8: a2 00 ClearOam ldx #$00
aeea: a9 ef lda #$ef
aeec: 9d 00 02 @lp sta oamBuffer,x
aeef: e8 inx
aef0: d0 fa bne @lp
aef2: 60 rts
vZpColorCntUp .var $02 {addr/1} ;count down to write palette colors
vZpColorCntDown .var $7c {addr/1} ;count up, the actual color num to set
aef3: a2 00 CmdFn_PALET ldx #$00
aef5: 86 65 stx zpPaletBgOrSpr ;start with assumption: bg sprite
aef7: c9 42 cmp #‘B’ ;first arg 'B'?
aef9: f0 08 beq @bgPal ;yes -> user said bg palette, our guess was right!
aefb: c9 53 cmp #‘S’ ;first arg 'S'?
aefd: d0 4d bne errSyn_AF4C ;no -> err
aeff: a9 10 lda #$10 ;set sprite palette offset
af01: 85 65 sta zpPaletBgOrSpr
af03: 20 65 a2 @bgPal jsr AdvThenEvalByteInt ;which palette num?
af06: c9 04 cmp #$04 ; >= 4?
af08: b0 3f bcs errIllVal_AF49 ;yes -> error
af0a: 48 pha ;push palette num
af0b: a9 04 lda #$04
af0d: 85 02 sta vZpColorCntUp ;four colors in a palette
af0f: a9 00 lda #$00
af11: 85 7c sta vZpColorCntDown ;start with color #0
af13: 20 6b a2 @colorLp jsr EvalCommaThenByteInt ;read comma, then user's color arg
af16: a4 7c ldy vZpColorCntDown
af18: 99 3d 00 sta zpNumStrTmp,y ;write color into arbitrary buf
af1b: e6 7c inc vZpColorCntDown ;incr color #
af1d: c6 02 dec vZpColorCntUp ;did we do 4?
af1f: d0 f2 bne @colorLp ;no -> loop to next color
af21: 68 pla ;pull the palette num
af22: 8d 00 07 sta genPurposeBuf
af25: a5 3d lda zpNumStrTmp ;copy color choices into genPurposeBuf
af27: 8d 01 07 sta genPurposeBuf+1 ; (why didn't we just write there
af2a: a5 3e lda zpNumStrTmp+1 ; in the first place?!)
af2c: 8d 02 07 sta genPurposeBuf+2
af2f: a5 3f lda zpNumStrTmp+2
af31: 8d 03 07 sta genPurposeBuf+3
af34: a5 40 lda zpNumStrTmp+3
af36: 8d 04 07 sta genPurposeBuf+4
af39: a0 06 ldy #NC_SetBgPalette ;select "set bg palette" cmd
af3b: a6 65 ldx zpPaletBgOrSpr ;what did user actually ask for?
af3d: f0 01 beq @bg ;bg? -> good call then
af3f: c8 iny ;spr -> #NC_SetSprPalette it is
af40: 98 @bg tya
af41: 4c 6a 89 jmp WaitForNmiSubcommand ;set the palette
ErrUnlessZeroThruSeven
af44: c9 08 cmp #$08
af46: b0 01 bcs errIllVal_AF49
af48: 60 rts
af49: 4c 94 84 errIllVal_AF49 jmp ErrorIllegalValue
af4c: 4c 91 84 errSyn_AF4C jmp ErrorSyntax
; Selects a preset set-of-four-pallets, to save as current pallet.
; First arg is bg pallet set (0 or 1), 2nd is sprite pallet set (0, 1 or 2).
• Clear variables
vZpPaletteSetPtr .var $52 {addr/2}
vZpNmiCmd .var $73 {addr/1} ;which palette-setting NMI subcmd we'll use
vZpColorCpyCtr .var $74 {addr/1}
vZpPaletteCpyIdx .var $75 {addr/1} ;iterates the selected palette set as copy source
af4f: c9 2c CmdFn_CGSET cmp #‘,’ ;got comma?
af51: f0 25 beq @arg2 ;skip arg one, go to arg two
af53: 20 6e a2 jsr EvalByteInteger ;evaluate arg one
af56: f0 04 beq @argGood ;error unless it's zero or one
af58: c9 01 cmp #$01
af5a: d0 ed bne errIllVal_AF49
af5c: 0a @argGood asl A ;multiply by two, for index into table
af5d: aa tax
af5e: bd a4 cf lda tbl_CgsetBgPaletteSets,x ;get the pallet-set pointer from table
af61: 85 52 sta vZpPaletteSetPtr
af63: bd a5 cf lda tbl_CgsetBgPaletteSets+1,x
af66: 85 53 sta vZpPaletteSetPtr+1
af68: a9 00 lda #$00
af6a: 85 65 sta zpPaletBgOrSpr ;indicate we're doing background palette set
af6c: 20 93 af jsr CGSET_SetPaletteSet ;and set it
af6f: 20 9c 84 jsr IsEndOfCmd
af72: f0 1e beq @rts
af74: c9 2c cmp #$2c
af76: d0 d4 bne errSyn_AF4C
af78: 20 65 a2 @arg2 jsr AdvThenEvalByteInt ;evaluate 2nd arg
af7b: c9 03 cmp #$03 ;error unless 0, 1, or 2
af7d: b0 ca bcs errIllVal_AF49
af7f: 0a asl A ;multiply by two, for index into table
af80: aa tax
af81: bd a8 cf lda tbl_CgsetSprPaletteSets,x ;get the pallet pointer from table
af84: 85 52 sta vZpPaletteSetPtr
af86: bd a9 cf lda tbl_CgsetSprPaletteSets+1,x
af89: 85 53 sta vZpPaletteSetPtr+1
af8b: a9 01 lda #$01
af8d: 85 65 sta zpPaletBgOrSpr ;indicate we're doing sprite palette set
af8f: 20 93 af jsr CGSET_SetPaletteSet ;and set it
af92: 60 @rts rts ;done
CGSET_SetPaletteSet
af93: a9 00 lda #$00
af95: 8d 00 07 sta genPurposeBuf ;PPU palette number (start at zero)
af98: 85 75 sta vZpPaletteCpyIdx ;index for copying from palette set
af9a: a9 04 @palSetLp lda #$04
af9c: a2 01 ldx #$01 ;just after the PPU palette number we saved for the NMI handler
af9e: 85 74 sta vZpColorCpyCtr ;set to count of 4 colors to copy
afa0: a4 75 ldy vZpPaletteCpyIdx
afa2: b1 52 @palCpyLp lda (vZpPaletteSetPtr),y ;load color from palette
afa4: 9d 00 07 sta genPurposeBuf,x ;save color to genPurposeBuf
afa7: e8 inx
afa8: c8 iny
afa9: c6 74 dec vZpColorCpyCtr
afab: d0 f5 bne @palCpyLp ;repeat until total of 4 colors copied
afad: 84 75 sty vZpPaletteCpyIdx ;save index into palette set (we're at start of next palette to copy)
afaf: a9 06 lda #NC_SetBgPalette
afb1: 85 73 sta vZpNmiCmd ;default to setting bg palette set; might change before use
afb3: a5 65 lda zpPaletBgOrSpr ;was bg palettes correct?
afb5: f0 02 beq @doBg ;yes -> leave NMI cmd alone
afb7: e6 73 inc vZpNmiCmd ;no -> use #NC_SetSprPalette instead
afb9: a5 73 @doBg lda vZpNmiCmd
afbb: 20 6a 89 jsr WaitForNmiSubcommand ;invoke/wait for NMI palette-setting handler
afbe: ee 00 07 inc genPurposeBuf ;increment which palette num we're on
afc1: ad 00 07 lda genPurposeBuf
afc4: c9 04 cmp #$04 ;did we copy 4 palettes?
afc6: d0 d2 bne @palSetLp ;no -> loop around to set next palette
afc8: 60 rts ;that's the full set - we're done
; Not a real CmdFn. CmdFn_SPRITE when followed by ON token.
• Clear variables
afc9: a5 33 CmdFn_SPRITE_ON lda zpPpuMaskVal
afcb: 09 10 ora #$10 ;add "sprites enabled" flag to PPUMASK
afcd: 85 33 @savePpu sta zpPpuMaskVal
afcf: 8d 01 20 sta PPUMASK
afd2: 4c 8c 90 jmp TxtPtrIncr ;advance TXTPTR past ON/OFF token
; Not a real CmdFn. CmdFn_SPRITE when followed by OFF token.
CmdFn_SPRITE_OFF
afd5: a5 33 lda zpPpuMaskVal
afd7: 29 ef and #$ef ;mask out "sprites enabled" flag from PPUMASK
afd9: 4c cd af jmp @savePpu ;and save it
afdc: 68 SpriteErase pla ;get sprite number
afdd: 20 53 ad jsr MulABy16 ; *= 16
afe0: aa tax ;and transfer to X reg
afe1: a0 04 ldy #$04
afe3: a9 ef @clrSprLp lda #SPR_NullTile ;repeat four times (one for each sprite tile in actor):
afe5: 9d 00 02 sta oamSprYPos,x ;move ypos off-screen
afe8: 9d 03 02 sta oamSprXPos,x ;move xpos off-screen
afeb: 9d 01 02 sta oamSprTileId,x ;wset tile ID to empty tile
afee: e8 inx
afef: e8 inx
aff0: e8 inx
aff1: e8 inx
aff2: 88 dey
aff3: d0 ee bne @clrSprLp
aff5: 60 rts ;SPRITE command is finished disabling the sprite.
; dead/unreachable code
aff6: 4c 94 84 jmp ErrorIllegalValue
aff9: 4c 91 84 jmp ErrorSyntax
vZpXPos .var $19 {addr/1}
vZpXOff .var $1a {addr/1} ;offset to add to X pos for a given tile
vZpYPos .var $1b {addr/1}
vZpYOff .var $1c {addr/1} ;offset to add to Y pos for a given tile
vZpSpriteNum .var $6d {addr/1}
vZpTileCtr .var $74 {addr/1}
vZpTwoByTwo .var $90 {addr/1} ;Whether the sprite is 2x2 tiles (1), or 1x1 tiles (0)
affc: c9 9a CmdFn_SPRITE cmp #tok_ON
affe: f0 c9 beq CmdFn_SPRITE_ON
b000: c9 9b cmp #tok_OFF
b002: f0 d1 beq CmdFn_SPRITE_OFF
b004: 20 6e a2 jsr EvalByteInteger ;get sprite num
b007: 20 44 af jsr ErrUnlessZeroThruSeven ;complain if not 0-7
b00a: 48 pha ;push
b00b: 20 9c 84 jsr IsEndOfCmd ;is that all there is?
b00e: f0 cc beq SpriteErase ;yes -> disable the sprite
b010: 20 6b a2 jsr EvalCommaThenByteInt ;no, then expect a comma followed by an int (x pos)
b013: 48 pha ;push x pos
b014: 20 6b a2 jsr EvalCommaThenByteInt ;expect another comma and int (y pos)
b017: 85 1b sta vZpYPos ;store it
b019: 68 pla
b01a: 85 19 sta vZpXPos ;store x pos
b01c: 68 pla
b01d: 85 6d sta vZpSpriteNum ;and sprite num
b01f: aa tax ;and move sprite num into X reg
b020: bd 3d 06 lda sprTwoByTwoFlags,x
b023: 85 90 sta vZpTwoByTwo ;save info on whether this sprite is 1x1 or 2x2
b025: a5 6d lda vZpSpriteNum ;get sprite num
b027: 0a asl A ; *= 4
b028: 0a asl A
b029: aa tax ;save as X-reg
b02a: 0a asl A
b02b: 0a asl A ; *= 4 again (so, * 16 total)
b02c: 48 pha ;push that for later
b02d: a8 tay ;and also set Y-reg
b02e: a9 04 lda #$04
b030: 85 74 sta vZpTileCtr ;counter for 4 tiles of the sprite character
b032: bd 80 06 @tileCpy lda sprTileMaps,x ;copy tile ID from sprite char definition
b035: 99 01 02 sta oamSprTileId,y ;copy to its spot in the OAM buffer
b038: e8 inx ;increment one for source
b039: c8 iny ;and four for dest (OAM buffer)
b03a: c8 iny
b03b: c8 iny
b03c: c8 iny
b03d: c6 74 dec vZpTileCtr ;decrement tile counter
b03f: d0 f1 bne @tileCpy ;tiles left to copy? -> loop until we did 4
b041: 68 pla ;pull sprite # times 16
b042: a8 tay ;reset Y-reg to that again
b043: a5 1b lda vZpYPos
b045: 99 00 02 sta oamSprYPos,y ;copy Y position to OAM
b048: a5 19 lda vZpXPos
b04a: 99 03 02 sta oamSprXPos,y ;and X position
b04d: a5 90 lda vZpTwoByTwo ;is this a 2x2 sprite character?
b04f: d0 01 bne @setOtherTiles ;yes -> set X and Y positions for those tiles, too
b051: 60 rts ;no: done!
b052: a9 00 @setOtherTiles lda #$00 ;top-right tile
b054: 85 1c sta vZpYOff
b056: a9 08 lda #$08
b058: 85 1a sta vZpXOff
b05a: 20 6e b0 jsr @setOneTile
b05d: a9 00 lda #$00 ;bottom-left tile
b05f: 85 1a sta vZpXOff
b061: a9 08 lda #$08
b063: 85 1c sta vZpYOff
b065: 20 6e b0 jsr @setOneTile
b068: a9 08 lda #$08 ;bottom-right tile
b06a: 85 1a sta vZpXOff
b06c: 85 1c sta vZpYOff
b06e: c8 @setOneTile iny
b06f: c8 iny
b070: c8 iny
b071: c8 iny
b072: a5 19 lda vZpXPos
b074: 18 clc
b075: 65 1a adc vZpXOff
b077: 99 03 02 sta oamSprXPos,y
b07a: a5 1b lda vZpYPos
b07c: 18 clc
b07d: 65 1c adc vZpYOff
b07f: 99 00 02 sta oamSprYPos,y
b082: 60 rts_B082 rts ;done!
• Clear variables
b083: c9 a1 DEF_maybeMove cmp #tok_MOVE
b085: d0 03 bne errSyn_B08A
b087: 4c 5b ca jmp CmdFn_DEFMOVE
b08a: 4c 91 84 errSyn_B08A jmp ErrorSyntax
ErrUnlessZeroOrOne
b08d: c9 01 cmp #$01
b08f: f0 f1 beq rts_B082
b091: 29 ff and #$ff
b093: f0 ed beq rts_B082
b095: 4c 94 84 errIllVal_B095 jmp ErrorIllegalValue
• Clear variables
vZpSprAttr .var $19 {addr/1} ;Value to use for OAM sprite attributes bitflags
vZpTilesPtr .var $1b {addr/2} ;Pointer to user-specified tiles (the assigned string from user)
vZpArgCntr .var $6d {addr/1} ;counter for loading DEF SPRITE args
vZpTwoByTwo .var $ad {addr/1} ;Holds the value of the 2nd parenthesized arg: character construction
b098: c9 8a CmdFn_DEF cmp #tok_SPRITE ;next token SPRITE?
b09a: d0 e7 bne DEF_maybeMove ;no -> check if it's MOVE then (error otherwise)
b09c: 20 65 a2 jsr AdvThenEvalByteInt ;get the sprite number
b09f: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure it's 0-7
b0a2: 48 pha ;push it
b0a3: 20 14 ac jsr SkipCommaOrSynErr ;skip a comma
b0a6: 20 7a a8 jsr SkipLParenOrSynErr ;and a paren
b0a9: a9 04 lda #$04
b0ab: 85 6d sta vZpArgCntr ;counter to loop around fetching last 4 args
b0ad: 20 6e a2 jsr EvalByteInteger ;eval 1st parenthesized arg: color combo, 0 to 3
b0b0: c9 04 cmp #$04
b0b2: b0 e1 bcs errIllVal_B095 ; >= 4? -> error
b0b4: 48 pha
b0b5: 20 6b a2 @argLp jsr EvalCommaThenByteInt ;get another arg
b0b8: 48 pha ;push it
b0b9: c6 6d dec vZpArgCntr
b0bb: d0 f8 bne @argLp ;do 3 more times. Now have 5 args on stack in reverse order
b0bd: 20 7d a8 jsr SkipRParenOrSynErr ;ensure closing paren
b0c0: 20 d9 86 jsr SkipEqualsOrErr ;ensure equal sign
b0c3: 68 pla ;grab last arg: Y-axis inversion
b0c4: 20 8d b0 jsr ErrUnlessZeroOrOne ;we're expecting zero or one
b0c7: 6a ror A ;move 1 to high-bit instead (yflip attr bit)
b0c8: 6a ror A
b0c9: 29 80 and #$80 ;<- this is redundant: we already knew it was 0 or 1
b0cb: 85 19 sta vZpSprAttr ;save
b0cd: 68 pla ;pull 4th paren arg: X-axis inversion
b0ce: 20 8d b0 jsr ErrUnlessZeroOrOne ;ensure zero or one
b0d1: 6a ror A ;move 1 to bit 6 instead (#$01 -> #$40)
b0d2: 6a ror A
b0d3: 6a ror A
b0d4: 29 40 and #$40 ;<- redundant
b0d6: 05 19 ora vZpSprAttr ;merge x-flip attr with y-flip attr
b0d8: 85 19 sta vZpSprAttr
b0da: 68 pla ;pull 3rd paren arg: display priority
b0db: 20 8d b0 jsr ErrUnlessZeroOrOne ;ensure zero or one
b0de: 6a ror A ;move 1 to bit 5 instead (#$01 -> #$20)
b0df: 6a ror A
b0e0: 6a ror A
b0e1: 6a ror A
b0e2: 29 20 and #$20 ;<- redundant
b0e4: 05 19 ora vZpSprAttr ;merge the flag
b0e6: 85 19 sta vZpSprAttr
b0e8: 68 pla ;pull 2nd paren arg: construction pattern
b0e9: 20 8d b0 jsr ErrUnlessZeroOrOne ;ensure 0 or 1
b0ec: 85 ad sta vZpTwoByTwo ;save
b0ee: 68 pla ;pull 1st paren arg: color preset
b0ef: 29 03 and #$03 ;<- redundant; we checked <= 3 aleady
b0f1: 05 19 ora vZpSprAttr ;merge with other sprite attrs
b0f3: 85 19 sta vZpSprAttr
b0f5: 68 pla ;pull sprite number
vZpSpriteNum .var $6d {addr/1}
b0f6: 85 6d sta vZpSpriteNum ;get the sprite num
b0f8: 20 53 ad jsr MulABy16 ; *= 16
b0fb: a0 04 ldy #$04
b0fd: aa tax
b0fe: a9 ef @oamClrLp lda #$ef ;for each of the four tiles of this sprite:
b100: 9d 00 02 sta oamSprYPos,x ;move y-pos off-screen
b103: 9d 03 02 sta oamSprXPos,x ;move x-pos off-screen
b106: a5 19 lda vZpSprAttr
b108: 9d 02 02 sta oamSprAttr,x ;save sprite attrs from args
b10b: a9 20 lda #$20 ;BUG. SPACE char? Actually Lady face char. This ought to be $EF for empty sprite tile (overwritten later anyway)
b10d: 9d 01 02 sta oamSprTileId,x ;save sprite tile ID
b110: e8 inx
b111: e8 inx
b112: e8 inx
b113: e8 inx
b114: 88 dey
b115: d0 e7 bne @oamClrLp ;do the same for the other 3 tiles
b117: 20 7d a2 jsr EvalString ;evaluate the expected string after the "="
b11a: ad 01 04 lda exprParseStk+1
b11d: 85 1b sta vZpTilesPtr ;copy string contents ptr from parse stack to ZP pointer var
b11f: ad 02 04 lda exprParseStk+2
b122: 85 1c sta vZpTilesPtr+1
b124: a0 00 ldy #$00
b126: a5 6d lda vZpSpriteNum ;get sprite num
b128: 0a asl A
b129: 0a asl A ; *= 4
b12a: aa tax
b12b: ad 00 04 lda exprParseStk
b12e: f0 10 beq @emptyString
b130: b1 1b @tileCpy lda (vZpTilesPtr),y ;copy tile ID
b132: 9d 80 06 sta sprTileMaps,x ;to data for this sprite
b135: e8 inx
b136: c8 iny
b137: c0 04 cpy #$04 ;copied 4 tiles?
b139: f0 10 beq @tileCpyDone ;-> done copying
b13b: ce 00 04 dec exprParseStk ;decrement string length. Ran out of string chars?
b13e: d0 f0 bne @tileCpy ;no -> loop back around for another copy from string
b140: a9 20 @emptyString lda #$20 ;yes. Copy Lady face tile (s/b $EF instead)
b142: 9d 80 06 @cpyLadyTiles sta sprTileMaps,x ; over the remaining tiles.
b145: e8 inx
b146: c8 iny
b147: c0 04 cpy #$04
b149: d0 f7 bne @cpyLadyTiles
b14b: a6 6d @tileCpyDone ldx vZpSpriteNum ;Finally, save whether this is a 8x8 or 16x16 actor
b14d: a5 ad lda vZpTwoByTwo
b14f: 9d 3d 06 sta sprTwoByTwoFlags,x
b152: 60 rts_B152 rts
; Copy contents of screen 1 to screen 0.
; If screen 1 is the active screen, do nothing.
• Clear variables
vZpScr0Page .var $74 {addr/1}
b153: a5 f0 CmdFn_VIEW lda zpCurScreenNum
b155: d0 fb bne rts_B152 ;not SCREEN 0? -> nothing to do.
b157: a9 20 lda #$20
b159: 85 74 sta vZpScr0Page ;set VRAM copy dest page to start of screen 0
b15b: a9 0c lda #NC_PpuMaskClear
b15d: 20 6a 89 jsr WaitForNmiSubcommand ;clear PPUMASK
b160: a2 00 ldx #$00
b162: a5 74 @cpyLp lda vZpScr0Page
b164: 18 clc
b165: 69 04 adc #$04
b167: 20 ec 81 jsr PpuGoToAX ;set PPUADDR to screen 1 (copy source) page
b16a: ad 07 20 lda PPUDATA ;first faked read
b16d: ad 07 20 lda PPUDATA ;read the character/tile ID
b170: 48 pha ;push
b171: a5 74 lda vZpScr0Page
b173: 20 ec 81 jsr PpuGoToAX ;set PPUADDR to same page in screen 0 (dest)
b176: 68 pla ;pull tile ID
b177: 8d 07 20 sta PPUDATA ;and write to screen 0
b17a: e8 inx ;next byte of vRAM page
b17b: d0 e5 bne @cpyLp ;page not done? -> loop
b17d: e6 74 inc vZpScr0Page ;page done; increment page number
b17f: a5 74 lda vZpScr0Page
b181: c9 24 cmp #$24 ;did we finish copying to VRAM page #$23?
b183: d0 dd bne @cpyLp ;no -> copy next page
b185: a5 e5 lda zpVScroll1 ;yes: copy scr 1 vert scroll to scr 0
b187: 85 e4 sta zpVScroll0
b189: 20 3c b5 jsr NmiSetVScrollAndEnableNmi ;and update
b18c: 20 14 be jsr SetPpuCtrlAndEnableNmi
b18f: a9 0b lda #NC_PpuMaskUpdate
b191: 4c 6a 89 jmp WaitForNmiSubcommand ;restore PPUMASK, and return
• Clear variables
b194: a2 3d ClearSpriteData ldx #$3d
b196: a9 00 lda #$00
b198: 9d 00 06 @sprClrLp sta $0600,x ;clear sprites' 2x2 vs 1x1 data
b19b: e8 inx
b19c: d0 fa bne @sprClrLp
b19e: a2 00 ldx #$00
b1a0: a9 ef lda #SPR_NullTile
b1a2: 9d 80 06 @tileClrLp sta sprTileMaps,x ;set all actor tilemaps to empty tile
b1a5: e8 inx
b1a6: e0 20 cpx #32 ;8 actors * 4 tiles
b1a8: d0 f8 bne @tileClrLp
b1aa: a2 c0 ldx #192
b1ac: a9 00 lda #$00
b1ae: 9d 00 06 @clrSprFacing sta defMovSFacing-192,x
b1b1: e8 inx
b1b2: d0 fa bne @clrSprFacing
b1b4: 60 rts
b1b5: 4c c8 8b errOOM_B1B5 jmp ErrorOutOfMemory
b1b8: 20 a6 97 CmdFn_BGGET jsr Clear ;clear vars
b1bb: a5 20 lda zpVAREND+1
b1bd: c9 6c cmp #>bgGetRam ;is there room after program for bg data?
b1bf: b0 f4 bcs errOOM_B1B5 ;no -> OOM error
b1c1: a9 0c lda #NC_PpuMaskClear
b1c3: 20 6a 89 jsr WaitForNmiSubcommand ;turn off rendering
b1c6: a9 00 lda #<bgGetRam
b1c8: 85 19 sta zpMisc ;point zpMisc for storing the background data
b1ca: a9 6c lda #>bgGetRam
b1cc: 85 1a sta zpMisc+1
b1ce: a9 24 lda #>VRAM_screen1 ;set PPUADDR to SCREEN 1 in RAM
b1d0: 8d 06 20 sta PPUADDR
b1d3: a9 00 lda #<VRAM_screen1
b1d5: 8d 06 20 sta PPUADDR
b1d8: ad 07 20 lda PPUDATA ;faked first read
b1db: a0 00 ldy #$00
b1dd: a2 04 ldx #$04 ; # pages to copy
b1df: ad 07 20 @cpyLp lda PPUDATA ;read byte from background data
b1e2: 91 19 sta (zpMisc),y ;write it to (battery-backed) RAM
b1e4: c8 iny ;did we read a full page?
b1e5: d0 f8 bne @cpyLp ;no -> keep copying
b1e7: e6 1a inc zpMisc+1 ;copied page, incr write page
b1e9: ca dex ;copied four pages?
b1ea: d0 f3 bne @cpyLp ;no -> copy next page
b1ec: a5 e5 lda zpVScroll1 ;yes: done copying
b1ee: 8d 05 60 sta bgGetVScroll ;save vscroll for saved screen
b1f1: a9 ff lda #$ff ;indicate screen 1 is saved in RAM
b1f3: 8d 04 60 UpdateBgGotFlag sta bgGotFlag
b1f6: a9 0b lda #NC_PpuMaskUpdate
b1f8: 20 6a 89 jsr WaitForNmiSubcommand ;restore rendering
b1fb: 60 rts
b1fc: a9 14 ErrorNoBgData lda #err_NB
b1fe: 4c 37 b2 jmp PrintErrorAndStop
b201: ad 04 60 CmdFn_BGPUT lda bgGotFlag ;flag indicate we have saved bg data?
b204: f0 f6 beq ErrorNoBgData ;no? nothing to put then, is there? -> error
RestoreBackupBgData
b206: a9 0c lda #NC_PpuMaskClear
b208: 20 6a 89 jsr WaitForNmiSubcommand ;disable rendering
b20b: a9 6c lda #>bgGetRam
b20d: 85 1a sta zpMisc+1 ;point zpMisc at the saved bg data
b20f: a9 00 lda #<bgGetRam
b211: 85 19 sta zpMisc
b213: a0 00 ldy #$00
b215: a2 04 ldx #$04 ; # pages to copy
b217: a9 24 lda #>VRAM_screen1
b219: 8d 06 20 sta PPUADDR
b21c: a9 00 lda #<VRAM_screen1
b21e: 8d 06 20 sta PPUADDR ;set PPUADDR to addr of SCREEN 1 data
b221: b1 19 @cpyLp lda (zpMisc),y ;read a byte from RAM-saved BG data
b223: 8d 07 20 sta PPUDATA ;copy to VRAM
b226: c8 iny ;have we copied a full page?
b227: d0 f8 bne @cpyLp ;no -> copy next byte
b229: e6 1a inc zpMisc+1 ;increment RAM page to read from
b22b: ca dex ;did we copy 4 pages?
b22c: d0 f3 bne @cpyLp ;no -> copy next page
b22e: ad 05 60 lda bgGetVScroll ;yes, done copying
b231: 85 e5 sta zpVScroll1 ;restore saved vscroll
b233: a9 00 lda #$00
b235: f0 bc beq UpdateBgGotFlag ;(ALWAYS) -> clear bgGotFlag and restore PPU rendering
; Prints the two-character error code, saves running context, and returns to
; direct mode.
PrintErrorAndStop
b237: c9 16 cmp #22 ;if the error code >=
b239: 90 02 bcc @errCodeGood
b23b: a9 15 lda #err_UP
b23d: 85 b5 @errCodeGood sta zpErrCode ;save error code for BASIC
b23f: a6 0b ldx zpRunLineNum
b241: 86 b7 stx zpErrLineNum ;save cur line # as error line
b243: 86 fa stx zpRsmRunLineNum ;and also resume line
b245: a6 0c ldx zpRunLineNum+1
b247: 86 b8 stx zpErrLineNum+1
b249: 86 fb stx zpRsmRunLineNum+1
b24b: a6 e7 ldx zpErrHandStat ;is an error handler installed?
b24d: f0 2a beq PrintError_print ;no -> print error and exit
b24f: e0 02 cpx #EH_IN_HANDLER ;are we currently in error handler?
b251: f0 26 beq PrintError_print ;yes -> print new error and exit
b253: a5 58 lda zpTxtThisCmd ;else: we should call the error handler instead of erroring
b255: 85 f6 sta zpRsmTxtThisCmd ;save away all the resumable context
b257: a5 59 lda zpTxtThisCmd+1
b259: 85 f7 sta zpRsmTxtThisCmd+1
b25b: a5 0d lda zpRunTxtNxtLnk
b25d: 85 f8 sta zpRsmRunTxtNxtL
b25f: a5 0e lda zpRunTxtNxtLnk+1
b261: 85 f9 sta zpRsmRunTxtNxtL+1
b263: a5 e8 lda zpErrHandLNum
b265: 85 44 sta zpFindLNum
b267: a5 e9 lda zpErrHandLNum+1
b269: 85 45 sta zpFindLNum+1
b26b: 20 40 85 jsr FindLineNum ;find error-handler line
b26e: e6 e7 inc zpErrHandStat ;from 1 (EH_NO_HANDLER) to 2 (EH_IN_HANDLER)
b270: a6 17 ldx zpSavedStack ;restore the saved stack
b272: 9a txs
b273: 20 c9 96 jsr GoTo ;-> go to the erorr handler and execute
b276: 4c 4a 83 jmp ExecuteCommand
PrintError_print
b279: 48 pha
b27a: 20 6b b5 jsr EnsureSOL ;move cursor to start of new line (if it isn't already)
b27d: 68 pla
b27e: a2 00 ldx #$00 ;clear the stack
b280: 9a txs
b281: 0a asl A ;multiply error code by 2 for indexing
b282: a8 tay
b283: a9 3f lda #‘?’
b285: 20 4d 89 jsr QueueCharForOutput ;queue a '?'
b288: b9 7f b3 lda tbl_TwoCharErrCodes,y
b28b: 20 4d 89 jsr QueueCharForOutput ;get the two-char code
b28e: b9 80 b3 lda tbl_TwoCharErrCodes+1,y
b291: 20 4d 89 jsr QueueCharForOutput ;and queue that
b294: a9 60 lda #<strERROR
b296: 85 52 sta zpOutputStr
b298: a9 b3 lda #>strERROR
b29a: 85 53 sta zpOutputStr+1
b29c: 20 3c 89 jsr QueueOutputStr ;queue " ERROR"
b29f: 20 c0 88 jsr ClearStoppedFlag ;clear stopped flag
; Code below this line is also used by STOP
StopOrErr_PrintBeepExit
b2a2: a9 ff lda #$ff ;are we in direct mode?
b2a4: c5 0c cmp zpRunLineNum+1
b2a6: d0 04 bne @INmsg ;low byte doesn't match -> not direct
b2a8: c5 0b cmp zpRunLineNum
b2aa: f0 0e beq @skipIN ;yes -> skip "IN [line #]" message
b2ac: a9 55 @INmsg lda #<strIN
b2ae: 85 52 sta zpOutputStr
b2b0: a9 b3 lda #>strIN
b2b2: 85 53 sta zpOutputStr+1
b2b4: 20 3c 89 jsr QueueOutputStr ;queue for output: " IN "
b2b7: 20 c3 b2 jsr @prLineNum ;queue the line number
b2ba: 20 5d 89 @skipIN jsr PrintOutBuf ;print queued STOP/ERROR message
b2bd: 20 d0 bc jsr CmdFn_BEEP ;beep
b2c0: 4c 84 82 jmp DirectModeLoop ;go to command prompt
b2c3: a5 0b @prLineNum lda zpRunLineNum ;get line number low byte
b2c5: 85 11 sta zpContRunLineNum ;save it away
b2c7: 85 28 sta zpWAccum ;and store in WAccum
b2c9: a5 0c lda zpRunLineNum+1 ;ditto for high byte
b2cb: 85 12 sta zpContRunLineNum+1
b2cd: 85 29 sta zpWAccum+1
QueueWordAsDecimalOutput
b2cf: 20 3e 91 jsr PosDecStrFromWord ;translate to decimal string
b2d2: 20 d6 90 jsr FindFirstNonzeroDigit ;skip leading zeroes
b2d5: 4c e0 87 jmp QueueOutpNumStrTmp ;and queue for output
Stop_PrintBreakBeepExit
b2d8: 20 6b b5 jsr EnsureSOL ;move cursor to start of a line (unless it is already)
b2db: a9 00 lda #$00
b2dd: 85 62 sta zpOutTxtCurPos
b2df: a9 5a lda #<strBREAK
b2e1: 85 52 sta zpOutputStr
b2e3: a9 b3 lda #>strBREAK
b2e5: 85 53 sta zpOutputStr+1
b2e7: 20 3c 89 jsr QueueOutputStr ;queue for output: "BREAK"
b2ea: 4c a2 b2 jmp StopOrErr_PrintBeepExit
b2ed: a9 4f PrintOK lda #‘O’
b2ef: 20 75 b5 jsr PrintOneChar
b2f2: a9 4b lda #‘K’
b2f4: 4c 75 b5 jmp PrintOneChar
PrintStartupText
b2f7: a9 7b lda #<strNS_HUDSON
b2f9: 85 52 sta zpOutputStr
b2fb: a9 cf lda #>strNS_HUDSON
b2fd: 85 53 sta zpOutputStr+1
b2ff: 20 5a 89 jsr PrintString ;print "NS-HUBASIC V3.0 "
b302: 20 6b b5 jsr EnsureSOL ;next line
b305: a9 8c lda #<strCOPYRIGHT_MSG
b307: 85 52 sta zpOutputStr
b309: a9 cf lda #>strCOPYRIGHT_MSG
b30b: 85 53 sta zpOutputStr+1
b30d: 20 5a 89 jsr PrintString ;print "(C) NINTENDO/SHARP/HUDSON"
b310: 20 6b b5 jsr EnsureSOL ;next line
b313: 20 25 a9 @bytesFree jsr AmtFreeMemToWAccum
b316: 20 cf b2 jsr QueueWordAsDecimalOutput
b319: 20 5d 89 jsr PrintOutBuf
b31c: a9 49 lda #<strBYTES_FREE
b31e: 85 52 sta zpOutputStr
b320: a9 b3 lda #>strBYTES_FREE
b322: 85 53 sta zpOutputStr+1
b324: 4c 5a 89 jmp PrintString ;-> print "BYTES FREE", then return
b327: a9 38 PrintHotStart lda #<strBASIC_HOT_START
b329: 85 52 sta zpOutputStr
b32b: a9 b3 lda #>strBASIC_HOT_START
b32d: 85 53 sta zpOutputStr+1
b32f: 20 5a 89 jsr PrintString ;print "BASIC HOT START "
b332: 20 6b b5 jsr EnsureSOL ;next line
b335: 4c 13 b3 jmp @bytesFree ;-> print how many bytes free, then return
strBASIC_HOT_START
b338: 42 41 53 49+ .zstr “BASIC HOT START ”
b349: 20 42 59 54+ strBYTES_FREE .zstr “ BYTES FREE”
b355: 20 49 4e 20+ strIN .zstr “ IN ”
b35a: 42 52 45 41+ strBREAK .zstr “BREAK”
b360: 20 45 52 52+ strERROR .zstr “ ERROR”
b367: 57 52 49 54+ strWRITING .zstr “WRITING ”
b370: 4c 4f 41 44+ strLOADING .zstr “LOADING ”
b379: 53 4b 49 50+ strSKIP .zstr “SKIP ”
tbl_TwoCharErrCodes
b37f: 4e 46 .str “NF” ;NEXT without FOR
b381: 53 4e .str “SN” ;Syntax Error
b383: 52 47 .str “RG” ;RETURN without GOSUB
b385: 4f 44 .str “OD” ;Out of Data
b387: 49 4c .str “IL” ;Illegal value
b389: 4f 56 .str “OV” ;Overflow
b38b: 4f 4d .str “OM” ;Out of Memory
b38d: 55 4c .str “UL” ;Undefined Line number
b38f: 53 4f .str “SO” ;Subscript Out of range
b391: 44 44 .str “DD” ;Duplicate Definition
b393: 44 5a .str “DZ” ;Division by Zero
b395: 54 4d .str “TM” ;Type Mismatch
b397: 53 54 .str “ST” ;String Too long
b399: 46 54 .str “FT” ;Formula Too complex
b39b: 43 43 .str “CC” ;Cannot Continue
b39d: 55 46 .str “UF” ;Undefined Function
b39f: 4d 4f .str “MO” ;Missing Operand
b3a1: 54 50 .str “TP” ;Tape read error
b3a3: 4e 52 .str “NR” ;No Resume. Given on RESUME without error handler being called, or on program exit when error handled without resume.
b3a5: 52 45 .str “RE” ;RESUME without ERROR. RESUME called but no ON ERROR installed.
b3a7: 4e 42 .str “NB” ;No BG Data
b3a9: 55 50 .str “UP” ;Unprintable Error
b3ab: a9 10 InitPpuApu lda #DEFAULT_PPUCTRL
b3ad: 85 32 sta zpPpuCtrlVal ;when used below (SetPpuCtrlAndEnableNmi), sets bg to use separate tiles from sprites; everything else defaults
b3af: a9 0e lda #$0e
b3b1: 85 33 sta zpPpuMaskVal ;if used, will disable sprites, enable background
b3b3: 20 59 b5 jsr AwaitVBlankAndClearPpuMask ;disables all rendering
b3b6: a9 00 lda #$00
b3b8: 85 f5 sta $f5
b3ba: 85 f0 sta zpCurScreenNum
b3bc: 85 e4 sta zpVScroll0
b3be: 85 e5 sta zpVScroll1
b3c0: 85 63 sta zpNMICMD
b3c2: 85 ab sta zpNmiAnimNxtFr
b3c4: 85 ac sta zpNmiAnimate
b3c6: 85 62 sta zpOutTxtCurPos
b3c8: 85 72 sta zpKanaFlag
b3ca: 85 50 sta zpKanaHeld
b3cc: 85 18 sta zpModifierKeyDn
b3ce: 8d 10 40 sta DMC_FREQ
b3d1: 8d 01 40 sta SQ1_SWEEP
b3d4: 8d 05 40 sta SQ2_SWEEP
b3d7: 8d 15 40 sta SND_CHN
b3da: 85 4c sta zpInsertMode
b3dc: 85 66 sta zpRkimMidMacro
b3de: 85 67 sta zpFnKeyDfnOff
b3e0: a9 4f lda #$4f
b3e2: 8d 17 40 sta JOY2 ;initialize the APU frame counter
b3e5: 20 20 b4 jsr InitPromptChars
b3e8: 20 14 be jsr SetPpuCtrlAndEnableNmi
b3eb: a9 20 lda #>VRAM_screen0
b3ed: 85 4f sta zpPpuScreenBasePage ;set SCREEN 0 as the current screen
b3ef: 20 ab b4 jsr DoCLS ;clear and activate SCREEN 0
b3f2: 20 36 b4 jsr ResetPaletteSets ;reset palette sets to their startup values
b3f5: a2 00 ldx #$00
b3f7: 8a txa
b3f8: a0 80 ldy #$80
b3fa: 9d 80 04 @fkeyClrLp sta fnKey1_DfnBuf,x ;clear the function-key definitions
b3fd: e8 inx
b3fe: 88 dey
b3ff: d0 f9 bne @fkeyClrLp
b401: a2 00 ldx #$00
; initialize the fn key definitions to their boot-up defaults
b403: a5 67 @fkeyDefsLp lda zpFnKeyDfnOff ;<- this is being as a counter of function keys, not offsets
b405: c9 08 cmp #$08 ;did we do 8 fn key definition?
b407: f0 2c beq rts_B435 ;yes -> we're done
b409: 20 53 ad jsr MulABy16 ;convert fnkey # to an index into the defns area
b40c: a8 tay ;and move to the Y reg
b40d: bd 4a b4 @defnCpyLp lda FnKeyDefaultDefs,x ;load a character from the hard-coded default
b410: f0 08 beq @nxtFnKey ;terminating null char? -> finished with this def, on to the next
b412: 99 80 04 sta fnKey1_DfnBuf,y ;store char in assigned definition space
b415: e8 inx ;incr source idx
b416: c8 iny ;incr dest idx
b417: 4c 0d b4 jmp @defnCpyLp ;(always) -> on to copying next character
b41a: e8 @nxtFnKey inx
b41b: e6 67 inc zpFnKeyDfnOff
b41d: 4c 03 b4 jmp @fkeyDefsLp
b420: a9 fd InitPromptChars lda #TileId_whiteBox ;"white box" tile character
b422: 85 e0 sta zpKanaCursChr ;(set as cursor when KANA mode active)
b424: a9 5f lda #‘_’ ;"underscore" tile character
b426: 85 e1 sta zpNonKCursChr ;(set as normal cursor)
b428: 4c 66 b7 jmp DeactivateKanaMode ;initialize to normal (non-KANA) mode
b42b: ad 02 20 WaitForVBlank lda PPUSTATUS ;read from PPUSTATUS until vblank flag is clear, to clear it
b42e: 30 fb bmi WaitForVBlank
b430: ad 02 20 @waitLp lda PPUSTATUS ;now read from it until it's set again.
b433: 10 fb bpl @waitLp ;then we know we've hit vblank
b435: 60 rts_B435 rts
ResetPaletteSets
b436: a9 be lda #<cgsetBgPalSet1
b438: 85 52 sta zpOutputStr
b43a: a9 cf lda #>cgsetBgPalSet1 ;setup bg palette set no. 1
b43c: 85 53 sta zpOutputStr+1
b43e: a9 00 lda #$00
b440: 85 65 sta zpPaletBgOrSpr ;select writing bg palette set
b442: 20 93 af jsr CGSET_SetPaletteSet ;set the palette set
b445: e6 65 inc zpPaletBgOrSpr ;select writing spr palette set
b447: 4c 93 af jmp CGSET_SetPaletteSet ;set the palette set (using the bg set for both bg and spr)
; These strings are used to set the default FN key shorthands.
FnKeyDefaultDefs
b44a: 47 41 4d 45+ .zstr “GAME 0”,$0d
b452: 47 41 4d 45+ .zstr “GAME 1”,$0d
b45a: 47 41 4d 45+ .zstr “GAME 2,1”,$0d
b464: 47 41 4d 45+ .zstr “GAME 3”,$0d
b46c: 53 50 52 49+ .zstr “SPRITE ”
b474: 4c 4f 41 44+ .zstr “LOAD”,$0d
b47a: 4c 49 53 54+ .zstr “LIST”,$0d
b480: 52 55 4e 0d+ .zstr “RUN”,$0d
ClearScreenAndAttrs
b485: a0 04 ldy #$04
b487: a2 c0 ldx #$c0 ;#$c0 first iteration, #$00 (256) iters 2-4.
b489: a9 20 lda #$20 ;SPC char
b48b: 8d 07 20 @lpSpaces sta PPUDATA
b48e: ca dex
b48f: d0 fa bne @lpSpaces ;end of inner loop
b491: 88 dey
b492: d0 f7 bne @lpSpaces ;end outer loop. 960x (30 rows * 30 cols, w margins)
ClearScreenAttrs
b494: a2 c0 ldx #$c0
b496: a5 4f lda zpPpuScreenBasePage
b498: 18 clc
b499: 69 03 adc #$03
b49b: 20 ec 81 jsr PpuGoToAX ;go to the update screen's attribute table location
b49e: a9 00 lda #$00
b4a0: 8d 07 20 @clrAttr sta PPUDATA ;and clear it
b4a3: e8 inx
b4a4: d0 fa bne @clrAttr
b4a6: 60 rts
b4a7: a9 24 DoCLS_1 lda #>VRAM_screen1
b4a9: 85 4f sta zpPpuScreenBasePage
b4ab: a9 0c DoCLS lda #NC_PpuMaskClear
b4ad: 20 6a 89 jsr WaitForNmiSubcommand ;Set PPUMASK = 0
b4b0: 20 0e be jsr SetPpuCtrl
b4b3: 20 59 b5 jsr AwaitVBlankAndClearPpuMask
b4b6: a5 4f lda zpPpuScreenBasePage
b4b8: a2 00 ldx #$00
b4ba: 20 ec 81 jsr PpuGoToAX ;Set PPU cur addr to start of update screen
b4bd: 20 85 b4 jsr ClearScreenAndAttrs
b4c0: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
b4c3: 20 14 be jsr SetPpuCtrlAndEnableNmi
b4c6: a9 0b lda #NC_PpuMaskUpdate
b4c8: 20 6a 89 jsr WaitForNmiSubcommand ;Restore PPUMASK
b4cb: 20 48 b7 jsr CursorHome ;LOCATE 0,0 (also sets A = 0)
b4ce: a4 4f ldy zpPpuScreenBasePage
b4d0: c0 20 cpy #>VRAM_screen0 ;upd scr == 0?
b4d2: f0 05 beq @scrIs0 ;yes ->
b4d4: 85 e5 sta zpVScroll1 ;upd scr == 1, clear zpVScroll1
b4d6: 4c db b4 jmp @skip
b4d9: 85 e4 @scrIs0 sta zpVScroll0 ;upd scr == -, clear zpVScroll0
b4db: aa @skip tax
b4dc: a4 4f ldy zpPpuScreenBasePage
b4de: c0 20 cpy #>VRAM_screen0 ;upd scr == 0?
b4e0: f0 09 beq @clrScrInf0 ;yes -> clear that screen's info
b4e2: 9d 1a 06 @clrScrInf1 sta isRowWrappedArray1-1,x ;no -> clear screen 1's info
b4e5: e8 inx
b4e6: e0 1a cpx #$1a
b4e8: d0 f8 bne @clrScrInf1
b4ea: 60 rts
b4eb: 9d 00 06 @clrScrInf0 sta isRowWrappedArray0-1,x
b4ee: e8 inx
b4ef: e0 1a cpx #$1a
b4f1: d0 f8 bne @clrScrInf0
b4f3: 60 rts
CalcCursorTilemapPos
b4f4: a9 20 lda #$20
b4f6: 85 28 sta zpWAccum
b4f8: a9 00 lda #$00
b4fa: 85 29 sta zpWAccum+1
b4fc: 85 2d sta zpWParam+1
b4fe: a5 e4 lda zpVScroll0
b500: a4 4f ldy zpPpuScreenBasePage
b502: c0 20 cpy #>VRAM_screen0
b504: f0 02 beq @scr0
b506: a5 e5 lda zpVScroll1
b508: 4a @scr0 lsr A
b509: 4a lsr A
b50a: 4a lsr A
b50b: 18 clc
b50c: 65 49 adc zpCV
b50e: 69 03 adc #VMARGIN
b510: c9 1e cmp #MAP_ROWS
b512: 90 02 bcc @notOverfl
b514: e9 1e sbc #MAP_ROWS
b516: 85 2c @notOverfl sta zpWParam
; At this point, zpPARAM16 ($2C.2D) = ((zpVScroll/8) + CV + 3) MOD 30), or the
; current row number relative to the actual start of the tilemap in VRAM (not
; just "top of screen", like zpCV).
;
; We set the multiplier to 32, or the size of a row on the screen (including
; margins)
b518: 20 47 8f jsr WordMultiply
b51b: 18 clc
b51c: a5 48 lda zpCH
b51e: 69 02 adc #$02
b520: 65 28 adc zpWAccum
b522: 85 4a sta zpCursTileAddr
b524: a9 00 lda #$00
b526: 65 29 adc zpWAccum+1
b528: 18 clc
b529: 65 4f adc zpPpuScreenBasePage
b52b: 85 4b sta zpCursTileAddr+1
b52d: 60 rts
; Sets the current VRAM address to the cursor's current tile position
b52e: a5 4b PpuGoToCursor lda zpCursTileAddr+1
b530: 8d 06 20 sta PPUADDR
b533: a5 4a lda zpCursTileAddr
b535: 8d 06 20 sta PPUADDR
b538: 60 rts
NmiWrTileSetVScrollAndEnableNmi
b539: 8d 07 20 sta PPUDATA
; Set the vertical scroll position from either zpVScroll0 or zpVScroll1,
; depending on the value of $F0
NmiSetVScrollAndEnableNmi
b53c: 20 14 be jsr SetPpuCtrlAndEnableNmi
b53f: a5 f0 lda zpCurScreenNum ;which screen is active?
b541: f0 0b beq @scr0 ;screen 0? -> update scroll for that
b543: a9 00 lda #$00 ;otherwise, update scroll for scr 1
b545: 8d 05 20 sta PPUSCROLL
b548: a5 e5 lda zpVScroll1
b54a: 8d 05 20 sta PPUSCROLL
b54d: 60 rts
b54e: a9 00 @scr0 lda #$00
b550: 8d 05 20 sta PPUSCROLL
b553: a5 e4 lda zpVScroll0
b555: 8d 05 20 sta PPUSCROLL
b558: 60 rts
AwaitVBlankAndClearPpuMask
b559: 20 2b b4 jsr WaitForVBlank
b55c: a9 00 lda #$00
b55e: 8d 01 20 sta PPUMASK
b561: 60 rts
AwaitVBlankAndUpdatePpuMask
b562: 20 2b b4 jsr WaitForVBlank
b565: a5 33 lda zpPpuMaskVal
b567: 8d 01 20 sta PPUMASK
b56a: 60 rts_B56A rts
; Go to start of next line... unless we're already at the start of a line.
b56b: a5 48 EnsureSOL lda zpCH
b56d: f0 fb beq rts_B56A
b56f: a9 00 DoCRLF lda #$00 ;<- here's the CR
b571: 85 48 sta zpCH
b573: a9 0a lda #$0a ;<- and here's the LF
b575: c9 20 PrintOneChar cmp #$20 ;is it a printable character?
b577: b0 23 bcs OutputChar
b579: c9 08 cmp #$08 ;backspace (ctrl-H)
b57b: f0 10 beq @leaveInsertAlone
b57d: c9 01 cmp #$01 ;insert space (ctrl-A)
b57f: f0 0c beq @leaveInsertAlone
b581: c9 12 cmp #$12 ;toggle insert/overwrite (ctrl-R)
b583: f0 08 beq @leaveInsertAlone
b585: c9 09 cmp #$09 ;tab (ctrl-I)
b587: f0 04 beq @leaveInsertAlone
b589: a0 00 ldy #$00 ;all other controls disable insert mode
b58b: 84 4c sty zpInsertMode
@leaveInsertAlone
b58d: 0a asl A
b58e: aa tax
b58f: bd be b8 lda jtbl_CtrlCharHandlers,x
b592: 85 28 sta zpWAccum
b594: bd bf b8 lda jtbl_CtrlCharHandlers+1,x
b597: 85 29 sta zpWAccum+1
b599: 6c 28 00 jmp (zpWAccum)
b59c: a4 4c OutputChar ldy zpInsertMode
b59e: f0 07 beq @overwr
b5a0: 48 pha
b5a1: a9 12 lda #$12 ;control-R
b5a3: 20 75 b5 jsr PrintOneChar
b5a6: 68 pla
b5a7: 20 4d 89 @overwr jsr QueueCharForOutput
b5aa: 4c 5d 89 jmp PrintOutBuf
; Advance the cursor to the start of the next row.
; Does exactly the same thing as CursorDown.
CursorToNextRowStart
b5ad: e6 49 inc zpCV
b5af: a5 49 lda zpCV
b5b1: c9 18 cmp #ROWS
b5b3: f0 12 beq ScrollScreenUpOneRow
; Handler for control chars that don't do anything when input.
b5b5: a9 00 ControlCharNOOP lda #$00
b5b7: 60 rts
; Advance the cursor to the start of the next line, skipping over any wrapped
; rows in the current one.
CursorToNextLineStart
b5b8: 20 4c b7 jsr CarriageReturn
b5bb: a6 49 @findStart ldx zpCV
b5bd: 20 37 b6 jsr DoesRowWrap
b5c0: f0 eb beq CursorToNextRowStart
b5c2: e6 49 inc zpCV
b5c4: 4c bb b5 jmp @findStart
; Scrolls the screen up by one row, leaving the cursor on the first row of the
; (new) last row).
;
; Erases the top line just before scrolling it up (so it can become a future new
; line when we scroll down to it in future).
ScrollScreenUpOneRow
b5c7: c6 7e dec zp7E
b5c9: 20 48 b7 jsr CursorHome
b5cc: 20 f4 b4 jsr CalcCursorTilemapPos
; Tell NMI handler to erase to end of row (cursor is at home row, so erase
; entire top row)
b5cf: a9 04 lda #NC_ClearToEndOfRow
b5d1: 20 6a 89 jsr WaitForNmiSubcommand
; Scrolls the screen WITHOUT clearing any lines. Unlike many other systems,
; Family Basic doesn't scroll by copying lines upward: the tilemap is vertically
; 'mirrored' (repeated), so we have only to adjust the PPU vertical scroll
; register. Very fast scrolls!
;
ScrollScreenOneRowNoErase
b5d4: a5 32 lda zpPpuCtrlVal
b5d6: 8d 00 20 sta PPUCTRL
b5d9: a5 e4 lda zpVScroll0
b5db: a4 4f ldy zpPpuScreenBasePage
b5dd: c0 20 cpy #>VRAM_screen0
b5df: f0 02 beq @scr0
b5e1: a5 e5 lda zpVScroll1
b5e3: 18 @scr0 clc
b5e4: 69 08 adc #TILE_PIXELS
b5e6: c9 f0 cmp #VPIXELS
b5e8: 90 02 bcc @notOvfl
b5ea: a9 00 lda #$00
b5ec: a4 4f @notOvfl ldy zpPpuScreenBasePage
b5ee: c0 20 cpy #<VRAM_screen0+32
b5f0: f0 05 beq @scr0
b5f2: 85 e5 sta zpVScroll1
b5f4: 4c f9 b5 jmp @scr1
b5f7: 85 e4 @scr0 sta zpVScroll0
b5f9: 20 2b b4 @scr1 jsr WaitForVBlank
b5fc: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
b5ff: a0 18 ldy #ROWS
b601: a2 00 ldx #$00
b603: a5 4f lda zpPpuScreenBasePage
b605: c9 20 cmp #>VRAM_screen0
b607: f0 13 beq @scrZLp
b609: bd 1c 06 @scrOneLp lda isRowWrappedArray1+1,x
b60c: 9d 1b 06 sta isRowWrappedArray1,x
b60f: e8 inx
b610: 88 dey
b611: d0 f6 bne @scrOneLp
b613: 98 tya
b614: 8c 32 06 sty isRowWrappedArray1+23
b617: 8c 33 06 sty $0633
b61a: f0 12 beq @setLoc ;always branches
b61c: bd 02 06 @scrZLp lda isRowWrappedArray0+1,x
b61f: 9d 01 06 sta isRowWrappedArray0,x
b622: e8 inx
b623: 88 dey
b624: d0 f6 bne @scrZLp
b626: a9 00 lda #$00
b628: 8d 18 06 sta isRowWrappedArray0+23
b62b: 8d 19 06 sta $0619
b62e: 85 48 @setLoc sta zpCH
b630: a9 17 lda #ROWS-1
b632: 85 49 sta zpCV
b634: 4c 14 be jmp SetPpuCtrlAndEnableNmi
; Returns whether the row identified in register X wraps to the next row
b637: a5 4f DoesRowWrap lda zpPpuScreenBasePage
b639: c9 20 cmp #>VRAM_screen0
b63b: f0 04 beq @scr0
b63d: bd 1b 06 lda isRowWrappedArray1,x
b640: 60 rts
b641: bd 01 06 @scr0 lda isRowWrappedArray0,x
b644: 60 rts
; Record that a given row was wrapped, and does not begin a new line of its own.
b645: 48 MarkWrappedRow pha
b646: a5 4f lda zpPpuScreenBasePage
b648: c9 20 cmp #>VRAM_screen0
b64a: f0 05 beq @scr0
b64c: 68 pla
b64d: 9d 1b 06 sta isRowWrappedArray1,x
b650: 60 rts
b651: 68 @scr0 pla
b652: 9d 01 06 sta isRowWrappedArray0,x
b655: 60 rts
b656: a5 48 SaveCursorPos lda zpCH
b658: 85 6b sta zpSavedCH
b65a: a5 49 lda zpCV
b65c: 85 6c sta zpSavedCV
b65e: 60 rts
RestoreCursorPos
b65f: a5 6b lda zpSavedCH
b661: 85 48 sta zpCH
b663: a5 6c lda zpSavedCV
b665: 85 49 sta zpCV
b667: 60 rts
; Invoked by Control-D
ResetGraphicSettings
b668: 20 59 b5 jsr AwaitVBlankAndClearPpuMask ;clear PPU mask
b66b: a9 20 lda #>VRAM_screen0
b66d: 85 4f sta zpPpuScreenBasePage ;set SCREEN 0 in zp vars
b66f: a9 00 lda #$00
b671: 85 f0 sta zpCurScreenNum
b673: a9 10 lda #$10
b675: 85 32 sta zpPpuCtrlVal
b677: 20 14 be jsr SetPpuCtrlAndEnableNmi ;and switch to it
b67a: a9 0e lda #$0e
b67c: 8d 01 20 sta PPUMASK ;reasonable PPUMASK default settings
b67f: 85 33 sta zpPpuMaskVal
b681: 20 36 b4 jsr ResetPaletteSets ;set bg and spr palettes from bg palette set 1
b684: 20 94 b4 jsr ClearScreenAttrs
b687: 85 4c sta zpInsertMode ;turn off auto-insert
b689: 20 3c b5 jsr NmiSetVScrollAndEnableNmi ;make sure vert scroll is up to date
b68c: 4c 62 b5 jmp AwaitVBlankAndUpdatePpuMask
; Clears to end of current (possibly multi-row) line
ClearToEndOfLine
b68f: 20 56 b6 jsr SaveCursorPos
b692: 20 f4 b4 jsr CalcCursorTilemapPos
b695: a9 04 lda #NC_ClearToEndOfRow
b697: 20 6a 89 @rowLp jsr WaitForNmiSubcommand
b69a: a6 49 ldx zpCV
b69c: 20 37 b6 jsr DoesRowWrap
b69f: f0 0d beq restoreCursPos_B6AE
b6a1: e6 49 inc zpCV
b6a3: 20 4c b7 jsr CarriageReturn
b6a6: 20 f4 b4 jsr CalcCursorTilemapPos
b6a9: a9 04 lda #$04
b6ab: 4c 97 b6 jmp @rowLp
restoreCursPos_B6AE
b6ae: 4c 5f b6 jmp RestoreCursorPos
; Control-Z
ClearToEndOfScreen
b6b1: 20 56 b6 jsr SaveCursorPos ;save cursor pos
b6b4: 20 f4 b4 jsr CalcCursorTilemapPos ;calc VRAM addr for cursor pos
b6b7: a9 04 @clrLp lda #NC_ClearToEndOfRow
b6b9: 20 6a 89 jsr WaitForNmiSubcommand ;clear from cursor to end of row
b6bc: a6 49 ldx zpCV
b6be: a9 00 lda #$00
b6c0: 20 45 b6 jsr MarkWrappedRow ;mark this row doesn't wrap to next
b6c3: 85 48 sta zpCH
b6c5: e6 49 inc zpCV ;set cursor to start of next row
b6c7: e8 inx
b6c8: e0 18 cpx #ROWS ;did we just move past the last row of screen?
b6ca: f0 e2 beq restoreCursPos_B6AE ;yes -> restore cursor pos and return
b6cc: 20 f4 b4 jsr CalcCursorTilemapPos ;no -> calc VRAM addr for new pos
b6cf: 4c b7 b6 jmp @clrLp ;and loop 'round to clear this row
; dead/unreachable code
b6d2: a6 49 ldx zpCV
b6d4: ca dex
b6d5: 8a txa
b6d6: aa tax
b6d7: e8 inx
b6d8: 20 37 b6 jsr DoesRowWrap
b6db: 60 rts
b6dc: 4c 6f b5 jmp DoCRLF
b6df: a5 48 HandleTabKey lda zpCH
b6e1: c9 15 cmp #21
b6e3: b0 0b bcs @ts3
b6e5: c9 0e cmp #14
b6e7: b0 0a bcs @ts2
b6e9: c9 07 cmp #7
b6eb: b0 09 bcs @ts1
b6ed: a9 07 lda #$07
b6ef: ec cpx ▼ $1ca9
b6f0: a9 1c @ts3 lda #28
b6f2: ec cpx ▼ $15a9
b6f3: a9 15 @ts2 lda #21
b6f5: ec cpx ▼ $0ea9
b6f6: a9 0e @ts1 lda #14
b6f8: 38 sec
b6f9: e5 48 sbc zpCH
b6fb: 85 62 sta zpOutTxtCurPos
b6fd: aa tax
b6fe: a9 20 lda #$20
b700: 9d 00 07 @spcLp sta genPurposeBuf,x
b703: ca dex
b704: 10 fa bpl @spcLp
b706: 4c 5d 89 jmp PrintOutBuf
ToggleInsertOrOverwriteMode
b709: a9 ff lda #$ff
b70b: 45 4c eor zpInsertMode
b70d: 85 4c sta zpInsertMode
b70f: 60 rts
b710: a5 49 CursorUp lda zpCV
b712: f0 02 beq @rts
b714: c6 49 dec zpCV
b716: 60 @rts rts
; Exactly the same as LineFeed, _except_ in the case when the cursor moves past
; the end of the screen. In that case, LineFeed will leave cursor at the start
; of the new line; CursorDown will leave cursor at the original column.
b717: a5 48 CursorDown lda zpCH
b719: 48 pha
b71a: 20 51 b7 jsr LineFeed
b71d: 68 pla
b71e: 85 48 sta zpCH
b720: 60 rts
b721: a5 48 CursorLeft lda zpCH ;already at leftmost column?
b723: f0 03 beq @chkUp ;yes -> check if we can go up a row
b725: c6 48 dec zpCH ;no, so just back up a column.
b727: 60 rts
b728: a5 49 @chkUp lda zpCV ;already at topmost row?
b72a: f0 06 beq @rts ;yes -> nothing to do, exit
b72c: a9 1b lda #$1b ;no, so go up a row and to the rightmost column
b72e: 85 48 sta zpCH
b730: c6 49 dec zpCV
b732: 60 @rts rts
b733: a5 48 CursorRight lda zpCH
b735: c9 1b cmp #$1b ;already in rightmost column?
b737: f0 03 beq @ckNext ;yes -> check if we can go to next row
b739: e6 48 inc zpCH ;no, just go right a column
b73b: 60 rts
b73c: 20 4c b7 @ckNext jsr CarriageReturn
b73f: a5 49 lda zpCV
b741: c9 17 cmp #$17 ;are we on the last row?
b743: f0 02 beq @rts ;yes -> nothing to do, exit
b745: e6 49 inc zpCV ;no, just drop down a row
b747: 60 @rts rts
b748: a9 00 CursorHome lda #$00
b74a: 85 49 sta zpCV
b74c: a9 00 CarriageReturn lda #$00
b74e: 85 48 sta zpCH
b750: 60 rts
b751: a6 49 LineFeed ldx zpCV
b753: e8 inx ;increment row
b754: e0 18 cpx #ROWS ;past last row?
b756: d0 03 bne @nextRow ;no -> store new row and exit
b758: 4c c7 b5 jmp ScrollScreenUpOneRow ;yes
b75b: 86 49 @nextRow stx zpCV
b75d: 60 rts
ActivateKanaMode
b75e: a9 ff lda #$ff
b760: 85 72 sta zpKanaFlag
b762: a5 e1 lda zpNonKCursChr
b764: d0 06 bne @setCurs
DeactivateKanaMode
b766: a9 00 lda #$00
b768: 85 72 sta zpKanaFlag
b76a: a5 e0 lda zpKanaCursChr
b76c: 85 61 @setCurs sta zpCursorChar
b76e: 60 rts
; Move cursor left one (unless at start of line), and shift all chars forward
; from the cursor, back by one, across all screen rows occupied by the (possibly
; wrapped) line.
;
; If cursor is at start of the line, still shifts all chars forward, performing
; a forward-delete instead of backspace.
b76f: a6 48 Backspace ldx zpCH
b771: f0 05 beq @bsFromSol
b773: c6 48 dec zpCH
b775: 4c 88 b7 jmp @dontGoUpRow
b778: a6 49 @bsFromSol ldx zpCV ;backspacing from start of line
b77a: f0 0c beq @dontGoUpRow ;backspacing from home position? -> can't back up higher
b77c: ca dex
b77d: 20 37 b6 jsr DoesRowWrap ;check preceding row's wrap status
b780: f0 06 beq @dontGoUpRow ;doesn't wrap -> not same line; not backing up
b782: c6 49 dec zpCV ;wraps.
b784: a9 1b lda #COLS-1 ;back up to last column of preceding row.
b786: 85 48 sta zpCH
b788: 20 56 b6 @dontGoUpRow jsr SaveCursorPos ;save cursor pos
BS_ShiftRowLeftOneCol
b78b: a5 48 lda zpCH
b78d: c9 1b cmp #COLS-1 ;on last char of row?
b78f: f0 1b beq @skip ;yes -> skip
b791: 20 f4 b4 jsr CalcCursorTilemapPos
b794: a5 4a lda zpCursTileAddr ;pass backspaced position
b796: 85 19 sta zpMisc ; as parameter to upcoming NMI subcmd
b798: a5 4b lda zpCursTileAddr+1
b79a: 85 1a sta zpMisc+1
b79c: e6 48 inc zpCH ;go forward a char
b79e: 20 f4 b4 jsr CalcCursorTilemapPos ;recalc CalcCursorTilemapPos
b7a1: c6 48 dec zpCH ;go back to our backspaced position
b7a3: a9 03 lda #NC_CopyCharsLeft
b7a5: 20 6a 89 jsr WaitForNmiSubcommand ;copy this row back one char
b7a8: a9 1b lda #COLS-1
b7aa: 85 48 sta zpCH
b7ac: a6 49 @skip ldx zpCV
b7ae: 20 37 b6 jsr DoesRowWrap ;does the present row wrap to the next?
b7b1: d0 11 bne @backshiftNextRow ;yes -> give next row same treatment
b7b3: 20 f4 b4 jsr CalcCursorTilemapPos ;calculate position of last character of last row we moved
b7b6: 20 2b b4 jsr WaitForVBlank ;wait for PPU ops to be safe
b7b9: 20 2e b5 jsr PpuGoToCursor ;go to that location
b7bc: a9 20 lda #$20 ;overwrite character at this position with SPACE
b7be: 20 39 b5 jsr NmiWrTileSetVScrollAndEnableNmi
b7c1: 4c 5f b6 jmp RestoreCursorPos ;restore cursor position
@backshiftNextRow
b7c4: e6 49 inc zpCV ;incr row
b7c6: a9 ff lda #$ff ;set x pos to last column
b7c8: 85 48 sta zpCH
b7ca: 20 d5 b7 jsr BS_moveStartCharBack ;move first char of next row to here (last char of preceding row)
b7cd: e6 49 inc zpCV ;CV just got dec'd; re-increment
b7cf: 20 4c b7 jsr CarriageReturn ;move cursor to start of the row
b7d2: 4c 8b b7 jmp BS_ShiftRowLeftOneCol ;shift this forward row back one
; This routine has some generalized handling, but is only used in very specific
; conditions (always grabs first char on next line). Special setup before
; calling is also performed to make it do that. It's all pretty silly.
BS_moveStartCharBack
b7d5: e6 48 inc zpCH ;bump cursor x
b7d7: 20 f4 b4 jsr CalcCursorTilemapPos ;calc position
b7da: 20 2b b4 jsr WaitForVBlank ;wait until it's safe for PPU ops
b7dd: 20 2e b5 jsr PpuGoToCursor ;go to bumped cursor position
b7e0: ad 07 20 lda PPUDATA ;fake first PPU read
b7e3: ad 07 20 lda PPUDATA ;get the char value at bumped loc
b7e6: 48 pha ;push it
b7e7: 20 3c b5 jsr NmiSetVScrollAndEnableNmi ;make sure vert scroll is up-to-date
b7ea: c6 48 dec zpCH ;back up one char (back to original)
b7ec: a5 48 lda zpCH
b7ee: c9 ff cmp #$ff ;did we wrap to prev line?
b7f0: d0 06 bne @notEndOfPrev ;(NEVER) no -> skip adjust
b7f2: a9 1b lda #COLS-1 ;yes: fix #$ff to #COLS-1
b7f4: 85 48 sta zpCH
b7f6: c6 49 dec zpCV ;and decrement row
b7f8: 20 f4 b4 @notEndOfPrev jsr CalcCursorTilemapPos ;calc new position
b7fb: 20 2b b4 jsr WaitForVBlank ;wait again for safe PPU ops
b7fe: 20 2e b5 jsr PpuGoToCursor ;and go there
b801: 68 pla ;pull char we got from start
b802: 4c 39 b5 jmp NmiWrTileSetVScrollAndEnableNmi ;and put it on last char of prev line
vZpCpyLimitRow .var $91 {addr/1}
vZpInsChar .var $b0 {addr/1}
b805: 20 56 b6 InsertSpaceChar jsr SaveCursorPos ;save cursor pos
b808: a9 20 lda #$20 ;SPACE char
b80a: 85 b0 sta vZpInsChar ;set as char to insert
b80c: 20 f4 b4 @shiftRowsLp jsr CalcCursorTilemapPos ;update cursor VRAM addr
b80f: a9 0d lda #NC_InsertChar
b811: 20 6a 89 jsr WaitForNmiSubcommand ;insert the char, shifting others right
b814: a6 49 ldx zpCV
b816: 20 37 b6 jsr DoesRowWrap ;does the current row wrap to the next?
b819: f0 0b beq InsSpC_checkFinalWrap ;no -> check if we just wrapped
b81b: e6 49 inc zpCV ;yes: go to next row
b81d: 20 4c b7 jsr CarriageReturn
b820: 4c 0c b8 jmp @shiftRowsLp ;and insert the shifted-off char onto next line
restoreCursorPos_B823
b823: 4c 5f b6 jmp RestoreCursorPos
InsSpC_checkFinalWrap
b826: a5 b0 lda vZpInsChar ;get the shifted-off char from this row
b828: c9 20 cmp #$20 ;was it a SPACE char?
b82a: f0 f7 beq restoreCursorPos_B823 ;yes -> don't need to re-insert. Restore curs, return
b82c: a5 49 lda zpCV ;no; gotta wrap it to next row
b82e: c9 17 cmp #ROWS-1 ;are we on the last row?
b830: f0 61 beq InsSpC_scrollAndInsert ;yes -> got to scroll screen
b832: 20 4c b7 jsr CarriageReturn ;no, don't need to scroll
b835: a4 49 ldy zpCV
b837: c8 iny ;<- (could skip that by `cpy #ROWS-2` instead...)
b838: c0 17 cpy #ROWS-1 ;is next row the last one?
b83a: f0 26 beq @haveRoom ;yes -> skip making room for a new row
b83c: a5 49 lda zpCV
b83e: 85 91 sta vZpCpyLimitRow ;set current row for limit of down-copies
b840: a9 16 lda #ROWS-2 ;start at penultimate row
b842: 85 49 sta zpCV
b844: 20 f4 b4 @rowMoveLp jsr CalcCursorTilemapPos ;calc cursor VRAM addr
b847: a5 49 lda zpCV
b849: 48 pha ;push y pos
b84a: a9 0e lda #NC_CopyRowDown
b84c: 20 6a 89 jsr WaitForNmiSubcommand ;copy this row down to next
b84f: 68 pla ;pull y pos
b850: 85 49 sta zpCV
b852: aa tax
b853: 20 37 b6 jsr DoesRowWrap ;copy "row wrapped" mark down one, too
b856: e8 inx
b857: 20 45 b6 jsr MarkWrappedRow
b85a: c6 49 dec zpCV ;go up one row
b85c: a5 49 lda zpCV
b85e: c5 91 cmp vZpCpyLimitRow ;did we finish making room for new row?
b860: d0 e2 bne @rowMoveLp ;no -> copy this row down, too
b862: a6 49 @haveRoom ldx zpCV
b864: a9 ff lda #$ff ;mark that we wrapped to new row (that's how we got here!)
b866: 20 45 b6 jsr MarkWrappedRow
b869: e6 49 inc zpCV ;move to new row
b86b: e8 inx
b86c: a9 00 lda #$00
b86e: 20 45 b6 jsr MarkWrappedRow ;mark that new row doesn't wrap (it's new!)
b871: a2 17 ldx #ROWS-1
b873: 20 45 b6 jsr MarkWrappedRow ;mark that final row of screen also doesn't wrap (nowhere to go)
b876: a5 6b lda zpSavedCH
b878: 48 pha ;push original cursor pos, and auto-insert flag
b879: a5 6c lda zpSavedCV
b87b: 48 pha
b87c: a5 4c lda zpInsertMode
b87e: 48 pha
b87f: a9 05 lda #$05 ;"print" Control-E (clear to end of row)
b881: 20 75 b5 jsr PrintOneChar
b884: 68 pla ;pull auto-insert flag
b885: 85 4c sta zpInsertMode ;restore it
b887: a9 0d lda #NC_InsertChar
b889: 20 6a 89 jsr WaitForNmiSubcommand ;insert the shifted-off char (uh... blank row, we should directly set it instead)
b88c: 68 pla
b88d: 85 49 sta zpCV
b88f: 68 pla
b890: 85 48 sta zpCH ;restore cursor pos
b892: 60 rts ;and return
InsSpC_scrollAndInsert
b893: a5 6b lda zpSavedCH ;push saved cursor x pos
b895: 48 pha
b896: a6 6c ldx zpSavedCV ;decrement cursor y pos unless at top of screen
b898: f0 01 beq @noDec
b89a: ca dex
b89b: 8a @noDec txa
b89c: 48 pha ;and push
b89d: 20 c7 b5 jsr ScrollScreenUpOneRow ;scroll screen up by one row
b8a0: a9 17 lda #ROWS-1 ;set cursor to start of (new) last row
b8a2: 85 49 sta zpCV
b8a4: a9 00 lda #$00
b8a6: 85 48 sta zpCH
b8a8: 20 f4 b4 jsr CalcCursorTilemapPos ;recalc cursor VRAM addr
b8ab: a9 0d lda #NC_InsertChar ;insert new char (uh... new row, shouldn't we just
b8ad: 20 6a 89 jsr WaitForNmiSubcommand ; set it directly?)
b8b0: a2 16 ldx #$16 ;set X-reg to preceding row
b8b2: a9 ff lda #$ff
b8b4: 20 45 b6 jsr MarkWrappedRow ;mark that preceding row wrapped to this one
b8b7: 68 pla
b8b8: 85 49 sta zpCV ;restore cursor position (but up one row, bc of scroll)
b8ba: 68 pla
b8bb: 85 48 sta zpCH
b8bd: 60 rts
jtbl_CtrlCharHandlers
b8be: b5 b5 .dd2 ControlCharNOOP
b8c0: 09 b7 .dd2 ToggleInsertOrOverwriteMode ;Control-A
b8c2: b5 b5 .dd2 ControlCharNOOP
b8c4: b8 b5 .dd2 CursorToNextLineStart ;STOP (Control-C)
b8c6: 68 b6 .dd2 ResetGraphicSettings ;Control-D
b8c8: 8f b6 .dd2 ClearToEndOfLine ;Control-E
b8ca: b5 b5 .dd2 ControlCharNOOP
b8cc: d0 bc .dd2 CmdFn_BEEP ;Control-G
b8ce: 6f b7 .dd2 Backspace ;BACKSPACE (Control-H)
b8d0: df b6 .dd2 HandleTabKey ;TAB (Control-I)
b8d2: 51 b7 .dd2 LineFeed ;LF (Control-J)
b8d4: 48 b7 .dd2 CursorHome ;HOME (Control-K)
b8d6: ab b4 .dd2 DoCLS ;CLR/Shift-HOME (Control-L)
b8d8: 4c b7 .dd2 CarriageReturn ;CR (Control-M)
b8da: b5 b5 .dd2 ControlCharNOOP
b8dc: b5 b5 .dd2 ControlCharNOOP
b8de: b5 b5 .dd2 ControlCharNOOP
b8e0: b5 b5 .dd2 ControlCharNOOP
b8e2: 05 b8 .dd2 InsertSpaceChar ;INSERT (Control-R)
b8e4: b5 b5 .dd2 ControlCharNOOP
b8e6: b5 b5 .dd2 ControlCharNOOP
b8e8: b5 b5 .dd2 ControlCharNOOP
b8ea: 5e b7 .dd2 ActivateKanaMode ;Control-V
b8ec: 66 b7 .dd2 DeactivateKanaMode ;Control-W
b8ee: b5 b5 .dd2 ControlCharNOOP
b8f0: b5 b5 .dd2 ControlCharNOOP
b8f2: b1 b6 .dd2 ClearToEndOfScreen ;Control-Z
b8f4: b5 b5 .dd2 ControlCharNOOP
b8f6: 33 b7 .dd2 CursorRight ;Control-\
b8f8: 21 b7 .dd2 CursorLeft ;Control-]
b8fa: 10 b7 .dd2 CursorUp ;Control-^
b8fc: 17 b7 .dd2 CursorDown ;Control-_
• Clear variables
b8fe: a9 00 Rkim_returnNull lda #$00
b900: 60 rts
b901: 85 6d Rkim_maybeFkey sta zpKeyWhichKey ;save char code that is > #$F0. Might be fn key, might be graphical tile.
b903: 20 99 ba jsr GetFnKey ;if it's a function key, find out which.
b906: b0 05 bcs Rkim_handleFkey ;fn key? -> set up reads from macro definition
b908: a5 6d lda zpKeyWhichKey ;not a fn key, so return the char code as-is
b90a: 29 ff and #$ff
b90c: 60 rts
b90d: a5 6d Rkim_handleFkey lda zpKeyWhichKey
b90f: a6 68 ldx zpFnKeyHeld ;has this fn key already been processed, and just not released yet?
b911: d0 eb bne Rkim_returnNull ;yes -> return null char
b913: 85 6a sta zpFnKeyCode ;save the char code as the fn key that was pressed
b915: a9 04 lda #$04
b917: 85 74 sta zpCursFlashCtr ;set a counter to 4
b919: 20 58 b9 @lp4 jsr ReadKeyboard ;scan keyboard again
b91c: c5 6a cmp zpFnKeyCode ;is it still the same function key code?
b91e: d0 25 bne ReadKeyInsMacros ;no -> restart our whole key-reading thing
b920: c6 74 dec zpCursFlashCtr
b922: d0 f5 bne @lp4 ;loop until the function key has been scanned 4 times since when it was first detected
b924: 38 sec
b925: e9 f0 sbc #$f0 ;subtract the #$f0, leaving 0-7
b927: 20 53 ad jsr MulABy16 ;multiply by 16 to get offset into fnkey defs
b92a: 85 67 sta zpFnKeyDfnOff ;store that offset
b92c: a9 ff lda #$ff
b92e: 85 66 sta zpRkimMidMacro ;indicate that a not-already-handled fkey was typed
Rkim_typeFromMacro
b930: a6 67 ldx zpFnKeyDfnOff
b932: bd 80 04 lda fnKey1_DfnBuf,x ;is the fkey definition exhausted?
b935: f0 06 beq Rkim_cancelFkeyAndReread ;yes -> cancel fkey and re-scan for keypress
b937: e8 inx ;else, point offset at next char in macro dfn
b938: 86 67 stx zpFnKeyDfnOff
b93a: 29 ff and #$ff ;and return this current char from it
b93c: 60 rts
Rkim_cancelFkeyAndReread
b93d: a9 00 lda #$00
b93f: 85 66 sta zpRkimMidMacro
b941: a2 ff ldx #$ff
b943: 86 68 stx zpFnKeyHeld
; Read a keyboard character and return it. If a function key is typed, insert
; its macro definition one character at a time on successive calls.
ReadKeyInsMacros
b945: a5 66 lda zpRkimMidMacro ;are we typing from an fkey macro dfn?
b947: d0 e7 bne Rkim_typeFromMacro ;yes -> type next char from that
b949: 20 58 b9 jsr ReadKeyboard ;read a char from keyboard
b94c: f0 07 beq @nullChar ;null? -> finish out
b94e: c9 f0 cmp #$f0 ;possible fkey?
b950: b0 af bcs Rkim_maybeFkey ;yes -> investigate further
b952: 29 ff and #$ff ;else return the kbd char
b954: 60 rts
b955: 85 68 @nullChar sta zpFnKeyHeld ;Mark that no fkey is currently pressed
b957: 60 rts
; Kbd reading routine.
; See https://www.nesdev.org/wiki/Family_BASIC_Keyboard#Hardware_interface for
; details on how keyboard reading works.
b958: a9 05 ReadKeyboard lda #$05
b95a: 8d 16 40 sta JOY1 ;Write $05 to keyboard: reset row/col scanning
b95d: ea nop
b95e: ea nop
b95f: ea nop
b960: ea nop
b961: ea nop
b962: ea nop
b963: a2 00 ldx #$00
b965: a9 04 @kbdLp lda #$04
b967: 8d 16 40 sta JOY1 ;Write $04: prep read for column 0 of a new row
b96a: a0 0a ldy #$0a
b96c: 88 @dlyLp dey
b96d: d0 fd bne @dlyLp
b96f: ea nop
b970: ea nop
; bit: 7 0
; Read col 0 scan results: ---x xxx-
;
; row 0: ] [ RET F8
; 1: ; : @ F7
; 2: K L O F6
; 3: J U I F5
; 4: H G Y F4
; 5: D R T F3
; 6: A S W F2
; 7: CTR Q ESC F1
; 8: LFT RGT UP CLR
;
b971: ad 17 40 lda JOY2
b974: 4a lsr A ;Move to occupy the low nybble
b975: 29 0f and #$0f
b977: 9d 34 06 sta kbdDataAry,x ;...and store in KbdData + <<ROW>>
b97a: a9 06 lda #$06
b97c: 8d 16 40 sta JOY1 ;Write $06: prep read for column 1, same row
b97f: a0 0a ldy #$0a
b981: 88 @dlyLp dey
b982: d0 fd bne @dlyLp
b984: ea nop
b985: ea nop
;
; bit: 7 0
; Read col 1 scan results: ---x xxx-
;
; row 0: STP \ RSH KANA
; 1: ^ - / _
; 2: 0 P , .
; 3: 8 9 N M
; 4: 6 7 V B
; 5: 4 5 C F
; 6: 3 E Z X
; 7: 2 1 GRP LSH
; 8: INS DEL SPC DWN
;
b986: ad 17 40 lda JOY2
b989: 2a rol A ;Move to occupy the high nybble
b98a: 2a rol A
b98b: 2a rol A
b98c: 29 f0 and #$f0
b98e: 1d 34 06 ora kbdDataAry,x ;Combine with low nybble (cols 0 and 1 now combined)
b991: 49 ff eor #$ff ;Invert the bits, so that 1 = pressed
b993: a0 08 ldy #$08 ;And then store them in reversed order
b995: 0a @rvsLp asl A
b996: 7e 34 06 ror kbdDataAry,x
b999: 88 dey
b99a: d0 f9 bne @rvsLp
b99c: e8 inx
b99d: e0 09 cpx #$09
b99f: d0 c4 bne @kbdLp ;Loop around to handle the next keyboard row
; We now have in kbdDataAry:
;
; bit 7 6 5 4 3 2 1 0
;
; kbdDataAry
; + 0: F8 RET [ ] KANA RSH \ STP
; + 1: F7 @ : ; _ / - ^
; + 2: F6 O L K . , P 0
; + 3: F5 I U J M N 9 8
; + 4: F4 Y G H B V 7 6
; + 5: F3 T R D F C 5 4
; + 6: F2 W S A X Z E 3
; + 7: F1 ESC Q CTR LSH GRP 1 2
; + 8: CLR UP RGT LFT DWN SPC DEL INS
b9a1: a9 00 lda #$00
b9a3: 85 6f sta zpCtrlKeyDn
b9a5: 85 70 sta zpShiftKeyDn
b9a7: 85 71 sta zpGrphKeyDn
b9a9: ad 3b 06 lda kbdDataAry+7
b9ac: 29 10 and #$10 ;is control key down?
b9ae: d0 2b bne RdKey_handleControl
b9b0: ad 3b 06 lda kbdDataAry+7
b9b3: 29 08 and #$08 ;is left-shift down?
b9b5: d0 44 bne RdKey_handleLShift
b9b7: ad 34 06 lda kbdDataAry
b9ba: 29 04 and #$04 ;is right-shift down?
b9bc: d0 45 bne RdKey_handleRShift
b9be: ad 3b 06 lda kbdDataAry+7
b9c1: 29 04 and #$04 ;is GRPH key down?
b9c3: d0 4f bne RdKey_handleGrph
b9c5: 20 e9 b9 jsr RdKey_keyFindRecMap ;if no modifiers, just map the key to a character
b9c8: d0 03 bne @nonNull ;non-null char? ->
b9ca: 85 18 sta zpModifierKeyDn ;null char. Set "special key pressed" flag to zero
b9cc: 60 rts
b9cd: a4 18 @nonNull ldy zpModifierKeyDn ;if modifier or kana key is down, return null
b9cf: d0 07 bne @retNull
b9d1: a4 50 ldy zpKanaHeld
b9d3: d0 03 bne @retNull
b9d5: 29 ff and #$ff ;else, return mapped character code
b9d7: 60 rts
b9d8: a9 00 @retNull lda #$00
b9da: 60 rts
RdKey_handleControl
b9db: a9 ff lda #$ff ;set control-key, modifier-key flags
b9dd: 85 6f sta zpCtrlKeyDn
b9df: 85 18 sta zpModifierKeyDn
RdKey_maskModsFindRecMap
b9e1: ad 3b 06 lda kbdDataAry+7
b9e4: 29 e3 and #$e3 ;mask out left-shift, control, graph keys
b9e6: 8d 3b 06 sta kbdDataAry+7
; Find the pressed non-modifier key, record it, and map it, modifying it as
; necessary by any active modifier keys. The mapped character will be returned.
; If multiple non-modifier keys are pressed simultaneously, only the first found
; is reported.
RdKey_keyFindRecMap
b9e9: a2 00 ldx #$00
b9eb: a0 09 ldy #$09
b9ed: bd 34 06 @keyLp lda kbdDataAry,x ;search for other keys down
b9f0: d0 2b bne RdKey_keyRecMap ;key? -> record and modify
b9f2: e8 inx
b9f3: 88 dey
b9f4: d0 f7 bne @keyLp ;no keys this row? check next
b9f6: 85 6d sta zpKeyWhichKey ;If we get here, no non-modifier keys were down.
b9f8: 85 50 sta zpKanaHeld
b9fa: 60 rts
RdKey_handleLShift
b9fb: a9 ff lda #$ff ;set shift-key, modifier-key flags
b9fd: 85 18 sta zpModifierKeyDn
b9ff: 85 70 sta zpShiftKeyDn
ba01: d0 de bne RdKey_maskModsFindRecMap
RdKey_handleRShift
ba03: a9 ff lda #$ff ;set shift-key, modifier-key flags
ba05: 85 18 sta zpModifierKeyDn
ba07: 85 70 sta zpShiftKeyDn
ba09: ad 34 06 lda kbdDataAry
ba0c: 29 fb and #$fb ;mask out right-shift (why is this only done here, and not for other modifiers?)
ba0e: 8d 34 06 sta kbdDataAry
ba11: 4c e1 b9 jmp RdKey_maskModsFindRecMap
RdKey_handleGrph
ba14: a9 ff lda #$ff ;set modifier-key, special-key flags
ba16: 85 71 sta zpGrphKeyDn
ba18: 85 18 sta zpModifierKeyDn
ba1a: 4c e1 b9 jmp RdKey_maskModsFindRecMap
ba1d: 86 6e RdKey_keyRecMap stx zpKeyWhichRow ;save which key matrix row we found a key down
ba1f: a0 00 ldy #$00
ba21: 18 clc
ba22: 4a @findKey lsr A ;loop over bits in row until we find which key
ba23: b0 04 bcs RdKey_mapKey ;found -> translate and modify it
ba25: c8 iny
ba26: 4c 22 ba jmp @findKey
ba29: 8a RdKey_mapKey txa
ba2a: 0a asl A ;multiply row by 8
ba2b: 0a asl A
ba2c: 0a asl A
ba2d: 84 6d sty zpKeyWhichKey ;save which bit,
ba2f: 18 clc
ba30: 65 6d adc zpKeyWhichKey ; and add that (0-7) to A
ba32: aa tax ;X now holds a keycode
ba33: a5 6f lda zpCtrlKeyDn ;is control being pressed?
ba35: d0 22 bne RdKey_mapCtrlChar ;yes -> modify
ba37: a5 70 lda zpShiftKeyDn ;else is shift being pressed?
ba39: d0 34 bne RdKey_mapShiftedChar ;yes -> modify
ba3b: a5 71 lda zpGrphKeyDn ;else is GRPH key down?
ba3d: d0 38 bne RdKey_mapGrphModChar ;yes -> modify
ba3f: a5 72 lda zpKanaFlag ;else is kana mode active?
ba41: d0 3d bne RdKey_mapKanaChar ;yes -> map
ba43: bd a9 ba lda tbl_KeyMap,x ;else map normally
ba46: c9 ff cmp #$ff ;was KANA key pressed?
ba48: f0 03 beq RdKey_maybeActivateKana ;yes -> check if we should handle
ba4a: 29 ff and #$ff ;return mapped char
ba4c: 60 rts
RdKey_maybeActivateKana
ba4d: c5 50 cmp zpKanaHeld ;did we already handle kana key being pressed?
ba4f: f0 05 beq RdKey_returnNull ;yes -> return null
ba51: 85 50 sta zpKanaHeld ;no -> indicate we've handled it
ba53: 20 5e b7 jsr ActivateKanaMode ;activate KANA mode
RdKey_returnNull
ba56: a9 00 lda #$00 ;then return null
ba58: 60 rts
RdKey_mapCtrlChar
ba59: bd a9 ba lda tbl_KeyMap,x ;translate key code to char code (~ASCII)
ba5c: c9 20 cmp #$20 ;is the looked-up key already a control?
ba5e: b0 03 bcs @notCtrlYet ;no -> modify
ba60: 29 ff and #$ff ;yes -> return it unmodified
ba62: 60 rts
ba63: 38 @notCtrlYet sec
ba64: e9 40 sbc #$40 ;subtract 'A'-1
ba66: 90 ee bcc RdKey_returnNull ;underflow? -> return null
ba68: c9 20 cmp #$20 ;still outside range of control chars?
ba6a: b0 ea bcs RdKey_returnNull ;yes -> return null
RdKey_returnAsIs
ba6c: 29 ff and #$ff ;else: return the control char
ba6e: 60 rts
RdKey_mapShiftedChar
ba6f: a5 72 lda zpKanaFlag ;is kana flag on?
ba71: d0 13 bne RdKey_mapShiftedKanaChar ;yes -> handle shifted kana
ba73: bd f1 ba lda tbl_ShiftedKeyMap,x ;else return shifted non-kana key
ba76: 60 rts
RdKey_mapGrphModChar
ba77: a5 72 lda zpKanaFlag ;is kana mode active?
ba79: d0 01 bne @grphOk ;yes -> map grph char
ba7b: 60 rts ;no -> return null (graph means nothing outside kana mode)
ba7c: bd c9 bb @grphOk lda tbl_GrphKeyMap,x
ba7f: 60 rts
RdKey_mapKanaChar
ba80: bd 39 bb lda tbl_KanaKeyMap,x
ba83: 4c 89 ba jmp @kana
RdKey_mapShiftedKanaChar
ba86: bd 81 bb lda tbl_ShiftedKanaKeyMap,x
ba89: c9 ff @kana cmp #$ff ;was KANA key pressed while in KANA mode?
ba8b: d0 df bne RdKey_returnAsIs ;no -> return the mapped char
ba8d: c5 50 cmp zpKanaHeld ;did we already handle this same KANA press?
ba8f: f0 c5 beq RdKey_returnNull ;yes -> return null
ba91: 85 50 sta zpKanaHeld ;no: mark that we've proceesed it
ba93: 20 66 b7 jsr DeactivateKanaMode ;and deactivate kana mode
ba96: a9 00 lda #$00 ;then return null
ba98: 60 rts_BA98 rts
; Scan through the captured keyboard matrix (kbdDataAry) for function
; keypresses.
; If found, carry will be set, and the X register will contain (8 - fnKey). If
; not found, carry will be unset.
;
; Example: if the pressed function key is F8, X will be 8 - 8 = 0. If it is F1,
; X will be 8 - 1 = 7.
ba99: a0 08 GetFnKey ldy #$08
ba9b: a2 00 ldx #$00
ba9d: bd 34 06 @fkyLp lda kbdDataAry,x
baa0: 0a asl A
baa1: b0 f5 bcs rts_BA98
baa3: e8 inx
baa4: 88 dey
baa5: d0 f6 bne @fkyLp
baa7: 18 clc
baa8: 60 rts
; Keymap of non-modifier keys.
;
; The four keymaps below are used to decode from a key code to a character code.
; tbl_KeyMap is a map of unmodified character codes (note that all the modifier
; keys in it are mapped to #$00); and the keymaps after that are used in various
; situations when the shift key or GRPH key are down, or when kana mode is
; activated.
;
; The keycode is represented as 8 * BYTE_NUM + BIT_NUM where the key was found,
; in kbdDataAry. BYTE_NUM is stored in zpKeyWhichRow ($6E), and BIT_NUM in
; zpKeyWhichKey ($6D).
;
; Keycode values:
;
; +1 +2 +3 +4 +5 +6 +7
; 0: STP \ RSH KANA ] [ RET F8
; 8: ^ - / _ ; : @ F7
; 16: 0 P , . K L O F6
; 24: 8 9 N M J U I F5
; 32: 6 7 V B H G Y F4
; 40: 4 5 C F D R T F3
; 48: 3 E Z X A S W F2
; 56: 2 1 GRP LSH CTR Q ESC F1.
; 64: INS DEL SPC DWN LFT RGT UP CLR
;
; NOTE: Function keys are translated to $F0-$F7... but! other keys _also_ can
; translate to those values (especially from the GRPH key). Therefore, when the
; character code is at or above $F0, one must examine the keycode or key matrix
; bitmap to distinguish whether it was graphical characters, or a function key.
; (Note: although KANA key maps to $FF, it is never returned from ReadKeyboard;
; it is handled internally, and then the null character is returned.)
baa9: 03 5c 00 ff+ tbl_KeyMap .bulk $03,$5c,$00,$ff,$b3,$b2,$0d,$f7
bab1: 5e 2d 2f 00+ .bulk $5e,$2d,$2f,$00,$3b,$3a,$40,$f6
bab9: 30 50 2c 2e+ .bulk $30,$50,$2c,$2e,$4b,$4c,$4f,$f5
bac1: 38 39 4e 4d+ .bulk $38,$39,$4e,$4d,$4a,$55,$49,$f4
bac9: 36 37 56 42+ .bulk $36,$37,$56,$42,$48,$47,$59,$f3
bad1: 34 35 43 46+ .bulk $34,$35,$43,$46,$44,$52,$54,$f2
bad9: 33 45 5a 58+ .bulk $33,$45,$5a,$58,$41,$53,$57,$f1
bae1: 32 31 00 00+ .bulk $32,$31,$00,$00,$00,$51,$1b,$f0
bae9: 12 08 20 1f+ .bulk $12,$08,$20,$1f,$1d,$1c,$1e,$0b
; Keymap for shift + key.
; Since Family BASIC does not support lowercase, this deos not insert anything
; when a letter key is typed (hence all the zero values).
tbl_ShiftedKeyMap
baf1: 03 00 00 00+ .bulk $03,$00,$00,$00,$00,$00,$0d,$18
baf9: 00 3d 3f 5f+ .bulk $00,$3d,$3f,$5f,$2b,$2a,$00,$17
bb01: 00 00 3c 3e+ .bulk $00,$00,$3c,$3e,$00,$00,$00,$11
bb09: 28 29 00 00+ .bulk $28,$29,$00,$00,$00,$00,$00,$10
bb11: 26 27 00 00+ .bulk $26,$27,$00,$00,$00,$00,$00,$0f
bb19: 24 25 00 00+ .bulk $24,$25,$00,$00,$00,$00,$00,$0e
bb21: 23 00 00 00+ .bulk $23,$00,$00,$00,$00,$00,$00,$06
bb29: 22 21 00 00+ .bulk $22,$21,$00,$00,$00,$00,$1b,$02
bb31: 12 08 20 1f+ .bulk $12,$08,$20,$1f,$1d,$1c,$1e,$0c
bb39: 03 88 00 ff+ tbl_KanaKeyMap .bulk $03,$88,$00,$ff,$b1,$8a,$0d,$f7
bb41: 87 86 8d 8c+ .bulk $87,$86,$8d,$8c,$82,$2d,$89,$f6
bb49: 78 7d 85 8b+ .bulk $78,$7d,$85,$8b,$80,$81,$7c,$f5
bb51: 76 77 83 84+ .bulk $76,$77,$83,$84,$7f,$7a,$7b,$f4
bb59: 74 75 72 73+ .bulk $74,$75,$72,$73,$7e,$6e,$79,$f3
bb61: 63 64 71 6d+ .bulk $63,$64,$71,$6d,$6c,$68,$69,$f2
bb69: 62 67 6f 70+ .bulk $62,$67,$6f,$70,$6a,$6b,$66,$f1
bb71: 61 60 00 00+ .bulk $61,$60,$00,$00,$00,$65,$1b,$f0
bb79: 12 08 20 1f+ .bulk $12,$08,$20,$1f,$1d,$1c,$1e,$0b
tbl_ShiftedKanaKeyMap
bb81: 03 00 00 ff+ .bulk $03,$00,$00,$ff,$5d,$5b,$0d,$f7
bb89: 00 00 00 00+ dmFakeNextLine .bulk $00,$00,$00,$00,$00,$00,$00,$f6 ;<- this location used as a fake "next line"
bb91: 00 af 95 00+ .bulk $00,$af,$95,$00,$00,$00,$ae,$f5 ; when executing in direct mode
bb99: 00 00 93 94+ .bulk $00,$00,$93,$94,$00,$ac,$ad,$f4
bba1: 00 00 00 00+ .bulk $00,$00,$00,$00,$00,$00,$ab,$f3
bba9: 91 92 96 00+ .bulk $91,$92,$96,$00,$00,$00,$00,$f2
bbb1: 90 00 00 00+ .bulk $90,$00,$00,$00,$00,$00,$00,$f1
bbb9: 8f 8e 00 00+ .bulk $8f,$8e,$00,$00,$00,$00,$1b,$f0
bbc1: 12 08 20 1f+ .bulk $12,$08,$20,$1f,$1d,$1c,$1e,$0c
; Only applies when kana mode is active and GRPH key is pressed
bbc9: 03 fe 00 00+ tbl_GrphKeyMap .bulk $03,$fe,$00,$00,$00,$00,$0d,$f7
bbd1: fd f2 00 00+ .bulk $fd,$f2,$00,$00,$00,$00,$00,$f6
bbd9: f1 aa 00 00+ .bulk $f1,$aa,$00,$00,$00,$00,$a9,$f5
bbe1: ef f0 00 00+ .bulk $ef,$f0,$00,$00,$00,$a7,$a8,$f4
bbe9: ed ee a4 a5+ .bulk $ed,$ee,$a4,$a5,$00,$a0,$a6,$f3
bbf1: eb ec a3 9f+ .bulk $eb,$ec,$a3,$9f,$9e,$9a,$9b,$f2
bbf9: ea 99 a1 a2+ .bulk $ea,$99,$a1,$a2,$9c,$9d,$98,$f1
bc01: e9 e8 00 00+ .bulk $e9,$e8,$00,$00,$00,$97,$1b,$f0
bc09: 12 08 20 1f+ .bulk $12,$08,$20,$1f,$1d,$1c,$1e,$0b
bc11: 20 f4 b4 ShowCursor jsr CalcCursorTilemapPos
bc14: a9 08 lda #NC_ShowCursor
bc16: d0 05 bne @waitNmi
bc18: 20 f4 b4 HideCursor jsr CalcCursorTilemapPos
bc1b: a9 09 lda #NC_RestoreCharAtCursor
bc1d: 4c 6a 89 @waitNmi jmp WaitForNmiSubcommand
; Read a character from the keyboard.
;
; Flashes the cursor when nothing is being pressed, and handles key-repeat
; timings.
; Since it uses ReadKeyInsMacro, it will also honor F-keys.
bc20: a9 00 ReadChar lda #$00
bc22: 85 5e sta zpUnusedStopFlg ;clear the STOP flag that is never set to anything else
bc24: 20 45 b9 jsr ReadKeyInsMacros ;read keyboard
bc27: f0 46 beq RC_awaitChar ;no key? -> await with cursor flash
bc29: a6 66 ldx zpRkimMidMacro ;else, are we in an fkey macro read?
bc2b: d0 25 bne RdChr_RetChar ;yes -> set and return key from that
bc2d: c5 75 cmp zpHeldKeyChar ;else, is the key we got the same one we got last read?
bc2f: d0 3e bne RC_awaitChar ;no -> new char, handle via await
bc31: a5 7b lda zpKeyRepeating ;has this key reached the rapid-repeat phase?
bc33: d0 20 bne RC_shortRepeatingKey ;yes -> skip the longer first wait for repeat
bc35: 20 11 bc jsr ShowCursor ;show the cursor while we wait for keypress to exceed repeat timer
bc38: 20 45 b9 @frstKeyHoldLp jsr ReadKeyInsMacros ;read key character
bc3b: f0 2f beq RC_hideCursAwaitChar ;no key? -> restart await, with flashing cursor
bc3d: c5 75 cmp zpHeldKeyChar ;is it the held key?
bc3f: d0 2b bne RC_hideCursAwaitChar ;no -> new cbar, handle via await
bc41: c6 77 dec zpKyRptLongCtr ;decrement long-wait keypress timer
bc43: d0 f3 bne @frstKeyHoldLp ;loop until timer expires
bc45: c6 78 dec zpKyNotRpt ;<- this is only ever #01
bc47: d0 ef bne @frstKeyHoldLp ;<- so this never rebranches
bc49: a9 ff lda #$ff
bc4b: 85 7b sta zpKeyRepeating ;we reached first repeat, so indicate that
bc4d: 4c c3 bc jmp RC_clickAndReturnChar ;and click, return repeated char
RdChr_RetStopChar
bc50: a9 03 lda #$03
bc52: 85 75 RdChr_RetChar sta zpHeldKeyChar
bc54: 60 rts
RC_shortRepeatingKey
bc55: 20 45 b9 jsr ReadKeyInsMacros
bc58: f0 15 beq RC_awaitChar
bc5a: c5 75 cmp zpHeldKeyChar
bc5c: d0 11 bne RC_awaitChar
bc5e: c6 79 dec zpKyRptShtCtr
bc60: d0 f3 bne RC_shortRepeatingKey
bc62: a9 20 lda #$20
bc64: 85 79 sta zpKyRptShtCtr
bc66: 20 11 bc jsr ShowCursor ;show cursor for a frame right before hiding again
bc69: 4c c3 bc jmp RC_clickAndReturnChar
RC_hideCursAwaitChar
bc6c: 20 18 bc jsr HideCursor ;not really sure what one frame of hidden cursor accomplishes
; Await keypress (may have already got one, but point is key-repeating is
; reset).
; Flashes cursor while waiting.
bc6f: a9 00 RC_awaitChar lda #$00 ;reset repeat-key settings
bc71: 85 7b sta zpKeyRepeating
bc73: 85 77 sta zpKyRptLongCtr
bc75: a9 20 lda #$20
bc77: 85 79 sta zpKyRptShtCtr
bc79: a9 01 lda #$01
bc7b: 85 78 sta zpKyNotRpt
bc7d: 20 11 bc jsr ShowCursor ;show the cursor at current tile position
bc80: a9 80 lda #CURS_FLASH_TM
bc82: 85 74 sta zpCursFlashCtr ;set a count-down for when we should hide the cursor
bc84: 20 45 b9 @showLp jsr ReadKeyInsMacros ;Read a key
bc87: d0 25 bne RC_recheckThenClickAndReturnChar ;got one? -> recheck, click, and return
bc89: c6 74 dec zpCursFlashCtr
bc8b: d0 f7 bne @showLp ;loop around Rkim until we get a key, or timer expires
bc8d: 20 18 bc jsr HideCursor ;timer expired; hide the cursor
bc90: a5 5e lda zpUnusedStopFlg ;check for STOP flag (no one ever sets)
bc92: d0 bc bne RdChr_RetStopChar ;and return STOP char if set (never)
bc94: a9 80 lda #CURS_FLASH_TM
bc96: 85 74 sta zpCursFlashCtr ;reset count-down for when we should _show_ cursor again
bc98: 20 45 b9 @hideLp jsr ReadKeyInsMacros ;Read a key
bc9b: d0 11 bne RC_recheckThenClickAndReturnChar ;got one? -> recheck, click, and return
bc9d: c6 74 dec zpCursFlashCtr
bc9f: d0 f7 bne @hideLp ;loop around Rkim until we get a key, or timer expires
bca1: a5 5e lda zpUnusedStopFlg ;check for STOP flag (no one ever sets)
bca3: d0 ab bne RdChr_RetStopChar ;and return STOP char if set (never)
bca5: 4c 6f bc jmp RC_awaitChar ;timer expired -> loop around to show-cursor side of routine
RC_hideCursAwaitChar1
bca8: 20 18 bc jsr HideCursor ;not really sure what one frame of hidden cursor accomplishes
bcab: 4c 6f bc jmp RC_awaitChar ;also $BC6C does this same thing already
RC_recheckThenClickAndReturnChar
bcae: 85 75 sta zpHeldKeyChar ;set the new char as the "held" char, in case it's held for repeats
bcb0: a5 66 lda zpRkimMidMacro ;was a function-key macro invoked?
bcb2: d0 0f bne RC_clickAndReturnChar ;yes -> skip re-check, we'd get a different char!
bcb4: a9 01 lda #$01
bcb6: 85 74 sta zpCursFlashCtr
bcb8: 20 45 b9 @nonLp jsr ReadKeyInsMacros ;check keyboard again
bcbb: c5 75 cmp zpHeldKeyChar ;was same key still held?
bcbd: d0 e9 bne RC_hideCursAwaitChar1 ;no-> start keypress check over again
bcbf: c6 74 dec zpCursFlashCtr
bcc1: d0 f5 bne @nonLp ;we set $74 to #$01 before the first dec, so this never loops
RC_clickAndReturnChar
bcc3: 20 18 bc jsr HideCursor
bcc6: a5 e3 lda zpKeyClickFlg ;is keyclick on?
bcc8: f0 03 beq @skpClick ;no -> skip click
bcca: 20 d5 bc jsr KeyClick ;make the key-click sound
bccd: a5 75 @skpClick lda zpHeldKeyChar ;and return the key we got
bccf: 60 rts
bcd0: a9 f0 CmdFn_BEEP lda #$f0
bcd2: 85 19 sta zpMisc ;set beep length short
bcd4: 2c bit ▼ $01a9 ;(bypass next instr)
bcd5: a9 01 KeyClick lda #$01 ;set beep length itty-bitty, teenie-weenie
bcd7: 85 19 sta zpMisc
bcd9: a9 01 lda #$01
bcdb: 8d 15 40 sta SND_CHN ;enable pulse channel 1
bcde: a9 c0 lda #$c0
bce0: 8d 17 40 sta JOY2 ;sync APU frame counter
bce3: a9 ef lda #$ef
bce5: 8d 00 40 sta SQ1_VOL ;set volume fairly high
bce8: a9 00 lda #$00
bcea: 8d 01 40 sta SQ1_SWEEP ;no sweep
bced: a9 5f lda #$5f
bcef: 8d 02 40 sta SQ1_LO ;set pitch (E5?)
bcf2: a0 00 ldy #$00
bcf4: 8c 03 40 sty SQ1_HI
bcf7: 88 @waitLp dey
bcf8: d0 fd bne @waitLp ;delay loop, 256 iterations
bcfa: c6 19 dec zpMisc ;beep length: was set at top of fn
bcfc: d0 f9 bne @waitLp ;not done playing? -> loop another 256
bcfe: a9 00 lda #$00
bd00: 8d 15 40 sta SND_CHN ;done: disable the sound channel
bd03: 60 rts ;and return
; Read a line of user input into lineBuffer
bd04: a9 00 ReadLine lda #$00
bd06: 85 76 sta zpLineBufIdx ;set character index to start
bd08: 85 63 sta zpNMICMD ;deactivate any active NMI commands
bd0a: 20 20 bc @readLineChars jsr ReadChar
bd0d: c9 0d cmp #$0d
bd0f: f0 1e beq RL_handleCR
bd11: c9 03 cmp #$03
bd13: f0 0a beq RL_handleStopOrCtrlD
bd15: c9 04 cmp #$04
bd17: f0 06 beq RL_handleStopOrCtrlD
bd19: 20 75 b5 jsr PrintOneChar
bd1c: 4c 0a bd jmp @readLineChars ;loop and read more chars
RL_handleStopOrCtrlD
bd1f: 20 75 b5 jsr PrintOneChar ;process the control char
bd22: a6 76 ldx zpLineBufIdx
bd24: a9 00 lda #$00 ;abort line-reading
bd26: 9d 01 05 sta lineBuffer+1,x
bd29: a9 03 lda #$03
bd2b: 9d 00 05 sta lineBuffer,x
bd2e: 60 rts
; Handle CR character from input
; First: back up rows until we're not on a wrapped one (that is, we're at the
; first row of a possible multi-row line)
bd2f: a5 49 RL_handleCR lda zpCV ;get current row
bd31: f0 0c beq @lineStart ;Exit loop if we're on the topmost line
bd33: aa tax
bd34: ca dex ;check preceding row
bd35: 20 37 b6 jsr DoesRowWrap ;does that row wrap to this?
bd38: f0 05 beq @lineStart ;no -> we found our line start
bd3a: c6 49 dec zpCV ;yes; go to that row and re-check
bd3c: 4c 2f bd jmp RL_handleCR ;Row is wrapped: loop back to preceding row
bd3f: a5 49 @lineStart lda zpCV
bd41: 85 47 sta zpLineStartRow ;set the line row start
bd43: a9 02 lda #NC_BufferCurLine
bd45: 20 6a 89 jsr WaitForNmiSubcommand ;Fetch the on-screen, possibly-wrapped line from PPU into lineBuffer
bd48: a5 64 lda zpOutTxtStartPos ;<- NMICMD_BufferCurLine always sets before deactivating
bd4a: d0 0a bne @skpTrm ;<- so this always branches ->
bd4c: a6 76 ldx zpLineBufIdx
bd4e: ca dex
bd4f: a9 00 lda #$00
bd51: 9d 00 05 sta lineBuffer,x
bd54: 86 76 stx zpLineBufIdx
bd56: a6 76 @skpTrm ldx zpLineBufIdx
bd58: ca @stripLp dex ;strip trailing spaces
bd59: 8a txa
bd5a: f0 07 beq @brk
bd5c: bd 00 05 lda lineBuffer,x
bd5f: c9 20 cmp #$20
bd61: f0 f5 beq @stripLp
bd63: e8 @brk inx
bd64: a9 00 lda #$00 ;set null terminator where we found the first trailing space
bd66: 9d 00 05 sta lineBuffer,x
bd69: 4c b8 b5 jmp CursorToNextLineStart ;do the actual CRLF
bd6c: a6 49 INPUT_readLine ldx zpCV ;get curent row
bd6e: f0 06 beq @topRow ;top row? -> skip next bit
bd70: ca dex
bd71: a9 00 lda #$00
bd73: 20 45 b6 jsr MarkWrappedRow ;else, disconnect this row from the preceding one (if it was wrapped, make it... not)
bd76: a9 00 @topRow lda #$00
bd78: 85 76 sta zpLineBufIdx ;reset line bufer
bd7a: 85 63 sta zpNMICMD ;deactivate any active NMI command
bd7c: 20 20 bc @rdLp jsr ReadChar ;read a character
bd7f: c9 0d cmp #$0d ;is it carriage-return (Enter)?
bd81: f0 0e beq INPUT_RL_handleCR ;yes -> finish up
bd83: c9 03 cmp #$03
bd85: f0 98 beq RL_handleStopOrCtrlD
bd87: c9 04 cmp #$04 ;is it STOP, or Ctrl-D?
bd89: f0 94 beq RL_handleStopOrCtrlD ;yes -> handle it
bd8b: 20 75 b5 jsr PrintOneChar ;otherwise print the char to screen and advance
bd8e: 4c 7c bd jmp @rdLp ;-> loop back to read next character
; Does basically same thing as RL_handleCR, not sure why we need another sub
; to do the same thing. Slurps the user-typed input into lineBuffer, and removes
; trailing spaces.
INPUT_RL_handleCR
bd91: a5 49 lda zpCV ;get current row #
bd93: f0 0c beq @recordInpLine ;first row of screen? doesn't wrap -> proceed
bd95: aa tax
bd96: ca dex
bd97: 20 37 b6 jsr DoesRowWrap ;else, does the preceding row wrap to this one?
bd9a: f0 05 beq @recordInpLine ;yes -> proceed
bd9c: c6 49 dec zpCV ;else go up one row
bd9e: 4c 91 bd jmp INPUT_RL_handleCR ;and repeat the check
bda1: a5 49 @recordInpLine lda zpCV ;we're at the starting row for the line input
bda3: 85 47 sta zpLineStartRow ;save it as such
bda5: a9 02 lda #NC_BufferCurLine
bda7: 20 6a 89 jsr WaitForNmiSubcommand ;copy this line (this row and any it wraps to) into lineBuffer
bdaa: a5 64 lda zpOutTxtStartPos ;did NMICMD_BufferCurLine set $64 to non-zero? (always true)
bdac: d0 0a bne @remTrailSpc ;(always) -> remove trailing spaces
bdae: a6 76 ldx zpLineBufIdx ;(never): delete final buffered character
bdb0: ca dex
bdb1: a9 00 lda #$00
bdb3: 9d 00 05 sta lineBuffer,x
bdb6: 86 76 stx zpLineBufIdx
; remove trailing spaces from lineBuffer
bdb8: a6 76 @remTrailSpc ldx zpLineBufIdx ;work from end of lineBuffer, back toward its start
bdba: ca @remLp dex ;back up by one character
bdbb: 8a txa
bdbc: f0 07 beq @lpDone ;at start of line? -> done backing up
bdbe: bd 00 05 lda lineBuffer,x ;get the char
bdc1: c9 20 cmp #$20 ;is it a space?
bdc3: f0 f5 beq @remLp ;yes -> loop back around until we get non-space
bdc5: e8 @lpDone inx
bdc6: a9 00 lda #$00
bdc8: 9d 00 05 sta lineBuffer,x ;null-terminate at this spot (removing the spaces)
bdcb: 4c b8 b5 jmp CursorToNextLineStart ;-> move cursor to following line, and return
bdce: e6 ad IncrCasBufPtr inc zpCasBufPtr
bdd0: d0 02 bne rts_BDD4
bdd2: e6 ae inc zpCasBufPtr+1
bdd4: 60 rts_BDD4 rts
bdd5: e6 91 IncrOnesCounter inc zpCasCksum
bdd7: d0 fb bne rts_BDD4
bdd9: e6 92 inc zpCasCksum+1
bddb: 60 rts
; Z is set iff zpCasBufPtr has reached zpCasEndPtr, and we're done sending the
; buffer.
bddc: a5 ad IsCasDataDone lda zpCasBufPtr
bdde: c5 af cmp zpCasEndPtr
bde0: d0 f2 bne rts_BDD4
bde2: a5 ae lda zpCasBufPtr+1
bde4: c5 b0 cmp zpCasEndPtr+1
bde6: 60 rts
bde7: ad 14 05 CasBufSetData lda lineBuffer+20
bdea: 85 ad sta zpCasBufPtr
bdec: 18 clc
bded: 6d 12 05 adc lineBuffer+18
bdf0: 85 af sta zpCasEndPtr
bdf2: ad 15 05 lda lineBuffer+21
bdf5: 85 ae sta zpCasBufPtr+1
bdf7: 6d 13 05 adc lineBuffer+19
bdfa: 85 b0 sta zpCasEndPtr+1
bdfc: 60 rts
bdfd: a9 00 CasBufSetHeader lda #<lineBuffer
bdff: 85 ad sta zpCasBufPtr
be01: a9 05 lda #>lineBuffer
be03: 85 ae sta zpCasBufPtr+1
be05: a9 80 lda #<lineBuffer+128
be07: 85 af sta zpCasEndPtr
be09: a9 05 lda #>lineBuffer
be0b: 85 b0 sta zpCasEndPtr+1
be0d: 60 rts
; NOTE: Vblank NMI will be DISABLED, unless the high bit is set in zpPpuCtrlVal
be0e: a5 32 SetPpuCtrl lda zpPpuCtrlVal
be10: 8d 00 20 sta PPUCTRL
be13: 60 rts
SetPpuCtrlAndEnableNmi
be14: a5 32 lda zpPpuCtrlVal
be16: 09 80 ora #$80
be18: 8d 00 20 sta PPUCTRL
be1b: 60 rts
be1c: 20 0e be CasVrfyProgram jsr SetPpuCtrl ;disable NMI
be1f: a9 01 lda #$01
be21: 4c 29 be jmp CLP_vrfyEntry
; Load a file (whether program, or bg data) from cassette (after having
; processed the header)
• Clear variables
vZpLoopCtr .var $91 {addr/1}
be24: 20 0e be CasRcvFileData jsr SetPpuCtrl ;disable NMI
be27: a9 00 lda #$00
be29: 85 b1 CLP_vrfyEntry sta zpVerfFlag
be2b: a9 00 lda #$00
be2d: 85 92 sta zpCasCksum+1
be2f: 20 e7 bd jsr CasBufSetData ;set up cassette loading to go where header says
; Read a data announce/tape mark
; (skipping everything that isn't that)
;
; String of 20ones, first
be32: a9 14 CasRcvDataAnn lda #20
be34: 85 91 sta vZpLoopCtr
be36: 20 e3 be @lpTwentyOnes jsr CassetteRcvBit ;read a bit
be39: b0 0d bcs @errIfSTOPped ;STOP pressed? -> re-enable NMI, then TP error
be3b: d0 f5 bne CasRcvDataAnn ;zero bit? -> Not announce mark yet, check next
be3d: c6 91 dec vZpLoopCtr ;got a "one" bit, see if we get 20 total
be3f: d0 f5 bne @lpTwentyOnes ;not 20 yet? get another "one"
; String of 20 zeroes, next
be41: a9 14 lda #20
be43: 85 91 sta vZpLoopCtr ;read a bit
be45: 20 e3 be @lpTwentyZeroes jsr CassetteRcvBit ;STOP pressed? -> re-enable NMI, then TP error
be48: b0 71 @errIfSTOPped bcs casLdEnableNmiRetErr ;one bit? -> Not announce mark yet, check next
be4a: f0 e6 beq CasRcvDataAnn ;got a zero bit, see if we get 20 total
be4c: c6 91 dec vZpLoopCtr ;not 20 yet? get another zero
be4e: d0 f5 bne @lpTwentyZeroes
be50: 4c 7b be jmp CasRcvJustBeforeAnnEnd ;Got the announce. Go get the program data
be53: 20 0e be CasRcvHeader jsr SetPpuCtrl ;presumably to disable NMI
be56: a9 00 lda #$00
be58: 85 b1 sta zpVerfFlag
be5a: 20 fd bd jsr CasBufSetHeader ;set up for loading a header
; Read a header announce/tape mark
; (skipping everything that isn't that)
;
; String of 40 ones, first
be5d: a9 28 CasRcvHdrAnn lda #40
be5f: 85 91 sta vZpLoopCtr
be61: 20 e3 be @lpFortyOnes jsr CassetteRcvBit ;read a bit
be64: b0 55 bcs casLdEnableNmiRetErr ;STOP pressed? -> re-enable NMI, then TP error
be66: d0 f5 bne CasRcvHdrAnn ;zero bit? -> Not announce mark yet, check next
be68: c6 91 dec vZpLoopCtr ;got a "one" bit, see if we get 40 total
be6a: d0 f5 bne @lpFortyOnes ;not 40 yet? get another "one"
; String of 40 zeroes, next
be6c: a9 28 lda #40
be6e: 85 91 sta vZpLoopCtr
be70: 20 e3 be @lpFortyZeroes jsr CassetteRcvBit ;read a bit
be73: b0 46 bcs casLdEnableNmiRetErr ;STOP pressed? -> re-enable NMI, then TP error
be75: f0 e6 beq CasRcvHdrAnn ;one bit? -> Not announce mark yet, check next
be77: c6 91 dec vZpLoopCtr ;got a zero bit, see if we get 40 total
be79: d0 f5 bne @lpFortyZeroes ;not 40 yet? get another zero
; Header announce mark successfully read!
• Clear variables
CasRcvJustBeforeAnnEnd
be7b: a9 00 lda #$00
be7d: 85 91 sta zpCasCksum ;receive first (higher-order) byte for checksum
be7f: 85 92 sta zpCasCksum+1
be81: 20 c9 be jsr CasSkipNextHigh ;finish up the zero cycle
be84: 20 c9 be jsr CasSkipNextHigh ;and skip the following cycle (a final "one")
be87: 20 f7 be @rdHdrPayload jsr CassetteRcvByte ;we're on low; this will skip a start bit before read
be8a: b0 2f bcs casLdEnableNmiRetErr
be8c: a6 b1 ldx zpVerfFlag
be8e: d0 07 bne @verify
be90: a0 00 ldy #$00
be92: 91 ad sta (zpCasBufPtr),y
be94: 4c 9d be jmp @load
be97: a0 00 @verify ldy #$00 ;verify the byte matches, rather than load
be99: d1 ad cmp (zpCasBufPtr),y
be9b: d0 1e bne casLdEnableNmiRetErr ;not matched -> TP error
be9d: 20 ce bd @load jsr IncrCasBufPtr ;prep next byte save loc
bea0: 20 dc bd jsr IsCasDataDone ;did we reach end?
bea3: d0 e2 bne @rdHdrPayload ;no -> read next byte
bea5: a5 91 lda zpCasCksum
bea7: 85 2c sta zpWParam
bea9: a5 92 lda zpCasCksum+1
beab: 85 2d sta zpWParam+1
bead: 20 f7 be jsr CassetteRcvByte ;get first (higher-order) byte of checksum
beb0: c5 2d cmp zpWParam+1 ;does it match our high byte?
beb2: d0 07 bne casLdEnableNmiRetErr ;no -> TP error!
beb4: 20 f7 be jsr CassetteRcvByte ;get second (lower-order) byte of checksum
beb7: c5 2c cmp zpWParam ;does it match our low byte?
beb9: f0 07 beq @success ;yes -> re-enable NMI and return success
casLdEnableNmiRetErr
bebb: 20 14 be jsr SetPpuCtrlAndEnableNmi
bebe: a9 02 lda #$02
bec0: 38 rtnFalse_BEC0 sec
bec1: 60 rts
bec2: 20 14 be @success jsr SetPpuCtrlAndEnableNmi
bec5: a9 00 lda #$00
bec7: 18 clc
bec8: 60 rts
; If the incoming cassette signal is low, skip that.
; If the incoming cassete signal is high, skips THAT.
; Was going to call it "CasSkipOneCycle" - which it may do - but the way it's
; typically used, it will more often just skip a high signal, so the name would
; be misleading.
bec9: 20 3a 88 CasSkipNextHigh jsr CheckForStopKey
becc: f0 f2 beq rtnFalse_BEC0
bece: ad 16 40 lda JOY1
bed1: 29 02 and #$02
bed3: f0 f4 beq CasSkipNextHigh ;loop while cassette signal is low
bed5: 20 3a 88 @lpWhileHi jsr CheckForStopKey
bed8: f0 e6 beq rtnFalse_BEC0
beda: ad 16 40 lda JOY1
bedd: 29 02 and #$02
bedf: d0 f4 bne @lpWhileHi ;loop while cassette signal is high
bee1: 18 clc ;return true/success
bee2: 60 rts
; Rather unintuitively, this routine with Z set if it's a "1" bit, and Z reset
; if it's a "0" bit.
;
; If called when signal is low, will skip a full cycle, otherwise just skip
; high.
; It then waits 623 cycles (~348 ns) and checks signal state (this number of
; cycles was chosen so that it should fall right where you could determine
; whether this is a long pulse or a short one, distinguishing the bit value.
;
; Also checks for the STOP key being pressed. If pressed, aborts early with
; carry set; otherwisse carry will be reset.
bee3: 20 c9 be CassetteRcvBit jsr CasSkipNextHigh
bee6: b0 d8 bcs rtnFalse_BEC0 ;STOP pressed? -> return false
bee8: a0 4e ldy #$4e ;we're def at start of low signal from cassette
beea: 84 75 sty zpHeldKeyChar
beec: c6 75 @waitLp dec zpHeldKeyChar
beee: d0 fc bne @waitLp ;77 * 8 + 7 = 623 cycles (~348 ns)
bef0: ad 16 40 lda JOY1
bef3: 29 02 and #$02 ;signal level to accum
bef5: 18 clc ;return true/success
bef6: 60 rts
vZpByteVal .var $19 {addr/1}
bef7: a9 08 CassetteRcvByte lda #$08 ;prepare to read 8 bits
bef9: 85 74 sta zpCursFlashCtr
befb: a9 00 lda #$00
befd: 85 19 sta vZpByteVal
beff: a5 19 @nextBit lda vZpByteVal
bf01: 18 clc
bf02: 65 19 adc vZpByteVal ;equiv: asl vZpByteVal (so why not do that?)
bf04: 85 19 sta vZpByteVal
bf06: 20 e3 be jsr CassetteRcvBit
bf09: b0 b5 bcs rtnFalse_BEC0 ;STOPped? -> return early
bf0b: d0 05 bne @zero ;zero? -> don't modify the byte
bf0d: 20 d5 bd jsr IncrOnesCounter ;one. Stick it in the low bit.
bf10: e6 19 inc vZpByteVal
bf12: c6 74 @zero dec zpCursFlashCtr
bf14: d0 e9 bne @nextBit
bf16: 20 c9 be jsr CasSkipNextHigh ;finish cycle (CassetteRcvBit will skip start bit)
bf19: b0 a5 bcs rtnFalse_BEC0 ;another opportunutiy to handle STOP
bf1b: a5 19 lda vZpByteVal
bf1d: 18 clc ;return true, accum has the byte read
bf1e: 60 rts
• Clear variables
bf1f: 20 0e be CasSendHeader jsr SetPpuCtrl
bf22: 20 fd bd jsr CasBufSetHeader
bf25: a9 28 lda #40 ;Prog header starts with 40 high bits followed by 40 zeroes
bf27: 85 91 sta zpCasCksum
bf29: a9 00 lda #$00
bf2b: 85 92 sta zpCasCksum+1
bf2d: 4c 3e bf jmp CassetteSend
bf30: 20 0e be CasSendData jsr SetPpuCtrl ;I think this is to disable NMI?
bf33: 20 e7 bd jsr CasBufSetData
bf36: a9 14 lda #20 ;Prog data starts with 20 high bits followed by 20 zeroes
bf38: 85 91 sta zpCasCksum
bf3a: a9 00 lda #$00
bf3c: 85 92 sta zpCasCksum+1
bf3e: a9 20 CassetteSend lda #32
bf40: 85 19 sta zpMisc
bf42: a9 4e lda #78 ;if this had been a #79, we'd send 20,000, probably the intended amt
bf44: 85 1a sta zpMisc+1
bf46: 20 a9 bf @twenThouZeroLp jsr CassetteSendZero ;send 77 * 256 + 32 = 19,744 zeroes
bf49: c6 19 dec zpMisc
bf4b: d0 f9 bne @twenThouZeroLp
bf4d: c6 1a dec zpMisc+1
bf4f: d0 f5 bne @twenThouZeroLp
bf51: a5 91 lda zpCasCksum
bf53: 85 19 sta zpMisc
; After the (not quite) 20,000 zeroes, we send an announce string of N ones and
; N zeroes, where N is 40 for the header, and 20 for the packet that contains
; the actual payload (BASIC program code, or page of BG graphics)
bf55: 20 b3 bf @onesLp jsr CassetteSendOne ;send a string of ones
bf58: c6 19 dec zpMisc
bf5a: d0 f9 bne @onesLp
bf5c: 20 a9 bf @zeroesLp jsr CassetteSendZero ;send a string of zeroes
bf5f: c6 91 dec zpCasCksum
bf61: d0 f9 bne @zeroesLp
bf63: 20 b3 bf jsr CassetteSendOne
bf66: a9 00 lda #$00
bf68: 85 91 sta zpCasCksum
bf6a: 85 92 sta zpCasCksum+1
bf6c: a0 00 @sendBuffer ldy #$00 ;now send the designated data buffer
bf6e: b1 ad lda (zpCasBufPtr),y
bf70: 20 8d bf jsr CassetteSendByte
bf73: 20 ce bd jsr IncrCasBufPtr
bf76: 20 dc bd jsr IsCasDataDone
bf79: d0 f1 bne @sendBuffer
bf7b: a5 91 lda zpCasCksum ;and a checksum (count of the ones in all bytes)
bf7d: 48 pha
bf7e: a5 92 lda zpCasCksum+1
bf80: 20 8d bf jsr CassetteSendByte
bf83: 68 pla
bf84: 20 8d bf jsr CassetteSendByte
bf87: 20 b3 bf jsr CassetteSendOne ;send a final "one" bit at the end
bf8a: 4c 14 be jmp SetPpuCtrlAndEnableNmi
; Sends a one (start bit), followed by eight bits from the accumulator, most-
; significant bit first.
;
; Instead of using the ASL operator, the author uses CLC and ADC to add a zero-
; page value to itself, doubling it each iteration.
CassetteSendByte
bf8d: 20 b3 bf jsr CassetteSendOne
bf90: a2 08 ldx #$08
bf92: 85 19 @shftLp sta zpMisc
bf94: 18 clc
bf95: 65 19 adc zpMisc
bf97: b0 07 bcs @sndOne
bf99: 20 a9 bf jsr CassetteSendZero
bf9c: ca @dex dex
bf9d: d0 f3 bne @shftLp
bf9f: 60 rts
bfa0: 20 b3 bf @sndOne jsr CassetteSendOne
bfa3: 20 d5 bd jsr IncrOnesCounter
bfa6: 4c 9c bf jmp @dex
; Sends the shorter (higher-freq) off/on cycle, indicating a zero (reset bit)
CassetteSendZero
bfa9: 48 pha
bfaa: a9 34 lda #$34
bfac: 85 1b sta zpMisc+2
bfae: 85 1c sta zpMisc+3
bfb0: 4c ba bf jmp CassetteSendBit
; Sends the longer (lower-freq) off/on cycle, indicating a one (set bit)
bfb3: 48 CassetteSendOne pha
bfb4: a9 6a lda #$6a
bfb6: 85 1b sta zpMisc+2
bfb8: 85 1c sta zpMisc+3
; zpMisc+2 specifies how long to send low signal,
; zpMisc+3 specifies how long to send high signal
bfba: a9 04 CassetteSendBit lda #$04
bfbc: 8d 16 40 sta JOY1
bfbf: c6 1c @loLp dec zpMisc+3
bfc1: d0 fc bne @loLp
bfc3: a9 ff lda #$ff
bfc5: 8d 16 40 sta JOY1
bfc8: c6 1b @hiLp dec zpMisc+2
bfca: d0 fc bne @hiLp
bfcc: 68 pla
bfcd: 60 rts
vZpBgtTileBufIdx .var $3d {addr/1} ;index into buffer for saving tiles from behind BGTOOL mode-select menu
vZpRowsRemain .var $74 {addr/1}
vZpBgtMenuItemIdx .var $75 {addr/1}
vZpBgToolModeId .var $7c {addr/1}
vZpBgtSelectCurTileRow .var $7d {addr/1} ;which row of tiles is showing in the tile SELECT menu
vZpTileIdTmp .var $7e {addr/1}
vZpBgtCurRowSelTileNum .var $7f {addr/1} ;index to which of the tiles in current tile row is the active tile for insertion
vZpBgtCurPaletteNum .var $88 {addr/1} ;currently-selected palette num for inserted tiles
bfce: 20 a7 b4 BgtMode_CLEAR jsr DoCLS_1 ;clear SCREEN 1, restart BGTOOL
bfd1: 20 36 b4 CmdFn_BGTOOL jsr ResetPaletteSets ;reset the palette sets to their hard-coded defaults
bfd4: a0 08 ldy #$08
bfd6: a2 00 ldx #$00
bfd8: 8a txa
bfd9: 9d 45 06 @attrClrLp sta sprAttrFlags,x ;clear sprite tile attrs for the 8 actors.
bfdc: e8 inx
bfdd: 88 dey
bfde: d0 f9 bne @attrClrLp ;-> loop until 8 actors' sprite attrs cleared
bfe0: a9 24 lda #>VRAM_screen1
bfe2: 85 4f sta zpPpuScreenBasePage ;set SCREEN 1 as current screen
bfe4: a9 00 lda #0
bfe6: 85 48 sta zpCH
bfe8: a9 15 lda #21
bfea: 85 49 sta zpCV ;put cursor at x:0,y:21
bfec: a9 1a lda #key_CtrlZ ;Control-Z
bfee: 20 75 b5 jsr PrintOneChar ;invokes Control-Z's ClearToEndOfScreen function
bff1: a9 01 lda #$01 ; (clearing from 0,21 to end of screen: last 4 rows)
bff3: 85 f0 sta zpCurScreenNum ;set the other SCREEN 1 zp var
bff5: 20 e8 ae jsr ClearOam ;clear all sprite info from OAM DMA area
bff8: 20 2b b4 jsr WaitForVBlank ;wait for vblank
bffb: a5 32 lda zpPpuCtrlVal
bffd: 09 09 ora #$09
bfff: 85 32 sta zpPpuCtrlVal ;set screen 1 for PPU, and set BG to use BG tiles
c001: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
c004: 20 14 be jsr SetPpuCtrlAndEnableNmi ;enact the PPUCTRL changes
c007: 20 c9 af jsr CmdFn_SPRITE_ON ;turn sprites on
c00a: a9 00 lda #$00
c00c: 8d 05 05 sta bgtCH ;move BGTOOL cursor to 0,0
c00f: 8d 06 05 sta bgtCV
c012: 85 88 sta vZpBgtCurPaletteNum ;use BG palette 0
c014: 8d 04 05 sta lineBuffer+4 ;???
c017: 4c e8 c2 jmp BgtStartupSelectMode ;-> done with init, enter SELECT mode
; Copy BGTOOL's cursor coords to global (text) cursor coords
BgtCursorToGlobalCursor
c01a: ad 05 05 lda bgtCH
c01d: 85 48 sta zpCH
c01f: ad 06 05 lda bgtCV
c022: 85 49 sta zpCV
c024: 60 rts
BgtSetCursorAndReadChar
c025: 85 e0 sta zpKanaCursChr
c027: 85 e1 sta zpNonKCursChr
c029: 20 66 b7 jsr DeactivateKanaMode
c02c: 4c 20 bc jmp ReadChar
; Move the tile cursor (box sprite) according to the cursor key that was typed.
BgtHandleCursorKeys
c02f: c9 1c cmp #key_CursorRight
c031: f0 0d beq BgtCursorRight
c033: c9 1d cmp #key_CursorLeft
c035: f0 16 beq BgtCursorLeft
c037: c9 1e cmp #key_CursorUp
c039: f0 22 beq BgtCursorUp
c03b: c9 1f cmp #key_CursorDown
c03d: f0 2e beq BgtCursorDown
c03f: 60 rts
c040: ad 05 05 BgtCursorRight lda bgtCH
c043: c9 1b cmp #COLS-1 ;cursor on last column?
c045: f0 21 beq BgtCursStartOfNextLn ;yes -> cursor to start of next screen row
c047: ee 05 05 inc bgtCH ;else, move cursor right
c04a: a9 00 BgtNullKey lda #$00 ;zero register A, to prevent further key handling
c04c: 60 rts
c04d: ad 05 05 BgtCursorLeft lda bgtCH ;cursor on first column?
c050: f0 06 beq BgtCursEndOfPrevLn ;yes -> cursor to end of prev row
c052: ce 05 05 dec bgtCH ;else, move cursor left
c055: 4c 4a c0 jmp BgtNullKey ;-> done
BgtCursEndOfPrevLn
c058: a9 1b lda #COLS-1
c05a: 8d 05 05 sta bgtCH ;move cursor to last column
c05d: ad 06 05 BgtCursorUp lda bgtCV ;cursor already at top line?
c060: f0 e8 beq BgtNullKey ;-> done (nothing to do)
c062: ce 06 05 dec bgtCV ;else move cursor up
c065: 4c 4a c0 jmp BgtNullKey ;-> done
BgtCursStartOfNextLn
c068: a9 00 lda #$00
c06a: 8d 05 05 sta bgtCH ;move cursor to start of line
c06d: ad 06 05 BgtCursorDown lda bgtCV
c070: c9 14 cmp #BGT_ROWS-1 ;cursor already on bottom line?
c072: f0 d6 beq BgtNullKey ;yes -> done (nothing to do)
c074: ee 06 05 inc bgtCV ;else move cursor down one row
c077: d0 d1 bne BgtNullKey ;(always) -> done
;
BgtModeSelectMenu
c079: 20 d5 af jsr CmdFn_SPRITE_OFF ;turn off sprites
c07c: 20 05 c1 jsr BgtSaveTilesBehindMenu ;save the tiles behind where menu will be drawn
c07f: 20 48 b7 jsr CursorHome ;LOCATE 0,0
c082: 20 5c c2 jsr BgtDisplayModeSelectMenu ;display the mode-select menu
c085: 20 48 b7 jsr CursorHome ;LOCATE 0,0
c088: a9 3e BgtMenu_Prompt lda #‘>’
c08a: 20 25 c0 jsr BgtSetCursorAndReadChar ;set '>' as prompt at 0,0 and read a character
c08d: c9 1e cmp #key_CursorUp ;handle menu keypresses
c08f: f0 3e beq BgtModeSel_PrvMenuItem
c091: c9 1f cmp #key_CursorDown
c093: f0 43 beq BgtModeSel_NxtMenuItem
c095: c9 20 cmp #key_Space
c097: f0 49 beq BgtModeSel_Select
c099: c9 03 cmp #key_Stop
c09b: f0 02 beq BgtModeSel_ExitTool
c09d: d0 e9 bne BgtMenu_Prompt ;no match? -> reprompt
BgtModeSel_ExitTool
c09f: 20 08 c1 jsr BgtRestoreTilesBehindMenu ;restore tiles that were behind mode-select menu
c0a2: 20 4c b7 jsr CarriageReturn
c0a5: a9 15 lda #21
c0a7: 85 49 sta zpCV ;LOCATE 0,21
c0a9: a9 1a lda #key_CtrlZ
c0ab: 20 75 b5 jsr PrintOneChar ;print ^Z (clear to end of screen)
c0ae: 20 20 b4 jsr InitPromptChars ;restore BASIC prompt cursor chars
c0b1: a9 20 lda #>VRAM_screen0
c0b3: 85 4f sta zpPpuScreenBasePage ;set SCREEN 0 base page
c0b5: 20 36 b4 jsr ResetPaletteSets ;reset palette set definitions
c0b8: 20 ab b4 jsr DoCLS ;clear the screen (we just did this with Ctrl-Z?)
c0bb: 20 94 b1 jsr ClearSpriteData ;clear sprite definitions
c0be: 20 e8 ae jsr ClearOam ;hide all sprites, clear attributes
c0c1: a9 10 lda #DEFAULT_PPUCTRL
c0c3: 85 32 sta zpPpuCtrlVal
c0c5: a9 00 lda #$00
c0c7: 85 f0 sta zpCurScreenNum ;set SCREEN 0
c0c9: 20 14 be jsr SetPpuCtrlAndEnableNmi ;activate new PPUCTRL value, enable NMI
c0cc: 4c 6a 82 jmp CmdFn_END ;-> return to BASIC
BgtModeSel_PrvMenuItem
c0cf: a5 49 lda zpCV ;already at top menu item?
c0d1: f0 b5 beq BgtMenu_Prompt ;yes -> just reprompt
c0d3: c6 49 dec zpCV ;else go up a row
c0d5: 4c 88 c0 jmp BgtMenu_Prompt ;-> reprompt
BgtModeSel_NxtMenuItem
c0d8: a5 49 lda zpCV
c0da: c9 05 cmp #BGT_MODESEL_ITEMS ;already at bottom menu item?
c0dc: f0 aa beq BgtMenu_Prompt ;yes -> just reprompt
c0de: e6 49 inc zpCV ;else go down a row
c0e0: d0 a6 bne BgtMenu_Prompt ;-> reprompt
;
BgtModeSel_Select
c0e2: a5 49 lda zpCV ;get cursor row # (= menu item #)
c0e4: 0a asl A ;times 2 for jump-table index
c0e5: aa tax
c0e6: bd f3 c0 lda jtbl_BgtModeEntryPoints,x ;get entry point for selected mode
c0e9: 85 19 sta zpMisc
c0eb: bd f4 c0 lda jtbl_BgtModeEntryPoints+1,x
c0ee: 85 1a sta zpMisc+1
c0f0: 6c 19 00 jmp (zpMisc) ;-> and jump to entry point
jtbl_BgtModeEntryPoints
c0f3: e5 c2 .dd2 BgtMode_SELECT
c0f5: be c5 .dd2 BgtMode_COPY
c0f7: bb c5 .dd2 BgtMode_MOVE
c0f9: ce bf .dd2 BgtMode_CLEAR
c0fb: 97 c6 .dd2 BgtMode_FILE
c0fd: 7d c1 .dd2 BgtMode_CHAR
bgtMenu_SvRstWrapup
c0ff: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
c102: 4c 62 b5 jmp AwaitVBlankAndUpdatePpuMask
BgtSaveTilesBehindMenu
c105: a9 ff lda #$ff
c107: ae ldx ▼ a:$00a9
BgtRestoreTilesBehindMenu
c108: a9 00 lda #$00
c10a: 8d 00 05 sta lineBuffer ;using this location to indicate save vs restore
c10d: 20 59 b5 jsr AwaitVBlankAndClearPpuMask ;turn off screen rendering
c110: a9 09 lda #9
c112: 85 74 sta vZpRowsRemain ;number of rows to save/restore = 9
c114: a9 00 lda #$00
c116: 85 3d sta vZpBgtTileBufIdx ;start buffer index at 0
c118: 20 48 b7 jsr CursorHome ;LOCATE 0,0
bgtMenu_SvRstLoop
c11b: 20 f4 b4 jsr CalcCursorTilemapPos
c11e: 20 2e b5 jsr PpuGoToCursor ;set current VRAM addr to cursor position
c121: ad 00 05 lda lineBuffer ;"restore" mode?
c124: f0 03 beq @skipFake ;yes -> skip faked PPUDATA read
c126: ad 07 20 lda PPUDATA ;else, fake a read from PPU
c129: a0 0a @skipFake ldy #10 ;number of cols to save/restore per row = 10
c12b: a6 3d ldx vZpBgtTileBufIdx
c12d: ad 00 05 @nextTile lda lineBuffer ;"restore" mode?
c130: f0 09 beq @restore ;yes -> restore buffered tile
c132: ad 07 20 lda PPUDATA ;else, get next tile from VRAM
c135: 9d 20 05 sta bgtMenuTilesBuf,x ;save to buffer
c138: 4c 41 c1 jmp @nextIter
c13b: bd 20 05 @restore lda bgtMenuTilesBuf,x ;get the original tile ID from the save/restore buffer
c13e: 8d 07 20 sta PPUDATA ;write it to VRAM
c141: e8 @nextIter inx ;incr index into tile buffer
c142: 88 dey ;decr remaining rows to copy
c143: d0 e8 bne @nextTile ;rows left? -> save or restore next row
c145: 86 3d stx vZpBgtTileBufIdx ;save buffer index
c147: c6 74 dec vZpRowsRemain ;one fewer row to save/restore
c149: f0 b4 beq bgtMenu_SvRstWrapup ;out of rows? -> done
c14b: e6 49 inc zpCV ;move cursor to next screen row
c14d: d0 cc bne bgtMenu_SvRstLoop ;-> loop to handle next row
;
BgtUpdateCursorAndPrintPos
c14f: 20 e8 ae jsr ClearOam ;clear all sprite data
c152: a9 b0 lda #TileId_emptyBox ;empty box tile ID
c154: 8d 01 02 sta oamSprTileId ;set that as sprite graphic
c157: a9 00 lda #$00
c159: 8d 02 02 sta oamSprAttr ;clear sprite attrs
c15c: ad 05 05 lda bgtCH ;get bgtool cursor X position
c15f: 18 clc
c160: 69 02 adc #HMARGIN ;add 2 for margin
c162: 0a asl A
c163: 0a asl A
c164: 0a asl A ;times 8 (tiles -> pixels)
c165: 8d 03 02 sta oamSprXPos ;set as cursor sprite x pos
c168: ad 06 05 lda bgtCV ;get bgtool cursor Y position
c16b: 18 clc
c16c: 69 03 adc #VMARGIN ;add 3 for vert margin
c16e: 0a asl A
c16f: 0a asl A
c170: 0a asl A ;times 8 (tiles -> pixels)
c171: 8d 00 02 sta oamSprYPos ;set as cursor sprite y pos
c174: ce 00 02 dec oamSprYPos ;subtract one (for framing? idk)
c177: 4c cc c1 jmp BgtPrintCursorPos
modeSelMenu_C17A
c17a: 4c 79 c0 jmp BgtModeSelectMenu
c17d: 20 20 b4 BgtMode_CHAR jsr InitPromptChars ;restore text prompt cursor chars
c180: 20 08 c1 jsr BgtRestoreTilesBehindMenu ;restore the tiles from before mode-select menu
c183: a9 05 lda #$05
c185: 20 76 c2 jsr BgtSetAndPrintCurrentMode ;print that we're in CHAR mode
c188: 20 d5 af jsr CmdFn_SPRITE_OFF ;turn off sprites (using normal prompt cursor chars instead)
c18b: 20 1a c0 jsr BgtCursorToGlobalCursor ;copy BGTOOL cursor pos to global (text) cursor
c18e: 20 56 b6 BgtChar_Prompt jsr SaveCursorPos ;save the new cursor pos
c191: a5 48 lda zpCH ;copy global (text) cursor pos to BGTOOL cursor pos
c193: 8d 05 05 sta bgtCH
c196: a5 49 lda zpCV
c198: 8d 06 05 sta bgtCV
c19b: 20 cc c1 jsr BgtPrintCursorPos ;update cursor position display
c19e: 20 5f b6 jsr RestoreCursorPos ;restore cursor pos (changed during display update)
c1a1: 20 20 bc jsr ReadChar ;read keyboard (with flashing cursor prompt)
c1a4: c9 0d cmp #key_Enter ;handle special keys:
c1a6: f0 0f beq BgtChar_Enter
c1a8: c9 1b cmp #key_Escape
c1aa: f0 ce beq modeSelMenu_C17A
c1ac: 90 e0 bcc BgtChar_Prompt
c1ae: 20 75 b5 jsr PrintOneChar ;no special key? then echo it here (draw to background)
c1b1: 20 c0 c1 jsr BgtChar_MaybeFixCV ;if we exceeded last row, return to it
c1b4: 4c 8e c1 jmp BgtChar_Prompt ;-> prompt for another input key
c1b7: 20 6f b5 BgtChar_Enter jsr DoCRLF ;go to start of next line
c1ba: 20 c0 c1 jsr BgtChar_MaybeFixCV
c1bd: 4c 8e c1 jmp BgtChar_Prompt ;-> prompt for next input
BgtChar_MaybeFixCV
c1c0: a5 49 lda zpCV
c1c2: c9 15 cmp #BGT_ROWS ;cursor y pos >= last BG row?
c1c4: b0 01 bcs @fix ;yes -> fix
c1c6: 60 rts ;else -> return
c1c7: a9 14 @fix lda #$14
c1c9: 85 49 sta zpCV ;set y pos to last BG row
c1cb: 60 rts ;-> return
BgtPrintCursorPos
c1cc: 20 4c b7 jsr CarriageReturn
c1cf: a9 16 lda #22
c1d1: 85 49 sta zpCV ;LOCATE 0,22
c1d3: a9 58 lda #‘X’
c1d5: 20 4d 89 jsr QueueCharForOutput
c1d8: a9 3a lda #‘:’
c1da: 20 4d 89 jsr QueueCharForOutput
c1dd: 20 5d 89 jsr PrintOutBuf ;print "X:"
c1e0: 20 6b b5 jsr EnsureSOL ;CRLF (start of next line)
c1e3: a9 59 lda #‘Y’
c1e5: 20 4d 89 jsr QueueCharForOutput
c1e8: a9 3a lda #‘:’
c1ea: 20 4d 89 jsr QueueCharForOutput
c1ed: 20 5d 89 jsr PrintOutBuf ;print "Y:"
c1f0: ad 05 05 lda bgtCH
c1f3: 85 28 sta zpWAccum ;put cursor x pos into WAccum
c1f5: a9 02 lda #2
c1f7: 85 48 sta zpCH
c1f9: a9 16 lda #22
c1fb: 85 49 sta zpCV ;LOCATE 2,22
c1fd: 20 0b c2 jsr @prCursPos ;print cursor x pos
c200: ad 06 05 lda bgtCV ;put cursor y pos into WAccum
c203: 85 28 sta zpWAccum
c205: a9 02 lda #2
c207: 85 48 sta zpCH
c209: e6 49 inc zpCV ;LOCATE 2,23
c20b: 20 ce a7 @prCursPos jsr ClearOperandHiBytes ;set WAccum high byte to 0
c20e: 20 3e 91 jsr PosDecStrFromWord ;convert WAccum (cursor position) to decimal string
c211: a9 3d lda #zpNumStrTmp ;point output string at decimal string
c213: 18 clc
c214: 69 03 adc #$03 ;...plus 3, so we get the final 2 digits (skips 3 leading zeroes)
c216: 85 52 sta zpOutputStr
c218: a9 00 lda #>zpNumStrTmp
c21a: 85 53 sta zpOutputStr+1
c21c: 4c 5a 89 jmp PrintString ;-> print the coordinate, and return
c21f: 4d 4f 44 45+ strMODE .zstr “MODE ”
c226: 53 45 4c 45+ strSELECT .zstr “SELECT”
c22d: 4d 4f 56 45+ strMOVE .zstr “MOVE ”
c234: 43 4f 50 59+ strCOPY .zstr “COPY ”
c23b: 43 48 41 52+ strCHAR .zstr “CHAR ”
c242: 43 4c 45 41+ strCLEAR .zstr “CLEAR ”
c249: 46 49 4c 45+ strFILE .zstr “FILE ”
tbl_BgToolModeStrs
c250: 26 c2 .dd2 strSELECT
c252: 34 c2 .dd2 strCOPY
c254: 2d c2 .dd2 strMOVE
c256: 42 c2 .dd2 strCLEAR
c258: 49 c2 .dd2 strFILE
c25a: 3b c2 .dd2 strCHAR
BgtDisplayModeSelectMenu
c25c: a9 00 lda #$00
c25e: 85 75 sta vZpBgtMenuItemIdx ;start with first menu item
c260: a9 20 @menuLp lda #$20
c262: 20 4d 89 jsr QueueCharForOutput ;print SPACE char
c265: a5 75 lda vZpBgtMenuItemIdx
c267: 20 93 c2 jsr BgtPrintModeStr ;print the menu item string
c26a: 20 6f b5 jsr DoCRLF ;move cursor to start of next line
c26d: e6 75 inc vZpBgtMenuItemIdx ;advance menu item
c26f: a5 75 lda vZpBgtMenuItemIdx
c271: c9 06 cmp #$06 ;printed six menu items?
c273: d0 eb bne @menuLp ;no -> loop and print next item
c275: 60 rts
; Store the mode id in register A, and print the mode at bottom of screen.
BgtSetAndPrintCurrentMode
c276: 85 7c sta vZpBgToolModeId
; Print the currently-selected BGTOOL mode at the bottom of the screen.
BgtPrintCurrentMode
c278: a9 05 lda #5
c27a: 85 48 sta zpCH
c27c: a9 16 lda #22
c27e: 85 49 sta zpCV ;LOCATE 5,22
c280: a9 1f lda #<strMODE
c282: 85 52 sta zpOutputStr
c284: a9 c2 lda #>strMODE
c286: 85 53 sta zpOutputStr+1
c288: 20 5a 89 jsr PrintString ;print "MODE" at 5,22
c28b: a9 05 lda #$05
c28d: 85 48 sta zpCH
c28f: e6 49 inc zpCV ;LOCATE 5,23
c291: a5 7c lda vZpBgToolModeId ;get the current mode index
; Print the name of the BGTOOL mode whose ID is in the A register.
c293: 0a BgtPrintModeStr asl A ;times 2 for use as a string-table index
c294: aa tax
c295: bd 50 c2 lda tbl_BgToolModeStrs,x
c298: 85 52 sta zpOutputStr
c29a: bd 51 c2 lda tbl_BgToolModeStrs+1,x
c29d: 85 53 sta zpOutputStr+1 ;point zpOutputStr at the indexed string
c29f: 4c 5a 89 jmp PrintString ;and print it
; the starting tile ID for each row of 8 tiles that can be viewed in BGTOOL's
; SELECT mode. (Skips "character" tiles; hence the leap to $b8 after the row
; that starts with $18 - $20 would be the space character)
tbl_BgSelectTileRowStarts
c2a2: 00 08 10 18+ .bulk $00,$08,$10,$18,$b8,$c0,$c8,$d0
c2aa: d8 e0 e8 f0+ .bulk $d8,$e0,$e8,$f0,$f8
BgtSELECT_NextPalette
c2af: 20 b5 c2 jsr BgtIncrPaletteNumAndDisplay ;select next palette in palette set
c2b2: 4c 03 c3 jmp BgtSelectModeContinue
BgtIncrPaletteNumAndDisplay
c2b5: 20 53 c3 jsr BgtIncrPaletteNumAndSet
c2b8: a5 48 lda zpCH ;push CH, CV
c2ba: 48 pha
c2bb: a5 49 lda zpCV
c2bd: 48 pha
c2be: a9 0a lda #10
c2c0: 85 48 sta zpCH
c2c2: a9 16 lda #22
c2c4: 85 49 sta zpCV ;locate 10,22
c2c6: a5 88 lda vZpBgtCurPaletteNum ;get the currently-selected palette number
c2c8: 09 30 ora #$30 ;add #$30 to convert to ASCII digit
c2ca: 20 75 b5 jsr PrintOneChar ;print the digit (after "MODE") at bottom of screen
c2cd: 68 pla
c2ce: 85 49 sta zpCV ;pull & restore CH, CV
c2d0: 68 pla
c2d1: 85 48 sta zpCH
c2d3: 60 rts
BgtSELECT_DeleteTile
c2d4: 20 56 b6 jsr SaveCursorPos ;save global cursor pos
c2d7: 20 1a c0 jsr BgtCursorToGlobalCursor ;set it to bgt cursor pos
c2da: a9 20 lda #key_Space
c2dc: 20 75 b5 jsr PrintOneChar ;write a space over this location
c2df: 20 5f b6 jsr RestoreCursorPos ;restore global cursor pos
c2e2: 4c 03 c3 jmp BgtSelectModeContinue ;-> done
; BGTOOL SELECT mode, entry from mode-select menu
c2e5: 20 08 c1 BgtMode_SELECT jsr BgtRestoreTilesBehindMenu
; entry from BGTOOL command, or via CLEAR mode selection
BgtStartupSelectMode
c2e8: 20 c9 af jsr CmdFn_SPRITE_ON ;turn sprites on
c2eb: a9 00 lda #$00
c2ed: 85 7f sta vZpBgtCurRowSelTileNum ;currently-selected tile idx = 0
c2ef: 85 7d sta vZpBgtSelectCurTileRow ;current tile row = 0
c2f1: 20 76 c2 jsr BgtSetAndPrintCurrentMode ;print that we're in SELECT mode
c2f4: a9 03 lda #$03
c2f6: 85 88 sta vZpBgtCurPaletteNum ;set current palette to 3 bc we'll increment before displaying
c2f8: 20 b5 c2 jsr BgtIncrPaletteNumAndDisplay ;increment (wraparound to 0) and set for tile menu
c2fb: a9 0c lda #12
c2fd: 85 48 sta zpCH
c2ff: a9 17 lda #23
c301: 85 49 sta zpCV ;LOCATE 12,23 (useless, immediately relocated in next subrtn)
BgtSelectModeContinue
c303: 20 08 c4 jsr BgtCalcAndDisplayTileSelectRow ;show the current set of bg tiles
c306: 20 56 b6 @prompt jsr SaveCursorPos
c309: 20 4f c1 jsr BgtUpdateCursorAndPrintPos ;show the cursor box, and print its position
c30c: 20 5f b6 jsr RestoreCursorPos ;LOCATE 12,23 (just under the first SELECT-menu tile)
c30f: a9 5e lda #‘^’
c311: 20 25 c0 jsr BgtSetCursorAndReadChar ;set '^' as the kbd cursor and prompt for keyboard input
c314: 20 2f c0 jsr BgtHandleCursorKeys
c317: a2 00 ldx #$00 ;start with index 0
c319: dd 35 c3 @whatKeyLp cmp tbl_bgtSelModeKeys,x ;into SELECT's recognized-keys table
c31c: f0 07 beq @handleKey ;got one? -> handle key
c31e: e8 inx ;else, index++
c31f: e0 08 cpx #$08 ;out of acceptable keys in table?
c321: d0 f6 bne @whatKeyLp ;no -> loop to check next acceptable key value
c323: f0 e1 beq @prompt
c325: 8a @handleKey txa
c326: 0a asl A ;multiply key index times 2, for jump-table index
c327: aa tax
c328: bd 3d c3 lda jtbl_bgtSelectKeyHandlers,x ;get the address to jump to
c32b: 85 19 sta zpMisc
c32d: bd 3e c3 lda jtbl_bgtSelectKeyHandlers+1,x
c330: 85 1a sta zpMisc+1
c332: 6c 19 00 jmp (zpMisc) ;-> then do the jump
; A table of the recognized input keys in BGTOOL's SELECT mode
tbl_bgtSelModeKeys
c335: 1b .dd1 key_Escape
c336: 0d .dd1 key_Enter
c337: 0c .dd1 key_Cls
c338: 0b .dd1 key_Home
c339: 08 .dd1 key_Del
c33a: 12 .dd1 key_Insert
c33b: 20 .dd1 key_Space
c33c: 44 .dd1 ‘D’
jtbl_bgtSelectKeyHandlers
c33d: 7d c3 .dd2 BgtSELECT_ModeMenu ;Esc
c33f: af c2 .dd2 BgtSELECT_NextPalette ;Enter
c341: 94 c3 .dd2 BgtSELECT_TileMenuPrevRow ;Shift-Home
c343: 9d c3 .dd2 BgtSELECT_TileMenuNextRow ;Home
c345: a7 c3 .dd2 BgtSELECT_TileMenuNextTile ;Backspace (DEL)
c347: b5 c3 .dd2 BgtSELECT_TileMenuPrevTile ;Ins
c349: c3 c3 .dd2 BgtSELECT_WriteTile ;Space
c34b: d4 c2 .dd2 BgtSELECT_DeleteTile ;D
c34d: a9 00 @wrapAround lda #$00
c34f: 85 88 sta vZpBgtCurPaletteNum
c351: f0 08 beq @wrapCont
BgtIncrPaletteNumAndSet
c353: e6 88 inc vZpBgtCurPaletteNum ;increment the palette number
c355: a5 88 lda vZpBgtCurPaletteNum
c357: c9 04 cmp #NUM_PALETTES ;did we advance past the last palette #?
c359: f0 f2 beq @wrapAround ;yes -> wrap around to zero and then continue
c35b: 0a @wrapCont asl A
c35c: 0a asl A
c35d: 05 88 ora vZpBgtCurPaletteNum ;copy for the top two of the quadrants covered in an attr byte
c35f: 48 pha ;then push that value to CPU stack
c360: 20 2b b4 jsr WaitForVBlank ;wait for vblank so it's safe to mess with PPU
c363: a9 27 lda #$27
c365: a2 f3 ldx #$f3 ;go to $27F3 in VRAM, corresponding to
c367: 20 ec 81 jsr PpuGoToAX ; the palette ids for SELECT mode's displayed tiles
c36a: 68 pla
c36b: aa tax
c36c: 29 fc and #$fc ;make a version that covers just the top-right quadrant
c36e: 8d 07 20 sta PPUDATA ;store that as the first attr (this avoids setting color on the text
c371: 8a txa ; to the left of the tile menu)
c372: a2 04 ldx #$04
c374: 8d 07 20 @cpyLp sta PPUDATA ;copy the full attribute byte, for the next 4 bytes
c377: ca dex
c378: d0 fa bne @cpyLp
c37a: 4c 3c b5 jmp NmiSetVScrollAndEnableNmi ;-> done
; When ESC is typed while in SELECT mode
BgtSELECT_ModeMenu
c37d: a9 0c lda #12
c37f: 85 48 sta zpCH
c381: a9 16 lda #22
c383: 85 49 sta zpCV ;LOCATE 5,22
c385: a9 05 lda #$05 ;Control-E
c387: 20 75 b5 jsr PrintOneChar ;print ^E (clear to end of line - erases menu tiles)
c38a: e6 49 inc zpCV ;LOCATE 5,23
c38c: a9 05 lda #$05 ;Control-E
c38e: 20 75 b5 jsr PrintOneChar ;print ^E (clear to end of line - erases ^ prompt)
c391: 4c 79 c0 jmp BgtModeSelectMenu ;-> present mode-select menu
BgtSELECT_TileMenuPrevRow
c394: a5 7d lda vZpBgtSelectCurTileRow ;get current tiles-row index
c396: f0 02 beq bgtSelModeCont_C39A ;first row? -> done; nothing to do
c398: c6 7d dec vZpBgtSelectCurTileRow ;else decr row
bgtSelModeCont_C39A
c39a: 4c 03 c3 jmp BgtSelectModeContinue ;-> continue SELECT mode
BgtSELECT_TileMenuNextRow
c39d: a5 7d lda vZpBgtSelectCurTileRow ;get current tiles-row index
c39f: c9 0c cmp #BGT_SELECT_NUM_TROWS-1 ;on last row of tiles?
c3a1: f0 f7 beq bgtSelModeCont_C39A ;yes -> done; nothing to do
c3a3: e6 7d inc vZpBgtSelectCurTileRow ;else go to next tile row
c3a5: d0 f3 bne bgtSelModeCont_C39A ;(always) -> done (redisplay)
;
BgtSELECT_TileMenuNextTile
c3a7: a5 48 lda zpCH ;get cursor position
c3a9: c9 1a cmp #SELMD_LAST_TILEPOS ;cursor at last tile of row?
c3ab: f0 ed beq bgtSelModeCont_C39A ;yes -> done; nothing to do
c3ad: e6 48 inc zpCH ;else advance cursor by two
c3af: e6 48 inc zpCH
c3b1: e6 7f inc vZpBgtCurRowSelTileNum ;and tile selection by one
c3b3: d0 e5 bne bgtSelModeCont_C39A ;(always) -> done (redisplay)
;
BgtSELECT_TileMenuPrevTile
c3b5: a5 48 lda zpCH
c3b7: c9 0c cmp #SELMD_FRST_TILEPOS ;cursor at first tile of row?
c3b9: f0 df beq bgtSelModeCont_C39A ;yes -> done; nothing to do
c3bb: c6 48 dec zpCH ;else back up cursor two spaces
c3bd: c6 48 dec zpCH
c3bf: c6 7f dec vZpBgtCurRowSelTileNum ;select previous tile
c3c1: 10 d7 bpl bgtSelModeCont_C39A ;(always) -> done (redisplay)
;
BgtSELECT_WriteTile
c3c3: 20 56 b6 jsr SaveCursorPos ;save prompt cursor location
c3c6: 20 1a c0 jsr BgtCursorToGlobalCursor ;copy bgtCH,bgtCV to CH,CV
c3c9: 20 f4 b4 jsr CalcCursorTilemapPos ;calculate VRAM addr for new position
c3cc: a6 7d ldx vZpBgtSelectCurTileRow ;get tile-menu row #
c3ce: bd a2 c2 lda tbl_BgSelectTileRowStarts,x ;get starting tile ID for row
c3d1: 18 clc
c3d2: 65 7f adc vZpBgtCurRowSelTileNum ;add which tile in row is selected
c3d4: 85 73 sta zpCharUnderCurs ;save as the tile ID to write
c3d6: a9 09 lda #NC_RestoreCharAtCursor
c3d8: 20 6a 89 jsr WaitForNmiSubcommand ;write the tile to screen
c3db: a5 88 lda vZpBgtCurPaletteNum
c3dd: 85 9c sta zpPaletteNum ;set the palette at cursor's current quadrant,
c3df: 20 ff c4 jsr COLOR_SetPalette ; to current SELECT palette choice
c3e2: ee 05 05 inc bgtCH ;after writing tile, advance BGTOOL cursor to next tile cell
c3e5: ad 05 05 lda bgtCH
c3e8: c9 1c cmp #COLS ;did we go past the end of the screen?
c3ea: f0 06 beq @cursNextRow ;yes -> move cursor to first cell of next row
c3ec: 20 5f b6 @rstCursAndDone jsr RestoreCursorPos ;restore prompt cursor location
c3ef: 4c 03 c3 jmp BgtSelectModeContinue ;-> return to SELECT kbd prompt
c3f2: a9 00 @cursNextRow lda #$00
c3f4: 8d 05 05 sta bgtCH ;put bgtool cursor at start of line
c3f7: ee 06 05 inc bgtCV ;advance bgtool cursor to next line
c3fa: ad 06 05 lda bgtCV
c3fd: c9 15 cmp #BGT_ROWS ;moved past the end of BGTOOL screen?
c3ff: d0 eb bne @rstCursAndDone ;no -> done
c401: a9 14 lda #BGT_ROWS-1 ;yes -> lock it to last row of BGTOOL
c403: 8d 06 05 sta bgtCV
c406: 10 e4 bpl @rstCursAndDone ;-> done
BgtCalcAndDisplayTileSelectRow
c408: 20 56 b6 jsr SaveCursorPos ;save cursor position
c40b: a9 0c lda #12
c40d: 85 48 sta zpCH
c40f: a9 16 lda #22
c411: 85 49 sta zpCV ;LOCATE 12, 22
c413: a6 7d ldx vZpBgtSelectCurTileRow ;get the current tile row for displaying
c415: bd a2 c2 lda tbl_BgSelectTileRowStarts,x ;load the starting tile ID for that row
c418: 85 7e sta vZpTileIdTmp ;and save it
c41a: a0 08 ldy #$08
c41c: a2 00 ldx #$00
c41e: a5 7e @tilesLp lda vZpTileIdTmp ;get tile ID
c420: 9d 10 05 sta lineBuffer+16,x ;store in buffer
c423: e8 inx ;increment dst buffer index
c424: a9 20 lda #$20 ;SPACE character
c426: 9d 10 05 sta lineBuffer+16,x ;store that after row tile, in buffer
c429: e8 inx ;incr dst buf index
c42a: e6 7e inc vZpTileIdTmp ;next tile ID for row
c42c: 88 dey ;one fewer tile remaining
c42d: d0 ef bne @tilesLp ;-> loop until 8 tiles done
c42f: 20 f4 b4 jsr CalcCursorTilemapPos
c432: a2 00 ldx #$00
c434: a0 08 ldy #$08
c436: 20 2b b4 jsr WaitForVBlank ;wait for next vblank so it's safe to write to VRAM
c439: 20 2e b5 jsr PpuGoToCursor ;LOCATE 12, 13
c43c: bd 10 05 @writeLp lda lineBuffer+16,x ;copy the tile row buffer out to screen
c43f: 8d 07 20 sta PPUDATA
c442: e8 inx
c443: a9 20 lda #$20
c445: 8d 07 20 sta PPUDATA
c448: e8 inx
c449: 88 dey
c44a: d0 f0 bne @writeLp
c44c: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
c44f: 4c 5f b6 jmp RestoreCursorPos ;-> restore cursor position, then done
; Given a screen tile position in CH, CV, and a palette num in zpPaletteNum
; ($9C), returns the byte value that holds palette nums for the 4x4 tiles
; holding that location, and an inverted mask for the palette num of the
; sekected 2x2 tile quad within that byte in zpMisc+1 ($1A). Also bit-shifts
; zpPaletteNum to align with that bit position.
;
; See https://www.nesdev.org/wiki/PPU_attribute_tables for how palette selection
; for a tile quad works in the PPU.
• Clear variables
GetTileQuadPaletteVal
c452: e6 48 inc zpCH ;xpos += 2 (left margin)
c454: e6 48 inc zpCH
c456: a5 e4 lda zpVScroll0 ;get the scroll value for the active screen
c458: a6 4f ldx zpPpuScreenBasePage
c45a: e0 20 cpx #>VRAM_screen0
c45c: f0 02 beq @scr0
c45e: a5 e5 lda zpVScroll1
c460: 4a @scr0 lsr A ;divide vert. scroll amt by 8 (convert pixels to tiles)
c461: 4a lsr A
c462: 4a lsr A
c463: 18 clc
c464: 65 49 adc zpCV ;add Y pos + 3 (upper margin)
c466: 69 03 adc #$03
c468: c9 1e cmp #30 ;modulo 30
c46a: 90 02 bcc @lt30
c46c: e9 1e sbc #30 ;we now have Y, relative to start of screen in memory
c46e: 85 49 @lt30 sta zpCV ;save adjusted Y
c470: 29 fc and #$fc ;strip off lowest two bits (now multiple of 4)
c472: 0a asl A ;and mul by 2 (now multiple of 8)
c473: 85 4a sta zpCursTileAddr
c475: a5 48 lda zpCH
c477: 4a lsr A
c478: 4a lsr A ;add xpos/4
c479: 18 clc
c47a: 65 4a adc zpCursTileAddr
c47c: 69 c0 adc #$c0 ;add to location of start of screen 0 or 1's
c47e: 85 4a sta zpCursTileAddr ; attribute table in VRAM (either $23C0 or $27C0)
c480: a5 4f lda zpPpuScreenBasePage
c482: 69 03 adc #$03 ;and save as the VRAM address
c484: 85 4b sta zpCursTileAddr+1 ;(we have the byte addr, but which pair of bits is palette num for our quad?)
c486: a9 02 lda #$02
c488: 25 49 and zpCV ;((ypos / 2) mod 2) * 2 [upper vs lower half of byte]
c48a: 85 19 sta zpMisc
c48c: a5 48 lda zpCH
c48e: 29 03 and #$03
c490: 4a lsr A ;+ ((xpos / 2) mod 2) [pair of bits within that byte half]
c491: 18 clc
c492: 65 19 adc zpMisc
c494: 85 19 sta zpMisc ;save as our bit-pair position
c496: a9 fc lda #$fc ;binary 11111100, to mask out just our palette num
c498: 85 1a sta zpMisc+1
c49a: 06 19 asl zpMisc ;bit position *= 2
c49c: a6 19 ldx zpMisc
c49e: f0 08 beq @shiftDone ;bit pos zero? -> nothing to shift
c4a0: 06 9c @shLftLp asl zpPaletteNum ;shift our palette-num val left one bit
c4a2: 38 sec
c4a3: 26 1a rol zpMisc+1 ;shift our palettes mask left one bit
c4a5: ca dex ;decrement bit position
c4a6: d0 f8 bne @shLftLp ;not yet at bit position? -> shift left again
c4a8: a9 05 @shiftDone lda #NC_GetCharUnderCursor ;zpPaletteNum aligned with its palette nun, and zpMisc holds aligned mask
c4aa: 20 6a 89 jsr WaitForNmiSubcommand ;get the palettes byte at the VRAM loc we calc'd
c4ad: a5 73 lda zpCharUnderCurs
c4af: 60 rts ;and return it
; Calls GetTileQuadPaletteVal, and then shifts the value over to the right as
; needed, so we have the actual number value.
GetTileQuadPaletteNum
c4b0: 20 52 c4 jsr GetTileQuadPaletteVal ;get the palette number for this tile quad
c4b3: 85 9c sta zpPaletteNum
c4b5: a5 1a lda zpMisc+1
c4b7: 49 ff eor #$ff ;invert bitmask for palette num
c4b9: 25 9c and zpPaletteNum ;mask out everything but our palette num
c4bb: 85 9c sta zpPaletteNum
c4bd: a4 19 ldy zpMisc
c4bf: f0 05 beq @rts
c4c1: 46 9c @shftPalLp lsr zpPaletteNum ;shift palette number right until it's
c4c3: 88 dey ; in the appropriate range
c4c4: d0 fb bne @shftPalLp
c4c6: 60 @rts rts ;and return it
c4c7: 4c 94 84 errIll_C4C7 jmp ErrorIllegalValue
; COLOR x, y, N
;
; Selects not a color, but a palette, numbered from the currently-selectded
; palette set (as specified by CGSET). Associates it with the group of four
; (2x2) background tiles which contains the location given by x, y.
c4ca: a5 48 CmdFn_COLOR lda zpCH
c4cc: 48 pha ;push cursor position to stack
c4cd: a5 49 lda zpCV
c4cf: 48 pha
c4d0: 20 6e a2 jsr EvalByteInteger ;get 1st arg (x pos)
c4d3: c9 1c cmp #COLS ;>= num screen cols?
c4d5: b0 f0 bcs errIll_C4C7 ;yes -> error
c4d7: 48 pha ;push Xpos arg
c4d8: 20 6b a2 jsr EvalCommaThenByteInt ;get 2nd arg
c4db: ad 00 04 lda exprParseStk
c4de: c9 18 cmp #ROWS ;>= num screen rows?
c4e0: b0 e5 bcs errIll_C4C7 ;yes -> error
c4e2: 85 49 sta zpCV ;save as cursor Y
c4e4: 68 pla
c4e5: 85 48 sta zpCH ;pull 1st arg (x) and set as cursor X
c4e7: 20 6b a2 jsr EvalCommaThenByteInt ;final arg (color palette num)
c4ea: ad 00 04 lda exprParseStk
c4ed: c9 04 cmp #$04 ;>= 4?
c4ef: b0 d6 bcs errIll_C4C7 ;yes -> error
c4f1: 29 03 and #$03 ;<- redundant
c4f3: 85 9c sta zpPaletteNum ;save palette num
c4f5: 20 ff c4 jsr COLOR_SetPalette
c4f8: 68 pla
c4f9: 85 49 sta zpCV
c4fb: 68 pla
c4fc: 85 48 sta zpCH ;restore original cursor pos
c4fe: 60 rts
COLOR_SetPalette
c4ff: 20 52 c4 jsr GetTileQuadPaletteVal
c502: 25 1a and zpMisc+1 ;mask out the palette for our tile quad
c504: 05 9c ora zpPaletteNum ;replace it with user-specified palette num
c506: 85 73 sta zpCharUnderCurs
c508: a9 09 lda #NC_RestoreCharAtCursor
c50a: 4c 6a 89 jmp WaitForNmiSubcommand ;store the new value, and end command
; BACKGROUND SCREEN HEADER FORMAT:
;
; byte 0: #$03
; bytes 1 - 17: null-term'd filename (no len byte)
; byte 18 - 19: size of following packet's payload (always #$0100)
; bytes 20 - 21: address of genPurposeBuf (#$0700)
; byte 22: vScroll1
; bytes 23-127: (unused)
vZpVramCpyPage .var $5f {addr/2}
SaveBackgroundMap
c50d: a9 03 lda #CAS_HDR_SCREEN ;START HEADER CREATION
c50f: 8d 00 05 sta lineBuffer ;Indicate this header precedes screen data
c512: a9 00 lda #$00
c514: 8d 14 05 sta lineBuffer+20
c517: a9 07 lda #$07
c519: 8d 15 05 sta lineBuffer+21
c51c: a5 e5 lda zpVScroll1
c51e: 8d 16 05 sta lineBuffer+22
c521: a2 00 ldx #$00
c523: 8e 12 05 stx lineBuffer+18
c526: e8 inx
c527: 8e 13 05 stx lineBuffer+19 ;END HEADER CREATION
c52a: a9 00 lda #<VRAM_screen1
c52c: 85 5f sta vZpVramCpyPage
c52e: a9 24 lda #>VRAM_screen1
c530: 85 60 sta vZpVramCpyPage+1
c532: 20 9f 9d jsr Print_WRITING_fileName
; Sends the header, then a payload with one page (256 bytes) of tiles
; from screen 1.
;
; Very expensive, but can't be helped: we have to wait for a vblank before we
; can safely access the screen data, and I guess we can't send a second page
; without risking vblank ending and getting garbage data. After we send a page,
; we must await vblank again, and we can't do that _and_ send the cassette
; signals at the same time. So, individual header + payload it is.
;
; Header is exactly the same each time.
c535: 20 59 b5 @sendNextPage jsr AwaitVBlankAndClearPpuMask ;wait for next vblank, then disable screen rendering
c538: a5 60 lda vZpVramCpyPage+1
c53a: a6 5f ldx vZpVramCpyPage
c53c: 20 ec 81 jsr PpuGoToAX ;go to the VRAM address we'll be copying from
c53f: ad 07 20 lda PPUDATA ;fake PPU read
c542: a2 00 ldx #$00
c544: ad 07 20 @cpyTiles lda PPUDATA ;read a tile ID
c547: 9d 00 07 sta genPurposeBuf,x ;save it to buffer
c54a: e8 inx ;next tile
c54b: d0 f7 bne @cpyTiles ;-> loop until 256 tiles read
c54d: 20 3c b5 jsr NmiSetVScrollAndEnableNmi
c550: 20 62 b5 jsr AwaitVBlankAndUpdatePpuMask ;restore screen rendering
c553: 20 1f bf jsr CasSendHeader ;send the header for this page of the BG data
c556: 20 30 bf jsr CasSendData ;send the data packet for this page
c559: e6 60 inc vZpVramCpyPage+1 ;advance to next VRAM page
c55b: a5 60 lda vZpVramCpyPage+1
c55d: c9 28 cmp #(>VRAM_screen1)+4 ;sent all VRAM pages for the bg screen?
c55f: d0 d4 bne @sendNextPage ;no -> loop until 4 pages sent
c561: 60 rts_C561 rts
• Clear variables
vZpVramAddr .var $5f {addr/2} ;Where to send loaded screen 1 data
c562: 20 b9 9d LBM_SkipFile jsr Print_SKIP_fileName
LoadBackgroundMap
c565: a9 00 lda #$00 ;set up dest ptr: first page of screen 1
c567: 85 5f sta vZpVramAddr
c569: a9 24 lda #$24
c56b: 85 60 sta vZpVramAddr+1
c56d: 20 53 be LBM_readAPage jsr CasRcvHeader ;find and read a header
c570: b0 ef bcs rts_C561 ;error? -> rts
c572: ad 00 05 lda lineBuffer ;get header type
c575: c9 03 cmp #CAS_HDR_SCREEN ;is it a graphics chunk?
c577: d0 e9 bne LBM_SkipFile ;no -> skip next 'til next header
c579: 20 b1 9e jsr LoadCheckFilename ;yes, it's a BG save. Does filename match?
c57c: b0 e4 bcs LBM_SkipFile ;no -> skip to next header
c57e: 20 ac 9d jsr Print_LOADING_fileName ;match! (or missing.) Tell user we'll load it.
c581: ad 16 05 lda lineBuffer+22
c584: 85 e5 sta zpVScroll1 ;set vert scroll from header val
c586: 20 24 be jsr CasRcvFileData ;load the BG data
c589: b0 d6 bcs rts_C561
c58b: 20 59 b5 jsr AwaitVBlankAndClearPpuMask ;wait until next vblank so we can safely write VRAM
c58e: a5 60 lda vZpVramAddr+1
c590: a6 5f ldx vZpVramAddr
c592: 20 ec 81 jsr PpuGoToAX ;tell PPU: writes go to screen 1
c595: a2 00 ldx #$00
c597: bd 00 07 @cpyToVram lda genPurposeBuf,x ;transfer a page of data (one quarter-screen) to screen 1
c59a: 8d 07 20 sta PPUDATA
c59d: e8 inx
c59e: d0 f7 bne @cpyToVram ;loop until page sent
c5a0: 20 3c b5 jsr NmiSetVScrollAndEnableNmi ;re-enable NMI on vblank
c5a3: 20 62 b5 jsr AwaitVBlankAndUpdatePpuMask
c5a6: e6 60 inc vZpVramAddr+1 ;we'll set PPU to the succeeding page
c5a8: a5 60 lda vZpVramAddr+1
c5aa: c9 28 cmp #$28 ;are we at the end of screen 1?
c5ac: d0 bf bne LBM_readAPage ;no -> read next page of screen data
c5ae: 60 rts ;yes: return!
• Clear variables
vZpDestCursPalette .var $88 {addr/1} ;color palette # for the destination cursor
vZpDestCursTileId .var $91 {addr/1} ;tile id for the move/copy destination cursor sprite
vZpDestStateOn .var $ae {addr/1} ;true if we're the destination cursor is active
vZpAmMove .var $af {addr/1} ;flag var for whether we're MOVE or COPY
BgtMvCpy_GoToModeSelect
c5af: a9 00 lda #$00
c5b1: 85 ae sta vZpDestStateOn ;turn off dest mode
c5b3: a9 f8 lda #$f8
c5b5: 8d 10 02 sta oamBuffer+16 ;move dest cursor sprite offscreen
c5b8: 4c 79 c0 jmp BgtModeSelectMenu ;go to mode-select menu
c5bb: a9 02 BgtMode_MOVE lda #$02
c5bd: ae ldx ▼ $01a9
c5be: a9 01 BgtMode_COPY lda #$01
c5c0: 85 7c sta zpBgGotFlag ;(unused)
c5c2: aa tax
c5c3: ca dex
c5c4: 86 af stx vZpAmMove ;set flag for whether we're MOVE or COPY
c5c6: 20 08 c1 jsr BgtRestoreTilesBehindMenu ;restore the tiles that were behind the mode-select menu
c5c9: 20 c9 af jsr CmdFn_SPRITE_ON ;turn on sprites
c5cc: a9 00 lda #$00
c5ce: 85 ae sta vZpDestStateOn ;we're in "source" state
c5d0: 20 78 c2 jsr BgtPrintCurrentMode ;update display of what MODE this is
BgtMvCpy_KbdPrompt
c5d3: 20 4f c1 jsr BgtUpdateCursorAndPrintPos ;redraw cursor sprite and coords
c5d6: 20 49 c6 jsr BgtCpyMvUpdateDestCursor ;update/redraw destination cursor sprite
c5d9: a9 20 lda #key_Space
c5db: 20 25 c0 jsr BgtSetCursorAndReadChar ;prompt for a char (use a blank prompt char (no flashing))
c5de: 20 2f c0 jsr BgtHandleCursorKeys ;handle arrow keys
c5e1: c9 1b cmp #key_Escape ;handle other keys:
c5e3: f0 ca beq BgtMvCpy_GoToModeSelect
c5e5: c9 12 cmp #key_Insert
c5e7: f0 30 beq BgtCpyMv_PickupTile
c5e9: c9 08 cmp #key_Del
c5eb: f0 02 beq BgtCpyMv_DropTile
c5ed: d0 e4 bne BgtMvCpy_KbdPrompt ;unrecognized key? -> reprompt
BgtCpyMv_DropTile
c5ef: a5 ae lda vZpDestStateOn ;in "destination" state?
c5f1: f0 e0 beq BgtMvCpy_KbdPrompt ;no -> nothing to do; reprompt
c5f3: 20 56 b6 jsr SaveCursorPos ;save global cursor pos
c5f6: 20 1a c0 jsr BgtCursorToGlobalCursor ;bgt cursor pos to global cursor pos
c5f9: 20 f4 b4 jsr CalcCursorTilemapPos ;get VRAM addr for cursor location
c5fc: a5 91 lda vZpDestCursTileId
c5fe: 85 73 sta zpCharUnderCurs
c600: a9 09 lda #NC_RestoreCharAtCursor
c602: 20 6a 89 jsr WaitForNmiSubcommand ;write the source tile's ID to the current tile position
c605: a5 88 lda vZpDestCursPalette
c607: 85 9c sta zpPaletteNum
c609: 20 ff c4 jsr COLOR_SetPalette ;write the source tile's palette to the current tile quad
c60c: 20 5f b6 jsr RestoreCursorPos ;restore global cursor pos
c60f: a5 af lda vZpAmMove ;are we MOVE?
c611: f0 04 beq @reprompt ;no -> just reprompt then
c613: a9 00 lda #$00
c615: 85 ae sta vZpDestStateOn ;MOVE completed, turn off "destination" state
c617: f0 ba @reprompt beq BgtMvCpy_KbdPrompt
BgtCpyMv_PickupTile
c619: 20 56 b6 jsr SaveCursorPos ;save global cursor
c61c: 20 1a c0 jsr BgtCursorToGlobalCursor ;copy bgt cursor to global cursor pos
c61f: 20 f4 b4 jsr CalcCursorTilemapPos ;and calc the VRAM addr for that spot
c622: a9 05 lda #NC_GetCharUnderCursor
c624: 20 6a 89 jsr WaitForNmiSubcommand ;get the tile that's under the cursor
c627: a5 73 lda zpCharUnderCurs
c629: 85 91 sta vZpDestCursTileId ;and copy it as the destination cursor sprite graphic
c62b: a5 af lda vZpAmMove ;are we MOVE?
c62d: f0 09 beq @amCopy ;no, copy -> skip erasing existing tile
c62f: a9 20 lda #key_Space ;yes, move
c631: 85 73 sta zpCharUnderCurs
c633: a9 09 lda #NC_RestoreCharAtCursor
c635: 20 6a 89 jsr WaitForNmiSubcommand ;write space char to current tile pos (erasing existing)
c638: a9 ff @amCopy lda #$ff
c63a: 85 ae sta vZpDestStateOn ;turn on destination state
c63c: 20 b0 c4 jsr GetTileQuadPaletteNum ;copy tile's palette to dest cursor palette
c63f: a5 9c lda zpPaletteNum
c641: 85 88 sta vZpDestCursPalette
c643: 20 5f b6 jsr RestoreCursorPos ;restore global cursor
c646: 4c d3 c5 jmp BgtMvCpy_KbdPrompt ;-> go back to COPY/MOVE input prompt
BgtCpyMvUpdateDestCursor
c649: a5 ae lda vZpDestStateOn ;are we in "source" state?
c64b: f0 28 beq BgtCpyMvHideDestCursor ;yes -> hide the dest cursor sprite
c64d: a5 91 lda vZpDestCursTileId
c64f: 8d 11 02 sta oamSprTileId+16
c652: a5 88 lda vZpDestCursPalette
c654: 29 0f and #$0f ;get the sprite palette for dest cursor
c656: 8d 12 02 sta oamSprAttr+16 ;and set for dest cursor sprite
c659: ad 05 05 lda bgtCH ;get bgt cursor x pos
c65c: 18 clc
c65d: 69 02 adc #HMARGIN ;add 2 for the margin
c65f: 0a asl A ;times 8 (tiles -> pixels)
c660: 0a asl A
c661: 0a asl A
c662: 8d 13 02 sta oamSprXPos+16 ;save as dest cursor x pos
c665: ad 06 05 lda bgtCV ;get bgt cursor y pos
c668: 18 clc
c669: 69 03 adc #VMARGIN ;add 3 for margin
c66b: 0a asl A ;times 8 (tiles -> pixels)
c66c: 0a asl A
c66d: 0a asl A
c66e: 8d 10 02 sta oamSprYPos+16 ;save as dest cursor y pos
c671: ce 10 02 dec oamSprYPos+16 ;decrement by one (for framing?)
c674: 60 rts ;-> done
BgtCpyMvHideDestCursor
c675: a9 f8 lda #248
c677: 8d 10 02 sta oamSprYPos+16 ;move dest sprite offscreen to hide it
c67a: 60 rts
strSAVE_OR_LOAD_Q
c67b: 53 41 56 45+ .zstr “SAVE(S),LOAD(L)?”
c68c: 46 49 4c 45+ strFILE_NAME .zstr “FILE NAME"”
• Clear variables
c697: 20 08 c1 BgtMode_FILE jsr BgtRestoreTilesBehindMenu ;restore tiles from before menu display
c69a: 20 4c b7 jsr CarriageReturn
c69d: a9 16 lda #22
c69f: 85 49 sta zpCV ;LOCATE 0,22
c6a1: a9 1a lda #key_CtrlZ
c6a3: 20 75 b5 jsr PrintOneChar ;print Ctrl-Z, clearing to end of screen (rows 22,23)
c6a6: 20 96 ae jsr BgtFile_GoToScreen0 ;activate SCREEN 0
c6a9: 20 20 b4 jsr InitPromptChars ;restore normal text prompt characters
c6ac: 20 ab b4 jsr DoCLS ;clear screen 0
c6af: a9 7b lda #<strSAVE_OR_LOAD_Q
c6b1: 85 52 sta zpOutputStr
c6b3: a9 c6 lda #>strSAVE_OR_LOAD_Q
c6b5: 85 53 sta zpOutputStr+1
c6b7: 20 5a 89 jsr PrintString ;print "SAVE OR LOAD?"
c6ba: 20 20 bc jsr ReadChar ;prompt for keyboard char
c6bd: c9 53 cmp #‘S’
c6bf: f0 06 beq BgtFile_Save
c6c1: c9 4c cmp #‘L’
c6c3: f0 0e beq BgtFile_Load
c6c5: d0 09 bne bgtoolRestart_C6D0
c6c7: 20 75 b5 BgtFile_Save jsr PrintOneChar ;echo the typed 'S'
c6ca: 20 df c6 jsr BgtFile_PromptFilename ;prompt for the filename
c6cd: 20 0d c5 jsr SaveBackgroundMap ;save the background out to tape
bgtoolRestart_C6D0
c6d0: 4c d1 bf jmp CmdFn_BGTOOL ;-> restart BGTOOL
c6d3: 20 75 b5 BgtFile_Load jsr PrintOneChar ;echo the typed 'L'
c6d6: 20 df c6 jsr BgtFile_PromptFilename ;prompt for the filename
c6d9: 20 65 c5 jsr LoadBackgroundMap ;load the background from tape
c6dc: 4c d0 c6 jmp bgtoolRestart_C6D0 ;-> restart BGTOOL
BgtFile_PromptFilename
c6df: 20 6f b5 jsr DoCRLF ;cursor to start of next line
c6e2: a9 8c lda #<strFILE_NAME
c6e4: 85 52 sta zpOutputStr
c6e6: a9 c6 lda #>strFILE_NAME
c6e8: 85 53 sta zpOutputStr+1
c6ea: 20 5a 89 jsr PrintString ;print "FILENAME", followed by double-quote char
c6ed: 20 04 bd jsr ReadLine ;read line of input from user
c6f0: ad 00 05 lda lineBuffer
c6f3: c9 03 cmp #key_Stop ;did we get STOP key?
c6f5: f0 0d beq @abort ;yes -> abort load/save, restart BGTOOL in SELECT mode
c6f7: 20 a4 8b jsr InitLineBufferScanning ;point TXTPTR at lineBuffer
c6fa: a9 09 lda #$09 ;lineBuffer includes "FILE NAME" in it;
c6fc: 85 09 sta zpTXTPTR ; skip over that prompt (but not double-quote char)
c6fe: 20 52 9e jsr GetFileNameArg ;get the filename into the "cassette header" area of buffer
c701: 4c 9d 9e jmp FileNameCpyToLineBuffer ;-> and copy that back to the start of lineBuffer (erasing "FILE NAME" prompt and dquot)
c704: 68 @abort pla ;pull up caller subrtn's return address
c705: 68 pla
c706: 4c d0 c6 jmp bgtoolRestart_C6D0 ;-> and restart BGTOOL
c709: 39 c7 tbl_MoveTables .dd2 mtbl_SideProfiles ;Mario
c70b: 39 c7 .dd2 mtbl_SideProfiles ;Lady
c70d: 45 c7 .dd2 mtbl_StaticFacing ;Fighter Fly
c70f: 4b c7 .dd2 mtbl_EightDir ;Achilles
c711: 51 c7 .dd2 mtbl_Penguin ;Penguin
c713: 3f c7 .dd2 mtbl_TwoFrame ;Fireball
c715: 4b c7 .dd2 mtbl_EightDir ;Car
c717: 45 c7 .dd2 mtbl_StaticFacing ;Spinner
c719: 57 c7 .dd2 mtbl_Spaceships ;Star Killer
c71b: 57 c7 .dd2 mtbl_Spaceships ;Starship
c71d: 45 c7 .dd2 mtbl_StaticFacing ;Explosion
c71f: 45 c7 .dd2 mtbl_StaticFacing ;Smiley
c721: 5d c7 .dd2 mtbl_Laser ;Laser
c723: 3f c7 .dd2 mtbl_TwoFrame ;Shell Creeper
c725: 45 c7 .dd2 mtbl_StaticFacing ;Side Stepper
c727: 3f c7 .dd2 mtbl_TwoFrame ;Nitpicker
tbl_ActorSprShtOffsets
c729: 00 .dd1 0 ;Mario
c72a: 1c .dd1 28 ;Lady (added to tile IDs from ss_Mario)
c72b: 00 .dd1 0 ;Fighter Fly
c72c: 00 .dd1 0 ;Achilles
c72d: 00 .dd1 0 ;Penguin
c72e: 00 .dd1 0 ;Fireball
c72f: 38 .dd1 56 ;Car (added to tile IDs from ss_Achilles)
c730: 58 .dd1 88 ;Spinner (added to tile IDs from ss_FighterFly)
c731: 00 .dd1 0 ;Star Killer
c732: 0c .dd1 12 ;Starship (added to tile IDs from ss_StarKiller)
c733: 78 .dd1 120 ;Explosion (added to tile IDs from ss_FighterFly)
c734: 20 .dd1 32 ;Smiley (added to tile IDs from ss_FighterFly)
c735: 00 .dd1 0 ;Laser
c736: 48 .dd1 72 ;Shell Creeper (added to tile IDs from ss_Fireball)
c737: 88 .dd1 136 ;Side Stepper (added to tile IDs from ss_FighterFly)
c738: 58 .dd1 88 ;Nitpicker (added to tile IDs from ss_Fireball)
; Mario and Lady
mtbl_SideProfiles
c739: 63 c7 .dd2 fftbl_SideProfiles ;animation frame counts
c73b: 99 c7 .dd2 ss_Mario
c73d: 63 c7 .dd2 fftbl_SideProfiles ;tile flips
; Fireball, Shell Creeper, Nitpicker
c73f: 6c c7 mtbl_TwoFrame .dd2 fftbl_NoFlipsTwoFrames ;animation frame counts (always two)
c741: 05 c8 .dd2 ss_Fireball
c743: 7e c7 .dd2 fftbl_OnlyHFlips_TwoFOneSt ;tile flips (just horizontally)
; Fighter Fly, Spinner, Explosion, Smiley, Side Stepper
mtbl_StaticFacing
c745: 6c c7 .dd2 fftbl_NoFlipsTwoFrames ;animation frame counts (always two)
c747: ab c7 .dd2 ss_FighterFly
c749: 6c c7 .dd2 fftbl_NoFlipsTwoFrames ;tile flips (never flip)
; Achilles and Car
c74b: 6c c7 mtbl_EightDir .dd2 fftbl_NoFlipsTwoFrames ;animation frame counts (always two)
c74d: bd c7 .dd2 ss_Achilles
c74f: 75 c7 .dd2 fftbl_AllFlipsNoAnim ;tile flips (every direction)
; Penguin
c751: 7e c7 mtbl_Penguin .dd2 fftbl_OnlyHFlips_TwoFOneSt ;animation frame cnts (always two, except when stationary)
c753: cf c7 .dd2 ss_Penguin
c755: 7e c7 .dd2 fftbl_OnlyHFlips_TwoFOneSt ;tile flips (horizontal only)
; Star Killer and Starship
c757: 75 c7 mtbl_Spaceships .dd2 fftbl_AllFlipsNoAnim ;animation frame counts (always one: no animation!)
c759: e1 c7 .dd2 ss_StarKiller
c75b: 75 c7 .dd2 fftbl_AllFlipsNoAnim ;tile flips (every direction)
; Laser (identical to spaceships, except spritesheets)
c75d: 75 c7 mtbl_Laser .dd2 fftbl_AllFlipsNoAnim ;animation frame counts (always one: no animation!)
c75f: f3 c7 .dd2 ss_Laser
c761: 75 c7 .dd2 fftbl_AllFlipsNoAnim ;tile flips (every direction)
; The following four tables are pointed at by the first and last entries of the
; mtbl_* tables above, and are indexed by the 9 possible sprite movement
; directions.
;
; The high nybbles represent the horizontal and vertical "flip" to use with
; tiles in the actor's sprite sheets (bits 7 and 8 of the sprite object's sprite
; attr flags (in sprAttrFlags).
;
; The low nybble indicates how many frames of animation exist for that
; facing/posture.
;
; The structure of the mtbl_* vars allows mix-and-match between using the flip
; bits of one row, and the number-of-frames from a different row. For instance,
; both the spaceships, and the sprites I've dubbed "EightDir" (Car, Achilles),
; can truly point in eight different directions (I'm not counting "stationary",
; here), and both use three separate sprite sheets in total, flipping and
; reordering the tiles of those three to make up the other five. So, they both
; use the same row to specify their "flip" bits for the various orientations.
; However, they differ in that Car and Achilles each have two frames of
; animation for each facing, while the spaceships only have one. Therefore, they
; use _different_ rows to specify the number of frames.
;
; Using "fftbl_" as the prefix for these tables; "ff" for "flips-n'-frames".
;
; fftbl_SideProfiles, the first of these, is used only by Mario and Lady, and is
; used for both the flips, and the animation frame counts.
fftbl_SideProfiles
c763: 01 02 41 43+ .bulk $01,$02,$41,$43,$41,$02,$01,$03,$01
; fftbl_NoFlipsTwoFrames: When used for animation frame counts, specifies two
; frames. When used for tile flips, never flips any tiles. Both regardless fo
; direction, including stationary.
fftbl_NoFlipsTwoFrames
c76c: 02 02 02 02+ .bulk $02,$02,$02,$02,$02,$02,$02,$02,$02
; fftbl_AllFlipsNoAnim: flips in all directions, all sprite sheets
; have one frame
fftbl_AllFlipsNoAnim
c775: 01 01 41 41+ .bulk $01,$01,$41,$41,$c1,$81,$81,$01,$01
; fftbl_OnlyHFlips_TwoFOneSt: Only does horizontal flips. Two frames for all
; animations, except no animation on "stationary" direction.
fftbl_OnlyHFlips_TwoFOneSt
c77e: 01 02 42 42+ .bulk $01,$02,$42,$42,$42,$02,$02,$02,$02
; Translation table for direction -> deltaX, deltaY. Indexed by sprite's
; movement direction (all actor types):
c787: 00 tbl_moveDeltaX .dd1 0 ;dir 0 (stationary)
c788: 00 tbl_moveDeltaY .dd1 0
c789: 00 .dd1 0 ;dir 1 (directly upward)
c78a: fe .dd1 -2
c78b: 02 .dd1 2 ;dir 2 (up/right)
c78c: fe .dd1 -2
c78d: 02 .dd1 2 ;dir 3 (right)
c78e: 00 .dd1 0
c78f: 02 .dd1 2 ;dir 4 (right/down)
c790: 02 .dd1 2
c791: 00 .dd1 0 ;dir 5 (down)
c792: 02 .dd1 2
c793: fe .dd1 -2 ;dir 6 (left/down)
c794: 02 .dd1 2
c795: fe .dd1 -2 ;dir 7 (left)
c796: 00 .dd1 0
c797: fe .dd1 -2 ;dur 8 (left/up
c798: fe .dd1 -2
; The ss_* tables below are sprite sheets, describing one 2x2 tilemap of a
; sprite for each possible facing value for a "DEF MOVE"-defined sprite.
;
; Most of these sheets are used for multiple actor (character) types, but the
; sprites listed correspond to just one specific actor. The other actors will
; add their value from tbl_ActorSprShtOffsets to each tile ID, to derive arrive
; at their own sprite tiles.
;
; If a given facing has multiple facing frames, these are typically derived by
; adding 4 to each tile ID for each additional frame. However, for the up or
; down animations for Penguin, Mario, and Lady, actor-specific code is used to
; handle the second frame, and directly specify the pointer to the 2x2 tilemap
; to be used (a horizontal mirror of the first frame).
c799: 37 c8 ss_Mario .dd2 spr_MarioStat
c79b: 33 c8 .dd2 spr_MarioClimb
c79d: 3f c8 .dd2 spr_MarioJumpRt
c79f: 2f c8 .dd2 spr_MarioRight
c7a1: 3f c8 .dd2 spr_MarioJumpRt
c7a3: 33 c8 .dd2 spr_MarioClimb
c7a5: 3b c8 .dd2 spr_MarioJumpLt
c7a7: 2b c8 .dd2 spr_MarioLeft
c7a9: 3b c8 .dd2 spr_MarioJumpLt
c7ab: 47 c8 ss_FighterFly .dd2 spr_FighterFly
c7ad: 47 c8 .dd2 spr_FighterFly
c7af: 47 c8 .dd2 spr_FighterFly
c7b1: 47 c8 .dd2 spr_FighterFly
c7b3: 47 c8 .dd2 spr_FighterFly
c7b5: 47 c8 .dd2 spr_FighterFly
c7b7: 47 c8 .dd2 spr_FighterFly
c7b9: 47 c8 .dd2 spr_FighterFly
c7bb: 47 c8 .dd2 spr_FighterFly
c7bd: 53 c8 ss_Achilles .dd2 spr_AchillesLeft
c7bf: 4b c8 .dd2 spr_AchillesUp
c7c1: 5f c8 .dd2 spr_AchillesUpRt
c7c3: 57 c8 .dd2 spr_AchillesRight
c7c5: 67 c8 .dd2 spr_AchillesDownRt
c7c7: 4f c8 .dd2 spr_AchillesDown
c7c9: 63 c8 .dd2 spr_AchillesDownLt
c7cb: 53 c8 .dd2 spr_AchillesLeft
c7cd: 5b c8 .dd2 spr_AchillesUpLt
c7cf: 8b c8 ss_Penguin .dd2 spr_PenguinDown
c7d1: 8f c8 .dd2 spr_PenguinUp
c7d3: 97 c8 .dd2 spr_PenguinRight
c7d5: 97 c8 .dd2 spr_PenguinRight
c7d7: 97 c8 .dd2 spr_PenguinRight
c7d9: 8b c8 .dd2 spr_PenguinDown
c7db: 93 c8 .dd2 spr_PenguinLeft
c7dd: 93 c8 .dd2 spr_PenguinLeft
c7df: 93 c8 .dd2 spr_PenguinLeft
c7e1: 6b c8 ss_StarKiller .dd2 spr_StKillUp
c7e3: 6b c8 .dd2 spr_StKillUp
c7e5: 7f c8 .dd2 spr_StKillUpRt
c7e7: 77 c8 .dd2 spr_StKillRight
c7e9: 87 c8 .dd2 spr_StKillDownRt
c7eb: 6f c8 .dd2 spr_StKillDown
c7ed: 83 c8 .dd2 spr_StKillDownLt
c7ef: 73 c8 .dd2 spr_StKillLeft
c7f1: 7b c8 .dd2 spr_StKillUpLt
c7f3: 1f c8 ss_Laser .dd2 spr_LaserUp
c7f5: 1f c8 .dd2 spr_LaserUp
c7f7: 27 c8 .dd2 spr_LaserUpLeft
c7f9: 23 c8 .dd2 spr_LaserLeft
c7fb: 27 c8 .dd2 spr_LaserUpLeft
c7fd: 1f c8 .dd2 spr_LaserUp
c7ff: 27 c8 .dd2 spr_LaserUpLeft
c801: 23 c8 .dd2 spr_LaserLeft
c803: 27 c8 .dd2 spr_LaserUpLeft
c805: 17 c8 ss_Fireball .dd2 spr_FballLeft
c807: 17 c8 .dd2 spr_FballLeft
c809: 1b c8 .dd2 spr_FballRight
c80b: 1b c8 .dd2 spr_FballRight
c80d: 1b c8 .dd2 spr_FballRight
c80f: 17 c8 .dd2 spr_FballLeft
c811: 17 c8 .dd2 spr_FballLeft
c813: 17 c8 .dd2 spr_FballLeft
c815: 17 c8 .dd2 spr_FballLeft
; spr_* quads below are sprite tilemaps; individual frames used in the sprite
; sheets above. Given in decimal to faciliate correlation with the Family BASIC
; manual (which gives sprite tiles in decimal).
c817: 70 spr_FballLeft .dd1 112
c818: 71 .dd1 113
c819: 72 .dd1 114
c81a: 73 .dd1 115
c81b: 71 spr_FballRight .dd1 113
c81c: 70 .dd1 112
c81d: 73 .dd1 115
c81e: 72 .dd1 114
c81f: d4 spr_LaserUp .dd1 212
c820: ef .dd1 239
c821: ef .dd1 239
c822: ef .dd1 239
c823: d0 spr_LaserLeft .dd1 208
c824: ef .dd1 239
c825: ef .dd1 239
c826: ef .dd1 239
c827: d2 spr_LaserUpLeft .dd1 210
c828: ef .dd1 239
c829: ef .dd1 239
c82a: ef .dd1 239
c82b: 00 spr_MarioLeft .dd1 0
c82c: 01 .dd1 1
c82d: 02 .dd1 2
c82e: 03 .dd1 3
c82f: 01 spr_MarioRight .dd1 1
c830: 00 .dd1 0
c831: 03 .dd1 3
c832: 02 .dd1 2
c833: 14 spr_MarioClimb .dd1 20
c834: 15 .dd1 21
c835: 16 .dd1 22
c836: 17 .dd1 23
c837: 18 spr_MarioStat .dd1 24
c838: 19 .dd1 25
c839: 1a .dd1 26
c83a: 1b .dd1 27
c83b: 0c spr_MarioJumpLt .dd1 12
c83c: 0d .dd1 13
c83d: 0e .dd1 14
c83e: 0f .dd1 15
c83f: 0d spr_MarioJumpRt .dd1 13
c840: 0c .dd1 12
c841: 0f .dd1 15
c842: 0e .dd1 14
c843: 15 spr_MarioClimb1 .dd1 21 ;2nd frame of climbing up/down. Horizontal mirror of spr_MarioClimb
c844: 14 .dd1 20
c845: 17 .dd1 23
c846: 16 .dd1 22
c847: 38 spr_FighterFly .dd1 56
c848: 39 .dd1 57
c849: 3a .dd1 58
c84a: 3b .dd1 59
c84b: 50 spr_AchillesUp .dd1 80
c84c: 51 .dd1 81
c84d: 52 .dd1 82
c84e: 53 .dd1 83
spr_AchillesDown
c84f: 52 .dd1 82
c850: 53 .dd1 83
c851: 50 .dd1 80
c852: 51 .dd1 81
spr_AchillesLeft
c853: 40 .dd1 64
c854: 41 .dd1 65
c855: 42 .dd1 66
c856: 43 .dd1 67
spr_AchillesRight
c857: 41 .dd1 65
c858: 40 .dd1 64
c859: 43 .dd1 67
c85a: 42 .dd1 66
spr_AchillesUpLt
c85b: 48 .dd1 72
c85c: 49 .dd1 73
c85d: 4a .dd1 74
c85e: 4b .dd1 75
spr_AchillesUpRt
c85f: 49 .dd1 73
c860: 48 .dd1 72
c861: 4b .dd1 75
c862: 4a .dd1 74
spr_AchillesDownLt
c863: 4a .dd1 74
c864: 4b .dd1 75
c865: 48 .dd1 72
c866: 49 .dd1 73
spr_AchillesDownRt
c867: 4b .dd1 75
c868: 4a .dd1 74
c869: 49 .dd1 73
c86a: 48 .dd1 72
c86b: a0 spr_StKillUp .dd1 160
c86c: a1 .dd1 161
c86d: a2 .dd1 162
c86e: a3 .dd1 163
c86f: a2 spr_StKillDown .dd1 162
c870: a3 .dd1 163
c871: a0 .dd1 160
c872: a1 .dd1 161
c873: 98 spr_StKillLeft .dd1 152
c874: 99 .dd1 153
c875: 9a .dd1 154
c876: 9b .dd1 155
c877: 99 spr_StKillRight .dd1 153
c878: 98 .dd1 152
c879: 9b .dd1 155
c87a: 9a .dd1 154
c87b: 9c spr_StKillUpLt .dd1 156
c87c: 9d .dd1 157
c87d: 9e .dd1 158
c87e: 9f .dd1 159
c87f: 9d spr_StKillUpRt .dd1 157
c880: 9c .dd1 156
c881: 9f .dd1 159
c882: 9e .dd1 158
spr_StKillDownLt
c883: 9e .dd1 158
c884: 9f .dd1 159
c885: 9c .dd1 156
c886: 9d .dd1 157
spr_StKillDownRt
c887: 9f .dd1 159
c888: 9e .dd1 158
c889: 9d .dd1 157
c88a: 9c .dd1 156
c88b: 68 spr_PenguinDown .dd1 104
c88c: 69 .dd1 105
c88d: 6a .dd1 106
c88e: 6b .dd1 107
c88f: 6c spr_PenguinUp .dd1 108
c890: 6d .dd1 109
c891: 6e .dd1 110
c892: 6f .dd1 111
c893: 60 spr_PenguinLeft .dd1 96
c894: 61 .dd1 97
c895: 62 .dd1 98
c896: 63 .dd1 99
spr_PenguinRight
c897: 61 .dd1 97
c898: 60 .dd1 96
c899: 63 .dd1 99
c89a: 62 .dd1 98
c89b: 6d spr_PenguinUp1 .dd1 109 ;2nd frame of upward walk. Horizontal mirror of spr_PenguinUp.
c89c: 6c .dd1 108
c89d: 6f .dd1 111
c89e: 6e .dd1 110
spr_PenguinDown1
c89f: 69 .dd1 105 ;2nd frame of downward walk. Horizontal mirror of spr_PenguinDown.
c8a0: 68 .dd1 104
c8a1: 6b .dd1 107
c8a2: 6a .dd1 106
AnimateAndToggleNextFrame
c8a3: 8a txa ;push X
c8a4: 48 pha
c8a5: 98 tya ;push Y
c8a6: 48 pha
c8a7: 20 b4 c8 jsr Animate ;run the animations!
c8aa: 68 pla
c8ab: a8 tay ;pull Y
c8ac: 68 pla
c8ad: aa tax ;pull X
c8ae: a9 00 lda #$00
c8b0: 85 ab sta zpNmiAnimNxtFr ;indicate next frame should do NMI subcmds, not animate
c8b2: 68 pla
c8b3: 40 rti ;return from interrupt
c8b4: a2 00 Animate ldx #$00
c8b6: 86 a9 stx zpNmiActorNum ;start with sprite #0
c8b8: a9 08 lda #$08
c8ba: 85 5d sta zpNmiAnimActCtr ;start with assumption that all 8 sprites are animated
c8bc: a6 a9 @sprLoop ldx zpNmiActorNum
c8be: bd 45 06 lda sprAttrFlags,x
c8c1: 29 10 and #SpAtt_ANIMATE ;is this sprite animated?
c8c3: d0 26 bne AnimateOne ;yes -> handle
c8c5: c6 5d dec zpNmiAnimActCtr ;no: decrement animated sprites counter
c8c7: f0 0a beq @disableAnims ;# animated sprites == 0? -> disable animation flag
c8c9: e8 inx ;(useless)
c8ca: e6 a9 AnimateCont inc zpNmiActorNum
c8cc: a5 a9 lda zpNmiActorNum
c8ce: c9 08 cmp #$08 ;have we done all 8 sprites yet?
c8d0: d0 ea bne @sprLoop ;no -> go to next sprite
c8d2: 60 rts ;return
c8d3: a9 00 @disableAnims lda #$00
c8d5: 85 ac sta zpNmiAnimate
c8d7: 60 rts
c8d8: bd c5 06 MoveExpired lda defMovSMotion,x ;reset total movement remaining
c8db: 9d c3 06 sta defMovSMotionCtr,x
c8de: a6 a9 ldx zpNmiActorNum
c8e0: bd 45 06 lda sprAttrFlags,x
c8e3: 29 ef and #$ef
c8e5: 9d 45 06 sta sprAttrFlags,x ;stop animation for this sprite
c8e8: 4c ca c8 jmp AnimateCont ;continue on to next sprite
; Handle animation of one sprite
c8eb: a5 a9 AnimateOne lda zpNmiActorNum ;sprite number...
c8ed: 0a asl A
c8ee: 0a asl A
c8ef: 0a asl A ;...times eight...
c8f0: 85 a6 sta zpNmiDefMovIdx
c8f2: aa tax ;...gives offset to start of this sprite's defMovS* vars
c8f3: de c2 06 dec defMovSSpdCtr,x ;decrement speed counter. Is it zero?
c8f6: d0 d2 bne AnimateCont ;no -> skip animating this sprite this frame (throttle speed)
c8f8: bd c4 06 lda defMovSSpeed,x ;yes: reset the counter to user-specified speed value
c8fb: 9d c2 06 sta defMovSSpdCtr,x
c8fe: de c3 06 dec defMovSMotionCtr,x ;decrement movement counter. Is it zero?
c901: f0 d5 beq MoveExpired ;yes -> reached max motion; stop animating this sprite
c903: bd c1 06 lda defMovSActorFrm,x ;no: get MOVE actor id and frame number
c906: 29 0f and #$0f ;mask for the actor id nybble (removing frame number)
c908: a8 tay
c909: b9 29 c7 lda tbl_ActorSprShtOffsets,y
c90c: 85 dd sta zpNmiSprShtOff
c90e: 98 tya
c90f: 0a asl A ;multiply actor id * 2, to get entry in tbl_MoveTables
c910: a8 tay
c911: b9 09 c7 lda tbl_MoveTables,y ;get the pointer entry into zpNmiCurMTbl
c914: 85 a7 sta zpNmiCurMTbl
c916: b9 0a c7 lda tbl_MoveTables+1,y
c919: 85 a8 sta zpNmiCurMTbl+1
c91b: bd c0 06 lda defMovSFacing,x ;get the sprite's direction
c91e: 0a asl A ; * 2 for pointer entry
c91f: a8 tay
c920: b9 87 c7 lda tbl_moveDeltaX,y ;look up sprite's horizontal motion
c923: 18 clc
c924: 7d c6 06 adc defMovSPosX,x
c927: 9d c6 06 sta defMovSPosX,x ;and add it to sprite's horizontal position (move the sprite)
c92a: 18 clc
c92b: b9 88 c7 lda tbl_moveDeltaY,y ;look up sprite's vertical motion
c92e: 7d c7 06 adc defMovSPosY,x
c931: 9d c7 06 sta defMovSPosY,x ;and add it to sprite's vert position (move the sprite)
c934: a0 00 ldy #$00
c936: b1 a7 lda (zpNmiCurMTbl),y ;get a pointer to this sprite's frame counts vector
c938: 85 d5 sta zpNmiCurFrCnts
c93a: c8 iny
c93b: b1 a7 lda (zpNmiCurMTbl),y
c93d: 85 d6 sta zpNmiCurFrCnts+1
c93f: bd c0 06 lda defMovSFacing,x ;get sprite's facing again
c942: a8 tay
c943: b1 d5 lda (zpNmiCurFrCnts),y ;use that to get frame count for cur facing's animation
c945: 29 0f and #$0f ;mask out h/v tile flip info, we just want frame cnt
c947: 20 53 ad jsr MulABy16 ;move low nybble to high
c94a: 29 ff and #$ff ;(for comparison below?)
c94c: 85 d7 sta zpNmiNumFrames ;and save
c94e: f0 20 beq AnimOne_skipAnim ;(never branches! should've compared to #$01 first)
c950: bd c1 06 lda defMovSActorFrm,x ;get the sprite's actor id and frame num
c953: 29 f0 and #$f0 ;mask out the id, just want frm num (high nybble)
c955: 18 clc
c956: 69 10 adc #$10 ;increment frame count
c958: c5 d7 cmp zpNmiNumFrames ;did we exceed maximum frame count for this facing?
c95a: d0 02 bne @noRst ;no -> don't reset
c95c: a9 00 lda #$00 ;yes: reset frame num to zero
c95e: 48 @noRst pha ;push frame number
c95f: a9 0f lda #$0f
c961: 3d c1 06 and defMovSActorFrm,x
c964: 9d c1 06 sta defMovSActorFrm,x ;mask out sprite's frame num, and save
c967: 68 pla
c968: 48 pha
c969: 1d c1 06 ora defMovSActorFrm,x
c96c: 9d c1 06 sta defMovSActorFrm,x ;save our new frame num in sprite's info
c96f: 68 pla
AnimOne_skipAnim
c970: 4a lsr A
c971: 4a lsr A ;Multiply by 4 and save in zpNmiNumFrames
c972: 85 d7 sta zpNmiNumFrames ; which is now an offset to add to tile IDs, to get cur frame's tilemap
c974: a0 02 ldy #$02
c976: b1 a7 lda (zpNmiCurMTbl),y ;fetch a pointer to the facings spritsheet for cur spr
c978: 85 d9 sta zpNmiCurSprSht
c97a: c8 iny
c97b: b1 a7 lda (zpNmiCurMTbl),y
c97d: 85 da sta zpNmiCurSprSht+1
c97f: bd c0 06 lda defMovSFacing,x ;get facing
c982: 0a asl A
c983: a8 tay
c984: b1 d9 lda (zpNmiCurSprSht),y ;index spritesheet by the facing
c986: 85 db sta zpNmiSprTileMp ;save pointer to the 2x2 tilemap
c988: c8 iny
c989: b1 d9 lda (zpNmiCurSprSht),y
c98b: 85 dc sta zpNmiSprTileMp+1
c98d: bd c0 06 lda defMovSFacing,x ;get facing yet again
c990: c9 01 cmp #$01 ;is it facing #1 (straight up)?
c992: f0 04 beq @oneOrFive
c994: c9 05 cmp #$05 ;is it facing #1 (up) or #5 (down)?
c996: d0 50 bne AnimOne_ordinaryAnim ;no -> handle normally
c998: bd c1 06 @oneOrFive lda defMovSActorFrm,x ;yes: get actor id
c99b: 29 0f and #$0f ;mask out frame num
c99d: f0 23 beq AnimOne_marioOrLady
c99f: c9 01 cmp #$01
c9a1: f0 1f beq AnimOne_marioOrLady ;id 0 (Mario) or 1 (Lady)? -> handle specially
c9a3: c9 04 cmp #$04
c9a5: d0 41 bne AnimOne_ordinaryAnim ;NOT id 4 (penguin)? -> handle normally
c9a7: a5 d7 lda zpNmiNumFrames ;(If we're here, we are the penguin)
c9a9: f0 33 beq AnimOne_frameZero ;anim frame 0? -> no alterations
c9ab: bd c0 06 lda defMovSFacing,x ;get facing
c9ae: c9 05 cmp #$05 ;facing 5 (down)?
c9b0: f0 08 beq AnimOne_penguinDown ;yes -> deal with that
c9b2: a9 9b lda #<spr_PenguinUp1 ;no: we're facing 1 (up)
c9b4: 85 db sta zpNmiSprTileMp ;set the tilemap ptr value directly
c9b6: a9 c8 lda #>spr_PenguinUp1
c9b8: d0 12 bne AnimOne_setTMapHiByt
AnimOne_penguinDown
c9ba: a9 9f lda #<spr_PenguinDown1 ;set the tilemap ptr value directly
c9bc: 85 db sta zpNmiSprTileMp
c9be: a9 c8 lda #>spr_PenguinDown1
c9c0: d0 0a bne AnimOne_setTMapHiByt
AnimOne_marioOrLady
c9c2: a5 d7 lda zpNmiNumFrames
c9c4: f0 18 beq AnimOne_frameZero ;anim frame 0? -> no alterations
c9c6: a9 43 lda #<spr_MarioClimb1 ;2nd frame: use alternate (h. mirrored) tilemap
c9c8: 85 db sta zpNmiSprTileMp ;(doesn't matter if we're climbing up or down)
c9ca: a9 c8 lda #>spr_MarioClimb1
AnimOne_setTMapHiByt
c9cc: 85 dc sta zpNmiSprTileMp+1
c9ce: a4 a9 ldy zpNmiActorNum
c9d0: a9 40 lda #$40 ;ensure horizontal tile flipping is on for mirrored sprite
c9d2: 19 45 06 ora sprAttrFlags,y
c9d5: 99 45 06 sta sprAttrFlags,y
c9d8: a9 00 lda #$00 ;clear tile ID offsets for animation frame; we took care of it
c9da: 85 d7 sta zpNmiNumFrames
c9dc: f0 0a beq AnimOne_ordinaryAnim
AnimOne_frameZero
c9de: a4 a9 ldy zpNmiActorNum
c9e0: b9 45 06 lda sprAttrFlags,y
c9e3: 29 3f and #$3f ;clear tile flipping (frame zero of up/down anim, so not mirrored)
c9e5: 99 45 06 sta sprAttrFlags,y
AnimOne_ordinaryAnim
c9e8: a5 a9 lda zpNmiActorNum
c9ea: 20 53 ad jsr MulABy16 ;get index into DMA buffer for this sprite number
c9ed: aa tax
c9ee: a0 00 ldy #$00
c9f0: b1 db lda (zpNmiSprTileMp),y ;get tile 0 ID
c9f2: 18 clc
c9f3: 65 dd adc zpNmiSprShtOff ;add offset for this actor (character)
c9f5: 65 d7 adc zpNmiNumFrames ;and the offset for the anim frame
c9f7: 9d 81 02 sta oamBuffer+129,x ;save to PPU DMA area
c9fa: c8 iny
c9fb: b1 db lda (zpNmiSprTileMp),y ;..repeat for tile 1 ID...
c9fd: 18 clc
c9fe: 65 dd adc zpNmiSprShtOff
ca00: 65 d7 adc zpNmiNumFrames
ca02: 9d 85 02 sta oamBuffer+133,x
ca05: c8 iny
ca06: b1 db lda (zpNmiSprTileMp),y ;..repeat for tile 2 ID...
ca08: 18 clc
ca09: 65 dd adc zpNmiSprShtOff
ca0b: 65 d7 adc zpNmiNumFrames
ca0d: 9d 89 02 sta oamBuffer+137,x
ca10: c8 iny
ca11: b1 db lda (zpNmiSprTileMp),y ;..repeat for (final) tile 3 ID...
ca13: 18 clc
ca14: 65 dd adc zpNmiSprShtOff
ca16: 65 d7 adc zpNmiNumFrames
ca18: 9d 8d 02 sta oamBuffer+141,x
ca1b: a4 a9 ldy zpNmiActorNum
ca1d: b9 45 06 lda sprAttrFlags,y ;get the sprite attrs
ca20: 9d 82 02 sta oamBuffer+130,x ;and copy to each of the four sprite tiles
ca23: 9d 86 02 sta oamBuffer+134,x
ca26: 9d 8a 02 sta oamBuffer+138,x
ca29: 9d 8e 02 sta oamBuffer+142,x
ca2c: a4 a6 ldy zpNmiDefMovIdx
ca2e: b9 c6 06 lda defMovSPosX,y
ca31: 9d 83 02 sta oamBuffer+131,x ;copy sprite X position to tile 0 X pos
ca34: 9d 8b 02 sta oamBuffer+139,x ;and tile 2 X pos
ca37: 18 clc
ca38: 69 08 adc #$08 ;add 8
ca3a: 9d 87 02 sta oamBuffer+135,x ;and save that as tile 1 X pos
ca3d: 9d 8f 02 sta oamBuffer+143,x ;and tile 3
ca40: b9 c7 06 lda defMovSPosY,y
ca43: 9d 80 02 sta oamBuffer+128,x ;copy sprite Y position to tile 0 Y pos
ca46: 9d 84 02 sta oamBuffer+132,x ;and tile 2 Y pos
ca49: 18 clc
ca4a: 69 08 adc #$08 ;add 8
ca4c: 9d 88 02 sta oamBuffer+136,x ;and save that as tile 1 Y pos
ca4f: 9d 8c 02 sta oamBuffer+140,x ;and tile 3
ca52: 4c ca c8 jmp AnimateCont ;Done with this sprite! On to the next.
ca55: 4c 94 84 errIllVal_CA55 jmp ErrorIllegalValue
ca58: 4c 91 84 errSyn_CA58 jmp ErrorSyntax
; Not in jtbl_Commands: in reality, this is the "DEF" command when immediately
; followed by the "MOVE" token. CmdFn_DEF jumps to here in that case.
vZpSprMvTbl .var $19 {addr/2}
vZpSprFacingFlipTbl .var $1b {addr/2}
vZpSavedX .var $ad {addr/1}
ca5b: 20 8c 90 CmdFn_DEFMOVE jsr TxtPtrIncr
ca5e: 20 7a a8 jsr SkipLParenOrSynErr ;parse '('
ca61: 20 6e a2 jsr EvalByteInteger ;get sprite #
ca64: 20 44 af jsr ErrUnlessZeroThruSeven ;demand it be less than 8
ca67: 20 7d a8 jsr SkipRParenOrSynErr ;parse ')'
ca6a: ad 00 04 lda exprParseStk ;sprite #
ca6d: aa tax
ca6e: a9 e7 lda #$e7
ca70: 3d 45 06 and sprAttrFlags,x
ca73: 9d 45 06 sta sprAttrFlags,x ;clear sprite attr flags SpAtt_ANIMATE and SpAtt_DEFMOVE
ca76: 8a txa
ca77: 85 a4 sta zpActorNum ;store sprite number
ca79: 0a asl A
ca7a: 0a asl A
ca7b: 0a asl A
ca7c: 85 a5 sta zpDefMovIdx ;store offset for defMovS* vars
ca7e: 20 d9 86 jsr SkipEqualsOrErr ;parse '='
ca81: 20 bf 90 jsr TxtSkipSpaces
ca84: c9 8a cmp #tok_SPRITE ;parse 'SPRITE'
ca86: d0 d0 bne errSyn_CA58 ;(or error)
ca88: 20 8c 90 jsr TxtPtrIncr
ca8b: 20 7a a8 jsr SkipLParenOrSynErr ;parse '('
ca8e: 20 7d cb jsr DefMvEvalFirstArg ;get first arg after SPRITE
ca91: c9 10 cmp #$10
ca93: b0 c0 bcs errIllVal_CA55 ;>= 16? -> error
ca95: 9d c1 06 sta defMovSActorFrm,x ;first arg is charactor/actor id
ca98: 20 75 cb jsr DefMvEvalNextArg ;2nd arg
ca9b: 86 ad stx vZpSavedX ;save X away (...isn't it already saved in zpDefMovIdx?)
ca9d: 85 28 sta zpWAccum ;save 2nd arg to WAccum
ca9f: c9 09 cmp #$09 ;<= 8?
caa1: 90 13 bcc @argFits ;yes -> leave arg val alone
caa3: a9 08 lda #$08
caa5: 85 2c sta zpWParam
caa7: a9 00 lda #$00
caa9: 85 29 sta zpWAccum+1
caab: 85 2d sta zpWParam+1
caad: 20 0e a8 jsr ModAndReplaceExpr ;get value of (arg MOD 8)
cab0: a5 28 lda zpWAccum
cab2: d0 02 bne @argFits
cab4: e6 28 inc zpWAccum ;add one if MOD gave zero (forcing to 1..7)
cab6: a5 28 @argFits lda zpWAccum
cab8: a6 ad ldx vZpSavedX
caba: 9d c0 06 sta defMovSFacing,x ;save (possibly force-fitted) 2nd arg as facing
cabd: bd c1 06 lda defMovSActorFrm,x ;get character/actor
cac0: 0a asl A ;multiply times 2
cac1: a8 tay
cac2: b9 09 c7 lda tbl_MoveTables,y ;Multiple indirections! We're doing something like:
cac5: 85 19 sta vZpSprMvTbl ; moveTables[actor id]->[2]->[facing]
cac7: b9 0a c7 lda tbl_MoveTables+1,y ;index tbl_MoveTables by actor id
caca: 85 1a sta vZpSprMvTbl+1 ; to get movement tables for this actor
cacc: a0 04 ldy #$04 ;indicate actor-specific table by [2] (bytes #4 and #5)
cace: b1 19 lda (vZpSprMvTbl),y
cad0: 85 1b sta vZpSprFacingFlipTbl
cad2: c8 iny
cad3: b1 19 lda (vZpSprMvTbl),y
cad5: 85 1c sta vZpSprFacingFlipTbl+1
cad7: bd c0 06 lda defMovSFacing,x ;get the movement direction
cada: a8 tay
cadb: b1 1b lda (vZpSprFacingFlipTbl),y ;and index by that
cadd: 29 f0 and #$f0 ;mask to obtain just horizontal and vertical flip
cadf: a4 a4 ldy zpActorNum
cae1: 48 pha
cae2: b9 45 06 lda sprAttrFlags,y ;clear horizontal and vertical flip in sprite's flags
cae5: 29 3f and #$3f
cae7: 99 45 06 sta sprAttrFlags,y
caea: 68 pla
caeb: 19 45 06 ora sprAttrFlags,y ;and insert the ones we got from the table lookups
caee: 99 45 06 sta sprAttrFlags,y
caf1: 20 84 cb jsr DefMvMaybeParseRParen ;are we at ')'?
caf4: b0 71 bcs DefMvFinish ;yes -> finish up
caf6: 20 75 cb jsr DefMvEvalNextArg ;no: get 3rd arg (speed)
caf9: b0 0b bcs @arg4 ;empty arg (comma)? -> skip this one
cafb: 9d c2 06 sta defMovSSpdCtr,x ;set frames-til-move
cafe: 9d c4 06 sta defMovSSpeed,x ;and initializer for same
cb01: 20 84 cb jsr DefMvMaybeParseRParen ;are we at ')'?
cb04: b0 61 bcs DefMvFinish ;yes -> finish up
cb06: 20 75 cb @arg4 jsr DefMvEvalNextArg ;no: get 4th arg (movement distance)
cb09: b0 0d bcs @arg5 ;empty arg (comma)? -> skip this one
cb0b: 69 01 adc #$01 ;increment
cb0d: 9d c3 06 sta defMovSMotionCtr,x ;set as how far we have left to move
cb10: 9d c5 06 sta defMovSMotion,x ;and initializer for same
cb13: 20 84 cb jsr DefMvMaybeParseRParen ;are we at ')'?
cb16: b0 4f bcs DefMvFinish ;yes -> finish up
cb18: 20 75 cb @arg5 jsr DefMvEvalNextArg ;no: get 5th arg (display priority)
cb1b: b0 24 bcs @arg6 ;empty arg (comma)? -> skip this one
cb1d: a0 00 ldy #$00 ;"fore" (empty) flag
cb1f: ad 00 04 lda exprParseStk
cb22: 20 8d b0 jsr ErrUnlessZeroOrOne
cb25: 29 ff and #$ff
cb27: f0 02 beq @fore ;zero? -> skip next instr
cb29: a0 20 ldy #SpAtt_BEHIND ;"behind" flag
cb2b: a6 a4 @fore ldx zpActorNum
cb2d: bd 45 06 lda sprAttrFlags,x
cb30: 29 df and #$df ;clear fore/behind flag in sprite attr flags
cb32: 9d 45 06 sta sprAttrFlags,x
cb35: 98 tya
cb36: 1d 45 06 ora sprAttrFlags,x ;store new fore/behind flag in sprite attr flags
cb39: 9d 45 06 sta sprAttrFlags,x
cb3c: 20 84 cb jsr DefMvMaybeParseRParen ;are we at ')'?
cb3f: b0 26 bcs DefMvFinish ;yes -> finish up
cb41: 20 75 cb @arg6 jsr DefMvEvalNextArg ;no: get 6th arg (color palette #)
cb44: c9 04 cmp #$04
cb46: b0 2a bcs errIllVal_CB72 ;-> error if arg >= 4
cb48: a6 a4 ldx zpActorNum
cb4a: 48 pha ;push arg
cb4b: bd 45 06 lda sprAttrFlags,x
cb4e: 29 fc and #$fc
cb50: 9d 45 06 sta sprAttrFlags,x ;clear palette info from sprite attr flags
cb53: 68 pla ;pull arg
cb54: 1d 45 06 ora sprAttrFlags,x ;combine with other attr flags
cb57: 9d 45 06 sta sprAttrFlags,x ;set combined flags
cb5a: 20 7d a8 jsr SkipRParenOrSynErr ;must end with ')' here
cb5d: a9 78 lda #120
cb5f: a6 a5 ldx zpDefMovIdx
cb61: 9d c6 06 sta defMovSPosX,x ;center sprite in the middle of the screen
cb64: 9d c7 06 sta defMovSPosY,x ;(only if all 6 SPRITE arguments were provided)
cb67: a6 a4 DefMvFinish ldx zpActorNum
cb69: bd 45 06 lda sprAttrFlags,x
cb6c: 09 08 ora #SpAtt_DEFMOVE ;indicate DEF MOVE completed
cb6e: 9d 45 06 sta sprAttrFlags,x
cb71: 60 rts
cb72: 4c 94 84 errIllVal_CB72 jmp ErrorIllegalValue
DefMvEvalNextArg
cb75: 20 14 ac jsr SkipCommaOrSynErr ;parse comma
cb78: 20 92 cb jsr DefMvSecIfEmptyArg ;is it an empty arg?
cb7b: b0 13 bcs DefMvSecAndRts ;yes -> return with carry set
DefMvEvalFirstArg
cb7d: 20 6e a2 jsr EvalByteInteger ;no: parse byte arg
cb80: a6 a5 ldx zpDefMovIdx ;reload x
cb82: 18 clc ;and return with carry unset
cb83: 60 rts
DefMvMaybeParseRParen
cb84: 20 bf 90 jsr TxtSkipSpaces
cb87: c9 29 cmp #‘)’ ;did we get a ')'?
cb89: f0 02 beq DefMvAdvTxtPtrAndSetCarry ;yes -> move past it, set carry
cb8b: 18 clc ;no: clear carry
cb8c: 60 rts ;return
DefMvAdvTxtPtrAndSetCarry
cb8d: 20 8c 90 jsr TxtPtrIncr
cb90: 38 DefMvSecAndRts sec
cb91: 60 rts
DefMvSecIfEmptyArg
cb92: 20 bf 90 jsr TxtSkipSpaces
cb95: c9 2c cmp #‘,’
cb97: f0 f7 beq DefMvSecAndRts
cb99: 18 clc
cb9a: 60 rts
• Clear variables
cb9b: 20 6e a2 CmdFn_POSITION jsr EvalByteInteger ;get first arg
cb9e: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure 0-7
cba1: 0a asl A ;multiply by 8
cba2: 0a asl A
cba3: 0a asl A
cba4: 85 a5 sta zpDefMovIdx
cba6: 20 75 cb jsr DefMvEvalNextArg ;get next arg
cba9: 9d c6 06 sta defMovSPosX,x ;set actor's new X pos (in its DEF MOVE data)
cbac: 20 75 cb jsr DefMvEvalNextArg
cbaf: 9d c7 06 sta defMovSPosY,x ;set actor's new Y pos (in its DEF MOVE data)
cbb2: 60 rts ;return
cbb3: 20 6e a2 CmdFn_MOVE jsr EvalByteInteger ;evaluate argument
cbb6: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure it's 0 thru 7
cbb9: aa tax
cbba: bd 45 06 lda sprAttrFlags,x ;load sprite flags
cbbd: 29 08 and #SpAtt_DEFMOVE ;check if DEF MOVE was defined for this sprite
cbbf: f0 08 beq @skipArg ;no -> do nothing for this arg
cbc1: a9 10 lda #SpAtt_ANIMATE
cbc3: 1d 45 06 ora sprAttrFlags,x
cbc6: 9d 45 06 sta sprAttrFlags,x ;set the SpAtt_ANIMATE flag for this move;
cbc9: 20 9c 84 @skipArg jsr IsEndOfCmd ;NMI handler will start animating it
cbcc: f0 06 beq @animsOn ;end of command? -> close up
cbce: 20 14 ac jsr SkipCommaOrSynErr ;otherwise, we should have a comma here. Skip it.
cbd1: 4c b3 cb jmp CmdFn_MOVE ;and evaluate next arg
cbd4: a9 ff @animsOn lda #$ff
cbd6: 85 ac sta zpNmiAnimate ;tell NMI hanlder there are animations to perform
cbd8: 60 rts_CBD8 rts
vZpEraOrCut .var $75 {addr/1} ;#$FF if ERA; #$00 if CUT
cbd9: a9 ff CmdFn_ERA lda #$ff ;ERA mode
cbdb: ae ldx ▼ a:$00a9 ;(disable next instr)
cbdc: a9 00 CmdFn_CUT lda #$00 ;CUT mode
cbde: 85 75 sta vZpEraOrCut ;save mode
cbe0: 20 6e a2 jsr EvalByteInteger ;eval arg
cbe3: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure it's 0-7
cbe6: a8 tay
cbe7: b9 45 06 lda sprAttrFlags,y
cbea: 29 ef and #$ef
cbec: 99 45 06 sta sprAttrFlags,y ;disable animation for the sprite
cbef: a5 75 lda vZpEraOrCut ;check ERA or CUT
cbf1: f0 03 beq @skipEra ;CUT? -> skip erase
cbf3: 20 97 cc jsr EraseSprite
cbf6: 20 9c 84 @skipEra jsr IsEndOfCmd ;any other args?
cbf9: f0 dd beq rts_CBD8 ;no -> done
cbfb: 20 14 ac jsr SkipCommaOrSynErr ;skip a comma
cbfe: a5 75 lda vZpEraOrCut
cc00: f0 da beq CmdFn_CUT ;and loop to start of command again
cc02: d0 d5 bne CmdFn_ERA
• Clear variables
vZpActorNum .var $74 {addr/1}
cc04: 20 4e a9 FnFn_CRASH jsr EvalParenZeroThruSevenOrError ;parse left paren, and actor number
cc07: a5 28 lda zpWAccum ;<- UNNECESSARY, we already have the value in both A reg and X reg.
cc09: 85 74 sta vZpActorNum ;save actor num aside
cc0b: bd 45 06 lda sprAttrFlags,x
cc0e: 29 08 and #SpAtt_DEFMOVE ;does actor has a DEF MOVE set?
cc10: f0 43 beq CRASH_retMinusTwo ;no -> return -2 (as mandated by the manual)
cc12: 20 44 a9 jsr GetActorDefMoveIdx ;multiply by 8 to get index to DEF MOVE data
cc15: bd c6 06 lda defMovSPosX,x
cc18: 85 6b sta zpSavedCH ;grab and save actor's X pos
cc1a: bd c7 06 lda defMovSPosY,x
cc1d: 85 6c sta zpSavedCV ;grab and save actor's Y pos
cc1f: a9 00 lda #$00
cc21: 85 28 sta zpWAccum ;set current actor-to-check to actor #0
cc23: a0 08 ldy #NUM_ACTORS ;set Y reg to num actors (for count-down)
cc25: a5 74 CRASH_actorLoop lda vZpActorNum
cc27: c5 28 cmp zpWAccum ;is the requested actor # same as one we're checking?
cc29: f0 1c beq CRASH_nextActor ;yes -> skip this one (can't "crash" with self)
cc2b: 20 44 a9 jsr GetActorDefMoveIdx ;else, multiply check-actor # by 8 to get index
cc2e: bd c6 06 lda defMovSPosX,x ;get X pos
cc31: 38 sec
cc32: e5 6b sbc zpSavedCH ;subtract user-specified actor's X pos from that
cc34: 20 5d cc jsr CRASH_cmpAbs16 ;compare absolute value with 16
cc37: b0 0e bcs CRASH_nextActor ; >= 16? -> no collision. Next actor
cc39: bd c7 06 lda defMovSPosY,x ;get Y pos of check-actor
cc3c: 38 sec
cc3d: e5 6c sbc zpSavedCV ;subtract our actor's Y pos
cc3f: 20 5d cc jsr CRASH_cmpAbs16 ;compare absolute value with 16
cc42: b0 03 bcs CRASH_nextActor ; >= 16? -> no collision, check next actor
cc44: 4c 7e a9 jmp SkipRParenSetParseStkInt ;-> collision! Return this actor's number.
cc47: e6 28 CRASH_nextActor inc zpWAccum ;incr actor-to-check's number
cc49: 88 dey ;decr Y (count-down # actors)
cc4a: d0 d9 bne CRASH_actorLoop ;actors remaining to check? -> check next
cc4c: a9 ff lda #$ff ;set WAccum to -1
cc4e: 85 28 sta zpWAccum
cc50: 85 29 @retMinus sta zpWAccum+1 ;set WAccum high byte
cc52: 4c 7e a9 jmp SkipRParenSetParseStkInt ;parse closing paren, return WAccum via expr parse stack
CRASH_retMinusTwo
cc55: a9 fe lda #$fe ;set WAccum to -2
cc57: 85 28 sta zpWAccum
cc59: a9 ff lda #$ff
cc5b: d0 f3 bne @retMinus ;(always) -> parse closing paren, and return WAccum
cc5d: b0 0a CRASH_cmpAbs16 bcs @pos ;positive difference? -> skip negation
cc5f: 85 75 sta zpHeldKeyChar ;save original difference
cc61: a9 ff lda #-1 ;<- could've set this to zero, and skipped the clc/adc at end
cc63: 38 sec
cc64: e5 75 sbc zpHeldKeyChar ;subtract difference from -1
cc66: 18 clc
cc67: 69 01 adc #$01 ;add zero so it's subtraction from zero (straight negation)
cc69: c9 10 @pos cmp #$10 ;have positive. Cmp with 16, and return
cc6b: 60 rts_CC6B rts
• Clear variables
cc6c: 20 6e a2 CmdFn_CAN jsr EvalByteInteger ;get actor # arg
cc6f: 20 44 af jsr ErrUnlessZeroThruSeven ;ensure < 8
cc72: 48 pha ;push actor num
cc73: a8 tay
cc74: a9 00 lda #$00
cc76: 99 45 06 sta sprAttrFlags,y ;clear actor's sprite flags (including animation)
cc79: 20 97 cc jsr EraseSprite ;erase the sprite from screen
cc7c: 68 pla ;pull actor # again
cc7d: 0a asl A
cc7e: 0a asl A
cc7f: 0a asl A ;multiply by 8, for indexing
cc80: aa tax
cc81: a9 00 lda #$00
cc83: 9d c0 06 sta defMovSFacing,x ;clear facing and position
cc86: 9d c6 06 sta defMovSPosX,x
cc89: 9d c7 06 sta defMovSPosY,x
cc8c: 20 9c 84 jsr IsEndOfCmd ;more args?
cc8f: f0 da beq rts_CC6B ;no -> done!
cc91: 20 14 ac jsr SkipCommaOrSynErr ;more args: ensure comma first.
cc94: 4c 6c cc jmp CmdFn_CAN ; then loop back to the top!
cc97: 98 EraseSprite tya
cc98: 20 53 ad jsr MulABy16
cc9b: a8 tay
cc9c: a9 f8 lda #$f8
cc9e: 99 80 02 sta oamBuffer+128,y ;move the actor's sprites off-screen so they're hidden
cca1: 99 84 02 sta oamBuffer+132,y
cca4: 99 88 02 sta oamBuffer+136,y
cca7: 99 8c 02 sta oamBuffer+140,y
ccaa: 60 rts
tbl_KeywordTokens
ccab: 80 .dd1 $80
ccac: 47 4f 54 4f .str “GOTO”
ccb0: 81 .dd1 $81
ccb1: 47 4f 53 55+ .str “GOSUB”
ccb6: 82 .dd1 $82
ccb7: 52 55 4e .str “RUN”
ccba: 83 .dd1 $83
ccbb: 52 45 54 55+ .str “RETURN”
ccc1: 84 .dd1 $84
ccc2: 52 45 53 54+ .str “RESTORE”
ccc9: 85 .dd1 $85
ccca: 54 48 45 4e .str “THEN”
ccce: 86 .dd1 $86
cccf: 4c 49 53 54 .str “LIST”
ccd3: 87 .dd1 $87
ccd4: 53 59 53 54+ .str “SYSTEM” ;all commands above this line take line number arguments
ccda: 88 .dd1 $88
ccdb: 54 4f .str “TO”
ccdd: 89 .dd1 $89
ccde: 53 54 45 50 .str “STEP”
cce2: 8a .dd1 $8a
cce3: 53 50 52 49+ .str “SPRITE”
cce9: 8b .dd1 $8b
ccea: 50 52 49 4e+ .str “PRINT”
ccef: 8c .dd1 $8c
ccf0: 46 4f 52 .str “FOR”
ccf3: 8d .dd1 $8d
ccf4: 4e 45 58 54 .str “NEXT”
ccf8: 8e .dd1 $8e
ccf9: 50 41 55 53+ .str “PAUSE”
ccfe: 8f .dd1 $8f
ccff: 49 4e 50 55+ .str “INPUT”
cd04: 90 .dd1 $90
cd05: 4c 49 4e 50+ .str “LINPUT”
cd0b: 91 .dd1 $91
cd0c: 44 41 54 41 .str “DATA”
cd10: 92 .dd1 $92
cd11: 49 46 .str “IF”
cd13: 93 .dd1 $93
cd14: 52 45 41 44 .str “READ”
cd18: 94 .dd1 $94
cd19: 44 49 4d .str “DIM”
cd1c: 95 .dd1 $95
cd1d: 52 45 4d .str “REM”
cd20: 96 .dd1 $96
cd21: 53 54 4f 50 .str “STOP”
cd25: 97 .dd1 $97
cd26: 43 4f 4e 54 .str “CONT”
cd2a: 98 .dd1 $98
cd2b: 43 4c 53 .str “CLS”
cd2e: 99 .dd1 $99
cd2f: 43 4c 45 41+ .str “CLEAR”
cd34: 9a .dd1 $9a
cd35: 4f 4e .str “ON”
cd37: 9b .dd1 $9b
cd38: 4f 46 46 .str “OFF”
cd3b: 9c .dd1 $9c
cd3c: 43 55 54 .str “CUT”
cd3f: 9d .dd1 $9d
cd40: 4e 45 57 .str “NEW”
cd43: 9e .dd1 $9e
cd44: 50 4f 4b 45 .str “POKE”
cd48: 9f .dd1 $9f
cd49: 43 47 53 45+ .str “CGSET”
cd4e: a0 .dd1 $a0
cd4f: 56 49 45 57 .str “VIEW”
cd53: a1 .dd1 $a1
cd54: 4d 4f 56 45 .str “MOVE”
cd58: a2 .dd1 $a2
cd59: 45 4e 44 .str “END”
cd5c: a3 .dd1 $a3
cd5d: 50 4c 41 59 .str “PLAY”
cd61: a4 .dd1 $a4
cd62: 42 45 45 50 .str “BEEP”
cd66: a5 .dd1 $a5
cd67: 4c 4f 41 44 .str “LOAD”
cd6b: a6 .dd1 $a6
cd6c: 53 41 56 45 .str “SAVE”
cd70: a7 .dd1 $a7
cd71: 50 4f 53 49+ .str “POSITION”
cd79: a8 .dd1 $a8
cd7a: 4b 45 59 .str “KEY”
cd7d: a9 .dd1 $a9
cd7e: 43 4f 4c 4f+ .str “COLOR”
cd83: aa .dd1 $aa
cd84: 44 45 46 .str “DEF”
cd87: ab .dd1 $ab
cd88: 43 47 45 4e .str “CGEN”
cd8c: ac .dd1 $ac
cd8d: 53 57 41 50 .str “SWAP”
cd91: ad .dd1 $ad
cd92: 43 41 4c 4c .str “CALL”
cd96: ae .dd1 $ae
cd97: 4c 4f 43 41+ .str “LOCATE”
cd9d: af .dd1 $af
cd9e: 50 41 4c 45+ .str “PALET”
cda3: b0 .dd1 $b0
cda4: 45 52 41 .str “ERA”
cda7: b1 .dd1 $b1
cda8: 54 52 .str “TR”
cdaa: b2 .dd1 $b2
cdab: 46 49 4e 44 .str “FIND”
cdaf: b3 .dd1 $b3
cdb0: 47 41 4d 45 .str “GAME”
cdb4: b4 .dd1 $b4
cdb5: 42 47 54 4f+ .str “BGTOOL”
cdbb: b5 .dd1 $b5
cdbc: 41 55 54 4f .str “AUTO”
cdc0: b6 .dd1 $b6
cdc1: 44 45 4c 45+ .str “DELETE”
cdc7: b7 .dd1 $b7
cdc8: 52 45 4e 55+ .str “RENUM”
cdcd: b8 .dd1 $b8
cdce: 46 49 4c 54+ .str “FILTER”
cdd4: b9 .dd1 $b9
cdd5: 43 4c 49 43+ .str “CLICK”
cdda: ba .dd1 $ba
cddb: 53 43 52 45+ .str “SCREEN”
cde1: bb .dd1 $bb
cde2: 42 41 43 4b+ .str “BACKUP”
cde8: bc .dd1 $bc
cde9: 45 52 52 4f+ .str “ERROR”
cdee: bd .dd1 $bd
cdef: 52 45 53 55+ .str “RESUME”
cdf5: be .dd1 $be
cdf6: 42 47 50 55+ .str “BGPUT”
cdfb: bf .dd1 $bf
cdfc: 42 47 47 45+ .str “BGGET”
ce01: c0 .dd1 $c0
ce02: 43 41 4e .str “CAN”
ce05: ef .dd1 $ef
ce06: 58 4f 52 .str “XOR”
ce09: f0 .dd1 $f0
ce0a: 4f 52 .str “OR”
ce0c: f1 .dd1 $f1
ce0d: 41 4e 44 .str “AND”
ce10: f2 .dd1 $f2
ce11: 4e 4f 54 .str “NOT”
ce14: f3 .dd1 $f3
ce15: 3c 3e .str “<>”
ce17: f4 .dd1 $f4
ce18: 3e 3d .str “>=”
ce1a: f5 .dd1 $f5
ce1b: 3c 3d .str “<=”
ce1d: f6 .dd1 $f6
ce1e: 3d .dd1 ‘=’
ce1f: f7 .dd1 $f7
ce20: 3e .dd1 ‘>’
ce21: f8 .dd1 $f8
ce22: 3c .dd1 ‘<’
ce23: f9 .dd1 $f9
ce24: 2b .dd1 ‘+’
ce25: fa .dd1 $fa
ce26: 2d .dd1 ‘-’
ce27: fb .dd1 $fb
ce28: 4d 4f 44 .str “MOD”
ce2b: fc .dd1 $fc
ce2c: 2f .dd1 ‘/’
ce2d: fd .dd1 $fd
ce2e: 2a .dd1 ‘*’
ce2f: ca .dd1 $ca ;all keywords below this line are built-in functions
ce30: 41 42 53 .str “ABS”
ce33: cb .dd1 $cb
ce34: 41 53 43 .str “ASC”
ce37: cc .dd1 $cc
ce38: 53 54 52 24 .str “STR$”
ce3c: cd .dd1 $cd
ce3d: 46 52 45 .str “FRE”
ce40: ce .dd1 $ce
ce41: 4c 45 4e .str “LEN”
ce44: cf .dd1 $cf
ce45: 50 45 45 4b .str “PEEK”
ce49: d0 .dd1 $d0
ce4a: 52 4e 44 .str “RND”
ce4d: d1 .dd1 $d1
ce4e: 53 47 4e .str “SGN”
ce51: d2 .dd1 $d2
ce52: 53 50 43 .str “SPC”
ce55: d3 .dd1 $d3
ce56: 54 41 42 .str “TAB”
ce59: d4 .dd1 $d4
ce5a: 4d 49 44 24 .str “MID$”
ce5e: d5 .dd1 $d5
ce5f: 53 54 49 43+ .str “STICK”
ce64: d6 .dd1 $d6
ce65: 53 54 52 49+ .str “STRIG”
ce6a: d7 .dd1 $d7
ce6b: 58 50 4f 53 .str “XPOS”
ce6f: d8 .dd1 $d8
ce70: 59 50 4f 53 .str “YPOS”
ce74: d9 .dd1 $d9
ce75: 56 41 4c .str “VAL”
ce78: da .dd1 $da
ce79: 50 4f 53 .str “POS”
ce7c: db .dd1 $db
ce7d: 43 53 52 4c+ .str “CSRLIN”
ce83: dc .dd1 $dc
ce84: 43 48 52 24 .str “CHR$”
ce88: dd .dd1 $dd
ce89: 48 45 58 24 .str “HEX$”
ce8d: de .dd1 $de
ce8e: 49 4e 4b 45+ .str “INKEY$”
ce94: df .dd1 $df
ce95: 52 49 47 48+ .str “RIGHT$”
ce9b: e0 .dd1 $e0
ce9c: 4c 45 46 54+ .str “LEFT$”
cea1: e1 .dd1 $e1
cea2: 53 43 52 24 .str “SCR$”
cea6: e2 .dd1 $e2
cea7: 49 4e 53 54+ .str “INSTR”
ceac: e3 .dd1 $e3
cead: 43 52 41 53+ .str “CRASH”
ceb2: e4 .dd1 $e4
ceb3: 45 52 52 .str “ERR”
ceb6: e5 .dd1 $e5
ceb7: 45 52 4c .str “ERL”
ceba: e6 .dd1 $e6
cebb: 56 43 54 .str “VCT”
cebe: ff .dd1 $ff
cebf: c6 96 jtbl_Commands .dd2 CmdFn_GOTO
cec1: 48 96 .dd2 CmdFn_GOSUB
cec3: b0 86 .dd2 CmdFn_RUN
cec5: a6 96 .dd2 CmdFn_RETURN
cec7: c9 9e .dd2 CmdFn_RESTORE
cec9: 91 84 .dd2 ErrorSyntax
cecb: 67 87 .dd2 CmdFn_LIST
cecd: 8f 81 .dd2 CmdFn_SYSTEMorBACKUP
cecf: 91 84 .dd2 ErrorSyntax
ced1: 91 84 .dd2 ErrorSyntax
ced3: fc af .dd2 CmdFn_SPRITE
ced5: c5 88 .dd2 CmdFn_PRINT
ced7: fc 97 .dd2 CmdFn_FOR
ced9: 87 98 .dd2 CmdFn_NEXT
cedb: 16 97 .dd2 CmdFn_PAUSE
cedd: 3c 99 .dd2 CmdFn_INPUT
cedf: 6c 9a .dd2 CmdFn_LINPUT
cee1: 67 84 .dd2 CmdFn_DATAorREM
cee3: 82 97 .dd2 CmdFn_IF
cee5: 36 9f .dd2 CmdFn_READ
cee7: 0c 9c .dd2 CmdFn_DIM
cee9: 67 84 .dd2 CmdFn_DATAorREM
ceeb: 5b 88 .dd2 CmdFn_STOP
ceed: 9c 88 .dd2 CmdFn_CONT
ceef: 0a ae .dd2 CmdFn_CLS
cef1: a4 97 .dd2 CmdFn_CLEAR
cef3: fd 9a .dd2 CmdFn_ON
cef5: 91 84 .dd2 ErrorSyntax
cef7: dc cb .dd2 CmdFn_CUT
cef9: b6 84 .dd2 CmdFn_NEW
cefb: 5d 97 .dd2 CmdFn_POKE
cefd: 4f af .dd2 CmdFn_CGSET
ceff: 53 b1 .dd2 CmdFn_VIEW
cf01: b3 cb .dd2 CmdFn_MOVE
cf03: 6a 82 .dd2 CmdFn_END
cf05: 1e a0 .dd2 CmdFn_PLAY
cf07: d0 bc .dd2 CmdFn_BEEP
cf09: e1 9d .dd2 CmdFn_LOAD
cf0b: 64 9d .dd2 CmdFn_SAVE
cf0d: 9b cb .dd2 CmdFn_POSITION
cf0f: 68 9b .dd2 CmdFn_KEY
cf11: ca c4 .dd2 CmdFn_COLOR
cf13: 98 b0 .dd2 CmdFn_DEF
cf15: d1 ae .dd2 CmdFn_CGEN
cf17: 0e 9a .dd2 CmdFn_SWAP
cf19: 0a 97 .dd2 CmdFn_CALL
cf1b: 48 97 .dd2 CmdFn_LOCATE
cf1d: f3 ae .dd2 CmdFn_PALET
cf1f: d9 cb .dd2 CmdFn_ERA
cf21: 2b 96 .dd2 CmdFn_TR
cf23: 73 95 .dd2 CmdFn_FIND
cf25: 5b ad .dd2 CmdFn_GAME
cf27: d1 bf .dd2 CmdFn_BGTOOL
cf29: da 8b .dd2 CmdFn_AUTO
cf2b: 58 87 .dd2 CmdFn_DELETE
cf2d: 30 8c .dd2 CmdFn_RENUM
cf2f: b8 ae .dd2 CmdFn_FILTER
cf31: 25 96 .dd2 CmdFn_CLICK
cf33: 48 ae .dd2 CmdFn_SCREEN
cf35: 8f 81 .dd2 CmdFn_SYSTEMorBACKUP ;tok_BACKUP
cf37: 10 95 .dd2 CmdFn_ERROR
cf39: 27 95 .dd2 CmdFn_RESUME
cf3b: 01 b2 .dd2 CmdFn_BGPUT
cf3d: b8 b1 .dd2 CmdFn_BGGET
cf3f: 6c cc .dd2 CmdFn_CAN
cf41: 84 a9 jtbl_Functions .dd2 FnFn_ABS
cf43: 28 aa .dd2 FnFn_ASC
cf45: a6 aa .dd2 FnFn_STR_STR
cf47: 1f a9 .dd2 FnFn_FRE
cf49: 1d aa .dd2 FnFn_LEN
cf4b: 73 a9 .dd2 FnFn_PEEK
cf4d: bd a9 .dd2 FnFn_RND
cf4f: 9c a9 .dd2 FnFn_SGN
cf51: 91 84 .dd2 ErrorSyntax ;SPC, only allowed in PRINT
cf53: 91 84 .dd2 ErrorSyntax ;TAB, only allowed in PRINT
cf55: 81 ac .dd2 FnFn_MID_STR
cf57: 38 ad .dd2 FnFn_STICK
cf59: 47 ad .dd2 FnFn_STRIG
cf5b: 58 a9 .dd2 FnFn_XPOS
cf5d: 61 a9 .dd2 FnFn_YPOS
cf5f: df aa .dd2 FnFn_VAL
cf61: 33 a9 .dd2 FnFn_POS
cf63: e6 a8 .dd2 FnFn_CSRLIN
cf65: 82 aa .dd2 FnFn_CHR_STR
cf67: d6 aa .dd2 FnFn_HEX_STR
cf69: 58 ab .dd2 FnFn_INKEY_STR
cf6b: 41 ac .dd2 FnFn_RIGHT_STR
cf6d: ed ab .dd2 FnFn_LEFT_STR
cf6f: 3b aa .dd2 FnFn_SCR_STR
cf71: af ab .dd2 FnFn_INSTR
cf73: 04 cc .dd2 FnFn_CRASH
cf75: 0d a9 .dd2 FnFn_ERR
cf77: 14 a9 .dd2 FnFn_ERL
cf79: 6a a9 .dd2 FnFn_VCT
cf7b: 4e 53 2d 48+ strNS_HUDSON .zstr “NS-HUBASIC V3.0 ”
strCOPYRIGHT_MSG
cf8c: b4 20 4e 49+ .zstr $b4,“ NINTENDO/SHARP/HUDSON”
tbl_CgsetBgPaletteSets
cfa4: ae cf .dd2 cgsetBgPalSet0
cfa6: be cf .dd2 cgsetBgPalSet1
tbl_CgsetSprPaletteSets
cfa8: ce cf .dd2 cgsetSprPalSet0
cfaa: de cf .dd2 cgsetSprPalSet1
cfac: ee cf .dd2 cgsetSprPalSet2
cfae: 0f 2c 15 07 cgsetBgPalSet0 .bulk $0f,$2c,$15,$07
cfb2: 0f 27 21 12 .bulk $0f,$27,$21,$12
cfb6: 0f 29 36 17 .bulk $0f,$29,$36,$17
cfba: 0f 30 26 07 .bulk $0f,$30,$26,$07
cfbe: 0f 30 21 02 cgsetBgPalSet1 .bulk $0f,$30,$21,$02 ;defailt set
cfc2: 0f 30 27 18 .bulk $0f,$30,$27,$18
cfc6: 0f 30 27 16 .bulk $0f,$30,$27,$16
cfca: 0f 29 36 17 .bulk $0f,$29,$36,$17
cfce: 0f 36 16 02 cgsetSprPalSet0 .bulk $0f,$36,$16,$02
cfd2: 0f 27 30 19 .bulk $0f,$27,$30,$19
cfd6: 0f 35 25 17 .bulk $0f,$35,$25,$17
cfda: 0f 30 27 16 .bulk $0f,$30,$27,$16
cfde: 0f 30 16 01 cgsetSprPalSet1 .bulk $0f,$30,$16,$01 ;default set
cfe2: 0f 10 00 01 .bulk $0f,$10,$00,$01
cfe6: 0f 30 29 09 .bulk $0f,$30,$29,$09
cfea: 0f 30 16 07 .bulk $0f,$30,$16,$07
cfee: 0f 30 26 12 cgsetSprPalSet2 .bulk $0f,$30,$26,$12
cff2: 0f 30 15 12 .bulk $0f,$30,$15,$12
cff6: 0f 30 12 16 .bulk $0f,$30,$12,$16
cffa: 0f 30 26 19 .bulk $0f,$30,$26,$19
cffe: ff ff .bulk $ff,$ff
; The built-in background map for use with "GAME n,1"
gameBackgroundMap
d000: 20 20 20 20+ .bulk $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$32,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$34,$20,$20,$20,$20,$d7,$20,$d7,$20,$d7,$20,$20,$20,$20
+ $20,$20,$c0,$c0,$c0,$d2,$20,$20,$20,$20,$20,$39,$20,$20,$20,$20
+ $20,$c0,$c0,$c0,$ed,$ed,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$20,$20
+ $20,$20,$20,$20,$20,$d2,$20,$20,$c0,$c0,$c0,$c0,$c0,$c0,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$e3,$20,$d2,$20,$20,$20,$20,$20,$20,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$e3,$20,$20,$38,$20,$20,$20
+ $20,$20,$c0,$c0,$c0,$d2,$20,$20,$20,$20,$20,$e3,$20,$20,$d2,$20
+ $20,$20,$d7,$20,$c0,$c0,$c0,$d2,$c0,$c0,$c0,$c0,$c0,$c0,$20,$20
+ $20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$c0,$c0,$c0,$c0,$d2,$20
+ $20,$c0,$c0,$c0,$c0,$20,$20,$d2,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$d2,$20,$d7,$31,$20,$20,$20,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$e3,$20,$20,$20,$20
+ $20,$20,$c0,$d2,$c0,$c0,$c0,$c0,$c0,$d2,$20,$20,$20,$20,$d2,$20
+ $20,$20,$e3,$20,$20,$20,$20,$d2,$20,$20,$c0,$c0,$c0,$c0,$20,$20
+ $20,$20,$20,$d2,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$d2,$c0
+ $c0,$c0,$c0,$c0,$c0,$20,$20,$d2,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$d2,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$35,$d2,$20,$20,$20,$e3,$20,$d2,$20,$20,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$d7,$20,$20,$20
+ $20,$20,$c0,$c0,$ed,$ed,$c0,$c0,$c0,$c0,$c0,$d2,$20,$20,$d2,$20
+ $20,$20,$e3,$20,$20,$37,$20,$d2,$20,$20,$20,$d2,$c0,$c0,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$d2,$20
+ $20,$c0,$c0,$c0,$c0,$c0,$c0,$d2,$20,$20,$20,$d2,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20
+ $20,$20,$20,$d7,$20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$d2,$20
+ $33,$d7,$20,$20,$20,$d7,$20,$20,$20,$d7,$20,$d2,$e3,$e3,$20,$20
+ $20,$20,$c0,$c0,$c0,$d2,$20,$20,$20,$20,$20,$d2,$20,$20,$d2,$c0
+ $c0,$c0,$ed,$ed,$c0,$c0,$ed,$ed,$c0,$c0,$c0,$d2,$c0,$c0,$20,$20
+ $20,$20,$20,$20,$20,$d2,$20,$20,$20,$20,$20,$d2,$20,$20,$d2,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$d2,$20,$20,$20,$20
+ $20,$20,$20,$36,$20,$d2,$20,$20,$20,$30,$20,$d2,$20,$20,$20,$20
+ $20,$e3,$20,$20,$20,$e3,$20,$20,$20,$e3,$20,$d2,$20,$20,$20,$20
+ $20,$20,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0
+ $c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$c0,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20
+ $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$03,$00,$00,$19,$0a,$00
+ $00,$c0,$00,$00,$08,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$30
+ $c0,$01,$00,$00,$30,$e0,$30,$00,$00,$00,$00,$00,$04,$04,$00,$00
+ $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
; "GAME 0" built-in BASIC program, tokenized
d400: 1d 0a 00 a3+ txtGame0 .bulk $1d,$0a,$00,$a3,$22,$4d,$31,$56,$39,$59,$32,$54,$33,$3a,$4d,$31
+ $56,$37,$59,$31,$54,$33,$3a,$4d,$31,$54,$33,$22,$00,$18,$14,$00
+ $a3,$22,$4f,$33,$45,$35,$47,$3a,$4f,$33,$43,$35,$43,$3a,$4f,$32
+ $47,$35,$47,$22,$00,$18,$1e,$00,$a3,$22,$4f,$34,$43,$4f,$33,$42
+ $34,$41,$31,$3a,$43,$43,$3a,$23,$46,$23,$46,$22,$00,$19,$28,$00
+ $a3,$22,$47,$34,$41,$31,$3a,$46,$34,$46,$31,$3a,$4f,$33,$44,$34
+ $4f,$32,$42,$31,$22,$00,$17,$32,$00,$a3,$22,$42,$34,$4f,$34,$43
+ $37,$3a,$46,$34,$45,$37,$3a,$47,$34,$43,$37,$22,$00,$40,$3c,$00
+ $98,$3a,$ae,$20,$09,$2c,$12,$0a,$00,$3a,$51,$f6,$cf,$28,$11,$16
+ $40,$29,$20,$f1,$20,$11,$04,$00,$3a,$8f,$22,$50,$4c,$41,$59,$20
+ $3f,$20,$59,$2f,$4e,$3d,$3e,$22,$2c,$59,$24,$3a,$92,$20,$59,$24
+ $f6,$22,$4e,$22,$20,$85,$20,$51,$f6,$12,$e7,$03,$00,$16,$46,$00
+ $98,$3a,$b0,$01,$2c,$02,$2c,$03,$2c,$04,$2c,$05,$2c,$06,$2c,$07
+ $2c,$08,$00,$12,$50,$00,$8a,$20,$9a,$3a,$9f,$01,$2c,$01,$3a,$55
+ $f6,$11,$15,$00,$00,$16,$5a,$00,$af,$42,$20,$01,$2c,$12,$0f,$00
+ $2c,$12,$30,$00,$2c,$12,$30,$00,$2c,$55,$00,$1d,$64,$00,$41,$24
+ $f6,$22,$41,$49,$49,$49,$49,$49,$49,$49,$49,$49,$49,$49,$49,$47
+ $49,$47,$47,$47,$47,$48,$22,$00,$15,$6e,$00,$42,$24,$f6,$22,$48
+ $48,$42,$48,$42,$42,$42,$45,$42,$45,$45,$45,$22,$00,$12,$78,$00
+ $4b,$24,$f6,$22,$41,$42,$43,$44,$45,$46,$47,$48,$49,$22,$00,$12
+ $82,$00,$49,$24,$f6,$22,$41,$42,$43,$47,$48,$49,$44,$45,$46,$22
+ $00,$1f,$8c,$00,$98,$3a,$ae,$0a,$2c,$12,$0a,$00,$3a,$8b,$22,$53
+ $54,$41,$52,$54,$20,$21,$21,$22,$3a,$8e,$12,$50,$00,$3a,$98,$00
+ $13,$96,$00,$58,$f6,$12,$0d,$00,$3a,$59,$f6,$12,$16,$00,$3a,$49
+ $f6,$01,$00,$14,$a0,$00,$81,$0b,$dc,$00,$3a,$ac,$41,$24,$2c,$42
+ $24,$3a,$81,$0b,$dc,$00,$00,$19,$aa,$00,$92,$20,$49,$f6,$01,$85
+ $ac,$4b,$24,$2c,$49,$24,$3a,$49,$f6,$02,$3a,$80,$0b,$a0,$00,$00
+ $0d,$b4,$00,$81,$0b,$36,$01,$3a,$81,$0b,$ae,$01,$00,$0f,$be,$00
+ $92,$20,$51,$f3,$12,$e7,$03,$85,$0b,$3c,$00,$00,$08,$c8,$00,$80
+ $0b,$46,$00,$00,$05,$d2,$00,$a2,$00,$12,$dc,$00,$4c,$f6,$ce,$28
+ $41,$24,$29,$3a,$8c,$50,$f6,$02,$88,$4c,$00,$1a,$e6,$00,$92,$28
+ $cf,$28,$11,$16,$40,$29,$20,$f1,$20,$11,$04,$00,$29,$f6,$51,$20
+ $85,$0b,$e6,$00,$00,$07,$f0,$00,$4b,$f6,$50,$00,$0d,$fa,$00,$92
+ $20,$49,$f6,$01,$85,$0b,$0e,$01,$00,$0b,$04,$01,$4b,$f6,$4c,$f9
+ $02,$fa,$50,$00,$17,$0e,$01,$43,$f6,$e2,$28,$4b,$24,$2c,$d4,$28
+ $41,$24,$2c,$4b,$2c,$02,$29,$29,$fa,$02,$00,$10,$18,$01,$56,$f6
+ $43,$fc,$04,$3a,$48,$f6,$43,$20,$fb,$04,$00,$1f,$22,$01,$58,$f6
+ $58,$f9,$48,$f9,$04,$fd,$28,$48,$f6,$03,$29,$3a,$59,$f6,$59,$f9
+ $56,$f9,$04,$fd,$28,$56,$f6,$03,$29,$00,$12,$2c,$01,$ae,$58,$2c
+ $59,$3a,$8b,$22,$cf,$22,$3b,$3a,$8d,$3a,$83,$00,$17,$36,$01,$59
+ $f6,$12,$16,$00,$3a,$53,$f6,$01,$3a,$45,$f6,$12,$0d,$00,$3a,$54
+ $f6,$01,$00,$17,$40,$01,$ae,$12,$12,$00,$2c,$12,$14,$00,$3a,$8b
+ $22,$53,$43,$4f,$52,$45,$3a,$22,$3b,$00,$1a,$4a,$01,$92,$28,$cf
+ $28,$11,$16,$40,$29,$20,$f1,$20,$11,$04,$00,$29,$f6,$51,$20,$85
+ $0b,$4a,$01,$00,$15,$54,$01,$54,$f6,$54,$f9,$02,$3a,$ae,$12,$18
+ $00,$2c,$12,$14,$00,$3a,$8b,$54,$00,$2c,$5e,$01,$58,$f6,$58,$f9
+ $02,$3a,$92,$e1,$28,$58,$2c,$59,$29,$f6,$22,$20,$22,$f1,$20,$58
+ $f5,$45,$20,$85,$20,$ae,$20,$58,$2c,$59,$3a,$8b,$22,$cf,$22,$3a
+ $80,$0b,$4a,$01,$00,$12,$68,$01,$59,$f6,$59,$fa,$02,$3a,$92,$59
+ $f8,$02,$85,$0b,$86,$01,$00,$12,$72,$01,$81,$0b,$9a,$01,$3a,$92
+ $58,$f6,$fa,$02,$85,$0b,$68,$01,$00,$08,$7c,$01,$80,$0b,$5e,$01
+ $00,$24,$86,$01,$92,$53,$f6,$01,$85,$53,$f6,$12,$0d,$00,$3a,$45
+ $f6,$12,$1a,$00,$3a,$59,$f6,$12,$14,$00,$3a,$81,$0b,$9a,$01,$3a
+ $80,$0b,$4a,$01,$00,$05,$90,$01,$83,$00,$33,$9a,$01,$58,$f6,$fa
+ $02,$3a,$8c,$43,$f6,$53,$20,$88,$45,$3a,$92,$e1,$28,$43,$2c,$59
+ $29,$f6,$22,$cf,$22,$f1,$20,$e1,$28,$43,$f9,$02,$2c,$59,$29,$f6
+ $22,$20,$22,$85,$20,$58,$f6,$43,$3a,$43,$f6,$45,$00,$07,$a4,$01
+ $8d,$3a,$83,$00,$38,$ae,$01,$57,$f6,$11,$34,$00,$3a,$8c,$4e,$f6
+ $01,$88,$02,$3a,$aa,$a1,$28,$4e,$29,$f6,$8a,$28,$4e,$2c,$04,$f9
+ $05,$fd,$4e,$2c,$02,$2c,$02,$2c,$01,$2c,$01,$29,$3a,$a7,$4e,$2c
+ $12,$ec,$00,$fd,$4e,$2c,$12,$c8,$00,$3a,$8d,$00,$1a,$b8,$01,$92
+ $28,$cf,$28,$11,$16,$40,$29,$20,$f1,$20,$11,$04,$00,$29,$f6,$51
+ $20,$85,$0b,$b8,$01,$00,$15,$c2,$01,$54,$f6,$54,$f9,$02,$3a,$ae
+ $12,$18,$00,$2c,$12,$14,$00,$3a,$8b,$54,$00,$14,$cc,$01,$a1,$01
+ $2c,$02,$3a,$92,$e3,$28,$01,$29,$f6,$02,$85,$0b,$e0,$01,$00,$08
+ $d6,$01,$80,$0b,$b8,$01,$00,$38,$e0,$01,$ae,$12,$0a,$00,$2c,$12
+ $0a,$00,$3a,$8b,$22,$50,$45,$52,$46,$45,$43,$54,$22,$3a,$8c,$4e
+ $f6,$01,$88,$12,$64,$00,$3a,$af,$42,$20,$01,$2c,$07,$2c,$11,$30
+ $00,$2c,$11,$30,$00,$2c,$55,$3a,$ac,$55,$2c,$57,$3a,$8d,$00,$1c
+ $ea,$01,$a3,$22,$4d,$31,$56,$32,$59,$31,$54,$32,$3a,$4d,$31,$56
+ $31,$30,$59,$31,$54,$32,$3a,$54,$32,$22,$00,$20,$f4,$01,$a3,$22
+ $4f,$33,$45,$33,$47,$23,$44,$47,$44,$47,$23,$44,$47,$3a,$4f,$31
+ $23,$42,$35,$42,$23,$41,$42,$3a,$52,$22,$00,$1f,$fe,$01,$a3,$22
+ $45,$47,$4f,$34,$43,$4f,$33,$45,$3a,$59,$32,$4f,$32,$23,$42,$36
+ $45,$33,$3a,$4f,$31,$43,$35,$43,$22,$00,$18,$08,$02,$a3,$22,$23
+ $47,$41,$4f,$34,$44,$43,$3a,$46,$35,$23,$46,$3a,$23,$43,$23,$43
+ $22,$00,$1d,$12,$02,$a3,$22,$4f,$33,$42,$23,$41,$42,$4f,$34,$41
+ $3a,$46,$33,$4f,$33,$44,$46,$41,$3a,$44,$36,$46,$33,$22,$00,$1e
+ $1c,$02,$a3,$22,$47,$4f,$33,$41,$4f,$34,$46,$4f,$33,$42,$3a,$45
+ $35,$44,$56,$31,$35,$3a,$47,$41,$23,$41,$42,$22,$00,$23,$26,$02
+ $a3,$22,$23,$42,$33,$47,$54,$33,$46,$47,$45,$31,$44,$56,$31,$32
+ $45,$37,$3a,$43,$39,$3a,$23,$42,$35,$54,$33,$47,$43,$37,$22,$00
+ $1a,$30,$02,$af,$42,$20,$01,$2c,$12,$0f,$00,$2c,$12,$30,$00,$2c
+ $12,$30,$00,$2c,$12,$15,$00,$3a,$83,$00,$00,$53,$44,$24,$f3,$22
+ $20,$22,$20,$85,$20,$4b,$44,$f6,$06,$20,$80,$20,$0b,$24,$04,$00
+ $1a,$10,$04,$92,$20,$53,$55,$24,$f3,$22,$20,$22,$20,$85,$20,$4b
+ $44,$f6,$02,$20,$80,$20,$0b,$24,$04,$00,$09,$1a,$04,$80,$20,$0b
+ $f2,$03,$00,$09,$24,$04,$27,$fe,$62,$9b,$67,$00,$37,$2e,$04,$aa
+ $20,$a1,$28,$46,$29,$f6,$8a,$20,$28,$12,$0e,$00,$2c,$4b,$44,$2c
+ $02,$2c,$05,$2c,$01,$2c,$04,$29,$3a,$a7,$20,$46,$2c,$4b,$58,$fd
+ $09,$f9,$12,$0c,$00,$2c,$4b,$59,$fd,$09,$f9,$12,$10,$00,$3a,$a1
+ $20,$46,$00,$0c,$38,$04,$27,$fe,$a8,$71,$65,$96,$6f,$3f,$00,$23
+ $42,$04,$92,$20,$e3,$28,$08,$29,$f4,$01,$20,$85,$20,$a3,$20,$22
+ $4f,$31,$42,$32,$41,$47,$46,$45,$44,$43,$22,$3a,$80,$20,$0b,$60
+ $04,$00,$07,$4c,$04,$27,$fe,$fe,$00,$09,$56,$04,$80,$20,$0b,$d6
+ $01,$00,$0b,$60,$04,$27,$fe,$fe,$fe,$64,$8b,$87,$00,$21,$6a,$04
+ $8c,$20,$4a,$58,$f6,$07,$20,$88,$20,$09,$3a,$93,$20,$53,$24,$3a
+ $ae,$20,$0a,$2c,$4a,$58,$3a,$8b,$20,$53,$24,$3a,$8d,$00,$07,$74
+ $04,$8a,$20,$9b,$00,$12,$7e,$04,$91,$20,$22,$fd,$fe,$fe,$fe,$fe
+ $fe,$fe,$fe,$fe,$fd,$22,$00,$12,$88,$04,$91,$20,$22,$fe,$fd,$20
+ $64,$6b,$7e,$61,$20,$fd,$fe,$22,$00,$12,$92,$04,$91,$20,$22,$fd
+ $fe,$fe,$fe,$fe,$fe,$fe,$fe,$fe,$fd,$22,$00,$16,$9c,$04,$53,$f6
+ $d6,$28,$01,$29,$3a,$92,$20,$53,$f6,$01,$20,$80,$20,$0b,$9c,$04
+ $00,$0d,$a6,$04,$92,$20,$53,$f6,$02,$20,$85,$20,$82,$00,$05,$b0
+ $04,$a2,$00,$00,$f6,$58,$4c,$fd,$09,$f9,$12,$0c,$00,$3a,$59,$f6
+ $59,$4c,$fd,$09,$f9,$08,$3a,$a7,$20,$08,$2c,$58,$2c,$59,$3a,$83
+ $00,$32,$70,$03,$ae,$20,$58,$42,$2c,$59,$4c,$fa,$02,$3a,$8b,$20
+ $22,$20,$22,$3a,$52,$f6,$52,$f9,$02,$3a,$57,$f6,$57,$f9,$12,$0f
+ $00,$3a,$ae,$20,$12,$0e,$00,$2c,$12,$17,$00,$3a,$8b,$20,$57,$3b
+ $3a,$83,$00,$5d,$7a,$03,$a0,$3a,$49,$f6,$01,$3a,$46,$f6,$01,$3a
+ $4b,$f6,$01,$3a,$43,$f6,$01,$3a,$52,$f6,$01,$3a,$5a,$f6,$12,$30
+ $00,$3a,$ae,$20,$12,$0a,$00,$2c,$12,$15,$00,$3a,$8b,$22,$54,$49
+ $4d,$45,$22,$3b,$54,$3a,$ae,$20,$07,$2c,$12,$16,$00,$3a,$8b,$22
+ $48,$49,$2d,$53,$43,$4f,$52,$45,$22,$3b,$48,$3a,$ae,$20,$0a,$2c
+ $12,$17,$00,$3a,$8b,$22,$53,$43,$4f,$52,$45,$22,$3b,$57,$3b,$00
+ $05,$84,$03,$83,$00,$14,$8e,$03,$27,$63,$86,$2d,$20,$6b,$95,$87
+ $20,$78,$20,$ad,$8a,$99,$86,$80,$00,$2c,$98,$03,$92,$20,$e5,$f6
+ $12,$30,$02,$20,$85,$20,$58,$4c,$f6,$fa,$12,$1b,$00,$fd,$28,$58
+ $4c,$f8,$02,$29,$3a,$81,$20,$0b,$66,$03,$3a,$a1,$20,$08,$3a,$bd
+ $20,$0b,$76,$02,$00,$2d,$a2,$03,$92,$20,$e5,$f6,$12,$86,$01,$20
+ $85,$20,$58,$4c,$f6,$fa,$12,$1b,$00,$fd,$28,$58,$4c,$f8,$01,$29
+ $3a,$81,$20,$0b,$66,$03,$3a,$53,$f6,$53,$31,$3a,$bd,$20,$0b,$26
+ $02,$00,$14,$ac,$03,$92,$20,$59,$4c,$f7,$12,$17,$00,$20,$85,$20
+ $bd,$20,$0b,$94,$02,$00,$1b,$b6,$03,$92,$20,$58,$4c,$f7,$12,$1b
+ $00,$20,$85,$20,$58,$4c,$f6,$01,$3a,$81,$20,$0b,$66,$03,$3a,$bd
+ $00,$1b,$c0,$03,$92,$20,$58,$4c,$f8,$01,$20,$85,$20,$58,$4c,$f6
+ $12,$1b,$00,$3a,$81,$20,$0b,$66,$03,$3a,$bd,$00,$18,$ca,$03,$92
+ $20,$58,$42,$f7,$12,$1b,$00,$20,$85,$20,$58,$42,$f6,$01
; "GAME 1" built-in BASIC program, tokenized
dbfe: 15 64 00 99+ txtGame1 .bulk $15,$64,$00,$99,$3a,$ab,$20,$03,$3a,$9f,$20,$02,$2c,$02,$3a,$ba
+ $20,$01,$2c,$02,$00,$17,$6e,$00,$8a,$20,$9a,$3a,$a3,$20,$22,$56
+ $31,$35,$54,$31,$3a,$56,$31,$35,$54,$31,$22,$00,$16,$78,$00,$92
+ $20,$e1,$28,$02,$2c,$03,$29,$f3,$22,$20,$22,$20,$80,$20,$0b,$f0
+ $00,$00,$09,$82,$00,$27,$fe,$81,$61,$8a,$00,$25,$8c,$00,$8c,$20
+ $4a,$58,$f6,$02,$20,$88,$20,$12,$15,$00,$20,$89,$20,$05,$3a,$8c
+ $20,$4a,$59,$f6,$03,$20,$88,$20,$12,$14,$00,$20,$89,$20,$04,$00
+ $24,$96,$00,$ae,$20,$4a,$58,$2c,$4a,$59,$3a,$8b,$20,$22,$e8,$ed
+ $ed,$ed,$e8,$22,$3a,$92,$20,$4a,$59,$f7,$12,$13,$00,$20,$80,$20
+ $0b,$b4,$00,$00,$17,$a0,$00,$ae,$20,$4a,$58,$2c,$4a,$59,$f9,$02
+ $3a,$8b,$20,$22,$ee,$20,$20,$20,$ee,$22,$00,$17,$aa,$00,$ae,$20
+ $4a,$58,$2c,$4a,$59,$f9,$03,$3a,$8b,$20,$22,$ee,$20,$20,$20,$ee
+ $22,$00,$07,$b4,$00,$8d,$3a,$8d,$00,$2f,$be,$00,$8c,$20,$53,$f6
+ $06,$20,$88,$20,$12,$11,$00,$20,$89,$20,$04,$3a,$ae,$20,$02,$2c
+ $53,$3a,$8b,$20,$22,$ec,$22,$3a,$ae,$20,$12,$19,$00,$2c,$53,$3a
+ $8b,$20,$22,$eb,$22,$3a,$8d,$00,$10,$c8,$00,$ae,$20,$02,$2c,$03
+ $3a,$8b,$20,$22,$ef,$22,$3b,$00,$22,$d2,$00,$8c,$20,$53,$f6,$01
+ $20,$88,$20,$05,$3a,$8b,$20,$22,$ed,$ed,$ed,$ea,$22,$3b,$3a,$8d
+ $3a,$8b,$20,$22,$ed,$ed,$ed,$f0,$22,$00,$12,$dc,$00,$ae,$20,$02
+ $2c,$12,$14,$00,$3a,$8b,$20,$22,$f1,$22,$3b,$00,$22,$e6,$00,$8c
+ $20,$53,$f6,$01,$20,$88,$20,$05,$3a,$8b,$20,$22,$ed,$ed,$ed,$e9
+ $22,$3b,$3a,$8d,$3a,$8b,$20,$22,$ed,$ed,$ed,$f2,$22,$00,$0a,$f0
+ $00,$27,$fe,$73,$88,$82,$78,$00,$27,$fa,$00,$ba,$20,$01,$2c,$01
+ $3a,$a0,$3a,$8b,$20,$22,$65,$61,$a0,$62,$6c,$88,$3f,$20,$20,$20
+ $41,$3d,$6e,$78,$7e,$7e,$20,$20,$20,$42,$3d,$6c,$88,$22,$00,$16
+ $04,$01,$4b,$f6,$d6,$28,$01,$29,$3a,$92,$20,$4b,$f6,$01,$20,$80
+ $20,$0b,$04,$01,$00,$0d,$0e,$01,$92,$20,$4b,$f6,$05,$20,$85,$20
+ $b4,$00,$15,$18,$01,$a3,$20,$22,$4f,$33,$43,$31,$44,$45,$3a,$4f
+ $33,$45,$31,$46,$47,$22,$00,$22,$22,$01,$50,$53,$24,$f6,$22,$31
+ $32,$33,$34,$35,$36,$37,$38,$39,$22,$3a,$50,$4c,$f6,$ce,$28,$50
+ $53,$24,$29,$3a,$50,$53,$f6,$02,$00,$0e,$2c,$01,$8c,$20,$53,$f6
+ $02,$20,$88,$20,$50,$4c,$00,$28,$36,$01,$20,$53,$58,$f6,$03,$f9
+ $d0,$28,$12,$18,$00,$29,$3a,$53,$59,$f6,$03,$f9,$d0,$28,$12,$12
+ $00,$29,$3a,$53,$24,$f6,$e1,$28,$53,$58,$2c,$53,$59,$29,$00,$21
+ $40,$01,$20,$92,$20,$53,$24,$f6,$22,$20,$22,$20,$f0,$20,$e2,$28
+ $50,$53,$24,$2c,$53,$24,$29,$f7,$01,$20,$80,$20,$0b,$36,$01,$00
+ $18,$4a,$01,$ae,$20,$53,$58,$2c,$53,$59,$3a,$8b,$20,$d4,$28,$50
+ $53,$24,$2c,$53,$2c,$02,$29,$00,$05,$54,$01,$8d,$00,$0a,$5e,$01
+ $27,$fe,$7c,$8c,$6c,$62,$00,$2c,$68,$01,$50,$58,$f6,$12,$0d,$00
+ $fd,$09,$f9,$12,$0c,$00,$3a,$50,$59,$f6,$03,$fd,$09,$f9,$12,$10
+ $00,$3a,$43,$f6,$04,$3a,$54,$f6,$01,$3a,$46,$57,$f6,$01,$3a,$46
+ $f6,$01,$00,$1d,$72,$01,$4b,$4e,$f6,$03,$3a,$57,$f6,$12,$db,$00
+ $3a,$4b,$58,$f6,$01,$3a,$4b,$59,$f6,$01,$3a,$4b,$44,$f6,$01,$00
+ $28,$7c,$01,$ae,$20,$01,$2c,$01,$3a,$8b,$20,$22,$20,$20,$20,$8b
+ $74,$78,$65,$9e,$22,$3b,$43,$3b,$22,$20,$20,$20,$20,$20,$20,$20
+ $73,$67,$72,$8c,$22,$3b,$54,$00,$28,$86,$01,$aa,$20,$a1,$28,$08
+ $29,$f6,$8a,$20,$28,$05,$2c,$01,$2c,$03,$2c,$05,$2c,$01,$2c,$01
+ $29,$3a,$a7,$20,$08,$2c,$50,$58,$2c,$50,$59,$3a,$a1,$20,$08,$00
+ $0d,$90,$01,$94,$20,$45,$28,$4b,$4e,$fa,$02,$29,$00,$0a,$9a,$01
+ $27,$fe,$65,$75,$a4,$88,$00,$10,$a4,$01,$8c,$20,$46,$f6,$01,$20
+ $88,$20,$4b,$4e,$fa,$02,$00,$1b,$ae,$01,$aa,$20,$a1,$28,$46,$29
+ $f6,$8a,$28,$12,$0e,$00,$2c,$02,$2c,$03,$2c,$05,$2c,$01,$2c,$04
+ $29,$00,$43,$b8,$01,$53,$58,$f6,$02,$f9,$d0,$28,$12,$1a,$00,$29
+ $3a,$53,$59,$f6,$12,$0a,$00,$f9,$d0,$28,$12,$0a,$00,$29,$3a,$92
+ $20,$e1,$28,$53,$58,$2c,$53,$59,$29,$f6,$22,$20,$22,$20,$f0,$20
+ $e1,$28,$53,$58,$2c,$53,$59,$fa,$02,$29,$f6,$22,$20,$22,$20,$80
+ $20,$0b,$b8,$01,$00,$1d,$c2,$01,$a7,$20,$46,$2c,$53,$58,$fd,$09
+ $f9,$12,$0c,$00,$2c,$53,$59,$fd,$09,$f9,$12,$18,$00,$3a,$a1,$20
+ $46,$00,$05,$cc,$01,$8d,$00,$0e,$d6,$01,$27,$fe,$fe,$fe,$81,$61
+ $8c,$fe,$fe,$fe,$00,$0e,$e0,$01,$27,$fe,$fe,$fe,$fe,$fe,$ae,$8c
+ $98,$8c,$00,$0e,$f4,$01,$27,$fe,$66,$2d,$61,$8c,$2f,$62,$9b,$67
+ $00,$24,$fe,$01,$53,$f6,$01,$3a,$53,$58,$f6,$01,$3a,$53,$59,$f6
+ $01,$3a,$4b,$f6,$d5,$28,$01,$29,$3a,$92,$20,$4b,$f6,$01,$20,$80
+ $20,$0b,$4e,$02,$00,$14,$08,$02,$92,$20,$4b,$f6,$02,$20,$85,$20
+ $53,$f6,$04,$3a,$53,$58,$f6,$02,$00,$15,$12,$02,$92,$20,$4b,$f6
+ $03,$20,$85,$20,$53,$f6,$08,$3a,$53,$58,$f6,$fa,$02,$00,$14,$1c
+ $02,$92,$20,$4b,$f6,$05,$20,$85,$20,$53,$f6,$06,$3a,$53,$59,$f6
+ $02,$00,$15,$26,$02,$92,$20,$4b,$f6,$09,$20,$85,$20,$53,$f6,$02
+ $3a,$53,$59,$f6,$fa,$02,$00,$23,$2b,$02,$50,$58,$f6,$28,$d7,$28
+ $08,$29,$fa,$12,$0c,$00,$29,$fc,$09,$3a,$50,$59,$f6,$28,$d8,$28
+ $08,$29,$fa,$12,$10,$00,$29,$fc,$09,$00,$15,$30,$02,$53,$24,$f6
+ $e1,$28,$50,$58,$f9,$53,$58,$2c,$50,$59,$f9,$53,$59,$29,$00,$18
+ $3a,$02,$92,$20,$53,$f6,$01,$20,$f0,$20,$53,$24,$f6,$22,$20,$22
+ $20,$80,$20,$0b,$4e,$02,$00,$34,$44,$02,$aa,$20,$a1,$28,$08,$29
+ $f6,$8a,$20,$28,$05,$2c,$53,$2c,$03,$2c,$05,$2c,$01,$2c,$01,$29
+ $3a,$a7,$20,$08,$2c,$50,$58,$fd,$09,$f9,$12,$0c,$00,$2c,$50,$59
+ $fd,$09,$f9,$12,$10,$00,$3a,$a1,$20,$08,$00,$0c,$4e,$02,$27,$fe
+ $6c,$62,$9d,$7a,$8a,$62,$00,$1a,$58,$02,$92,$20,$53,$24,$f3,$d4
+ $28,$50,$53,$24,$2c,$50,$53,$2c,$02,$29,$20,$80,$20,$0b,$8a,$02
+ $00,$18,$62,$02,$a3,$20,$22,$4f,$34,$43,$30,$23,$43,$44,$23,$44
+ $45,$46,$23,$46,$47,$23,$47,$22,$00,$1b,$6c,$02,$ae,$20,$50,$58
+ $f9,$53,$58,$2c,$50,$59,$f9,$53,$59,$3a,$8b,$20,$dc,$28,$12,$c7
+ $00,$29,$3b,$00,$17,$76,$02,$54,$f6,$54,$f9,$12,$0a,$00,$3a,$ae
+ $20,$12,$15,$00,$2c,$01,$3a,$8b,$20,$54,$00,$31,$80,$02,$50,$53
+ $f6,$50,$53,$f9,$02,$3a,$92,$20,$50,$53,$f7,$50,$4c,$20,$85,$20
+ $a3,$20,$22,$4f,$34,$43,$33,$44,$45,$46,$47,$3a,$4f,$34,$45,$33
+ $46,$47,$41,$42,$22,$3a,$80,$20,$0b,$60,$04,$00,$0a,$8a,$02,$27
+ $fe,$8b,$74,$64,$67,$00,$2d,$94,$02,$92,$20,$d6,$28,$01,$29,$f3
+ $09,$20,$f0,$20,$e2,$28,$50,$53,$24,$2c,$53,$24,$29,$f3,$01,$20
+ $f0,$20,$43,$f8,$02,$20,$f0,$20,$46,$57,$f6,$02,$20,$80,$20,$0b
+ $b2,$02,$00,$28,$9e,$02,$a3,$20,$22,$4f,$32,$45,$32,$43,$22,$3a
+ $57,$24,$f6,$53,$24,$3a,$ae,$20,$50,$58,$f9,$53,$58,$2c,$50,$59
+ $f9,$53,$59,$3a,$8b,$20,$dc,$28,$57,$29,$00,$18,$a8,$02,$46,$57
+ $f6,$02,$3a,$43,$f6,$43,$fa,$02,$3a,$ae,$20,$09,$2c,$01,$3a,$8b
+ $20,$43,$00,$0c,$b2,$02,$27,$fe,$fe,$fe,$fe,$fe,$65,$75,$00,$18
+ $bc,$02,$46,$f6,$46,$f9,$02,$3a,$92,$20,$46,$f7,$4b,$4e,$fa,$02
+ $20,$85,$20,$46,$f6,$01,$00,$0c,$c6,$02,$27,$fe,$6b,$a7,$89,$72
+ $88,$3f,$00,$18,$d0,$02,$4b,$44,$f6,$e6,$28,$46,$29,$3a,$92,$20
+ $4b,$44,$f7,$01,$20,$80,$20,$0b,$f8,$02,$00,$22,$da,$02,$92,$20
+ $a1,$28,$46,$29,$f3,$01,$20,$85,$20,$a3,$20,$22,$4f,$30,$43,$30
+ $45,$47,$43,$45,$47,$22,$3a,$80,$20,$0b,$38,$04,$00,$0c,$e4,$02
+ $27,$fe,$6b,$a7,$89,$64,$8b,$87,$00,$0b,$ee,$02,$4b,$44,$f6,$45
+ $28,$46,$29,$00,$0a,$f8,$02,$27,$fe,$9c,$7a,$95,$62,$00,$1f,$02
+ $03,$4b,$58,$f6,$28,$d7,$28,$46,$29,$fa,$12,$0c,$00,$29,$fc,$09
+ $3a,$4a,$58,$f6,$d1,$28,$50,$58,$fa,$4b,$58,$29,$00,$1f,$0c,$03
+ $4b,$59,$f6,$28,$d8,$28,$46,$29,$fa,$12,$10,$00,$29,$fc,$09,$3a
+ $4a,$59,$f6,$d1,$28,$50,$59,$fa,$4b,$59,$29,$00,$4c,$16,$03,$53
+ $4b,$24,$f6,$e1,$28,$4b,$58,$2c,$4b,$59,$29,$3a,$53,$52,$24,$f6
+ $e1,$28,$4b,$58,$f9,$02,$2c,$4b,$59,$29,$3a,$53,$4c,$24,$f6,$e1
+ $28,$4b,$58,$fa,$02,$2c,$4b,$59,$29,$3a,$53,$44,$24,$f6,$e1,$28
+ $4b,$58,$2c,$4b,$59,$f9,$02,$29,$3a,$53,$55,$24,$f6,$e1,$28,$4b
+ $58,$2c,$4b,$59,$fa,$02,$29,$00,$09,$20,$03,$27,$fe,$8b,$74,$3f
+ $00,$14,$2a,$03,$92,$20,$53,$4b,$24,$f3,$dc,$28,$57,$29,$20,$80
+ $20,$0b,$52,$03,$00,$38,$34,$03,$aa,$20,$a1,$28,$46,$29,$f6,$8a
+ $20,$28,$12,$0e,$00,$2c,$01,$2c,$02,$2c,$12,$c8,$00,$2c,$02,$2c
+ $03,$29,$3a,$a7,$20,$46,$2c,$4b,$58,$fd,$09,$f9,$12,$0c,$00,$2c
+ $4b,$59,$fd,$09,$f9,$12,$10,$00,$3a,$a1,$20,$46,$00,$1d,$3e,$03
+ $45,$28,$46,$29,$f6,$4b,$44,$3a,$ae,$20,$4b,$58,$2c,$4b,$59,$3a
+ $8b,$20,$57,$24,$3a,$46,$57,$f6,$01,$00,$09,$48,$03,$80,$20,$0b
+ $38,$04,$00,$0d,$52,$03,$27,$fe,$fe,$fe,$64,$61,$65,$68,$88,$00
+ $28,$5c,$03,$92,$28,$53,$52,$24,$f9,$53,$4c,$24,$f3,$22,$20,$20
+ $22,$20,$f1,$20,$53,$44,$24,$f9,$53,$55,$24,$f3,$22,$20,$20,$22
+ $29,$20,$80,$20,$0b,$8e,$03,$00,$0c,$66,$03,$27,$fe,$fe,$70,$95
+ $67,$6b,$8c,$00,$44,$70,$03,$92,$28,$4b,$44,$f6,$04,$20,$f1,$20
+ $4a,$58,$f6,$02,$29,$f0,$28,$4b,$44,$f6,$08,$20,$f1,$20,$4a,$58
+ $f6,$fa,$02,$29,$f0,$28,$4b,$44,$f6,$06,$20,$f1,$20,$4a,$59,$f6
+ $02,$29,$f0,$28,$4b,$44,$f6,$02,$20,$f1,$20,$4a,$59,$f6,$fa,$02
+ $29,$20,$80,$20,$0b,$24,$04,$00,$0a,$7a,$03,$27,$fe,$79,$8c,$72
+ $8c,$00,$1c,$84,$03,$4b,$44,$f6,$28,$4b,$44,$f9,$05,$29,$f9,$09
+ $fd,$28,$4b,$44,$f7,$04,$29,$3a,$80,$20,$0b,$24,$04,$00,$0a,$8e
+ $03,$27,$fe,$fe,$a8,$8c,$66,$00,$09,$98,$03,$27,$fe,$61,$68,$88
+ $00,$1c,$a2,$03,$92,$20,$4a,$58,$fd,$4a,$59,$f3,$01,$20,$f1,$20
+ $d0,$28,$03,$29,$f6,$02,$20,$80,$20,$0b,$c0,$03,$00,$21,$ac,$03
+ $92,$20,$4a,$58,$f6,$02,$20,$f1,$20,$53,$52,$24,$f3,$22,$20,$22
+ $20,$85,$20,$4b,$44,$f6,$04,$3a,$80,$20,$0b,$24,$04,$00,$22,$b6
+ $03,$92,$20,$4a,$58,$f6,$fa,$02,$20,$f1,$20,$53,$4c,$24,$f3,$22
+ $20,$22,$20,$85,$20,$4b,$44,$f6,$08,$3a,$80,$20,$0b,$24,$04,$00
+ $21,$c0,$03,$92,$20,$4a,$59,$f6,$02,$20,$f1,$20,$53,$44,$24,$f3
+ $22,$20,$22,$20,$85,$20,$4b,$44,$f6,$06,$3a,$80,$20,$0b,$24,$04
+ $00,$22,$ca,$03,$92,$20,$4a,$59,$f6,$fa,$02,$20,$f1,$20,$53,$55
+ $24,$f3,$22,$20,$22,$20,$85,$20,$4b,$44,$f6,$02,$3a,$80,$20,$0b
+ $24,$04,$00,$0a,$d4,$03,$27,$fe,$61,$68,$74,$61,$00,$0c,$de,$03
+ $53,$f6,$d0,$28,$05,$29,$f9,$02,$00,$19,$e8,$03,$9a,$20,$53,$20
+ $80,$20,$0b,$f2,$03,$2c,$0b,$fc,$03,$2c,$0b,$06,$04,$2c,$0b,$10
+ $04,$00,$1a,$f2,$03,$92,$20,$53,$52,$24,$f3,$22,$20,$22,$20,$85
+ $20,$4b,$44,$f6,$04,$20,$80,$20,$0b,$24,$04,$00,$1a,$fc,$03,$92
+ $20,$53,$4c,$24,$f3,$22,$20,$22,$20,$85,$20,$4b,$44,$f6,$08,$20
+ $80,$20,$0b,$24,$04,$00,$1a,$06,$04,$92,$20,$53,$44,$24,$f3,$22
+ $20,$22,$20,$85,$20,$4b,$44,$f6,$06,$20,$80,$20,$0b,$24,$04,$00
+ $1a,$10,$04,$92,$20,$53,$55,$24,$f3,$22,$20,$22,$20,$85,$20,$4b
+ $44,$f6,$02,$20,$80,$20,$0b,$24,$04,$00,$09,$1a,$04,$80,$20,$0b
+ $f2,$03,$00,$09,$24,$04,$27,$fe,$62,$9b,$67,$00,$37,$2e,$04,$aa
+ $20,$a1,$28,$46,$29,$f6,$8a,$20,$28,$12,$0e,$00,$2c,$4b,$44,$2c
+ $02,$2c,$05,$2c,$01,$2c,$04,$29,$3a,$a7,$20,$46,$2c,$4b,$58,$fd
+ $09,$f9,$12,$0c,$00,$2c,$4b,$59,$fd,$09,$f9,$12,$10,$00,$3a,$a1
+ $20,$46,$00,$0c,$38,$04,$27,$fe,$a8,$71,$65,$96,$6f,$3f,$00,$23
+ $42,$04,$92,$20,$e3,$28,$08,$29,$f4,$01,$20,$85,$20,$a3,$20,$22
+ $4f,$31,$42,$32,$41,$47,$46,$45,$44,$43,$22,$3a,$80,$20,$0b,$60
+ $04,$00,$07,$4c,$04,$27,$fe,$fe,$00,$09,$56,$04,$80,$20,$0b,$d6
+ $01,$00,$0b,$60,$04,$27,$fe,$fe,$fe,$64,$8b,$87,$00,$21,$6a,$04
+ $8c,$20,$4a,$58,$f6,$07,$20,$88,$20,$09,$3a,$93,$20,$53,$24,$3a
+ $ae,$20,$0a,$2c,$4a,$58,$3a,$8b,$20,$53,$24,$3a,$8d,$00,$07,$74
+ $04,$8a,$20,$9b,$00,$12,$7e,$04,$91,$20,$22,$fd,$fe,$fe,$fe,$fe
+ $fe,$fe,$fe,$fe,$fd,$22,$00,$12,$88,$04,$91,$20,$22,$fe,$fd,$20
+ $64,$6b,$7e,$61,$20,$fd,$fe,$22,$00,$12,$92,$04,$91,$20,$22,$fd
+ $fe,$fe,$fe,$fe,$fe,$fe,$fe,$fe,$fd,$22,$00,$16,$9c,$04,$53,$f6
+ $d6,$28,$01,$29,$3a,$92,$20,$53,$f6,$01,$20,$80,$20,$0b,$9c,$04
+ $00,$0d,$a6,$04,$92,$20,$53,$f6,$02,$20,$85,$20,$82,$00,$05,$b0
+ $04,$a2,$00,$00
; "GAME 2" built-in BASIC program, tokenized
e682: 09 0a 00 9f+ txtGame2 .bulk $09,$0a,$00,$9f,$20,$02,$2c,$01,$00,$0d,$14,$00,$9a,$20,$bc,$20
+ $80,$20,$0b,$8e,$03,$00,$48,$1e,$00,$57,$f6,$46,$f6,$4b,$f6,$54
+ $f6,$52,$f6,$5a,$f6,$4e,$49,$f6,$4e,$4e,$f6,$58,$45,$f6,$59,$45
+ $f6,$58,$4e,$f6,$59,$4e,$f6,$58,$42,$f6,$58,$4c,$f6,$59,$4c,$f6
+ $53,$32,$f6,$53,$31,$f6,$53,$f6,$47,$f6,$56,$f6,$58,$f6,$59,$f6
+ $49,$f6,$44,$3a,$48,$f6,$01,$3a,$53,$24,$f6,$47,$24,$00,$1c,$28
+ $00,$af,$42,$20,$04,$2c,$12,$0d,$00,$2c,$12,$30,$00,$2c,$12,$36
+ $00,$2c,$12,$16,$00,$3a,$8a,$20,$9a,$00,$2b,$32,$00,$4b,$24,$f6
+ $dc,$28,$12,$18,$00,$29,$3a,$52,$24,$f6,$dc,$28,$12,$19,$00,$29
+ $3a,$48,$24,$f6,$dc,$28,$12,$1a,$00,$29,$3a,$55,$24,$f6,$dc,$28
+ $12,$1b,$00,$29,$00,$21,$3c,$00,$aa,$20,$8a,$20,$01,$2c,$28,$01
+ $2c,$02,$2c,$01,$2c,$02,$2c,$01,$29,$f6,$52,$24,$f9,$4b,$24,$f9
+ $55,$24,$f9,$48,$24,$00,$2b,$46,$00,$4b,$24,$f6,$dc,$28,$12,$0c
+ $00,$29,$3a,$52,$24,$f6,$dc,$28,$12,$0d,$00,$29,$3a,$48,$24,$f6
+ $dc,$28,$12,$0e,$00,$29,$3a,$55,$24,$f6,$dc,$28,$12,$0f,$00,$29
+ $00,$21,$50,$00,$aa,$20,$8a,$20,$02,$2c,$28,$01,$2c,$02,$2c,$01
+ $2c,$02,$2c,$01,$29,$f6,$52,$24,$f9,$4b,$24,$f9,$55,$24,$f9,$48
+ $24,$00,$21,$5a,$00,$aa,$20,$8a,$20,$03,$2c,$28,$01,$2c,$02,$2c
+ $01,$2c,$01,$2c,$01,$29,$f6,$4b,$24,$f9,$52,$24,$f9,$48,$24,$f9
+ $55,$24,$00,$2b,$64,$00,$4b,$24,$f6,$dc,$28,$12,$14,$00,$29,$3a
+ $52,$24,$f6,$dc,$28,$12,$15,$00,$29,$3a,$48,$24,$f6,$dc,$28,$12
+ $16,$00,$29,$3a,$55,$24,$f6,$dc,$28,$12,$17,$00,$29,$00,$21,$6e
+ $00,$aa,$20,$8a,$20,$04,$2c,$28,$01,$2c,$02,$2c,$01,$2c,$01,$2c
+ $01,$29,$f6,$4b,$24,$f9,$52,$24,$f9,$48,$24,$f9,$55,$24,$00,$37
+ $78,$00,$4b,$24,$f6,$dc,$28,$12,$e3,$00,$29,$3a,$52,$24,$f6,$dc
+ $28,$12,$d7,$00,$29,$3a,$48,$24,$f6,$dc,$28,$12,$d2,$00,$29,$3a
+ $55,$24,$f6,$dc,$28,$12,$c0,$00,$29,$3a,$94,$20,$51,$28,$04,$29
+ $2c,$44,$28,$04,$29,$00,$0d,$82,$00,$27,$6b,$95,$66,$20,$6d,$96
+ $72,$61,$00,$36,$8c,$00,$54,$f6,$12,$20,$03,$3a,$57,$f6,$01,$3a
+ $4e,$4e,$f6,$02,$3a,$58,$f6,$12,$74,$00,$3a,$59,$f6,$12,$7f,$00
+ $3a,$a7,$20,$08,$2c,$58,$2c,$59,$3a,$81,$20,$0b,$7a,$03,$3a,$56
+ $f6,$01,$3a,$80,$20,$0b,$1c,$02,$00,$16,$96,$00,$27,$6f,$61,$80
+ $20,$73,$20,$73,$67,$72,$8c,$20,$78,$20,$79,$8c,$72,$61,$00,$36
+ $a0,$00,$54,$f6,$54,$fa,$02,$3a,$92,$20,$54,$20,$fb,$20,$12,$0a
+ $00,$f6,$01,$20,$85,$20,$ae,$20,$12,$0e,$00,$2c,$12,$15,$00,$3a
+ $8b,$20,$54,$3b,$22,$20,$20,$22,$3a,$92,$20,$54,$f6,$01,$20,$85
+ $20,$0b,$94,$02,$00,$1d,$aa,$00,$92,$20,$5a,$f6,$12,$3a,$00,$20
+ $85,$20,$92,$20,$52,$f7,$12,$0f,$00,$20,$85,$20,$81,$20,$0b,$7a
+ $03,$00,$11,$b4,$00,$27,$75,$6f,$75,$6f,$20,$78,$20,$ad,$8a,$99
+ $86,$80,$00,$13,$be,$00,$49,$f6,$fa,$28,$49,$f9,$02,$29,$fd,$28
+ $49,$f3,$4e,$4e,$29,$00,$13,$c8,$00,$58,$4e,$f6,$d7,$28,$49,$29
+ $3a,$59,$4e,$f6,$d8,$28,$49,$29,$00,$50,$d2,$00,$92,$20,$58,$4e
+ $f8,$12,$0c,$00,$20,$f0,$20,$58,$4e,$f7,$12,$de,$00,$20,$f0,$20
+ $59,$4e,$f7,$12,$bc,$00,$20,$85,$20,$58,$4e,$f6,$d0,$28,$12,$1b
+ $00,$29,$fd,$09,$f9,$12,$0c,$00,$3a,$59,$4e,$f6,$08,$3a,$a7,$20
+ $49,$2c,$58,$4e,$2c,$59,$4e,$3a,$44,$f6,$01,$3a,$51,$28,$49,$29
+ $f6,$06,$3a,$80,$20,$0b,$18,$01,$00,$1b,$dc,$00,$58,$45,$f6,$28
+ $58,$4e,$fa,$09,$29,$fc,$09,$3a,$59,$45,$f6,$28,$59,$4e,$fa,$03
+ $29,$fc,$09,$00,$0f,$e6,$00,$47,$24,$f6,$e1,$28,$58,$45,$2c,$59
+ $45,$29,$00,$0a,$f0,$00,$44,$f6,$51,$28,$49,$29,$00,$39,$fa,$00
+ $92,$20,$47,$24,$f3,$55,$24,$20,$85,$20,$92,$20,$59,$4c,$f3,$59
+ $45,$20,$f0,$20,$47,$24,$f3,$48,$24,$20,$85,$20,$44,$f6,$06,$3a
+ $51,$28,$49,$29,$f6,$04,$fa,$05,$fd,$28,$58,$f8,$58,$4e,$29,$3a
+ $80,$20,$0b,$0e,$01,$00,$12,$04,$01,$92,$20,$44,$28,$49,$29,$f6
+ $44,$20,$85,$20,$0b,$2c,$01,$00,$23,$0e,$01,$58,$4e,$f6,$58,$45
+ $fd,$09,$f9,$12,$0c,$00,$3a,$59,$4e,$f6,$59,$45,$fd,$09,$f9,$08
+ $3a,$a7,$20,$49,$2c,$58,$4e,$2c,$59,$4e,$00,$2e,$18,$01,$aa,$20
+ $a1,$28,$49,$29,$f6,$8a,$28,$12,$0b,$00,$2c,$44,$2c,$03,$2c,$12
+ $ff,$00,$f9,$12,$fb,$00,$fd,$28,$44,$f6,$06,$29,$29,$3a,$a1,$20
+ $49,$3a,$44,$28,$49,$29,$f6,$44,$00,$10,$22,$01,$27,$a8,$71,$65
+ $87,$20,$78,$20,$79,$8c,$72,$61,$00,$43,$2c,$01,$4e,$49,$f6,$e3
+ $28,$08,$29,$3a,$92,$20,$4e,$49,$f7,$fa,$02,$20,$85,$20,$92,$20
+ $ca,$28,$d8,$28,$4e,$49,$29,$fa,$d8,$28,$08,$29,$29,$f8,$12,$0e
+ $00,$20,$f1,$20,$ca,$28,$d7,$28,$4e,$49,$29,$fa,$d7,$28,$08,$29
+ $29,$f8,$12,$0e,$00,$20,$85,$20,$0b,$94,$02,$00,$0f,$36,$01,$92
+ $20,$46,$f6,$02,$20,$85,$20,$0b,$0c,$03,$00,$10,$40,$01,$27,$7e
+ $87,$64,$20,$78,$20,$ad,$8a,$99,$86,$80,$00,$21,$4a,$01,$53,$31
+ $f6,$53,$3a,$53,$f6,$d5,$28,$01,$29,$3a,$92,$20,$53,$f7,$03,$20
+ $85,$20,$53,$f6,$04,$fa,$28,$53,$f7,$08,$29,$00,$11,$54,$01,$58
+ $f6,$d7,$28,$08,$29,$3a,$59,$f6,$d8,$28,$08,$29,$00,$17,$5e,$01
+ $58,$4c,$f6,$58,$fc,$09,$fa,$02,$3a,$59,$4c,$f6,$28,$59,$fa,$03
+ $29,$fc,$09,$00,$19,$68,$01,$9a,$20,$53,$20,$80,$20,$0b,$26,$02
+ $2c,$0b,$26,$02,$2c,$0b,$e0,$01,$2c,$0b,$fe,$01,$00,$05,$72,$01
+ $27,$00,$0f,$7c,$01,$56,$f6,$01,$3a,$4b,$f6,$01,$3a,$9c,$20,$08
+ $00,$32,$86,$01,$92,$20,$e1,$28,$58,$4c,$2c,$59,$4c,$29,$f6,$22
+ $20,$22,$20,$85,$20,$92,$20,$e1,$28,$58,$4c,$2c,$59,$4c,$fa,$02
+ $29,$f3,$48,$24,$20,$85,$20,$81,$20,$0b,$66,$03,$3a,$80,$20,$0b
+ $ee,$02,$00,$2f,$90,$01,$58,$42,$f6,$58,$4c,$f9,$28,$53,$31,$f6
+ $02,$29,$fa,$28,$53,$31,$f6,$03,$29,$3a,$92,$20,$e1,$28,$58,$42
+ $2c,$59,$4c,$fa,$02,$29,$f6,$52,$24,$20,$85,$20,$81,$20,$0b,$70
+ $03,$00,$22,$9a,$01,$92,$20,$e1,$28,$58,$4c,$2c,$59,$4c,$fa,$02
+ $29,$f6,$4b,$24,$20,$85,$20,$47,$f6,$fa,$12,$0f,$00,$3a,$80,$20
+ $0b,$02,$03,$00,$44,$a4,$01,$92,$20,$cb,$28,$e1,$28,$58,$4c,$2c
+ $59,$4c,$fa,$02,$29,$29,$f6,$5a,$20,$85,$20,$ae,$20,$58,$4c,$2c
+ $59,$4c,$fa,$02,$3a,$8b,$20,$22,$20,$22,$3a,$5a,$f6,$5a,$f9,$02
+ $3a,$57,$f6,$57,$f9,$12,$1e,$00,$3a,$ae,$20,$12,$0e,$00,$2c,$12
+ $17,$00,$3a,$8b,$20,$57,$3b,$00,$12,$ae,$01,$92,$20,$d6,$28,$01
+ $29,$f3,$09,$20,$85,$20,$0b,$d6,$01,$00,$19,$b8,$01,$92,$20,$e1
+ $28,$58,$4c,$fa,$02,$2c,$59,$4c,$29,$f6,$55,$24,$20,$85,$20,$0b
+ $f8,$02,$00,$17,$c2,$01,$92,$20,$e1,$28,$58,$4c,$2c,$59,$4c,$29
+ $f6,$55,$24,$20,$85,$20,$0b,$f8,$02,$00,$19,$cc,$01,$92,$20,$e1
+ $28,$58,$4c,$f9,$02,$2c,$59,$4c,$29,$f6,$55,$24,$20,$85,$20,$0b
+ $f8,$02,$00,$09,$d6,$01,$80,$20,$0b,$a0,$00,$00,$13,$e0,$01,$56
+ $f6,$06,$3a,$47,$24,$f6,$e1,$28,$58,$4c,$2c,$59,$4c,$29,$00,$25
+ $ea,$01,$92,$20,$47,$24,$f3,$48,$24,$20,$85,$20,$92,$20,$47,$24
+ $f3,$55,$24,$20,$85,$20,$53,$f6,$01,$3a,$56,$f6,$01,$3a,$80,$20
+ $0b,$ee,$02,$00,$09,$f4,$01,$80,$20,$0b,$08,$02,$00,$15,$fe,$01
+ $56,$f6,$02,$3a,$47,$24,$f6,$e1,$28,$58,$4c,$2c,$59,$4c,$fa,$02
+ $29,$00,$17,$08,$02,$92,$20,$47,$24,$f3,$48,$24,$20,$85,$20,$53
+ $f6,$01,$3a,$80,$20,$0b,$7c,$01,$00,$1c,$12,$02,$92,$20,$53,$31
+ $f6,$01,$20,$f0,$20,$4b,$f7,$01,$20,$85,$20,$81,$20,$0b,$66,$03
+ $3a,$4b,$f6,$01,$00,$28,$1c,$02,$aa,$20,$a1,$28,$08,$29,$f6,$8a
+ $28,$01,$2c,$56,$2c,$02,$2c,$05,$29,$3a,$a1,$20,$08,$3a,$8a,$20
+ $53,$32,$3a,$8e,$20,$06,$3a,$80,$20,$0b,$a0,$00,$00,$20,$26,$02
+ $56,$f6,$05,$fd,$53,$fa,$02,$3a,$4b,$f6,$02,$fa,$28,$4b,$f9,$28
+ $4b,$f7,$04,$29,$29,$fd,$28,$53,$f6,$53,$31,$29,$00,$25,$30,$02
+ $47,$24,$f6,$e1,$28,$58,$4c,$f9,$02,$f9,$03,$fd,$28,$53,$f6,$03
+ $29,$2c,$59,$4c,$29,$3a,$53,$24,$f6,$e1,$28,$58,$4c,$2c,$59,$4c
+ $29,$00,$33,$3a,$02,$58,$42,$f6,$58,$4c,$f9,$02,$f9,$03,$fd,$28
+ $53,$f6,$02,$29,$3a,$92,$20,$4b,$f7,$03,$20,$85,$20,$92,$20,$e1
+ $28,$58,$42,$2c,$59,$4c,$fa,$02,$29,$f6,$52,$24,$20,$85,$20,$81
+ $20,$0b,$70,$03,$00,$21,$44,$02,$92,$20,$e1,$28,$58,$4c,$2c,$59
+ $4c,$fa,$02,$29,$f6,$52,$24,$20,$85,$20,$58,$42,$f6,$58,$4c,$3a
+ $81,$20,$0b,$70,$03,$00,$38,$4e,$02,$92,$20,$47,$24,$f3,$55,$24
+ $20,$f1,$20,$47,$24,$f3,$48,$24,$20,$85,$20,$58,$f6,$58,$f9,$07
+ $fd,$28,$53,$24,$f3,$22,$20,$22,$29,$fd,$28,$28,$53,$f6,$02,$29
+ $fa,$28,$53,$f6,$03,$29,$29,$3a,$80,$20,$0b,$ee,$02,$00,$19,$58
+ $02,$92,$20,$53,$24,$f6,$22,$20,$22,$20,$85,$20,$53,$f6,$53,$31
+ $3a,$80,$20,$0b,$ee,$02,$00,$32,$62,$02,$92,$20,$d6,$28,$01,$29
+ $f6,$09,$20,$85,$20,$92,$20,$47,$24,$f6,$55,$24,$20,$f0,$20,$53
+ $24,$f6,$55,$24,$20,$f0,$20,$e1,$28,$58,$42,$2c,$59,$4c,$29,$f6
+ $55,$24,$20,$85,$20,$0b,$f8,$02,$00,$2f,$6c,$02,$4e,$49,$f6,$e3
+ $28,$08,$29,$3a,$92,$20,$4e,$49,$f7,$fa,$02,$20,$85,$20,$92,$20
+ $ca,$28,$d7,$28,$4e,$49,$29,$fa,$d7,$28,$08,$29,$29,$f8,$12,$0e
+ $00,$20,$85,$20,$0b,$94,$02,$00,$2a,$76,$02,$92,$20,$4b,$f8,$05
+ $20,$85,$20,$aa,$20,$a1,$28,$08,$29,$f6,$8a,$28,$01,$2c,$56,$2c
+ $05,$fa,$4b,$2c,$12,$ff,$00,$29,$3a,$a1,$20,$08,$3a,$8a,$20,$53
+ $32,$00,$09,$80,$02,$80,$20,$0b,$a0,$00,$00,$10,$8a,$02,$27,$64
+ $8b,$87,$20,$78,$20,$ad,$8a,$99,$86,$80,$00,$15,$94,$02,$8a,$20
+ $53,$3a,$8a,$20,$53,$31,$3a,$8a,$20,$53,$32,$3a,$b0,$20,$08,$00
+ $23,$9e,$02,$8c,$20,$49,$f6,$01,$20,$88,$20,$4e,$4e,$3a,$b0,$20
+ $49,$3a,$a7,$20,$49,$2c,$01,$2c,$01,$3a,$44,$28,$49,$29,$f6,$01
+ $3a,$8d,$00,$1a,$a8,$02,$ae,$20,$09,$2c,$0a,$3a,$8b,$22,$2a,$2a
+ $2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$22,$00,$1c,$b2,$02
+ $ae,$20,$09,$2c,$12,$0a,$00,$3a,$8b,$22,$2a,$20,$47,$41,$4d,$45
+ $20,$4f,$56,$45,$52,$20,$2a,$22,$00,$1c,$bc,$02,$ae,$20,$09,$2c
+ $12,$0b,$00,$3a,$8b,$22,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a,$2a
+ $2a,$2a,$2a,$22,$00,$1e,$c6,$02,$92,$20,$57,$f7,$48,$20,$85,$20
+ $48,$f6,$57,$3a,$ae,$20,$12,$0e,$00,$2c,$12,$16,$00,$3a,$8b,$20
+ $48,$3b,$00,$12,$d0,$02,$92,$20,$d6,$28,$01,$29,$f6,$02,$20,$85
+ $20,$0b,$8c,$00,$00,$09,$da,$02,$80,$20,$0b,$d0,$02,$00,$11,$e4
+ $02,$27,$9d,$93,$8c,$ad,$20,$78,$20,$ad,$8a,$99,$86,$80,$00,$13
+ $ee,$02,$47,$f6,$03,$3a,$59,$f6,$59,$f9,$04,$3a,$80,$20,$0b,$02
+ $03,$00,$2a,$f8,$02,$47,$f6,$fa,$0a,$3a,$58,$f6,$58,$f9,$28,$03
+ $fd,$4b,$f9,$07,$29,$fd,$28,$28,$53,$f6,$03,$29,$fa,$28,$53,$f6
+ $02,$29,$29,$3a,$59,$f6,$59,$fa,$12,$0c,$00,$00,$3d,$02,$03,$46
+ $f6,$02,$3a,$8a,$20,$53,$2c,$58,$2c,$59,$3a,$b0,$20,$08,$3a,$3a
+ $8a,$20,$04,$3a,$a7,$20,$08,$2c,$58,$2c,$59,$3a,$53,$32,$f6,$53
+ $3a,$4b,$f6,$4b,$fd,$28,$28,$53,$f6,$03,$29,$fa,$28,$53,$f6,$02
+ $29,$29,$3a,$80,$20,$0b,$a0,$00,$00,$2c,$0c,$03,$4e,$49,$f6,$e3
+ $28,$08,$29,$3a,$92,$20,$4e,$49,$f7,$fa,$02,$20,$85,$20,$92,$20
+ $ca,$28,$d8,$28,$4e,$49,$29,$fa,$59,$29,$f8,$12,$0c,$00,$20,$85
+ $20,$0b,$94,$02,$00,$2d,$16,$03,$47,$f6,$47,$f9,$04,$f9,$04,$fd
+ $28,$47,$f7,$07,$29,$3a,$58,$f6,$58,$f9,$4b,$3a,$59,$f6,$59,$f9
+ $47,$3a,$8a,$20,$53,$2c,$58,$2c,$59,$3a,$a7,$20,$08,$2c,$58,$2c
+ $59,$00,$31,$20,$03,$58,$4c,$f6,$58,$fc,$09,$fa,$02,$3a,$59,$4c
+ $f6,$28,$59,$fa,$03,$29,$fc,$09,$3a,$47,$24,$f6,$e1,$28,$58,$4c
+ $2c,$59,$4c,$29,$3a,$53,$24,$f6,$e1,$28,$58,$4c,$2c,$59,$4c,$fa
+ $02,$29,$00,$1d,$2a,$03,$92,$20,$47,$24,$f6,$55,$24,$20,$85,$20
+ $81,$20,$0b,$5c,$03,$3a,$4b,$f6,$02,$3a,$80,$20,$0b,$1c,$02,$00
+ $2b,$34,$03,$92,$20,$53,$24,$f6,$48,$24,$20,$85,$20,$81,$20,$0b
+ $5c,$03,$3a,$8a,$20,$04,$2c,$58,$2c,$59,$3a,$8a,$20,$53,$32,$3a
+ $53,$32,$f6,$04,$3a,$80,$20,$0b,$a0,$00,$00,$1d,$3e,$03,$92,$20
+ $47,$24,$f6,$48,$24,$20,$85,$20,$81,$20,$0b,$5c,$03,$3a,$56,$f6
+ $01,$3a,$80,$20,$0b,$1c,$02,$00,$09,$48,$03,$80,$20,$0b,$0c,$03
+ $00,$10,$52,$03,$27,$53,$55,$42,$20,$78,$20,$ad,$8a,$99,$86,$80
+ $00,$1b,$5c,$03,$81,$20,$0b,$66,$03,$3a,$46,$f6,$01,$3a,$4b,$f6
+ $01,$3a,$8a,$20,$53,$2c,$58,$2c,$59,$3a,$83,$00,$25,$66,$03,$9c
+ $20,$08,$3a,$58,$f6,$58,$4c,$fd,$09,$f9,$12,$0c,$00,$3a,$59,$f6
+ $59,$4c,$fd,$09,$f9,$08,$3a,$a7,$20,$08,$2c,$58,$2c,$59,$3a,$83
+ $00,$32,$70,$03,$ae,$20,$58,$42,$2c,$59,$4c,$fa,$02,$3a,$8b,$20
+ $22,$20,$22,$3a,$52,$f6,$52,$f9,$02,$3a,$57,$f6,$57,$f9,$12,$0f
+ $00,$3a,$ae,$20,$12,$0e,$00,$2c,$12,$17,$00,$3a,$8b,$20,$57,$3b
+ $3a,$83,$00,$5d,$7a,$03,$a0,$3a,$49,$f6,$01,$3a,$46,$f6,$01,$3a
+ $4b,$f6,$01,$3a,$43,$f6,$01,$3a,$52,$f6,$01,$3a,$5a,$f6,$12,$30
+ $00,$3a,$ae,$20,$12,$0a,$00,$2c,$12,$15,$00,$3a,$8b,$22,$54,$49
+ $4d,$45,$22,$3b,$54,$3a,$ae,$20,$07,$2c,$12,$16,$00,$3a,$8b,$22
+ $48,$49,$2d,$53,$43,$4f,$52,$45,$22,$3b,$48,$3a,$ae,$20,$0a,$2c
+ $12,$17,$00,$3a,$8b,$22,$53,$43,$4f,$52,$45,$22,$3b,$57,$3b,$00
+ $05,$84,$03,$83,$00,$14,$8e,$03,$27,$63,$86,$2d,$20,$6b,$95,$87
+ $20,$78,$20,$ad,$8a,$99,$86,$80,$00,$2c,$98,$03,$92,$20,$e5,$f6
+ $12,$30,$02,$20,$85,$20,$58,$4c,$f6,$fa,$12,$1b,$00,$fd,$28,$58
+ $4c,$f8,$02,$29,$3a,$81,$20,$0b,$66,$03,$3a,$a1,$20,$08,$3a,$bd
+ $20,$0b,$76,$02,$00,$2d,$a2,$03,$92,$20,$e5,$f6,$12,$86,$01,$20
+ $85,$20,$58,$4c,$f6,$fa,$12,$1b,$00,$fd,$28,$58,$4c,$f8,$01,$29
+ $3a,$81,$20,$0b,$66,$03,$3a,$53,$f6,$53,$31,$3a,$bd,$20,$0b,$26
+ $02,$00,$14,$ac,$03,$92,$20,$59,$4c,$f7,$12,$17,$00,$20,$85,$20
+ $bd,$20,$0b,$94,$02,$00,$1b,$b6,$03,$92,$20,$58,$4c,$f7,$12,$1b
+ $00,$20,$85,$20,$58,$4c,$f6,$01,$3a,$81,$20,$0b,$66,$03,$3a,$bd
+ $00,$1b,$c0,$03,$92,$20,$58,$4c,$f8,$01,$20,$85,$20,$58,$4c,$f6
+ $12,$1b,$00,$3a,$81,$20,$0b,$66,$03,$3a,$bd,$00,$18,$ca,$03,$92
+ $20,$58,$42,$f7,$12,$1b,$00,$20,$85,$20,$58,$42,$f6,$01,$3a,$a1
+ $08,$3a,$bd,$00,$18,$d4,$03,$92,$20,$58,$42,$f8,$01,$20,$85,$20
+ $58,$42,$f6,$12,$1b,$00,$3a,$a1,$08,$3a,$bd,$00,$1d,$de,$03,$92
+ $20,$58,$4c,$f9,$02,$f7,$12,$1b,$00,$20,$85,$20,$58,$4c,$f6,$01
+ $3a,$81,$20,$0b,$66,$03,$3a,$bd,$00,$1d,$e8,$03,$92,$20,$58,$4c
+ $fa,$02,$f8,$01,$20,$85,$20,$58,$4c,$f6,$12,$1b,$00,$3a,$81,$20
+ $0b,$66,$03,$3a,$bd,$00,$15,$f2,$03,$92,$20,$58,$f8,$12,$0c,$00
+ $20,$85,$20,$58,$f6,$12,$e4,$00,$3a,$bd,$00,$15,$fc,$03,$92,$20
+ $58,$f7,$12,$e4,$00,$20,$85,$20,$58,$f6,$12,$0c,$00,$3a,$bd,$00
+ $05,$06,$04,$a2,$00,$00
; "GAME 3" built-in BASIC program, tokenized
f308: 13 0a 00 27+ txtGame3 .bulk $13,$0a,$00,$27,$22,$41,$52,$45,$41,$20,$38,$38,$20,$56,$37,$2e
+ $33,$22,$00,$37,$14,$00,$54,$f6,$4f,$f6,$55,$f6,$43,$f6,$48,$f6
+ $44,$f6,$49,$f6,$4b,$f6,$4c,$f6,$4d,$f6,$4e,$f6,$50,$f6,$53,$f6
+ $56,$f6,$58,$f6,$59,$f6,$4d,$41,$f6,$58,$30,$f6,$58,$31,$f6,$59
+ $30,$f6,$59,$31,$f6,$50,$50,$f6,$51,$00,$1f,$1e,$00,$94,$20,$44
+ $28,$02,$29,$2c,$56,$28,$02,$29,$2c,$46,$28,$09,$29,$2c,$53,$43
+ $28,$02,$29,$2c,$53,$28,$02,$29,$00,$26,$28,$00,$4c,$f6,$0a,$3a
+ $4d,$41,$f6,$03,$3a,$4e,$f6,$04,$3a,$56,$f6,$08,$3a,$43,$f6,$08
+ $3a,$53,$28,$01,$29,$f6,$05,$3a,$53,$28,$02,$29,$f6,$05,$00,$1f
+ $32,$00,$58,$30,$f6,$12,$46,$00,$3a,$59,$30,$f6,$12,$50,$00,$3a
+ $58,$31,$f6,$12,$6e,$00,$3a,$59,$31,$f6,$12,$3c,$00,$00,$3d,$3c
+ $00,$44,$28,$01,$29,$f6,$04,$3a,$56,$28,$01,$29,$f6,$03,$3a,$44
+ $28,$02,$29,$f6,$08,$3a,$56,$28,$02,$29,$f6,$03,$3a,$46,$28,$01
+ $29,$f6,$04,$3a,$46,$28,$02,$29,$f6,$04,$3a,$4d,$41,$f6,$4d,$41
+ $f9,$02,$3a,$55,$f6,$4e,$3a,$4d,$f6,$02,$00,$32,$46,$00,$8a,$20
+ $9a,$3a,$9f,$20,$02,$2c,$02,$3a,$a3,$22,$54,$31,$59,$32,$4d,$31
+ $56,$39,$4f,$33,$52,$31,$22,$3a,$af,$53,$20,$02,$2c,$12,$0d,$00
+ $2c,$12,$30,$00,$2c,$12,$26,$00,$2c,$12,$12,$00,$00,$2a,$50,$00
+ $8c,$20,$49,$f6,$12,$0f,$00,$20,$88,$20,$12,$12,$00,$3a,$41,$24
+ $f6,$41,$24,$f9,$e1,$28,$49,$2c,$12,$16,$00,$29,$3a,$8d,$3a,$48
+ $f6,$d9,$28,$41,$24,$29,$00,$09,$5a,$00,$81,$20,$0b,$2e,$04,$00
+ $2c,$64,$00,$a0,$3a,$8c,$50,$f6,$01,$88,$12,$c8,$00,$3a,$ae,$d0
+ $28,$12,$1b,$00,$29,$2c,$d0,$28,$12,$17,$00,$29,$3a,$8b,$dc,$28
+ $12,$cd,$00,$29,$3b,$3a,$8d,$3a,$50,$f6,$01,$00,$05,$6e,$00,$27
+ $00,$2d,$78,$00,$49,$f6,$50,$f9,$07,$3a,$92,$20,$46,$28,$50,$29
+ $20,$85,$20,$9a,$20,$46,$28,$50,$29,$20,$81,$20,$0b,$9e,$02,$2c
+ $0b,$bc,$02,$2c,$0b,$a8,$02,$3a,$80,$20,$0b,$96,$00,$00,$17,$82
+ $00,$92,$20,$4d,$f6,$50,$20,$85,$20,$81,$20,$0b,$86,$01,$3a,$80
+ $20,$0b,$96,$00,$00,$09,$8c,$00,$81,$20,$0b,$d2,$00,$00,$12,$96
+ $00,$92,$20,$a1,$28,$49,$29,$f6,$01,$20,$85,$20,$b0,$20,$49,$00
+ $09,$a0,$00,$81,$20,$0b,$12,$02,$00,$09,$aa,$00,$81,$20,$0b,$20
+ $03,$00,$16,$b4,$00,$9a,$20,$46,$28,$09,$29,$f9,$02,$20,$81,$20
+ $0b,$ac,$03,$2c,$0b,$10,$04,$00,$0f,$be,$00,$50,$f6,$02,$fa,$50
+ $3a,$80,$20,$0b,$78,$00,$00,$05,$c8,$00,$27,$00,$11,$d2,$00,$53
+ $f6,$d5,$28,$50,$29,$3a,$54,$f6,$d6,$28,$50,$29,$00,$12,$dc,$00
+ $92,$20,$54,$f3,$05,$20,$85,$20,$46,$28,$49,$29,$f6,$02,$00,$0f
+ $e6,$00,$92,$20,$53,$f9,$54,$f6,$01,$20,$85,$20,$83,$00,$0c,$f0
+ $00,$92,$53,$f6,$01,$85,$0b,$5e,$01,$00,$48,$fa,$00,$53,$f6,$fa
+ $02,$fd,$28,$53,$f6,$09,$29,$fa,$04,$fd,$28,$53,$f6,$02,$29,$fa
+ $06,$fd,$28,$53,$f6,$05,$29,$fa,$08,$fd,$28,$53,$f6,$03,$29,$fa
+ $03,$fd,$28,$53,$f6,$0a,$29,$fa,$05,$fd,$28,$53,$f6,$06,$29,$fa
+ $07,$fd,$28,$53,$f6,$07,$29,$fa,$09,$fd,$28,$53,$f6,$12,$0a,$00
+ $29,$00,$0a,$04,$01,$51,$f6,$44,$28,$50,$29,$00,$0f,$0e,$01,$92
+ $20,$53,$f6,$51,$20,$85,$20,$0b,$5e,$01,$00,$0f,$18,$01,$92,$20
+ $51,$f7,$05,$20,$85,$20,$0b,$36,$01,$00,$19,$22,$01,$92,$20,$28
+ $53,$f7,$51,$20,$f1,$20,$53,$f5,$51,$f9,$05,$29,$20,$85,$20,$0b
+ $40,$01,$00,$09,$2c,$01,$80,$20,$0b,$4a,$01,$00,$18,$36,$01,$92
+ $20,$28,$53,$f4,$51,$fa,$05,$20,$f1,$20,$53,$f8,$51,$29,$85,$20
+ $0b,$4a,$01,$00,$0f,$40,$01,$51,$f6,$51,$f9,$02,$3a,$80,$20,$0b
+ $54,$01,$00,$09,$4a,$01,$51,$f6,$51,$fa,$02,$00,$2d,$54,$01,$50
+ $50,$f6,$fa,$02,$fd,$28,$51,$f7,$09,$29,$fa,$09,$fd,$28,$51,$f8
+ $02,$29,$3a,$51,$f6,$fa,$51,$fd,$28,$50,$50,$f6,$01,$29,$f9,$50
+ $50,$3a,$44,$28,$50,$29,$f6,$51,$00,$28,$5e,$01,$92,$20,$54,$20
+ $f1,$05,$20,$85,$20,$92,$20,$46,$28,$49,$29,$20,$85,$20,$56,$28
+ $50,$29,$f6,$56,$fa,$56,$28,$50,$29,$3a,$46,$28,$49,$29,$f6,$01
+ $00,$26,$68,$01,$aa,$20,$a1,$28,$50,$29,$f6,$8a,$20,$28,$50,$f9
+ $09,$2c,$44,$28,$50,$29,$2c,$56,$28,$50,$29,$2c,$12,$ff,$00,$2c
+ $02,$29,$3a,$a1,$20,$50,$00,$4a,$72,$01,$92,$20,$54,$f7,$08,$20
+ $85,$20,$aa,$20,$a1,$28,$49,$29,$f6,$8a,$28,$12,$0c,$00,$2c,$44
+ $28,$50,$29,$2c,$02,$2c,$12,$50,$00,$2c,$01,$2c,$50,$29,$3a,$a7
+ $20,$49,$2c,$28,$d7,$28,$50,$29,$f9,$05,$29,$fb,$12,$00,$01,$2c
+ $28,$d8,$28,$50,$29,$f9,$05,$29,$fb,$12,$00,$01,$3a,$a1,$20,$49
+ $00,$05,$7c,$01,$83,$00,$05,$86,$01,$27,$00,$13,$90,$01,$4b,$f6
+ $4b,$fa,$02,$3a,$92,$20,$4b,$f7,$01,$20,$85,$20,$83,$00,$33,$9a
+ $01,$54,$f6,$01,$3a,$44,$f6,$01,$3a,$58,$f6,$28,$d7,$28,$02,$fa
+ $50,$29,$fa,$d7,$28,$50,$29,$29,$fc,$12,$0d,$00,$3a,$59,$f6,$28
+ $d8,$28,$02,$fa,$50,$29,$fa,$d8,$28,$50,$29,$29,$fc,$12,$0d,$00
+ $00,$0f,$a4,$01,$92,$20,$58,$f7,$01,$20,$85,$20,$44,$f6,$04,$00
+ $0f,$ae,$01,$92,$20,$58,$f8,$01,$20,$85,$20,$44,$f6,$08,$00,$11
+ $b8,$01,$92,$20,$59,$f7,$01,$20,$85,$20,$44,$f6,$44,$f9,$06,$00
+ $1f,$c2,$01,$92,$20,$59,$f8,$01,$20,$85,$20,$44,$f6,$44,$f9,$02
+ $3a,$92,$20,$58,$f8,$01,$20,$85,$20,$44,$f6,$12,$10,$00,$00,$13
+ $cc,$01,$92,$20,$58,$20,$f1,$20,$59,$20,$85,$20,$44,$f6,$44,$fc
+ $03,$00,$29,$d6,$01,$53,$f6,$03,$3a,$4f,$f6,$44,$fa,$44,$28,$50
+ $29,$3a,$92,$28,$01,$f8,$4f,$20,$f1,$20,$4f,$f8,$06,$29,$f0,$20
+ $4f,$f8,$fa,$04,$20,$85,$20,$53,$f6,$02,$00,$0f,$e0,$01,$92,$20
+ $4f,$f6,$01,$20,$85,$20,$53,$f6,$01,$00,$17,$ea,$01,$92,$20,$56
+ $28,$02,$fa,$50,$29,$f3,$56,$28,$50,$29,$20,$85,$20,$54,$f6,$05
+ $00,$1f,$f4,$01,$92,$20,$a1,$28,$49,$29,$f6,$01,$20,$f0,$20,$d0
+ $28,$12,$0a,$00,$29,$f6,$01,$20,$85,$20,$54,$f6,$54,$f9,$09,$00
+ $15,$fe,$01,$92,$20,$58,$fd,$58,$f9,$59,$fd,$59,$f7,$12,$c8,$00
+ $20,$85,$20,$83,$00,$10,$08,$02,$4b,$f6,$d0,$28,$4c,$29,$3a,$80
+ $20,$0b,$dc,$00,$00,$05,$12,$02,$27,$00,$22,$1c,$02,$44,$f6,$e3
+ $28,$01,$29,$3a,$92,$20,$44,$f7,$01,$20,$f1,$20,$44,$f8,$07,$20
+ $85,$20,$4f,$f6,$01,$3a,$81,$20,$0b,$4e,$02,$00,$22,$26,$02,$44
+ $f6,$e3,$28,$02,$29,$3a,$92,$20,$44,$f7,$02,$20,$f1,$20,$44,$f8
+ $07,$20,$85,$20,$4f,$f6,$02,$3a,$81,$20,$0b,$4e,$02,$00,$22,$30
+ $02,$44,$f6,$e3,$28,$07,$29,$3a,$92,$20,$44,$f7,$01,$20,$f1,$20
+ $44,$f8,$07,$20,$85,$20,$4f,$f6,$07,$3a,$81,$20,$0b,$62,$02,$00
+ $29,$3a,$02,$44,$f6,$e3,$28,$08,$29,$3a,$92,$20,$44,$f6,$01,$20
+ $f0,$20,$28,$44,$f7,$02,$20,$f1,$20,$44,$f8,$07,$29,$85,$20,$4f
+ $f6,$08,$3a,$81,$20,$0b,$62,$02,$00,$05,$44,$02,$83,$00,$6f,$4e
+ $02,$58,$f6,$ca,$28,$d7,$28,$4f,$29,$fa,$d7,$28,$44,$29,$29,$3a
+ $59,$f6,$ca,$28,$d8,$28,$4f,$29,$fa,$d8,$28,$44,$29,$29,$3a,$92
+ $20,$58,$f8,$12,$0a,$00,$20,$f1,$20,$59,$f8,$12,$0a,$00,$20,$85
+ $20,$92,$20,$a1,$28,$4f,$29,$20,$f1,$20,$a1,$28,$44,$29,$20,$f1
+ $20,$46,$28,$44,$29,$f6,$01,$20,$f1,$20,$46,$28,$4f,$29,$f6,$01
+ $20,$85,$20,$46,$28,$4f,$29,$f6,$02,$3a,$46,$28,$44,$29,$f6,$02
+ $3a,$92,$20,$44,$f7,$02,$20,$85,$20,$0b,$8a,$02,$00,$05,$58,$02
+ $83,$00,$5d,$62,$02,$58,$f6,$ca,$28,$d7,$28,$4f,$29,$fa,$d7,$28
+ $44,$29,$fa,$05,$29,$3a,$59,$f6,$ca,$28,$d8,$28,$4f,$29,$fa,$d8
+ $28,$44,$29,$fa,$05,$29,$3a,$92,$20,$58,$f8,$09,$20,$f1,$20,$59
+ $f8,$09,$20,$85,$20,$92,$20,$a1,$28,$4f,$29,$20,$f1,$20,$a1,$28
+ $44,$29,$20,$f1,$20,$46,$28,$44,$29,$f6,$01,$20,$85,$20,$b0,$20
+ $4f,$3a,$46,$28,$44,$29,$f6,$02,$3a,$80,$20,$0b,$76,$02,$00,$05
+ $6c,$02,$83,$00,$21,$76,$02,$92,$20,$44,$f8,$03,$20,$85,$20,$53
+ $43,$28,$4f,$fa,$07,$29,$f6,$53,$43,$28,$4f,$fa,$07,$29,$f9,$12
+ $0a,$00,$3a,$83,$00,$1c,$80,$02,$53,$43,$28,$4f,$fa,$07,$29,$f6
+ $53,$43,$28,$4f,$fa,$07,$29,$f9,$02,$f9,$46,$28,$09,$29,$fd,$05
+ $00,$2e,$8a,$02,$aa,$20,$a1,$28,$44,$29,$f6,$8a,$28,$12,$0a,$00
+ $2c,$d0,$28,$0a,$29,$2c,$d0,$28,$04,$29,$f9,$05,$2c,$12,$0a,$00
+ $29,$3a,$a1,$20,$44,$3a,$a3,$22,$4f,$34,$43,$22,$3a,$83,$00,$05
+ $94,$02,$27,$00,$50,$9e,$02,$46,$28,$50,$29,$f6,$03,$3a,$aa,$20
+ $a1,$28,$50,$29,$f6,$8a,$28,$12,$0a,$00,$2c,$44,$28,$50,$29,$2c
+ $56,$28,$50,$29,$2c,$12,$46,$00,$fc,$56,$28,$50,$29,$2c,$01,$29
+ $3a,$a1,$20,$50,$3a,$a3,$22,$4f,$22,$f9,$dd,$28,$50,$f9,$04,$29
+ $f9,$22,$47,$47,$22,$3a,$53,$28,$50,$29,$f6,$53,$28,$50,$29,$fa
+ $02,$3a,$83,$00,$32,$a8,$02,$92,$20,$50,$f6,$4d,$20,$f0,$20,$d6
+ $28,$50,$29,$f9,$d5,$28,$50,$29,$20,$85,$20,$46,$28,$50,$29,$f6
+ $01,$3a,$a7,$20,$50,$2c,$58,$30,$f9,$50,$fd,$58,$31,$2c,$59,$30
+ $f9,$50,$fd,$59,$31,$00,$05,$b2,$02,$83,$00,$0e,$bc,$02,$92,$20
+ $a1,$28,$50,$29,$20,$85,$20,$83,$00,$0e,$c6,$02,$46,$28,$50,$29
+ $f6,$04,$3a,$b0,$20,$50,$00,$1d,$d0,$02,$92,$20,$53,$43,$28,$01
+ $29,$f7,$48,$20,$f1,$20,$4d,$f3,$01,$20,$85,$20,$48,$f6,$53,$43
+ $28,$01,$29,$00,$1d,$da,$02,$92,$20,$53,$43,$28,$02,$29,$f7,$48
+ $20,$f1,$20,$4d,$f3,$02,$20,$85,$20,$48,$f6,$53,$43,$28,$02,$29
+ $00,$0f,$e4,$02,$92,$20,$4c,$f8,$02,$20,$85,$20,$4c,$f6,$06,$00
+ $51,$ee,$02,$ae,$20,$01,$2c,$12,$14,$00,$3a,$8b,$22,$53,$43,$4f
+ $52,$45,$20,$22,$2c,$53,$43,$28,$01,$29,$2c,$53,$43,$28,$02,$29
+ $2c,$22,$4c,$45,$56,$45,$4c,$22,$3a,$8b,$22,$53,$48,$49,$50,$53
+ $22,$2c,$53,$28,$01,$29,$2c,$53,$28,$02,$29,$2c,$4c,$3b,$22,$20
+ $22,$3a,$8b,$22,$48,$49,$2d,$53,$43,$4f,$52,$45,$22,$2c,$48,$2c
+ $00,$10,$f8,$02,$92,$20,$53,$28,$50,$29,$f7,$01,$20,$85,$20,$83
+ $00,$1e,$02,$03,$92,$20,$4d,$f6,$50,$20,$85,$20,$4c,$f6,$4c,$fa
+ $02,$3a,$53,$28,$50,$29,$f6,$05,$3a,$80,$20,$0b,$e4,$02,$00,$23
+ $0c,$03,$92,$20,$4d,$f6,$fa,$02,$20,$85,$20,$4d,$f6,$50,$3a,$8b
+ $20,$50,$3b,$22,$20,$43,$4f,$4d,$22,$3a,$a3,$22,$45,$45,$22,$3a
+ $83,$00,$34,$16,$03,$ae,$20,$12,$0a,$00,$2c,$12,$0a,$00,$3a,$8b
+ $20,$22,$47,$41,$4d,$45,$20,$4f,$56,$45,$52,$22,$3a,$a3,$22,$4f
+ $31,$43,$35,$43,$44,$45,$44,$36,$43,$34,$43,$35,$22,$3a,$8e,$20
+ $12,$c8,$00,$3a,$82,$00,$05,$20,$03,$27,$00,$19,$2a,$03,$58,$f6
+ $d7,$28,$50,$29,$fc,$09,$fa,$02,$3a,$59,$f6,$d8,$28,$50,$29,$fc
+ $09,$fa,$03,$00,$25,$34,$03,$92,$20,$58,$f8,$01,$20,$f0,$20,$58
+ $f7,$12,$1b,$00,$20,$f0,$20,$59,$f8,$01,$20,$f0,$20,$59,$f7,$12
+ $17,$00,$20,$85,$20,$0b,$70,$03,$00,$15,$3e,$03,$92,$20,$e1,$28
+ $58,$2c,$59,$29,$f3,$22,$cf,$22,$85,$20,$0b,$70,$03,$00,$11,$48
+ $03,$53,$f6,$cb,$28,$e1,$28,$58,$2c,$59,$2c,$02,$29,$29,$00,$11
+ $52,$03,$92,$20,$53,$f6,$50,$f9,$02,$20,$85,$20,$0b,$70,$03,$00
+ $15,$5c,$03,$92,$20,$46,$28,$50,$29,$f6,$01,$20,$85,$20,$46,$28
+ $50,$29,$f6,$02,$00,$05,$66,$03,$27,$00,$10,$70,$03,$92,$20,$a1
+ $28,$49,$29,$f6,$01,$20,$85,$20,$83,$00,$21,$7a,$03,$58,$f6,$28
+ $d7,$28,$49,$29,$fa,$12,$0c,$00,$29,$fc,$09,$3a,$59,$f6,$28,$d8
+ $28,$49,$29,$fa,$12,$14,$00,$29,$fc,$09,$00,$23,$84,$03,$92,$20
+ $58,$f8,$01,$20,$f0,$20,$58,$f7,$12,$1b,$00,$20,$f0,$20,$59,$f8
+ $01,$20,$f0,$20,$59,$f7,$12,$17,$00,$20,$85,$20,$83,$00,$14,$8e
+ $03,$92,$20,$e1,$28,$58,$2c,$59,$29,$f3,$22,$cf,$22,$20,$85,$20
+ $83,$00,$1d,$98,$03,$53,$f6,$cb,$28,$e1,$28,$58,$2c,$59,$2c,$02
+ $29,$29,$3a,$92,$20,$53,$f6,$50,$f9,$02,$20,$85,$20,$83,$00,$16
+ $a2,$03,$b0,$20,$49,$3a,$ae,$20,$58,$2c,$59,$3a,$8b,$20,$22,$20
+ $22,$3b,$3a,$83,$00,$05,$ac,$03,$27,$00,$23,$b6,$03,$8c,$20,$49
+ $f6,$03,$20,$88,$20,$4d,$41,$3a,$92,$20,$a1,$28,$49,$29,$f0,$20
+ $46,$28,$49,$29,$f6,$03,$20,$85,$20,$0b,$d4,$03,$00,$1a,$c0,$03
+ $92,$20,$46,$28,$49,$29,$f6,$02,$20,$85,$20,$81,$20,$0b,$de,$03
+ $3a,$80,$20,$0b,$d4,$03,$00,$28,$ca,$03,$aa,$20,$a1,$28,$49,$29
+ $f6,$8a,$20,$28,$43,$2c,$d0,$28,$0a,$29,$2c,$d0,$28,$04,$29,$f9
+ $43,$fc,$03,$2c,$12,$50,$00,$2c,$01,$29,$3a,$a1,$20,$49,$00,$07
+ $d4,$03,$8d,$3a,$83,$00,$05,$de,$03,$27,$00,$2a,$e8,$03,$46,$28
+ $49,$29,$f6,$01,$3a,$b0,$20,$49,$3a,$a7,$20,$49,$2c,$12,$78,$00
+ $2c,$12,$78,$00,$3a,$92,$20,$55,$f8,$4d,$41,$20,$85,$20,$46,$28
+ $49,$29,$f6,$03,$00,$21,$f2,$03,$55,$f6,$55,$fa,$02,$3a,$92,$20
+ $55,$f6,$01,$20,$85,$20,$46,$28,$03,$29,$f6,$01,$3a,$46,$28,$09
+ $29,$f6,$02,$3a,$83,$00,$63,$fc,$03,$92,$20,$55,$f8,$01,$20,$85
+ $20,$55,$f6,$4e,$3a,$46,$28,$03,$29,$f6,$01,$3a,$46,$28,$04,$29
+ $f6,$01,$3a,$46,$28,$05,$29,$f6,$01,$3a,$46,$28,$06,$29,$f6,$01
+ $3a,$46,$28,$09,$29,$f6,$01,$3a,$43,$f6,$43,$fa,$03,$3a,$4c,$f6
+ $4c,$fa,$02,$3a,$92,$20,$43,$f8,$03,$20,$85,$20,$43,$f6,$08,$3a
+ $92,$20,$4d,$f7,$fa,$02,$20,$85,$20,$53,$28,$02,$fa,$4d,$29,$f6
+ $53,$28,$02,$fa,$4d,$29,$f9,$02,$00,$05,$06,$04,$83,$00,$15,$10
+ $04,$92,$20,$46,$28,$03,$29,$20,$85,$20,$49,$f6,$03,$3a,$80,$0b
+ $de,$03,$00,$32,$1a,$04,$92,$20,$a1,$28,$03,$29,$f6,$01,$20,$85
+ $20,$aa,$20,$a1,$28,$03,$29,$f6,$8a,$28,$03,$2c,$d0,$28,$09,$29
+ $f9,$02,$2c,$d0,$28,$04,$29,$f9,$02,$2c,$12,$0a,$00,$2c,$01,$29
+ $3a,$a1,$20,$03,$00,$05,$24,$04,$83,$00,$05,$2e,$04,$27,$00,$0b
+ $38,$04,$a0,$3a,$84,$20,$0b,$2e,$04,$00,$25,$42,$04,$8c,$50,$f6
+ $01,$88,$12,$c8,$00,$3a,$ae,$d0,$28,$12,$1b,$00,$29,$2c,$d0,$28
+ $12,$17,$00,$29,$3a,$8b,$dc,$28,$12,$cd,$00,$29,$3a,$8d,$00,$17
+ $4c,$04,$ae,$09,$2c,$04,$3a,$8b,$22,$53,$54,$41,$52,$20,$4b,$49
+ $4c,$4c,$45,$52,$22,$00,$4c,$56,$04,$8c,$20,$50,$f6,$01,$20,$88
+ $20,$02,$3a,$ae,$20,$12,$0a,$00,$2c,$12,$0a,$00,$f9,$50,$fd,$09
+ $3a,$aa,$20,$a1,$28,$50,$29,$f6,$8a,$28,$50,$f9,$09,$2c,$44,$28
+ $50,$29,$2c,$03,$2c,$12,$1e,$00,$29,$3a,$a7,$20,$50,$2c,$58,$30
+ $f9,$50,$fd,$58,$31,$2c,$59,$30,$f9,$50,$fd,$59,$31,$3a,$a1,$20
+ $50,$00,$10,$60,$04,$92,$20,$a1,$28,$50,$29,$20,$85,$20,$0b,$60
+ $04,$00,$2c,$6a,$04,$93,$20,$41,$24,$3a,$8c,$20,$49,$f6,$02,$20
+ $88,$20,$ce,$28,$41,$24,$29,$3a,$8b,$20,$d4,$28,$41,$24,$2c,$49
+ $2c,$02,$29,$3b,$3a,$a3,$22,$43,$22,$3a,$8d,$3a,$8d,$00,$64,$74
+ $04,$8c,$20,$50,$f6,$03,$20,$88,$20,$04,$3a,$ae,$20,$12,$0a,$00
+ $2c,$12,$11,$00,$f9,$50,$3a,$93,$20,$41,$24,$3a,$8c,$20,$49,$f6
+ $02,$20,$88,$20,$ce,$28,$41,$24,$29,$3a,$8b,$20,$d4,$28,$41,$24
+ $2c,$49,$2c,$02,$29,$3b,$3a,$a3,$22,$43,$22,$3a,$8d,$3a,$8d,$3a
+ $ae,$02,$2c,$12,$16,$00,$3a,$8b,$22,$7b,$6f,$87,$20,$a4,$20,$60
+ $6e,$a7,$7e,$6c,$65,$28,$59,$2f,$4e,$29,$22,$3b,$3a,$4d,$f6,$fa
+ $02,$00,$17,$7e,$04,$41,$24,$f6,$de,$28,$01,$29,$3a,$92,$41,$24
+ $f6,$22,$4e,$22,$85,$4d,$f6,$02,$00,$0a,$88,$04,$8e,$12,$50,$00
+ $3a,$83,$00,$2e,$92,$04,$91,$20,$22,$50,$4c,$41,$59,$45,$52,$20
+ $31,$22,$2c,$22,$50,$4c,$41,$59,$45,$52,$20,$32,$22,$2c,$22,$20
+ $20,$20,$4f,$52,$22,$2c,$22,$43,$4f,$4d,$50,$55,$54,$45,$52,$22
+ $00,$00
fffa: ed 00 _Vectors6502 .dd2 zpNmiTrampoline
fffc: ba 80 .dd2 _Reset
fffe: ba 80 .dd2 _Reset
.adrend ↑ $8000
.addrs NA ;(auto-generated)
0000: 00 00 07 1f+ .incbin "FB3_CHRROM.bin'
.adrend ↑ NA
No exported symbols found.