GB MBC: Add Sintax support

This commit is contained in:
Vicki Pfau 2025-02-12 03:52:03 -08:00
parent 6221cd2d06
commit 7607a5bea9
10 changed files with 152 additions and 2 deletions

View File

@ -9,7 +9,7 @@ Features:
- Scripting: New `storage` API for saving data for a script, e.g. settings
- Scripting: New `image` and `canvas` APIs for drawing images and displaying on-screen
- Scripting: Debugger integration to allow for breakpoints and watchpoints
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81, Sintax
- Initial support for bootleg GBA multicarts
- Debugger: Add range watchpoints
- "Headless" frontend for running tests, automation, etc.

View File

@ -73,6 +73,7 @@ The following mappers are partially supported:
- Hitek (missing logo switching)
- GGB-81 (missing logo switching)
- Li Cheng (missing logo switching)
- Sintax (missing logo switching)
### Planned features

View File

@ -50,6 +50,7 @@ enum GBMemoryBankControllerType {
GB_UNL_GGB81 = 0x223,
GB_UNL_SACHEN_MMC1 = 0x230,
GB_UNL_SACHEN_MMC2 = 0x231,
GB_UNL_SINTAX = 0x240,
};
enum GBVideoLayer {

View File

@ -261,6 +261,13 @@ struct GBSachenState {
uint8_t baseBank;
};
struct GBSintaxState {
uint8_t mode;
uint8_t xorValues[4];
uint8_t bankNo;
uint8_t romBankXor;
};
union GBMBCState {
struct GBMBC1State mbc1;
struct GBMBC6State mbc6;
@ -274,6 +281,7 @@ union GBMBCState {
struct GBPKJDState pkjd;
struct GBBBDState bbd;
struct GBSachenState sachen;
struct GBSintaxState sintax;
};
struct mRotationSource;

View File

@ -451,6 +451,12 @@ struct GBSerializedState {
uint8_t unmaskedBank;
uint8_t baseBank;
} sachen;
struct {
uint8_t mode;
uint8_t xorValues[4];
uint8_t bankNo;
uint8_t romBankXor;
} sintax;
struct {
uint8_t reserved[16];
} padding;

View File

@ -113,7 +113,7 @@ static struct {
{"M161", GB_MBC_AUTODETECT}, // TODO
{"BBD", GB_UNL_BBD},
{"HITK", GB_UNL_HITEK},
{"SNTX", GB_MBC_AUTODETECT}, // TODO
{"SNTX", GB_UNL_SINTAX},
{"NTO1", GB_UNL_NT_OLD_1},
{"NTO2", GB_UNL_NT_OLD_2},
{"NTN", GB_UNL_NT_NEW},
@ -128,6 +128,8 @@ static struct {
{"NGHK", GB_MBC_AUTODETECT}, // TODO
{"GB81", GB_UNL_GGB81},
{"TPP1", GB_MBC_AUTODETECT}, // TODO
{"VF01", GB_MBC_AUTODETECT}, // TODO
{"SKL8", GB_MBC_AUTODETECT}, // TODO
{NULL, GB_MBC_AUTODETECT},
};
@ -223,6 +225,12 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t
return GB_UNL_LI_CHENG;
}
break;
case 0x6c1dcf2d:
case 0x99e3449d:
if (mem[0x7FFF] != 0x01) { // Make sure we're not using a "fixed" version
return GB_UNL_SINTAX;
}
break;
}
if (mem[0x104] == 0xCE && mem[0x144] == 0xED && mem[0x114] == 0x66) {
@ -504,6 +512,14 @@ void GBMBCInit(struct GB* gb) {
gb->memory.sramAccess = true;
}
break;
case GB_UNL_SINTAX:
gb->memory.mbcWrite = _GBSintax;
gb->memory.mbcRead = _GBSintaxRead;
gb->memory.mbcReadBank1 = true;
if (gb->sramSize) {
gb->memory.sramAccess = true;
}
break;
}
gb->memory.currentBank = 1;
@ -559,6 +575,9 @@ void GBMBCReset(struct GB* gb) {
GBMBCSwitchBank0(gb, gb->memory.romSize / GB_SIZE_CART_BANK0 - 2);
GBMBCSwitchBank(gb, gb->memory.romSize / GB_SIZE_CART_BANK0 - 1);
break;
case GB_UNL_SINTAX:
gb->memory.mbcState.sintax.mode = 0xF;
break;
default:
break;
}

View File

@ -38,6 +38,7 @@ void _GBHitek(struct GB* gb, uint16_t address, uint8_t value);
void _GBLiCheng(struct GB* gb, uint16_t address, uint8_t value);
void _GBGGB81(struct GB* gb, uint16_t address, uint8_t value);
void _GBSachen(struct GB* gb, uint16_t address, uint8_t value);
void _GBSintax(struct GB* gb, uint16_t address, uint8_t value);
uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address);
uint8_t _GBMBC6Read(struct GBMemory*, uint16_t address);
@ -54,6 +55,7 @@ uint8_t _GBHitekRead(struct GBMemory*, uint16_t address);
uint8_t _GBGGB81Read(struct GBMemory*, uint16_t address);
uint8_t _GBSachenMMC1Read(struct GBMemory*, uint16_t address);
uint8_t _GBSachenMMC2Read(struct GBMemory*, uint16_t address);
uint8_t _GBSintaxRead(struct GBMemory*, uint16_t address);
void _GBMBCLatchRTC(struct mRTCSource* rtc, uint8_t* rtcRegs, time_t* rtcLastLatch);
void _GBMBCAppendSaveSuffix(struct GB* gb, const void* buffer, size_t size);

