commit 24e6c77eb8d1ed6d0eb6da400c67ea229eca2278 Author: Alcaro Date: Sun Jan 17 18:14:53 2016 +0100 Initial commit - version 1.31 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ceb6250 --- /dev/null +++ b/Makefile @@ -0,0 +1,90 @@ +CFLAGS_gtk = -DFLIPS_GTK $(GTKFLAGS) $(GTKLIBS) +CFLAGS_windows := -DFLIPS_WINDOWS -mwindows -lgdi32 -lcomdlg32 -lcomctl32 -luser32 -lkernel32 -lshell32 -ladvapi32 +CFLAGS_win := $(CFLAGS_windows) +CFLAGS_cli := -DFLIPS_CLI + +CFLAGS_G = -fno-rtti -fno-exceptions + +FNAME_gtk := flips +FNAME_windows := flips.exe +FNAME_win := $(FNAME_windows) +FNAME_cli := flips + +CXX = g++ + +XFILES := + +ifeq ($(TARGET),) + targetmachine := $(shell $(CC) -dumpmachine) + ifneq ($(findstring mingw,$(targetmachine)),) + TARGET := windows + else ifneq ($(findstring linux,$(targetmachine)),) + TARGET := gtk + else + TARGET := + endif +endif + +ifeq ($(TARGET),win) + override TARGET := windows +endif + +ifeq ($(TARGET),) + uname := $(shell uname -a) + ifeq ($(uname),) + TARGET := windows + else ifneq ($(findstring CYGWIN,$(uname)),) + TARGET := windows + else ifneq ($(findstring Darwin,$(uname)),) + TARGET := cli + else + TARGET := gtk + endif +endif + +ifeq ($(TARGET),gtk) + ifeq ($(GTKFLAGS),) + GTKFLAGS := $(shell pkg-config --cflags --libs gtk+-3.0 2>/dev/null) + endif + ifeq ($(GTKFLAGS),) + $(warning pkg-config can't find gtk+-3.0, or pkg-config itself can't be found) + $(warning if you have the needed files installed, specify their locations and names with `make 'GTKFLAGS=-I/usr/include' 'GTKLIBS=-L/usr/lib -lgtk'') + $(warning if not, the package name under Debian and derivates is `libgtk-3-dev'; on other distros, consult a search engine) + $(warning switching to CLI build) + TARGET := cli + endif +endif + +all: $(FNAME_$(TARGET)) + +ifeq ($(TARGET),windows) + XFILES += rc.o +rc.o: + windres flips.rc rc.o +endif + +MOREFLAGS := $(CFLAGS_$(TARGET)) + + +ifneq ($(DIVSUF),no) + DIVSUF_EXIST := $(lastword $(wildcard libdivsufsort-2*/include/config.h)) + DIVSUF := $(subst /include/config.h,,$(DIVSUF_EXIST)) + ifneq ($(DIVSUF),) + DIVSUF_SOURCES := $(filter-out %/utils.c,$(wildcard $(DIVSUF)/lib/*.c)) + ifeq ($(DIVSUF_OPENMP),) + ifeq ($(TARGET),gtk) + DIVSUF_OPENMP := -fopenmp + endif + endif + DIVSUF_CFLAGS := -DUSE_DIVSUFSORT -I$(DIVSUF)/include -DHAVE_CONFIG_H -DNDEBUG -D__STDC_FORMAT_MACROS $(DIVSUF_OPENMP) + DIVSUF_LFLAGS := + MOREFLAGS += $(DIVSUF_CFLAGS) $(DIVSUF_SOURCES) $(DIVSUF_LFLAGS) + else + $(warning no libdivsufsort-2.x detected; switching to fallback SA-IS) + $(warning libdivsufsort is approximately twice as fast as SA-IS) + endif +endif + + +$(FNAME_$(TARGET)): *.cpp $(XFILES) + $(CXX) $^ -std=c++98 $(CFLAGS) $(LFLAGS) $(CFLAGS_G) $(MOREFLAGS) $(XFILES) -o$@ diff --git a/bps.ico b/bps.ico new file mode 100644 index 0000000..9beb98b Binary files /dev/null and b/bps.ico differ diff --git a/crc32.cpp b/crc32.cpp new file mode 100644 index 0000000..3abeca3 --- /dev/null +++ b/crc32.cpp @@ -0,0 +1,26 @@ +//Module name: crc32 +//Author: Alcaro +//Date: June 3, 2015 +//Licence: GPL v3.0 or higher + +#include "crc32.h" + +static const uint32_t crctable_4bits[]={ + 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C, + 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C, 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C, +}; +uint32_t crc32_update(const uint8_t* data, size_t len, uint32_t crc) +{ + crc = ~crc; + for (size_t i=0;i>4); + crc = crctable_4bits[(crc^(data[i]>>4))&0x0F] ^ (crc>>4); + } + return ~crc; +} + +uint32_t crc32(const uint8_t* data, size_t len) +{ + return crc32_update(data, len, 0); +} diff --git a/crc32.h b/crc32.h new file mode 100644 index 0000000..e5fb5a7 --- /dev/null +++ b/crc32.h @@ -0,0 +1,10 @@ +//Module name: crc32 +//Author: Alcaro +//Date: June 3, 2015 +//Licence: GPL v3.0 or higher + +#include +#include + +uint32_t crc32(const uint8_t* data, size_t len); +uint32_t crc32_update(const uint8_t* data, size_t len, uint32_t crc); diff --git a/flips-cli.cpp b/flips-cli.cpp new file mode 100644 index 0000000..5c9f156 --- /dev/null +++ b/flips-cli.cpp @@ -0,0 +1,73 @@ +//Module name: Floating IPS, command line frontend +//Author: Alcaro +//Date: December 22, 2014 +//Licence: GPL v3.0 or higher + +#include "flips.h" + +#ifdef FLIPS_CLI +class file_libc : public file { + size_t size; + FILE* io; + +public: + static file* create(const char * filename) + { + FILE* f = fopen(filename, "rb"); + if (!f) return NULL; + return new file_libc(f); + } + +private: + file_libc(FILE* io) : io(io) + { + fseek(io, 0, SEEK_END); + size = ftell(io); + } + +public: + size_t len() { return size; } + + bool read(uint8_t* target, size_t start, size_t len) + { + fseek(io, start, SEEK_SET); + return (fread(target, 1,len, io) == len); + } + + ~file_libc() { fclose(io); } +}; + +file* file::create(const char * filename) { return file_libc::create(filename); } + + +class filewrite_libc : public filewrite { + FILE* io; + +public: + static filewrite* create(const char * filename) + { + FILE* f = fopen(filename, "wb"); + if (!f) return NULL; + return new filewrite_libc(f); + } + +private: + filewrite_libc(FILE* io) : io(io) {} + +public: + bool append(const uint8_t* data, size_t len) + { + return (fwrite(data, 1,len, io)==len); + } + + ~filewrite_libc() { fclose(io); } +}; + +filewrite* filewrite::create(const char * filename) { return filewrite_libc::create(filename); } + + +int main(int argc, char * argv[]) +{ + return flipsmain(argc, argv); +} +#endif diff --git a/flips-gtk.cpp b/flips-gtk.cpp new file mode 100644 index 0000000..d5505d3 --- /dev/null +++ b/flips-gtk.cpp @@ -0,0 +1,904 @@ +//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 + +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.levelanySuccess=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.levelanySuccess=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;idata; + 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); +} + +int ShowGUI(const char * filename) +{ + if (!canShowGUI) + { + g_warning("couldn't parse command line arguments, fix them or use command line"); + 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); + + const gchar * cfgdir=g_get_user_config_dir(); + gchar * cfgpath=g_strndup(cfgdir, strlen(cfgdir)+strlen("/flipscfg")+1); + strcat(cfgpath, "/flipscfg"); + struct mem cfgin = file::read(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); + + 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(cfgpath, cfgout); + + return 0; +} + +int main(int argc, char * argv[]) +{ + canShowGUI=gtk_parse_args(&argc, &argv); + return flipsmain(argc, argv); +} +#endif diff --git a/flips-w32.cpp b/flips-w32.cpp new file mode 100644 index 0000000..30a567e --- /dev/null +++ b/flips-w32.cpp @@ -0,0 +1,966 @@ +//Module name: Floating IPS, Windows frontend +//Author: Alcaro +//Date: June 18, 2015 +//Licence: GPL v3.0 or higher + +#include "flips.h" + +#ifdef FLIPS_WINDOWS +class file_w32 : public file { + size_t size; + HANDLE io; + +public: + static file* create(LPCWSTR filename) + { + HANDLE io = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (io==INVALID_HANDLE_VALUE) return NULL; + return new file_w32(io, (size_t)0); + } + +private: + file_w32(HANDLE io, uint32_t sizetsize) : io(io) + { + size = GetFileSize(io, NULL); + } + + file_w32(HANDLE io, uint64_t sizetsize) : io(io) + { + GetFileSizeEx(io, (PLARGE_INTEGER)&size); + } + +public: + size_t len() { return size; } + + bool read(uint8_t* target, size_t start, size_t len) + { + OVERLAPPED ov = {0}; + ov.Offset = start; + ov.OffsetHigh = start>>16>>16; + DWORD actuallen; + return (ReadFile(io, target, len, &actuallen, &ov) && len==actuallen); + } + + ~file_w32() { CloseHandle(io); } +}; + +file* file::create(LPCWSTR filename) { return file_w32::create(filename); } + + +class filewrite_w32 : public filewrite { + HANDLE io; + +public: + static filewrite* create(LPCWSTR filename) + { + HANDLE io = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (!io) return NULL; + return new filewrite_w32(io); + } + +private: + filewrite_w32(HANDLE io) : io(io) {} + +public: + bool append(const uint8_t* data, size_t len) + { + DWORD truelen; + return (WriteFile(io, data, len, &truelen, NULL) && truelen==len); + } + + ~filewrite_w32() { CloseHandle(io); } +}; + +filewrite* filewrite::create(LPCWSTR filename) { return filewrite_w32::create(filename); } + + +HWND hwndMain=NULL; +HWND hwndSettings=NULL; + +struct { + char signature[9]; + unsigned char cfgversion; + unsigned char lastRomType; + bool openInEmulatorOnAssoc; + bool enableAutoRomSelector; + enum patchtype lastPatchType; + int windowleft; + int windowtop; +} static state; +#define mycfgversion 2 +WCHAR * st_emulator=NULL; +void set_st_emulator_len(LPCWSTR newemu, int len) +{ + free(st_emulator); + st_emulator=(WCHAR*)malloc((len+1)*sizeof(WCHAR)); + if (newemu) memcpy(st_emulator, newemu, len*sizeof(WCHAR)); + st_emulator[len]='\0'; +} +void set_st_emulator(LPCWSTR newemu) +{ + set_st_emulator_len(newemu, wcslen(newemu)); +} + + +HWND hwndProgress; +LRESULT CALLBACK bpsdProgressWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +bool bpsdCancel; + +void bpsdeltaBegin() +{ + bpsdCancel=false; + RECT mainwndpos; + GetWindowRect(hwndMain, &mainwndpos); + hwndProgress=CreateWindowA( + "floatingmunchers", flipsversion, + WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_BORDER, + mainwndpos.left+53, mainwndpos.top+27, 101, 39, hwndMain, NULL, GetModuleHandle(NULL), NULL); + SetWindowLongPtrA(hwndProgress, GWLP_WNDPROC, (LONG_PTR)bpsdProgressWndProc); + + ShowWindow(hwndProgress, SW_SHOW); + EnableWindow(hwndMain, FALSE); + + bpsdeltaProgress(NULL, 0, 1); +} + +bool bpsdeltaProgress(void* userdata, size_t done, size_t total) +{ + if (!bpsdeltaGetProgress(done, total)) return !bpsdCancel; + if (hwndProgress) InvalidateRect(hwndProgress, NULL, false); + MSG Msg; + while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&Msg); + return !bpsdCancel; +} + +LRESULT CALLBACK bpsdProgressWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_ERASEBKGND: return TRUE; + case WM_PAINT: + { + PAINTSTRUCT ps; + RECT rc; + BeginPaint(hwnd, &ps); + GetClientRect(hwnd, &rc); + FillRect(ps.hdc, &rc, GetSysColorBrush(COLOR_3DFACE)); + SetBkColor(ps.hdc, GetSysColor(COLOR_3DFACE)); + SelectObject(ps.hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT)); + DrawTextA(ps.hdc, bpsdProgStr, -1, &rc, DT_CENTER | DT_NOCLIP); + EndPaint(hwnd, &ps); + } + break; + case WM_CLOSE: + bpsdCancel=true; + break; + default: + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } + return 0; +} + +void bpsdeltaEnd() +{ + EnableWindow(hwndMain, TRUE); + DestroyWindow(hwndProgress); + hwndProgress=NULL; +} + + +bool SelectRom(LPWSTR filename, LPCWSTR title, bool output) +{ + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize=sizeof(ofn); + ofn.hwndOwner=hwndMain; + ofn.lpstrFilter=TEXT("Most Common ROM Files\0*.smc;*.sfc;*.nes;*.gb;*.gbc;*.gba;*.vb;*.sms;*.smd;*.ngp;*.n64;*.z64\0All Files (*.*)\0*.*\0"); + ofn.lpstrFile=filename; + ofn.nMaxFile=MAX_PATH; + ofn.nFilterIndex=state.lastRomType; + ofn.lpstrTitle=title; + ofn.Flags=OFN_PATHMUSTEXIST|(output?OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT:OFN_FILEMUSTEXIST); + ofn.lpstrDefExt=TEXT("smc"); + if (!output && !GetOpenFileName(&ofn)) return false; + if ( output && !GetSaveFileName(&ofn)) return false; + state.lastRomType=ofn.nFilterIndex; + return true; +} + +UINT mboxtype[]={ MB_OK, MB_OK, MB_OK|MB_ICONWARNING, MB_OK|MB_ICONWARNING, MB_OK|MB_ICONERROR, MB_OK|MB_ICONERROR }; + +LPCWSTR patchextensions[]={ + NULL,//unused, ty_null + TEXT("bps"), + TEXT("ips"), +}; + +static struct errorinfo error(errorlevel level, const char * text) +{ + struct errorinfo errinf = { level, text }; + return errinf; +} + +int a_ApplyPatch(LPCWSTR clipatchname) +{ + WCHAR patchnames[65536]; + patchnames[0]='\0'; + bool multiplePatches; + if (clipatchname) + { + multiplePatches=false; + wcscpy(patchnames, clipatchname); + } + else + { + //get patch names + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize=sizeof(ofn); + ofn.hwndOwner=hwndMain; + ofn.lpstrFilter=TEXT("All supported patches (*.ips, *.bps)\0*.ips;*.bps;*.ups\0All files (*.*)\0*.*\0"); + ofn.lpstrFile=patchnames; + ofn.nMaxFile=65535; + ofn.lpstrTitle=TEXT("Select Patches to Use"); + ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_ALLOWMULTISELECT|OFN_EXPLORER; + ofn.lpstrDefExt=patchextensions[state.lastPatchType]; + if (!GetOpenFileName(&ofn)) return 0; + multiplePatches=(ofn.nFileOffset && patchnames[ofn.nFileOffset-1]=='\0'); + } + + //get rom name and apply + + if (!multiplePatches) + { + file* patch = file::create(patchnames); + if (patch) + { + + WCHAR inromname_buf[MAX_PATH]; + LPCWSTR inromname=NULL; + if (state.enableAutoRomSelector) inromname=FindRomForPatch(patch, NULL); + if (!inromname) + { + inromname=inromname_buf; + inromname_buf[0]='\0'; + if (!SelectRom(inromname_buf, TEXT("Select File to Patch"), false)) goto cancel; + } + WCHAR outromname[MAX_PATH]; + wcscpy(outromname, inromname); + LPWSTR outrompath=GetBaseName(outromname); + LPWSTR patchpath=GetBaseName(patchnames); + if (outrompath && patchpath) + { + wcscpy(outrompath, patchpath); + LPWSTR outromext=GetExtension(outrompath); + LPWSTR inromext=GetExtension(inromname); + if (*inromext && *outromext) wcscpy(outromext, inromext); + } + if (!SelectRom(outromname, TEXT("Select Output File"), true)) goto cancel; + struct errorinfo errinf=ApplyPatchMem(patch, inromname, true, outromname, NULL, state.enableAutoRomSelector); + delete patch; + MessageBoxA(hwndMain, errinf.description, flipsversion, mboxtype[errinf.level]); + return errinf.level; + } + cancel: + delete patch; + return 0; + } + else + { +#define max(a, b) (a > b ? a : b) + if (state.enableAutoRomSelector) + { + LPCWSTR foundRom=NULL; + bool canUseFoundRom=true; + bool usingFoundRom=false; + + redo: ; + WCHAR thisFileNameWithPath[MAX_PATH]; + bool anySuccess=false; + enum { e_none, e_notice, e_warning, e_invalid, e_io_rom_write, e_io_rom_read, e_no_auto, e_io_read_patch } worsterror=e_none; + LPCSTR messages[8]={ + "The patches were applied successfully!",//e_none + "The patches were applied successfully!",//e_notice (ignore) + "The patches were applied, but one or more may be mangled or improperly created...",//e_warning + "Some patches were applied, but not all of the given patches are valid...",//e_invalid + "Some patches were applied, but not all of the desired ROMs could be created...",//e_rom_io_write + "Some patches were applied, but not all of the input ROMs could be read...",//e_io_rom_read + "Some patches were applied, but not all of the required input ROMs could be located...",//e_no_auto + "Some patches were applied, but not all of the given patches could be read...",//e_io_read_patch + }; + + wcscpy(thisFileNameWithPath, patchnames); + LPWSTR thisFileName=wcschr(thisFileNameWithPath, '\0'); + *thisFileName='\\'; + thisFileName++; + + LPWSTR thisPatchName=wcschr(patchnames, '\0')+1; + while (*thisPatchName) + { + wcscpy(thisFileName, thisPatchName); + file* patch = file::create(thisFileNameWithPath); + { + if (!patch) + { + worsterror=max(worsterror, e_io_read_patch); + canUseFoundRom=false; + goto multi_auto_next; + } + bool possible; + LPCWSTR romname=FindRomForPatch(patch, &possible); + if (usingFoundRom) + { + if (!romname) romname=foundRom; + else goto multi_auto_next; + } + else + { + if (!romname) + { + if (possible) canUseFoundRom=false; + worsterror=max(worsterror, e_no_auto); + goto multi_auto_next; + } + } + if (!foundRom) foundRom=romname; + if (foundRom!=romname) canUseFoundRom=false; + + wcscpy(GetExtension(thisFileName), GetExtension(romname)); + struct errorinfo errinf=ApplyPatchMem(patch, romname, true, thisFileNameWithPath, NULL, true); + + if (errinf.level==el_broken) worsterror=max(worsterror, e_invalid); + if (errinf.level==el_notthis) worsterror=max(worsterror, e_no_auto); + if (errinf.level==el_warning) worsterror=max(worsterror, e_warning); + if (errinf.level=el_notthis) return el_broken; + + delete patch; + if (rommem.ptr) FreeFileMemory(rommem); + if (patchedmem.ptr) free(patchedmem.ptr); + + WCHAR cmdline[1+MAX_PATH+3+MAX_PATH+1+1]; + swprintf(cmdline, 1+MAX_PATH+3+MAX_PATH+1+1, TEXT("\"%s\" \"%s\""), st_emulator, outfilename); + WCHAR * dirend=GetBaseName(patchpath); + if (dirend) *dirend='\0'; + STARTUPINFO startupinfo; + ZeroMemory(&startupinfo, sizeof(STARTUPINFO)); + PROCESS_INFORMATION processinformation; + if (!CreateProcess(st_emulator, cmdline, NULL, NULL, FALSE, 0, NULL, patchpath, &startupinfo, &processinformation)) + { + MessageBoxA(hwndMain, "Couldn't open emulator.", flipsversion, mboxtype[el_broken]); + //DeleteFile(tempfilename); + return el_broken; + } + + //I don't clean up the temp file when the emulator is done. + //- It would just force me to keep track of a bunch of state. + //- It'd force me to not exit when the window is closed. + //- The bsnes profile selector would confuse it. + //- The emulator may have created a bunch of other files, for example SRAM. + //Few other apps clean up anyways. + CloseHandle(processinformation.hProcess); + CloseHandle(processinformation.hThread); + return errinf.level; +} + +void a_AssignFileTypes(bool checkonly); + +HWND assocText; +HWND assocButton; +void a_ShowSettings() +{ + if (hwndSettings) + { + SetActiveWindow(hwndSettings); + return; + } + + hwndSettings=CreateWindowA( + "floatingmunchers", flipsversion, + WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_BORDER|WS_MINIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, 3+6+202+6+3, 21 + 6+23+6+23+3+13+1+17+4+17+6 + 3, NULL, NULL, GetModuleHandle(NULL), NULL); + + HFONT hfont=(HFONT)GetStockObject(DEFAULT_GUI_FONT); + HWND item; + + int x=6; + int y=6; + int lineheight; + +#define endline(padding) do { x=6; y+=lineheight+padding; } while(0) +#define line(height) lineheight=height + +#define widget(type, style, text, w, h, action) \ + do { \ + int thisy=y+(lineheight-h)/2; \ + item=CreateWindowA(type, text, WS_CHILD|WS_TABSTOP|WS_VISIBLE|style, \ + x, thisy, w, h, hwndSettings, (HMENU)(action), GetModuleHandle(NULL), NULL); \ + SendMessage(item, WM_SETFONT, (WPARAM)hfont, 0); \ + x+=w+6; \ + } while(0) + +#define firstbutton(text, w, h, action) \ + widget(WC_BUTTONA, WS_GROUP|BS_DEFPUSHBUTTON, text, w, h, action) + +#define button(text, w, h, action) \ + widget(WC_BUTTONA, BS_PUSHBUTTON, text, w, h, action) + +#define labelL(text, w, h, action) \ + widget(WC_STATICA, SS_LEFT, text, w, h, action) +#define labelC(text, w, h, action) \ + widget(WC_STATICA, SS_CENTER, text, w, h, action) + +#define radio(text, w, h, action) \ + widget(WC_BUTTONA, BS_AUTORADIOBUTTON, text, w, h, action) +#define check(text, w, h, action) \ + widget(WC_BUTTONA, BS_AUTOCHECKBOX, text, w, h, action) + + line(23); + firstbutton("Select emulator", 202/*94*/, 23, 101); + endline(6); + + line(23); + button("Assign file types", 98, 23, 102); assocButton=item; + labelL("(can not be undone)", 98, 13, 0); assocText=item; + endline(3); + + line(13); + labelC("When opening through associations:", 175, 13, 0); + endline(1); + line(17); + radio("Create ROM", 79, 17, 103); Button_SetCheck(item, (state.openInEmulatorOnAssoc==false)); + radio("Run in emulator", 95, 17, 104); Button_SetCheck(item, (state.openInEmulatorOnAssoc==true)); + endline(4); + + line(17); + check("Enable automatic ROM selector", 202, 17, 105); Button_SetCheck(item, (state.enableAutoRomSelector)); + endline(3); + + ShowWindow(hwndSettings, SW_SHOW); +#undef firstbutton +#undef button +#undef label +#undef radio + //if (!fileTypesAssigned) button(6,68,200,23, "Assign File Types"); +} + +void key_core(bool checkonly, LPCWSTR path, LPCWSTR value, bool * p_hasExts, bool * p_refresh) +{ + HKEY hkey; + DWORD type; + WCHAR truepath[60]; + wcscpy(truepath, TEXT("Software\\Classes\\")); + wcscat(truepath, path); + WCHAR regval[MAX_PATH+30]; + DWORD regvallen; + bool hasExts=true; + if (checkonly) + { + regvallen=sizeof(regval); + if (RegOpenKeyEx(HKEY_CURRENT_USER, truepath, 0, KEY_READ, &hkey)!=ERROR_SUCCESS) goto add; + if (value && RegQueryValueEx(hkey, NULL, NULL, &type, (LPBYTE)regval, ®vallen)!=ERROR_SUCCESS) hasExts=false; + RegCloseKey(hkey); + if (!hasExts) goto add; + if (value && wcsncmp(regval, value, sizeof(regval)/sizeof(*regval))) *p_hasExts=false; + return; + } + else + { + add: + regvallen=sizeof(regval); + if (RegCreateKeyExW(HKEY_CURRENT_USER, truepath, 0, NULL, 0, KEY_WRITE, NULL, &hkey, NULL)==ERROR_SUCCESS) + { + if (value) RegSetValueExW(hkey, NULL, 0, REG_SZ, (BYTE*)value, (wcslen(value)+1)*sizeof(WCHAR)); + RegCloseKey(hkey); + } + if (path[0]=='.') *p_refresh=true; + return; + } +} + +void a_AssignFileTypes(bool checkonly) +{ + WCHAR outstring[MAX_PATH+30]; + outstring[0]='"'; + GetModuleFileNameW(NULL, outstring+1, MAX_PATH); + LPWSTR outstringend=wcschr(outstring, '\0'); + *outstringend='"'; + outstringend++; + + bool hasExts=true; + bool refresh=false; +#define key(path, value) \ + key_core(checkonly, TEXT(path), TEXT(value), &hasExts, &refresh) +#define key_path(path, value) \ + wcscpy(outstringend, TEXT(value)); key_core(checkonly, TEXT(path), outstring, &hasExts, &refresh) +#define key_touch(path) \ + key_core(checkonly, TEXT(path), NULL, &hasExts, &refresh) + + key(".ips", "FloatingIPSFileIPS"); + key("FloatingIPSFileIPS", "Floating IPS File"); + key_path("FloatingIPSFileIPS\\DefaultIcon", ",1"); + key_touch("FloatingIPSFileIPS\\shell"); + key_touch("FloatingIPSFileIPS\\shell\\open"); + key_path("FloatingIPSFileIPS\\shell\\open\\command", " \"%1\""); + + key(".bps", "FloatingIPSFileBPS"); + key("FloatingIPSFileBPS", "Floating IPS File"); + key_path("FloatingIPSFileBPS\\DefaultIcon", ",2"); + key_touch("FloatingIPSFileBPS\\shell"); + key_touch("FloatingIPSFileBPS\\shell\\open"); + key_path("FloatingIPSFileBPS\\shell\\open\\command", " \"%1\""); + + if (refresh) + { + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + + if (hwndMain) + { + RECT wndpos; + GetWindowRect(hwndMain, &wndpos); + MoveWindow(hwndMain, wndpos.left, wndpos.top, 218, 93, true); + } + } + if (!checkonly || hasExts) + { + SetWindowText(assocText, TEXT("(already done)")); + Button_Enable(assocButton, false); + } +} + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_COMMAND: + { + if (wParam==1) a_ApplyPatch(NULL); + if (wParam==2) a_CreatePatch(); + if (wParam==3) a_ApplyRun(NULL); + if (wParam==4) a_ShowSettings(); + + if (wParam==101) a_SetEmulator(); + if (wParam==102) a_AssignFileTypes(false); + if (wParam==103) state.openInEmulatorOnAssoc=false; + if (wParam==104) state.openInEmulatorOnAssoc=true; + if (wParam==105) state.enableAutoRomSelector^=1; + } + break; + case WM_CLOSE: + { + if (hwnd==hwndMain && !IsIconic(hwnd)) + { + RECT wndpos; + GetWindowRect(hwnd, &wndpos); + state.windowleft=wndpos.left; + state.windowtop=wndpos.top; + } + DestroyWindow(hwnd); + } + break; + case WM_DESTROY: + { + if (hwnd==hwndMain) PostQuitMessage(0); + if (hwnd==hwndSettings) hwndSettings=NULL; + break; + } + default: + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } + return 0; +} + +static HFONT try_create_font(const char * name, int size) +{ + return CreateFontA(-size*96/72, 0, 0, 0, FW_NORMAL, + FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, + name); +} + +int ShowMainWindow(HINSTANCE hInstance, int nCmdShow) +{ + WNDCLASSA wc; + wc.style=0; + wc.lpfnWndProc=WindowProc; + wc.cbClsExtra=0; + wc.cbWndExtra=0; + wc.hInstance=GetModuleHandle(NULL); + wc.hIcon=LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(0)); + wc.hCursor=LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground=GetSysColorBrush(COLOR_3DFACE);//(HBRUSH)(COLOR_WINDOW + 1); + wc.lpszMenuName=NULL; + wc.lpszClassName="floatingmunchers"; + RegisterClassA(&wc); + + MSG msg; + hwndMain=CreateWindowA( + "floatingmunchers", flipsversion, + WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_BORDER|WS_MINIMIZEBOX, + state.windowleft, state.windowtop, 204, 93, NULL, NULL, GetModuleHandle(NULL), NULL); + + HFONT hfont=try_create_font("Segoe UI", 9); + if (!hfont) hfont=try_create_font("MS Shell Dlg 2", 8); + if (!hfont) hfont=(HFONT)GetStockObject(DEFAULT_GUI_FONT); + + int buttonid=0; + HWND lastbutton; +#define button(x,y,w,h, text) \ + do { \ + lastbutton=CreateWindowA("BUTTON", text, WS_CHILD|WS_TABSTOP|WS_VISIBLE|(buttonid==0?(BS_DEFPUSHBUTTON|WS_GROUP):(BS_PUSHBUTTON)), \ + x, y, w, h, hwndMain, (HMENU)(uintptr_t)(buttonid+1), GetModuleHandle(NULL), NULL); \ + SendMessage(lastbutton, WM_SETFONT, (WPARAM)hfont, 0); \ + buttonid++; \ + } while(0) + button(6, 6, 90/*77*/,23, "Apply Patch"); SetActiveWindow(lastbutton); + button(104,6, 90/*83*/,23, "Create Patch"); + button(6, 37, 90/*90*/,23, "Apply and Run"); + button(104,37, 90/*59*/,23, "Settings"); + + ShowWindow(hwndMain, nCmdShow); + + a_AssignFileTypes(true); + + while (GetMessageA(&msg, NULL, 0, 0)>0) + { + if (!IsDialogMessageA(hwndMain, &msg)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } + + return msg.wParam; +} + +void ClaimConsole() +{ + //this one makes it act like a console app in all cases except it doesn't create a new console if + // not launched from one (it'd swiftly go away on app exit anyways), and it doesn't like being + // launched from cmd since cmd wants to run a new command if spawning a gui app (I can't make it + // not be a gui app because that flashes a console; it acts sanely from batch files) + + bool claimstdin=(GetFileType(GetStdHandle(STD_INPUT_HANDLE))==FILE_TYPE_UNKNOWN); + bool claimstdout=(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE))==FILE_TYPE_UNKNOWN); + bool claimstderr=(GetFileType(GetStdHandle(STD_ERROR_HANDLE))==FILE_TYPE_UNKNOWN); + + if (claimstdin || claimstdout || claimstderr) AttachConsole(ATTACH_PARENT_PROCESS); + + if (claimstdin) freopen("CONIN$", "rt", stdin); + if (claimstdout) freopen("CONOUT$", "wt", stdout); + if (claimstderr) freopen("CONOUT$", "wt", stderr); +} + +HINSTANCE hInstance_; +int nCmdShow_; + +int ShowGUI(LPCWSTR filename) +{ + WCHAR cfgfname[MAX_PATH+8]; + GetModuleFileNameW(NULL, cfgfname, MAX_PATH); + WCHAR * ext=GetExtension(cfgfname); + if (ext) *ext='\0'; + wcscat(cfgfname, TEXT("cfg.bin")); + + memset(&state, 0, sizeof(state)); + struct mem configbin=ReadWholeFile(cfgfname); + void* configbin_org=configbin.ptr; + if (configbin.len >= sizeof(state)) + { +#define readconfig(target, size) \ + if (size<0 || configbin.len < size) goto badconfig; \ + memcpy(target, configbin.ptr, size); \ + configbin.ptr += size; \ + configbin.len -= size + + readconfig(&state, sizeof(state)); + if (strncmp(state.signature, "FlipscfgW", sizeof(state.signature)) || state.cfgversion!=mycfgversion) goto badconfig; + int emulen; + readconfig(&emulen, sizeof(emulen)); + set_st_emulator_len(NULL, emulen); + readconfig(st_emulator, (unsigned)emulen*sizeof(WCHAR)); + SetRomList(configbin); + } + else + { + badconfig: + strncpy(state.signature, "FlipscfgW", sizeof(state.signature)); + state.cfgversion=mycfgversion; + state.lastRomType=0; + state.openInEmulatorOnAssoc=false; + state.enableAutoRomSelector=false; + state.lastPatchType=ty_bps; + state.windowleft=CW_USEDEFAULT; + state.windowtop=CW_USEDEFAULT; + set_st_emulator(TEXT("")); + } + free(configbin_org); + + INITCOMMONCONTROLSEX initctrls; + initctrls.dwSize=sizeof(initctrls); + initctrls.dwICC=ICC_STANDARD_CLASSES; + InitCommonControlsEx(&initctrls); + + int ret; + if (filename) + { + if (state.openInEmulatorOnAssoc==false) ret=a_ApplyPatch(filename); + else ret=a_ApplyRun(filename); + } + else ret=ShowMainWindow(hInstance_, nCmdShow_); + + HANDLE file=CreateFile(cfgfname, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL); + if (file!=INVALID_HANDLE_VALUE) + { + DWORD whocares; + WriteFile(file, &state, sizeof(state), &whocares, NULL); + int len=wcslen(st_emulator); + WriteFile(file, &len, sizeof(len), &whocares, NULL); + WriteFile(file, st_emulator, sizeof(WCHAR)*wcslen(st_emulator), &whocares, NULL); + struct mem romlist=GetRomList(); + WriteFile(file, romlist.ptr, romlist.len, &whocares, NULL); + CloseHandle(file); + } + + return ret; +} + +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + hInstance_=hInstance; + nCmdShow_=nCmdShow; + int argc; + wchar_t ** argv=CommandLineToArgvW(GetCommandLineW(), &argc); + return flipsmain(argc, argv); +} +#endif diff --git a/flips.Manifest b/flips.Manifest new file mode 100644 index 0000000..44c4450 --- /dev/null +++ b/flips.Manifest @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flips.cpp b/flips.cpp new file mode 100644 index 0000000..54839c3 --- /dev/null +++ b/flips.cpp @@ -0,0 +1,813 @@ +//Module name: Floating IPS, shared core for all frontends +//Author: Alcaro +//Date: June 18, 2015 +//Licence: GPL v3.0 or higher + +#include "flips.h" + +//get rid of dependencies on libstdc++, they eat 200KB on Windows +void* operator new(size_t n) { return malloc(n); } // forget allocation failures, let them segfault. +void operator delete(void * p) { free(p); } +extern "C" void __cxa_pure_virtual() { while(1); } + + +//TODO: delete +struct mem ReadWholeFile(LPCWSTR filename) +{ + return file::read(filename); +} + +bool WriteWholeFile(LPCWSTR filename, struct mem data) +{ + return filewrite::write(filename, data); +} + +bool WriteWholeFileWithHeader(LPCWSTR filename, struct mem header, struct mem data) +{ + filewrite* f = filewrite::create(filename); + if (!f) return false; + bool ret = (f->append(header.ptr, 512) && f->append(data.ptr, data.len)); // do not use header.len, that'd prepend the entire file + delete f; + return ret; +} + +void FreeFileMemory(struct mem mem) +{ + free(mem.ptr); +} + + + + + +LPWSTR GetExtension(LPCWSTR fname) +{ + LPWSTR ptr1=(LPWSTR)fname; + LPWSTR ptr2; + ptr2=wcsrchr(ptr1, '/'); if (ptr2) ptr1=ptr2; +#ifdef FLIPS_WINDOWS + ptr2=wcsrchr(ptr1, '\\'); if (ptr2) ptr1=ptr2; +#endif + ptr2=wcsrchr(ptr1, '.'); if (ptr2) ptr1=ptr2; + if (*ptr1=='.') return ptr1; + else return wcsrchr(ptr1, '\0'); +} + +LPWSTR GetBaseName(LPCWSTR fname) +{ + LPWSTR ptr1=(LPWSTR)fname; + LPWSTR ptr2; + ptr2=wcsrchr(ptr1, '/'); if (ptr2) ptr1=ptr2+1; +#ifdef FLIPS_WINDOWS + ptr2=wcsrchr(ptr1, '\\'); if (ptr2) ptr1=ptr2+1; +#endif + return ptr1; +} + +bool forceKeepHeader=false; + +#ifndef FLIPS_CLI +bool guiActive=false; +#endif + + +struct mem file::read() +{ + struct mem out; + out.len = len(); + out.ptr = (uint8_t*)malloc(out.len); + if (!read(out.ptr, 0, out.len)) + { + free(out.ptr); + struct mem err = {NULL, 0}; + return err; + } + return out; +} + +struct mem file::read(LPCWSTR filename) +{ + struct mem err = {NULL, 0}; + file* f = file::create(filename); + if (!f) return err; + struct mem ret = f->read(); + delete f; + return ret; +} + +bool filewrite::write(LPCWSTR filename, struct mem data) +{ + filewrite* f = filewrite::create(filename); + if (!f) return false; + bool ret = f->append(data.ptr, data.len); + delete f; + return ret; +} + + +class fileheader : public file { + file* child; + +public: + fileheader(file* child) : child(child) {} + + size_t len() { return child->len()-512; } + bool read(uint8_t* target, size_t start, size_t len) { return child->read(target, start+512, len); } + + ~fileheader() { delete child; } +}; + + + + +const struct errorinfo ipserrors[]={ + { el_ok, NULL },//ips_ok + { el_unlikelythis, "The patch was applied, but is most likely not intended for this ROM." },//ips_notthis + { el_unlikelythis, "The patch was applied, but did nothing. You most likely already had the output file of this patch." },//ips_thisout + { el_warning, "The patch was applied, but appears scrambled or malformed." },//ips_suspicious + { el_broken, "The patch is broken and can't be used." },//ips_invalid + + { el_broken, "The IPS format does not support files larger than 16MB." },//ips_16MB + { el_warning, "The files are identical! The patch will do nothing." },//ips_identical + }; + +const struct errorinfo bpserrors[]={ + { el_ok, NULL },//bps_ok, + { el_notthis, "That's the output file already." },//bps_to_output + { el_notthis, "This patch is not intended for this ROM." },//bps_not_this + { el_broken, "This patch is broken and can't be used." },//bps_broken + + { el_warning, "The files are identical! The patch will do nothing." },//bps_identical + { el_broken, "These files are too big for this program to handle." },//bps_too_big + { el_broken, "These files are too big for this program to handle." },//bps_out_of_mem (same message as above, it's accurate for both.) + { el_broken, "Patch creation was canceled." },//bps_canceled + }; + +LPCWSTR GetManifestName(LPCWSTR romname) +{ + //static WCHAR manifestname[MAX_PATH]; + //wcscpy(manifestname, romname); + //LPWSTR manifestext=GetExtension(manifestname); + //if (!manifestext) manifestext=wcschr(manifestname, '\0'); + //wcscpy(manifestext, TEXT(".bml")); + //return manifestname; + + static WCHAR * manifestname=NULL; + if (manifestname) free(manifestname); + manifestname=(WCHAR*)malloc((wcslen(romname)+1+4)*sizeof(WCHAR)); + wcscpy(manifestname, romname); + LPWSTR manifestext=GetExtension(manifestname); + if (manifestext) wcscpy(manifestext, TEXT(".bml")); + return manifestname; +} + +enum patchtype IdentifyPatch(file* patch) +{ + size_t len = patch->len(); + uint8_t data[16]; + if (len>16) len=16; + + patch->read(data, 0, len); + if (len>=5 && !memcmp(data, "PATCH", 5)) return ty_ips; + if (len>=4 && !memcmp(data, "BPS1", 4)) return ty_bps; + if (len>=4 && !memcmp(data, "UPS1", 4)) return ty_ups; + return ty_null; +} + +enum { + ch_crc32, + ch_last +}; +struct checkmap { + uint8_t* sum; + LPWSTR name; +}; +static struct checkmap * checkmap[ch_last]={NULL}; +static uint32_t checkmap_len[ch_last]={0}; +static const uint8_t checkmap_sum_size[]={ 4 }; +static const uint8_t checkmap_sum_size_max = 4; + +static LPCWSTR FindRomForSum(int type, void* sum) +{ +//printf("SE.CRC=%.8X\n",*(uint32_t*)sum); + for (unsigned int i=0;isum=(uint8_t*)malloc(checkmap_sum_size[type]); + memcpy(item->sum, sum, checkmap_sum_size[type]); + item->name=wcsdup(filename); +} + +struct mem GetRomList() +{ + struct mem out={NULL, 0}; + for (unsigned int type=0;type data.len) return; \ + memcpy(target, data.ptr, bytes); \ + data.ptr += bytes; \ + data.len -= bytes +#define read_discard(bytes) \ + if (bytes > data.len) return; \ + data.ptr += bytes; \ + data.len -= bytes + uint32_t count; + read(&count, sizeof(count)); + checkmap[type]=(struct checkmap*)malloc(sizeof(struct checkmap)*count*2);//overallocate so I won't need to round the count + + while (count--) + { + uint8_t hashlen; + read(&hashlen, sizeof(hashlen)); + uint16_t strlen; + read(&strlen, sizeof(strlen)); + if (hashlen==checkmap_sum_size[type]) + { + if (data.len < hashlen+strlen) return; + + struct checkmap* item=&checkmap[type][checkmap_len[type]++]; + item->sum=(uint8_t*)malloc(checkmap_sum_size[type]); + read(item->sum, hashlen); + item->name=(WCHAR*)malloc(strlen+sizeof(WCHAR)); + read(item->name, strlen); + memset((uint8_t*)item->name + strlen, 0, sizeof(WCHAR)); + } + else + { + read_discard(hashlen); + read_discard(strlen); + } + } +#undef read + } +} + +LPCWSTR FindRomForPatch(file* patch, bool * possibleToFind) +{ + if (possibleToFind) *possibleToFind=false; + enum patchtype patchtype=IdentifyPatch(patch); + if (patchtype==ty_bps) + { + uint32_t crc; + if (bps_get_checksums(patch, &crc, NULL, NULL)!=bps_ok) return NULL; + if (possibleToFind) *possibleToFind=true; + return FindRomForSum(ch_crc32, &crc); + } + //UPS has checksums too, but screw UPS. Nobody cares. + return NULL; +} + +void AddToRomList(file* patch, LPCWSTR path) +{ + enum patchtype patchtype=IdentifyPatch(patch); + if (patchtype==ty_bps) + { + uint32_t crc; + if (bps_get_checksums(patch, &crc, NULL, NULL)!=bps_ok) return; + AddRomForSum(ch_crc32, &crc, path); + } +} + +void DeleteRomFromList(LPCWSTR path) +{ + for (unsigned int type=0;typeread(); // There's no real reason to remove this, no patcher knows how to handle these file objects. + + enum patchtype patchtype=IdentifyPatch(patch); + struct errorinfo errinf; + removeheader=(removeheader && patchtype==ty_bps); + if (removeheader) + { + inrom.ptr+=512; + inrom.len-=512; + } + struct mem outrom={NULL,0}; + struct mem manifest={NULL,0}; + + errinf=error(el_broken, "Unknown patch format."); + if (patchtype==ty_bps) + { + errinf=bpserrors[bps_apply(patchmem, inrom, &outrom, &manifest, !verifyinput)]; + if (!verifyinput && outrom.ptr) errinf.level=el_warning; + } + if (patchtype==ty_ips) errinf=ipserrors[ips_apply(patchmem, inrom, &outrom)]; + if (patchtype==ty_ups) errinf=bpserrors[ups_apply(patchmem, inrom, &outrom)]; + if (errinf.level==el_ok) errinf.description="The patch was applied successfully!"; + + struct manifestinfo defmanifestinfo={true,false,NULL}; + if (!manifestinfo) manifestinfo=&defmanifestinfo; + if (manifestinfo->use) + { + if (manifest.ptr) + { + LPCWSTR manifestname; + if (manifestinfo->name) manifestname=manifestinfo->name; + else manifestname=GetManifestName(outromname); + if (!WriteWholeFile(manifestname, manifest) && manifestinfo->required) + { + if (errinf.level==el_ok) errinf=error(el_warning, "The patch was applied, but the manifest could not be created."); + } + } + else if (manifestinfo->required && errinf.level==el_ok) errinf=error(el_warning, "The patch was applied, but there was no manifest present."); + } + + if (removeheader) + { + inrom.ptr-=512; + inrom.len+=512; + if (errinf.level=1000) return false; + strcpy(bpsdProgStr, "Please wait... "); + bpsdProgStr[15]='0'+promille/100; + int digit1=((promille<100)?15:16); + bpsdProgStr[digit1+0]='0'+promille/10%10; + bpsdProgStr[digit1+1]='.'; + bpsdProgStr[digit1+2]='0'+promille%10; + bpsdProgStr[digit1+3]='%'; + bpsdProgStr[digit1+4]='\0'; + return true; +} + +bool bpsdeltaProgressCLI(void* userdata, size_t done, size_t total) +{ + if (!bpsdeltaGetProgress(done, total)) return true; + fputs(bpsdProgStr, stdout); + putchar('\r'); + fflush(stdout); + return true; +} + +struct errorinfo CreatePatchToMem(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, + struct manifestinfo * manifestinfo, struct mem * patchmem) +{ + //pick roms + file* roms[2]={NULL, NULL}; + for (int i=0;i<2;i++) + { + LPCWSTR romname=((i==0)?inromname:outromname); + roms[i] = file::create(romname); + if (!roms[i]) + { + return error(el_broken, "Couldn't read this ROM. What exactly are you doing?"); + } + if (shouldRemoveHeader(romname, roms[i]->len()) && (patchtype==ty_bps || patchtype==ty_bps_linear || patchtype==ty_bps_moremem)) + { + roms[i] = new fileheader(roms[i]); + } + } + + struct mem manifest={NULL,0}; + struct errorinfo manifesterr={el_ok, NULL}; + struct manifestinfo defmanifestinfo={true,false,NULL}; + if (!manifestinfo) manifestinfo=&defmanifestinfo; + if (patchtype==ty_bps || patchtype==ty_bps_linear) + { + LPCWSTR manifestname; + if (manifestinfo->name) manifestname=manifestinfo->name; + else manifestname=GetManifestName(outromname); + manifest=ReadWholeFile(manifestname); + if (!manifest.ptr) manifesterr=error(el_warning, "The patch was created, but the manifest could not be read."); + } + else manifesterr=error(el_warning, "The patch was created, but this patch format does not support manifests."); + + struct errorinfo errinf={ el_broken, "Unknown patch format." }; + if (patchtype==ty_ips) + { + struct mem rommem[2]={ roms[0]->read(), roms[1]->read() }; + errinf=ipserrors[ips_create(rommem[0], rommem[1], patchmem)]; + free(rommem[0].ptr); + free(rommem[1].ptr); + } + if (patchtype==ty_bps || patchtype==ty_bps_moremem) + { +#ifndef FLIPS_CLI + if (guiActive) + { + bpsdeltaBegin(); + errinf=bpserrors[bps_create_delta(roms[0], roms[1], manifest, patchmem, bpsdeltaProgress, NULL, (patchtype==ty_bps_moremem))]; + bpsdeltaEnd(); + } + else +#endif + { + errinf=bpserrors[bps_create_delta(roms[0], roms[1], manifest, patchmem, bpsdeltaProgressCLI, NULL, (patchtype==ty_bps_moremem))]; + } + } + if (patchtype==ty_bps_linear) + { + struct mem rommem[2]={ roms[0]->read(), roms[1]->read() }; + errinf=bpserrors[bps_create_linear(rommem[0], rommem[1], manifest, patchmem)]; + free(rommem[0].ptr); + free(rommem[1].ptr); + } + FreeFileMemory(manifest); + if (errinf.level==el_ok) errinf.description="The patch was created successfully!"; + + if (manifestinfo->required && errinf.level==el_ok && manifesterr.level!=el_ok) errinf=manifesterr; + + if (errinf.level==el_ok && roms[0]->len() > roms[1]->len()) + { + errinf=error(el_warning, "The patch was created, but the input ROM is larger than the " + "output ROM. Double check whether you've gotten them backwards."); + } + + delete roms[0]; + delete roms[1]; + + return errinf; +} + +struct errorinfo CreatePatch(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, + struct manifestinfo * manifestinfo, LPCWSTR patchname) +{ + struct mem patch={NULL,0}; + struct errorinfo errinf = CreatePatchToMem(inromname, outromname, patchtype, manifestinfo, &patch); + + if (errinf.level FLIPS_WINDOWS, Linux -> FLIPS_GTK, anything +// else -> FLIPS_CLI). FLIPS_WINDOWS and FLIPS_CLI can be compiled under both C99 and C++98; +// FLIPS_GTK is only tested under C99. +// Note that picking the platform native frontend will bring a few advantages even if you only +// intend to use Flips from the command line; Windows gains access to filenames outside the 8bit +// charset, and GTK+ will gain the ability to handle files on URIs and not the local file system. +// +//All of these must be defined globally, or Flips will behave erratically. + +#if defined(FLIPS_WINDOWS) || defined(FLIPS_GTK) || defined(FLIPS_CLI) +//already picked +#elif defined(_WIN32) +#define FLIPS_WINDOWS +#elif defined(__linux__) +#define FLIPS_GTK +#else +#define FLIPS_CLI +#endif + +//#ifdef __cplusplus +//#define EXTERN_C extern "C" +//#else +//#define EXTERN_C +//#endif + +#define flipsversion "Flips v1.31" + + +#if defined(FLIPS_WINDOWS) +#define UNICODE +//# define _UNICODE +//#define WINVER 0x0501 +#define _WIN32_WINNT 0x0501 +#define _WIN32_IE 0x0600 + +//#define _WIN32_IE 0x0600 +//#define __MSVCRT_VERSION__ 0x0601 +#define NOMINMAX // this seems automatically on in C++ - crazy. +#include +#include +#include +#include +#include +#include + +#define wcsicmp _wcsicmp//wcsicmp deprecated? fuck that, I use what I want. I do not add underlines to a few randomly chosen functions. +#define wcsdup _wcsdup +//EXTERN_C int _wcsicmp(const wchar_t * string1, const wchar_t * string2); +//EXTERN_C int swprintf(wchar_t * buffer, const wchar_t * format, ...);//also tdm quit having outdated and/or incomplete headers. + + +#else +#include +#include +#include +#include + +//Flips uses Windows types internally, since it's easier to #define them to Linux types than +//defining "const char *" to anything else, and since I use char* at some places (mainly libips/etc) +//and really don't want to try to split them. Inventing my own typedefs seems counterproductive as +//well; they would bring no advantage over Windows types except not being Windows types, and I don't +//see that as a valid argument. +#define LPCWSTR const char * +#define LPWSTR char * +#define WCHAR char +#define wcscpy strcpy +#define wcscat strcat +#define wcschr strchr +#define wcslen strlen +#define wcsdup strdup +#define wcsrchr strrchr +#define wcscmp strcmp +#define wcsncmp strncmp +#define wcsicmp strcasecmp +//#define wcsnicmp strncasecmp +#define wprintf printf +#define TEXT(text) text +//EXTERN_C int strcasecmp(const char *s1, const char *s2); +#define ClaimConsole() // all other platforms have consoles already + +#define strdup strdup_flips +static inline char* strdup(const char * in) +{ + size_t len=strlen(in); + char * ret=(char*)malloc(len+1); + strcpy(ret, in); + return ret; +} +#endif + +#include "libbps.h" +#include "libips.h" +#include "libups.h" + +#ifndef __cplusplus +#include //If this file does not exist, remove it and uncomment the following three lines. +//#define bool int +//#define true 1 +//#define false 0 +#endif + + +//provided by Flips core +#include "global.h" + +enum patchtype { + ty_null, + ty_bps, + ty_ips, + + //non-recommended formats + ty_bps_linear, + ty_bps_moremem, + ty_ups, + + ty_shut_up_gcc +}; + +enum errorlevel { + el_ok, + el_notice, + el_unlikelythis, + el_warning, + el_notthis, + el_broken, + el_shut_up_gcc +}; + +struct errorinfo { + enum errorlevel level; + const char * description; +}; + +struct manifestinfo { + bool use; + bool required; + LPCWSTR name; +}; + +class file; +class filewrite; + +LPWSTR GetExtension(LPCWSTR fname); +LPWSTR GetBaseName(LPCWSTR fname); +bool shouldRemoveHeader(LPCWSTR romname, size_t romlen); + +struct mem GetRomList(); +void SetRomList(struct mem data); +LPCWSTR FindRomForPatch(file* patch, bool * possibleToFind); +void AddToRomList(file* patch, LPCWSTR path); +void DeleteRomFromList(LPCWSTR path); + +struct errorinfo ApplyPatchMem2(file* patch, struct mem inrom, bool removeheader, bool verifyinput, + LPCWSTR outromname, struct manifestinfo * manifestinfo); +struct errorinfo ApplyPatchMem(file* patch, LPCWSTR inromname, bool verifyinput, + LPCWSTR outromname, struct manifestinfo * manifestinfo, bool update_rom_list); +struct errorinfo ApplyPatch(LPCWSTR patchname, LPCWSTR inromname, bool verifyinput, + LPCWSTR outromname, struct manifestinfo * manifestinfo, bool update_rom_list); +//struct errorinfo CreatePatchToMem(file* inrom, file* outrom, enum patchtype patchtype, + //struct manifestinfo * manifestinfo, struct mem * patchmem); +//struct errorinfo CreatePatch(file* inrom, file* outrom, enum patchtype patchtype, + //struct manifestinfo * manifestinfo, LPCWSTR patchname); +struct errorinfo CreatePatchToMem(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, + struct manifestinfo * manifestinfo, struct mem * patchmem); +struct errorinfo CreatePatch(LPCWSTR inromname, LPCWSTR outromname, enum patchtype patchtype, + struct manifestinfo * manifestinfo, LPCWSTR patchname); + +extern char bpsdProgStr[24]; +extern int bpsdLastPromille; +bool bpsdeltaGetProgress(size_t done, size_t total); + +int flipsmain(int argc, WCHAR * argv[]); +void usage();//does not return + + +//provided by the OS port +//several functions of file:: and filewrite:: also belong to the OS port + +//TODO: delete +struct mem ReadWholeFile(LPCWSTR filename); +bool WriteWholeFile(LPCWSTR filename, struct mem data); +bool WriteWholeFileWithHeader(LPCWSTR filename, struct mem header, struct mem data); +void FreeFileMemory(struct mem mem); + +void bpsdeltaBegin(); +bool bpsdeltaProgress(void* userdata, size_t done, size_t total); +void bpsdeltaEnd(); + +int ShowGUI(LPCWSTR filename); +#ifdef FLIPS_WINDOWS +void ClaimConsole(); +#endif + +//the OS port is responsible for main() diff --git a/flips.ico b/flips.ico new file mode 100644 index 0000000..a38c89e Binary files /dev/null and b/flips.ico differ diff --git a/flips.rc b/flips.rc new file mode 100644 index 0000000..4deead2 --- /dev/null +++ b/flips.rc @@ -0,0 +1,33 @@ +#include + + +0 ICON DISCARDABLE "flips.ico" +1 ICON DISCARDABLE "ips.ico" +2 ICON DISCARDABLE "bps.ico" +1 24 "flips.Manifest" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1,3,1,0 +PRODUCTVERSION 1,3,1,0 +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS 0 +//VS_FF_DEBUG VS_FF_PATCHED VS_FF_PRERELEASE VS_FF_PRIVATEBUILD VS_FF_SPECIALBUILD VS_FFI_FILEFLAGSMASK +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "Alcaro" + VALUE "FileDescription", "Flips Patch Utility" + VALUE "FileVersion", "1.3.1.0" + VALUE "InternalName", "Floating IPS" + VALUE "LegalCopyright", "©2013-2015 Alcaro" + VALUE "OriginalFilename", "flips.exe" + VALUE "ProductName", "Floating IPS" + VALUE "ProductVersion", "1.3.1.0" + END + END +END diff --git a/global.h b/global.h new file mode 100644 index 0000000..1a86b41 --- /dev/null +++ b/global.h @@ -0,0 +1,52 @@ +//Module name: Floating IPS, global header +//Author: Alcaro +//Date: June 18, 2015 +//Licence: GPL v3.0 or higher + +#ifndef struct_mem +#define struct_mem + +//the standard library can be assumed to exist +#include //size_t, SIZE_MAX +#include //uint8_t + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +struct mem { + unsigned char * ptr; + size_t len; +}; + +#if defined(FLIPS_WINDOWS) +#define LPCWSTR const wchar_t * +#else +#define LPCWSTR const char * +#endif + +class file { +public: + static file* create(LPCWSTR filename); + + virtual size_t len() = 0; + virtual bool read(uint8_t* target, size_t start, size_t len) = 0; + + static struct mem read(LPCWSTR filename); // provided by Flips core + struct mem read(); // provided by Flips core + + virtual ~file() {} +}; + +class filewrite { +public: + static filewrite* create(LPCWSTR filename); + + virtual bool append(const uint8_t* data, size_t len) = 0; + + static bool write(LPCWSTR filename, struct mem data); // provided by Flips core + + virtual ~filewrite() {} +}; + +#endif diff --git a/ips.ico b/ips.ico new file mode 100644 index 0000000..2c69ef0 Binary files /dev/null and b/ips.ico differ diff --git a/libbps-suf.cpp b/libbps-suf.cpp new file mode 100644 index 0000000..80a09f9 --- /dev/null +++ b/libbps-suf.cpp @@ -0,0 +1,891 @@ +//Module name: libbps-suf +//Author: Alcaro +//Date: June 18, 2015 +//Licence: GPL v3.0 or higher + + +#include "libbps.h" +#include "crc32.h" +#include +#include +#include +#include + +//These two give minor performance penalties and will print some random stuff to stdout. +//The former will verify the correctness of the output patch, the latter will print some performance data. +//Can be useful for debugging, but should be disabled for release builds. +#ifdef BPS_STANDALONE +#endif +//#define TEST_CORRECT +//#define TEST_PERF + +//If the suffix array of [0, 0, 0, 0] is [3, 2, 1, 0], set to true. If it's [0, 1, 2, 3], this is false. +//If it's [4, 3, 2, 1, 0] or [0, 1, 2, 3, 4], remove the 4 (easily done with some pointer math), and follow the above. +//If it's something else, get a non-broken array calculator. +#define EOF_IS_LAST false + +#if defined(TEST_CORRECT) || defined(TEST_PERF) +#include +#endif + +//Algorithm description: +// +//This is heavily built upon suffix sorting; the implementation I use, libdivsufsort, claims +// O(n log n) complexity, so I'll believe that. There is also SA-IS, which claims O(n), but if that +// is true, its constant factors are ridiculously high. +// +//The program starts by taking an equal amount of the source file and target file, concatenates that +// with target first, and suffix sorts it. +//It also calculates a reverse index, such that reverse[sorted[i]]==i. +// +//To find a match, it goes to reverse[outpos], and scans sorted[] up and down for the closest entry +// that either starts before the current output position, or is somewhere in the source file. +//As the source file comes last, the end-of-file marker (whose value is outside the range of a byte) +// is guaranteed to not be in the way for a better match. +//This is called O(n) times, and averages O(1) as at least 50% of sorted[] is in range. However, it +// is worst-case O(n) for sorted inputs, giving a total of O(n^2). +// +//It then checks which of the two candidates are superior, by checking how far they match each +// other, and then checking if the upper one has another correct byte. +//This is potentially O(n), but for each matched byte, another iteration is removed from the outer +// loop, so the sum of all calls is O(n). +// +//When the program approaches the end of the sorted area, it re-sorts twice as much as last time. +// This gives O(log n) calls to the suffix sorter. +//Given O(n log n) for one sorting step, the time taken is O(n/1 log n/1 + n/2 log n/2 + +// n/4 log n/4 + ...), which is strictly less than O(n/1 log n + n/2 log n + n/4 log n + ...), which +// equals O(2n log n), which is O(n log n). (The exact value of that infinite sum is 2n*log(n/2).) +// +//Many details were omitted from the above, but that's the basic setup. +// +//Thus, the program is O(max(n log n, n, n) = n log n) average and O(max(n log n, n^2, n) = n^2) +// worst case. +// +//I conclude that the task of finding, understanding and implementing a sub-O(n^2) algorithm for +// delta patching is resolved. + + +//Known cases where this function does not emit the optimal encoding: +//If a match in the target file would extend further than target_search_size, it is often skipped. +// Penalty: O(log n), with extremely low constants (it'd require a >256B match to be exactly there). +// Even for big files, the penalty is very likely to remain zero; even hitting double-digit bytes +// would require a file designed exactly for that. +//If multiple matches are equally good, it picks one at random, not the one that's cheaper to encode. +// Penalty: Likely O(n) or O(n log log n), with low constants. I'd guess ~1.4% for my 48MB test file. +//However, due to better heuristics and others' performance optimizations, this one still beats its +// competitors. + + +//Possible optimizations: +//divsufsort() takes approximately 2/3 of the total time. create_reverse_index() takes roughly a third of the remainder. +//Each iteration takes four times as long as the previous one. +//If each iteration takes 4 times as long as the previous one, then the last one takes 3/4 of the total time. +//Since divsufsort+create_reverse_index doesn't depend on anything else, the last iteration can be split off to its own thread. +//This would split it to +//Search, non-final: 2/9 * 1/4 = 2/36 +//Search, final: 2/9 * 3/4 = 6/36 +//Sort+rev, non-final: 7/9 * 1/4 = 7/36 +//Sort+rev, final: 7/9 * 3/4 = 21/36 +//All non-final must be done sequentially. Both Sort Final and non-final must be done before Search Final can start. +//This means the final time, if Sort Final is split off, is +//max(7/36+2/36, 21/36) + 6/36 = 27/36 = 3/4 +//of the original time. +//Due to +//- the considerable complexity costs (OpenMP doesn't seem able to represent the "insert a wait in +// the middle of this while loop" I would need) +//- the added memory use, approximately 25% higher - it's already high enough +//- libdivsufsort already using threads, which would make the gains lower +// and would increase complexity, as I have to ensure the big one remains threaded - +// and that the small ones are not, as that'd starve the big one +//I deem a possible 25% boost not worthwhile. + + +//Both sorting algorithms claim O(1) memory use (in addition to the bytes and the output). In +// addition to that, this algorithm uses (source.len*target.len)*(sizeof(uint8_t)+2*sizeof(off_t)) +// bytes of memory, plus the input and output files, plus the patch. +//For most hardware, this is 9*(source.len+target.len), or 5*(source+target) for the slim one. + + +#include "sais.cpp" +template +static void +sufsort(sais_index_type* SA, const uint8_t* T, sais_index_type n) { + if(n <= 1) { if(n == 1) SA[0] = 0; return; } + sais_main(T, SA, 0, n, 256); +} + +//According to , divsufsort achieves +// approximately half the time of SAIS for nearly all files, despite SAIS' promises of linear +// performance (divsufsort claims O(n log n)). + +//divsufsort only allocates O(1) for some radix/bucket sorting. SAIS seems constant too. +//I'd prefer to let them allocate from an array I give it, but divsuf doesn't allow that, and there +// are only half a dozen allocations per call anyways. + +#ifdef USE_DIVSUFSORT +#include "divsufsort.h" + +static void sufsort(int32_t* SA, uint8_t* T, int32_t n) +{ + divsufsort(T, SA, n); +} +#endif + +#ifdef USE_DIVSUFSORT64 +#include "divsufsort64.h" + +static void sufsort(int64_t* SA, uint8_t* T, int64_t n) +{ + divsufsort(T, SA, n); +} +#endif + + + +template static T min(T a, T b) { return a static T max(T a, T b) { return a outbuflen) + { + if (!outbuflen) outbuflen = 128; + while (outlen+len > outbuflen) outbuflen *= 2; + out = (uint8_t*)realloc(out, outbuflen); + } + } + + void append(const uint8_t * data, size_t len) + { + reserve(len); + memcpy(out+outlen, data, len); + outlen+=len; + } + + void appendnum(size_t num) + { +#ifdef TEST_CORRECT + if (num > 1000000000) + printf("ERROR: Attempt to write %.8lX\n",(unsigned long)num),abort(); +#endif + reserve(sizeof(size_t)*8/7+1); + + while (num >= 128) + { + out[outlen++]=(num&0x7F); + num>>=7; + num--; + } + out[outlen++]=num|0x80; + } + + void appendnum32(uint32_t num) + { + reserve(4); + out[outlen++] = num>>0; + out[outlen++] = num>>8; + out[outlen++] = num>>16; + out[outlen++] = num>>24; + } + + size_t sourcelen; + size_t targetlen; + const uint8_t* targetmem; + + enum bpscmd { SourceRead, TargetRead, SourceCopy, TargetCopy }; + + size_t outpos; + + size_t sourcecopypos; + size_t targetcopypos; + + size_t numtargetread; + + bps_creator(file* source, file* target, struct mem metadata) + { + outlen = 0; + outbuflen = 128; + out = (uint8_t*)malloc(outbuflen); + + outpos = 0; + + sourcelen = source->len(); + targetlen = target->len(); + + sourcecopypos = 0; + targetcopypos = 0; + + numtargetread = 0; + + append((const uint8_t*)"BPS1", 4); + appendnum(sourcelen); + appendnum(targetlen); + appendnum(metadata.len); + append(metadata.ptr, metadata.len); + + setProgress(NULL, NULL); + } + + + void move_target(const uint8_t* ptr) + { + targetmem = ptr; + } + + size_t encode_delta(size_t prev, size_t next) + { + bool negative = (next= 1+cost+hastargetread+(len==1); + } + + + //Return value is how many bytes were used. If you believe the given one sucks, use TargetRead and return 1. + size_t match(bool is_target, size_t pos, size_t len) + { + if (!use_match( + numtargetread, + (!is_target && pos==outpos) ? 1 : // SourceRead + (num_cost(abs_diff(pos, (is_target ? targetcopypos : sourcecopypos)))+1), + len + )) + { + return emit_target_read(); + } + + if (is_target) return emit_target_copy(pos, len); + else return emit_source_copy(pos, len); + } + + + bool (*prog_func)(void* userdata, size_t done, size_t total); + void* prog_dat; + + static bool prog_func_null(void* userdata, size_t done, size_t total) { return true; } + + void setProgress(bool (*progress)(void* userdata, size_t done, size_t total), void* userdata) + { + if (!progress) progress = prog_func_null; + + prog_func=progress; + prog_dat=userdata; + } + + bool progress(size_t done, size_t total) + { + return prog_func(prog_dat, done, total); + } + + + void finish(const uint8_t* source, const uint8_t* target) + { + flush_target_read(); +#ifdef TEST_CORRECT + if (outpos != targetlen) + puts("ERROR: patch creates wrong ROM size"),abort(); +#endif + + appendnum32(crc32(source, sourcelen)); + appendnum32(crc32(target, targetlen)); + appendnum32(crc32(out, outlen)); + } + + struct mem getpatch() + { + struct mem ret = { out, outlen }; + out = NULL; + return ret; + } + + ~bps_creator() { free(out); } +}; +} + + + +#ifdef TEST_PERF +static int match_len_n=0; +static int match_len_tot=0; +#endif + +template +static off_t match_len(const uint8_t* a, const uint8_t* b, off_t len) +{ + off_t i; + for (i=0;i +static off_t pick_best_of_two(const uint8_t* search, off_t searchlen, + const uint8_t* data, off_t datalen, + off_t a, off_t b, + off_t* bestlen) +{ + off_t commonlen = match_len(data+a, data+b, min(datalen-a, datalen-b)); + if (commonlen>=searchlen) + { + *bestlen=searchlen; + return a; + } + + if (a+commonlen +static off_t adjust_match(off_t match, const uint8_t* search, off_t searchlen, + const uint8_t* data,off_t datalen, off_t maxstart,off_t minstart, + const off_t* sorted, off_t sortedlen, + off_t* bestlen) +{ + off_t match_up = match; + off_t match_dn = match; + while (match_up>=0 && sorted[match_up]>=maxstart && sorted[match_up]=maxstart && sorted[match_dn]=sortedlen) + { + if (match_up<0 && match_dn>=sortedlen) + { + *bestlen=0; + return 0; + } + off_t pos = sorted[match_up<0 ? match_dn : match_up]; + *bestlen = match_len(search, data+pos, min(searchlen, datalen-pos)); + return pos; + } + + return pick_best_of_two(search,searchlen, data,datalen, sorted[match_up],sorted[match_dn], bestlen); +} + + + +static uint16_t read2_uc(const uint8_t* data) +{ + return data[0]<<8 | data[1]; +} + +template +static uint16_t read2(const uint8_t* data, off_t len) +{ + if (len>=2) return read2_uc(data); + else + { + uint16_t out = (EOF_IS_LAST ? 0xFFFF : 0x0000); + if (len==1) out = (data[0]<<8) | (out&0x00FF); + return out; + } +} + +template +static void create_buckets(const uint8_t* data, off_t* index, off_t len, off_t* buckets) +{ + off_t low = 0; + off_t high; + + for (int n=0;n<65536;n++) + { + //'low' remains from the previous iteration and is a known minimum + high = low+(len/131072)+1; // optimal value: slightly above a third of the distance to the next one + while (true) + { + if (high > len-1) break; + + off_t pos = index[high]; + uint16_t here = read2(data+pos, len-pos); + + if (here >= n) break; + else + { + off_t diff = high-low; + low = high; + high = high+diff*2; + } + } + if (high > len-1) high = len-1; + + + while (low < high) + { + off_t mid = low + (high-low)/2; + off_t midpos = index[mid]; + + uint16_t here = read2(data+midpos, len-midpos); + if (here < n) low = mid+1; + else high = mid; + } + buckets[n] = low; + } + + buckets[65536] = len; + +#ifdef TEST_CORRECT + if (buckets[0]!=0) + { + printf("e: buckets suck, [0]=%i\n", buckets[0]); + abort(); + } + for (int n=0;n<65536;n++) + { + off_t low = buckets[n]; + off_t high = buckets[n+1]; + for (off_t i=low;i +static off_t find_index(off_t pos, const uint8_t* data, off_t datalen, const off_t* index, const off_t* reverse, off_t* buckets) +{ + if (reverse) return reverse[pos]; + + //if (datalen<2) return 0; + uint16_t bucket = read2(data+pos, datalen-pos); +//printf("p=%i b=%i\n",pos,bucket); + + //TODO + //off_t low = 0; + //off_t high = datalen-1; + off_t low = buckets[bucket]; + off_t high = buckets[bucket+1]-1; + + off_t lowmatch = 2; + off_t highmatch = 2; + +//printf("b=%i r=%i(%i)-%i(%i)\n",bucket,low,read2(data+index[low],datalen-index[low]),high,read2(data+index[high],datalen-index[high])); +//fflush(stdout); + while (true) + { + off_t mid = low + (high-low)/2; + off_t midpos = index[mid]; + if (midpos == pos) return mid; +//printf("r=[%i]%i-%i \n",high-low,low,high,); +//fflush(stdout); +#ifdef TEST_CORRECT + if (low >= high) + { + printf("E: [%i](%i): stuck at %i(%i)-%i(%i)\n", pos, read2_uc(data+pos), + low, read2_uc(data+index[low]), high, read2_uc(data+index[high])); + int n=0; + while (index[n]!=pos) n++; + printf("correct one is %i(%i)\n",n, read2_uc(data+index[n])); + abort(); + } +#endif + + off_t matchlenstart = min(lowmatch, highmatch); + + off_t len = datalen - max(pos, midpos) - matchlenstart; + + const uint8_t* search = data+pos+matchlenstart; + const uint8_t* here = data+midpos+matchlenstart; + + while (len>0 && *search==*here) + { + search++; + here++; + len--; + } + + off_t matchlen = search-data-pos; + + bool less; + if (len > 0) less = (*here<*search); + else less = (here > search) ^ EOF_IS_LAST; + + if (less) + { + low = mid+1; + lowmatch = matchlen; + } + else + { + high = mid-1; + highmatch = matchlen; + } + + if (low+256 > high) + { + off_t i=low; + while (true) + { + if (index[i]==pos) return i; + i++; + } + } + } +} + + +template +static void create_reverse_index(off_t* index, off_t* reverse, off_t len) +{ +//testcase: linux 3.18.14 -> 4.0.4 .xz +//without: real23.544 user32.930 +//with: real22.636 user40.168 +//'user' jumps up quite a lot, while 'real' only moves a short bit +//I'm not sure why the tradeoff is so bad (do the cachelines bounce THAT badly?), but I deem it not worth it. +//#pragma omp parallel for + for (off_t i=0;i +static off_t nextsize(off_t outpos, off_t sortedsize, off_t targetlen) +{ + while (outpos >= sortedsize-256 && sortedsize < targetlen) + sortedsize = min(sortedsize*4+3, targetlen); + return sortedsize; +} + +template +off_t lerp(off_t x, off_t y, float frac) +{ + return x + (y-x)*frac; +} + +template +static bpserror bps_create_suf_core(file* source, file* target, bool moremem, struct bps_creator * out) +{ + bpserror err; + + size_t realsourcelen = source->len(); + size_t realtargetlen = target->len(); + + size_t overflowtest = realsourcelen + realtargetlen; + + //source+target length is bigger than size_t (how did that manage to get allocated?) + if (overflowtest < realsourcelen) return bps_too_big; + + //source+target doesn't fit in unsigned off_t + if ((size_t)(off_t)overflowtest != overflowtest) return bps_too_big; + + //source+target doesn't fit in signed off_t + if ((off_t)overflowtest < 0) return bps_too_big; + + //the mallocs would overflow + if ((size_t)realsourcelen+realtargetlen >= SIZE_MAX/sizeof(off_t)) return bps_too_big; + + + off_t sourcelen = realsourcelen; + off_t targetlen = realtargetlen; + + uint8_t* mem_joined = (uint8_t*)malloc(sizeof(uint8_t)*(sourcelen+targetlen)); + + off_t* sorted = (off_t*)malloc(sizeof(off_t)*(sourcelen+targetlen)); + + off_t* sorted_inverse = NULL; + if (moremem) sorted_inverse = (off_t*)malloc(sizeof(off_t)*(sourcelen+targetlen)); + + off_t* buckets = NULL; + if (!sorted_inverse) buckets = (off_t*)malloc(sizeof(off_t)*65537); + + if (!sorted || !mem_joined || (!sorted_inverse && !buckets)) + { + free(mem_joined); + free(sorted); + free(sorted_inverse); + free(buckets); + return bps_out_of_mem; + } + + //sortedsize is how much of the target file is sorted + off_t sortedsize = targetlen; + //divide by 4 for each iteration, to avoid sorting 50% of the file (the sorter is slow) + while (sortedsize/4 > sourcelen && sortedsize > 1024) sortedsize >>= 2; + + off_t prevsortedsize = 0; + off_t outpos = 0; + + goto reindex; // jump into the middle so I won't need a special case to enter it + + while (outpos < targetlen) + { + if (outpos >= sortedsize-256 && sortedsize < targetlen) + { + sortedsize = nextsize(outpos, sortedsize, targetlen); + + reindex: + + //this isn't an exact science + const float percSort = sorted_inverse ? 0.67 : 0.50; + const float percInv = sorted_inverse ? 0.11 : 0.10; + //const float percFind = sorted_inverse ? 0.22 : 0.40; // unused + + const size_t progPreSort = lerp(prevsortedsize, sortedsize, 0); + const size_t progPreInv = lerp(prevsortedsize, sortedsize, percSort); + const size_t progPreFind = lerp(prevsortedsize, sortedsize, percSort+percInv); + + prevsortedsize = sortedsize; + + if (!out->progress(progPreSort, targetlen)) + { + err = bps_canceled; + goto error; + } + + target->read(mem_joined, 0, sortedsize); + source->read(mem_joined+sortedsize, 0, sourcelen); + out->move_target(mem_joined); + sufsort(sorted, mem_joined, sortedsize+sourcelen); + + if (!out->progress(progPreInv, targetlen)) + { + err = bps_canceled; + goto error; + } + + if (sorted_inverse) + create_reverse_index(sorted, sorted_inverse, sortedsize+sourcelen); + else + create_buckets(mem_joined, sorted, sortedsize+sourcelen, buckets); + + if (!out->progress(progPreFind, targetlen)) + { + err = bps_canceled; + goto error; + } + } + + off_t matchlen = 0; + off_t matchpos = adjust_match(find_index(outpos, mem_joined, sortedsize+sourcelen, sorted, sorted_inverse, buckets), + mem_joined+outpos, sortedsize-outpos, + mem_joined,sortedsize+sourcelen, outpos,sortedsize, + sorted, sortedsize+sourcelen, + &matchlen); + +#ifdef TEST_CORRECT + if (matchlen && matchpos >= outpos && matchpos < sortedsize) puts("ERROR: found match in invalid location"),abort(); + if (memcmp(mem_joined+matchpos, mem_joined+outpos, matchlen)) puts("ERROR: found match doesn't match"),abort(); +#endif + + off_t taken; + if (matchpos >= sortedsize) taken = out->match(false, matchpos-sortedsize, matchlen); + else taken = out->match(true, matchpos, matchlen); +#ifdef TEST_CORRECT + if (taken < 0) puts("ERROR: match() returned negative"),abort(); + if (matchlen >= 7 && taken < matchlen) printf("ERROR: match() took %i bytes, offered %i\n", taken, matchlen),abort(); +#endif + outpos += taken; + } + + out->finish(mem_joined+sortedsize, mem_joined); + + err = bps_ok; + +error: + free(buckets); + free(sorted_inverse); + free(sorted); + free(mem_joined); + + return err; +} + + +template static bpserror bps_create_suf_pick(file* source, file* target, bool moremem, struct bps_creator * bps); +template<> bpserror bps_create_suf_pick(file* source, file* target, bool moremem, struct bps_creator * bps) +{ + return bps_create_suf_core(source, target, moremem, bps); +} +template<> bpserror bps_create_suf_pick(file* source, file* target, bool moremem, struct bps_creator * bps) +{ + bpserror err = bps_create_suf_core(source, target, moremem, bps); + if (err==bps_too_big) err = bps_create_suf_core(source, target, moremem, bps); + return err; +} + +//This one picks a function based on 32-bit integers if that fits. This halves memory use for common inputs. +//It also handles some stuff related to the BPS headers and footers. +extern "C" +bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patchmem, + bool (*progress)(void* userdata, size_t done, size_t total), void* userdata, bool moremem) +{ + bps_creator bps(source, target, metadata); + bps.setProgress(progress, userdata); + + size_t maindata = bps.outlen; + + //off_t must be signed + bpserror err = bps_create_suf_pick(source, target, moremem, &bps); + if (err!=bps_ok) return err; + + *patchmem = bps.getpatch(); + + while ((patchmem->ptr[maindata]&0x80) == 0x00) maindata++; + if (maindata==patchmem->len-12-1) return bps_identical; + return bps_ok; +} + + + + +#ifdef BPS_STANDALONE +#include +static struct mem ReadWholeFile(const char * filename) +{ + struct mem null = {NULL, 0}; + + FILE * file=fopen(filename, "rb"); + if (!file) return null; + fseek(file, 0, SEEK_END); + size_t len=ftell(file); + fseek(file, 0, SEEK_SET); + unsigned char * data=(unsigned char*)malloc(len); + size_t truelen=fread(data, 1,len, file); + fclose(file); + if (len!=truelen) + { + free(data); + return null; + } + + struct mem ret = { (unsigned char*)data, len }; + return ret; +} +static bool WriteWholeFile(const char * filename, struct mem data) +{ + FILE * file=fopen(filename, "wb"); + if (!file) return false; + unsigned int truelen=fwrite(data.ptr, 1,data.len, file); + fclose(file); + return (truelen==data.len); +} +int main(int argc, char * argv[]) +{ +//struct mem out = ReadWholeFile(argv[2]); +//printf("check=%.8X\n",crc32(out.ptr, out.len)); + +struct mem in = ReadWholeFile(argv[1]); +struct mem out = ReadWholeFile(argv[2]); +struct mem null = {NULL, 0}; +struct mem p={NULL,0}; +//int n=50; +//for(int i=0;i//malloc, realloc, free +#include //memcpy, memset +#include //uint8_t, uint32_t +#include "crc32.h"//crc32 + +static uint32_t read32(uint8_t * ptr) +{ + uint32_t out; + out =ptr[0]; + out|=ptr[1]<<8; + out|=ptr[2]<<16; + out|=ptr[3]<<24; + return out; +} + +enum { SourceRead, TargetRead, SourceCopy, TargetCopy }; + +#define error(which) do { error=which; goto exit; } while(0) +#define assert_sum(a,b) do { if (SIZE_MAX-(a)<(b)) error(bps_too_big); } while(0) +#define assert_shift(a,b) do { if (SIZE_MAX>>(b)<(a)) error(bps_too_big); } while(0) +enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input) +{ + enum bpserror error = bps_ok; + out->len=0; + out->ptr=NULL; + if (metadata) + { + metadata->len=0; + metadata->ptr=NULL; + } + if (patch.len<4+3+12) return bps_broken; + + if (true) + { +#define read8() (*(patchat++)) +#define decodeto(var) \ + do { \ + var=0; \ + unsigned int shift=0; \ + while (true) \ + { \ + uint8_t next=read8(); \ + assert_shift(next&0x7F, shift); \ + size_t addthis=(next&0x7F)<len=outlen; + out->ptr=(uint8_t*)malloc(outlen); + + const uint8_t * instart=in.ptr; + const uint8_t * inreadat=in.ptr; + const uint8_t * inend=in.ptr+in.len; + + uint8_t * outstart=out->ptr; + uint8_t * outreadat=out->ptr; + uint8_t * outat=out->ptr; + uint8_t * outend=out->ptr+out->len; + + size_t metadatalen; + decodeto(metadatalen); + + if (metadata && metadatalen) + { + metadata->len=metadatalen; + metadata->ptr=(uint8_t*)malloc(metadatalen+1); + for (size_t i=0;iptr[i]=read8(); + metadata->ptr[metadatalen]='\0';//just to be on the safe side - that metadata is assumed to be text, might as well terminate it + } + else + { + for (size_t i=0;i>2)+1; + int action=(thisinstr&3); + if (outat+length>outend) error(bps_broken); + + switch (action) + { + case SourceRead: + { + if (outat-outstart+length > in.len) error(bps_broken); + for (size_t i=0;ipatchend) error(bps_broken); + for (size_t i=0;i>1; + if ((encodeddistance&1)==0) inreadat+=distance; + else inreadat-=distance; + + if (inreadatinend) error(bps_broken); + for (size_t i=0;i>1; + if ((encodeddistance&1)==0) outreadat+=distance; + else outreadat-=distance; + + if (outreadat=outat || outreadat+length>outend) error(bps_broken); + for (size_t i=0;iptr, out->len); + + if (crc_out_a!=crc_out_e) + { + error=bps_not_this; + if (!accept_wrong_input) goto exit; + } + return error; +#undef read8 +#undef decodeto +#undef write8 + } + +exit: + free(out->ptr); + out->len=0; + out->ptr=NULL; + if (metadata) + { + free(metadata->ptr); + metadata->len=0; + metadata->ptr=NULL; + } + return error; +} + +#define write(val) \ + do { \ + out[outlen++]=(val); \ + if (outlen==outbuflen) \ + { \ + outbuflen*=2; \ + out=(uint8_t*)realloc(out, outbuflen); \ + } \ + } while(0) +#define write32(val) \ + do { \ + uint32_t tmp=(val); \ + write(tmp); \ + write(tmp>>8); \ + write(tmp>>16); \ + write(tmp>>24); \ + } while(0) +#define writenum(val) \ + do { \ + size_t tmpval=(val); \ + while (true) \ + { \ + uint8_t tmpbyte=(tmpval&0x7F); \ + tmpval>>=7; \ + if (!tmpval) \ + { \ + write(tmpbyte|0x80); \ + break; \ + } \ + write(tmpbyte); \ + tmpval--; \ + } \ + } while(0) + +enum bpserror bps_create_linear(struct mem sourcemem, struct mem targetmem, struct mem metadata, struct mem * patchmem) +{ + if (sourcemem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;//the 16 is just to be on the safe side, I don't think it's needed. + if (targetmem.len>=(SIZE_MAX>>2) - 16) return bps_too_big; + + const uint8_t * source=sourcemem.ptr; + const uint8_t * sourceend=sourcemem.ptr+sourcemem.len; + if (sourcemem.len>targetmem.len) sourceend=sourcemem.ptr+targetmem.len; + const uint8_t * targetbegin=targetmem.ptr; + const uint8_t * target=targetmem.ptr; + const uint8_t * targetend=targetmem.ptr+targetmem.len; + + const uint8_t * targetcopypos=targetbegin; + + size_t outbuflen=4096; + uint8_t * out=(uint8_t*)malloc(outbuflen); + size_t outlen=0; + write('B'); + write('P'); + write('S'); + write('1'); + writenum(sourcemem.len); + writenum(targetmem.len); + writenum(metadata.len); + for (size_t i=0;i1) + { + //assert_shift((numunchanged-1), 2); + writenum((numunchanged-1)<<2 | 0);//SourceRead + source+=numunchanged; + target+=numunchanged; + } + + size_t numchanged=0; + if (lastknownchange>target) numchanged=lastknownchange-target; + while ((source+numchanged>=sourceend || + source[numchanged]!=target[numchanged] || + source[numchanged+1]!=target[numchanged+1] || + source[numchanged+2]!=target[numchanged+2]) && + target+numchanged=sourceend) numchanged=targetend-target; + } + lastknownchange=target+numchanged; + if (numchanged) + { + //assert_shift((numchanged-1), 2); + size_t rle1start=(target==targetbegin); + while (true) + { + if ( + target[rle1start-1]==target[rle1start+0] && + target[rle1start+0]==target[rle1start+1] && + target[rle1start+1]==target[rle1start+2] && + target[rle1start+2]==target[rle1start+3]) + { + numchanged=rle1start; + break; + } + if ( + target[rle1start-2]==target[rle1start+0] && + target[rle1start-1]==target[rle1start+1] && + target[rle1start+0]==target[rle1start+2] && + target[rle1start+1]==target[rle1start+3] && + target[rle1start+2]==target[rle1start+4]) + { + numchanged=rle1start; + break; + } + if (rle1start+3>=numchanged) break; + rle1start++; + } + if (numchanged) + { + writenum((numchanged-1)<<2 | TargetRead); + for (size_t i=0;iptr=out; + patchmem->len=outlen; + + //while this may look like it can be fooled by a patch containing one of any other command, it + // can't, because the ones that aren't SourceRead requires an argument. + size_t i; + for (i=mainContentPos;(out[i]&0x80)==0x00;i++) {} + if (i==outlen-12-1) return bps_identical; + + return bps_ok; +} + +#undef write_nocrc +#undef write +#undef writenum + +enum bpserror bps_get_checksums(file* patch, uint32_t * inromsum, uint32_t * outromsum, uint32_t * patchsum) +{ + size_t len = patch->len(); + if (len<4+3+12) return bps_broken; + + uint8_t verify[4]; + if (!patch->read(verify, 0, 4) || memcmp(verify, "BPS1", 4)) return bps_broken; + + uint8_t checksums[12]; + if (!patch->read(checksums, len-12, 12)) return bps_broken; + if (inromsum) *inromsum =read32(checksums+0); + if (outromsum) *outromsum=read32(checksums+4); + if (patchsum) *patchsum =read32(checksums+8); + return bps_ok; +} + +void bps_free(struct mem mem) +{ + free(mem.ptr); +} + +#if 0 +#warning Disable this in release versions. + +#include + +//Congratulations, you found the undocumented feature! It compares two equivalent BPS patches and +// tells where each one is more compact. (It crashes or gives bogus answers on invalid or +// non-equivalent patches.) Have fun. +void bps_compare(struct mem patch1mem, struct mem patch2mem) +{ + const uint8_t * patch[2]={patch1mem.ptr, patch2mem.ptr}; + size_t patchpos[2]={0,0}; + size_t patchlen[2]={patch1mem.len-12, patch2mem.len-12}; + size_t patchoutpos[2]={0,0}; + + size_t patchcopypos[2][4]={0,0};//[0] and [1] are unused, but this is just debug code, it doesn't need to be neat. + +#define read8(id) (patch[id][patchpos[id]++]) +#define decodeto(id, var) \ + do { \ + var=0; \ + int shift=0; \ + while (true) \ + { \ + uint8_t next=read8(id); \ + size_t addthis=(next&0x7F)<=patchoutpos[1])}; + char describe[2][256]; + for (int i=0;i<2;i++) + { + if (step[i]) + { + size_t patchposstart=patchpos[i]; + decodeto(i, tempuint); + size_t len=(tempuint>>2)+1; + patchoutpos[i]+=len; + int action=(tempuint&3); +//enum { SourceRead, TargetRead, SourceCopy, TargetCopy }; + const char * actionnames[]={"SourceRead", "TargetRead", "SourceCopy", "TargetCopy"}; + if (action==TargetRead) patchpos[i]+=len; + if (action==SourceCopy || action==TargetCopy) + { + decodeto(i, tempuint); + int delta = tempuint>>1; + if (tempuint&1) delta=-delta; + patchcopypos[i][action]+=delta; + sprintf(describe[i], "%s from %i (%+i) for %i in %i", actionnames[action], patchcopypos[i][action], delta, len, patchpos[i]-patchposstart); + patchcopypos[i][action]+=len; + } + else sprintf(describe[i], "%s from %i for %i in %i", actionnames[action], patchoutpos[i], len, patchpos[i]-patchposstart); + if (!step[i^1]) + { + printf("%i: %s\n", i+1, describe[i]); + show=true; + } + } + } + if (step[0] && step[1]) + { + if (!strcmp(describe[0], describe[1])) /*printf("3: %s\n", describe[0])*/; + else + { + printf("1: %s\n2: %s\n", describe[0], describe[1]); + show=true; + } + } + if (patchoutpos[0]==patchoutpos[1]) + { + size_t used[2]={patchpos[0]-patchposatmatch[0], patchpos[1]-patchposatmatch[1]}; + char which='='; + if (used[0]used[1]) which='-'; + if (show) + { + printf("%c: %i,%i bytes since last match (%i)\n", which, used[0], used[1], patchoutpos[0]); + show=false; + } + patchposatmatch[0]=patchpos[0]; + patchposatmatch[1]=patchpos[1]; + lastmatch=patchoutpos[0]; + } + } +} + +static struct mem ReadWholeFile(const char * filename) +{ + struct mem null = {NULL, 0}; + + FILE * file=fopen(filename, "rb"); + if (!file) return null; + fseek(file, 0, SEEK_END); + size_t len=ftell(file); + fseek(file, 0, SEEK_SET); + unsigned char * data=(unsigned char*)malloc(len); + size_t truelen=fread(data, 1,len, file); + fclose(file); + if (len!=truelen) + { + free(data); + return null; + } + + struct mem ret = { (unsigned char*)data, len }; + return ret; +} +int main(int argc,char**argv) +{ +bps_compare(ReadWholeFile(argv[1]),ReadWholeFile(argv[2])); +} +#endif diff --git a/libbps.h b/libbps.h new file mode 100644 index 0000000..8894b98 --- /dev/null +++ b/libbps.h @@ -0,0 +1,70 @@ +//Module name: libbps +//Author: Alcaro +//Date: June 18, 2015 +//Licence: GPL v3.0 or higher + +#include "global.h" +#include + +#ifndef __cplusplus +#include //bool; if this file does not exist (hi msvc), remove it and uncomment the following three lines. +//#define bool int +//#define true 1 +//#define false 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum bpserror { + bps_ok,//Patch applied or created successfully. + + bps_to_output,//You attempted to apply a patch to its output. + bps_not_this, //This is not the intended input file for this patch. + bps_broken, //This is not a BPS patch, or it's malformed somehow. + + bps_identical, //The input files are identical. + bps_too_big, //Somehow, you're asking for something a size_t can't represent. + bps_out_of_mem,//Memory allocation failure. + bps_canceled, //The callback returned false. + + bps_shut_the_fuck_up_gcc//This one isn't used, it's just to kill a stray comma warning. +}; + +//Applies the given BPS patch to the given ROM and puts it in 'out'. Metadata, if present and +// requested ('metadata'!=NULL), is also returned. Send both to bps_free when you're done with them. +//If accept_wrong_input is true, it may return bps_to_output or bps_not_this, while putting non-NULL in out/metadata. +enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input); + +//Creates a BPS patch that converts source to target and stores it to patch. It is safe to give +// {NULL,0} as metadata. +enum bpserror bps_create_linear(struct mem source, struct mem target, struct mem metadata, struct mem * patch); + +//Very similar to bps_create_linear; the difference is that this one takes longer to run, but +// generates smaller patches. +//Because it can take much longer, a progress meter is supplied; total is guaranteed to be constant +// between every call until this function returns, done is guaranteed to increase between each +// call, and done/total is an approximate percentage counter. Anything else is undefined; for +// example, progress may or may not be called for done=0, progress may or may not be called for +// done=total, done may or may not increase by the same amount between each call, and the duration +// between each call may or may not be constant. In fact, it can +//To cancel the patch creation, return false from the callback. +//It is safe to pass in NULL for the progress indicator if you're not interested. If the callback is +// NULL, it can obviously not be canceled that way (though if it's a CLI program, you can always +// Ctrl-C it). +//The 'moremem' flag makes it use about twice as much memory (9*(source+target) instead of 5*), but is usually slightly faster. +enum bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patch, + bool (*progress)(void* userdata, size_t done, size_t total), void* userdata, + bool moremem); + +enum bpserror bps_get_checksums(file* patch, uint32_t * inromsum, uint32_t * outromsum, uint32_t * patchsum); + +//Frees the memory returned in the output parameters of the above. Do not call it twice on the same +// input, nor on anything you got from anywhere else. bps_free is guaranteed to be equivalent to +// calling stdlib.h's free() on mem.ptr. +void bps_free(struct mem mem); + +#ifdef __cplusplus +} +#endif diff --git a/libips.cpp b/libips.cpp new file mode 100644 index 0000000..904c5d1 --- /dev/null +++ b/libips.cpp @@ -0,0 +1,401 @@ +//Module name: libips +//Author: Alcaro +//Date: July 11, 2013 +//Licence: GPL v3.0 or higher + +#ifndef __cplusplus +#include //bool; if this does not exist, remove it and uncomment the following three lines. +//#define bool int +//#define true 1 +//#define false 0 +#endif +#include //malloc, realloc, free +#include //memcpy, memset + +#include "libips.h" + +typedef unsigned char byte; + +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) +#define clamp(a,b,c) max(a,min(b,c)) + +struct ipsstudy { + enum ipserror error; + unsigned int outlen_min; + unsigned int outlen_max; + unsigned int outlen_min_mem; +}; + +enum ipserror ips_study(struct mem patch, struct ipsstudy * study) +{ + study->error=ips_invalid; + if (patch.len<8) return ips_invalid; + const unsigned char * patchat=patch.ptr; + const unsigned char * patchend=patchat+patch.len; +#define read8() ((patchatoutlen) outlen=thisout; + if (patchat>=patchend) return ips_invalid; + offset=read24(); + } + study->outlen_min_mem=outlen; + study->outlen_max=0xFFFFFFFF; + if (patchat+3==patchend) + { + unsigned int truncate=read24(); + study->outlen_max=truncate; + if (outlen>truncate) + { + outlen=truncate; + w_scrambled=true; + } + } + if (patchat!=patchend) return ips_invalid; + study->outlen_min=outlen; +#undef read8 +#undef read16 +#undef read24 + study->error=ips_ok; + if (w_scrambled) study->error=ips_scrambled; + return study->error; +} + +enum ipserror ips_apply_study(struct mem patch, struct ipsstudy * study, struct mem in, struct mem * out) +{ + out->ptr=NULL; + out->len=0; + if (study->error==ips_invalid) return study->error; +#define read8() (*patchat++)//guaranteed to not overflow at this point, we already checked the patch +#define read16() (patchat+=2,((patchat[-2]<<8)|patchat[-1])) +#define read24() (patchat+=3,((patchat[-3]<<16)|(patchat[-2]<<8)|patchat[-1])) + unsigned int outlen=clamp(study->outlen_min, in.len, study->outlen_max); + out->ptr=(byte*)malloc(max(outlen, study->outlen_min_mem)); + out->len=outlen; + + bool anychanges=false; + if (outlen!=in.len) anychanges=true; + + if (out->len>in.len) + { + memcpy(out->ptr, in.ptr, in.len); + memset(out->ptr+in.len, 0, out->len-in.len); + } + else memcpy(out->ptr, in.ptr, outlen); + const unsigned char * patchat=patch.ptr+5; + unsigned int offset=read24(); + while (offset!=0x454F46) + { + unsigned int size=read16(); + if (size==0) + { + size=read16(); + if (!size) {}//no clue (fix the change detector if changing this) + unsigned char b=read8(); + + if (size && (out->ptr[offset]!=b || memcmp(out->ptr+offset, out->ptr+offset, size-1))) anychanges=true; + + memset(out->ptr+offset, b, size); + } + else + { + if (memcmp(out->ptr+offset, patchat, size)) anychanges=true; + + memcpy(out->ptr+offset, patchat, size); + patchat+=size; + } + offset=read24(); + } +#undef read8 +#undef read16 +#undef read24 + + if (study->outlen_max!=0xFFFFFFFF && in.len<=study->outlen_max) study->error=ips_notthis;//truncate data without this being needed is a poor idea + if (!anychanges) study->error=ips_thisout; + return study->error; +} + +enum ipserror ips_apply(struct mem patch, struct mem in, struct mem * out) +{ + struct ipsstudy study; + ips_study(patch, &study); + return ips_apply_study(patch, &study, in, out); +} + +//Known situations where this function does not generate an optimal patch: +//In: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 +//Out: FF FF FF FF FF FF FF FF 00 01 02 03 04 05 06 07 FF FF FF FF FF FF FF FF +//IPS: [ RLE ] [ Copy ] [ RLE ] +//Possible improvement: RLE across the entire file, copy on top of that. +//Rationale: It would be a huge pain to create such a multi-pass tool if it should support writing a byte +// more than twice, and I don't like half-assing stuff. It's also unlikely to apply to anything. + + +//Known improvements over LIPS: +//In: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +//Out: FF 01 02 03 04 05 FF FF FF FF FF FF FF FF FF FF +//LIPS:[ Copy ] [ RLE ] +//Mine:[] [ Unchanged ] [ RLE ] +//Rationale: While LIPS can break early if it finds something RLEable in the middle of a block, it's not +// smart enough to back off if there's something unchanged between the changed area and the RLEable spot. + +//In: FF FF FF FF FF FF FF +//Out: 00 00 00 00 01 02 03 +//LIPS:[ RLE ] [ Copy ] +//Mine:[ Copy ] +//Rationale: Mistuned heuristics in LIPS. + +//It is also known that I win in some other situations. I didn't bother checking which, though. + +//There are no known cases where LIPS wins over libips. + +enum ipserror ips_create(struct mem sourcemem, struct mem targetmem, struct mem * patchmem) +{ + unsigned int sourcelen=sourcemem.len; + unsigned int targetlen=targetmem.len; + const unsigned char * source=sourcemem.ptr; + const unsigned char * target=targetmem.ptr; + + patchmem->ptr=NULL; + patchmem->len=0; + + if (targetlen>=16777216) return ips_16MB; + + unsigned int offset=0; + unsigned int outbuflen=4096; + unsigned char * out=(byte*)malloc(outbuflen); + unsigned int outlen=0; +#define write8(val) do { out[outlen++]=(val); if (outlen==outbuflen) { outbuflen*=2; out=(byte*)realloc(out, outbuflen); } } while(0) +#define write16(val) do { write8((val)>>8); write8((val)); } while(0) +#define write24(val) do { write8((val)>>16); write8((val)>>8); write8((val)); } while(0) + write8('P'); + write8('A'); + write8('T'); + write8('C'); + write8('H'); + int lastknownchange=0; + //int forcewrite=(targetlen>sourcelen?1:0); + while (offset=6 || thislen>=65536) break; + } + + //avoid premature EOF + if (offset==0x454F46) + { + offset--; + thislen++; + } + + lastknownchange=offset+thislen; + if (thislen>65535) thislen=65535; + if (offset+thislen>targetlen) thislen=targetlen-offset; + if (offset==targetlen) continue; + + //check if RLE here is worthwhile + int byteshere; + for (byteshere=0;byteshere=targetlen || target[pos]!=thisbyte || byteshere+i>65535) break; + if (pos>=sourcelen || (pos8-5 && byteshere==thislen) || byteshere>8) + { + write24(offset); + write16(0); + write16(byteshere); + write8(target[offset]); + offset+=byteshere; + } + else + { + //check if we'd gain anything from ending the block early and switching to RLE + int byteshere=0; + int stopat=0; + while (stopat+byteshere8+5 || //rle-worthy despite two ips headers + (byteshere>8 && stopat+byteshere==thislen) || //rle-worthy at end of data + (byteshere>8 && !memcmp(&target[offset+stopat+byteshere], &target[offset+stopat+byteshere+1], 9-1)))//rle-worthy before another rle-worthy + { + if (stopat) thislen=stopat; + break;//we don't scan the entire block if we know we'll want to RLE, that'd gain nothing. + } + } + //don't write unchanged bytes at the end of a block if we want to RLE the next couple of bytes + if (offset+thislen!=targetlen) + { + while (offset+thislen-13 && !memcmp(&target[offset], &target[offset+1], thislen-1))//still worth it? + { + write24(offset); + write16(0); + write16(thislen); + write8(target[offset]); + } + else + { + write24(offset); + write16(thislen); + int i; + for (i=0;itargetlen) write24(targetlen); +#undef write + patchmem->ptr=out; + patchmem->len=outlen; + if (outlen==8) return ips_identical; + return ips_ok; +} + +void ips_free(struct mem mem) +{ + free(mem.ptr); +} + +#if 0 +#warning Disable this in release versions. +#include + +//Congratulations, you found the undocumented feature! I don't think it's useful for anything except debugging libips, though. +void ips_dump(struct mem patch) +{ + if (patch.len<8) + { + puts("Invalid"); + return; + } + const unsigned char * patchat=patch.ptr; + const unsigned char * patchend=patchat+patch.len; +#define read8() ((patchatoutlen) outlen=thisout; + if (patchat>=patchend) + { + puts("Invalid"); + return; + } + blockstart=patchat-patch.ptr; + offset=read24(); + } + printf("Expand to 0x%X\n", outlen); + if (patchat+3==patchend) + { + int truncate=read24(); + printf("Truncate to 0x%X\n", truncate); + } + if (patchat!=patchend) puts("Invalid"); +#undef read8 +#undef read16 +#undef read24 +} +#endif diff --git a/libips.h b/libips.h new file mode 100644 index 0000000..aa131fd --- /dev/null +++ b/libips.h @@ -0,0 +1,41 @@ +//Module name: libips +//Author: Alcaro +//Date: March 8, 2013 +//Licence: GPL v3.0 or higher + +#include "global.h" + +enum ipserror { + ips_ok,//Patch applied or created successfully. + + ips_notthis,//The patch is most likely not intended for this ROM. + ips_thisout,//You most likely applied the patch on the output ROM. + ips_scrambled,//The patch is technically valid, but seems scrambled or malformed. + ips_invalid,//The patch is invalid. + + ips_16MB,//One or both files is bigger than 16MB. The IPS format doesn't support that. The created + //patch contains only the differences to that point. + ips_identical,//The input buffers are identical. + + ips_shut_the_fuck_up_gcc//This one isn't used, it's just to kill a stray comma warning. +}; + +//Applies the IPS patch in [patch, patchlen] to [in, inlen] and stores it to [out, outlen]. Send the +// return value in out to ips_free when you're done with it. +enum ipserror ips_apply(struct mem patch, struct mem in, struct mem * out); + +//Creates an IPS patch that converts source to target and stores it to patch. +enum ipserror ips_create(struct mem source, struct mem target, struct mem * patch); + +//Frees the memory returned in the output parameters of the above. Do not call it twice on the same +// input, nor on anything you got from anywhere else. ips_free is guaranteed to be equivalent to +// calling stdlib.h's free() on mem.ptr. +void ips_free(struct mem mem); + +//ips_study allows you to detect most patching errors without applying it to a ROM, or even a ROM to +// apply it to. ips_apply calls ips_study and ips_apply_study, so if you call ips_study yourself, +// it's recommended to call ips_apply_study to not redo the calculation. ips_free is still +// required. +struct ipsstudy; +enum ipserror ips_study(struct mem patch, struct ipsstudy * study); +enum ipserror ips_apply_study(struct mem patch, struct ipsstudy * study, struct mem in, struct mem * out); diff --git a/libups.cpp b/libups.cpp new file mode 100644 index 0000000..3af8e74 --- /dev/null +++ b/libups.cpp @@ -0,0 +1,178 @@ +//Module name: libups +//Author: Alcaro +//Date: April 4, 2013 +//Licence: GPL v3.0 or higher + +#include "libups.h" + +#ifndef __cplusplus +#include //bool; if this file does not exist (hi msvc), remove it and uncomment the following three lines. +//#define bool int +//#define true 1 +//#define false 0 +#endif +#include //uint8_t, uint32_t +#include //malloc, realloc, free +#include //memcpy, memset +#include "crc32.h" + +static uint32_t read32(uint8_t * ptr) +{ + uint32_t out; + out =ptr[0]; + out|=ptr[1]<<8; + out|=ptr[2]<<16; + out|=ptr[3]<<24; + return out; +} + +#define error(which) do { error=which; goto exit; } while(0) +#define assert_sum(a,b) do { if (SIZE_MAX-(a)<(b)) error(ups_too_big); } while(0) +#define assert_shift(a,b) do { if (SIZE_MAX>>(b)<(a)) error(ups_too_big); } while(0) +enum upserror ups_apply(struct mem patch, struct mem in, struct mem * out) +{ + enum upserror error; + out->len=0; + out->ptr=NULL; + if (patch.len<4+2+12) return ups_broken; + + if (true) + { +#define readpatch8() (*(patchat++)) +#define readin8() (*(inat++)) +#define writeout8(byte) (*(outat++)=byte) + +#define decodeto(var) \ + do { \ + var=0; \ + unsigned int shift=0; \ + while (true) \ + { \ + uint8_t next=readpatch8(); \ + assert_shift(next&0x7F, shift); \ + size_t addthis=(next&0x7F)<len=outlen; + out->ptr=(uint8_t*)malloc(outlen); + memset(out->ptr, 0, outlen); + + //uint8_t * instart=in.ptr; + uint8_t * inat=in.ptr; + uint8_t * inend=in.ptr+in.len; + + //uint8_t * outstart=out->ptr; + uint8_t * outat=out->ptr; + uint8_t * outend=out->ptr+out->len; + + while (patchat0) + { + uint8_t out; + if (inat>=inend) out=0; + else out=readin8(); + if (outat=inend) out=0; + else out=readin8(); + if (outatptr, out->len); + uint32_t crc_patch=crc32(patch.ptr, patch.len-4); + + if (inlen==outlen) + { + if ((crc_in!=crc_in_expected || crc_out!=crc_out_expected) && (crc_in!=crc_out_expected || crc_out!=crc_in_expected)) error(ups_not_this); + } + else + { + if (!backwards) + { + if (crc_in!=crc_in_expected) error(ups_not_this); + if (crc_out!=crc_out_expected) error(ups_not_this); + } + else + { + if (crc_in!=crc_out_expected) error(ups_not_this); + if (crc_out!=crc_in_expected) error(ups_not_this); + } + } + if (crc_patch!=crc_patch_expected) error(ups_broken); + return ups_ok; +#undef read8 +#undef decodeto +#undef write8 + } + +exit: + free(out->ptr); + out->len=0; + out->ptr=NULL; + return error; +} + +enum upserror ups_create(struct mem sourcemem, struct mem targetmem, struct mem * patchmem) +{ + patchmem->ptr=NULL; + patchmem->len=0; + return ups_broken;//unimplemented, just pick a random error +} + +void ups_free(struct mem mem) +{ + free(mem.ptr); +} + +#if 0 +//Sorry, no undocumented features here. The only thing that can change an UPS patch is swapping the two sizes and checksums, and I don't create anyways. +#endif diff --git a/libups.h b/libups.h new file mode 100644 index 0000000..1cd3c2b --- /dev/null +++ b/libups.h @@ -0,0 +1,34 @@ +//Module name: libups +//Author: Alcaro +//Date: April 4, 2013 +//Licence: GPL v3.0 or higher + +#include "global.h" + +//Several of those are unused, but remain there so the remaining ones match bpserror. +enum upserror { + ups_ok,//Patch applied or created successfully. + + ups_unused1, //bps_too_output + ups_not_this,//This is not the intended input file for this patch. + ups_broken, //This is not a UPS patch, or it's malformed somehow. + + ups_identical,//The input files are identical. + ups_too_big, //Somehow, you're asking for something a size_t can't represent. + ups_unused2, //bps_out_of_mem + ups_unused3, //bps_canceled + + ups_shut_the_fuck_up_gcc//This one isn't used, it's just to kill a stray comma warning. +}; + +//Applies the UPS patch in [patch, patchlen] to [in, inlen] and stores it to [out, outlen]. Send the +// return value in out to ups_free when you're done with it. +enum upserror ups_apply(struct mem patch, struct mem in, struct mem * out); + +//Creates an UPS patch that converts source to target and stores it to patch. +enum upserror ups_create(struct mem source, struct mem target, struct mem * patch); + +//Frees the memory returned in the output parameters of the above. Do not call it twice on the same +// input, nor on anything you got from anywhere else. ups_free is guaranteed to be equivalent to +// calling stdlib.h's free() on mem.ptr. +void ups_free(struct mem mem); diff --git a/license-gpl.txt b/license-gpl.txt new file mode 100644 index 0000000..818433e --- /dev/null +++ b/license-gpl.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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 Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..c3bf78f --- /dev/null +++ b/license.txt @@ -0,0 +1,11 @@ +Flips is licensed under GNU General Public License, version 3.0 or higher. The full legal text can + be found in boring.zip; a rough interpretation (for non-lawyers only) follows: + +- You must credit the author. Don't claim it as your own. You may modify it and take credit for your + modifications, but the author (Alcaro) must be credited for the original software. +- If you modify this software, it must clearly be labeled as a modification. +- Any applications containing any part of this software must provide the full source code needed to + modify and rebuild this application, under the same license. Including this interpretation is + optional. +- The author claims no copyright over input, output, or error messages generated by this tool. Use + it however you want. diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..78553d3 --- /dev/null +++ b/make.sh @@ -0,0 +1,56 @@ +#clean up +rm flips.exe floating.zip flips rc.o *.gcda + +FLAGS='-Wall -Werror -O3 -fomit-frame-pointer -fmerge-all-constants -fno-exceptions -fno-asynchronous-unwind-tables' + +#create windows binary +echo 'Windows/Resource (Wine warmup)' +wine windres flips.rc rc.o + +echo 'Windows (1/3)' +rm flips.exe; CFLAGS=$FLAGS' -fprofile-generate' wine mingw32-make TARGET=windows LFLAGS='-s -lgcov' +#wine gcc -pipe -std=c99 $FLAGS -fprofile-generate *.c rc.o -mwindows -lgdi32 -lcomdlg32 -lcomctl32 -s -lgcov -oflips.exe +[ -e flips.exe ] || exit +echo 'Windows (2/3)' +profile/profile.sh 'wine flips.exe' NUL +echo 'Windows (3/3)' +rm flips.exe; CFLAGS=$FLAGS' -fprofile-use' wine mingw32-make TARGET=windows LFLAGS='-s' +#wine g++ -pipe -std=c99 $FLAGS -fprofile-use *.c rc.o -mwindows -lgdi32 -lcomdlg32 -lcomctl32 -s -oflips.exe +rm *.gcda rc.o + +#verify there are no unexpected dependencies +objdump -p flips.exe | grep 'DLL Name' | \ + grep -Pvi '(msvcrt|advapi32|comctl32|comdlg32|gdi32|kernel32|shell32|user32)' && \ + echo "Invalid dependency" && exit + +#test cli binaries +echo CLI +rm flips; make TARGET=cli DIVSUF=no +[ -e flips ] || exit + +#create linux binary +echo 'GTK+ (1/3)' +rm flips; CFLAGS=$FLAGS' -fprofile-generate' make TARGET=gtk LFLAGS='-s -lgcov' +[ -e flips ] || exit +echo 'GTK+ (2/3)' +profile/profile.sh ./flips +echo 'GTK+ (3/3)' +rm flips; CFLAGS=$FLAGS' -fprofile-use' make TARGET=gtk LFLAGS='-s' +rm *.gcda +mv flips ~/bin/flips # keeping this one for myself + +echo Finishing +#compress source +7z a -mx0 src.zip *.cpp *.h Makefile flips.rc flips.Manifest *.ico make.sh profile/profile.sh profile/profile1.sh special.sh +7z a -mx9 boring.zip license-*.txt +zipcrush boring.zip + +7z a floating.zip flips.exe src.zip boring.zip license.txt +zipcrush floating.zip +echo Size: $(stat -c%s flips.exe)/96768 +echo \(Linux: $(stat -c%s ~/bin/flips)\) +echo \(Zipped: $(stat -c%s floating.zip)/59881\) + +./special.sh + +rm src.zip boring.zip diff --git a/profile/profile.sh b/profile/profile.sh new file mode 100755 index 0000000..b5585a1 --- /dev/null +++ b/profile/profile.sh @@ -0,0 +1,16 @@ +#sorry guys, you'll have to supply the ROMs yourself. +#not sure if these compile scripts work for anyone else, either. +OMP_NUM_THREADS=1 $1 --apply profile/dl.ups profile/langrisser.sfc profile/tmp +echo 1/6 +profile/profile1.sh "$1" profile/smw.smc profile/2dland-dec2013.smc +echo 2/6 +profile/profile1.sh "$1" profile/smw.smc profile/smwcp.smc +echo 3/6 +profile/profile1.sh "$1" profile/smw.smc profile/nsmw-tll.smc +echo 4/6 +profile/profile1.sh "$1" profile/smw.smc profile/senate13.smc +echo 5/6 +profile/profile1.sh "$1" profile/smw.smc profile/kamek.smc +echo 6/6 +profile/profile1.sh "$1" profile/sm64.z64 profile/star.z64 +rm profile/tmp diff --git a/profile/profile1.sh b/profile/profile1.sh new file mode 100755 index 0000000..e4f237d --- /dev/null +++ b/profile/profile1.sh @@ -0,0 +1,9 @@ +OMP_NUM_THREADS=1 $1 --create --ips $2 $3 profile/tmp +OMP_NUM_THREADS=1 $1 --apply --ips profile/tmp $2 profile/tmp + +OMP_NUM_THREADS=1 $1 --create --bps-linear $2 $3 profile/tmp +OMP_NUM_THREADS=1 $1 --apply --bps-linear profile/tmp $2 profile/tmp + +OMP_NUM_THREADS=1 $1 --create --bps-delta $2 $3 profile/tmp +OMP_NUM_THREADS=1 $1 --create --bps-delta-moremem $2 $3 profile/tmp +OMP_NUM_THREADS=1 $1 --apply --bps-delta profile/tmp $2 profile/tmp diff --git a/sais.cpp b/sais.cpp new file mode 100644 index 0000000..c975b9f --- /dev/null +++ b/sais.cpp @@ -0,0 +1,479 @@ +/* + * sais.c for sais-lite + * Copyright (c) 2008-2010 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +//This file is heavily modified from the original ; +//while the algorithm is the same, many changes were done. +//- The 'cs' parameters (1 or 4, depending on whether T is int* or uint8_t*) were replaced with C++ templates. This gave a fair speedup. +//- sais_index_type was replaced with a C++ template. +//- bwt, and various other stuff I don't use, was removed. +//- Assertions were removed, as they too showed up heavily in profiles; however, I suspect that just shifted the time taken elsewhere. + +#include +#include +#undef assert +#define assert(x) + +#ifndef MINBUCKETSIZE +# define MINBUCKETSIZE 256 +#endif + +#define SAIS_LMSSORT2_LIMIT 0x3fffffff + +//static void* allocstack[256]; +//static size_t allocstacklen[256]; +//static size_t allocstackpos=0; +//static size_t allocstacksum=0; +//static size_t allocstackmax=0; +//static void* mymalloc(size_t n) +//{ +// void* ret=malloc(n); +// allocstacksum+=n; +// if(allocstacksum>allocstackmax)allocstackmax=allocstacksum; +// printf("a %p(%i) (max %i)\n",ret,n,allocstackmax); +// allocstacklen[allocstackpos]=n; +// allocstack[allocstackpos]=ret; +// allocstackpos++; +// return ret; +//} +//static void myfree(void* p) +//{ +// printf("f %p\n",p); +// allocstackpos--; +// allocstacksum-=allocstacklen[allocstackpos]; +// if (p!=allocstack[allocstackpos]) printf("E: not stack\n"); +// if (allocstackpos==0) allocstackmax=0; +// free(p); +//} +#define SAIS_MYMALLOC(_num, _type) ((_type *)malloc((_num) * sizeof(_type))) +#define SAIS_MYFREE(_ptr, _num, _type) free((_ptr)) +#define chr(_a) T[_a] + +/* find the start or end of each bucket */ +template +static +void +getCounts(const TT *T, sais_index_type *C, sais_index_type n, sais_index_type k) { + sais_index_type i; + for(i = 0; i < k; ++i) { C[i] = 0; } + for(i = 0; i < n; ++i) { ++C[chr(i)]; } +} +template +static +void +getBuckets(const sais_index_type *C, sais_index_type *B, sais_index_type k, bool end) { + sais_index_type i, sum = 0; + if(end) { for(i = 0; i < k; ++i) { sum += C[i]; B[i] = sum; } } + else { for(i = 0; i < k; ++i) { sum += C[i]; B[i] = sum - C[i]; } } +} + +/* sort all type LMS suffixes */ +template +static +void +LMSsort1(const TT *T, sais_index_type *SA, + sais_index_type *C, sais_index_type *B, + sais_index_type n, sais_index_type k) { + sais_index_type *b, i, j; + sais_index_type c0, c1; + + /* compute SAl */ + if(C == B) { getCounts(T, C, n, k); } + getBuckets(C, B, k, false); /* find starts of buckets */ + j = n - 1; + b = SA + B[c1 = chr(j)]; + --j; + *b++ = (chr(j) < c1) ? ~j : j; + for(i = 0; i < n; ++i) { + if(0 < (j = SA[i])) { + assert(chr(j) >= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert(i < (b - SA)); + --j; + *b++ = (chr(j) < c1) ? ~j : j; + SA[i] = 0; + } else if(j < 0) { + SA[i] = ~j; + } + } + /* compute SAs */ + if(C == B) { getCounts(T, C, n, k); } + getBuckets(C, B, k, true); /* find ends of buckets */ + for(i = n - 1, b = SA + B[c1 = 0]; 0 <= i; --i) { + if(0 < (j = SA[i])) { + assert(chr(j) <= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert((b - SA) <= i); + --j; + *--b = (chr(j) > c1) ? ~(j + 1) : j; + SA[i] = 0; + } + } +} +template +static +sais_index_type +LMSpostproc1(const TT *T, sais_index_type *SA, + sais_index_type n, sais_index_type m) { + sais_index_type i, j, p, q, plen, qlen, name; + sais_index_type c0, c1; + bool diff; + + /* compact all the sorted substrings into the first m items of SA + 2*m must be not larger than n (proveable) */ + assert(0 < n); + for(i = 0; (p = SA[i]) < 0; ++i) { SA[i] = ~p; assert((i + 1) < n); } + if(i < m) { + for(j = i, ++i;; ++i) { + assert(i < n); + if((p = SA[i]) < 0) { + SA[j++] = ~p; SA[i] = 0; + if(j == m) { break; } + } + } + } + + /* store the length of all substrings */ + i = n - 1; j = n - 1; c0 = chr(n - 1); + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + for(; 0 <= i;) { + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) <= c1)); + if(0 <= i) { + SA[m + ((i + 1) >> 1)] = j - i; j = i + 1; + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + } + } + + /* find the lexicographic names of all substrings */ + for(i = 0, name = 0, q = n, qlen = 0; i < m; ++i) { + p = SA[i], plen = SA[m + (p >> 1)], diff = true; + if((plen == qlen) && ((q + plen) < n)) { + for(j = 0; (j < plen) && (chr(p + j) == chr(q + j)); ++j) { } + if(j == plen) { diff = false; } + } + if(diff) { ++name, q = p, qlen = plen; } + SA[m + (p >> 1)] = name; + } + + return name; +} +template +static +void +LMSsort2(const TT *T, sais_index_type *SA, + sais_index_type *C, sais_index_type *B, sais_index_type *D, + sais_index_type n, sais_index_type k) { + sais_index_type *b, i, j, t, d; + sais_index_type c0, c1; + assert(C != B); + + /* compute SAl */ + getBuckets(C, B, k, false); /* find starts of buckets */ + j = n - 1; + b = SA + B[c1 = chr(j)]; + --j; + t = (chr(j) < c1); + j += n; + *b++ = (t & 1) ? ~j : j; + for(i = 0, d = 0; i < n; ++i) { + if(0 < (j = SA[i])) { + if(n <= j) { d += 1; j -= n; } + assert(chr(j) >= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert(i < (b - SA)); + --j; + t = c0; t = (t << 1) | (chr(j) < c1); + if(D[t] != d) { j += n; D[t] = d; } + *b++ = (t & 1) ? ~j : j; + SA[i] = 0; + } else if(j < 0) { + SA[i] = ~j; + } + } + for(i = n - 1; 0 <= i; --i) { + if(0 < SA[i]) { + if(SA[i] < n) { + SA[i] += n; + for(j = i - 1; SA[j] < n; --j) { } + SA[j] -= n; + i = j; + } + } + } + + /* compute SAs */ + getBuckets(C, B, k, true); /* find ends of buckets */ + for(i = n - 1, d += 1, b = SA + B[c1 = 0]; 0 <= i; --i) { + if(0 < (j = SA[i])) { + if(n <= j) { d += 1; j -= n; } + assert(chr(j) <= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert((b - SA) <= i); + --j; + t = c0; t = (t << 1) | (chr(j) > c1); + if(D[t] != d) { j += n; D[t] = d; } + *--b = (t & 1) ? ~(j + 1) : j; + SA[i] = 0; + } + } +} +template +static +sais_index_type +LMSpostproc2(sais_index_type *SA, sais_index_type n, sais_index_type m) { + sais_index_type i, j, d, name; + + /* compact all the sorted LMS substrings into the first m items of SA */ + assert(0 < n); + for(i = 0, name = 0; (j = SA[i]) < 0; ++i) { + j = ~j; + if(n <= j) { name += 1; } + SA[i] = j; + assert((i + 1) < n); + } + if(i < m) { + for(d = i, ++i;; ++i) { + assert(i < n); + if((j = SA[i]) < 0) { + j = ~j; + if(n <= j) { name += 1; } + SA[d++] = j; SA[i] = 0; + if(d == m) { break; } + } + } + } + if(name < m) { + /* store the lexicographic names */ + for(i = m - 1, d = name + 1; 0 <= i; --i) { + if(n <= (j = SA[i])) { j -= n; --d; } + SA[m + (j >> 1)] = d; + } + } else { + /* unset flags */ + for(i = 0; i < m; ++i) { + if(n <= (j = SA[i])) { j -= n; SA[i] = j; } + } + } + + return name; +} + +/* compute SA and BWT */ +template +static +void +induceSA(const TT *T, sais_index_type *SA, + sais_index_type *C, sais_index_type *B, + sais_index_type n, sais_index_type k) { + sais_index_type *b, i, j; + sais_index_type c0, c1; + /* compute SAl */ + if(C == B) { getCounts(T, C, n, k); } + getBuckets(C, B, k, false); /* find starts of buckets */ + j = n - 1; + b = SA + B[c1 = chr(j)]; + *b++ = ((0 < j) && (chr(j - 1) < c1)) ? ~j : j; + for(i = 0; i < n; ++i) { + j = SA[i], SA[i] = ~j; + if(0 < j) { + --j; + assert(chr(j) >= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert(i < (b - SA)); + *b++ = ((0 < j) && (chr(j - 1) < c1)) ? ~j : j; + } + } + /* compute SAs */ + if(C == B) { getCounts(T, C, n, k); } + getBuckets(C, B, k, true); /* find ends of buckets */ + for(i = n - 1, b = SA + B[c1 = 0]; 0 <= i; --i) { + if(0 < (j = SA[i])) { + --j; + assert(chr(j) <= chr(j + 1)); + if((c0 = chr(j)) != c1) { B[c1] = b - SA; b = SA + B[c1 = c0]; } + assert((b - SA) <= i); + *--b = ((j == 0) || (chr(j - 1) > c1)) ? ~j : j; + } else { + SA[i] = ~j; + } + } +} + +/* find the suffix array SA of T[0..n-1] in {0..255}^n */ +template +static +sais_index_type +sais_main(const TT *T, sais_index_type *SA, + sais_index_type fs, sais_index_type n, sais_index_type k) { + sais_index_type *C, *B, *D, *RA, *b; + sais_index_type i, j, m, p, q, t, name, pidx = 0, newfs; + sais_index_type c0, c1; + unsigned int flags; + + assert((T != NULL) && (SA != NULL)); + assert((0 <= fs) && (0 < n) && (1 <= k)); + + if(k <= MINBUCKETSIZE) { + if((C = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { return -2; } + if(k <= fs) { + B = SA + (n + fs - k); + flags = 1; + } else { + if((B = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { SAIS_MYFREE(C, k, sais_index_type); return -2; } + flags = 3; + } + } else if(k <= fs) { + C = SA + (n + fs - k); + if(k <= (fs - k)) { + B = C - k; + flags = 0; + } else if(k <= (MINBUCKETSIZE * 4)) { + if((B = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { return -2; } + flags = 2; + } else { + B = C; + flags = 8; + } + } else { + if((C = B = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { return -2; } + flags = 4 | 8; + } + if((n <= SAIS_LMSSORT2_LIMIT) && (2 <= (n / k))) { + if(flags & 1) { flags |= ((k * 2) <= (fs - k)) ? 32 : 16; } + else if((flags == 0) && ((k * 2) <= (fs - k * 2))) { flags |= 32; } + } + + /* stage 1: reduce the problem by at least 1/2 + sort all the LMS-substrings */ + getCounts(T, C, n, k); getBuckets(C, B, k, true); /* find ends of buckets */ + for(i = 0; i < n; ++i) { SA[i] = 0; } + b = &t; i = n - 1; j = n; m = 0; c0 = chr(n - 1); + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + for(; 0 <= i;) { + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) <= c1)); + if(0 <= i) { + *b = j; b = SA + --B[c1]; j = i; ++m; + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + } + } + + if(1 < m) { + if(flags & (16 | 32)) { + if(flags & 16) { + if((D = SAIS_MYMALLOC(k * 2, sais_index_type)) == NULL) { + if(flags & (1 | 4)) { SAIS_MYFREE(C, k, sais_index_type); } + if(flags & 2) { SAIS_MYFREE(B, k, sais_index_type); } + return -2; + } + } else { + D = B - k * 2; + } + assert((j + 1) < n); + ++B[chr(j + 1)]; + for(i = 0, j = 0; i < k; ++i) { + j += C[i]; + if(B[i] != j) { assert(SA[B[i]] != 0); SA[B[i]] += n; } + D[i] = D[i + k] = 0; + } + LMSsort2(T, SA, C, B, D, n, k); + name = LMSpostproc2(SA, n, m); + if(flags & 16) { SAIS_MYFREE(D, k * 2, sais_index_type); } + } else { + LMSsort1(T, SA, C, B, n, k); + name = LMSpostproc1(T, SA, n, m); + } + } else if(m == 1) { + *b = j + 1; + name = 1; + } else { + name = 0; + } + + /* stage 2: solve the reduced problem + recurse if names are not yet unique */ + if(name < m) { + if(flags & 4) { SAIS_MYFREE(C, k, sais_index_type); } + if(flags & 2) { SAIS_MYFREE(B, k, sais_index_type); } + newfs = (n + fs) - (m * 2); + if((flags & (1 | 4 | 8)) == 0) { + if((k + name) <= newfs) { newfs -= k; } + else { flags |= 8; } + } + assert((n >> 1) <= (newfs + m)); + RA = SA + m + newfs; + for(i = m + (n >> 1) - 1, j = m - 1; m <= i; --i) { + if(SA[i] != 0) { + RA[j--] = SA[i] - 1; + } + } + if(sais_main(RA, SA, newfs, m, name) != 0) { + if(flags & 1) { SAIS_MYFREE(C, k, sais_index_type); } + return -2; + } + + i = n - 1; j = m - 1; c0 = chr(n - 1); + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + for(; 0 <= i;) { + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) <= c1)); + if(0 <= i) { + RA[j--] = i + 1; + do { c1 = c0; } while((0 <= --i) && ((c0 = chr(i)) >= c1)); + } + } + for(i = 0; i < m; ++i) { SA[i] = RA[SA[i]]; } + if(flags & 4) { + if((C = B = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { return -2; } + } + if(flags & 2) { + if((B = SAIS_MYMALLOC(k, sais_index_type)) == NULL) { + if(flags & 1) { SAIS_MYFREE(C, k, sais_index_type); } + return -2; + } + } + } + + /* stage 3: induce the result for the original problem */ + if(flags & 8) { getCounts(T, C, n, k); } + /* put all left-most S characters into their buckets */ + if(1 < m) { + getBuckets(C, B, k, true); /* find ends of buckets */ + i = m - 1, j = n, p = SA[m - 1], c1 = chr(p); + do { + q = B[c0 = c1]; + while(q < j) { SA[--j] = 0; } + do { + SA[--j] = p; + if(--i < 0) { break; } + p = SA[i]; + } while((c1 = chr(p)) == c0); + } while(0 <= i); + while(0 < j) { SA[--j] = 0; } + } + induceSA(T, SA, C, B, n, k); + if(flags & 2) { SAIS_MYFREE(B, k, sais_index_type); } + if(flags & (1 | 4)) { SAIS_MYFREE(C, k, sais_index_type); } + + return pidx; +} diff --git a/special.sh b/special.sh new file mode 100755 index 0000000..736f5ed --- /dev/null +++ b/special.sh @@ -0,0 +1,7 @@ +cp flips.exe special.exe +positions=`strings -td flips.exe | grep 'Flips v' | awk '{ print $1 }'` +for pos in $positions; do + echo 'IPS FLIPPER' | dd if=/dev/stdin of=special.exe bs=1 count=11 conv=notrunc seek=$pos +done +flips --create --bps-delta flips.exe special.exe special.bps +rm special.exe