Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 114 additions & 13 deletions workspace/all/minarch/ma_cheats.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ struct Cheats cheatcodes;
#define CHEAT_MAX_LINE_LEN 52
#define CHEAT_MAX_LINES 3

static size_t parse_count(FILE *file) {
size_t count = 0;
fscanf(file, " cheats = %lu\n", (unsigned long *)&count);
return count;
}

static const char *find_val(const char *start) {
start--;
while(!isspace(*++start))
Expand All @@ -33,9 +27,36 @@ static const char *find_val(const char *start) {
while(isspace(*++start))
;

// newer cheat files quote every value, older ones quote only strings
if (*start == '"')
start++;

return start;
}

// the count sits on the first line in older cheat files and on the last line in
// newer ones, so scan the whole file and keep the last match
static size_t parse_count(FILE *file) {
char line[512];
size_t count = 0;

while (fgets(line, sizeof(line), file)) {
const char *ptr = line;
while (isspace(*ptr))
ptr++;

// anchored, so a description mentioning "cheats = " can't hijack us
if (strncmp(ptr, "cheats", 6))
continue;

if ((ptr = find_val(ptr)))
count = strtoul(ptr, NULL, 10);
}

rewind(file);
return count;
}

static int parse_bool(const char *ptr, int *out) {
if (!strncasecmp(ptr, "true", 4)) {
*out = 1;
Expand All @@ -53,9 +74,7 @@ static int parse_string(const char *ptr, char *buf, size_t len) {

buf[0] = '\0';

if (*ptr++ != '"')
return -1;

// find_val already consumed the opening quote; the closing one is checked below
while (*ptr != '\0' && *ptr != '"' && index < len - 1) {
if (*ptr == '\\' && index < input_len - 1) {
ptr++;
Expand Down Expand Up @@ -100,10 +119,14 @@ static int parse_cheats(struct Cheats *cheats, FILE *file) {
size_t len;
sscanf(ptr, "cheat%d", &index);

if (index >= cheats->count)
if (index < 0 || (size_t)index >= cheats->count)
continue;
cheat = &cheats->cheats[index];

// these would otherwise match the _value and _address tests below
if (strstr(ptr, "_rumble") || strstr(ptr, "_repeat"))
continue;

if (strstr(ptr, "_desc")) {
ptr = find_val(ptr);
if (!ptr || parse_string(ptr, buf, sizeof(buf))) {
Expand Down Expand Up @@ -153,6 +176,27 @@ static int parse_cheats(struct Cheats *cheats, FILE *file) {
continue;
}
}
// the rest describe a memory write, see Cheats_apply
// note: _address_bit_position must be tested before _address
else if (strstr(ptr, "_address_bit_position")) {
if ((ptr = find_val(ptr)))
cheat->bit_position = strtoul(ptr, NULL, 10);
} else if (strstr(ptr, "_address")) {
if ((ptr = find_val(ptr)))
cheat->address = strtoul(ptr, NULL, 10);
} else if (strstr(ptr, "_value")) {
if ((ptr = find_val(ptr)))
cheat->value = strtoul(ptr, NULL, 10);
} else if (strstr(ptr, "_cheat_type")) {
if ((ptr = find_val(ptr)))
cheat->type = strtoul(ptr, NULL, 10);
} else if (strstr(ptr, "_memory_search_size")) {
if ((ptr = find_val(ptr)))
cheat->size = strtoul(ptr, NULL, 10);
} else if (strstr(ptr, "_big_endian")) {
if ((ptr = find_val(ptr)))
parse_bool(ptr, &cheat->big_endian);
}
}
} while(1);

Expand Down Expand Up @@ -249,7 +293,7 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) {

void Cheats_free() {
size_t i;
for (i = 0; i < cheatcodes.count; i++) {
for (i = 0; cheatcodes.cheats && i < cheatcodes.count; i++) {
struct Cheat *cheat = &cheatcodes.cheats[i];
if (cheat) {
free((char *)cheat->name);
Expand All @@ -258,6 +302,7 @@ void Cheats_free() {
}
}
free(cheatcodes.cheats);
cheatcodes.cheats = NULL;
cheatcodes.count = 0;
}

Expand Down Expand Up @@ -320,7 +365,7 @@ bool Cheats_load() {
}

cheatcodes.count = parse_count(file);
if (cheatcodes.count <= 0) {
if (cheatcodes.count == 0 || cheatcodes.count > CHEAT_MAX_COUNT) {
LOG_error("Couldn't read cheat count\n");
goto finish;
}
Expand All @@ -336,7 +381,9 @@ bool Cheats_load() {
goto finish;
}

LOG_info("Found %i cheats for the current game.\n", cheatcodes.count);
// memory-write cheats are useless without system ram, so report what we got
LOG_info("Found %i cheats for the current game (system ram: %zu bytes).\n",
cheatcodes.count, core.get_memory_size ? core.get_memory_size(RETRO_MEMORY_SYSTEM_RAM) : 0);

success = 1;
finish:
Expand All @@ -346,4 +393,58 @@ bool Cheats_load() {

if (file)
fclose(file);

return success;
}

// Cheats from RetroArch's newer format carry no code for the core to interpret,
// they name a memory location to hold at a value. Those have to be rewritten
// every frame, otherwise the game just overwrites them again.
// Addresses are treated as offsets into system RAM; cores that publish a memory
// map (RETRO_ENVIRONMENT_SET_MEMORY_MAPS) may expect a different layout.
void Cheats_apply(void) {
static const unsigned char widths[] = { 0, 0, 0, 1, 2, 4 }; // by memory_search_size
unsigned char *ram;
size_t ram_size, i;

if (!core.get_memory_data || !core.get_memory_size)
return;

ram_size = core.get_memory_size(RETRO_MEMORY_SYSTEM_RAM);
ram = core.get_memory_data(RETRO_MEMORY_SYSTEM_RAM);
if (!ram || !ram_size)
return;

for (i = 0; i < cheatcodes.count; i++) {
const struct Cheat *cheat = &cheatcodes.cheats[i];
unsigned char width;
unsigned value;
int shift;

// code means the core applies it, see Core_applyCheats
if (!cheat->enabled || cheat->code || cheat->type != CHEAT_TYPE_SET_TO_VALUE)
continue;

if (cheat->size < 3) {
// sub-byte cheat: mask into the byte at bit_position
unsigned char mask = (1 << (1 << cheat->size)) - 1;
if (cheat->address >= ram_size || cheat->bit_position > 7)
continue;

shift = cheat->bit_position;
ram[cheat->address] = (ram[cheat->address] & ~(mask << shift)) | ((cheat->value & mask) << shift);
continue;
}

// written this way round so address + width can't overflow
width = cheat->size < sizeof(widths) ? widths[cheat->size] : 0;
if (!width || width > ram_size || cheat->address > ram_size - width)
continue;

value = cheat->value;
for (shift = 0; shift < width; shift++) {
int byte = cheat->big_endian ? width - 1 - shift : shift;
ram[cheat->address + byte] = (value >> (shift * 8)) & 0xff;
}
}
}
14 changes: 13 additions & 1 deletion workspace/all/minarch/ma_cheats.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include "defines.h"

#define CHEAT_TYPE_SET_TO_VALUE 1
#define CHEAT_MAX_COUNT 4096
#define CHEAT_MAX_PATHS 48
#define CHEAT_MAX_DISPLAY_PATHS 8
#define CHEAT_MAX_LIST_LENGTH (CHEAT_MAX_DISPLAY_PATHS * MAX_PATH)
Expand All @@ -13,7 +15,16 @@ struct Cheat {
const char *name;
const char *info;
int enabled;
const char *code;
const char *code; // classic format, handed to retro_cheat_set()

// RetroArch's newer format describes cheats as a memory write instead of a
// code string (handler = 1). Those have no code and are applied by us.
unsigned address;
unsigned value;
unsigned char type; // cheat_type, 1 = set to value (the only one we apply)
unsigned char size; // memory_search_size, 0-5 = 1,2,4,8,16,32 bits
unsigned char bit_position; // address_bit_position, sub-byte sizes only
int big_endian;
};

struct Cheats {
Expand All @@ -27,3 +38,4 @@ extern struct Cheats cheatcodes;
void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count);
void Cheats_free(void);
bool Cheats_load(void);
void Cheats_apply(void);
3 changes: 2 additions & 1 deletion workspace/all/minarch/ma_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ void Core_applyCheats(struct Cheats *cheats)

core.cheat_reset();
for (int i = 0; i < cheats->count; i++) {
if (cheats->cheats[i].enabled) {
// no code means it describes a memory write, applied by Cheats_apply
if (cheats->cheats[i].enabled && cheats->cheats[i].code) {
core.cheat_set(i, cheats->cheats[i].enabled, cheats->cheats[i].code);
}
}
Expand Down
3 changes: 3 additions & 0 deletions workspace/all/minarch/ma_runframe.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <SDL2/SDL.h>
#include "ma_internal.h"
#include "ma_cheats.h"
#include "ma_rewind.h"
#include "ma_input.h"
#include "ma_config.h"
Expand Down Expand Up @@ -46,6 +47,8 @@ static void limitFF(void) {
}

void run_frame(void) {
Cheats_apply();

// if rewind is toggled, fast-forward toggle must stay off; fast-forward hold pauses rewind
int do_rewind = (rewind_pressed || rewind_toggle) && !(rewind_toggle && ff_hold_active);
if (do_rewind) {
Expand Down
Loading