diff --git a/BESS.md b/BESS.md index 7c9296e..1807943 100644 --- a/BESS.md +++ b/BESS.md @@ -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) | | 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 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.h b/Core/gb.h index 04d7f27..11f0b3b 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -479,7 +479,7 @@ struct GB_gameboy_internal_s { bool eeprom_cs:1; uint16_t eeprom_command:11; uint16_t read_bits; - uint8_t bits_countdown:5; + uint8_t argument_bits_left:5; bool secondary_ram_enable:1; bool eeprom_write_enabled:1; } mbc7; diff --git a/Core/memory.c b/Core/memory.c index 9c9048b..1d71490 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -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.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*/ gb->mbc7.eeprom_command <<= 1; 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) { ((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 break; 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) { 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 break; } @@ -1131,10 +1131,10 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } else { // 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; 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) { // WRITE ((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.read_bits = (gb->mbc7.eeprom_command & 0x100)? 0xFF : 0x3FFF; // Emulate some time to settle } diff --git a/Core/save_state.c b/Core/save_state.c index d24e2f1..ebb3646 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -119,6 +119,28 @@ typedef struct __attribute__((packed)){ GB_huc3_rtc_time_t data; } 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)){ BESS_block_t header; 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); case GB_MBC5: 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: return sizeof(BESS_block_t) + 8 * sizeof(BESS_MBC_pair_t); case GB_HUC1: @@ -270,7 +294,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + sizeof(BESS_CORE_t) + sizeof(BESS_XOAM_t) + (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_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}; mbc_block.size = 4 * sizeof(pairs[0]); 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: 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)}; @@ -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; if (gb->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.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; case BE32('SGB '): if (!found_core) goto parse_error; diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index c35e539..f369e46 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -879,7 +879,7 @@ static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ cycle_write(gb, gb->hl, gb->y); \ } -LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) + LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a)