nestopia/source/linux/cheats.cpp
Ryan Danbrook 2dc4d10d20 Added install/uninstall to the Makefile
Added a README
Updated README.Linux
2012-09-03 18:02:21 -04:00

578 lines
14 KiB
C++

/*
NEStopia / Linux
Port by R. Belmont
cheats.cpp - Cheat Manager UI and cheat handling
*/
#include <iostream>
#include <fstream>
#include <strstream>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <cassert>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <vector>
#include <cstdlib>
#include <cwchar>
#include <cerrno>
#include <cstring>
#include "core/api/NstApiEmulator.hpp"
#include "core/api/NstApiCheats.hpp"
#include "../core/NstStream.hpp"
#include "../core/NstXml.hpp" // not entirely kosher but Marty does it on Windows :)
#include "cheats.h"
#include "settings.h"
extern "C" {
#include <gtk/gtk.h>
#include "interface.h"
#include "support.h"
#include "callbacks.h"
}
#include "uihelp.h"
using namespace Nes::Api;
using namespace LinuxNst;
typedef Nes::Core::Xml Xml;
typedef struct
{
Cheats::Code code;
char gg[9], par[9], raw[32], description[64];
bool enabled;
unsigned int crc;
} NstCheatEntry;
extern Emulator emulator;
extern GtkWidget *mainwindow;
extern char rootname[512];
static GtkWidget *cheatwin, *cheattree, *ggedit, *paredit, *ggokbut, *parokbut;
static GtkWidget *chopen, *chsave, *chdelete;
static GtkTreeStore *treestore;
static GtkCellRenderer *renderer, *togglerend;
static GtkTreeViewColumn *column[5];
static GtkTreeSelection *selection;
static std::vector<NstCheatEntry> cheatlist; // our private list of cheats
static std::vector<GtkTreeIter> treeiters;
static bool sIsOpen;
static int find_current_selection(void)
{
int i;
for (i = 0; i < treeiters.size(); i++)
{
if (gtk_tree_selection_iter_is_selected(selection, &treeiters[i]))
{
return i;
}
}
return -1;
}
static void enable_toggle(GtkCellRendererToggle *cell_renderer, gchar *path, gpointer user_data)
{
int item;
char *eptr;
item = strtoul(path, &eptr, 0);
cheatlist[item].enabled = !cheatlist[item].enabled;
gtk_tree_store_set(treestore, &treeiters[item], 0, cheatlist[item].enabled, -1);
}
static void on_cheatwin_destroy(GObject *object, gpointer user_data)
{
treeiters.clear();
sIsOpen = false;
}
CheatMgr::CheatMgr()
{
sIsOpen = false;
}
CheatMgr::~CheatMgr()
{
}
void CheatMgr::ShowManager()
{
int i;
unsigned short addr;
unsigned char val, cmp;
bool usecmp;
Cheats cheats(emulator);
std::vector<NstCheatEntry> templist;
if (sIsOpen)
{
return;
}
sIsOpen = true;
cheatwin = create_cheatwind();
cheattree = lookup_widget(cheatwin, "cheattree");
ggedit = lookup_widget(cheatwin, "ggedit");
paredit = lookup_widget(cheatwin, "paredit");
ggokbut = lookup_widget(cheatwin, "genieok");
parokbut = lookup_widget(cheatwin, "parok");
chopen = lookup_widget(cheatwin, "cheatopen");
chsave = lookup_widget(cheatwin, "cheatsave");
chdelete = lookup_widget(cheatwin, "chdelete");
gtk_window_set_icon(GTK_WINDOW(cheatwin), UIHelp_GetNSTIcon());
g_signal_connect((gpointer)cheatwin, "destroy", G_CALLBACK(on_cheatwin_destroy), NULL);
/* The cheats dialog seems to still work the same without this codeblock.
* I have no idea why. Not a GTK+ expert. Maybe I'll figure it out later.
* Either way, these functions are deprecated and no longer exist in GTK+3,
* so this will need to change in the future if it's ever re-enabled.
// set various widgets' user data to our "this" pointer
gtk_object_set_user_data(GTK_OBJECT(ggokbut), (gpointer)this);
gtk_object_set_user_data(GTK_OBJECT(parokbut), (gpointer)this);
gtk_object_set_user_data(GTK_OBJECT(chopen), (gpointer)this);
gtk_object_set_user_data(GTK_OBJECT(chsave), (gpointer)this);
gtk_object_set_user_data(GTK_OBJECT(chdelete), (gpointer)this); */
gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW (cheattree), FALSE);
// set up our tree store
treestore = gtk_tree_store_new(5, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
// attach the store to the tree
gtk_tree_view_set_model(GTK_TREE_VIEW(cheattree), GTK_TREE_MODEL(treestore));
// create a cell renderer using the stock text one
renderer = gtk_cell_renderer_text_new();
// get the stock toggle renderer
togglerend = gtk_cell_renderer_toggle_new();
// get us an event handler for it
g_signal_connect(G_OBJECT(togglerend), "toggled", G_CALLBACK(enable_toggle), NULL);
// create the display columns
column[0] = gtk_tree_view_column_new_with_attributes("Enable", togglerend, "active", 0, NULL);
column[1] = gtk_tree_view_column_new_with_attributes("Game Genie", renderer, "text", 1, NULL);
column[2] = gtk_tree_view_column_new_with_attributes("PAR", renderer, "text", 2, NULL);
column[3] = gtk_tree_view_column_new_with_attributes("Raw", renderer, "text", 3, NULL);
column[4] = gtk_tree_view_column_new_with_attributes("Description", renderer, "text", 4, NULL);
// add the display column and renderer to the tree view
gtk_tree_view_append_column(GTK_TREE_VIEW(cheattree), column[0]);
gtk_tree_view_append_column(GTK_TREE_VIEW(cheattree), column[1]);
gtk_tree_view_append_column(GTK_TREE_VIEW(cheattree), column[2]);
gtk_tree_view_append_column(GTK_TREE_VIEW(cheattree), column[3]);
gtk_tree_view_append_column(GTK_TREE_VIEW(cheattree), column[4]);
// get the selection object too
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cheattree));
// add the existing cheats
// since AddCode inserts into the master cheatlist we cheat ourselves and
// make a copy of the cheatlist, clear the master, then insert from the copy
templist = cheatlist;
cheatlist.clear();
for (i = 0; i < templist.size(); i++)
{
Cheats::Code codetoadd;
codetoadd = templist[i].code;
AddCode(codetoadd, true, templist[i].enabled, templist[i].description);
}
// show the window
gtk_widget_show(cheatwin);
}
void CheatMgr::Enable(void)
{
Cheats cheats(emulator);
int i;
// clear anything in the engine
cheats.ClearCodes();
// now scan our list and add any enabled codes
for (i = 0; i < cheatlist.size(); i++)
{
if (cheatlist[i].enabled)
{
cheats.SetCode(cheatlist[i].code);
}
}
}
void CheatMgr::Unload(void)
{
Cheats cheats(emulator);
// clear any in-engine cheats
cheats.ClearCodes();
// clear our cheatlist
cheatlist.clear();
}
void CheatMgr::AddCode(Cheats::Code &code, bool useenable, bool enable, char *description)
{
Cheats cheats(emulator);
GtkTreeIter tmpiter;
unsigned short addr;
unsigned char val, cmp;
bool usecmp;
int iteridx = treeiters.size();
int clidx = cheatlist.size();
NstCheatEntry newentry;
newentry.code = code;
newentry.crc = 0;
newentry.description[0] = '\0';
if (description != NULL)
{
strncpy(newentry.description, description, 63);
}
if (useenable)
{
newentry.enabled = enable;
}
else
{
newentry.enabled = true;
}
if (cheats.GameGenieEncode(code, newentry.gg) != Nes::RESULT_OK)
{
strcpy(newentry.gg, "---");
}
// not all GG codes can be represented in PAR
if (cheats.ProActionRockyEncode(code, newentry.par) != Nes::RESULT_OK)
{
strcpy(newentry.par, "---");
}
// add it to the list box
sprintf(newentry.raw, "%04x - %02x - %02x", code.address, code.value, code.compare);
// printf("AddCode [GG %s]: iteridx = %d, clidx = %d, useenable %c, enable %c\n", newentry.gg, iteridx, clidx, useenable ? 'T' : 'F', enable ? 'T' : 'F');
cheatlist.push_back(newentry);
treeiters.push_back(tmpiter);
gtk_tree_store_insert(treestore, &treeiters[iteridx], NULL, 999999);
gtk_tree_store_set(treestore, &treeiters[iteridx], 0, cheatlist[clidx].enabled, 1, cheatlist[clidx].gg, 2, cheatlist[clidx].par, 3, cheatlist[clidx].raw, 4, cheatlist[clidx].description, -1);
// add it to the engine so it takes effect
// cheats.SetCode(code);
}
void on_parok_clicked(GtkButton *button, gpointer user_data)
{
Cheats cheats(emulator);
Cheats::Code code;
CheatMgr *cmgr = (CheatMgr *)user_data;
if (cheats.ProActionRockyDecode(gtk_entry_get_text(GTK_ENTRY(paredit)), code) == Nes::RESULT_OK)
{
cmgr->AddCode(code);
gtk_entry_set_text(GTK_ENTRY(ggedit), "");
gtk_entry_set_text(GTK_ENTRY(paredit), "");
}
}
void on_parvalid_clicked(GtkButton *button, gpointer user_data)
{
Cheats cheats(emulator);
Cheats::Code code;
char gg[9];
if (cheats.ProActionRockyDecode(gtk_entry_get_text(GTK_ENTRY(paredit)), code) == Nes::RESULT_OK)
{
cheats.GameGenieEncode(code, gg);
gtk_entry_set_text(GTK_ENTRY(ggedit), gg);
}
}
void on_genieok_clicked(GtkButton *button, gpointer user_data)
{
Cheats cheats(emulator);
Cheats::Code code;
CheatMgr *cmgr = (CheatMgr *)user_data;
if (cheats.GameGenieDecode(gtk_entry_get_text(GTK_ENTRY(ggedit)), code) == Nes::RESULT_OK)
{
cmgr->AddCode(code);
gtk_entry_set_text(GTK_ENTRY(ggedit), "");
gtk_entry_set_text(GTK_ENTRY(paredit), "");
}
}
void on_chtggvalid_clicked(GtkButton *button, gpointer user_data)
{
Cheats cheats(emulator);
Cheats::Code code;
char par[9];
if (cheats.GameGenieDecode(gtk_entry_get_text(GTK_ENTRY(ggedit)), code) == Nes::RESULT_OK)
{
cheats.ProActionRockyEncode(code, par);
gtk_entry_set_text(GTK_ENTRY(paredit), par);
}
}
void on_chdelete_clicked(GtkButton *button, gpointer user_data)
{
int cursel = find_current_selection();
if (cursel != -1)
{
gtk_tree_store_remove(treestore, &treeiters[cursel]);
memset(&treeiters[cursel], 0, sizeof(GtkTreeIter));
cheatlist.erase(cheatlist.begin()+cursel);
treeiters.erase(treeiters.begin()+cursel);
}
}
void on_cheatopen_clicked(GtkButton *button, gpointer user_data)
{
GtkWidget *dialog;
GtkFileFilter *filter;
Xml xml;
Cheats cheats(emulator);
CheatMgr *cmgr = (CheatMgr *)user_data;
char description[64];
dialog = gtk_file_chooser_dialog_new ("Import cheats (.xml)",
GTK_WINDOW(mainwindow),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
NULL);
filter = gtk_file_filter_new();
gtk_file_filter_set_name(filter, "NEStopia XML cheats");
gtk_file_filter_add_pattern(filter, "*.xml");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
std::ifstream stream( filename, std::ifstream::in|std::ifstream::binary );
if (stream.is_open())
{
xml.Read( stream );
if (xml.GetRoot().IsType( L"cheats" ))
{
for (Xml::Node node(xml.GetRoot().GetFirstChild()); node; node=node.GetNextSibling())
{
if (!node.IsType( L"cheat" ))
continue;
Cheats::Code code;
code.useCompare = false;
if (const Xml::Node address=node.GetChild( L"address" ))
{
uint v;
if (0xFFFF < (v=address.GetUnsignedValue()))
continue;
code.address = v;
if (const Xml::Node value=node.GetChild( L"value" ))
{
if (0xFF < (v=value.GetUnsignedValue()))
continue;
code.value = v;
}
if (const Xml::Node compare=node.GetChild( L"compare" ))
{
if (0xFF < (v=compare.GetUnsignedValue()))
continue;
code.compare = v;
code.useCompare = true;
}
}
else
{
char codestr[512];
if (const Xml::Node genie=node.GetChild( L"genie" ))
{
wcstombs(codestr, genie.GetValue(), 511);
if (cheats.GameGenieDecode(codestr, code) != Nes::RESULT_OK)
{
continue;
}
}
else if (const Xml::Node rocky=node.GetChild( L"rocky" ))
{
wcstombs(codestr, rocky.GetValue(), 511);
if (cheats.ProActionRockyDecode(codestr, code) != Nes::RESULT_OK)
{
continue;
}
}
else
{
continue;
}
}
// add the code, with a description if it's got one
if (node.GetChild( L"description" ))
{
wcstombs(description, node.GetChild( L"description" ).GetValue(), 63);
cmgr->AddCode(code, true, node.GetAttribute( L"enabled" ).IsValue( L"0" ) ? false : true, description);
}
else
{
cmgr->AddCode(code, true, node.GetAttribute( L"enabled" ).IsValue( L"0" ) ? false : true);
}
// poke in the CRC too if there is one
cheatlist[cheatlist.size()-1].crc = node.GetAttribute( L"game" ).GetUnsignedValue();
}
}
}
g_free (filename);
}
gtk_widget_destroy(dialog);
}
void on_cheatsave_clicked(GtkButton *button, gpointer user_data)
{
GtkWidget *dialog;
char defname[512];
Cheats cheats(emulator);
// nothing to export?
if (cheats.NumCodes() == 0)
{
return;
}
defname[0] = '\0';
strcpy(defname, rootname);
strcat(defname, ".xml");
dialog = gtk_file_chooser_dialog_new ("Export cheats (.xml)",
GTK_WINDOW(mainwindow),
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defname);
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
std::ofstream stream( filename, std::ifstream::out|std::ifstream::binary );
if (stream.is_open())
{
int i;
Xml xml;
Xml::Node root(xml.GetRoot());
root = xml.Create( L"cheats" );
root.AddAttribute( L"version", L"1.0" );
for (i = 0; i < cheatlist.size(); i++)
{
Cheats::Code code;
char buffer[9];
wchar_t wbuffer[64];
code = cheatlist[i].code;
Xml::Node node( root.AddChild( L"cheat" ) );
node.AddAttribute( L"enabled", cheatlist[i].enabled ? L"1" : L"0" );
if (cheatlist[i].crc)
{
sprintf(buffer, "%08X", cheatlist[i].crc);
mbstowcs(wbuffer, buffer, 63);
node.AddAttribute( L"game", wbuffer );
}
if (NES_SUCCEEDED(cheats.GameGenieEncode(code, buffer)))
{
mbstowcs(wbuffer, buffer, 63);
node.AddChild( L"genie", wbuffer );
}
if (NES_SUCCEEDED(cheats.ProActionRockyEncode(code, buffer)))
{
mbstowcs(wbuffer, buffer, 63);
node.AddChild( L"rocky", wbuffer );
}
sprintf(buffer, "0x%04X", code.address);
mbstowcs(wbuffer, buffer, 63);
node.AddChild( L"address", wbuffer );
sprintf(buffer, "0x%02X", code.value);
mbstowcs(wbuffer, buffer, 63);
node.AddChild( L"value", wbuffer );
if (code.useCompare)
{
sprintf(buffer, "0x%02X", code.compare);
mbstowcs(wbuffer, buffer, 63);
node.AddChild( L"compare", wbuffer );
}
if (strlen(cheatlist[i].description))
{
mbstowcs(wbuffer, cheatlist[i].description, 63);
node.AddChild( L"description", wbuffer );
}
}
xml.Write( root, stream );
}
g_free (filename);
}
gtk_widget_destroy(dialog);
}
void on_cheatok_clicked(GtkButton *button, gpointer user_data)
{
gtk_widget_destroy(cheatwin);
treeiters.clear();
sIsOpen = false;
}