src/gba/cheats.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
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#include "cheats.h"
7
8#include "gba/gba.h"
9#include "gba/io.h"
10
11const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE;
12
13DEFINE_VECTOR(GBACheatList, struct GBACheat);
14
15static int32_t _read(struct ARMCore* cpu, uint32_t address, int width) {
16 switch (width) {
17 case 1:
18 return cpu->memory.load8(cpu, address, 0);
19 case 2:
20 return cpu->memory.load16(cpu, address, 0);
21 case 4:
22 return cpu->memory.load32(cpu, address, 0);
23 }
24 return 0;
25}
26
27static void _write(struct ARMCore* cpu, uint32_t address, int width, int32_t value) {
28 switch (width) {
29 case 1:
30 cpu->memory.store8(cpu, address, value, 0);
31 break;
32 case 2:
33 cpu->memory.store16(cpu, address, value, 0);
34 break;
35 case 4:
36 cpu->memory.store32(cpu, address, value, 0);
37 break;
38 }
39}
40
41static int _hexDigit(char digit) {
42 switch (digit) {
43 case '0':
44 case '1':
45 case '2':
46 case '3':
47 case '4':
48 case '5':
49 case '6':
50 case '7':
51 case '8':
52 case '9':
53 return digit - '0';
54
55 case 'a':
56 case 'b':
57 case 'c':
58 case 'd':
59 case 'e':
60 case 'f':
61 return digit - 'a' + 10;
62
63 case 'A':
64 case 'B':
65 case 'C':
66 case 'D':
67 case 'E':
68 case 'F':
69 return digit - 'A' + 10;
70
71 default:
72 return -1;
73 }
74}
75
76static const char* _hex32(const char* line, uint32_t* out) {
77 uint32_t value = 0;
78 int i;
79 for (i = 0; i < 8; ++i, ++line) {
80 char digit = *line;
81 value <<= 4;
82 int nybble = _hexDigit(digit);
83 if (nybble < 0) {
84 return 0;
85 }
86 value |= nybble;
87 }
88 *out = value;
89 return line;
90}
91
92static const char* _hex16(const char* line, uint16_t* out) {
93 uint16_t value = 0;
94 int i;
95 for (i = 0; i < 4; ++i, ++line) {
96 char digit = *line;
97 value <<= 4;
98 int nybble = _hexDigit(digit);
99 if (nybble < 0) {
100 return 0;
101 }
102 value |= nybble;
103 }
104 *out = value;
105 return line;
106}
107
108static void _addBreakpoint(struct GBACheatDevice* device) {
109 if (!device->cheats || !device->p) {
110 return;
111 }
112 GBASetBreakpoint(device->p, &device->d, device->cheats->hookAddress, device->cheats->hookMode, &device->cheats->patchedOpcode);
113}
114
115static void _removeBreakpoint(struct GBACheatDevice* device) {
116 if (!device->cheats || !device->p) {
117 return;
118 }
119 GBAClearBreakpoint(device->p, device->cheats->hookAddress, device->cheats->hookMode, device->cheats->patchedOpcode);
120}
121
122static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*);
123static void GBACheatDeviceDeinit(struct ARMComponent*);
124
125void GBACheatDeviceCreate(struct GBACheatDevice* device) {
126 device->d.id = GBA_CHEAT_DEVICE_ID;
127 device->d.init = GBACheatDeviceInit;
128 device->d.deinit = GBACheatDeviceDeinit;
129}
130
131void GBACheatSetInit(struct GBACheatSet* set) {
132 set->hookAddress = 0;
133 set->hookMode = MODE_THUMB;
134 GBACheatListInit(&set->list, 4);
135 set->incompleteCheat = 0;
136 set->patchedOpcode = 0;
137}
138
139void GBACheatSetDeinit(struct GBACheatSet* set) {
140 GBACheatListDeinit(&set->list);
141}
142
143void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) {
144 if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
145 ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
146 }
147 gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d;
148 ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
149}
150
151void GBACheatInstallSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
152 _removeBreakpoint(device);
153 device->cheats = cheats;
154 _addBreakpoint(device);
155}
156
157bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
158 enum GBACodeBreakerType type = op1 >> 28;
159 struct GBACheat* cheat = 0;
160
161 if (cheats->incompleteCheat) {
162 cheats->incompleteCheat->repeat = op1 & 0xFFFF;
163 cheats->incompleteCheat->addressOffset = op2;
164 cheats->incompleteCheat->operandOffset = 0;
165 cheats->incompleteCheat = 0;
166 return true;
167 }
168
169 switch (type) {
170 case CB_GAME_ID:
171 // TODO: Run checksum
172 return true;
173 case CB_HOOK:
174 if (cheats->hookAddress) {
175 return false;
176 }
177 cheats->hookAddress = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
178 cheats->hookMode = MODE_THUMB;
179 return true;
180 case CB_OR_2:
181 cheat = GBACheatListAppend(&cheats->list);
182 cheat->type = CHEAT_OR;
183 cheat->width = 2;
184 break;
185 case CB_ASSIGN_1:
186 cheat = GBACheatListAppend(&cheats->list);
187 cheat->type = CHEAT_ASSIGN;
188 cheat->width = 1;
189 break;
190 case CB_FILL:
191 cheat = GBACheatListAppend(&cheats->list);
192 cheat->type = CHEAT_ASSIGN;
193 cheat->width = 2;
194 cheats->incompleteCheat = cheat;
195 break;
196 case CB_FILL_8:
197 GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
198 return false;
199 case CB_AND_2:
200 cheat = GBACheatListAppend(&cheats->list);
201 cheat->type = CHEAT_AND;
202 cheat->width = 2;
203 break;
204 case CB_IF_EQ:
205 cheat = GBACheatListAppend(&cheats->list);
206 cheat->type = CHEAT_IF_EQ;
207 cheat->width = 2;
208 break;
209 case CB_ASSIGN_2:
210 cheat = GBACheatListAppend(&cheats->list);
211 cheat->type = CHEAT_ASSIGN;
212 cheat->width = 2;
213 break;
214 case CB_ENCRYPT:
215 GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
216 return false;
217 case CB_IF_NE:
218 cheat = GBACheatListAppend(&cheats->list);
219 cheat->type = CHEAT_IF_NE;
220 cheat->width = 2;
221 break;
222 case CB_IF_GT:
223 cheat = GBACheatListAppend(&cheats->list);
224 cheat->type = CHEAT_IF_GT;
225 cheat->width = 2;
226 break;
227 case CB_IF_LT:
228 cheat = GBACheatListAppend(&cheats->list);
229 cheat->type = CHEAT_IF_LT;
230 cheat->width = 2;
231 break;
232 case CB_IF_SPECIAL:
233 switch (op1 & 0x0FFFFFFF) {
234 case 0x20:
235 cheat = GBACheatListAppend(&cheats->list);
236 cheat->type = CHEAT_IF_AND;
237 cheat->width = 2;
238 cheat->address = BASE_IO | REG_JOYSTAT;
239 cheat->operand = op2;
240 cheat->repeat = 1;
241 return true;
242 default:
243 GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
244 return false;
245 }
246 case CB_ADD_2:
247 cheat = GBACheatListAppend(&cheats->list);
248 cheat->type = CHEAT_ADD;
249 cheat->width = 2;
250 break;
251 case CB_IF_AND:
252 cheat = GBACheatListAppend(&cheats->list);
253 cheat->type = CHEAT_IF_AND;
254 cheat->width = 2;
255 break;
256 }
257
258 cheat->address = op1 & 0x0FFFFFFF;
259 cheat->operand = op2;
260 cheat->repeat = 1;
261 return true;
262}
263
264bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
265 uint32_t op1;
266 uint16_t op2;
267 line = _hex32(line, &op1);
268 if (!line) {
269 return false;
270 }
271 while (*line == ' ') {
272 ++line;
273 }
274 line = _hex16(line, &op2);
275 if (!line) {
276 return false;
277 }
278 return GBACheatAddCodeBreaker(cheats, op1, op2);
279}
280
281void GBACheatRefresh(struct GBACheatDevice* device) {
282 bool condition = true;
283 int conditionRemaining = 0;
284
285 size_t nCodes = GBACheatListSize(&device->cheats->list);
286 size_t i;
287 for (i = 0; i < nCodes; ++i) {
288 if (conditionRemaining > 0) {
289 --conditionRemaining;
290 if (!condition) {
291 continue;
292 }
293 } else {
294 condition = true;
295 }
296 struct GBACheat* cheat = GBACheatListGetPointer(&device->cheats->list, i);
297 int32_t value = 0;
298 int32_t operand = cheat->operand;
299 uint32_t operationsRemaining = cheat->repeat;
300 uint32_t address = cheat->address;
301 bool performAssignment = false;
302 for (; operationsRemaining; --operationsRemaining) {
303 switch (cheat->type) {
304 case CHEAT_ASSIGN:
305 value = operand;
306 performAssignment = true;
307 break;
308 case CHEAT_AND:
309 value = _read(device->p->cpu, address, cheat->width) & operand;
310 performAssignment = true;
311 break;
312 case CHEAT_ADD:
313 value = _read(device->p->cpu, address, cheat->width) + operand;
314 performAssignment = true;
315 break;
316 case CHEAT_OR:
317 value = _read(device->p->cpu, address, cheat->width) | operand;
318 performAssignment = true;
319 break;
320 case CHEAT_IF_EQ:
321 condition = _read(device->p->cpu, address, cheat->width) == operand;
322 conditionRemaining = cheat->repeat;
323 break;
324 case CHEAT_IF_NE:
325 condition = _read(device->p->cpu, address, cheat->width) != operand;
326 conditionRemaining = cheat->repeat;
327 break;
328 case CHEAT_IF_LT:
329 condition = _read(device->p->cpu, address, cheat->width) < operand;
330 conditionRemaining = cheat->repeat;
331 break;
332 case CHEAT_IF_GT:
333 condition = _read(device->p->cpu, address, cheat->width) > operand;
334 conditionRemaining = cheat->repeat;
335 break;
336 case CHEAT_IF_AND:
337 condition = _read(device->p->cpu, address, cheat->width) & operand;
338 conditionRemaining = cheat->repeat;
339 break;
340 }
341
342 if (performAssignment) {
343 _write(device->p->cpu, address, cheat->width, value);
344 }
345
346 address += cheat->addressOffset;
347 operand += cheat->operandOffset;
348 }
349 }
350}
351
352void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) {
353 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
354 device->p = (struct GBA*) cpu->master;
355 _addBreakpoint(device);
356}
357
358void GBACheatDeviceDeinit(struct ARMComponent* component) {
359 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
360 _removeBreakpoint(device);
361}