diff --git a/CHANGES b/CHANGES index cdef7640e..d7b13c1aa 100644 --- a/CHANGES +++ b/CHANGES @@ -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. diff --git a/README.md b/README.md index d937fa0c9..5bb847e9f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index de26ef731..e9bec3044 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -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 { diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index e4b34088b..2d1278feb 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -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; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index e76d0accb..4963340e5 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -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; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 397b50088..2a19575a8 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -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; } diff --git a/src/gb/mbc/mbc-private.h b/src/gb/mbc/mbc-private.h index 4a07ab716..b6f7671a5 100644 --- a/src/gb/mbc/mbc-private.h +++ b/src/gb/mbc/mbc-private.h @@ -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); diff --git a/src/gb/mbc/unlicensed.c b/src/gb/mbc/unlicensed.c index 844c0d0cd..00b0ef303 100644 --- a/src/gb/mbc/unlicensed.c +++ b/src/gb/mbc/unlicensed.c @@ -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; + } +} diff --git a/src/gb/memory.c b/src/gb/memory.c index fdacc1a9b..05d4a9669 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -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; } diff --git a/src/platform/qt/GameBoy.cpp b/src/platform/qt/GameBoy.cpp index b07982721..9f64ae3b1 100644 --- a/src/platform/qt/GameBoy.cpp +++ b/src/platform/qt/GameBoy.cpp @@ -44,6 +44,7 @@ static const QList s_mbcList{ GB_UNL_LI_CHENG, GB_UNL_SACHEN_MMC1, GB_UNL_SACHEN_MMC2, + GB_UNL_SINTAX, }; static QMap 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];