mirror of https://github.com/bsnes-emu/bsnes.git
Emulate HuC-3’s IR and RTC
This commit is contained in:
parent
2cc980755e
commit
a9023d08c6
|
@ -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]);
|
||||
}
|
||||
|
|
98
Core/gb.c
98
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;
|
||||
|
|
12
Core/gb.h
12
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;
|
||||
);
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
121
Core/memory.c
121
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue