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 =