From 44c75ae7be199d7267921fdb988a53b4d562698b Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 18:43:24 +0300 Subject: [PATCH 01/23] Remove commented out code --- Core/display.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/display.c b/Core/display.c index 72e89c8..6979a0a 100644 --- a/Core/display.c +++ b/Core/display.c @@ -574,7 +574,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) else if (gb->model & GB_MODEL_NO_SFC_BIT) { if (gb->icd_pixel_callback) { icd_pixel = pixel; - //gb->icd_pixel_callback(gb, pixel); } } else if (gb->cgb_palettes_ppu_blocked) { From f24489b9834781ef4c3fff86fa58da3f7560c151 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 10 Apr 2021 23:56:41 +0300 Subject: [PATCH 02/23] TPP1 support --- Core/gb.h | 7 +++- Core/mbc.c | 17 ++++++++ Core/mbc.h | 1 + Core/memory.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++--- Core/timing.c | 47 +++++++++++++++------- 5 files changed, 156 insertions(+), 21 deletions(-) diff --git a/Core/gb.h b/Core/gb.h index 7210e7a..b09a506 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -441,10 +441,10 @@ struct GB_gameboy_internal_s { uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ bool camera_registers_mapped; uint8_t camera_registers[0x36]; - bool rumble_state; + uint8_t rumble_strength; bool cart_ir; - // TODO: move to huc3/mbc3 struct when breaking save compat + // TODO: move to huc3/mbc3/tpp1 struct when breaking save compat uint8_t huc3_mode; uint8_t huc3_access_index; uint16_t huc3_minutes, huc3_days; @@ -453,6 +453,9 @@ struct GB_gameboy_internal_s { uint8_t huc3_read; uint8_t huc3_access_flags; bool mbc3_rtc_mapped; + uint16_t tpp1_rom_bank; + uint8_t tpp1_ram_bank; + uint8_t tpp1_mode; ); diff --git a/Core/mbc.c b/Core/mbc.c index 1e91ce2..ffb8d9d 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -111,12 +111,24 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb) gb->mbc_rom_bank = gb->huc3.rom_bank; gb->mbc_ram_bank = gb->huc3.ram_bank; break; + case GB_TPP1: + gb->mbc_rom_bank = gb->tpp1_rom_bank; + gb->mbc_ram_bank = gb->tpp1_ram_bank; + gb->mbc_ram_enable = (gb->tpp1_mode == 2) || (gb->tpp1_mode == 3); + break; } } void GB_configure_cart(GB_gameboy_t *gb) { gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; + if (gb->rom[0x147] == 0xbc && + gb->rom[0x149] == 0xc1 && + gb->rom[0x14a] == 0x65) { + static const GB_cartridge_t tpp1 = {GB_TPP1, GB_STANDARD_MBC, true, true, true, true}; + gb->cartridge_type = &tpp1; + gb->tpp1_rom_bank = 1; + } if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); @@ -130,6 +142,11 @@ void GB_configure_cart(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_MBC2) { gb->mbc_ram_size = 0x200; } + else if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (gb->rom[0x152] >= 1 && gb->rom[0x152] <= 9) { + gb->mbc_ram_size = 0x2000 << (gb->rom[0x152] - 1); + } + } else { static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; diff --git a/Core/mbc.h b/Core/mbc.h index 6a23300..3bbe782 100644 --- a/Core/mbc.h +++ b/Core/mbc.h @@ -12,6 +12,7 @@ typedef struct { GB_MBC5, GB_HUC1, GB_HUC3, + GB_TPP1, } mbc_type; enum { GB_STANDARD_MBC, diff --git a/Core/memory.c b/Core/memory.c index b1619a6..3e7d4a4 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -178,7 +178,31 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) } } - if ((!gb->mbc_ram_enable) && + if (gb->cartridge_type->mbc_type == GB_TPP1) { + switch (gb->tpp1_mode) { + case 0: + switch (addr & 3) { + case 0: return gb->tpp1_rom_bank; + case 1: return gb->tpp1_rom_bank >> 8; + case 2: return gb->tpp1_ram_bank; + case 3: return gb->rumble_strength | (((gb->rtc_real.high & 0xC0) ^ 0x40) >> 4); + } + case 2: + case 3: + break; // Read RAM + case 5: + switch (addr & 3) { + case 0: return (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) / 7; // Week count + case 1: return gb->rtc_latched.hours | + (((((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) % 7) << 5); // Hours and weekday + case 2: return gb->rtc_latched.minutes; + case 3: return gb->rtc_latched.seconds; + } + default: + return 0xFF; + } + } + else if ((!gb->mbc_ram_enable) && gb->cartridge_type->mbc_subtype != GB_CAMERA && gb->cartridge_type->mbc_type != GB_HUC1 && gb->cartridge_type->mbc_type != GB_HUC3) { @@ -335,9 +359,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) } if (addr < 0xFF00) { - return 0; - } if (addr < 0xFF80) { @@ -539,8 +561,8 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x3000: gb->mbc5.rom_bank_high = value; break; case 0x4000: case 0x5000: if (gb->cartridge_type->has_rumble) { - if (!!(value & 8) != gb->rumble_state) { - gb->rumble_state = !gb->rumble_state; + if (!!(value & 8) != !!gb->rumble_strength) { + gb->rumble_strength = gb->rumble_strength? 0 : 3; } value &= 7; } @@ -567,6 +589,49 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; } break; + case GB_TPP1: + switch (addr & 3) { + case 0: + gb->tpp1_rom_bank &= 0xFF00; + gb->tpp1_rom_bank |= value; + break; + case 1: + gb->tpp1_rom_bank &= 0xFF; + gb->tpp1_rom_bank |= value << 8; + break; + case 2: + gb->tpp1_ram_bank = value; + break; + case 3: + switch (value) { + case 0: + case 2: + case 3: + case 5: + gb->tpp1_mode = value; + break; + case 0x10: + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + break; + case 0x11: { + uint8_t flags = gb->rtc_real.high & 0xc0; + memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); + gb->rtc_real.high &= ~0xc0; + gb->rtc_real.high |= flags; + break; + } + case 0x14: + gb->rtc_real.high &= ~0x80; + break; + case 0x18: + gb->rtc_real.high |= 0x40; + break; + case 0x19: + gb->rtc_real.high &= ~0x40; + break; + } + } + break; } GB_update_mbc_mappings(gb); } @@ -688,6 +753,36 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + switch (gb->tpp1_mode) { + case 3: + break; + case 5: + switch (addr & 3) { + case 0: { + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + total_days = total_days % 7 + value * 7; + gb->rtc_latched.days = total_days; + gb->rtc_latched.high = total_days >> 8; + return; + } + case 1: { + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + total_days = total_days / 7 * 7 + (value >> 5); + gb->rtc_latched.hours = value & 0x1F; + gb->rtc_latched.days = total_days; + gb->rtc_latched.high = total_days >> 8; + return; + } + case 2: gb->rtc_latched.minutes = value; return; + case 3: gb->rtc_latched.seconds = value; return; + } + return; + default: + return; + } + } + if ((!gb->mbc_ram_enable) && gb->cartridge_type->mbc_type != GB_HUC1) return; diff --git a/Core/timing.c b/Core/timing.c index 1da82a3..d240525 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -288,10 +288,22 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) while (gb->last_rtc_second + 60 * 60 * 24 < current_time) { gb->last_rtc_second += 60 * 60 * 24; if (++gb->rtc_real.days == 0) { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ + gb->rtc_real.high &= 0x40; + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + else { + gb->rtc_real.high++; + } + } + else { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; } - gb->rtc_real.high ^= 1; } } @@ -308,11 +320,22 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (++gb->rtc_real.days != 0) continue; - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/ + gb->rtc_real.high &= 0x40; + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + else { + gb->rtc_real.high++; + } + } + else { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + + gb->rtc_real.high ^= 1; } - - gb->rtc_real.high ^= 1; } } } @@ -344,13 +367,9 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) gb->cycles_since_last_sync += cycles; gb->cycles_since_run += cycles; - if (gb->rumble_state) { - gb->rumble_on_cycles++; - } - else { - gb->rumble_off_cycles++; - } - + gb->rumble_on_cycles += gb->rumble_strength & 3; + gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3; + if (!gb->stopped) { // TODO: Verify what happens in STOP mode GB_dma_run(gb); GB_hdma_run(gb); From 0c5e15b49dd8a8167dffc11a2fadecc74464bd33 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 02:38:58 +0300 Subject: [PATCH 03/23] Correct emulation of count overflow in ATTR_CHR, fixes #372 --- Core/sgb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sgb.c b/Core/sgb.c index c77b0db..f22eb3e 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -269,7 +269,8 @@ static void command_ready(GB_gameboy_t *gb) #endif uint8_t x = command->x; uint8_t y = command->y; - if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) { + count = MIN(count, 20 * 18); + if (x >= 20 || y >= 18) { /* TODO: Verify with the SFC BIOS */ break; } From 42471095e41e6d062cee9cf408360fbbbfa33540 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 22:38:25 +0300 Subject: [PATCH 04/23] Normalize invalid weekdays only after a $11 command --- Core/memory.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Core/memory.c b/Core/memory.c index 3e7d4a4..20aaac9 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -192,9 +192,20 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) break; // Read RAM case 5: switch (addr & 3) { - case 0: return (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) / 7; // Week count - case 1: return gb->rtc_latched.hours | - (((((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days) % 7) << 5); // Hours and weekday + case 0: { // Week count + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + if (gb->rtc_latched.high & 0x20) { + return total_days / 7 - 1; + } + return total_days / 7; + } + case 1: { // Week count + unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); + if (gb->rtc_latched.high & 0x20) { + return gb->rtc_latched.hours | 0xe0; // Hours and weekday + } + return gb->rtc_latched.hours | ((total_days % 7) << 5); // Hours and weekday + } case 2: return gb->rtc_latched.minutes; case 3: return gb->rtc_latched.seconds; } @@ -616,7 +627,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x11: { uint8_t flags = gb->rtc_real.high & 0xc0; memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); - gb->rtc_real.high &= ~0xc0; + gb->rtc_real.high &= ~0xe0; gb->rtc_real.high |= flags; break; } @@ -762,8 +773,12 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0: { unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days); total_days = total_days % 7 + value * 7; + bool had_illegal_weekday = gb->rtc_latched.high & 0x20; gb->rtc_latched.days = total_days; gb->rtc_latched.high = total_days >> 8; + if (had_illegal_weekday) { + gb->rtc_latched.high |= 0x20; + } return; } case 1: { @@ -772,6 +787,9 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) gb->rtc_latched.hours = value & 0x1F; gb->rtc_latched.days = total_days; gb->rtc_latched.high = total_days >> 8; + if ((value & 0xE0) == 0xE0) { // Illegal weekday + gb->rtc_latched.high |= 0x20; + } return; } case 2: gb->rtc_latched.minutes = value; return; From 763de9d2e0c8846a4dc82f67971392f88e028a75 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 22:52:34 +0300 Subject: [PATCH 05/23] Fix Rumble support in TPP1 --- Core/memory.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Core/memory.c b/Core/memory.c index 20aaac9..0082131 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -640,6 +640,13 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case 0x19: gb->rtc_real.high &= ~0x40; break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + gb->rumble_strength = value & 3; + break; } } break; From 80f422d0cad6580f81028c1bdfb91b6aadf609f7 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 23:16:31 +0300 Subject: [PATCH 06/23] Respect TPP1 feature flags for rumble and RTC --- Core/debugger.c | 7 +++++-- Core/gb.c | 4 ++++ Core/rumble.c | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 73bd80a..be80fd0 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1523,7 +1523,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg const GB_cartridge_t *cartridge = gb->cartridge_type; if (cartridge->has_ram) { - GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size); + bool has_battery = gb->cartridge_type->has_battery && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 8)); + GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", has_battery? " battery-backed": "", gb->mbc_ram_size); } else { GB_log(gb, "No cartridge RAM\n"); @@ -1565,7 +1567,8 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg GB_log(gb, "No MBC\n"); } - if (cartridge->has_rumble) { + if (gb->cartridge_type->has_rumble && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) { GB_log(gb, "Cart contains a Rumble Pak\n"); } diff --git a/Core/gb.c b/Core/gb.c index 3a0864d..e6747c6 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -609,6 +609,8 @@ typedef union { int GB_save_battery_size(GB_gameboy_t *gb) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (gb->cartridge_type->mbc_type == GB_HUC3) { @@ -621,6 +623,7 @@ int GB_save_battery_size(GB_gameboy_t *gb) int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (size < GB_save_battery_size(gb)) return EIO; @@ -678,6 +681,7 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save. if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ FILE *f = fopen(path, "wb"); if (!f) { diff --git a/Core/rumble.c b/Core/rumble.c index 87eb870..5f83c47 100644 --- a/Core/rumble.c +++ b/Core/rumble.c @@ -15,7 +15,8 @@ void GB_handle_rumble(GB_gameboy_t *gb) if (gb->rumble_mode == GB_RUMBLE_DISABLED) { return; } - if (gb->cartridge_type->has_rumble) { + if (gb->cartridge_type->has_rumble && + (gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) { if (gb->rumble_on_cycles + gb->rumble_off_cycles) { gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles)); gb->rumble_on_cycles = gb->rumble_off_cycles = 0; From 251dd15ff91cf7638a93d8386f4eee3af3b4fcdd Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 11 Apr 2021 23:36:42 +0300 Subject: [PATCH 07/23] Fixed a bug where the screen would not redraw when certain controllers are rumbling in specific strengths in the Cocoa port --- JoyKit/JOYController.m | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/JoyKit/JOYController.m b/JoyKit/JOYController.m index ca2d1b1..8ec1279 100644 --- a/JoyKit/JOYController.m +++ b/JoyKit/JOYController.m @@ -166,6 +166,7 @@ typedef union { double _sentRumbleAmp; unsigned _rumbleCounter; bool _deviceCantSendReports; + dispatch_queue_t _rumbleQueue; } - (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks @@ -490,9 +491,11 @@ typedef union { {.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0}, } }; - } + _rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String, + NULL); + return self; } @@ -564,7 +567,9 @@ typedef union { } } } - [self updateRumble]; + dispatch_async(_rumbleQueue, ^{ + [self updateRumble]; + }); } - (void)elementChanged:(IOHIDElementRef)element @@ -699,7 +704,9 @@ typedef union { _physicallyConnected = false; [exposedControllers removeObject:self]; [self setRumbleAmplitude:0]; - [self updateRumble]; + dispatch_sync(_rumbleQueue, ^{ + [self updateRumble]; + }); _device = nil; } From 9a1f962281864f53ee9336fe385d6e5560b77d9f Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:39:13 +0300 Subject: [PATCH 08/23] Spec update --- BESS.md | 40 +++++++++++++++++++++------------------- Core/save_state.c | 5 +++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/BESS.md b/BESS.md index da601f3..ab4f3ea 100644 --- a/BESS.md +++ b/BESS.md @@ -34,7 +34,7 @@ Every block is followed by another blocked, until the END block is reached. The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. -The length of the CORE block is 0xCF bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: +The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: | Offset | Content | |--------|---------------------------------------| @@ -54,7 +54,7 @@ BESS uses a four-character string to identify Game Boy models: * The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character. * The last character is used for padding and must be a space character. -For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `CCE ` represent a CGB using CPU revision E. +For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E. | Offset | Content | |--------|-------------------------------------------------------| @@ -67,7 +67,8 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr | 0x14 | The value of IME (0 or 1) | | 0x15 | The value of the IE register | | 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | -| 0x17 | The values of every memory-mapped register (128 bytes) | +| 0x17 | Reserved, must be 0 | +| 0x18 | The values of every memory-mapped register (128 bytes) | The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored @@ -81,25 +82,26 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * The value store for DIV will be used to set the internal divisor to `DIV << 8` * Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers) -|Offset | Content | -|-------|--------------------------------------------------------------------| -| 0x97 | The size of RAM (32-bit integer) | -| 0x9B | The offset of RAM from file start (32-bit integer) | -| 0x9F | The size of VRAM (32-bit integer) | -| 0xA3 | The offset of VRAM from file start (32-bit integer) | -| 0xA7 | The size of MBC RAM (32-bit integer) | -| 0xAB | The offset of MBC RAM from file start (32-bit integer) | -| 0xAF | The size of OAM (=0xA0, 32-bit integer) | -| 0xB3 | The offset of OAM from file start (32-bit integer) | -| 0xB7 | The size of HRAM (=0x7F, 32-bit integer) | -| 0xBB | The offset of HRAM from file start (32-bit integer) | -| 0xBF | The size of background palettes (=0x40 or 0, 32-bit integer) | -| 0xC3 | The offset of background palettes from file start (32-bit integer) | -| 0xC7 | The size of object palettes (=0x40 or 0, 32-bit integer) | -| 0xCB | The offset of object palettes from file start (32-bit integer) | +| Offset | Content | +|--------|--------------------------------------------------------------------| +| 0x98 | The size of RAM (32-bit integer) | +| 0x9C | The offset of RAM from file start (32-bit integer) | +| 0xA0 | The size of VRAM (32-bit integer) | +| 0xA4 | The offset of VRAM from file start (32-bit integer) | +| 0xA9 | The size of MBC RAM (32-bit integer) | +| 0xAC | The offset of MBC RAM from file start (32-bit integer) | +| 0xB0 | The size of OAM (=0xA0, 32-bit integer) | +| 0xB4 | The offset of OAM from file start (32-bit integer) | +| 0xB9 | The size of HRAM (=0x7F, 32-bit integer) | +| 0xBC | The offset of HRAM from file start (32-bit integer) | +| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) | +| 0xC4 | The offset of background palettes from file start (32-bit integer) | +| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) | +| 0xCC | The offset of object palettes from file start (32-bit integer) | The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color. +An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros. #### NAME block diff --git a/Core/save_state.c b/Core/save_state.c index 8d00416..e5df68c 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -50,6 +50,7 @@ typedef struct __attribute__((packed)) { uint8_t ime; uint8_t ie; uint8_t execution_mode; // 0 = running; 1 = halted; 2 = stopped + uint8_t _padding; uint8_t io_registers[0x80]; @@ -725,6 +726,10 @@ static void read_bess_buffer(const BESS_buffer_t *buffer, virtual_file_t *file, file->seek(file, LE32(buffer->offset), SEEK_SET); file->read(file, dest, MIN(LE32(buffer->size), max_size)); file->seek(file, pos, SEEK_SET); + + if (LE32(buffer->size) < max_size) { + memset(dest + LE32(buffer->size), 0, max_size - LE32(buffer->size)); + } } static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_sameboy) From 9c1889f4500b29daeb07631649a4f34308e78088 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:43:23 +0300 Subject: [PATCH 09/23] Actually update spec --- BESS.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/BESS.md b/BESS.md index ab4f3ea..d31e174 100644 --- a/BESS.md +++ b/BESS.md @@ -38,14 +38,14 @@ The length of the CORE block is 0xD0 bytes, but implementations are expected to | Offset | Content | |--------|---------------------------------------| -| 0x00 | Major BESS version as a 16-bit integer | -| 0x02 | Minor BESS version as a 16-bit integer | +| 0x00 | Major BESS version as a 16-bit integer | +| 0x02 | Minor BESS version as a 16-bit integer | Both major and minor versions should be 1. Implementations are expected to reject incompatible majors, but still attempt to read newer minor versions. | Offset | Content | |--------|----------------------------------------| -| 0x04 | A four-character ASCII model identifier | +| 0x04 | A four-character ASCII model identifier | BESS uses a four-character string to identify Game Boy models: @@ -58,23 +58,24 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr | Offset | Content | |--------|-------------------------------------------------------| -| 0x08 | The value of the PC register | -| 0x0A | The value of the AF register | -| 0x0C | The value of the BC register | -| 0x0E | The value of the DE register | -| 0x10 | The value of the HL register | -| 0x12 | The value of the SP register | -| 0x14 | The value of IME (0 or 1) | -| 0x15 | The value of the IE register | -| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | -| 0x17 | Reserved, must be 0 | -| 0x18 | The values of every memory-mapped register (128 bytes) | +| 0x08 | The value of the PC register | +| 0x0A | The value of the AF register | +| 0x0C | The value of the BC register | +| 0x0E | The value of the DE register | +| 0x10 | The value of the HL register | +| 0x12 | The value of the SP register | +| 0x14 | The value of IME (0 or 1) | +| 0x15 | The value of the IE register | +| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) | +| 0x17 | Reserved, must be 0 | +| 0x18 | The values of every memory-mapped register (128 bytes) | The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored -* The value of KEY0 (FF4C) must be valid as it determines CGB mode +* The value of KEY0 (FF4C) must be valid as it determines DMG mode when the model is CGB or newer * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect +* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. * Implementations should not start a serial transfer when writing the value of SB * Similarly, no value of NRx4 should trigger a sound pulse on save state load From 4346b063f595d4d058106a6cd81e11bcf4c4e660 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 22:48:05 +0300 Subject: [PATCH 10/23] Wording --- BESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BESS.md b/BESS.md index d31e174..099985c 100644 --- a/BESS.md +++ b/BESS.md @@ -73,7 +73,7 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note: * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored -* The value of KEY0 (FF4C) must be valid as it determines DMG mode when the model is CGB or newer +* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect * If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. From 5b993ed7752e73a54f5b277701c89aea20aaf887 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 23:36:35 +0300 Subject: [PATCH 11/23] Add HuC3 to BESS --- BESS.md | 16 +++++++++- Core/gb.c | 10 +----- Core/gb.h | 8 +++++ Core/save_state.c | 78 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/BESS.md b/BESS.md index 099985c..e935f45 100644 --- a/BESS.md +++ b/BESS.md @@ -135,7 +135,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed. #### RTC block -The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. +The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. The length of this block is 0x30 bytes long and it follows the following structure: @@ -153,6 +153,20 @@ The length of this block is 0x30 bytes long and it follows the following structu | 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding | | 0x28 | UNIX timestamp at the time of the save state (64-bit) | +#### HUC3 block +The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy. + +The length of this block is 0x11 bytes long and it follows the following structure: + +|Offset | Content | +|--------|-------------------------------------------------------| +| 0x00 | UNIX timestamp at the time of the save state (64-bit) | +| 0x08 | RTC minutes (16-bit) | +| 0x0A | RTC days (16-bit) | +| 0x0C | Scheduled alarm time minutes (16-bit) | +| 0x0E | Scheduled alarm time days (16-bit) | +| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) | + #### SGB block The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing. diff --git a/Core/gb.c b/Core/gb.c index 9e06042..e6a5d94 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -578,14 +578,6 @@ typedef struct { uint8_t padding5[3]; } GB_vba_rtc_time_t; -typedef struct __attribute__((packed)) { - uint64_t last_rtc_second; - uint16_t minutes; - uint16_t days; - uint16_t alarm_minutes, alarm_days; - uint8_t alarm_enabled; -} GB_huc3_rtc_time_t; - typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -609,7 +601,7 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ if (gb->cartridge_type->mbc_type == GB_HUC3) { - return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); + return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); } GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); diff --git a/Core/gb.h b/Core/gb.h index 065e176..2cc2137 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -101,6 +101,14 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; +typedef struct __attribute__((packed)) { + uint64_t last_rtc_second; + uint16_t minutes; + uint16_t days; + uint16_t alarm_minutes, alarm_days; + uint8_t alarm_enabled; +} GB_huc3_rtc_time_t; + typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, diff --git a/Core/save_state.c b/Core/save_state.c index e5df68c..d38bd25 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -100,6 +100,12 @@ typedef struct __attribute__((packed)){ uint64_t last_rtc_second; } BESS_RTC_t; +/* Same HuC3 RTC format as used by SameBoy and BGB in battery saves */ +typedef struct __attribute__((packed)){ + BESS_block_t header; + GB_huc3_rtc_time_t data; +} BESS_HUC3_t; + typedef struct __attribute__((packed)) { uint16_t address; uint8_t value; @@ -208,7 +214,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) case GB_HUC1: return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); case GB_HUC3: - return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t); + return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); } } @@ -233,7 +239,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_NAME) - 1 + sizeof(BESS_XOAM_t) + (gb->sgb? sizeof(BESS_SGB_t) : 0) - + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3 block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -582,22 +588,40 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) } save_bess_mbc_block(gb, file); - if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { - BESS_RTC_t bess_rtc = {0,}; - bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; - bess_rtc.real.seconds = gb->rtc_real.seconds; - bess_rtc.real.minutes = gb->rtc_real.minutes; - bess_rtc.real.hours = gb->rtc_real.hours; - bess_rtc.real.days = gb->rtc_real.days; - bess_rtc.real.high = gb->rtc_real.high; - bess_rtc.latched.seconds = gb->rtc_latched.seconds; - bess_rtc.latched.minutes = gb->rtc_latched.minutes; - bess_rtc.latched.hours = gb->rtc_latched.hours; - bess_rtc.latched.days = gb->rtc_latched.days; - bess_rtc.latched.high = gb->rtc_latched.high; - bess_rtc.last_rtc_second = LE64(gb->last_rtc_second); - if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { - goto error; + if (gb->cartridge_type->has_rtc) { + if (gb->cartridge_type ->mbc_type != GB_HUC3) { + BESS_RTC_t bess_rtc = {0,}; + bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))}; + bess_rtc.real.seconds = gb->rtc_real.seconds; + bess_rtc.real.minutes = gb->rtc_real.minutes; + bess_rtc.real.hours = gb->rtc_real.hours; + bess_rtc.real.days = gb->rtc_real.days; + bess_rtc.real.high = gb->rtc_real.high; + bess_rtc.latched.seconds = gb->rtc_latched.seconds; + bess_rtc.latched.minutes = gb->rtc_latched.minutes; + bess_rtc.latched.hours = gb->rtc_latched.hours; + bess_rtc.latched.days = gb->rtc_latched.days; + bess_rtc.latched.high = gb->rtc_latched.high; + bess_rtc.last_rtc_second = LE64(gb->last_rtc_second); + if (file->write(file, &bess_rtc, sizeof(bess_rtc)) != sizeof(bess_rtc)) { + goto error; + } + } + else { + BESS_HUC3_t bess_huc3 = {0,}; + bess_huc3.header = (BESS_block_t){BE32('HUC3'), LE32(sizeof(bess_huc3) - sizeof(bess_huc3.header))}; + + bess_huc3.data = (GB_huc3_rtc_time_t) { + LE64(gb->last_rtc_second), + LE16(gb->huc3_minutes), + LE16(gb->huc3_days), + LE16(gb->huc3_alarm_minutes), + LE16(gb->huc3_alarm_days), + gb->huc3_alarm_enabled, + }; + if (file->write(file, &bess_huc3, sizeof(bess_huc3)) != sizeof(bess_huc3)) { + goto error; + } } } @@ -900,8 +924,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo if (!found_core) goto parse_error; BESS_RTC_t bess_rtc; if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error; - if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) { - if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3) { gb->rtc_real.seconds = bess_rtc.real.seconds; gb->rtc_real.minutes = bess_rtc.real.minutes; gb->rtc_real.hours = bess_rtc.real.hours; @@ -915,6 +939,20 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo gb->last_rtc_second = LE64(bess_rtc.last_rtc_second); } break; + case BE32('HUC3'): + if (!found_core) goto parse_error; + BESS_HUC3_t bess_huc3; + if (LE32(block.size) != sizeof(bess_huc3) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_huc3.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type == GB_HUC3) { + gb->last_rtc_second = LE64(bess_huc3.data.last_rtc_second); + gb->huc3_minutes = LE16(bess_huc3.data.minutes); + gb->huc3_days = LE16(bess_huc3.data.days); + gb->huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); + gb->huc3_alarm_days = LE16(bess_huc3.data.alarm_days); + gb->huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + } + break; case BE32('SGB '): if (!found_core) goto parse_error; if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; From a3a73602fc2396d7d19b459476af96659a7490f8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 01:09:29 +0300 Subject: [PATCH 12/23] ATF is only 0xFD2 bytes, not 0xFE0 --- Core/sgb.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/sgb.h b/Core/sgb.h index 320fb6a..1e67835 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -51,7 +51,8 @@ struct GB_sgb_s { uint16_t effective_palettes[4 * 4]; uint16_t ram_palettes[4 * 512]; uint8_t attribute_map[20 * 18]; - uint8_t attribute_files[0xFE0]; + uint8_t attribute_files[0xFD2]; + uint8_t attribute_files_padding[0xFE0 - 0xFD2]; /* Intro */ int16_t intro_animation; From 6ee488688bd763a96e0b9b81c803aa3b06b174a8 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 01:11:06 +0300 Subject: [PATCH 13/23] Update spec --- BESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BESS.md b/BESS.md index e935f45..9ad36ff 100644 --- a/BESS.md +++ b/BESS.md @@ -187,7 +187,7 @@ The length of this block is 0x39 bytes and it follows the following structure: | 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | | 0x28 | The size of the attribute map (=0x168, 32-bit integer) | | 0x2C | The offset of the attribute map (32-bit integer) | -| 0x30 | The size of the attribute files (=0xfe0, 32-bit integer) | +| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | | 0x34 | The offset of the attribute files (32-bit integer) | | 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) | From dfdbff7304fead42832574bf06eb48bf67bb03d0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 16:01:44 +0300 Subject: [PATCH 14/23] Allow writes to the $a000-$bfff range in the MBC block --- BESS.md | 2 +- Core/save_state.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BESS.md b/BESS.md index 9ad36ff..2595219 100644 --- a/BESS.md +++ b/BESS.md @@ -132,7 +132,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t | 0x9 | The value 0x4000 as a 16-bit integer | | 0xB | The current RAM bank | -An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed. +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. #### RTC block The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. diff --git a/Core/save_state.c b/Core/save_state.c index d38bd25..f279b61 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -916,7 +916,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo for (unsigned i = LE32(block.size); i > 0; i -= 3) { BESS_MBC_pair_t pair; file->read(file, &pair, sizeof(pair)); - if (LE16(pair.address) >= 0x8000) goto parse_error; + if (LE16(pair.address) >= 0x8000 && LE16(pair.address) < 0xA000) goto parse_error; + if (LE16(pair.address) >= 0xC000) goto parse_error; GB_write_memory(&save, LE16(pair.address), pair.value); } break; From fada772cb141aeb24e71a0e5aa00086765bff3ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 20:35:07 +0300 Subject: [PATCH 15/23] Don't use BESS for internal in-memory saves --- Core/debugger.c | 20 ++++++++++---------- Core/rewind.c | 8 ++++---- Core/save_state.c | 31 ++++++++++++++++++++++++++----- Core/save_state.h | 7 +++++++ 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 73bd80a..64fc9ee 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1899,7 +1899,7 @@ static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } uint16_t pc = gb->pc; - GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size(gb)); + GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size_no_bess(gb)); GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label); if (pc != gb->pc) { GB_cpu_disassemble(gb, gb->pc, 5); @@ -2205,8 +2205,8 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) const debugger_command_t *command = find_command(command_string); if (command) { - uint8_t *old_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, old_state); + uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, old_state); bool ret = command->implementation(gb, arguments, modifiers, command); if (!ret) { // Command continues, save state in any case free(gb->undo_state); @@ -2214,9 +2214,9 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) gb->undo_label = command->command; } else { - uint8_t *new_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, new_state); - if (memcmp(new_state, old_state, GB_get_save_state_size(gb)) != 0) { + uint8_t *new_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, new_state); + if (memcmp(new_state, old_state, GB_get_save_state_size_no_bess(gb)) != 0) { // State changed, save the old state as the new undo state free(gb->undo_state); gb->undo_state = old_state; @@ -2306,8 +2306,8 @@ void GB_debugger_run(GB_gameboy_t *gb) if (gb->debug_disable) return; if (!gb->undo_state) { - gb->undo_state = malloc(GB_get_save_state_size(gb)); - GB_save_state_to_buffer(gb, gb->undo_state); + gb->undo_state = malloc(GB_get_save_state_size_no_bess(gb)); + GB_save_state_to_buffer_no_bess(gb, gb->undo_state); } char *input = NULL; @@ -2355,9 +2355,9 @@ next_command: } else if (jump_to_result == JUMP_TO_NONTRIVIAL) { if (!gb->nontrivial_jump_state) { - gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); + gb->nontrivial_jump_state = malloc(GB_get_save_state_size_no_bess(gb)); } - GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); + GB_save_state_to_buffer_no_bess(gb, gb->nontrivial_jump_state); gb->non_trivial_jump_breakpoint_occured = false; should_delete_state = false; } diff --git a/Core/rewind.c b/Core/rewind.c index c3900d6..d305528 100644 --- a/Core/rewind.c +++ b/Core/rewind.c @@ -108,7 +108,7 @@ static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, void GB_rewind_push(GB_gameboy_t *gb) { - const size_t save_size = GB_get_save_state_size(gb); + const size_t save_size = GB_get_save_state_size_no_bess(gb); if (!gb->rewind_sequences) { if (gb->rewind_buffer_length) { gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length); @@ -140,11 +140,11 @@ void GB_rewind_push(GB_gameboy_t *gb) if (!gb->rewind_sequences[gb->rewind_pos].key_state) { gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size); - GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state); + GB_save_state_to_buffer_no_bess(gb, gb->rewind_sequences[gb->rewind_pos].key_state); } else { uint8_t *save_state = malloc(save_size); - GB_save_state_to_buffer(gb, save_state); + GB_save_state_to_buffer_no_bess(gb, save_state); gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] = state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size); free(save_state); @@ -158,7 +158,7 @@ bool GB_rewind_pop(GB_gameboy_t *gb) return false; } - const size_t save_size = GB_get_save_state_size(gb); + const size_t save_size = GB_get_save_state_size_no_bess(gb); if (gb->rewind_sequences[gb->rewind_pos].pos == 0) { GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size); free(gb->rewind_sequences[gb->rewind_pos].key_state); diff --git a/Core/save_state.c b/Core/save_state.c index f279b61..6aa4f37 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -218,7 +218,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) } } -size_t GB_get_save_state_size(GB_gameboy_t *gb) +size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb) { return GB_SECTION_SIZE(header) + GB_SECTION_SIZE(core_state) + sizeof(uint32_t) @@ -232,7 +232,12 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size - + gb->vram_size + + gb->vram_size; +} + +size_t GB_get_save_state_size(GB_gameboy_t *gb) +{ + return GB_get_save_state_size_no_bess(gb) + // BESS + sizeof(BESS_CORE_t) + sizeof(BESS_block_t) // NAME @@ -453,7 +458,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) return 0; } -static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) +static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess) { if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error; if (!DUMP_SECTION(gb, file, core_state)) goto error; @@ -475,6 +480,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error; } + BESS_CORE_t bess_core = {0,}; bess_core.mbc_ram.offset = LE32(file->tell(file)); @@ -495,6 +501,8 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file) goto error; } + if (!append_bess) return 0; + BESS_footer_t bess_footer = { .start_offset = LE32(file->tell(file)), .magic = BE32('BESS'), @@ -694,7 +702,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) .tell = file_tell, .file = f, }; - int ret = save_state_internal(gb, &file); + int ret = save_state_internal(gb, &file, true); fclose(f); return ret; } @@ -709,10 +717,23 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) .position = 0, }; - save_state_internal(gb, &file); + save_state_internal(gb, &file, true); assert(file.position == GB_get_save_state_size(gb)); } +void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer) +{ + virtual_file_t file = { + .write = buffer_write, + .seek = buffer_seek, + .tell = buffer_tell, + .buffer = (uint8_t *)buffer, + .position = 0, + }; + + save_state_internal(gb, &file, false); + assert(file.position == GB_get_save_state_size_no_bess(gb)); +} static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool fix_broken_windows_saves) { diff --git a/Core/save_state.h b/Core/save_state.h index 8e5fc4e..0c447d9 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,4 +27,11 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); + +#ifdef GB_INTERNAL +/* For internal in-memory save states (rewind, debugger) that do not need BESS */ +size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); +void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); +#endif + #endif /* save_state_h */ From 24915e41eb02735771f28de424af71175234530e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 20:56:09 +0300 Subject: [PATCH 16/23] TPP1 in BESS --- Core/save_state.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Core/save_state.c b/Core/save_state.c index 6aa4f37..f97251a 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -215,6 +215,8 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); case GB_HUC3: return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t); + case GB_TPP1: + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_RTC_t); } } @@ -443,6 +445,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file) pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc3.ram_bank}; mbc_block.size = 3 * sizeof(pairs[0]); break; + + case GB_TPP1: + pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->tpp1_rom_bank}; + pairs[1] = (BESS_MBC_pair_t){LE16(0x0001), gb->tpp1_rom_bank >> 8}; + pairs[2] = (BESS_MBC_pair_t){LE16(0x0002), gb->tpp1_rom_bank}; + pairs[3] = (BESS_MBC_pair_t){LE16(0x0003), gb->tpp1_mode}; + mbc_block.size = 4 * sizeof(pairs[0]); + break; } mbc_block.size = LE32(mbc_block.size); From 0af4f1fa4d7632d28b91f8a0c2805617156fe06e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 21:33:13 +0300 Subject: [PATCH 17/23] Clarify SGB multiplayer, handle count = 0 --- BESS.md | 38 +++++++++++++++++++------------------- Core/save_state.c | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/BESS.md b/BESS.md index 2595219..54efe04 100644 --- a/BESS.md +++ b/BESS.md @@ -139,7 +139,7 @@ The RTC block uses the `'RTC '` identifier, and is an optional block that is use The length of this block is 0x30 bytes long and it follows the following structure: -|Offset | Content | +| Offset | Content | |--------|------------------------------------------------------------------------| | 0x00 | Current seconds (1 byte), followed by 3 bytes of padding | | 0x04 | Current minutes (1 byte), followed by 3 bytes of padding | @@ -158,7 +158,7 @@ The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is us The length of this block is 0x11 bytes long and it follows the following structure: -|Offset | Content | +| Offset | Content | |--------|-------------------------------------------------------| | 0x00 | UNIX timestamp at the time of the save state (64-bit) | | 0x08 | RTC minutes (16-bit) | @@ -173,23 +173,23 @@ The SGB block uses the `'SGB '` identifier, and is an optional block that is onl The length of this block is 0x39 bytes and it follows the following structure: -|Offset | Content | -|--------|--------------------------------------------------------------------------------------------------| -| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | -| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | -| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | -| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | -| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | -| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | -| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | -| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | -| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | -| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | -| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | -| 0x2C | The offset of the attribute map (32-bit integer) | -| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | -| 0x34 | The offset of the attribute files (32-bit integer) | -| 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) | +| Offset | Content | +|--------|--------------------------------------------------------------------------------------------------------------------------| +| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) | +| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) | +| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) | +| 0x0C | The offset of the border tilemap (LE 16-bit sequences, 32-bit integer) | +| 0x10 | The size of the border palettes (=0x80, 32-bit integer) | +| 0x14 | The offset of the border palettes (LE 16-bit sequences, 32-bit integer) | +| 0x18 | The size of active colorization palettes (=0x20, 32-bit integer) | +| 0x1C | The offset of the active colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x20 | The size of RAM colorization palettes (=0x1000, 32-bit integer) | +| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) | +| 0x28 | The size of the attribute map (=0x168, 32-bit integer) | +| 0x2C | The offset of the attribute map (32-bit integer) | +| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) | +| 0x34 | The offset of the attribute files (32-bit integer) | +| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) | If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. diff --git a/Core/save_state.c b/Core/save_state.c index f97251a..89924ac 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1032,7 +1032,7 @@ done: gb->sgb->player_count = sgb.multiplayer_state >> 4; gb->sgb->current_player = sgb.multiplayer_state & 0xF; - if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3) { + if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3 || gb->sgb->player_count == 0) { gb->sgb->player_count = 1; gb->sgb->current_player = 0; } From 43fb86320e239537882658cc3d9b4f399ae5f7c0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:05:13 +0300 Subject: [PATCH 18/23] Hard fail on unexpected SGB blocks --- Core/save_state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/save_state.c b/Core/save_state.c index 89924ac..6ef62c1 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -987,6 +987,7 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo break; case BE32('SGB '): if (!found_core) goto parse_error; + if (!gb->sgb) goto parse_error; if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error; file->read(file, &sgb.header + 1, sizeof(BESS_SGB_t) - sizeof(block)); found_sgb = true; From 79f109b463f8e0aac224efe5aba9c687626905f9 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:08:25 +0300 Subject: [PATCH 19/23] Clarify MBC block --- BESS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BESS.md b/BESS.md index 54efe04..d4232ba 100644 --- a/BESS.md +++ b/BESS.md @@ -56,8 +56,8 @@ BESS uses a four-character string to identify Game Boy models: For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E. -| Offset | Content | -|--------|-------------------------------------------------------| +| Offset | Content | +|--------|--------------------------------------------------------| | 0x08 | The value of the PC register | | 0x0A | The value of the AF register | | 0x0C | The value of the BC register | @@ -132,7 +132,7 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t | 0x9 | The value 0x4000 as a 16-bit integer | | 0xB | The current RAM bank | -An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. +An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order) #### RTC block The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB. From 6f0b6407021bdaf574bf2a077f9fce4df89044c0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 22:32:45 +0300 Subject: [PATCH 20/23] More clarifications --- BESS.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/BESS.md b/BESS.md index d4232ba..59ca3db 100644 --- a/BESS.md +++ b/BESS.md @@ -28,11 +28,11 @@ BESS uses a block format where each block contains the following header: | 0 | A four-letter ASCII identifier | | 4 | Length of the block, excluding header | -Every block is followed by another blocked, until the END block is reached. +Every block is followed by another blocked, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure). #### CORE block -The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block. +The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block (But an implementation should allow unknown blocks to appear before it for future compatibility). The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows: @@ -194,5 +194,15 @@ The length of this block is 0x39 bytes and it follows the following structure: If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default. #### END block -The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. +The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0. +## Validation and Failures + +Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation: +* Duplicate CORE block +* A known block, other than NAME, appearing before CORE +* An invalid length for the XOAM, RTC, SGB or HUC3 blocks +* An invalid length of MBC (not a multiple of 3) +* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block +* An SGB block on a save state targeting another model +* An END block with non-zero length \ No newline at end of file From c1509b633934eb3841f61062d263a2016e7231f3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 13 Apr 2021 23:34:49 +0300 Subject: [PATCH 21/23] KEY0 info --- BESS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BESS.md b/BESS.md index 59ca3db..03cb54c 100644 --- a/BESS.md +++ b/BESS.md @@ -74,6 +74,7 @@ The values of memory-mapped registers should be written 'as-is' to memory as if * Unused registers have Don't-Care values which should be ignored * Unused register bits have Don't-Care values which should be ignored * If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode + * Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode. * Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect * If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored. * BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid. From 8a84a5897e5cd44b6fd0c1baa92cc48d7eddbe00 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 15:20:01 +0300 Subject: [PATCH 22/23] Allow drag&drop of state files --- Cocoa/Document.h | 1 + Cocoa/Document.m | 10 ++++++++-- Cocoa/GBView.m | 27 +++++++++++++++++++++++++++ Core/gb.c | 6 ------ Core/save_state.c | 33 +++++++++++++++++++++++++++++++++ Core/save_state.h | 8 +++++++- SDL/gui.c | 13 ++++++++++--- SDL/gui.h | 2 ++ SDL/main.c | 17 +++++++++++++++-- 9 files changed, 103 insertions(+), 14 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index b651646..6effe48 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -44,5 +44,6 @@ -(void) writeMemory:(uint16_t) addr value:(uint8_t)value; -(void) performAtomicBlock: (void (^)())block; -(void) connectLinkCable:(NSMenuItem *)sender; +- (bool)loadStateFile:(const char *)path; @end diff --git a/Cocoa/Document.m b/Cocoa/Document.m index e7812d4..1d9072b 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -1172,12 +1172,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } -- (IBAction)loadState:(id)sender +- (bool)loadStateFile:(const char *)path { bool __block success = false; NSString *error = [self captureOutputForBlock:^{ - success = GB_load_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0; + success = GB_load_state(&gb, path) == 0; }]; if (!success) { @@ -1186,6 +1186,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) if (error) { [GBWarningPopover popoverWithContents:error onWindow:self.mainWindow]; } + return success; +} + +- (IBAction)loadState:(id)sender +{ + [self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String]; } - (IBAction)clearConsole:(id)sender diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 0d834c0..645544f 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -142,6 +142,8 @@ static const uint8_t workboy_vk_to_key[] = { - (void) _init { + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeFileURL, nil]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -626,4 +628,29 @@ static const uint8_t workboy_vk_to_key[] = { return image_buffers[(current_buffer + 2) % self.numberOfBuffers]; } +-(NSDragOperation)draggingEntered:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + if (GB_is_stave_state(fileURL.fileSystemRepresentation)) { + return NSDragOperationGeneric; + } + } + return NSDragOperationNone; +} + +-(BOOL)performDragOperation:(id)sender +{ + NSPasteboard *pboard = [sender draggingPasteboard]; + + if ( [[pboard types] containsObject:NSURLPboardType] ) { + NSURL *fileURL = [NSURL URLFromPasteboard:pboard]; + return [_document loadStateFile:fileURL.fileSystemRepresentation]; + } + + return false; +} + @end diff --git a/Core/gb.c b/Core/gb.c index 8816552..39a265c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -19,12 +19,6 @@ #endif -static inline uint32_t state_magic(void) -{ - if (sizeof(bool) == 1) return 'SAME'; - return 'S4ME'; -} - void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { char *string = NULL; diff --git a/Core/save_state.c b/Core/save_state.c index 6ef62c1..17932ee 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1170,3 +1170,36 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return load_state_internal(gb, &file); } + + +bool GB_is_stave_state(const char *path) +{ + bool ret = false; + FILE *f = fopen(path, "rb"); + if (!f) return false; + uint32_t magic = 0; + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + + // Legacy corrupted Windows save state + if (magic == 0) { + fread(&magic, sizeof(magic), 1, f); + if (magic == state_magic()) { + ret = true; + goto exit; + } + } + + fseek(f, -sizeof(magic), SEEK_END); + fread(&magic, sizeof(magic), 1, f); + if (magic == BE32('BESS')) { + ret = true; + } + +exit: + fclose(f); + return ret; +} diff --git a/Core/save_state.h b/Core/save_state.h index 0c447d9..79e8c06 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -27,8 +27,14 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); - +bool GB_is_stave_state(const char *path); #ifdef GB_INTERNAL +static inline uint32_t state_magic(void) +{ + if (sizeof(bool) == 1) return 'SAME'; + return 'S4ME'; +} + /* For internal in-memory save states (rewind, debugger) that do not need BESS */ size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb); void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer); diff --git a/SDL/gui.c b/SDL/gui.c index 580a3f6..f872af5 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -18,6 +18,7 @@ SDL_Texture *texture = NULL; SDL_PixelFormat *pixel_format = NULL; enum pending_command pending_command; unsigned command_parameter; +char *dropped_state_file = NULL; #ifdef __APPLE__ #define MODIFIER_NAME " " CMD_STRING @@ -1300,9 +1301,15 @@ void run_gui(bool is_running) break; } case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; - return; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + return; + } } case SDL_JOYBUTTONDOWN: { diff --git a/SDL/gui.h b/SDL/gui.h index 5db7aff..baa6789 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -39,12 +39,14 @@ enum pending_command { GB_SDL_RESET_COMMAND, GB_SDL_NEW_FILE_COMMAND, GB_SDL_QUIT_COMMAND, + GB_SDL_LOAD_STATE_FROM_FILE_COMMAND, }; #define GB_SDL_DEFAULT_SCALE_MAX 8 extern enum pending_command pending_command; extern unsigned command_parameter; +extern char *dropped_state_file; typedef enum { JOYPAD_BUTTON_LEFT, diff --git a/SDL/main.c b/SDL/main.c index d10590d..fa74a6a 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -142,8 +142,14 @@ static void handle_events(GB_gameboy_t *gb) break; case SDL_DROPFILE: { - set_filename(event.drop.file, SDL_free); - pending_command = GB_SDL_NEW_FILE_COMMAND; + if (GB_is_stave_state(event.drop.file)) { + dropped_state_file = event.drop.file; + pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND; + } + else { + set_filename(event.drop.file, SDL_free); + pending_command = GB_SDL_NEW_FILE_COMMAND; + } break; } @@ -433,6 +439,13 @@ static bool handle_pending_command(void) end_capturing_logs(true, false); return false; } + + case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: + start_capturing_logs(); + GB_load_state(&gb, dropped_state_file); + end_capturing_logs(true, false); + SDL_free(dropped_state_file); + return false; case GB_SDL_NO_COMMAND: return false; From dd860774109b50b66267106f20d99e0a8d2a6396 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Wed, 14 Apr 2021 15:24:06 +0300 Subject: [PATCH 23/23] Use the older, more available API --- Cocoa/GBView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 645544f..5c1922c 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -142,7 +142,7 @@ static const uint8_t workboy_vk_to_key[] = { - (void) _init { - [self registerForDraggedTypes:[NSArray arrayWithObjects: NSPasteboardTypeFileURL, nil]]; + [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}