diff --git a/Makefile b/Makefile index 7a79f9651b..e4b04e2d9e 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ ifeq ($(HAVE_CG), 1) endif ifeq ($(HAVE_XML), 1) - OBJ += gfx/shader_glsl.o sha256.o + OBJ += gfx/shader_glsl.o sha256.o cheats.o LIBS += $(XML_LIBS) DEFINES += $(XML_CFLAGS) endif diff --git a/Makefile.win32 b/Makefile.win32 index 3dc6423846..1bb41fde16 100644 --- a/Makefile.win32 +++ b/Makefile.win32 @@ -50,7 +50,7 @@ ifeq ($(HAVE_RSOUND), 1) endif ifeq ($(HAVE_XML), 1) - OBJ += gfx/shader_glsl.o sha256.o + OBJ += gfx/shader_glsl.o sha256.o cheats.o DEFINES += $(XML_CFLAGS) -DHAVE_XML LIBS += -lxml2 endif diff --git a/cheats.c b/cheats.c new file mode 100644 index 0000000000..a85a6012fb --- /dev/null +++ b/cheats.c @@ -0,0 +1,163 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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. + * + * SSNES 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 SSNES. + * If not, see . + */ + +#include "cheats.h" +#include "sha256.h" +#include "dynamic.h" +#include "general.h" + +#include +#include +#include + +#include +#include + +struct cheat +{ + char *desc; + bool state; + char *code; +}; + +struct cheat_manager +{ + struct cheat *cheats; + unsigned ptr; + unsigned size; +}; + +static void xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr) +{ + xmlNodePtr node = NULL; + for (node = ptr; node; node = node->next) + { + if (strcmp((const char*)node->name, "name") == 0) + { + xmlChar *name = xmlNodeGetContent(node); + if (name) + { + SSNES_LOG("Found cheat for game: \"%s\"\n", name); + xmlFree(name); + } + } + } +} + +cheat_manager_t* cheat_manager_new(const char *path) +{ + psnes_cheat_reset(); + + cheat_manager_t *handle = calloc(1, sizeof(handle)); + if (!handle) + return NULL; + + xmlParserCtxtPtr ctx = NULL; + xmlDocPtr doc = NULL; + + ctx = xmlNewParserCtxt(); + if (!ctx) + goto error; + + doc = xmlCtxtReadFile(ctx, path, NULL, 0); + if (!doc) + { + SSNES_ERR("Failed to parse XML file: %s\n", path); + goto error; + } + + if (ctx->valid == 0) + { + SSNES_ERR("Cannot validate XML file: %s\n", path); + goto error; + } + + xmlNodePtr head = xmlDocGetRootElement(doc); + xmlNodePtr cur = NULL; + for (cur = head; cur; cur = cur->next) + { + if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "database") == 0) + break; + } + + if (!cur) + goto error; + + for (cur = cur->children; cur; cur = cur->next) + { + if (cur->type != XML_ELEMENT_NODE) + continue; + + if (strcmp((const char*)cur->name, "cartridge") == 0) + { + xmlChar *sha256 = xmlGetProp(cur, (const xmlChar*)"sha256"); + if (!sha256) + continue; + + if (memcmp(sha256, g_extern.sha256, 64) == 0) + { + xmlFree(sha256); + break; + } + + xmlFree(sha256); + } + } + + if (!cur) + goto error; + + xml_grab_cheats(handle, cur->children); + + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctx); + return handle; + +error: + cheat_manager_free(handle); + if (doc) + xmlFreeDoc(doc); + if (ctx) + xmlFreeParserCtxt(ctx); + return NULL; +} + +void cheat_manager_free(cheat_manager_t *handle) +{ + if (!handle) + return; + + if (handle->cheats) + { + for (unsigned i = 0; i < handle->size; i++) + { + xmlFree(handle->cheats[i].desc); + xmlFree(handle->cheats[i].code); + } + + free(handle->cheats); + } + + free(handle); +} + +void cheat_manager_toggle(cheat_manager_t *handle) +{ + handle->cheats[handle->ptr].state ^= true; + psnes_cheat_set(handle->ptr, handle->cheats[handle->ptr].state, handle->cheats[handle->ptr].code); +} + diff --git a/cheats.h b/cheats.h new file mode 100644 index 0000000000..c0c4e99a00 --- /dev/null +++ b/cheats.h @@ -0,0 +1,29 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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. + * + * SSNES 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 SSNES. + * If not, see . + */ + +#ifndef __SSNES_CHEATS_H +#define __SSNES_CHEATS_H + +typedef struct cheat_manager cheat_manager_t; + +cheat_manager_t* cheat_manager_new(const char *path); +void cheat_manager_free(cheat_manager_t *handle); + +void cheat_manager_index_offset(cheat_manager_t *handle, int offset); +void cheat_manager_toggle(cheat_manager_t *handle); + +#endif diff --git a/driver.h b/driver.h index c03957f704..02c3b6ed32 100644 --- a/driver.h +++ b/driver.h @@ -42,6 +42,9 @@ enum SSNES_RESET, SSNES_SHADER_NEXT, SSNES_SHADER_PREV, + SSNES_CHEAT_INDEX_PLUS, + SSNES_CHEAT_INDEX_MINUS, + SSNES_CHEAT_TOGGLE }; diff --git a/dynamic.c b/dynamic.c index 367a2fb1d5..e1ed48cd74 100644 --- a/dynamic.c +++ b/dynamic.c @@ -57,6 +57,9 @@ void (*psnes_set_input_state)(snes_input_state_t); void (*psnes_reset)(void); void (*psnes_run)(void); +void (*psnes_cheat_reset)(void); +void (*psnes_cheat_set)(unsigned, bool, const char*); + const char *(*psnes_library_id)(void) = NULL; unsigned (*psnes_library_revision_minor)(void); unsigned (*psnes_library_revision_major)(void); @@ -111,6 +114,8 @@ static void load_dynamic(void) OPT_SYM(snes_library_id); SYM(snes_library_revision_minor); SYM(snes_library_revision_major); + SYM(snes_cheat_reset); + SYM(snes_cheat_set); SYM(snes_reset); SYM(snes_run); SYM(snes_get_region); @@ -145,6 +150,8 @@ static void set_statics(void) SSYM(snes_set_input_state); SSYM(snes_library_revision_minor); SSYM(snes_library_revision_major); + SSYM(snes_cheat_reset); + SSYM(snes_cheat_set); SSYM(snes_reset); SSYM(snes_run); SSYM(snes_get_region); diff --git a/dynamic.h b/dynamic.h index d8bfea86bc..6572c48cee 100644 --- a/dynamic.h +++ b/dynamic.h @@ -37,6 +37,9 @@ extern void (*psnes_set_audio_sample)(snes_audio_sample_t); extern void (*psnes_set_input_poll)(snes_input_poll_t); extern void (*psnes_set_input_state)(snes_input_state_t); +extern void (*psnes_cheat_reset)(void); +extern void (*psnes_cheat_set)(unsigned, bool, const char*); + extern const char* (*psnes_library_id)(void); extern unsigned (*psnes_library_revision_minor)(void); extern unsigned (*psnes_library_revision_major)(void); diff --git a/general.h b/general.h index 1ed14fee98..4d1d1b2a58 100644 --- a/general.h +++ b/general.h @@ -29,6 +29,7 @@ #include "autosave.h" #include "netplay.h" #include "dynamic.h" +#include "cheats.h" #ifdef HAVE_CONFIG_H #include "config.h" @@ -42,7 +43,7 @@ #define MAX_PLAYERS 5 -#define MAX_BINDS 28 // Needs to be increased every time there are new binds added. +#define MAX_BINDS 31 // Needs to be increased every time there are new binds added. #define SSNES_NO_JOYPAD 0xFFFF enum ssnes_shader_type @@ -110,6 +111,7 @@ struct settings } input; char libsnes[256]; + char cheat_database[256]; bool rewind_enable; unsigned rewind_buffer_size; @@ -245,6 +247,10 @@ struct global } shader_dir; char sha256[65]; + +#ifdef HAVE_XML + cheat_manager_t *cheat; +#endif }; void parse_config(void); diff --git a/settings.c b/settings.c index 7f2cea5efb..4d63bca723 100644 --- a/settings.c +++ b/settings.c @@ -369,6 +369,8 @@ static void parse_config_file(void) CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive"); CONFIG_GET_INT(autosave_interval, "autosave_interval"); + CONFIG_GET_STRING(cheat_database, "cheat_database_path"); + read_keybinds(conf); config_file_free(conf); @@ -414,6 +416,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { DECLARE_BIND(reset, SSNES_RESET) DECLARE_BIND(shader_next, SSNES_SHADER_NEXT) DECLARE_BIND(shader_prev, SSNES_SHADER_PREV) + DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS) + DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS) + DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE) }, { DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A) @@ -443,6 +448,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { DECLARE_BIND(reset, SSNES_RESET) DECLARE_BIND(shader_next, SSNES_SHADER_NEXT) DECLARE_BIND(shader_prev, SSNES_SHADER_PREV) + DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS) + DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS) + DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE) }, { DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A) @@ -472,6 +480,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { DECLARE_BIND(reset, SSNES_RESET) DECLARE_BIND(shader_next, SSNES_SHADER_NEXT) DECLARE_BIND(shader_prev, SSNES_SHADER_PREV) + DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS) + DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS) + DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE) }, { DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A) @@ -501,6 +512,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { DECLARE_BIND(reset, SSNES_RESET) DECLARE_BIND(shader_next, SSNES_SHADER_NEXT) DECLARE_BIND(shader_prev, SSNES_SHADER_PREV) + DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS) + DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS) + DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE) }, { DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A) @@ -530,6 +544,9 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = { DECLARE_BIND(reset, SSNES_RESET) DECLARE_BIND(shader_next, SSNES_SHADER_NEXT) DECLARE_BIND(shader_prev, SSNES_SHADER_PREV) + DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS) + DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS) + DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE) }, }; diff --git a/ssnes.c b/ssnes.c index d31349e8f4..9ec2a3dc06 100644 --- a/ssnes.c +++ b/ssnes.c @@ -32,6 +32,7 @@ #include "movie.h" #include "netplay.h" #include "strl.h" +#include "cheats.h" #include #ifdef HAVE_SRC #include @@ -708,6 +709,20 @@ static void deinit_msg_queue(void) msg_queue_free(g_extern.msg_queue); } +#ifdef HAVE_XML +static void init_cheats(void) +{ + if (*g_settings.cheat_database) + g_extern.cheat = cheat_manager_new(g_settings.cheat_database); +} + +static void deinit_cheats(void) +{ + if (g_extern.cheat) + cheat_manager_free(g_extern.cheat); +} +#endif + static void init_rewind(void) { if (g_settings.rewind_enable) @@ -1279,6 +1294,10 @@ int main(int argc, char *argv[]) if (!init_rom_file(g_extern.game_type)) goto error; +#ifdef HAVE_XML + init_cheats(); +#endif + init_msg_queue(); init_movie(); @@ -1380,6 +1399,10 @@ int main(int argc, char *argv[]) if (!g_extern.netplay) deinit_rewind(); +#ifdef HAVE_XML + deinit_cheats(); +#endif + deinit_movie(); deinit_msg_queue(); diff --git a/ssnes.cfg b/ssnes.cfg index a5ee066b5f..ef450e78e7 100644 --- a/ssnes.cfg +++ b/ssnes.cfg @@ -252,3 +252,11 @@ # When being client over netplay, use keybinds for player 1. # netplay_client_swap_input = false + +# Path to XML cheat database (as used by bSNES). +# cheat_database_path = + +# Cheats. +# input_cheat_index_plus = +# input_cheat_index_minus = +# input_cheat_toggle =