@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2021 Daid | |||
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. |
@ -0,0 +1,26 @@ | |||
# Gameboy Software Development Kit | |||
This is the Gameboy software development kit. For mixed assembly and C programming. Note that this isn't a mature project yet. | |||
If you want to do C programming for the Gameboy, https://github.com/gbdk-2020/gbdk-2020 is much more mature. | |||
If you want to do assembly programming for the Gameboy, https://rgbds.gbdev.io/ is the goto toolchain. | |||
# But... but...? Why this then? | |||
This project has a few goals: | |||
* Thin/no abstractions. GBDK-2020 is an oddball of low level functions, badly named functions and high level functions with horrible performance and side effects. | |||
* Mixing of C code with ASM code, without subjecting yourself to the asxxxx syntax. | |||
* A different linker. By using the rgbds linker instead of the SDCC linker, a bunch of features that exernal tools provide on gbdk-2020 are standard. | |||
# Usage | |||
To use this, you need to have installed: | |||
* sdcc (version 4.1.0) | |||
* rgbds (version 0.5.1) | |||
* python (version 3.6 or newer) | |||
* A strong will, and a bit of crazy. | |||
See https://github.com/daid/gbsdk-template for a ready to use template of a project setup. |
@ -0,0 +1,18 @@ | |||
#ifndef GBSDK_ASSETS_H | |||
#define GBSDK_ASSETS_H | |||
#include <stdint.h> | |||
#define EXTERN_ASSET(var_name) \ | |||
extern const uint8_t var_name[]; \ | |||
extern const uint8_t var_name ## _end[] | |||
#define ASSET(var_name, filename) \ | |||
void __ ## var_name ## __() __naked { \ | |||
__asm__("_" #var_name "::"); \ | |||
__asm__(".incbin \"_build/assets/" filename "\""); \ | |||
__asm__("_" #var_name "_end::"); \ | |||
} EXTERN_ASSET(var_name) | |||
#endif//GBSDK_ASSETS_H |
@ -0,0 +1,6 @@ | |||
; Usage: asset label_name, "filename" | |||
MACRO asset | |||
\1:: | |||
INCBIN STRCAT("_build/assets/", \2) | |||
.end:: | |||
ENDM |
@ -0,0 +1,13 @@ | |||
#ifndef GBSDK_BANKING_H | |||
#define GBSDK_BANKING_H | |||
#include <stdint.h> | |||
extern __sfr current_bank; | |||
inline void switch_bank(bank_nr) { | |||
current_bank = bank_nr; | |||
*((uint8_t*)0x2000) = bank_nr; | |||
} | |||
#endif//LIB_BANKING_H |
@ -0,0 +1,494 @@ | |||
#ifndef GBSDK_HARDWARE_H | |||
#define GBSDK_HARDWARE_H | |||
// Register for reading joy pad info. (R/W) | |||
static volatile __sfr __at(0x00) rP1; | |||
#define P1_5 0b00100000 // P15 out port, set to 0 to get buttons | |||
#define P1_4 0b00010000 // P14 out port, set to 0 to get dpad | |||
#define P1_3 0b00001000 // P13 in port | |||
#define P1_2 0b00000100 // P12 in port | |||
#define P1_1 0b00000010 // P11 in port | |||
#define P1_0 0b00000001 // P10 in port | |||
#define P1_GET_DPAD P1_5 | |||
#define P1_GET_BTN P1_4 | |||
#define P1_GET_NONE (P1_4 | P1_5) | |||
// Serial Transfer Data (R/W) | |||
static volatile __sfr __at(0x01) rSB; | |||
// Serial I/O Control (R/W) | |||
static volatile __sfr __at(0x02) rSC; | |||
// Divider register (R/W) | |||
static volatile __sfr __at(0x04) rDIV; | |||
// Timer counter (R/W) | |||
static volatile __sfr __at(0x05) rTIMA; | |||
// Timer modulo (R/W) | |||
static volatile __sfr __at(0x06) rTMA; | |||
// Timer control (R/W) | |||
static volatile __sfr __at(0x07) rTAC; | |||
#define TAC_START 0b00000100 | |||
#define TAC_STOP 0b00000000 | |||
#define TAC_4KHZ 0b00000000 | |||
#define TAC_16KHZ 0b00000011 | |||
#define TAC_65KHZ 0b00000010 | |||
#define TAC_262KHZ 0b00000001 | |||
// Interrupt Flag (R/W) | |||
static volatile __sfr __at(0x0F) rIF; | |||
// 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) | |||
static volatile __sfr __at(0x10) rNR10; | |||
#define rAUD1SWEEP rNR10 | |||
#define AUD1SWEEP_UP 0b00000000 | |||
#define AUD1SWEEP_DOWN 0b00001000 | |||
// 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) | |||
static volatile __sfr __at(0x11) rNR11; | |||
#define rAUD1LEN 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) | |||
static volatile __sfr __at(0x12) rNR12; | |||
#define rAUD1ENV rNR12 | |||
// AUD1LOW/NR13 ($FF13) | |||
// Frequency low byte (W) | |||
static volatile __sfr __at(0x13) rNR13; | |||
#define rAUD1LOW rNR13 | |||
// AUD1HIGH/NR14 ($FF14) | |||
// Frequency high byte (W) | |||
// | |||
// Bit 7 - Initial (when set, sound restarts) | |||
// Bit 6 - Counter/consecutive selection | |||
// Bit 2-0 - Frequency's higher 3 bits | |||
static volatile __sfr __at(0x14) rNR14; | |||
#define rAUD1HIGH rNR14 | |||
// AUD2LEN/NR21 ($FF16) | |||
// Sound Length; Wave Pattern Duty (R/W) | |||
// | |||
// see AUD1LEN for info | |||
static volatile __sfr __at(0x16) rNR21; | |||
#define rAUD2LEN rNR21 | |||
// AUD2ENV/NR22 ($FF17) | |||
// Envelope (R/W) | |||
// | |||
// see AUD1ENV for info | |||
static volatile __sfr __at(0x17) rNR22; | |||
#define rAUD2ENV rNR22 | |||
// AUD2LOW/NR23 ($FF18) | |||
// Frequency low byte (W) | |||
static volatile __sfr __at(0x18) rNR23; | |||
#define rAUD2LOW rNR23 | |||
// AUD2HIGH/NR24 ($FF19) | |||
// Frequency high byte (W) | |||
// | |||
// see AUD1HIGH for info | |||
static volatile __sfr __at(0x19) rNR24; | |||
#define rAUD2HIGH rNR24 | |||
// AUD3ENA/NR30 ($FF1A) | |||
// Sound on/off (R/W) | |||
// | |||
// Bit 7 - Sound ON/OFF (1=ON,0=OFF) | |||
static volatile __sfr __at(0x1A) rNR30; | |||
#define rAUD3ENA rNR30 | |||
// AUD3LEN/NR31 ($FF1B) | |||
// Sound length (R/W) | |||
// | |||
// Bit 7-0 - Sound length | |||
static volatile __sfr __at(0x1B) rNR31; | |||
#define rAUD3LEN 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 | |||
static volatile __sfr __at(0x1C) rNR32; | |||
#define rAUD3LEVEL rNR32 | |||
// AUD3LOW/NR33 ($FF1D) | |||
// Frequency low byte (W) | |||
// | |||
// see AUD1LOW for info | |||
static volatile __sfr __at(0x1D) rNR33; | |||
#define rAUD3LOW rNR33 | |||
// AUD3HIGH/NR34 ($FF1E) | |||
// Frequency high byte (W) | |||
// | |||
// see AUD1HIGH for info | |||
static volatile __sfr __at(0x1E) rNR34; | |||
#define rAUD3HIGH rNR34 | |||
// AUD4LEN/NR41 ($FF20) | |||
// Sound length (R/W) | |||
// | |||
// Bit 5-0 - Sound length data (# 0-63) | |||
static volatile __sfr __at(0x20) rNR41; | |||
#define rAUD4LEN rNR41 | |||
// AUD4ENV/NR42 ($FF21) | |||
// Envelope (R/W) | |||
// | |||
// see AUD1ENV for info | |||
static volatile __sfr __at(0x21) rNR42; | |||
#define rAUD4ENV rNR42 | |||
// AUD4POLY/NR43 ($FF22) | |||
// Polynomial counter (R/W) | |||
// | |||
// Bit 7-4 - Selection of the shift clock frequency of the (scf) | |||
// polynomial counter (0000-1101) | |||
// freq=drf*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 (f=4.194304 Mhz) | |||
static volatile __sfr __at(0x22) rNR43; | |||
#define rAUD4POLY rNR43 | |||
// AUD4GO/NR44 ($FF23) | |||
// | |||
// Bit 7 - Inital | |||
// Bit 6 - Counter/consecutive selection | |||
static volatile __sfr __at(0x23) rNR44; | |||
#define rAUD4GO rNR44 | |||
// AUDVOL/NR50 ($FF24) | |||
// Channel control / ON-OFF / Volume (R/W) | |||
// | |||
// Bit 7 - Vin->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) | |||
static volatile __sfr __at(0x24) rNR50; | |||
#define rAUDVOL rNR50 | |||
#define AUDVOL_VIN_LEFT 0b10000000 // SO2 | |||
#define AUDVOL_VIN_RIGHT 0b00001000 // SO1 | |||
// 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 | |||
static volatile __sfr __at(0x25) rNR51; | |||
#define rAUDTERM rNR51 | |||
// SO2 | |||
#define AUDTERM_4_LEFT 0b10000000 | |||
#define AUDTERM_3_LEFT 0b01000000 | |||
#define AUDTERM_2_LEFT 0b00100000 | |||
#define AUDTERM_1_LEFT 0b00010000 | |||
// SO1 | |||
#define AUDTERM_4_RIGHT 0b00001000 | |||
#define AUDTERM_3_RIGHT 0b00000100 | |||
#define AUDTERM_2_RIGHT 0b00000010 | |||
#define AUDTERM_1_RIGHT 0b00000001 | |||
// 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 (read only) | |||
// Bit 2 - Sound 3 ON flag (read only) | |||
// Bit 1 - Sound 2 ON flag (read only) | |||
// Bit 0 - Sound 1 ON flag (read only) | |||
static volatile __sfr __at(0x26) rNR52; | |||
#define rAUDENA rNR52 | |||
#define AUDENA_ON 0b10000000 | |||
#define AUDENA_OFF 0b00000000 // sets all audio regs to 0! | |||
// LCDC ($FF40) | |||
// LCD Control (R/W) | |||
static volatile __sfr __at(0x40) rLCDC; | |||
#define LCDC_OFF 0b00000000 // LCD Control Operation | |||
#define LCDC_ON 0b10000000 // LCD Control Operation | |||
#define LCDC_WIN9800 0b00000000 // Window Tile Map Display Select | |||
#define LCDC_WIN9C00 0b01000000 // Window Tile Map Display Select | |||
#define LCDC_WINOFF 0b00000000 // Window Display | |||
#define LCDC_WINON 0b00100000 // Window Display | |||
#define LCDC_BG8800 0b00000000 // BG & Window Tile Data Select | |||
#define LCDC_BG8000 0b00010000 // BG & Window Tile Data Select | |||
#define LCDC_BG9800 0b00000000 // BG Tile Map Display Select | |||
#define LCDC_BG9C00 0b00001000 // BG Tile Map Display Select | |||
#define LCDC_OBJ8 0b00000000 // OBJ Construction | |||
#define LCDC_OBJ16 0b00000100 // OBJ Construction | |||
#define LCDC_OBJOFF 0b00000000 // OBJ Display | |||
#define LCDC_OBJON 0b00000010 // OBJ Display | |||
#define LCDC_BGOFF 0b00000000 // BG Display | |||
#define LCDC_BGON 0b00000001 // BG Display | |||
// STAT ($FF41) | |||
// LCDC Status (R/W) | |||
static volatile __sfr __at(0x41) rSTAT; | |||
#define STAT_LYC 0b01000000 // LYC=LY Coincidence (Selectable) | |||
#define STAT_MODE10 0b00100000 // Mode 10 | |||
#define STAT_MODE01 0b00010000 // Mode 01 (V-Blank) | |||
#define STAT_MODE00 0b00001000 // Mode 00 (H-Blank) | |||
#define STAT_LYCF 0b00000100 // Coincidence Flag | |||
#define STAT_HBL 0b00000000 // H-Blank | |||
#define STAT_VBL 0b00000001 // V-Blank | |||
#define STAT_OAM 0b00000010 // OAM-RAM is used by system | |||
#define STAT_LCD 0b00000011 // Both OAM and VRAM used by system | |||
#define STAT_BUSY 0b00000010 // When set, VRAM access is unsafe | |||
// SCY ($FF42) | |||
// Scroll Y (R/W) | |||
static volatile __sfr __at(0x42) rSCY; | |||
// SCX ($FF43) | |||
// Scroll X (R/W) | |||
static volatile __sfr __at(0x43) rSCX; | |||
// LY ($FF44) | |||
// LCDC Y-Coordinate (R) | |||
// | |||
// Values range from 0->153. 144->153 is the VBlank period. | |||
static volatile __sfr __at(0x44) rLY; | |||
// LYC ($FF45) | |||
// LY Compare (R/W) | |||
// | |||
// When LY==LYC, STATF_LYCF will be set in STAT | |||
static volatile __sfr __at(0x45) rLYC; | |||
// DMA ($FF46) | |||
// DMA Transfer and Start Address (W) | |||
static volatile __sfr __at(0x46) rDMA; | |||
// 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 | |||
static volatile __sfr __at(0x47) rBGP; | |||
// OBP0 ($FF48) | |||
// Object Palette 0 Data (W) | |||
// | |||
// See BGP for info | |||
static volatile __sfr __at(0x48) rOBP0; | |||
// OBP1 ($FF49) | |||
// Object Palette 1 Data (W) | |||
// | |||
// See BGP for info | |||
static volatile __sfr __at(0x49) rOBP1; | |||
// WY ($FF4A) | |||
// Window Y Position (R/W) | |||
// | |||
// 0 <= WY <= 143 | |||
// When WY = 0, the window is displayed from the top edge of the LCD screen. | |||
static volatile __sfr __at(0x4A) rWY; | |||
// WX ($FF4B) | |||
// Window X Position (R/W) | |||
// | |||
// 7 <= WX <= 166 | |||
// When WX = 7, the window is displayed from the left edge of the LCD screen. | |||
// Values of 0-6 and 166 are unreliable due to hardware bugs. | |||
static volatile __sfr __at(0x4B) rWX; | |||
#if CGB | |||
// SPEED ($FF4D) | |||
// Select CPU Speed (R/W) | |||
static volatile __sfr __at(0x4D) rKEY1; | |||
#define rSPD rKEY1 | |||
#define KEY1_DBLSPEED 0b10000000 // 0=Normal Speed, 1=Double Speed (R) | |||
#define KEY1_PREPARE 0b00000001 // 0=No, 1=Prepare (R/W) | |||
// VBK ($FF4F) | |||
// Select Video RAM Bank (R/W) | |||
// | |||
// Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) | |||
static volatile __sfr __at(0x4F) rVBK; | |||
// HDMA1 ($FF51) | |||
// High byte for Horizontal Blanking/General Purpose DMA source address (W) | |||
static volatile __sfr __at(0x51) rHDMA1; | |||
// HDMA2 ($FF52) | |||
// Low byte for Horizontal Blanking/General Purpose DMA source address (W) | |||
static volatile __sfr __at(0x52) rHDMA2; | |||
// HDMA3 ($FF53) | |||
// High byte for Horizontal Blanking/General Purpose DMA destination address (W) | |||
static volatile __sfr __at(0x53) rHDMA3; | |||
// HDMA4 ($FF54) | |||
// Low byte for Horizontal Blanking/General Purpose DMA destination address (W) | |||
static volatile __sfr __at(0x54) rHDMA4; | |||
// HDMA5 ($FF55) | |||
// Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) | |||
static volatile __sfr __at(0x55) rHDMA5; | |||
#define HDMA5_MODE_GP 0b00000000 // General Purpose DMA (W) | |||
#define HDMA5_MODE_HBL 0b10000000 // HBlank DMA (W) | |||
// Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete | |||
#define HDMA5_BUSY 0b10000000 // 0=Busy (DMA still in progress), 1=Transfer complete (R) | |||
// RP ($FF56) | |||
// Infrared Communications Port (R/W) | |||
static volatile __sfr __at(0x56) rRP; | |||
#define RP_ENREAD 0b11000000 | |||
#define RP_DATAIN 0b00000010 // 0=Receiving IR Signal, 1=Normal | |||
#define RP_WRITE_HI 0b00000001 | |||
#define RP_WRITE_LO 0b00000000 | |||
// BCPS ($FF68) | |||
// Background Color Palette Specification (R/W) | |||
static volatile __sfr __at(0x68) rBCPS; | |||
#define BCPS_AUTOINC 0b10000000 // Auto Increment (0=Disabled, 1=Increment after Writing) | |||
// BCPD ($FF69) | |||
// Background Color Palette Data (R/W) | |||
static volatile __sfr __at(0x69) rBCPD; | |||
// OCPS ($FF6A) | |||
// Object Color Palette Specification (R/W) | |||
static volatile __sfr __at(0x6A) rOCPS; | |||
#define OCPS_AUTOINC 0b10000000 // Auto Increment (0=Disabled, 1=Increment after Writing) | |||
// OCPD ($FF6B) | |||
// Object Color Palette Data (R/W) | |||
static volatile __sfr __at(0x6B) rOCPD; | |||
// SMBK/SVBK ($FF70) | |||
// Select Main RAM Bank (R/W) | |||
// | |||
// Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) | |||
static volatile __sfr __at(0x70) rSVBK; | |||
#define rSMBK rSVBK | |||
// PCM12 ($FF76) | |||
// Sound channel 1&2 PCM amplitude (R) | |||
// | |||
// Bit 7-4 - Copy of sound channel 2's PCM amplitude | |||
// Bit 3-0 - Copy of sound channel 1's PCM amplitude | |||
static volatile __sfr __at(0x76) rPCM12; | |||
// PCM34 ($FF77) | |||
// Sound channel 3&4 PCM amplitude (R) | |||
// | |||
// Bit 7-4 - Copy of sound channel 4's PCM amplitude | |||
// Bit 3-0 - Copy of sound channel 3's PCM amplitude | |||
static volatile __sfr __at(0x77) rPCM34; | |||
#endif //CGB | |||
// IE ($FFFF) | |||
// Interrupt Enable (R/W) | |||
static volatile __sfr __at(0xFF) rIE; | |||
#define IE_HILO 0b00010000 // Transition from High to Low of Pin number P10-P13 | |||
#define IE_SERIAL 0b00001000 // Serial I/O transfer end | |||
#define IE_TIMER 0b00000100 // Timer Overflow | |||
#define IE_LCDC 0b00000010 // LCDC (see STAT) | |||
#define IE_VBLANK 0b00000001 // V-Blank | |||
/*************************************************************************** | |||
* | |||
* Flags common to multiple sound channels | |||
* | |||
***************************************************************************/ | |||
// Square wave duty cycle | |||
// | |||
// Can be used with AUD1LEN and AUD2LEN | |||
// See AUD1LEN for more info | |||
#define AUDLEN_DUTY_12_5 0b00000000 // 12.5% | |||
#define AUDLEN_DUTY_25 0b01000000 // 25% | |||
#define AUDLEN_DUTY_50 0b10000000 // 50% | |||
#define AUDLEN_DUTY_75 0b11000000 // 75% | |||
// Audio envelope flags | |||
// | |||
// Can be used with AUD1ENV, AUD2ENV, AUD4ENV | |||
// See AUD1ENV for more info | |||
#define AUDENV_UP 0b00001000 | |||
#define AUDENV_DOWN 0b00000000 | |||
// Audio trigger flags | |||
// | |||
// Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH | |||
// See AUD1HIGH for more info | |||
#define AUDHIGH_RESTART 0b10000000 | |||
#define AUDHIGH_LENGTH_ON 0b01000000 | |||
#define AUDHIGH_LENGTH_OFF 0b00000000 | |||
// Shared bits between the OAM attributes and the CGB background attributes. | |||
#define ATTR_PRI 0b10000000 | |||
#define ATTR_YFLIP 0b01000000 | |||
#define ATTR_XFLIP 0b00100000 | |||
#define ATTR_PAL0 0b00000000 | |||
#define ATTR_PAL1 0b00010000 | |||
#if CGB | |||
#define ATTR_BANK0 0b00000000 | |||
#define ATTR_BANK1 0b00001000 | |||
#define ATTR_PALMASK 0b00000111 | |||
#endif | |||
// Defines for specific instructions | |||
#define HALT() __asm__("halt") | |||
#define DISABLE_INTERRUPTS() __asm__("di") | |||
#define ENABLE_INTERRUPTS() __asm__("ei") | |||
#endif |
@ -0,0 +1,913 @@ | |||
;* | |||
;* 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) | |||
;* Rev 2.7 - 19-Jan-19 : Added rPCMXX (ISSOtm) | |||
;* Rev 2.8 - 03-Feb-19 : Added audio registers flags (�lvaro Cuesta) | |||
;* Rev 2.9 - 28-Feb-20 : Added utility rP1 constants | |||
;* Rev 3.0 - 27-Aug-20 : Register ordering, byte-based sizes, OAM additions, general cleanup (Blitter Object) | |||
; 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 > 3.0 ;PUT REVISION NUMBER HERE | |||
WARN "Version \1 or later of 'hardware.inc' is required." | |||
ENDC | |||
ENDM | |||
_VRAM EQU $8000 ; $8000->$9FFF | |||
_VRAM8000 EQU _VRAM | |||
_VRAM8800 EQU _VRAM+$800 | |||
_VRAM9000 EQU _VRAM+$1000 | |||
_SCRN0 EQU $9800 ; $9800->$9BFF | |||
_SCRN1 EQU $9C00 ; $9C00->$9FFF | |||
_SRAM EQU $A000 ; $A000->$BFFF | |||
_RAM EQU $C000 ; $C000->$CFFF / $C000->$DFFF | |||
_RAMBANK EQU $D000 ; $D000->$DFFF | |||
_OAMRAM EQU $FE00 ; $FE00->$FE9F | |||
_IO EQU $FF00 ; $FF00->$FF7F,$FFFF | |||
_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) | |||
;*************************************************************************** | |||
;* | |||
;* Custom registers | |||
;* | |||
;*************************************************************************** | |||
; -- | |||
; -- P1 ($FF00) | |||
; -- Register for reading joy pad info. (R/W) | |||
; -- | |||
rP1 EQU $FF00 | |||
P1F_5 EQU %00100000 ; P15 out port, set to 0 to get buttons | |||
P1F_4 EQU %00010000 ; P14 out port, set to 0 to get dpad | |||
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 | |||
P1F_GET_DPAD EQU P1F_5 | |||
P1F_GET_BTN EQU P1F_4 | |||
P1F_GET_NONE EQU P1F_4 | P1F_5 | |||
; -- | |||
; -- 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 | |||
; -- | |||
; -- 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 | |||
AUD1SWEEP_UP EQU %00000000 | |||
AUD1SWEEP_DOWN EQU %00001000 | |||
; -- | |||
; -- 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 low byte (W) | |||
; -- | |||
rNR13 EQU $FF13 | |||
rAUD1LOW EQU rNR13 | |||
; -- | |||
; -- AUD1HIGH/NR14 ($FF14) | |||
; -- Frequency high byte (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 | |||
; -- | |||
; -- 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 low byte (W) | |||
; -- | |||
rNR23 EQU $FF18 | |||
rAUD2LOW EQU rNR23 | |||
; -- | |||
; -- AUD2HIGH/NR24 ($FF19) | |||
; -- Frequency high byte (W) | |||
; -- | |||
; -- see AUD1HIGH for info | |||
; -- | |||
rNR24 EQU $FF19 | |||
rAUD2HIGH EQU rNR24 | |||
; -- | |||
; -- AUD3ENA/NR30 ($FF1A) | |||
; -- Sound on/off (R/W) | |||
; -- | |||
; -- Bit 7 - Sound ON/OFF (1=ON,0=OFF) | |||
; -- | |||
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 low byte (W) | |||
; -- | |||
; -- see AUD1LOW for info | |||
; -- | |||
rNR33 EQU $FF1D | |||
rAUD3LOW EQU rNR33 | |||
; -- | |||
; -- AUD3HIGH/NR34 ($FF1E) | |||
; -- Frequency high byte (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) | |||
; -- freq=drf*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 (f=4.194304 Mhz) | |||
; -- | |||
rNR43 EQU $FF22 | |||
rAUD4POLY EQU rNR43 | |||
; -- | |||
; -- AUD4GO/NR44 ($FF23) | |||
; -- | |||
; -- Bit 7 - Inital | |||
; -- Bit 6 - Counter/consecutive selection | |||
; -- | |||
rNR44 EQU $FF23 | |||
rAUD4GO EQU rNR44 | |||
; -- | |||
; -- AUDVOL/NR50 ($FF24) | |||
; -- Channel control / ON-OFF / Volume (R/W) | |||
; -- | |||
; -- Bit 7 - Vin->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 | |||
AUDVOL_VIN_LEFT EQU %10000000 ; SO2 | |||
AUDVOL_VIN_RIGHT EQU %00001000 ; SO1 | |||
; -- | |||
; -- 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 | |||
; SO2 | |||
AUDTERM_4_LEFT EQU %10000000 | |||
AUDTERM_3_LEFT EQU %01000000 | |||
AUDTERM_2_LEFT EQU %00100000 | |||
AUDTERM_1_LEFT EQU %00010000 | |||
; SO1 | |||
AUDTERM_4_RIGHT EQU %00001000 | |||
AUDTERM_3_RIGHT EQU %00000100 | |||
AUDTERM_2_RIGHT EQU %00000010 | |||
AUDTERM_1_RIGHT EQU %00000001 | |||
; -- | |||
; -- 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 (read only) | |||
; -- Bit 2 - Sound 3 ON flag (read only) | |||
; -- Bit 1 - Sound 2 ON flag (read only) | |||
; -- Bit 0 - Sound 1 ON flag (read only) | |||
; -- | |||
rNR52 EQU $FF26 | |||
rAUDENA EQU rNR52 | |||
AUDENA_ON EQU %10000000 | |||
AUDENA_OFF EQU %00000000 ; sets all audio regs to 0! | |||
; -- | |||
; -- 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 ; LYC=LY 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_HBL EQU %00000000 ; H-Blank | |||
STATF_VBL 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 | |||
; -- | |||
; -- SCX ($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 LY==LYC, 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 <= WY <= 143 | |||
; -- When WY = 0, the window is displayed from the top edge of the LCD screen. | |||
; -- | |||
rWY EQU $FF4A | |||
; -- | |||
; -- WX ($FF4B) | |||
; -- Window X Position (R/W) | |||
; -- | |||
; -- 7 <= WX <= 166 | |||
; -- When WX = 7, the window is displayed from the left edge of the LCD screen. | |||
; -- Values of 0-6 and 166 are unreliable due to hardware bugs. | |||
; -- | |||
rWX EQU $FF4B | |||
; -- | |||
; -- SPEED ($FF4D) | |||
; -- Select CPU Speed (R/W) | |||
; -- | |||
rKEY1 EQU $FF4D | |||
rSPD EQU rKEY1 | |||
KEY1F_DBLSPEED EQU %10000000 ; 0=Normal Speed, 1=Double Speed (R) | |||
KEY1F_PREPARE EQU %00000001 ; 0=No, 1=Prepare (R/W) | |||
; -- | |||
; -- VBK ($FF4F) | |||
; -- Select Video RAM Bank (R/W) | |||
; -- | |||
; -- Bit 0 - Bank Specification (0: Specify Bank 0; 1: Specify Bank 1) | |||
; -- | |||
rVBK EQU $FF4F | |||
; -- | |||
; -- HDMA1 ($FF51) | |||
; -- High byte for Horizontal Blanking/General Purpose DMA source address (W) | |||
; -- CGB Mode Only | |||
; -- | |||
rHDMA1 EQU $FF51 | |||
; -- | |||
; -- HDMA2 ($FF52) | |||
; -- Low byte for Horizontal Blanking/General Purpose DMA source address (W) | |||
; -- CGB Mode Only | |||
; -- | |||
rHDMA2 EQU $FF52 | |||
; -- | |||
; -- HDMA3 ($FF53) | |||
; -- High byte for Horizontal Blanking/General Purpose DMA destination address (W) | |||
; -- CGB Mode Only | |||
; -- | |||
rHDMA3 EQU $FF53 | |||
; -- | |||
; -- HDMA4 ($FF54) | |||
; -- Low byte for Horizontal Blanking/General Purpose DMA destination address (W) | |||
; -- CGB Mode Only | |||
; -- | |||
rHDMA4 EQU $FF54 | |||
; -- | |||
; -- HDMA5 ($FF55) | |||
; -- Transfer length (in tiles minus 1)/mode/start for Horizontal Blanking, General Purpose DMA (R/W) | |||
; -- CGB Mode Only | |||
; -- | |||
rHDMA5 EQU $FF55 | |||
HDMA5F_MODE_GP EQU %00000000 ; General Purpose DMA (W) | |||
HDMA5F_MODE_HBL EQU %10000000 ; HBlank DMA (W) | |||
; -- Once DMA has started, use HDMA5F_BUSY to check when the transfer is complete | |||
HDMA5F_BUSY EQU %10000000 ; 0=Busy (DMA still in progress), 1=Transfer complete (R) | |||
; -- | |||
; -- RP ($FF56) | |||
; -- Infrared Communications Port (R/W) | |||
; -- CGB Mode Only | |||
; -- | |||
rRP EQU $FF56 | |||
RPF_ENREAD EQU %11000000 | |||
RPF_DATAIN EQU %00000010 ; 0=Receiving IR Signal, 1=Normal | |||
RPF_WRITE_HI EQU %00000001 | |||
RPF_WRITE_LO EQU %00000000 | |||
; -- | |||
; -- BCPS ($FF68) | |||
; -- Background Color Palette Specification (R/W) | |||
; -- | |||
rBCPS EQU $FF68 | |||
BCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) | |||
; -- | |||
; -- BCPD ($FF69) | |||
; -- Background Color Palette Data (R/W) | |||
; -- | |||
rBCPD EQU $FF69 | |||
; -- | |||
; -- OCPS ($FF6A) | |||
; -- Object Color Palette Specification (R/W) | |||
; -- | |||
rOCPS EQU $FF6A | |||
OCPSF_AUTOINC EQU %10000000 ; Auto Increment (0=Disabled, 1=Increment after Writing) | |||
; -- | |||
; -- OCPD ($FF6B) | |||
; -- Object Color Palette Data (R/W) | |||
; -- | |||
rOCPD EQU $FF6B | |||
; -- | |||
; -- SMBK/SVBK ($FF70) | |||
; -- Select Main RAM Bank (R/W) | |||
; -- | |||
; -- Bit 2-0 - Bank Specification (0,1: Specify Bank 1; 2-7: Specify Banks 2-7) | |||
; -- | |||
rSVBK EQU $FF70 | |||
rSMBK EQU rSVBK | |||
; -- | |||
; -- PCM12 ($FF76) | |||
; -- Sound channel 1&2 PCM amplitude (R) | |||
; -- | |||
; -- Bit 7-4 - Copy of sound channel 2's PCM amplitude | |||
; -- Bit 3-0 - Copy of sound channel 1's PCM amplitude | |||
; -- | |||
rPCM12 EQU $FF76 | |||
; -- | |||
; -- PCM34 ($FF77) | |||
; -- Sound channel 3&4 PCM amplitude (R) | |||
; -- | |||
; -- Bit 7-4 - Copy of sound channel 4's PCM amplitude | |||
; -- Bit 3-0 - Copy of sound channel 3's PCM amplitude | |||
; -- | |||
rPCM34 EQU $FF77 | |||
; -- | |||
; -- IE ($FFFF) | |||
; -- Interrupt Enable (R/W) | |||
; -- | |||
rIE EQU $FFFF | |||
IEF_HILO EQU %00010000 ; Transition from High to Low of Pin number P10-P13 | |||
IEF_SERIAL EQU %00001000 ; Serial I/O transfer end | |||
IEF_TIMER EQU %00000100 ; Timer Overflow | |||
IEF_LCDC EQU %00000010 ; LCDC (see STAT) | |||
IEF_VBLANK EQU %00000001 ; V-Blank | |||
;*************************************************************************** | |||
;* | |||
;* Flags common to multiple sound channels | |||
;* | |||
;*************************************************************************** | |||
; -- | |||
; -- Square wave duty cycle | |||
; -- | |||
; -- Can be used with AUD1LEN and AUD2LEN | |||
; -- See AUD1LEN for more info | |||
; -- | |||
AUDLEN_DUTY_12_5 EQU %00000000 ; 12.5% | |||
AUDLEN_DUTY_25 EQU %01000000 ; 25% | |||
AUDLEN_DUTY_50 EQU %10000000 ; 50% | |||
AUDLEN_DUTY_75 EQU %11000000 ; 75% | |||
; -- | |||
; -- Audio envelope flags | |||
; -- | |||
; -- Can be used with AUD1ENV, AUD2ENV, AUD4ENV | |||
; -- See AUD1ENV for more info | |||
; -- | |||
AUDENV_UP EQU %00001000 | |||
AUDENV_DOWN EQU %00000000 | |||
; -- | |||
; -- Audio trigger flags | |||
; -- | |||
; -- Can be used with AUD1HIGH, AUD2HIGH, AUD3HIGH | |||
; -- See AUD1HIGH for more info | |||
; -- | |||
AUDHIGH_RESTART EQU %10000000 | |||
AUDHIGH_LENGTH_ON EQU %01000000 | |||
AUDHIGH_LENGTH_OFF EQU %00000000 | |||
;*************************************************************************** | |||
;* | |||
;* CPU values on bootup (a=type, b=qualifier) | |||
;* | |||
;*************************************************************************** | |||
BOOTUP_A_DMG EQU $01 ; Dot Matrix Game | |||
BOOTUP_A_CGB EQU $11 ; Color GameBoy | |||
BOOTUP_A_MGB EQU $FF ; Mini GameBoy (Pocket GameBoy) | |||
; if a=BOOTUP_A_CGB, bit 0 in b can be checked to determine if real CGB or | |||
; other system running in GBC mode | |||
BOOTUP_B_CGB EQU %00000000 | |||
BOOTUP_B_AGB EQU %00000001 ; GBA, GBA SP, Game Boy Player, or New GBA SP | |||
;*************************************************************************** | |||
;* | |||
;* Cart related | |||
;* | |||
;*************************************************************************** | |||
; $0143 Color GameBoy compatibility code | |||
CART_COMPATIBLE_DMG EQU $00 | |||
CART_COMPATIBLE_DMG_GBC EQU $80 | |||
CART_COMPATIBLE_GBC EQU $C0 | |||
; $0146 GameBoy/Super GameBoy indicator | |||
CART_INDICATOR_GB EQU $00 | |||
CART_INDICATOR_SGB EQU $03 | |||
; $0147 Cartridge type | |||
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_MMM01 EQU $0B | |||
CART_ROM_MMM01_RAM EQU $0C | |||
CART_ROM_MMM01_RAM_BAT EQU $0D | |||
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_BANDAI_TAMA5 EQU $FD | |||
CART_ROM_HUDSON_HUC3 EQU $FE | |||
CART_ROM_HUDSON_HUC1 EQU $FF | |||
; $0148 ROM size | |||
; these are kilobytes | |||
CART_ROM_32KB EQU $00 ; 2 banks | |||
CART_ROM_64KB EQU $01 ; 4 banks | |||
CART_ROM_128KB EQU $02 ; 8 banks | |||
CART_ROM_256KB EQU $03 ; 16 banks | |||
CART_ROM_512KB EQU $04 ; 32 banks | |||
CART_ROM_1024KB EQU $05 ; 64 banks | |||
CART_ROM_2048KB EQU $06 ; 128 banks | |||
CART_ROM_4096KB EQU $07 ; 256 banks | |||
CART_ROM_8192KB EQU $08 ; 512 banks | |||
CART_ROM_1152KB EQU $52 ; 72 banks | |||
CART_ROM_1280KB EQU $53 ; 80 banks | |||
CART_ROM_1536KB EQU $54 ; 96 banks | |||
; $0149 SRAM size | |||
; these are kilobytes | |||
CART_SRAM_NONE EQU 0 | |||
CART_SRAM_2KB EQU 1 ; 1 incomplete bank | |||
CART_SRAM_8KB EQU 2 ; 1 bank | |||
CART_SRAM_32KB EQU 3 ; 4 banks | |||
CART_SRAM_128KB EQU 4 ; 16 banks | |||
CART_SRAM_ENABLE EQU $0A | |||
CART_SRAM_DISABLE EQU $00 | |||
; $014A Destination code | |||
CART_DEST_JAPANESE EQU $00 | |||
CART_DEST_NON_JAPANESE EQU $01 | |||
;*************************************************************************** | |||
;* | |||
;* 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 | |||
;*************************************************************************** | |||
;* | |||
;* OAM related | |||
;* | |||
;*************************************************************************** | |||
; OAM attributes | |||
; each entry in OAM RAM is 4 bytes (sizeof_OAM_ATTRS) | |||
RSRESET | |||
OAMA_Y RB 1 ; y pos | |||
OAMA_X RB 1 ; x pos | |||
OAMA_TILEID RB 1 ; tile id | |||
OAMA_FLAGS RB 1 ; flags (see below) | |||
sizeof_OAM_ATTRS RB 0 | |||
OAM_COUNT EQU 40 ; number of OAM entries in OAM RAM | |||
; 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) | |||
;* | |||
;* 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 |
@ -0,0 +1,20 @@ | |||
#ifndef GBSDK_JOYPAD_H | |||
#define GBSDK_JOYPAD_H | |||
#include <stdint.h> | |||
#define PAD_DOWN 0x80 | |||
#define PAD_UP 0x40 | |||
#define PAD_LEFT 0x20 | |||
#define PAD_RIGHT 0x10 | |||
#define PAD_START 0x08 | |||
#define PAD_SELECT 0x04 | |||
#define PAD_B 0x02 | |||
#define PAD_A 0x01 | |||
extern uint8_t joypad_state; | |||
extern uint8_t joypad_pressed; | |||
void joypad_update(void) __preserves_regs(b, c); | |||
#endif//GBSDK_JOYPAD_H |
@ -0,0 +1,19 @@ | |||
#ifndef GBSDK_OAM_H | |||
#define GBSDK_OAM_H | |||
#include <stdint.h> | |||
#include <sdk/hardware.h> | |||
struct oam_entry { | |||
uint8_t y; | |||
uint8_t x; | |||
uint8_t tile; | |||
uint8_t attr; | |||
}; | |||
extern struct oam_entry shadow_oam[40]; | |||
void oam_init(void) __preserves_regs(b); | |||
void oam_dma_copy(void) __preserves_regs(b, c, d, e, h, l); | |||
#endif//GBSDK_OAM_H |
@ -0,0 +1,39 @@ | |||
#ifndef GBSDK_SGB_H | |||
#define GBSDK_SGB_H | |||
#include <stdint.h> | |||
#if SGB | |||
#define SGB_PAL01 0x00 //Set SGB Palette 0 & 1 | |||
#define SGB_PAL23 0x01 //Set SGB Palette 2 & 3 | |||
#define SGB_PAL03 0x02 //Set SGB Palette 0 & 3 | |||
#define SGB_PAL12 0x03 //Set SGB Palette 1 & 2 | |||
#define SGB_ATTR_BLK 0x04 //"Block" Area Designation Mode | |||
#define SGB_ATTR_LIN 0x05 //"Line" Area Designation Mode | |||
#define SGB_ATTR_DIV 0x06 //"Divide" Area Designation Mode | |||
#define SGB_ATTR_CHR 0x07 //"1CHR" Area Designation Mode | |||
#define SGB_SOUND 0x08 //Sound On/Off | |||
#define SGB_SOU_TRN 0x09 //Transfer Sound PRG/DATA | |||
#define SGB_PAL_SET 0x0A //Set SGB Palette Indirect | |||
#define SGB_PAL_TRN 0x0B //Set System Color Palette Data | |||
#define SGB_ATRC_EN 0x0C //Enable/disable Attraction Mode | |||
#define SGB_TEST_EN 0x0D //Speed Function | |||
#define SGB_ICON_EN 0x0E //SGB Function | |||
#define SGB_DATA_SND 0x0F //SUPER NES WRAM Transfer 1 | |||
#define SGB_DATA_TRN 0x10 //SUPER NES WRAM Transfer 2 | |||
#define SGB_MLT_REQ 0x11 //Controller 2 Request | |||
#define SGB_JUMP 0x12 //Set SNES Program Counter | |||
#define SGB_CHR_TRN 0x13 //Transfer Character Font Data | |||
#define SGB_PCT_TRN 0x14 //Set Screen Data Color Data | |||
#define SGB_ATTR_TRN 0x15 //Set Attribute from ATF | |||
#define SGB_ATTR_SET 0x16 //Set Data to ATF | |||
#define SGB_MASK_EN 0x17 //Game Boy Window Mask | |||
#define SGB_OBJ_TRN 0x18 //Super NES OBJ Mode | |||
#define SGB_HEADER(command, length) ((uint8_t)(((command) << 3) | (length))) | |||
void sgb_send(const uint8_t* packet); | |||
#endif | |||
#endif |
@ -0,0 +1,40 @@ | |||
#ifndef GBSDK_VIDEO_H | |||
#define GBSDK_VIDEO_H | |||
#include <stdint.h> | |||
#include <sdk/hardware.h> | |||
//Turn off the LCD, with the proper checks that this happens during VBLANK | |||
void lcd_off(void) __preserves_regs(b, c, d, e, h, l); | |||
//Copy to VRAM with STAT checks, so these function are safe to write to VRAM at any time. | |||
void vram_memcpy(uint16_t dst, void* src, uint16_t size) __preserves_regs(b, c); | |||
void vram_memset(uint16_t dst, uint8_t value, uint16_t size) __preserves_regs(b, c); | |||
inline void vram_set(uint16_t dst, uint8_t value) { | |||
while(rSTAT & STAT_BUSY) {} | |||
*((uint8_t*)dst) = value; | |||
} | |||
#if CGB | |||
#define PAL24(rgb) (((rgb & 0xF80000) >> 19) | ((rgb & 0x00F800) >> 6) | ((rgb & 0x0000F8) << 7)) | |||
void cgb_background_palette(uint16_t* palette) __preserves_regs(b, c); | |||
void cgb_object_palette(uint16_t* palette) __preserves_regs(b, c); | |||
inline void cgb_gdma(uint16_t dst, const void* src, uint8_t count) { | |||
rHDMA1 = ((uint16_t)src) >> 8; | |||
rHDMA2 = ((uint16_t)src) & 0xFF; | |||
rHDMA3 = dst >> 8; | |||
rHDMA4 = dst & 0xFF; | |||
rHDMA5 = count - 1; | |||
} | |||
inline void cgb_hdma(uint16_t dst, const void* src, uint8_t count) { | |||
rHDMA1 = ((uint16_t)src) >> 8; | |||
rHDMA2 = ((uint16_t)src) & 0xFF; | |||
rHDMA3 = dst >> 8; | |||
rHDMA4 = dst & 0xFF; | |||
rHDMA5 = (count - 1) | 0x80; | |||
} | |||
#endif | |||
#endif//GBSDK_VIDEO_H |
@ -0,0 +1,169 @@ | |||
.PHONY: all clean | |||
MYDIR := $(dir $(lastword $(MAKEFILE_LIST))) | |||
ifndef PROJECT_NAME | |||
$(error PROJECT_NAME is not set to the name of your rom.) | |||
endif | |||
ifndef MBC | |||
$(error MBC is not set. Should be set to the MBC name or value you want. Example "MBC5+RAM+BATTERY") | |||
endif | |||
ifndef TARGETS | |||
$(error TARGETS is not set. Should be a combination of DMG SGB CGB) | |||
endif | |||
ifneq ($(filter-out DMG SGB CGB,$(TARGETS)),) | |||
$(error TARGETS should be a combination of DMG SGB CGB, unknown: $(filter-out DMG SGB CGB,$(TARGETS))) | |||
endif | |||
MAPS := $(shell find level/ -type f -name '*.png' -not -name '*_coll.png') | |||
SRC := $(shell find src/ -type f -name '*.c') $(shell find $(MYDIR)/src/ -type f -name '*.c') $(MAPS:level/%.png=_build/level/%.c) | |||
ASM := $(shell find src/ -type f -name '*.asm') $(shell find $(MYDIR)/src/ -type f -name '*.asm') | |||
# Which files do we use from the sdcc libc | |||
LIBC := _memset.c gbz80/memcpy.s | |||
LIBC += _divuchar.c _divschar.c _muluchar.c _mulschar.c _modschar.c _moduchar.c | |||
LIBC += _divuint.c _divsint.c _mulint.c _modsint.c _moduint.c | |||
# the 32 bit functions use 10% of ROM0, so do not include them | |||
#LIBC += _divulong.c _divslong.c _mullong.c _modslong.c _modulong.c | |||
LIBC_PATH := $(subst \,/,$(shell sdcc -mgbz80 --print-search-dirs | grep gbz80 | tail -n 1))/../src | |||
ifneq (1,$(words [$(LIBC_PATH)])) | |||
$(error "your environment or sdcc is installed in a path with spaces in it, this is not allowed, as it will break sdcc") | |||
endif | |||
Q ?= @ | |||
BUILD := _build | |||
TOOLS := $(MYDIR)/tools | |||
OBJS := $(patsubst %, $(BUILD)/%.o, $(ASM) $(SRC)) $(patsubst %, $(BUILD)/libc/%.o, $(LIBC)) $(BUILD)/gsinit.end.o | |||
CFLAGS := -mgbz80 -Isrc/ -I$(MYDIR)/inc -I$(BUILD)/level | |||
ASFLAGS := -isrc/ -i$(MYDIR)/inc -i$(BUILD)/assets/ -Wall | |||
LDFLAGS := --pad 0xFF | |||
FIXFLAGS := --validate --pad-value 0xFF --title "$(PROJECT_NAME)" --mbc-type "$(MBC)" -l 0x33 | |||
ROM_EXTENSION := gb | |||
# Replace spaces with underscores in the project name. | |||
PROJECT_NAME := $(subst $() $(),_,$(PROJECT_NAME)) | |||
ifeq ($(filter CGB,$(TARGETS)),) # Not targeting CGB, so disable CGB features | |||
CFLAGS += -DCGB=0 | |||
ASFLAGS += -DCGB=0 | |||
LDFLAGS += --dmg | |||
else | |||
CFLAGS += -DCGB=1 | |||
ASFLAGS += -DCGB=1 | |||
ifeq ($(filter DMG,$(TARGETS))$(filter SGB,$(TARGETS)),) # Check if not targeting both CGB and DMG | |||
FIXFLAGS += --color-only | |||
else | |||
FIXFLAGS += --color-compatible | |||
endif | |||
ROM_EXTENSION := gbc | |||
endif | |||
ifeq ($(filter SGB,$(TARGETS)),) # Not targeting SGB, so disable SGB features | |||
CFLAGS += -DSGB=0 | |||
ASFLAGS += -DSGB=0 | |||
else # Targeting SGB as well | |||
CFLAGS += -DSGB=1 | |||
ASFLAGS += -DSGB=1 | |||
FIXFLAGS += --sgb-compatible | |||
endif | |||
ifneq ($(findstring RAM,$(MBC)),) | |||
FIXFLAGS += --ram-size 2 | |||
endif | |||
all: $(PROJECT_NAME).$(ROM_EXTENSION) | |||
# Rules to make c files | |||
$(BUILD)/%.c.as: %.c | |||
@echo Compiling $< | |||
@mkdir -p $(dir $@) | |||
$(Q)sdcc $(CFLAGS) -S $< -o $@ -Wp "-MQ $@ -MD $(@:.as=.as.d)" | |||
-include $(patsubst %.c, $(BUILD)/%.c.as.d, $(SRC)) | |||
$(BUILD)/libc/%.c.as: $(LIBC_PATH)/%.c | |||
@echo Compiling $< | |||
@mkdir -p $(dir $@) | |||
$(Q)sdcc $(CFLAGS) -S $< -o $@ | |||
$(BUILD)/%.c.asm: $(BUILD)/%.c.as $(TOOLS)/asmconvert.py | |||
$(Q)python3 $(TOOLS)/asmconvert.py $(notdir $<) < $< > $@ | |||
$(BUILD)/%.c.asm.d: $(BUILD)/%.c.asm | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbasm $(ASFLAGS) $< -M $@ -MT $(@:.asm.d=.o) -MT $@ -MG | |||
# rgbds dependency generation is broken, as it stops after the first missing file. so list all incbins always | |||
$(Q)cat $< | grep -i '^ *INCBIN' | sed -E 's/ *incbin *"([^"]*)"/$(subst /,\/,$(@:.asm.d=.o)): \1/gI' >> $@ | |||
-include $(patsubst %.c, $(BUILD)/%.c.asm.d, $(SRC)) | |||
$(BUILD)/%.c.o: $(BUILD)/%.c.asm $(BUILD)/%.c.asm.d | |||
@echo "Assembling (c)" $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbasm $(ASFLAGS) $< -o $@ | |||
# Rules to build libc asm files | |||
$(BUILD)/libc/%.s.asm: $(LIBC_PATH)/%.s $(TOOLS)/asmconvert.py | |||
@mkdir -p $(dir $@) | |||
$(Q)python3 $(TOOLS)/asmconvert.py $(notdir $<) < $< > $@ | |||
$(BUILD)/libc/%.s.o: $(BUILD)/libc/%.s.asm | |||
@echo Assembling $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbasm $(ASFLAGS) $< -o $@ | |||
# Rules to build project asm files | |||
$(BUILD)/%.asm.d: %.asm | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbasm $(ASFLAGS) $< -M $@ -MT $(@:.d=.o) -MT $@ -MG | |||
# rgbds dependency generation is broken, as it stops after the first missing file. so list all incbins always | |||
$(Q)cat $< | grep -i '^ *INCBIN' | sed -E 's/incbin "([^"]*)"/$(subst /,\/,$(@:.d=.o)): \1/gI' >> $@ | |||
$(BUILD)/%.asm.o: %.asm $(BUILD)/%.asm.d | |||
@echo Assembling $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbasm $(ASFLAGS) $< -o $@ | |||
-include $(patsubst %.asm, $(BUILD)/%.asm.d, $(ASM)) | |||
# Rule to build the final rom | |||
$(PROJECT_NAME).$(ROM_EXTENSION) $(PROJECT_NAME).map $(PROJECT_NAME).sym: $(OBJS) | |||
@echo Linking $@ | |||
$(Q)rgblink $(LDFLAGS) $^ -o $@ -m $(basename $@).map -n $(basename $@).sym | |||
$(Q)rgbfix $(FIXFLAGS) $@ | |||
@python3 $(TOOLS)/romspace.py $(basename $@).map | |||
# Asset related rules | |||
$(BUILD)/assets/%.2bpp $(BUILD)/assets/%.map: assets/%.png | |||
@echo Converting $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbgfx \ | |||
-t "$(BUILD)/$(shell dirname $<)/$(shell basename --suffix=.png $<).map" \ | |||
-o "$(BUILD)/$(shell dirname $<)/$(shell basename --suffix=.png $<).2bpp" \ | |||
-u \ | |||
$< | |||
$(BUILD)/level/%.h $(BUILD)/level/%.c: level/%.png level/%_coll.png | |||
@echo Generating level $< | |||
@mkdir -p $(BUILD)/level | |||
@cp $< $(BUILD)/level/ | |||
@cp $(shell dirname $<)/$(shell basename --suffix=.png $<)_coll.png $(BUILD)/level/ | |||
@python scripts/generate_map.py --compress 1 "$(BUILD)/$<" | |||
$(BUILD)/assets/%.oam.2bpp: assets/%.oam.png | |||
@echo Converting $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbgfx -h $< -o $@ | |||
$(BUILD)/assets/%.1bpp: assets/%.png | |||
@echo Converting $< | |||
@mkdir -p $(dir $@) | |||
$(Q)rgbgfx -d 1 $< -o $@ | |||
# Special hack to place a ret instruction at the end of the GSINIT section. | |||
# This has to be linked last, as that ensures that this fragment is at the end of the "GSINIT" section. | |||
$(BUILD)/gsinit.end.o: | |||
@/bin/echo -e 'SECTION FRAGMENT "GSINIT", ROMX, BANK[1]\n ret' | rgbasm - -o $@ | |||
clean: | |||
@rm -rf $(BUILD) $(PROJECT_NAME).$(ROM_EXTENSION) $(PROJECT_NAME).map $(PROJECT_NAME).sym | |||
# Do not remove intermediate files (like assets, c->asm files, etc) | |||
.SECONDARY: |
@ -0,0 +1,25 @@ | |||
INCLUDE "sdk/hardware.inc" | |||
SECTION "BankingHRAM", HRAM | |||
wCurrentBank:: | |||
_current_bank:: ds 1 | |||
SECTION "BankingCode", ROM0 | |||
bankedCall:: | |||
___sdcc_bcall_ehl:: | |||
ldh a, [wCurrentBank] | |||
push af | |||
ld a, e | |||
ldh [wCurrentBank], a | |||
ld [rROMB0], a | |||
rst callHL | |||
pop af | |||
setCurrentBank:: | |||
ldh [wCurrentBank], a | |||
ld [rROMB0], a | |||
ret | |||
SECTION "callHL", ROM0[$0008] | |||
callHL:: | |||
___sdcc_call_hl:: | |||
jp hl |
@ -0,0 +1,18 @@ | |||
include "sdk/hardware.inc" | |||
SECTION "Header", ROM0[$100] | |||
di | |||
jp startRom | |||
ds $150 - @ | |||
SECTION "Init", ROMX, BANK[1] | |||
startRom: | |||
ld sp, $E000 | |||
call globalsInit ; init C globals | |||
call _main | |||
db $DD ; lock up if main returns ($DD is an invalid opcode) | |||
SECTION FRAGMENT "GSINIT", ROMX, BANK[1] | |||
globalsInit: | |||
; This will be the first fragment of GSINIT. |
@ -0,0 +1,48 @@ | |||
include "sdk/hardware.inc" | |||
SECTION "gbsdk_joypad_ram", WRAM0 | |||
_joypad_state:: | |||
wJoypadState:: ds 1 | |||
_joypad_pressed:: | |||
wJoypadPressed:: ds 1 | |||
SECTION "gbsdk_joypad_functions", ROM0 | |||
_joypad_update:: | |||
; Call this routine once per frame to update the joypad related variables. | |||
; Routine also returns the currently pressed buttons in the a register. | |||
ld hl, rP1 | |||
ld [hl], P1F_GET_BTN | |||
; After the initial enable we need to read twice to ensure | |||
; we get the proper hardware state on real hardware | |||
ld a, [hl] | |||
ld a, [hl] | |||
ld [hl], P1F_GET_DPAD | |||
cpl ; Inputs are active low, so a bit being 0 is a button pressed. So we invert this. | |||
and PADF_A | PADF_B | PADF_SELECT | PADF_START | |||
ld e, a ; Store the lower 4 button bits in e | |||
; We need to read rP1 8 times to ensure the proper button state is available. | |||
; This is only needed on real hardware, as it takes a while for the | |||
; inputs to change state back from the first set. | |||
ld d, 8 | |||
: ld a, [hl] | |||
dec d | |||
jr nz, :- | |||
ld [hl], P1F_GET_NONE ; Disable the joypad inputs again, saves a tiny bit of power and allows the lines to settle before the next read | |||
swap a ; We want the directional keys as upper 4 bits, so swap the nibbles. | |||
cpl ; Inputs are active low, so a bit being 0 is a button pressed. So we invert this. | |||
and PADF_RIGHT | PADF_LEFT | PADF_UP | PADF_DOWN | |||
or e | |||
ld e, a | |||
; Compare the new joypad state with the previous one, and store the | |||
; new bits in wJoypadPressed | |||
ld hl, wJoypadState | |||
xor [hl] | |||
and e | |||
ld [wJoypadPressed], a | |||
ld a, e | |||
ld [wJoypadState], a | |||
ret |
@ -0,0 +1,44 @@ | |||
include "sdk/hardware.inc" | |||
SECTION "gbsdk_oam_memory", WRAM0, ALIGN[8] | |||
_shadow_oam:: ; OAM Memory is for 40 sprites with 4 bytes per sprite | |||
wShadowOAM:: | |||
ds 40 * 4 | |||
.end: | |||
SECTION "gbsdk_oam_init_function", ROMX, BANK[1] | |||
oamInit:: | |||
_oam_init:: | |||
ld hl, wShadowOAM | |||
xor a | |||
ld c, wShadowOAM.end - wShadowOAM | |||
: ld [hl+], a | |||
dec c | |||
jr nz, :- | |||
ld hl, hOAMCopyRoutine | |||
ld de, oamCopyRoutine | |||
ld c, hOAMCopyRoutine.end - hOAMCopyRoutine | |||
: ld a, [de] | |||
inc de | |||
ld [hl+], a | |||
dec c | |||
jr nz, :- | |||
; We directly copy to clear the initial OAM memory, which else contains garbage. | |||
jp hOAMCopyRoutine | |||
oamCopyRoutine: | |||
LOAD "hram", HRAM | |||
_oam_dma_copy:: | |||
hOAMCopyRoutine:: | |||
ld a, HIGH(wShadowOAM) | |||
ldh [rDMA], a | |||
ld a, $28 | |||
.wait: | |||
dec a | |||
jr nz, .wait | |||
ret | |||
.end: | |||
ENDL |
@ -0,0 +1,44 @@ | |||
include "sdk/hardware.inc" | |||
SECTION "gbsdk_sgb_functions", ROM0 | |||
_sgb_send:: | |||
pop de | |||
pop hl | |||
; hl=pointer to data to send | |||
; SGB RESET pulse | |||
xor a | |||
ldh [rP1], a | |||
push hl ; done here for delay reasons | |||
cpl | |||
ldh [rP1], a | |||
push de ; done here for delay reasons | |||
ld e, 16 ; amount of bytes to send | |||
.byteLoop: | |||
ld a, [hl+] | |||
ld d, a ; byte to send in d | |||
ld c, 8 ; amount of bits to send | |||
.bitLoop: | |||
bit 0, d | |||
ld a, P1F_5 | |||
jr z, .zero | |||
ld a, P1F_4 | |||
.zero: | |||
ldh [rP1], a | |||
rr d ; 2cycles | |||
ld a, P1F_5 | P1F_4 ; 2cycles | |||
dec c ; 1cycle | |||
ldh [rP1], a | |||
jr nz, .bitLoop | |||
dec e | |||
jr nz, .byteLoop | |||
ld a, P1F_5 | |||
ldh [rP1], a | |||
ldh [rP1], a ; (3) | |||
ldh [rP1], a ; (3) | |||
xor a ; (1) | |||
ldh [rP1], a | |||
ret |
@ -0,0 +1,116 @@ | |||
include "sdk/hardware.inc" | |||
SECTION "gbsdk_video_functions", ROM0 | |||
_lcd_off:: | |||
: ldh a, [rLY] | |||
cp 144 | |||
jr c, :- | |||
xor a | |||
ldh [rLCDC], a | |||
ret | |||
_vram_memcpy:: | |||
ld hl, sp + 7 | |||
ld a, [hl-] | |||
ld d, a | |||
ld a, [hl-] | |||
ld e, a | |||
or a, d | |||
ret z | |||
; size -> de | |||
push bc | |||
ld a, [hl-] | |||
ld b, a | |||
ld a, [hl-] | |||
ld c, a | |||
ld a, [hl-] | |||
ld l, [hl] | |||
ld h, a | |||
; src -> bc, dst -> hl, size -> de | |||
inc d | |||
inc e | |||
jr .start | |||
.copy_loop: | |||
ldh a, [rSTAT] | |||
and a, STATF_BUSY | |||
jr nz, .copy_loop | |||
ld a, [bc] | |||
ld [hl+], a | |||
inc bc | |||
.start: | |||
dec e | |||
jr nz, .copy_loop | |||
dec d | |||
jr nz, .copy_loop | |||
pop bc | |||
ret | |||
_vram_memset:: | |||
ld hl, sp + 6 | |||
ld a, [hl-] | |||
ld d, a | |||
ld a, [hl-] | |||
ld e, a | |||
or a, d | |||
ret z | |||
; size -> de | |||
push bc | |||
ld a, [hl-] | |||
ld c, a | |||
ld a, [hl-] | |||
ld l, [hl] | |||
ld h, a | |||
; value -> c, dst -> hl, size -> de | |||
inc d | |||
inc e | |||
jr .start | |||
.copy_loop: | |||
ldh a, [rSTAT] | |||
and a, STATF_BUSY | |||
jr nz, .copy_loop | |||
ld a, c | |||
ld [hl+], a | |||
.start: | |||
dec e | |||
jr nz, .copy_loop | |||
dec d | |||
jr nz, .copy_loop | |||
pop bc | |||
ret | |||
IF CGB | |||
_cgb_background_palette:: | |||
pop de | |||
pop hl | |||
push hl | |||
push de | |||
ld a, BCPSF_AUTOINC | |||
ldh [rBCPS], a | |||
ld e, 8*4*2 | |||
: ld a, [hl+] | |||
ldh [rBCPD], a | |||
dec e | |||
jr nz, :- | |||
ret | |||
_cgb_object_palette:: | |||
pop de | |||
pop hl | |||
push hl | |||
push de | |||
ld a, OCPSF_AUTOINC | |||
ldh [rOCPS], a | |||
ld e, 8*4*2 | |||
: ld a, [hl+] | |||
ldh [rOCPD], a | |||
dec e | |||
jr nz, :- | |||
ret | |||
ENDC |
@ -0,0 +1,227 @@ | |||
import sys | |||
import re | |||
## Need to convert asxxxx syntax to rgbds syntax | |||
module = sys.argv[1] | |||
class Token: | |||
def __init__(self, kind, value, line_nr): | |||
self.kind = kind | |||
self.value = value | |||
self.line_nr = line_nr | |||
def isA(self, kind, value=None): | |||
return self.kind == kind and (value is None or value == self.value) | |||
def __repr__(self): | |||
return "[%s:%s:%d]" % (self.kind, self.value, self.line_nr) | |||
class Tokenizer: | |||
TOKEN_REGEX = re.compile('|'.join('(?P<%s>%s)' % pair for pair in [ | |||
('HEX', r'0x[0-9A-Fa-f]+'), | |||
('ASSIGN', r'='), | |||
('ALABEL', r'\d+\$'), | |||
('NUMBER', r'\d+(\.\d*)?'), | |||
('COMMENT', r';[^\n]*'), | |||
('LABEL', r':+'), | |||
('EXPR', r'#'), | |||
('STRING', '"[^"]*"'), | |||
('DIRECTIVE', r'\.[A-Za-z_][A-Za-z0-9_\.]*'), | |||
('ID', r'[A-Za-z_][A-Za-z0-9_\.]*'), | |||
('OP', r'[+\-*/,\(\)<>]'), | |||
('NEWLINE', r'\n'), | |||
('SKIP', r'[ \t]+'), | |||
('MISMATCH', r'.'), | |||
])) | |||
def __init__(self, code): | |||
self.__tokens = [] | |||
line_num = 1 | |||
for mo in self.TOKEN_REGEX.finditer(code): | |||
kind = mo.lastgroup | |||
value = mo.group() | |||
if kind == 'MISMATCH': | |||
print("ERR:", code.split("\n")[line_num-1]) | |||
raise RuntimeError("Syntax error on line: %d: %s\n%s", line_num, value) | |||
elif kind == 'SKIP': | |||
pass | |||
else: | |||
if kind == 'HEX': | |||
value = "$" + value[2:] | |||
if kind == 'ALABEL': | |||
value = "._ANNO_" + value[:-1] | |||
kind = "ID" | |||
self.__tokens.append(Token(kind, value, line_num)) | |||
if kind == 'NEWLINE': | |||
line_num += 1 | |||
self.__tokens.append(Token('NEWLINE', '\n', line_num)) | |||
def peek(self): | |||
return self.__tokens[0] | |||
def pop(self): | |||
return self.__tokens.pop(0) | |||
def expect(self, kind, value=None): | |||
pop = self.pop() | |||
if not pop.isA(kind, value): | |||
if value is not None: | |||
raise SyntaxError("%s != %s:%s" % (pop, kind, value)) | |||
raise SyntaxError("%s != %s" % (pop, kind)) | |||
def __bool__(self): | |||
return bool(self.__tokens) | |||
tok = Tokenizer(sys.stdin.read()) | |||
def processExpression(): | |||
while True: | |||
t = tok.peek() | |||
if t.isA('EXPR'): | |||
tok.pop() | |||
t = tok.peek() | |||
if t.isA('OP', '<'): | |||
sys.stdout.write('LOW') | |||
tok.pop() | |||
t = tok.peek() | |||
if t.isA('OP', '>'): | |||
sys.stdout.write('HIGH') | |||
tok.pop() | |||
t = tok.peek() | |||
if t.isA('OP', '('): | |||
tok.pop() | |||
sys.stdout.write('(') | |||
processExpression() | |||
t = tok.pop() | |||
assert t.isA('OP', ')') | |||
sys.stdout.write(')') | |||
if t.isA('ID') and t.value.startswith("b_"): | |||
t.value = "BANK(%s)" % (t.value[1:]) | |||
if t.isA('NEWLINE') or t.isA('OP', ')') or t.isA('OP', ','): | |||
break | |||
sys.stdout.write(t.value) | |||
tok.pop() | |||
def processParameter(): | |||
t = tok.pop() | |||
if t.isA('EXPR'): | |||
processExpression() | |||
elif t.isA('NEWLINE'): | |||
return | |||
elif t.isA('ID') or t.isA('NUMBER') or t.isA('HEX'): | |||
sys.stdout.write(t.value) | |||
elif t.isA('OP', '('): | |||
sys.stdout.write("[") | |||
processExpression() | |||
t = tok.pop() | |||
while not t.isA('OP', ')'): | |||
sys.stdout.write(t.value) | |||
t = tok.pop() | |||
assert t.isA('OP', ')'), t | |||
sys.stdout.write("]") | |||
else: | |||
raise Exception(t) | |||
while tok: | |||
start = tok.pop() | |||
if start.isA('NEWLINE'): | |||
pass | |||
elif start.isA('COMMENT'): | |||
print(start.value) | |||
elif start.isA('DIRECTIVE'): | |||
if start.value in {'.module', '.optsdcc', '.globl'}: | |||
while not tok.pop().isA('NEWLINE'): | |||
pass | |||
elif start.value == '.area': | |||
area_name = tok.pop().value | |||
if area_name == "_DATA": | |||
print('SECTION "%s_%s", WRAM0' % (module, area_name)) | |||
elif area_name == "_DABS": | |||
print('SECTION "%s_%s", SRAM' % (module, area_name)) | |||
elif area_name == "_HOME": | |||
print('SECTION FRAGMENT "%s_%s", ROM0' % (module, area_name)) | |||
elif area_name == "_CODE": | |||
print('SECTION FRAGMENT "%s_%s", ROM0' % (module, area_name)) | |||
elif area_name.startswith("_CODE_"): | |||
print('SECTION FRAGMENT "%s_%s", ROMX, BANK[%d]' % (module, area_name, int(area_name[6:]))) | |||
elif area_name == "_CABS": | |||
print('SECTION FRAGMENT "%s_%s", ROM0' % (module, area_name)) | |||
elif area_name == "_GSINIT": | |||
print('SECTION FRAGMENT "GSINIT", ROMX, BANK[1]') | |||
elif area_name == "_GSFINAL": | |||
print('SECTION FRAGMENT "GSFINAL", ROMX, BANK[1]') | |||
elif area_name == "_auto": | |||
print('SECTION FRAGMENT "code_%s", ROMX' % (module)) | |||
else: | |||
raise Exception(area_name) | |||
while not tok.pop().isA('NEWLINE'): | |||
pass | |||
elif start.value == '.ds': | |||
sys.stdout.write("ds ") | |||
processExpression() | |||
sys.stdout.write("\n") | |||
elif start.value == '.ascii': | |||
sys.stdout.write("db ") | |||
sys.stdout.write(tok.pop().value) | |||
sys.stdout.write("\n") | |||
elif start.value == '.db': | |||
sys.stdout.write("db ") | |||
processParameter() | |||
while tok.peek().isA('OP', ','): | |||
sys.stdout.write(",") | |||
tok.pop() | |||
processParameter() | |||
sys.stdout.write("\n") | |||
elif start.value == '.dw': | |||
sys.stdout.write("dw ") | |||
processParameter() | |||
while tok.peek().isA('OP', ','): | |||
sys.stdout.write(",") | |||
tok.pop() | |||
processParameter() | |||
sys.stdout.write("\n") | |||
elif start.value == '.incbin': | |||
sys.stdout.write("incbin ") | |||
while not tok.peek().isA('NEWLINE'): | |||
sys.stdout.write(tok.pop().value) | |||
sys.stdout.write("\n") | |||
tok.pop() | |||
else: | |||
raise Exception(start, tok.peek()) | |||
elif start.isA('ID'): | |||
if tok.peek().isA('ASSIGN'): | |||
tok.pop() | |||
sys.stdout.write("%s = " % (start.value)) | |||
processExpression() | |||
sys.stdout.write("\n") | |||
elif tok.peek().isA('LABEL'): | |||
print("%s%s" % (start.value, tok.pop().value)) | |||
elif start.isA('ID', 'ldhl'): | |||
tok.expect('ID', 'sp') | |||
tok.expect('OP', ',') | |||
sys.stdout.write("ld hl, sp + ") | |||
processParameter() | |||
sys.stdout.write("\n") | |||
elif start.isA('ID', 'lda'): | |||
tok.expect('ID', 'hl') | |||
tok.expect('OP', ',') | |||
t = tok.pop() | |||
assert t.isA('NUMBER') or t.isA('HEX') | |||
tok.expect('OP', '(') | |||
tok.expect('ID', 'sp') | |||
tok.expect('OP', ')') | |||
sys.stdout.write("ld hl, sp + %s\n" % (t.value)) | |||
else: | |||
sys.stdout.write("%s " % (start.value)) | |||
if not tok.peek().isA('NEWLINE'): | |||
processParameter() | |||
if tok.peek().isA('OP', ','): | |||
tok.pop() | |||
sys.stdout.write(", ") | |||
processParameter() | |||
sys.stdout.write("\n") | |||
tok.expect('NEWLINE') | |||
else: | |||
raise Exception(start) |
@ -0,0 +1,16 @@ | |||
import sys | |||
import re | |||
SIZE = {"ROM0": 0x4000, "ROMX": 0x4000, "WRAM0": 0x2000, "HRAM": 0x007F, "VRAM": 0x4000} | |||
area_re = re.compile(r'([A-Z0-9]+) bank #([0-9a-f]+):\n') | |||
slack_re = re.compile(r' SLACK: \$([0-9a-f]+) byte') | |||
for line in open(sys.argv[1], "rt"): | |||
area = area_re.match(line) | |||
if area: | |||
area_name = area[1] | |||
bank_nr = int(area[2]) | |||
slack = slack_re.match(line) | |||
if slack: | |||
used = SIZE[area_name] - int(slack[1], 16) | |||
print("%5s:%2d %04x/%04x (%.1f%%)" % (area_name, bank_nr, used, SIZE[area_name], used * 100 / SIZE[area_name])) |