mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
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.
This commit is contained in:
1 parent
09a110512b
commit
cdf84534e2
4 files changed
+659
-12
No files matched your search
+283
-12
@@ -27,6 +27,8 @@
|
||||
#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
|
||||
@@ -1193,14 +1195,246 @@ static void cheat_manager_setup_search_meta(
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
cheat_region_t region = { NULL, 0, 0 };
|
||||
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;
|
||||
@@ -1223,16 +1457,46 @@ static int cheat_manager_search(enum cheat_search_type search_type)
|
||||
|
||||
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)
|
||||
/* 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 (!cheat_region_seek(®ion, idx, bytes_per_item))
|
||||
continue;
|
||||
curr = region.base;
|
||||
offset = region.start;
|
||||
|
||||
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:
|
||||
@@ -1244,12 +1508,18 @@ static int cheat_manager_search(enum cheat_search_type search_type)
|
||||
*(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 ?
|
||||
(*(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);
|
||||
((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 ?
|
||||
(*(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);
|
||||
((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:
|
||||
@@ -1315,6 +1585,7 @@ static int cheat_manager_search(enum cheat_search_type search_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
|
||||
@@ -30,6 +30,9 @@ bool core_get_system_info(struct retro_system_info *info) { (void)info; return f
|
||||
bool core_set_cheat(retro_ctx_cheat_info_t *info) { (void)info; return true; }
|
||||
bool core_reset_cheat(void) { return true; }
|
||||
retro_time_t cpu_features_get_time_usec(void) { return 0; }
|
||||
/* No SIMD reported: the search scans with its baseline path here;
|
||||
* samples/cheats/cheat_search runs the wider ones. */
|
||||
uint64_t cpu_features_get(void) { return 0; }
|
||||
bool input_set_rumble_state(unsigned port, enum retro_rumble_effect e,
|
||||
uint16_t strength) { (void)port; (void)e; (void)strength; return true; }
|
||||
config_file_t *config_file_new_from_path_to_string(const char *p) { (void)p; return NULL; }
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
include ../../sample_objdir.mk
|
||||
|
||||
TARGET := cheat_search_test
|
||||
|
||||
REPO_ROOT := ../../..
|
||||
LRC := $(REPO_ROOT)/libretro-common
|
||||
|
||||
SOURCES := cheat_search_test.c \
|
||||
$(REPO_ROOT)/cheat_manager.c \
|
||||
$(LRC)/compat/compat_strl.c \
|
||||
$(LRC)/string/stdstring.c \
|
||||
$(LRC)/encodings/encoding_utf.c \
|
||||
$(LRC)/features/features_cpu.c
|
||||
|
||||
# cheat_manager.h reaches setting_list.h as ../setting_list.h
|
||||
CFLAGS += -Wall -std=gnu99 -g1 -O0 -DRARCH_INTERNAL -DHAVE_CHEATS -DCHEAT_SCAN_TEST_HOOKS \
|
||||
-I$(REPO_ROOT) -I$(REPO_ROOT)/menu -I$(LRC)/include
|
||||
|
||||
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
check: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGET) $(TARGET).exe $(OBJS)
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,328 @@
|
||||
/* cheat_manager.c's memory search against the algorithm it replaced,
|
||||
* kept here as the reference: random core memory maps (regions of
|
||||
* sizes that are not multiples of 2 or 4), every item size (1, 2 and 4
|
||||
* bits, bytes, 16- and 32-bit words), both byte orders, every search
|
||||
* type, the item size changed between searches without starting over,
|
||||
* chained searches with memory changing between them - in runs,
|
||||
* scattered, or not at all - and after each search the match bytes,
|
||||
* the match count and the saved copy of memory must agree exactly.
|
||||
* features_cpu.c is linked for real and the whole run repeats with the
|
||||
* match-byte scan capped at each tier (SSE2, AVX2, AVX-512BW), so every
|
||||
* one this CPU has is checked. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "cheat_manager.h"
|
||||
#include "runloop.h"
|
||||
#include "core.h"
|
||||
#include "msg_hash.h"
|
||||
#include "verbosity.h"
|
||||
|
||||
/* What cheat_manager.c calls outside itself */
|
||||
static runloop_state_t test_runloop;
|
||||
runloop_state_t *runloop_state_get_ptr(void) { return &test_runloop; }
|
||||
void runloop_msg_queue_push(const char *msg, size_t len, unsigned prio,
|
||||
unsigned duration, bool flush, char *title,
|
||||
enum message_queue_icon icon, enum message_queue_category category)
|
||||
{ (void)msg; (void)len; (void)prio; (void)duration; (void)flush;
|
||||
(void)title; (void)icon; (void)category; }
|
||||
void runloop_frame_work_set(unsigned work, bool on) { (void)work; (void)on; }
|
||||
const char *msg_hash_to_str(enum msg_hash_enums msg) { (void)msg; return ""; }
|
||||
bool core_get_memory(retro_ctx_memory_info_t *info) { (void)info; return false; }
|
||||
bool core_get_system_info(struct retro_system_info *info) { (void)info; return false; }
|
||||
bool core_set_cheat(retro_ctx_cheat_info_t *info) { (void)info; return true; }
|
||||
bool core_reset_cheat(void) { return true; }
|
||||
bool input_set_rumble_state(unsigned port, enum retro_rumble_effect e,
|
||||
uint16_t strength) { (void)port; (void)e; (void)strength; return true; }
|
||||
config_file_t *config_file_new_from_path_to_string(const char *p) { (void)p; return NULL; }
|
||||
config_file_t *config_file_new_alloc(void) { return NULL; }
|
||||
config_file_t *config_file_new_with_callback(const char *p,
|
||||
config_file_cb_t *cb) { (void)p; (void)cb; return NULL; }
|
||||
void config_file_free(config_file_t *c) { (void)c; }
|
||||
bool config_file_write(config_file_t *c, const char *p, bool s)
|
||||
{ (void)c; (void)p; (void)s; return false; }
|
||||
size_t config_set_int(config_file_t *c, const char *k, int v) { (void)c; (void)k; (void)v; return 0; }
|
||||
size_t config_set_uint(config_file_t *c, const char *k, unsigned v) { (void)c; (void)k; (void)v; return 0; }
|
||||
void config_set_string(config_file_t *c, const char *k, const char *v) { (void)c; (void)k; (void)v; }
|
||||
int filestream_delete(const char *p) { (void)p; return -1; }
|
||||
size_t fill_pathname_join(char *o, const char *d, const char *p, size_t n)
|
||||
{ (void)d; (void)p; if (n) *o = '\0'; return 0; }
|
||||
const char *path_basename_nocompression(const char *p) { return p; }
|
||||
bool path_is_valid(const char *p) { (void)p; return false; }
|
||||
bool path_mkdir(const char *p) { (void)p; return false; }
|
||||
|
||||
|
||||
void RARCH_LOG(const char *fmt, ...) { (void)fmt; }
|
||||
|
||||
static unsigned failures;
|
||||
#ifdef CHEAT_SCAN_TEST_HOOKS
|
||||
extern int cheat_scan_tier_cap;
|
||||
#endif
|
||||
static uint32_t rng = 0xC0FFEEu;
|
||||
static unsigned rnd(void) { rng = rng * 1103515245u + 12345u; return rng >> 8; }
|
||||
|
||||
#define MAX_REGIONS 6
|
||||
static unsigned char *mem[MAX_REGIONS];
|
||||
static unsigned mem_len[MAX_REGIONS];
|
||||
static unsigned nregions;
|
||||
static rarch_memory_descriptor_t descs[MAX_REGIONS];
|
||||
|
||||
static void map_memory(unsigned scale)
|
||||
{
|
||||
unsigned i;
|
||||
nregions = 1 + rnd() % MAX_REGIONS;
|
||||
for (i = 0; i < nregions; i++)
|
||||
{
|
||||
unsigned j;
|
||||
mem_len[i] = 1 + rnd() % scale;
|
||||
mem[i] = (unsigned char*)malloc(mem_len[i]);
|
||||
for (j = 0; j < mem_len[i]; j++)
|
||||
mem[i][j] = (unsigned char)((rnd() & 3) ? rnd() % 4 : rnd());
|
||||
memset(&descs[i], 0, sizeof(descs[i]));
|
||||
descs[i].core.flags = RETRO_MEMDESC_SYSTEM_RAM;
|
||||
descs[i].core.ptr = mem[i];
|
||||
descs[i].core.len = mem_len[i];
|
||||
}
|
||||
test_runloop.system.mmaps.descriptors = descs;
|
||||
test_runloop.system.mmaps.num_descriptors = nregions;
|
||||
}
|
||||
|
||||
static void unmap_memory(void)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < nregions; i++)
|
||||
free(mem[i]);
|
||||
}
|
||||
|
||||
/* The previous search, item by item through a region lookup per item. */
|
||||
static bool ref_seek(unsigned address, unsigned size, unsigned char **base,
|
||||
unsigned *start)
|
||||
{
|
||||
unsigned i, s = 0;
|
||||
cheat_manager_t *st = &cheat_manager_state;
|
||||
for (i = 0; i < st->num_memory_buffers; i++)
|
||||
{
|
||||
unsigned len = st->memory_size_list[i];
|
||||
if (address - s < len)
|
||||
{
|
||||
if (size > len - (address - s))
|
||||
return false;
|
||||
*base = st->memory_buf_list[i];
|
||||
*start = s;
|
||||
return true;
|
||||
}
|
||||
s += len;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ref_search(enum cheat_search_type type, uint8_t *matches,
|
||||
unsigned *num_matches, uint8_t *prev)
|
||||
{
|
||||
cheat_manager_t *st = &cheat_manager_state;
|
||||
unsigned bytes_per_item = 1, bits = 8, mask = 0xFF, idx, i, off = 0;
|
||||
switch (st->search_bit_size)
|
||||
{
|
||||
case 0: bits = 1; mask = 0x01; break;
|
||||
case 1: bits = 2; mask = 0x03; break;
|
||||
case 2: bits = 4; mask = 0x0F; break;
|
||||
case 3: break;
|
||||
case 4: bytes_per_item = 2; mask = 0xFFFF; break;
|
||||
case 5: bytes_per_item = 4; mask = 0xFFFFFFFF; break;
|
||||
}
|
||||
for (idx = 0; idx < st->total_memory_size; idx += bytes_per_item)
|
||||
{
|
||||
unsigned char *curr;
|
||||
unsigned offset, curr_val, prev_val, byte_part;
|
||||
if (!ref_seek(idx, bytes_per_item, &curr, &offset))
|
||||
continue;
|
||||
switch (bytes_per_item)
|
||||
{
|
||||
case 2:
|
||||
curr_val = st->big_endian
|
||||
? (curr[idx - offset] * 256) + curr[idx + 1 - offset]
|
||||
: curr[idx - offset] + (curr[idx + 1 - offset] * 256);
|
||||
prev_val = st->big_endian
|
||||
? (prev[idx] * 256) + prev[idx + 1]
|
||||
: prev[idx] + (prev[idx + 1] * 256);
|
||||
break;
|
||||
case 4:
|
||||
curr_val = st->big_endian
|
||||
? ((unsigned)curr[idx - offset] << 24) | (curr[idx + 1 - offset] << 16)
|
||||
| (curr[idx + 2 - offset] << 8) | curr[idx + 3 - offset]
|
||||
: curr[idx - offset] | (curr[idx + 1 - offset] << 8)
|
||||
| (curr[idx + 2 - offset] << 16) | ((unsigned)curr[idx + 3 - offset] << 24);
|
||||
prev_val = st->big_endian
|
||||
? ((unsigned)prev[idx] << 24) | (prev[idx + 1] << 16)
|
||||
| (prev[idx + 2] << 8) | prev[idx + 3]
|
||||
: prev[idx] | (prev[idx + 1] << 8)
|
||||
| (prev[idx + 2] << 16) | ((unsigned)prev[idx + 3] << 24);
|
||||
break;
|
||||
default:
|
||||
curr_val = curr[idx - offset];
|
||||
prev_val = prev[idx];
|
||||
break;
|
||||
}
|
||||
for (byte_part = 0; byte_part < 8 / bits; byte_part++)
|
||||
{
|
||||
unsigned cs = (curr_val >> (byte_part * bits)) & mask;
|
||||
unsigned ps = (prev_val >> (byte_part * bits)) & mask;
|
||||
unsigned pm = (bits < 8) ? (matches[idx] & (mask << (byte_part * bits)))
|
||||
: matches[idx];
|
||||
bool m = false;
|
||||
if (!pm)
|
||||
continue;
|
||||
switch (type)
|
||||
{
|
||||
case CHEAT_SEARCH_TYPE_EXACT: m = cs == st->search_exact_value; break;
|
||||
case CHEAT_SEARCH_TYPE_LT: m = cs < ps; break;
|
||||
case CHEAT_SEARCH_TYPE_GT: m = cs > ps; break;
|
||||
case CHEAT_SEARCH_TYPE_LTE: m = cs <= ps; break;
|
||||
case CHEAT_SEARCH_TYPE_GTE: m = cs >= ps; break;
|
||||
case CHEAT_SEARCH_TYPE_EQ: m = cs == ps; break;
|
||||
case CHEAT_SEARCH_TYPE_NEQ: m = cs != ps; break;
|
||||
case CHEAT_SEARCH_TYPE_EQPLUS: m = cs == ps + st->search_eqplus_value; break;
|
||||
case CHEAT_SEARCH_TYPE_EQMINUS: m = cs == ps - st->search_eqminus_value; break;
|
||||
}
|
||||
if (!m)
|
||||
{
|
||||
if (bits < 8)
|
||||
matches[idx] &= (~(mask << (byte_part * bits))) & 0xFF;
|
||||
else
|
||||
memset(matches + idx, 0, bytes_per_item);
|
||||
if (*num_matches > 0)
|
||||
(*num_matches)--;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < st->num_memory_buffers; i++)
|
||||
{
|
||||
memcpy(prev + off, st->memory_buf_list[i], st->memory_size_list[i]);
|
||||
off += st->memory_size_list[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void run_search(enum cheat_search_type type, rarch_setting_t *s)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CHEAT_SEARCH_TYPE_EXACT: cheat_manager_search_exact(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_LT: cheat_manager_search_lt(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_GT: cheat_manager_search_gt(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_LTE: cheat_manager_search_lte(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_GTE: cheat_manager_search_gte(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_EQ: cheat_manager_search_eq(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_NEQ: cheat_manager_search_neq(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_EQPLUS: cheat_manager_search_eqplus(s, 0, false); break;
|
||||
case CHEAT_SEARCH_TYPE_EQMINUS: cheat_manager_search_eqminus(s, 0, false); break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Changes a few bytes, a run, or nothing. */
|
||||
static void mutate(void)
|
||||
{
|
||||
unsigned r = rnd() % nregions, k, n;
|
||||
switch (rnd() % 3)
|
||||
{
|
||||
case 0:
|
||||
n = rnd() % 4;
|
||||
for (k = 0; k < n; k++)
|
||||
mem[r][rnd() % mem_len[r]] += (unsigned char)(1 + rnd() % 3);
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
unsigned at = rnd() % mem_len[r], len = 1 + rnd() % 40;
|
||||
for (k = at; k < at + len && k < mem_len[r]; k++)
|
||||
mem[r][k] = (unsigned char)(mem[r][k] - 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const enum cheat_search_type types[] = {
|
||||
CHEAT_SEARCH_TYPE_EXACT, CHEAT_SEARCH_TYPE_LT, CHEAT_SEARCH_TYPE_GT,
|
||||
CHEAT_SEARCH_TYPE_LTE, CHEAT_SEARCH_TYPE_GTE, CHEAT_SEARCH_TYPE_EQ,
|
||||
CHEAT_SEARCH_TYPE_NEQ, CHEAT_SEARCH_TYPE_EQPLUS, CHEAT_SEARCH_TYPE_EQMINUS
|
||||
};
|
||||
unsigned round, searches = 0;
|
||||
int tier;
|
||||
rarch_setting_t setting;
|
||||
memset(&setting, 0, sizeof(setting));
|
||||
|
||||
for (tier = 0; tier <= 2; tier++)
|
||||
for (round = 0; round < 600; round++)
|
||||
{
|
||||
cheat_manager_t *st = &cheat_manager_state;
|
||||
unsigned total, step, scale = (round % 10 == 0) ? 3000 : 70;
|
||||
uint8_t *m_ref, *p_ref;
|
||||
unsigned n_ref;
|
||||
|
||||
#ifdef CHEAT_SCAN_TEST_HOOKS
|
||||
cheat_scan_tier_cap = tier;
|
||||
#endif
|
||||
map_memory(scale);
|
||||
st->search_bit_size = rnd() % 6;
|
||||
cheat_manager_initialize_memory(&setting, 0, false);
|
||||
st->big_endian = (rnd() & 1) != 0;
|
||||
total = st->total_memory_size;
|
||||
if (!st->matches || !st->prev_memory_buf)
|
||||
{
|
||||
printf("FAIL round %u: search not initialised\n", round);
|
||||
failures++;
|
||||
unmap_memory();
|
||||
continue;
|
||||
}
|
||||
m_ref = (uint8_t*)malloc(total);
|
||||
p_ref = (uint8_t*)malloc(total);
|
||||
memcpy(m_ref, st->matches, total);
|
||||
memcpy(p_ref, st->prev_memory_buf, total);
|
||||
n_ref = st->num_matches;
|
||||
|
||||
for (step = 0; step < 8; step++)
|
||||
{
|
||||
enum cheat_search_type t = types[rnd() % 9];
|
||||
/* The size can change in the menu without the search being
|
||||
* started again; match bytes cleared at one size are then read
|
||||
* at another. */
|
||||
if (rnd() % 8 == 0)
|
||||
st->search_bit_size = rnd() % 6;
|
||||
st->search_exact_value = rnd() % 4;
|
||||
st->search_eqplus_value = 1 + rnd() % 2;
|
||||
st->search_eqminus_value = 1 + rnd() % 2;
|
||||
mutate();
|
||||
ref_search(t, m_ref, &n_ref, p_ref);
|
||||
run_search(t, &setting);
|
||||
searches++;
|
||||
if ( st->num_matches != n_ref
|
||||
|| memcmp(st->matches, m_ref, total)
|
||||
|| memcmp(st->prev_memory_buf, p_ref, total))
|
||||
{
|
||||
printf("FAIL tier %d round %u step %u: type %d, bit size %u, %s, %u bytes "
|
||||
"in %u regions: %u matches, reference %u\n",
|
||||
tier, round, step, (int)t, st->search_bit_size,
|
||||
st->big_endian ? "big-endian" : "little-endian",
|
||||
total, nregions, st->num_matches, n_ref);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(m_ref);
|
||||
free(p_ref);
|
||||
unmap_memory();
|
||||
}
|
||||
|
||||
if (failures)
|
||||
{
|
||||
printf("cheat_search: %u failing rounds\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("cheat_search: %u searches agree with the reference\n", searches);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user