From a5976e6c342f879bbb2786b6c84b002c3f35abf9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 7 Feb 2022 22:19:19 -0800 Subject: [PATCH] GB MBC: Sachen MMC1 support --- CHANGES | 2 +- include/mgba/gb/interface.h | 2 + include/mgba/internal/gb/memory.h | 15 ++++++ include/mgba/internal/gb/serialize.h | 10 ++++ src/gb/gb.c | 25 ++++++++-- src/gb/mbc.c | 71 ++++++++++++++++++++++++++++ src/gb/memory.c | 23 +++++++++ src/platform/qt/GameBoy.cpp | 3 ++ 8 files changed, 145 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index bcffc0b31..34e8e94b1 100644 --- a/CHANGES +++ b/CHANGES @@ -13,7 +13,7 @@ Features: - Additional scaling shaders - Support for GameShark Advance SP (.gsv) save file importing - Support for multiple saves per game using .sa2, .sa3, etc. - - New unlicensed GB mappers: NT (newer type) + - New unlicensed GB mappers: NT (newer type), Sachen (MMC1) Emulation fixes: - ARM7: Fix unsigned multiply timing - GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378) diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 997312c3a..6b7e1adac 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -44,6 +44,8 @@ enum GBMemoryBankControllerType { GB_UNL_NT_NEW = 0x212, GB_UNL_BBD = 0x220, // Also used as a mask for MBCs that need special read behavior GB_UNL_HITEK = 0x221, + GB_UNL_SACHEN_MMC1 = 0x230, + GB_UNL_SACHEN_MMC2 = 0x231, }; enum GBVideoLayer { diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index ce3e9079c..bb4029bf2 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -137,6 +137,12 @@ enum GBHuC3Command { GBHUC3_CMD_TONE = 0xE, }; +enum GBSachenLockMode { + GB_SACHEN_LOCKED_DMG = 0, + GB_SACHEN_LOCKED_CGB, + GB_SACHEN_UNLOCKED +}; + struct GBMBC1State { int mode; int multicartStride; @@ -196,6 +202,14 @@ struct GBBBDState { int bankSwapMode; }; +struct GBSachenState { + enum GBSachenLockMode locked; + int transition; + uint8_t mask; + uint8_t unmaskedBank; + uint8_t baseBank; +}; + union GBMBCState { struct GBMBC1State mbc1; struct GBMBC6State mbc6; @@ -207,6 +221,7 @@ union GBMBCState { struct GBNTNewState ntNew; struct GBPKJDState pkjd; struct GBBBDState bbd; + struct GBSachenState sachen; }; struct mRotationSource; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 274172cbe..6973d4e57 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -262,6 +262,10 @@ DECL_BITFIELD(GBSerializedMBC7Flags, uint8_t); DECL_BITS(GBSerializedMBC7Flags, Command, 0, 2); DECL_BIT(GBSerializedMBC7Flags, Writable, 2); +DECL_BITFIELD(GBSerializedSachenFlags, uint8_t); +DECL_BITS(GBSerializedSachenFlags, Transition, 0, 6); +DECL_BITS(GBSerializedSachenFlags, Locked, 6, 2); + DECL_BITFIELD(GBSerializedMemoryFlags, uint16_t); DECL_BIT(GBSerializedMemoryFlags, SramAccess, 0); DECL_BIT(GBSerializedMemoryFlags, RtcAccess, 1); @@ -404,6 +408,12 @@ struct GBSerializedState { uint8_t dataSwapMode; uint8_t bankSwapMode; } bbd; + struct { + GBSerializedSachenFlags flags; + uint8_t mask; + uint8_t unmaskedBank; + uint8_t baseBank; + } sachen; struct { uint8_t reserved[16]; } padding; diff --git a/src/gb/gb.c b/src/gb/gb.c index 9d53bb99b..6f8aa97ff 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -24,6 +24,7 @@ const uint32_t SGB_SM83_FREQUENCY = 0x418B1E; const uint32_t GB_COMPONENT_MAGIC = 0x400000; static const uint8_t _knownHeader[4] = {0xCE, 0xED, 0x66, 0x66}; +static const uint8_t _knownHeaderSachen[4] = {0x7C, 0xE7, 0xC0, 0x00}; static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C}; #define DMG0_BIOS_CHECKSUM 0xC2F5CC97 @@ -906,16 +907,30 @@ bool GBIsROM(struct VFile* vf) { if (!vf) { return false; } - vf->seek(vf, 0x104, SEEK_SET); - uint8_t header[4]; + vf->seek(vf, 0x100, SEEK_SET); + uint8_t header[0x100]; if (vf->read(vf, &header, sizeof(header)) < (ssize_t) sizeof(header)) { return false; } - if (memcmp(header, _knownHeader, sizeof(header))) { - return false; + if (memcmp(&header[4], _knownHeader, sizeof(_knownHeader)) == 0) { + return true; } - return true; + if (memcmp(&header[4], _knownHeaderSachen, sizeof(_knownHeaderSachen)) == 0) { + // Sachen logo + return true; + } + if (header[0x04] == _knownHeader[0] && header[0x44] == _knownHeader[1] && + header[0x14] == _knownHeader[2] && header[0x54] == _knownHeader[3]) { + // Sachen MMC1 scrambled header + return true; + } + if (header[0x04] == _knownHeaderSachen[0] && header[0x44] == _knownHeaderSachen[1] && + header[0x14] == _knownHeaderSachen[2] && header[0x54] == _knownHeaderSachen[3]) { + // Sachen MMC2 scrambled header + return true; + } + return false; } void GBGetGameTitle(const struct GB* gb, char* out) { diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 21932a456..f844540d2 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -41,6 +41,7 @@ static void _GBPKJD(struct GB* gb, uint16_t address, uint8_t value); static void _GBNTNew(struct GB* gb, uint16_t address, uint8_t value); static void _GBBBD(struct GB* gb, uint16_t address, uint8_t value); static void _GBHitek(struct GB* gb, uint16_t address, uint8_t value); +static void _GBSachen(struct GB* gb, uint16_t address, uint8_t value); static uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address); static uint8_t _GBMBC6Read(struct GBMemory*, uint16_t address); @@ -52,6 +53,7 @@ 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); +static uint8_t _GBSachenMMC1Read(struct GBMemory*, uint16_t address); static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address); static void _GBPocketCamCapture(struct GBMemory*); @@ -198,6 +200,10 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t } } + if (mem[0x104] == 0xCE && mem[0x144] == 0xED && mem[0x114] == 0x66) { + return GB_UNL_SACHEN_MMC1; + } + return GB_MBC_AUTODETECT; } @@ -424,6 +430,10 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcState.bbd.dataSwapMode = 7; gb->memory.mbcState.bbd.bankSwapMode = 7; break; + case GB_UNL_SACHEN_MMC1: + gb->memory.mbcWrite = _GBSachen; + gb->memory.mbcRead = _GBSachenMMC1Read; + break; } gb->memory.currentBank = 1; @@ -1724,6 +1734,67 @@ uint8_t _GBHitekRead(struct GBMemory* memory, uint16_t address) { } } +void _GBSachen(struct GB* gb, uint16_t address, uint8_t value) { + struct GBSachenState* state = &gb->memory.mbcState.sachen; + uint8_t bank = value; + switch (address >> 13) { + case 0: + if ((state->unmaskedBank & 0x30) == 0x30) { + state->baseBank = bank; + GBMBCSwitchBank0(gb, state->baseBank & state->mask); + } + break; + case 1: + if (!bank) { + bank = 1; + } + state->unmaskedBank = bank; + bank = (bank & ~state->mask) | (state->baseBank & state->mask); + GBMBCSwitchBank(gb, bank); + break; + case 2: + if ((state->unmaskedBank & 0x30) == 0x30) { + state->mask = value; + bank = (state->unmaskedBank & ~state->mask) | (state->baseBank & state->mask); + GBMBCSwitchBank(gb, bank); + GBMBCSwitchBank0(gb, state->baseBank & state->mask); + } + break; + } +} + +static uint16_t _unscrambleSachen(uint16_t address) { + uint16_t unscrambled = address & 0xFFAC; + unscrambled |= (address & 0x40) >> 6; + unscrambled |= (address & 0x10) >> 3; + unscrambled |= (address & 0x02) << 3; + unscrambled |= (address & 0x01) << 6; + return unscrambled; +} + +uint8_t _GBSachenMMC1Read(struct GBMemory* memory, uint16_t address) { + struct GBSachenState* state = &memory->mbcState.sachen; + if (state->locked != GB_SACHEN_UNLOCKED) { + ++state->transition; + if (state->transition == 0x31) { + state->locked = GB_SACHEN_UNLOCKED; + } + address |= 0x80; + } + + if ((address & 0xFF00) == 0x0100) { + address = _unscrambleSachen(address); + } + + if (address < GB_BASE_CART_BANK1) { + return memory->romBase[address]; + } else if (address < GB_BASE_VRAM) { + return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)]; + } else { + return 0xFF; + } +} + static void _appendSaveSuffix(struct GB* gb, const void* buffer, size_t size) { struct VFile* vf = gb->sramVf; if ((size_t) vf->size(vf) < gb->sramSize + size) { diff --git a/src/gb/memory.c b/src/gb/memory.c index f80fef77b..10b09eed1 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -73,6 +73,10 @@ static void GBSetActiveRegion(struct SM83Core* cpu, uint16_t address) { case GB_REGION_CART_BANK0 + 1: case GB_REGION_CART_BANK0 + 2: case GB_REGION_CART_BANK0 + 3: + if ((gb->memory.mbcType & GB_UNL_SACHEN_MMC1) == GB_UNL_SACHEN_MMC1) { + cpu->memory.cpuLoad8 = GBLoad8; + break; + } cpu->memory.cpuLoad8 = GBCartLoad8; cpu->memory.activeRegion = memory->romBase; cpu->memory.activeRegionEnd = GB_BASE_CART_BANK1; @@ -245,6 +249,8 @@ uint8_t GBLoad8(struct SM83Core* cpu, uint16_t address) { case GB_REGION_CART_BANK0 + 3: if (address >= memory->romSize) { memory->cartBus = 0xFF; + } else if ((memory->mbcType & GB_UNL_SACHEN_MMC1) == GB_UNL_SACHEN_MMC1) { + memory->cartBus = memory->mbcRead(memory, address); } else { memory->cartBus = memory->romBase[address & (GB_SIZE_CART_BANK0 - 1)]; } @@ -766,6 +772,14 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { state->memory.bbd.dataSwapMode = memory->mbcState.bbd.dataSwapMode; state->memory.bbd.bankSwapMode = memory->mbcState.bbd.bankSwapMode; break; + case GB_UNL_SACHEN_MMC1: + case GB_UNL_SACHEN_MMC2: + state->memory.sachen.flags = GBSerializedSachenFlagsSetTransition(0, memory->mbcState.sachen.transition); + state->memory.sachen.flags = GBSerializedSachenFlagsSetLocked(state->memory.sachen.flags, memory->mbcState.sachen.locked); + state->memory.sachen.mask = memory->mbcState.sachen.mask; + state->memory.sachen.unmaskedBank = memory->mbcState.sachen.unmaskedBank; + state->memory.sachen.baseBank = memory->mbcState.sachen.baseBank; + break; default: break; } @@ -872,6 +886,15 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { memory->mbcState.bbd.dataSwapMode = state->memory.bbd.dataSwapMode & 0x7; memory->mbcState.bbd.bankSwapMode = state->memory.bbd.bankSwapMode & 0x7; break; + case GB_UNL_SACHEN_MMC1: + case GB_UNL_SACHEN_MMC2: + memory->mbcState.sachen.transition = GBSerializedSachenFlagsGetTransition(state->memory.sachen.flags); + memory->mbcState.sachen.locked = GBSerializedSachenFlagsGetLocked(state->memory.sachen.flags); + memory->mbcState.sachen.mask = state->memory.sachen.mask; + memory->mbcState.sachen.unmaskedBank = state->memory.sachen.unmaskedBank; + memory->mbcState.sachen.baseBank = state->memory.sachen.baseBank; + GBMBCSwitchBank0(gb, memory->mbcState.sachen.baseBank & memory->mbcState.sachen.mask); + break; default: break; } diff --git a/src/platform/qt/GameBoy.cpp b/src/platform/qt/GameBoy.cpp index c27218afc..181461188 100644 --- a/src/platform/qt/GameBoy.cpp +++ b/src/platform/qt/GameBoy.cpp @@ -38,6 +38,7 @@ static const QList s_mbcList{ GB_UNL_NT_NEW, GB_UNL_BBD, GB_UNL_HITEK, + GB_UNL_SACHEN_MMC1, }; static QMap s_gbModelNames; @@ -92,6 +93,8 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) { s_mbcNames[GB_UNL_PKJD] = tr("Pokémon Jade/Diamond"); s_mbcNames[GB_UNL_BBD] = tr("BBD"); s_mbcNames[GB_UNL_HITEK] = tr("Hitek"); + s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)"); + s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)"); } return s_mbcNames[mbc];