GBA Memory: Add initial support for N-in-1 style multicarts

This commit is contained in:
Vicki Pfau 2024-11-17 02:25:12 -08:00
parent d5fbd0ff1c
commit 91cf829261
13 changed files with 409 additions and 81 deletions

View File

@ -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 <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/core/timing.h>
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

View File

@ -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 <mgba-util/common.h>
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

View File

@ -18,7 +18,7 @@ CXX_GUARD_START
#include <mgba/internal/gba/cart/ereader.h> #include <mgba/internal/gba/cart/ereader.h>
#include <mgba/internal/gba/cart/gpio.h> #include <mgba/internal/gba/cart/gpio.h>
#include <mgba/internal/gba/cart/matrix.h> #include <mgba/internal/gba/cart/matrix.h>
#include <mgba/internal/gba/cart/vfame.h> #include <mgba/internal/gba/cart/unlicensed.h>
enum GBAMemoryRegion { enum GBAMemoryRegion {
GBA_REGION_BIOS = 0x0, GBA_REGION_BIOS = 0x0,
@ -108,8 +108,8 @@ struct GBAMemory {
struct GBACartridgeHardware hw; struct GBACartridgeHardware hw;
struct GBASavedata savedata; struct GBASavedata savedata;
struct GBAVFameCart vfame;
struct GBAMatrix matrix; struct GBAMatrix matrix;
struct GBAUnlCart unl;
struct GBACartEReader ereader; struct GBACartEReader ereader;
size_t romSize; size_t romSize;
uint32_t romMask; uint32_t romMask;

View File

@ -186,11 +186,14 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | bit 3: Reserved * | bit 3: Reserved
* | bits 4 - 15: Light counter * | bits 4 - 15: Light counter
* | 0x002C0 - 0x002C0: Light sample * | 0x002C0 - 0x002C0: Light sample
* | 0x002C1 - 0x002C3: Flags * | 0x002C1: Flags
* | bits 0 - 1: Tilt state machine * | bits 0 - 1: Tilt state machine
* | bits 2 - 3: GB Player inputs posted * | bits 2 - 3: GB Player inputs posted
* | bits 4 - 8: GB Player transmit position * | bits 4 - 7: GB Player transmit position
* | bits 9 - 23: Reserved * | 0x002C2 - 0x002C3: Unlicensed cart flags
* | bits 0 - 4: Cartridge type
* | bits 5 - 7: Cartridge subtype
* | bits 8 - 15: Reserved
* 0x002C4 - 0x002C7: SIO next event * 0x002C4 - 0x002C7: SIO next event
* 0x002C8 - 0x002CB: Current DMA transfer word * 0x002C8 - 0x002CB: Current DMA transfer word
* 0x002CC - 0x002CF: Last DMA transfer PC * 0x002CC - 0x002CF: Last DMA transfer PC
@ -230,8 +233,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | bits 15 - 31: Reserved * | bits 15 - 31: Reserved
* 0x00320 - 0x00323: Next IRQ event * 0x00320 - 0x00323: Next IRQ event
* 0x00324 - 0x00327: Interruptable BIOS stall cycles * 0x00324 - 0x00327: Interruptable BIOS stall cycles
* 0x00328 - 0x00367: Matrix memory mapping table * 0x00328 - 0x0036F: Special cartridge state, one of:
* 0x00368 - 0x0036F: Reserved (leave zero) * | 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 * 0x00370 - 0x0037F: Audio FIFO A samples
* 0x00380 - 0x0038F: Audio FIFO B samples * 0x00380 - 0x0038F: Audio FIFO B samples
* 0x00390 - 0x003CF: Audio rendered samples * 0x00390 - 0x003CF: Audio rendered samples
@ -269,9 +287,14 @@ DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12);
DECL_BITFIELD(GBASerializedHWFlags2, uint8_t); DECL_BITFIELD(GBASerializedHWFlags2, uint8_t);
DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2); DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2);
DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 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_BITFIELD(GBASerializedSavedataFlags, uint8_t);
DECL_BITS(GBASerializedSavedataFlags, FlashState, 0, 2); DECL_BITS(GBASerializedSavedataFlags, FlashState, 0, 2);
@ -370,7 +393,7 @@ struct GBASerializedState {
GBASerializedHWFlags1 flags1; GBASerializedHWFlags1 flags1;
uint8_t lightSample; uint8_t lightSample;
GBASerializedHWFlags2 flags2; GBASerializedHWFlags2 flags2;
GBASerializedHWFlags3 flags3; GBASerializedUnlCartFlags unlCartFlags;
uint32_t sioNextEvent; uint32_t sioNextEvent;
} hw; } hw;
@ -407,8 +430,23 @@ struct GBASerializedState {
uint32_t nextIrq; uint32_t nextIrq;
int32_t biosStall; int32_t biosStall;
uint32_t matrixMappings[16]; union {
uint32_t reservedMatrix[2]; 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 { struct {
int8_t chA[16]; int8_t chA[16];

View File

@ -6,6 +6,7 @@ set(SOURCE_FILES
cart/ereader.c cart/ereader.c
cart/gpio.c cart/gpio.c
cart/matrix.c cart/matrix.c
cart/unlicensed.c
cart/vfame.c cart/vfame.c
cheats.c cheats.c
cheats/codebreaker.c cheats/codebreaker.c

View File

@ -109,7 +109,7 @@ void GBAMatrixSerialize(const struct GBA* gba, struct GBASerializedState* state)
int i; int i;
for (i = 0; i < 16; ++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; int i;
for (i = 0; i < 16; ++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.paddr = gba->memory.matrix.mappings[i];
gba->memory.matrix.vaddr = i << 9; gba->memory.matrix.vaddr = i << 9;
_remapMatrix(gba); _remapMatrix(gba);

219
src/gba/cart/unlicensed.c Normal file
View File

@ -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 <mgba/internal/gba/cart/unlicensed.h>
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/serialize.h>
#include <mgba-util/vfs.h>
#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;
}
}

View File

@ -4,7 +4,7 @@
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/gba/cart/vfame.h> #include <mgba/internal/gba/cart/unlicensed.h>
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/memory.h> #include <mgba/internal/gba/memory.h>
@ -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 uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode);
static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength); static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength);
void GBAVFameInit(struct GBAVFameCart* cart) { bool GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32) {
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;
// The initialisation code is also present & run in the dumps of Digimon Ruby & Sapphire from hacked/deprotected reprint carts, // 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.. // 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 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 // 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 // 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) { 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; cart->cartType = VFAME_STANDARD;
mLOG(GBA_MEM, INFO, "Vast Fame game detected"); 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 // Their initialisation seems to be identical so the difference must be in the cart HW itself
// Other undumped games may have similar differences // Other undumped games may have similar differences
if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) { if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) {
detected = true;
cart->cartType = VFAME_GEORGE; cart->cartType = VFAME_GEORGE;
mLOG(GBA_MEM, INFO, "George mode"); mLOG(GBA_MEM, INFO, "George mode");
} else if (crc32 == DIGIMON_SAPPHIRE_CHINESE_CRC32) { } else if (crc32 == DIGIMON_SAPPHIRE_CHINESE_CRC32) {
// Chinese version of Digimon Sapphire; header is identical to the English version which uses the normal reordering // 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 // so we have to use some other way to detect it
detected = true;
cart->cartType = VFAME_ALTERNATE; 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 // 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) { 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 // 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 // Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything
if (address >= 0xFFF8 && address <= 0xFFFC) { if (address >= 0xFFF8 && address <= 0xFFFC) {

View File

@ -140,6 +140,9 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
void GBAUnloadROM(struct GBA* gba) { void GBAUnloadROM(struct GBA* gba) {
GBAMemoryClearAGBPrint(gba); GBAMemoryClearAGBPrint(gba);
if (gba->memory.unl.type) {
GBAUnlCartUnload(gba);
}
if (gba->memory.rom && !gba->isPristine) { if (gba->memory.rom && !gba->isPristine) {
if (gba->yankedRomSize) { if (gba->yankedRomSize) {
gba->yankedRomSize = 0; 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]); gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]);
} }
GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); 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 // TODO: error check
return true; return true;
} }

View File

@ -1033,6 +1033,7 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
STORE_32(gba->bus, 0, &state->bus); STORE_32(gba->bus, 0, &state->bus);
GBAHardwareSerialize(&gba->memory.hw, state); GBAHardwareSerialize(&gba->memory.hw, state);
GBAUnlCartSerialize(gba, state);
} }
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* 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); GBADMARecalculateCycles(gba);
GBADMAUpdate(gba); GBADMAUpdate(gba);
GBAHardwareDeserialize(&gba->memory.hw, state); GBAHardwareDeserialize(&gba->memory.hw, state);
GBAUnlCartDeserialize(gba, state);
} }

