diff --git a/include/mgba/internal/gba/cart/unlicensed.h b/include/mgba/internal/gba/cart/unlicensed.h new file mode 100644 index 000000000..ec13abb55 --- /dev/null +++ b/include/mgba/internal/gba/cart/unlicensed.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2013-2024 Jeffrey Pfau + * Copyright (c) 2016 taizou + * + * 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/. */ +#ifndef GBA_UNLICENSED_H +#define GBA_UNLICENSED_H + +#include + +CXX_GUARD_START + +#include + +enum GBAVFameCartType { + VFAME_STANDARD = 0, + VFAME_GEORGE = 1, + VFAME_ALTERNATE = 2, +}; + +enum GBAUnlCartType { + GBA_UNL_CART_NONE = 0, + GBA_UNL_CART_VFAME = 1, + GBA_UNL_CART_MULTICART = 2, +}; + +struct GBAVFameCart { + enum GBAVFameCartType cartType; + int sramMode; + int romMode; + int8_t writeSequence[5]; + bool acceptingModeChange; +}; + +struct GBAMulticart { + struct mTimingEvent settle; + uint32_t* rom; + size_t fullSize; + + uint8_t bank; + uint8_t offset; + uint8_t size; + bool sramActive; + uint8_t unk; +}; + +struct GBAUnlCart { + enum GBAUnlCartType type; + union { + struct GBAVFameCart vfame; + struct GBAMulticart multi; + }; +}; + +struct GBA; +struct GBAMemory; +void GBAUnlCartInit(struct GBA*); +void GBAUnlCartReset(struct GBA*); +void GBAUnlCartUnload(struct GBA*); +void GBAUnlCartDetect(struct GBA*); +void GBAUnlCartWriteSRAM(struct GBA*, uint32_t address, uint8_t value); + +struct GBASerializedState; +void GBAUnlCartSerialize(const struct GBA* gba, struct GBASerializedState* state); +void GBAUnlCartDeserialize(struct GBA* gba, const struct GBASerializedState* state); + +bool GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32); +void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData); +uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize); +uint32_t GBAVFameGetPatternValue(uint32_t address, int bits); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gba/cart/vfame.h b/include/mgba/internal/gba/cart/vfame.h deleted file mode 100644 index 5bee28848..000000000 --- a/include/mgba/internal/gba/cart/vfame.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright (c) 2016 taizou - * - * 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/. */ - -// Support for copy protected unlicensed games from the company Vast Fame - -#ifndef GBA_VFAME_H -#define GBA_VFAME_H - -#include - -CXX_GUARD_START - -#define DIGIMON_SAPPHIRE_CHINESE_CRC32 0x793A328F - -enum GBAVFameCartType { - VFAME_NO = 0, - VFAME_STANDARD = 1, - VFAME_GEORGE = 2, - VFAME_ALTERNATE = 3, -}; - -struct GBAVFameCart { - enum GBAVFameCartType cartType; - int sramMode; - int romMode; - int8_t writeSequence[5]; - bool acceptingModeChange; -}; - -void GBAVFameInit(struct GBAVFameCart* cart); -void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32); -void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData); -uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize); -uint32_t GBAVFameGetPatternValue(uint32_t address, int bits); - -CXX_GUARD_END - -#endif diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index c88e1d092..1b32763fc 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -18,7 +18,7 @@ CXX_GUARD_START #include #include #include -#include +#include enum GBAMemoryRegion { GBA_REGION_BIOS = 0x0, @@ -108,8 +108,8 @@ struct GBAMemory { struct GBACartridgeHardware hw; struct GBASavedata savedata; - struct GBAVFameCart vfame; struct GBAMatrix matrix; + struct GBAUnlCart unl; struct GBACartEReader ereader; size_t romSize; uint32_t romMask; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 8b1823a17..48393ed7b 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -186,11 +186,14 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bit 3: Reserved * | bits 4 - 15: Light counter * | 0x002C0 - 0x002C0: Light sample - * | 0x002C1 - 0x002C3: Flags + * | 0x002C1: Flags * | bits 0 - 1: Tilt state machine * | bits 2 - 3: GB Player inputs posted - * | bits 4 - 8: GB Player transmit position - * | bits 9 - 23: Reserved + * | bits 4 - 7: GB Player transmit position + * | 0x002C2 - 0x002C3: Unlicensed cart flags + * | bits 0 - 4: Cartridge type + * | bits 5 - 7: Cartridge subtype + * | bits 8 - 15: Reserved * 0x002C4 - 0x002C7: SIO next event * 0x002C8 - 0x002CB: Current DMA transfer word * 0x002CC - 0x002CF: Last DMA transfer PC @@ -230,8 +233,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 15 - 31: Reserved * 0x00320 - 0x00323: Next IRQ event * 0x00324 - 0x00327: Interruptable BIOS stall cycles - * 0x00328 - 0x00367: Matrix memory mapping table - * 0x00368 - 0x0036F: Reserved (leave zero) + * 0x00328 - 0x0036F: Special cartridge state, one of: + * | Matrix Memory: + * | 0x00328 - 0x00367: Matrix memory mapping table + * | 0x00368 - 0x0036F: Reserved (leave zero) + * | Unlicensed multicart: + * | 0x00328: Bank value + * | 0x00329: Offset value + * | 0x0032A: Size value + * | 0x0032B: SRAM active value + * | 0x0032C: Unknown value + * | 0x0032D: Current size + * | 0x0032E - 0x0032F: Current bank/offset + * | 0x00330 - 0x00333: Next settle event + * | 0x00334 - 0x00337: Flags + * | bit 0: Is settling occuring? + * | bits 1 - 31: Reserved + * | 0x00338 - 0x0036F: Reserved (leave zero) * 0x00370 - 0x0037F: Audio FIFO A samples * 0x00380 - 0x0038F: Audio FIFO B samples * 0x00390 - 0x003CF: Audio rendered samples @@ -269,9 +287,14 @@ DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12); DECL_BITFIELD(GBASerializedHWFlags2, uint8_t); DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2); DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 2); -DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 5); +DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 4); -DECL_BITFIELD(GBASerializedHWFlags3, uint16_t); +DECL_BITFIELD(GBASerializedUnlCartFlags, uint16_t); +DECL_BITS(GBASerializedUnlCartFlags, Type, 0, 5); +DECL_BITS(GBASerializedUnlCartFlags, Subtype, 5, 3); + +DECL_BITFIELD(GBASerializedMulticartFlags, uint32_t); +DECL_BIT(GBASerializedMulticartFlags, DustSettling, 0); DECL_BITFIELD(GBASerializedSavedataFlags, uint8_t); DECL_BITS(GBASerializedSavedataFlags, FlashState, 0, 2); @@ -370,7 +393,7 @@ struct GBASerializedState { GBASerializedHWFlags1 flags1; uint8_t lightSample; GBASerializedHWFlags2 flags2; - GBASerializedHWFlags3 flags3; + GBASerializedUnlCartFlags unlCartFlags; uint32_t sioNextEvent; } hw; @@ -407,8 +430,23 @@ struct GBASerializedState { uint32_t nextIrq; int32_t biosStall; - uint32_t matrixMappings[16]; - uint32_t reservedMatrix[2]; + union { + struct { + uint32_t mappings[16]; + uint32_t reserved[2]; + } matrix2; + struct { + uint8_t bank; + uint8_t offset; + uint8_t size; + uint8_t sramActive; + uint8_t unk; + uint8_t currentSize; + uint16_t currentOffset; + uint32_t settleNextEvent; + GBASerializedMulticartFlags flags; + } multicart; + }; struct { int8_t chA[16]; diff --git a/src/gba/CMakeLists.txt b/src/gba/CMakeLists.txt index 01a968a41..71ab7bbb1 100644 --- a/src/gba/CMakeLists.txt +++ b/src/gba/CMakeLists.txt @@ -6,6 +6,7 @@ set(SOURCE_FILES cart/ereader.c cart/gpio.c cart/matrix.c + cart/unlicensed.c cart/vfame.c cheats.c cheats/codebreaker.c diff --git a/src/gba/cart/matrix.c b/src/gba/cart/matrix.c index c594d66b9..9ccbe8832 100644 --- a/src/gba/cart/matrix.c +++ b/src/gba/cart/matrix.c @@ -109,7 +109,7 @@ void GBAMatrixSerialize(const struct GBA* gba, struct GBASerializedState* state) int i; for (i = 0; i < 16; ++i) { - STORE_32(gba->memory.matrix.mappings[i], i << 2, state->matrixMappings); + STORE_32(gba->memory.matrix.mappings[i], i << 2, state->matrix2.mappings); } } @@ -121,7 +121,7 @@ void GBAMatrixDeserialize(struct GBA* gba, const struct GBASerializedState* stat int i; for (i = 0; i < 16; ++i) { - LOAD_32(gba->memory.matrix.mappings[i], i << 2, state->matrixMappings); + LOAD_32(gba->memory.matrix.mappings[i], i << 2, state->matrix2.mappings); gba->memory.matrix.paddr = gba->memory.matrix.mappings[i]; gba->memory.matrix.vaddr = i << 9; _remapMatrix(gba); diff --git a/src/gba/cart/unlicensed.c b/src/gba/cart/unlicensed.c new file mode 100644 index 000000000..318ffe891 --- /dev/null +++ b/src/gba/cart/unlicensed.c @@ -0,0 +1,219 @@ +/* Copyright (c) 2013-2024 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 + +#include +#include +#include +#include + +#define MULTI_SETTLE 512 +#define MULTI_BLOCK 0x80000 +#define MULTI_BANK 0x2000000 + +enum GBMulticartCfgOffset { + GBA_MULTICART_CFG_BANK = 0x2, + GBA_MULTICART_CFG_OFFSET = 0x3, + GBA_MULTICART_CFG_SIZE = 0x4, + GBA_MULTICART_CFG_SRAM = 0x5, + GBA_MULTICART_CFG_UNK = 0x6, +}; + +static void _multicartSettle(struct mTiming* timing, void* context, uint32_t cyclesLate); + +void GBAUnlCartInit(struct GBA* gba) { + memset(&gba->memory.unl, 0, sizeof(gba->memory.unl)); +} + +void GBAUnlCartDetect(struct GBA* gba) { + if (!gba->memory.rom) { + return; + } + + struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; + if (GBAVFameDetect(&gba->memory.unl.vfame, gba->memory.rom, gba->memory.romSize, gba->romCrc32)) { + gba->memory.unl.type = GBA_UNL_CART_VFAME; + return; + } + + if (memcmp(&cart->id, "AXVJ01", 6) == 0) { + if (gba->romVf && gba->romVf->size(gba->romVf) >= 0x04000000) { + // Bootleg multicart + // TODO: Identify a bit more precisely + gba->isPristine = false; + GBASavedataInitSRAM(&gba->memory.savedata); + gba->memory.unl.type = GBA_UNL_CART_MULTICART; + + gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); + gba->memory.unl.multi.fullSize = gba->romVf->size(gba->romVf); + gba->memory.unl.multi.rom = gba->romVf->map(gba->romVf, gba->memory.unl.multi.fullSize, MAP_READ); + gba->memory.rom = gba->memory.unl.multi.rom; + gba->memory.hw.gpioBase = NULL; + + gba->memory.unl.multi.settle.context = gba; + gba->memory.unl.multi.settle.callback = _multicartSettle; + gba->memory.unl.multi.settle.name = "GBA Unlicensed Multicart Settle"; + gba->memory.unl.multi.settle.priority = 0x71; + } + } +} + +void GBAUnlCartReset(struct GBA* gba) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART) { + gba->memory.unl.multi.bank = 0; + gba->memory.unl.multi.offset = 0; + gba->memory.unl.multi.size = 0; + gba->memory.rom = gba->memory.unl.multi.rom; + gba->memory.romSize = GBA_SIZE_ROM0; + } +} + +void GBAUnlCartUnload(struct GBA* gba) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART && gba->romVf) { + gba->romVf->unmap(gba->romVf, gba->memory.unl.multi.rom, gba->memory.unl.multi.size); + gba->memory.unl.multi.rom = NULL; + gba->memory.rom = NULL; + } +} + +void GBAUnlCartWriteSRAM(struct GBA* gba, uint32_t address, uint8_t value) { + struct GBAUnlCart* unl = &gba->memory.unl; + + switch (unl->type) { + case GBA_UNL_CART_VFAME: + GBAVFameSramWrite(&unl->vfame, address, value, gba->memory.savedata.data); + return; + case GBA_UNL_CART_MULTICART: + mLOG(GBA_MEM, DEBUG, "Multicart writing SRAM %06X:%02X", address, value); + switch (address) { + case GBA_MULTICART_CFG_BANK: + unl->multi.bank = value >> 4; + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + break; + case GBA_MULTICART_CFG_OFFSET: + unl->multi.offset = value & 0x3F; + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + break; + case GBA_MULTICART_CFG_SIZE: + unl->multi.size = 0x40 - (value & 0x3F); + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + break; + case GBA_MULTICART_CFG_SRAM: + if (value == 0 && unl->multi.sramActive) { + unl->multi.sramActive = false; + } else if (value == 1 && !unl->multi.sramActive) { + unl->multi.sramActive = true; + } + break; + case GBA_MULTICART_CFG_UNK: + // TODO: What does this do? + unl->multi.unk = value; + break; + default: + break; + } + break; + case GBA_UNL_CART_NONE: + break; + } + + gba->memory.savedata.data[address & (GBA_SIZE_SRAM - 1)] = value; +} + +static void _multicartSettle(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + struct GBA* gba = context; + mLOG(GBA_MEM, INFO, "Switching to bank %i offset %i, size %i", + gba->memory.unl.multi.bank, gba->memory.unl.multi.offset, gba->memory.unl.multi.size); + size_t offset = gba->memory.unl.multi.bank * (MULTI_BANK >> 2) + gba->memory.unl.multi.offset * (MULTI_BLOCK >> 2); + size_t size = gba->memory.unl.multi.size * MULTI_BLOCK; + if (offset * 4 >= gba->memory.unl.multi.fullSize || offset * 4 + size > gba->memory.unl.multi.fullSize) { + mLOG(GBA_MEM, GAME_ERROR, "Bank switch was out of bounds, %07" PRIz "X + %" PRIz "X > %07" PRIz "X", + offset * 4, size, gba->memory.unl.multi.fullSize); + return; + } + gba->memory.rom = gba->memory.unl.multi.rom + offset; + gba->memory.romSize = size; +} + +void GBAUnlCartSerialize(const struct GBA* gba, struct GBASerializedState* state) { + GBASerializedUnlCartFlags flags = 0; + const struct GBAUnlCart* unl = &gba->memory.unl; + switch (unl->type) { + case GBA_UNL_CART_NONE: + return; + case GBA_UNL_CART_VFAME: + flags = GBASerializedUnlCartFlagsSetType(flags, GBA_UNL_CART_VFAME); + flags = GBASerializedUnlCartFlagsSetSubtype(flags, unl->vfame.cartType); + mLOG(GBA_MEM, STUB, "Vast Fame save states are not yet implemented"); + break; + case GBA_UNL_CART_MULTICART: + flags = GBASerializedUnlCartFlagsSetType(0, GBA_UNL_CART_MULTICART); + state->multicart.bank = unl->multi.bank; + state->multicart.offset = unl->multi.offset; + state->multicart.size = unl->multi.size; + state->multicart.sramActive = unl->multi.sramActive; + state->multicart.unk = unl->multi.unk; + state->multicart.currentSize = gba->memory.romSize / MULTI_BLOCK; + STORE_16((gba->memory.rom - unl->multi.rom) / 0x20000, 0, &state->multicart.currentOffset); + STORE_32(unl->multi.settle.when, 0, &state->multicart.settleNextEvent); + if (mTimingIsScheduled(&gba->timing, &unl->multi.settle)) { + STORE_32(GBASerializedMulticartFlagsFillDustSettling(0), 0, &state->multicart.flags); + } + break; + } + STORE_32(flags, 0, &state->hw.unlCartFlags); +} + +void GBAUnlCartDeserialize(struct GBA* gba, const struct GBASerializedState* state) { + GBASerializedUnlCartFlags flags; + struct GBAUnlCart* unl = &gba->memory.unl; + + LOAD_32(flags, 0, &state->hw.unlCartFlags); + enum GBAUnlCartType type = GBASerializedUnlCartFlagsGetType(flags); + if (type != unl->type) { + mLOG(GBA_STATE, WARN, "Save state expects different bootleg type; not restoring bootleg state"); + return; + } + + uint32_t when; + uint32_t offset; + size_t size; + GBASerializedMulticartFlags multiFlags; + + switch (type) { + case GBA_UNL_CART_NONE: + return; + case GBA_UNL_CART_VFAME: + mLOG(GBA_MEM, STUB, "Vast Fame save states are not yet implemented"); + return; + case GBA_UNL_CART_MULTICART: + unl->multi.bank = state->multicart.bank; + unl->multi.offset = state->multicart.offset; + unl->multi.size = state->multicart.size; + unl->multi.sramActive = state->multicart.sramActive; + unl->multi.unk = state->multicart.unk; + size = state->multicart.currentSize * MULTI_BLOCK; + LOAD_16(offset, 0, &state->multicart.currentOffset); + offset *= 0x20000; + if (offset * 4 >= gba->memory.unl.multi.fullSize || offset * 4 + size > gba->memory.unl.multi.fullSize) { + mLOG(GBA_STATE, WARN, "Multicart save state has corrupted ROM offset"); + } else { + gba->memory.romSize = size; + gba->memory.rom = unl->multi.rom + offset; + } + LOAD_32(multiFlags, 0, &state->multicart.flags); + if (GBASerializedMulticartFlagsIsDustSettling(multiFlags)) { + LOAD_32(when, 0, &state->multicart.settleNextEvent); + mTimingSchedule(&gba->timing, &unl->multi.settle, when); + } + break; + } +} diff --git a/src/gba/cart/vfame.c b/src/gba/cart/vfame.c index b66b7d756..a45a420c0 100644 --- a/src/gba/cart/vfame.c +++ b/src/gba/cart/vfame.c @@ -4,7 +4,7 @@ * 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 +#include #include #include @@ -60,25 +60,19 @@ static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mo static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode); static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength); -void GBAVFameInit(struct GBAVFameCart* cart) { - cart->cartType = VFAME_NO; - cart->sramMode = -1; - cart->romMode = -1; - cart->acceptingModeChange = false; -} - -void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32) { - cart->cartType = VFAME_NO; - +bool GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32) { // The initialisation code is also present & run in the dumps of Digimon Ruby & Sapphire from hacked/deprotected reprint carts, // which would break if run in "proper" VFame mode so we need to exclude those.. if (romSize == 0x2000000) { // the deprotected dumps are 32MB but no real VF games are this size - return; + return false; } + bool detected = false; + // Most games have the same init sequence in the same place // but LOTR/Mo Jie Qi Bing doesn't, probably because it's based on the Kiki KaiKai engine, so just detect based on its title if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0 || memcmp("\0LORD\0WORD\0\0AKIJ", &((struct GBACartridge*) rom)->title, 16) == 0) { + detected = true; cart->cartType = VFAME_STANDARD; mLOG(GBA_MEM, INFO, "Vast Fame game detected"); } @@ -87,13 +81,23 @@ void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, ui // Their initialisation seems to be identical so the difference must be in the cart HW itself // Other undumped games may have similar differences if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) { + detected = true; cart->cartType = VFAME_GEORGE; mLOG(GBA_MEM, INFO, "George mode"); } else if (crc32 == DIGIMON_SAPPHIRE_CHINESE_CRC32) { // Chinese version of Digimon Sapphire; header is identical to the English version which uses the normal reordering // so we have to use some other way to detect it + detected = true; cart->cartType = VFAME_ALTERNATE; } + + if (detected) { + cart->sramMode = -1; + cart->romMode = -1; + cart->acceptingModeChange = false; + } + + return detected; } // This is not currently being used but would be called on ROM reads @@ -235,7 +239,6 @@ static uint32_t _patternRightShift2(uint32_t addr) { } void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData) { - address &= 0x00FFFFFF; // A certain sequence of writes to SRAM FFF8->FFFC can enable or disable "mode change" mode // Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything if (address >= 0xFFF8 && address <= 0xFFFC) { diff --git a/src/gba/gba.c b/src/gba/gba.c index 6b833838e..1b84038ae 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -140,6 +140,9 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { void GBAUnloadROM(struct GBA* gba) { GBAMemoryClearAGBPrint(gba); + if (gba->memory.unl.type) { + GBAUnlCartUnload(gba); + } if (gba->memory.rom && !gba->isPristine) { if (gba->yankedRomSize) { gba->yankedRomSize = 0; @@ -492,7 +495,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); } GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); - GBAVFameDetect(&gba->memory.vfame, gba->memory.rom, gba->memory.romSize, gba->romCrc32); + GBAUnlCartDetect(gba); // TODO: error check return true; } diff --git a/src/gba/io.c b/src/gba/io.c index cb6058f7f..a72171c1f 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -1033,6 +1033,7 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) { STORE_32(gba->bus, 0, &state->bus); GBAHardwareSerialize(&gba->memory.hw, state); + GBAUnlCartSerialize(gba, state); } void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { @@ -1082,4 +1083,5 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { GBADMARecalculateCycles(gba); GBADMAUpdate(gba); GBAHardwareDeserialize(&gba->memory.hw, state); + GBAUnlCartDeserialize(gba, state); } diff --git a/src/gba/memory.c b/src/gba/memory.c index 87e31f746..53d0f7cc9 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -93,7 +93,7 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.iwram = &gba->memory.wram[GBA_SIZE_EWRAM >> 2]; GBADMAInit(gba); - GBAVFameInit(&gba->memory.vfame); + GBAUnlCartInit(gba); gba->memory.ereader.p = gba; gba->memory.ereader.dots = NULL; @@ -139,6 +139,7 @@ void GBAMemoryReset(struct GBA* gba) { } GBADMAReset(gba); + GBAUnlCartReset(gba); memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix)); } @@ -411,7 +412,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { wait += waitstatesRegion[address >> BASE_OFFSET]; \ if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ - } else if (memory->vfame.cartType) { \ + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { \ value = GBAVFameGetPatternValue(address, 32); \ } else { \ mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ @@ -576,7 +577,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 16); } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { uint32_t agbPrintAddr = address & 0x00FFFFFF; @@ -601,7 +602,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBACartEReaderRead(&memory->ereader, address); } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 16); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); @@ -692,7 +693,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { value = ((uint8_t*) memory->rom)[address & (GBA_SIZE_ROM0 - 1)]; - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 8); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); @@ -1056,8 +1057,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo } else if (memory->savedata.type == GBA_SAVEDATA_FLASH512 || memory->savedata.type == GBA_SAVEDATA_FLASH1M) { GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == GBA_SAVEDATA_SRAM) { - if (memory->vfame.cartType) { - GBAVFameSramWrite(&memory->vfame, address, value, memory->savedata.data); + if (memory->unl.type) { + GBAUnlCartWriteSRAM(gba, address & 0xFFFF, value); } else { memory->savedata.data[address & (GBA_SIZE_SRAM - 1)] = value; } diff --git a/src/gba/overrides.c b/src/gba/overrides.c index a63616e61..1e3e9e6fc 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -377,6 +377,12 @@ void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overr struct GBACartridgeOverride override = { .idleLoop = GBA_IDLE_LOOP_NONE }; const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; if (cart) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART) { + override.savetype = GBA_SAVEDATA_SRAM; + GBAOverrideApply(gba, &override); + return; + } + memcpy(override.id, &cart->id, sizeof(override.id)); static const uint32_t pokemonTable[] = { diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 5eabaedea..9d6130385 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -32,8 +32,17 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { STORE_64LE(gba->timing.globalCycles, 0, &state->globalCycles); if (gba->memory.rom) { - state->id = ((struct GBACartridge*) gba->memory.rom)->id; - memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + switch (gba->memory.unl.type) { + case GBA_UNL_CART_NONE: + case GBA_UNL_CART_VFAME: + state->id = ((struct GBACartridge*) gba->memory.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + break; + case GBA_UNL_CART_MULTICART: + state->id = ((struct GBACartridge*) gba->memory.unl.multi.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.unl.multi.rom)->title, sizeof(state->title)); + break; + } } else { state->id = 0; memset(state->title, 0, sizeof(state->title)); @@ -106,9 +115,21 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { error = true; } } - if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { - mLOG(GBA_STATE, WARN, "Savestate is for a different game"); - error = true; + if (gba->memory.rom) { + struct GBACartridge* cart; + switch (gba->memory.unl.type) { + case GBA_UNL_CART_NONE: + case GBA_UNL_CART_VFAME: + cart = (struct GBACartridge*) gba->memory.rom; + break; + case GBA_UNL_CART_MULTICART: + cart = (struct GBACartridge*) gba->memory.unl.multi.rom; + break; + } + if (state->id != cart->id || memcmp(state->title, cart->title, sizeof(state->title))) { + mLOG(GBA_STATE, WARN, "Savestate is for a different game"); + error = true; + } } else if (!gba->memory.rom && state->id != 0) { mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded"); error = true;