GBA Memory: Add emulation of Vast Fame protected carts

This commit is contained in:
taizou 2016-06-22 17:56:13 +01:00
parent 6b1cbbd5e2
commit c5092559ef
6 changed files with 352 additions and 1 deletions

View File

@ -2,6 +2,7 @@
Features:
- Game Boy support
- Support for encrypted CodeBreaker GBA cheats
- Emulation of Vast Fame protected GBA carts (taizou)
Bugfixes:
- VFS: Fix reading 7z archives without rewinding first
- Qt: Fix sending gameStopped twice

View File

@ -18,6 +18,7 @@
#include "gba/rr/rr.h"
#include "gba/serialize.h"
#include "gba/sio.h"
#include "gba/vfame.h"
#include "util/crc32.h"
#include "util/memory.h"
@ -474,6 +475,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
gba->memory.mirroring = false;
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);
GBAVFameDetect(&gba->memory.vfame, gba->memory.rom, gba->memory.romSize);
// TODO: error check
return true;
}

View File

@ -81,6 +81,8 @@ void GBAMemoryInit(struct GBA* gba) {
cpu->memory.activeNonseqCycles16 = 0;
gba->memory.biosPrefetch = 0;
gba->memory.mirroring = false;
GBAVFameInit(&gba->memory.vfame);
}
void GBAMemoryDeinit(struct GBA* gba) {
@ -383,6 +385,8 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { \
LOAD_32(value, address & memory->romMask, memory->rom); \
} else if (memory->vfame.cartType) { \
value = GBAVFameGetPatternValue(address, 32); \
} else { \
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \
value = ((address & ~3) >> 1) & 0xFFFF; \
@ -515,6 +519,8 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
LOAD_16(value, address & memory->romMask, memory->rom);
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 16);
} else {
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address);
value = (address >> 1) & 0xFFFF;
@ -528,6 +534,8 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom);
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
LOAD_16(value, address & memory->romMask, memory->rom);
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 16);
} else {
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address);
value = (address >> 1) & 0xFFFF;
@ -613,6 +621,8 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
} else if (memory->mirroring && (address & memory->romMask) < memory->romSize) {
value = ((uint8_t*) memory->rom)[address & memory->romMask];
} else if (memory->vfame.cartType) {
value = GBAVFameGetPatternValue(address, 8);
} else {
mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address);
value = (address >> 1) & 0xFF;
@ -873,7 +883,11 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) {
GBASavedataWriteFlash(&memory->savedata, address, value);
} else if (memory->savedata.type == SAVEDATA_SRAM) {
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
if (memory->vfame.cartType) {
GBAVFameSramWrite(&memory->vfame, address, value, memory->savedata.data);
} else {
memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value;
}
memory->savedata.dirty |= SAVEDATA_DIRT_NEW;
} else if (memory->hw.devices & HW_TILT) {
GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value);

View File

@ -12,6 +12,7 @@
#include "gba/hardware.h"
#include "gba/savedata.h"
#include "gba/vfame.h"
enum GBAMemoryRegion {
REGION_BIOS = 0x0,
@ -118,6 +119,7 @@ struct GBAMemory {
struct GBACartridgeHardware hw;
struct GBASavedata savedata;
struct GBAVFameCart vfame;
size_t romSize;
uint32_t romMask;
uint16_t romID;

298
src/gba/vfame.c Normal file
View File

@ -0,0 +1,298 @@
/* 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/. */
#include "vfame.h"
#include "gba/gba.h"
#include "gba/memory.h"
static const uint8_t ADDRESS_REORDERING[4][16] = {
{ 15, 14, 9, 1, 8, 10, 7, 3, 5, 11, 4, 0, 13, 12, 2, 6 },
{ 15, 7, 13, 5, 11, 6, 0, 9, 12, 2, 10, 14, 3, 1, 8, 4 },
{ 15, 0, 3, 12, 2, 4, 14, 13, 1, 8, 6, 7, 9, 5, 11, 10 }
};
static const uint8_t ADDRESS_REORDERING_GEORGE[4][16] = {
{ 15, 7, 13, 1, 11, 10, 14, 9, 12, 2, 4, 0, 3, 5, 8, 6 },
{ 15, 14, 3, 12, 8, 4, 0, 13, 5, 11, 6, 7, 9, 1, 2, 10 },
{ 15, 0, 9, 5, 2, 6, 7, 3, 1, 8, 10, 14, 13, 12, 11, 4 }
};
static const uint8_t VALUE_REORDERING[4][16] = {
{ 5, 4, 3, 2, 1, 0, 7, 6 },
{ 3, 2, 1, 0, 7, 6, 5, 4 },
{ 1, 0, 7, 6, 5, 4, 3, 2 }
};
static const uint8_t VALUE_REORDERING_GEORGE[4][16] = {
{ 3, 0, 7, 2, 1, 4, 5, 6 },
{ 1, 4, 3, 0, 5, 6, 7, 2 },
{ 5, 2, 1, 6, 7, 0, 3, 4 }
};
static const int8_t MODE_CHANGE_START_SEQUENCE[5] = { 0x99, 0x02, 0x05, 0x02, 0x03 };
static const int8_t MODE_CHANGE_END_SEQUENCE[5] = { 0x99, 0x03, 0x62, 0x02, 0x56 };
// A portion of the initialisation routine that gets copied into RAM - Always seems to be present at 0x15C in VFame game ROM
static const char INIT_SEQUENCE[16] = { 0xB4, 0x00, 0x9F, 0xE5, 0x99, 0x10, 0xA0, 0xE3, 0x00, 0x10, 0xC0, 0xE5, 0xAC, 0x00, 0x9F, 0xE5 };
static bool _isInMirroredArea(uint32_t address, size_t romSize);
static uint32_t _getPatternValue(uint32_t addr);
static uint32_t _patternRightShift2(uint32_t addr);
static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, 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);
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) {
cart->cartType = VFAME_NO;
// 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;
}
if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0) {
cart->cartType = VFAME_STANDARD;
mLOG(GBA_MEM, INFO, "Vast Fame game detected");
}
// This game additionally operates with a different set of SRAM modes
// Its 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) {
cart->cartType = VFAME_GEORGE;
mLOG(GBA_MEM, INFO, "George mode");
}
}
// This is not currently being used but would be called on ROM reads
// Emulates mirroring used by real VF carts, but no games seem to rely on this behaviour
uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize) {
if (cart->romMode == -1 && (address & 0x01000000) == 0) {
// When ROM mode is uninitialised, it just mirrors the first 0x80000 bytes
// All known games set the ROM mode to 00 which enables full range of reads, it's currently unknown what other values do
address &= 0x7FFFF;
} else if (_isInMirroredArea(address, romSize)) {
address -= 0x800000;
}
return address;
}
static bool _isInMirroredArea(uint32_t address, size_t romSize) {
address &= 0x01FFFFFF;
// For some reason known 4m games e.g. Zook, Sango repeat the game at 800000 but the 8m Digimon R. does not
if (romSize != 0x400000) {
return false;
}
if (address < 0x800000) {
return false;
}
if (address >= 0x800000 + romSize) {
return false;
}
return true;
}
// Looks like only 16-bit reads are done by games but others are possible...
uint32_t GBAVFameGetPatternValue(uint32_t address, int bits) {
switch (bits) {
case 8:
if (address & 1) {
return _getPatternValue(address) & 0xFF;
} else {
return (_getPatternValue(address) & 0xFF00) >> 8;
}
case 16:
return _getPatternValue(address);
case 32:
return (_getPatternValue(address) << 2) + _getPatternValue(address + 2);
}
return 0;
}
// when you read from a ROM location outside the actual ROM data or its mirror, it returns a value based on some 16-bit transformation of the address
// which the game relies on to run
static uint32_t _getPatternValue(uint32_t addr) {
addr &= 0x1FFFFF;
uint32_t value = 0;
switch (addr & 0x1F0000) {
case 0x000000:
case 0x010000:
value = (addr >> 1) & 0xFFFF;
break;
case 0x020000:
value = addr & 0xFFFF;
break;
case 0x030000:
value = (addr & 0xFFFF) + 1;
break;
case 0x040000:
value = 0xFFFF - (addr & 0xFFFF);
break;
case 0x050000:
value = (0xFFFF - (addr & 0xFFFF)) - 1;
break;
case 0x060000:
value = (addr & 0xFFFF) ^ 0xAAAA;
break;
case 0x070000:
value = ((addr & 0xFFFF) ^ 0xAAAA) + 1;
break;
case 0x080000:
value = (addr & 0xFFFF) ^ 0x5555;
break;
case 0x090000:
value = ((addr & 0xFFFF) ^ 0x5555) - 1;
break;
case 0x0A0000:
case 0x0B0000:
value = _patternRightShift2(addr);
break;
case 0x0C0000:
case 0x0D0000:
value = 0xFFFF - _patternRightShift2(addr);
break;
case 0x0E0000:
case 0x0F0000:
value = _patternRightShift2(addr) ^ 0xAAAA;
break;
case 0x100000:
case 0x110000:
value = _patternRightShift2(addr) ^ 0x5555;
break;
case 0x120000:
value = 0xFFFF - ((addr & 0xFFFF) >> 1);
break;
case 0x130000:
value = 0xFFFF - ((addr & 0xFFFF) >> 1) - 0x8000;
break;
case 0x140000:
case 0x150000:
value = ((addr >> 1) & 0xFFFF) ^ 0xAAAA;
break;
case 0x160000:
case 0x170000:
value = ((addr >> 1) & 0xFFFF) ^ 0x5555;
break;
case 0x180000:
case 0x190000:
value = ((addr >> 1) & 0xFFFF) ^ 0xF0F0;
break;
case 0x1A0000:
case 0x1B0000:
value = ((addr >> 1) & 0xFFFF) ^ 0x0F0F;
break;
case 0x1C0000:
case 0x1D0000:
value = ((addr >> 1) & 0xFFFF) ^ 0xFF00;
break;
case 0x1E0000:
case 0x1F0000:
value = ((addr >> 1) & 0xFFFF) ^ 0x00FF;
break;
}
return value & 0xFFFF;
}
static uint32_t _patternRightShift2(uint32_t addr) {
uint32_t value = addr & 0xFFFF;
value >>= 2;
value += (addr & 3) == 2 ? 0x8000 : 0;
value += (addr & 0x10000) ? 0x4000 : 0;
return value;
}
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) {
cart->writeSequence[address - 0xFFF8] = value;
if (address == 0xFFFC) {
if (memcmp(MODE_CHANGE_START_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_START_SEQUENCE)) == 0) {
cart->acceptingModeChange = true;
}
if (memcmp(MODE_CHANGE_END_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_END_SEQUENCE)) == 0) {
cart->acceptingModeChange = false;
}
}
}
// If we are in "mode change mode" we can change either SRAM or ROM modes
// Currently unknown if other SRAM writes in this mode should have any effect
if (cart->acceptingModeChange) {
if (address == 0xFFFE) {
cart->sramMode = value;
} else if (address == 0xFFFD) {
cart->romMode = value;
}
}
if (cart->sramMode == -1) {
// when SRAM mode is uninitialised you can't write to it
return;
}
// if mode has been set - the address and value of the SRAM write will be modified
address = _modifySramAddress(cart->cartType, address, cart->sramMode);
value = _modifySramValue(cart->cartType, value, cart->sramMode);
// these writes are mirrored
address &= 0x7FFF;
sramData[address] = value;
sramData[address + 0x8000] = value;
}
static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode) {
mode &= 0x3;
if (mode == 0) {
return address;
} else if (type == VFAME_GEORGE) {
return _reorderBits(address, ADDRESS_REORDERING_GEORGE[mode - 1], 16);
} else {
return _reorderBits(address, ADDRESS_REORDERING[mode - 1], 16);
}
}
static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode) {
mode = (mode & 0xF) >> 2;
if (mode == 0) {
return value;
} else if (type == VFAME_GEORGE) {
return _reorderBits(value, VALUE_REORDERING_GEORGE[mode - 1], 8);
} else {
return _reorderBits(value, VALUE_REORDERING[mode - 1], 8);
}
}
// Reorder bits in a byte according to the reordering given
static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength) {
uint32_t retval = value;
int x;
for (x = reorderLength; x > 0; x--) {
uint8_t reorderPlace = reordering[reorderLength - x]; // get the reorder position
uint32_t mask = 1 << reorderPlace; // move the bit to the position we want
uint32_t val = value & mask; // AND it with the original value
val >>= reorderPlace; // move the bit back, so we have the correct 0 or 1
unsigned destinationPlace = x - 1;
uint32_t newMask = 1 << destinationPlace;
if (val == 1) {
retval |= newMask;
} else {
retval &= ~newMask;
}
}
return retval;
}

34
src/gba/vfame.h Normal file
View File

@ -0,0 +1,34 @@
/* 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 "util/common.h"
enum GBAVFameCartType {
VFAME_NO = 0,
VFAME_STANDARD = 1,
VFAME_GEORGE = 2
};
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);
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);
#endif