Added MBC7 BESS support, documented BESS TPP1

This commit is contained in:
Lior Halphon 2022-06-05 14:09:33 +03:00
parent abf6e5632c
commit 4f91b19a94
5 changed files with 128 additions and 9 deletions

42
BESS.md
View File

@ -176,6 +176,48 @@ The length of this block is 0x11 bytes long and it follows the following structu
| 0x0E | Scheduled alarm time days (16-bit) | | 0x0E | Scheduled alarm time days (16-bit) |
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) | | 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
#### TPP1 block
The TPP1 block uses the `'TPP1'` identifier, and is an optional block that is used while emulating a TPP1 cartridge to store RTC information. This block can be omitted if the ROM header does not specify the inclusion of a RTC.
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 | The current RTC data (4 bytes) |
| 0x0C | The latched RTC data (4 bytes) |
| 0x10 | The value of the MR4 register (8-bits) |
#### MBC7 block
The MBC7 block uses the `'MBC7'` identifier, and is an optional block that is used while emulating an MBC7 cartridge to store the EEPROM communication state and motion control state.
The length of this block is 0xA bytes long and it follows the following structure:
| Offset | Content |
|--------|-------------------------------------------------------|
| 0x00 | Flags (8-bits) |
| 0x01 | Argument bits left (8-bits) |
| 0x02 | Current EEPROM command (16-bits) |
| 0x04 | Pending bits to read (16-bits) |
| 0x06 | Latched gyro X value (16-bits) |
| 0x08 | Latched gyro Y value (16-bits) |
The meaning of the individual bits in flags are:
* Bit 0: Latch ready; set after writing `0x55` to `0xAX0X` and reset after writing `0xAA` to `0xAX1X`
* Bit 1: EEPROM DO line
* Bit 2: EEPROM DI line
* Bit 3: EEPROM CLK line
* Bit 4: EEPROM CS line
* Bit 5: EEPROM write enable; set after an `EWEN` command, reset after an `EWDS` command
* Bits 6-7: Unused.
The current EEPROM command field has bits pushed to its LSB first, padded with zeros. For example, if the ROM clocked a single `1` bit, this field should contain `0b1`; if the ROM later clocks a `0` bit, this field should contain `0b10`.
If the currently transmitted command has an argument, the "Argument bits left" field should contain the number argument bits remaining. Otherwise, it should contain 0.
The "Pending bits to read" field contains the pending bits waiting to be shifted into the DO signal, MSB first, padded with ones.
#### SGB block #### 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. 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

@ -479,7 +479,7 @@ struct GB_gameboy_internal_s {
bool eeprom_cs:1; bool eeprom_cs:1;
uint16_t eeprom_command:11; uint16_t eeprom_command:11;
uint16_t read_bits; uint16_t read_bits;
uint8_t bits_countdown:5; uint8_t argument_bits_left:5;
bool secondary_ram_enable:1; bool secondary_ram_enable:1;
bool eeprom_write_enabled:1; bool eeprom_write_enabled:1;
} mbc7; } mbc7;

View File

