mirror of https://github.com/mgba-emu/mgba.git
GB MBC: Sachen MMC1 support
This commit is contained in:
parent
9b8a31a7a5
commit
a5976e6c34
2
CHANGES
2
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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
25
src/gb/gb.c
25
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) {
|
||||
|
|
71
src/gb/mbc.c
71
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in New Issue