mirror of
https://github.com/pj64team/Project64-Legacy.git
synced 2026-04-02 12:45:18 +00:00
722 lines
20 KiB
C
722 lines
20 KiB
C
#include "Cheats_Preprocessor.h"
|
|
|
|
void FetchRedirection(char** string);
|
|
BOOL Cheats_Store(char** string, char* source, unsigned int length);
|
|
void Cheats_ClearCheat(CHEAT* cheat);
|
|
|
|
// Verify the cheat string and return the type (See the defines above, NO_REPLACE, O_REPLACE, SO_REPLACE, MO_REPLACE, BAD_REPLACE)
|
|
BOOL Cheats_Verify(CHEAT* cheat);
|
|
|
|
// Replace any ? in a one to one relationship based on a selected option
|
|
// Returns FALSE on failure, TRUE otherwise
|
|
BOOL Cheats_LoadOption(CHEAT* cheat);
|
|
|
|
|
|
BOOL Cheats_Read(CHEAT* cheat, unsigned int cheat_num) {
|
|
char Identifier[100], CheatFormat[20];
|
|
char* tmp, *find1, *find2;
|
|
|
|
RomID(Identifier, RomHeader);
|
|
|
|
// Read the main cheat entry that contains the name and the code string
|
|
sprintf(CheatFormat, CHT_ENT, cheat_num);
|
|
Settings_Read(CDB_NAME, Identifier, CheatFormat, STR_EMPTY, &tmp);
|
|
|
|
// The cheat name is encapsulated by " "
|
|
find1 = strchr(tmp, '"');
|
|
find2 = strrchr(tmp, '"');
|
|
|
|
// The cheat does not contain a name
|
|
if (find1 == NULL || find2 == NULL || find1 == find2) {
|
|
if (tmp)
|
|
free(tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
// Populate the name
|
|
// This only fails if memory could not be allocated
|
|
if (!Cheats_Store(&(cheat->name), find1 + 1, find2 - find1 - 1)) {
|
|
if (tmp)
|
|
free(tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
// Populate the code string
|
|
// This only fails if memory could not be allocated
|
|
if (!Cheats_Store(&(cheat->codestring), find2 + 1, strlen((find2 + 1)))) {
|
|
free(cheat->name);
|
|
cheat->name = NULL;
|
|
|
|
if (tmp)
|
|
free(tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
// Populate the code string that will be used for the loading of options
|
|
// This only fails if memory could not be allocated
|
|
if (!Cheats_Store(&(cheat->replacedstring), find2 + 1, strlen((find2 + 1)))) {
|
|
free(cheat->name);
|
|
cheat->name = NULL;
|
|
|
|
free(cheat->codestring);
|
|
cheat->codestring = NULL;
|
|
|
|
if (tmp)
|
|
free(tmp);
|
|
return FALSE;
|
|
}
|
|
|
|
if (tmp)
|
|
free(tmp);
|
|
|
|
// Make sure the code string that is associated with the name is valid
|
|
Cheats_Verify(cheat);
|
|
|
|
// Load any options that may exist (If none this will be an empty string)
|
|
sprintf(CheatFormat, CHT_ENT_O, cheat_num);
|
|
Settings_Read(CDB_NAME, Identifier, CheatFormat, STR_EMPTY, &cheat->options);
|
|
FetchRedirection(&cheat->options);
|
|
|
|
// Load any option that may be selected (If none this will be an empty string)
|
|
tmp = malloc(sizeof(*tmp) * (strlen(cheat->name) + strlen(CHT_EXT_O) + 1));
|
|
if (tmp != NULL) {
|
|
sprintf(tmp, CHT_EXT_O, cheat->name);
|
|
Settings_Read(APPS_NAME, Identifier, tmp, STR_EMPTY, &cheat->selected);
|
|
free(tmp);
|
|
}
|
|
|
|
// See if the selected option can be loaded, if it can't replacedstring will be shrunk
|
|
if (!Cheats_LoadOption(cheat)) {
|
|
// Could not load the option
|
|
// Copy STR_EMPTY and reallocate space to only the minimum
|
|
char* copy;
|
|
strcpy(cheat->replacedstring, STR_EMPTY);
|
|
copy = realloc(cheat->replacedstring, strlen(cheat->replacedstring) + 1);
|
|
if (!copy)
|
|
cheat->replacedstring = copy;
|
|
}
|
|
|
|
// Read the note
|
|
sprintf(CheatFormat, CHT_ENT_N, cheat_num);
|
|
Settings_Read(CDB_NAME, Identifier, CheatFormat, STR_EMPTY, &cheat->note);
|
|
FetchRedirection(&cheat->note);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Write out the cheat with any appropriate options
|
|
// This will delete anything with the name that is currently in use
|
|
// Will return false on failure to allocate memory or write to file
|
|
BOOL Cheats_Write(CHEAT* cheat) {
|
|
char* tmp;
|
|
unsigned int length, count;
|
|
char format[50], Identifier[100];
|
|
|
|
// Find the next available location (This is up to MaxCheats, a define inside of Cheats.h)
|
|
for (count = 0; count < MaxCheats; count++) {
|
|
CHEAT read = { 0 };
|
|
|
|
if (!Cheats_Read(&read, count))
|
|
break;
|
|
|
|
if ((read.name != NULL) && strcmp(cheat->name, read.name) == 0) {
|
|
DisplayError(GS(MSG_CHEAT_NAME_IN_USE));
|
|
Cheats_ClearCheat(&read);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Should never reach MaxCheats
|
|
if (count == MaxCheats) {
|
|
DisplayError(GS(MSG_MAX_CHEATS));
|
|
return FALSE;
|
|
}
|
|
|
|
RomID(Identifier, RomHeader);
|
|
|
|
Settings_Write(CDB_NAME, Identifier, "Name", RomFullName);
|
|
|
|
// To store the name and code string together
|
|
// Need to add two " and one , and add space for the null byte at the end
|
|
length = strlen(cheat->name) + strlen(cheat->codestring) + 4;
|
|
tmp = malloc(sizeof(*tmp) * length);
|
|
if (!tmp)
|
|
return FALSE;
|
|
|
|
// Write out the main entry (This is always written, the format is Cheat0="Name Here",80000000 0000
|
|
// The code string should already have been verified so it's fine to simply write it out
|
|
sprintf(format, CHT_ENT, count);
|
|
snprintf(tmp, length, "\"%s\"%s", cheat->name, cheat->codestring);
|
|
Settings_Write(CDB_NAME, Identifier, format, tmp);
|
|
free(tmp);
|
|
|
|
// Write the note out if the cheat has one
|
|
if (cheat->note != NULL && strcmp(cheat->note, STR_EMPTY) != 0) {
|
|
sprintf(format, CHT_ENT_N, count);
|
|
Settings_Write(CDB_NAME, Identifier, format, cheat->note);
|
|
}
|
|
|
|
// Write the options out if the cheat has any
|
|
if (cheat->options != NULL && strcmp(cheat->options, STR_EMPTY) != 0) {
|
|
sprintf(format, CHT_ENT_O, count);
|
|
Settings_Write(CDB_NAME, Identifier, format, cheat->options);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void Cheats_Delete(CHEAT* cheat) {
|
|
char* tmp;
|
|
unsigned int length, count, i;
|
|
char format[50], previous[50], Identifier[100];
|
|
char *names[] = { CHT_ENT, CHT_ENT_N, CHT_ENT_O };
|
|
|
|
// Delete the first cheat that matches the current name
|
|
for (count = 0; count < MaxCheats; count++) {
|
|
CHEAT read = { 0 };
|
|
|
|
if (!Cheats_Read(&read, count))
|
|
continue;
|
|
|
|
if ((read.name != NULL) && strcmp(cheat->name, read.name) == 0) {
|
|
Cheats_ClearCheat(&read);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Nothing to delete
|
|
if (count == MaxCheats)
|
|
return;
|
|
|
|
RomID(Identifier, RomHeader);
|
|
|
|
// Delete the three entries: the main one, the note, and the option
|
|
for (i = 0; i < sizeof(names) / sizeof(*names); i++) {
|
|
sprintf(format, names[i], count);
|
|
strcat(format, "=");
|
|
Settings_Delete(CDB_NAME, Identifier, format);
|
|
}
|
|
|
|
// Now change the keys on the remaining cheats to reflect the newly emptied count
|
|
count++;
|
|
while (count != MaxCheats) {
|
|
// Change the keys for the three entries: the main one, the note, and the option
|
|
for (i = 0; i < sizeof(names) / sizeof(*names); i++) {
|
|
sprintf(format, names[i], count);
|
|
strcat(format, "=");
|
|
|
|
sprintf(previous, names[i], count - 1);
|
|
strcat(previous, "=");
|
|
|
|
Settings_ChangeKey(CDB_NAME, Identifier, format, previous);
|
|
}
|
|
count++;
|
|
}
|
|
|
|
// To do! Remove this hack once the file handler has been updated to do delayed/timed writes
|
|
Settings_Delete(CDB_NAME, Identifier, "Cheat9000");
|
|
|
|
// Remove the cheat from the APPS file
|
|
Settings_Delete(APPS_NAME, Identifier, cheat->name);
|
|
|
|
// Remove any selected option from the APPS file
|
|
length = strlen(cheat->name) + strlen(CHT_EXT_O);
|
|
tmp = malloc(sizeof(*tmp) * length);
|
|
if (!tmp)
|
|
return;
|
|
snprintf(tmp, length, CHT_EXT_O, cheat->name);
|
|
Settings_Delete(APPS_NAME, Identifier, cheat->name);
|
|
}
|
|
|
|
|
|
BOOL Cheats_Store(char** string, char* source, unsigned int length) {
|
|
*string = malloc(sizeof(**string) * (length + 1));
|
|
|
|
if (!*string)
|
|
return FALSE;
|
|
|
|
strncpy(*string, source, length);
|
|
(*string)[length] = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void Cheats_ClearCheat(CHEAT* cheat) {
|
|
if (cheat->name) {
|
|
free(cheat->name);
|
|
cheat->name = NULL;
|
|
}
|
|
|
|
if (cheat->codestring) {
|
|
free(cheat->codestring);
|
|
cheat->codestring = NULL;
|
|
}
|
|
|
|
if (cheat->options) {
|
|
free(cheat->options);
|
|
cheat->options = NULL;
|
|
}
|
|
|
|
if (cheat->selected) {
|
|
free(cheat->selected);
|
|
cheat->selected = NULL;
|
|
}
|
|
|
|
if (cheat->note) {
|
|
free(cheat->note);
|
|
cheat->note = NULL;
|
|
}
|
|
|
|
cheat->type = BAD_CODE;
|
|
}
|
|
|
|
|
|
// There are multiple formats available to the cheat string but ultimately it is as follows, Cheat0="Name Here" 80?????? ????
|
|
// With a comma following additional cheats to be activated, example 80?????? ????,80?????? ????
|
|
// The 80 is an activator and has limits -- This will be checked for at a later time for now this code is written without verification of activator
|
|
BOOL Cheats_Verify(CHEAT *cheat) {
|
|
int address_replace = -1, addr_tmp, value_replace = -1, val_tmp, tmp;
|
|
char* find;
|
|
|
|
if ((cheat->name != NULL && strlen(cheat->name) == 0) || (cheat->codestring != NULL && strlen(cheat->codestring) == 0)) {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
find = cheat->codestring;
|
|
while (find != NULL) {
|
|
addr_tmp = 0;
|
|
val_tmp = 0;
|
|
|
|
// The next check will be ,XXXXXXXX XXXX so at least 14 characters long
|
|
if (strlen(find) < 14) {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Skip past the ,
|
|
find++;
|
|
|
|
// A space may be the next character, simply ignore it
|
|
// This means we now support ", 80000000 0000, 80000010 0000" (Note the space after the comma)
|
|
if (find[0] == ' ')
|
|
find++;
|
|
|
|
// This is where the activator is checked and where more code may be added in the future (The list of activators is in Cheat.c)
|
|
// For now this must be a hexadecimal digit and we are not allowing ? in the activator
|
|
if (!isxdigit(find[0]) || !isxdigit(find[1])) {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
for (int i = 2; i < 8; i++) {
|
|
if (!isxdigit(find[i])) {
|
|
if (find[i] == '?')
|
|
addr_tmp++;
|
|
else {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bypass the check on replacements, this will be completely rewritten at a later date
|
|
if (find[7] != '?') {
|
|
|
|
// 16bit cheat code addresses (81 activator) must be even aligned
|
|
tmp = strtoul(find, NULL, 16);
|
|
if (strncmp(find, "81", 2) == 0 && tmp & 1) {
|
|
char junk[500];
|
|
sprintf(junk, "Detected Odd Aligned 16 bit code!\r\nThis is not supported by any known cheat device!\r\n%s, %.13s", cheat->name, find);
|
|
DisplayError(junk);
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Set the number of replaces for address if not set
|
|
if (address_replace == -1 && addr_tmp != 0)
|
|
address_replace = addr_tmp;
|
|
|
|
// Fow now this supports uniform replacement, 0 to 6 replacement
|
|
// So, a replacement of 4 for the address will only accept 0 or 4 replacements
|
|
// If something else is read, such as a 3 then the code will be rejected
|
|
// In the future this will be changed to check against the appropriate option (Be it address, value, or both)
|
|
if (addr_tmp != 0 && address_replace != addr_tmp) {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
// A space separates the address and value
|
|
if (find[8] != ' ') {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
// The first two digits in the value may be replaced
|
|
if (!isxdigit(find[9]) || !isxdigit(find[10])) {
|
|
if (find[9] == '?' && find[10] == '?')
|
|
val_tmp++;
|
|
else {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// The second two digits in the value may be replaced
|
|
if (!isxdigit(find[11]) || !isxdigit(find[12])) {
|
|
if (find[11] == '?' && find[12] == '?')
|
|
val_tmp++;
|
|
else {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Set the number of replaces for value if not set
|
|
if (value_replace == -1 && val_tmp != 0)
|
|
value_replace = val_tmp;
|
|
|
|
// For now this is supporting uniform replacement, 8bit or 16bit replacement and not a mixture
|
|
// In the future this will be changed to check against the appropriate option (Be it address, value, or both)
|
|
if (val_tmp != 0 && value_replace != val_tmp) {
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Check the next code (Doing + 13 just to skip past characters that have already been verified)
|
|
find = strchr(find + 13, ',');
|
|
}
|
|
|
|
if (address_replace == -1)
|
|
address_replace = 0;
|
|
|
|
if (value_replace == -1)
|
|
value_replace = 0;
|
|
|
|
if (address_replace == 0 && value_replace == 0) {
|
|
cheat->type = NO_REPLACE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (address_replace == 0 && value_replace != 0) {
|
|
cheat->type = O_REPLACE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (address_replace != 0 && value_replace == 0) {
|
|
cheat->type = SO_REPLACE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (address_replace != 0 && value_replace != 0) {
|
|
cheat->type = MO_REPLACE;
|
|
return TRUE;
|
|
}
|
|
|
|
cheat->type = BAD_CODE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Replace the ? inside the code string
|
|
// The option being loaded should be in one of these formats:
|
|
// $Value(:Value) Name
|
|
// $Value Name
|
|
// $Value:Value(:Value:Value) Name
|
|
// Where parenthesis notes potential repeating sections
|
|
BOOL Cheats_LoadOption(CHEAT* cheat) {
|
|
char* Ext, * check;
|
|
unsigned int i, rep, old_rep;
|
|
BOOL serial_replacement;
|
|
|
|
// Cannot replace if the code is bad
|
|
if (cheat->type == BAD_CODE)
|
|
return FALSE;
|
|
|
|
// Cheat did not have a replacement
|
|
if (cheat->type == NO_REPLACE)
|
|
return TRUE;
|
|
|
|
Ext = cheat->selected;
|
|
|
|
// No selected option or the option is not in the proper format
|
|
if (strcmp(Ext, STR_EMPTY) == 0 || Ext[0] != '$') {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Check to see if this selected option has a name
|
|
check = strrchr(Ext, ' ');
|
|
if (!check || strlen(check) == 0) {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Check to see if the replacement string will be looped back or if the replacement will happen in sequence
|
|
serial_replacement = FALSE;
|
|
check = strchr(Ext, ':');
|
|
if (check != NULL) {
|
|
// Multiple Option replacement uses a :, this is only a serial replacement if there is more than one :
|
|
if (cheat->type == MO_REPLACE) {
|
|
if (strrchr(Ext, ':') != check)
|
|
serial_replacement = TRUE;
|
|
}
|
|
// The other options don't use a : so existence of one indicates a serial replacement
|
|
else
|
|
serial_replacement = TRUE;
|
|
}
|
|
|
|
// Scan the string for ? to replace
|
|
check = cheat->replacedstring;
|
|
rep = 1; // To skip past the $ in Ext
|
|
while (check != NULL) {
|
|
|
|
old_rep = rep; // This will be used to ensure a replacement actually happened (If these two are equal then no replacement occured)
|
|
|
|
if (check[0] != ',') {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
else
|
|
check++;
|
|
|
|
if (check[0] == ' ')
|
|
check++;
|
|
|
|
// Scan the address (First 8 bytes, ignoring the first 2 for the activator) for ? to replace
|
|
for (i = 2; i < 8; i++) {
|
|
if (check[i] == '?') {
|
|
// Regular option does not allow for address replacement
|
|
if (cheat->type == O_REPLACE) {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Replacement requires Ext to contain a valid hexadecimal number
|
|
if (strlen(Ext) >= rep && isxdigit(Ext[rep])) {
|
|
check[i] = Ext[rep];
|
|
rep++;
|
|
}
|
|
else {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only for Multiple Option, a : must be present
|
|
if (cheat->type == MO_REPLACE) {
|
|
if (Ext[rep] != ':') {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
else
|
|
rep++;
|
|
}
|
|
|
|
// Increment past the ' '
|
|
check += 9;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (check[i] == '?') {
|
|
// Special Option does not allow for value replacement
|
|
if (cheat->type == SO_REPLACE) {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Replacement requires Ext to contain a valid hexadecimal number
|
|
if (strlen(Ext) >= rep && isxdigit(Ext[rep])) {
|
|
check[i] = Ext[rep];
|
|
rep++;
|
|
}
|
|
else {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// These checks are to be made only if a replacement occured (rep would be greater than 1)
|
|
if (rep != old_rep) {
|
|
// Decide what to do with rep, the 'counter' for the replacement string
|
|
if (serial_replacement) {
|
|
// This must contain a :
|
|
if (Ext[rep] == ':')
|
|
rep++;
|
|
else {
|
|
// Reached the end of the replacement
|
|
if (Ext[rep] != ' ') {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// By this point the next character should be a space, to denote the end of the replacement
|
|
if (Ext[rep] != ' ') {
|
|
cheat->type = BAD_REPLACE;
|
|
return FALSE;
|
|
}
|
|
rep = 1;
|
|
}
|
|
}
|
|
|
|
// Check the next code
|
|
check = strchr(check, ',');
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// Used only when reading _O (Option) or _N (Note)
|
|
void FetchRedirection(char** string) {
|
|
|
|
// Empty string passed
|
|
if (*string == NULL || strcmp(*string, STR_EMPTY) == 0)
|
|
return;
|
|
|
|
// This redirection goes to [Global] by default, used to go outside of the current cheat entry
|
|
if (*string[0] == '@') {
|
|
Settings_Read(CDB_NAME, "Global", *string + 1, STR_EMPTY, string);
|
|
return;
|
|
}
|
|
|
|
// This redirection goes to [Name], used to go outside of the current cheat entry
|
|
if (*string[0] == '[') {
|
|
char* find, Id2[100];
|
|
|
|
find = strchr(*string, ']');
|
|
|
|
// This is not a proper redirection
|
|
if (find == NULL || strlen(find) < 2)
|
|
return;
|
|
|
|
memset(Id2, '\0', sizeof(Id2));
|
|
strncpy(Id2, *string + 1, find - *string - 1);
|
|
Settings_Read(CDB_NAME, Id2, find + 1, STR_EMPTY, string);
|
|
|
|
return;
|
|
}
|
|
|
|
// If this is not an option (denoted by a $ at the start) then try to fetch a redirection
|
|
if (*string[0] != '$') {
|
|
char* tmp, Identifier[100];
|
|
RomID(Identifier, RomHeader);
|
|
|
|
Settings_Read(CDB_NAME, Identifier, *string, *string, &tmp);
|
|
free(*string);
|
|
*string = tmp;
|
|
}
|
|
}
|
|
|
|
|
|
// The Cheat List will display the cheat by name
|
|
// This includes any added options or a notice that options are available (via a ?)
|
|
void Cheats_DisplayName(CHEAT* cheat, char* string, unsigned int string_length) {
|
|
|
|
// The cheat supports replacement of values (Either the address, value, or a combination of both)
|
|
// So append the name of the option if one is selected or append a ? if none is selected
|
|
if (cheat->type != NO_REPLACE) {
|
|
|
|
// This is a bad code, show (Bad Code) after the cheat name
|
|
if (cheat->type == BAD_CODE) {
|
|
snprintf(string, string_length, "%s (Bad Code)", cheat->name);
|
|
return;
|
|
}
|
|
|
|
// No option selected, show (=> ?)
|
|
if (cheat->selected == NULL || strcmp(cheat->selected, STR_EMPTY) == 0) {
|
|
snprintf(string, string_length, CHT_EXT, cheat->name, "?");
|
|
return;
|
|
}
|
|
|
|
// An option is selected, show only the name of the option
|
|
else {
|
|
char* find = strchr(cheat->selected, ' ');
|
|
|
|
// What's stored in the file is not in the appropriate format so show (=> Bad Option?)
|
|
if (find == NULL || cheat->selected[0] != '$')
|
|
snprintf(string, string_length, CHT_EXT, cheat->name, "Bad Option?");
|
|
|
|
// Skip past the value and just display the name of the option so for example (=> Invincible)
|
|
else
|
|
snprintf(string, string_length, CHT_EXT, cheat->name, find + 1);
|
|
}
|
|
}
|
|
// No option to show so simply output the name
|
|
else
|
|
snprintf(string, string_length, "%s", cheat->name);
|
|
}
|
|
|
|
BOOL Cheats_VerifyInput(CHEAT* cheat) {
|
|
unsigned int length;
|
|
char* navigate, * end;
|
|
|
|
// First verify the code string
|
|
if (!Cheats_Verify(cheat))
|
|
return FALSE;
|
|
|
|
// A code that does not need replacements is good
|
|
if (cheat->type == NO_REPLACE)
|
|
return TRUE;
|
|
|
|
// No options to verify
|
|
if (cheat->options == NULL || strcmp(cheat->options, STR_EMPTY) == 0)
|
|
return FALSE;
|
|
|
|
// Allocate enough space for any option
|
|
// This will use a lot more memory than is needed but it shouldn't be an issue
|
|
if (cheat->selected)
|
|
free(cheat->selected);
|
|
length = strlen(cheat->options);
|
|
if (length > 0) {
|
|
cheat->selected = malloc(sizeof(*cheat->selected) * (length + 1));
|
|
if (!cheat->selected)
|
|
return FALSE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
|
|
// This will be a copy of cheat->codestring
|
|
if (!cheat->replacedstring) {
|
|
length = strlen(cheat->codestring);
|
|
cheat->replacedstring = malloc(sizeof(*cheat->replacedstring) * (length + 1));
|
|
if (!cheat->replacedstring)
|
|
return FALSE;
|
|
}
|
|
|
|
navigate = cheat->options;
|
|
|
|
while (navigate != NULL) {
|
|
// Always reset this, it must contain ? to be replaced
|
|
strcpy(cheat->replacedstring, cheat->codestring);
|
|
|
|
// Calculate how much to copy for the next option (Up to a , or the remainder of the options string)
|
|
end = strchr(navigate, ',');
|
|
if (!end)
|
|
length = strlen(navigate);
|
|
else
|
|
length = end - navigate - 1;
|
|
|
|
if (length > 0) {
|
|
// This strncpy should be fine, it will copy at most the entire length of the options string
|
|
// That's the amount of memory that was allocated for this
|
|
strncpy(cheat->selected, navigate, length);
|
|
cheat->selected[length - 1] = '\0';
|
|
|
|
if (strlen(cheat->selected) > 0) {
|
|
// Note: Not deallocating cheat->selected because it should be deallocated by Cheats_ClearCheat()
|
|
if (!Cheats_LoadOption(cheat))
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Go to the next option to try
|
|
navigate = strchr(navigate + 1, '$');
|
|
}
|
|
|
|
return TRUE;
|
|
} |