aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorneonloop2021-08-30 15:25:52 +0000
committerneonloop2021-08-30 15:25:52 +0000
commit05c5d66301f14bd8ae50490ebdc7e32e371f851b (patch)
tree2a7a376bf0c7e32fcd0c16bb931d4ac2f046269b
parent8a8a5726cef8b8cb3c32e670fd200d49f3b04c28 (diff)
downloadpicoarch-05c5d66301f14bd8ae50490ebdc7e32e371f851b.tar.gz
picoarch-05c5d66301f14bd8ae50490ebdc7e32e371f851b.tar.bz2
picoarch-05c5d66301f14bd8ae50490ebdc7e32e371f851b.zip
Adds cheat support
Cheats use RetroArch .cht file format. Cheats are loaded from [save_dir]/cheats/[name].cht, where name is the name of the ROM without extension. Cheat menu only shows when cheat file is found.
-rw-r--r--Makefile2
-rw-r--r--cheat.c224
-rw-r--r--cheat.h22
-rw-r--r--core.c41
-rw-r--r--core.h3
-rw-r--r--main.c3
-rw-r--r--menu.c60
7 files changed, 343 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index e1db308..e67cb70 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ SYSROOT = $(shell $(CC) --print-sysroot)
PROCS = -j4
-OBJS = libpicofe/input.o libpicofe/in_sdl.o libpicofe/linux/in_evdev.o libpicofe/linux/plat.o libpicofe/fonts.o libpicofe/readpng.o libpicofe/config_file.o config.o core.o menu.o main.o options.o overrides.o scale.o unzip.o util.o
+OBJS = libpicofe/input.o libpicofe/in_sdl.o libpicofe/linux/in_evdev.o libpicofe/linux/plat.o libpicofe/fonts.o libpicofe/readpng.o libpicofe/config_file.o cheat.o config.o core.o menu.o main.o options.o overrides.o scale.o unzip.o util.o
BIN = picoarch
diff --git a/cheat.c b/cheat.c
new file mode 100644
index 0000000..6b57993
--- /dev/null
+++ b/cheat.c
@@ -0,0 +1,224 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "cheat.h"
+#include "main.h"
+#include "util.h"
+
+#define MAX_DESC_LEN 27
+#define MAX_LINE_LEN 52
+#define MAX_LINES 3
+
+static size_t parse_count(FILE *file) {
+ size_t count = 0;
+ fscanf(file, " cheats = %d\n", &count);
+ return count;
+}
+
+static const char *find_val(const char *start) {
+ start--;
+ while(!isspace(*++start))
+ ;
+
+ while(isspace(*++start))
+ ;
+
+ if (*start != '=')
+ return NULL;
+
+ while(isspace(*++start))
+ ;
+
+ return start;
+}
+
+static int parse_bool(const char *ptr, int *out) {
+ if (!strncasecmp(ptr, "true", 4)) {
+ *out = 1;
+ } else if (!strncasecmp(ptr, "false", 5)) {
+ *out = 0;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int parse_string(const char *ptr, char *buf, size_t len) {
+ int index = 0;
+ size_t input_len = strlen(ptr);
+
+ buf[0] = '\0';
+
+ if (*ptr++ != '"')
+ return -1;
+
+ while (*ptr != '\0' && *ptr != '"' && index < len - 1) {
+ if (*ptr == '\\' && index < input_len - 1) {
+ ptr++;
+ buf[index++] = *ptr++;
+ } else if (*ptr == '&' && !strncmp(ptr, "&quot;", 6)) {
+ buf[index++] = '"';
+ ptr += 6;
+ } else {
+ buf[index++] = *ptr++;
+ }
+ }
+
+ if (*ptr != '"') {
+ buf[0] = '\0';
+ return -1;
+ }
+
+ buf[index] = '\0';
+ return 0;
+}
+
+static int parse_cheats(struct cheats *cheats, FILE *file) {
+ int ret = -1;
+ char line[512];
+ char buf[512];
+ const char *ptr;
+
+ do {
+ if (!fgets(line, sizeof(line), file)) {
+ ret = 0;
+ break;
+ }
+
+ if (line[strlen(line) - 1] != '\n' && !feof(file)) {
+ PA_WARN("Cheat line too long\n");
+ continue;
+ }
+
+ if ((ptr = strstr(line, "cheat"))) {
+ int index = -1;
+ struct cheat *cheat;
+ size_t len;
+ sscanf(ptr, "cheat%d", &index);
+
+ if (index >= cheats->count)
+ continue;
+ cheat = &cheats->cheats[index];
+
+ if (strstr(ptr, "_desc")) {
+ ptr = find_val(ptr);
+ if (!ptr || parse_string(ptr, buf, sizeof(buf))) {
+ PA_WARN("Couldn't parse cheat %d description\n", index);
+ continue;
+ }
+
+ len = strlen(buf);
+ if (len == 0)
+ continue;
+
+ cheat->name = calloc(len+1, sizeof(char));
+ if (!cheat->name)
+ goto finish;
+
+ strncpy((char *)cheat->name, buf, len);
+ string_truncate((char *)cheat->name, MAX_DESC_LEN);
+
+ if (len >= MAX_DESC_LEN) {
+ cheat->info = calloc(len+1, sizeof(char));
+ if (!cheat->info)
+ goto finish;
+
+ strncpy((char *)cheat->info, buf, len);
+ string_wrap((char *)cheat->info, MAX_LINE_LEN, MAX_LINES);
+ }
+ } else if (strstr(ptr, "_code")) {
+ ptr = find_val(ptr);
+ if (!ptr || parse_string(ptr, buf, sizeof(buf))) {
+ PA_WARN("Couldn't parse cheat %d code\n", index);
+ continue;
+ }
+
+ len = strlen(buf);
+ if (len == 0)
+ continue;
+
+ cheat->code = calloc(len+1, sizeof(char));
+ if (!cheat->code)
+ goto finish;
+
+ strncpy((char *)cheat->code, buf, len);
+ } else if (strstr(ptr, "_enable")) {
+ ptr = find_val(ptr);
+ if (!ptr || parse_bool(ptr, &cheat->enabled)) {
+ PA_WARN("Couldn't parse cheat %d enabled\n", index);
+ continue;
+ }
+ }
+ }
+ } while(1);
+
+finish:
+ return ret;
+}
+
+struct cheats *cheats_load(const char *filename) {
+ int success = 0;
+ struct cheats *cheats = NULL;
+ FILE *file = NULL;
+ size_t i;
+
+ file = fopen(filename, "r");
+ if (!file)
+ goto finish;
+
+ PA_INFO("Loading cheats from %s\n", filename);
+
+ cheats = calloc(1, sizeof(struct cheats));
+ if (!cheats) {
+ PA_ERROR("Couldn't allocate memory for cheats\n");
+ goto finish;
+ }
+
+ cheats->count = parse_count(file);
+ if (cheats->count <= 0) {
+ PA_ERROR("Couldn't read cheat count\n");
+ goto finish;
+ }
+
+ cheats->cheats = calloc(cheats->count, sizeof(struct cheat));
+ if (!cheats->cheats) {
+ PA_ERROR("Couldn't allocate memory for cheats\n");
+ goto finish;
+ }
+
+ if (parse_cheats(cheats, file)) {
+ PA_ERROR("Error reading cheat %d\n", i);
+ goto finish;
+ }
+
+ success = 1;
+finish:
+
+ if (!success) {
+ cheats_free(cheats);
+ cheats = NULL;
+ }
+
+ if (file)
+ fclose(file);
+
+ return cheats;
+}
+
+void cheats_free(struct cheats *cheats) {
+ size_t i;
+ if (cheats) {
+ for (i = 0; i < cheats->count; i++) {
+ struct cheat *cheat = &cheats->cheats[i];
+ if (cheat) {
+ free((char *)cheat->name);
+ free((char *)cheat->info);
+ free((char *)cheat->code);
+ }
+ }
+ free(cheats->cheats);
+ }
+ free(cheats);
+}
diff --git a/cheat.h b/cheat.h
new file mode 100644
index 0000000..0f255b9
--- /dev/null
+++ b/cheat.h
@@ -0,0 +1,22 @@
+#ifndef __CHEAT_H_
+#define __CHEAT_H_
+
+#include <stdlib.h>
+
+struct cheat {
+ const char *name;
+ const char *info;
+ int enabled;
+ const char *code;
+};
+
+struct cheats {
+ int enabled;
+ size_t count;
+ struct cheat *cheats;
+};
+
+struct cheats *cheats_load(const char *filename);
+void cheats_free(struct cheats *cheats);
+
+#endif
diff --git a/core.c b/core.c
index 500145b..3a801e5 100644
--- a/core.c
+++ b/core.c
@@ -6,6 +6,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include "cheat.h"
#include "core.h"
#include "libpicofe/input.h"
#include "main.h"
@@ -19,6 +20,7 @@ struct core_cbs current_core;
char core_path[MAX_PATH];
char content_path[MAX_PATH];
static struct string_list *extensions;
+struct cheats *cheats;
double sample_rate;
double frame_rate;
@@ -115,7 +117,7 @@ static void core_free_game_info(struct retro_game_info *game_info) {
}
}
-static void gamepak_related_name(char *buf, size_t len, const char *new_extension)
+static void gamepak_related_name(char *buf, size_t len, const char *subdir, const char *new_extension)
{
char filename[MAX_PATH];
char *dot;
@@ -127,13 +129,13 @@ static void gamepak_related_name(char *buf, size_t len, const char *new_extensio
if (dot)
*dot = 0;
- snprintf(buf, len, "%s%s%s", save_dir, filename, new_extension);
+ snprintf(buf, len, "%s%s%s%s", save_dir, subdir, filename, new_extension);
}
void config_file_name(char *buf, size_t len, int is_game)
{
if (is_game && content_path[0]) {
- gamepak_related_name(buf, len, ".cfg");
+ gamepak_related_name(buf, len, "", ".cfg");
} else {
snprintf(buf, len, "%s%s", config_dir, "picoarch.cfg");
}
@@ -153,7 +155,7 @@ void sram_write(void) {
return;
}
- gamepak_related_name(filename, MAX_PATH, ".sav");
+ gamepak_related_name(filename, MAX_PATH, "", ".sav");
sram_file = fopen(filename, "w");
if (!sram_file) {
@@ -182,7 +184,7 @@ void sram_read(void) {
return;
}
- gamepak_related_name(filename, MAX_PATH, ".sav");
+ gamepak_related_name(filename, MAX_PATH, "", ".sav");
sram_file = fopen(filename, "r");
if (!sram_file) {
@@ -206,7 +208,7 @@ void state_file_name(char *name, size_t size, int slot) {
char extension[5] = {0};
snprintf(extension, 5, ".st%d", slot);
- gamepak_related_name(name, MAX_PATH, extension);
+ gamepak_related_name(name, MAX_PATH, "", extension);
}
int state_read(void) {
@@ -676,6 +678,7 @@ int core_load_content(const char *path) {
struct retro_game_info game_info = {0};
struct retro_system_av_info av_info = {0};
int ret = -1;
+ char cheats_path[MAX_PATH] = {0};
if (core_load_game_info(path, &game_info)) {
goto finish;
@@ -686,6 +689,12 @@ int core_load_content(const char *path) {
goto finish;
}
+ gamepak_related_name(cheats_path, sizeof(cheats_path), "cheats/", ".cht");
+ if (cheats_path[0] != '\0') {
+ cheats = cheats_load(cheats_path);
+ core_apply_cheats(cheats);
+ }
+
sram_read();
current_core.retro_get_system_av_info(&av_info);
@@ -700,7 +709,7 @@ int core_load_content(const char *path) {
aspect_ratio = av_info.geometry.aspect_ratio;
#ifdef MMENU
- gamepak_related_name(save_template_path, MAX_PATH, ".st%i");
+ gamepak_related_name(save_template_path, MAX_PATH, "", ".st%i");
#endif
ret = 0;
@@ -709,9 +718,27 @@ finish:
return ret;
}
+void core_apply_cheats(struct cheats *cheats) {
+ if (!cheats)
+ return;
+
+ if (!current_core.retro_cheat_reset || !current_core.retro_cheat_set)
+ return;
+
+ current_core.retro_cheat_reset();
+ for (int i = 0; i < cheats->count; i++) {
+ if (cheats->cheats[i].enabled) {
+ current_core.retro_cheat_set(i, cheats->cheats[i].enabled, cheats->cheats[i].code);
+ }
+ }
+}
+
void core_unload_content(void) {
sram_write();
+ cheats_free(cheats);
+ cheats = NULL;
+
current_core.retro_unload_game();
if (temp_rom[0]) {
remove(temp_rom);
diff --git a/core.h b/core.h
index 34143ef..8958e9e 100644
--- a/core.h
+++ b/core.h
@@ -1,6 +1,7 @@
#ifndef _CORE_H__
#define _CORE_H__
+#include "cheat.h"
#include "libretro.h"
#include "main.h"
@@ -32,6 +33,7 @@ struct core_cbs {
extern struct core_cbs current_core;
extern char core_path[MAX_PATH];
extern char content_path[MAX_PATH];
+extern struct cheats *cheats;
extern double sample_rate;
extern double frame_rate;
@@ -60,6 +62,7 @@ bool disc_replace_index(unsigned index, const char *content_path);
void core_extract_name(const char* core_file, char *buf, size_t len);
int core_load(const char *corefile);
int core_load_content(const char *path);
+void core_apply_cheats(struct cheats *cheats);
void core_unload_content(void);
const char **core_extensions(void);
void core_unload(void);
diff --git a/main.c b/main.c
index c1df689..08f83d1 100644
--- a/main.c
+++ b/main.c
@@ -553,10 +553,9 @@ int main(int argc, char **argv) {
if (ResumeSlot) resume_slot = ResumeSlot();
}
#endif
-
+ show_startup_message();
state_resume();
- show_startup_message();
do {
count_fps();
adjust_audio();
diff --git a/menu.c b/menu.c
index 6ba1488..2674007 100644
--- a/menu.c
+++ b/menu.c
@@ -26,6 +26,7 @@ typedef enum
MA_MAIN_SAVE_STATE,
MA_MAIN_LOAD_STATE,
MA_MAIN_DISC_CTRL,
+ MA_MAIN_CHEATS,
MA_MAIN_CORE_SEL,
MA_MAIN_CONTENT_SEL,
MA_MAIN_RESET_GAME,
@@ -365,6 +366,60 @@ static int menu_loop_disc(int id, int keys)
return 0;
}
+static int menu_loop_cheats_page(int offset, int keys) {
+ static int sel = 0;
+ menu_entry *e_menu_cheats;
+ size_t i, menu_idx;
+
+ /* cheats + 2 for possible "Next page" + NULL */
+ e_menu_cheats = (menu_entry *)calloc(cheats->count + 2, sizeof(menu_entry));
+
+ if (!e_menu_cheats) {
+ PA_ERROR("Error allocating cheats\n");
+ return 0;
+ }
+
+ for (i = offset, menu_idx = 0; i < cheats->count && menu_idx < MENU_ITEMS_PER_PAGE; i++) {
+ struct cheat *cheat = &cheats->cheats[i];
+ menu_entry *option;
+
+ option = &e_menu_cheats[menu_idx];
+
+ option->name = cheat->name;
+ option->beh = MB_OPT_ONOFF;
+ option->var = &cheat->enabled;
+ option->enabled = 1;
+ option->mask = 1;
+ option->need_to_save = 1;
+ option->selectable = 1;
+ option->help = cheat->info;
+ menu_idx++;
+ }
+
+ if (i < cheats->count) {
+ menu_entry *option;
+ option = &e_menu_cheats[menu_idx];
+ option->name = "Next page";
+ option->beh = MB_OPT_CUSTOM;
+ option->id = i;
+ option->enabled = 1;
+ option->selectable = 1;
+ option->handler = menu_loop_cheats_page;
+ }
+
+ me_loop(e_menu_cheats, &sel);
+ free(e_menu_cheats);
+
+ return 0;
+}
+
+static int menu_loop_cheats(int id, int keys)
+{
+ int ret = menu_loop_cheats_page(0, keys);
+ core_apply_cheats(cheats);
+ return ret;
+}
+
static int menu_loop_core_options_page(int offset, int keys) {
static int sel = 0;
menu_entry *e_menu_core_options;
@@ -564,7 +619,7 @@ static menu_entry e_menu_main[] =
mee_handler_id("Save state", MA_MAIN_SAVE_STATE, main_menu_handler),
mee_handler_id("Load state", MA_MAIN_LOAD_STATE, main_menu_handler),
mee_handler_id("Disc control", MA_MAIN_DISC_CTRL, menu_loop_disc),
- /* mee_handler_id("Cheats", MA_MAIN_CHEATS, main_menu_handler), */
+ mee_handler_id("Cheats", MA_MAIN_CHEATS, menu_loop_cheats),
mee_handler ("Options", menu_loop_options),
mee_handler_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler),
mee_handler_id("Load new game", MA_MAIN_CONTENT_SEL, menu_loop_select_content),
@@ -619,7 +674,8 @@ void menu_loop(void)
me_enable(e_menu_main, MA_MAIN_SAVE_STATE, state_allowed());
me_enable(e_menu_main, MA_MAIN_LOAD_STATE, state_allowed());
-
+ me_enable(e_menu_main, MA_MAIN_CHEATS, cheats != NULL);
+
me_enable(e_menu_main, MA_MAIN_DISC_CTRL, needs_disc_ctrl);
#ifdef MMENU