diff --git a/BootROMs/sgb_boot.asm b/BootROMs/sgb_boot.asm new file mode 100644 index 0000000..c4d537f --- /dev/null +++ b/BootROMs/sgb_boot.asm @@ -0,0 +1,209 @@ +; Sameboy CGB bootstrap ROM +; Todo: use friendly names for HW registers instead of magic numbers +SECTION "BootCode", ROM0[$0] +Start: +; Init stack pointer + ld sp, $fffe + +; Clear memory VRAM + ld hl, $8000 + +.clearVRAMLoop + ldi [hl], a + bit 5, h + jr z, .clearVRAMLoop + +; Init Audio + ld a, $80 + ldh [$26], a + ldh [$11], a + ld a, $f3 + ldh [$12], a + ldh [$25], a + ld a, $77 + ldh [$24], a + +; Init BG palette to white + ld a, $0 + ldh [$47], a + +; Load logo from ROM. +; A nibble represents a 4-pixels line, 2 bytes represent a 4x4 tile, scaled to 8x8. +; Tiles are ordered left to right, top to bottom. + ld de, $104 ; Logo start + ld hl, $8010 ; This is where we load the tiles in VRAM + +.loadLogoLoop + ld a, [de] ; Read 2 rows + ld b, a + call DoubleBitsAndWriteRow + call DoubleBitsAndWriteRow + inc de + ld a, e + xor $34 ; End of logo + jr nz, .loadLogoLoop + +; Load trademark symbol + ld de, TrademarkSymbol + ld c,$08 +.loadTrademarkSymbolLoop: + ld a,[de] + inc de + ldi [hl],a + inc hl + dec c + jr nz, .loadTrademarkSymbolLoop + +; Set up tilemap + ld a,$19 ; Trademark symbol + ld [$9910], a ; ... put in the superscript position + ld hl,$992f ; Bottom right corner of the logo + ld c,$c ; Tiles in a logo row +.tilemapLoop + dec a + jr z, .tilemapDone + ldd [hl], a + dec c + jr nz, .tilemapLoop + ld l,$0f ; Jump to top row + jr .tilemapLoop +.tilemapDone + + ; Turn on LCD + ld a, $91 + ldh [$40], a + + ld a, $f1 ; Packet magic, increases by 2 for every packet + ldh [$80], a + ld hl, $104 ; Header start + + xor a + ld c, a ; JOYP + +.sendCommand + xor a + ldh [c], a + ld a, $30 + ldh [c], a + + ldh a, [$80] + call SendByte + push hl + ld b, $e + ld d, 0 + +.checksumLoop + call ReadHeaderByte + add d + ld d, a + dec b + jr nz, .checksumLoop + + ; Send checksum + call SendByte + pop hl + + ld b, $e +.sendLoop + call ReadHeaderByte + call SendByte + dec b + jr nz, .sendLoop + + ; Done bit + ld a, $20 + ldh [c], a + ld a, $30 + ldh [c], a + + ; Update command + ldh a, [$80] + add 2 + ldh [$80], a + + ld a, $58 + cp l + jr nz, .sendCommand + + ; Write to sound registers for DMG compatibility + ld c, $13 + ld a, $c1 + ldh [c], a + inc c + ld a, 7 + ldh [c], a + + ; Init BG palette + ld a, $fc + ldh [$47], a + +; Set registers to match the original SGB boot + ld a, 1 + ld hl, $c060 + +; Boot the game + jp BootGame + +ReadHeaderByte: + ld a, $4F + cp l + jr c, .zero + ld a, [hli] + ret +.zero: + inc hl + xor a + ret + +SendByte: + ld e, a + ld d, 8 +.loop + ld a, $10 + rr e + jr c, .zeroBit + add a ; 10 -> 20 +.zeroBit + ldh [c], a + ld a, $30 + ldh [c], a + dec d + ret z + jr .loop + +DoubleBitsAndWriteRow: +; Double the most significant 4 bits, b is shifted by 4 + ld a, 4 + ld c, 0 +.doubleCurrentBit + sla b + push af + rl c + pop af + rl c + dec a + jr nz, .doubleCurrentBit + ld a, c +; Write as two rows + ldi [hl], a + inc hl + ldi [hl], a + inc hl + ret + +WaitFrame: + push hl + ld hl, $FF0F + res 0, [hl] +.wait + bit 0, [hl] + jr z, .wait + pop hl + ret + +TrademarkSymbol: +db $3c,$42,$b9,$a5,$b9,$a5,$42,$3c + +SECTION "BootGame", ROM0[$fe] +BootGame: + ldh [$50], a \ No newline at end of file diff --git a/Cocoa/Document.m b/Cocoa/Document.m index cf2a7f9..05b3b45 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -16,13 +16,15 @@ enum model { MODEL_DMG, MODEL_CGB, MODEL_AGB, + MODEL_SGB, }; static const GB_model_t cocoa_to_internal_model[] = { [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_E, - [MODEL_AGB] = GB_MODEL_AGB + [MODEL_AGB] = GB_MODEL_AGB, + [MODEL_SGB] = GB_MODEL_SGB, }; @interface Document () @@ -157,7 +159,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, return [[NSBundle mainBundle] pathForResource:name ofType:@"bin"]; } -/* Todo: Unify the 3 init functions */ +/* Todo: Unify the 4 init functions */ - (void) initDMG { current_model = MODEL_DMG; @@ -166,6 +168,14 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, [self initCommon]; } +- (void) initSGB +{ + current_model = MODEL_SGB; + GB_init(&gb, cocoa_to_internal_model[current_model]); + GB_load_boot_rom(&gb, [[self bootROMPathForName:@"sgb_boot"] UTF8String]); + [self initCommon]; +} + - (void) initCGB { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]) { @@ -274,7 +284,7 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, current_model = (enum model)[sender tag]; } - static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot"}; + static NSString * const boot_names[] = {@"dmg_boot", @"cgb_boot", @"agb_boot", @"sgb_boot"}; GB_load_boot_rom(&gb, [[self bootROMPathForName:boot_names[current_model - 1]] UTF8String]); if ([sender tag] == MODEL_NONE) { @@ -287,7 +297,8 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if ([sender tag] != 0) { /* User explictly selected a model, save the preference */ [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_DMG forKey:@"EmulateDMG"]; - [[NSUserDefaults standardUserDefaults] setBool:current_model== MODEL_AGB forKey:@"EmulateAGB"]; + [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_SGB forKey:@"EmulateSGB"]; + [[NSUserDefaults standardUserDefaults] setBool:current_model == MODEL_AGB forKey:@"EmulateAGB"]; } /* Reload the ROM, SAV and SYM files */ @@ -382,6 +393,9 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height, if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { [self initDMG]; } + else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) { + [self initSGB]; + } else { [self initCGB]; } diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index 3658f83..844aa0c 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -1,7 +1,8 @@ - - + + - + + @@ -311,7 +312,13 @@ - + + + + + + + diff --git a/Core/gb.c b/Core/gb.c index edc0a56..00c4e7b 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -506,7 +506,12 @@ bool GB_is_inited(GB_gameboy_t *gb) bool GB_is_cgb(GB_gameboy_t *gb) { - return ((gb->model) & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; + return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY; +} + +bool GB_is_sgb(GB_gameboy_t *gb) +{ + return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2; } void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip) @@ -541,6 +546,8 @@ static void reset_ram(GB_gameboy_t *gb) break; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: /* Unverified*/ + case GB_MODEL_SGB_PAL: /* Unverified */ for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = (random() & 0xFF); if (i & 0x100) { @@ -551,15 +558,13 @@ static void reset_ram(GB_gameboy_t *gb) } } break; -#if 0 - /* Not emulated yet, for documentation only */ + case GB_MODEL_SGB2: for (unsigned i = 0; i < gb->ram_size; i++) { gb->ram[i] = 0x55; gb->ram[i] ^= random() & random() & random(); } break; -#endif case GB_MODEL_CGB_C: for (unsigned i = 0; i < gb->ram_size; i++) { @@ -729,5 +734,11 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) uint32_t GB_get_clock_rate(GB_gameboy_t *gb) { + if (gb->model == GB_MODEL_SGB_NTSC) { + return SGB_NTSC_FREQUENCY * gb->clock_multiplier; + } + if (gb->model == GB_MODEL_SGB_PAL) { + return SGB_PAL_FREQUENCY * gb->clock_multiplier; + } return CPU_FREQUENCY * gb->clock_multiplier; } diff --git a/Core/gb.h b/Core/gb.h index d20ef2b..97c7dd4 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -19,6 +19,7 @@ #include "rewind.h" #include "z80_cpu.h" #include "symbol_hash.h" +#include "sgb.h" #define GB_STRUCT_VERSION 13 @@ -27,6 +28,7 @@ #define GB_MODEL_DMG_FAMILY 0x000 #define GB_MODEL_MGB_FAMILY 0x100 #define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_PAL_BIT 0x1000 #endif #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ @@ -48,15 +50,18 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; + typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, GB_MODEL_DMG_B = 0x002, // GB_MODEL_DMG_C = 0x003, - // GB_MODEL_SGB = 0x004, + GB_MODEL_SGB = 0x004, + GB_MODEL_SGB_NTSC = GB_MODEL_SGB, + GB_MODEL_SGB_PAL = 0x1004, // GB_MODEL_MGB = 0x100, - // GB_MODEL_SGB2 = 0x101, + GB_MODEL_SGB2 = 0x101, // GB_MODEL_CGB_0 = 0x200, // GB_MODEL_CGB_A = 0x201, // GB_MODEL_CGB_B = 0x202, @@ -203,6 +208,8 @@ typedef enum { #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 +#define SGB_NTSC_FREQUENCY (21477272 / 5) +#define SGB_PAL_FREQUENCY (21281370 / 5) #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) @@ -459,6 +466,14 @@ struct GB_gameboy_internal_s { uint8_t mode_for_interrupt; bool lyc_interrupt_line; ); + + /* Super Game Boy state, only dumped/loaded for relevant models */ + GB_SECTION(sgb, + uint8_t sgb_command[16]; + uint8_t sgb_command_write_index; + bool sgb_ready_for_pulse; + bool sgb_ready_for_write; + ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* This data is reserved on reset and must come last in the struct */ @@ -584,6 +599,7 @@ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) void GB_init(GB_gameboy_t *gb, GB_model_t model); bool GB_is_inited(GB_gameboy_t *gb); bool GB_is_cgb(GB_gameboy_t *gb); +bool GB_is_sgb(GB_gameboy_t *gb); GB_model_t GB_get_model(GB_gameboy_t *gb); void GB_free(GB_gameboy_t *gb); void GB_reset(GB_gameboy_t *gb); diff --git a/Core/joypad.c b/Core/joypad.c index 8b7a8da..edee8c3 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -47,12 +47,14 @@ void GB_update_joyp(GB_gameboy_t *gb) default: break; } + if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { /* The joypad interrupt DOES occur on CGB (Tested on CGB-CPU-06), unlike what some documents say. */ gb->io_registers[GB_IO_IF] |= 0x10; gb->stopped = false; } - gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support + + gb->io_registers[GB_IO_JOYP] |= 0xC0; } void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed) diff --git a/Core/memory.c b/Core/memory.c index 4053530..cf5a0c0 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -271,6 +271,9 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) return gb->extra_oam[addr - 0xfea0]; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB2: ; } } @@ -575,6 +578,9 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->extra_oam[addr - 0xfea0] = value; break; case GB_MODEL_DMG_B: + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB2: case GB_MODEL_CGB_E: case GB_MODEL_AGB: break; @@ -727,6 +733,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->io_registers[GB_IO_JOYP] &= 0x0F; gb->io_registers[GB_IO_JOYP] |= value & 0xF0; GB_update_joyp(gb); + GB_sgb_write(gb, value); return; case GB_IO_BIOS: diff --git a/Core/sgb.c b/Core/sgb.c new file mode 100644 index 0000000..9dce2d8 --- /dev/null +++ b/Core/sgb.c @@ -0,0 +1,51 @@ +#include "sgb.h" + +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) +{ + if (!GB_is_sgb(gb)) return; + switch ((value >> 4) & 3 ) { + case 3: + gb->sgb_ready_for_pulse = true; + break; + + case 2: // Zero + if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; + if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { + GB_log(gb, "Got SGB command: "); + for (unsigned i = 0; i < 16; i++) { + GB_log(gb, "%02x ", gb->sgb_command[i]); + } + GB_log(gb, "\n"); + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = false; + } + else { + gb->sgb_command_write_index++; + gb->sgb_ready_for_pulse = false; + } + break; + case 1: // One + if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; + if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) { + GB_log(gb, "Corrupt SGB command.\n"); + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = false; + } + else { + gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); + gb->sgb_command_write_index++; + gb->sgb_ready_for_pulse = false; + } + break; + + case 0: + gb->sgb_ready_for_pulse = false; + gb->sgb_ready_for_write = true; + gb->sgb_command_write_index = 0; + memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + break; + + default: + break; + } +} diff --git a/Core/sgb.h b/Core/sgb.h new file mode 100644 index 0000000..e3b52bd --- /dev/null +++ b/Core/sgb.h @@ -0,0 +1,9 @@ +#ifndef sgb_h +#define sgb_h +#include "gb.h" + +#ifdef GB_INTERNAL +void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); +#endif + +#endif diff --git a/Makefile b/Makefile index 7038ee9..9eeefac 100755 --- a/Makefile +++ b/Makefile @@ -106,9 +106,9 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders -bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin -tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders +bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin +tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin all: cocoa sdl tester libretro # Get a list of our source files and their respective object file targets @@ -180,6 +180,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(BIN)/SameBoy.app/Contents/Resources/dmg_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/cgb_boot.bin \ $(BIN)/SameBoy.app/Contents/Resources/agb_boot.bin \ + $(BIN)/SameBoy.app/Contents/Resources/sgb_boot.bin \ $(patsubst %.xib,%.nib,$(addprefix $(BIN)/SameBoy.app/Contents/Resources/Base.lproj/,$(shell cd Cocoa;ls *.xib))) \ $(BIN)/SameBoy.qlgenerator \ Shaders @@ -307,7 +308,7 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm -@$(MKDIR) -p $(dir $@) cd BootROMs && rgbasm -o ../$@.tmp ../$< rgblink -o $@.tmp2 $@.tmp - head -c $(if $(findstring dmg,$@), 256, 2304) $@.tmp2 > $@ + head -c $(if $(findstring dmg,$@)$(findstring sgb,$@), 256, 2304) $@.tmp2 > $@ @rm $@.tmp $@.tmp2 # Libretro Core (uses its own build system)