diff --git a/CHANGES b/CHANGES index 3806127ed..dc8ad9aa0 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +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. + - Support for GBX format Game Boy ROMs - New unlicensed GB mappers: NT (newer type), Sachen (MMC1, MMC2) Emulation fixes: - ARM7: Fix unsigned multiply timing diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index a0196c162..5359176ac 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -72,6 +72,19 @@ enum GBSGBCommand { SGB_OBJ_TRN }; +struct GBXMetadata { + enum GBMemoryBankControllerType mbc; + bool battery; + bool rumble; + bool timer; + uint32_t romSize; + uint32_t ramSize; + union { + uint8_t u8[32]; + uint32_t u32[8]; + } mapperVars; +}; + struct SM83Core; struct mCoreSync; struct mAVStream; @@ -85,6 +98,7 @@ struct GB { struct GBAudio audio; struct GBSIO sio; enum GBModel model; + struct GBXMetadata gbx; struct mCoreSync* sync; struct mTiming timing; @@ -165,6 +179,7 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf); void GBUnloadROM(struct GB* gb); void GBSynthesizeROM(struct VFile* vf); void GBYankROM(struct GB* gb); +bool GBLoadGBX(struct GBXMetadata* metadata, struct VFile* vf); void GBLoadBIOS(struct GB* gb, struct VFile* vf); diff --git a/include/mgba/internal/gb/mbc.h b/include/mgba/internal/gb/mbc.h index a63de3b84..a59132ba4 100644 --- a/include/mgba/internal/gb/mbc.h +++ b/include/mgba/internal/gb/mbc.h @@ -24,6 +24,8 @@ void GBMBCSwitchHalfBank(struct GB* gb, int half, int bank); void GBMBCSwitchSramBank(struct GB* gb, int bank); void GBMBCSwitchSramHalfBank(struct GB* gb, int half, int bank); +enum GBMemoryBankControllerType GBMBCFromGBX(const void* fourcc); + enum GBCam { GBCAM_WIDTH = 128, GBCAM_HEIGHT = 112 diff --git a/src/gb/CMakeLists.txt b/src/gb/CMakeLists.txt index 0dc642803..74e7800de 100644 --- a/src/gb/CMakeLists.txt +++ b/src/gb/CMakeLists.txt @@ -30,6 +30,7 @@ set(DEBUGGER_FILES set(TEST_FILES test/core.c + test/gbx.c test/mbc.c test/memory.c test/rtc.c) diff --git a/src/gb/gb.c b/src/gb/gb.c index 0da504c61..ee6699839 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -84,6 +84,8 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->pristineRomSize = 0; gb->yankedRomSize = 0; + memset(&gb->gbx, 0, sizeof(gb->gbx)); + mCoreCallbacksListInit(&gb->coreCallbacks, 0); gb->stream = NULL; @@ -101,13 +103,79 @@ static void GBDeinit(struct mCPUComponent* component) { mTimingDeinit(&gb->timing); } +bool GBLoadGBX(struct GBXMetadata* metadata, struct VFile* vf) { + uint8_t footer[16]; + if (vf->seek(vf, -sizeof(footer), SEEK_END) < 0) { + return false; + } + if (vf->read(vf, footer, sizeof(footer)) < (ssize_t) sizeof(footer)) { + return false; + } + int32_t gbxSize = 0; + uint32_t vers; + LOAD_32BE(gbxSize, 0, footer); + LOAD_32BE(vers, 4, footer); + if (memcmp(&footer[12], "GBX!", 4) != 0 || gbxSize != 0x40 || vers != 1) { + return false; + } + if (vf->seek(vf, -gbxSize, SEEK_END) < 0) { + return false; + } + if (vf->read(vf, footer, sizeof(footer)) != (ssize_t) sizeof(footer)) { + return false; + } + memset(metadata, 0, sizeof(*metadata)); + metadata->mbc = GBMBCFromGBX(footer); + + if (footer[4] == 1) { + metadata->battery = true; + } + if (footer[5] == 1) { + metadata->rumble = true; + if (metadata->mbc == GB_MBC5) { + metadata->mbc = GB_MBC5_RUMBLE; + } + } + if (footer[6] == 1) { + metadata->timer = true; + if (metadata->mbc == GB_MBC3) { + metadata->mbc = GB_MBC3_RTC; + } + } + LOAD_32BE(metadata->romSize, 8, footer); + LOAD_32BE(metadata->ramSize, 12, footer); + vf->read(vf, &metadata->mapperVars, 0x20); + + // There's no dedicated mapper type for MBC1M so let's stash some data here + if (memcmp(footer, "MBC1", 4) == 0) { + metadata->mapperVars.u8[0] = 5; + } else if (memcmp(footer, "MB1M", 4) == 0) { + metadata->mapperVars.u8[0] = 4; + } + return true; +} + bool GBLoadROM(struct GB* gb, struct VFile* vf) { if (!vf) { return false; } GBUnloadROM(gb); + + if (!GBLoadGBX(&gb->gbx, vf)) { + // GBX handles the pristine size itself, but other formats don't + gb->pristineRomSize = vf->size(vf); + } else { + uint32_t fileSize = vf->size(vf); + if (gb->gbx.romSize <= fileSize - 0x40) { + gb->pristineRomSize = gb->gbx.romSize; + } else { + // TODO: Should we make a temporary buffer? + mLOG(GB, WARN, "GBX file size %d is larger than real file size %d", gb->gbx.romSize, fileSize - 0x40); + gb->pristineRomSize = fileSize - 0x40; + } + } + gb->romVf = vf; - gb->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET); gb->isPristine = true; gb->memory.rom = vf->map(vf, gb->pristineRomSize, MAP_READ); @@ -921,6 +989,21 @@ bool GBIsROM(struct VFile* vf) { // Sachen MMC2 scrambled header return true; } + + uint8_t footer[16]; + vf->seek(vf, -sizeof(footer), SEEK_END); + if (vf->read(vf, footer, sizeof(footer)) < (ssize_t) sizeof(footer)) { + return false; + } + uint32_t size; + uint32_t vers; + LOAD_32BE(size, 0, footer); + LOAD_32BE(vers, 4, footer); + if (memcmp(&footer[12], "GBX!", 4) == 0 && size == 0x40 && vers == 1) { + // GBX file + return true; + } + return false; } diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 20ab20322..43f6c7a90 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -131,6 +131,55 @@ void GBMBCSwitchHalfBank(struct GB* gb, int half, int bank) { } } +static struct { + const char* fourcc; + enum GBMemoryBankControllerType mbc; +} _gbxToMbc[] = { + {"ROM", GB_MBC_NONE}, + {"MBC1", GB_MBC1}, + {"MBC2", GB_MBC2}, + {"MBC3", GB_MBC3}, + {"MBC5", GB_MBC5}, + {"MBC6", GB_MBC6}, + {"MBC7", GB_MBC7}, + {"MB1M", GB_MBC1}, + {"MMM1", GB_MMM01}, + {"CAMR", GB_POCKETCAM}, + {"HUC1", GB_HuC1}, + {"HUC3", GB_HuC3}, + {"TAM5", GB_TAMA5}, + {"M161", GB_MBC_AUTODETECT}, // TODO + {"BBD", GB_UNL_BBD}, + {"HITK", GB_UNL_HITEK}, + {"SNTX", GB_MBC_AUTODETECT}, // TODO + {"NTO1", GB_MBC_AUTODETECT}, // TODO + {"NTO2", GB_MBC_AUTODETECT}, // TODO + {"NTN", GB_UNL_NT_NEW}, + {"LICH", GB_MBC_AUTODETECT}, // TODO + {"LBMC", GB_MBC_AUTODETECT}, // TODO + {"LIBA", GB_MBC_AUTODETECT}, // TODO + {"PKJD", GB_UNL_PKJD}, + {"WISD", GB_UNL_WISDOM_TREE}, + {"SAM1", GB_UNL_SACHEN_MMC1}, + {"SAM2", GB_UNL_SACHEN_MMC2}, + {"ROCK", GB_MBC_AUTODETECT}, // TODO + {"NGHK", GB_MBC_AUTODETECT}, // TODO + {"GB81", GB_MBC_AUTODETECT}, // TODO + {"TPP1", GB_MBC_AUTODETECT}, // TODO + + {NULL, GB_MBC_AUTODETECT}, +}; + +enum GBMemoryBankControllerType GBMBCFromGBX(const void* fourcc) { + size_t i; + for (i = 0; _gbxToMbc[i].fourcc; ++i) { + if (memcmp(fourcc, _gbxToMbc[i].fourcc, 4) == 0) { + break; + } + } + return _gbxToMbc[i].mbc; +} + static bool _isMulticart(const uint8_t* mem) { bool success; struct VFile* vf; @@ -249,23 +298,28 @@ void GBMBCInit(struct GB* gb) { cart = cartFooter; } } - switch (cart->ramSize) { - case 0: - gb->sramSize = 0; - break; - default: - case 2: - gb->sramSize = 0x2000; - break; - case 3: - gb->sramSize = 0x8000; - break; - case 4: - gb->sramSize = 0x20000; - break; - case 5: - gb->sramSize = 0x10000; - break; + if (gb->gbx.romSize) { + gb->sramSize = gb->gbx.ramSize; + gb->memory.mbcType = gb->gbx.mbc; + } else { + switch (cart->ramSize) { + case 0: + gb->sramSize = 0; + break; + default: + case 2: + gb->sramSize = 0x2000; + break; + case 3: + gb->sramSize = 0x8000; + break; + case 4: + gb->sramSize = 0x20000; + break; + case 5: + gb->sramSize = 0x10000; + break; + } } if (gb->memory.mbcType == GB_MBC_AUTODETECT) { gb->memory.mbcType = _detectUnlMBC(gb->memory.rom, gb->memory.romSize); @@ -350,7 +404,9 @@ void GBMBCInit(struct GB* gb) { break; case GB_MBC1: gb->memory.mbcWrite = _GBMBC1; - if (gb->memory.romSize >= GB_SIZE_CART_BANK0 * 0x31 && _isMulticart(gb->memory.rom)) { + if (gb->gbx.mapperVars.u8[0]) { + gb->memory.mbcState.mbc1.multicartStride = gb->gbx.mapperVars.u8[0]; + } else if (gb->memory.romSize >= GB_SIZE_CART_BANK0 * 0x31 && _isMulticart(gb->memory.rom)) { gb->memory.mbcState.mbc1.multicartStride = 4; } else { gb->memory.mbcState.mbc1.multicartStride = 5; diff --git a/src/gb/test/gbx.c b/src/gb/test/gbx.c new file mode 100644 index 000000000..b15502e37 --- /dev/null +++ b/src/gb/test/gbx.c @@ -0,0 +1,557 @@ +/* Copyright (c) 2013-2021 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include +#include +#include +#include + +#define GBX_FOOTER_SIZE 0x40 + +struct GBXParams { + uint32_t major; + uint32_t minor; + char fourcc[4]; + bool battery; + bool rumble; + bool timer; + uint32_t romSize; + uint32_t ramSize; + union { + uint8_t u8[32]; + uint32_t u32[8]; + } mapperVars; +}; + +static struct VFile* makeGBX(const struct GBXParams* params, unsigned padding) { + struct VFile* vf = VFileMemChunk(NULL, padding + GBX_FOOTER_SIZE); + uint8_t bool2flag[2] = {0, 1}; + vf->seek(vf, -GBX_FOOTER_SIZE, SEEK_END); + vf->write(vf, params->fourcc, 4); + vf->write(vf, &bool2flag[(int) params->battery], 1); + vf->write(vf, &bool2flag[(int) params->rumble], 1); + vf->write(vf, &bool2flag[(int) params->timer], 1); + vf->write(vf, &bool2flag[0], 1); // Reserved + + uint32_t beint; + STORE_32BE(params->romSize, 0, &beint); + vf->write(vf, &beint, 4); + STORE_32BE(params->ramSize, 0, &beint); + vf->write(vf, &beint, 4); + vf->write(vf, ¶ms->mapperVars, 0x20); + + STORE_32BE(0x40, 0, &beint); // Footer size + vf->write(vf, &beint, 4); + + STORE_32BE(params->major, 0, &beint); + vf->write(vf, &beint, 4); + + STORE_32BE(params->minor, 0, &beint); + vf->write(vf, &beint, 4); + + vf->write(vf, "GBX!", 4); // Magic + return vf; +} + +M_TEST_SUITE_SETUP(GBGBX) { + struct mCore* core = GBCoreCreate(); + core->init(core); + mCoreInitConfig(core, NULL); + *state = core; + return 0; +} + +M_TEST_SUITE_TEARDOWN(GBGBX) { + if (!*state) { + return 0; + } + struct mCore* core = *state; + mCoreConfigDeinit(&core->config); + core->deinit(core); + return 0; +} + +M_TEST_DEFINE(failTooSmall) { + struct mCore* core = *state; + struct GB* gb = core->board; + char truncGbx[0x3F] = { + [0x32] = 0x40, + [0x36] = 0x1, + [0x3B] = 'G', + [0x3C] = 'B', + [0x3D] = 'X', + [0x3E] = '!', + }; + struct VFile* vf = VFileFromConstMemory(truncGbx, sizeof(truncGbx)); + bool loaded = core->loadROM(core, vf); + assert_false(loaded && gb->pristineRomSize == 0); +} + +M_TEST_DEFINE(failNoMagic) { + struct mCore* core = *state; + struct GB* gb = core->board; + char gbx[0x40] = { + [0x0] = 'R', + [0x1] = 'O', + [0x2] = 'M', + [0x33] = 0x40, + [0x37] = 0x1, + [0x3C] = 'G', + [0x3D] = 'B', + [0x3E] = 'X', + }; + struct VFile* vf = VFileFromConstMemory(gbx, sizeof(gbx)); + bool loaded = core->loadROM(core, vf); + assert_false(loaded && gb->pristineRomSize == 0); +} + +M_TEST_DEFINE(invalidVersionLow) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 0, + .fourcc = "ROM", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_false(loaded && gb->pristineRomSize == 0x8000); +} + +M_TEST_DEFINE(invalidVersionHigh) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 2, + .fourcc = "ROM", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_false(loaded && gb->pristineRomSize == 0x8000); +} + +M_TEST_DEFINE(mbcInvalidNone) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "INVL", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC_NONE); +} + +M_TEST_DEFINE(mbcInvalidFallback) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "INVL", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + vf->seek(vf, 0x147, SEEK_SET); + char one = 1; + vf->write(vf, &one, 1); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC1); +} + +M_TEST_DEFINE(mbcRom) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "ROM", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC_NONE); +} + +M_TEST_DEFINE(mbc1) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC1", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC1); + assert_int_equal(gb->memory.mbcState.mbc1.multicartStride, 5); +} + +M_TEST_DEFINE(mbc2) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC2", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC2); +} + +M_TEST_DEFINE(mbc3) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC3", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC3); +} + +M_TEST_DEFINE(mbc3Rtc) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC3", + .timer = true, + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC3_RTC); +} + +M_TEST_DEFINE(mbc5) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC5", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC5); +} + +M_TEST_DEFINE(mbc5Rumble) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC5", + .romSize = 0x8000, + .rumble = true + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC5_RUMBLE); +} + +M_TEST_DEFINE(mbc6) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC6", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC6); +} + +M_TEST_DEFINE(mbc7) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC7", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC7); +} + +M_TEST_DEFINE(mbc1m) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MB1M", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MBC1); + assert_int_equal(gb->memory.mbcState.mbc1.multicartStride, 4); +} + +M_TEST_DEFINE(mmm01) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MMM1", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_MMM01); +} + +M_TEST_DEFINE(pocketCam) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "CAMR", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_POCKETCAM); +} + +M_TEST_DEFINE(huc1) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "HUC1", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_HuC1); +} + +M_TEST_DEFINE(huc3) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "HUC3", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_HuC3); +} + +M_TEST_DEFINE(tama5) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "TAM5", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_TAMA5); +} + +M_TEST_DEFINE(bbd) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "BBD", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_BBD); +} + +M_TEST_DEFINE(hitek) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "HITK", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_HITEK); +} + +M_TEST_DEFINE(ntNew) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "NTN", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_NT_NEW); +} + +M_TEST_DEFINE(pkjd) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "PKJD", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_PKJD); +} + +M_TEST_DEFINE(wisdomTree) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "WISD", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_WISDOM_TREE); +} + +M_TEST_DEFINE(sachenMmc1) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "SAM1", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_SACHEN_MMC1); +} + +M_TEST_DEFINE(sachenMmc2) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "SAM2", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_int_equal(gb->memory.mbcType, GB_UNL_SACHEN_MMC2); +} + +M_TEST_DEFINE(resetMbc1m) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MB1M", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + core->reset(core); + assert_int_equal(gb->memory.mbcType, GB_MBC1); + assert_int_equal(gb->memory.mbcState.mbc1.multicartStride, 4); +} + +M_TEST_DEFINE(fakeRomSize) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC1", + .romSize = 0x8000 + }; + struct VFile* vf = makeGBX(¶ms, 0x10000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); +} + +M_TEST_DEFINE(fakeRamSize) { + struct mCore* core = *state; + struct GB* gb = core->board; + struct GBXParams params = { + .major = 1, + .fourcc = "MBC1", + .romSize = 0x8000, + .ramSize = 0x4000 + }; + struct VFile* vf = makeGBX(¶ms, 0x8000); + bool loaded = core->loadROM(core, vf); + assert_true(loaded && gb->pristineRomSize == 0x8000); + assert_true(gb->sramSize == 0x4000); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBGBX, + cmocka_unit_test(failTooSmall), + cmocka_unit_test(failNoMagic), + cmocka_unit_test(invalidVersionLow), + cmocka_unit_test(invalidVersionHigh), + cmocka_unit_test(mbcInvalidNone), + cmocka_unit_test(mbcInvalidFallback), + cmocka_unit_test(mbcRom), + cmocka_unit_test(mbc1), + cmocka_unit_test(mbc2), + cmocka_unit_test(mbc3), + cmocka_unit_test(mbc3Rtc), + cmocka_unit_test(mbc5), + cmocka_unit_test(mbc5Rumble), + cmocka_unit_test(mbc6), + cmocka_unit_test(mbc7), + cmocka_unit_test(mbc1m), + cmocka_unit_test(mmm01), + cmocka_unit_test(pocketCam), + cmocka_unit_test(huc1), + cmocka_unit_test(huc3), + cmocka_unit_test(tama5), + cmocka_unit_test(bbd), + cmocka_unit_test(hitek), + cmocka_unit_test(ntNew), + cmocka_unit_test(pkjd), + cmocka_unit_test(wisdomTree), + cmocka_unit_test(sachenMmc1), + cmocka_unit_test(sachenMmc2), + cmocka_unit_test(resetMbc1m), + cmocka_unit_test(fakeRomSize), + cmocka_unit_test(fakeRamSize))