From e003fed52a982da9bc0ae995e7694b803e3f6af5 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 3 Mar 2026 19:43:21 +0100 Subject: [PATCH] gb: hacked some gbdk-2020 header files and got hello.c to compile at least --- presets/gb/gb/crash_handler.sgb | 481 ++++++++++++++++++++++++++++ presets/gb/gb/crt0.sgb | 537 ++++++++++++++++++++++++++++++++ presets/gb/gb/global.sgb | 508 ++++++++++++++++++++++++++++++ presets/gb/gb/sfr.sgb | 111 +++++++ presets/gb/hello.c | 262 ++++++++++++++++ src/worker/platforms.ts | 9 +- 6 files changed, 1906 insertions(+), 2 deletions(-) create mode 100644 presets/gb/gb/crash_handler.sgb create mode 100644 presets/gb/gb/crt0.sgb create mode 100644 presets/gb/gb/global.sgb create mode 100644 presets/gb/gb/sfr.sgb create mode 100644 presets/gb/hello.c diff --git a/presets/gb/gb/crash_handler.sgb b/presets/gb/gb/crash_handler.sgb new file mode 100644 index 00000000..ec08a7f8 --- /dev/null +++ b/presets/gb/gb/crash_handler.sgb @@ -0,0 +1,481 @@ +; Crash handler support +; Original code by ISSOtm +; Adapted by Toxa from gb-starter-kit: https://github.com/ISSOtm/gb-starter-kit + + .include "global.s" + + .globl _font_ibm + + SCRN_X = 160 ; Width of screen in pixels + SCRN_Y = 144 ; Height of screen in pixels + SCRN_X_B = 20 ; Width of screen in bytes + SCRN_Y_B = 18 ; Height of screen in bytes + + SCRN_VX = 256 ; Virtual width of screen in pixels + SCRN_VY = 256 ; Virtual height of screen in pixels + SCRN_VX_B = 32 ; Virtual width of screen in bytes + SCRN_VY_B = 32 ; Virtual height of screen in bytes + + + .area _CRASH_HEADER(ABS) + + .org 0x00 + nop + nop + rst 0x38 + + .org 0x38 + di + jp ___HandleCrash + + + .area _HOME + +___HandleCrash:: + + ; We will use VRAM as scratch, since we are going to overwrite it for + ; screen output anyways. The thing is, we need to turn the LCD off + ; *without* affecting flags... fun task, eh? + + ; Note: it's assumed that this was jumped to with IME off. + ; Don't call this directly, use `rst Crash`. + + ld (wCrashA), a ; We need to have at least one working register, so... + ldh a, (.IE) ; We're also going to overwrite this + ld (wCrashIE), a + ldh a, (.LCDC) + ld (wCrashLCDC), a + ld a, #LCDCF_ON ; LCDCF_ON Make sure the LCD is turned on to avoid waiting infinitely + ldh (.LCDC), a + ld a, #IEF_VBLANK ; IEF_VBLANK + ld (.IE), a + ld a, #0 ; `xor a` would overwrite flags + ld (.IF), a ; No point in backing up that register, it's always changing + halt ; With interrupts disabled, this will exit when `IE & IF != 0` + nop ; Handle hardware bug if it becomes true *before* starting to execute the instruction (1-cycle window) + + ; We're now in VBlank! So we can now use VRAM as scratch for some cycles + ld a, #0 + ldh (.LCDC), a ; Turn off LCD so VRAM can always be safely accessed + ; Save regs + ld (vCrashSP), sp + ld sp, #vCrashSP + push hl + push de + push bc + ld a, (wCrashA) + push af + + ; We need to have all the data in bank 0, but we can't guarantee we were there + ldh a, (.VBK) + ld e, a + bit #0, a + jr z, .bank0 + ; Oh noes. We need to copy the data across banks! + ld hl, #vCrashAF + ld c, #(5 * 2) +.copyAcross: + ld b, (hl) + xor a + ldh (.VBK), a + ld (hl), b + inc l ; inc hl + inc a ; ld a, 1 + ldh (.VBK), a + dec c + jr nz, .copyAcross +.bank0: + xor a + ldh (.NR52), a ; Kill sound for this screen + + ldh (.VBK), a + ld a, e + ld (vCrashVBK), a ; copy vCrashVBK across banks + + ld a, #1 + ldh (.VBK), a + ld hl, #vCrashDumpScreen + ld b, #SCRN_Y_B +.writeAttrRow: + xor a + ld c, #(SCRN_X_B + 1) + rst #0x28 ; .MemsetSmall + ld a, l + add a, #(SCRN_VX_B - SCRN_X_B - 1) + ld l, a + dec b + jr nz, .writeAttrRow + xor a + ldh (.VBK), a + + ; Load palettes + ld a, #0x03 + ldh (.BGP), a + ld a, #0x80 + ldh (.BCPS), a + xor a + ld c, #.BCPD + ldh (c), a + ldh (c), a + dec a ; ld a, $FF + ldh (c), a + ldh (c), a + ldh (c), a + ldh (c), a + ldh (c), a + ldh (c), a + + ld a, #(SCRN_VY - SCRN_Y) + ldh (.SCY), a + ld a, #(SCRN_VX - SCRN_X - 4) + ldh (.SCX), a + + call loadfont + + ; Copy the registers to the dump viewers + ld hl, #vDumpHL + ld de, #vCrashHL + ld c, #4 + rst #0x30 ; .MemcpySmall + + ; We're now going to draw the screen, top to bottom + ld hl, #vCrashDumpScreen + + ; First 3 lines of text + ld de, #.header + ld b, #3 +.writeHeaderLine: + ld a, #0x20 ; " " + ld (hl+), a + ld c, #19 + rst #0x30 ; .MemcpySmall + ld a, #0x20 ; " " + ld (hl+), a + ld a, l + add a, #(SCRN_VX_B - SCRN_X_B - 1) + ld l, a + dec b + jr nz, .writeHeaderLine + + ; Blank line + ld a, #0x20 ; " " + ld c, #(SCRN_X_B + 1) + rst #0x28 ; .MemsetSmall + + ; AF and console model + ld l, #_shadow_OAM + JP .refresh_OAM + (.refresh_OAM_DMA - .start_refresh_OAM) + +.clear_WRAM: + PUSH DE + XOR A + LD BC, #l__DATA + LD HL, #s__DATA + CALL .memset_simple + + LD A, #>_shadow_OAM + LDH (__shadow_OAM_base), A + LD H, A + XOR A + LD L, A + LD C, #(40 << 2) ; 40 entries 4 bytes each + RST 0x28 + POP DE + RET + + ;; GameBoy Header + + ;; DO NOT CHANGE... + .org 0x100 +.header: + JR .code_start + + ;; Nintendo logo + .org 0x104 + .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 + + ;; soft reset: falldown to .code_start +.reset:: +_reset:: + LD A, (__is_GBA) + LD B, A + LD A, (__cpu) + + ;; Initialization code +.code_start:: + DI ; Disable interrupts + LD D, A ; Store CPU type in D + LD E, B ; Store GBA flag in E + ;; Initialize the stack + LD SP, #.STACK + + CALL .clear_WRAM + +; LD (.mode),A ; Clearing (.mode) is performed when clearing RAM + + ;; Store CPU type + LD A, D + LD (__cpu), A + CP #.CGB_TYPE + JR NZ, 1$ + XOR A + SRL E + RLA + LD (__is_GBA), A +1$: + ;; Turn the screen off + CALL .display_off + + XOR A + ;; Initialize the display + LDH (.SCY),A + LDH (.SCX),A + LDH (.STAT),A + LDH (.WY),A + LD A,#0x07 + LDH (.WX),A + + ;; Copy refresh_OAM routine to HRAM + LD DE,#.start_refresh_OAM ; source + LD HL,#.refresh_OAM ; dest + LD C,#(.end_refresh_OAM - .start_refresh_OAM) ; size + RST 0x30 ; call .MemcpySmall + + ;; Clear the OAM by calling refresh_OAM + CALL .refresh_OAM + + ;; Install interrupt routines + LD BC,#.std_vbl + CALL .add_VBL + + ;; 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,#(LCDCF_ON | LCDCF_WIN9C00 | LCDCF_WINOFF | LCDCF_BG8800 | LCDCF_BG9800 | LCDCF_OBJ8 | LCDCF_OBJOFF | LCDCF_BGOFF) + LDH (.LCDC),A + XOR A + LDH (.IF),A + LD A,#.VBL_IFLAG ; switch on VBlank interrupt only + LDH (.IE),A + + LDH (__current_bank),A ; current bank is 1 at startup + + XOR A + + LD HL,#.sys_time + LD (HL+),A + LD (HL),A + + LDH (.NR52),A ; Turn sound off + + CALL gsinit + + EI ; Enable interrupts + + ;; Call the main function + CALL _main +_exit:: +99$: + HALT + NOP + JR 99$ ; Wait forever + +_set_interrupts:: + DI + LDA HL,2(SP) ; Skip return address + XOR A + LDH (.IF),A ; Clear pending interrupts + LD A,(HL) + EI ; Enable interrupts + LDH (.IE),A ; interrupts are still disabled here + RET + + ;; Copy OAM data to OAM RAM +.start_refresh_OAM: + LDH A,(__shadow_OAM_base) + OR A + RET Z +.refresh_OAM_DMA: + 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: + + .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 + ;; #pragma bank 0 workaround + .area _CODE_0 + ;; Constant data + .area _LIT +; ;; since _CODE_1 area base address is pre-defined in the linker from 0x4000, +; ;; that moves initializer code and tables out of bank 0 +; .area _CODE_1 + ;; Constant data, used to init _DATA + .area _INITIALIZER + ;; Code, used to init _DATA + .area _GSINIT + .area _GSFINAL + ;; Uninitialised ram data + .area _DATA + .area _BSS + ;; Initialised in ram data + .area _INITIALIZED + ;; For malloc + .area _HEAP + + .area _DATA +.start_crt_globals: + +__cpu:: + .ds 0x01 ; GB type (GB, PGB, CGB) +__is_GBA:: + .ds 0x01 ; detect GBA +.mode:: + .ds 0x01 ; Current mode +.sys_time:: +_sys_time:: + .ds 0x02 ; System time in VBL units +.int_0x40:: + .blkw 0x0A ; 4 interrupt handlers (built-in + user-defined) + +.end_crt_globals: + + .area _HRAM (ABS) + + .org 0xFF90 +__current_bank:: ; Current bank + .ds 0x01 +.vbl_done: + .ds 0x01 ; Is VBL interrupt finished? +__shadow_OAM_base:: + .ds 0x01 + + ;; Runtime library + .area _GSINIT +gsinit:: + ;; initialize static storage variables + LD BC, #l__INITIALIZER + LD HL, #s__INITIALIZER + LD DE, #s__INITIALIZED + call .memcpy_simple + + .area _GSFINAL + ret + + .area _HOME + + ;; fills memory at HL of length BC with A, clobbers DE +.memset_simple:: + LD E, A + LD A, B + OR C + RET Z + LD (HL), E + DEC BC + LD D, H + LD E, L + INC DE + + ;; copies BC bytes from HL into DE +.memcpy_simple:: + LD A, B + OR C + RET Z + + SRL B + RR C + JR NC,3$ + LD A, (HL+) + LD (DE), A + INC DE +3$: + INC B + INC C + JR 2$ +1$: + LD A, (HL+) + LD (DE), A + INC DE + LD A, (HL+) + LD (DE), A + INC DE +2$: + DEC C + JR NZ,1$ + DEC B + JR NZ,1$ +4$: + RET + + ;; Remove interrupt routine in BC from the VBL interrupt list + ;; falldown to .remove_int +.remove_VBL:: + LD HL,#.int_0x40 + + ;; Remove interrupt BC from interrupt list HL if it exists + ;; Abort if a 0000 is found (end of list) +.remove_int:: +1$: + LD A,(HL+) + LD E,A + LD D,(HL) + INC HL + OR D + RET Z ; No interrupt found + + LD A,E + CP C + JR NZ,1$ + LD A,D + CP B + JR NZ,1$ + + LD D,H + LD E,L + DEC DE + DEC DE + + ;; Now do a memcpy from here until the end of the list +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 VBL interrupt list + ;; falldown to .add_int +.add_VBL:: + LD HL,#.int_0x40 + + ;; 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 A,B + LD (HL-),A + LD (HL),C + RET + + ;; Wait for VBL interrupt to be finished +.wait_vbl_done:: +_wait_vbl_done:: + ;; Check if the screen is on + LDH A,(.LCDC) + AND #LCDCF_ON + RET Z ; Return if screen is off + XOR A + LDH (.vbl_done),A ; Clear any previous sets of vbl_done +1$: + HALT ; Wait for any interrupt + NOP ; HALT sometimes skips the next instruction + LDH 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! + RET + +.display_off:: +_display_off:: + ;; Check if the screen is on + LDH A,(.LCDC) + AND #LCDCF_ON + RET Z ; 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 #~LCDCF_ON + LDH (.LCDC),A ; Turn off screen + RET + +_remove_VBL:: + PUSH BC + LDA HL,4(SP) ; Skip return address and registers + LD A,(HL+) + LD C,A + LD B,(HL) + CALL .remove_VBL + POP BC + RET + +_add_VBL:: + PUSH BC + LDA HL, 4(SP) ; Skip return address and registers + LD A,(HL+) + LD C,A + LD B,(HL) + CALL .add_VBL + POP BC + RET diff --git a/presets/gb/gb/global.sgb b/presets/gb/gb/global.sgb new file mode 100644 index 00000000..573817cf --- /dev/null +++ b/presets/gb/gb/global.sgb @@ -0,0 +1,508 @@ + .NEAR_CALLS = 1 ; - tag so that sed can change this + + ;; Changed by astorgb.pl to 1 + __RGBDS__ = 0 + + _VRAM = 0x8000 ; $8000->$9FFF + _VRAM8000 = 0x8000 + _VRAM8800 = 0x8800 + _VRAM9000 = 0x9000 + _SCRN0 = 0x9800 ; $9800->$9BFF + _SCRN1 = 0x9C00 ; $9C00->$9FFF + _SRAM = 0xA000 ; $A000->$BFFF + _RAM = 0xC000 ; $C000->$CFFF / $C000->$DFFF + _RAMBANK = 0xD000 ; $D000->$DFFF + _OAMRAM = 0xFE00 ; $FE00->$FE9F + _IO = 0xFF00 ; $FF00->$FF7F,$FFFF + _AUD3WAVERAM = 0xFF30 ; $FF30->$FF3F + _HRAM = 0xFF80 ; $FF80->$FFFE + + ;; MBC Equates + + .MBC1_ROM_PAGE = 0x2000 ; Address to write to for MBC1 switching + .MBC_ROM_PAGE = 0x2000 ; Default platform MBC rom switching address + + rRAMG = 0x0000 ; $0000->$1fff + rROMB0 = 0x2000 ; $2000->$2fff + rROMB1 = 0x3000 ; $3000->$3fff - If more than 256 ROM banks are present. + rRAMB = 0x4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + ;; Keypad + .UP = 0x04 + .DOWN = 0x08 + .LEFT = 0x02 + .RIGHT = 0x01 + .A = 0x10 + .B = 0x20 + .SELECT = 0x40 + .START = 0x80 + + .P14 = 0x10 + .P15 = 0x20 + + ;; Screen dimensions + .MAXCURSPOSX = 0x13 ; In tiles + .MAXCURSPOSY = 0x11 + + .SCREENWIDTH = 0xA0 + .SCREENHEIGHT = 0x90 + .MINWNDPOSX = 0x07 + .MINWNDPOSY = 0x00 + .MAXWNDPOSX = 0xA6 + .MAXWNDPOSY = 0x8F + + ;; Hardware registers + + .P1 = 0x00 ; Joystick: 1.1.P15.P14.P13.P12.P11.P10 + rP1 = 0xFF00 + + P1F_5 = 0b00100000 ; P15 out port, set to 0 to get buttons + P1F_4 = 0b00010000 ; P14 out port, set to 0 to get dpad + P1F_3 = 0b00001000 ; P13 in port + P1F_2 = 0b00000100 ; P12 in port + P1F_1 = 0b00000010 ; P11 in port + P1F_0 = 0b00000001 ; P10 in port + + P1F_GET_DPAD = 0b00100000 + P1F_GET_BTN = 0b00010000 + P1F_GET_NONE = 0b00110000 + + .SB = 0x01 ; Serial IO data buffer + rSB = 0xFF01 + + .SC = 0x02 ; Serial IO control register + rSC = 0xFF02 + + .DIV = 0x04 ; Divider register + rDIV = 0xFF04 + + .TIMA = 0x05 ; Timer counter + rTIMA = 0xFF05 + + .TMA = 0x06 ; Timer modulo + rTMA = 0xFF06 + + .TAC = 0x07 ; Timer control + rTAC = 0xFF07 + + TACF_START = 0b00000100 + TACF_STOP = 0b00000000 + TACF_4KHZ = 0b00000000 + TACF_16KHZ = 0b00000011 + TACF_65KHZ = 0b00000010 + TACF_262KHZ = 0b00000001 + + .IF = 0x0F ; Interrupt flags: 0.0.0.JST.SIO.TIM.LCD.VBL + rIF = 0xFF0F + + .NR10 = 0x10 ; Sound register + rNR10 = 0xFF10 + rAUD1SWEEP = 0xFF10 + + AUD1SWEEP_UP = 0b00000000 + AUD1SWEEP_DOWN = 0b00001000 + + .NR11 = 0x11 ; Sound register + rNR11 = 0xFF11 + rAUD1LEN = 0xFF11 + + .NR12 = 0x12 ; Sound register + rNR12 = 0xFF12 + rAUD1ENV = 0xFF12 + + .NR13 = 0x13 ; Sound register + rNR13 = 0xFF13 + rAUD1LOW = 0xFF13 + + .NR14 = 0x14 ; Sound register + rNR14 = 0xFF14 + rAUD1HIGH = 0xFF14 + + .NR21 = 0x16 ; Sound register + rNR21 = 0xFF16 + rAUD2LEN = 0xFF16 + + .NR22 = 0x17 ; Sound register + rNR22 = 0xFF17 + rAUD2ENV = 0xFF17 + + .NR23 = 0x18 ; Sound register + rNR23 = 0xFF18 + rAUD2LOW = 0xFF18 + + .NR24 = 0x19 ; Sound register + rNR24 = 0xFF19 + rAUD2HIGH = 0xFF19 + + .NR30 = 0x1A ; Sound register + rNR30 = 0xFF1A + rAUD3ENA = 0xFF1A + + .NR31 = 0x1B ; Sound register + rNR31 = 0xFF1B + rAUD3LEN = 0xFF1B + + .NR32 = 0x1C ; Sound register + rNR32 = 0xFF1C + rAUD3LEVEL = 0xFF1C + + .NR33 = 0x1D ; Sound register + rNR33 = 0xFF1D + rAUD3LOW = 0xFF1D + + .NR34 = 0x1E ; Sound register + rNR34 = 0xFF1E + rAUD3HIGH = 0xFF1E + + .NR41 = 0x20 ; Sound register + rNR41 = 0xFF20 + rAUD4LEN = 0xFF20 + + .NR42 = 0x21 ; Sound register + rNR42 = 0xFF21 + rAUD4ENV = 0xFF21 + + .NR43 = 0x22 ; Sound register + rNR43 = 0xFF22 + rAUD4POLY = 0xFF22 + + .NR44 = 0x23 ; Sound register + rNR44 = 0xFF23 + rAUD4GO = 0xFF23 + + .NR50 = 0x24 ; Sound register + rNR50 = 0xFF24 + rAUDVOL = 0xFF24 + + AUDVOL_VIN_LEFT = 0b10000000 ; SO2 + AUDVOL_VIN_RIGHT = 0b00001000 ; SO1 + + .NR51 = 0x25 ; Sound register + rNR51 = 0xFF25 + rAUDTERM = 0xFF25 + + AUDTERM_4_LEFT = 0b10000000 + AUDTERM_3_LEFT = 0b01000000 + AUDTERM_2_LEFT = 0b00100000 + AUDTERM_1_LEFT = 0b00010000 + AUDTERM_4_RIGHT = 0b00001000 + AUDTERM_3_RIGHT = 0b00000100 + AUDTERM_2_RIGHT = 0b00000010 + AUDTERM_1_RIGHT = 0b00000001 + + .NR52 = 0x26 ; Sound register + rNR52 = 0xFF26 + rAUDENA = 0xFF26 + + AUDENA_ON = 0b10000000 + AUDENA_OFF = 0b00000000 ; sets all audio regs to 0! + + .LCDC = 0x40 ; LCD control + rLCDC = 0xFF40 + + LCDCF_OFF = 0b00000000 ; LCD Control Operation + LCDCF_ON = 0b10000000 ; LCD Control Operation + LCDCF_WIN9800 = 0b00000000 ; Window Tile Map Display Select + LCDCF_WIN9C00 = 0b01000000 ; Window Tile Map Display Select + LCDCF_WINOFF = 0b00000000 ; Window Display + LCDCF_WINON = 0b00100000 ; Window Display + LCDCF_BG8800 = 0b00000000 ; BG & Window Tile Data Select + LCDCF_BG8000 = 0b00010000 ; BG & Window Tile Data Select + LCDCF_BG9800 = 0b00000000 ; BG Tile Map Display Select + LCDCF_BG9C00 = 0b00001000 ; BG Tile Map Display Select + LCDCF_OBJ8 = 0b00000000 ; OBJ Construction + LCDCF_OBJ16 = 0b00000100 ; OBJ Construction + LCDCF_OBJOFF = 0b00000000 ; OBJ Display + LCDCF_OBJON = 0b00000010 ; OBJ Display + LCDCF_BGOFF = 0b00000000 ; BG Display + LCDCF_BGON = 0b00000001 ; BG Display + LCDCF_B_ON = 7 + LCDCF_B_WIN9C00 = 6 + LCDCF_B_WINON = 5 + LCDCF_B_BG8000 = 4 + LCDCF_B_BG9C00 = 3 + LCDCF_B_OBJ16 = 2 + LCDCF_B_OBJON = 1 + LCDCF_B_BGON = 0 + + .STAT = 0x41 ; LCD status + rSTAT = 0xFF41 + + STATF_LYC = 0b01000000 ; LYC=LY Coincidence (Selectable) + STATF_MODE10 = 0b00100000 ; Mode 10 + STATF_MODE01 = 0b00010000 ; Mode 01 (V-Blank) + STATF_MODE00 = 0b00001000 ; Mode 00 (H-Blank) + STATF_LYCF = 0b00000100 ; Coincidence Flag + STATF_HBL = 0b00000000 ; H-Blank + STATF_VBL = 0b00000001 ; V-Blank + STATF_OAM = 0b00000010 ; OAM-RAM is used by system + STATF_LCD = 0b00000011 ; Both OAM and VRAM used by system + STATF_BUSY = 0b00000010 ; When set, VRAM access is unsafe + STATF_B_LYC = 6 + STATF_B_MODE10 = 5 + STATF_B_MODE01 = 4 + STATF_B_MODE00 = 3 + STATF_B_LYCF = 2 + STATF_B_VBL = 0 + STATF_B_OAM = 1 + STATF_B_BUSY = 1 + + .SCY = 0x42 ; Scroll Y + rSCY = 0xFF42 + + .SCX = 0x43 ; Scroll X + rSCX = 0xFF43 + + .LY = 0x44 ; LCDC Y-coordinate + rLY = 0xFF44 + + .LYC = 0x45 ; LY compare + rLYC = 0xFF45 + + .DMA = 0x46 ; DMA transfer + rDMA = 0xFF46 + + .BGP = 0x47 ; BG palette data + rBGP = 0xFF47 + + .OBP0 = 0x48 ; OBJ palette 0 data + rOBP0 = 0xFF48 + + .OBP1 = 0x49 ; OBJ palette 1 data + rOBP1 = 0xFF49 + + .WY = 0x4A ; Window Y coordinate + rWY = 0xFF4A + + .WX = 0x4B ; Window X coordinate + rWX = 0xFF4B + + .KEY1 = 0x4D ; CPU speed + rKEY1 = 0xFF4D + rSPD = 0xFF4D + + KEY1F_DBLSPEED = 0b10000000 ; 0=Normal Speed, 1=Double Speed (R) + KEY1F_PREPARE = 0b00000001 ; 0=No, 1=Prepare (R/W) + + .VBK = 0x4F ; VRAM bank + rVBK = 0xFF4F + + .HDMA1 = 0x51 ; DMA control 1 + rHDMA1 = 0xFF51 + + .HDMA2 = 0x52 ; DMA control 2 + rHDMA2 = 0xFF52 + + .HDMA3 = 0x53 ; DMA control 3 + rHDMA3 = 0xFF53 + + .HDMA4 = 0x54 ; DMA control 4 + rHDMA4 = 0xFF54 + + .HDMA5 = 0x55 ; DMA control 5 + rHDMA5 = 0xFF55 + + HDMA5F_MODE_GP = 0b00000000 ; General Purpose DMA (W) + HDMA5F_MODE_HBL = 0b10000000 ; HBlank DMA (W) + + HDMA5F_BUSY = 0b10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) + + .RP = 0x56 ; IR port + rRP = 0xFF56 + + RPF_ENREAD = 0b11000000 + RPF_DATAIN = 0b00000010 ; 0=Receiving IR Signal, 1=Normal + RPF_WRITE_HI = 0b00000001 + RPF_WRITE_LO = 0b00000000 + + .BCPS = 0x68 ; BG color palette specification + rBCPS = 0xFF68 + + BCPSF_AUTOINC = 0b10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + .BCPD = 0x69 ; BG color palette data + rBCPD = 0xFF69 + + .OCPS = 0x6A ; OBJ color palette specification + rOCPS = 0xFF6A + + OCPSF_AUTOINC = 0b10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) + + .OCPD = 0x6B ; OBJ color palette data + rOCPD = 0xFF6B + + .SVBK = 0x70 ; WRAM bank + rSVBK = 0xFF70 + rSMBK = 0xFF70 + + rPCM12 = 0xFF76 + + rPCM34 = 0xFF77 + + .IE = 0xFF ; Interrupt enable + rIE = 0xFFFF + + .VBL_IFLAG = 0x01 + .LCD_IFLAG = 0x02 + .TIM_IFLAG = 0x04 + .SIO_IFLAG = 0x08 + .JOY_IFLAG = 0x10 + + IEF_HILO = 0b00010000 ; Transition from High to Low of Pin number P10-P13 + IEF_SERIAL = 0b00001000 ; Serial I/O transfer end + IEF_TIMER = 0b00000100 ; Timer Overflow + IEF_STAT = 0b00000010 ; STAT + IEF_VBLANK = 0b00000001 ; V-Blank + + ;; Flags common to multiple sound channels + + AUDLEN_DUTY_12_5 = 0b00000000 ; 12.5% + AUDLEN_DUTY_25 = 0b01000000 ; 25% + AUDLEN_DUTY_50 = 0b10000000 ; 50% + AUDLEN_DUTY_75 = 0b11000000 ; 75% + + AUDENV_UP = 0b00001000 + AUDENV_DOWN = 0b00000000 + + AUDHIGH_RESTART = 0b10000000 + AUDHIGH_LENGTH_ON = 0b01000000 + AUDHIGH_LENGTH_OFF = 0b00000000 + + ;; OAM related constants + + OAM_COUNT = 40 ; number of OAM entries in OAM RAM + + OAMF_PRI = 0b10000000 ; Priority + OAMF_YFLIP = 0b01000000 ; Y flip + OAMF_XFLIP = 0b00100000 ; X flip + OAMF_PAL0 = 0b00000000 ; Palette number; 0,1 (DMG) + OAMF_PAL1 = 0b00010000 ; Palette number; 0,1 (DMG) + OAMF_BANK0 = 0b00000000 ; Bank number; 0,1 (GBC) + OAMF_BANK1 = 0b00001000 ; Bank number; 0,1 (GBC) + + OAMF_PALMASK = 0b00000111 ; Palette (GBC) + + OAMB_PRI = 7 ; Priority + OAMB_YFLIP = 6 ; Y flip + OAMB_XFLIP = 5 ; X flip + OAMB_PAL1 = 4 ; Palette number; 0,1 (DMG) + OAMB_BANK1 = 3 ; Bank number; 0,1 (GBC) + + ;; CPU detection + .DMG_TYPE = 0x01 ; Original GB or Super GB + .MGB_TYPE = 0xFF ; Pocket GB or Super GB 2 + .CGB_TYPE = 0x11 ; Color GB + + ;; GBDK library screen modes + + .G_MODE = 0x01 ; Graphic mode + .T_MODE = 0x02 ; Text mode (bit 2) + .T_MODE_OUT = 0x02 ; Text mode output only + .T_MODE_INOUT = 0x03 ; Text mode with input + .M_NO_SCROLL = 0x04 ; Disables scrolling of the screen in text mode + .M_NO_INTERP = 0x08 ; Disables special character interpretation + + ;; Status codes for IO + .IO_IDLE = 0x00 + .IO_SENDING = 0x01 + .IO_RECEIVING = 0x02 + .IO_ERROR = 0x04 + + ;; Type of IO data + .DT_IDLE = 0x66 + .DT_RECEIVING = 0x55 + + ;; Table of routines for modes + .MODE_TABLE = 0x01E0 + + ;; C related + ;; Overheap of a banked call. Used for parameters + ;; = ret + real ret + bank + + .if .NEAR_CALLS + .BANKOV = 2 + + .else + .BANKOV = 6 + + .endif + + .globl __current_bank + .globl __shadow_OAM_base + + ;; Global variables + .globl .mode + + .globl __cpu + .globl __is_GBA + + ;; Global routines +; .globl .set_mode ;; don't link mode.o by default + + .globl .reset + + .globl .display_off + + .globl .wait_vbl_done + + ;; Interrupt routines + .globl .add_VBL +; .globl .add_LCD ;; don't link LCD.o by default +; .globl .add_TIM ;; don't link TIM.o by default +; .globl .add_SIO ;; don't link serial.o by default +; .globl .add_JOY ;; don't link JOY.o by default + + ;; Symbols defined at link time + .globl .STACK + .globl _shadow_OAM + .globl .refresh_OAM + + ;; Main user routine + .globl _main + + ;; Macro definitions + +.macro WAIT_STAT ?lbl +lbl: LDH A, (.STAT) + AND #STATF_BUSY ; Check if in LCD modes 0 or 1 + JR NZ, lbl +.endm + +.macro ADD_A_REG16 regH regL + ADD regL + LD regL, A + ADC regH + SUB regL + LD regH, A +.endm + +.macro SIGNED_ADD_A_REG16 regH regL ?lbl + ; If A is negative, we need to subtract 1 from upper byte of 16-bit value + BIT 7, A ; set z if a signed bit is 0 + JR Z, lbl ; if z is set jump to positive + dec regH ; if negative decrement upper byte +lbl: + ADD_A_REG16 regH, regL +.endm + +.macro SIGNED_SUB_A_REG16 regH regL ?lbl + ; negate A then add to 16-bit value + CPL + INC A + SIGNED_ADD_A_REG16 regH, regL +.endm + +.macro MUL_DE_BY_A_RET_HL ?lbl1 ?lbl2 + ; Multiply DE by A, return result in HL; preserves: BC + LD HL, #0 +lbl1: + SRL A + JR NC, lbl2 + ADD HL, DE +lbl2: + SLA E + RL D + OR A + JR NZ, lbl1 +.endm + diff --git a/presets/gb/gb/sfr.sgb b/presets/gb/gb/sfr.sgb new file mode 100644 index 00000000..64febc7a --- /dev/null +++ b/presets/gb/gb/sfr.sgb @@ -0,0 +1,111 @@ +__VRAM = 0x8000 +__VRAM8000 = 0x8000 +__VRAM8800 = 0x8800 +__VRAM9000 = 0x9000 +__SCRN0 = 0x9800 +__SCRN1 = 0x9C00 +__SRAM = 0xA000 +__RAM = 0xC000 +__RAMBANK = 0xD000 +__OAMRAM = 0xFE00 +__IO = 0xFF00 +__AUD3WAVERAM = 0xFF30 +__HRAM = 0xFF80 + +.globl __VRAM, __VRAM8000, __VRAM8800, __VRAM9000, __SCRN0, __SCRN1, __SRAM, __RAM, __RAMBANK, __OAMRAM, __IO, __AUD3WAVERAM, __HRAM + +_rRAMG = 0x0000 +_rROMB0 = 0x2000 +_rROMB1 = 0x3000 +_rRAMB = 0x4000 + +.globl _rRAMG, _rROMB0, _rROMB1, _rRAMB + +_P1_REG = 0xFF00 ; Joystick: 1.1.P15.P14.P13.P12.P11.P10 +_SB_REG = 0xFF01 ; Serial IO data buffer +_SC_REG = 0xFF02 ; Serial IO control register +_DIV_REG = 0xFF04 ; Divider register +_TIMA_REG = 0xFF05 ; Timer counter +_TMA_REG = 0xFF06 ; Timer modulo +_TAC_REG = 0xFF07 ; Timer control +_IF_REG = 0xFF0F ; Interrupt flags: 0.0.0.JOY.SIO.TIM.LCD.VBL +_NR10_REG = 0xFF10 ; Sound register +_NR11_REG = 0xFF11 ; Sound register +_NR12_REG = 0xFF12 ; Sound register +_NR13_REG = 0xFF13 ; Sound register +_NR14_REG = 0xFF14 ; Sound register +_NR21_REG = 0xFF16 ; Sound register +_NR22_REG = 0xFF17 ; Sound register +_NR23_REG = 0xFF18 ; Sound register +_NR24_REG = 0xFF19 ; Sound register +_NR30_REG = 0xFF1A ; Sound register +_NR31_REG = 0xFF1B ; Sound register +_NR32_REG = 0xFF1C ; Sound register +_NR33_REG = 0xFF1D ; Sound register +_NR34_REG = 0xFF1E ; Sound register +_NR41_REG = 0xFF20 ; Sound register +_NR42_REG = 0xFF21 ; Sound register +_NR43_REG = 0xFF22 ; Sound register +_NR44_REG = 0xFF23 ; Sound register +_NR50_REG = 0xFF24 ; Sound register +_NR51_REG = 0xFF25 ; Sound register +_NR52_REG = 0xFF26 ; Sound register +_PCM_SAMPLE = 0xFF30 ; PCM wave pattern +_AUD3WAVE = 0xFF30 ; PCM wave pattern +_LCDC_REG = 0xFF40 ; LCD control +_STAT_REG = 0xFF41 ; LCD status +_SCY_REG = 0xFF42 ; Scroll Y +_SCX_REG = 0xFF43 ; Scroll X +_LY_REG = 0xFF44 ; LCDC Y-coordinate +_LYC_REG = 0xFF45 ; LY compare +_DMA_REG = 0xFF46 ; DMA transfer +_BGP_REG = 0xFF47 ; BG palette data +_OBP0_REG = 0xFF48 ; OBJ palette 0 data +_OBP1_REG = 0xFF49 ; OBJ palette 1 data +_WY_REG = 0xFF4A ; Window Y coordinate +_WX_REG = 0xFF4B ; Window X coordinate +_KEY1_REG = 0xFF4D ; CPU speed +_VBK_REG = 0xFF4F ; VRAM bank +_HDMA1_REG = 0xFF51 ; DMA control 1 +_HDMA2_REG = 0xFF52 ; DMA control 2 +_HDMA3_REG = 0xFF53 ; DMA control 3 +_HDMA4_REG = 0xFF54 ; DMA control 4 +_HDMA5_REG = 0xFF55 ; DMA control 5 +_RP_REG = 0xFF56 ; IR port +_BCPS_REG = 0xFF68 ; BG color palette specification +_BCPD_REG = 0xFF69 ; BG color palette data +_OCPS_REG = 0xFF6A ; OBJ color palette specification +_OCPD_REG = 0xFF6B ; OBJ color palette data +_SVBK_REG = 0xFF70 ; WRAM bank +_PCM12_REG = 0xFF76 ; Sound channel 1&2 PCM amplitude (R) +_PCM34_REG = 0xFF77 ; Sound channel 3&4 PCM amplitude (R) +_IE_REG = 0xFFFF ; Interrupt enable + +.globl _P1_REG +.globl _SB_REG, _SC_REG +.globl _DIV_REG +.globl _TIMA_REG, _TMA_REG, _TAC_REG +.globl _IF_REG +.globl _NR10_REG, _NR11_REG, _NR12_REG, _NR13_REG, _NR14_REG +.globl _NR21_REG, _NR22_REG, _NR23_REG, _NR24_REG +.globl _NR30_REG, _NR31_REG, _NR32_REG, _NR33_REG, _NR34_REG +.globl _NR41_REG, _NR42_REG, _NR43_REG, _NR44_REG +.globl _NR50_REG, _NR51_REG, _NR52_REG +.globl _PCM_SAMPLE, _AUD3WAVE +.globl _LCDC_REG +.globl _STAT_REG +.globl _SCY_REG, _SCX_REG +.globl _LY_REG, _LYC_REG +.globl _DMA_REG +.globl _BGP_REG +.globl _OBP0_REG, _OBP1_REG +.globl _WY_REG, _WX_REG +.globl _KEY1_REG +.globl _VBK_REG +.globl _HDMA1_REG, _HDMA2_REG, _HDMA3_REG, _HDMA4_REG, _HDMA5_REG +.globl _RP_REG +.globl _BCPS_REG, _BCPD_REG +.globl _OCPS_REG, _OCPD_REG +.globl _SVBK_REG +.globl _PCM12_REG, _PCM34_REG +.globl _IE_REG \ No newline at end of file diff --git a/presets/gb/hello.c b/presets/gb/hello.c new file mode 100644 index 00000000..e6ca245c --- /dev/null +++ b/presets/gb/hello.c @@ -0,0 +1,262 @@ + +//#resource "gb/global.sgb" +//#link "gb/crt0.sgb" +//#link "gb/sfr.sgb" +#include +#include "gb/types.h" +#include "gb/hardware.h" +#include "gb/gb.h" + +/* VRAM regions */ +#define TILE_DATA ((volatile uint8_t *)0x8000) /* 384 tiles × 16 bytes */ +#define BG_MAP ((volatile uint8_t *)0x9800) /* 32×32 tile map */ + +/* --------------------------------------------------------------------------- + * wait_vblank + * Spin until LY reaches 144 (start of V-Blank period). + * ------------------------------------------------------------------------- */ +static void wait_vblank(void) +{ + while (rLY != 144); +} + +/* --------------------------------------------------------------------------- + * Minimal 8×8 ASCII font (space 0x20 … 'Z' 0x5A) + * Each character is 8 bytes, one byte per row, MSB = leftmost pixel. + * Only printable upper-case letters, digits, space, and basic punctuation + * are included. + * ------------------------------------------------------------------------- */ +static const uint8_t font[][8] = { + /* 0x20 ' ' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x21 '!' */ {0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x00}, + /* 0x22 '"' */ {0x6C,0x6C,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x23 '#' */ {0x6C,0x6C,0xFE,0x6C,0xFE,0x6C,0x6C,0x00}, + /* 0x24 '$' */ {0x18,0x7E,0xC0,0x7C,0x06,0xFC,0x18,0x00}, + /* 0x25 '%' */ {0xC6,0xCC,0x18,0x30,0x66,0xC6,0x00,0x00}, + /* 0x26 '&' */ {0x38,0x6C,0x68,0x76,0xDC,0xCC,0x76,0x00}, + /* 0x27 ''' */ {0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00}, + /* 0x28 '(' */ {0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00}, + /* 0x29 ')' */ {0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00}, + /* 0x2A '*' */ {0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00}, + /* 0x2B '+' */ {0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00}, + /* 0x2C ',' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30}, + /* 0x2D '-' */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00}, + /* 0x2E '.' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, + /* 0x2F '/' */ {0x06,0x0C,0x18,0x30,0x60,0xC0,0x00,0x00}, + /* 0x30 '0' */ {0x7C,0xC6,0xCE,0xD6,0xE6,0xC6,0x7C,0x00}, + /* 0x31 '1' */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, + /* 0x32 '2' */ {0x7C,0xC6,0x06,0x1C,0x70,0xC6,0xFE,0x00}, + /* 0x33 '3' */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00}, + /* 0x34 '4' */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x1E,0x00}, + /* 0x35 '5' */ {0xFE,0xC0,0xF8,0x0C,0x06,0xCC,0x78,0x00}, + /* 0x36 '6' */ {0x38,0x60,0xC0,0xF8,0xCC,0xCC,0x78,0x00}, + /* 0x37 '7' */ {0xFE,0xC6,0x0C,0x18,0x30,0x30,0x30,0x00}, + /* 0x38 '8' */ {0x78,0xCC,0xCC,0x78,0xCC,0xCC,0x78,0x00}, + /* 0x39 '9' */ {0x78,0xCC,0xCC,0x7C,0x0C,0x18,0x70,0x00}, + /* 0x3A ':' */ {0x00,0x18,0x18,0x00,0x18,0x18,0x00,0x00}, + /* 0x3B ';' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x3C '<' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x3D '=' */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00}, + /* 0x3E '>' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x3F '?' */ {0x7C,0xC6,0x06,0x1C,0x18,0x00,0x18,0x00}, + /* 0x40 '@' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + /* 0x41 'A' */ {0x10,0x38,0x6C,0xC6,0xFE,0xC6,0xC6,0x00}, + /* 0x42 'B' */ {0xFC,0xC6,0xC6,0xFC,0xC6,0xC6,0xFC,0x00}, + /* 0x43 'C' */ {0x78,0xCC,0xC0,0xC0,0xC0,0xCC,0x78,0x00}, + /* 0x44 'D' */ {0xF8,0xCC,0xC6,0xC6,0xC6,0xCC,0xF8,0x00}, + /* 0x45 'E' */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00}, + /* 0x46 'F' */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xC0,0x00}, + /* 0x47 'G' */ {0x78,0xCC,0xC0,0xDE,0xCC,0xCC,0x7E,0x00}, + /* 0x48 'H' */ {0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00}, + /* 0x49 'I' */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x7E,0x00}, + /* 0x4A 'J' */ {0x1E,0x06,0x06,0x06,0xC6,0xC6,0x7C,0x00}, + /* 0x4B 'K' */ {0xC6,0xCC,0xD8,0xF0,0xD8,0xCC,0xC6,0x00}, + /* 0x4C 'L' */ {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFE,0x00}, + /* 0x4D 'M' */ {0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0x00}, + /* 0x4E 'N' */ {0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00}, + /* 0x4F 'O' */ {0x78,0xCC,0xCC,0xCC,0xCC,0xCC,0x78,0x00}, + /* 0x50 'P' */ {0xFC,0xC6,0xC6,0xFC,0xC0,0xC0,0xC0,0x00}, + /* 0x51 'Q' */ {0x78,0xCC,0xCC,0xCC,0xEC,0x78,0x1C,0x00}, + /* 0x52 'R' */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00}, + /* 0x53 'S' */ {0x7C,0xC6,0xC0,0x7C,0x06,0xC6,0x7C,0x00}, + /* 0x54 'T' */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, + /* 0x55 'U' */ {0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00}, + /* 0x56 'V' */ {0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00}, + /* 0x57 'W' */ {0xC6,0xC6,0xD6,0xFE,0xFE,0xEE,0xC6,0x00}, + /* 0x58 'X' */ {0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00}, + /* 0x59 'Y' */ {0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00}, + /* 0x5A 'Z' */ {0xFE,0x06,0x0C,0x18,0x30,0x60,0xFE,0x00}, +}; +#define FONT_FIRST 0x20 +#define FONT_LAST 0x5A +#define FONT_COUNT (FONT_LAST - FONT_FIRST + 1) + +/* --------------------------------------------------------------------------- + * load_font + * Write font glyphs into VRAM tile data. + * The Game Boy tile format stores two bits per pixel across two bitplanes; + * for a 2-colour font we set both bitplanes to the same byte so colour + * index 3 (black) is used for set pixels and 0 (white) for clear pixels. + * Tile 0 is reserved as blank; glyphs start at tile 1. + * ------------------------------------------------------------------------- */ +static void load_font(void) +{ + uint8_t t, r; + /* Tile 0: blank (already zeroed by boot ROM, but be explicit) */ + for (r = 0; r < 16; r++) { + TILE_DATA[r] = 0x00; + } + /* Tiles 1 … FONT_COUNT: one tile per glyph */ + for (t = 0; t < FONT_COUNT; t++) { + volatile uint8_t *dst = TILE_DATA + (t + 1) * 16; + for (r = 0; r < 8; r++) { + uint8_t row = font[t][r]; + dst[r * 2] = row; /* bitplane 0 */ + dst[r * 2 + 1] = row; /* bitplane 1 — same → colour index 3 */ + } + } +} + +/* --------------------------------------------------------------------------- + * put_char / put_str + * Write a tile index into the BG map at tile position (col, row). + * Tile index = (ascii - FONT_FIRST) + 1, clamped to blank for unknowns. + * ------------------------------------------------------------------------- */ +static void put_char(uint8_t col, uint8_t row, char c) +{ + uint8_t tile; + if (c >= FONT_FIRST && c <= FONT_LAST) + tile = (uint8_t)(c - FONT_FIRST) + 1; + else + tile = 0; /* blank */ + BG_MAP[row * 32u + col] = tile; +} + +static void put_str(uint8_t col, uint8_t row, const char *s) +{ + while (*s) { + put_char(col++, row, *s++); + } +} + +/* --------------------------------------------------------------------------- + * clear_map + * Fill the visible 20×18 region of the BG map with tile 0 (blank). + * ------------------------------------------------------------------------- */ +static void clear_map(void) +{ + uint8_t r, c; + for (r = 0; r < 18; r++) + for (c = 0; c < 20; c++) + BG_MAP[r * 32u + c] = 0; +} + +/* --------------------------------------------------------------------------- + * read_joypad + * Returns the buttons currently held. + * Bits: 7=start 6=select 5=B 4=A (action keys, P14 low) + * 3=down 2=up 1=left 0=right (d-pad, P15 low) + * We return a simple mask where bit 7 = START, bit 0 = RIGHT. + * ------------------------------------------------------------------------- */ +#define BTN_START (1 << 7) +#define BTN_SELECT (1 << 6) +#define BTN_B (1 << 5) +#define BTN_A (1 << 4) +#define BTN_DOWN (1 << 3) +#define BTN_UP (1 << 2) +#define BTN_LEFT (1 << 1) +#define BTN_RIGHT (1 << 0) + +static uint8_t read_joypad(void) +{ + uint8_t result = 0; + + /* Read action buttons (P14 = 0) */ + P1_REG = 0x20; + P1_REG; P1_REG; /* dummy reads for settling */ + result |= ((~P1_REG) & 0x0F) << 4; + + /* Read d-pad (P15 = 0) */ + P1_REG = 0x10; + P1_REG; P1_REG; + result |= ((~P1_REG) & 0x0F); + + P1_REG = 0x30; /* deselect both */ + return result; +} + +/* --------------------------------------------------------------------------- + * main + * ------------------------------------------------------------------------- */ +void main(void) +{ + uint8_t prev_joy = 0; + uint8_t curr_joy = 0; + uint8_t screen = 0; + + /* --- Disable LCD so we can safely write to VRAM --- */ + wait_vblank(); + LCDC_REG = 0x00; + + /* --- Load font, clear map, set palette --- */ + load_font(); + clear_map(); + + /* + * Palette: 0=white, 1=light grey, 2=dark grey, 3=black + * BGP format: bits[1:0]=colour0, bits[3:2]=colour1, etc. + * 0xE4 = 11 10 01 00 → shade 0→0,1→1,2→2,3→3 (standard) + */ + BGP_REG = 0xE4; + SCX_REG = 0; + SCY_REG = 0; + + /* --- Draw initial screen --- */ + put_str(3, 1, "GAME BOY DEMO"); + put_str(2, 3, "HELLO, WORLD!"); + put_str(1, 5, "BUILT WITH SDCC"); + put_str(0, 16, "PRESS START"); + + /* + * Re-enable LCD: + * Bit 7: LCD on + * Bit 4: tile data at 0x8000 (unsigned indexing) + * Bit 0: BG enable + */ + LCDC_REG = LCDCF_ON | LCDCF_WIN9800 | LCDCF_BG8000 | LCDCF_B_BGON; + + /* --- Main loop --- */ + for (;;) { + wait_vblank(); + + curr_joy = read_joypad(); + + /* Edge detect START */ + if ((curr_joy & BTN_START) && !(prev_joy & BTN_START)) { + if (screen == 0) { + /* Switch to second screen */ + wait_vblank(); + LCDC_REG = 0x00; + clear_map(); + put_str(1, 4, "YOU PRESSED START!"); + put_str(3, 6, "HAVE FUN :)"); + put_str(0, 16, "PRESS START AGAIN"); + LCDC_REG = LCDCF_ON | LCDCF_B_BGON; + screen = 1; + } else { + /* Back to title */ + wait_vblank(); + LCDC_REG = 0x00; + clear_map(); + put_str(3, 1, "GAME BOY DEMO"); + put_str(2, 3, "HELLO, WORLD!"); + put_str(1, 5, "BUILT WITH SDCC"); + put_str(0, 16, "PRESS START"); + LCDC_REG = LCDCF_ON | LCDCF_B_BGON; + screen = 0; + } + } + + prev_joy = curr_joy; + } +} diff --git a/src/worker/platforms.ts b/src/worker/platforms.ts index a1591d71..60b69826 100644 --- a/src/worker/platforms.ts +++ b/src/worker/platforms.ts @@ -366,9 +366,14 @@ export var PLATFORM_PARAMS = { arch: 'gbz80', code_start: 0x0, rom_size: 0x8000, - data_start: 0xc000, - data_size: 0x2000, + data_start: 0xc0a0, + data_size: 0x1f60, stack_end: 0xe000, + extra_link_args: [ + '-g', '_shadow_OAM=0xC000', + '-g', '.STACK=0xE000', + '-g', '.refresh_OAM=0xFF80', + ], wiz_sys_type: 'gb', wiz_inc_dir: 'gb', },