├── .gitignore ├── Makefile ├── include ├── all.asm ├── vectors.asm ├── zp.asm ├── registers.asm ├── labels.asm ├── vic.asm ├── basic.asm ├── pseudocommands.asm ├── kernal.asm ├── timer.asm └── macros.asm ├── sprites.asm ├── memory.asm ├── reu.asm ├── README.md └── main.asm /.gitignore: -------------------------------------------------------------------------------- 1 | ByteDump.txt 2 | main.dbg 3 | main.prg 4 | main.sym 5 | main.vs 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KICK=kickass 2 | 3 | main.prg: main.asm 4 | $(KICK) main.asm -vicesymbols -bytedump -debugdump 5 | 6 | run: clean main.prg 7 | x64sc main.prg 8 | 9 | clean: 10 | rm -f main.prg 11 | -------------------------------------------------------------------------------- /include/all.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | #import "registers.asm" 4 | #import "pseudocommands.asm" 5 | #import "labels.asm" 6 | #import "macros.asm" 7 | #import "vectors.asm" 8 | #import "zp.asm" 9 | #import "kernal.asm" 10 | #import "vic.asm" 11 | #import "basic.asm" 12 | -------------------------------------------------------------------------------- /include/vectors.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | .namespace vectors { 4 | .label IERROR = $0300 5 | .label IMAIN = $0302 6 | .label ICRNCH = $0304 7 | .label IQPLOP = $0306 8 | .label IGONE = $0308 9 | .label IEVAL = $030a 10 | } 11 | -------------------------------------------------------------------------------- /include/zp.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | .namespace zp { 4 | .label ENDCHAR = $08 5 | .label COUNT = $0b 6 | .label VALTYP = $0d 7 | .label GARBFL = $0f 8 | .label INDEX = $22 9 | .label FORPNT = $49 10 | .label JMPER = $54 11 | .label FLOAT = $5d 12 | .label EXP = $61 13 | .label FBUFPT = $71 14 | .label CHRGET = $73 15 | .label CHRGOT = CHRGET + $06 16 | .label TXTPTR = $7a 17 | } 18 | -------------------------------------------------------------------------------- /include/registers.asm: -------------------------------------------------------------------------------- 1 | // Pseudo 16-bit registers 2 | // NOTE: These may or may not be completely safe for BASIC and the Kernal. Let me know if they cause problems. 3 | // I tried to target casette related addresses, so if they're going to interfere, it's probably going to be 4 | // with anything using cassette. 5 | 6 | .label r0 = $fb 7 | .label r0L = $fb 8 | .label r0H = $fc 9 | .label r1 = $fd 10 | .label r1L = $fd 11 | .label r1H = $fe 12 | .label r2 = $9e 13 | .label r2L = $9e 14 | .label r2H = $9f 15 | .label r3 = $9b 16 | .label r3L = $9b 17 | .label r3H = $9c 18 | .label r4 = $be 19 | .label r4L = $be 20 | .label r4H = $bf 21 | .label r5 = $b0 22 | .label r5L = $b0 23 | .label r5H = $b1 24 | .label r6 = $b2 25 | .label r6L = $b2 26 | .label r6H = $b3 27 | -------------------------------------------------------------------------------- /include/labels.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | // Timers are stored in the tape buffer. Hopefully you're not using tape. 4 | .label c64lib_timers = kernal.TBUFFER 5 | 6 | // Enable/Disable flags 7 | .label ENABLE = $80 8 | .label DISABLE = $00 9 | .label TRUE = ENABLE 10 | .label FALSE = DISABLE 11 | 12 | // Timer constants 13 | .label TIMER_SINGLE = $00 14 | .label TIMER_CONTINUOUS = $01 15 | .label TIMER_HALF_SECOND = $1e 16 | .label TIMER_ONE_SECOND = $3c 17 | .label TIMER_TWO_SECONDS = $78 18 | .label TIMER_THREE_SECONDS = $b4 19 | .label TIMER_FOUR_SECONDS = $f0 20 | .label TIMER_STRUCT_BYTES = $40 21 | 22 | // The bank the VIC-II chip will be in 23 | .label BANK = $00 24 | 25 | // The start of physical RAM the VIC-II will see 26 | .label VIC_START = (BANK * $4000) 27 | 28 | // Offsets for start of VIC memory for each sprite attribute 29 | .label SPR_VISIBLE = vic.SPENA - vic.SP0X 30 | .label SPR_X_EXPAND = vic.XXPAND - vic.SP0X 31 | .label SPR_Y_EXPAND = vic.YXPAND - vic.SP0X 32 | .label SPR_HMC = vic.SPMC - vic.SP0X 33 | .label SPR_PRIORITY = vic.SPBGPR - vic.SP0X 34 | 35 | // Flags for various sprite settings 36 | .label SPR_HIDE = DISABLE 37 | .label SPR_SHOW = ENABLE 38 | .label SPR_NORMAL = DISABLE 39 | .label SPR_EXPAND = ENABLE 40 | .label SPR_FG = DISABLE 41 | .label SPR_BG = ENABLE 42 | .label SPR_HIRES = DISABLE 43 | .label SPR_MULTICOLOR = ENABLE 44 | 45 | // Joystick constants 46 | .label JOY_BUTTON = %00010000 47 | .label JOY_UP = %00000001 48 | .label JOY_DOWN = %00000010 49 | .label JOY_LEFT = %00000100 50 | .label JOY_RIGHT = %00001000 51 | -------------------------------------------------------------------------------- /sprites.asm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Change a sprite's attribute 4 | 5 | r0L = sprite number (0-7) 6 | r0H = attribute to change (SPR_VISIBLE, SPR_X_EXPAND, SPR_Y_EXPAND, SPR_HMC, SPR_PRIORITY) 7 | r1L = value for the attribute 8 | - Sprite visibility (SPR_SHOW, SPR_HIDE) 9 | - X/Y expansion (SPR_NORMAL, SPR_EXPAND) 10 | - Priority (SPR_FG, SPR_BG) 11 | - Hires or Multicolor (SPR_HIRES, SPR_MULTICOLOR) 12 | */ 13 | ChangeSpriteAttribute: 14 | ldy r0H 15 | ldx r0L 16 | lda BitMaskPow2, x 17 | ldx r1L 18 | cpx #ENABLE 19 | beq !set_flag+ 20 | eor #$ff 21 | and vic.SP0X, y 22 | jmp !clear_flag+ 23 | !set_flag: 24 | ora vic.SP0X, y 25 | !clear_flag: 26 | sta vic.SP0X, y 27 | 28 | rts 29 | 30 | /* 31 | 32 | Position a sprite anywhere on the screen. This also takes care of the MSIGX register, which is used to push the sprite 33 | beyond an X position of 255. 34 | 35 | r0L = sprite number (0-7) 36 | r1: x position (0-319) 37 | r2L: y position (0-199) 38 | 39 | Also trashes r3 40 | */ 41 | PositionSprite: 42 | lda r0L 43 | mult2 44 | tay 45 | stb r2L:vic.SP0Y, y 46 | stb r1L:r3L 47 | lda r1H 48 | adc #$00 49 | sta r3H 50 | stb r3L:vic.SP0X, y 51 | ldx r0L 52 | lda BitMaskPow2, x 53 | eor #$ff 54 | and vic.MSIGX 55 | tay 56 | lda #$01 57 | and r3H 58 | beq !no_msb+ 59 | tya 60 | ora BitMaskPow2, x 61 | tay 62 | 63 | !no_msb: 64 | sty vic.MSIGX 65 | 66 | !return: 67 | rts 68 | 69 | /* 70 | 71 | Quick lookup table for sprite numbers 72 | 73 | */ 74 | BitMaskPow2: 75 | .byte %00000001 76 | .byte %00000010 77 | .byte %00000100 78 | .byte %00001000 79 | .byte %00010000 80 | .byte %00100000 81 | .byte %01000000 82 | .byte %10000000 -------------------------------------------------------------------------------- /memory.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | /* 4 | 5 | Copy a block of memory from one location to another 6 | 7 | r0: source 8 | r1: target 9 | r2: number of bytes 10 | 11 | Pilfered from https://github.com/mist64/geos/blob/2090bca64fc2627beef5c8232aafaec61f1f5a53/kernal/memory/memory2.s#L123 12 | 13 | */ 14 | MemCopy: 15 | lda r2L 16 | ora r2H 17 | beq !l7+ 18 | PushW(r0) 19 | PushB(r1H) 20 | PushB(r2H) 21 | PushB(r3L) 22 | !l1: 23 | CmpW(r0, r1) 24 | !l2: 25 | bcs !l3+ 26 | bcc !l8+ 27 | !l3: 28 | ldy #0 29 | lda r2H 30 | beq !l5+ 31 | !l4: 32 | lda (r0), y 33 | sta (r1), y 34 | iny 35 | bne !l4- 36 | inc r0H 37 | inc r1H 38 | dec r2H 39 | bne !l4- 40 | !l5: 41 | cpy r2L 42 | beq !l6+ 43 | lda (r0), y 44 | sta (r1), y 45 | iny 46 | bra !l5- 47 | !l6: 48 | PopB(r3L) 49 | PopB(r2H) 50 | PopB(r1H) 51 | PopW(r0) 52 | !l7: 53 | rts 54 | 55 | !l8: 56 | clc 57 | lda r2H 58 | adc r0H 59 | sta r0H 60 | clc 61 | lda r2H 62 | adc r1H 63 | sta r1H 64 | ldy r2L 65 | beq !lA+ 66 | !l9: 67 | dey 68 | lda (r0), y 69 | sta (r1), y 70 | tya 71 | bne !l9- 72 | !lA: 73 | dec r0H 74 | dec r1H 75 | lda r2H 76 | beq !l6- 77 | !lB: 78 | dey 79 | lda (r0), y 80 | sta (r1), y 81 | tya 82 | bne !lB- 83 | dec r2H 84 | bra !lA- 85 | 86 | /* 87 | 88 | Fill a block of memory with a byte 89 | 90 | r0: start address 91 | r1: number of bytes 92 | r2L: byte to write 93 | 94 | */ 95 | MemFill: 96 | ldy #$00 97 | !l1: 98 | lda r2L 99 | sta (r0), y 100 | Dec16(r1) 101 | CmpWI(r1, $0000) 102 | beq !end+ 103 | iny 104 | bne !l1- 105 | inc r0H 106 | jmp !l1- 107 | 108 | !end: 109 | rts 110 | -------------------------------------------------------------------------------- /include/vic.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | .namespace vic { 4 | // The 47 VIC-II registers 5 | .label SP0X = $d000 6 | .label SP0Y = $d001 7 | .label SP1X = $d002 8 | .label SP1Y = $d003 9 | .label SP2X = $d004 10 | .label SP2Y = $d005 11 | .label SP3X = $d006 12 | .label SP3Y = $d007 13 | .label SP4X = $d008 14 | .label SP4Y = $d009 15 | .label SP5X = $d00a 16 | .label SP5Y = $d00b 17 | .label SP6X = $d00c 18 | .label SP6Y = $d00d 19 | .label SP7X = $d00e 20 | .label SP7Y = $d00f 21 | .label MSIGX = $d010 // MSB of sprite x positions. 1 bit per sprite 22 | .label SCROLY = $d011 23 | .label RASTER = $d012 24 | .label LPENX = $d013 25 | .label LPENY = $d014 26 | .label SPENA = $d015 // Sprite enable 27 | .label SCROLX = $d016 28 | .label YXPAND = $d017 29 | .label VMCSB = $d018 30 | .label VICIRQ = $d019 31 | .label IRQMSK = $d01a 32 | .label SPBGPR = $d01b 33 | .label SPMC = $d01c 34 | .label XXPAND = $d01d 35 | .label SPSPCL = $d01e 36 | .label SPBGCL = $d01f 37 | .label EXTCOL = $d020 38 | .label BGCOL0 = $d021 39 | .label BGCOL1 = $d022 40 | .label BGCOL2 = $d023 41 | .label BGCOL3 = $d024 42 | .label SPMC0 = $d025 43 | .label SPMC1 = $d026 44 | .label SP0COL = $d027 45 | .label SP1COL = $d028 46 | .label SP2COL = $d029 47 | .label SP3COL = $d02a 48 | .label SP4COL = $d02b 49 | .label SP5COL = $d02c 50 | .label SP6COL = $d02d 51 | .label SP7COL = $d02e 52 | 53 | // These are not in the VIC address space. Move them to the CIA namespace? 54 | .label CLRRAM = $d800 55 | .label COLCLK = $d81a 56 | .label TODTN1 = $dc08 57 | .label CIAICR = $dc0d 58 | .label TODTN2 = $dd08 59 | .label CI2ICR = $dd0d 60 | 61 | .label BLACK = $00 62 | .label WHITE = $01 63 | .label RED = $02 64 | .label CYAN = $03 65 | .label PURPLE = $04 66 | .label VIOLET = $04 67 | .label GREEN = $05 68 | .label BLUE = $06 69 | .label YELLOW = $07 70 | .label ORANGE = $08 71 | .label BROWN = $09 72 | .label LT_RED = $0a 73 | .label DK_GRAY = $0b 74 | .label MED_GRAY = $0c 75 | .label LT_GREEN = $0d 76 | .label LT_BLUE = $0e 77 | .label LT_GRAY = $0f 78 | } 79 | -------------------------------------------------------------------------------- /include/basic.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | /* 4 | 5 | Labels for common BASIC routines 6 | 7 | */ 8 | .namespace basic { 9 | .label RESLST = $a09e 10 | .label ERROR = $a437 11 | .label CUSTERROR = $a445 12 | .label NEWSTT = $a7ae 13 | .label EXECOLD = $a7ed 14 | .label CHAROUT = $ab47 15 | .label FRMNUM = $ad8a 16 | .label FRMEVL = $ad9e 17 | .label EVAL = $ae83 18 | .label FUNCTOLD = $ae8d 19 | .label PARCHK = $aef1 20 | .label CHKCOM = $aefd 21 | .label FACINX = $b1aa 22 | .label ILLEGAL_QUANTITY = $b248 23 | .label GETBYTC = $b79b 24 | .label GETADR = $b7f7 25 | .label OVERR = $b97e 26 | .label FINLOG = $bd7e 27 | 28 | .label ERROR_TOO_MANY_FILES = $01 29 | .label ERROR_FILE_OPEN = $02 30 | .label ERROR_FILE_NOT_OPEN = $03 31 | .label ERROR_FILE_NOT_FOUND = $04 32 | .label ERROR_DEVICE_NOT_PRESENT = $05 33 | .label ERROR_NOT_INPUT_FILE = $06 34 | .label ERROR_NOT_OUTPUT_FILE = $07 35 | .label ERROR_MISSING_FILENAME = $08 36 | .label ERROR_ILLEGAL_DEVICE_NUM = $09 37 | .label ERROR_NEXT_WITHOUT_FOR = $0a 38 | .label ERROR_SYNTAX = $0b 39 | .label ERROR_RETURN_WITHOUT_GOSUB = $0c 40 | .label ERROR_OUT_OF_DATA = $0d 41 | .label ERROR_ILLEGAL_QUANTITY = $0e 42 | .label ERROR_OVERFLOW = $0f 43 | .label ERROR_OUT_OF_MEMORY = $10 44 | .label ERROR_UNDEFD_STATEMENT = $11 45 | .label ERROR_BAD_SUBSCRIPT = $12 46 | .label ERROR_REDIMD_ARRAY = $13 47 | .label ERROR_DIVISION_BY_ZERO = $14 48 | .label ERROR_ILLEGAL_DIRECT = $15 49 | .label ERROR_TYPE_MISMATCH = $16 50 | .label ERROR_STRING_TOO_LONG = $17 51 | .label ERROR_FILE_DATA = $18 52 | .label ERROR_FORMULA_TOO_COMPLEX = $19 53 | .label ERROR_CANT_CONTINUE = $1a 54 | .label ERROR_UNDEFD_FUNCTION = $1b 55 | .label ERROR_VERIFY = $1c 56 | .label ERROR_LOAD = $1d 57 | } 58 | -------------------------------------------------------------------------------- /include/pseudocommands.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | /* 4 | 5 | KickAssembler has a cool feature called 'pseudocommands' that let you build 6 | your own pseudo instructions. They're similar to macros, but the calling 7 | syntax very much resembles standard 6502 instructions. 8 | 9 | */ 10 | 11 | 12 | /* BFS: branch if flag set */ 13 | .pseudocommand bfs flag:location { 14 | lda flag 15 | bmi location 16 | } 17 | 18 | /* BFC: branch if flag clear */ 19 | .pseudocommand bfc flag:location { 20 | lda flag 21 | bpl location 22 | } 23 | 24 | /* CLF: clear flag */ 25 | .pseudocommand clf flag { 26 | Disable(flag) 27 | } 28 | 29 | /* SEF: set flag */ 30 | .pseudocommand sef flag { 31 | Enable(flag) 32 | } 33 | 34 | /* TGF: toggle flag */ 35 | .pseudocommand tgf flag { 36 | Toggle(flag) 37 | } 38 | 39 | /* WFS: wait for flag set */ 40 | .pseudocommand wfs flag { 41 | !wait: 42 | bfc flag:!wait- 43 | } 44 | 45 | /* WFC: wait for flag clear */ 46 | .pseudocommand wfc flag { 47 | !wait: 48 | bfs flag:!wait- 49 | } 50 | 51 | /* WFV: wait for value */ 52 | .pseudocommand wfv address:value { 53 | !wait: 54 | jne address:value:!wait- 55 | } 56 | 57 | /* JNE: jump if not equal */ 58 | .pseudocommand jne address:value:location { 59 | lda address 60 | cmp value 61 | bne location 62 | } 63 | 64 | /* JEQ: jump if equal */ 65 | .pseudocommand jeq address:value:location { 66 | lda address 67 | cmp value 68 | beq location 69 | } 70 | 71 | /* STB: store byte */ 72 | .pseudocommand stb value:address { 73 | lda value 74 | sta address 75 | } 76 | 77 | /* MULT2: multiply the accumulator by 2 */ 78 | .pseudocommand mult2 { 79 | asl 80 | } 81 | 82 | /* DIV2: divide the accumulator by 2 */ 83 | .pseudocommand div2 { 84 | lsr 85 | } 86 | 87 | /* MULT4: multiply the accumulator by 4 */ 88 | .pseudocommand mult4 { 89 | mult2 90 | mult2 91 | } 92 | 93 | /* DIV4: divide the accumulator by 4 */ 94 | .pseudocommand div4 { 95 | div2 96 | div2 97 | } 98 | 99 | /* MULT8: multiply the accumulator by 8 */ 100 | .pseudocommand mult8 { 101 | mult4 102 | asl 103 | } 104 | 105 | /* MULT16: multiply the accumulator by 16 */ 106 | .pseudocommand mult16 { 107 | mult8 108 | mult4 109 | } 110 | 111 | /* BRA: branch always */ 112 | .pseudocommand bra address { 113 | clv 114 | bvc address 115 | } 116 | -------------------------------------------------------------------------------- /include/kernal.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | .namespace kernal { 4 | .label LSTX = $c5 5 | .label CINV = $0314 6 | .label IRQVEC = $0314 7 | 8 | .label CBINV = $0316 9 | .label NMINV = $0318 10 | 11 | // KERNAL vectors 12 | .label IOPEN = $031a 13 | .label ICLOSE = $031c 14 | .label ICHKIN = $031e 15 | .label ICKOUT = $0320 16 | .label ICLRCH = $0322 17 | .label IBASIN = $0324 18 | .label IBSOUT = $0326 19 | .label ISTOP = $0328 20 | .label IGETIN = $032a 21 | .label ICLALL = $032c 22 | .label USRCMD = $032e 23 | .label ILOAD = $0330 24 | .label ISAVE = $0332 25 | 26 | .label FREE1 = $0334 // 8 free bytes (0334 - 033b) 27 | .label TBUFFER= $033c // cassette buffer (033c - 03fb) 28 | .label FREE2 = $03fc // 4 free bytes (03fc - 03ff) 29 | 30 | .label POLY1 = $e043 31 | .label POLY2 = $e059 32 | .label RMULC = $e08d 33 | .label RADDC = $e092 34 | .label RND = $e097 35 | .label SYS = $e12a 36 | .label SAVE = $e156 37 | .label VERIFY = $e165 38 | .label LOAD = $e168 39 | .label OPEN = $e1be 40 | .label CLOSE = $e1c7 41 | 42 | .label CLRSCR = $e544 43 | .label HOME = $e566 44 | 45 | .label IRQNOR = $ea31 46 | 47 | // KERNAL jump Table 48 | .label VEC_CINT = $ff81 49 | .label VEC_IOINIT = $ff84 50 | .label VEC_RAMTAS = $ff87 51 | .label VEC_RESTOR = $ff8a 52 | .label VEC_VECTOR = $ff8d 53 | .label VEC_SETMSG = $ff90 54 | .label VEC_SECOND = $ff93 55 | .label VEC_TKSA = $ff96 56 | .label VEC_MEMBOT = $ff99 57 | .label VEC_MEMTOP = $ff9c 58 | .label VEC_SCNKEY = $ff9f 59 | .label VEC_SETTMO = $ffa2 60 | .label VEC_ACPTR = $ffa5 61 | .label VEC_CIOUT = $ffa8 62 | .label VEC_UNTLK = $ffab 63 | .label VEC_UNLSN = $ffae 64 | .label VEC_LISTEN = $ffb1 65 | .label VEC_TALK = $ffb4 66 | .label VEC_READST = $ffb7 67 | .label VEC_SETLFS = $ffba 68 | .label VEC_SETNAM = $ffbd 69 | .label VEC_OPEN = $ffc0 70 | .label VEC_CLOSE = $ffc3 71 | .label VEC_CHKIN = $ffc6 72 | .label VEC_CHKOUT = $ffc9 73 | .label VEC_CLRCHN = $ffcc 74 | .label VEC_CHRIN = $ffcf 75 | .label VEC_CHROUT = $ffd2 // https://www.c64-wiki.com/wiki/CHROUT 76 | .label VEC_LOAD = $ffd5 77 | .label VEC_SAVE = $ffd8 78 | .label VEC_SETTIM = $ffdb 79 | .label VEC_RDTIM = $ffde 80 | .label VEC_STOP = $ffe1 81 | .label VEC_GETIN = $ffe4 82 | .label VEC_CLALL = $ffe7 83 | .label VEC_UDTIM = $ffea 84 | .label VEC_SCREEN = $ffed 85 | .label VEC_PLOT = $fff0 // https://www.c64-wiki.com/wiki/PLOT_(KERNAL) 86 | .label VEC_IOBASE = $fff3 87 | 88 | .label VEC_NMI = $fffa // NMI vector 89 | .label VEC_RESET = $fffc // Hardware reset vector 90 | .label VEC_IRQ = $fffe // IRQ / BRK vector 91 | } 92 | -------------------------------------------------------------------------------- /include/timer.asm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | These routines can be used to create and fire single-shot and continuous timers. The UpdateTimers 4 | routine should be called 60 times a second. 5 | 6 | Each timer is comprised of 8 bytes: 7 | 8 | 0 - enabled/disabled (1 = enabled) 9 | 1 - single-shot/continuous (0 = single-shot) 10 | 2,3 - the timer's current value 11 | 4,5 - the timer's frequency (60 = 1 second) 12 | 6,7 - the address of the routine to call when the timer fires 13 | 14 | The 8 timers are stored in memory 'c64lib_timers' 15 | 16 | */ 17 | 18 | /* 19 | Clear memory used for timers 20 | */ 21 | // ClearTimers: 22 | // FillMI($00, c64lib_timers, TIMER_STRUCT_BYTES) 23 | // rts 24 | 25 | /* 26 | 27 | Create a timer that fires either on a schedule or as a single shot 28 | 29 | r0 frequency in 60ths of a second (60 = wait 1 second) 30 | r1 location to call when timer fires 31 | r2L single shot or continuous (0 = single shot, 1 = continuous) 32 | r2H timer number (0 - 7) 33 | r3L enabled or disabled (0 = disabled, 1 = enabled) 34 | 35 | */ 36 | CreateTimer: 37 | lda r2H 38 | mult8 39 | tax 40 | stb r3L:c64lib_timers, x // enabled 41 | stb r2L:c64lib_timers + $01, x // type 42 | stb r0L:c64lib_timers + $02, x // current value 43 | stb r0H:c64lib_timers + $03, x 44 | stb r0L:c64lib_timers + $04, x // frequency 45 | stb r0H:c64lib_timers + $05, x 46 | stb r1L:c64lib_timers + $06, x // address 47 | stb r1H:c64lib_timers + $07, x 48 | 49 | rts 50 | 51 | /* 52 | 53 | Should be called every 1/60th of a second and will update timers and call timer targets if required. 54 | 55 | */ 56 | UpdateTimers: 57 | ldy #$00 58 | !loop: 59 | tya 60 | mult8 61 | sta r0L 62 | tax 63 | // Is the timer enabled? 64 | jne c64lib_timers, x:#ENABLE:!continue+ 65 | inx 66 | stb c64lib_timers, x:r3H 67 | inx 68 | stx r1H 69 | lda c64lib_timers, x // current value 70 | bne !return+ 71 | dec c64lib_timers + $01, x 72 | !return: 73 | dec c64lib_timers, x 74 | 75 | lda c64lib_timers, x 76 | bne !continue+ 77 | lda c64lib_timers + $01, x 78 | bne !continue+ 79 | 80 | // Counter has hit 0 - reset it 81 | inx 82 | inx 83 | stb c64lib_timers, x:r2L 84 | inx 85 | stb c64lib_timers, x:r2H 86 | 87 | lda r1H 88 | tax 89 | 90 | stb r2L:c64lib_timers, x 91 | inx 92 | stb r2H:c64lib_timers, x 93 | 94 | inx 95 | inx 96 | inx 97 | tya 98 | pha 99 | 100 | // Set up the vector to point at the timer's routine 101 | stb c64lib_timers, x:r2L 102 | inx 103 | stb c64lib_timers, x:r2H 104 | 105 | // Call the timer's routine 106 | jsr !dispatch+ 107 | jmp !done_dispatch+ 108 | 109 | !dispatch: 110 | jmp (r2) 111 | 112 | !done_dispatch: 113 | pla 114 | tay 115 | 116 | // If the timer is continuous, skip the disabling of it 117 | jeq r3H:#TIMER_CONTINUOUS:!continue+ 118 | 119 | // For single shot timers, disable it after the first execution 120 | ldx r0L 121 | stb #DISABLE:c64lib_timers, x 122 | 123 | !continue: 124 | iny 125 | cpy #$07 126 | bne !loop- 127 | 128 | rts 129 | 130 | /* 131 | 132 | Enable or disable a timer 133 | 134 | r2H the timer to control 135 | r3L enable or disable (0 = disable, 1 = enable) 136 | 137 | */ 138 | EnDisTimer: 139 | lda r2H 140 | mult8 141 | tax 142 | stb r3L:c64lib_timers, x 143 | rts 144 | -------------------------------------------------------------------------------- /include/macros.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | /* 4 | Push everything onto the stack. This lets us do whatever we want with the 5 | registers and put it back the way it was before returning. This is mostly 6 | used by the raster interrupt routine, but can be used anywhere. 7 | */ 8 | .macro PushStack() { 9 | php 10 | pha 11 | txa 12 | pha 13 | tya 14 | pha 15 | } 16 | 17 | /* 18 | Sets the registers and processor status back to the way they were 19 | */ 20 | .macro PopStack() { 21 | pla 22 | tay 23 | pla 24 | tax 25 | pla 26 | plp 27 | } 28 | 29 | /* 30 | Toggle a flag 31 | */ 32 | .macro Toggle(address) { 33 | lda address 34 | eor #ENABLE 35 | sta address 36 | } 37 | 38 | /* 39 | Disable a flag 40 | */ 41 | .macro Disable(address) { 42 | stb #DISABLE:address 43 | } 44 | 45 | /* 46 | Enable a flag 47 | */ 48 | .macro Enable(address) { 49 | stb #ENABLE:address 50 | } 51 | 52 | /* 53 | Copy the contents of the source address to the target address 54 | */ 55 | .macro CpyW(source, target) { 56 | stb source:target 57 | stb source + $01:target + $01 58 | } 59 | 60 | /* 61 | Load a word into a target address 62 | */ 63 | .macro CpyWI(word, target) { 64 | stb #word:target + $01 66 | } 67 | 68 | #if MEMORY 69 | 70 | /* 71 | Copy a block of memory 72 | */ 73 | .macro CpyM(source, target, length) { 74 | CpyWI(source, r0) 75 | CpyWI(target, r1) 76 | CpyWI(length, r2) 77 | 78 | jsr CopyMemory 79 | } 80 | 81 | /* 82 | Fill a chunk of memory with an immediate value 83 | */ 84 | .macro FillMI(value, target, length) { 85 | stb #value:r0L 86 | CpyWI(target, r1) 87 | CpyWI(length, r2) 88 | 89 | jsr FillMemory 90 | } 91 | 92 | /* 93 | Fill a chunk of memory with a value from memory 94 | */ 95 | .macro FillM(value, target, length) { 96 | stb value:r0L 97 | CpyWI(target, r1) 98 | CpyWI(length, r2) 99 | 100 | jsr FillMemory 101 | } 102 | 103 | #endif 104 | 105 | /* 106 | Do a 16-bit increment of a memory location 107 | */ 108 | .macro Inc16(word) { 109 | inc word 110 | bne !return+ 111 | inc word + $01 112 | !return: 113 | } 114 | 115 | /* 116 | Do a 16-bit decrement of a memory location 117 | */ 118 | .macro Dec16(word) { 119 | lda word 120 | bne !return+ 121 | dec word + $01 122 | !return: 123 | dec word 124 | } 125 | 126 | /* 127 | Compare 2 bytes 128 | */ 129 | .macro CmpB(byte1, byte2) { 130 | lda byte1 131 | cmp byte2 132 | } 133 | 134 | /* 135 | Compare a byte in memory with an immediate value 136 | */ 137 | .macro CmpBI(byte1, byte2) { 138 | lda byte1 139 | cmp #byte2 140 | } 141 | 142 | /* 143 | Compare a word in memory to an immediate word value 144 | */ 145 | .macro CmpWI(word1, word2) { 146 | CmpBI(word1 + $01, >word2) 147 | bne !return+ 148 | CmpBI(word1 + $00, reutext 156 | jmp $ab1e 157 | //------------------------------------------------- 158 | rinit: 159 | ldx #00 // 1764 wake up 160 | rinit2: 161 | lda #$80 // stash 162 | sta config 163 | lda #$12 // write some crap in ... 164 | sta c64hi+1 165 | stx bank+1 166 | jsr main 167 | inx 168 | cpx #$21 // ... 33 banks into somewhere 169 | bne rinit2 170 | jmp action 171 | noreu: 172 | lda #notext 174 | jmp $ab1e 175 | 176 | //-------------------------------------------------- 177 | // Count banks 178 | //-------------------------------------------------- 179 | action: 180 | lda reustatus 181 | ldx #$00 182 | stx bank+1 // reset bank counter 183 | check: 184 | lda #$81 // fetch : transfer to C64 : $1300 185 | sta config 186 | lda #$13 // C64 : $1300 187 | sta c64hi+1 188 | jsr main 189 | lda #$80 // stash 190 | sta config 191 | lda #$0a // write dummy bytes from $0900 192 | sta c64hi+1 193 | jsr main 194 | jsr bankcheck // check for existing ram banks 195 | inx 196 | cpx #$21 // try 33 197 | stx bank+1 198 | bne check 199 | //-------------------------------------------------- 200 | 201 | lda banks // banks found ? 202 | cmp #4 203 | bne j1 204 | r1764: 205 | lda #4 206 | sta banks 207 | lda #text1764 209 | jmp $ab1e 210 | j1: 211 | cmp #8 212 | bne j2 213 | r512: 214 | lda #reut512 216 | jmp $ab1e 217 | j2: 218 | cmp #16 219 | bne j3 220 | r1024: 221 | lda #reut1024 223 | jmp $ab1e 224 | j3: 225 | cmp #20 226 | beq r1764 227 | j4: 228 | cmp #32 229 | bne j6 230 | r2048: 231 | lda #reut2048 233 | jmp $ab1e 234 | j6: 235 | lda #reuunk 237 | jmp $ab1e 238 | 239 | //-------------------------------------------------- 240 | // Bank Check 241 | //-------------------------------------------------- 242 | bankcheck: 243 | ldy #$00 244 | l2: 245 | lda $0A00,y 246 | l6: 247 | cmp $1300,y 248 | bne l5 // bank found ? 249 | l3: 250 | iny 251 | cpy #16 252 | bne l2 // loop 253 | end: 254 | ldy #00 255 | lda #00 256 | delete: 257 | sta $1300,y // delete buffer 258 | iny 259 | cpy #16 260 | bne delete 261 | rts 262 | l5: 263 | inc banks // bank found (inc), border color change and exit 264 | rts 265 | 266 | //-------------------------------------------------- 267 | // Bytes and text 268 | //-------------------------------------------------- 269 | 270 | reutext: 271 | .text "REU 1700 : 128KB DETECTED" 272 | .byte $00 273 | text1764: 274 | .text "REU 1764 : 256KB DETECTED" 275 | .byte $00 276 | reut512: 277 | .text "REU 1750 : 512KB DETECTED" 278 | .byte $00 279 | reut1024: 280 | .text "REU 1024KB DETECTED" 281 | .byte $00 282 | reut2048: 283 | .text "REU 2048KB DETECTED" 284 | .byte $00 285 | reuunk: 286 | .text "REU PORT DETECTED - BANK ERROR" 287 | .byte $00 288 | notext: 289 | .text "NO REU" 290 | .byte $00 291 | 292 | //-------------------------------------------------- 293 | // REU TRANSFER ROUTINE 294 | //-------------------------------------------------- 295 | 296 | main: 297 | lda config 298 | sta reustatus+1 299 | lda #$00 300 | sta reustatus+2 301 | c64hi: 302 | lda #$09 303 | sta reustatus+3 304 | lda #$00 305 | sta reustatus+4 306 | sta reustatus+5 307 | bank: 308 | lda #0 309 | sta reustatus+6 // Bank 310 | rbytes: 311 | lda #16 312 | sta reustatus+7 // 16 Bytes 313 | lda #$00 314 | sta reustatus+8 315 | irq: 316 | lda #$00 317 | sta reustatus+9 318 | lda #$00 319 | sta reustatus+10 320 | lda $1 321 | pha 322 | lda #$30 // All RAM 323 | sei 324 | sta $1 325 | sta $ff00 326 | pla 327 | sta $1 328 | cli 329 | rts 330 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Introduction 3 | 4 | This is a skeleton C64 program that will allow you to add your own custom functions and commands to the C64's BASIC. We all know how much the C64's BASIC **sucks**, so with this you can add the commands that you've always wanted! 5 | 6 | A lot of the code in here comes from the book **The Advanced Machine Language Book for the Commodore 64** by Abacus Software. There's a section in the later part of the book that shows you how to add your own custom commands. 7 | 8 | I simply took this and ran with it. I also commented it as best as I could. I know that it can be very hard to follow other people's 6510 assembly, but I'm really hoping it's clear what each line of code does. 9 | 10 | ### Goals 11 | 12 | I grew up during the 8-bit era and really acquired a passion for technology, mostly because of how simple things were. It was easy to get started with BASIC. I was always disappointed in the C64's BASIC and dreamed of being able to create my own extensions (like David Simons did). I was too poor to be able to afford an assembler, so my dream was put on hold. 13 | 14 | Now, with tools like Kick Assembler and VICE plus every C64 programming book known to man being available on the Internet Archive, I'm able to **finally** do what I've always wanted! 15 | 16 | My goals are: 17 | 18 | - Make C64 BASIC more usable. I don't have a domain in mind, and I'm not really targeting games. I'm sure it would be fun to build in some really cool sprite commands, but I think that may be beyond me at the moment. 19 | - Create an easy-to-use, well-documented framework to allow others to add their own commands/functions to C64 BASIC. 20 | - Add examples that others can use to create their own commands. 21 | - Get better at 6510 assembly. It's not a current skill, but it absolutely will teach you to really break problems down. 22 | - Have fun and hopefully excite others. 23 | 24 | 25 | Suggestions and pull requests are welcome! 26 | 27 | 28 | ### Building 29 | 30 | This is built with Kick Assembler. The latest version should be fine. There's a Makefile at the root of the directory that you can use to build and run it. 31 | 32 | The code assembles at $c000 (49152) which is a 4k block of RAM that's popular with ML programmers. 33 | 34 | Once it starts in VICE, type: 35 | 36 | ```basic 37 | NEW 38 | SYS49152 39 | ``` 40 | 41 | There are now a handful of commands available: 42 | 43 | - `BACKGROUND` - Sets the background color: Example: `BACKGROUND 2` would set it to red 44 | - `BORDER` - Sets the border color. Works the same as BACKGROUND 45 | - `CLS` - Clears the screen. Nice and simple. 46 | - `WOKE` - A 16-bit version of the POKE command. Example: `WOKE $61, $0400` would put $00 into $61 and $04 into $62. 47 | - `MEMCOPY` - A fast way to move bytes around in memory. Example: `MEMCOPY $0428, $0400, $28` would copy the 2nd line of text to the first. This could be used to quickly scroll portions of the screen. 48 | - `MEMFILL` - No more slow FOR/NEXT loops to POKE values into memory. Example: `MEMFILL $0400, 1000, 32` would clear the default screen. 49 | - `BANK` - Select the current VIC bank. Must be between 0-3 where 0 starts at $0000 and 3 starts at $c000. This affects all graphics on the machine including screen, chars and sprites 50 | - `SCREEN` - Select the 1k offset for the current video screen. The default is 1 ($0400) in bank 0. Each bank can hold 16 screens (not all can be used for screen RAM), so the value here has to be between 0 and 15. 51 | - `STASH` - Copy bytes from the C64 to an attached REU. Example `STASH $0400, $0, 1000, 0` would copy the default screen to REU address $0 in bank $0. 52 | - `FETCH` - Copy bytes from an attached REU to the C64. Example `FETCH $0400, $0, 1000, 0` would copy bytes from REU address $0, bank $0 to the C64's default screen $0400 53 | - `MEMLOAD` - Loads binary data from disk into memory. Example `MEMLOAD "SHAPES.SPR",8,$8000` would load shape data from the file `SHAPES.SPR` to $8000, which is the start of VIC bank 2 54 | - `MEMSAVE` - Saves binary data from memory to disk. Example `MEMSAVE "@:SHAPES.SPR,P,W",$8000,$8040` would save the 64 bytes between $8000 and $8040 to a file named `SHAPES.SPR` 55 | - `SPRSET` - Turn sprites on/off as well as set their pointer to their shape data. 56 | - `SPRPOS` - Set a sprite's X and Y positions. X can be 0 - 511 and Y can be 0 - 255 57 | - `SPRCOLOR` - Set a sprite's color 58 | - `DIR` - Display a disk directory listing. Example `DIR 8` would display the disk directory of a disk in device 8. 59 | 60 | As well as a couple of functions: 61 | 62 | - `WEEK` - A version of the PEEK function which returns a 16-bit word instead of an 8-bit byte. Example: `PRINT WEEK($61)` 63 | - `SCRLOC` - Returns the absolute address for the start of screen RAM. At startup `PRINT SCRLOC(0)` would return `1024`. You can use the `BANK` and `SCREEN` commands to relocate the screen. 64 | - `REU` - Detect the type of attached REU, if any. Currently this is broken and needs some TLC. 65 | 66 | In addition to the commands and functions, BASIC will now accept integers as either HEX or binary. So for example: `POKE $0400, $0a` or `POKE $d020, %00001111`. You're not limited to 16-bit values, so `PRINT $d00000` is perfectly valid. 67 | 68 | ### Extending 69 | 70 | There are 4 hooks currently implemented: 71 | 72 | - `ICRNCH` - This routine is what BASIC uses to tokenize input. All commands and functions have a single byte token that gets stored internally. This is redirected to the `ConvertToTokens` routine which allows us to tokenize our custom commands/functions. 73 | - `IQPLOP` - This is the routine that BASIC uses to convert from token to text and is used when LISTing programs. We redirect this to the `ConvertFromTokens` routine so that our commands/functions can be expanded from their token form. 74 | - `IGONE` - This is what BASIC calls when it's found a command token and needs to call its execution routine. This is directed to the `ExecuteCommand` routine which has a lookup table to decide, based on the token, which routine to call. 75 | - `IEVAL` - When BASIC finds a function, it calls this to execute the function's routine. We use the routine `ExecuteFunction` to handle that which also has a lookup table to determine which routine goes with which function. 76 | 77 | You shouldn't have to modify any of the hooks unless you want to do something different. If you just want to add commands and functions, follow these steps: 78 | 79 | - Modify the table located at the `NewTab` label. These are the strings for your new commands/functions. Each must have the high bit of the last character in its string set. Token numbers for this table start at $cc and you must keep your functions and commands grouped together with commands coming first. You can add commands up to $fe ($ff is pi) 80 | 81 | - In order to identify the start/end token for commands, set `CMDSTART`, `CMDEND`, and for functions set `FUNSTART` and `FUNEND` at the top of the file. 82 | 83 | - Modify the tables located at the `CmdTab` and `FunTab` labels to point at your new commands and functions. They **must** be in token number order. For commands, the address must be the execution address -1. For functions, it's the actual address. 84 | 85 | - Add your new command/function execution routines and make sure your functions return their value in FAC1. Look at the existing examples to see how things are done. 86 | 87 | There are a bunch of labels defined for BASIC, the Kernal, the VIC and ZP, so feel free to use them instead of hardcoding addresses & values. For the most part these labels come from **Mapping the Commodore 64** from Compute!. It's a must-have when you're writing assembly on the 64. 88 | 89 | 90 | ### Future enhancements 91 | 92 | I have some thoughts on things I'd like to add. Here are just some examples: 93 | 94 | - The REU function needs to be made to work. It should return a value for each type of valid REU and 0 if no REU was detected. This would make it easy for a BASIC program to detect whether an REU was attached and behave accordingly. 95 | - Commands to load data from disk into the REU. 96 | - Commands to interface with modern hardware, like WiFi modems. 97 | 98 | 99 | ### References 100 | 101 | These are the books I used while creating this framework. The tokenization/detokenization routines are copied nearly verbatim from the Advanced Machine Language book. 102 | 103 | - [Mapping the Commodore 64](https://archive.org/details/Compute_s_Mapping_the_Commodore_64) 104 | - [The Advanced Machine Language Book for the Commodore 64](https://archive.org/details/The_Advanced_Machine_Language_Book_for_the_Commodore_64) 105 | 106 | 107 | ### License 108 | 109 | Copyright 2023 Barry Walker 110 | 111 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 112 | 113 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 114 | 115 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 116 | -------------------------------------------------------------------------------- /main.asm: -------------------------------------------------------------------------------- 1 | *=$c000 2 | 3 | #import "include/all.asm" 4 | 5 | .label DATA = $83 6 | .label REM = $8f 7 | .label PRINT = $99 8 | .label QUOTE = $22 9 | .label BUFFER = $200 10 | 11 | 12 | // Set these to the start/end tokens for commands and functions. 13 | // You can find the table of tokens below at NewTab 14 | .label CMDSTART = $cc 15 | .label CMDEND = $db 16 | .label FUNSTART = CMDEND + $01 17 | .label FUNEND = $de 18 | 19 | /* 20 | 21 | Set up our vectors 22 | 23 | */ 24 | Init: 25 | ldx #ConvertToTokens 27 | stx vectors.ICRNCH 28 | sty vectors.ICRNCH + $01 29 | 30 | ldx #ConvertFromTokens 32 | stx vectors.IQPLOP 33 | sty vectors.IQPLOP + $01 34 | 35 | ldx #ExecuteCommand 37 | stx vectors.IGONE 38 | sty vectors.IGONE + $01 39 | 40 | ldx #ExecuteFunction 42 | stx vectors.IEVAL 43 | sty vectors.IEVAL + $01 44 | 45 | rts 46 | 47 | #import "memory.asm" // Memory commands 48 | #import "reu.asm" // REU functions/commands 49 | #import "sprites.asm" // Sprite commands and functions 50 | #import "include/timer.asm" // Timer functions 51 | 52 | /* 53 | 54 | Add your commands and functions here. The last byte of each command/function name 55 | must have $80 added to it. Your commands should come first starting from $cc. You can 56 | let the execution routine know how to identify commands and functions above in CMDSTART, 57 | CMDEND, FUNSTART, FUNEND. These are the token numbers for each block of commands/functions. 58 | Our tokens start at $cc and can go up to $fe ($ff is pi). Both the tokenization and 59 | detokenization routines use this table, so adding them here will ensure that BASIC 60 | will recognize them as you enter them and will detokenize them when LISTed. 61 | 62 | */ 63 | NewTab: 64 | // Commands start here 65 | .text "BORDE" // $cc 66 | .byte 'R' + $80 67 | .text "BACKGROUN" // $cd 68 | .byte 'D' + $80 69 | .text "WOK" // $ce 70 | .byte 'E' + $80 71 | .text "CL" // $cf 72 | .byte 'S' + $80 73 | .text "MEMCOP" // $d0 74 | .byte 'Y' + $80 75 | .text "MEMFIL" // $d1 76 | .byte 'L' + $80 77 | .text "SCREE" // $d2 78 | .byte 'N' + $80 79 | .text "BAN" // $d3 80 | .byte 'K' + $80 81 | .text "STAS" // $d4 82 | .byte 'H' + $80 83 | .text "FETC" // $d5 84 | .byte 'H' + $80 85 | .text "SPRSE" // $d6 86 | .byte 'T' + $80 87 | .text "SPRPO" // $d7 88 | .byte 'S' + $80 89 | .text "SPRCOLO" // $d8 90 | .byte 'R' + $80 91 | .text "MEMLOA" // $d9 92 | .byte 'D' + $80 93 | .text "MEMSAV" // $da 94 | .byte 'E' + $80 95 | .text "DI" // $db 96 | .byte 'R' + $80 97 | // Functions start here 98 | .text "WEE" // $dc 99 | .byte 'K' + $80 100 | .text "SCRLO" // $dd 101 | .byte 'C' + $80 102 | .text "RE" // $de 103 | .byte 'U' + $80 104 | .byte 0 105 | 106 | CmdTab: // A table of vectors pointing at your commands' execution addresses 107 | .word BorderCmd - 1 // Address - 1 of first command. Token = CMDSTART 108 | .word BackgroundCmd - 1 109 | .word WokeCmd - 1 110 | .word ClsCmd - 1 111 | .word MemCopyCmd - 1 112 | .word MemFillCmd - 1 113 | .word ScreenCmd - 1 114 | .word BankCmd - 1 115 | .word StashCmd - 1 116 | .word FetchCmd - 1 117 | .word SpriteSetCmd - 1 118 | .word SpritePosCmd - 1 119 | .word SpriteColorCmd - 1 120 | .word MemLoadCmd - 1 121 | .word MemSaveCmd - 1 122 | .word DirectoryCmd - 1 123 | 124 | FunTab: // A table of vectors pointing at your functions' execution addresses 125 | .word WeekFun // Address of first function. Token = FUNSTART 126 | .word ScrLocFun 127 | .word ReuFun 128 | 129 | /* 130 | 131 | Command Execution. This is the meat of it. This is where the magic happens. 132 | We first have to figure out whether we're executing one of our custom functions. We 133 | do this by looking at the current token that's returned by CHRGET. If this token 134 | is in the range of CMDSTART to CMDEND, then it's one of ours and we should find its 135 | routine in a lookup table. If it's not, then just perform the normal BASIC command 136 | handler. 137 | 138 | */ 139 | ExecuteCommand: 140 | jsr zp.CHRGET 141 | jsr TestCmd 142 | jmp basic.NEWSTT 143 | TestCmd: 144 | cmp #CMDSTART 145 | bcc OldCmd 146 | cmp #CMDEND + 1 147 | bcc OkNew 148 | OldCmd: 149 | jsr zp.CHRGOT 150 | jmp basic.EXECOLD 151 | OkNew: 152 | sec 153 | sbc #CMDSTART 154 | asl 155 | tax 156 | lda CmdTab+1, x 157 | pha 158 | lda CmdTab, x 159 | pha 160 | jmp zp.CHRGET 161 | 162 | /* 163 | 164 | Function Execution. This is the meat of it. This is where the magic happens. 165 | We first have to figure out whether we're executing one of our custom functions. We 166 | do this by looking at the current token that's returned by CHRGET. If this token 167 | is in the range of FUNSTART to FUNEND, then it's one of ours and we should find its 168 | routine in a lookup table. If it's not, then just perform the normal BASIC function 169 | handler. 170 | 171 | */ 172 | ExecuteFunction: 173 | lda #0 174 | sta zp.VALTYP 175 | jsr zp.CHRGET 176 | cmp #'$' // Is this a HEX number? 177 | beq ProcessHex 178 | cmp #'%' // Is this a binary number? 179 | beq ProcessBinary 180 | cmp #FUNSTART // Is this one of ours? 181 | bcc OldFun 182 | cmp #FUNEND + 1 183 | bcc Ok1New 184 | OldFun: 185 | jsr zp.CHRGOT // It's a built-in Commodore function, so re-fetch the token 186 | jmp basic.FUNCTOLD // and call the normal BASIC function handler. 187 | Ok1New: 188 | sec 189 | sbc #FUNSTART // We need to get an index to the function in the vector table 190 | asl // Start by subtracting to get a 0 based index, and then mult by 2. 191 | pha 192 | jsr zp.CHRGET 193 | jsr basic.PARCHK // Grab whatever is in parens and evaluate it. This is passed to our function. 194 | pla 195 | tay 196 | lda FunTab, y // Get the function's vector address... 197 | sta zp.JMPER + 1 198 | lda FunTab + 1, y 199 | sta zp.JMPER + 2 200 | jsr zp.JMPER // ... and then call it. 201 | rts 202 | 203 | /* 204 | 205 | Allow us to represent numbers as HEX using $ABCD syntax 206 | 207 | */ 208 | ProcessHex: 209 | jsr ClearFAC 210 | !: 211 | jsr zp.CHRGET 212 | bcc !+ 213 | cmp #'A' 214 | bcc !+++ 215 | cmp #'F' + 1 216 | bcs !+++ 217 | sec 218 | sbc #$07 219 | !: 220 | sec 221 | sbc #'0' 222 | pha 223 | lda zp.EXP 224 | beq !+ 225 | clc 226 | adc #$04 227 | bcs !+++ 228 | sta zp.EXP 229 | !: 230 | pla 231 | beq !--- 232 | jsr basic.FINLOG 233 | jmp !--- 234 | !: 235 | jmp zp.CHRGOT 236 | !: 237 | jmp basic.OVERR 238 | 239 | /* 240 | 241 | Allow us to represent numbers as binary using %1010101 syntax 242 | 243 | */ 244 | ProcessBinary: 245 | jsr ClearFAC 246 | !: 247 | jsr zp.CHRGET 248 | cmp #'2' 249 | bcs !--- 250 | cmp #'0' 251 | bcc !--- 252 | sbc #'0' 253 | pha 254 | lda zp.EXP 255 | beq !+ 256 | inc zp.EXP 257 | beq !+ 258 | !: 259 | pla 260 | beq !-- 261 | jsr basic.FINLOG 262 | jmp !-- 263 | 264 | ClearFAC: 265 | lda #$00 266 | ldx #$0a 267 | !: 268 | sta zp.FLOAT, x 269 | dex 270 | bpl !- 271 | rts 272 | 273 | /* 274 | 275 | Clear the screen using PETSCII $93. Easy peasey. 276 | 277 | Example: CLS 278 | 279 | */ 280 | ClsCmd: 281 | lda #$93 282 | jmp kernal.VEC_CHROUT 283 | 284 | /* 285 | 286 | Set the border color 287 | 288 | Example: BORDER 0. Sets the border color to black 289 | 290 | */ 291 | BorderCmd: 292 | jsr GetColor 293 | sta vic.EXTCOL 294 | rts 295 | 296 | /* 297 | 298 | Set the background color 299 | 300 | Example: BACKGROUND 0. Sets the background color to black 301 | 302 | */ 303 | BackgroundCmd: 304 | jsr GetColor 305 | sta vic.BGCOL0 306 | rts 307 | 308 | /* 309 | 310 | Execute a POKE but allow passing in a word for the address and the value. 311 | 312 | Example: WOKE 250, 65535. Puts 255 in location 250 and 251. 313 | 314 | */ 315 | WokeCmd: 316 | jsr Get16Bit // Get the address to WOKE to 317 | lda $14 318 | sta $57 319 | lda $15 320 | sta $58 321 | 322 | jsr basic.CHKCOM // Make sure we have a comma 323 | 324 | jsr Get16Bit // Get the 16-bit word to WOKE 325 | 326 | ldy #$00 // WOKE IT! 327 | lda $14 328 | sta ($57), y 329 | lda $15 330 | iny 331 | sta ($57), y 332 | 333 | rts 334 | 335 | /* 336 | 337 | Select the VIC bank (0-3) 338 | 339 | Example: BANK 0 would specify 0-16384 as the range of locations that the VIC sees 340 | 341 | */ 342 | BankCmd: 343 | jsr Get8Bit // Get the bank # 344 | tya 345 | pha 346 | and #$fc // Strip the bottom 2 bits. 347 | cmp #$00 // Is the value > 0? That means we specified a number > 3 348 | bne !+ // Illegal quantity 349 | pla 350 | sta r0L 351 | lda #$03 // The bit patterns are backwards. 11 means bank 0 and 00 means bank 3 352 | sec 353 | sbc r0L 354 | sta r0H 355 | lda $dd00 // Read the existing value from $dd00 356 | and #$fc // Set just the bank selection bits (0 & 1) 357 | ora r0H 358 | sta $dd00 359 | rts 360 | !: 361 | jmp basic.ILLEGAL_QUANTITY 362 | 363 | /* 364 | 365 | Get the current VIC bank 0-3 366 | 367 | */ 368 | CurrentBank: 369 | lda $dd00 370 | and #$03 371 | sta r0L 372 | lda #$03 373 | sec 374 | sbc r0L 375 | 376 | rts 377 | 378 | /* 379 | 380 | Set the screen location within the current VIC bank 381 | 382 | Example: SCREEN 1 would set the screen location to offset $0400 of the current VIC bank. 383 | If in bank 0, this would be $0400 since bank 0 starts at $0000. This is also the default 384 | at startup. 385 | 386 | */ 387 | ScreenCmd: 388 | jsr CurrentBank // Get the current VIC bank 389 | sta r0L 390 | jsr Get8Bit // Get the screen number (0-15) 391 | tya 392 | pha 393 | and #$f0 // Strip the bottom 4 bits 394 | cmp #$00 // Is the value > 0? Means we've specified a number > 15 395 | bne !+ // Illegal quantity 396 | pla 397 | tay 398 | sty r0H // Tuck away the screen number (0-15) 399 | lda $d018 // Set the screen number in $d018 400 | and #$0f 401 | ora ScreenLoc, y 402 | sta $d018 403 | lda r0H // Now let BASIC know where that screen is 404 | asl // Multiply by 4 405 | asl 406 | sta r0H 407 | lda r0L 408 | asl 409 | asl 410 | asl 411 | asl 412 | asl 413 | asl 414 | ora r0H 415 | sta $288 416 | rts 417 | 418 | !: 419 | jmp basic.ILLEGAL_QUANTITY 420 | 421 | SpriteDiskCommandCommon: 422 | pha 423 | jsr $ad9e // TODO: Add this to the basic.asm 424 | jsr $b6a3 // TODO: Add this to the basic.asm 425 | sta r0L // Filename length 426 | lda $22 427 | sta r1L // Filename 428 | lda $23 429 | sta r1H 430 | jsr basic.CHKCOM 431 | jsr Get8Bit 432 | sty r0H // Device number 433 | jsr basic.CHKCOM 434 | jsr Get16Bit 435 | lda $14 436 | sta r2L // Load/Save address 437 | lda $15 438 | sta r2H 439 | pla 440 | beq !+ // If this is a save, we need to fetch the end address 441 | jsr basic.CHKCOM 442 | jsr Get16Bit 443 | lda $14 444 | sta r3L // Number of bytes to save 445 | lda $15 446 | sta r3H 447 | !: 448 | lda r0L 449 | ldx r1L 450 | ldy r1H 451 | jsr kernal.VEC_SETNAM 452 | 453 | lda #$02 454 | ldx r0H 455 | ldy #$02 456 | jsr kernal.VEC_SETLFS 457 | jsr kernal.VEC_OPEN 458 | bcs !+ 459 | ldx #$02 460 | ldy #$00 461 | rts 462 | !: 463 | lda #DiskError 465 | jmp CustomError 466 | 467 | DiskClose: 468 | lda #$02 469 | jsr kernal.VEC_CLOSE 470 | jsr kernal.VEC_CLRCHN 471 | rts 472 | 473 | /* 474 | 475 | Load bytes from disk directly into memory. 476 | 477 | Example: MEMLOAD "SPRITES.SPR", 8, $2000 would load the file SPRITES.SPR from device 8 into memory at $2000 478 | 479 | */ 480 | MemLoadCmd: 481 | lda #$00 482 | jsr SpriteDiskCommandCommon 483 | jsr kernal.VEC_CHKIN 484 | !: // LOOP 485 | jsr kernal.VEC_READST 486 | bne !++ // EOF 487 | jsr kernal.VEC_CHRIN 488 | bcs !-- 489 | sta (r2), y 490 | inc r2L 491 | bne !+ // SKIP2 492 | inc r2H 493 | !: // SKIP2 494 | jmp !-- // LOOP 495 | !: // EOF 496 | ora #$40 497 | cmp #$40 498 | bne !---- 499 | !: // CLOSE 500 | jmp DiskClose 501 | 502 | /* 503 | 504 | Save memory to disk. 505 | 506 | Example: MEMSAVE "@:SPRITES.SPR,P,W", 8, $2000, $2040 would save memory from $2000 - $2040 to the file SPRITES.SPR on device 8. 507 | 508 | */ 509 | MemSaveCmd: 510 | lda #$80 511 | jsr SpriteDiskCommandCommon 512 | jsr kernal.VEC_CHKOUT 513 | !: // LOOP 514 | jsr kernal.VEC_READST 515 | bne !------ // WERROR 516 | lda (r2), y 517 | jsr kernal.VEC_CHROUT 518 | bcs !------ 519 | inc r2L 520 | bne !+ // SKIP 521 | inc r2H 522 | !: // SKIP 523 | lda r2L 524 | cmp r3L 525 | lda r2H 526 | sbc r3H 527 | bcc !-- // LOOP 528 | !: // CLOSE 529 | jmp DiskClose 530 | 531 | /* 532 | 533 | Perform a non-destructive directory listing. 534 | Pilfered from https://csdb.dk/forums/?roomid=11&topicid=17487 535 | 536 | Example: DIR 8 would list the directory of device 8 537 | 538 | */ 539 | DirectoryCmd: 540 | jsr Get8Bit 541 | tya 542 | pha 543 | lda #$01 544 | tax 545 | ldy #$e8 546 | jsr kernal.VEC_SETNAM 547 | pla 548 | sta $ba 549 | lda #$60 550 | sta $b9 551 | jsr $f3d5 552 | jsr $f219 553 | ldy #$04 554 | !: 555 | jsr $ee13 556 | dey 557 | bne !- 558 | lda $c6 559 | ora $90 560 | bne !++ 561 | jsr $ee13 562 | tax 563 | jsr $ee13 564 | jsr $bdcd 565 | !: 566 | jsr $ee13 567 | jsr $e716 568 | bne !- 569 | jsr $aad7 570 | ldy #$02 571 | bne !-- 572 | !: 573 | jsr $f642 574 | jsr $f6f3 575 | !: 576 | rts 577 | 578 | /* 579 | 580 | Grab the sprite number as the first argument for the sprite commands. Stores in r0L 581 | 582 | */ 583 | SpriteCommon: 584 | jsr Get8Bit // Get the sprite number (0-7) 585 | cpy #$08 586 | bcs !+ 587 | sty r0L // Sprite # 588 | rts 589 | !: 590 | jmp basic.ILLEGAL_QUANTITY 591 | 592 | /* 593 | 594 | Set a sprite's color 595 | 596 | */ 597 | SpriteColorCmd: 598 | jsr SpriteCommon 599 | jsr basic.CHKCOM 600 | jsr GetColor 601 | ldy r0L 602 | sta vic.SP0COL, y 603 | rts 604 | !: 605 | jmp basic.ILLEGAL_QUANTITY 606 | 607 | /* 608 | 609 | Set a sprite's X and Y position 610 | 611 | Example: SPRPOS 0, 100, 100 would set sprite 0's X position to 100 and its Y position to 100 612 | The x position can be 0-511. 613 | 614 | */ 615 | SpritePosCmd: 616 | jsr SpriteCommon 617 | jsr basic.CHKCOM 618 | jsr Get16Bit 619 | lda $14 620 | sta r1L // X 621 | lda $15 622 | sta r1H 623 | jsr basic.CHKCOM 624 | jsr Get8Bit 625 | sty r2L // Y 626 | jsr PositionSprite 627 | rts 628 | 629 | !: 630 | jmp basic.ILLEGAL_QUANTITY 631 | 632 | /* 633 | 634 | Turn a sprite on or off and set its shape data location 635 | 636 | Example: SPRSET 0, 1, $0d would turn sprite 0 on and set its shape data pointer to $0d in the current bank, 637 | which by default would be location $340 (bank 0 starts at $0 + $40 * $0d) 638 | 639 | */ 640 | SpriteSetCmd: 641 | jsr SpriteCommon 642 | jsr basic.CHKCOM 643 | jsr Get8Bit 644 | cpy #$02 645 | bcs !+ 646 | tya 647 | ror 648 | ror 649 | sta r1L // On/Off 650 | jsr basic.CHKCOM 651 | jsr Get8Bit 652 | sty r3L // Pointer to shape data in the current bank 653 | lda #SPR_VISIBLE 654 | sta r0H 655 | jsr ChangeSpriteAttribute 656 | lda $288 // Get the page number of the current screen 657 | clc 658 | adc #$03 // Add 1016 to the start of screen RAM to get the sprite pointers 659 | sta r0H 660 | lda #$00 661 | clc 662 | adc #$f8 663 | sta r0L 664 | ldy #$00 665 | lda r3L 666 | sta (r0), y // Write the pointer to the sprite data 667 | 668 | rts 669 | !: 670 | jmp basic.ILLEGAL_QUANTITY 671 | 672 | /* 673 | 674 | Execute a PEEK function but return a 16-bit word instead of an 8-bit byte. 675 | 676 | Example: PRINT WEEK(250). Would return the 16-bit value in 250 & 251. 677 | 678 | */ 679 | WeekFun: 680 | jsr basic.GETADR // Get the WEEK address 681 | 682 | ldy #$00 683 | lda ($14), y 684 | sta $63 685 | iny 686 | lda ($14), y 687 | sta $62 688 | 689 | // Thanks to Gregory Naçu for this trick. It allows writing a uint16 to the FAC 690 | // https://c64os.com/post/floatingpointmath 691 | ldx #$90 692 | sec 693 | jmp $bc49 694 | 695 | /* 696 | 697 | Return the start address of the current screen. 698 | 699 | Example: PRINT SCRLOC(0). In the default C64 configuration, this would return 1024. 700 | 701 | */ 702 | ScrLocFun: 703 | lda $288 704 | sta $62 705 | lda #$00 706 | sta $63 707 | ldx #$90 708 | sec 709 | jmp $bc49 710 | 711 | /* 712 | 713 | Check to see if there's a REU attached 714 | 715 | TODO: The reudetect routine is completely broken and needs a lot of work 716 | 717 | */ 718 | ReuFun: 719 | jsr reudetect 720 | lda #$00 721 | sta $62 722 | sta $63 723 | ldx #$90 724 | sec 725 | jmp $bc49 726 | 727 | /* 728 | 729 | Store data from the C64's memory into an REU 730 | 731 | Example: STASH $400, $0, 1000, 0 would store the default screen to the REU at address $0, bank $0 732 | 733 | */ 734 | StashCmd: 735 | jsr ReuCommandCommon 736 | jsr REUStash 737 | 738 | rts 739 | 740 | /* 741 | 742 | Retrieve data from the REU and store it in the C64's memory 743 | 744 | Example: FETCH $400, $0, 1000, 0 would retrieve 1000 bytes from the REU starting at address $0, bank $0 and store it to the default screen 745 | 746 | */ 747 | FetchCmd: 748 | jsr ReuCommandCommon 749 | jsr REUFetch 750 | 751 | rts 752 | 753 | /* 754 | 755 | Copy a block of memory. 756 | 757 | Example: MEMCOPY $a000, $a000, $2000 would copy BASIC from ROM to RAM 758 | 759 | */ 760 | MemCopyCmd: 761 | jsr MemCommon 762 | jsr basic.CHKCOM 763 | jsr Get16Bit 764 | lda $14 765 | sta r2L 766 | lda $15 767 | sta r2H 768 | 769 | jsr MemCopy 770 | rts 771 | 772 | /* 773 | 774 | Fill a block of memory with a character 775 | 776 | Example: MEMFILL $0400, $03e8, $20 would fill the screen with space characters. 777 | 778 | */ 779 | MemFillCmd: 780 | jsr MemCommon 781 | jsr basic.CHKCOM 782 | jsr Get8Bit 783 | sty r2L 784 | 785 | jsr MemFill 786 | 787 | rts 788 | 789 | /* 790 | 791 | Set up parameters for STASH and FETCH since they're pretty much identical 792 | 793 | */ 794 | ReuCommandCommon: 795 | jsr MemCommon 796 | jsr basic.CHKCOM 797 | jsr Get16Bit // Transfer length 798 | lda $14 799 | sta r2L 800 | lda $15 801 | sta r2H 802 | jsr basic.CHKCOM 803 | jsr Get8Bit // REU bank number 804 | sty r3L 805 | 806 | rts 807 | 808 | /* 809 | 810 | The existing memory routines start with 2 16-bit values and they're written to 811 | the same registers. 812 | 813 | */ 814 | MemCommon: 815 | jsr Get16Bit 816 | lda $14 817 | sta r0L 818 | lda $15 819 | sta r0H 820 | 821 | jsr basic.CHKCOM 822 | 823 | jsr Get16Bit 824 | lda $14 825 | sta r1L 826 | lda $15 827 | sta r1H 828 | 829 | rts 830 | 831 | /* 832 | 833 | Fetch a 16 bit value from the current pointer. Value is returned in $14/$15 834 | 835 | */ 836 | Get16Bit: 837 | lda #$00 838 | sta zp.VALTYP 839 | jsr basic.FRMNUM 840 | jsr basic.GETADR 841 | rts 842 | 843 | /* 844 | 845 | Fetch an 8 bit value which is returned in Y 846 | 847 | */ 848 | Get8Bit: 849 | jsr basic.FRMEVL // Evaluate the expression after the token 850 | jsr $ad8d 851 | jsr basic.FACINX // Convert the value in FAC1 to A(H)&Y(L) 852 | cmp #$00 // Is the high byte 0? (>255) 853 | bne !+ // Yup. Illegal quantity 854 | rts 855 | !: 856 | jmp basic.ILLEGAL_QUANTITY 857 | 858 | /* 859 | 860 | Common routine to grab some text, ensure it's a number and make sure 861 | it's < 16. This is used for the Background and Border commands which 862 | set the colors of each. Returns the value in A 863 | 864 | */ 865 | GetColor: 866 | jsr Get8Bit 867 | cpy #$10 // Is the color > 15? 868 | bcs !+ 869 | tya 870 | rts 871 | !: 872 | lda #InvalidColorError 874 | jmp CustomError 875 | 876 | /* 877 | 878 | Display a custom error. Pass in the LB of the error message in A and the HB in Y 879 | 880 | */ 881 | CustomError: 882 | sta $22 883 | tya 884 | jmp basic.CUSTERROR 885 | 886 | /* 887 | 888 | Detokenize. Converts tokens back into PETSCII. This is called when you list a program 889 | with custom commands. It ensures that those commands expand correctly to their PETSCII 890 | form. 891 | 892 | */ 893 | ConvertFromTokens: 894 | bpl Out 895 | bit zp.GARBFL 896 | bmi Out 897 | cmp #$ff 898 | beq Out 899 | cmp #CMDSTART 900 | bcs NewList 901 | jmp $a724 902 | Out: 903 | jmp $a6f3 904 | NewList: 905 | sec 906 | sbc #$cb 907 | tax 908 | sty zp.FORPNT 909 | ldy #-1 910 | Next: 911 | dex 912 | beq Found 913 | Loop: 914 | iny 915 | lda NewTab, y 916 | bpl Loop 917 | bmi Next 918 | Found: 919 | iny 920 | lda NewTab, y 921 | bmi OldEnd 922 | jsr basic.CHAROUT 923 | bne Found 924 | OldEnd: 925 | jmp $a6ef 926 | 927 | /* 928 | 929 | Tokenize. Converts PETSCII commands into tokens. This routine is called 930 | as you enter commands in either immediate mode, or as you enter lines of 931 | BASIC code. 932 | 933 | */ 934 | ConvertToTokens: 935 | ldx zp.TXTPTR 936 | ldy #4 937 | sty zp.GARBFL 938 | NextChar: 939 | lda BUFFER, x 940 | bpl Normal 941 | cmp #$ff 942 | beq TakChar 943 | inx 944 | bne NextChar 945 | Normal: 946 | cmp #' ' 947 | beq TakChar 948 | sta zp.ENDCHAR 949 | cmp #QUOTE 950 | beq GetChar 951 | bit zp.GARBFL 952 | bvs TakChar 953 | cmp #'?' 954 | bne Skip 955 | lda #PRINT 956 | bne TakChar 957 | Skip: 958 | cmp #'0' 959 | bcc Skip1 960 | cmp #'<' 961 | bcc TakChar 962 | Skip1: 963 | sty zp.FBUFPT 964 | ldy #0 965 | sty zp.COUNT 966 | dey 967 | stx zp.TXTPTR 968 | dex 969 | CmpLoop: 970 | iny 971 | inx 972 | TestNext: 973 | lda BUFFER, x 974 | sec 975 | sbc basic.RESLST, y 976 | beq CmpLoop 977 | cmp #$80 978 | bne NextCmd 979 | ora zp.COUNT 980 | TakChar1: 981 | ldy zp.FBUFPT 982 | TakChar: 983 | inx 984 | iny 985 | sta BUFFER-5, y 986 | cmp #0 987 | beq End 988 | sec 989 | sbc #':' 990 | beq Skip2 991 | cmp #DATA-':' 992 | bne Skip3 993 | Skip2: 994 | sta zp.GARBFL 995 | Skip3: 996 | sec 997 | sbc #REM-':' 998 | bne NextChar 999 | sta zp.ENDCHAR 1000 | RemLoop: 1001 | lda BUFFER, x 1002 | beq TakChar 1003 | cmp zp.ENDCHAR 1004 | beq TakChar 1005 | GetChar: 1006 | iny 1007 | sta BUFFER-5, y 1008 | inx 1009 | bne RemLoop 1010 | NextCmd: 1011 | ldx zp.TXTPTR 1012 | inc zp.COUNT 1013 | Continue: 1014 | iny 1015 | lda basic.RESLST-1, y 1016 | bpl Continue 1017 | lda basic.RESLST, y 1018 | bne TestNext 1019 | beq NewTok 1020 | NotFound: 1021 | lda BUFFER, x 1022 | bpl TakChar1 1023 | End: 1024 | sta BUFFER-3, y 1025 | dec zp.TXTPTR+1 1026 | lda #$ff 1027 | sta zp.TXTPTR 1028 | rts 1029 | NewTok: 1030 | ldy #0 1031 | lda NewTab, y 1032 | bne NewTest 1033 | NewCmp: 1034 | iny 1035 | inx 1036 | NewTest: 1037 | lda BUFFER, x 1038 | sec 1039 | sbc NewTab, y 1040 | beq NewCmp 1041 | cmp #$80 1042 | bne NextNew 1043 | ora zp.COUNT 1044 | bne TakChar1 1045 | NextNew: 1046 | ldx zp.TXTPTR 1047 | inc zp.COUNT 1048 | Cont1: 1049 | iny 1050 | lda NewTab-1,y 1051 | bpl Cont1 1052 | lda NewTab, y 1053 | bne NewTest 1054 | beq NotFound 1055 | 1056 | /* 1057 | 1058 | You can create your own custom error messages as well. Set $22 to the LB of the error message 1059 | and A with the HB of the error message and then call basic.CUSTERROR 1060 | 1061 | */ 1062 | InvalidColorError: 1063 | .text "INVALID COLO" 1064 | .byte 'R' + $80 1065 | 1066 | NoReuError: 1067 | .text "NO ATTACHED RE" 1068 | .byte 'U' + $80 1069 | 1070 | InvalidReuBankError: 1071 | .text "INVALID REU BAN" 1072 | .byte 'K' + $80 1073 | 1074 | DiskError: 1075 | .text "DISK I/" 1076 | .byte 'O' + $80 1077 | 1078 | // These are bit patterns that we poke into $d018 to set the screen location. We only care about 1079 | // bits 4-7 since bit 0 is unused and bits 1-3 select the char rom location within the VIC bank. 1080 | // There's probably a way to do this in code, but this is easier. 1081 | ScreenLoc: 1082 | .byte %00000000, %00010000, %00100000, %00110000, %01000000, %01010000, %01100000, %01110000 1083 | .byte %10000000, %10010000, %10100000, %10110000, %11000000, %11010000, %11100000, %11110000 1084 | --------------------------------------------------------------------------------