View File

@ -93,7 +93,7 @@ void GBAMemoryInit(struct GBA* gba) {
gba->memory.iwram = &gba->memory.wram[GBA_SIZE_EWRAM >> 2]; gba->memory.iwram = &gba->memory.wram[GBA_SIZE_EWRAM >> 2];
GBADMAInit(gba); GBADMAInit(gba);
GBAVFameInit(&gba->memory.vfame); GBAUnlCartInit(gba);
gba->memory.ereader.p = gba; gba->memory.ereader.p = gba;
gba->memory.ereader.dots = NULL; gba->memory.ereader.dots = NULL;
@ -139,6 +139,7 @@ void GBAMemoryReset(struct GBA* gba) {
} }
GBADMAReset(gba); GBADMAReset(gba);
GBAUnlCartReset(gba);
memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix)); 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]; \ wait += waitstatesRegion[address >> BASE_OFFSET]; \
if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \
LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ 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); \ value = GBAVFameGetPatternValue(address, 32); \
} else { \ } else { \
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ 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]; wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) {
LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); 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); value = GBAVFameGetPatternValue(address, 16);
} else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) {
uint32_t agbPrintAddr = address & 0x00FFFFFF; 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); value = GBACartEReaderRead(&memory->ereader, address);
} else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) {
LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); 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); value = GBAVFameGetPatternValue(address, 16);
} else { } else {
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); 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]; wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) {
value = ((uint8_t*) memory->rom)[address & (GBA_SIZE_ROM0 - 1)]; 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); value = GBAVFameGetPatternValue(address, 8);
} else { } else {
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); 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) { } else if (memory->savedata.type == GBA_SAVEDATA_FLASH512 || memory->savedata.type == GBA_SAVEDATA_FLASH1M) {
GBASavedataWriteFlash(&memory->savedata, address, value); GBASavedataWriteFlash(&memory->savedata, address, value);
} else if (memory->savedata.type == GBA_SAVEDATA_SRAM) { } else if (memory->savedata.type == GBA_SAVEDATA_SRAM) {
if (memory->vfame.cartType) { if (memory->unl.type) {
GBAVFameSramWrite(&memory->vfame, address, value, memory->savedata.data); GBAUnlCartWriteSRAM(gba, address & 0xFFFF, value);
} else { } else {
memory->savedata.data[address & (GBA_SIZE_SRAM - 1)] = value; memory->savedata.data[address & (GBA_SIZE_SRAM - 1)] = value;
} }

View File

@ -377,6 +377,12 @@ void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overr
struct GBACartridgeOverride override = { .idleLoop = GBA_IDLE_LOOP_NONE }; struct GBACartridgeOverride override = { .idleLoop = GBA_IDLE_LOOP_NONE };
const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom;
if (cart) { 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)); memcpy(override.id, &cart->id, sizeof(override.id));
static const uint32_t pokemonTable[] = { static const uint32_t pokemonTable[] = {

View File

@ -32,8 +32,17 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
STORE_64LE(gba->timing.globalCycles, 0, &state->globalCycles); STORE_64LE(gba->timing.globalCycles, 0, &state->globalCycles);
if (gba->memory.rom) { if (gba->memory.rom) {
state->id = ((struct GBACartridge*) gba->memory.rom)->id; switch (gba->memory.unl.type) {
memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); 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 { } else {
state->id = 0; state->id = 0;
memset(state->title, 0, sizeof(state->title)); memset(state->title, 0, sizeof(state->title));
@ -106,9 +115,21 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
error = true; 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)))) { if (gba->memory.rom) {
mLOG(GBA_STATE, WARN, "Savestate is for a different game"); struct GBACartridge* cart;
error = true; 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) { } else if (!gba->memory.rom && state->id != 0) {
mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded"); mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded");
error = true; error = true;