Files
RetroArch/cheat_manager.c
T
LibretroAdmin cdf84534e2 cheat_manager: search by region, skip ruled-out items, one loop per test
A memory search visited every item of the core's memory in turn: a
region lookup, a branch on item size, a loop over sub-items that at
byte size and wider runs once, and a branch on the search type, even
for items an earlier search had already ruled out - after a search or
two, nearly all of them. Each failing item also cost a memset call,
and the match count was reloaded and stored every item, since stores
through the uint8_t match bytes may alias it.

Now:

- Regions are walked in order, items at multiples of their size lying
  whole inside one, as before, without a lookup per item.
- Runs of clear match bytes are stepped over 16 bytes at a time with
  SSE2, 32 with AVX2 or 64 with AVX-512BW (chosen at run time from
  cpu_features_get(); built per function, and under PlayStation clang
  or clang-cl only where the baseline enables them), or a word at a
  time elsewhere. An item is still judged by its first match byte: a
  set byte part-way through one, which a change of item size between
  searches can leave, rounds back to the item's start.
- At byte, 16- and 32-bit sizes the search type is chosen once and
  each type gets its own loop, with direct stores for a failing item
  and the count held in a local. The 1-, 2- and 4-bit sizes keep the
  general path.

32-bit values were assembled as byte * 256 * 256 * 256 in int, which
overflows (undefined) for a top byte of 0x80 or more; they use
unsigned shifts now, the same value.

33 MB of memory on an AVX-512 Xeon, before -> after:

  8-bit items:  first search 270-344 -> 45-48 ms,
                after one search 146-188 -> 91-126 ms,
                after two 128-162 -> 9-12 ms
  32-bit items: first search 75-125 -> 18-80 ms,
                later searches 49-66 -> 5-11 ms

samples/cheats/cheat_search keeps the previous algorithm as its
reference and checks the match bytes, the match count and the saved
memory after each of 14400 chained searches: random region layouts,
every item size, both byte orders, every search type, memory changed
between searches in runs or scattered, the item size changed without
starting over, and the scan capped at each SIMD tier in turn.
samples/cheats/cheat_regions reports no SIMD, so it runs the baseline
scan.
2026-10-28 05:43:40 +01:00

