Add HuC3 to BESS

This commit is contained in:
Lior Halphon 2021-04-12 23:36:35 +03:00
parent 4346b063f5
commit 5b993ed775
4 changed files with 82 additions and 30 deletions

16
BESS.md
View File

@ -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.

View File

@ -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);

View File

@ -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,

View File

@ -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;