@ -1064,7 +1064,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
gb->mbc7.eeprom_do = gb->mbc7.read_bits >> 15; gb->mbc7.eeprom_do = gb->mbc7.read_bits >> 15;
gb->mbc7.read_bits <<= 1; gb->mbc7.read_bits <<= 1;
gb->mbc7.read_bits |= 1; gb->mbc7.read_bits |= 1;
if (gb->mbc7.bits_countdown == 0) { if (gb->mbc7.argument_bits_left == 0) {
/* Not transferring extra bits for a command*/ /* Not transferring extra bits for a command*/
gb->mbc7.eeprom_command <<= 1; gb->mbc7.eeprom_command <<= 1;
gb->mbc7.eeprom_command |= gb->mbc7.eeprom_di; gb->mbc7.eeprom_command |= gb->mbc7.eeprom_di;
@ -1095,7 +1095,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->mbc7.eeprom_write_enabled) { if (gb->mbc7.eeprom_write_enabled) {
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0; ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0;
} }
gb->mbc7.bits_countdown = 16; gb->mbc7.argument_bits_left = 16;
// We still need to process this command, don't erase eeprom_command // We still need to process this command, don't erase eeprom_command
break; break;
case 0xC: case 0xC:
@ -1123,7 +1123,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (gb->mbc7.eeprom_write_enabled) { if (gb->mbc7.eeprom_write_enabled) {
memset(gb->mbc_ram, 0, gb->mbc_ram_size); memset(gb->mbc_ram, 0, gb->mbc_ram_size);
} }
gb->mbc7.bits_countdown = 16; gb->mbc7.argument_bits_left = 16;
// We still need to process this command, don't erase eeprom_command // We still need to process this command, don't erase eeprom_command
break; break;
} }
@ -1131,10 +1131,10 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
} }
else { else {
// We're shifting in extra bits for a WRITE/WRAL command // We're shifting in extra bits for a WRITE/WRAL command
gb->mbc7.bits_countdown--; gb->mbc7.argument_bits_left--;
gb->mbc7.eeprom_do = true; gb->mbc7.eeprom_do = true;
if (gb->mbc7.eeprom_di) { if (gb->mbc7.eeprom_di) {
uint16_t bit = LE16(1 << gb->mbc7.bits_countdown); uint16_t bit = LE16(1 << gb->mbc7.argument_bits_left);
if (gb->mbc7.eeprom_command & 0x100) { if (gb->mbc7.eeprom_command & 0x100) {
// WRITE // WRITE
((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit; ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit;
@ -1146,7 +1146,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
} }
} }
} }
if (gb->mbc7.bits_countdown == 0) { // We're done if (gb->mbc7.argument_bits_left == 0) { // We're done
gb->mbc7.eeprom_command = 0; gb->mbc7.eeprom_command = 0;
gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle gb->mbc7.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle
} }

View File

@ -119,6 +119,28 @@ typedef struct __attribute__((packed)){
GB_huc3_rtc_time_t data; GB_huc3_rtc_time_t data;
} BESS_HUC3_t; } BESS_HUC3_t;
typedef struct __attribute__((packed)) {
BESS_block_t header;
// Flags
bool latch_ready:1;
bool eeprom_do:1;
bool eeprom_di:1;
bool eeprom_clk:1;
bool eeprom_cs:1;
bool eeprom_write_enabled:1;
uint8_t padding:2;
uint8_t argument_bits_left;
uint16_t eeprom_command;
uint16_t read_bits;
uint16_t x_latch;
uint16_t y_latch;
} BESS_MBC7_t;
typedef struct __attribute__((packed)){ typedef struct __attribute__((packed)){
BESS_block_t header; BESS_block_t header;
uint64_t last_rtc_second; uint64_t last_rtc_second;
@ -232,6 +254,8 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart)
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0); return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + (cart->has_rtc? sizeof(BESS_RTC_t) : 0);
case GB_MBC5: case GB_MBC5:
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t); return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
case GB_MBC7:
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_MBC7_t);
case GB_MMM01: case GB_MMM01:
return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t); return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t);
case GB_HUC1: case GB_HUC1:
@ -270,7 +294,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
+ sizeof(BESS_CORE_t) + sizeof(BESS_CORE_t)
+ sizeof(BESS_XOAM_t) + sizeof(BESS_XOAM_t)
+ (gb->sgb? sizeof(BESS_SGB_t) : 0) + (gb->sgb? sizeof(BESS_SGB_t) : 0)
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1/MBC7 block
+ sizeof(BESS_block_t) // END block + sizeof(BESS_block_t) // END block
+ sizeof(BESS_footer_t); + sizeof(BESS_footer_t);
} }
@ -435,6 +459,12 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank}; pairs[3] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc5.ram_bank};
mbc_block.size = 4 * sizeof(pairs[0]); mbc_block.size = 4 * sizeof(pairs[0]);
break; break;
case GB_MBC7:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->mbc_ram_enable? 0xA : 0x0};
pairs[1] = (BESS_MBC_pair_t){LE16(0x2000), gb->mbc7.rom_bank};
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->mbc7.secondary_ram_enable? 0x40 : 0};
mbc_block.size = 3 * sizeof(pairs[0]);
break;
case GB_MMM01: case GB_MMM01:
pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)}; pairs[0] = (BESS_MBC_pair_t){LE16(0x2000), (gb->mmm01.rom_bank_low & (gb->mmm01.rom_bank_mask << 1)) | (gb->mmm01.rom_bank_mid << 5)};
pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)}; pairs[1] = (BESS_MBC_pair_t){LE16(0x6000), gb->mmm01.mbc1_mode | (gb->mmm01.rom_bank_mask << 2) | (gb->mmm01.multiplex_mode << 6)};
@ -700,6 +730,30 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
} }
} }
if (gb->cartridge_type ->mbc_type == GB_MBC7) {
BESS_MBC7_t bess_mbc7 = {
.latch_ready = gb->mbc7.latch_ready,
.eeprom_do = gb->mbc7.eeprom_do,
.eeprom_di = gb->mbc7.eeprom_di,
.eeprom_clk = gb->mbc7.eeprom_clk,
.eeprom_cs = gb->mbc7.eeprom_cs,
.eeprom_write_enabled = gb->mbc7.eeprom_write_enabled,
.argument_bits_left = gb->mbc7.argument_bits_left,
.eeprom_command = LE16(gb->mbc7.eeprom_command),
.read_bits = LE16(gb->mbc7.read_bits),
.x_latch = LE16(gb->mbc7.x_latch),
.y_latch = LE16(gb->mbc7.y_latch),
};
bess_mbc7.header = (BESS_block_t){BE32('MBC7'), LE32(sizeof(bess_mbc7) - sizeof(bess_mbc7.header))};
if (file->write(file, &bess_mbc7, sizeof(bess_mbc7)) != sizeof(bess_mbc7)) {
goto error;
}
}
bool needs_sgb_padding = false; bool needs_sgb_padding = false;
if (gb->sgb) { if (gb->sgb) {
/* BESS SGB */ /* BESS SGB */
@ -1089,6 +1143,29 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i]; save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i];
} }
save.tpp1_mr4 = bess_tpp1.mr4; save.tpp1_mr4 = bess_tpp1.mr4;
break;
case BE32('MBC7'):
if (!found_core) goto parse_error;
BESS_MBC7_t bess_mbc7;
if (LE32(block.size) != sizeof(bess_mbc7) - sizeof(block)) goto parse_error;
if (file->read(file, &bess_mbc7.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
if (gb->cartridge_type->mbc_type != GB_MBC7) break;
save.mbc7.latch_ready = bess_mbc7.latch_ready;
save.mbc7.eeprom_do = bess_mbc7.eeprom_do;
save.mbc7.eeprom_di = bess_mbc7.eeprom_di;
save.mbc7.eeprom_clk = bess_mbc7.eeprom_clk;
save.mbc7.eeprom_cs = bess_mbc7.eeprom_cs;
save.mbc7.eeprom_write_enabled = bess_mbc7.eeprom_write_enabled;
save.mbc7.argument_bits_left = bess_mbc7.argument_bits_left;
save.mbc7.eeprom_command = LE16(bess_mbc7.eeprom_command);
save.mbc7.read_bits = LE16(bess_mbc7.read_bits);
save.mbc7.x_latch = LE16(bess_mbc7.x_latch);
save.mbc7.y_latch = LE16(bess_mbc7.y_latch);
break; break;
case BE32('SGB '): case BE32('SGB '):
if (!found_core) goto parse_error; if (!found_core) goto parse_error;