1 #include "gb.h"
2 #include "cheats.h"
3 #include <stdio.h>
4 #include <assert.h>
5 #include <errno.h>
6 
hash_addr(uint16_t addr)7 static inline uint8_t hash_addr(uint16_t addr)
8 {
9     return addr;
10 }
11 
bank_for_addr(GB_gameboy_t * gb,uint16_t addr)12 static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
13 {
14     if (addr < 0x4000) {
15         return gb->mbc_rom0_bank;
16     }
17 
18     if (addr < 0x8000) {
19         return gb->mbc_rom_bank;
20     }
21 
22     if (addr < 0xD000) {
23         return 0;
24     }
25 
26     if (addr < 0xE000) {
27         return gb->cgb_ram_bank;
28     }
29 
30     return 0;
31 }
32 
GB_apply_cheat(GB_gameboy_t * gb,uint16_t address,uint8_t * value)33 void GB_apply_cheat(GB_gameboy_t *gb, uint16_t address, uint8_t *value)
34 {
35     if (!gb->cheat_enabled) return;
36     if (!gb->boot_rom_finished) return;
37     const GB_cheat_hash_t *hash = gb->cheat_hash[hash_addr(address)];
38     if (hash) {
39         for (unsigned i = 0; i < hash->size; i++) {
40             GB_cheat_t *cheat = hash->cheats[i];
41             if (cheat->address == address && cheat->enabled && (!cheat->use_old_value || cheat->old_value == *value)) {
42                 if (cheat->bank == GB_CHEAT_ANY_BANK || cheat->bank == bank_for_addr(gb, address)) {
43                     *value = cheat->value;
44                     break;
45                 }
46             }
47         }
48     }
49 }
50 
GB_cheats_enabled(GB_gameboy_t * gb)51 bool GB_cheats_enabled(GB_gameboy_t *gb)
52 {
53     return gb->cheat_enabled;
54 }
55 
GB_set_cheats_enabled(GB_gameboy_t * gb,bool enabled)56 void GB_set_cheats_enabled(GB_gameboy_t *gb, bool enabled)
57 {
58     gb->cheat_enabled = enabled;
59 }
60 
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)61 void 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)
62 {
63     GB_cheat_t *cheat = malloc(sizeof(*cheat));
64     cheat->address = address;
65     cheat->bank = bank;
66     cheat->value = value;
67     cheat->old_value = old_value;
68     cheat->use_old_value = use_old_value;
69     cheat->enabled = enabled;
70     strncpy(cheat->description, description, sizeof(cheat->description));
71     cheat->description[sizeof(cheat->description) - 1] = 0;
72     gb->cheats = realloc(gb->cheats, (++gb->cheat_count) * sizeof(gb->cheats[0]));
73     gb->cheats[gb->cheat_count - 1] = cheat;
74 
75     GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(address)];
76     if (!*hash) {
77         *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
78         (*hash)->size = 1;
79         (*hash)->cheats[0] = cheat;
80     }
81     else {
82         (*hash)->size++;
83         *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
84         (*hash)->cheats[(*hash)->size - 1] = cheat;
85     }
86 }
87 
GB_get_cheats(GB_gameboy_t * gb,size_t * size)88 const GB_cheat_t *const *GB_get_cheats(GB_gameboy_t *gb, size_t *size)
89 {
90     *size = gb->cheat_count;
91     return (void *)gb->cheats;
92 }
GB_remove_cheat(GB_gameboy_t * gb,const GB_cheat_t * cheat)93 void GB_remove_cheat(GB_gameboy_t *gb, const GB_cheat_t *cheat)
94 {
95     for (unsigned i = 0; i < gb->cheat_count; i++) {
96         if (gb->cheats[i] == cheat) {
97             gb->cheats[i] = gb->cheats[--gb->cheat_count];
98             if (gb->cheat_count == 0) {
99                 free(gb->cheats);
100                 gb->cheats = NULL;
101             }
102             else {
103                 gb->cheats = realloc(gb->cheats, gb->cheat_count * sizeof(gb->cheats[0]));
104             }
105             break;
106         }
107     }
108 
109     GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
110     for (unsigned i = 0; i < (*hash)->size; i++) {
111         if ((*hash)->cheats[i] == cheat) {
112             (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
113             if ((*hash)->size == 0) {
114                 free(*hash);
115                 *hash = NULL;
116             }
117             else {
118                 *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
119             }
120             break;
121         }
122     }
123 
124     free((void *)cheat);
125 }
126 
GB_import_cheat(GB_gameboy_t * gb,const char * cheat,const char * description,bool enabled)127 bool GB_import_cheat(GB_gameboy_t *gb, const char *cheat, const char *description, bool enabled)
128 {
129     uint8_t dummy;
130     /* GameShark */
131     {
132         uint8_t bank;
133         uint8_t value;
134         uint16_t address;
135         if (sscanf(cheat, "%02hhx%02hhx%04hx%c", &bank, &value, &address, &dummy) == 3) {
136             if (bank >= 0x80) {
137                 bank &= 0xF;
138             }
139             GB_add_cheat(gb, description, address, bank, value, 0, false, enabled);
140             return true;
141         }
142     }
143 
144     /* GameGenie */
145     {
146         char stripped_cheat[10] = {0,};
147         for (unsigned i = 0; i < 9 && *cheat; i++) {
148             stripped_cheat[i] = *(cheat++);
149             while (*cheat == '-') {
150                 cheat++;
151             }
152         }
153 
154         // Delete the 7th character;
155         stripped_cheat[7] = stripped_cheat[8];
156         stripped_cheat[8] = 0;
157 
158         uint8_t old_value;
159         uint8_t value;
160         uint16_t address;
161         if (sscanf(stripped_cheat, "%02hhx%04hx%02hhx%c", &value, &address, &old_value, &dummy) == 3) {
162             address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
163             address ^= 0xF000;
164             if (address > 0x7FFF) {
165                 return false;
166             }
167             old_value = (uint8_t)(old_value >> 2) | (uint8_t)(old_value << 6);
168             old_value ^= 0xBA;
169             GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, old_value, true, enabled);
170             return true;
171         }
172 
173         if (sscanf(stripped_cheat, "%02hhx%04hx%c", &value, &address, &dummy) == 2) {
174             address = (uint16_t)(address >> 4) | (uint16_t)(address << 12);
175             address ^= 0xF000;
176             if (address > 0x7FFF) {
177                 return false;
178             }
179             GB_add_cheat(gb, description, address, GB_CHEAT_ANY_BANK, value, false, true, enabled);
180             return true;
181         }
182     }
183     return false;
184 }
185 
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)186 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)
187 {
188     GB_cheat_t *cheat = NULL;
189     for (unsigned i = 0; i < gb->cheat_count; i++) {
190         if (gb->cheats[i] == _cheat) {
191             cheat = gb->cheats[i];
192             break;
193         }
194     }
195 
196     assert(cheat);
197 
198     if (cheat->address != address) {
199         /* Remove from old bucket */
200         GB_cheat_hash_t **hash = &gb->cheat_hash[hash_addr(cheat->address)];
201         for (unsigned i = 0; i < (*hash)->size; i++) {
202             if ((*hash)->cheats[i] == cheat) {
203                 (*hash)->cheats[i] = (*hash)->cheats[--(*hash)->size];
204                 if ((*hash)->size == 0) {
205                     free(*hash);
206                     *hash = NULL;
207                 }
208                 else {
209                     *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
210                 }
211                 break;
212             }
213         }
214         cheat->address = address;
215 
216         /* Add to new bucket */
217         hash = &gb->cheat_hash[hash_addr(address)];
218         if (!*hash) {
219             *hash = malloc(sizeof(GB_cheat_hash_t) + sizeof(cheat));
220             (*hash)->size = 1;
221             (*hash)->cheats[0] = cheat;
222         }
223         else {
224             (*hash)->size++;
225             *hash = realloc(*hash, sizeof(GB_cheat_hash_t) + sizeof(cheat) * (*hash)->size);
226             (*hash)->cheats[(*hash)->size - 1] = cheat;
227         }
228     }
229     cheat->bank = bank;
230     cheat->value = value;
231     cheat->old_value = old_value;
232     cheat->use_old_value = use_old_value;
233     cheat->enabled = enabled;
234     if (description != cheat->description) {
235         strncpy(cheat->description, description, sizeof(cheat->description));
236         cheat->description[sizeof(cheat->description) - 1] = 0;
237     }
238 }
239 
240 #define CHEAT_MAGIC 'SBCh'
241 
GB_load_cheats(GB_gameboy_t * gb,const char * path)242 void GB_load_cheats(GB_gameboy_t *gb, const char *path)
243 {
244     FILE *f = fopen(path, "rb");
245     if (!f) {
246         return;
247     }
248 
249     uint32_t magic = 0;
250     uint32_t struct_size = 0;
251     fread(&magic, sizeof(magic), 1, f);
252     fread(&struct_size, sizeof(struct_size), 1, f);
253     if (magic != CHEAT_MAGIC && magic != __builtin_bswap32(CHEAT_MAGIC)) {
254         GB_log(gb, "The file is not a SameBoy cheat database");
255         return;
256     }
257 
258     if (struct_size != sizeof(GB_cheat_t)) {
259         GB_log(gb, "This cheat database is not compatible with this version of SameBoy");
260         return;
261     }
262 
263     // Remove all cheats first
264     while (gb->cheats) {
265         GB_remove_cheat(gb, gb->cheats[0]);
266     }
267 
268     GB_cheat_t cheat;
269     while (fread(&cheat, sizeof(cheat), 1, f)) {
270         if (magic == __builtin_bswap32(CHEAT_MAGIC)) {
271             cheat.address = __builtin_bswap16(cheat.address);
272             cheat.bank = __builtin_bswap16(cheat.bank);
273         }
274         cheat.description[sizeof(cheat.description) - 1] = 0;
275         GB_add_cheat(gb, cheat.description, cheat.address, cheat.bank, cheat.value, cheat.old_value, cheat.use_old_value, cheat.enabled);
276     }
277 
278     return;
279 }
280 
GB_save_cheats(GB_gameboy_t * gb,const char * path)281 int GB_save_cheats(GB_gameboy_t *gb, const char *path)
282 {
283     if (!gb->cheat_count) return 0; // Nothing to save.
284     FILE *f = fopen(path, "wb");
285     if (!f) {
286         GB_log(gb, "Could not dump cheat database: %s.\n", strerror(errno));
287         return errno;
288     }
289 
290     uint32_t magic = CHEAT_MAGIC;
291     uint32_t struct_size = sizeof(GB_cheat_t);
292 
293     if (fwrite(&magic, sizeof(magic), 1, f) != 1) {
294         fclose(f);
295         return EIO;
296     }
297 
298     if (fwrite(&struct_size, sizeof(struct_size), 1, f) != 1) {
299         fclose(f);
300         return EIO;
301     }
302 
303     for (size_t i = 0; i <gb->cheat_count; i++) {
304         if (fwrite(gb->cheats[i], sizeof(*gb->cheats[i]), 1, f) != 1) {
305             fclose(f);
306             return EIO;
307         }
308     }
309 
310     errno = 0;
311     fclose(f);
312     return errno;
313 }
314