mirror of
https://github.com/Alcaro/Flips.git
synced 2026-03-22 01:54:26 -05:00
917 lines
29 KiB
C++
917 lines
29 KiB
C++
//Module name: Floating IPS, GTK+ frontend
|
|
//Author: Alcaro
|
|
//Date: June 18, 2015
|
|
//Licence: GPL v3.0 or higher
|
|
|
|
//List of assumptions made whose correctness is not guaranteed by GTK+:
|
|
//The character '9' is as wide as the widest of '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'.
|
|
// Failure leads to: The BPS delta creation progress window being a little too small.
|
|
// Fixable: Not hard, but unlikely to be worth it.
|
|
#include "flips.h"
|
|
|
|
#ifdef FLIPS_GTK
|
|
#include <gtk/gtk.h>
|
|
|
|
class file_gtk : public file {
|
|
size_t size;
|
|
GFileInputStream* io;
|
|
|
|
public:
|
|
static file* create(const char * filename)
|
|
{
|
|
GFile* file = g_file_new_for_commandline_arg(filename);
|
|
if (!file) return NULL;
|
|
GFileInputStream* io=g_file_read(file, NULL, NULL);
|
|
g_object_unref(file);
|
|
if (!io) return NULL;
|
|
return new file_gtk(io);
|
|
}
|
|
|
|
private:
|
|
file_gtk(GFileInputStream* io) : io(io)
|
|
{
|
|
GFileInfo* info = g_file_input_stream_query_info(io, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL);
|
|
size = g_file_info_get_size(info);
|
|
g_object_unref(info);
|
|
}
|
|
|
|
public:
|
|
size_t len() { return size; }
|
|
|
|
bool read(uint8_t* target, size_t start, size_t len)
|
|
{
|
|
g_seekable_seek(G_SEEKABLE(io), start, G_SEEK_SET, NULL, NULL);
|
|
gsize actualsize;
|
|
return (g_input_stream_read_all(G_INPUT_STREAM(io), target, len, &actualsize, NULL, NULL) && actualsize == len);
|
|
}
|
|
|
|
~file_gtk() { g_object_unref(io); }
|
|
};
|
|
|
|
file* file::create(const char * filename) { return file_gtk::create(filename); }
|
|
|
|
|
|
class filewrite_gtk : public filewrite {
|
|
GOutputStream* io;
|
|
|
|
public:
|
|
static filewrite* create(const char * filename)
|
|
{
|
|
GFile* file = g_file_new_for_commandline_arg(filename);
|
|
if (!file) return NULL;
|
|
GFileOutputStream* io = g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
|
g_object_unref(file);
|
|
if (!io) return NULL;
|
|
return new filewrite_gtk(G_OUTPUT_STREAM(io));
|
|
}
|
|
|
|
private:
|
|
filewrite_gtk(GOutputStream* io) : io(io) {}
|
|
|
|
public:
|
|
bool append(const uint8_t* data, size_t len)
|
|
{
|
|
return g_output_stream_write_all(io, data, len, NULL, NULL, NULL);
|
|
}
|
|
|
|
~filewrite_gtk() { g_object_unref(io); }
|
|
};
|
|
|
|
filewrite* filewrite::create(const char * filename) { return filewrite_gtk::create(filename); }
|
|
|
|
//struct mem ReadWholeFile(const char * filename)
|
|
//{
|
|
// GFile* file=g_file_new_for_commandline_arg(filename);
|
|
// if (!file) return (struct mem){NULL, 0};
|
|
// GFileInputStream* io=g_file_read(file, NULL, NULL);
|
|
// if (!io)
|
|
// {
|
|
// g_object_unref(file);
|
|
// return (struct mem){NULL, 0};
|
|
// }
|
|
// GFileInfo* info=g_file_input_stream_query_info(io, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL);
|
|
// gsize size=g_file_info_get_size(info);
|
|
// struct mem mem={(uint8_t*)malloc(size), size};
|
|
// gsize actualsize;
|
|
// bool success=g_input_stream_read_all(G_INPUT_STREAM(io), mem.ptr, size, &actualsize, NULL, NULL);
|
|
// if (size!=actualsize) success=false;
|
|
// g_input_stream_close(G_INPUT_STREAM(io), NULL, NULL);
|
|
// g_object_unref(file);
|
|
// g_object_unref(io);
|
|
// g_object_unref(info);
|
|
// if (!success)
|
|
// {
|
|
// free(mem.ptr);
|
|
// return (struct mem){NULL, 0};
|
|
// }
|
|
// return mem;
|
|
//}
|
|
//
|
|
//bool WriteWholeFile(const char * filename, struct mem data)
|
|
//{
|
|
// GFile* file=g_file_new_for_commandline_arg(filename);
|
|
// if (!file) return false;
|
|
// GFileOutputStream* io=g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
|
// if (!io)
|
|
// {
|
|
// g_object_unref(file);
|
|
// return false;
|
|
// }
|
|
//
|
|
// bool success=g_output_stream_write_all(G_OUTPUT_STREAM(io), data.ptr, data.len, NULL, NULL, NULL);
|
|
// g_output_stream_close(G_OUTPUT_STREAM(io), NULL, NULL);
|
|
// g_object_unref(file);
|
|
// return success;
|
|
//}
|
|
//
|
|
//bool WriteWholeFileWithHeader(const char * filename, struct mem header, struct mem data)
|
|
//{
|
|
// GFile* file=g_file_new_for_commandline_arg(filename);
|
|
// if (!file) return false;
|
|
// GFileOutputStream* io=g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
|
// if (!io)
|
|
// {
|
|
// g_object_unref(file);
|
|
// return false;
|
|
// }
|
|
//
|
|
// bool success=(g_output_stream_write_all(G_OUTPUT_STREAM(io), header.ptr, 512, NULL, NULL, NULL) &&
|
|
// g_output_stream_write_all(G_OUTPUT_STREAM(io), data.ptr, data.len, NULL, NULL, NULL));
|
|
// g_output_stream_close(G_OUTPUT_STREAM(io), NULL, NULL);
|
|
// g_object_unref(file);
|
|
// return success;
|
|
//}
|
|
//
|
|
//void FreeFileMemory(struct mem mem)
|
|
//{
|
|
// free(mem.ptr);
|
|
//}
|
|
|
|
|
|
static bool canShowGUI;
|
|
static GtkWidget* window;
|
|
|
|
struct {
|
|
char signature[9];
|
|
unsigned int lastPatchType;
|
|
bool createFromAllFiles;
|
|
bool openInEmulatorOnAssoc;
|
|
bool autoSelectRom;
|
|
gchar * emulator;
|
|
} static state;
|
|
#define cfgversion 5
|
|
|
|
static GtkWidget* windowBpsd;
|
|
static GtkWidget* labelBpsd;
|
|
static bool bpsdCancel;
|
|
|
|
void bpsdeltaCancel(GtkWindow* widget, gpointer user_data)
|
|
{
|
|
bpsdCancel=true;
|
|
}
|
|
|
|
void bpsdeltaBegin()
|
|
{
|
|
bpsdCancel=false;
|
|
windowBpsd=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
if (window)
|
|
{
|
|
gtk_window_set_modal(GTK_WINDOW(windowBpsd), true);
|
|
gtk_window_set_transient_for(GTK_WINDOW(windowBpsd), GTK_WINDOW(window));
|
|
}
|
|
gtk_window_set_title(GTK_WINDOW(windowBpsd), flipsversion);
|
|
|
|
labelBpsd=gtk_label_new("Please wait... 99.9%");
|
|
gtk_container_add(GTK_CONTAINER(windowBpsd), labelBpsd);
|
|
GtkRequisition size;
|
|
gtk_widget_get_preferred_size(labelBpsd, NULL, &size);
|
|
gtk_label_set_text(GTK_LABEL(labelBpsd), "Please wait... 0.0%");
|
|
gtk_widget_set_size_request(labelBpsd, size.width, size.height);
|
|
gtk_window_set_resizable(GTK_WINDOW(windowBpsd), false);
|
|
|
|
gtk_misc_set_alignment(GTK_MISC(labelBpsd), 0.0f, 0.5f);
|
|
|
|
gtk_widget_show_all(windowBpsd);
|
|
}
|
|
|
|
bool bpsdeltaProgress(void* userdata, size_t done, size_t total)
|
|
{
|
|
if (bpsdeltaGetProgress(done, total))
|
|
{
|
|
gtk_label_set_text(GTK_LABEL(labelBpsd), bpsdProgStr);
|
|
}
|
|
gtk_main_iteration_do(false);
|
|
return !bpsdCancel;
|
|
}
|
|
|
|
void bpsdeltaEnd()
|
|
{
|
|
if (!bpsdCancel) gtk_widget_destroy(windowBpsd);
|
|
}
|
|
|
|
char * SelectRom(const char * defaultname, const char * title, bool isForSaving)
|
|
{
|
|
GtkWidget* dialog;
|
|
if (!isForSaving)
|
|
{
|
|
dialog=gtk_file_chooser_dialog_new(title, GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
|
}
|
|
else
|
|
{
|
|
dialog=gtk_file_chooser_dialog_new(title, GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
"_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL);
|
|
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defaultname);
|
|
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), true);
|
|
}
|
|
|
|
GtkFileFilter* filterRom=gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filterRom, "Most Common ROM Files");
|
|
gtk_file_filter_add_pattern(filterRom, "*.smc");
|
|
gtk_file_filter_add_pattern(filterRom, "*.sfc");
|
|
gtk_file_filter_add_pattern(filterRom, "*.nes");
|
|
gtk_file_filter_add_pattern(filterRom, "*.gb");
|
|
gtk_file_filter_add_pattern(filterRom, "*.gbc");
|
|
gtk_file_filter_add_pattern(filterRom, "*.gba");
|
|
gtk_file_filter_add_pattern(filterRom, "*.vb");
|
|
gtk_file_filter_add_pattern(filterRom, "*.sms");
|
|
gtk_file_filter_add_pattern(filterRom, "*.smd");
|
|
gtk_file_filter_add_pattern(filterRom, "*.ngp");
|
|
gtk_file_filter_add_pattern(filterRom, "*.n64");
|
|
gtk_file_filter_add_pattern(filterRom, "*.z64");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterRom);
|
|
|
|
GtkFileFilter* filterAll=gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filterAll, "All files");
|
|
gtk_file_filter_add_pattern(filterAll, "*");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterAll);
|
|
|
|
if (state.createFromAllFiles) gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filterAll);
|
|
else gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filterRom);
|
|
|
|
char * ret=NULL;
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
|
{
|
|
ret=gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
|
|
}
|
|
|
|
GtkFileFilter* thisfilter=gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
|
|
if (thisfilter==filterRom) state.createFromAllFiles=false;
|
|
if (thisfilter==filterAll) state.createFromAllFiles=true;
|
|
|
|
gtk_widget_destroy(dialog);
|
|
return ret;
|
|
}
|
|
|
|
GSList * SelectPatches(bool allowMulti, bool demandLocal)
|
|
{
|
|
GtkWidget* dialog=gtk_file_chooser_dialog_new(allowMulti?"Select Patches to Use":"Select Patch to Use", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
|
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), allowMulti);
|
|
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), demandLocal);
|
|
|
|
GtkFileFilter* filter;
|
|
|
|
filter=gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, "All supported patches (*.bps, *.ips)");
|
|
gtk_file_filter_add_pattern(filter, "*.bps");
|
|
gtk_file_filter_add_pattern(filter, "*.ips");
|
|
gtk_file_filter_add_pattern(filter, "*.ups");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
//apparently the file chooser takes ownership of the filter. would be nice to document that in gtk_file_chooser_set_filter...
|
|
|
|
filter=gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, "All files");
|
|
gtk_file_filter_add_pattern(filter, "*");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog))!=GTK_RESPONSE_ACCEPT)
|
|
{
|
|
gtk_widget_destroy(dialog);
|
|
return NULL;
|
|
}
|
|
|
|
GSList * ret;
|
|
if (demandLocal) ret=gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
|
|
else ret=gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
return ret;
|
|
}
|
|
|
|
void ShowMessage(struct errorinfo errinf)
|
|
{
|
|
GtkMessageType errorlevels[]={ GTK_MESSAGE_OTHER, GTK_MESSAGE_OTHER, GTK_MESSAGE_WARNING, GTK_MESSAGE_WARNING, GTK_MESSAGE_ERROR, GTK_MESSAGE_ERROR };
|
|
GtkWidget* dialog=gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, errorlevels[errinf.level], GTK_BUTTONS_CLOSE, "%s",errinf.description);
|
|
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
|
|
|
|
enum worsterrorauto { ea_none, ea_warning, ea_invalid, ea_io_rom_write, ea_io_rom_read, ea_no_auto, ea_io_read_patch };
|
|
|
|
struct multiapplystateauto {
|
|
enum worsterrorauto error;
|
|
bool anySuccess;
|
|
|
|
const char * foundRom;
|
|
bool canUseFoundRom;
|
|
bool usingFoundRom;
|
|
};
|
|
|
|
static void ApplyPatchMultiAutoSub(gpointer data, gpointer user_data)
|
|
{
|
|
#define max(a,b) ((a)>(b)?(a):(b))
|
|
#define error(which) do { state->error=max(state->error, which); } while(0)
|
|
gchar * patchpath=(gchar*)data;
|
|
struct multiapplystateauto * state=(struct multiapplystateauto*)user_data;
|
|
|
|
file* patch = file::create(patchpath);
|
|
if (!patch)
|
|
{
|
|
state->canUseFoundRom=false;
|
|
error(ea_io_read_patch);
|
|
return;
|
|
}
|
|
|
|
bool possible;
|
|
const char * rompath=FindRomForPatch(patch, &possible);
|
|
if (state->usingFoundRom)
|
|
{
|
|
if (!rompath) rompath=state->foundRom;
|
|
else goto cleanup;
|
|
}
|
|
else
|
|
{
|
|
if (!rompath)
|
|
{
|
|
if (possible) state->canUseFoundRom=false;
|
|
error(ea_no_auto);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if (!state->foundRom) state->foundRom=rompath;
|
|
if (state->foundRom!=rompath) state->canUseFoundRom=false;
|
|
|
|
{
|
|
const char * romext=GetExtension(rompath);
|
|
gchar * outrompath=g_strndup(patchpath, strlen(patchpath)+strlen(romext)+1);
|
|
strcpy(GetExtension(outrompath), romext);
|
|
|
|
struct errorinfo errinf=ApplyPatchMem(patch, rompath, true, outrompath, NULL, true);
|
|
if (errinf.level==el_broken) error(ea_invalid);
|
|
if (errinf.level==el_notthis) error(ea_no_auto);
|
|
if (errinf.level==el_warning) error(ea_warning);
|
|
if (errinf.level<el_notthis) state->anySuccess=true;
|
|
else state->canUseFoundRom=false;
|
|
g_free(outrompath);
|
|
}
|
|
|
|
cleanup:
|
|
delete patch;
|
|
#undef max
|
|
#undef error
|
|
}
|
|
|
|
static bool ApplyPatchMultiAuto(GSList * filenames)
|
|
{
|
|
struct multiapplystateauto state;
|
|
state.error=ea_none;
|
|
state.anySuccess=false;
|
|
|
|
state.foundRom=NULL;
|
|
state.canUseFoundRom=true;
|
|
state.usingFoundRom=false;
|
|
|
|
g_slist_foreach(filenames, ApplyPatchMultiAutoSub, &state);
|
|
if (state.error==ea_no_auto && state.foundRom && state.canUseFoundRom)
|
|
{
|
|
state.usingFoundRom=true;
|
|
state.error=ea_none;
|
|
g_slist_foreach(filenames, ApplyPatchMultiAutoSub, &state);
|
|
}
|
|
|
|
if (state.anySuccess)
|
|
{
|
|
struct errorinfo messages[8]={
|
|
{ el_ok, "The patches were applied successfully!" },//ea_none
|
|
{ el_warning, "The patches were applied, but one or more may be mangled or improperly created..." },//ea_warning
|
|
{ el_warning, "Some patches were applied, but not all of the given patches are valid..." },//ea_invalid
|
|
{ el_warning, "Some patches were applied, but not all of the desired ROMs could be created..." },//ea_rom_io_write
|
|
{ el_warning, "Some patches were applied, but not all of the input ROMs could be read..." },//ea_io_rom_read
|
|
{ el_warning, "Some patches were applied, but not all of the required input ROMs could be located..." },//ea_no_auto
|
|
{ el_warning, "Some patches were applied, but not all of the given patches could be read..." },//ea_io_read_patch
|
|
{ el_broken, NULL },//ea_no_found
|
|
};
|
|
ShowMessage(messages[state.error]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
enum worsterror { e_none, e_warning_notthis, e_warning, e_invalid_this, e_invalid, e_io_write, e_io_read, e_io_read_rom };
|
|
|
|
struct multiapplystate {
|
|
const gchar * romext;
|
|
struct mem rommem;
|
|
bool anySuccess;
|
|
bool removeHeaders;
|
|
enum worsterror worsterror;
|
|
};
|
|
|
|
void ApplyPatchMulti(gpointer data, gpointer user_data)
|
|
{
|
|
char * patchname=(char*)data;
|
|
struct multiapplystate * state=(struct multiapplystate*)user_data;
|
|
#define max(a,b) ((a)>(b)?(a):(b))
|
|
#define error(which) do { state->worsterror=max(state->worsterror, which); } while(0)
|
|
|
|
file* patch = file::create(patchname);
|
|
if (patch)
|
|
{
|
|
char * outromname=g_strndup(patchname, strlen(patchname)+strlen(state->romext)+1);
|
|
char * outromext=GetExtension(outromname);
|
|
strcpy(outromext, state->romext);
|
|
|
|
struct errorinfo errinf=ApplyPatchMem2(patch, state->rommem, state->removeHeaders, true, outromname, NULL);
|
|
if (errinf.level==el_broken) error(e_invalid);
|
|
if (errinf.level==el_notthis) error(e_invalid_this);
|
|
if (errinf.level==el_warning) error(e_warning);
|
|
if (errinf.level==el_unlikelythis) error(e_warning_notthis);
|
|
if (errinf.level<el_notthis) state->anySuccess=true;
|
|
|
|
delete patch;
|
|
g_free(outromname);
|
|
}
|
|
else error(e_io_read);
|
|
g_free(data);
|
|
#undef max
|
|
#undef error
|
|
}
|
|
|
|
void a_ApplyPatch(GtkButton* widget, gpointer user_data)
|
|
{
|
|
gchar * filename=(gchar*)user_data;
|
|
GSList * filenames=NULL;
|
|
if (!filename)
|
|
{
|
|
filenames=SelectPatches(true, false);
|
|
if (!filenames) return;
|
|
if (!filenames->next) filename=(gchar*)filenames->data;
|
|
}
|
|
if (filename)//do not change to else, this is set if the user picks only one file
|
|
{
|
|
struct errorinfo errinf;
|
|
file* patchfile = file::create(filename);
|
|
if (!patchfile)
|
|
{
|
|
errinf=(struct errorinfo){ el_broken, "Couldn't read input patch. What exactly are you doing?" };
|
|
ShowMessage(errinf);
|
|
return;
|
|
}
|
|
|
|
char * inromname=NULL;
|
|
if (state.autoSelectRom) inromname=g_strdup(FindRomForPatch(patchfile, NULL)); // g_strdup(NULL) is NULL
|
|
if (!inromname) inromname=SelectRom(NULL, "Select File to Patch", false);
|
|
if (!inromname) goto cleanup;
|
|
|
|
{
|
|
char * patchbasename=GetBaseName(filename);
|
|
const char * inromext=GetExtension(inromname);
|
|
if (!inromext) inromext="";
|
|
|
|
char * outromname_d=g_strndup(patchbasename, strlen(patchbasename)+strlen(inromext)+1);
|
|
char * ext=GetExtension(outromname_d);
|
|
strcpy(ext, inromext);
|
|
|
|
char * outromname=SelectRom(outromname_d, "Select Output File", true);
|
|
if (outromname)
|
|
{
|
|
struct errorinfo errinf=ApplyPatchMem(patchfile, inromname, true, outromname, NULL, state.autoSelectRom);
|
|
ShowMessage(errinf);
|
|
}
|
|
g_free(inromname);
|
|
g_free(outromname_d);
|
|
g_free(outromname);
|
|
}
|
|
|
|
cleanup:
|
|
delete patchfile;
|
|
}
|
|
else
|
|
{
|
|
if (state.autoSelectRom)
|
|
{
|
|
if (ApplyPatchMultiAuto(filenames))
|
|
{
|
|
g_slist_free_full(filenames, g_free);
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct multiapplystate state;
|
|
char * inromname=SelectRom(NULL, "Select Base File", false);
|
|
state.romext=GetExtension(inromname);
|
|
if (!*state.romext) state.romext=".sfc";
|
|
state.rommem=ReadWholeFile(inromname);
|
|
state.removeHeaders=shouldRemoveHeader(inromname, state.rommem.len);
|
|
state.worsterror=e_none;
|
|
state.anySuccess=false;
|
|
g_slist_foreach(filenames, ApplyPatchMulti, &state);
|
|
g_free(inromname);
|
|
FreeFileMemory(state.rommem);
|
|
struct errorinfo errormessages[2][8]={
|
|
{
|
|
//no error-free
|
|
{ el_ok, NULL },//e_none
|
|
{ el_warning, NULL},//e_warning_notthis
|
|
{ el_warning, NULL},//e_warning
|
|
{ el_broken, "None of these are valid patches for this ROM!" },//e_invalid_this
|
|
{ el_broken, "None of these are valid patches!" },//e_invalid
|
|
{ el_broken, "Couldn't write any ROMs. Are you on a read-only medium?" },//e_io_write
|
|
{ el_broken, "Couldn't read any patches. What exactly are you doing?" },//e_io_read
|
|
{ el_broken, "Couldn't read the input ROM. What exactly are you doing?" },//e_io_read_rom
|
|
},{
|
|
//at least one error-free
|
|
{ el_ok, "The patches were applied successfully!" },//e_none
|
|
{ el_warning, "The patches were applied, but one or more is unlikely to be intended for this ROM..." },//e_warning_notthis
|
|
{ el_warning, "The patches were applied, but one or more may be mangled or improperly created..." },//e_warning
|
|
{ el_warning, "Some patches were applied, but not all of the given patches are valid for this ROM..." },//e_invalid_this
|
|
{ el_warning, "Some patches were applied, but not all of the given patches are valid..." },//e_invalid
|
|
{ el_warning, "Some patches were applied, but not all of the desired ROMs could be created..." },//e_io_write
|
|
{ el_warning, "Some patches were applied, but not all of the given patches could be read..." },//e_io_read
|
|
{ el_broken, NULL,//e_io_read_rom
|
|
},
|
|
}};
|
|
ShowMessage(errormessages[state.anySuccess][state.worsterror]);
|
|
}
|
|
g_slist_free(filenames);
|
|
}
|
|
|
|
void a_CreatePatch(GtkButton* widget, gpointer user_data)
|
|
{
|
|
char * inrom=NULL;
|
|
char * outrom=NULL;
|
|
char * patchname=NULL;
|
|
|
|
inrom=SelectRom(NULL, "Select ORIGINAL UNMODIFIED File to Use", false);
|
|
if (!inrom) goto cleanup;
|
|
outrom=SelectRom(NULL, "Select NEW MODIFIED File to Use", false);
|
|
if (!outrom) goto cleanup;
|
|
if (!strcmp(inrom, outrom))
|
|
{
|
|
ShowMessage((struct errorinfo){ el_broken, "That's the same file! You should really use two different files." });
|
|
goto cleanup;
|
|
}
|
|
|
|
struct {
|
|
const char * filter;
|
|
const char * description;
|
|
} static const typeinfo[]={
|
|
{ "*.bps", "BPS Patch File" },
|
|
{ "*.ips", "IPS Patch File" },
|
|
};
|
|
static const size_t numtypeinfo = sizeof(typeinfo)/sizeof(*typeinfo);
|
|
|
|
{
|
|
char * defpatchname=g_strndup(outrom, strlen(outrom)+4+1);
|
|
char * ext=GetExtension(defpatchname);
|
|
strcpy(ext, typeinfo[state.lastPatchType-1].filter+1);
|
|
|
|
GtkWidget* dialog=gtk_file_chooser_dialog_new("Select File to Save As", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
"_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL);
|
|
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defpatchname);
|
|
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), true);
|
|
|
|
GtkFileFilter* filters[numtypeinfo];
|
|
for (size_t i=0;i<numtypeinfo;i++)
|
|
{
|
|
GtkFileFilter* filter=gtk_file_filter_new();
|
|
filters[i]=filter;
|
|
gtk_file_filter_set_name(filter, typeinfo[i].description);
|
|
gtk_file_filter_add_pattern(filter, typeinfo[i].filter);
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
}
|
|
|
|
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filters[state.lastPatchType-1]);
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
|
{
|
|
patchname=gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
|
|
}
|
|
|
|
GtkFileFilter* filter=gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
|
|
for (size_t i=0;i<numtypeinfo;i++)
|
|
{
|
|
if (filter==filters[i])
|
|
{
|
|
if (state.lastPatchType!=i && !strcmp(GetExtension(patchname), typeinfo[state.lastPatchType-1].filter+1))
|
|
{
|
|
strcpy(GetExtension(patchname), typeinfo[i].filter+1);
|
|
}
|
|
state.lastPatchType=i+1;
|
|
}
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
if (!patchname) goto cleanup;
|
|
|
|
bpsdCancel=false;
|
|
struct errorinfo errinf;
|
|
errinf=CreatePatch(inrom, outrom, (patchtype)state.lastPatchType, NULL, patchname);
|
|
if (!bpsdCancel) ShowMessage(errinf);
|
|
|
|
cleanup:
|
|
g_free(inrom);
|
|
g_free(outrom);
|
|
g_free(patchname);
|
|
}
|
|
|
|
void a_SetEmulator(GtkButton* widget, gpointer user_data);
|
|
void a_ApplyRun(GtkButton* widget, gpointer user_data)
|
|
{
|
|
gchar * patchname=(gchar*)user_data;
|
|
if (!patchname)
|
|
{
|
|
GSList * patchnames=SelectPatches(false, true);
|
|
if (!patchnames) return;
|
|
patchname=(gchar*)patchnames->data;
|
|
g_slist_free(patchnames);
|
|
}
|
|
|
|
file* patchfile = file::create(patchname);
|
|
gchar * romname=NULL;
|
|
{
|
|
if (!patchfile)
|
|
{
|
|
ShowMessage((struct errorinfo){ el_broken, "Couldn't read input patch. What exactly are you doing?" });
|
|
goto cleanup;
|
|
}
|
|
|
|
if (state.autoSelectRom) romname=g_strdup(FindRomForPatch(patchfile, NULL)); // g_strdup(NULL) is NULL
|
|
if (!romname) romname=SelectRom(NULL, "Select Base File", false);
|
|
if (!romname) goto cleanup;
|
|
|
|
if (!state.emulator) a_SetEmulator(NULL, NULL);
|
|
if (!state.emulator) goto cleanup;
|
|
|
|
//gchar * outromname;
|
|
//gint fd=g_file_open_tmp("flipsXXXXXX.smc", &outromname, NULL);
|
|
|
|
gchar * outromname_rel=g_strndup(patchname, strlen(patchname)+4+1);
|
|
strcpy(GetExtension(outromname_rel), GetExtension(romname));
|
|
|
|
GFile* outrom_file=g_file_new_for_commandline_arg(outromname_rel);
|
|
g_free(outromname_rel);
|
|
gchar * outromname;
|
|
if (g_file_is_native(outrom_file)) outromname=g_file_get_path(outrom_file);
|
|
else outromname=g_file_get_uri(outrom_file);
|
|
g_object_unref(outrom_file);
|
|
|
|
struct errorinfo errinf=ApplyPatchMem(patchfile, romname, true, outromname, NULL, state.autoSelectRom);
|
|
if (errinf.level!=el_ok) ShowMessage(errinf);
|
|
if (errinf.level>=el_notthis) goto cleanup;
|
|
|
|
gchar * patchend=GetBaseName(patchname);
|
|
*patchend='\0';
|
|
|
|
gchar * argv[3];
|
|
argv[0]=state.emulator;
|
|
argv[1]=outromname;
|
|
argv[2]=NULL;
|
|
|
|
GPid pid;
|
|
GError* error=NULL;
|
|
if (!g_spawn_async(patchname, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, &error))
|
|
{
|
|
//g_unlink(tempname);//apparently this one isn't in the headers.
|
|
ShowMessage((struct errorinfo){ el_broken, error->message });
|
|
g_error_free(error);
|
|
}
|
|
else g_spawn_close_pid(pid);
|
|
g_free(outromname);
|
|
//close(fd);
|
|
}
|
|
|
|
cleanup:
|
|
delete patchfile;
|
|
g_free(patchname);
|
|
g_free(romname);
|
|
}
|
|
|
|
void a_ShowSettings(GtkButton* widget, gpointer user_data)
|
|
{
|
|
//used mnemonics:
|
|
//E - Select Emulator
|
|
//M - Create ROM
|
|
//U - Run in Emulator
|
|
//A - Enable automatic ROM selector
|
|
|
|
GtkWidget* settingswindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(settingswindow), flipsversion);
|
|
gtk_window_set_resizable(GTK_WINDOW(settingswindow), false);
|
|
gtk_window_set_modal(GTK_WINDOW(settingswindow), true);
|
|
gtk_window_set_transient_for(GTK_WINDOW(settingswindow), GTK_WINDOW(window));
|
|
g_signal_connect(settingswindow, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
|
|
|
GtkGrid* grid=GTK_GRID(gtk_grid_new());
|
|
gtk_grid_set_row_spacing(grid, 3);
|
|
|
|
GtkWidget* button=gtk_button_new_with_mnemonic("Select _Emulator");
|
|
g_signal_connect(button, "clicked", G_CALLBACK(a_SetEmulator), NULL);
|
|
gtk_grid_attach(grid, button, 0,0, 1,1);
|
|
|
|
GtkWidget* text=gtk_label_new("When opening through associations:");
|
|
gtk_grid_attach(grid, text, 0,1, 1,1);
|
|
|
|
GtkGrid* radioGrid=GTK_GRID(gtk_grid_new());
|
|
gtk_grid_set_column_homogeneous(radioGrid, true);
|
|
GtkWidget* emuAssoc;
|
|
emuAssoc=gtk_radio_button_new_with_mnemonic(NULL, "Create RO_M");
|
|
gtk_grid_attach(radioGrid, emuAssoc, 0,0, 1,1);
|
|
emuAssoc=gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(emuAssoc), "R_un in Emulator");
|
|
gtk_grid_attach(radioGrid, emuAssoc, 1,0, 1,1);
|
|
g_object_ref(emuAssoc);//otherwise it, and its value, gets eaten when I close the window, before I can save its value anywhere
|
|
if (state.openInEmulatorOnAssoc) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(emuAssoc), true);
|
|
gtk_grid_attach(grid, GTK_WIDGET(radioGrid), 0,2, 1,1);
|
|
|
|
GtkWidget* autoRom;
|
|
autoRom=gtk_check_button_new_with_mnemonic("Enable _automatic ROM selector");
|
|
if (state.autoSelectRom) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoRom), true);
|
|
g_object_ref(autoRom);
|
|
gtk_grid_attach(grid, autoRom, 0,3, 1,1);
|
|
|
|
gtk_container_add(GTK_CONTAINER(settingswindow), GTK_WIDGET(grid));
|
|
|
|
gtk_widget_show_all(settingswindow);
|
|
gtk_main();
|
|
|
|
state.openInEmulatorOnAssoc=(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(emuAssoc)));
|
|
g_object_unref(emuAssoc);
|
|
state.autoSelectRom=(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(autoRom)));
|
|
g_object_unref(autoRom);
|
|
}
|
|
|
|
gboolean filterExecOnly(const GtkFileFilterInfo* filter_info, gpointer data)
|
|
{
|
|
GFile* file=g_file_new_for_uri(filter_info->uri);
|
|
GFileInfo* info=g_file_query_info(file, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
|
bool ret=g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
|
|
g_object_unref(file);
|
|
g_object_unref(info);
|
|
return ret;
|
|
}
|
|
|
|
void a_SetEmulator(GtkButton* widget, gpointer user_data)
|
|
{
|
|
GtkWidget* dialog=gtk_file_chooser_dialog_new("Select Emulator to Use", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
|
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), true);
|
|
|
|
GtkFileFilter* filter=gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, "Executable files");
|
|
gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_URI, filterExecOnly, NULL, NULL);
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
|
|
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
|
{
|
|
g_free(state.emulator);
|
|
state.emulator=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
|
|
gchar * get_cfgpath()
|
|
{
|
|
static gchar * cfgpath=NULL;
|
|
if (!cfgpath) cfgpath=g_strconcat(g_get_user_config_dir(), "/flipscfg", NULL);
|
|
return cfgpath;
|
|
}
|
|
|
|
void GUILoadConfig()
|
|
{
|
|
if (!canShowGUI) return;
|
|
|
|
struct mem cfgin = file::read(get_cfgpath());
|
|
if (cfgin.len>=10+1+1+1+1+4+4 && !memcmp(cfgin.ptr, "FlipscfgG", 9) && cfgin.ptr[9]==cfgversion)
|
|
{
|
|
state.lastPatchType=cfgin.ptr[10];
|
|
state.createFromAllFiles=cfgin.ptr[11];
|
|
state.openInEmulatorOnAssoc=cfgin.ptr[12];
|
|
state.autoSelectRom=cfgin.ptr[13];
|
|
int len=0;
|
|
len|=cfgin.ptr[14]<<24;
|
|
len|=cfgin.ptr[15]<<16;
|
|
len|=cfgin.ptr[16]<<8;
|
|
len|=cfgin.ptr[17]<<0;
|
|
if (len==0) state.emulator=NULL;
|
|
else
|
|
{
|
|
state.emulator=(gchar*)g_malloc(len+1);
|
|
memcpy(state.emulator, cfgin.ptr+22, len);
|
|
state.emulator[len]=0;
|
|
}
|
|
struct mem romlist={cfgin.ptr+22+len, 0};
|
|
romlist.len|=cfgin.ptr[18]<<24;
|
|
romlist.len|=cfgin.ptr[19]<<16;
|
|
romlist.len|=cfgin.ptr[20]<<8;
|
|
romlist.len|=cfgin.ptr[21]<<0;
|
|
SetRomList(romlist);
|
|
}
|
|
else
|
|
{
|
|
memset(&state, 0, sizeof(state));
|
|
state.lastPatchType=ty_bps;
|
|
}
|
|
free(cfgin.ptr);
|
|
}
|
|
|
|
int GUIShow(const char * filename)
|
|
{
|
|
if (!canShowGUI)
|
|
{
|
|
g_warning("couldn't parse command line arguments, what are you doing?");
|
|
usage();
|
|
}
|
|
|
|
GdkDisplay* display=gdk_display_open(gdk_get_display_arg_name());
|
|
if (!display) display=gdk_display_get_default();
|
|
if (!display)
|
|
{
|
|
g_warning("couldn't connect to display, fix it or use command line");
|
|
usage();
|
|
}
|
|
gdk_display_manager_set_default_display(gdk_display_manager_get(), display);
|
|
|
|
GUILoadConfig();
|
|
|
|
if (filename)
|
|
{
|
|
window=NULL;
|
|
if (state.openInEmulatorOnAssoc==false) a_ApplyPatch(NULL, g_strdup(filename));
|
|
else a_ApplyRun(NULL, g_strdup(filename));
|
|
return 0;
|
|
}
|
|
|
|
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(window), flipsversion);
|
|
gtk_window_set_resizable(GTK_WINDOW(window), false);
|
|
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
|
|
|
GtkGrid* grid=GTK_GRID(gtk_grid_new());
|
|
gtk_grid_set_row_homogeneous(grid, true);
|
|
gtk_grid_set_column_homogeneous(grid, true);
|
|
gtk_grid_set_row_spacing(grid, 5);
|
|
gtk_grid_set_column_spacing(grid, 5);
|
|
GtkWidget* button;
|
|
#define button(x, y, text, function) \
|
|
button=gtk_button_new_with_mnemonic(text); \
|
|
g_signal_connect(button, "clicked", G_CALLBACK(function), NULL); \
|
|
gtk_grid_attach(grid, button, x, y, 1, 1);
|
|
button(0,0, "_Apply Patch", G_CALLBACK(a_ApplyPatch));
|
|
button(1,0, "_Create Patch", G_CALLBACK(a_CreatePatch));
|
|
button(0,1, "Apply and _Run", G_CALLBACK(a_ApplyRun));
|
|
button(1,1, "_Settings", G_CALLBACK(a_ShowSettings));
|
|
#undef button
|
|
|
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(grid));
|
|
|
|
gtk_widget_show_all(window);
|
|
gtk_main();
|
|
|
|
int emulen=state.emulator?strlen(state.emulator):0;
|
|
struct mem romlist=GetRomList();
|
|
struct mem cfgout=(struct mem){ NULL, 10+1+1+1+1+4+4+emulen+romlist.len };
|
|
cfgout.ptr=(uint8_t*)g_malloc(cfgout.len);
|
|
memcpy(cfgout.ptr, "FlipscfgG", 9);
|
|
cfgout.ptr[9]=cfgversion;
|
|
cfgout.ptr[10]=state.lastPatchType;
|
|
cfgout.ptr[11]=state.createFromAllFiles;
|
|
cfgout.ptr[12]=state.openInEmulatorOnAssoc;
|
|
cfgout.ptr[13]=state.autoSelectRom;
|
|
cfgout.ptr[14]=emulen>>24;
|
|
cfgout.ptr[15]=emulen>>16;
|
|
cfgout.ptr[16]=emulen>>8;
|
|
cfgout.ptr[17]=emulen>>0;
|
|
cfgout.ptr[18]=romlist.len>>24;
|
|
cfgout.ptr[19]=romlist.len>>16;
|
|
cfgout.ptr[20]=romlist.len>>8;
|
|
cfgout.ptr[21]=romlist.len>>0;
|
|
memcpy(cfgout.ptr+22, state.emulator, emulen);
|
|
memcpy(cfgout.ptr+22+emulen, romlist.ptr, romlist.len);
|
|
filewrite::write(get_cfgpath(), cfgout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
canShowGUI=gtk_parse_args(&argc, &argv);
|
|
return flipsmain(argc, argv);
|
|
}
|
|
#endif
|