mirror of https://github.com/mgba-emu/mgba.git
GBA Memory: Add initial support for N-in-1 style multicarts
This commit is contained in:
parent
d5fbd0ff1c
commit
91cf829261
|
@ -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
|
|
@ -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
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[] = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue