GBA Memory: Add emulation of Vast Fame protected carts
taizou cactusgunman@gmail.com
Wed, 22 Jun 2016 17:56:13 +0100
6 files changed,
352 insertions(+),
1 deletions(-)
M
src/gba/gba.c
→
src/gba/gba.c
@@ -18,6 +18,7 @@ #include "gba/overrides.h"
#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 @@ gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
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; }
M
src/gba/memory.c
→
src/gba/memory.c
@@ -81,6 +81,8 @@ cpu->memory.activeNonseqCycles32 = 0;
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 @@ if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \
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 @@ if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
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 @@ } else if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
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 @@ if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
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 @@ }
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);
M
src/gba/memory.h
→
src/gba/memory.h
@@ -12,6 +12,7 @@ #include "arm/arm.h"
#include "gba/hardware.h" #include "gba/savedata.h" +#include "gba/vfame.h" enum GBAMemoryRegion { REGION_BIOS = 0x0,@@ -118,6 +119,7 @@ uint16_t io[SIZE_IO >> 1];
struct GBACartridgeHardware hw; struct GBASavedata savedata; + struct GBAVFameCart vfame; size_t romSize; uint32_t romMask; uint16_t romID;
A
src/gba/vfame.c
@@ -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; +}
A
src/gba/vfame.h
@@ -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