2451 lines
81 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 <retro_environment.h>
#include <compat/intrinsics.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(&region, 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;
}
}
/* First address from @idx, stepping by @step, below @limit whose match
* byte is set; @limit when there is none. Clear bytes are scanned over
* a machine word at a time (16, 32 or 64 bytes with SSE2, AVX2 or
* AVX-512BW), since after a search or two nearly all of them are clear.
* An item is judged by its first match byte alone, as the search does,
* so a set byte part-way through an item rounds back to the item's
* start and is looked at there. */
#if defined(__SSE2__) && (defined(__x86_64__) || defined(__i386__) \
|| defined(_M_X64) || defined(_M_IX86))
#include <emmintrin.h>
#define CHEAT_SCAN_SSE2 1
#if defined(_MSC_VER) && !defined(__clang__)
#if _MSC_VER >= 1911
#define CHEAT_SCAN_WIDE 1
#define CHEAT_SCAN_TARGET_AVX2
#define CHEAT_SCAN_TARGET_AVX512
#endif
#elif defined(__clang__) && (defined(__SCE__) || defined(_MSC_VER))
/* PlayStation clang and clang-cl declare only the intrinsics of ISAs
* the whole build enables: a wide tier only where the baseline has it. */
#if defined(__AVX2__) && defined(__AVX512F__) && defined(__AVX512BW__)
#define CHEAT_SCAN_WIDE 1
#define CHEAT_SCAN_TARGET_AVX2
#define CHEAT_SCAN_TARGET_AVX512
#endif
#elif (defined(__clang__) && __clang_major__ >= 4) \
|| (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 5)
#define CHEAT_SCAN_WIDE 1
#define CHEAT_SCAN_TARGET_AVX2 __attribute__((target("avx2")))
#define CHEAT_SCAN_TARGET_AVX512 __attribute__((target("avx512f,avx512bw")))
#endif
#endif
#ifdef CHEAT_SCAN_WIDE
#include <immintrin.h>
/* Index of the first set byte in [i, limit), or limit. */
CHEAT_SCAN_TARGET_AVX2
static unsigned cheat_scan_avx2(const uint8_t *m, unsigned i, unsigned limit)
{
while (limit - i >= 32)
{
unsigned zero = (unsigned)_mm256_movemask_epi8(_mm256_cmpeq_epi8(
_mm256_loadu_si256((const __m256i*)(m + i)),
_mm256_setzero_si256()));
if (zero != 0xffffffffu)
return i + (unsigned)compat_ctz(~zero);
i += 32;
}
return i;
}
CHEAT_SCAN_TARGET_AVX512
static unsigned cheat_scan_avx512(const uint8_t *m, unsigned i, unsigned limit)
{
while (limit - i >= 64)
{
__mmask64 set = _mm512_test_epi8_mask(
_mm512_loadu_si512((const void*)(m + i)),
_mm512_set1_epi8((char)0xff));
if (set)
return i + (unsigned)compat_ctz_u64((uint64_t)set);
i += 64;
}
return i;
}
#ifdef CHEAT_SCAN_TEST_HOOKS
/* -1 picks by CPU; otherwise caps the tier for a test. */
int cheat_scan_tier_cap = -1;
#endif
/* 0 = SSE2, 1 = AVX2, 2 = AVX-512BW; looked up on the first search,
* which runs on the main thread like every search. */
static int cheat_scan_tier(void)
{
static int tier = -1;
if (tier < 0)
{
uint64_t cpu = cpu_features_get();
tier = (cpu & RETRO_SIMD_AVX512) ? 2 : (cpu & RETRO_SIMD_AVX2) ? 1 : 0;
}
#ifdef CHEAT_SCAN_TEST_HOOKS
if (cheat_scan_tier_cap >= 0 && tier > cheat_scan_tier_cap)
return cheat_scan_tier_cap;
#endif
return tier;
}
#endif
static unsigned cheat_skip_unmatched(const uint8_t *m, unsigned idx,
unsigned limit, unsigned step)
{
unsigned i = idx;
for (;;)
{
if (i >= limit)
return limit;
#ifdef CHEAT_SCAN_WIDE
switch (cheat_scan_tier())
{
case 2:
i = cheat_scan_avx512(m, i, limit);
break;
case 1:
i = cheat_scan_avx2(m, i, limit);
break;
default:
break;
}
#endif
#ifdef CHEAT_SCAN_SSE2
while (limit - i >= 16)
{
unsigned zero = (unsigned)_mm_movemask_epi8(_mm_cmpeq_epi8(
_mm_loadu_si128((const __m128i*)(m + i)),
_mm_setzero_si128()));
if (zero != 0xffffu)
{
i += (unsigned)compat_ctz(~zero & 0xffffu);
break;
}
i += 16;
}
#else
while (limit - i >= sizeof(uint32_t))
{
uint32_t w;
memcpy(&w, m + i, sizeof(w));
if (w)
break;
i += sizeof(uint32_t);
}
#endif
while (i < limit && !m[i])
i++;
if (i >= limit)
return limit;
/* Back to the start of the item holding byte i. */
i -= (i - idx) % step;
if (m[i])
return i;
i += step;
}
}
/* An item's value at byte, 16- or 32-bit size in the given order. */
static INLINE unsigned cheat_read_item(const unsigned char *p,
unsigned bytes, bool big_endian)
{
switch (bytes)
{
case 2:
return big_endian ? ((unsigned)p[0] << 8) | p[1]
: p[0] | ((unsigned)p[1] << 8);
case 4:
return big_endian
? ((unsigned)p[0] << 24) | ((unsigned)p[1] << 16)
| ((unsigned)p[2] << 8) | p[3]
: p[0] | ((unsigned)p[1] << 8)
| ((unsigned)p[2] << 16) | ((unsigned)p[3] << 24);
default:
break;
}
return p[0];
}
/* One region's items at byte size or wider, where an item is one
* match: the search type is chosen once, outside the loop. Same rule as
* the general path - an item still in the running that fails the test
* has its match bytes cleared and the count lowered. */
#define CHEAT_SEARCH_ITEMS(COND) \
for (;; idx += bytes) \
{ \
unsigned cs, ps; \
if (idx >= end || end - idx < bytes) \
break; \
if (!matches[idx]) \
{ \
idx = cheat_skip_unmatched(matches, idx, end - bytes + 1, bytes); \
if (idx >= end || end - idx < bytes) \
break; \
} \
cs = cheat_read_item(curr + (idx - start), bytes, big_endian); \
ps = cheat_read_item(prev + idx, bytes, big_endian); \
(void)ps; /* unread by an exact-value search */ \
if (!(COND)) \
{ \
switch (bytes) \
{ \
case 4: \
matches[idx + 3] = 0; \
matches[idx + 2] = 0; \
/* fall through */ \
case 2: \
matches[idx + 1] = 0; \
/* fall through */ \
default: \
matches[idx] = 0; \
break; \
} \
if (num > 0) \
num--; \
} \
}
static void cheat_search_whole_items(enum cheat_search_type type,
const unsigned char *curr, unsigned start, unsigned end,
unsigned bytes, bool big_endian, uint8_t *matches,
const unsigned char *prev, unsigned *num_matches,
unsigned exact, unsigned plus, unsigned minus)
{
unsigned idx = (start + bytes - 1) / bytes * bytes;
/* Counted in a local: stores through the uint8_t match array may
* alias anything, so the count would be reloaded every item. */
unsigned num = *num_matches;
switch (type)
{
case CHEAT_SEARCH_TYPE_EXACT: CHEAT_SEARCH_ITEMS(cs == exact) break;
case CHEAT_SEARCH_TYPE_LT: CHEAT_SEARCH_ITEMS(cs < ps) break;
case CHEAT_SEARCH_TYPE_GT: CHEAT_SEARCH_ITEMS(cs > ps) break;
case CHEAT_SEARCH_TYPE_LTE: CHEAT_SEARCH_ITEMS(cs <= ps) break;
case CHEAT_SEARCH_TYPE_GTE: CHEAT_SEARCH_ITEMS(cs >= ps) break;
case CHEAT_SEARCH_TYPE_EQ: CHEAT_SEARCH_ITEMS(cs == ps) break;
case CHEAT_SEARCH_TYPE_NEQ: CHEAT_SEARCH_ITEMS(cs != ps) break;
case CHEAT_SEARCH_TYPE_EQPLUS: CHEAT_SEARCH_ITEMS(cs == ps + plus) break;
case CHEAT_SEARCH_TYPE_EQMINUS: CHEAT_SEARCH_ITEMS(cs == ps - minus) break;
}
*num_matches = num;
}
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;
unsigned char *curr = NULL;
unsigned char *prev = cheat_st->prev_memory_buf;
unsigned int r = 0;
unsigned int region_start = 0;
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);
/* Region by region: an item is searched only where it lies whole
* inside one region, at addresses that are multiples of its size.
* An item whose match byte is clear was ruled out by an earlier
* search and cannot match again, so runs of them are stepped over
* by cheat_skip_unmatched rather than visited one at a time; after
* the first search or two that is nearly all of memory.
* little endian FF000000 = 256 */
for (r = 0, region_start = 0; r < cheat_st->num_memory_buffers;
region_start += cheat_st->memory_size_list[r], r++)
{
unsigned region_end = region_start + cheat_st->memory_size_list[r];
curr = cheat_st->memory_buf_list[r];
offset = region_start;
idx = (region_start + bytes_per_item - 1)
/ bytes_per_item * bytes_per_item;
if (bits == 8)
{
cheat_search_whole_items(search_type, curr, region_start,
region_end, bytes_per_item, cheat_st->big_endian,
cheat_st->matches, prev, &cheat_st->num_matches,
cheat_st->search_exact_value,
cheat_st->search_eqplus_value,
cheat_st->search_eqminus_value);
continue;
}
for (;; idx += bytes_per_item)
{
unsigned byte_part;
if (region_end - idx < bytes_per_item || idx >= region_end)
break;
if (!cheat_st->matches[idx])
{
idx = cheat_skip_unmatched(cheat_st->matches, idx,
region_end - bytes_per_item + 1, bytes_per_item);
if (idx >= region_end || region_end - idx < bytes_per_item)
break;
}
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:
/* Unsigned shifts: a top byte of 0x80 or more times 2^24 in
* int overflowed, which is undefined. Same value. */
curr_val = cheat_st->big_endian ?
((unsigned)curr[idx - offset] << 24) | ((unsigned)curr[idx + 1 - offset] << 16)
| ((unsigned)curr[idx + 2 - offset] << 8) | curr[idx + 3 - offset] :
curr[idx - offset] | ((unsigned)curr[idx + 1 - offset] << 8)
| ((unsigned)curr[idx + 2 - offset] << 16) | ((unsigned)curr[idx + 3 - offset] << 24);
prev_val = cheat_st->big_endian ?
((unsigned)prev[idx] << 24) | ((unsigned)prev[idx + 1] << 16)
| ((unsigned)prev[idx + 2] << 8) | prev[idx + 3] :
prev[idx] | ((unsigned)prev[idx + 1] << 8)
| ((unsigned)prev[idx + 2] << 16) | ((unsigned)prev[idx + 3] << 24);
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(&region, 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(&region, 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(&region, 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(&region, 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;
}