diff --git a/CHANGES b/CHANGES index 4e82bc981..7dbcf3446 100644 --- a/CHANGES +++ b/CHANGES @@ -78,6 +78,7 @@ Misc: - FFmpeg: Support dynamic audio sample rate - GB Audio: Increase sample rate - GB MBC: Filter out MBC errors when cartridge is yanked (fixes mgba.io/i/2488) + - GB MBC: Partially implement TAMA5 RTC - GB Video: Add default SGB border - GBA: Automatically skip BIOS if ROM has invalid logo - GBA: Refine multiboot detection (fixes mgba.io/i/2192) diff --git a/README.md b/README.md index 134c59ee8..05530f019 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The following mappers are partially supported: - MBC6 (missing flash memory write support) - MMM01 - Pocket Cam -- TAMA5 (missing RTC support) +- TAMA5 (incomplete RTC support) - HuC-1 (missing IR support) - HuC-3 (missing IR support) - Sachen MMC2 (missing alternate wiring support) diff --git a/include/mgba/internal/gb/mbc.h b/include/mgba/internal/gb/mbc.h index a59132ba4..c12202d77 100644 --- a/include/mgba/internal/gb/mbc.h +++ b/include/mgba/internal/gb/mbc.h @@ -50,12 +50,23 @@ struct GBMBCHuC3SaveBuffer { uint64_t latchedUnix; }; +struct GBMBCTAMA5SaveBuffer { + uint8_t rtcTimerPage[0x8]; + uint8_t rtcAlarmPage[0x8]; + uint8_t rtcFreePage0[0x8]; + uint8_t rtcFreePage1[0x8]; + uint64_t latchedUnix; +}; + void GBMBCRTCRead(struct GB* gb); void GBMBCRTCWrite(struct GB* gb); void GBMBCHuC3Read(struct GB* gb); void GBMBCHuC3Write(struct GB* gb); +void GBMBCTAMA5Read(struct GB* gb); +void GBMBCTAMA5Write(struct GB* gb); + CXX_GUARD_END #endif diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 43ece98d2..f3ba4cf8f 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -94,7 +94,7 @@ enum GBTAMA5Register { GBTAMA5_BANK_HI = 0x1, GBTAMA5_WRITE_LO = 0x4, GBTAMA5_WRITE_HI = 0x5, - GBTAMA5_CS = 0x6, + GBTAMA5_ADDR_HI = 0x6, GBTAMA5_ADDR_LO = 0x7, GBTAMA5_MAX = 0x8, GBTAMA5_ACTIVE = 0xA, @@ -102,6 +102,33 @@ enum GBTAMA5Register { GBTAMA5_READ_HI = 0xD, }; +enum GBTAMA6RTCRegister { + GBTAMA6_RTC_PA0_SECOND_1 = 0x0, + GBTAMA6_RTC_PA0_SECOND_10 = 0x1, + GBTAMA6_RTC_PA0_MINUTE_1 = 0x2, + GBTAMA6_RTC_PA0_MINUTE_10 = 0x3, + GBTAMA6_RTC_PA0_HOUR_1 = 0x4, + GBTAMA6_RTC_PA0_HOUR_10 = 0x5, + GBTAMA6_RTC_PA0_WEEK = 0x6, + GBTAMA6_RTC_PA0_DAY_1 = 0x7, + GBTAMA6_RTC_PA0_DAY_10 = 0x8, + GBTAMA6_RTC_PA0_MONTH_1 = 0x9, + GBTAMA6_RTC_PA0_MONTH_10 = 0xA, + GBTAMA6_RTC_PA0_YEAR_1 = 0xB, + GBTAMA6_RTC_PA0_YEAR_10 = 0xC, + GBTAMA6_RTC_PAGE = 0xD, + GBTAMA6_RTC_TEST = 0xE, + GBTAMA6_RTC_RESET = 0xF, + GBTAMA6_RTC_MAX +}; + +enum GBTAMA6Command { + GBTAMA6_MINUTE_WRITE = 0x4, + GBTAMA6_HOUR_WRITE = 0x5, + GBTAMA6_MINUTE_READ = 0x6, + GBTAMA6_HOUR_READ = 0x7, +}; + enum GBHuC3Register { GBHUC3_RTC_MINUTES_LO = 0x10, GBHUC3_RTC_MINUTES_MI = 0x11, @@ -180,6 +207,7 @@ struct GBPocketCamState { struct GBTAMA5State { uint8_t reg; uint8_t registers[GBTAMA5_MAX]; + uint8_t rtcTimerPage[GBTAMA6_RTC_MAX]; }; struct GBHuC3State { diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 2332171ab..52f1fe8aa 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -406,6 +406,10 @@ struct GBSerializedState { uint8_t locked; uint8_t bank0; } mmm01; + struct { + uint64_t lastLatch; + uint8_t reg; + } tama5; struct { uint64_t lastLatch; uint8_t index; @@ -456,7 +460,13 @@ struct GBSerializedState { uint32_t reserved2[0xA4]; - uint8_t huc3Registers[0x80]; + union { + uint8_t huc3Registers[0x80]; + struct { + uint8_t registers[8]; + uint8_t rtcTimerPage[8]; + } tama5Registers; + }; struct { uint8_t attributes[90]; diff --git a/src/gb/gb.c b/src/gb/gb.c index 81d059b00..e2459e8fe 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -220,6 +220,8 @@ static void GBSramDeinit(struct GB* gb) { GBMBCRTCWrite(gb); } else if (gb->memory.mbcType == GB_HuC3) { GBMBCHuC3Write(gb); + } else if (gb->memory.mbcType == GB_TAMA5) { + GBMBCTAMA5Write(gb); } } gb->sramVf = NULL; @@ -244,6 +246,8 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf) { GBMBCRTCRead(gb); } else if (gb->memory.mbcType == GB_HuC3) { GBMBCHuC3Read(gb); + } else if (gb->memory.mbcType == GB_TAMA5) { + GBMBCTAMA5Read(gb); } } return vf; @@ -329,6 +333,8 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) { GBMBCRTCWrite(gb); } else if (gb->memory.mbcType == GB_HuC3) { GBMBCHuC3Write(gb); + } else if (gb->memory.mbcType == GB_TAMA5) { + GBMBCTAMA5Write(gb); } if (gb->sramVf == gb->sramRealVf) { if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 282891cfe..995fc0f9f 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -453,8 +453,6 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcRead = _GBHuC3Read; break; case GB_TAMA5: - mLOG(GB_MBC, WARN, "unimplemented MBC: TAMA5"); - memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); gb->memory.mbcWrite = _GBTAMA5; gb->memory.mbcRead = _GBTAMA5Read; gb->sramSize = 0x20; @@ -540,6 +538,8 @@ void GBMBCInit(struct GB* gb) { GBMBCRTCRead(gb); } else if (gb->memory.mbcType == GB_HuC3) { GBMBCHuC3Read(gb); + } else if (gb->memory.mbcType == GB_TAMA5) { + GBMBCTAMA5Read(gb); } } @@ -1514,6 +1514,150 @@ void _GBPocketCamCapture(struct GBMemory* memory) { } } +static const int _daysToMonth[] = { + [ 1] = 0, + [ 2] = 31, + [ 3] = 31 + 28, + [ 4] = 31 + 28 + 31, + [ 5] = 31 + 28 + 31 + 30, + [ 6] = 31 + 28 + 31 + 30 + 31, + [ 7] = 31 + 28 + 31 + 30 + 31 + 30, + [ 8] = 31 + 28 + 31 + 30 + 31 + 30 + 31, + [ 9] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + [10] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + [11] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + [12] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, +}; + +static int _tama6DMYToDayOfYear(int day, int month, int year) { + if (month < 1 || month > 12) { + return -1; + } + day += _daysToMonth[month]; + if (month > 2 && (year % 4) == 0) { + ++day; + } + return day; +} + +static int _tama6DayOfYearToMonth(int day, int year) { + int month; + for (month = 1; month < 12; ++month) { + if (day <= _daysToMonth[month + 1]) { + return month; + } + if (month == 2 && year % 4 == 0) { + if (day == 60) { + return 2; + } + --day; + } + } + return 12; +} + +static int _tama6DayOfYearToDayOfMonth(int day, int year) { + int month; + for (month = 1; month < 12; ++month) { + if (day <= _daysToMonth[month + 1]) { + return day - _daysToMonth[month]; + } + if (month == 2 && year % 4 == 0) { + if (day == 60) { + return 29; + } + --day; + } + } + return day - _daysToMonth[12]; +} + +static void _latchTAMA6Rtc(struct mRTCSource* rtc, uint8_t* timerRegs, time_t* rtcLastLatch) { + time_t t; + if (rtc) { + if (rtc->sample) { + rtc->sample(rtc); + } + t = rtc->unixTime(rtc); + } else { + t = time(0); + } + time_t currentLatch = t; + t -= *rtcLastLatch; + *rtcLastLatch = currentLatch; + if (!t) { + return; + } + + int64_t diff; + diff = timerRegs[GBTAMA6_RTC_PA0_SECOND_1] + timerRegs[GBTAMA6_RTC_PA0_SECOND_10] * 10 + t % 60; + if (diff < 0) { + diff += 60; + t -= 60; + } + timerRegs[GBTAMA6_RTC_PA0_SECOND_1] = diff % 10; + timerRegs[GBTAMA6_RTC_PA0_SECOND_10] = (diff % 60) / 10; + t /= 60; + t += diff / 60; + + diff = timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] + timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] * 10 + t % 60; + if (diff < 0) { + diff += 60; + t -= 60; + } + timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] = diff % 10; + timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] = (diff % 60) / 10; + t /= 60; + t += diff / 60; + + diff = timerRegs[GBTAMA6_RTC_PA0_HOUR_1] + timerRegs[GBTAMA6_RTC_PA0_HOUR_10] * 10 + t % 24; + if (diff < 0) { + diff += 24; + t -= 24; + } + timerRegs[GBTAMA6_RTC_PA0_HOUR_1] = (diff % 24) % 10; + timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 24) / 10; + t /= 24; + t += diff / 24; + + int day = timerRegs[GBTAMA6_RTC_PA0_DAY_1] + timerRegs[GBTAMA6_RTC_PA0_DAY_10] * 10; + int month = timerRegs[GBTAMA6_RTC_PA0_MONTH_1] + timerRegs[GBTAMA6_RTC_PA0_MONTH_10] * 10; + int year = timerRegs[GBTAMA6_RTC_PA0_YEAR_1] + timerRegs[GBTAMA6_RTC_PA0_YEAR_10] * 10; + int dayInYear = _tama6DMYToDayOfYear(day, month, year); + diff = dayInYear + t; + while (diff <= 0) { + // Previous year + if (year % 4) { + diff += 365; + } else { + diff += 366; + } + --year; + } + while (diff > (year % 4 ? 365 : 366)) { + // Future year + if (year % 4) { + diff -= 365; + } else { + diff -= 366; + } + ++year; + } + year %= 100; + + day = _tama6DayOfYearToDayOfMonth(diff, year); + month = _tama6DayOfYearToMonth(diff, year); + + timerRegs[GBTAMA6_RTC_PA0_DAY_1] = day % 10; + timerRegs[GBTAMA6_RTC_PA0_DAY_10] = day / 10; + + timerRegs[GBTAMA6_RTC_PA0_MONTH_1] = month % 10; + timerRegs[GBTAMA6_RTC_PA0_MONTH_10] = month / 10; + + timerRegs[GBTAMA6_RTC_PA0_YEAR_1] = year % 10; + timerRegs[GBTAMA6_RTC_PA0_YEAR_10] = year / 10; +} + void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; struct GBTAMA5State* tama5 = &memory->mbcState.tama5; @@ -1524,8 +1668,9 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) { } else { value &= 0xF; if (tama5->reg < GBTAMA5_MAX) { + mLOG(GB_MBC, DEBUG, "TAMA5 write: %02X:%X", tama5->reg, value); tama5->registers[tama5->reg] = value; - uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; + uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; uint8_t out = (tama5->registers[GBTAMA5_WRITE_HI] << 4) | tama5->registers[GBTAMA5_WRITE_LO]; switch (tama5->reg) { case GBTAMA5_BANK_LO: @@ -1534,18 +1679,36 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) { break; case GBTAMA5_WRITE_LO: case GBTAMA5_WRITE_HI: - case GBTAMA5_CS: + case GBTAMA5_ADDR_HI: break; case GBTAMA5_ADDR_LO: - switch (tama5->registers[GBTAMA5_CS] >> 1) { + switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) { case 0x0: // RAM write memory->sram[address] = out; gb->sramDirty |= mSAVEDATA_DIRT_NEW; break; case 0x1: // RAM read break; + case 0x2: // Other commands + switch (address) { + case GBTAMA6_MINUTE_WRITE: + tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1] = out & 0xF; + tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] = out >> 4; + break; + case GBTAMA6_HOUR_WRITE: + tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1] = out & 0xF; + tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] = out >> 4; + break; + } + break; + case 0x4: // RTC access + if (!(address & 1)) { + tama5->rtcTimerPage[out & 0xF] = out >> 4; + } + break; default: - mLOG(GB_MBC, STUB, "TAMA5 unknown address: %X-%02X:%02X", tama5->registers[GBTAMA5_CS] >> 1, address, out); + mLOG(GB_MBC, STUB, "TAMA5 unknown address: %02X:%02X", address, out); + break; } break; default: @@ -1571,18 +1734,41 @@ uint8_t _GBTAMA5Read(struct GBMemory* memory, uint16_t address) { return 0xFF; } else { uint8_t value = 0xF0; - uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; + uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; switch (tama5->reg) { case GBTAMA5_ACTIVE: return 0xF1; case GBTAMA5_READ_LO: case GBTAMA5_READ_HI: - switch (tama5->registers[GBTAMA5_CS] >> 1) { - case 1: + switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) { + case 0x1: value = memory->sram[address]; break; + case 0x2: + mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address); + _latchTAMA6Rtc(memory->rtc, tama5->rtcTimerPage, &memory->rtcLastLatch); + switch (address) { + case GBTAMA6_MINUTE_READ: + value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1]; + break; + case GBTAMA6_HOUR_READ: + value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1]; + break; + default: + value = address; + break; + } + break; + case 0x4: + if (tama5->reg == GBTAMA5_READ_HI) { + mLOG(GB_MBC, GAME_ERROR, "TAMA5 reading RTC incorrectly"); + break; + } + _latchTAMA6Rtc(memory->rtc, tama5->rtcTimerPage, &memory->rtcLastLatch); + value = tama5->rtcTimerPage[tama5->registers[GBTAMA5_WRITE_LO]]; + break; default: - mLOG(GB_MBC, STUB, "TAMA5 unknown read: %02X", tama5->reg); + mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address); break; } if (tama5->reg == GBTAMA5_READ_HI) { @@ -2015,3 +2201,42 @@ void GBMBCHuC3Write(struct GB* gb) { _appendSaveSuffix(gb, &buffer, sizeof(buffer)); } + +void GBMBCTAMA5Read(struct GB* gb) { + struct GBMBCTAMA5SaveBuffer buffer; + struct VFile* vf = gb->sramVf; + if (!vf) { + return; + } + vf->seek(vf, gb->sramSize, SEEK_SET); + if (vf->read(vf, &buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) { + return; + } + + size_t i; + for (i = 0; i < 0x8; ++i) { + gb->memory.mbcState.tama5.rtcTimerPage[i * 2] = buffer.rtcTimerPage[i] & 0xF; + gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] = buffer.rtcTimerPage[i] >> 4; + } + LOAD_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix); +} + +void GBMBCTAMA5Write(struct GB* gb) { + struct VFile* vf = gb->sramVf; + if (!vf) { + return; + } + + struct GBMBCTAMA5SaveBuffer buffer; + size_t i; + for (i = 0; i < 8; ++i) { + buffer.rtcTimerPage[i] = gb->memory.mbcState.tama5.rtcTimerPage[i * 2] & 0xF; + buffer.rtcTimerPage[i] |= gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4; + buffer.rtcAlarmPage[i] = 0; + buffer.rtcFreePage0[i] = 0; + buffer.rtcFreePage1[i] = 0; + } + STORE_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix); + + _appendSaveSuffix(gb, &buffer, sizeof(buffer)); +} diff --git a/src/gb/memory.c b/src/gb/memory.c index ec9a81ac8..b1a4f84ed 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -765,6 +765,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_TAMA5: + STORE_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch); + state->memory.tama5.reg = memory->mbcState.tama5.reg; + for (i = 0; i < 8; ++i) { + state->tama5Registers.registers[i] = memory->mbcState.tama5.registers[i * 2] & 0xF; + state->tama5Registers.registers[i] |= memory->mbcState.tama5.registers[i * 2 + 1] << 4; + state->tama5Registers.rtcTimerPage[i] = memory->mbcState.tama5.rtcTimerPage[i * 2] & 0xF; + state->tama5Registers.rtcTimerPage[i] |= memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4; + } + break; case GB_HuC3: STORE_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch); state->memory.huc3.index = memory->mbcState.huc3.index; @@ -874,6 +884,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_TAMA5: + LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch); + memory->mbcState.tama5.reg = state->memory.tama5.reg; + for (i = 0; i < 8; ++i) { + memory->mbcState.tama5.registers[i * 2] = state->tama5Registers.registers[i] & 0xF; + memory->mbcState.tama5.registers[i * 2 + 1] = state->tama5Registers.registers[i] >> 4; + memory->mbcState.tama5.rtcTimerPage[i * 2] = state->tama5Registers.rtcTimerPage[i] & 0xF; + memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] = state->tama5Registers.rtcTimerPage[i] >> 4; + } + break; case GB_HuC3: LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch); memory->mbcState.huc3.index = state->memory.huc3.index;