| 1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| 2 | * Mupen64plus - cheat.c * |
| 3 | * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * |
| 4 | * Copyright (C) 2009 Richard Goedeken * |
| 5 | * Copyright (C) 2008 Okaygo * |
| 6 | * * |
| 7 | * This program is free software; you can redistribute it and/or modify * |
| 8 | * it under the terms of the GNU General Public License as published by * |
| 9 | * the Free Software Foundation; either version 2 of the License, or * |
| 10 | * (at your option) any later version. * |
| 11 | * * |
| 12 | * This program is distributed in the hope that it will be useful, * |
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
| 15 | * GNU General Public License for more details. * |
| 16 | * * |
| 17 | * You should have received a copy of the GNU General Public License * |
| 18 | * along with this program; if not, write to the * |
| 19 | * Free Software Foundation, Inc., * |
| 20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
| 21 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 22 | |
| 23 | // gameshark and xploder64 reference: http://doc.kodewerx.net/hacking_n64.html |
| 24 | |
| 25 | #include <SDL.h> |
| 26 | #include <SDL_thread.h> |
| 27 | |
| 28 | #include "api/m64p_types.h" |
| 29 | #include "api/callbacks.h" |
| 30 | #include "api/config.h" |
| 31 | |
| 32 | #include "memory/memory.h" |
| 33 | #include "osal/preproc.h" |
| 34 | #include "cheat.h" |
| 35 | #include "main.h" |
| 36 | #include "rom.h" |
| 37 | #include "eventloop.h" |
| 38 | #include "list.h" |
| 39 | |
| 40 | #include <stdio.h> |
| 41 | #include <string.h> |
| 42 | |
| 43 | // local definitions |
| 44 | #define CHEAT_CODE_MAGIC_VALUE 0xDEAD0000 |
| 45 | |
| 46 | typedef struct cheat_code { |
| 47 | unsigned int address; |
| 48 | int value; |
| 49 | int old_value; |
| 50 | struct list_head list; |
| 51 | } cheat_code_t; |
| 52 | |
| 53 | typedef struct cheat { |
| 54 | char *name; |
| 55 | int enabled; |
| 56 | int was_enabled; |
| 57 | struct list_head cheat_codes; |
| 58 | struct list_head list; |
| 59 | } cheat_t; |
| 60 | |
| 61 | // local variables |
| 62 | static LIST_HEAD(active_cheats); |
| 63 | static SDL_mutex *cheat_mutex = NULL; |
| 64 | |
| 65 | // private functions |
| 66 | static unsigned short read_address_16bit(unsigned int address) |
| 67 | { |
| 68 | return *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16))); |
| 69 | } |
| 70 | |
| 71 | static unsigned char read_address_8bit(unsigned int address) |
| 72 | { |
| 73 | return *(unsigned char *)((rdramb + ((address & 0xFFFFFF)^S8))); |
| 74 | } |
| 75 | |
| 76 | static void update_address_16bit(unsigned int address, unsigned short new_value) |
| 77 | { |
| 78 | *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16))) = new_value; |
| 79 | } |
| 80 | |
| 81 | static void update_address_8bit(unsigned int address, unsigned char new_value) |
| 82 | { |
| 83 | *(unsigned char *)((rdramb + ((address & 0xFFFFFF)^S8))) = new_value; |
| 84 | } |
| 85 | |
| 86 | static int address_equal_to_8bit(unsigned int address, unsigned char value) |
| 87 | { |
| 88 | unsigned char value_read; |
| 89 | value_read = *(unsigned char *)((rdramb + ((address & 0xFFFFFF)^S8))); |
| 90 | return value_read == value; |
| 91 | } |
| 92 | |
| 93 | static int address_equal_to_16bit(unsigned int address, unsigned short value) |
| 94 | { |
| 95 | unsigned short value_read; |
| 96 | value_read = *(unsigned short *)((rdramb + ((address & 0xFFFFFF)^S16))); |
| 97 | return value_read == value; |
| 98 | } |
| 99 | |
| 100 | // individual application - returns 0 if we are supposed to skip the next cheat |
| 101 | // (only really used on conditional codes) |
| 102 | static int execute_cheat(unsigned int address, unsigned short value, int *old_value) |
| 103 | { |
| 104 | switch (address & 0xFF000000) |
| 105 | { |
| 106 | case 0x80000000: |
| 107 | case 0x88000000: |
| 108 | case 0xA0000000: |
| 109 | case 0xA8000000: |
| 110 | case 0xF0000000: |
| 111 | // if pointer to old value is valid and uninitialized, write current value to it |
| 112 | if(old_value && (*old_value == CHEAT_CODE_MAGIC_VALUE)) |
| 113 | *old_value = (int) read_address_8bit(address); |
| 114 | update_address_8bit(address,(unsigned char) value); |
| 115 | return 1; |
| 116 | case 0x81000000: |
| 117 | case 0x89000000: |
| 118 | case 0xA1000000: |
| 119 | case 0xA9000000: |
| 120 | case 0xF1000000: |
| 121 | // if pointer to old value is valid and uninitialized, write current value to it |
| 122 | if(old_value && (*old_value == CHEAT_CODE_MAGIC_VALUE)) |
| 123 | *old_value = (int) read_address_16bit(address); |
| 124 | update_address_16bit(address,value); |
| 125 | return 1; |
| 126 | case 0xD0000000: |
| 127 | case 0xD8000000: |
| 128 | return address_equal_to_8bit(address,(unsigned char) value); |
| 129 | case 0xD1000000: |
| 130 | case 0xD9000000: |
| 131 | return address_equal_to_16bit(address,value); |
| 132 | case 0xD2000000: |
| 133 | case 0xDB000000: |
| 134 | return !(address_equal_to_8bit(address,(unsigned char) value)); |
| 135 | case 0xD3000000: |
| 136 | case 0xDA000000: |
| 137 | return !(address_equal_to_16bit(address,value)); |
| 138 | case 0xEE000000: |
| 139 | // most likely, this doesnt do anything. |
| 140 | execute_cheat(0xF1000318, 0x0040, NULL); |
| 141 | execute_cheat(0xF100031A, 0x0000, NULL); |
| 142 | return 1; |
| 143 | default: |
| 144 | return 1; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | static cheat_t *find_or_create_cheat(const char *name) |
| 149 | { |
| 150 | cheat_t *cheat; |
| 151 | int found = 0; |
| 152 | |
| 153 | list_for_each_entry(cheat, &active_cheats, cheat_t, list) { |
| 154 | if (strcmp(cheat->name, name) == 0) { |
| 155 | found = 1; |
| 156 | break; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | if (found) |
| 161 | { |
| 162 | /* delete any pre-existing cheat codes */ |
| 163 | cheat_code_t *code, *safe; |
| 164 | |
| 165 | list_for_each_entry_safe(code, safe, &cheat->cheat_codes, cheat_code_t, list) { |
| 166 | list_del(&code->list); |
| 167 | free(code); |
| 168 | } |
| 169 | |
| 170 | cheat->enabled = 0; |
| 171 | cheat->was_enabled = 0; |
| 172 | } |
| 173 | else |
| 174 | { |
| 175 | cheat = malloc(sizeof(*cheat)); |
| 176 | cheat->name = strdup(name); |
| 177 | cheat->enabled = 0; |
| 178 | cheat->was_enabled = 0; |
| 179 | INIT_LIST_HEAD(&cheat->cheat_codes); |
| 180 | list_add_tail(&cheat->list, &active_cheats); |
| 181 | } |
| 182 | |
| 183 | return cheat; |
| 184 | } |
| 185 | |
| 186 | |
| 187 | // public functions |
| 188 | void cheat_init(void) |
| 189 | { |
| 190 | cheat_mutex = SDL_CreateMutex(); |
| 191 | } |
| 192 | |
| 193 | void cheat_uninit(void) |
| 194 | { |
| 195 | if (cheat_mutex != NULL) |
| 196 | SDL_DestroyMutex(cheat_mutex); |
| 197 | cheat_mutex = NULL; |
| 198 | } |
| 199 | |
| 200 | void cheat_apply_cheats(int entry) |
| 201 | { |
| 202 | cheat_t *cheat; |
| 203 | cheat_code_t *code; |
| 204 | int skip; |
| 205 | int execute_next; |
| 206 | |
| 207 | // If game is Zelda OOT, apply subscreen delay fix |
| 208 | if (entry == ENTRY_VI && strncmp((char *)ROM_HEADER.Name, "THE LEGEND OF ZELDA", 19) == 0) { |
| 209 | uint32_t subscreen_address = 0; |
| 210 | uint32_t credits_address[4]; |
| 211 | credits_address[0] = 0; |
| 212 | if (sl(ROM_HEADER.CRC1) == 0xEC7011B7 && sl(ROM_HEADER.CRC2) == 0x7616D72B) { |
| 213 | // Legend of Zelda, The - Ocarina of Time (U) + (J) (V1.0) |
| 214 | subscreen_address = 0x801DA5CB; |
| 215 | } else if (sl(ROM_HEADER.CRC1) == 0xD43DA81F && sl(ROM_HEADER.CRC2) == 0x021E1E19) { |
| 216 | // Legend of Zelda, The - Ocarina of Time (U) + (J) (V1.1) |
| 217 | subscreen_address = 0x801DA78B; |
| 218 | } else if (sl(ROM_HEADER.CRC1) == 0x693BA2AE && sl(ROM_HEADER.CRC2) == 0xB7F14E9F) { |
| 219 | // Legend of Zelda, The - Ocarina of Time (U) + (J) (V1.2) |
| 220 | subscreen_address = 0x801DAE8B; |
| 221 | } else if (sl(ROM_HEADER.CRC1) == 0xB044B569 && sl(ROM_HEADER.CRC2) == 0x373C1985) { |
| 222 | // Legend of Zelda, The - Ocarina of Time (E) (V1.0) |
| 223 | subscreen_address = 0x801D860B; |
| 224 | } else if (sl(ROM_HEADER.CRC1) == 0xB2055FBD && sl(ROM_HEADER.CRC2) == 0x0BAB4E0C) { |
| 225 | // Legend of Zelda, The - Ocarina of Time (E) (V1.1) |
| 226 | subscreen_address = 0x801D864B; |
| 227 | // GC Versions such as Master Quest also require the End Credits Fix. |
| 228 | } else if (sl(ROM_HEADER.CRC1) == 0x1D4136F3 && sl(ROM_HEADER.CRC2) == 0xAF63EEA9) { |
| 229 | // Legend of Zelda, The - Ocarina of Time - Master Quest (E) (GC Version) |
| 230 | subscreen_address = 0x801D8F4B; |
| 231 | credits_address[0] = 0xD109A8C4; |
| 232 | credits_address[1] = 0x8109A8C4; |
| 233 | credits_address[2] = 0xD109A8C6; |
| 234 | credits_address[3] = 0x8109A8C6; |
| 235 | } else if (sl(ROM_HEADER.CRC1) == 0x09465AC3 && sl(ROM_HEADER.CRC2) == 0xF8CB501B) { |
| 236 | // Legend of Zelda, The - Ocarina of Time (E) (GC Version) |
| 237 | subscreen_address = 0x801D8F8B; |
| 238 | credits_address[0] = 0xD109A8E4; |
| 239 | credits_address[1] = 0x8109A8E4; |
| 240 | credits_address[2] = 0xD109A8E6; |
| 241 | credits_address[3] = 0x8109A8E6; |
| 242 | } else if (sl(ROM_HEADER.CRC1) == 0xF3DD35BA && sl(ROM_HEADER.CRC2) == 0x4152E075) { |
| 243 | // Legend of Zelda, The - Ocarina of Time (U) (GC Version) |
| 244 | subscreen_address = 0x801DB78B; |
| 245 | credits_address[0] = 0xD109A814; |
| 246 | credits_address[1] = 0x8109A814; |
| 247 | credits_address[2] = 0xD109A816; |
| 248 | credits_address[3] = 0x8109A816; |
| 249 | } else if (sl(ROM_HEADER.CRC1) == 0xF034001A && sl(ROM_HEADER.CRC2) == 0xAE47ED06) { |
| 250 | // Legend of Zelda, The - Ocarina of Time - Master Quest (U) (GC Version) |
| 251 | subscreen_address = 0x801DB74B; |
| 252 | credits_address[0] = 0xD109A7F4; |
| 253 | credits_address[1] = 0x8109A7F4; |
| 254 | credits_address[2] = 0xD109A7F6; |
| 255 | credits_address[3] = 0x8109A7F6; |
| 256 | } else if (sl(ROM_HEADER.CRC1) == 0xF7F52DB8 && sl(ROM_HEADER.CRC2) == 0x2195E636) { |
| 257 | // Zelda no Densetsu - Toki no Ocarina - Zelda Collection Version (J) (GC Version) |
| 258 | subscreen_address = 0x801DB78B; |
| 259 | credits_address[0] = 0xD109A814; |
| 260 | credits_address[1] = 0x8109A814; |
| 261 | credits_address[2] = 0xD109A816; |
| 262 | credits_address[3] = 0x8109A816; |
| 263 | } else if (sl(ROM_HEADER.CRC1) == 0xF611F4BA && sl(ROM_HEADER.CRC2) == 0xC584135C) { |
| 264 | // Zelda no Densetsu - Toki no Ocarina GC (J) (GC Version) |
| 265 | subscreen_address = 0x801DB78B; |
| 266 | credits_address[0] = 0xD109A834; |
| 267 | credits_address[1] = 0x8109A834; |
| 268 | credits_address[2] = 0xD109A836; |
| 269 | credits_address[3] = 0x8109A836; |
| 270 | } else if (sl(ROM_HEADER.CRC1) == 0xF43B45BA && sl(ROM_HEADER.CRC2) == 0x2F0E9B6F) { |
| 271 | // Zelda no Densetsu - Toki no Ocarina GC Ura (J) (GC Version) |
| 272 | subscreen_address = 0x801DB78B; |
| 273 | credits_address[0] = 0xD109A814; |
| 274 | credits_address[1] = 0x8109A814; |
| 275 | credits_address[2] = 0xD109A816; |
| 276 | credits_address[3] = 0x8109A816; |
| 277 | } else { |
| 278 | // UNKNOWN VERSION |
| 279 | DebugMessage(M64MSG_WARNING, "Warning: Ocarina of Time version could not be determined. No fixes applied."); |
| 280 | } |
| 281 | if (subscreen_address) { |
| 282 | execute_cheat(subscreen_address, 0x0002, NULL); |
| 283 | if (credits_address[0]){ |
| 284 | if (execute_cheat(credits_address[0], 0x0320, NULL)) |
| 285 | execute_cheat(credits_address[1], 0x0000, NULL); |
| 286 | if (execute_cheat(credits_address[2], 0xF809, NULL)) |
| 287 | execute_cheat(credits_address[3], 0x0000, NULL); |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | if (list_empty(&active_cheats)) |
| 293 | return; |
| 294 | |
| 295 | if (cheat_mutex == NULL || SDL_LockMutex(cheat_mutex) != 0) |
| 296 | { |
| 297 | DebugMessage(M64MSG_ERROR, "Internal error: failed to lock mutex in cheat_apply_cheats()"); |
| 298 | return; |
| 299 | } |
| 300 | |
| 301 | list_for_each_entry(cheat, &active_cheats, cheat_t, list) { |
| 302 | if (cheat->enabled) |
| 303 | { |
| 304 | cheat->was_enabled = 1; |
| 305 | switch(entry) |
| 306 | { |
| 307 | case ENTRY_BOOT: |
| 308 | list_for_each_entry(code, &cheat->cheat_codes, cheat_code_t, list) { |
| 309 | // code should only be written once at boot time |
| 310 | if((code->address & 0xF0000000) == 0xF0000000) |
| 311 | execute_cheat(code->address, code->value, &code->old_value); |
| 312 | } |
| 313 | break; |
| 314 | case ENTRY_VI: |
| 315 | skip = 0; |
| 316 | execute_next = 0; |
| 317 | list_for_each_entry(code, &cheat->cheat_codes, cheat_code_t, list) { |
| 318 | if (skip) { |
| 319 | skip = 0; |
| 320 | continue; |
| 321 | } |
| 322 | if (execute_next) { |
| 323 | execute_next = 0; |
| 324 | |
| 325 | // if code needs GS button pressed, don't save old value |
| 326 | if(((code->address & 0xFF000000) == 0xD8000000 || |
| 327 | (code->address & 0xFF000000) == 0xD9000000 || |
| 328 | (code->address & 0xFF000000) == 0xDA000000 || |
| 329 | (code->address & 0xFF000000) == 0xDB000000)) |
| 330 | execute_cheat(code->address, code->value, NULL); |
| 331 | else |
| 332 | execute_cheat(code->address, code->value, &code->old_value); |
| 333 | |
| 334 | continue; |
| 335 | } |
| 336 | // conditional cheat codes |
| 337 | if((code->address & 0xF0000000) == 0xD0000000) |
| 338 | { |
| 339 | // if code needs GS button pressed and it's not, skip it |
| 340 | if(((code->address & 0xFF000000) == 0xD8000000 || |
| 341 | (code->address & 0xFF000000) == 0xD9000000 || |
| 342 | (code->address & 0xFF000000) == 0xDA000000 || |
| 343 | (code->address & 0xFF000000) == 0xDB000000) && |
| 344 | !event_gameshark_active()) |
| 345 | { |
| 346 | // skip next code |
| 347 | skip = 1; |
| 348 | continue; |
| 349 | } |
| 350 | |
| 351 | if (execute_cheat(code->address, code->value, NULL)) { |
| 352 | // if condition true, execute next cheat code |
| 353 | execute_next = 1; |
| 354 | } else { |
| 355 | // if condition false, skip next code |
| 356 | skip = 1; |
| 357 | continue; |
| 358 | } |
| 359 | } |
| 360 | // GS button triggers cheat code |
| 361 | else if((code->address & 0xFF000000) == 0x88000000 || |
| 362 | (code->address & 0xFF000000) == 0x89000000 || |
| 363 | (code->address & 0xFF000000) == 0xA8000000 || |
| 364 | (code->address & 0xFF000000) == 0xA9000000) |
| 365 | { |
| 366 | if(event_gameshark_active()) |
| 367 | execute_cheat(code->address, code->value, NULL); |
| 368 | } |
| 369 | // normal cheat code |
| 370 | else |
| 371 | { |
| 372 | // exclude boot-time cheat codes |
| 373 | if((code->address & 0xF0000000) != 0xF0000000) |
| 374 | execute_cheat(code->address, code->value, &code->old_value); |
| 375 | } |
| 376 | } |
| 377 | break; |
| 378 | default: |
| 379 | break; |
| 380 | } |
| 381 | } |
| 382 | // if cheat was enabled, but is now disabled, restore old memory values |
| 383 | else if (cheat->was_enabled) |
| 384 | { |
| 385 | cheat->was_enabled = 0; |
| 386 | switch(entry) |
| 387 | { |
| 388 | case ENTRY_VI: |
| 389 | list_for_each_entry(code, &cheat->cheat_codes, cheat_code_t, list) { |
| 390 | // set memory back to old value and clear saved copy of old value |
| 391 | if(code->old_value != CHEAT_CODE_MAGIC_VALUE) |
| 392 | { |
| 393 | execute_cheat(code->address, code->old_value, NULL); |
| 394 | code->old_value = CHEAT_CODE_MAGIC_VALUE; |
| 395 | } |
| 396 | } |
| 397 | break; |
| 398 | default: |
| 399 | break; |
| 400 | } |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | SDL_UnlockMutex(cheat_mutex); |
| 405 | } |
| 406 | |
| 407 | |
| 408 | void cheat_delete_all(void) |
| 409 | { |
| 410 | cheat_t *cheat, *safe_cheat; |
| 411 | cheat_code_t *code, *safe_code; |
| 412 | |
| 413 | if (list_empty(&active_cheats)) |
| 414 | return; |
| 415 | |
| 416 | if (cheat_mutex == NULL || SDL_LockMutex(cheat_mutex) != 0) |
| 417 | { |
| 418 | DebugMessage(M64MSG_ERROR, "Internal error: failed to lock mutex in cheat_delete_all()"); |
| 419 | return; |
| 420 | } |
| 421 | |
| 422 | list_for_each_entry_safe(cheat, safe_cheat, &active_cheats, cheat_t, list) { |
| 423 | free(cheat->name); |
| 424 | |
| 425 | list_for_each_entry_safe(code, safe_code, &cheat->cheat_codes, cheat_code_t, list) { |
| 426 | list_del(&code->list); |
| 427 | free(code); |
| 428 | } |
| 429 | list_del(&cheat->list); |
| 430 | free(cheat); |
| 431 | } |
| 432 | |
| 433 | SDL_UnlockMutex(cheat_mutex); |
| 434 | } |
| 435 | |
| 436 | int cheat_set_enabled(const char *name, int enabled) |
| 437 | { |
| 438 | cheat_t *cheat = NULL; |
| 439 | |
| 440 | if (list_empty(&active_cheats)) |
| 441 | return 0; |
| 442 | |
| 443 | if (cheat_mutex == NULL || SDL_LockMutex(cheat_mutex) != 0) |
| 444 | { |
| 445 | DebugMessage(M64MSG_ERROR, "Internal error: failed to lock mutex in cheat_set_enabled()"); |
| 446 | return 0; |
| 447 | } |
| 448 | |
| 449 | list_for_each_entry(cheat, &active_cheats, cheat_t, list) { |
| 450 | if (strcmp(name, cheat->name) == 0) |
| 451 | { |
| 452 | cheat->enabled = enabled; |
| 453 | SDL_UnlockMutex(cheat_mutex); |
| 454 | return 1; |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | SDL_UnlockMutex(cheat_mutex); |
| 459 | return 0; |
| 460 | } |
| 461 | |
| 462 | int cheat_add_new(const char *name, m64p_cheat_code *code_list, int num_codes) |
| 463 | { |
| 464 | cheat_t *cheat; |
| 465 | int i, j; |
| 466 | |
| 467 | if (cheat_mutex == NULL || SDL_LockMutex(cheat_mutex) != 0) |
| 468 | { |
| 469 | DebugMessage(M64MSG_ERROR, "Internal error: failed to lock mutex in cheat_add_new()"); |
| 470 | return 0; |
| 471 | } |
| 472 | |
| 473 | /* create a new cheat function or erase the codes in an existing cheat function */ |
| 474 | cheat = find_or_create_cheat(name); |
| 475 | if (cheat == NULL) |
| 476 | { |
| 477 | SDL_UnlockMutex(cheat_mutex); |
| 478 | return 0; |
| 479 | } |
| 480 | |
| 481 | cheat->enabled = 1; /* default for new cheats is enabled */ |
| 482 | |
| 483 | for (i = 0; i < num_codes; i++) |
| 484 | { |
| 485 | /* if this is a 'patch' code, convert it and dump out all of the individual codes */ |
| 486 | if ((code_list[i].address & 0xFFFF0000) == 0x50000000 && i < num_codes - 1) |
| 487 | { |
| 488 | int code_count = ((code_list[i].address & 0xFF00) >> 8); |
| 489 | int incr_addr = code_list[i].address & 0xFF; |
| 490 | int incr_value = code_list[i].value; |
| 491 | int cur_addr = code_list[i+1].address; |
| 492 | int cur_value = code_list[i+1].value; |
| 493 | i += 1; |
| 494 | for (j = 0; j < code_count; j++) |
| 495 | { |
| 496 | cheat_code_t *code = malloc(sizeof(*code)); |
| 497 | code->address = cur_addr; |
| 498 | code->value = cur_value; |
| 499 | code->old_value = CHEAT_CODE_MAGIC_VALUE; |
| 500 | list_add_tail(&code->list, &cheat->cheat_codes); |
| 501 | cur_addr += incr_addr; |
| 502 | cur_value += incr_value; |
| 503 | } |
| 504 | } |
| 505 | else |
| 506 | { /* just a normal code */ |
| 507 | cheat_code_t *code = malloc(sizeof(*code)); |
| 508 | code->address = code_list[i].address; |
| 509 | code->value = code_list[i].value; |
| 510 | code->old_value = CHEAT_CODE_MAGIC_VALUE; |
| 511 | list_add_tail(&code->list, &cheat->cheat_codes); |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | SDL_UnlockMutex(cheat_mutex); |
| 516 | return 1; |
| 517 | } |
| 518 | |
| 519 | |