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