From ad3623c9d2a794f6c857576543553a35cbcbaeb4 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Fri, 19 Oct 2018 00:45:50 +0200 Subject: [PATCH] Add files --- .vscode/settings.json | 7 + .vscode/tasks.json | 24 ++ Makefile | 101 +++++ Makefile.conf | 57 +++ src/constants/constants.asm | 4 + src/constants/enums.asm | 68 ++++ src/constants/hardware.inc | 755 +++++++++++++++++++++++++++++++++++ src/constants/misc.asm | 23 ++ src/engine.asm | 6 + src/engine/error_handler.asm | 746 ++++++++++++++++++++++++++++++++++ src/engine/init.asm | 8 + src/home.asm | 11 + src/home/header.asm | 111 +++++ src/home/raster_fx.asm | 221 ++++++++++ src/home/sgb.asm | 130 ++++++ src/home/unpb16.asm | 89 +++++ src/home/utils.asm | 192 +++++++++ src/home/vectors.asm | 307 ++++++++++++++ src/macros/macros.asm | 5 + src/macros/misc.asm | 127 ++++++ src/macros/pseudo_instr.asm | 16 + src/macros/struct_decls.asm | 0 src/macros/structs.asm | 156 ++++++++ src/memory.asm | 7 + src/memory/hram.asm | 85 ++++ src/memory/sram.asm | 0 src/memory/vram.asm | 20 + src/memory/wram.asm | 40 ++ src/res/build_date.asm | 7 + src/tools/pb16.py | 76 ++++ 30 files changed, 3399 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Makefile create mode 100644 Makefile.conf create mode 100644 src/constants/constants.asm create mode 100644 src/constants/enums.asm create mode 100644 src/constants/hardware.inc create mode 100644 src/constants/misc.asm create mode 100644 src/engine.asm create mode 100644 src/engine/error_handler.asm create mode 100644 src/engine/init.asm create mode 100644 src/home.asm create mode 100644 src/home/header.asm create mode 100644 src/home/raster_fx.asm create mode 100644 src/home/sgb.asm create mode 100644 src/home/unpb16.asm create mode 100644 src/home/utils.asm create mode 100644 src/home/vectors.asm create mode 100644 src/macros/macros.asm create mode 100644 src/macros/misc.asm create mode 100644 src/macros/pseudo_instr.asm create mode 100644 src/macros/struct_decls.asm create mode 100644 src/macros/structs.asm create mode 100644 src/memory.asm create mode 100644 src/memory/hram.asm create mode 100644 src/memory/sram.asm create mode 100644 src/memory/vram.asm create mode 100644 src/memory/wram.asm create mode 100644 src/res/build_date.asm create mode 100644 src/tools/pb16.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..acda1e6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "rgbdsz80.includePath": [ + "src/", + "src/constants", + "src/macros" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..a5815bf --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "command": "make", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$rgbdserror" + }, + { + "label": "Clean", + "type": "shell", + "command": "make clean", + "group": "none", + "problemMatcher": "$rgbdserror" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a91c52 --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ + +.SUFFIXES: +.DEFAULTTARGET: all + + +include Makefile.conf + + +# Directory constants +SRCDIR = src +BINDIR = bin +OBJDIR = obj +DEPSDIR = deps + +ROMFILE = $(BINDIR)/$(ROMName).$(ROMExt) + +# Program constants +RGBASM = rgbasm +RGBLINK = rgblink +RGBFIX = rgbfix + +# Argument constants +ASFLAGS += -E -h -i $(SRCDIR)/ -i $(SRCDIR)/constants/ -i $(SRCDIR)/macros/ -p $(FillValue) +LDFLAGS += -p $(FillValue) +FXFLAGS += -jv -i $(GameID) -k $(NewLicensee) -l $(OldLicensee) -m $(MBCType) -n $(ROMVersion) -p $(FillValue) -r $(SRAMSize) -t $(GameTitle) + +# The list of "root" ASM files that RGBASM will be invoked on +ASMFILES := $(wildcard $(SRCDIR)/*.asm) + + + +# `all` (Default target): build the ROM +.PHONY: all +all: $(ROMFILE) + +# `clean`: Clean temp and bin files +.PHONY: clean +CLEANTARGETS := $(BINDIR) $(DEPSDIR) $(OBJDIR) $(SRCDIR)/res/build.date dummy # The list of things that must be cleared; expanded by the resource Makefiles +clean: + -rm -rf $(CLEANTARGETS) + +# `rebuild`: Build everything from scratch +.PHONY: rebuild +rebuild: + $(MAKE) clean + $(MAKE) all + + +# Define how to compress files (same recipe for any file) +%.pb16: % + src/tools/pb16.py $< $@ + +# Include all resource Makefiles +include $(wildcard $(SRCDIR)/res/*/Makefile) + + +# `dummy` is a dummy target to build the resource files necessary for RGBASM to not fail on compilation +# It's made an actual file to avoid an infinite compilation loop +# INITTARGETS is defined by the resource Makefiles +dummy: $(INITTARGETS) + @echo "THIS FILE ENSURES THAT COMPILATION GOES RIGHT THE FIRST TIME, DO NOT DELETE" > $@ + +# `.d` files are generated as dependency lists of the "root" ASM files, to save a lot of hassle. +# > Deps files also depend on `dummy` to ensure all the binary files are present, so RGBASM doesn't choke on them not being present; +# > This would cause the first compilation to never finish, thus Make never knows to build the binary files, thus deadlocking everything. +$(DEPSDIR)/%.d: $(SRCDIR)/%.asm dummy + @echo Building deps file $@ + @mkdir -p $(DEPSDIR) + @mkdir -p $(OBJDIR) + @set -e + @# Generate dependency file (side effect: also compiles!) + @# Output to a different file to not overwrite the old one on compilation failure (Make would think the compilation succeeded the next time) + $(RGBASM) -M $(@D)/tmp.d $(ASFLAGS) -o $(patsubst $(SRCDIR)/%.asm,$(OBJDIR)/%.o,$<) $< + @# Add all obj dependencies to the deps file too, so Make knows to remake it + @sed 's,\($*\)\.o[ :]*,\1.o $@: ,g' < $(DEPSDIR)/tmp.d > $@ + @rm $(DEPSDIR)/tmp.d + +# Include (and potentially remake) all dependency files +include $(patsubst $(SRCDIR)/%.asm,$(DEPSDIR)/%.d,$(ASMFILES)) + + +# How to make the ROM +$(ROMFILE): $(patsubst $(SRCDIR)/%.asm,$(OBJDIR)/%.o,$(ASMFILES)) + @mkdir -p $(BINDIR) + + @# Generate build date + date +"%FT%H:%M:%S" > $(SRCDIR)/res/build.date + $(RGBASM) $(ASFLAGS) -o $(OBJDIR)/build_date.o $(SRCDIR)/res/build_date.asm + + $(RGBLINK) $(LDFLAGS) -o $(BINDIR)/tmp.gb -m $(@:.$(ROMExt)=.map) -n $(@:.$(ROMExt)=.sym) $^ $(OBJDIR)/build_date.o + @# This pass does NOT fix the ROM checksum, to work around a RGBFIX bug. And because it's unnecessary. + $(RGBFIX) $(FXFLAGS) $(BINDIR)/tmp.gb + + mv $(BINDIR)/tmp.gb $@ + +# How to make the objects files +# (Just in case; since generating the deps files also generates the OBJ files, this should not be run ever, unless the OBJ files are destroyed but the deps files aren't.) +$(OBJDIR)/%.o: $(SRCDIR)/%.asm + @mkdir -p $(OBJDIR) + $(RGBASM) $(ASFLAGS) -o $@ $< + diff --git a/Makefile.conf b/Makefile.conf new file mode 100644 index 0000000..34cc551 --- /dev/null +++ b/Makefile.conf @@ -0,0 +1,57 @@ +# This file contains project configuration + + +# Value that the ROM will be filled with +FillValue = 0xFF + + +## Header constants (passed to RGBFIX) + +ROMVersion = 1 + +# 4-ASCII letter game ID +GameID = BOIL + +# Game title, up to 11 ASCII chars +GameTitle = BOILERPLATE + +# New licensee, 2 ASCII chars +NewLicensee = HB # Licensed by Homebrew (lel) +# Old licensee, please set to 0x33 (required to get SGB compatibility) +OldLicensee = 0x33 + +# MBC type, tells which hardware is in the cart +# MBC5 0x19 +# MBC5+RAM 0x1A +# MBC5+RAM+BATTERY 0x1B +MBCType = 0x1B + +# ROM size is automatic + +# None 0x00 +# 8k 0x02 +# 32k 0x03 +# 128k 0x04 +# 64k 0x05 +SRAMSize = 0x03 + +# ROM name +ROMName = boilerplate +ROMExt = gb + + +# Compilation parameters, uncomment to apply + +# Game Boy Color compatible +# FXFLAGS += -c +# Game Boy Color required +# FXFLAGS += -C + +# Super Game Boy compatible +# FXFLAGS += -s + +# Game Boy mode +LDFLAGS += -d + +# 32k mode +# LDFLAGS += -t diff --git a/src/constants/constants.asm b/src/constants/constants.asm new file mode 100644 index 0000000..a46f391 --- /dev/null +++ b/src/constants/constants.asm @@ -0,0 +1,4 @@ + +INCLUDE "constants/hardware.inc" +INCLUDE "constants/enums.asm" +INCLUDE "constants/misc.asm" diff --git a/src/constants/enums.asm b/src/constants/enums.asm new file mode 100644 index 0000000..73657cf --- /dev/null +++ b/src/constants/enums.asm @@ -0,0 +1,68 @@ + +set_enum_value: MACRO +enum_value = \1 +ENDM + +enum_start: MACRO + IF _NARG == 0 + set_enum_value 0 + ELSE + set_enum_value \1 + ENDC +ENDM + +enum_elem: MACRO + IF _NARG >= 2 + set_enum_value \2 + ENDC + +\1 = enum_value + set_enum_value enum_value+1 +ENDM + + +; SGB packet types + enum_start + enum_elem PAL01 + enum_elem PAL23 + enum_elem PAL12 + enum_elem PAL03 + enum_elem ATTR_BLK + enum_elem ATTR_LIN + enum_elem ATTR_DIV + enum_elem ATTR_CHR + enum_elem SOUND ; $08 + enum_elem SOU_TRN + enum_elem PAL_SET + enum_elem PAL_TRN + enum_elem ATRC_EN + enum_elem TEST_EN + enum_elem ICON_EN + enum_elem DATA_SND + enum_elem DATA_TRN ; $10 + enum_elem MLT_REQ + enum_elem JUMP + enum_elem CHR_TRN + enum_elem PCT_TRN + enum_elem ATTR_TRN + enum_elem ATTR_SET + enum_elem MASK_EN + enum_elem OBJ_TRN ; $18 + + +; Error IDs + enum_start + enum_elem ERROR_JUMP_HL + enum_elem ERROR_JUMP_DE + enum_elem ERROR_NULL_EXEC + enum_elem ERROR_RST38 + enum_elem ERROR_UNKNOWN + + +; Directions + enum_start + enum_elem DIR_UP + enum_elem DIR_DOWN + enum_elem DIR_LEFT + enum_elem DIR_RIGHT + diff --git a/src/constants/hardware.inc b/src/constants/hardware.inc new file mode 100644 index 0000000..d11442a --- /dev/null +++ b/src/constants/hardware.inc @@ -0,0 +1,755 @@ +;* +;* Gameboy Hardware definitions +;* +;* Based on Jones' hardware.inc +;* And based on Carsten Sorensen's ideas. +;* +;* Rev 1.1 - 15-Jul-97 : Added define check +;* Rev 1.2 - 18-Jul-97 : Added revision check macro +;* Rev 1.3 - 19-Jul-97 : Modified for RGBASM V1.05 +;* Rev 1.4 - 27-Jul-97 : Modified for new subroutine prefixes +;* Rev 1.5 - 15-Aug-97 : Added _HRAM, PAD, CART defines +;* : and Nintendo Logo +;* Rev 1.6 - 30-Nov-97 : Added rDIV, rTIMA, rTMA, & rTAC +;* Rev 1.7 - 31-Jan-98 : Added _SCRN0, _SCRN1 +;* Rev 1.8 - 15-Feb-98 : Added rSB, rSC +;* Rev 1.9 - 16-Feb-98 : Converted I/O registers to $FFXX format +;* Rev 2.0 - : Added GBC registers +;* Rev 2.1 - : Added MBC5 & cart RAM enable/disable defines +;* Rev 2.2 - : Fixed NR42,NR43, & NR44 equates +;* Rev 2.3 - : Fixed incorrect _HRAM equate +;* Rev 2.4 - 27-Apr-13 : Added some cart defines (AntonioND) +;* Rev 2.5 - 03-May-15 : Fixed format (AntonioND) +;* Rev 2.6 - 09-Apr-16 : Added GBC OAM and cart defines (AntonioND) + +; If all of these are already defined, don't do it again. + + IF !DEF(HARDWARE_INC) +HARDWARE_INC SET 1 + +rev_Check_hardware_inc : MACRO +;NOTE: REVISION NUMBER CHANGES MUST BE ADDED +;TO SECOND PARAMETER IN FOLLOWING LINE. + IF \1 > 2.6 ;PUT REVISION NUMBER HERE + WARN "Version \1 or later of 'hardware.inc' is required." + ENDC +ENDM + +_HW EQU $FF00 + +_VRAM EQU $8000 ; $8000->$9FFF +_SCRN0 EQU $9800 ; $9800->$9BFF +_SCRN1 EQU $9C00 ; $9C00->$9FFF +_SRAM EQU $A000 ; $A000->$BFFF +_RAM EQU $C000 ; $C000->$DFFF +_OAMRAM EQU $FE00 ; $FE00->$FE9F +_AUD3WAVERAM EQU $FF30 ; $FF30->$FF3F +_HRAM EQU $FF80 ; $FF80->$FFFE + +; *** MBC5 Equates *** + +rRAMG EQU $0000 ; $0000->$1fff +rROMB0 EQU $2000 ; $2000->$2fff +rROMB1 EQU $3000 ; $3000->$3fff - If more than 256 ROM banks are present. +rRAMB EQU $4000 ; $4000->$5fff - Bit 3 enables rumble (if present) + + +; -- +; -- OAM flags +; -- + +OAMF_PRI EQU %10000000 ; Priority +OAMF_YFLIP EQU %01000000 ; Y flip +OAMF_XFLIP EQU %00100000 ; X flip +OAMF_PAL0 EQU %00000000 ; Palette number; 0,1 (DMG) +OAMF_PAL1 EQU %00010000 ; Palette number; 0,1 (DMG) +OAMF_BANK0 EQU %00000000 ; Bank number; 0,1 (GBC) +OAMF_BANK1 EQU %00001000 ; Bank number; 0,1 (GBC) + +OAMF_PALMASK EQU %00000111 ; Palette (GBC) + +OAMB_PRI EQU 7 ; Priority +OAMB_YFLIP EQU 6 ; Y flip +OAMB_XFLIP EQU 5 ; X flip +OAMB_PAL1 EQU 4 ; Palette number; 0,1 (DMG) +OAMB_BANK1 EQU 3 ; Bank number; 0,1 (GBC) + + +;*************************************************************************** +;* +;* Custom registers +;* +;*************************************************************************** + +; -- +; -- P1 ($FF00) +; -- Register for reading joy pad info. (R/W) +; -- +rP1 EQU $FF00 + +P1F_5 EQU %00100000 ; P15 out port +P1F_4 EQU %00010000 ; P14 out port +P1F_3 EQU %00001000 ; P13 in port +P1F_2 EQU %00000100 ; P12 in port +P1F_1 EQU %00000010 ; P11 in port +P1F_0 EQU %00000001 ; P10 in port + +; -- +; -- SB ($FF01) +; -- Serial Transfer Data (R/W) +; -- +rSB EQU $FF01 + +; -- +; -- SC ($FF02) +; -- Serial I/O Control (R/W) +; -- +rSC EQU $FF02 + +; -- +; -- DIV ($FF04) +; -- Divider register (R/W) +; -- +rDIV EQU $FF04 + + +; -- +; -- TIMA ($FF05) +; -- Timer counter (R/W) +; -- +rTIMA EQU $FF05 + + +; -- +; -- TMA ($FF06) +; -- Timer modulo (R/W) +; -- +rTMA EQU $FF06 + + +; -- +; -- TAC ($FF07) +; -- Timer control (R/W) +; -- +rTAC EQU $FF07 + +TACF_START EQU %00000100 +TACF_STOP EQU %00000000 +TACF_4KHZ EQU %00000000 +TACF_16KHZ EQU %00000011 +TACF_65KHZ EQU %00000010 +TACF_262KHZ EQU %00000001 + +; -- +; -- IF ($FF0F) +; -- Interrupt Flag (R/W) +; -- +rIF EQU $FF0F + +; -- +; -- LCDC ($FF40) +; -- LCD Control (R/W) +; -- +rLCDC EQU $FF40 + +LCDCF_OFF EQU %00000000 ; LCD Control Operation +LCDCF_ON EQU %10000000 ; LCD Control Operation +LCDCF_WIN9800 EQU %00000000 ; Window Tile Map Display Select +LCDCF_WIN9C00 EQU %01000000 ; Window Tile Map Display Select +LCDCF_WINOFF EQU %00000000 ; Window Display +LCDCF_WINON EQU %00100000 ; Window Display +LCDCF_BG8800 EQU %00000000 ; BG & Window Tile Data Select +LCDCF_BG8000 EQU %00010000 ; BG & Window Tile Data Select +LCDCF_BG9800 EQU %00000000 ; BG Tile Map Display Select +LCDCF_BG9C00 EQU %00001000 ; BG Tile Map Display Select +LCDCF_OBJ8 EQU %00000000 ; OBJ Construction +LCDCF_OBJ16 EQU %00000100 ; OBJ Construction +LCDCF_OBJOFF EQU %00000000 ; OBJ Display +LCDCF_OBJON EQU %00000010 ; OBJ Display +LCDCF_BGOFF EQU %00000000 ; BG Display +LCDCF_BGON EQU %00000001 ; BG Display +; "Window Character Data Select" follows BG + + +; -- +; -- STAT ($FF41) +; -- LCDC Status (R/W) +; -- +rSTAT EQU $FF41 + +STATF_LYC EQU %01000000 ; LYCEQULY Coincidence (Selectable) +STATF_MODE10 EQU %00100000 ; Mode 10 +STATF_MODE01 EQU %00010000 ; Mode 01 (V-Blank) +STATF_MODE00 EQU %00001000 ; Mode 00 (H-Blank) +STATF_LYCF EQU %00000100 ; Coincidence Flag +STATF_HB EQU %00000000 ; H-Blank +STATF_VB EQU %00000001 ; V-Blank +STATF_OAM EQU %00000010 ; OAM-RAM is used by system +STATF_LCD EQU %00000011 ; Both OAM and VRAM used by system +STATF_BUSY EQU %00000010 ; When set, VRAM access is unsafe + + +; -- +; -- SCY ($FF42) +; -- Scroll Y (R/W) +; -- +rSCY EQU $FF42 + + +; -- +; -- SCY ($FF43) +; -- Scroll X (R/W) +; -- +rSCX EQU $FF43 + + +; -- +; -- LY ($FF44) +; -- LCDC Y-Coordinate (R) +; -- +; -- Values range from 0->153. 144->153 is the VBlank period. +; -- +rLY EQU $FF44 + + +; -- +; -- LYC ($FF45) +; -- LY Compare (R/W) +; -- +; -- When LYEQUEQULYC, STATF_LYCF will be set in STAT +; -- +rLYC EQU $FF45 + + +; -- +; -- DMA ($FF46) +; -- DMA Transfer and Start Address (W) +; -- +rDMA EQU $FF46 + + +; -- +; -- BGP ($FF47) +; -- BG Palette Data (W) +; -- +; -- Bit 7-6 - Intensity for %11 +; -- Bit 5-4 - Intensity for %10 +; -- Bit 3-2 - Intensity for %01 +; -- Bit 1-0 - Intensity for %00 +; -- +rBGP EQU $FF47 + + +; -- +; -- OBP0 ($FF48) +; -- Object Palette 0 Data (W) +; -- +; -- See BGP for info +; -- +rOBP0 EQU $FF48 + + +; -- +; -- OBP1 ($FF49) +; -- Object Palette 1 Data (W) +; -- +; -- See BGP for info +; -- +rOBP1 EQU $FF49 + + +; -- +; -- WY ($FF4A) +; -- Window Y Position (R/W) +; -- +; -- 0 SO2 ON/OFF (Vin??) +; -- Bit 6-4 - SO2 output level (volume) (# 0-7) +; -- Bit 3 - Vin->SO1 ON/OFF (Vin??) +; -- Bit 2-0 - SO1 output level (volume) (# 0-7) +; -- +rNR50 EQU $FF24 +rAUDVOL EQU rNR50 + + +; -- +; -- AUDTERM/NR51 ($FF25) +; -- Selection of Sound output terminal (R/W) +; -- +; -- Bit 7 - Output sound 4 to SO2 terminal +; -- Bit 6 - Output sound 3 to SO2 terminal +; -- Bit 5 - Output sound 2 to SO2 terminal +; -- Bit 4 - Output sound 1 to SO2 terminal +; -- Bit 3 - Output sound 4 to SO1 terminal +; -- Bit 2 - Output sound 3 to SO1 terminal +; -- Bit 1 - Output sound 2 to SO1 terminal +; -- Bit 0 - Output sound 0 to SO1 terminal +; -- +rNR51 EQU $FF25 +rAUDTERM EQU rNR51 + + +; -- +; -- AUDENA/NR52 ($FF26) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - All sound on/off (sets all audio regs to 0!) +; -- Bit 3 - Sound 4 ON flag (doesn't work!) +; -- Bit 2 - Sound 3 ON flag (doesn't work!) +; -- Bit 1 - Sound 2 ON flag (doesn't work!) +; -- Bit 0 - Sound 1 ON flag (doesn't work!) +; -- +rNR52 EQU $FF26 +rAUDENA EQU rNR52 + + +;*************************************************************************** +;* +;* SoundChannel #1 registers +;* +;*************************************************************************** + +; -- +; -- AUD1SWEEP/NR10 ($FF10) +; -- Sweep register (R/W) +; -- +; -- Bit 6-4 - Sweep Time +; -- Bit 3 - Sweep Increase/Decrease +; -- 0: Addition (frequency increases???) +; -- 1: Subtraction (frequency increases???) +; -- Bit 2-0 - Number of sweep shift (# 0-7) +; -- Sweep Time: (n*7.8ms) +; -- +rNR10 EQU $FF10 +rAUD1SWEEP EQU rNR10 + + +; -- +; -- AUD1LEN/NR11 ($FF11) +; -- Sound length/Wave pattern duty (R/W) +; -- +; -- Bit 7-6 - Wave Pattern Duty (00:12.5% 01:25% 10:50% 11:75%) +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR11 EQU $FF11 +rAUD1LEN EQU rNR11 + + +; -- +; -- AUD1ENV/NR12 ($FF12) +; -- Envelope (R/W) +; -- +; -- Bit 7-4 - Initial value of envelope +; -- Bit 3 - Envelope UP/DOWN +; -- 0: Decrease +; -- 1: Range of increase +; -- Bit 2-0 - Number of envelope sweep (# 0-7) +; -- +rNR12 EQU $FF12 +rAUD1ENV EQU rNR12 + + +; -- +; -- AUD1LOW/NR13 ($FF13) +; -- Frequency lo (W) +; -- +rNR13 EQU $FF13 +rAUD1LOW EQU rNR13 + + +; -- +; -- AUD1HIGH/NR14 ($FF14) +; -- Frequency hi (W) +; -- +; -- Bit 7 - Initial (when set, sound restarts) +; -- Bit 6 - Counter/consecutive selection +; -- Bit 2-0 - Frequency's higher 3 bits +; -- +rNR14 EQU $FF14 +rAUD1HIGH EQU rNR14 + + +;*************************************************************************** +;* +;* SoundChannel #2 registers +;* +;*************************************************************************** + +; -- +; -- AUD2LEN/NR21 ($FF16) +; -- Sound Length; Wave Pattern Duty (R/W) +; -- +; -- see AUD1LEN for info +; -- +rNR21 EQU $FF16 +rAUD2LEN EQU rNR21 + + +; -- +; -- AUD2ENV/NR22 ($FF17) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR22 EQU $FF17 +rAUD2ENV EQU rNR22 + + +; -- +; -- AUD2LOW/NR23 ($FF18) +; -- Frequency lo (W) +; -- +rNR23 EQU $FF18 +rAUD2LOW EQU rNR23 + + +; -- +; -- AUD2HIGH/NR24 ($FF19) +; -- Frequency hi (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR24 EQU $FF19 +rAUD2HIGH EQU rNR24 + + +;*************************************************************************** +;* +;* SoundChannel #3 registers +;* +;*************************************************************************** + +; -- +; -- AUD3ENA/NR30 ($FF1A) +; -- Sound on/off (R/W) +; -- +; -- Bit 7 - Sound ON/OFF (1EQUON,0EQUOFF) +; -- +rNR30 EQU $FF1A +rAUD3ENA EQU rNR30 + + +; -- +; -- AUD3LEN/NR31 ($FF1B) +; -- Sound length (R/W) +; -- +; -- Bit 7-0 - Sound length +; -- +rNR31 EQU $FF1B +rAUD3LEN EQU rNR31 + + +; -- +; -- AUD3LEVEL/NR32 ($FF1C) +; -- Select output level +; -- +; -- Bit 6-5 - Select output level +; -- 00: 0/1 (mute) +; -- 01: 1/1 +; -- 10: 1/2 +; -- 11: 1/4 +; -- +rNR32 EQU $FF1C +rAUD3LEVEL EQU rNR32 + + +; -- +; -- AUD3LOW/NR33 ($FF1D) +; -- Frequency lo (W) +; -- +; -- see AUD1LOW for info +; -- +rNR33 EQU $FF1D +rAUD3LOW EQU rNR33 + + +; -- +; -- AUD3HIGH/NR34 ($FF1E) +; -- Frequency hi (W) +; -- +; -- see AUD1HIGH for info +; -- +rNR34 EQU $FF1E +rAUD3HIGH EQU rNR34 + + +; -- +; -- AUD4LEN/NR41 ($FF20) +; -- Sound length (R/W) +; -- +; -- Bit 5-0 - Sound length data (# 0-63) +; -- +rNR41 EQU $FF20 +rAUD4LEN EQU rNR41 + + +; -- +; -- AUD4ENV/NR42 ($FF21) +; -- Envelope (R/W) +; -- +; -- see AUD1ENV for info +; -- +rNR42 EQU $FF21 +rAUD4ENV EQU rNR42 + + +; -- +; -- AUD4POLY/NR43 ($FF22) +; -- Polynomial counter (R/W) +; -- +; -- Bit 7-4 - Selection of the shift clock frequency of the (scf) +; -- polynomial counter (0000-1101) +; -- freqEQUdrf*1/2^scf (not sure) +; -- Bit 3 - Selection of the polynomial counter's step +; -- 0: 15 steps +; -- 1: 7 steps +; -- Bit 2-0 - Selection of the dividing ratio of frequencies (drf) +; -- 000: f/4 001: f/8 010: f/16 011: f/24 +; -- 100: f/32 101: f/40 110: f/48 111: f/56 (fEQU4.194304 Mhz) +; -- +rNR43 EQU $FF22 +rAUD4POLY EQU rNR43 + + +; -- +; -- AUD4GO/NR44 ($FF23) +; -- (has wrong name and value (ff30) in Dr.Pan's doc!) +; -- +; -- Bit 7 - Inital +; -- Bit 6 - Counter/consecutive selection +; -- +rNR44 EQU $FF23 +rAUD4GO EQU rNR44 ; silly name! + +;*************************************************************************** +;* +;* Cart related +;* +;*************************************************************************** + +CART_COMPATIBLE_DMG EQU $00 +CART_COMPATIBLE_DMG_GBC EQU $80 +CART_COMPATIBLE_GBC EQU $C0 + +CART_ROM EQU $00 +CART_ROM_MBC1 EQU $01 +CART_ROM_MBC1_RAM EQU $02 +CART_ROM_MBC1_RAM_BAT EQU $03 +CART_ROM_MBC2 EQU $05 +CART_ROM_MBC2_BAT EQU $06 +CART_ROM_RAM EQU $08 +CART_ROM_RAM_BAT EQU $09 +CART_ROM_MBC3_BAT_RTC EQU $0F +CART_ROM_MBC3_RAM_BAT_RTC EQU $10 +CART_ROM_MBC3 EQU $11 +CART_ROM_MBC3_RAM EQU $12 +CART_ROM_MBC3_RAM_BAT EQU $13 +CART_ROM_MBC5 EQU $19 +CART_ROM_MBC5_BAT EQU $1A +CART_ROM_MBC5_RAM_BAT EQU $1B +CART_ROM_MBC5_RUMBLE EQU $1C +CART_ROM_MBC5_RAM_RUMBLE EQU $1D +CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E +CART_ROM_MBC7_RAM_BAT_GYRO EQU $22 +CART_ROM_POCKET_CAMERA EQU $FC + +CART_ROM_256K EQU 0 ; 2 banks +CART_ROM_512K EQU 1 ; 4 banks +CART_ROM_1M EQU 2 ; 8 banks +CART_ROM_2M EQU 3 ; 16 banks +CART_ROM_4M EQU 4 ; 32 banks +CART_ROM_8M EQU 5 ; 64 banks +CART_ROM_16M EQU 6 ; 128 banks +CART_ROM_32M EQU 7 ; 256 banks +CART_ROM_64M EQU 8 ; 512 banks + +CART_RAM_NONE EQU 0 +CART_RAM_16K EQU 1 ; 1 incomplete bank +CART_RAM_64K EQU 2 ; 1 bank +CART_RAM_256K EQU 3 ; 4 banks +CART_RAM_1M EQU 4 ; 16 banks + +CART_RAM_ENABLE EQU $0A +CART_RAM_DISABLE EQU $00 + +;*************************************************************************** +;* +;* Keypad related +;* +;*************************************************************************** + +PADF_DOWN EQU $80 +PADF_UP EQU $40 +PADF_LEFT EQU $20 +PADF_RIGHT EQU $10 +PADF_START EQU $08 +PADF_SELECT EQU $04 +PADF_B EQU $02 +PADF_A EQU $01 + +PADB_DOWN EQU $7 +PADB_UP EQU $6 +PADB_LEFT EQU $5 +PADB_RIGHT EQU $4 +PADB_START EQU $3 +PADB_SELECT EQU $2 +PADB_B EQU $1 +PADB_A EQU $0 + +;*************************************************************************** +;* +;* Screen related +;* +;*************************************************************************** + +SCRN_X EQU 160 ; Width of screen in pixels +SCRN_Y EQU 144 ; Height of screen in pixels +SCRN_X_B EQU 20 ; Width of screen in bytes +SCRN_Y_B EQU 18 ; Height of screen in bytes + +SCRN_VX EQU 256 ; Virtual width of screen in pixels +SCRN_VY EQU 256 ; Virtual height of screen in pixels +SCRN_VX_B EQU 32 ; Virtual width of screen in bytes +SCRN_VY_B EQU 32 ; Virtual height of screen in bytes + +;* +;* Nintendo scrolling logo +;* (Code won't work on a real GameBoy) +;* (if next lines are altered.) +NINTENDO_LOGO : MACRO + DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D + DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 + DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E +ENDM + + ENDC ;HARDWARE_INC diff --git a/src/constants/misc.asm b/src/constants/misc.asm new file mode 100644 index 0000000..a083c76 --- /dev/null +++ b/src/constants/misc.asm @@ -0,0 +1,23 @@ + +; RST labels +null_exec equ $00 +memcpy_small equ $08 +memset_small equ $10 +memset equ $18 +bankswitch equ $20 +call_hl equ $28 +wait_vblank equ $30 +rst38_err equ $38 + + +; Seconds-to-frames converter +; "wait 30 seconds" is nice RGBDS magic :D +second EQUS "* 60" +seconds EQUS "* 60" +frames EQUS "" ; lol + + +STACK_SIZE = $40 + +SGB_PACKET_SIZE = 16 + diff --git a/src/engine.asm b/src/engine.asm new file mode 100644 index 0000000..1cc8bb1 --- /dev/null +++ b/src/engine.asm @@ -0,0 +1,6 @@ +INCLUDE "constants.asm" +INCLUDE "macros.asm" + +INCLUDE "engine/init.asm" + +INCLUDE "engine/error_handler.asm" diff --git a/src/engine/error_handler.asm b/src/engine/error_handler.asm new file mode 100644 index 0000000..899034c --- /dev/null +++ b/src/engine/error_handler.asm @@ -0,0 +1,746 @@ + +SECTION "Error handler trampoline", ROM0 + +handle_error: MACRO + ; Make sure we're not interrupted + di + ; Save current value of A + ld [wErrorA], a + ld a, \1 ; Preserve flags, don't "xor a"! + ; Will generate an extra, unnecessary "jr", but eh. + jr ErrorHandler +ENDM + + +HLJumpingError:: + handle_error ERROR_JUMP_HL +DEJumpingError:: + handle_error ERROR_JUMP_DE +NullExecError:: + handle_error ERROR_NULL_EXEC +Rst38Error:: + handle_error ERROR_RST38 + +; Perform minimal init, and jump to error handler in ROMX +ErrorHandler: + ld [wErrorType], a + + ld a, BANK(_ErrorHandler) + ld [rROMB0], a + xor a ; Ensure the error handler WILL be called, even if we end up with 512 banks + ld [rROMB1], a + jp _ErrorHandler + + +SECTION "Error handler", ROMX + +; Note: `call` is BANNED in this section of code!! +_ErrorHandler: + ld [wErrorSP], sp + ld sp, wErrorSP + push hl + push de + push bc + ld a, [wErrorA] + push af + + xor a + ldh [rNR52], a + + ldh a, [rLCDC] + bit 7, a + jr z, .lcdOff +.waitVBlank + ld a, [rLY] + cp SCRN_Y + jr c, .waitVBlank + xor a + ld [rLCDC], a +.lcdOff + + ; Load palette + ld a, $F4 + ld [rBGP], a + + xor a + ldh [rSCY], a + ldh [rSCX], a + + ld hl, _SCRN0 + ld bc, SCRN_VX_B * SCRN_Y_B +.clearVRAM + xor a + ld [hli], a + dec bc + ld a, b + or c + jr nz, .clearVRAM + + ; Copy monospace font + ld hl, vBlankTile + ld de, ErrorFont + ld bc, $800 +.copyFont + ld a, [de] + inc de + ld [hli], a + dec bc + ld a, b + or c + jr nz, .copyFont + + ; Oh noes!! + ; ld hl, _SCRN0 + ; ld de, ErrorUwu +.copyUwU + ld a, [de] + inc de + ld [hli], a + and a + jr nz, .copyUwU + + ld a, LCDCF_ON | LCDCF_BGON + ld [rLCDC], a + lb bc, 180, LOW(rLY) +.delayBetweenUwUs + ld a, [$ff00+c] + cp SCRN_Y + 1 + jr nz, .delayBetweenUwUs +.waitUwUBlank + ld a, [$ff00+c] + cp SCRN_Y + jr nz, .waitUwUBlank + dec b + jr nz, .delayBetweenUwUs + + ldcoord hl, 8, 0, _SCRN0 +.copySeriousUwU + ldh a, [rSTAT] + and STATF_BUSY + jr nz, .copySeriousUwU + ld a, [de] + ld [hli], a + inc de + and a + jr nz, .copySeriousUwU + + ; But can you do it in 0.5x A presses? + lb bc, 1, LOW(rP1) + ld a, $10 ; Select buttons + ld [$ff00+c], a +.waitAPress +REPT 6 + ld a, [$ff00+c] +ENDR + xor b + rra + jr nc, .waitAPress + dec b ; No, you can't. + jr z, .waitAPress + + +ErrorDumpScreen: + ldh a, [rLY] + sub SCRN_Y + jr nz, ErrorDumpScreen + ldh [rLCDC], a + + ld hl, _SCRN0 + ld bc, SCRN_VX_B * SCRN_Y_B +.clearSCRN0 + xor a + ld [hli], a + dec bc + ld a, b + or c + jr nz, .clearSCRN0 + + ld a, [wErrorType] + ld c, a + ; We can start using wErrorType for the drawing status purpose + xor a + ld [wErrorDrawStatus], a + + ld a, c + cp ERROR_UNKNOWN + jr c, .errorTypeOK + ld a, ERROR_UNKNOWN +.errorTypeOK + add a, a + add a, LOW(ErrorStrings) + ld l, a + adc a, HIGH(ErrorStrings) + sub l + ld h, a + ld a, [hli] + ld d, [hl] + ld e, a + ldcoord hl, 3, 1, _SCRN0 + jp StrcpyNoTerm +PrintedErrorStr: + ld a, c + cp ERROR_UNKNOWN + jp nc, PrintHex +PrintedErrorCode: + ld hl, wErrorDrawStatus + inc [hl] + + pop bc + ; From now on, there is 1 free stack slot, which means we can `call`!! + ld de, AFStr + ldcoord hl, 4, 1, _SCRN0 + call StrcpyNoTerm + ld a, b + call PrintHex + ld a, c + call PrintHex + + ldcoord hl, 0, 1, _SCRN0 + ld de, SendNudesStr + call Strcpy + ld l, $21 ; ld hl, $9821 + call Strcpy + + ldcoord hl, 5, 1, _SCRN0 + ld de, BCStr + call StrcpyNoTerm + pop bc + ld a, b + call PrintHex + ld a, c + call PrintHex + inc hl + ; ld de, DEStr + call StrcpyNoTerm + pop bc + ld a, b + call PrintHex + ld a, c + call PrintHex + + ldcoord hl, 14, 1, _SCRN0 + ld de, MemRegs +.printMemReg + ld a, [de] + and a + jr z, .doneWithMemRegs + call StrcpyNoTerm + ld a, [de] + inc de + ld c, a + ld a, [de] + inc de + ld b, a + ld a, [bc] + call PrintHex + inc hl + jr .printMemReg +.doneWithMemRegs + + ldcoord hl, 15, 1, _SCRN0 + ld de, BankStr + call StrcpyNoTerm + ldh a, [hCurROMBank] + call PrintHex + + ldcoord hl, 16, 1, _SCRN0 + ld de, BuildDate + call Strcpy + + ; Now, we need to do the hex dumps. Boi are we in for some trouble! + ldcoord hl, 6, 1, _SCRN0 + ld de, HLStr + call StrcpyNoTerm + + ; We first need to get the two registers (both at once to increase efficiency) + pop bc ; hl + pop de ; sp + ; Move SP back by 2 entries (we need to preserve those for dump printing) + add sp, -4 + + ld a, b + call PrintHex + ld a, c + call PrintHex + + ld b, d + ld c, e + ldcoord hl, 10, 1, _SCRN0 + ld de, SPstr + call StrcpyNoTerm + ld a, b + call PrintHex + ld a, c + call PrintHex + + ldcoord hl, 6, 10, _SCRN0 + ld de, ViewStr + call StrcpyNoTerm + ldcoord hl, 10, 10, _SCRN0 + ld de, ViewStr + call StrcpyNoTerm + + ld a, 2 + ld [wErrorWhichDump], a +.printOneDump + call PrintDump + ld hl, wErrorWhichDump + dec [hl] + jr nz, .printOneDump + + ldcoord hl, 8, 0, _SCRN0 + ld [hl], $1F + ld l, $80 ; ld hl, $9980 + ld [hl], $1F + + + ld a, 4 + ldh [rSCX], a + ld a, $FF + ldh [rSCY], a + + ; ld a, $FF + ld [wErrorHeldButtons], a + ld a, 30 + ld [wErrorFramesTillUnlock], a + + ld a, LCDCF_ON | LCDCF_BGON + ldh [rLCDC], a + + +; From now on, we'll be using the upper byte of the AF stack slot to save keys +; Thus, we're only allowed 2 stack slots (bc and de) +ErrorLoop: + ; Wait till next frame + ld a, [rLY] + cp SCRN_Y + 1 + jr c, ErrorLoop + ; Wait until right before VBlank, to account for the overhead +.waitVBlank + ld a, [rLY] + cp SCRN_Y + jr nz, .waitVBlank + + ld c, LOW(rP1) + ld a, $20 ; Select D-pad + ld [$ff00+c], a +REPT 6 + ld a, [$ff00+c] +ENDR + or $F0 ; Set 4 upper bits (give them consistency) + ld b, a + + ; Filter impossible D-pad combinations + and $0C ; Filter only Down and Up + ld a, b + jr nz, .notUpAndDown + or $0C ; If both are pressed, "unpress" them + ld b, a +.notUpAndDown + and $03 ; Filter only Left and Right + jr nz, .notLeftAndRight + ld a, b + or $03 ; If both are pressed, "unpress" them + ld b, a +.notLeftAndRight + swap b ; Put D-pad buttons in upper nibble + + ld a, $10 ; Select buttons + ld [$ff00+c], a +REPT 6 + ld a, [$ff00+c] +ENDR + or $F0 ; Set 4 upper bits + and b ; Mix with D-pad bits (use AND because pressed=0) + ld b, a + + ld a, [wErrorHeldButtons] + cpl + or b + ld e, a ; Pressed buttons + + ld a, b + ld [wErrorHeldButtons], a + + ; Release joypad + ld a, $30 + ld [$ff00+c], a + + + ld hl, wErrorFramesTillUnlock + ld a, [hl] + and a + jr z, .dumpsUnlocked + ld a, b ; Get back held buttons + and 3 ; Get only A and B + jr nz, ErrorLoop ; Decrement only if both are held + dec [hl] + jr ErrorLoop + +.dumpsUnlocked + ld hl, wErrorWhichDump + bit 1, e ; Check if B was pressed + jr z, .changeDumps + ; Prevent any dump-related operation if none have been chosen yet + ld a, [hl] + and a + jr z, ErrorLoop + + ; Select dump to act upon + ld hl, wErrorHL + dec a + jr z, .usingHLDump + ld hl, wErrorSP +.usingHLDump + bit 0, b + lb bc, 1, 8 + jr nz, .moveSmall + lb bc, $10, 1 +.moveSmall + ld a, [hl] + bit 7, e + jr z, .moveDown + bit 6, e + jr z, .moveUp + inc hl ; The next 2 target the high byte only + ld a, [hl] + bit 5, e + jr z, .moveLeft + bit 4, e + jp nz, ErrorLoop +.moveRight + add a, b + db $0E +.moveLeft + sub a, b + ld [hl], a + jr .redrawDump + +.moveDown + add a, c + ld [hli], a + jr nc, .redrawDump + inc [hl] + jr .redrawDump +.moveUp + sub c + ld [hli], a + jp nc, .redrawDump + dec [hl] +.redrawDump + call PrintDump + ; Unfortunately, PrintDump uses 3 stack slots, which overwrites the held keys and the lock frames + ; We can get around that, though + ld hl, wErrorFramesTillUnlock + xor a ; Set no remaining frames (obv) + ld [hld], a + ; Set all buttons as held (avoid spurious presses) + ld [hl], a + jp ErrorLoop + +.changeDumps + ld a, [hl] + dec a ; Check if on HL dump + jr z, .toSPDump + ld [hl], 1 ; Switch to HL dump (from SP dump & no dump yet) + db $21 +.toSPDump + ld [hl], 2 + + ; Toggle cursors + ld a, $1F + jr z, .emptyHLCursor + ld a, $7F +.emptyHLCursor + ld [$9900], a + xor $1F ^ $7F + ld [$9980], a + jp ErrorLoop + + +PrintHex: + ld b, a + and $F0 + swap a + cp 10 + jr c, .highIsDigit + add a, "A" - "0" - 10 +.highIsDigit + add a, "0" + ld [hli], a + + ld a, b + and $0F + cp 10 + jr c, .lowIsDigit + add a, "A" - "0" - 10 +.lowIsDigit + add a, "0" + ld [hli], a + + ld a, [wErrorDrawStatus] + and a + jp z, PrintedErrorCode + ret + + +StrcpyNoTerm: + ld a, [de] + inc de + and a + jr z, .return + ld [hli], a + jr StrcpyNoTerm + +.return + ld a, [wErrorDrawStatus] + and a + jp z, PrintedErrorStr + ret + + +PrintDump: + ldcoord hl, 6, 15, _SCRN0 + ld bc, wErrorHL + ld a, [wErrorWhichDump] + dec a + jr z, .dumpHL + ldcoord hl, 10, 15, _SCRN0 + ld bc, wErrorSP +.dumpHL + ld a, [bc] + ld e, a + inc bc + ld a, [bc] + ld d, a + call PrintHex + ld a, e + call PrintHex + inc hl + + ld a, e + sub 4 * 2 + ld e, a + ld a, d + sbc 0 + ld d, a + + ld b, 3 +.printNextRow + ld a, b + ld bc, $20 - 20 + add hl, bc + ld c, 4 + ld b, a + dec a ; Dirty hack: when the LCD is on, VBlank expires slightly too early, so only 2 rows can be drawn correctly + jr nz, .printOneWord + ; If the LCD is off, no worries + ldh a, [rLCDC] + add a, a + jr nc, .printOneWord + ; Wait till VBlank again +.waitVBlank + ldh a, [rLY] + cp $90 + jr nz, .waitVBlank +.printOneWord + inc hl + push bc ; B is trashed by PrintHex, so we need to save it anyways; but we also free c for storage + ld a, [de] + inc de + ld c, a ; Save low byte for later + ld a, [de] + inc de + call PrintHex + ld a, c + call PrintHex + pop bc + dec c + jr nz, .printOneWord + + dec b + jr nz, .printNextRow + ret + + +SendNudesStr: + dstr "Pls send us a pic" + dstr "of this screen! =)" + +ErrorStrings: + dw .hlJumpErrorStr + dw .deJumpErrorStr + dw .nullExecErrorStr + dw .rst38ErrorStr + + dw .unknownErrorStr + +.hlJumpErrorStr + dstr "Bad hl jump" +.deJumpErrorStr + dstr "Bad de jump" +.nullExecErrorStr + dstr "Null exec" +.rst38ErrorStr + dstr "rst 38 error" +.unknownErrorStr + dstr "Unk err $" + + +AFStr: + dstr "AF:" +BCStr: + dstr "BC:" +DEStr: + dstr "DE:" +HLStr: + dstr "HL:" +SPstr: + dstr "SP:" + +ViewStr: + dstr "View:" + +MemRegs: + dstr "STAT:" + dw rSTAT + dstr "IE:" + dw rIE + db 0 + +BankStr: + dstr "Bank:" + +ErrorFont: +REPT " " - 1 + dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000 +ENDR + dw $0000, $0000, $0800, $0C00, $0800, $0000, $0000, $0000 ; Empty arrow + dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000 ; Space + + ; Symbols 1 + dw $8000, $8000, $8000, $8000, $8000, $0000, $8000, $0000 + dw $0000, $6C00, $6C00, $4800, $0000, $0000, $0000, $0000 + dw $4800, $FC00, $4800, $4800, $4800, $FC00, $4800, $0000 + dw $1000, $7C00, $9000, $7800, $1400, $F800, $1000, $0000 + dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000 ; %, empty slot for now + dw $6000, $9000, $5000, $6000, $9400, $9800, $6C00, $0000 + dw $0000, $3800, $3800, $0800, $1000, $0000, $0000, $0000 + dw $1800, $2000, $2000, $2000, $2000, $2000, $1800, $0000 + dw $1800, $0400, $0400, $0400, $0400, $0400, $1800, $0000 + dw $0000, $1000, $5400, $3800, $5400, $1000, $0000, $0000 + dw $0000, $1000, $1000, $7C00, $1000, $1000, $0000, $0000 + dw $0000, $0000, $0000, $0000, $3000, $3000, $6000, $0000 + dw $0000, $0000, $0000, $7C00, $0000, $0000, $0000, $0000 + dw $0000, $0000, $0000, $0000, $0000, $6000, $6000, $0000 + dw $0000, $0400, $0800, $1000, $2000, $4000, $8000, $0000 + dw $3000, $5800, $CC00, $CC00, $CC00, $6800, $3000, $0000 + dw $3000, $7000, $F000, $3000, $3000, $3000, $FC00, $0000 + dw $7800, $CC00, $1800, $3000, $6000, $C000, $FC00, $0000 + dw $7800, $8C00, $0C00, $3800, $0C00, $8C00, $7800, $0000 + dw $3800, $5800, $9800, $FC00, $1800, $1800, $1800, $0000 + dw $FC00, $C000, $C000, $7800, $0C00, $CC00, $7800, $0000 + dw $7800, $CC00, $C000, $F800, $CC00, $CC00, $7800, $0000 + dw $FC00, $0C00, $0C00, $1800, $1800, $3000, $3000, $0000 + dw $7800, $CC00, $CC00, $7800, $CC00, $CC00, $7800, $0000 + dw $7800, $CC00, $CC00, $7C00, $0C00, $CC00, $7800, $0000 + dw $0000, $C000, $C000, $0000, $C000, $C000, $0000, $0000 + dw $0000, $C000, $C000, $0000, $C000, $4000, $8000, $0000 + dw $0400, $1800, $6000, $8000, $6000, $1800, $0400, $0000 + dw $0000, $0000, $FC00, $0000, $FC00, $0000, $0000, $0000 + dw $8000, $6000, $1800, $0400, $1800, $6000, $8000, $0000 + dw $7800, $CC00, $1800, $3000, $2000, $0000, $2000, $0000 + dw $0000, $2000, $7000, $F800, $F800, $F800, $0000, $0000 ; "Up" arrow, not ASCII but otherwise unused :P + + ; Uppercase + dw $3000, $4800, $8400, $8400, $FC00, $8400, $8400, $0000 + dw $F800, $8400, $8400, $F800, $8400, $8400, $F800, $0000 + dw $3C00, $4000, $8000, $8000, $8000, $4000, $3C00, $0000 + dw $F000, $8800, $8400, $8400, $8400, $8800, $F000, $0000 + dw $FC00, $8000, $8000, $FC00, $8000, $8000, $FC00, $0000 + dw $FC00, $8000, $8000, $FC00, $8000, $8000, $8000, $0000 + dw $7C00, $8000, $8000, $BC00, $8400, $8400, $7800, $0000 + dw $8400, $8400, $8400, $FC00, $8400, $8400, $8400, $0000 + dw $7C00, $1000, $1000, $1000, $1000, $1000, $7C00, $0000 + dw $0400, $0400, $0400, $0400, $0400, $0400, $F800, $0000 + dw $8400, $8800, $9000, $A000, $E000, $9000, $8C00, $0000 + dw $8000, $8000, $8000, $8000, $8000, $8000, $FC00, $0000 + dw $8400, $CC00, $B400, $8400, $8400, $8400, $8400, $0000 + dw $8400, $C400, $A400, $9400, $8C00, $8400, $8400, $0000 + dw $7800, $8400, $8400, $8400, $8400, $8400, $7800, $0000 + dw $F800, $8400, $8400, $F800, $8000, $8000, $8000, $0000 + dw $7800, $8400, $8400, $8400, $A400, $9800, $6C00, $0000 + dw $F800, $8400, $8400, $F800, $9000, $8800, $8400, $0000 + dw $7C00, $8000, $8000, $7800, $0400, $8400, $7800, $0000 + dw $7C00, $1000, $1000, $1000, $1000, $1000, $1000, $0000 + dw $8400, $8400, $8400, $8400, $8400, $8400, $7800, $0000 + dw $8400, $8400, $8400, $8400, $8400, $4800, $3000, $0000 + dw $8400, $8400, $8400, $8400, $B400, $CC00, $8400, $0000 + dw $8400, $8400, $4800, $3000, $4800, $8400, $8400, $0000 + dw $4400, $4400, $4400, $2800, $1000, $1000, $1000, $0000 + dw $FC00, $0400, $0800, $1000, $2000, $4000, $FC00, $0000 + + ; Symbols 2 + dw $3800, $2000, $2000, $2000, $2000, $2000, $3800, $0000 + dw $0000, $8000, $4000, $2000, $1000, $0800, $0400, $0000 + dw $1C00, $0400, $0400, $0400, $0400, $0400, $1C00, $0000 + dw $1000, $2800, $0000, $0000, $0000, $0000, $0000, $0000 + dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $FF00 + dw $C000, $6000, $0000, $0000, $0000, $0000, $0000, $0000 + + ; Lowercase + dw $0000, $0000, $7800, $0400, $7C00, $8400, $7800, $0000 + dw $8000, $8000, $8000, $F800, $8400, $8400, $7800, $0000 + dw $0000, $0000, $7C00, $8000, $8000, $8000, $7C00, $0000 + dw $0400, $0400, $0400, $7C00, $8400, $8400, $7800, $0000 + dw $0000, $0000, $7800, $8400, $F800, $8000, $7C00, $0000 + dw $0000, $3C00, $4000, $FC00, $4000, $4000, $4000, $0000 + dw $0000, $0000, $7800, $8400, $7C00, $0400, $F800, $0000 + dw $8000, $8000, $F800, $8400, $8400, $8400, $8400, $0000 + dw $0000, $1000, $0000, $1000, $1000, $1000, $1000, $0000 + dw $0000, $1000, $0000, $1000, $1000, $1000, $E000, $0000 + dw $8000, $8000, $8400, $9800, $E000, $9800, $8400, $0000 + dw $1000, $1000, $1000, $1000, $1000, $1000, $1000, $0000 + dw $0000, $0000, $6800, $9400, $9400, $9400, $9400, $0000 + dw $0000, $0000, $7800, $8400, $8400, $8400, $8400, $0000 + dw $0000, $0000, $7800, $8400, $8400, $8400, $7800, $0000 + dw $0000, $0000, $7800, $8400, $8400, $F800, $8000, $0000 + dw $0000, $0000, $7800, $8400, $8400, $7C00, $0400, $0000 + dw $0000, $0000, $BC00, $C000, $8000, $8000, $8000, $0000 + dw $0000, $0000, $7C00, $8000, $7800, $0400, $F800, $0000 + dw $0000, $4000, $F800, $4000, $4000, $4000, $3C00, $0000 + dw $0000, $0000, $8400, $8400, $8400, $8400, $7800, $0000 + dw $0000, $0000, $8400, $8400, $4800, $4800, $3000, $0000 + dw $0000, $0000, $8400, $8400, $8400, $A400, $5800, $0000 + dw $0000, $0000, $8C00, $5000, $2000, $5000, $8C00, $0000 + dw $0000, $0000, $8400, $8400, $7C00, $0400, $F800, $0000 + dw $0000, $0000, $FC00, $0800, $3000, $4000, $FC00, $0000 + + ; Symbols 3 + dw $1800, $2000, $2000, $4000, $2000, $2000, $1800, $0000 + dw $1000, $1000, $1000, $1000, $1000, $1000, $1000, $0000 + dw $3000, $0800, $0800, $0400, $0800, $0800, $3000, $0000 + dw $0000, $0000, $4800, $A800, $9000, $0000, $0000, $0000 + + dw $0000, $0800, $0C00, $0E00, $0C00, $0800, $0000, $0000 ; Left arrow + +ErrorUwu: + db "OOPSIE WOOPSIE!! Uwu " + db " We made a fucky " + db " wucky!! A wittle " + db " fucko boingo! The " + db "code monkeys at our " + db " headquarters are " + db "working VEWY HAWD to " + db " fix this! ",0 + + db " " + db "More seriously, I'm " + db "sorry, but the game " + db "has encountered a " + db "fatal error blah " + db "blah blah, tl;dr it " + db "has crashed. " + db "To allow us to fix " + db "it, please be a " + db "peach and press A =3",0 diff --git a/src/engine/init.asm b/src/engine/init.asm new file mode 100644 index 0000000..f6bc0e0 --- /dev/null +++ b/src/engine/init.asm @@ -0,0 +1,8 @@ + +SECTION "Init code", ROMX + +Init:: + ret + + + diff --git a/src/home.asm b/src/home.asm new file mode 100644 index 0000000..0db199b --- /dev/null +++ b/src/home.asm @@ -0,0 +1,11 @@ +INCLUDE "constants.asm" +INCLUDE "macros.asm" + +INCLUDE "home/vectors.asm" +INCLUDE "home/header.asm" + +INCLUDE "home/utils.asm" +INCLUDE "home/sgb.asm" +INCLUDE "home/raster_fx.asm" +INCLUDE "home/unpb16.asm" + diff --git a/src/home/header.asm b/src/home/header.asm new file mode 100644 index 0000000..34e01b9 --- /dev/null +++ b/src/home/header.asm @@ -0,0 +1,111 @@ + +NB_ROM_BANKS = 2 + +SECTION "Header", ROM0[$100] + +EntryPoint:: + ld b, $60 + jr LogoFade + + dbr 0, $150 - $104 + + ; Header ends here + +LogoFade: + xor a + ldh [rAUDENA], a + +.fadeLogo + ld c, 7 ; Number of frames between each logo fade step +.logoWait + ld a, [rLY] + cp a, SCRN_Y + jr nc, .logoWait +.waitVBlank + ld a, [rLY] + cp a, SCRN_Y + jr c, .waitVBlank + dec c + jr nz, .logoWait + ; Shift all colors (fading the logo progressively) + ld a, b + rra + rra + and $FC ; Ensures a proper rotation and sets Z for final check + ldh [rBGP], a + ld b, a + jr nz, .fadeLogo ; End if the palette is fully blank (flag set from `and $FC`) + + ; xor a + ldh [rDIV], a + +Reset:: + di + ld sp, wStackBottom + + xor a + ldh [rAUDENA], a + +.waitVBlank + ld a, [rLY] + cp SCRN_Y + jr c, .waitVBlank + xor a + ldh [rLCDC], a + + + ; Perform some init + ; xor a + ldh [rAUDENA], a + + ; Init HRAM + ; Also clears IE, but we're gonna overwrite it just after + ld c, LOW(hClearStart) +.clearHRAM + xor a + ld [$ff00+c], a + inc c + jr nz, .clearHRAM + + ; Copy OAM DMA routine + ld hl, OAMDMA + lb bc, OAMDMAEnd - OAMDMA, LOW(hOAMDMA) +.copyOAMDMA + ld a, [hli] + ld [$ff00+c], a + inc c + dec b + jr nz, .copyOAMDMA + + ld a, LOW(hScanlineFXBuffer1) + ldh [hWhichScanlineBuffer], a + ld a, $FF + ldh [hScanlineFXBuffer1], a + ld a, STATF_LYC + ldh [rSTAT], a + + ld a, LCDCF_ON | LCDCF_BGON + ldh [hLCDC], a + ldh [rLCDC], a + + ld a, IEF_VBLANK | IEF_LCDC + ldh [rIE], a + xor a + ei ; Delayed until the next instruction: perfectly safe! + ldh [rIF], a + + + ; Init code goes here + + + +SECTION "OAM DMA routine", ROM0 + +OAMDMA: + ldh [rDMA], a + ld a, $28 +.wait + dec a + jr nz, .wait + ret +OAMDMAEnd: diff --git a/src/home/raster_fx.asm b/src/home/raster_fx.asm new file mode 100644 index 0000000..25fdfe2 --- /dev/null +++ b/src/home/raster_fx.asm @@ -0,0 +1,221 @@ + +SECTION "Raster fx helper functions", ROM0 + +; Get a pointer to the currently free scanline buffer +; @return a The pointer +; @return c The pointer +; @destroys a c +GetFreeScanlineBuf:: + ldh a, [hWhichScanlineBuffer] + xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1) + ld c, a + ret + +; Switches to the currently free scanline buffer +; @return c A pointer to the newly freed buffer +; @return b A pointer to the newly used buffer +; @destroys a c +SwitchScanlineBuf:: + call GetFreeScanlineBuf + ldh [hWhichScanlineBuffer], a + ld b, a + xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1) + ld c, a + ret + +; Switches to the currently free scanline buffer, and copies it over to the other buffer +; @destroys a c hl +SwitchAndCopyScanlineBuf:: + call SwitchScanlineBuf + ld l, b + ld h, HIGH(hScanlineFXBuffer1) +.loop + ld a, [hli] + ld [$ff00+c], a + inc c + inc a + jr nz, .loop + ret + + +; Finds the effect applied to a given scanline +; @param b The scanline being looked for +; @return Zero Set if the lookup succeeded +; @return c A pointer to the effect's scanline, otherwise to the terminating $FF byte +; @destroys a c +GetFXByScanline:: + call GetFreeScanlineBuf +.lookup + ld a, [$ff00+c] + cp b + ret nc ; Return if scanline was greater than or equal + inc c + inc c + inc c + jr .lookup + +; Insert effect into the free buffer at the given scanline +; The effect's scanline will have been set, and the other two values will be zero'd +; @param b The scanline to insert the effect at +; @return c A pointer to the new effect's port address +; @return Zero Set if the scanline is already taken (in which case c still holds the correct value) +; @destroys a bc hl +InsertFX:: + call GetFreeScanlineBuf + ld l, c + ld h, HIGH(hScanlineFXBuffer1) + scf ; Don't skip the check +.lookForEnd + ld a, [hli] + inc l + ; C is inherited from previous `cp b` + jr nc, .skip ; If we're already greater then the scanline, skip this + cp b + ret z ; End now if the scanline is already taken, because two FX can't be on the same line + jr c, .skip ; Skip if we aren't the first greater than the scanline + ld c, l ; Make c point to the target's value +.skip + inc l + inc a + jr nz, .lookForEnd + ; Write new terminator ($FF) + ld [hl], h ; Bet you didn't expect this opcode to ever be used, eh? + dec l +.copy + dec l + dec l + dec l + ld a, [hli] + inc l + inc l + ld [hld], a ; If we just copied the target scanline, + ld a, l ; this points to the value + cp c ; which we know the address of! + jr nz, .copy + ; Move the pointer and init the new fx + xor a + ld [hld], a + ld [hld], a + ld [hl], b ; Write the desired scanline + inc a ; Don't have Z set + ret + + +; Remove effect from the free buffer +; @param b The targeted effect's scanline +; @return Zero Set if the lookup succeeded +; @destroys a c hl +RemoveFX:: + call GetFXByScanline + ret nz + ld l, c + ld h, HIGH(hScanlineFXBuffer1) + inc c + inc c + inc c +.copy + ; Copy scanline + ld a, [$ff00+c] + ld [hli], a + inc a + ret z ; End if we copied the terminator + inc c + ; Copy port address + ld a, [$ff00+c] + ld [hli], a + inc c + ; Copy value + ld a, [$ff00+c] + ld [hli], a + inc c + jr .copy + + +; Add the textbox raster FX +; Caution: overwrites the currently free fx buffer with the active (+textbox) +; @param b The number of pixels of the textbox to display (0 closes it) +; @destroys a bc de hl +SetUpTextbox:: + ld h, HIGH(hWhichScanlineBuffer) + + ; Check if backup operations should be performed + ldh a, [hIsTextboxActive] + and a + jr nz, .dontBackup + ld a, b + and a + ret z ; Do nothing if the textbox is closed while it is closed (lol) + ldh a, [hWhichScanlineBuffer] + ld c, a + ld l, LOW(hBackupScanlineFXBuffer) +.backup + ld a, [$ff00+c] + inc c + ld [hli], a + inc a + jr nz, .backup + inc a ; ld a, 1 + ldh [hIsTextboxActive], a + ; Fall through, but this won't get executed +.dontBackup + ld a, b + and a + jr z, .restoreAndQuit + + ; Get pointers to buffers + ldh a, [hWhichScanlineBuffer] + ld c, a + xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1) + ld l, a + ; Calculate scanline + ld a, SCRN_Y + sub b + ld b, a + + scf +.copy + ld a, [$ff00+c] ; Get scanline + cp b + jr nc, .insertTextbox + inc c + ld [hl], a + ld a, [$ff00+c] ; Read port + inc c + inc c ; Skip value, jic + and a ; Are we copying a textbox FX? + jr z, .copy ; Abort + inc l + ld [hli], a + dec c + ld a, [$ff00+c] ; Read value + inc c + ld [hli], a + jr .copy + +.restoreAndQuit + call GetFreeScanlineBuf +.restore + ld a, [hli] + ld [$ff00+c], a + inc c + inc a + jr nz, .restore + ; a = 0 + ldh [hIsTextboxActive], a + ret + +.insertTextbox + ; Place the textbox FX + ld [hl], b + inc l + ld [hl], 0 + inc l + ld a, b + sub SCRN_Y + ld [hli], a + ld [hl], $FF ; Don't forget to terminate! + + ldh a, [hWhichScanlineBuffer] + xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1) + ldh [hWhichScanlineBuffer], a + ret diff --git a/src/home/sgb.asm b/src/home/sgb.asm new file mode 100644 index 0000000..297b50a --- /dev/null +++ b/src/home/sgb.asm @@ -0,0 +1,130 @@ + +SECTION "SGB routines", ROM0 + +; Sends SGB packets to the SGB, assuming we're running on one. +; @param hl Pointer to the packet data to be sent (can send any number of packets btw) +; @return hl Points to the end of the packet data +; @return de Zero +; @return bc Zero +; @return a Zero +SendPackets:: + ld a, [hl] ; Length + and %111 + ret z + ld c, a + +.sendPacket + call SendPacketNoDelay + call SGBDelay ; Let the ICD chip rest a bit + dec c + jr nz, .sendPacket + ret + + +; Sends a SGB packet to the SGB to freeze the screen, assuming we're running on one. +; Does not perform any delay after sending the packet. +; Use only if you're not going to send another SGB packet in the next few frames. +; You're likely to perform some decompression or smth after this +; @param hl Pointer to the packet data to be sent. +; @return hl Points to the end of the packet data +; @return b Zero +; @return d Zero +; @return a $30 +; @destroy e +FreezeSGBScreen:: + ld hl, FreezeScreenPacket +; Sends a SGB packet to the SGB, assuming it's running on one. +; Does not perform any delay after sending the packet. +; Unsuitable to send multi-packet packets. +; Use only if you're not going to send another SGB packet in the next four frames. +; Assumes the joypad won't be polled by interrupts during this time, but since the VBlank handler doesn't poll except when waited for, this is fine. +; @param hl Pointer to the packet data to be sent. +; @return hl Points to the end of the packet data +; @return b Zero +; @return d Zero +; @return a $30 +; @destroy e +SendPacketNoDelay:: + ; Packet transmission begins by sending $00 then $30 + xor a + ldh [rP1], a + ld a, $30 + ldh [rP1], a + + ld b, SGB_PACKET_SIZE +.sendByte + ld d, 8 ; 8 bits in a byte + ld a, [hli] ; Read byte to send + ld e, a + +.sendBit + ld a, $10 ; 1 bits are sent with $10 + rr e ; Rotate d and get its lower bit, two birds in one stone! + jr c, .bitSet + add a, a ; 0 bits are sent with $20 +.bitSet + ldh [rP1], a + ld a, $30 ; Terminate pulse + ldh [rP1], a + dec d + jr nz, .sendBit + + dec b + jr nz, .sendByte + + ; Packets are terminated by a "STOP" 0 bit + ld a, $20 + ldh [rP1], a + ld a, $30 + ldh [rP1], a + ret + +SGBDelay:: + ld de, 7000 ; Magic value, apparently +.loop + nop + nop + nop + dec de + ld a, d + or e + jr nz, .loop + ret + +FreezeScreenPacket: + sgb_packet MASK_EN, 1, 1 + + +; Fill the $9C00 tilemap with a pattern suitable for SGB _TRN +; Also sets up the rendering parameters for the transfer +; Finally, assumes the LCD is **off** +; @return hl +FillScreenWithSGBMap:: + xor a + ldh [hSCY], a + ldh [hSCX], a + ld b, a ; ld b, 0 + ld hl, $9C00 +.writeRow + ld c, SCRN_X_B +.writeTile + ld a, b + ld [hli], a + inc b + jr z, .done + dec c + jr nz, .writeTile + ld a, l + add a, SCRN_VX_B - SCRN_X_B + ld l, a + jr nc, .writeRow + inc h + jr .writeRow +.done + ld a, %11100100 + ldh [hBGP], a +SetupSGBLCDC:: + ld a, LCDCF_ON | LCDCF_WINOFF | LCDCF_BG8000 | LCDCF_BG9C00 | LCDCF_OBJOFF | LCDCF_BGON + ldh [rLCDC], a + ldh [hLCDC], a + ret diff --git a/src/home/unpb16.asm b/src/home/unpb16.asm new file mode 100644 index 0000000..6deba98 --- /dev/null +++ b/src/home/unpb16.asm @@ -0,0 +1,89 @@ +; +; PB16 decompression for Game Boy +; Copyright 2018 Damian Yerrick +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; +; This software has been modified by Eldred Habert (ISSOtm): +; - Removal of .inc file +; - Removal of variable allocation + + +section "pb16", ROM0 + +; The PB16 format is a starting point toward efficient RLE image +; codecs on Game Boy and Super NES. +; +; 0: Load a literal byte +; 1: Repeat from 2 bytes ago + +pb16_unpack_packet: + ; Read first bit of control byte. Treat B as a ring counter with + ; a 1 bit as the sentinel. Once the 1 bit reaches carry, B will + ; become 0, meaning the 8-byte packet is complete. + ld a,[de] + inc de + scf + rla + ld b,a +.byteloop: + ; If the bit from the control byte is clear, plane 0 is is literal + jr nc,.p0_is_literal + ldh a,[pb16_byte0] + jr .have_p0 +.p0_is_literal: + ld a,[de] + inc de + ldh [pb16_byte0],a +.have_p0: + ld [hl+],a + + ; Read next bit. If it's clear, plane 1 is is literal. + ld a,c + sla b + jr c,.have_p1 +.p1_is_copy: + ld a,[de] + inc de + ld c,a +.have_p1: + ld [hl+],a + + ; Read next bit of control byte + sla b + jr nz,.byteloop + ret + +;; +; Unpacks 2*B packets from DE to HL, producing 8 bytes per packet. +; About 127 cycles (2 scanlines) per 8-byte packet; filling CHR RAM +; thus takes (6144/8)*127 = about 97536 cycles or 93 ms +pb16_unpack_block:: + ; Prefill with zeroes + xor a + ldh [pb16_byte0],a + ld c,a +.packetloop: + push bc + call pb16_unpack_packet + call pb16_unpack_packet + ld a,c + pop bc + ld c,a + dec b + jr nz,.packetloop + ret diff --git a/src/home/utils.asm b/src/home/utils.asm new file mode 100644 index 0000000..95c9572 --- /dev/null +++ b/src/home/utils.asm @@ -0,0 +1,192 @@ + + +; Since all these functions are independent, declare each of them in individual sections +; Lets the linker place them more liberally. Hooray! +f: MACRO +PURGE \1 +SECTION "Utility function \1", ROM0 +\1:: +ENDM + +SECTION "Dummy section", ROM0 ; To have the first `Memcpy` declare properly + +; Copies bc bytes of data from de to hl +Memcpy:: + f Memcpy + ld a, [de] + ld [hli], a + inc de + dec bc + ld a, b + or c + jr nz, Memcpy + ret + +; Copies a null-terminated string from de to hl, including the terminating NUL +Strcpy:: + f Strcpy + ld a, [de] + ld [hli], a + inc de + and a + jr nz, Strcpy + ret + +; Copies c bytes of data from de to hl in a LCD-safe manner +LCDMemcpySmall:: + f LCDMemcpySmall + ldh a, [rSTAT] + and STATF_BUSY + jr nz, LCDMemcpySmall + ld a, [de] + ld [hli], a + inc de + dec c + jr nz, LCDMemcpySmall + ret + +; Copies bc bytes of data from de to hl in a LCD-safe manner +LCDMemcpy:: + f LCDMemcpy + ldh a, [rSTAT] + and STATF_BUSY + jr nz, LCDMemcpy + ld a, [de] + ld [hli], a + inc de + dec bc + ld a, b + or c + jr nz, LCDMemcpy + ret + +; Sets c bytes of data at hl with the value in a +LCDMemsetSmall:: + f LCDMemsetSmall + ld b, a + +; Sets c bytes of data at hl with the value in b +LCDMemsetSmallFromB:: +; No f (...) because there's the slide-in above +.loop + ldh a, [rSTAT] + and STATF_BUSY + jr nz, .loop + ld a, b + ld [hli], a + dec c + jr nz, .loop + ret + +; Sets bc bytes of data at hl with the value in a +LCDMemset:: + f LCDMemset + ld d, a + +; Sets bc bytes of data at hl with the value in d +LCDMemsetFromD:: +; No f (...) because of the slide-in above +.loop + ldh a, [rSTAT] + and STATF_BUSY + jr nz, .loop + ld a, d + ld [hli], a + dec bc + ld a, b + or c + jr nz, .loop + ret + + +; Opens SRAM at some bank +; @param a The bank's number +; @return a CART_RAM_ENABLE, ie. $0A +GetSRAMBank:: + f GetSRAMBank + ld [rRAMB], a + ld a, CART_RAM_ENABLE + ld [rRAMG], a + ret + +; Closes SRAM +; @return hl = rRAMB (I know, it sounds stupid) +CloseSRAM:: + f CloseSRAM +; Implementation note: MUST preserve the Z flag to avoid breaking the call to `PrintSRAMFailure` + ld hl, rRAMG + ld [hl], l ; ld [hl], 0 + ld h, HIGH(rRAMB) + ld [hl], l ; Avoid unintentional unlocks corrupting saved data, switch to bank 0 (which is scratch) + ret + + +; Gets the Nth struct in an array of 'em +; @param hl Array base +; @param bc Size of a struct +; @param a ID of the desired struct +; @return hl Pointer to the struct's base +; @destroys a +GetNthStruct:: + f GetNthStruct + and a + ret z +.next + add hl, bc + dec a + jr nz, .next + ret + + +; Copies tiles into VRAM, using an unrolled loop to go faster +; @param hl Destination (pointer) +; @param de Source +; @param c Number of tiles +; @return hl, de Pointer to end of blocks +; @return c 0 +; @destroys a +Tilecpy:: + f Tilecpy +REPT $10 / 2 + wait_vram + ld a, [de] + ld [hli], a + inc de + ld a, [de] + ld [hli], a + inc de +ENDR + dec c + jr nz, Tilecpy + ret + + +; Copies a tilemap to VRAM, assuming the LCD is off +; @param hl Destination +; @param de Source +; @return hl, de Pointer to end of blocks +; @return bc Zero +; @return a Equal to h +Mapcpy:: + f Mapcpy + ld b, SCRN_Y_B +.copyRow + ld c, SCRN_X_B +.copyTile + ld a, [de] + ld [hli], a + inc de + dec c + jr nz, .copyTile + ld a, l + add a, SCRN_VX_B - SCRN_X_B + ld l, a + adc a, h + sub l + ld h, a + dec b + jr nz, .copyRow + ret + + +PURGE f diff --git a/src/home/vectors.asm b/src/home/vectors.asm new file mode 100644 index 0000000..5bd4f9d --- /dev/null +++ b/src/home/vectors.asm @@ -0,0 +1,307 @@ + +SECTION "rst00", ROM0[$0000] +; Please do not call +; Traps execution errors (mostly to $FFFF / $0000) +rst00: + ; Pad, in case we come from FFFF and read a 2-byte operand + nop + nop + jp NullExecError + +SECTION "rst08", ROM0[$0008] +; Please call using `rst memcpy_small` +; Copies c bytes of data from de to hl +MemcpySmall: + ld a, [de] + ld [hli], a + inc de + dec c + jr nz, MemcpySmall +EmptyFunc:: + ret + +SECTION "rst10", ROM0[$0010] +; Please call using `rst memset_small` +; Sets c bytes at hl with the value in a +MemsetSmall: + ld [hli], a + dec c + jr nz, MemsetSmall + ret + +SECTION "rst18", ROM0[$0017] +; Please do not call. Use `rst memset`, or, if absolutely needed, `call rst18`. +; Sets bc bytes at hl with the value in d +Memset: + ld a, d +; Please call using `rst memset` +; Sets bc bytes at hl with the value in a +rst18: + ld d, a + ld [hli], a + dec bc + ld a, b + or c + jr nz, Memset + ret + +SECTION "rst20", ROM0[$0020] +; Please call using `rst bankswitch` +; Properly switches to a ROM bank +; @param a The ROM bank to switch to +; NOTE: only switches the lower 8 bytes, the upper bit (for 512 banks) is not considered +ROMbankswitch: + ldh [hCurROMBank], a + ld [rROMB0], a + ret + +SECTION "rst28", ROM0[$0028] +; Please call using `rst call_hl` +; Jumps to hl. Use as a placeholder for `call hl`! +; Will error out if the target is in RAM +CallHL: + bit 7, h ; Prevent jumping into RAM (doesn't protec against returning to it, but hey :D) + jr nz, .err + jp hl +.err + jp HLJumpingError + +SECTION "rst30", ROM0[$0030] +; Please call using `rst wait_vblank` +; Waits for the VBlank interrupt +; Note: if the interrupt occurs without being waited for, it will skip performing some actions +WaitVBlank: + xor a + ldh [hVBlankFlag], a +.waitVBlank + halt + jr z, .waitVBlank + ret + +SECTION "rst38", ROM0[$0038] +; Please do not call +; Traps execution of the $FF byte (which serves as padding of the ROM) +rst38: + jp Rst38Error + + +SECTION "Interrupt vectors", ROM0[$0040] + +transfer_reg: MACRO + ldh a, [h\1] + ldh [r\1], a +ENDM + + ; VBlank + push af + transfer_reg LCDC + jp VBlankHandler + + ; LCD + push af + push bc + ldh a, [hScanlineFXIndex] + ld c, a + ld a, [c] ; Get port ID + jr LCDHandler + + ; Timer + reti + ds 7 + + ; Serial + reti + +; Fit in a 7-byte function, too! + +; Jumps immediately to de, no questions asked (except RAM targets?). +CallDE:: + push de + bit 7, d + ret z + jp DEJumpingError + + ; Joypad + reti + + +LCDHandler: + ld b, a ; Save port ID for later + inc c + inc c + ld a, [c] ; Get next effect's scanline + dec a ; Compensate for processing time + ldh [rLYC], a ; Get set up (hopefully this should reset the interrupt trigger line) + ld a, c ; Point to next effect's port ID + inc a + ldh [hScanlineFXIndex], a + dec c + ; Wait a bit to write during HBlank, to avoid gfx artifacts + ld a, 4 +.waitMode0 + dec a + jr nz, .waitMode0 + + ; Check if we're trying to write to P1 ($FF*00*) + ld a, b + and a ; Note: `and $7F` can be used instead to have control on bit 7 (if ever needed) + ; Perform common ops + ld a, [c] ; Get value + ; rP1 is hardwired to instead perform textbox ops + jr nz, .notTextbox + + ldh [rSCY], a ; Store value, which is actually for SCY (dat plot twist, eh?) + xor a + ldh [rSCX], a + ld c, LOW(rLCDC) + ldh a, [hLCDC] ; Retrieve LCDC value + and ~(LCDCF_WINON | LCDCF_BG8000 | LCDCF_OBJON) + or LCDCF_BG9C00 + ; Note: this is scrapped support for sprites on the textbox + ; It was initially planned for JP diacritics. + ; If for whatever reason, you need to re-activate this feature... + ; ...uncomment this, and remove "LCDCF_OBJON" from above. + ; + ; ld [c], a ; Apply LCDC modification + ; ; Perform OAM DMA to get textbox's sprites + ; ; Luckily, sprites are hidden during DMA + ; ; Also no sprites should be present on the textbox 1st row, hiding our trickery >:P + ; ld a, HIGH(wTextboxOAM) + ; call hOAMDMA + ; ; Reload OAM on next frame + ; ldh a, [hCurrentOAMBuffer] + ; ldh [hOAMBuffer], a + ; jr .onlyOneEffect + +.notTextbox + ld c, b ; Retrieve port + res 7, c + ld [c], a ; Apply FX + bit 7, b + jr z, .onlyOneEffect + ldh a, [hSecondFXAddr] + ld c, a + ldh a, [hSecondFXValue] + ld [$ff00+c], a +.onlyOneEffect + pop bc + pop af + reti + + +SECTION "VBlank handler", ROM0 + +VBlankHandler: + push bc + + ; ============= Here are things that need to be updated, even on lag frames ============== + + ; Update IO from HRAM shadow + transfer_reg SCY + transfer_reg SCX + transfer_reg WY + transfer_reg WX + + + ldh a, [hWhichScanlineBuffer] + ld c, a + ; Get first effect's scanline + ld a, [$ff00+c] + dec a ; Compensate for the processing time + ; NOTE: this assumes no effect is scheduled on line 0 + ; This should never happen; instead, use the HRAM shadow regs (hSCY, etc.) + ldh [rLYC], a + inc c + ld a, c + ldh [hScanlineFXIndex], a + + + ; Update OAM if needed + ; Do this last so it will go through even without time + ; This will simply cause sprites to not be displayed on the top few scanlines, but that's not as bad as palettes not loading at all, huh? + ldh a, [hOAMBufferHigh] + and a + jr z, .dontUpdateOAM + ld b, a + ; Reset OAM buffer high vect + xor a + ldh [hOAMBufferHigh], a + ; Perform DMA as specified + ld a, b + call hOAMDMA +.dontUpdateOAM + + + ; ============== In case of lag, don't update further, to avoid breaking stuff =============== + + ldh a, [hVBlankFlag] + and a + jr nz, .lagFrame + + + ; Poll joypad and update regs + + ld c, LOW(rP1) + ld a, $20 ; Select D-pad + ld [$ff00+c], a +REPT 6 + ld a, [$ff00+c] +ENDR + or $F0 ; Set 4 upper bits (give them consistency) + ld b, a + + ; Filter impossible D-pad combinations + and $0C ; Filter only Down and Up + ld a, b + jr nz, .notUpAndDown + or $0C ; If both are pressed, "unpress" them + ld b, a +.notUpAndDown + and $03 ; Filter only Left and Right + jr nz, .notLeftAndRight + ld a, b + or $03 ; If both are pressed, "unpress" them + ld b, a +.notLeftAndRight + swap b ; Put D-pad buttons in upper nibble + + ld a, $10 ; Select buttons + ld [$ff00+c], a +REPT 6 + ld a, [$ff00+c] +ENDR + ; On SsAB held, soft-reset + and $0F + jp z, Reset + + or $F0 ; Set 4 upper bits + xor b ; Mix with D-pad bits, and invert all bits (such that pressed=1) thanks to "or $F0" + ld b, a + + ldh a, [hHeldButtons] + cpl + and b + ldh [hPressedButtons], a + + ld a, b + ldh [hHeldButtons], a + + ; Release joypad + ld a, $30 + ld [$ff00+c], a + + pop bc + pop af + + + ; The main code was waiting for VBlank, so tell it it's OK by resetting Z + xor a + inc a ; Clear Z + ldh [hVBlankFlag], a ; Mark VBlank as ACK'd + reti + + +.lagFrame + pop bc + pop af + reti diff --git a/src/macros/macros.asm b/src/macros/macros.asm new file mode 100644 index 0000000..5cecba4 --- /dev/null +++ b/src/macros/macros.asm @@ -0,0 +1,5 @@ + +INCLUDE "macros/structs.asm" +INCLUDE "macros/struct_decls.asm" +INCLUDE "macros/pseudo_instr.asm" +INCLUDE "macros/misc.asm" diff --git a/src/macros/misc.asm b/src/macros/misc.asm new file mode 100644 index 0000000..b2dfbed --- /dev/null +++ b/src/macros/misc.asm @@ -0,0 +1,127 @@ + +; dbr value, nb_times +; Writes nb_times consecutive bytes with value. +dbr: MACRO + REPT \2 + db \1 + ENDR +ENDM + +; dwr value, nb_times +; Writes nb_times consecutive words with value. +dwr: MACRO + REPT \2 + dw \1 + ENDR +ENDM + +; db's everything given to it, and terminates with a NUL +; For strings, obviously. +dstr: MACRO + REPT _NARG + db \1 + shift + ENDR + db 0 +ENDM + +; Places a sprite's data, but with screen coords instead of OAM coords +dspr: MACRO + db LOW(\1 + 16) + db LOW(\2 + 8) + db \3 + db \4 +ENDM + +; dwcoord y, x, base +dwcoord: MACRO + dw (\1) * SCRN_VX_B + (\2) + (\3) +ENDM + +; dptr symbol +; Places a symbol's bank and ptr +; +; dptr symbol_b, symbol_p +; Places a symbol's bank and another's ptr +; Useful for expressions: `dptr Label, Label+1` +dptr: MACRO + db BANK(\1) + IF _NARG < 2 + dw \1 + ELSE + dw \2 + ENDC +ENDM + + +lda: MACRO + IF \1 == 0 + xor a + ELSE + ld a, \1 + ENDC +ENDM + +lb: MACRO + ld \1, ((\2) << 8) | (\3) +ENDM + +ln: MACRO +REGISTER\@ = \1 +VALUE\@ = 0 +INDEX\@ = 1 + REPT _NARG + shift +INDEX\@ = INDEX\@ + 1 + IF \1 > $0F + FAIL "Argument {INDEX} to `ln` must be a 4-bit value!" + ENDC +VALUE\@ = VALUE\@ << 8 | \1 + ENDR + + ld REGISTER\@, VALUE\@ + +PURGE REGISTER\@ +PURGE VALUE\@ +PURGE INDEX\@ +ENDM + +; ldcoord reg16, y, x, base +ldcoord: MACRO + IF "\1" == "bc" + db $01 + ELIF "\1" == "de" + db $11 + ELIF "\1" == "hl" + db $21 + ELIF "\1" == "sp" + db $31 + ELSE + FAIL "Invalid 1st operand to ldcoord, \1 is not a 16-bit register" + ENDC + dwcoord \2, \3, \4 +ENDM + + +; sgb_packet packet_type, nb_packets, +sgb_packet: MACRO +SGBPacket: + db (\1 << 3) | (\2) +NB_REPT = _NARG + (-2) + + REPT NB_REPT + SHIFT + db \2 + ENDR + PURGE NB_REPT + +SGBPacketEnd: +PACKET_SIZE = SGBPacketEnd - SGBPacket + IF PACKET_SIZE != SGB_PACKET_SIZE + dbr 0, SGB_PACKET_SIZE - PACKET_SIZE + ENDC + + PURGE SGBPacket + PURGE SGBPacketEnd + PURGE PACKET_SIZE +ENDM diff --git a/src/macros/pseudo_instr.asm b/src/macros/pseudo_instr.asm new file mode 100644 index 0000000..5514816 --- /dev/null +++ b/src/macros/pseudo_instr.asm @@ -0,0 +1,16 @@ + +wait: MACRO + ld c, \1 +.delay\@ + rst wait_vblank + dec c + jr nz, .delay\@ +ENDM + + +wait_vram: MACRO +.waitVRAM\@ + ldh a, [rSTAT] + and STATF_BUSY + jr nz, .waitVRAM\@ +ENDM diff --git a/src/macros/struct_decls.asm b/src/macros/struct_decls.asm new file mode 100644 index 0000000..e69de29 diff --git a/src/macros/structs.asm b/src/macros/structs.asm new file mode 100644 index 0000000..fa64dfb --- /dev/null +++ b/src/macros/structs.asm @@ -0,0 +1,156 @@ + +; MIT License +; +; Copyright (c) 2018 Eldred Habert +; Originally hosted at https://github.com/ISSOtm/rgbds-structs +; +; 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: +; +; The above copyright notice and this permission notice shall be included in all +; copies or substantial portions of the Software. +; +; 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. + + +; struct struct_name +; Begins a struct declaration +struct: MACRO + IF DEF(NB_FIELDS) + FAIL "Please close struct definitions using `end_struct`" + ENDC + +STRUCT_NAME equs "\1" + +NB_FIELDS = 0 + RSRESET +ENDM + +; end_struct +; Ends a struct declaration +end_struct: MACRO +; Set nb of fields +STRUCT_NB_FIELDS equs "{STRUCT_NAME}_nb_fields" +STRUCT_NB_FIELDS = NB_FIELDS + PURGE STRUCT_NB_FIELDS + +; Set size of struct +STRUCT_SIZEOF equs "sizeof_{STRUCT_NAME}" +STRUCT_SIZEOF RB 0 + PURGE STRUCT_SIZEOF + + PURGE NB_FIELDS + PURGE STRUCT_NAME +ENDM + + +; field_name_from_id field_id +; For internal use, please do not use externally +field_name_from_id: MACRO +FIELD_ID_STR equs "{\1}" +STRUCT_FIELD equs STRCAT("{STRUCT_NAME}_field", STRSUB("{FIELD_ID_STR}", 2, STRLEN("{FIELD_ID_STR}") - 1)) +STRUCT_FIELD_NAME equs "{STRUCT_FIELD}_name" +STRUCT_FIELD_SIZE equs "{STRUCT_FIELD}_size" +ENDM + + +; new_field nb_elems, rs_type, field_name +; For internal use, please do not use externally +new_field: MACRO + IF !DEF(STRUCT_NAME) || !DEF(NB_FIELDS) + FAIL "Please start defining a struct, using `define_struct`" + ENDC + + field_name_from_id NB_FIELDS +; Set field name +STRUCT_FIELD_NAME equs "\"\3\"" + PURGE STRUCT_FIELD_NAME + +; Set field offset +STRUCT_FIELD \2 (\1) +; Alias this in a human-comprehensive manner +STRUCT_FIELD_NAME equs "{STRUCT_NAME}_\3" +STRUCT_FIELD_NAME = STRUCT_FIELD + +; Calculate field size +CURRENT_RS RB 0 +STRUCT_FIELD_SIZE = CURRENT_RS - STRUCT_FIELD + + PURGE FIELD_ID_STR + PURGE STRUCT_FIELD + PURGE STRUCT_FIELD_NAME + PURGE STRUCT_FIELD_SIZE + PURGE CURRENT_RS + +NB_FIELDS = NB_FIELDS + 1 +ENDM + +; bytes nb_bytes, field_name +; Defines a field of N bytes +bytes: MACRO + new_field \1, RB, \2 +ENDM + +; words nb_words, field_name +; Defines a field of N*2 bytes +words: MACRO + new_field \1, RW, \2 +ENDM + +; longs nb_longs, field_name +; Defines a field of N*4 bytes +longs: MACRO + new_field \1, RL, \2 +ENDM + + +; dstruct struct_type, var_name +; Allocates space for a struct in memory (primarily RAM) +dstruct: MACRO +NB_FIELDS equs "\1_nb_fields" + IF !DEF(NB_FIELDS) + FAIL "Struct \1 isn't defined!" + ENDC +STRUCT_NAME equs "\1" ; Target this struct for `field_name_from_id` + +\2:: ; Declare the struct's root + +FIELD_ID = 0 + REPT NB_FIELDS + + field_name_from_id FIELD_ID +FIELD_NAME equs STRCAT("\2_", STRUCT_FIELD_NAME) +FIELD_NAME:: + ds STRUCT_FIELD_SIZE + + ; Clean up vars for next iteration + PURGE FIELD_ID_STR + PURGE STRUCT_FIELD + PURGE STRUCT_FIELD_NAME + PURGE STRUCT_FIELD_SIZE + PURGE FIELD_NAME + +FIELD_ID = FIELD_ID + 1 + ENDR + + + ; Define variable's properties from struct's +\2_nb_fields = NB_FIELDS +sizeof_\2 = sizeof_\1 + + + ; Clean up + PURGE NB_FIELDS + PURGE STRUCT_NAME + PURGE FIELD_ID +ENDM diff --git a/src/memory.asm b/src/memory.asm new file mode 100644 index 0000000..2732846 --- /dev/null +++ b/src/memory.asm @@ -0,0 +1,7 @@ +INCLUDE "constants.asm" +INCLUDE "macros.asm" + +INCLUDE "memory/vram.asm" +INCLUDE "memory/sram.asm" +INCLUDE "memory/wram.asm" +INCLUDE "memory/hram.asm" diff --git a/src/memory/hram.asm b/src/memory/hram.asm new file mode 100644 index 0000000..0d856d4 --- /dev/null +++ b/src/memory/hram.asm @@ -0,0 +1,85 @@ + +SECTION "HRAM", HRAM + +; The OAM DMA routine +hOAMDMA:: + ds 8 ; OAMDMAEnd - OAMDMA + + +; Currently-loaded ROM bank, useful to save back (eg. during ints) +hCurROMBank:: + db + + +; Used by the PB16 decompressor +pb16_byte0:: + db + + +; Place variables that need to be zero-cleared on init (and soft-reset) below +hClearStart:: + + +; Used to let VBlank know it need to ACK +; NOTE: VBlank doesn't preserve AF **on purpose** when this is set +; Thus, make sure to wait for Z set before continuing +hVBlankFlag:: + db + +; Values transferred to hw regs on VBlank +hLCDC:: + db +hSCY:: + db +hSCX:: + db +hWY:: + db +hWX:: + db +hBGP:: + db +hOBP0:: + db +hOBP1:: + db + + +; Low byte of the current scanline buffer +; Permits double-buffering +hWhichScanlineBuffer:: + db +; Low byte of byte read by STAT handler +; NO TOUCHY +hScanlineFXIndex:: + db + +; Scanline FX buffers (scanline, addr, value) +; Double-buffering used to prevent ract conditions +hScanlineFXBuffer1:: + ds 3 * 5 + 1 +hScanlineFXBuffer2:: + ds 3 * 5 + 1 + +; Addr/value pair to allow writing to 2 regs in the same scanline +hSecondFXAddr:: + db +hSecondFXValue:: + db + +hIsTextboxActive:: + db +hBackupScanlineFXBuffer:: + ds 3 * 5 + 1 + + +; Joypad regs +hHeldButtons:: + db +hPressedButtons:: + db + +; High byte of the shadow OAM buffer to be transferred +; Reset by the VBlank handler to signal transfer completion +hOAMBufferHigh:: + db diff --git a/src/memory/sram.asm b/src/memory/sram.asm new file mode 100644 index 0000000..e69de29 diff --git a/src/memory/vram.asm b/src/memory/vram.asm new file mode 100644 index 0000000..f5b24c8 --- /dev/null +++ b/src/memory/vram.asm @@ -0,0 +1,20 @@ + +SECTION "VRAM", VRAM[$8000] + + + ds $1000 + +; $9000 + +vBlankTile:: + ds $10 + + ds $7F0 + +; $9800 + + ds SCRN_VX_B * SCRN_VY_B ; $400 + +; $9C00 + + ds SCRN_VX_B * SCRN_VY_B ; $400 diff --git a/src/memory/wram.asm b/src/memory/wram.asm new file mode 100644 index 0000000..540f576 --- /dev/null +++ b/src/memory/wram.asm @@ -0,0 +1,40 @@ + +SECTION "Error handler memory", WRAM0 + +; ID of the error that occurred +wErrorType:: +; Once the ID has been used, this is re-used as a status, to route calls because stack space is available +wErrorDrawStatus:: +; The status is also used to determine which dump to print +wErrorWhichDump:: + db + +wErrorRegs:: +; Value of A when the handler is called +; Re-used as part of the reg dump +wErrorA:: +; Re-used to hold last frame's keys +wErrorHeldButtons:: + db ; a +; Re-used to hold the number of frames till the debugger is unlocked +wErrorFramesTillUnlock:: + db ; f + dw ; bc + dw ; de +wErrorHL:: + dw +wErrorSP:: + dw + + +SECTION "Shadow OAM", WRAM0,ALIGN[8] + +wShadowOAM:: + ds $A0 + + +SECTION "Stack", WRAM0[$E000 - STACK_SIZE] + +wStackTop:: + ds STACK_SIZE +wStackBottom:: diff --git a/src/res/build_date.asm b/src/res/build_date.asm new file mode 100644 index 0000000..452edab --- /dev/null +++ b/src/res/build_date.asm @@ -0,0 +1,7 @@ + +SECTION "Build date", ROM0 + + db "Built " +BuildDate:: +INCBIN "res/build.date" + db 0 diff --git a/src/tools/pb16.py b/src/tools/pb16.py new file mode 100644 index 0000000..71546c1 --- /dev/null +++ b/src/tools/pb16.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +""" +PB16 encoder +Copyright 2018 Damian Yerrick +[License: zlib] + +PB16 can be thought of as either of two things: + +- Run-length encoding (RLE) of two interleaved streams, with + unary-coded run lengths +- LZSS with a fixed distance of 2 and a fixed copy length of 1 + +Each packet represents 8 bytes of uncompressed data. The bits +of the first byte in a packet, ordered from MSB to LSB, encode +which of the following eight bytes repeat the byte 2 bytes back. +A 0 means a literal byte follows; a 1 means a repeat. + +It's similar to the PB8 codec that I've used for NES CHR data, +adapted to the interleaving of Game Boy and Super NES CHR data. +""" +import itertools +import sys +import argparse + +def ichunk(data, count): + """Turn an iterable into lists of a fixed length.""" + data = iter(data) + while True: + b = list(itertools.islice(data, count)) + if len(b) == 0: break + yield b + +def pb16(data): + """Compress an iterable of bytes into a generator of PB16 packets.""" + prev = [0, 0] + for unco in ichunk(data, 8): + # Pad the chunk to a multiple of 2 then to 8 bytes + if len(unco) < 8: + if len(unco) == 1: + unco.append(prev[1]) + elif len(unco) % 2: + unco.append(unco[-2]) + unco.extend(unco[-2:]*(8 - len(unco))) + + packet = bytearray(1) + for i, value in enumerate(unco): + if value == prev[i % 2]: + packet[0] |= 0x80 >> i + else: + packet.append(value) + prev[i % 2] = value + yield packet + +def parse_argv(argv): + p = argparse.ArgumentParser() + p.add_argument("infile") + p.add_argument("outfile") + return p.parse_args(argv[1:]) + +def main(argv=None): + args = parse_argv(argv or sys.argv) + with open(args.infile, "rb") as infp: + data = infp.read() + with open(args.outfile, "wb") as outfp: + outfp.writelines(pb16(data)) + +def test(): + tests = [ + () + ] + s = b"ABAHBHCHCECEFEFE" + print(b''.join(pb16(s)).hex()) + +if __name__=='__main__': + main() +## test()