5288f542 |
1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
2 | * Mupen64plus - cheat.c * |
3 | * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ * |
4 | * Copyright (C) 2009-2010 Richard Goedeken * |
5 | * Copyright (C) 2010 Rhett Osborne (spinout) * |
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 | #include <stdlib.h> |
24 | #include <string.h> |
25 | #include <stdio.h> |
26 | |
27 | #include "m64p_types.h" |
28 | |
29 | #include "main.h" |
30 | #include "cheat.h" |
31 | #include "core_interface.h" |
32 | |
33 | /* local definitions */ |
34 | #define CHEAT_FILE "mupencheat.txt" |
35 | |
36 | typedef struct { |
37 | int address; |
38 | int *variables; |
39 | char **variable_names; |
40 | int var_to_use; |
41 | int var_count; |
42 | } cheat_code; |
43 | |
44 | typedef struct _sCheatInfo { |
45 | int Number; |
46 | int Count; |
47 | int VariableLine; |
48 | const char *Name; |
49 | const char *Description; |
50 | cheat_code *Codes; |
51 | struct _sCheatInfo *Next; |
52 | } sCheatInfo; |
53 | |
54 | /* local variables */ |
55 | static m64p_rom_header l_RomHeader; |
56 | static char *l_IniText = NULL; |
57 | static char *l_CheatGameName = NULL; |
58 | static sCheatInfo *l_CheatList = NULL; |
59 | static int l_CheatCodesFound = 0; |
60 | static int l_RomFound = 0; |
61 | |
62 | /********************************************************************************************************* |
63 | * Static (Local) functions |
64 | */ |
65 | |
66 | static int isSpace(char ch) |
67 | { |
68 | return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); |
69 | } |
70 | |
71 | /* Find cheat code */ |
72 | static sCheatInfo *CheatFindCode(int Number) |
73 | { |
74 | sCheatInfo *pCur = l_CheatList; |
75 | while (pCur != NULL) |
76 | { |
77 | if (pCur->Number == Number) break; |
78 | pCur = pCur->Next; |
79 | } |
80 | return pCur; |
81 | } |
82 | |
83 | |
84 | /* Activate a code */ |
85 | static void CheatActivate(sCheatInfo *pCheat) |
86 | { |
87 | int i; |
88 | |
89 | /* Get a m64p_cheat_code object */ |
90 | m64p_cheat_code * code = (m64p_cheat_code*) calloc(pCheat->Count, sizeof(m64p_cheat_code)); |
91 | if (code == NULL) |
92 | { |
93 | DebugMessage(M64MSG_WARNING, "could not allocate memory for code '%s'", pCheat->Name); |
94 | return; |
95 | } |
96 | /* Fill in members */ |
97 | for (i = 0; i < pCheat->Count; i++) |
98 | { |
99 | code[i].address = pCheat->Codes[i].address; |
100 | code[i].value = pCheat->Codes[i].variables[pCheat->Codes[i].var_to_use]; |
101 | } |
102 | /* Enable cheat */ |
103 | if (CoreAddCheat(pCheat->Name, code, pCheat->Count) != M64ERR_SUCCESS) |
104 | { |
105 | DebugMessage(M64MSG_WARNING, "CoreAddCheat() failed for cheat code %i (%s)", pCheat->Number, pCheat->Name); |
106 | free(code); |
107 | return; |
108 | } |
109 | |
110 | free(code); |
111 | DebugMessage(M64MSG_STATUS, "activated cheat code %i: %s", pCheat->Number, pCheat->Name); |
112 | } |
113 | |
114 | static void CheatFreeAll(void) |
115 | { |
116 | if (l_IniText != NULL) |
117 | free(l_IniText); |
118 | l_IniText = NULL; |
119 | |
120 | sCheatInfo *pCur = l_CheatList; |
121 | while (pCur != NULL) |
122 | { |
123 | sCheatInfo *pNext = pCur->Next; |
124 | if (pCur->Codes != NULL) |
125 | { |
126 | int i; |
127 | for (i=0; i < pCur->Count; i++) |
128 | { |
129 | if (pCur->Codes[i].variables != NULL) |
130 | free(pCur->Codes[i].variables); |
131 | if (pCur->Codes[i].variable_names != NULL) |
132 | free(pCur->Codes[i].variable_names); |
133 | } |
134 | free(pCur->Codes); |
135 | } |
136 | free(pCur); |
137 | pCur = pNext; |
138 | } |
139 | |
140 | l_CheatList = NULL; |
141 | } |
142 | |
143 | /* Append new code */ |
144 | static sCheatInfo * NewCode(char *CheatName, int CheatNum) |
145 | { |
146 | /* allocate memory for a new sCheatInfo struct */ |
147 | sCheatInfo *pNew = (sCheatInfo *) malloc(sizeof(sCheatInfo)); |
148 | if (pNew == NULL) return NULL; |
149 | |
150 | /* fill in the data members */ |
151 | pNew->Number = CheatNum; |
152 | pNew->Count = 0; |
153 | pNew->VariableLine = -1; |
154 | pNew->Name = CheatName; |
155 | pNew->Description = NULL; |
156 | pNew->Codes = NULL; |
157 | pNew->Next = NULL; |
158 | |
159 | l_CheatCodesFound++; |
160 | |
161 | /* stick it at the end of the list */ |
162 | if (l_CheatList == NULL) |
163 | { |
164 | l_CheatList = pNew; |
165 | return pNew; |
166 | } |
167 | sCheatInfo *pLast = l_CheatList; |
168 | while (pLast->Next != NULL) pLast = pLast->Next; |
169 | pLast->Next = pNew; |
170 | return pNew; |
171 | } |
172 | |
173 | static void CheatAddVariables(cheat_code * Code, char *varlist) |
174 | { |
175 | /* needs to be more verbose? */ |
176 | Code->variables = NULL; |
177 | Code->variable_names = NULL; |
178 | Code->var_count = 0; |
179 | while (*varlist != 0) |
180 | { |
181 | if ((Code->variables = (int*) realloc(Code->variables, sizeof(int) * (Code->var_count + 1))) == NULL) |
182 | return; |
183 | if ((Code->variable_names = (char**) realloc(Code->variable_names, sizeof(char*) * (Code->var_count + 1))) == NULL) |
184 | return; |
185 | if (sscanf(varlist, "%04X", &Code->variables[Code->var_count]) != 1) |
186 | Code->variables[Code->var_count] = 0; |
187 | if (strchr(varlist, '"') == NULL) |
188 | return; |
189 | Code->variable_names[Code->var_count] = strchr(varlist, '"') + 1; |
190 | if ((varlist = strchr(Code->variable_names[Code->var_count], '"')) == NULL) |
191 | return; |
192 | *varlist++ = 0; |
193 | if (*varlist == ',') |
194 | varlist++; |
195 | Code->var_count++; |
196 | } |
197 | } |
198 | |
199 | /********************************************************************************************************* |
200 | * global functions |
201 | */ |
202 | |
203 | static void ReadCheats(char *RomSection) |
204 | { |
205 | sCheatInfo *curr_code = NULL; |
206 | const char *romdbpath = ConfigGetSharedDataFilepath(CHEAT_FILE); |
207 | if (romdbpath == NULL) |
208 | { |
209 | DebugMessage(M64MSG_WARNING, "cheat code database file '%s' not found.", CHEAT_FILE); |
210 | return; |
211 | } |
212 | |
213 | /* read the INI file into a new buffer */ |
214 | FILE *fPtr = NULL; |
215 | fPtr = fopen(romdbpath, "rb"); |
216 | if (fPtr == NULL) |
217 | { |
218 | DebugMessage(M64MSG_WARNING, "Couldn't open cheat code database file '%s'.", romdbpath); |
219 | return; |
220 | } |
221 | fseek(fPtr, 0L, SEEK_END); |
222 | long IniLength = ftell(fPtr); |
223 | fseek(fPtr, 0L, SEEK_SET); |
224 | l_IniText = (char *) malloc(IniLength + 1); |
225 | if (l_IniText == NULL) |
226 | { |
227 | DebugMessage(M64MSG_WARNING, "Couldn't allocate %li bytes of memory to read cheat file.", IniLength); |
228 | fclose(fPtr); |
229 | return; |
230 | } |
231 | if (fread(l_IniText, 1, IniLength, fPtr) != IniLength) |
232 | { |
233 | DebugMessage(M64MSG_WARNING, "Couldn't read %li bytes from cheat file.", IniLength); |
234 | free(l_IniText); |
235 | l_IniText = NULL; |
236 | fclose(fPtr); |
237 | return; |
238 | } |
239 | fclose(fPtr); |
240 | l_IniText[IniLength] = 0; /* null-terminate the text data */ |
241 | |
242 | /* parse lines from cheat database */ |
243 | char *curline = NULL; |
244 | char *nextline = l_IniText; |
245 | |
246 | while(nextline != NULL && *nextline != 0) |
247 | { |
248 | curline = nextline; |
249 | /* get pointer to next line and NULL-terminate the current line */ |
250 | nextline = strchr(curline, '\n'); |
251 | if (nextline != NULL) |
252 | { |
253 | *nextline = 0; |
254 | nextline++; |
255 | } |
256 | |
257 | /* remove leading and trailing white space */ |
258 | while(isSpace(*curline)) curline++; |
259 | char *endptr = curline + strlen(curline) - 1; |
260 | while(isSpace(*endptr)) *endptr-- = 0; |
261 | |
262 | /* ignore line if comment or empty */ |
263 | if (*curline == '#' || strncmp(curline, "//", 2) == 0 || *curline == 0) |
264 | continue; |
265 | |
266 | /* handle beginning of new rom section */ |
267 | if (strncmp(curline, "crc ", 4) == 0) |
268 | { |
269 | /* if we have already found cheats for the given ROM file, then exit upon encountering a new ROM section */ |
270 | if (l_RomFound && (l_CheatGameName != NULL || l_CheatList != NULL)) |
271 | return; |
272 | /* else see if this Rom Section matches */ |
273 | if (strcmp(curline+4, RomSection) == 0) |
274 | l_RomFound = 1; |
275 | continue; |
276 | } |
277 | |
278 | /* if we haven't found the specified ROM section, then continue looking */ |
279 | if (!l_RomFound) |
280 | continue; |
281 | |
282 | /* Game name */ |
283 | if (strncmp(curline, "gn ", 3) == 0) |
284 | { |
285 | l_CheatGameName = curline+3; |
286 | continue; |
287 | } |
288 | |
289 | /* code name */ |
290 | if (strncmp(curline, "cn ", 3) == 0) |
291 | { |
292 | curr_code = NewCode(curline + 3, l_CheatCodesFound); |
293 | if (curr_code == NULL) |
294 | DebugMessage(M64MSG_WARNING, "error getting new code (%s)", curline+3); |
295 | continue; |
296 | } |
297 | |
298 | /* if curr_code is NULL, don't do these checks */ |
299 | if (curr_code == NULL) |
300 | continue; |
301 | |
302 | /* code description */ |
303 | if (strncmp(curline, "cd ", 3) == 0) |
304 | { |
305 | curr_code->Description = curline+3; |
306 | continue; |
307 | } |
308 | |
309 | /* code line */ |
310 | int address; |
311 | if (sscanf(curline, "%8X %*s", &address) == 1) |
312 | { |
313 | curr_code->Codes = (cheat_code*) realloc(curr_code->Codes, sizeof(cheat_code) * (curr_code->Count + 1)); |
314 | if (strncmp(curline+9, "????", 4) == 0) |
315 | { |
316 | curr_code->Codes[curr_code->Count].var_count = 0; |
317 | CheatAddVariables(&curr_code->Codes[curr_code->Count], curline+14); |
318 | curr_code->VariableLine = curr_code->Count; |
319 | } |
320 | else |
321 | { |
322 | int var; |
323 | curr_code->Codes[curr_code->Count].var_count = 1; |
324 | curr_code->Codes[curr_code->Count].variables = (int*) malloc(sizeof(int)); |
325 | if(curr_code->Codes[curr_code->Count].variables == NULL) |
326 | { |
327 | DebugMessage(M64MSG_WARNING, "couldn't allocate memory; ignoring line: '%s'", curline); |
328 | continue; |
329 | } |
330 | if (sscanf(curline+9, "%04X", &var) != 1) |
331 | var = 0; |
332 | curr_code->Codes[curr_code->Count].variables[0] = var; |
333 | curr_code->Codes[curr_code->Count].variable_names = NULL; |
334 | } |
335 | curr_code->Codes[curr_code->Count].var_to_use = 0; |
336 | curr_code->Codes[curr_code->Count].address = address; |
337 | curr_code->Count++; |
338 | continue; |
339 | } |
340 | |
341 | /* otherwise we don't know what this line is */ |
342 | DebugMessage(M64MSG_WARNING, "unrecognized line in cheat file: '%s'", curline); |
343 | } |
344 | |
345 | } |
346 | |
347 | void CheatStart(eCheatMode CheatMode, char *CheatNumList) |
348 | { |
349 | /* if cheat codes are disabled, then we don't have to do anything */ |
350 | if (CheatMode == CHEAT_DISABLE || (CheatMode == CHEAT_LIST && strlen(CheatNumList) == 0)) |
351 | { |
352 | DebugMessage(M64MSG_STATUS, "Cheat codes disabled."); |
353 | return; |
354 | } |
355 | |
356 | /* get the ROM header for the currently loaded ROM image from the core */ |
357 | if ((*CoreDoCommand)(M64CMD_ROM_GET_HEADER, sizeof(l_RomHeader), &l_RomHeader) != M64ERR_SUCCESS) |
358 | { |
359 | DebugMessage(M64MSG_WARNING, "couldn't get ROM header information from core library"); |
360 | return; |
361 | } |
362 | |
363 | /* generate section name from ROM's CRC and country code */ |
364 | char RomSection[24]; |
365 | sprintf(RomSection, "%08X-%08X-C:%X", sl(l_RomHeader.CRC1), sl(l_RomHeader.CRC2), l_RomHeader.Country_code & 0xff); |
366 | |
367 | /* parse through the cheat INI file and load up any cheat codes found for this ROM */ |
368 | ReadCheats(RomSection); |
369 | if (!l_RomFound || l_CheatCodesFound == 0) |
370 | { |
371 | DebugMessage(M64MSG_WARNING, "no cheat codes found for ROM image '%.20s'", l_RomHeader.Name); |
372 | CheatFreeAll(); |
373 | return; |
374 | } |
375 | |
376 | /* handle the list command */ |
377 | if (CheatMode == CHEAT_SHOW_LIST) |
378 | { |
379 | DebugMessage(M64MSG_INFO, "%i cheat code(s) found for ROM '%s'", l_CheatCodesFound, l_CheatGameName); |
380 | sCheatInfo *pCur = l_CheatList; |
381 | while (pCur != NULL) |
382 | { |
383 | if (pCur->Description == NULL) |
384 | DebugMessage(M64MSG_INFO, " %i: %s", pCur->Number, pCur->Name); |
385 | else |
386 | DebugMessage(M64MSG_INFO, " %i: %s (%s)", pCur->Number, pCur->Name, pCur->Description); |
387 | if(pCur->VariableLine != -1) |
388 | { |
389 | int i; |
390 | for (i = 0; i < pCur->Codes[pCur->VariableLine].var_count; i++) |
391 | DebugMessage(M64MSG_INFO, " %i: %s", i, pCur->Codes[pCur->VariableLine].variable_names[i]); |
392 | } |
393 | pCur = pCur->Next; |
394 | } |
395 | CheatFreeAll(); |
396 | return; |
397 | } |
398 | |
399 | /* handle all cheats enabled mode */ |
400 | if (CheatMode == CHEAT_ALL) |
401 | { |
402 | sCheatInfo *pCur = l_CheatList; |
403 | while (pCur != NULL) |
404 | { |
405 | CheatActivate(pCur); |
406 | pCur = pCur->Next; |
407 | } |
408 | CheatFreeAll(); |
409 | return; |
410 | } |
411 | |
412 | /* handle list of cheats enabled mode */ |
413 | if (CheatMode == CHEAT_LIST) |
414 | { |
415 | int option, number; |
416 | char *cheat_next; |
417 | sCheatInfo *pCheat; |
418 | while(CheatNumList != NULL && *CheatNumList) |
419 | { |
420 | if ((cheat_next = strchr(CheatNumList, ',')) != NULL) |
421 | { |
422 | *cheat_next = 0; |
423 | cheat_next ++; |
424 | } |
425 | |
426 | if (strchr(CheatNumList, '-') != NULL) |
427 | { |
428 | sscanf(CheatNumList, "%i-%i", &number, &option); /* option */ |
429 | } |
430 | else |
431 | { |
432 | option=0; |
433 | sscanf(CheatNumList, "%i", &number); |
434 | } |
435 | |
436 | pCheat = CheatFindCode(number); |
437 | if (pCheat == NULL) |
438 | DebugMessage(M64MSG_WARNING, "invalid cheat code number %i", number); |
439 | else |
440 | { |
441 | if (pCheat->VariableLine != -1 && pCheat->Count > pCheat->VariableLine && option < pCheat->Codes[pCheat->VariableLine].var_count) |
442 | pCheat->Codes[pCheat->VariableLine].var_to_use = option; |
443 | CheatActivate(pCheat); |
444 | } |
445 | |
446 | CheatNumList = cheat_next; |
447 | } |
448 | CheatFreeAll(); |
449 | |
450 | return; |
451 | } |
452 | |
453 | /* otherwise the mode is invalid */ |
454 | DebugMessage(M64MSG_WARNING, "internal error; invalid CheatMode in CheatStart()"); |
455 | |
456 | return; |
457 | } |
458 | |