diff options
author | neonloop | 2021-08-30 15:25:52 +0000 |
---|---|---|
committer | neonloop | 2021-08-30 15:25:52 +0000 |
commit | 05c5d66301f14bd8ae50490ebdc7e32e371f851b (patch) | |
tree | 2a7a376bf0c7e32fcd0c16bb931d4ac2f046269b | |
parent | 8a8a5726cef8b8cb3c32e670fd200d49f3b04c28 (diff) | |
download | picoarch-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-- | Makefile | 2 | ||||
-rw-r--r-- | cheat.c | 224 | ||||
-rw-r--r-- | cheat.h | 22 | ||||
-rw-r--r-- | core.c | 41 | ||||
-rw-r--r-- | core.h | 3 | ||||
-rw-r--r-- | main.c | 3 | ||||
-rw-r--r-- | menu.c | 60 |
7 files changed, 343 insertions, 12 deletions
@@ -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 @@ -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, """, 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); +} @@ -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 @@ -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); @@ -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); @@ -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(); @@ -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 |