diff --git a/CHANGES b/CHANGES index c35632def..9d9c5d807 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Features: - Cheat code support in homebrew ports - Acclerometer and gyro support for controllers on PC - Support for combo "Super Game Boy Color" SGB + GBC ROM hacks + - Improved support for HuC-3 mapper - Support for 64 kiB SRAM saves used in some bootlegs - Discord Rich Presence now supports time elapsed - Additional scaling shaders diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 7ba97e1eb..d75819d2c 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -182,6 +182,7 @@ struct mCoreCallbacks { void (*shutdown)(void* context); void (*keysRead)(void* context); void (*savedataUpdated)(void* context); + void (*alarm)(void* context); }; DECLARE_VECTOR(mCoreCallbacksList, struct mCoreCallbacks); diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 29ffb6960..59691682c 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -102,6 +102,41 @@ enum GBTAMA5Register { GBTAMA5_READ_HI = 0xD, }; +enum GBHuC3Register { + GBHUC3_RTC_MINUTES_LO = 0x10, + GBHUC3_RTC_MINUTES_MI = 0x11, + GBHUC3_RTC_MINUTES_HI = 0x12, + GBHUC3_RTC_DAYS_LO = 0x13, + GBHUC3_RTC_DAYS_MI = 0x14, + GBHUC3_RTC_DAYS_HI = 0x15, + GBHUC3_RTC_ENABLE = 0x16, + GBHUC3_SPEAKER_TONE = 0x26, + GBHUC3_SPEAKER_ENABLE = 0x27, + GBHUC3_ALARM_MINUTES_LO = 0x58, + GBHUC3_ALARM_MINUTES_MI = 0x59, + GBHUC3_ALARM_MINUTES_HI = 0x5A, + GBHUC3_ALARM_DAYS_LO = 0x5B, + GBHUC3_ALARM_DAYS_MI = 0x5C, + GBHUC3_ALARM_DAYS_HI = 0x5D, + GBHUC3_ALARM_TONE = 0x5E, + GBHUC3_ALARM_ENABLE = 0x5F, +}; + +enum GBHuC3Mode { + GBHUC3_MODE_SRAM_RO = 0x0, + GBHUC3_MODE_SRAM_RW = 0xA, + GBHUC3_MODE_IN = 0xB, + GBHUC3_MODE_OUT = 0xC, + GBHUC3_MODE_COMMIT = 0xD, +}; + +enum GBHuC3Command { + GBHUC3_CMD_LATCH = 0x0, + GBHUC3_CMD_SET_RTC = 0x1, + GBHUC3_CMD_RO = 0x2, + GBHUC3_CMD_TONE = 0xE, +}; + struct GBMBC1State { int mode; int multicartStride; @@ -145,6 +180,13 @@ struct GBTAMA5State { uint8_t registers[GBTAMA5_MAX]; }; +struct GBHuC3State { + uint8_t index; + uint8_t value; + uint8_t mode; + uint8_t registers[256]; +}; + struct GBPKJDState { uint8_t reg[2]; }; @@ -161,6 +203,7 @@ union GBMBCState { struct GBMMM01State mmm01; struct GBPocketCamState pocketCam; struct GBTAMA5State tama5; + struct GBHuC3State huc3; struct GBPKJDState pkjd; struct GBBBDState bbd; }; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 5a26a6937..274172cbe 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -168,7 +168,8 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * 0x003FF: Interrupts enabled * 0x00400 - 0x043FF: VRAM * 0x04400 - 0x0C3FF: WRAM - * 0x0C400 - 0x0C77F: Reserved + * 0x0C400 - 0x0C6FF: Reserved + * 0x0C700 - 0x0C77F: Reserved * 0x0C780 - 0x117FF: Super Game Boy * | 0x0C780 - 0x0C7D9: Current attributes * | 0x0C7DA: Current command @@ -393,6 +394,12 @@ struct GBSerializedState { uint8_t locked; uint8_t bank0; } mmm01; + struct { + uint64_t lastLatch; + uint8_t index; + uint8_t value; + uint8_t mode; + } huc3; struct { uint8_t dataSwapMode; uint8_t bankSwapMode; @@ -421,7 +428,9 @@ struct GBSerializedState { uint8_t vram[GB_SIZE_VRAM]; uint8_t wram[GB_SIZE_WORKING_RAM]; - uint32_t reserved2[0xC4]; + uint32_t reserved2[0xA4]; + + uint8_t huc3Registers[0x80]; struct { uint8_t attributes[90]; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index de6f7802d..e2837e2f1 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -47,6 +47,7 @@ static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value); static uint8_t _GBTAMA5Read(struct GBMemory*, uint16_t address); +static uint8_t _GBHuC3Read(struct GBMemory*, uint16_t address); static uint8_t _GBPKJDRead(struct GBMemory*, uint16_t address); static uint8_t _GBBBDRead(struct GBMemory*, uint16_t address); static uint8_t _GBHitekRead(struct GBMemory*, uint16_t address); @@ -373,6 +374,7 @@ void GBMBCInit(struct GB* gb) { break; case GB_HuC3: gb->memory.mbcWrite = _GBHuC3; + gb->memory.mbcRead = _GBHuC3Read; break; case GB_TAMA5: mLOG(GB_MBC, WARN, "unimplemented MBC: TAMA5"); @@ -1101,9 +1103,123 @@ void _GBHuC1(struct GB* gb, uint16_t address, uint8_t value) { } } +static void _latchHuC3Rtc(struct mRTCSource* rtc, uint8_t* huc3Regs, time_t* rtcLastLatch) { + time_t t; + if (rtc) { + if (rtc->sample) { + rtc->sample(rtc); + } + t = rtc->unixTime(rtc); + } else { + t = time(0); + } + t -= *rtcLastLatch; + t /= 60; + + if (!t) { + return; + } + *rtcLastLatch += t * 60; + + int minutes = huc3Regs[GBHUC3_RTC_MINUTES_HI] << 8; + minutes |= huc3Regs[GBHUC3_RTC_MINUTES_MI] << 4; + minutes |= huc3Regs[GBHUC3_RTC_MINUTES_LO]; + minutes += t % 1440; + t /= 1440; + if (minutes >= 1440) { + minutes -= 1440; + ++t; + } else if (minutes < 0) { + minutes += 1440; + --t; + } + huc3Regs[GBHUC3_RTC_MINUTES_LO] = minutes & 0xF; + huc3Regs[GBHUC3_RTC_MINUTES_MI] = (minutes >> 4) & 0xF; + huc3Regs[GBHUC3_RTC_MINUTES_HI] = (minutes >> 8) & 0xF; + + int days = huc3Regs[GBHUC3_RTC_DAYS_LO]; + days |= huc3Regs[GBHUC3_RTC_DAYS_MI] << 4; + days |= huc3Regs[GBHUC3_RTC_DAYS_HI] << 8; + + days += t; + + huc3Regs[GBHUC3_RTC_DAYS_LO] = days & 0xF; + huc3Regs[GBHUC3_RTC_DAYS_MI] = (days >> 4) & 0xF; + huc3Regs[GBHUC3_RTC_DAYS_HI] = (days >> 8) & 0xF; +} + +static void _huc3Commit(struct GB* gb, struct GBHuC3State* state) { + size_t c; + switch (state->value & 0x70) { + case 0x10: + if ((state->index & 0xF8) == 0x10) { + _latchHuC3Rtc(gb->memory.rtc, state->registers, &gb->memory.rtcLastLatch); + } + state->value &= 0xF0; + state->value |= state->registers[state->index] & 0xF; + mLOG(GB_MBC, DEBUG, "HuC-3 read: %02X:%X", state->index, state->value & 0xF); + if (state->value & 0x10) { + ++state->index; + } + break; + case 0x30: + mLOG(GB_MBC, DEBUG, "HuC-3 write: %02X:%X", state->index, state->value & 0xF); + state->registers[state->index] = state->value & 0xF; + if (state->value & 0x10) { + ++state->index; + } + break; + case 0x40: + state->index &= 0xF0; + state->index |= (state->value) & 0xF; + mLOG(GB_MBC, DEBUG, "HuC-3 index (low): %02X", state->index); + break; + case 0x50: + state->index &= 0x0F; + state->index |= ((state->value) & 0xF) << 4; + mLOG(GB_MBC, DEBUG, "HuC-3 index (high): %02X", state->index); + break; + case 0x60: + switch (state->value & 0xF) { + case GBHUC3_CMD_LATCH: + _latchHuC3Rtc(gb->memory.rtc, state->registers, &gb->memory.rtcLastLatch); + memcpy(state->registers, &state->registers[GBHUC3_RTC_MINUTES_LO], 6); + mLOG(GB_MBC, DEBUG, "HuC-3 RTC latch"); + break; + case GBHUC3_CMD_SET_RTC: + memcpy(&state->registers[GBHUC3_RTC_MINUTES_LO], state->registers, 6); + mLOG(GB_MBC, DEBUG, "HuC-3 set RTC"); + break; + case GBHUC3_CMD_RO: + mLOG(GB_MBC, STUB, "HuC-3 unimplemented read-only mode"); + break; + case GBHUC3_CMD_TONE: + if (state->registers[GBHUC3_SPEAKER_ENABLE] == 1) { + for (c = 0; c < mCoreCallbacksListSize(&gb->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gb->coreCallbacks, c); + if (callbacks->alarm) { + callbacks->alarm(callbacks->context); + } + } + mLOG(GB_MBC, DEBUG, "HuC-3 tone %i", state->registers[GBHUC3_SPEAKER_TONE] & 3); + } + break; + default: + mLOG(GB_MBC, STUB, "HuC-3 unknown command: %X", state->value & 0xF); + break; + } + state->value = 0xE1; + break; + default: + mLOG(GB_MBC, STUB, "HuC-3 unknown mode commit: %02X:%02X", state->index, state->value); + break; + } +} + void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; - int bank = value & 0x3F; + struct GBHuC3State* state = &memory->mbcState.huc3; + int bank = value & 0x7F; if (address & 0x1FFF) { mLOG(GB_MBC, STUB, "HuC-3 unknown value %04X:%02X", address, value); } @@ -1119,6 +1235,7 @@ void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { memory->sramAccess = false; break; } + state->mode = value; break; case 0x1: GBMBCSwitchBank(gb, bank); @@ -1126,6 +1243,18 @@ void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { case 0x2: GBMBCSwitchSramBank(gb, bank); break; + case 0x5: + switch (state->mode) { + case GBHUC3_MODE_IN: + state->value = 0x80 | value; + break; + case GBHUC3_MODE_COMMIT: + _huc3Commit(gb, state); + break; + default: + mLOG(GB_MBC, STUB, "HuC-3 unknown mode write: %02X:%02X", state->mode, value); + } + break; default: // TODO mLOG(GB_MBC, STUB, "HuC-3 unknown address: %04X:%02X", address, value); @@ -1133,6 +1262,20 @@ void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { } } +uint8_t _GBHuC3Read(struct GBMemory* memory, uint16_t address) { + struct GBHuC3State* state = &memory->mbcState.huc3; + switch (state->mode) { + case GBHUC3_MODE_SRAM_RO: + case GBHUC3_MODE_SRAM_RW: + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; + case GBHUC3_MODE_IN: + case GBHUC3_MODE_OUT: + return 0x80 | state->value; + default: + return 0xFF; + } +} + void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; int bank = value & 0x3F; diff --git a/src/gb/memory.c b/src/gb/memory.c index a89cbbcce..66c35c644 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -726,6 +726,7 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { state->memory.cartBus = memory->cartBus; STORE_16LE(memory->cartBusPc, 0, &state->cartBusPc); + int i; switch (memory->mbcType) { case GB_MBC1: state->memory.mbc1.mode = memory->mbcState.mbc1.mode; @@ -734,7 +735,7 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { state->memory.mbc1.bankHi = memory->mbcState.mbc1.bankHi; break; case GB_MBC3_RTC: - STORE_64LE(gb->memory.rtcLastLatch, 0, &state->memory.rtc.lastLatch); + STORE_64LE(memory->rtcLastLatch, 0, &state->memory.rtc.lastLatch); break; case GB_MBC7: state->memory.mbc7.state = memory->mbcState.mbc7.state; @@ -746,6 +747,16 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { STORE_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr); STORE_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable); break; + case GB_HuC3: + STORE_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch); + state->memory.huc3.index = memory->mbcState.huc3.index; + state->memory.huc3.value = memory->mbcState.huc3.value; + state->memory.huc3.mode = memory->mbcState.huc3.mode; + for (i = 0; i < 0x80; ++i) { + state->huc3Registers[i] = memory->mbcState.huc3.registers[i * 2] & 0xF; + state->huc3Registers[i] |= memory->mbcState.huc3.registers[i * 2 + 1] << 4; + } + break; case GB_MMM01: state->memory.mmm01.locked = memory->mbcState.mmm01.locked; state->memory.mmm01.bank0 = memory->mbcState.mmm01.currentBank0; @@ -808,6 +819,7 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { memory->cartBus = state->memory.cartBus; LOAD_16LE(memory->cartBusPc, 0, &state->cartBusPc); + int i; switch (memory->mbcType) { case GB_MBC1: memory->mbcState.mbc1.mode = state->memory.mbc1.mode; @@ -824,7 +836,7 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { } break; case GB_MBC3_RTC: - LOAD_64LE(gb->memory.rtcLastLatch, 0, &state->memory.rtc.lastLatch); + LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.rtc.lastLatch); break; case GB_MBC7: memory->mbcState.mbc7.state = state->memory.mbc7.state; @@ -836,6 +848,16 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { LOAD_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr); LOAD_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable); break; + case GB_HuC3: + LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch); + memory->mbcState.huc3.index = state->memory.huc3.index; + memory->mbcState.huc3.value = state->memory.huc3.value; + memory->mbcState.huc3.mode = state->memory.huc3.mode; + for (i = 0; i < 0x80; ++i) { + memory->mbcState.huc3.registers[i * 2] = state->huc3Registers[i] & 0xF; + memory->mbcState.huc3.registers[i * 2 + 1] = state->huc3Registers[i] >> 4; + } + break; case GB_MMM01: memory->mbcState.mmm01.locked = state->memory.mmm01.locked; memory->mbcState.mmm01.currentBank0 = state->memory.mmm01.bank0;