GB MBC: Improved support for HuC-3 mapper

This commit is contained in:
Vicki Pfau 2022-02-02 03:03:46 -08:00
parent e2040146ea
commit c829cd2e70
6 changed files with 224 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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