Redo TPP1 saving, fix RTC and HUC3 in BESS

This commit is contained in:
Lior Halphon 2021-04-16 00:35:54 +03:00
parent f0a6488546
commit 87a2d48675
5 changed files with 190 additions and 104 deletions

View File

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

View File

@ -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 */

View File

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

View File

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

View File

@ -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 */
}