4cade/src/macros.a

570 lines
13 KiB
Plaintext
Executable File

;license:MIT
;(c) 2018-2021 by 4am & qkumba
;
; common assembler macros (6502 compatible)
;
!ifndef _MACROS_ {
!source "src/constants.a"
; for functions that take parameters on the stack
; set (PARAM) to point to the parameters and
; move the stack pointer to the first byte after the parameters
; clobbers A,Y
; preserves X
!macro PARAMS_ON_STACK .bytes {
pla
sta PARAM
clc
adc #.bytes
tay
pla
sta PARAM+1
adc #0
pha
tya
pha
}
; for functions that take parameters on the stack
; load a 16-bit value from the parameters on the stack into A (low) and Y (high)
; (assumes PARAMS_ON_STACK was used first)
!macro LDPARAM .offset {
ldy #.offset
lda (PARAM),y
pha
iny
lda (PARAM),y
tay
pla
}
; for functions that take parameters on the stack
; load a 16-bit value from the parameters on the stack into A (low) and Y (high)
; then store it as new source
; (assumes PARAMS_ON_STACK was used first)
!macro LDPARAMPTR .offset,.dest {
ldy #.offset
lda (PARAM),y
sta .dest
iny
lda (PARAM),y
sta .dest+1
}
; load the address of .ptr into A (low) and Y (high)
!macro LDADDR .ptr {
lda #<.ptr
ldy #>.ptr
}
; load a 16-bit value into A (low) and Y (high)
!macro LD16 .ptr {
lda .ptr
ldy .ptr+1
}
; load a 16-bit value into X (low) and Y (high)
!macro LDX16 .ptr {
ldx .ptr
ldy .ptr+1
}
; store a 16-bit value from A (low) and Y (high)
!macro ST16 .ptr {
sta .ptr
sty .ptr+1
}
; store a 16-bit value from X (low) and Y (high)
!macro STX16 .ptr {
stx .ptr
sty .ptr+1
}
; decrement a 16-bit value in A (low) and Y (high)
!macro DEC16 {
sec
sbc #1
bcs +
dey
+
}
; decrement a 16-bit value in X (low) and Y (high)
; destroys A!
!macro DEX16 {
txa
bne +
dey
+ dex
}
; increment a 16-bit value in A (low) and Y (high)
!macro INC16 {
clc
adc #1
bne +
iny
+
}
; increment a 16-bit value in X (low) and Y (high)
!macro INX16 {
inx
bne +
iny
+
}
; compare a 16-bit value in A (low) and Y (high) to an absolute address
; branch to target if no match
; zeroes A!
!macro CMP16ADDR_NE .addr, .target {
eor .addr
bne .target
cpy .addr+1
bne .target
}
; compare a 16-bit value in X (low) and Y (high) to an absolute address
; branch to target if no match
!macro CPX16ADDR_NE .addr, .target {
cpx .addr
bne .target
cpy .addr+1
bne .target
}
; compare a 16-bit value in A (low) and Y (high) to an immediate value
; branch to target if match
!macro CMP16_E .val, .target {
cmp #<.val
bne +
cpy #>.val
beq .target
+
}
; compare a 16-bit value in A (low) and Y (high) to an immediate value
; branch to target if no match
!macro CMP16_NE .val, .target {
cmp #<.val
bne .target
cpy #>.val
bne .target
}
; compare a 16-bit value in X (low) and Y (high) against zero
; branch to target if not zero
; requires LDX16 immediately prior, since Y comparison is implicit!
; destroys A!
!macro CPX16_0_NE .target {
bne .target
txa
bne .target
}
!macro LBPL .target {
bmi +
jmp .target
+
}
!macro LBMI .target {
bpl +
jmp .target
+
}
!macro LBNE .target {
beq +
jmp .target
+
}
!macro LBCS .target {
bcc +
jmp .target
+
}
; use BIT to swallow the following 1-byte opcode
!macro HIDE_NEXT_BYTE {
!byte $24
}
; use BIT to swallow the following 2-byte opcode
!macro HIDE_NEXT_2_BYTES {
!byte $2C
}
; debugging
!macro DEBUGWAIT {
bit $c010
- bit $c000
bpl -
bit $c010
}
; various language card configurations
!macro READ_RAM1_NO_WRITE {
sta $C088
}
!macro READ_RAM1_WRITE_RAM1 {
bit $C08B
bit $C08B
}
!macro READ_RAM2_NO_WRITE {
sta $C080
}
!macro READ_RAM2_WRITE_RAM2 {
bit $C083
bit $C083
}
!macro READ_ROM_WRITE_RAM1 {
bit $C089
bit $C089
}
!macro READ_ROM_WRITE_RAM2 {
bit $C081
bit $C081
}
!macro READ_ROM_NO_WRITE {
sta $C082
}
!macro READ_AUX {
sta READMAINMEM
}
!macro READ_MAIN {
sta READAUXMEM
}
!macro WRITE_AUX {
sta WRITEAUXMEM
}
!macro WRITE_MAIN {
sta WRITEMAINMEM
}
; requires setting zpCharMask in zero page to #$FF or #$DF before use
!macro FORCE_UPPERCASE_IF_REQUIRED {
cmp #$E1
bcc +
and zpCharMask
+
}
!macro HGR_BYTE_TO_DHGR_BYTES {
;1GFEDCBA ->
;1GGFFEED (main) +
;1DCCBBAA (aux)
;
; in: A = HGR byte
; out: A = DHGR byte in mainmem
; X = DHGR byte in auxmem
; preserves Y
; clobbers zero page $00,$01,$02
sty $02
ldy #$02
-- stx $01
ldx #$04
- ror $00 ; duplicate previous bit
lsr ; fetch bit
php
ror $00 ; insert bit
plp
dex
bne -
ldx $00
dey
bne --
txa
sec
ror $01 ; set bit 7 explicitly on auxmem value
ldx $01
ldy $02
}
; these are mostly for prelaunchers -- code in the main program should keep track of which bank is active to minimize code size
!macro ENABLE_ACCEL {
+ENABLE_ACCEL_LC
+READ_ROM_NO_WRITE
}
; leave LC active on exit
!macro ENABLE_ACCEL_LC {
+READ_RAM2_NO_WRITE
jsr EnableAccelerator
}
!macro DISABLE_ACCEL {
+READ_RAM2_NO_WRITE
jsr DisableAccelerator
+READ_ROM_NO_WRITE
}
!macro GET_MACHINE_STATUS {
+READ_RAM2_NO_WRITE
lda MachineStatus
+READ_ROM_NO_WRITE
}
!macro GET_MOCKINGBOARD_SPEECH { ;sign set if SC-01, overflow set if SSI-263
+READ_RAM2_NO_WRITE
bit MockingboardStuff
+READ_ROM_NO_WRITE
}
!macro GET_MOCKINGBOARD_SPEECH_AND_MACHINE_STATUS { ;sign set if SC-01, overflow set if SSI-263
+READ_RAM2_NO_WRITE
lda MachineStatus
bit MockingboardStuff
+READ_ROM_NO_WRITE
}
!macro GET_MOCKINGBOARD_SLOT { ;carry set if present
+READ_RAM2_NO_WRITE
lda MockingboardStuff
cmp #1
and #7
ora #$C0
+READ_ROM_NO_WRITE
}
!macro GET_MOCKINGBOARD_SLOT_AND_MACHINE_STATUS { ;carry set if present
+READ_RAM2_NO_WRITE
lda MockingboardStuff
cmp #1
and #7
ora #$C0
tax
lda MachineStatus
+READ_ROM_NO_WRITE
}
!macro USES_TEXT_PAGE_2 { ; If we know we are going into a game one-time
lda ROM_MACHINEID ; only we can just blindly turn on TEXT2COPY, as
cmp #$06 ; Alternate Display Mode turns off on reset or reboot.
bne +
sec
jsr $FE1F ; check for IIgs
bcs +
jsr ROM_TEXT2COPY ; set alternate display mode on IIgs (required for some games)
cli ; enable VBL interrupts
+
}
!macro TEST_TEXT_PAGE_2 { ; On a ROM3 IIgs we can test if Alternate Display Mode
lda ROM_MACHINEID ; is already on. ROM0 and ROM1 versions of ADM use
cmp #$06 ; interrupts and can cause hangs, so safer to just
bne ++ ; leave it turned off, especially in ATTRACT/DEMO mode.
sec
jsr $FE1F ; check for IIgs
bcs ++
tya ; GS ID routine returns with ROM version in Y
cmp #0 ; ROM 0?
beq ++
cmp #1 ; ROM 1?
beq ++
lda #$20
sta $0800 ; check if Alternate Display Mode is already on
lda #$FF
jsr ROM_WAIT ; skip a VBL cycle
!cpu 65816
lda $E00800 ; did we shadow copy data to bank $E0?
cmp #$20
beq + ; only call TEXT2COPY if we know it's off
!cpu 6502 ; https://archive.org/details/develop-04_9010_October_1990/page/n51/mode/1up
jsr ROM_TEXT2COPY ; set alternate display mode on IIgs (required for some games)
+ cli ; enable VBL interrupts
++
}
!macro RESET_VECTOR .addr {
lda #<.addr
sta $3F2
!ifndef .addr {
!set emitted=1
lda #>.addr
sta $3F3
eor #$A5
sta $3F4
} else {
!ifdef emitted {
lda #>.addr
sta $3F3
eor #$A5
sta $3F4
} else {
!if (>.addr != 1) or (.addr = $100) {
lda #>.addr
sta $3F3
eor #$A5
sta $3F4
}
}
}
}
!macro RESET_VECTOR_HALF .addr {
lda #>.addr
sta $3F3
eor #$A5
sta $3F4
}
!macro RESET_AND_IRQ_VECTOR .addr {
lda #<.addr
sta $3F2
sta $3FE
lda #>.addr
sta $3F3
sta $3FF
eor #$A5
sta $3F4
}
; for games that clobber $100-$105, the prelaunch code constructs a new reset vector
; somewhere else and sets the reset vector to point at it.
!macro NEW_RESET_VECTOR .addr {
ldx #5
- lda $100,x
sta .addr,x
dex
bpl -
+RESET_VECTOR .addr
}
!macro NEW_RESET_VECTOR_64K .addr {
lda #$2C
sta .addr
lda #$89
sta .addr+1
lda #$C0
sta .addr+2
lda #$4C ; JMP $FAA6 to reboot
sta .addr+3
lda #$A6
sta .addr+4
lda #$FA
sta .addr+5
lda #<.addr
sta $3F2
sta $FFFC
!if >.addr != 1 {
lda #>.addr
sta $3F3
sta $FFFD
eor #$A5
sta $3F4
}
}
; for 64k games on ][+ which either hang or restart
; updates reset hook to reboot on ctrl-reset
!macro LC_REBOOT {
inc $101 ; (assumes LC is switched in)
lda #$A6
sta $104
lda #$FA
sta $105 ; update reset hook to reboot
lda #1
sta $FFFD
lsr
sta $FFFC ; LC reset vector fix
}
; load an external file by pathname
; LC RAM 2 MUST BE BANKED IN
; set .addr to $0000 to load to any aligned addres in main
; LOW BYTE OF .addr MUST BE $00 IN THAT CASE
!macro LOAD_FILE_AT .filepath, .addr {
!if .addr > 0 {
lda #>.addr
sta ldrhi
}
!if <.addr > 0 {
lda #<.addr
sta ldrlo
}
lda iCurBlockLo
pha
lda iCurBlockHi
pha
ldx #0 ; read to main memory
!if <.addr = 0 {
stx ldrlo
}
+LDADDR .filepath
jsr iLoadFileDirect
pla
sta iCurBlockHi
pla
sta iCurBlockLo
}
; load an external file by pathname
; set path to new directory
; LC RAM 2 MUST BE BANKED IN
!macro LOAD_FILE_KEEP_DIR .filepath, .dirlen {
ldx #0 ; read to main memory
stx ldrhi
stx ldrlo
+LDADDR .filepath
jsr iLoadFileDirect
ldx #.dirlen
stx $BFD0
- lda .filepath,x
sta $BFD0,x
dex
bne -
}
!macro LOAD_FILE_KEEP_DIR .filepath, .addr, .dirlen {
lda #>.addr
sta ldrhi
lda #<.addr
sta ldrlo
ldx #0 ; read to main memory
+LDADDR .filepath
jsr iLoadFileDirect
ldx #.dirlen
stx $BFD0
- lda .filepath,x
sta $BFD0,x
dex
bne -
}
!macro LOAD_XSINGLE .title {
+READ_RAM1_WRITE_RAM1
+LDADDR .title
jsr iLoadXSingle
+READ_ROM_NO_WRITE
}
; Macros for demo launchers that need to check whether they should run
; on the current machine. Exit demo if the requirements are not met.
!macro GAME_REQUIRES_JOYSTICK {
+GET_MACHINE_STATUS
and #HAS_JOYSTICK
bne +
jmp $100
+
}
_MACROS_=*
}