From 5b993ed7752e73a54f5b277701c89aea20aaf887 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Mon, 12 Apr 2021 23:36:35 +0300 Subject: [PATCH] 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;