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 // Most games have the same init sequence in the same place
62 // 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
63 if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0 || memcmp("\0LORD\0WORD\0\0AKIJ", &((struct GBACartridge*) rom)->title, 16) == 0) {
64 cart->cartType = VFAME_STANDARD;
65 mLOG(GBA_MEM, INFO, "Vast Fame game detected");
66 }
67
68 // This game additionally operates with a different set of SRAM modes
69 // Its initialisation seems to be identical so the difference must be in the cart HW itself
70 // Other undumped games may have similar differences
71 if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) {
72 cart->cartType = VFAME_GEORGE;
73 mLOG(GBA_MEM, INFO, "George mode");
74 }
75}
76
77// This is not currently being used but would be called on ROM reads
78// Emulates mirroring used by real VF carts, but no games seem to rely on this behaviour
79uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize) {
80 if (cart->romMode == -1 && (address & 0x01000000) == 0) {
81 // When ROM mode is uninitialised, it just mirrors the first 0x80000 bytes
82 // All known games set the ROM mode to 00 which enables full range of reads, it's currently unknown what other values do
83 address &= 0x7FFFF;
84 } else if (_isInMirroredArea(address, romSize)) {
85 address -= 0x800000;
86 }
87 return address;
88}
89
90static bool _isInMirroredArea(uint32_t address, size_t romSize) {
91 address &= 0x01FFFFFF;
92 // For some reason known 4m games e.g. Zook, Sango repeat the game at 800000 but the 8m Digimon R. does not
93 if (romSize != 0x400000) {
94 return false;
95 }
96 if (address < 0x800000) {
97 return false;
98 }
99 if (address >= 0x800000 + romSize) {
100 return false;
101 }
102 return true;
103}
104
105// Looks like only 16-bit reads are done by games but others are possible...
106uint32_t GBAVFameGetPatternValue(uint32_t address, int bits) {
107 switch (bits) {
108 case 8:
109 if (address & 1) {
110 return _getPatternValue(address) & 0xFF;
111 } else {
112 return (_getPatternValue(address) & 0xFF00) >> 8;
113 }
114 case 16:
115 return _getPatternValue(address);
116 case 32:
117 return (_getPatternValue(address) << 2) + _getPatternValue(address + 2);
118 }
119 return 0;
120}
121
122// 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
123// which the game relies on to run
124static uint32_t _getPatternValue(uint32_t addr) {
125 addr &= 0x1FFFFF;
126 uint32_t value = 0;
127 switch (addr & 0x1F0000) {
128 case 0x000000:
129 case 0x010000:
130 value = (addr >> 1) & 0xFFFF;
131 break;
132 case 0x020000:
133 value = addr & 0xFFFF;
134 break;
135 case 0x030000:
136 value = (addr & 0xFFFF) + 1;
137 break;
138 case 0x040000:
139 value = 0xFFFF - (addr & 0xFFFF);
140 break;
141 case 0x050000:
142 value = (0xFFFF - (addr & 0xFFFF)) - 1;
143 break;
144 case 0x060000:
145 value = (addr & 0xFFFF) ^ 0xAAAA;
146 break;
147 case 0x070000:
148 value = ((addr & 0xFFFF) ^ 0xAAAA) + 1;
149 break;
150 case 0x080000:
151 value = (addr & 0xFFFF) ^ 0x5555;
152 break;
153 case 0x090000:
154 value = ((addr & 0xFFFF) ^ 0x5555) - 1;
155 break;
156 case 0x0A0000:
157 case 0x0B0000:
158 value = _patternRightShift2(addr);
159 break;
160 case 0x0C0000:
161 case 0x0D0000:
162 value = 0xFFFF - _patternRightShift2(addr);
163 break;
164 case 0x0E0000:
165 case 0x0F0000:
166 value = _patternRightShift2(addr) ^ 0xAAAA;
167 break;
168 case 0x100000:
169 case 0x110000:
170 value = _patternRightShift2(addr) ^ 0x5555;
171 break;
172 case 0x120000:
173 value = 0xFFFF - ((addr & 0xFFFF) >> 1);
174 break;
175 case 0x130000:
176 value = 0xFFFF - ((addr & 0xFFFF) >> 1) - 0x8000;
177 break;
178 case 0x140000:
179 case 0x150000:
180 value = ((addr >> 1) & 0xFFFF) ^ 0xAAAA;
181 break;
182 case 0x160000:
183 case 0x170000:
184 value = ((addr >> 1) & 0xFFFF) ^ 0x5555;
185 break;
186 case 0x180000:
187 case 0x190000:
188 value = ((addr >> 1) & 0xFFFF) ^ 0xF0F0;
189 break;
190 case 0x1A0000:
191 case 0x1B0000:
192 value = ((addr >> 1) & 0xFFFF) ^ 0x0F0F;
193 break;
194 case 0x1C0000:
195 case 0x1D0000:
196 value = ((addr >> 1) & 0xFFFF) ^ 0xFF00;
197 break;
198 case 0x1E0000:
199 case 0x1F0000:
200 value = ((addr >> 1) & 0xFFFF) ^ 0x00FF;
201 break;
202 }
203
204 return value & 0xFFFF;
205}
206
207static uint32_t _patternRightShift2(uint32_t addr) {
208 uint32_t value = addr & 0xFFFF;
209 value >>= 2;
210 value += (addr & 3) == 2 ? 0x8000 : 0;
211 value += (addr & 0x10000) ? 0x4000 : 0;
212 return value;
213}
214
215void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData) {
216 address &= 0x00FFFFFF;
217 // A certain sequence of writes to SRAM FFF8->FFFC can enable or disable "mode change" mode
218 // Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything
219 if (address >= 0xFFF8 && address <= 0xFFFC) {
220 cart->writeSequence[address - 0xFFF8] = value;
221 if (address == 0xFFFC) {
222 if (memcmp(MODE_CHANGE_START_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_START_SEQUENCE)) == 0) {
223 cart->acceptingModeChange = true;
224 }
225 if (memcmp(MODE_CHANGE_END_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_END_SEQUENCE)) == 0) {
226 cart->acceptingModeChange = false;
227 }
228 }
229 }
230
231 // If we are in "mode change mode" we can change either SRAM or ROM modes
232 // Currently unknown if other SRAM writes in this mode should have any effect
233 if (cart->acceptingModeChange) {
234 if (address == 0xFFFE) {
235 cart->sramMode = value;
236 } else if (address == 0xFFFD) {
237 cart->romMode = value;
238 }
239 }
240
241 if (cart->sramMode == -1) {
242 // when SRAM mode is uninitialised you can't write to it
243 return;
244 }
245
246 // if mode has been set - the address and value of the SRAM write will be modified
247 address = _modifySramAddress(cart->cartType, address, cart->sramMode);
248 value = _modifySramValue(cart->cartType, value, cart->sramMode);
249 // these writes are mirrored
250 address &= 0x7FFF;
251 sramData[address] = value;
252 sramData[address + 0x8000] = value;
253}
254
255static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode) {
256 mode &= 0x3;
257 if (mode == 0) {
258 return address;
259 } else if (type == VFAME_GEORGE) {
260 return _reorderBits(address, ADDRESS_REORDERING_GEORGE[mode - 1], 16);
261 } else {
262 return _reorderBits(address, ADDRESS_REORDERING[mode - 1], 16);
263 }
264}
265
266static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode) {
267 int reorderType = (mode & 0xF) >> 2;
268 if (reorderType != 0) {
269 if (type == VFAME_GEORGE) {
270 value = _reorderBits(value, VALUE_REORDERING_GEORGE[reorderType - 1], 8);
271 } else {
272 value = _reorderBits(value, VALUE_REORDERING[reorderType - 1], 8);
273 }
274 }
275 if (mode & 0x80) {
276 value ^= 0xAA;
277 }
278 return value;
279}
280
281// Reorder bits in a byte according to the reordering given
282static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength) {
283 uint32_t retval = value;
284
285 int x;
286 for (x = reorderLength; x > 0; x--) {
287 uint8_t reorderPlace = reordering[reorderLength - x]; // get the reorder position
288
289 uint32_t mask = 1 << reorderPlace; // move the bit to the position we want
290 uint32_t val = value & mask; // AND it with the original value
291 val >>= reorderPlace; // move the bit back, so we have the correct 0 or 1
292
293 unsigned destinationPlace = x - 1;
294
295 uint32_t newMask = 1 << destinationPlace;
296 if (val == 1) {
297 retval |= newMask;
298 } else {
299 retval &= ~newMask;
300 }
301 }
302
303 return retval;
304}