GB MBC: Sachen MMC1 support

This commit is contained in:
Vicki Pfau 2022-02-07 22:19:19 -08:00
parent 9b8a31a7a5
commit a5976e6c34
8 changed files with 145 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
GB_UNL_NT_NEW,
GB_UNL_BBD,
GB_UNL_HITEK,
GB_UNL_SACHEN_MMC1,
};
static QMap<GBModel, QString> 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];