diff --git a/Core/gb.c b/Core/gb.c index 39a265c..7226646 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -572,6 +572,15 @@ typedef struct { uint8_t padding5[3]; } GB_vba_rtc_time_t; +typedef struct __attribute__((packed)) { + uint32_t magic; + uint16_t version; + uint8_t mr4; + uint8_t reserved; + uint64_t last_rtc_second; + uint8_t rtc_data[4]; +} GB_tpp1_rtc_save_t; + typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -589,6 +598,18 @@ typedef union { } vba64; } GB_rtc_save_t; +static void GB_fill_tpp1_save_data(GB_gameboy_t *gb, GB_tpp1_rtc_save_t *data) +{ + data->magic = BE32('TPP1'); + data->version = BE16(0x100); + data->mr4 = gb->tpp1_mr4; + data->reserved = 0; + data->last_rtc_second = LE64(time(NULL)); + unrolled for (unsigned i = 4; i--;) { + data->rtc_data[i] = gb->rtc_real.data[i ^ 3]; + } +} + int GB_save_battery_size(GB_gameboy_t *gb) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. @@ -599,6 +620,11 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (gb->cartridge_type->mbc_type == GB_HUC3) { return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t); } + + if (gb->cartridge_type->mbc_type == GB_TPP1) { + return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t); + } + GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); } @@ -613,7 +639,13 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); - if (gb->cartridge_type->mbc_type == GB_HUC3) { + if (gb->cartridge_type->mbc_type == GB_TPP1) { + buffer += gb->mbc_ram_size; + GB_tpp1_rtc_save_t rtc_save; + GB_fill_tpp1_save_data(gb, &rtc_save); + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->mbc_type == GB_HUC3) { buffer += gb->mbc_ram_size; #ifdef GB_BIG_ENDIAN @@ -676,7 +708,16 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) fclose(f); return EIO; } - if (gb->cartridge_type->mbc_type == GB_HUC3) { + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + GB_fill_tpp1_save_data(gb, &rtc_save); + + if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->mbc_type == GB_HUC3) { #ifdef GB_BIG_ENDIAN GB_huc3_rtc_time_t rtc_save = { __builtin_bswap64(gb->last_rtc_second), @@ -731,6 +772,14 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return errno; } +static void GB_load_tpp1_save_data(GB_gameboy_t *gb, const GB_tpp1_rtc_save_t *data) +{ + gb->last_rtc_second = LE64(data->last_rtc_second); + unrolled for (unsigned i = 4; i--;) { + gb->rtc_real.data[i ^ 3] = data->rtc_data[i]; + } +} + void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) { memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size)); @@ -738,6 +787,22 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t goto reset_rtc; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + if (size - gb->mbc_ram_size < sizeof(rtc_save)) { + goto reset_rtc; + } + memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save)); + + GB_load_tpp1_save_data(gb, &rtc_save); + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { GB_huc3_rtc_time_t rtc_save; if (size - gb->mbc_ram_size < sizeof(rtc_save)) { @@ -847,6 +912,21 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) goto reset_rtc; } + if (gb->cartridge_type->mbc_type == GB_TPP1) { + GB_tpp1_rtc_save_t rtc_save; + if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + goto reset_rtc; + } + + GB_load_tpp1_save_data(gb, &rtc_save); + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } + if (gb->cartridge_type->mbc_type == GB_HUC3) { GB_huc3_rtc_time_t rtc_save; if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) { diff --git a/Core/gb.h b/Core/gb.h index 4e5c4cc..c6a7f7c 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -98,6 +98,13 @@ typedef union { uint8_t days; uint8_t high; }; + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours:5; + uint8_t weekday:3; + uint8_t weeks; + } tpp1; uint8_t data[5]; } GB_rtc_time_t; @@ -515,6 +522,7 @@ struct GB_gameboy_internal_s { uint64_t last_rtc_second; bool rtc_latch; uint32_t rtc_cycles; + uint8_t tpp1_mr4; ); /* Video Display */ @@ -781,8 +789,6 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * void *GB_get_user_data(GB_gameboy_t *gb); void GB_set_user_data(GB_gameboy_t *gb, void *data); - - int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); @@ -850,5 +856,5 @@ unsigned GB_get_screen_width(GB_gameboy_t *gb); unsigned GB_get_screen_height(GB_gameboy_t *gb); double GB_get_usual_frame_rate(GB_gameboy_t *gb); unsigned GB_get_player_count(GB_gameboy_t *gb); - + #endif /* GB_h */ diff --git a/Core/memory.c b/Core/memory.c index 0082131..a25b860 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -185,30 +185,13 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) 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 3: return gb->rumble_strength | gb->tpp1_mr4; } case 2: case 3: break; // Read RAM case 5: - switch (addr & 3) { - 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; - } + return gb->rtc_latched.data[(addr & 3) ^ 3]; default: return 0xFF; } @@ -625,20 +608,17 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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 &= ~0xe0; - gb->rtc_real.high |= flags; break; } case 0x14: - gb->rtc_real.high &= ~0x80; + gb->tpp1_mr4 &= ~0x8; break; case 0x18: - gb->rtc_real.high |= 0x40; + gb->tpp1_mr4 &= ~0x4; break; case 0x19: - gb->rtc_real.high &= ~0x40; + gb->tpp1_mr4 |= 0x4; break; case 0x20: @@ -776,32 +756,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) 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; - 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: { - 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; - if ((value & 0xE0) == 0xE0) { // Illegal weekday - gb->rtc_latched.high |= 0x20; - } - return; - } - case 2: gb->rtc_latched.minutes = value; return; - case 3: gb->rtc_latched.seconds = value; return; - } + gb->rtc_latched.data[(addr & 3) ^ 3] = value; return; default: return; diff --git a/Core/save_state.c b/Core/save_state.c index 4516daa..af60203 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -112,6 +112,14 @@ typedef struct __attribute__((packed)){ GB_huc3_rtc_time_t data; } BESS_HUC3_t; +typedef struct __attribute__((packed)){ + BESS_block_t header; + uint64_t last_rtc_second; + uint8_t real_rtc_data[4]; + uint8_t latched_rtc_data[4]; + uint8_t mr4; +} BESS_TPP1_t; + typedef struct __attribute__((packed)) { uint16_t address; uint8_t value; @@ -222,7 +230,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart) 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); + return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_TPP1_t); } } @@ -253,7 +261,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 block + + bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block + sizeof(BESS_block_t) // END block + sizeof(BESS_footer_t); } @@ -630,7 +638,22 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe save_bess_mbc_block(gb, file); if (gb->cartridge_type->has_rtc) { - if (gb->cartridge_type ->mbc_type != GB_HUC3) { + if (gb->cartridge_type ->mbc_type == GB_TPP1) { + BESS_TPP1_t bess_tpp1 = {0,}; + bess_tpp1.header = (BESS_block_t){BE32('TPP1'), LE32(sizeof(bess_tpp1) - sizeof(bess_tpp1.header))}; + + bess_tpp1.last_rtc_second = LE64(gb->last_rtc_second); + unrolled for (unsigned i = 4; i--;) { + bess_tpp1.real_rtc_data[i] = gb->rtc_real.data[i ^ 3]; + bess_tpp1.latched_rtc_data[i] = gb->rtc_latched.data[i ^ 3]; + } + bess_tpp1.mr4 = gb->tpp1_mr4; + + if (file->write(file, &bess_tpp1, sizeof(bess_tpp1)) != sizeof(bess_tpp1)) { + goto error; + } + } + else 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; @@ -997,33 +1020,51 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo BESS_RTC_t bess_rtc; if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_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; - gb->rtc_real.days = bess_rtc.real.days; - gb->rtc_real.high = bess_rtc.real.high; - gb->rtc_latched.seconds = bess_rtc.latched.seconds; - gb->rtc_latched.minutes = bess_rtc.latched.minutes; - gb->rtc_latched.hours = bess_rtc.latched.hours; - gb->rtc_latched.days = bess_rtc.latched.days; - gb->rtc_latched.high = bess_rtc.latched.high; - gb->last_rtc_second = LE64(bess_rtc.last_rtc_second); + if (!gb->cartridge_type->has_rtc || gb->cartridge_type->mbc_type != GB_MBC3) break; + save.rtc_real.seconds = bess_rtc.real.seconds; + save.rtc_real.minutes = bess_rtc.real.minutes; + save.rtc_real.hours = bess_rtc.real.hours; + save.rtc_real.days = bess_rtc.real.days; + save.rtc_real.high = bess_rtc.real.high; + save.rtc_latched.seconds = bess_rtc.latched.seconds; + save.rtc_latched.minutes = bess_rtc.latched.minutes; + save.rtc_latched.hours = bess_rtc.latched.hours; + save.rtc_latched.days = bess_rtc.latched.days; + save.rtc_latched.high = bess_rtc.latched.high; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_rtc.last_rtc_second), time(NULL)); } + 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; + if (gb->cartridge_type->mbc_type != GB_HUC3) break; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_huc3.data.last_rtc_second), time(NULL)); } + save.huc3_minutes = LE16(bess_huc3.data.minutes); + save.huc3_days = LE16(bess_huc3.data.days); + save.huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes); + save.huc3_alarm_days = LE16(bess_huc3.data.alarm_days); + save.huc3_alarm_enabled = bess_huc3.data.alarm_enabled; + break; + case BE32('TPP1'): + if (!found_core) goto parse_error; + BESS_TPP1_t bess_tpp1; + if (LE32(block.size) != sizeof(bess_tpp1) - sizeof(block)) goto parse_error; + if (file->read(file, &bess_tpp1.header + 1, LE32(block.size)) != LE32(block.size)) goto error; + if (gb->cartridge_type->mbc_type != GB_TPP1) break; + if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) { + save.last_rtc_second = MIN(LE64(bess_tpp1.last_rtc_second), time(NULL)); + } + unrolled for (unsigned i = 4; i--;) { + save.rtc_real.data[i ^ 3] = bess_tpp1.real_rtc_data[i]; + save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i]; + } + save.tpp1_mr4 = bess_tpp1.mr4; break; case BE32('SGB '): if (!found_core) goto parse_error; diff --git a/Core/timing.c b/Core/timing.c index d240525..7b79b72 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -283,27 +283,31 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) } return; } + bool running = false; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + running = gb->tpp1_mr4 & 0x4; + } + else { + running = (gb->rtc_real.high & 0x40) == 0; + } - if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + if (running) { /* is timer running? */ 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->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++; + if (gb->cartridge_type->mbc_type == GB_TPP1) { + if (++gb->rtc_real.tpp1.weekday == 7) { + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ } } - else { - if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ - gb->rtc_real.high |= 0x80; /* Overflow bit */ - } - - gb->rtc_real.high ^= 1; + } + else if (++gb->rtc_real.days == 0) { + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ + gb->rtc_real.high |= 0x80; /* Overflow bit */ } + + gb->rtc_real.high ^= 1; } } @@ -315,21 +319,21 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles) if (++gb->rtc_real.minutes != 60) continue; gb->rtc_real.minutes = 0; - if (++gb->rtc_real.hours != 24) continue; - gb->rtc_real.hours = 0; - - if (++gb->rtc_real.days != 0) continue; - 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++; + if (++gb->rtc_real.tpp1.hours != 24) continue; + gb->rtc_real.tpp1.hours = 0; + if (++gb->rtc_real.tpp1.weekday != 7) continue; + gb->rtc_real.tpp1.weekday = 0; + if (++gb->rtc_real.tpp1.weeks == 0) { + gb->tpp1_mr4 |= 8; /* Overflow bit */ } } else { + if (++gb->rtc_real.hours != 24) continue; + gb->rtc_real.hours = 0; + + if (++gb->rtc_real.days != 0) continue; + if (gb->rtc_real.high & 1) { /* Bit 8 of days*/ gb->rtc_real.high |= 0x80; /* Overflow bit */ }