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}