; Cartridge Generator for the GMod3 cartridge
; based on "Magic Desk Cartridge Generator", taken from
; https://bitbucket.org/zzarko/magic-desk-cartridge-generator/src/master/
; Magic Desk Compatible Cartridge Generator (c) 2013-2019 Žarko Živanov
; Cartridge schematics and PCB design (c) 2013-2014 Marko Šolajić
; E-mails: zzarko and msolajic at gmail
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
; You should have received a copy of the GNU General Public License
; along with this program. If not, see .
; ZP usage:
; ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
; 02 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A FB FC FD FE
; ** ** ** ** ** **
; 52 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E
;--------------------------------
; RAM and ROM
;--------------------------------
SCREEN = $0400
COLORR = $D800
CHROUT = $FFD2
GETIN = $FFE4
SCNKEY = $FF9F
COLDRES = $FCE2
BORDER = $D020
PAPER = $D021
IRQ_Vector = $0314
IRQ_Kernel = $EA31
CursorColor = $0286
drive = $BA
status = $90
LISTEN = $FFB1
SECOND = $FF93
UNLSTN = $FFAE
;--------------------------------
; menu variables
;--------------------------------
; current menu variables
current_menu = $25
prev_menu = $5C
cmenu_offset = $1A ; 2B offset from screen start
cmenu_offset_it = $1C ; 2B offset from screen start for items
cmenu_offset_key = $26 ; 2B offset from screen start for item key
cmenu_width = $1E ; 1B menu inside width
cmenu_height = $1F ; 1B menu inside height
cmenu_iwidth = $20 ; 1B menu inside width minus chars for key
cmenu_items_no = $21 ; 1B number of items
cmenu_max_first = $22 ; 1B max value for first item
cmenu_items = $23 ; 1B pointer to list of items
cmenu_first_idx = $19 ; 1B index of first item to show
cmenu_item_adr = $14 ; 2B pointer to current item text
cmenu_item_idx = $16 ; 1B current item index
cmenu_spacing = $2A ; 1B menu items spacing (0-no spacing,1-1 line)
; misc menu variables
temp = $02 ; 1B temorary value
chrmem = $FB ; 2B pointer to addres on screen
colmem = $FD ; 2B pointer to addres in color ram
chrkeymem = $28 ; 2B pointer to addres on screen for key
drawm_line = $17 ; 1B current line when drawing items
drawm_idx = $18 ; 1B current item index when drawing items
;--------------------------------
; IRQ variables
;--------------------------------
SCR_FirstLine = 58
SCR_LastLine = 242
; IRQ wave variables
WaveSpeed = 2
CurrentLine = $58
CurrentWave = $59
FirstWave = $5A
WavePause = $5B
;--------------------------------
; cartridge start
;--------------------------------
* = $8000
;* = $0820
!word cold_start
!word init_menu
!byte $c3,$c2,$cd,$38,$30 ; cartridge magic bytes, CBM80
cold_start:
stx $d016
jsr $FDA3 ; IOINIT, init CIA,IRQ
jsr $FD50 ; RAMTAS, init memory
jsr $FD15 ; RESTOR, init I/O
jsr $FF5B ; SCINIT, init video
cli
jsr $E453 ; load BASIC vectors
jsr $E3BF ; init BASIC RAM
jsr $E422 ; print BASIC start up messages
ldx #$FB ; init BASIC stack
txs
; debug
; lda #5
; sta current_menu
; lda #12
; jmp prepare_run
jmp init_menu
;--------------------------------
; menu drawing
;--------------------------------
scrolld:
lda cmenu_first_idx
cmp cmenu_max_first
bpl +
inc cmenu_first_idx
+ jmp dm1
scrollu:
lda cmenu_first_idx
beq +
dec cmenu_first_idx
+ jmp dm1
paged:
lda cmenu_first_idx
clc
adc cmenu_height
sta cmenu_first_idx
cmp cmenu_max_first
bmi +
lda cmenu_max_first
sta cmenu_first_idx
+ jmp dm1
pageu:
lda cmenu_first_idx
sec
sbc cmenu_height
sta cmenu_first_idx
cmp #0
bpl +
lda #0
sta cmenu_first_idx
+ jmp dm1
init_menu:
lda DoSound
beq +
jsr sound_init
+ jsr WaveVIC
lda #$08 ; disable c=+sh
jsr CHROUT
lda #$0e ; lower chars
jsr CHROUT
lda #0
sta current_menu
lda #$FF
sta prev_menu
dm0: lda current_menu
cmp prev_menu
beq dm2
sta prev_menu
lda #$60
ldx #$A0
ldy #6
jsr clear_screen
jsr setup_menu
lda #0
sta cmenu_first_idx
jsr draw_menu_border
dm1: lda cmenu_first_idx
sta drawm_idx
jsr draw_menu_items
dm2: jsr read_key
beq dm2
cmp #$1D ; left-right key
beq scrolld
cmp #$11 ; up-down key
beq scrollu
cmp #$9D ; shift+left-right key
beq paged
cmp #$91 ; shift+up-down key
beq pageu
cmp #$0D ; return
beq nextmenu
cmp #$5F ; left arrow
beq basic
cmp #$85 ; F1
bmi prgkey
cmp #$8D ; 89 - F7+1, 8D - F8+1
bpl prgkey
sec
sbc #$85
tay
lda menu_items_no,y
beq dm2
sty current_menu
jmp dm0
prgkey: ldy #0 ; check if some program is selected
- cmp menu_keys,y
beq +
iny
cpy #menu_keys_no
bne -
jmp dm2
+ iny ; last item is basic, do not check that key
tya
ldy current_menu
cmp menu_items_no,y
bpl dm2
tay
dey
tya
jmp prepare_run
basic:
ldy cmenu_items_no
dey
tya
jmp prepare_run
nextmenu:
ldy current_menu
sty temp
iny
cpy #8
bne +
ldy #0
+ lda menu_items_no,y
sty current_menu
bne +
lda #0
sta current_menu
+ jmp dm0
draw_menu_border:
lda cmenu_offset
sta chrmem
sta colmem
lda #>SCREEN
clc
adc cmenu_offset+1
sta chrmem+1
lda #>COLORR
clc
adc cmenu_offset+1
sta colmem+1
ldx #0
jsr draw_menu_border_line
jsr menu_next_line
lda cmenu_height
ldy cmenu_spacing
beq dmb1
asl
tay
dey
tya
dmb1: pha
ldx #4
jsr draw_menu_border_line
lda cmenu_spacing
beq +
pla
pha
and #1
beq dmb2
+ ldy #1
ldx #0
- lda menu_key_chr,x
sta (chrmem),y
lda menu_key_col,x
sta (colmem),y
iny
inx
cpx #3
bmi -
dmb2: jsr menu_next_line
pla
tay
dey
tya
bne dmb1
ldx #8
jsr draw_menu_border_line
jsr menu_next_line
ldx #12
jsr draw_menu_border_line
rts
draw_menu_border_line:
ldy #0
lda menu_border_chr,x
sta (chrmem),y
lda menu_border_col,x
sta (colmem),y
iny
lda cmenu_width
sta temp
inc temp
inx
- lda menu_border_chr,x
sta (chrmem),y
lda menu_border_col,x
sta (colmem),y
iny
cpy temp
bmi -
inx
lda menu_border_chr,x
sta (chrmem),y
lda menu_border_col,x
sta (colmem),y
inx
iny
lda menu_border_chr,x
sta (chrmem),y
lda menu_border_col,x
sta (colmem),y
rts
menu_next_line:
clc
lda chrmem
adc #40
sta chrmem
sta colmem
bcc +
inc chrmem+1
inc colmem+1
+ clc
lda chrkeymem
adc #40
sta chrkeymem
bcc +
inc chrkeymem+1
+ rts
draw_menu_items:
lda cmenu_offset_it
sta chrmem
sta colmem
lda #>SCREEN
clc
adc cmenu_offset_it+1
sta chrmem+1
lda #>COLORR
clc
adc cmenu_offset_it+1
sta colmem+1
lda cmenu_offset_key
sta chrkeymem
lda #>SCREEN
clc
adc cmenu_offset_key+1
sta chrkeymem+1
lda drawm_idx
jsr find_menu_item
lda #0
sta drawm_line
- jsr draw_menu_item_line
jsr menu_next_line
jsr find_next_menu_item
ldy cmenu_spacing
beq dmi1
jsr menu_next_line
dmi1: inc drawm_idx
inc drawm_line
lda drawm_line
cmp cmenu_height
bne -
rts
draw_menu_item_line:
ldy #0
- lda (cmenu_item_adr),y
beq +
sta (chrmem),y
iny
cpy cmenu_iwidth
bne -
+
-
lda menu_chr_back
cpy cmenu_iwidth
beq +
sta (chrmem),y
iny
bne -
+ ldy drawm_idx ; convert PETSCII to inverted SCREEN codes
iny ; last item will always be "Basic", and its key is left arrow
cpy cmenu_items_no
bne +
ldy #menu_keys_no+1
+ dey
lda menu_keys,y
cmp #$C1 ; codes C1-DA translate to C1-DA
bcs dmil1
cmp #$40
bcc +
clc ; codes 41-5A translate to 81-9A
adc #$40
!byte $2C ; bit instruction
+ ora #$80 ; codes 41-5A translate to B0-B9
dmil1: ldy #0
sta (chrkeymem),y
rts
;--------------------------------
; item list functions
;--------------------------------
; find menu item whose index is in A register and set menu item pointer
find_menu_item:
sta cmenu_item_idx
lda cmenu_items
sta cmenu_item_adr
lda cmenu_items+1
sta cmenu_item_adr+1
ldx #0
fmi1: cpx cmenu_item_idx
beq fmi2
ldy #0
- lda (cmenu_item_adr),y
beq +
iny
bne -
+ iny
tya
clc
adc cmenu_item_adr
bcc +
inc cmenu_item_adr+1
+ sta cmenu_item_adr
inx
jmp fmi1
fmi2: rts
find_next_menu_item:
ldx cmenu_item_idx
inc cmenu_item_idx
jmp fmi1
;--------------------------------
; menu setup
;--------------------------------
; setup all current menu variables for menu whose index is in current_menu
setup_menu:
ldx current_menu
ldy menu_items_no,x
sty cmenu_items_no
tya
sec
sbc menu_height,x
sta cmenu_max_first
ldy menu_width,x
sty cmenu_width
dey
dey
dey
sty cmenu_iwidth ; menu_width-3 (width of key display)
ldy menu_height,x
sty cmenu_height
ldy menu_spacing,x
sty cmenu_spacing
txa ; offsets are 2-byte
asl
tax
lda menu_offset,x
sta cmenu_offset
lda menu_offset+1,x
sta cmenu_offset+1
lda menu_items,x
sta cmenu_items
lda menu_items+1,x
sta cmenu_items+1
; calculate offset for menu items
lda cmenu_offset+1
sta cmenu_offset_it+1
lda cmenu_offset
clc
adc #44 ; 1 line + 3 chars for key display
sta cmenu_offset_it
bcc +
inc cmenu_offset_it+1
; calculate offset for menu item keys
+ lda cmenu_offset+1
sta cmenu_offset_key+1
lda cmenu_offset
clc
adc #42 ; 1 line + 2
sta cmenu_offset_key
bcc +
inc cmenu_offset_key+1
+ lda menu_names,x
sta chrmem
lda menu_names+1,x
sta chrmem+1
ldy #0
- lda (chrmem),y
ora #$80
sta SCREEN,y
lda menu_help,y
ora #$80
sta SCREEN+24*40,y
iny
cpy #40
bne -
rts
;--------------------------------
; utility functions
;--------------------------------
; A - border/paper, X - char, Y - color
clear_screen:
sta PAPER
lsr
lsr
lsr
lsr
sta BORDER
txa
ldx #0
- sta SCREEN,x
sta SCREEN+$100,x
sta SCREEN+$200,x
sta SCREEN+$2E8,x
pha
tya
sta COLORR,x
sta COLORR+$100,x
sta COLORR+$200,x
sta COLORR+$2E8,x
pla
inx
bne -
rts
; returns key as read by GETIN, makes a small pause before reading
read_key:
ldy #40
ldx #0
- dex
bne -
dey
bne -
- jsr SCNKEY
jsr GETIN
beq -
rts
; find first active drive
FindFirstDrive:
lda #8
sta drive
- ldx #0
stx status
jsr LISTEN
lda #$FF
jsr SECOND
lda status
bpl +
jsr UNLSTN
inc drive
lda drive
cmp #31
bne -
lda #8 ; if not found, set 08
sta drive
+ jsr UNLSTN
rts
;--------------------------------
; IRQ
;--------------------------------
; setup VIC II for waving menu
WaveVIC:
sei
lda #WaveIRQ
sta IRQ_Vector+1
lda #$7f ;disable cia irq
sta $dc0d
sta $dd0d
lda $dc0d
lda $dd0d
lda $d01a ;enable raster irq
ora #01
sta $d01a
lda $d011 ;raster irq bit 8
and #$7f
sta $d011
lda #SCR_FirstLine
sta $d012
sta CurrentLine
lda #06
sta $d020
lda #00
sta $d021
sta CurrentWave
sta FirstWave
sta WavePause
; lda $dd00 ;vic bank
; and #$fc
; ora #00 !0-c000,1-8000
; sta $dd00
; ;vic setup $d018
; ;bit 7-4 screen 0001-$0400
; ;bit 3-1 charmem 001-$0800
; lda #%00010100
; sta $d018
cli
rts
; return VIC II to normal state
NormalVIC:
sei
lda #IRQ_Kernel
sta IRQ_Vector+1
jsr $FDA3 ; IOINIT, init CIA,IRQ
;jsr $FD50 ; RAMTAS, init memory
jsr $FD15 ; RESTOR, init I/O
jsr $FF5B ; SCINIT, init video
;lda $d01a
;and #$fe
;sta $d01a
;lda #$c8
;sta $d016
lda RunBorderColor
sta $d020
lda RunPaperColor
sta $d021
lda RunInkColor
sta CursorColor
ldx #$20 ; clear SID registers
lda #$00
- sta $D400,x
dex
bpl -
cli
rts
; IRQ for menu waves
WaveIRQ:
lda DoWave
beq dosound
nop
nop
nop
nop
nop
nop
ldy CurrentWave
lda WaveTable,y
sta $d016
iny
cpy #WaveTableMax
bne irq0
ldy #00
irq0: sty CurrentWave
ldy CurrentLine
cpy #SCR_LastLine-8
bcc +
dey
+ tya
clc
adc #08
cmp #SCR_LastLine
bcc irq1
lda #SCR_FirstLine
irq1: sta CurrentLine
sta $d012
cmp #SCR_FirstLine
bne irqend
lda #$c8
sta $d016
lda FirstWave
sta CurrentWave
ldy WavePause
iny
cpy #WaveSpeed
bne irq3
ldy #00
ldx FirstWave
inx
cpx #WaveTableMax
bne irq4
ldx #00
irq4: stx FirstWave
irq3: sty WavePause
dosound:
ldx #$50
- lda $D41B ;Oscillator 3 Output
sta $D401 ;Voice 1: Frequency Control - High-Byte
dex
bne -
irqend: ldy $d019
sty $d019
pla
tay
pla
tax
pla
rti
WaveTable: !byte $c0,$c0,$c1,$c1,$c2,$c3,$c4,$c5,$c5,$c6,$c6,$c7,$c7
!byte $c7,$c7,$c6,$c6,$c5,$c5,$c4,$c3,$c2,$c1,$c1,$c0,$c0
WaveTableMax = *-WaveTable
;--------------------------------
; memory copy
;--------------------------------
TableAddress = $FB
ProgramIndex = $FD
prepare_run:
sta ProgramIndex
ldy #0
- cpy current_menu
beq +
ldx menu_items_no,y
txa
clc
adc ProgramIndex
sta ProgramIndex
iny
bne -
+ jsr NormalVIC
jsr $E453 ; load BASIC vectors
jsr $E3BF ; init BASIC RAM
;jsr $A68E ; set current character pointer to start of basic - 1
;jsr $E422 ; print BASIC start up messages
;ldx #$FB
;txs
jsr FindFirstDrive
startCopy:
ldy #CartCopyLen ; copy routine to 0340
- lda CartCopy0340,y
sta $0340,y
dey
bpl -
ldy #0 ;calculate program table element address
sty TableAddress+1 ;each table element is 9 bytes
lda ProgramIndex ;ProgramIndex*8
asl
rol TableAddress+1
asl
rol TableAddress+1
asl
rol TableAddress+1
sta TableAddress
lda ProgramIndex ;ProgramIndex*8 + ProgramIndex
clc
adc TableAddress
sta TableAddress
bcc sc2
inc TableAddress+1
sc2: lda ProgramTable ;add Program table address
clc
adc TableAddress
sta TableAddress
lda ProgramTable+1
adc TableAddress+1
sta TableAddress+1
lda (TableAddress),y ;set values in copy routine
sta CartBank
iny
lda (TableAddress),y
sta CartAddr
iny
lda (TableAddress),y
sta CartAddr+1
iny
lda (TableAddress),y
sta PrgLenLo
iny
lda (TableAddress),y
sta PrgLenHi
iny
lda (TableAddress),y
sta MemAddr
iny
lda (TableAddress),y
sta MemAddr+1
iny
lda (TableAddress),y
sta pstart+1
iny
lda (TableAddress),y
sta pstart+2
beq sc3 ;if high byte of start address != 0
lda #$4c ;change lda to jmp
sta pstart ;else, run as basic
sc3: jmp $0340
CartCopy0340:
!pseudopc $0340 {
PrgLenLo = * + 1
ldx #00 ;program length lo
PrgLenHi = * + 1
ldy #00 ;program length hi
CartBank = * + 1
cbank: lda #00 ;cartridge start bank
sta $de00 ;cartridge bank switching address
CartAddr = * + 1
caddr: lda $8200 ;cartridge start adr
MemAddr = * + 1
sta $0801 ;c64 memory start adr
dex
cpx #$ff
bne cc1
dey
cpy #$ff
beq crtoff
cc1: inc MemAddr
bne cc2
inc MemAddr+1
cc2: inc CartAddr
bne caddr
inc CartAddr+1
lda CartAddr+1
cmp #$a0 ;next bank?
bne caddr
inc CartBank
lda #$80 ;cartridge bank is on $8000-$9fff
sta CartAddr+1
jmp cbank
crtoff:
;turn off cartridge (gmod3)
lda #%01100000
sta $de08
lda MemAddr ;set end of program (var start)
sta $2d
sta $2f
sta $31
sta $ae
lda MemAddr+1
sta $2e
sta $30
sta $32
sta $af
pstart: lda $0801 ;start the program
lda #00 ; basic start
jsr $A871 ; clr
jsr $A533 ; re-link
jsr $A68E ; set current character pointer to start of basic - 1
jmp $A7AE ; run
}
CartCopyLen = *-CartCopy0340
;--------------------------------
; menu sound
;--------------------------------
sound_init:
lda #$00
sta $D40F ;Voice 3: Frequency Control - High-Byte
lda #$1E
sta $D40E ;Voice 3: Frequency Control - Low-Byte
lda #$F0
sta $D414 ;Voice 3: Sustain / Release Cycle Control
sta $D406 ;Voice 1: Sustain / Release Cycle Control
lda #$81
sta $D412 ;Voice 3: Control Register
lda #$11
sta $D404 ;Voice 1: Control Register
lda #$83
sta $D418 ;Select Filter Mode and Volume
rts
;--------------------------------
; menu data
;--------------------------------
; menu chars corners
MENU_CHR_UL = $EC
MENU_CHR_UR = $FB
MENU_CHR_DL = $FC
MENU_CHR_DR = $FE
MENU_COL_UL = 10
MENU_COL_UR = 10
MENU_COL_DL = 10
MENU_COL_DR = 10
; menu chars sides
MENU_CHR_L = $61
MENU_CHR_R = $E1
MENU_CHR_U = $E2
MENU_CHR_D = $62
MENU_COL_L = 10
MENU_COL_R = 10
MENU_COL_U = 10
MENU_COL_D = 10
; menu chars misc
MENU_CHR_BACK = $20
MENU_CHR_SHAD = $E6
MENU_COL_BACK = 5
MENU_COL_SHAD = 2
MENU_COL1_KEY = 12
MENU_COL2_KEY = 15
MENU_COL_NAME = 4
SCREEN_CHR = $A0
SCREEN_COL = 6
; menu border first, middle, bottom and last line chars
menu_border_chr:
!byte MENU_CHR_UL,MENU_CHR_U,MENU_CHR_UR,SCREEN_CHR
!byte MENU_CHR_L,MENU_CHR_BACK,MENU_CHR_R,MENU_CHR_SHAD
!byte MENU_CHR_DL,MENU_CHR_D,MENU_CHR_DR,MENU_CHR_SHAD
!byte SCREEN_CHR,MENU_CHR_SHAD,MENU_CHR_SHAD,MENU_CHR_SHAD
; menu border first, middle, bottom and last line colors
menu_border_col:
!byte MENU_COL_UL,MENU_COL_U,MENU_COL_UR,SCREEN_COL
!byte MENU_COL_L,MENU_COL_BACK,MENU_COL_R,MENU_COL_SHAD
!byte MENU_COL_DL,MENU_COL_D,MENU_COL_DR,MENU_COL_SHAD
!byte SCREEN_COL,MENU_COL_SHAD,MENU_COL_SHAD,MENU_COL_SHAD
; misc menu chars
menu_chr_back: !byte MENU_CHR_BACK
menu_key_chr: !byte $F5,$B1,$F6
menu_key_col: !byte MENU_COL1_KEY,MENU_COL2_KEY,MENU_COL1_KEY
; keys for selecting a program
menu_keys: ; 1-9
!for i,0,8 {
!byte i+$31
}
!byte $30 ; 0
; a-z
!for i,0,25 {
!byte i+$41
}
; A-Z
!for i,0,25 {
!byte i+$c1
}
menu_keys_no = *-menu_keys
!byte $5F ; left arrow
ProgramTable = *
RunBorderColor = ProgramTable + 2
RunPaperColor = RunBorderColor + 1
RunInkColor = RunPaperColor + 1
DoWave = RunInkColor +1
DoSound = DoWave + 1
menu_items_no = DoSound + 1
menu_offset = menu_items_no + 8
menu_width = menu_offset + 16
menu_height = menu_width + 8
menu_spacing = menu_height + 8
;
menu_names = menu_spacing + 8
menu_items = menu_names + 16
menu_help = menu_items + 16
menu_name1 = menu_help + 40
;menu_items1 = menu_name1 + 40
;
;programs = menu_items1 + (20 * 2)
;ProgramTable: !word programs
; program run colors
; RunBorderColor: !byte 12 ;14
; RunPaperColor: !byte 15 ;6
; RunInkColor: !byte 0 ;14
; DoWave: !byte 1 ;menu waving
; DoSound: !byte 1 ;menu sound
; menu data for 8 menus
; menu_items_no: !byte 1,0,0,0,0,0,0,0
; menu_offset: !word 85,0,0,0,0,0,0,0
; menu_width: !byte 15,0,0,0,0,0,0,0
; menu_height: !byte 10,0,0,0,0,0,0,0
; menu_spacing: !byte 1,0,0,0,0,0,0,0
; menu_names: !word menu_name1,0,0,0,0,0,0,0
; menu_items: !word menu_items1,0,0,0,0,0,0,0
;
; ;1234567890123456789012345678901234567890
; menu_help: !text "(Shift)CRSR: Scroll, Fn/Ret: Menu select"
;
; menu_name1: !text " Testing... "
;
; menu_items1:
; ;!text @"Testing1\$00"
; ;!text @"Testing2\$00"
; !text "Testing1",0
; !text "Testing2",0
;
;
; programs:
; program table
; !byte bank
; !word address in bank
; !word length
; !word load address
; !word start address
; if hi byte=0, then run as basic
; ne radi
; f4/6 64tester
; f4/c basic 64 compiler
; f5/e turbocopy 5
; f5/h turbo nibbler 5