SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
Core/cheats.c
1 #include "gb.h"
2 #include "cheats.h"
3 #include <stdio.h>
4 #include <assert.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <stdlib.h>
8
9 static inline uint8_t hash_addr(uint16_t addr)
10 {
11 return addr;
12 }
13
14 static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
15 {
16 if (addr < 0x4000) {
17 return gb->mbc_rom0_bank;
18 }
19
20 if (addr < 0x8000) {
21 return gb->mbc_rom_bank;
22 }
23
24 if (addr < 0xD000) {
25 return 0;
26 }
27
28 if (addr < 0xE000) {
29 return gb->cgb_ram_bank;
30 }
31
32 return 0;
33 }
34
35 static noinline void apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
36 {
37 if (unlikely(!gb->boot_rom_finished)) return;
38 const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
39 if (likely(!hash)) return;
40
41 for (unsigned i = 0; i < hash->size; i++) {
42 GB_cheat_t *cheat = hash->cheats[i];
43 if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
44 if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) {
45 *value = cheat->value;
46 break;
47 }
48 }
49 }
50 }
51
52 void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
53 {
54 if (likely(!gb->cheat_enabled)) return;
55 if (likely(gb->cheat_count == 0)) return; // Optimization
56 apply_cheat(gb, address, value);
57 }
58
59 bool GB_cheats_enabled(GB_gameboy_t *gb)
60 {
61 return gb->cheat_enabled;
62 }
63
64 void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled)
65 {
66 gb->cheat_enabled = enabled;
67 }
68
69 const GB_cheat_t *GB_add_cheat(GB_gameboy_t *gb, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
70 {
71 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
72
73 GB_cheat_t *cheat = malloc(sizeof(*cheat));
74 cheat->address = address;
75 cheat->bank = bank;
76 cheat->value = value;
77 cheat->old_value = old_value;
78 cheat->use_old_value = use_old_value;
79 cheat->enabled = enabled;
80 strncpy(cheat->description, description, sizeof(cheat->description));
81 cheat->description[sizeof(cheat->description) - 1] = 0;
82 gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(gb->cheats[0]));
83 gb->cheats[gb->cheat_count - 1] = cheat;
84
85 GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
86 if (!*hash) {
87 *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
88 (*hash)->size = 1;
89 (*hash)->cheats[0] = cheat;
90 }
91 else {
92 (*hash)->size++;
93 *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
94 (*hash)->cheats[(*hash)->size - 1] = cheat;
95 }
96
97 return cheat;
98 }
99
100 const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
101 {
102 if (size) {
103 *size = gb->cheat_count;
104 }
105 return (void *)gb->cheats;
106 }
107
108 void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
109 {
110 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
111
112 for (unsigned i = 0; i < gb->cheat_count; i++) {
113 if (gb->cheats[i] == cheat) {
114 gb->cheats[i] = gb->cheats[--gb->cheat_count];
115 if (gb->cheat_count == 0) {
116 free(gb->cheats);
117 gb->cheats = NULL;
118 }
119 else {
120 gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0]));
121 }
122 break;
123 }
124 }
125
126 GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
127 for (unsigned i = 0; i < (*hash)->size; i++) {
128 if ((*hash)->cheats[i] == cheat) {
129 (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
130 if ((*hash)->size == 0) {
131 free(*hash);
132 *hash = NULL;
133 }
134 else {
135 *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
136 }
137 break;
138 }
139 }
140
141 free((void *)cheat);
142 }
143
144 void GB_remove_all_cheats(GB_gameboy_t *gb)
145 {
146 while (gb->cheat_count) {
147 GB_remove_cheat(gb, gb->cheats[0]);
148 }
149 }
150
151 const GB_cheat_t *GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
152 {
153 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
154
155 uint8_t dummy;
156 /* GameShark */
157 if (strlen(cheat) == 8) {
158 #ifdef _WIN32
159 // The hh modifier is not supported on old MSVCRT, it's completely ignored
160 uint32_t bank = 0;
161 uint32_t value = 0;
162 #pragma GCC diagnostic ignored "-Wformat"
163 #else
164 uint8_t bank;
165 uint8_t value;
166 #endif
167 uint16_t address;
168 if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
169 address = __builtin_bswap16(address);
170 return GB_add_cheat(gb, description, address, bank == 1? GB_CHEAT_ANY_BANK : (bank & 0xF), value, 0, false, enabled);
171 }
172 }
173
174 /* Game Genie */
175 {
176 char stripped_cheat[10] = {0,};
177 for (unsigned i = 0; i < 9 && *cheat; i++) {
178 stripped_cheat[i] = *(cheat++);
179 while (*cheat == '-') {
180 cheat++;
181 }
182 }
183
184 // Delete the 7th character;
185 stripped_cheat[7] = stripped_cheat[8];
186 stripped_cheat[8] = 0;
187
188 #ifdef _WIN32
189 uint32_t old_value = 0;
190 uint32_t value = 0;
191 #else
192 uint8_t old_value;
193 uint8_t value;
194 #endif
195 uint16_t address;
196 if (strlen(stripped_cheat) != 8 && strlen(stripped_cheat) != 6) {
197 return NULL;
198 }
199 if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) {
200 address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
201 address ^= 0xF000;
202 if (address > 0x7FFF) {
203 return false;
204 }
205 old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6);
206 old_value ^= 0xBA;
207 return GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled);
208 }
209
210 if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) {
211 address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
212 address ^= 0xF000;
213 if (address > 0x7FFF) {
214 return false;
215 }
216 return GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled);
217 }
218 }
219 return NULL;
220 }
221
222 void GB_update_cheat(GB_gameboy_t *gb, const GB_cheat_t *_cheat, const char *description, uint16_t address, uint16_t bank, uint8_t value, uint8_t old_value, bool use_old_value, bool enabled)
223 {
224 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
225
226 GB_cheat_t *cheat = NULL;
227 for (unsigned i = 0; i < gb->cheat_count; i++) {
228 if (gb->cheats[i] == _cheat) {
229 cheat = gb->cheats[i];
230 break;
231 }
232 }
233
234 assert(cheat);
235 if (!cheat) return;
236
237 if (cheat->address != address) {
238 /* Remove from old bucket */
239 GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
240 for (unsigned i = 0; i < (*hash)->size; i++) {
241 if ((*hash)->cheats[i] == cheat) {
242 (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
243 if ((*hash)->size == 0) {
244 free(*hash);
245 *hash = NULL;
246 }
247 else {
248 *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
249 }
250 break;
251 }
252 }
253 cheat->address = address;
254
255 /* Add to new bucket */
256 hash = &gb->cheat_hash[hash_addr(address)];
257 if (!*hash) {
258 *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
259 (*hash)->size = 1;
260 (*hash)->cheats[0] = cheat;
261 }
262 else {
263 (*hash)->size++;
264 *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
265 (*hash)->cheats[(*hash)->size - 1] = cheat;
266 }
267 }
268 cheat->bank = bank;
269 cheat->value = value;
270 cheat->old_value = old_value;
271 cheat->use_old_value = use_old_value;
272 cheat->enabled = enabled;
273 if (description != cheat->description) {
274 strncpy(cheat->description, description, sizeof(cheat->description));
275 cheat->description[sizeof(cheat->description) - 1] = 0;
276 }
277 }
278
279 #define CHEAT_MAGIC 'SBCh'
280
281 int GB_load_cheats(GB_gameboy_t *gb, const char *path, bool replace_existing)
282 {
283 GB_ASSERT_NOT_RUNNING_OTHER_THREAD(gb)
284
285 FILE *f = fopen(path, "rb");
286 if (!f) {
287 return errno;
288 }
289
290 uint32_t magic = 0;
291 uint32_t struct_size = 0;
292 if (fread(&magic, sizeof(magic), 1, f) != 1) {
293 goto error;
294 }
295 if (fread(&struct_size, sizeof(struct_size), 1, f) != 1) {
296 goto error;
297 }
298 if (magic != LE32(CHEAT_MAGIC) && magic != BE32(CHEAT_MAGIC)) {
299 GB_log(gb, "The file is not a SameBoy cheat database");
300 goto error;
301 }
302
303 if (struct_size != sizeof(GB_cheat_t)) {
304 GB_log(gb, "This cheat database is not compatible with this version of SameBoy");
305 goto error;
306 }
307
308 // Remove all cheats first
309 if (replace_existing) {
310 GB_remove_all_cheats(gb);
311 }
312
313 GB_cheat_t cheat;
314 while (fread(&cheat, sizeof(cheat), 1, f) == 1) {
315 if (magic != CHEAT_MAGIC) {
316 cheat.address = __builtin_bswap16(cheat.address);
317 cheat.bank = __builtin_bswap16(cheat.bank);
318 }
319 cheat.description[sizeof(cheat.description) - 1] = 0;
320 GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled);
321 }
322
323 fclose(f);
324 return 0;
325
326 error:
327 fclose(f);
328 return -1;
329 }
330
331 int GB_save_cheats(GB_gameboy_t *gb, const char *path)
332 {
333 if (!gb->cheat_count) return 0; // Nothing to save.
334 FILE *f = fopen(path, "wb");
335 if (!f) {
336 GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno));
337 return errno;
338 }
339
340 uint32_t magic = CHEAT_MAGIC;
341 uint32_t struct_size = sizeof(GB_cheat_t);
342
343 if (fwrite(&magic, sizeof(magic), 1, f) != 1) {
344 fclose(f);
345 return EIO;
346 }
347
348 if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) {
349 fclose(f);
350 return EIO;
351 }
352
353 for (size_t i = 0; i <gb->cheat_count; i++) {
354 if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) {
355 fclose(f);
356 return EIO;
357 }
358 }
359
360 fclose(f);
361 return 0;
362 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.