View File

@ -500,3 +500,102 @@ uint8_t _GBSachenMMC2Read(struct GBMemory* memory, uint16_t address) {
return 0xFF;
}
}
static const uint8_t _sintaxReordering[16][8] = {
{ 2, 1, 4, 3, 6, 5, 0, 7 },
{ 3, 2, 5, 4, 7, 6, 1, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 4, 5, 2, 3, 0, 1, 6, 7 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 6, 7, 4, 5, 1, 3, 0, 2 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 7, 6, 1, 0, 3, 2, 5, 4 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 5, 4, 7, 6, 1, 0, 3, 2 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 2, 3, 4, 5, 6, 7, 0, 1 },
{ 0, 1, 2, 3, 4, 5, 6, 7 }, // unknown
{ 0, 1, 2, 3, 4, 5, 6, 7 },
};
void _GBSintax(struct GB* gb, uint16_t address, uint8_t value) {
struct GBSintaxState* state = &gb->memory.mbcState.sintax;
if (address >= 0x2000 && address < 0x3000) {
state->bankNo = value;
value = _reorderBits(value, _sintaxReordering[state->mode]);
state->romBankXor = state->xorValues[state->bankNo & 0x3];
}
if ((address & 0xF0F0) == 0x5010) {
// contrary to previous belief it IS possible to change the mode after setting it initially
// The reason Metal Max was breaking is because it only recognises writes to 5x1x
// and that game writes to a bunch of other 5xxx addresses before battles
state->mode = value & 0xF;
mLOG(GB_MBC, DEBUG, "Sintax bank reorder mode: %X", state->mode);
switch (state->mode) {
// Supported modes
case 0x00: // Lion King, Golden Sun
case 0x01: // Langrisser
case 0x05: // Maple Story, Pokemon Platinum
case 0x07: // Bynasty Warriors 5
case 0x09: // ???
case 0x0B: // Shaolin Legend
case 0x0D: // Older games
case 0x0F: // Default mode, no reordering
break;
default:
mLOG(GB_MBC, DEBUG, "Bank reorder mode unsupported - %X", state->mode);
break;
}
_GBSintax(gb, 0x2000, state->bankNo); // fake a bank switch to select the correct bank
return;
}
if (address >= 0x7000 && address < 0x8000) {
int xorNo = (address & 0x00F0) >> 4;
switch (xorNo) {
case 2:
state->xorValues[0] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 0: %X", value);
break;
case 3:
state->xorValues[1] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 1: %X", value);
break;
case 4:
state->xorValues[2] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 2: %X", value);
break;
case 5:
state->xorValues[3] = value;
mLOG(GB_MBC, DEBUG, "Sintax XOR 3: %X", value);
break;
}
// xor is applied immediately to the current bank
state->romBankXor = state->xorValues[state->bankNo & 0x3];
}
_GBMBC5(gb, address, value);
}
uint8_t _GBSintaxRead(struct GBMemory* memory, uint16_t address) {
struct GBSintaxState* state = &memory->mbcState.sintax;
switch (address >> 13) {
case 0x2:
case 0x3:
return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)] ^ state->romBankXor;
case 0x5:
if (memory->sramAccess && memory->sram) {
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
}
return 0xFF;
default:
return 0xFF;
}
}

View File

@ -840,6 +840,12 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
state->memory.sachen.unmaskedBank = memory->mbcState.sachen.unmaskedBank;
state->memory.sachen.baseBank = memory->mbcState.sachen.baseBank;
break;
case GB_UNL_SINTAX:
state->memory.sintax.mode = memory->mbcState.sintax.mode;
memcpy(state->memory.sintax.xorValues, memory->mbcState.sintax.xorValues, sizeof(state->memory.sintax.xorValues));
state->memory.sintax.bankNo = memory->mbcState.sintax.bankNo;
state->memory.sintax.romBankXor = memory->mbcState.sintax.romBankXor;
break;
default:
break;
}
@ -1008,6 +1014,12 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
memory->mbcState.sachen.baseBank = state->memory.sachen.baseBank;
GBMBCSwitchBank0(gb, memory->mbcState.sachen.baseBank & memory->mbcState.sachen.mask);
break;
case GB_UNL_SINTAX:
memory->mbcState.sintax.mode = state->memory.sintax.mode;
memcpy(memory->mbcState.sintax.xorValues, state->memory.sintax.xorValues, sizeof(memory->mbcState.sintax.xorValues));
memory->mbcState.sintax.bankNo = state->memory.sintax.bankNo;
memory->mbcState.sintax.romBankXor = state->memory.sintax.romBankXor;
break;
default:
break;
}

View File

@ -44,6 +44,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
GB_UNL_LI_CHENG,
GB_UNL_SACHEN_MMC1,
GB_UNL_SACHEN_MMC2,
GB_UNL_SINTAX,
};
static QMap<GBModel, QString> s_gbModelNames;
@ -102,6 +103,7 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) {
s_mbcNames[GB_UNL_LI_CHENG] = tr("Li Cheng");
s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)");
s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)");
s_mbcNames[GB_UNL_SINTAX] = tr("Sintax");
}
return s_mbcNames[mbc];