diff --git a/Core/debugger.c b/Core/debugger.c index 20884c6..0f08fb1 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -1431,8 +1431,8 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg [GB_MBC2] = "MBC2", [GB_MBC3] = "MBC3", [GB_MBC5] = "MBC5", - [GB_HUC1] = "HUC1", - [GB_HUC3] = "HUC3", + [GB_HUC1] = "HUC-1", + [GB_HUC3] = "HUC-3", }; GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]); } diff --git a/Core/gb.c b/Core/gb.c index a1f573c..d0632dd 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -560,6 +560,12 @@ 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; +} GB_huc3_rtc_time_t; + typedef union { struct __attribute__((packed)) { GB_rtc_time_t rtc_real; @@ -582,6 +588,9 @@ int GB_save_battery_size(GB_gameboy_t *gb) if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. 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); + } GB_rtc_save_t rtc_save_size; return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); } @@ -595,7 +604,25 @@ 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->has_rtc) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + buffer += gb->mbc_ram_size; + +#ifdef GB_BIG_ENDIAN + GB_huc3_rtc_time_t rtc_save = { + __builtin_bswap64(gb->last_rtc_second), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + }; +#endif + memcpy(buffer, &rtc_save, sizeof(rtc_save)); + } + else if (gb->cartridge_type->has_rtc) { GB_rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; @@ -633,7 +660,27 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) fclose(f); return EIO; } - if (gb->cartridge_type->has_rtc) { + 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), + __builtin_bswap16(gb->huc3_minutes), + __builtin_bswap16(gb->huc3_days), + }; +#else + GB_huc3_rtc_time_t rtc_save = { + gb->last_rtc_second, + gb->huc3_minutes, + gb->huc3_days, + }; +#endif + + if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) { + fclose(f); + return EIO; + } + } + else if (gb->cartridge_type->has_rtc) { GB_rtc_save_t rtc_save = {{{{0,}},},}; rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; @@ -668,6 +715,28 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t if (size <= gb->mbc_ram_size) { goto reset_rtc; } + + if (gb->cartridge_type->mbc_type == GB_HUC3) { + GB_huc3_rtc_time_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)); +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } GB_rtc_save_t rtc_save; memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); @@ -731,6 +800,8 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; exit: return; } @@ -746,6 +817,27 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { goto reset_rtc; } + + 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) { + goto reset_rtc; + } +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.last_rtc_second); + gb->huc3_minutes = __builtin_bswap16(rtc_save.minutes); + gb->huc3_days = __builtin_bswap16(rtc_save.days); +#else + gb->last_rtc_second = rtc_save.last_rtc_second; + gb->huc3_minutes = rtc_save.minutes; + gb->huc3_days = rtc_save.days; +#endif + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + return; + } GB_rtc_save_t rtc_save; switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) { @@ -808,6 +900,8 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path) reset_rtc: gb->last_rtc_second = time(NULL); gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ + gb->huc3_days = 0xFFFF; + gb->huc3_minutes = 0xFFF; exit: fclose(f); return; diff --git a/Core/gb.h b/Core/gb.h index 29d394f..f5f7df5 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -422,8 +422,9 @@ struct GB_gameboy_internal_s { } huc1; struct { - uint8_t rom_bank; - uint8_t ram_bank; + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; } huc3; }; uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ @@ -431,6 +432,13 @@ struct GB_gameboy_internal_s { uint8_t camera_registers[0x36]; bool rumble_state; bool cart_ir; + + // TODO: move to huc3 struct when breaking save compat + uint8_t huc3_mode; + uint8_t huc3_access_index; + uint16_t huc3_minutes, huc3_days; + uint8_t huc3_read; + uint8_t huc3_access_flags; ); diff --git a/Core/mbc.c b/Core/mbc.c index 2ee53e8..72073f6 100644 --- a/Core/mbc.c +++ b/Core/mbc.c @@ -37,8 +37,8 @@ const GB_cartridge_t GB_cart_defs[256] = { [0xFC] = { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) - { GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only) - { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings) + { GB_HUC3 , GB_STANDARD_MBC, true , true , true, false}, // FEh HuC3 + { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY }; void GB_update_mbc_mappings(GB_gameboy_t *gb) diff --git a/Core/memory.c b/Core/memory.c index 6a65a98..fbf1318 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -113,6 +113,11 @@ static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); } +static bool effective_ir_input(GB_gameboy_t *gb) +{ + return gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; +} + static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) { if (addr < 0x100 && !gb->boot_rom_finished) { @@ -146,12 +151,33 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + switch (gb->huc3_mode) { + case 0xC: // RTC read + if (gb->huc3_access_flags == 0x2) { + return 1; + } + return gb->huc3_read; + case 0xD: // RTC status + return 1; + case 0xE: // IR mode + return effective_ir_input(gb); // TODO: What are the other bits? + default: + GB_log(gb, "Unsupported HuC-3 mode %x read: %04x\n", gb->huc3_mode, addr); + return 1; // TODO: What happens in this case? + case 0: // TODO: R/O RAM? (or is it disabled?) + case 0xA: // RAM + break; + } + } + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_subtype != GB_CAMERA && - gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF; + gb->cartridge_type->mbc_type != GB_HUC1 && + gb->cartridge_type->mbc_type != GB_HUC3) return 0xFF; if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { - return 0xc0 | gb->cart_ir | gb->infrared_input | (gb->io_registers[GB_IO_RP] & 1); + return 0xc0 | effective_ir_input(gb); } if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { @@ -383,9 +409,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) case GB_IO_RP: { if (!gb->cgb_mode) return 0xFF; /* You will read your own IR LED if it's on. */ - bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1) || gb->cart_ir; uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; - if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { + if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && effective_ir_input(gb)) { ret |= 2; } return ret; @@ -504,7 +529,10 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case GB_HUC3: switch (addr & 0xF000) { - case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x0000: case 0x1000: + gb->huc3_mode = value & 0xF; + gb->mbc_ram_enable = gb->huc3_mode == 0xA; + break; case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; } @@ -524,19 +552,82 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + switch (gb->huc3_mode) { + case 0xB: // RTC Write + switch (value >> 4) { + case 1: + if (gb->huc3_access_index < 3) { + gb->huc3_read = (gb->huc3_minutes >> (gb->huc3_access_index * 4)) & 0xF; + } + else if (gb->huc3_access_index < 7) { + gb->huc3_read = (gb->huc3_days >> ((gb->huc3_access_index - 3) * 4)) & 0xF; + } + else { + GB_log(gb, "Attempting to read from unsupported HuC-3 register: %03x\n", gb->huc3_access_index); + } + gb->huc3_access_index++; + return; + case 3: + if (gb->huc3_access_index < 3) { + gb->huc3_minutes &= ~(0xF << (gb->huc3_access_index * 4)); + gb->huc3_minutes |= ((value & 0xF) << (gb->huc3_access_index * 4)); + } + else if (gb->huc3_access_index < 7) { + gb->huc3_days &= ~(0xF << ((gb->huc3_access_index - 3) * 4)); + gb->huc3_days |= ((value & 0xF) << ((gb->huc3_access_index - 3) * 4)); + } + gb->huc3_access_index++; + return; + case 4: + gb->huc3_access_index &= 0xF0; + gb->huc3_access_index |= value & 0xF; + return; + case 5: + gb->huc3_access_index &= 0x0F; + gb->huc3_access_index |= (value & 0xF) << 4; + return; + case 6: + gb->huc3_access_flags = (value & 0xF); + return; + + default: + GB_log(gb, "HuC-3 RTC Write %02x\n", value); + break; + } + + return; + case 0xD: // RTC status + // Not sure what writes here mean, they're always 0xFE + return; + case 0xE: // IR mode + gb->cart_ir = value & 1; + return; + default: + GB_log(gb, "Unsupported HuC-3 mode %x write: [%04x] = %02x\n", gb->huc3_mode, addr, value); + return; + case 0: // Disabled + case 0xA: // RAM + break; + } + } + if (gb->camera_registers_mapped) { GB_camera_write_register(gb, addr, value); return; } - if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && gb->cartridge_type->mbc_type != GB_HUC1) return; + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) + && gb->cartridge_type->mbc_type != GB_HUC1) return; if (gb->cartridge_type->mbc_type == GB_HUC1 && gb->huc1.mode) { - if (gb->cart_ir != (value & 1) && gb->infrared_callback) { - gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); + bool old_input = effective_ir_input(gb); + gb->cart_ir = value & 1; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); gb->cycles_since_ir_change = 0; } - gb->cart_ir = value & 1; return; } @@ -943,13 +1034,13 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (!GB_is_cgb(gb)) { return; } - if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { - if (gb->infrared_callback) { - gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); - gb->cycles_since_ir_change = 0; - } - } + bool old_input = effective_ir_input(gb); gb->io_registers[GB_IO_RP] = value; + bool new_input = effective_ir_input(gb); + if (new_input != old_input) { + gb->infrared_callback(gb, new_input, gb->cycles_since_ir_change); + gb->cycles_since_ir_change = 0; + } return; } diff --git a/Core/sm83_cpu.c b/Core/sm83_cpu.c index 13f05df..d0b8ec4 100644 --- a/Core/sm83_cpu.c +++ b/Core/sm83_cpu.c @@ -917,10 +917,7 @@ static void halt(GB_gameboy_t *gb, uint8_t opcode) { assert(gb->pending_cycles == 4); gb->pending_cycles = 0; - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); - GB_advance_cycles(gb, 1); + GB_advance_cycles(gb, 4); gb->halted = true; /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ diff --git a/Core/timing.c b/Core/timing.c index f734caf..17983bc 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -279,6 +279,18 @@ void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) void GB_rtc_run(GB_gameboy_t *gb) { + if (gb->cartridge_type->mbc_type == GB_HUC3) { + time_t current_time = time(NULL); + while (gb->last_rtc_second / 60 < current_time / 60) { + gb->last_rtc_second += 60; + gb->huc3_minutes++; + if (gb->huc3_minutes == 60 * 24) { + gb->huc3_days++; + gb->huc3_minutes = 0; + } + } + return; + } if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ time_t current_time = time(NULL); while (gb->last_rtc_second < current_time) { diff --git a/Core/timing.h b/Core/timing.h index 02ca54c..d4fa07f 100644 --- a/Core/timing.h +++ b/Core/timing.h @@ -15,7 +15,6 @@ enum { GB_TIMA_RELOADED = 2 }; -#define GB_HALT_VALUE (0xFFFF) #define GB_SLEEP(gb, unit, state, cycles) do {\ (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \ @@ -26,12 +25,10 @@ enum { }\ } while (0) -#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE - #define GB_STATE_MACHINE(gb, unit, cycles, divisor) \ static const int __state_machine_divisor = divisor;\ (gb)->unit##_cycles += cycles; \ -if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\ +if ((gb)->unit##_cycles <= 0) {\ return;\ }\ switch ((gb)->unit##_state)