all repos — mgba @ c5092559ef04c700a233e8fcf3263e818766075f

mGBA Game Boy Advance Emulator

src/gba/vfame.c (view raw)

  1/* Copyright (c) 2016 taizou
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6
  7#include "vfame.h"
  8#include "gba/gba.h"
  9#include "gba/memory.h"
 10
 11static const uint8_t ADDRESS_REORDERING[4][16] = {
 12	{ 15, 14, 9, 1, 8, 10, 7, 3, 5, 11, 4, 0, 13, 12, 2, 6 },
 13	{ 15, 7, 13, 5, 11, 6, 0, 9, 12, 2, 10, 14, 3, 1, 8, 4 },
 14	{ 15, 0, 3, 12, 2, 4, 14, 13, 1, 8, 6, 7, 9, 5, 11, 10 }
 15};
 16static const uint8_t ADDRESS_REORDERING_GEORGE[4][16] = {
 17	{ 15, 7, 13, 1, 11, 10, 14, 9, 12, 2, 4, 0, 3, 5, 8, 6 },
 18	{ 15, 14, 3, 12, 8, 4, 0, 13, 5, 11, 6, 7, 9, 1, 2, 10 },
 19	{ 15, 0, 9, 5, 2, 6, 7, 3, 1, 8, 10, 14, 13, 12, 11, 4 }
 20};
 21static const uint8_t VALUE_REORDERING[4][16] = {
 22	{ 5, 4, 3, 2, 1, 0, 7, 6 },
 23	{ 3, 2, 1, 0, 7, 6, 5, 4 },
 24	{ 1, 0, 7, 6, 5, 4, 3, 2 }
 25};
 26static const uint8_t VALUE_REORDERING_GEORGE[4][16] = {
 27	{ 3, 0, 7, 2, 1, 4, 5, 6 },
 28	{ 1, 4, 3, 0, 5, 6, 7, 2 },
 29	{ 5, 2, 1, 6, 7, 0, 3, 4 }
 30};
 31
 32static const int8_t MODE_CHANGE_START_SEQUENCE[5] = { 0x99, 0x02, 0x05, 0x02, 0x03 };
 33static const int8_t MODE_CHANGE_END_SEQUENCE[5] = { 0x99, 0x03, 0x62, 0x02, 0x56 };
 34
 35// A portion of the initialisation routine that gets copied into RAM - Always seems to be present at 0x15C in VFame game ROM
 36static const char INIT_SEQUENCE[16] = { 0xB4, 0x00, 0x9F, 0xE5, 0x99, 0x10, 0xA0, 0xE3, 0x00, 0x10, 0xC0, 0xE5, 0xAC, 0x00, 0x9F, 0xE5 };
 37
 38static bool _isInMirroredArea(uint32_t address, size_t romSize);
 39static uint32_t _getPatternValue(uint32_t addr);
 40static uint32_t _patternRightShift2(uint32_t addr);
 41static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode);
 42static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode);
 43static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength);
 44
 45void GBAVFameInit(struct GBAVFameCart* cart) {
 46	cart->cartType = VFAME_NO;
 47	cart->sramMode = -1;
 48	cart->romMode = -1;
 49	cart->acceptingModeChange = false;
 50}
 51
 52void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize) {
 53	cart->cartType = VFAME_NO;
 54
 55	// The initialisation code is also present & run in the dumps of Digimon Ruby & Sapphire from hacked/deprotected reprint carts,
 56	// which would break if run in "proper" VFame mode so we need to exclude those..
 57	if (romSize == 0x2000000) { // the deprotected dumps are 32MB but no real VF games are this size
 58		return;
 59	}
 60
 61	if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0) {
 62		cart->cartType = VFAME_STANDARD;
 63		mLOG(GBA_MEM, INFO, "Vast Fame game detected");
 64	}
 65
 66	// This game additionally operates with a different set of SRAM modes
 67	// Its initialisation seems to be identical so the difference must be in the cart HW itself
 68	// Other undumped games may have similar differences
 69	if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) {
 70		cart->cartType = VFAME_GEORGE;
 71		mLOG(GBA_MEM, INFO, "George mode");
 72	}
 73}
 74
 75// This is not currently being used but would be called on ROM reads
 76// Emulates mirroring used by real VF carts, but no games seem to rely on this behaviour
 77uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize) {
 78	if (cart->romMode == -1 && (address & 0x01000000) == 0) {
 79		// When ROM mode is uninitialised, it just mirrors the first 0x80000 bytes
 80		// All known games set the ROM mode to 00 which enables full range of reads, it's currently unknown what other values do
 81		address &= 0x7FFFF;
 82	} else if (_isInMirroredArea(address, romSize)) {
 83		address -= 0x800000;
 84	}
 85	return address;
 86}
 87
 88static bool _isInMirroredArea(uint32_t address, size_t romSize) {
 89	address &= 0x01FFFFFF;
 90	// For some reason known 4m games e.g. Zook, Sango repeat the game at 800000 but the 8m Digimon R. does not
 91	if (romSize != 0x400000) {
 92		return false;
 93	}
 94	if (address < 0x800000) {
 95		return false;
 96	}
 97	if (address >= 0x800000 + romSize) {
 98		return false;
 99	}
100	return true;
101}
102
103// Looks like only 16-bit reads are done by games but others are possible...
104uint32_t GBAVFameGetPatternValue(uint32_t address, int bits) {
105	switch (bits) {
106	case 8:
107		if (address & 1) {
108			return _getPatternValue(address) & 0xFF;
109		} else {
110			return (_getPatternValue(address) & 0xFF00) >> 8;
111		}
112	case 16:
113		return _getPatternValue(address);
114	case 32:
115		return (_getPatternValue(address) << 2) + _getPatternValue(address + 2);
116	}
117	return 0;
118}
119
120// 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
121// which the game relies on to run
122static uint32_t _getPatternValue(uint32_t addr) {
123	addr &= 0x1FFFFF;
124	uint32_t value = 0;
125	switch (addr & 0x1F0000) {
126	case 0x000000:
127	case 0x010000:
128		value = (addr >> 1) & 0xFFFF;
129		break;
130	case 0x020000:
131		value = addr & 0xFFFF;
132		break;
133	case 0x030000:
134		value = (addr & 0xFFFF) + 1;
135		break;
136	case 0x040000:
137		value = 0xFFFF - (addr & 0xFFFF);
138		break;
139	case 0x050000:
140		value = (0xFFFF - (addr & 0xFFFF)) - 1;
141		break;
142	case 0x060000:
143		value = (addr & 0xFFFF) ^ 0xAAAA;
144		break;
145	case 0x070000:
146		value = ((addr & 0xFFFF) ^ 0xAAAA) + 1;
147		break;
148	case 0x080000:
149		value = (addr & 0xFFFF) ^ 0x5555;
150		break;
151	case 0x090000:
152		value = ((addr & 0xFFFF) ^ 0x5555) - 1;
153		break;
154	case 0x0A0000:
155	case 0x0B0000:
156		value = _patternRightShift2(addr);
157		break;
158	case 0x0C0000:
159	case 0x0D0000:
160		value = 0xFFFF - _patternRightShift2(addr);
161		break;
162	case 0x0E0000:
163	case 0x0F0000:
164		value = _patternRightShift2(addr) ^ 0xAAAA;
165		break;
166	case 0x100000:
167	case 0x110000:
168		value = _patternRightShift2(addr) ^ 0x5555;
169		break;
170	case 0x120000:
171		value = 0xFFFF - ((addr & 0xFFFF) >> 1);
172		break;
173	case 0x130000:
174		value = 0xFFFF - ((addr & 0xFFFF) >> 1) - 0x8000;
175		break;
176	case 0x140000:
177	case 0x150000:
178		value = ((addr >> 1) & 0xFFFF) ^ 0xAAAA;
179		break;
180	case 0x160000:
181	case 0x170000:
182		value = ((addr >> 1) & 0xFFFF) ^ 0x5555;
183		break;
184	case 0x180000:
185	case 0x190000:
186		value = ((addr >> 1) & 0xFFFF) ^ 0xF0F0;
187		break;
188	case 0x1A0000:
189	case 0x1B0000:
190		value = ((addr >> 1) & 0xFFFF) ^ 0x0F0F;
191		break;
192	case 0x1C0000:
193	case 0x1D0000:
194		value = ((addr >> 1) & 0xFFFF) ^ 0xFF00;
195		break;
196	case 0x1E0000:
197	case 0x1F0000:
198		value = ((addr >> 1) & 0xFFFF) ^ 0x00FF;
199		break;
200	}
201
202	return value & 0xFFFF;
203}
204
205static uint32_t _patternRightShift2(uint32_t addr) {
206	uint32_t value = addr & 0xFFFF;
207	value >>= 2;
208	value += (addr & 3) == 2 ? 0x8000 : 0;
209	value += (addr & 0x10000) ? 0x4000 : 0;
210	return value;
211}
212
213void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData) {
214	address &= 0x00FFFFFF;
215	// A certain sequence of writes to SRAM FFF8->FFFC can enable or disable "mode change" mode
216	// Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything
217	if (address >= 0xFFF8 && address <= 0xFFFC) {
218		cart->writeSequence[address - 0xFFF8] = value;
219		if (address == 0xFFFC) {
220			if (memcmp(MODE_CHANGE_START_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_START_SEQUENCE)) == 0) {
221				cart->acceptingModeChange = true;
222			}
223			if (memcmp(MODE_CHANGE_END_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_END_SEQUENCE)) == 0) {
224				cart->acceptingModeChange = false;
225			}
226		}
227	}
228
229	// If we are in "mode change mode" we can change either SRAM or ROM modes
230	// Currently unknown if other SRAM writes in this mode should have any effect
231	if (cart->acceptingModeChange) {
232		if (address == 0xFFFE) {
233			cart->sramMode = value;
234		} else if (address == 0xFFFD) {
235			cart->romMode = value;
236		}
237	}
238
239	if (cart->sramMode == -1) {
240		// when SRAM mode is uninitialised you can't write to it
241		return;
242	}
243
244	// if mode has been set - the address and value of the SRAM write will be modified
245	address = _modifySramAddress(cart->cartType, address, cart->sramMode);
246	value = _modifySramValue(cart->cartType, value, cart->sramMode);
247	// these writes are mirrored
248	address &= 0x7FFF;
249	sramData[address] = value;
250	sramData[address + 0x8000] = value;
251}
252
253static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode) {
254	mode &= 0x3;
255	if (mode == 0) {
256		return address;
257	} else if (type == VFAME_GEORGE) {
258		return _reorderBits(address, ADDRESS_REORDERING_GEORGE[mode - 1], 16);
259	} else {
260		return _reorderBits(address, ADDRESS_REORDERING[mode - 1], 16);
261	}
262}
263
264static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode) {
265	mode = (mode & 0xF) >> 2;
266	if (mode == 0) {
267		return value;
268	} else if (type == VFAME_GEORGE) {
269		return _reorderBits(value, VALUE_REORDERING_GEORGE[mode - 1], 8);
270	} else {
271		return _reorderBits(value, VALUE_REORDERING[mode - 1], 8);
272	}
273}
274
275// Reorder bits in a byte according to the reordering given
276static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength) {
277	uint32_t retval = value;
278
279	int x;
280	for (x = reorderLength; x > 0; x--) {
281		uint8_t reorderPlace = reordering[reorderLength - x]; // get the reorder position
282
283		uint32_t mask = 1 << reorderPlace; // move the bit to the position we want
284		uint32_t val = value & mask; // AND it with the original value
285		val >>= reorderPlace; // move the bit back, so we have the correct 0 or 1
286
287		unsigned destinationPlace = x - 1;
288
289		uint32_t newMask = 1 << destinationPlace;
290		if (val == 1) {
291			retval |= newMask;
292		} else {
293			retval &= ~newMask;
294		}
295	}
296
297	return retval;
298}