716 lines
11 KiB
ArmAsm
716 lines
11 KiB
ArmAsm
.include "global.s"
|
|
|
|
;; ****************************************
|
|
;; Beginning of module
|
|
;; BANKED: checked
|
|
.title "Runtime"
|
|
.module Runtime
|
|
.area _HEADER (ABS)
|
|
|
|
;; Standard header for the GB
|
|
.org 0x00
|
|
RET ; Empty function (default for interrupts)
|
|
|
|
.org 0x10
|
|
.byte 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
|
|
.byte 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
|
|
|
|
;; Interrupt vectors
|
|
.org 0x40 ; VBL
|
|
.int_VBL:
|
|
PUSH HL
|
|
LD HL,#.int_0x40
|
|
JP .int
|
|
|
|
.org 0x48 ; LCD
|
|
.int_LCD:
|
|
PUSH HL
|
|
LD HL,#.int_0x48
|
|
JP .int
|
|
|
|
.org 0x50 ; TIM
|
|
.int_TIM:
|
|
PUSH HL
|
|
LD HL,#.int_0x50
|
|
JP .int
|
|
|
|
.org 0x58 ; SIO
|
|
.int_SIO:
|
|
PUSH HL
|
|
LD HL,#.int_0x58
|
|
JP .int
|
|
|
|
.org 0x60 ; JOY
|
|
.int_JOY:
|
|
PUSH HL
|
|
LD HL,#.int_0x60
|
|
JP .int
|
|
|
|
.int:
|
|
PUSH AF
|
|
PUSH BC
|
|
PUSH DE
|
|
1$:
|
|
LD A,(HL+)
|
|
OR (HL)
|
|
JR Z,2$
|
|
PUSH HL
|
|
LD A,(HL-)
|
|
LD L,(HL)
|
|
LD H,A
|
|
CALL 3$
|
|
POP HL
|
|
INC HL
|
|
JR 1$
|
|
2$:
|
|
POP DE
|
|
POP BC
|
|
POP AF
|
|
POP HL
|
|
RETI
|
|
|
|
3$:
|
|
JP (HL)
|
|
|
|
;; GameBoy Header
|
|
|
|
;; DO NOT CHANGE...
|
|
.org 0x100
|
|
.header:
|
|
NOP
|
|
JP 0x150
|
|
.byte 0xCE,0xED,0x66,0x66
|
|
.byte 0xCC,0x0D,0x00,0x0B
|
|
.byte 0x03,0x73,0x00,0x83
|
|
.byte 0x00,0x0C,0x00,0x0D
|
|
.byte 0x00,0x08,0x11,0x1F
|
|
.byte 0x88,0x89,0x00,0x0E
|
|
.byte 0xDC,0xCC,0x6E,0xE6
|
|
.byte 0xDD,0xDD,0xD9,0x99
|
|
.byte 0xBB,0xBB,0x67,0x63
|
|
.byte 0x6E,0x0E,0xEC,0xCC
|
|
.byte 0xDD,0xDC,0x99,0x9F
|
|
.byte 0xBB,0xB9,0x33,0x3E
|
|
|
|
;; Title of the game
|
|
.org 0x134
|
|
.asciz "Title"
|
|
|
|
.org 0x144
|
|
.byte 0,0,0
|
|
|
|
;; Cartridge type is ROM only
|
|
.org 0x147
|
|
.byte 0
|
|
|
|
;; ROM size is 32kB
|
|
.org 0x148
|
|
.byte 0
|
|
|
|
;; RAM size is 0kB
|
|
.org 0x149
|
|
.byte 0
|
|
|
|
;; Maker ID
|
|
.org 0x14A
|
|
.byte 0x00,0x00
|
|
|
|
;; Version number
|
|
.org 0x14C
|
|
.byte 0x01
|
|
|
|
;; Complement check
|
|
.org 0x14D
|
|
.byte 0x00
|
|
|
|
;; Checksum
|
|
.org 0x14E
|
|
.byte 0x00,0x00
|
|
|
|
;; ****************************************
|
|
.org 0x150
|
|
.code_start:
|
|
;; Beginning of the code
|
|
DI ; Disable interrupts
|
|
LD D,A ; Store CPU type in D
|
|
XOR A
|
|
;; Initialize the stack
|
|
LD SP,#.STACK
|
|
;; Clear from 0xC000 to 0xDFFF
|
|
LD HL,#0xDFFF
|
|
LD C,#0x20
|
|
LD B,#0x00
|
|
1$:
|
|
LD (HL-),A
|
|
DEC B
|
|
JR NZ,1$
|
|
DEC C
|
|
JR NZ,1$
|
|
;; Clear from 0xFE00 to 0xFEFF
|
|
LD HL,#0xFEFF
|
|
LD B,#0x00
|
|
2$:
|
|
LD (HL-),A
|
|
DEC B
|
|
JR NZ,2$
|
|
;; Clear from 0xFF80 to 0xFFFF
|
|
LD HL,#0xFFFF
|
|
LD B,#0x80
|
|
3$:
|
|
LD (HL-),A
|
|
DEC B
|
|
JR NZ,3$
|
|
; LD (.mode),A ; Clearing (.mode) is performed when clearing RAM
|
|
;; Store CPU type
|
|
LD A,D
|
|
LD (__cpu),A
|
|
|
|
;; Turn the screen off
|
|
CALL .display_off
|
|
|
|
;; Initialize the display
|
|
XOR A
|
|
LDH (.SCY),A
|
|
LDH (.SCX),A
|
|
LDH (.STAT),A
|
|
LDH (.WY),A
|
|
LD A,#0x07
|
|
LDH (.WX),A
|
|
|
|
;; Copy refresh_OAM routine to HIRAM
|
|
LD BC,#.refresh_OAM
|
|
LD HL,#.start_refresh_OAM
|
|
LD B,#.end_refresh_OAM-.start_refresh_OAM
|
|
4$:
|
|
LD A,(HL+)
|
|
LDH (C),A
|
|
INC C
|
|
DEC B
|
|
JR NZ,4$
|
|
|
|
;; Install interrupt routines
|
|
LD BC,#.vbl
|
|
CALL .add_VBL
|
|
LD BC,#.serial_IO
|
|
CALL .add_SIO
|
|
|
|
;; Standard color palettes
|
|
LD A,#0b11100100 ; Grey 3 = 11 (Black)
|
|
; Grey 2 = 10 (Dark grey)
|
|
; Grey 1 = 01 (Light grey)
|
|
; Grey 0 = 00 (Transparent)
|
|
LDH (.BGP),A
|
|
LDH (.OBP0),A
|
|
LD A,#0b00011011
|
|
LDH (.OBP1),A
|
|
|
|
;; Turn the screen on
|
|
LD A,#0b11000000 ; LCD = On
|
|
; WindowBank = 0x9C00
|
|
; Window = Off
|
|
; BG Chr = 0x8800
|
|
; BG Bank = 0x9800
|
|
; OBJ = 8x8
|
|
; OBJ = Off
|
|
; BG = Off
|
|
LDH (.LCDC),A
|
|
XOR A
|
|
LDH (.IF),A
|
|
LD A,#0b00001001 ; Pin P10-P13 = Off
|
|
; Serial I/O = On
|
|
; Timer Ovfl = Off
|
|
; LCDC = Off
|
|
; V-Blank = On
|
|
LDH (.IE),A
|
|
|
|
XOR A
|
|
LDH (.NR52),A ; Turn sound off
|
|
LDH (.SC),A ; Use external clock
|
|
LD A,#.DT_IDLE
|
|
LDH (.SB),A ; Send IDLE byte
|
|
LD A,#0x80
|
|
LDH (.SC),A ; Use external clock
|
|
|
|
XOR A ; Erase the malloc list
|
|
; LD (_malloc_heap_start+0),A
|
|
; LD (_malloc_heap_start+1),A
|
|
; LD (.sys_time+0),A ; Zero the system clock
|
|
; LD (.sys_time+1),A
|
|
|
|
call gsinit
|
|
|
|
; CALL .init
|
|
|
|
EI ; Enable interrupts
|
|
|
|
;; Call the main function
|
|
CALL banked_call
|
|
.dw _main
|
|
.if __RGBDS__
|
|
.dw BANK(_main)
|
|
.else
|
|
.dw 1
|
|
.endif
|
|
_exit::
|
|
99$:
|
|
HALT
|
|
JR 99$ ; Wait forever
|
|
|
|
.org .MODE_TABLE
|
|
;; Jump table for modes
|
|
RET
|
|
|
|
;; ****************************************
|
|
|
|
;; Ordering of segments for the linker
|
|
;; Code that really needs to be in bank 0
|
|
.area _HOME
|
|
;; Similar to _HOME
|
|
.area _BASE
|
|
;; Code
|
|
.area _CODE
|
|
;; Constant data
|
|
.area _LIT
|
|
;; Constant data used to init _DATA
|
|
.area _GSINIT
|
|
.area _GSINITTAIL
|
|
.area _GSFINAL
|
|
;; Initialised in ram data
|
|
.area _DATA
|
|
;; Uninitialised ram data
|
|
.area _BSS
|
|
;; For malloc
|
|
.area _HEAP
|
|
|
|
.area _BSS
|
|
__cpu::
|
|
.ds 0x01 ; GB type (GB, PGB, CGB)
|
|
.mode::
|
|
.ds 0x01 ; Current mode
|
|
__io_out::
|
|
.ds 0x01 ; Byte to send
|
|
__io_in::
|
|
.ds 0x01 ; Received byte
|
|
__io_status::
|
|
.ds 0x01 ; Status of serial IO
|
|
.vbl_done::
|
|
.ds 0x01 ; Is VBL interrupt finished?
|
|
__current_bank::
|
|
.ds 0x01 ; Current MBC1 style bank.
|
|
.sys_time::
|
|
_sys_time::
|
|
.ds 0x02 ; System time in VBL units
|
|
.int_0x40::
|
|
.blkw 0x08
|
|
.int_0x48::
|
|
.blkw 0x08
|
|
.int_0x50::
|
|
.blkw 0x08
|
|
.int_0x58::
|
|
.blkw 0x08
|
|
.int_0x60::
|
|
.blkw 0x08
|
|
|
|
;; Runtime library
|
|
.area _GSINIT
|
|
gsinit::
|
|
.area _GSINITTAIL
|
|
ret
|
|
|
|
.area _HOME
|
|
;; Call the initialization function for the mode specified in HL
|
|
.set_mode::
|
|
LD A,L
|
|
LD (.mode),A
|
|
|
|
;; AND to get rid of the extra flags
|
|
AND #0x03
|
|
LD L,A
|
|
LD BC,#.MODE_TABLE
|
|
SLA L ; Multiply mode by 4
|
|
SLA L
|
|
ADD HL,BC
|
|
JP (HL) ; Jump to initialization routine
|
|
|
|
;; Add interrupt routine in BC to the interrupt list
|
|
.remove_VBL::
|
|
LD HL,#.int_0x40
|
|
JP .remove_int
|
|
.remove_LCD::
|
|
LD HL,#.int_0x48
|
|
JP .remove_int
|
|
.remove_TIM::
|
|
LD HL,#.int_0x50
|
|
JP .remove_int
|
|
.remove_SIO::
|
|
LD HL,#.int_0x58
|
|
JP .remove_int
|
|
.remove_JOY::
|
|
LD HL,#.int_0x60
|
|
JP .remove_int
|
|
.add_VBL::
|
|
LD HL,#.int_0x40
|
|
JP .add_int
|
|
.add_LCD::
|
|
LD HL,#.int_0x48
|
|
JP .add_int
|
|
.add_TIM::
|
|
LD HL,#.int_0x50
|
|
JP .add_int
|
|
.add_SIO::
|
|
LD HL,#.int_0x58
|
|
JP .add_int
|
|
.add_JOY::
|
|
LD HL,#.int_0x60
|
|
JP .add_int
|
|
|
|
;; Remove interrupt BC from interrupt list HL if it exists
|
|
;; Abort if a 0000 is found (end of list)
|
|
;; Will only remove last int on list
|
|
.remove_int::
|
|
1$:
|
|
LD A,(HL+)
|
|
LD E,A
|
|
LD D,(HL)
|
|
OR D
|
|
RET Z ; No interrupt found
|
|
|
|
LD A,E
|
|
CP C
|
|
JR NZ,1$
|
|
LD A,D
|
|
CP B
|
|
JR NZ,1$
|
|
|
|
XOR A
|
|
LD (HL-),A
|
|
LD (HL),A
|
|
INC A ; Clear Z flag
|
|
|
|
;; Now do a memcpy from here until the end of the list
|
|
LD D,H
|
|
LD E,L
|
|
DEC DE
|
|
|
|
INC HL
|
|
2$:
|
|
LD A,(HL+)
|
|
LD (DE),A
|
|
LD B,A
|
|
INC DE
|
|
LD A,(HL+)
|
|
LD (DE),A
|
|
INC DE
|
|
OR B
|
|
RET Z
|
|
JR 2$
|
|
|
|
;; Add interrupt routine in BC to the interrupt list in HL
|
|
.add_int::
|
|
1$:
|
|
LD A,(HL+)
|
|
OR (HL)
|
|
JR Z,2$
|
|
INC HL
|
|
JR 1$
|
|
2$:
|
|
LD (HL),B
|
|
DEC HL
|
|
LD (HL),C
|
|
RET
|
|
|
|
|
|
;; VBlank interrupt
|
|
.vbl:
|
|
LD HL,#.sys_time
|
|
INC (HL)
|
|
JR NZ,2$
|
|
INC HL
|
|
INC (HL)
|
|
2$:
|
|
CALL .refresh_OAM
|
|
|
|
LD A,#0x01
|
|
LD (.vbl_done),A
|
|
RET
|
|
|
|
;; Wait for VBL interrupt to be finished
|
|
.wait_vbl_done::
|
|
_wait_vbl_done::
|
|
;; Check if the screen is on
|
|
LDH A,(.LCDC)
|
|
ADD A
|
|
RET NC ; Return if screen is off
|
|
XOR A
|
|
DI
|
|
LD (.vbl_done),A ; Clear any previous sets of vbl_done
|
|
EI
|
|
1$:
|
|
HALT ; Wait for any interrupt
|
|
NOP ; HALT sometimes skips the next instruction
|
|
LD A,(.vbl_done) ; Was it a VBlank interrupt?
|
|
;; Warning: we may lose a VBlank interrupt, if it occurs now
|
|
OR A
|
|
JR Z,1$ ; No: back to sleep!
|
|
|
|
XOR A
|
|
LD (.vbl_done),A
|
|
RET
|
|
|
|
.display_off::
|
|
_display_off::
|
|
;; Check if the screen is on
|
|
LDH A,(.LCDC)
|
|
ADD A
|
|
RET NC ; Return if screen is off
|
|
1$: ; We wait for the *NEXT* VBL
|
|
LDH A,(.LY)
|
|
CP #0x92 ; Smaller than or equal to 0x91?
|
|
JR NC,1$ ; Loop until smaller than or equal to 0x91
|
|
2$:
|
|
LDH A,(.LY)
|
|
CP #0x91 ; Bigger than 0x90?
|
|
JR C,2$ ; Loop until bigger than 0x90
|
|
|
|
LDH A,(.LCDC)
|
|
AND #0b01111111
|
|
LDH (.LCDC),A ; Turn off screen
|
|
RET
|
|
|
|
;; Copy OAM data to OAM RAM
|
|
.start_refresh_OAM:
|
|
LD A,#>.OAM
|
|
LDH (.DMA),A ; Put A into DMA registers
|
|
LD A,#0x28 ; We need to wait 160 ns
|
|
1$:
|
|
DEC A
|
|
JR NZ,1$
|
|
RET
|
|
.end_refresh_OAM:
|
|
|
|
;; Serial interrupt
|
|
.serial_IO::
|
|
LD A,(__io_status) ; Get status
|
|
|
|
CP #.IO_RECEIVING
|
|
JR NZ,10$
|
|
|
|
;; Receiving data
|
|
LDH A,(.SB) ; Get data byte
|
|
LD (__io_in),A ; Store it
|
|
LD A,#.IO_IDLE
|
|
JR 11$
|
|
|
|
10$:
|
|
|
|
CP #.IO_SENDING
|
|
JR NZ,99$
|
|
|
|
;; Sending data
|
|
LDH A,(.SB) ; Get data byte
|
|
CP #.DT_RECEIVING
|
|
JR Z,11$
|
|
LD A,#.IO_ERROR
|
|
JR 12$
|
|
11$:
|
|
LD A,#.IO_IDLE
|
|
12$:
|
|
LD (__io_status),A ; Store status
|
|
|
|
XOR A
|
|
LDH (.SC),A ; Use external clock
|
|
LD A,#.DT_IDLE
|
|
LDH (.SB),A ; Reply with IDLE byte
|
|
99$:
|
|
LD A,#0x80
|
|
LDH (.SC),A ; Enable transfer with external clock
|
|
RET
|
|
|
|
_mode::
|
|
LDA HL,2(SP) ; Skip return address
|
|
LD L,(HL)
|
|
LD H,#0x00
|
|
CALL .set_mode
|
|
RET
|
|
|
|
_get_mode::
|
|
LD HL,#.mode
|
|
LD E,(HL)
|
|
RET
|
|
|
|
_enable_interrupts::
|
|
EI
|
|
RET
|
|
|
|
_disable_interrupts::
|
|
DI
|
|
RET
|
|
|
|
.reset::
|
|
_reset::
|
|
LD A,(__cpu)
|
|
JP .code_start
|
|
|
|
_set_interrupts::
|
|
DI
|
|
LDA HL,2(SP) ; Skip return address
|
|
XOR A
|
|
LDH (.IF),A ; Clear pending interrupts
|
|
LD A,(HL)
|
|
LDH (.IE),A
|
|
EI ; Enable interrupts
|
|
RET
|
|
|
|
_remove_VBL::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .remove_VBL
|
|
POP BC
|
|
RET
|
|
|
|
_remove_LCD::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .remove_LCD
|
|
POP BC
|
|
RET
|
|
|
|
_remove_TIM::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .remove_TIM
|
|
POP BC
|
|
RET
|
|
|
|
_remove_SIO::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .remove_SIO
|
|
POP BC
|
|
RET
|
|
|
|
_remove_JOY::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .remove_JOY
|
|
POP BC
|
|
RET
|
|
|
|
_add_VBL::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .add_VBL
|
|
POP BC
|
|
RET
|
|
|
|
_add_LCD::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .add_LCD
|
|
POP BC
|
|
RET
|
|
|
|
_add_TIM::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .add_TIM
|
|
POP BC
|
|
RET
|
|
|
|
_add_SIO::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .add_SIO
|
|
POP BC
|
|
RET
|
|
|
|
_add_JOY::
|
|
PUSH BC
|
|
LDA HL,4(SP) ; Skip return address and registers
|
|
LD C,(HL)
|
|
INC HL
|
|
LD B,(HL)
|
|
CALL .add_JOY
|
|
POP BC
|
|
RET
|
|
|
|
_clock::
|
|
ld hl,#.sys_time
|
|
di
|
|
ld a,(hl+)
|
|
ei
|
|
;; Interrupts are disabled for the next instruction...
|
|
ld d,(hl)
|
|
ld e,a
|
|
ret
|
|
|
|
__printTStates::
|
|
ret
|
|
|
|
;; Performs a long call.
|
|
;; Basically:
|
|
;; call banked_call
|
|
;; .dw low
|
|
;; .dw bank
|
|
;; remainder of the code
|
|
;; Total m-cycles:
|
|
;; 3+4+4 + 2+2+2+2+2+2 + 4+4+ 3+4+1+1+1
|
|
;; = 41 for the call
|
|
;; 3+3+4+4+1
|
|
;; = 15 for the ret
|
|
banked_call::
|
|
pop hl ; Get the return address
|
|
ld a,(__current_bank)
|
|
push af ; Push the current bank onto the stack
|
|
ld e,(hl) ; Fetch the call address
|
|
inc hl
|
|
ld d,(hl)
|
|
inc hl
|
|
ld a,(hl+) ; ...and page
|
|
inc hl ; Yes this should be here
|
|
push hl ; Push the real return address
|
|
ld (__current_bank),a
|
|
ld (.MBC1_ROM_PAGE),a ; Perform the switch
|
|
ld hl,#banked_ret ; Push the fake return address
|
|
push hl
|
|
ld l,e
|
|
ld h,d
|
|
jp (hl)
|
|
|
|
banked_ret::
|
|
pop hl ; Get the return address
|
|
pop af ; Pop the old bank
|
|
ld (.MBC1_ROM_PAGE),a
|
|
ld (__current_bank),a
|
|
jp (hl)
|
|
|
|
.area _HEAP
|
|
_malloc_heap_start::
|