mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
* Cheats: validate ranges before applying memory writes Cheat files can specify addresses outside the core memory map or place a multi-byte value across the end of a memory region. The apply path used the resulting offset for unchecked native memory reads and writes. Resolve the complete access range against one core memory buffer before reading it and before every repeated write. Skip entries and stop repeat sequences when their requested range is invalid. * cheats: one region lookup for every memory access Cheats and cheat searches reach core memory through cheat_region_seek, which finds the region holding a whole item and keeps it, so a walk that stays inside a region does not search the region list again. The three search paths read through it too: an item crossing a region's end is left out of the matches when a search starts, and browsing one leaves the values as they were. A condition whose address cannot be read does not hold, so the cheat it guards does not run. samples/cheats/cheat_regions maps three regions of awkward sizes, each allocated exactly, and runs cheats and searches across their edges under the sanitizers. The .gitignore rule for the cheats directory is anchored to the root so the sample can be tracked. --------- Co-authored-by: LibretroAdmin <[email protected]>
2180 lines
72 KiB
C
2180 lines
72 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
#include <file/config_file.h>
|
|
#include <file/file_path.h>
|
|
#include <streams/file_stream.h>
|
|
#include <compat/strl.h>
|
|
#include <compat/posix_string.h>
|
|
#include <string/stdstring.h>
|
|
#include <retro_miscellaneous.h>
|
|
#include <features/features_cpu.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MENU
|
|
#include "menu/menu_driver.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_CHEEVOS
|
|
#include "cheevos/cheevos.h"
|
|
#endif
|
|
|
|
#include "cheat_manager.h"
|
|
|
|
#include "msg_hash.h"
|
|
#include "retroarch.h"
|
|
#include "runloop.h"
|
|
#include "dynamic.h"
|
|
#include "core.h"
|
|
#include "verbosity.h"
|
|
|
|
/* TODO/FIXME - public global variables */
|
|
cheat_manager_t cheat_manager_state;
|
|
|
|
unsigned cheat_manager_get_buf_size(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
return cheat_st->buf_size;
|
|
}
|
|
|
|
unsigned cheat_manager_get_size(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
return cheat_st->size;
|
|
}
|
|
|
|
#ifdef HAVE_CHEEVOS
|
|
static void cheat_manager_pause_cheevos(void)
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_DISABLED_CHEAT), sizeof(msg));
|
|
rcheevos_pause_hardcore();
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
RARCH_LOG("[Cheats] %s\n", msg);
|
|
}
|
|
#endif
|
|
|
|
void cheat_manager_apply_cheats(bool notification_show_cheats_applied)
|
|
{
|
|
unsigned i, idx = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
if (!cheat_st->cheats)
|
|
return;
|
|
|
|
core_reset_cheat();
|
|
|
|
for (i = 0; i < cheat_st->size; i++)
|
|
{
|
|
if ( cheat_st->cheats[i].state
|
|
&& cheat_st->cheats[i].handler == CHEAT_HANDLER_TYPE_EMU)
|
|
{
|
|
retro_ctx_cheat_info_t cheat_info;
|
|
|
|
cheat_info.index = idx++;
|
|
cheat_info.enabled = true;
|
|
cheat_info.code = cheat_st->cheats[i].code;
|
|
|
|
if (cheat_info.code && *cheat_info.code)
|
|
core_set_cheat(&cheat_info);
|
|
}
|
|
}
|
|
|
|
if (cheat_st->size && notification_show_cheats_applied)
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_APPLYING_CHEAT), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
|
|
RARCH_LOG("[Cheats] %s\n", msg);
|
|
}
|
|
|
|
#ifdef HAVE_CHEEVOS
|
|
if (idx != 0 && rcheevos_hardcore_active())
|
|
cheat_manager_pause_cheevos();
|
|
#endif
|
|
}
|
|
|
|
void cheat_manager_set_code(unsigned i, const char *str)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || !str || !*str)
|
|
return;
|
|
|
|
if (cheat_st->cheats[i].code)
|
|
free(cheat_st->cheats[i].code);
|
|
|
|
cheat_st->cheats[i].code = strdup(str);
|
|
|
|
cheat_st->cheats[i].state = true;
|
|
}
|
|
|
|
/**
|
|
* cheat_manager_save:
|
|
* @path : Path to cheats file (relative path).
|
|
*
|
|
* Saves cheats to file on disk.
|
|
*
|
|
* Returns: true (1) if successful, otherwise false (0).
|
|
**/
|
|
bool cheat_manager_save(
|
|
const char *path,
|
|
const char *cheat_database,
|
|
bool overwrite)
|
|
{
|
|
bool ret;
|
|
unsigned i;
|
|
char cheats_file[PATH_MAX_LENGTH];
|
|
config_file_t *conf = NULL;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
unsigned int* data_ptrs[16] = { NULL};
|
|
char* keys[16] = {
|
|
(char*)"cheat%u_handler",
|
|
(char*)"cheat%u_memory_search_size",
|
|
(char*)"cheat%u_cheat_type",
|
|
(char*)"cheat%u_value",
|
|
(char*)"cheat%u_address",
|
|
(char*)"cheat%u_address_bit_position",
|
|
(char*)"cheat%u_rumble_type",
|
|
(char*)"cheat%u_rumble_value",
|
|
(char*)"cheat%u_rumble_port",
|
|
(char*)"cheat%u_rumble_primary_strength",
|
|
(char*)"cheat%u_rumble_primary_duration",
|
|
(char*)"cheat%u_rumble_secondary_strength",
|
|
(char*)"cheat%u_rumble_secondary_duration",
|
|
(char*)"cheat%u_repeat_count",
|
|
(char*)"cheat%u_repeat_add_to_value",
|
|
(char*)"cheat%u_repeat_add_to_address"
|
|
};
|
|
|
|
if (!cheat_st->cheats)
|
|
return false;
|
|
|
|
if (!cheat_st->size)
|
|
{
|
|
filestream_delete(path);
|
|
return false;
|
|
}
|
|
|
|
if (!cheat_database)
|
|
strlcpy(cheats_file, path, sizeof(cheats_file));
|
|
else
|
|
{
|
|
size_t _len = fill_pathname_join_special(cheats_file, cheat_database,
|
|
path, sizeof(cheats_file));
|
|
strlcpy_lit(cheats_file + _len, ".cht", sizeof(cheats_file) - _len);
|
|
}
|
|
|
|
if (!overwrite)
|
|
conf = config_file_new_from_path_to_string(cheats_file);
|
|
|
|
if (!conf)
|
|
{
|
|
if (!(conf = config_file_new_alloc()))
|
|
return false;
|
|
/* Only a config this function fills from empty can promise
|
|
* the setters that no key is already present. The parsed
|
|
* one above already holds every key written below - claiming
|
|
* otherwise appended a duplicate of each, and since the
|
|
* first of a duplicate pair wins on reload, the save looked
|
|
* fine in memory while the file kept its old values and grew
|
|
* by a full copy on every save. */
|
|
conf->flags |= CONF_FILE_FLG_GUARANTEED_NO_DUPLICATES;
|
|
}
|
|
|
|
config_set_int(conf, "cheats", cheat_st->size);
|
|
|
|
for (i = 0; i < cheat_st->size; i++)
|
|
{
|
|
unsigned j;
|
|
char key[256];
|
|
char var_key[128];
|
|
size_t _len = snprintf(var_key, sizeof(var_key), "cheat%u_", i);
|
|
|
|
strlcpy_lit(var_key + _len, "desc", sizeof(var_key) - _len);
|
|
if (cheat_st->cheats[i].desc && *cheat_st->cheats[i].desc)
|
|
config_set_string(conf, var_key, cheat_st->cheats[i].desc);
|
|
else
|
|
config_set_string(conf, var_key, cheat_st->cheats[i].code);
|
|
|
|
strlcpy_lit(var_key + _len, "code", sizeof(var_key) - _len);
|
|
config_set_string(conf, var_key, cheat_st->cheats[i].code);
|
|
|
|
strlcpy_lit(var_key + _len, "enable", sizeof(var_key) - _len);
|
|
config_set_string(conf, var_key,
|
|
cheat_st->cheats[i].state
|
|
? "true"
|
|
: "false");
|
|
|
|
strlcpy_lit(var_key + _len, "big_endian", sizeof(var_key) - _len);
|
|
config_set_string(conf, var_key,
|
|
cheat_st->cheats[i].big_endian
|
|
? "true"
|
|
: "false"
|
|
);
|
|
|
|
data_ptrs[0] = &cheat_st->cheats[i].handler;
|
|
data_ptrs[1] = &cheat_st->cheats[i].memory_search_size;
|
|
data_ptrs[2] = &cheat_st->cheats[i].cheat_type;
|
|
data_ptrs[3] = &cheat_st->cheats[i].value;
|
|
data_ptrs[4] = &cheat_st->cheats[i].address;
|
|
data_ptrs[5] = &cheat_st->cheats[i].address_mask;
|
|
data_ptrs[6] = &cheat_st->cheats[i].rumble_type;
|
|
data_ptrs[7] = &cheat_st->cheats[i].rumble_value;
|
|
data_ptrs[8] = &cheat_st->cheats[i].rumble_port;
|
|
data_ptrs[9] = &cheat_st->cheats[i].rumble_primary_strength;
|
|
data_ptrs[10] = &cheat_st->cheats[i].rumble_primary_duration;
|
|
data_ptrs[11] = &cheat_st->cheats[i].rumble_secondary_strength;
|
|
data_ptrs[12] = &cheat_st->cheats[i].rumble_secondary_duration;
|
|
data_ptrs[13] = &cheat_st->cheats[i].repeat_count;
|
|
data_ptrs[14] = &cheat_st->cheats[i].repeat_add_to_value;
|
|
data_ptrs[15] = &cheat_st->cheats[i].repeat_add_to_address;
|
|
|
|
for (j = 0; j < 16; j++)
|
|
{
|
|
key[0] = '\0';
|
|
snprintf(key, sizeof(key), keys[j], i);
|
|
config_set_uint(conf, key, *(data_ptrs[j]));
|
|
}
|
|
|
|
}
|
|
|
|
ret = config_file_write(conf, cheats_file, true);
|
|
config_file_free(conf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool cheat_manager_working_code_ensure(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->working_code)
|
|
cheat_st->working_code = (char*)calloc(1, CHEAT_CODE_SCRATCH_SIZE);
|
|
return cheat_st->working_code != NULL;
|
|
}
|
|
|
|
bool cheat_manager_copy_idx_to_working(unsigned idx)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || (cheat_st->size < idx + 1))
|
|
return false;
|
|
|
|
if (!cheat_manager_working_code_ensure())
|
|
return false;
|
|
|
|
memcpy(&cheat_st->working_cheat,
|
|
&cheat_st->cheats[idx], sizeof(struct item_cheat));
|
|
|
|
if (cheat_st->cheats[idx].desc)
|
|
strlcpy(cheat_st->working_desc, cheat_st->cheats[idx].desc, CHEAT_DESC_SCRATCH_SIZE);
|
|
else
|
|
cheat_st->working_desc[0] = '\0';
|
|
|
|
if (cheat_st->cheats[idx].code)
|
|
strlcpy(cheat_st->working_code,
|
|
cheat_st->cheats[idx].code,
|
|
CHEAT_CODE_SCRATCH_SIZE);
|
|
else
|
|
cheat_st->working_code[0] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
bool cheat_manager_copy_working_to_idx(unsigned idx)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
char *saved_desc;
|
|
char *saved_code;
|
|
if (!cheat_st->cheats || (cheat_st->size < idx + 1))
|
|
return false;
|
|
|
|
/* Save cheats[idx]'s own desc/code pointers BEFORE the memcpy
|
|
* overwrites them with working_cheat's aliases.
|
|
*
|
|
* working_cheat.desc / .code hold stale or aliased pointers:
|
|
* cheat_manager_copy_idx_to_working does a shallow memcpy
|
|
* from cheats[src] into working_cheat that copies the pointer
|
|
* values without strdup'ing the strings. After that,
|
|
* working_cheat.desc == cheats[src].desc (aliased), or if
|
|
* cheats[src].desc has since been freed / reassigned (e.g. by
|
|
* the user deleting a cheat, adding a new one at the same idx,
|
|
* or by a previous call to this function), working_cheat.desc
|
|
* dangles.
|
|
*
|
|
* The pre-patch code did
|
|
*
|
|
* memcpy(&cheats[idx], &working_cheat, ...);
|
|
* if (cheats[idx].desc) free(cheats[idx].desc);
|
|
* cheats[idx].desc = strdup(working_desc);
|
|
*
|
|
* which freed cheats[idx].desc via the just-memcpy'd pointer.
|
|
* If that pointer aliased working_cheat.desc, we lost track of
|
|
* the original cheats[idx] string (memory leak). Worse, when
|
|
* the user cancelled a cheat edit via menu_cbs_cancel.c:99's
|
|
* cheat_manager_copy_working_to_idx(working_cheat.idx) path
|
|
* after a prior add/delete churn, the dangling working_cheat.
|
|
* desc would be copied into cheats[idx] and then free()'d,
|
|
* producing a double-free or a free of freed memory.
|
|
*
|
|
* Fix: save cheats[idx]'s current desc/code pointers, do the
|
|
* memcpy for the scalar fields, restore the saved desc/code,
|
|
* then free them and reassign from the scratch buffers. This
|
|
* keeps ownership local to cheats[idx] across the whole
|
|
* function regardless of what working_cheat's desc/code
|
|
* happen to point at. */
|
|
saved_desc = cheat_st->cheats[idx].desc;
|
|
saved_code = cheat_st->cheats[idx].code;
|
|
|
|
memcpy(&cheat_st->cheats[idx], &cheat_st->working_cheat,
|
|
sizeof(struct item_cheat));
|
|
|
|
cheat_st->cheats[idx].desc = saved_desc;
|
|
cheat_st->cheats[idx].code = saved_code;
|
|
|
|
if (cheat_st->cheats[idx].desc)
|
|
free(cheat_st->cheats[idx].desc);
|
|
|
|
cheat_st->cheats[idx].desc = strdup(cheat_st->working_desc);
|
|
|
|
if (cheat_st->cheats[idx].code)
|
|
free(cheat_st->cheats[idx].code);
|
|
|
|
cheat_st->cheats[idx].code = strdup(
|
|
cheat_st->working_code ? cheat_st->working_code : "");
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cheat_manager_free(void)
|
|
{
|
|
unsigned i = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
if (cheat_st->cheats)
|
|
{
|
|
for (i = 0; i < cheat_st->size; i++)
|
|
{
|
|
if (cheat_st->cheats[i].desc)
|
|
free(cheat_st->cheats[i].desc);
|
|
if (cheat_st->cheats[i].code)
|
|
free(cheat_st->cheats[i].code);
|
|
cheat_st->cheats[i].desc = NULL;
|
|
cheat_st->cheats[i].code = NULL;
|
|
}
|
|
|
|
free(cheat_st->cheats);
|
|
}
|
|
|
|
if (cheat_st->prev_memory_buf)
|
|
free(cheat_st->prev_memory_buf);
|
|
|
|
if (cheat_st->matches)
|
|
free(cheat_st->matches);
|
|
|
|
if (cheat_st->memory_buf_list)
|
|
free(cheat_st->memory_buf_list);
|
|
|
|
if (cheat_st->memory_size_list)
|
|
free(cheat_st->memory_size_list);
|
|
|
|
cheat_st->cheats = NULL;
|
|
cheat_st->size = 0;
|
|
cheat_st->buf_size = 0;
|
|
cheat_st->prev_memory_buf = NULL;
|
|
cheat_st->curr_memory_buf = NULL;
|
|
cheat_st->memory_buf_list = NULL;
|
|
cheat_st->memory_size_list = NULL;
|
|
cheat_st->matches = NULL;
|
|
cheat_st->num_memory_buffers = 0;
|
|
cheat_st->total_memory_size = 0;
|
|
cheat_st->memory_initialized = false;
|
|
runloop_frame_work_set(RUNLOOP_WORK_CHEATS, false);
|
|
}
|
|
|
|
static void cheat_manager_new(unsigned size)
|
|
{
|
|
unsigned i;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
cheat_manager_free();
|
|
|
|
cheat_st->buf_size = size;
|
|
cheat_st->size = size;
|
|
cheat_st->search_bit_size = 3;
|
|
cheat_st->cheats = (struct item_cheat*)
|
|
calloc(cheat_st->buf_size, sizeof(struct item_cheat));
|
|
|
|
if (!cheat_st->cheats)
|
|
{
|
|
cheat_st->buf_size = 0;
|
|
cheat_st->size = 0;
|
|
cheat_st->cheats = NULL;
|
|
runloop_frame_work_set(RUNLOOP_WORK_CHEATS, false);
|
|
return;
|
|
}
|
|
runloop_frame_work_set(RUNLOOP_WORK_CHEATS, true);
|
|
|
|
for (i = 0; i < cheat_st->size; i++)
|
|
{
|
|
cheat_st->cheats[i].desc = NULL;
|
|
cheat_st->cheats[i].code = NULL;
|
|
cheat_st->cheats[i].state = false;
|
|
cheat_st->cheats[i].repeat_count = 1;
|
|
cheat_st->cheats[i].repeat_add_to_value = 0;
|
|
cheat_st->cheats[i].repeat_add_to_address = 1;
|
|
}
|
|
}
|
|
|
|
static void cheat_manager_load_cb_first_pass(char *key, char *value)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
errno = 0;
|
|
|
|
if (string_is_equal(key, "cheats"))
|
|
{
|
|
cheat_st->loading_cheat_size = (unsigned)strtoul(value, NULL, 0);
|
|
|
|
if (errno != 0)
|
|
cheat_st->loading_cheat_size = 0;
|
|
}
|
|
}
|
|
|
|
static void cheat_manager_load_cb_second_pass(char *s, char *value)
|
|
{
|
|
unsigned cheat_num, cheat_idx;
|
|
char *numend = NULL;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
if (strncmp(s, "cheat", 5) != 0 || s[5] == '\0')
|
|
return;
|
|
|
|
cheat_num = (unsigned)strtoul(&s[5], &numend, 10);
|
|
if (numend == &s[5] || *numend == '\0')
|
|
return;
|
|
|
|
if (cheat_num + cheat_st->loading_cheat_offset >= cheat_st->size)
|
|
return;
|
|
|
|
s = numend + 1;
|
|
|
|
cheat_idx = cheat_num + cheat_st->loading_cheat_offset;
|
|
|
|
if (string_is_equal(s, "address"))
|
|
cheat_st->cheats[cheat_idx].address = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "address_bit_position"))
|
|
cheat_st->cheats[cheat_idx].address_mask = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "big_endian"))
|
|
cheat_st->cheats[cheat_idx].big_endian = (string_is_equal(value, "true") || string_is_equal(value, "1"));
|
|
else if (string_is_equal(s, "cheat_type"))
|
|
cheat_st->cheats[cheat_idx].cheat_type = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "code"))
|
|
cheat_st->cheats[cheat_idx].code = strdup(value);
|
|
else if (string_is_equal(s, "desc"))
|
|
cheat_st->cheats[cheat_idx].desc = strdup(value);
|
|
else if (string_is_equal(s, "enable"))
|
|
cheat_st->cheats[cheat_idx].state = (string_is_equal(value, "true") || string_is_equal(value, "1"));
|
|
else if (string_is_equal(s, "handler"))
|
|
cheat_st->cheats[cheat_idx].handler = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "memory_search_size"))
|
|
cheat_st->cheats[cheat_idx].memory_search_size = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_starts_with_size(s, "repeat_", STRLEN_CONST("repeat_")))
|
|
{
|
|
if (string_is_equal(s, "repeat_add_to_address"))
|
|
cheat_st->cheats[cheat_idx].repeat_add_to_address = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "repeat_add_to_value"))
|
|
cheat_st->cheats[cheat_idx].repeat_add_to_value = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "repeat_count"))
|
|
cheat_st->cheats[cheat_idx].repeat_count = (unsigned)strtoul(value, NULL, 0);
|
|
}
|
|
else if (string_starts_with_size(s, "rumble", STRLEN_CONST("rumble")))
|
|
{
|
|
if (string_is_equal(s, "rumble_port"))
|
|
cheat_st->cheats[cheat_idx].rumble_port = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_primary_duration"))
|
|
cheat_st->cheats[cheat_idx].rumble_primary_duration = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_primary_strength"))
|
|
cheat_st->cheats[cheat_idx].rumble_primary_strength = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_secondary_duration"))
|
|
cheat_st->cheats[cheat_idx].rumble_secondary_duration = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_secondary_strength"))
|
|
cheat_st->cheats[cheat_idx].rumble_secondary_strength = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_type"))
|
|
cheat_st->cheats[cheat_idx].rumble_type = (unsigned)strtoul(value, NULL, 0);
|
|
else if (string_is_equal(s, "rumble_value"))
|
|
cheat_st->cheats[cheat_idx].rumble_value = (unsigned)strtoul(value, NULL, 0);
|
|
}
|
|
else if (string_is_equal(s, "value"))
|
|
cheat_st->cheats[cheat_idx].value = (unsigned)strtoul(value, NULL, 0);
|
|
}
|
|
|
|
bool cheat_manager_load(const char *path, bool append)
|
|
{
|
|
config_file_cb_t cb;
|
|
unsigned orig_size = 0;
|
|
unsigned cheats = 0;
|
|
unsigned i = 0;
|
|
config_file_t *conf = NULL;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
cb.config_file_new_entry_cb = cheat_manager_load_cb_first_pass;
|
|
|
|
cheat_st->loading_cheat_size = 0;
|
|
|
|
conf = config_file_new_with_callback(path, &cb);
|
|
|
|
if (!conf)
|
|
return false;
|
|
|
|
cheats = cheat_st->loading_cheat_size;
|
|
|
|
if (cheats == 0)
|
|
goto error;
|
|
|
|
config_file_free(conf);
|
|
conf = NULL;
|
|
|
|
cheat_manager_alloc_if_empty();
|
|
|
|
if (append)
|
|
{
|
|
orig_size = cheat_manager_get_size();
|
|
if (orig_size == 0)
|
|
cheat_manager_new(cheats);
|
|
else
|
|
{
|
|
cheats = cheats + orig_size;
|
|
if (cheat_manager_realloc(cheats, CHEAT_HANDLER_TYPE_EMU)) { }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
orig_size = 0;
|
|
cheat_manager_new(cheats);
|
|
}
|
|
|
|
for (i = orig_size; cheat_st->cheats && i < cheats; i++)
|
|
{
|
|
cheat_st->cheats[i].idx = i;
|
|
cheat_st->cheats[i].desc = NULL;
|
|
cheat_st->cheats[i].code = NULL;
|
|
cheat_st->cheats[i].state = false;
|
|
cheat_st->cheats[i].big_endian = false;
|
|
cheat_st->cheats[i].cheat_type = CHEAT_TYPE_SET_TO_VALUE;
|
|
cheat_st->cheats[i].memory_search_size = 3;
|
|
}
|
|
|
|
cheat_st->loading_cheat_offset = orig_size;
|
|
cb.config_file_new_entry_cb =
|
|
cheat_manager_load_cb_second_pass;
|
|
conf = config_file_new_with_callback(path, &cb);
|
|
|
|
if (!conf)
|
|
return false;
|
|
|
|
config_file_free(conf);
|
|
|
|
return true;
|
|
|
|
error:
|
|
config_file_free(conf);
|
|
return false;
|
|
}
|
|
|
|
bool cheat_manager_realloc(unsigned new_size, unsigned default_handler)
|
|
{
|
|
unsigned i;
|
|
unsigned orig_size = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
if (!cheat_st->cheats)
|
|
{
|
|
cheat_st->cheats = (struct item_cheat*)
|
|
calloc(new_size, sizeof(struct item_cheat));
|
|
orig_size = 0;
|
|
}
|
|
else
|
|
{
|
|
struct item_cheat *val = NULL;
|
|
orig_size = cheat_st->size;
|
|
|
|
/* if size is decreasing, free the items that will be lost */
|
|
for (i = new_size; i < orig_size; i++)
|
|
{
|
|
if (cheat_st->cheats[i].code)
|
|
free(cheat_st->cheats[i].code);
|
|
if (cheat_st->cheats[i].desc)
|
|
free(cheat_st->cheats[i].desc);
|
|
cheat_st->cheats[i].code = NULL;
|
|
cheat_st->cheats[i].desc = NULL;
|
|
}
|
|
|
|
if (new_size != 0)
|
|
{
|
|
val = (struct item_cheat*)
|
|
realloc(cheat_st->cheats,
|
|
new_size * sizeof(struct item_cheat));
|
|
}
|
|
|
|
/* realloc-to-tmp: on OOM 'val' is NULL, the original
|
|
* cheat_st->cheats is still valid. Pre-patch did
|
|
* 'cheats = val ? val : NULL' which silently leaked the
|
|
* valid old buffer (the pointer was the only reference).
|
|
*
|
|
* Free the old buffer explicitly on failure. Also free
|
|
* per-entry .code/.desc strings for indices still present
|
|
* in the old buffer - the shrink branch above (lines
|
|
* 561-569) already handled [new_size, orig_size) but
|
|
* the [0, min(new_size, orig_size)) entries still hold
|
|
* live strings. Without this cleanup we'd swap one leak
|
|
* (the container) for another (the strings inside).
|
|
*
|
|
* The failure contract here is 'size goes to 0, cheats
|
|
* cleared' (see the 'if (!cheat_st->cheats)' block below
|
|
* that sets size=0 and buf_size=0, and caller checks
|
|
* at line ~515 that gate on 'cheat_st->cheats &&'). */
|
|
if (val)
|
|
cheat_st->cheats = val;
|
|
else
|
|
{
|
|
unsigned keep = (new_size < orig_size) ? new_size : orig_size;
|
|
for (i = 0; i < keep; i++)
|
|
{
|
|
if (cheat_st->cheats[i].code)
|
|
free(cheat_st->cheats[i].code);
|
|
if (cheat_st->cheats[i].desc)
|
|
free(cheat_st->cheats[i].desc);
|
|
}
|
|
free(cheat_st->cheats);
|
|
cheat_st->cheats = NULL;
|
|
}
|
|
}
|
|
|
|
if (!cheat_st->cheats)
|
|
{
|
|
cheat_st->buf_size = cheat_st->size = 0;
|
|
cheat_st->cheats = NULL;
|
|
runloop_frame_work_set(RUNLOOP_WORK_CHEATS, false);
|
|
return false;
|
|
}
|
|
runloop_frame_work_set(RUNLOOP_WORK_CHEATS, true);
|
|
|
|
cheat_st->buf_size = new_size;
|
|
cheat_st->size = new_size;
|
|
|
|
for (i = orig_size; i < cheat_st->size; i++)
|
|
{
|
|
memset(&cheat_st->cheats[i], 0, sizeof(cheat_st->cheats[i]));
|
|
cheat_st->cheats[i].state = false;
|
|
cheat_st->cheats[i].handler = default_handler;
|
|
cheat_st->cheats[i].cheat_type = CHEAT_TYPE_SET_TO_VALUE;
|
|
cheat_st->cheats[i].memory_search_size = 3;
|
|
cheat_st->cheats[i].idx = i;
|
|
cheat_st->cheats[i].repeat_count = 1;
|
|
cheat_st->cheats[i].repeat_add_to_value = 0;
|
|
cheat_st->cheats[i].repeat_add_to_address = 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void cheat_manager_update(cheat_manager_t *handle, unsigned handle_idx)
|
|
{
|
|
size_t _len;
|
|
char msg[256];
|
|
enum message_queue_category msg_cat = MESSAGE_QUEUE_CATEGORY_INFO;
|
|
|
|
if (!handle || !handle->cheats || handle->size == 0)
|
|
return;
|
|
|
|
_len = snprintf(msg, sizeof(msg),
|
|
"%s #%u: %s: %s",
|
|
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CHEAT),
|
|
handle_idx,
|
|
handle->cheats[handle_idx].desc
|
|
? (handle->cheats[handle_idx].desc)
|
|
: (handle->cheats[handle_idx].code),
|
|
handle->cheats[handle_idx].state
|
|
? msg_hash_to_str(MENU_ENUM_LABEL_ON)
|
|
: msg_hash_to_str(MENU_ENUM_LABEL_OFF)
|
|
);
|
|
|
|
msg_cat = handle->cheats[handle_idx].state
|
|
? MESSAGE_QUEUE_CATEGORY_SUCCESS : MESSAGE_QUEUE_CATEGORY_ERROR;
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, msg_cat);
|
|
RARCH_LOG("[Cheats] %s\n", msg);
|
|
}
|
|
|
|
void cheat_manager_toggle_index(
|
|
bool apply_cheats_after_toggle,
|
|
bool notification_show_cheats_applied,
|
|
unsigned i)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || cheat_st->size == 0)
|
|
return;
|
|
|
|
cheat_st->cheats[i].state = !cheat_st->cheats[i].state;
|
|
|
|
if (notification_show_cheats_applied)
|
|
cheat_manager_update(cheat_st, i);
|
|
|
|
if (apply_cheats_after_toggle)
|
|
cheat_manager_apply_cheats(notification_show_cheats_applied);
|
|
}
|
|
|
|
void cheat_manager_toggle(bool notification_show_cheats_applied)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || cheat_st->size == 0)
|
|
return;
|
|
|
|
cheat_st->cheats[cheat_st->ptr].state ^= true;
|
|
cheat_manager_apply_cheats(notification_show_cheats_applied);
|
|
|
|
if (notification_show_cheats_applied)
|
|
cheat_manager_update(cheat_st, cheat_st->ptr);
|
|
}
|
|
|
|
void cheat_manager_index_next(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || cheat_st->size == 0)
|
|
return;
|
|
|
|
cheat_st->ptr = (cheat_st->ptr + 1) % cheat_st->size;
|
|
cheat_manager_update(cheat_st, cheat_st->ptr);
|
|
}
|
|
|
|
void cheat_manager_index_prev(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats || cheat_st->size == 0)
|
|
return;
|
|
|
|
if (cheat_st->ptr == 0)
|
|
cheat_st->ptr = cheat_st->size - 1;
|
|
else
|
|
cheat_st->ptr--;
|
|
|
|
cheat_manager_update(cheat_st, cheat_st->ptr);
|
|
}
|
|
|
|
const char *cheat_manager_get_code(unsigned i)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats)
|
|
return NULL;
|
|
return cheat_st->cheats[i].code;
|
|
}
|
|
|
|
const char *cheat_manager_get_desc(unsigned i)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats)
|
|
return NULL;
|
|
return cheat_st->cheats[i].desc;
|
|
}
|
|
|
|
bool cheat_manager_get_code_state(unsigned i)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats)
|
|
return false;
|
|
return cheat_st->cheats[i].state;
|
|
}
|
|
|
|
static size_t cheat_manager_get_game_specific_filename(
|
|
char *s, size_t len,
|
|
const char *path_cheat_database, bool saving)
|
|
{
|
|
char s1[PATH_MAX_LENGTH];
|
|
struct retro_system_info sysinfo;
|
|
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
|
const char *core_name = NULL;
|
|
const char *game_name = NULL;
|
|
if (!core_get_system_info(&sysinfo))
|
|
return 0;
|
|
core_name = sysinfo.library_name;
|
|
game_name = path_basename_nocompression(runloop_st->name.cheatfile);
|
|
if ( (!path_cheat_database || !*path_cheat_database)
|
|
|| (!core_name || !*core_name)
|
|
|| (!game_name || !*game_name))
|
|
return 0;
|
|
fill_pathname_join_special(s1, path_cheat_database, core_name,
|
|
sizeof(s1));
|
|
if (saving)
|
|
{
|
|
/* Check if directory is valid, if not, create it */
|
|
if (!path_is_valid(s1))
|
|
path_mkdir(s1);
|
|
}
|
|
return fill_pathname_join_special(s, s1, game_name, len);
|
|
}
|
|
|
|
void cheat_manager_load_game_specific_cheats(const char *path_cheat_database)
|
|
{
|
|
char cheat_file[PATH_MAX_LENGTH];
|
|
|
|
if (cheat_manager_get_game_specific_filename(
|
|
cheat_file, sizeof(cheat_file),
|
|
path_cheat_database,
|
|
false))
|
|
{
|
|
if (cheat_manager_load(cheat_file, true))
|
|
RARCH_LOG("[Cheats] Load game-specific cheatfile: \"%s\".\n", cheat_file);
|
|
}
|
|
}
|
|
|
|
void cheat_manager_save_game_specific_cheats(const char *path_cheat_database)
|
|
{
|
|
char cheat_file[PATH_MAX_LENGTH];
|
|
|
|
if (cheat_manager_get_game_specific_filename(
|
|
cheat_file, sizeof(cheat_file),
|
|
path_cheat_database,
|
|
true))
|
|
{
|
|
if (cheat_manager_save(cheat_file, NULL, true))
|
|
RARCH_LOG("[Cheats] Save game-specific cheatfile: \"%s\".\n", cheat_file);
|
|
}
|
|
}
|
|
|
|
void cheat_manager_state_free(void)
|
|
{
|
|
cheat_manager_free();
|
|
}
|
|
|
|
void cheat_manager_alloc_if_empty(void)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
if (!cheat_st->cheats)
|
|
cheat_manager_new(0);
|
|
}
|
|
|
|
/* The core memory region an address lies in, kept across a walk so
|
|
* that only a step out of it searches the region list again */
|
|
typedef struct
|
|
{
|
|
unsigned char *base;
|
|
unsigned start;
|
|
unsigned len;
|
|
} cheat_region_t;
|
|
|
|
/* Points @r at the region holding @size bytes from @address; false
|
|
* when they do not lie within one region. */
|
|
static bool cheat_region_seek(cheat_region_t *r, unsigned address,
|
|
unsigned size)
|
|
{
|
|
unsigned i;
|
|
unsigned start = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
|
|
if ( r->base
|
|
&& address - r->start < r->len
|
|
&& size <= r->len - (address - r->start))
|
|
return true;
|
|
|
|
for (i = 0; i < cheat_st->num_memory_buffers; i++)
|
|
{
|
|
unsigned len = cheat_st->memory_size_list[i];
|
|
if (address - start < len)
|
|
{
|
|
if (size > len - (address - start))
|
|
return false;
|
|
r->base = cheat_st->memory_buf_list[i];
|
|
r->start = start;
|
|
r->len = len;
|
|
return true;
|
|
}
|
|
start += len;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void cheat_manager_setup_search_meta(
|
|
unsigned int bitsize,
|
|
unsigned int *bytes_per_item,
|
|
unsigned int *mask,
|
|
unsigned int *bits);
|
|
|
|
int cheat_manager_initialize_memory(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
unsigned i;
|
|
retro_ctx_memory_info_t meminfo;
|
|
bool is_search_initialization = (setting != NULL);
|
|
rarch_system_info_t *sys_info = &runloop_state_get_ptr()->system;
|
|
unsigned offset = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
#ifdef HAVE_MENU
|
|
struct menu_state *menu_st = menu_state_get_ptr();
|
|
#endif
|
|
|
|
cheat_st->num_memory_buffers = 0;
|
|
cheat_st->total_memory_size = 0;
|
|
cheat_st->curr_memory_buf = NULL;
|
|
|
|
if (cheat_st->memory_buf_list)
|
|
{
|
|
free(cheat_st->memory_buf_list);
|
|
cheat_st->memory_buf_list = NULL;
|
|
}
|
|
|
|
if (cheat_st->memory_size_list)
|
|
{
|
|
free(cheat_st->memory_size_list);
|
|
cheat_st->memory_size_list = NULL;
|
|
}
|
|
|
|
if (sys_info && sys_info->mmaps.num_descriptors > 0)
|
|
{
|
|
for (i = 0; i < sys_info->mmaps.num_descriptors; i++)
|
|
{
|
|
if ((sys_info->mmaps.descriptors[i].core.flags
|
|
& RETRO_MEMDESC_SYSTEM_RAM) != 0
|
|
&& sys_info->mmaps.descriptors[i].core.ptr
|
|
&& sys_info->mmaps.descriptors[i].core.len > 0)
|
|
{
|
|
/* Grow both lists atomically and bail on OOM.
|
|
* Pre-patch:
|
|
* - num_memory_buffers++ was done first
|
|
* - memory_buf_list = realloc(memory_buf_list, ...)
|
|
* self-assigned on OOM (leak + NULL-deref at [i])
|
|
* - memory_size_list used realloc-into-tmp with a
|
|
* NULL-check but ignored the failure, which left
|
|
* the array one element too small while
|
|
* num_memory_buffers claimed the new size -
|
|
* heap buffer overflow on the [num-1] write
|
|
* below.
|
|
*
|
|
* Fix: compute new_count and grow both lists into
|
|
* temp pointers before incrementing num_memory_buffers.
|
|
* Skip this descriptor on OOM (continue outer loop);
|
|
* the loop's post-check 'if (cheat_st->num_memory_
|
|
* buffers == 0)' then falls through to the
|
|
* RETRO_MEMORY_SYSTEM_RAM fallback path below. */
|
|
unsigned new_count = cheat_st->num_memory_buffers + 1;
|
|
uint8_t **new_bufs;
|
|
unsigned *new_sizes;
|
|
|
|
if (!cheat_st->memory_buf_list)
|
|
new_bufs = (uint8_t**)calloc(1, sizeof(uint8_t *));
|
|
else
|
|
new_bufs = (uint8_t**)realloc(
|
|
cheat_st->memory_buf_list,
|
|
sizeof(uint8_t *) * new_count);
|
|
|
|
if (!new_bufs)
|
|
continue;
|
|
cheat_st->memory_buf_list = new_bufs;
|
|
|
|
if (!cheat_st->memory_size_list)
|
|
new_sizes = (unsigned*)calloc(1, sizeof(unsigned));
|
|
else
|
|
new_sizes = (unsigned*)realloc(
|
|
cheat_st->memory_size_list,
|
|
sizeof(unsigned) * new_count);
|
|
|
|
if (!new_sizes)
|
|
continue;
|
|
cheat_st->memory_size_list = new_sizes;
|
|
|
|
cheat_st->num_memory_buffers = new_count;
|
|
cheat_st->memory_buf_list[new_count - 1] = (uint8_t*)sys_info->mmaps.descriptors[i].core.ptr;
|
|
cheat_st->memory_size_list[new_count - 1] = (unsigned)sys_info->mmaps.descriptors[i].core.len;
|
|
cheat_st->total_memory_size += sys_info->mmaps.descriptors[i].core.len;
|
|
|
|
if (!cheat_st->curr_memory_buf)
|
|
cheat_st->curr_memory_buf = (uint8_t*)sys_info->mmaps.descriptors[i].core.ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cheat_st->num_memory_buffers == 0)
|
|
{
|
|
meminfo.id = RETRO_MEMORY_SYSTEM_RAM;
|
|
if (!core_get_memory(&meminfo))
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_INIT_FAIL), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
|
|
if (meminfo.size == 0)
|
|
return 0;
|
|
|
|
cheat_st->memory_buf_list = (uint8_t**)
|
|
calloc(1, sizeof(uint8_t *));
|
|
cheat_st->memory_size_list = (unsigned*)
|
|
calloc(1, sizeof(unsigned));
|
|
/* NULL-check both callocs: the [0] writes below NULL-deref
|
|
* on OOM. If either failed, free whichever succeeded and
|
|
* bail out to MSG_CHEAT_INIT_FAIL with the same path as
|
|
* the meminfo failure above. */
|
|
if (!cheat_st->memory_buf_list || !cheat_st->memory_size_list)
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_INIT_FAIL), sizeof(msg));
|
|
free(cheat_st->memory_buf_list);
|
|
cheat_st->memory_buf_list = NULL;
|
|
free(cheat_st->memory_size_list);
|
|
cheat_st->memory_size_list = NULL;
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
cheat_st->num_memory_buffers = 1;
|
|
cheat_st->memory_buf_list[0] = (uint8_t*)meminfo.data;
|
|
cheat_st->memory_size_list[0] = (unsigned)meminfo.size;
|
|
cheat_st->total_memory_size = (unsigned)meminfo.size;
|
|
cheat_st->curr_memory_buf = (uint8_t*)meminfo.data;
|
|
|
|
}
|
|
|
|
cheat_st->num_matches = (cheat_st->total_memory_size * 8) / (1 << cheat_st->search_bit_size);
|
|
|
|
if (is_search_initialization)
|
|
{
|
|
if (cheat_st->prev_memory_buf)
|
|
{
|
|
free(cheat_st->prev_memory_buf);
|
|
cheat_st->prev_memory_buf = NULL;
|
|
}
|
|
|
|
cheat_st->prev_memory_buf = (uint8_t*)calloc(
|
|
cheat_st->total_memory_size, sizeof(uint8_t));
|
|
|
|
if (!cheat_st->prev_memory_buf)
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_INIT_FAIL), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
|
|
if (cheat_st->matches)
|
|
{
|
|
free(cheat_st->matches);
|
|
cheat_st->matches = NULL;
|
|
}
|
|
|
|
cheat_st->matches = (uint8_t*)calloc(
|
|
cheat_st->total_memory_size, sizeof(uint8_t));
|
|
|
|
if (!cheat_st->matches)
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_INIT_FAIL), sizeof(msg));
|
|
free(cheat_st->prev_memory_buf);
|
|
cheat_st->prev_memory_buf = NULL;
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
|
|
memset(cheat_st->matches, 0xFF, cheat_st->total_memory_size);
|
|
|
|
/* an item crossing a region's end cannot be read: never a match */
|
|
{
|
|
cheat_region_t region = { NULL, 0, 0 };
|
|
unsigned bytes_per_item = 1;
|
|
unsigned mask = 0;
|
|
unsigned bits = 8;
|
|
unsigned addr;
|
|
cheat_manager_setup_search_meta(cheat_st->search_bit_size,
|
|
&bytes_per_item, &mask, &bits);
|
|
for (addr = 0; addr < cheat_st->total_memory_size;
|
|
addr += bytes_per_item)
|
|
{
|
|
if (cheat_region_seek(®ion, addr, bytes_per_item))
|
|
continue;
|
|
memset(cheat_st->matches + addr, 0,
|
|
MIN(bytes_per_item, cheat_st->total_memory_size - addr));
|
|
if ( addr + bytes_per_item <= cheat_st->total_memory_size
|
|
&& cheat_st->num_matches > 0)
|
|
cheat_st->num_matches--;
|
|
}
|
|
}
|
|
|
|
offset = 0;
|
|
|
|
for (i = 0; i < cheat_st->num_memory_buffers; i++)
|
|
{
|
|
memcpy(cheat_st->prev_memory_buf + offset,
|
|
cheat_st->memory_buf_list[i],
|
|
cheat_st->memory_size_list[i]);
|
|
offset += cheat_st->memory_size_list[i];
|
|
}
|
|
|
|
{
|
|
char msg[128];
|
|
size_t _len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_INIT_SUCCESS), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
}
|
|
}
|
|
|
|
cheat_st->memory_initialized = true;
|
|
|
|
#ifdef HAVE_MENU
|
|
if (!wraparound)
|
|
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
|
|
| MENU_ST_FLAG_PREVENT_POPULATE;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cheat_manager_setup_search_meta(
|
|
unsigned int bitsize,
|
|
unsigned int *bytes_per_item,
|
|
unsigned int *mask,
|
|
unsigned int *bits)
|
|
{
|
|
switch (bitsize)
|
|
{
|
|
case 0:
|
|
*bytes_per_item = 1;
|
|
*bits = 1;
|
|
*mask = 0x01;
|
|
break;
|
|
case 1:
|
|
*bytes_per_item = 1;
|
|
*bits = 2;
|
|
*mask = 0x03;
|
|
break;
|
|
case 2:
|
|
*bytes_per_item = 1;
|
|
*bits = 4;
|
|
*mask = 0x0F;
|
|
break;
|
|
case 3:
|
|
*bytes_per_item = 1;
|
|
*bits = 8;
|
|
*mask = 0xFF;
|
|
break;
|
|
case 4:
|
|
*bytes_per_item = 2;
|
|
*bits = 8;
|
|
*mask = 0xFFFF;
|
|
break;
|
|
case 5:
|
|
*bytes_per_item = 4;
|
|
*bits = 8;
|
|
*mask = 0xFFFFFFFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int cheat_manager_search(enum cheat_search_type search_type)
|
|
{
|
|
size_t _len;
|
|
char msg[100];
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
cheat_region_t region = { NULL, 0, 0 };
|
|
unsigned char *curr = NULL;
|
|
unsigned char *prev = cheat_st->prev_memory_buf;
|
|
unsigned int idx = 0;
|
|
unsigned int curr_val = 0;
|
|
unsigned int prev_val = 0;
|
|
unsigned int mask = 0;
|
|
unsigned int bytes_per_item = 1;
|
|
unsigned int bits = 8;
|
|
unsigned int offset = 0;
|
|
unsigned int i = 0;
|
|
#ifdef HAVE_MENU
|
|
struct menu_state *menu_st = menu_state_get_ptr();
|
|
#endif
|
|
|
|
if (cheat_st->num_memory_buffers == 0 || !prev || !cheat_st->matches)
|
|
{
|
|
_len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_SEARCH_NOT_INITIALIZED), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
|
|
cheat_manager_setup_search_meta(cheat_st->search_bit_size, &bytes_per_item, &mask, &bits);
|
|
|
|
/* little endian FF000000 = 256 */
|
|
for (idx = 0; idx < cheat_st->total_memory_size; idx = idx + bytes_per_item)
|
|
{
|
|
unsigned byte_part;
|
|
|
|
if (!cheat_region_seek(®ion, idx, bytes_per_item))
|
|
continue;
|
|
curr = region.base;
|
|
offset = region.start;
|
|
|
|
switch (bytes_per_item)
|
|
{
|
|
case 2:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256) + *(curr + idx + 1 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256);
|
|
prev_val = cheat_st->big_endian ?
|
|
(*(prev + idx) * 256) + *(prev + idx + 1) :
|
|
*(prev + idx) + (*(prev + idx + 1) * 256);
|
|
break;
|
|
case 4:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256 * 256 * 256) + (*(curr + idx + 1 - offset) * 256 * 256) + (*(curr + idx + 2 - offset) * 256) + *(curr + idx + 3 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256) + (*(curr + idx + 2 - offset) * 256 * 256) + (*(curr + idx + 3 - offset) * 256 * 256 * 256);
|
|
prev_val = cheat_st->big_endian ?
|
|
(*(prev + idx) * 256 * 256 * 256) + (*(prev + idx + 1) * 256 * 256) + (*(prev + idx + 2) * 256) + *(prev + idx + 3) :
|
|
*(prev + idx) + (*(prev + idx + 1) * 256) + (*(prev + idx + 2) * 256 * 256) + (*(prev + idx + 3) * 256 * 256 * 256);
|
|
break;
|
|
case 1:
|
|
default:
|
|
curr_val = *(curr - offset + idx);
|
|
prev_val = *(prev + idx);
|
|
break;
|
|
}
|
|
|
|
for (byte_part = 0; byte_part < 8 / bits; byte_part++)
|
|
{
|
|
unsigned int curr_subval = (curr_val >> (byte_part * bits)) & mask;
|
|
unsigned int prev_subval = (prev_val >> (byte_part * bits)) & mask;
|
|
unsigned int prev_match;
|
|
|
|
if (bits < 8)
|
|
prev_match = *(cheat_st->matches + idx) & (mask << (byte_part * bits));
|
|
else
|
|
prev_match = *(cheat_st->matches + idx);
|
|
|
|
if (prev_match > 0)
|
|
{
|
|
bool match = false;
|
|
switch (search_type)
|
|
{
|
|
case CHEAT_SEARCH_TYPE_EXACT:
|
|
match = (curr_subval == cheat_st->search_exact_value);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_LT:
|
|
match = (curr_subval < prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_GT:
|
|
match = (curr_subval > prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_LTE:
|
|
match = (curr_subval <= prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_GTE:
|
|
match = (curr_subval >= prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_EQ:
|
|
match = (curr_subval == prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_NEQ:
|
|
match = (curr_subval != prev_subval);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_EQPLUS:
|
|
match = (curr_subval == prev_subval + cheat_st->search_eqplus_value);
|
|
break;
|
|
case CHEAT_SEARCH_TYPE_EQMINUS:
|
|
match = (curr_subval == prev_subval - cheat_st->search_eqminus_value);
|
|
break;
|
|
}
|
|
|
|
if (!match)
|
|
{
|
|
if (bits < 8)
|
|
*(cheat_st->matches + idx) = *(cheat_st->matches + idx) &
|
|
((~(mask << (byte_part * bits))) & 0xFF);
|
|
else
|
|
memset(cheat_st->matches + idx, 0, bytes_per_item);
|
|
if (cheat_st->num_matches > 0)
|
|
cheat_st->num_matches--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
offset = 0;
|
|
|
|
for (i = 0; i < cheat_st->num_memory_buffers; i++)
|
|
{
|
|
memcpy(cheat_st->prev_memory_buf + offset, cheat_st->memory_buf_list[i], cheat_st->memory_size_list[i]);
|
|
offset += cheat_st->memory_size_list[i];
|
|
}
|
|
|
|
_len = snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEAT_SEARCH_FOUND_MATCHES), cheat_st->num_matches);
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
|
|
#ifdef HAVE_MENU
|
|
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
|
|
| MENU_ST_FLAG_PREVENT_POPULATE;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int cheat_manager_search_exact(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_EXACT);
|
|
}
|
|
|
|
static void cheat_manager_search_input_cb_common(
|
|
const char *line,
|
|
unsigned *target,
|
|
enum cheat_search_type search_type)
|
|
{
|
|
char *end = NULL;
|
|
unsigned long value = 0;
|
|
unsigned max_value = 0;
|
|
|
|
if (!line || !*line)
|
|
{
|
|
#ifdef HAVE_MENU
|
|
menu_input_dialog_end();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
errno = 0;
|
|
value = strtoul(line, &end, 0);
|
|
|
|
if (errno || end == line)
|
|
{
|
|
#ifdef HAVE_MENU
|
|
menu_input_dialog_end();
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
max_value = cheat_manager_get_state_search_size(
|
|
cheat_manager_state.search_bit_size);
|
|
|
|
if (value > max_value)
|
|
value = max_value;
|
|
|
|
*target = (unsigned)value;
|
|
|
|
#ifdef HAVE_MENU
|
|
menu_input_dialog_end();
|
|
#endif
|
|
|
|
cheat_manager_search(search_type);
|
|
}
|
|
|
|
static void cheat_manager_search_exact_input_cb(void *userdata,
|
|
const char *line)
|
|
{
|
|
cheat_manager_search_input_cb_common(
|
|
line,
|
|
&cheat_manager_state.search_exact_value,
|
|
CHEAT_SEARCH_TYPE_EXACT);
|
|
}
|
|
|
|
static void cheat_manager_search_eqplus_input_cb(void *userdata,
|
|
const char *line)
|
|
{
|
|
cheat_manager_search_input_cb_common(
|
|
line,
|
|
&cheat_manager_state.search_eqplus_value,
|
|
CHEAT_SEARCH_TYPE_EQPLUS);
|
|
}
|
|
|
|
static void cheat_manager_search_eqminus_input_cb(void *userdata,
|
|
const char *line)
|
|
{
|
|
cheat_manager_search_input_cb_common(
|
|
line,
|
|
&cheat_manager_state.search_eqminus_value,
|
|
CHEAT_SEARCH_TYPE_EQMINUS);
|
|
}
|
|
|
|
static int cheat_manager_search_input_start(
|
|
rarch_setting_t *setting,
|
|
size_t idx,
|
|
bool wraparound,
|
|
unsigned current_value,
|
|
enum msg_hash_enums label_value,
|
|
enum msg_hash_enums label,
|
|
input_keyboard_line_complete_t cb)
|
|
{
|
|
#ifdef HAVE_MENU
|
|
char value_buf[32];
|
|
menu_input_ctx_line_t line;
|
|
|
|
memset(&line, 0, sizeof(line));
|
|
|
|
snprintf(value_buf, sizeof(value_buf), "%u", current_value);
|
|
|
|
line.label = msg_hash_to_str(label_value);
|
|
line.label_setting = value_buf;
|
|
line.type = label;
|
|
line.idx = (unsigned)idx;
|
|
/* Not KB_TYPE_NUMBER: cheat_manager_search_input_cb_common() parses
|
|
* with strtoul(base 0), so '0x1f' is valid input here and a numeric
|
|
* keypad has no 'x' or 'a'-'f' to type it with. */
|
|
line.text_type = MENU_INPUT_DIALOG_KB_TYPE_TEXT;
|
|
line.cb = cb;
|
|
|
|
if (menu_input_dialog_start(&line))
|
|
return 0;
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
int cheat_manager_search_exact_input(rarch_setting_t *setting,
|
|
size_t idx, bool wraparound)
|
|
{
|
|
if (cheat_manager_search_input_start(
|
|
setting,
|
|
idx,
|
|
wraparound,
|
|
cheat_manager_state.search_exact_value,
|
|
MENU_ENUM_LABEL_VALUE_CHEAT_SEARCH_EXACT,
|
|
MENU_ENUM_LABEL_CHEAT_SEARCH_EXACT,
|
|
cheat_manager_search_exact_input_cb) == 0)
|
|
return 0;
|
|
|
|
return cheat_manager_search_exact(setting, idx, wraparound);
|
|
}
|
|
|
|
int cheat_manager_search_eqplus_input(rarch_setting_t *setting,
|
|
size_t idx, bool wraparound)
|
|
{
|
|
if (cheat_manager_search_input_start(
|
|
setting,
|
|
idx,
|
|
wraparound,
|
|
cheat_manager_state.search_eqplus_value,
|
|
MENU_ENUM_LABEL_VALUE_CHEAT_SEARCH_EQPLUS,
|
|
MENU_ENUM_LABEL_CHEAT_SEARCH_EQPLUS,
|
|
cheat_manager_search_eqplus_input_cb) == 0)
|
|
return 0;
|
|
|
|
return cheat_manager_search_eqplus(setting, idx, wraparound);
|
|
}
|
|
|
|
int cheat_manager_search_eqminus_input(rarch_setting_t *setting,
|
|
size_t idx, bool wraparound)
|
|
{
|
|
if (cheat_manager_search_input_start(
|
|
setting,
|
|
idx,
|
|
wraparound,
|
|
cheat_manager_state.search_eqminus_value,
|
|
MENU_ENUM_LABEL_VALUE_CHEAT_SEARCH_EQMINUS,
|
|
MENU_ENUM_LABEL_CHEAT_SEARCH_EQMINUS,
|
|
cheat_manager_search_eqminus_input_cb) == 0)
|
|
return 0;
|
|
|
|
return cheat_manager_search_eqminus(setting, idx, wraparound);
|
|
}
|
|
|
|
int cheat_manager_search_lt(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_LT);
|
|
}
|
|
|
|
int cheat_manager_search_gt(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_GT);
|
|
}
|
|
|
|
int cheat_manager_search_lte(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_LTE);
|
|
}
|
|
|
|
int cheat_manager_search_gte(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_GTE);
|
|
}
|
|
|
|
int cheat_manager_search_eq(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_EQ);
|
|
}
|
|
|
|
int cheat_manager_search_neq(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_NEQ);
|
|
}
|
|
|
|
int cheat_manager_search_eqplus(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_EQPLUS);
|
|
}
|
|
|
|
int cheat_manager_search_eqminus(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
return cheat_manager_search(CHEAT_SEARCH_TYPE_EQMINUS);
|
|
}
|
|
|
|
unsigned cheat_manager_get_state_search_size(unsigned search_size)
|
|
{
|
|
uint32_t n[] = {1,3,15,255,0x0000ffff,0xffffffff};
|
|
return n[search_size];
|
|
}
|
|
|
|
bool cheat_manager_add_new_code(unsigned int memory_search_size, unsigned int address, unsigned int address_mask,
|
|
bool big_endian, unsigned int value)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
int new_size = cheat_manager_get_size() + 1;
|
|
|
|
if (!cheat_manager_realloc(new_size, CHEAT_HANDLER_TYPE_RETRO))
|
|
return false;
|
|
|
|
cheat_st->cheats[cheat_st->size - 1].address = address;
|
|
cheat_st->cheats[cheat_st->size - 1].address_mask = address_mask;
|
|
cheat_st->cheats[cheat_st->size - 1].memory_search_size = memory_search_size;
|
|
cheat_st->cheats[cheat_st->size - 1].value = value;
|
|
cheat_st->cheats[cheat_st->size - 1].big_endian = big_endian;
|
|
|
|
return true;
|
|
}
|
|
|
|
int cheat_manager_add_matches(const char *path,
|
|
const char *label, unsigned type, size_t menuidx, size_t entry_idx)
|
|
{
|
|
size_t _len;
|
|
char msg[100];
|
|
unsigned byte_part = 0;
|
|
unsigned int idx = 0;
|
|
unsigned int mask = 0;
|
|
unsigned int bytes_per_item = 1;
|
|
unsigned int bits = 8;
|
|
unsigned int curr_val = 0;
|
|
unsigned int offset = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
cheat_region_t region = { NULL, 0, 0 };
|
|
unsigned char *curr = NULL;
|
|
#ifdef HAVE_MENU
|
|
struct menu_state *menu_st = menu_state_get_ptr();
|
|
#endif
|
|
|
|
if (cheat_st->num_matches + cheat_st->size > 100)
|
|
{
|
|
_len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_SEARCH_ADDED_MATCHES_TOO_MANY), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
|
|
return 0;
|
|
}
|
|
cheat_manager_setup_search_meta(cheat_st->search_bit_size, &bytes_per_item, &mask, &bits);
|
|
|
|
for (idx = 0; idx < cheat_st->total_memory_size; idx = idx + bytes_per_item)
|
|
{
|
|
if (!cheat_region_seek(®ion, idx, bytes_per_item))
|
|
continue;
|
|
curr = region.base;
|
|
offset = region.start;
|
|
|
|
switch (bytes_per_item)
|
|
{
|
|
case 2:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256) + *(curr + idx + 1 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256);
|
|
break;
|
|
case 4:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256 * 256 * 256) + (*(curr + idx + 1 - offset) * 256 * 256) + (*(curr + idx + 2 - offset) * 256) + *(curr + idx + 3 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256) + (*(curr + idx + 2 - offset) * 256 * 256) + (*(curr + idx + 3 - offset) * 256 * 256 * 256);
|
|
break;
|
|
case 1:
|
|
default:
|
|
curr_val = *(curr - offset + idx);
|
|
break;
|
|
}
|
|
for (byte_part = 0; byte_part < 8 / bits; byte_part++)
|
|
{
|
|
unsigned int prev_match;
|
|
|
|
if (bits < 8)
|
|
{
|
|
prev_match = *(cheat_st->matches + idx) & (mask << (byte_part * bits));
|
|
if (prev_match)
|
|
{
|
|
if (!cheat_manager_add_new_code(cheat_st->search_bit_size, idx, (mask << (byte_part * bits)),
|
|
cheat_st->big_endian, curr_val))
|
|
{
|
|
_len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_SEARCH_ADDED_MATCHES_FAIL), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
prev_match = *(cheat_st->matches + idx);
|
|
if (prev_match)
|
|
{
|
|
if (!cheat_manager_add_new_code(cheat_st->search_bit_size, idx, 0xFF,
|
|
cheat_st->big_endian, curr_val))
|
|
{
|
|
_len = strlcpy(msg, msg_hash_to_str(MSG_CHEAT_SEARCH_ADDED_MATCHES_FAIL), sizeof(msg));
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
_len = snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_CHEAT_SEARCH_ADDED_MATCHES_SUCCESS), cheat_st->num_matches);
|
|
|
|
runloop_msg_queue_push(msg, _len, 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
|
|
#ifdef HAVE_MENU
|
|
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
|
|
| MENU_ST_FLAG_PREVENT_POPULATE;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void cheat_manager_apply_rumble(struct item_cheat *cheat,
|
|
unsigned int curr_value, retro_time_t current_time)
|
|
{
|
|
bool rumble = false;
|
|
|
|
switch (cheat->rumble_type)
|
|
{
|
|
case RUMBLE_TYPE_DISABLED:
|
|
return;
|
|
case RUMBLE_TYPE_CHANGES:
|
|
rumble = (curr_value != cheat->rumble_prev_value);
|
|
break;
|
|
case RUMBLE_TYPE_DOES_NOT_CHANGE:
|
|
rumble = (curr_value == cheat->rumble_prev_value);
|
|
break;
|
|
case RUMBLE_TYPE_INCREASE:
|
|
rumble = (curr_value > cheat->rumble_prev_value);
|
|
break;
|
|
case RUMBLE_TYPE_DECREASE:
|
|
rumble = (curr_value < cheat->rumble_prev_value);
|
|
break;
|
|
case RUMBLE_TYPE_EQ_VALUE:
|
|
rumble = (curr_value == cheat->rumble_value);
|
|
break;
|
|
case RUMBLE_TYPE_NEQ_VALUE:
|
|
rumble = (curr_value != cheat->rumble_value);
|
|
break;
|
|
case RUMBLE_TYPE_LT_VALUE:
|
|
rumble = (curr_value < cheat->rumble_value);
|
|
break;
|
|
case RUMBLE_TYPE_GT_VALUE:
|
|
rumble = (curr_value > cheat->rumble_value);
|
|
break;
|
|
case RUMBLE_TYPE_INCREASE_BY_VALUE:
|
|
rumble = (curr_value == cheat->rumble_prev_value + cheat->rumble_value);
|
|
break;
|
|
case RUMBLE_TYPE_DECREASE_BY_VALUE:
|
|
rumble = (curr_value == cheat->rumble_prev_value - cheat->rumble_value);
|
|
break;
|
|
}
|
|
|
|
cheat->rumble_prev_value = curr_value;
|
|
|
|
/* Give the emulator enough time
|
|
* to initialize, load state, etc */
|
|
if (cheat->rumble_initialized > 300)
|
|
{
|
|
if (rumble)
|
|
{
|
|
cheat->rumble_primary_end_time = current_time + (cheat->rumble_primary_duration * 1000);
|
|
cheat->rumble_secondary_end_time = current_time + (cheat->rumble_secondary_duration * 1000);
|
|
input_set_rumble_state(cheat->rumble_port, RETRO_RUMBLE_STRONG, cheat->rumble_primary_strength);
|
|
input_set_rumble_state(cheat->rumble_port, RETRO_RUMBLE_WEAK, cheat->rumble_secondary_strength);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cheat->rumble_initialized++;
|
|
return;
|
|
}
|
|
|
|
if (cheat->rumble_primary_end_time <= current_time)
|
|
{
|
|
if (cheat->rumble_primary_end_time != 0)
|
|
input_set_rumble_state(cheat->rumble_port,
|
|
RETRO_RUMBLE_STRONG, 0);
|
|
cheat->rumble_primary_end_time = 0;
|
|
}
|
|
else
|
|
{
|
|
input_set_rumble_state(cheat->rumble_port,
|
|
RETRO_RUMBLE_STRONG, cheat->rumble_primary_strength);
|
|
}
|
|
|
|
if (cheat->rumble_secondary_end_time <= current_time)
|
|
{
|
|
if (cheat->rumble_secondary_end_time != 0)
|
|
input_set_rumble_state(cheat->rumble_port, RETRO_RUMBLE_WEAK, 0);
|
|
cheat->rumble_secondary_end_time = 0;
|
|
}
|
|
else
|
|
input_set_rumble_state(cheat->rumble_port, RETRO_RUMBLE_WEAK, cheat->rumble_secondary_strength);
|
|
}
|
|
|
|
void cheat_manager_apply_retro_cheats(void)
|
|
{
|
|
unsigned i;
|
|
unsigned int offset;
|
|
cheat_region_t region = { NULL, 0, 0 };
|
|
unsigned int mask = 0;
|
|
unsigned int bytes_per_item = 1;
|
|
unsigned int bits = 8;
|
|
unsigned int curr_val = 0;
|
|
bool run_cheat = true;
|
|
#ifdef HAVE_CHEEVOS
|
|
bool cheat_applied = false;
|
|
#endif
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
retro_time_t current_time;
|
|
|
|
if ((!cheat_st->cheats))
|
|
return;
|
|
|
|
/* One reading for the whole pass: every cheat in it is applied at
|
|
* the same instant, and the clock is a syscall on more than one
|
|
* platform - a large cheat file would otherwise pay for one per
|
|
* entry, per frame. */
|
|
current_time = cpu_features_get_time_usec();
|
|
|
|
for (i = 0; i < cheat_st->size; i++)
|
|
{
|
|
unsigned char *curr = NULL;
|
|
bool set_value = false;
|
|
unsigned int idx = 0;
|
|
unsigned int value_to_set = 0;
|
|
unsigned int repeat_iter = 0;
|
|
unsigned int address_mask = cheat_st->cheats[i].address_mask;
|
|
|
|
if (cheat_st->cheats[i].handler != CHEAT_HANDLER_TYPE_RETRO || !cheat_st->cheats[i].state)
|
|
continue;
|
|
if (!cheat_st->memory_initialized)
|
|
cheat_manager_initialize_memory(NULL, 0, false);
|
|
|
|
/* If we're still not initialized, something
|
|
* must have gone wrong - just bail */
|
|
if (!cheat_st->memory_initialized)
|
|
return;
|
|
|
|
if (!run_cheat)
|
|
{
|
|
run_cheat = true;
|
|
continue;
|
|
}
|
|
cheat_manager_setup_search_meta(cheat_st->cheats[i].memory_search_size, &bytes_per_item, &mask, &bits);
|
|
|
|
idx = cheat_st->cheats[i].address;
|
|
|
|
if (!cheat_region_seek(®ion, idx, bytes_per_item))
|
|
{
|
|
/* a condition that cannot be read does not hold */
|
|
if (cheat_st->cheats[i].cheat_type >= CHEAT_TYPE_RUN_NEXT_IF_EQ)
|
|
run_cheat = false;
|
|
continue;
|
|
}
|
|
curr = region.base;
|
|
offset = region.start;
|
|
|
|
switch (bytes_per_item)
|
|
{
|
|
case 2:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256) + *(curr + idx + 1 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256);
|
|
break;
|
|
case 4:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256 * 256 * 256) + (*(curr + idx + 1 - offset) * 256 * 256) + (*(curr + idx + 2 - offset) * 256) + *(curr + idx + 3 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256) + (*(curr + idx + 2 - offset) * 256 * 256) + (*(curr + idx + 3 - offset) * 256 * 256 * 256);
|
|
break;
|
|
case 1:
|
|
default:
|
|
curr_val = *(curr + idx - offset);
|
|
break;
|
|
}
|
|
|
|
cheat_manager_apply_rumble(&cheat_st->cheats[i], curr_val,
|
|
current_time);
|
|
|
|
switch (cheat_st->cheats[i].cheat_type)
|
|
{
|
|
case CHEAT_TYPE_SET_TO_VALUE:
|
|
set_value = true;
|
|
value_to_set = cheat_st->cheats[i].value;
|
|
break;
|
|
case CHEAT_TYPE_INCREASE_VALUE:
|
|
set_value = true;
|
|
value_to_set = curr_val + cheat_st->cheats[i].value;
|
|
break;
|
|
case CHEAT_TYPE_DECREASE_VALUE:
|
|
set_value = true;
|
|
value_to_set = curr_val - cheat_st->cheats[i].value;
|
|
break;
|
|
case CHEAT_TYPE_RUN_NEXT_IF_EQ:
|
|
if (!(curr_val == cheat_st->cheats[i].value))
|
|
run_cheat = false;
|
|
break;
|
|
case CHEAT_TYPE_RUN_NEXT_IF_NEQ:
|
|
if (!(curr_val != cheat_st->cheats[i].value))
|
|
run_cheat = false;
|
|
break;
|
|
case CHEAT_TYPE_RUN_NEXT_IF_LT:
|
|
if (!(cheat_st->cheats[i].value < curr_val))
|
|
run_cheat = false;
|
|
break;
|
|
case CHEAT_TYPE_RUN_NEXT_IF_GT:
|
|
if (!(cheat_st->cheats[i].value > curr_val))
|
|
run_cheat = false;
|
|
break;
|
|
}
|
|
|
|
if (set_value)
|
|
{
|
|
#ifdef HAVE_CHEEVOS
|
|
cheat_applied = true;
|
|
#endif
|
|
for (repeat_iter = 1; repeat_iter <= cheat_st->cheats[i].repeat_count; repeat_iter++)
|
|
{
|
|
if (!cheat_region_seek(®ion, idx, bytes_per_item))
|
|
break;
|
|
curr = region.base;
|
|
offset = region.start;
|
|
|
|
switch (bytes_per_item)
|
|
{
|
|
case 2:
|
|
if (cheat_st->cheats[i].big_endian)
|
|
{
|
|
*(curr + idx - offset) = (value_to_set >> 8) & 0xFF;
|
|
*(curr + idx + 1 - offset) = value_to_set & 0xFF;
|
|
}
|
|
else
|
|
{
|
|
*(curr + idx - offset) = value_to_set & 0xFF;
|
|
*(curr + idx + 1 - offset) = (value_to_set >> 8) & 0xFF;
|
|
}
|
|
break;
|
|
case 4:
|
|
if (cheat_st->cheats[i].big_endian)
|
|
{
|
|
*(curr + idx - offset) = (value_to_set >> 24) & 0xFF;
|
|
*(curr + idx + 1 - offset) = (value_to_set >> 16) & 0xFF;
|
|
*(curr + idx + 2 - offset) = (value_to_set >> 8) & 0xFF;
|
|
*(curr + idx + 3 - offset) = value_to_set & 0xFF;
|
|
}
|
|
else
|
|
{
|
|
*(curr + idx - offset) = value_to_set & 0xFF;
|
|
*(curr + idx + 1 - offset) = (value_to_set >> 8) & 0xFF;
|
|
*(curr + idx + 2 - offset) = (value_to_set >> 16) & 0xFF;
|
|
*(curr + idx + 3 - offset) = (value_to_set >> 24) & 0xFF;
|
|
}
|
|
break;
|
|
case 1:
|
|
if (bits < 8)
|
|
{
|
|
unsigned bitpos;
|
|
unsigned char val = *(curr + idx - offset);
|
|
|
|
for (bitpos = 0; bitpos < 8; bitpos++)
|
|
{
|
|
if ((address_mask >> bitpos) & 0x01)
|
|
{
|
|
mask = (~(1 << bitpos) & 0xFF);
|
|
/* Clear current bit value */
|
|
val = val & mask;
|
|
/* Inject cheat bit value */
|
|
val = val | (((value_to_set >> bitpos) & 0x01) << bitpos);
|
|
}
|
|
}
|
|
|
|
*(curr + idx - offset) = val;
|
|
}
|
|
else
|
|
*(curr + idx - offset) = value_to_set & 0xFF;
|
|
break;
|
|
default:
|
|
*(curr + idx - offset) = value_to_set & 0xFF;
|
|
break;
|
|
}
|
|
|
|
value_to_set += cheat_st->cheats[i].repeat_add_to_value;
|
|
|
|
if (mask != 0)
|
|
value_to_set = value_to_set % mask;
|
|
|
|
if (bits < 8)
|
|
{
|
|
unsigned int bit_iter;
|
|
for (bit_iter = 0; bit_iter < cheat_st->cheats[i].repeat_add_to_address; bit_iter++)
|
|
{
|
|
address_mask = (address_mask << mask) & 0xFF;
|
|
|
|
if (address_mask == 0)
|
|
{
|
|
address_mask = mask;
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
idx += (cheat_st->cheats[i].repeat_add_to_address * bytes_per_item);
|
|
|
|
idx = idx % cheat_st->total_memory_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_CHEEVOS
|
|
if (cheat_applied && rcheevos_hardcore_active())
|
|
cheat_manager_pause_cheevos();
|
|
#endif
|
|
}
|
|
|
|
void cheat_manager_match_action(enum cheat_match_action_type match_action, unsigned int target_match_idx, unsigned int *address, unsigned int *address_mask,
|
|
unsigned int *prev_value, unsigned int *curr_value)
|
|
{
|
|
unsigned int byte_part;
|
|
unsigned int idx;
|
|
unsigned int start_idx;
|
|
unsigned int mask = 0;
|
|
unsigned int bytes_per_item = 1;
|
|
unsigned int bits = 8;
|
|
unsigned int curr_val = 0;
|
|
unsigned int prev_val = 0;
|
|
unsigned int offset = 0;
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
cheat_region_t region = { NULL, 0, 0 };
|
|
unsigned char *curr = NULL;
|
|
unsigned char *prev = cheat_st->prev_memory_buf;
|
|
unsigned int curr_match_idx = 0;
|
|
|
|
if (target_match_idx > cheat_st->num_matches - 1)
|
|
return;
|
|
|
|
if (cheat_st->num_memory_buffers == 0)
|
|
return;
|
|
|
|
cheat_manager_setup_search_meta(cheat_st->search_bit_size, &bytes_per_item, &mask, &bits);
|
|
|
|
if (match_action == CHEAT_MATCH_ACTION_TYPE_BROWSE)
|
|
start_idx = *address;
|
|
else
|
|
start_idx = 0;
|
|
|
|
for (idx = start_idx; idx < cheat_st->total_memory_size; idx = idx + bytes_per_item)
|
|
{
|
|
if (!cheat_region_seek(®ion, idx, bytes_per_item))
|
|
{
|
|
if (match_action == CHEAT_MATCH_ACTION_TYPE_BROWSE)
|
|
return;
|
|
continue;
|
|
}
|
|
curr = region.base;
|
|
offset = region.start;
|
|
|
|
switch (bytes_per_item)
|
|
{
|
|
case 2:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256) + *(curr + idx + 1 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256);
|
|
if (prev)
|
|
prev_val = cheat_st->big_endian ?
|
|
(*(prev + idx) * 256) + *(prev + idx + 1) :
|
|
*(prev + idx) + (*(prev + idx + 1) * 256);
|
|
break;
|
|
case 4:
|
|
curr_val = cheat_st->big_endian ?
|
|
(*(curr + idx - offset) * 256 * 256 * 256) + (*(curr + idx + 1 - offset) * 256 * 256) + (*(curr + idx + 2 - offset) * 256) + *(curr + idx + 3 - offset) :
|
|
*(curr + idx - offset) + (*(curr + idx + 1 - offset) * 256) + (*(curr + idx + 2 - offset) * 256 * 256) + (*(curr + idx + 3 - offset) * 256 * 256 * 256);
|
|
if (prev)
|
|
prev_val = cheat_st->big_endian ?
|
|
(*(prev + idx) * 256 * 256 * 256) + (*(prev + idx + 1) * 256 * 256) + (*(prev + idx + 2) * 256) + *(prev + idx + 3) :
|
|
*(prev + idx) + (*(prev + idx + 1) * 256) + (*(prev + idx + 2) * 256 * 256) + (*(prev + idx + 3) * 256 * 256 * 256);
|
|
break;
|
|
case 1:
|
|
default:
|
|
curr_val = *(curr + idx - offset);
|
|
if (prev)
|
|
prev_val = *(prev + idx);
|
|
break;
|
|
}
|
|
|
|
if (match_action == CHEAT_MATCH_ACTION_TYPE_BROWSE)
|
|
{
|
|
*curr_value = curr_val;
|
|
*prev_value = prev_val;
|
|
return;
|
|
}
|
|
|
|
if (!prev)
|
|
return;
|
|
|
|
for (byte_part = 0; byte_part < 8 / bits; byte_part++)
|
|
{
|
|
unsigned int prev_match;
|
|
|
|
if (bits < 8)
|
|
{
|
|
prev_match = *(cheat_st->matches + idx) & (mask << (byte_part * bits));
|
|
if (prev_match)
|
|
{
|
|
if (target_match_idx == curr_match_idx)
|
|
{
|
|
switch (match_action)
|
|
{
|
|
case CHEAT_MATCH_ACTION_TYPE_BROWSE:
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_VIEW:
|
|
*address = idx;
|
|
*address_mask = (mask << (byte_part * bits));
|
|
*curr_value = curr_val;
|
|
*prev_value = prev_val;
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_COPY:
|
|
if (!cheat_manager_add_new_code(cheat_st->search_bit_size, idx, (mask << (byte_part * bits)),
|
|
cheat_st->big_endian, curr_val))
|
|
{
|
|
const char *_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_ADD_MATCH_FAIL);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
|
|
}
|
|
else
|
|
{
|
|
const char *_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_ADD_MATCH_SUCCESS);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
|
|
}
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_DELETE:
|
|
{
|
|
const char *_msg;
|
|
if (bits < 8)
|
|
*(cheat_st->matches + idx) = *(cheat_st->matches + idx) &
|
|
((~(mask << (byte_part * bits))) & 0xFF);
|
|
else
|
|
memset(cheat_st->matches + idx, 0, bytes_per_item);
|
|
if (cheat_st->num_matches > 0)
|
|
cheat_st->num_matches--;
|
|
_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_DELETE_MATCH_SUCCESS);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
|
|
}
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
curr_match_idx++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
prev_match = *(cheat_st->matches + idx);
|
|
if (prev_match)
|
|
{
|
|
if (target_match_idx == curr_match_idx)
|
|
{
|
|
switch (match_action)
|
|
{
|
|
case CHEAT_MATCH_ACTION_TYPE_BROWSE:
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_VIEW:
|
|
*address = idx;
|
|
*address_mask = 0xFF;
|
|
*curr_value = curr_val;
|
|
*prev_value = prev_val;
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_COPY:
|
|
if (!cheat_manager_add_new_code(cheat_st->search_bit_size, idx, 0xFF,
|
|
cheat_st->big_endian, curr_val))
|
|
{
|
|
const char *_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_ADD_MATCH_FAIL);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
}
|
|
else
|
|
{
|
|
const char *_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_ADD_MATCH_SUCCESS);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
}
|
|
return;
|
|
case CHEAT_MATCH_ACTION_TYPE_DELETE:
|
|
{
|
|
const char *_msg;
|
|
if (bits < 8)
|
|
*(cheat_st->matches + idx) = *(cheat_st->matches + idx) &
|
|
((~(mask << (byte_part * bits))) & 0xFF);
|
|
else
|
|
memset(cheat_st->matches + idx, 0, bytes_per_item);
|
|
if (cheat_st->num_matches > 0)
|
|
cheat_st->num_matches--;
|
|
_msg = msg_hash_to_str(MSG_CHEAT_SEARCH_DELETE_MATCH_SUCCESS);
|
|
runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
|
|
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
curr_match_idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int cheat_manager_copy_match(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
cheat_manager_match_action(CHEAT_MATCH_ACTION_TYPE_COPY,
|
|
cheat_st->match_idx, NULL, NULL, NULL, NULL);
|
|
return 0;
|
|
}
|
|
|
|
int cheat_manager_delete_match(rarch_setting_t *setting, size_t idx, bool wraparound)
|
|
{
|
|
cheat_manager_t *cheat_st = &cheat_manager_state;
|
|
#ifdef HAVE_MENU
|
|
struct menu_state *menu_st = menu_state_get_ptr();
|
|
#endif
|
|
cheat_manager_match_action(CHEAT_MATCH_ACTION_TYPE_DELETE,
|
|
cheat_st->match_idx, NULL, NULL, NULL, NULL);
|
|
#ifdef HAVE_MENU
|
|
menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH
|
|
| MENU_ST_FLAG_PREVENT_POPULATE;
|
|
#endif
|
|
return 0;
|
|
}
|