mirror of
https://github.com/Alcaro/Flips.git
synced 2026-03-21 17:45:09 -05:00
Initial commit - version 1.31
This commit is contained in:
commit
24e6c77eb8
90
Makefile
Normal file
90
Makefile
Normal file
|
|
@ -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$@
|
||||
26
crc32.cpp
Normal file
26
crc32.cpp
Normal file
|
|
@ -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<len;i++)
|
||||
{
|
||||
crc = crctable_4bits[(crc^ data[i] )&0x0F] ^ (crc>>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);
|
||||
}
|
||||
10
crc32.h
Normal file
10
crc32.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//Module name: crc32
|
||||
//Author: Alcaro
|
||||
//Date: June 3, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
uint32_t crc32(const uint8_t* data, size_t len);
|
||||
uint32_t crc32_update(const uint8_t* data, size_t len, uint32_t crc);
|
||||
73
flips-cli.cpp
Normal file
73
flips-cli.cpp
Normal file
|
|
@ -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
|
||||
904
flips-gtk.cpp
Normal file
904
flips-gtk.cpp
Normal file
|
|
@ -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 <gtk/gtk.h>
|
||||
|
||||
class file_gtk : public file {
|
||||
size_t size;
|
||||
GFileInputStream* io;
|
||||
|
||||
public:
|
||||
static file* create(const char * filename)
|
||||
{
|
||||
GFile* file = g_file_new_for_commandline_arg(filename);
|
||||
if (!file) return NULL;
|
||||
GFileInputStream* io=g_file_read(file, NULL, NULL);
|
||||
g_object_unref(file);
|
||||
if (!io) return NULL;
|
||||
return new file_gtk(io);
|
||||
}
|
||||
|
||||
private:
|
||||
file_gtk(GFileInputStream* io) : io(io)
|
||||
{
|
||||
GFileInfo* info = g_file_input_stream_query_info(io, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL);
|
||||
size = g_file_info_get_size(info);
|
||||
g_object_unref(info);
|
||||
}
|
||||
|
||||
public:
|
||||
size_t len() { return size; }
|
||||
|
||||
bool read(uint8_t* target, size_t start, size_t len)
|
||||
{
|
||||
g_seekable_seek(G_SEEKABLE(io), start, G_SEEK_SET, NULL, NULL);
|
||||
gsize actualsize;
|
||||
return (g_input_stream_read_all(G_INPUT_STREAM(io), target, len, &actualsize, NULL, NULL) && actualsize == len);
|
||||
}
|
||||
|
||||
~file_gtk() { g_object_unref(io); }
|
||||
};
|
||||
|
||||
file* file::create(const char * filename) { return file_gtk::create(filename); }
|
||||
|
||||
|
||||
class filewrite_gtk : public filewrite {
|
||||
GOutputStream* io;
|
||||
|
||||
public:
|
||||
static filewrite* create(const char * filename)
|
||||
{
|
||||
GFile* file = g_file_new_for_commandline_arg(filename);
|
||||
if (!file) return NULL;
|
||||
GFileOutputStream* io = g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
||||
g_object_unref(file);
|
||||
if (!io) return NULL;
|
||||
return new filewrite_gtk(G_OUTPUT_STREAM(io));
|
||||
}
|
||||
|
||||
private:
|
||||
filewrite_gtk(GOutputStream* io) : io(io) {}
|
||||
|
||||
public:
|
||||
bool append(const uint8_t* data, size_t len)
|
||||
{
|
||||
return g_output_stream_write_all(io, data, len, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
~filewrite_gtk() { g_object_unref(io); }
|
||||
};
|
||||
|
||||
filewrite* filewrite::create(const char * filename) { return filewrite_gtk::create(filename); }
|
||||
|
||||
//struct mem ReadWholeFile(const char * filename)
|
||||
//{
|
||||
// GFile* file=g_file_new_for_commandline_arg(filename);
|
||||
// if (!file) return (struct mem){NULL, 0};
|
||||
// GFileInputStream* io=g_file_read(file, NULL, NULL);
|
||||
// if (!io)
|
||||
// {
|
||||
// g_object_unref(file);
|
||||
// return (struct mem){NULL, 0};
|
||||
// }
|
||||
// GFileInfo* info=g_file_input_stream_query_info(io, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL);
|
||||
// gsize size=g_file_info_get_size(info);
|
||||
// struct mem mem={(uint8_t*)malloc(size), size};
|
||||
// gsize actualsize;
|
||||
// bool success=g_input_stream_read_all(G_INPUT_STREAM(io), mem.ptr, size, &actualsize, NULL, NULL);
|
||||
// if (size!=actualsize) success=false;
|
||||
// g_input_stream_close(G_INPUT_STREAM(io), NULL, NULL);
|
||||
// g_object_unref(file);
|
||||
// g_object_unref(io);
|
||||
// g_object_unref(info);
|
||||
// if (!success)
|
||||
// {
|
||||
// free(mem.ptr);
|
||||
// return (struct mem){NULL, 0};
|
||||
// }
|
||||
// return mem;
|
||||
//}
|
||||
//
|
||||
//bool WriteWholeFile(const char * filename, struct mem data)
|
||||
//{
|
||||
// GFile* file=g_file_new_for_commandline_arg(filename);
|
||||
// if (!file) return false;
|
||||
// GFileOutputStream* io=g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
||||
// if (!io)
|
||||
// {
|
||||
// g_object_unref(file);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// bool success=g_output_stream_write_all(G_OUTPUT_STREAM(io), data.ptr, data.len, NULL, NULL, NULL);
|
||||
// g_output_stream_close(G_OUTPUT_STREAM(io), NULL, NULL);
|
||||
// g_object_unref(file);
|
||||
// return success;
|
||||
//}
|
||||
//
|
||||
//bool WriteWholeFileWithHeader(const char * filename, struct mem header, struct mem data)
|
||||
//{
|
||||
// GFile* file=g_file_new_for_commandline_arg(filename);
|
||||
// if (!file) return false;
|
||||
// GFileOutputStream* io=g_file_replace(file, NULL, false, G_FILE_CREATE_NONE, NULL, NULL);
|
||||
// if (!io)
|
||||
// {
|
||||
// g_object_unref(file);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// bool success=(g_output_stream_write_all(G_OUTPUT_STREAM(io), header.ptr, 512, NULL, NULL, NULL) &&
|
||||
// g_output_stream_write_all(G_OUTPUT_STREAM(io), data.ptr, data.len, NULL, NULL, NULL));
|
||||
// g_output_stream_close(G_OUTPUT_STREAM(io), NULL, NULL);
|
||||
// g_object_unref(file);
|
||||
// return success;
|
||||
//}
|
||||
//
|
||||
//void FreeFileMemory(struct mem mem)
|
||||
//{
|
||||
// free(mem.ptr);
|
||||
//}
|
||||
|
||||
|
||||
static bool canShowGUI;
|
||||
static GtkWidget* window;
|
||||
|
||||
struct {
|
||||
char signature[9];
|
||||
unsigned int lastPatchType;
|
||||
bool createFromAllFiles;
|
||||
bool openInEmulatorOnAssoc;
|
||||
bool autoSelectRom;
|
||||
gchar * emulator;
|
||||
} static state;
|
||||
#define cfgversion 5
|
||||
|
||||
static GtkWidget* windowBpsd;
|
||||
static GtkWidget* labelBpsd;
|
||||
static bool bpsdCancel;
|
||||
|
||||
void bpsdeltaCancel(GtkWindow* widget, gpointer user_data)
|
||||
{
|
||||
bpsdCancel=true;
|
||||
}
|
||||
|
||||
void bpsdeltaBegin()
|
||||
{
|
||||
bpsdCancel=false;
|
||||
windowBpsd=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
if (window)
|
||||
{
|
||||
gtk_window_set_modal(GTK_WINDOW(windowBpsd), true);
|
||||
gtk_window_set_transient_for(GTK_WINDOW(windowBpsd), GTK_WINDOW(window));
|
||||
}
|
||||
gtk_window_set_title(GTK_WINDOW(windowBpsd), flipsversion);
|
||||
|
||||
labelBpsd=gtk_label_new("Please wait... 99.9%");
|
||||
gtk_container_add(GTK_CONTAINER(windowBpsd), labelBpsd);
|
||||
GtkRequisition size;
|
||||
gtk_widget_get_preferred_size(labelBpsd, NULL, &size);
|
||||
gtk_label_set_text(GTK_LABEL(labelBpsd), "Please wait... 0.0%");
|
||||
gtk_widget_set_size_request(labelBpsd, size.width, size.height);
|
||||
gtk_window_set_resizable(GTK_WINDOW(windowBpsd), false);
|
||||
|
||||
gtk_misc_set_alignment(GTK_MISC(labelBpsd), 0.0f, 0.5f);
|
||||
|
||||
gtk_widget_show_all(windowBpsd);
|
||||
}
|
||||
|
||||
bool bpsdeltaProgress(void* userdata, size_t done, size_t total)
|
||||
{
|
||||
if (bpsdeltaGetProgress(done, total))
|
||||
{
|
||||
gtk_label_set_text(GTK_LABEL(labelBpsd), bpsdProgStr);
|
||||
}
|
||||
gtk_main_iteration_do(false);
|
||||
return !bpsdCancel;
|
||||
}
|
||||
|
||||
void bpsdeltaEnd()
|
||||
{
|
||||
if (!bpsdCancel) gtk_widget_destroy(windowBpsd);
|
||||
}
|
||||
|
||||
char * SelectRom(const char * defaultname, const char * title, bool isForSaving)
|
||||
{
|
||||
GtkWidget* dialog;
|
||||
if (!isForSaving)
|
||||
{
|
||||
dialog=gtk_file_chooser_dialog_new(title, GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
dialog=gtk_file_chooser_dialog_new(title, GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defaultname);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), true);
|
||||
}
|
||||
|
||||
GtkFileFilter* filterRom=gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterRom, "Most Common ROM Files");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.smc");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.sfc");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.nes");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.gb");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.gbc");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.gba");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.vb");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.sms");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.smd");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.ngp");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.n64");
|
||||
gtk_file_filter_add_pattern(filterRom, "*.z64");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterRom);
|
||||
|
||||
GtkFileFilter* filterAll=gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filterAll, "All files");
|
||||
gtk_file_filter_add_pattern(filterAll, "*");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterAll);
|
||||
|
||||
if (state.createFromAllFiles) gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filterAll);
|
||||
else gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filterRom);
|
||||
|
||||
char * ret=NULL;
|
||||
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
ret=gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
|
||||
}
|
||||
|
||||
GtkFileFilter* thisfilter=gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
|
||||
if (thisfilter==filterRom) state.createFromAllFiles=false;
|
||||
if (thisfilter==filterAll) state.createFromAllFiles=true;
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GSList * SelectPatches(bool allowMulti, bool demandLocal)
|
||||
{
|
||||
GtkWidget* dialog=gtk_file_chooser_dialog_new(allowMulti?"Select Patches to Use":"Select Patch to Use", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), allowMulti);
|
||||
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), demandLocal);
|
||||
|
||||
GtkFileFilter* filter;
|
||||
|
||||
filter=gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filter, "All supported patches (*.bps, *.ips)");
|
||||
gtk_file_filter_add_pattern(filter, "*.bps");
|
||||
gtk_file_filter_add_pattern(filter, "*.ips");
|
||||
gtk_file_filter_add_pattern(filter, "*.ups");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
//apparently the file chooser takes ownership of the filter. would be nice to document that in gtk_file_chooser_set_filter...
|
||||
|
||||
filter=gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filter, "All files");
|
||||
gtk_file_filter_add_pattern(filter, "*");
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
|
||||
if (gtk_dialog_run(GTK_DIALOG(dialog))!=GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
gtk_widget_destroy(dialog);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GSList * ret;
|
||||
if (demandLocal) ret=gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
|
||||
else ret=gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ShowMessage(struct errorinfo errinf)
|
||||
{
|
||||
GtkMessageType errorlevels[]={ GTK_MESSAGE_OTHER, GTK_MESSAGE_OTHER, GTK_MESSAGE_WARNING, GTK_MESSAGE_WARNING, GTK_MESSAGE_ERROR, GTK_MESSAGE_ERROR };
|
||||
GtkWidget* dialog=gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, errorlevels[errinf.level], GTK_BUTTONS_CLOSE, "%s",errinf.description);
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum worsterrorauto { ea_none, ea_warning, ea_invalid, ea_io_rom_write, ea_io_rom_read, ea_no_auto, ea_io_read_patch };
|
||||
|
||||
struct multiapplystateauto {
|
||||
enum worsterrorauto error;
|
||||
bool anySuccess;
|
||||
|
||||
const char * foundRom;
|
||||
bool canUseFoundRom;
|
||||
bool usingFoundRom;
|
||||
};
|
||||
|
||||
static void ApplyPatchMultiAutoSub(gpointer data, gpointer user_data)
|
||||
{
|
||||
#define max(a,b) ((a)>(b)?(a):(b))
|
||||
#define error(which) do { state->error=max(state->error, which); } while(0)
|
||||
gchar * patchpath=(gchar*)data;
|
||||
struct multiapplystateauto * state=(struct multiapplystateauto*)user_data;
|
||||
|
||||
file* patch = file::create(patchpath);
|
||||
if (!patch)
|
||||
{
|
||||
state->canUseFoundRom=false;
|
||||
error(ea_io_read_patch);
|
||||
return;
|
||||
}
|
||||
|
||||
bool possible;
|
||||
const char * rompath=FindRomForPatch(patch, &possible);
|
||||
if (state->usingFoundRom)
|
||||
{
|
||||
if (!rompath) rompath=state->foundRom;
|
||||
else goto cleanup;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!rompath)
|
||||
{
|
||||
if (possible) state->canUseFoundRom=false;
|
||||
error(ea_no_auto);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (!state->foundRom) state->foundRom=rompath;
|
||||
if (state->foundRom!=rompath) state->canUseFoundRom=false;
|
||||
|
||||
{
|
||||
const char * romext=GetExtension(rompath);
|
||||
gchar * outrompath=g_strndup(patchpath, strlen(patchpath)+strlen(romext)+1);
|
||||
strcpy(GetExtension(outrompath), romext);
|
||||
|
||||
struct errorinfo errinf=ApplyPatchMem(patch, rompath, true, outrompath, NULL, true);
|
||||
if (errinf.level==el_broken) error(ea_invalid);
|
||||
if (errinf.level==el_notthis) error(ea_no_auto);
|
||||
if (errinf.level==el_warning) error(ea_warning);
|
||||
if (errinf.level<el_notthis) state->anySuccess=true;
|
||||
else state->canUseFoundRom=false;
|
||||
g_free(outrompath);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
delete patch;
|
||||
#undef max
|
||||
#undef error
|
||||
}
|
||||
|
||||
static bool ApplyPatchMultiAuto(GSList * filenames)
|
||||
{
|
||||
struct multiapplystateauto state;
|
||||
state.error=ea_none;
|
||||
state.anySuccess=false;
|
||||
|
||||
state.foundRom=NULL;
|
||||
state.canUseFoundRom=true;
|
||||
state.usingFoundRom=false;
|
||||
|
||||
g_slist_foreach(filenames, ApplyPatchMultiAutoSub, &state);
|
||||
if (state.error==ea_no_auto && state.foundRom && state.canUseFoundRom)
|
||||
{
|
||||
state.usingFoundRom=true;
|
||||
state.error=ea_none;
|
||||
g_slist_foreach(filenames, ApplyPatchMultiAutoSub, &state);
|
||||
}
|
||||
|
||||
if (state.anySuccess)
|
||||
{
|
||||
struct errorinfo messages[8]={
|
||||
{ el_ok, "The patches were applied successfully!" },//ea_none
|
||||
{ el_warning, "The patches were applied, but one or more may be mangled or improperly created..." },//ea_warning
|
||||
{ el_warning, "Some patches were applied, but not all of the given patches are valid..." },//ea_invalid
|
||||
{ el_warning, "Some patches were applied, but not all of the desired ROMs could be created..." },//ea_rom_io_write
|
||||
{ el_warning, "Some patches were applied, but not all of the input ROMs could be read..." },//ea_io_rom_read
|
||||
{ el_warning, "Some patches were applied, but not all of the required input ROMs could be located..." },//ea_no_auto
|
||||
{ el_warning, "Some patches were applied, but not all of the given patches could be read..." },//ea_io_read_patch
|
||||
{ el_broken, NULL },//ea_no_found
|
||||
};
|
||||
ShowMessage(messages[state.error]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum worsterror { e_none, e_warning_notthis, e_warning, e_invalid_this, e_invalid, e_io_write, e_io_read, e_io_read_rom };
|
||||
|
||||
struct multiapplystate {
|
||||
const gchar * romext;
|
||||
struct mem rommem;
|
||||
bool anySuccess;
|
||||
bool removeHeaders;
|
||||
enum worsterror worsterror;
|
||||
};
|
||||
|
||||
void ApplyPatchMulti(gpointer data, gpointer user_data)
|
||||
{
|
||||
char * patchname=(char*)data;
|
||||
struct multiapplystate * state=(struct multiapplystate*)user_data;
|
||||
#define max(a,b) ((a)>(b)?(a):(b))
|
||||
#define error(which) do { state->worsterror=max(state->worsterror, which); } while(0)
|
||||
|
||||
file* patch = file::create(patchname);
|
||||
if (patch)
|
||||
{
|
||||
char * outromname=g_strndup(patchname, strlen(patchname)+strlen(state->romext)+1);
|
||||
char * outromext=GetExtension(outromname);
|
||||
strcpy(outromext, state->romext);
|
||||
|
||||
struct errorinfo errinf=ApplyPatchMem2(patch, state->rommem, state->removeHeaders, true, outromname, NULL);
|
||||
if (errinf.level==el_broken) error(e_invalid);
|
||||
if (errinf.level==el_notthis) error(e_invalid_this);
|
||||
if (errinf.level==el_warning) error(e_warning);
|
||||
if (errinf.level==el_unlikelythis) error(e_warning_notthis);
|
||||
if (errinf.level<el_notthis) state->anySuccess=true;
|
||||
|
||||
delete patch;
|
||||
g_free(outromname);
|
||||
}
|
||||
else error(e_io_read);
|
||||
g_free(data);
|
||||
#undef max
|
||||
#undef error
|
||||
}
|
||||
|
||||
void a_ApplyPatch(GtkButton* widget, gpointer user_data)
|
||||
{
|
||||
gchar * filename=(gchar*)user_data;
|
||||
GSList * filenames=NULL;
|
||||
if (!filename)
|
||||
{
|
||||
filenames=SelectPatches(true, false);
|
||||
if (!filenames) return;
|
||||
if (!filenames->next) filename=(gchar*)filenames->data;
|
||||
}
|
||||
if (filename)//do not change to else, this is set if the user picks only one file
|
||||
{
|
||||
struct errorinfo errinf;
|
||||
file* patchfile = file::create(filename);
|
||||
if (!patchfile)
|
||||
{
|
||||
errinf=(struct errorinfo){ el_broken, "Couldn't read input patch. What exactly are you doing?" };
|
||||
ShowMessage(errinf);
|
||||
return;
|
||||
}
|
||||
|
||||
char * inromname=NULL;
|
||||
if (state.autoSelectRom) inromname=g_strdup(FindRomForPatch(patchfile, NULL)); // g_strdup(NULL) is NULL
|
||||
if (!inromname) inromname=SelectRom(NULL, "Select File to Patch", false);
|
||||
if (!inromname) goto cleanup;
|
||||
|
||||
{
|
||||
char * patchbasename=GetBaseName(filename);
|
||||
const char * inromext=GetExtension(inromname);
|
||||
if (!inromext) inromext="";
|
||||
|
||||
char * outromname_d=g_strndup(patchbasename, strlen(patchbasename)+strlen(inromext)+1);
|
||||
char * ext=GetExtension(outromname_d);
|
||||
strcpy(ext, inromext);
|
||||
|
||||
char * outromname=SelectRom(outromname_d, "Select Output File", true);
|
||||
if (outromname)
|
||||
{
|
||||
struct errorinfo errinf=ApplyPatchMem(patchfile, inromname, true, outromname, NULL, state.autoSelectRom);
|
||||
ShowMessage(errinf);
|
||||
}
|
||||
g_free(inromname);
|
||||
g_free(outromname_d);
|
||||
g_free(outromname);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
delete patchfile;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state.autoSelectRom)
|
||||
{
|
||||
if (ApplyPatchMultiAuto(filenames))
|
||||
{
|
||||
g_slist_free_full(filenames, g_free);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct multiapplystate state;
|
||||
char * inromname=SelectRom(NULL, "Select Base File", false);
|
||||
state.romext=GetExtension(inromname);
|
||||
if (!*state.romext) state.romext=".sfc";
|
||||
state.rommem=ReadWholeFile(inromname);
|
||||
state.removeHeaders=shouldRemoveHeader(inromname, state.rommem.len);
|
||||
state.worsterror=e_none;
|
||||
state.anySuccess=false;
|
||||
g_slist_foreach(filenames, ApplyPatchMulti, &state);
|
||||
g_free(inromname);
|
||||
FreeFileMemory(state.rommem);
|
||||
struct errorinfo errormessages[2][8]={
|
||||
{
|
||||
//no error-free
|
||||
{ el_ok, NULL },//e_none
|
||||
{ el_warning, NULL},//e_warning_notthis
|
||||
{ el_warning, NULL},//e_warning
|
||||
{ el_broken, "None of these are valid patches for this ROM!" },//e_invalid_this
|
||||
{ el_broken, "None of these are valid patches!" },//e_invalid
|
||||
{ el_broken, "Couldn't write any ROMs. Are you on a read-only medium?" },//e_io_write
|
||||
{ el_broken, "Couldn't read any patches. What exactly are you doing?" },//e_io_read
|
||||
{ el_broken, "Couldn't read the input ROM. What exactly are you doing?" },//e_io_read_rom
|
||||
},{
|
||||
//at least one error-free
|
||||
{ el_ok, "The patches were applied successfully!" },//e_none
|
||||
{ el_warning, "The patches were applied, but one or more is unlikely to be intended for this ROM..." },//e_warning_notthis
|
||||
{ el_warning, "The patches were applied, but one or more may be mangled or improperly created..." },//e_warning
|
||||
{ el_warning, "Some patches were applied, but not all of the given patches are valid for this ROM..." },//e_invalid_this
|
||||
{ el_warning, "Some patches were applied, but not all of the given patches are valid..." },//e_invalid
|
||||
{ el_warning, "Some patches were applied, but not all of the desired ROMs could be created..." },//e_io_write
|
||||
{ el_warning, "Some patches were applied, but not all of the given patches could be read..." },//e_io_read
|
||||
{ el_broken, NULL,//e_io_read_rom
|
||||
},
|
||||
}};
|
||||
ShowMessage(errormessages[state.anySuccess][state.worsterror]);
|
||||
}
|
||||
g_slist_free(filenames);
|
||||
}
|
||||
|
||||
void a_CreatePatch(GtkButton* widget, gpointer user_data)
|
||||
{
|
||||
char * inrom=NULL;
|
||||
char * outrom=NULL;
|
||||
char * patchname=NULL;
|
||||
|
||||
inrom=SelectRom(NULL, "Select ORIGINAL UNMODIFIED File to Use", false);
|
||||
if (!inrom) goto cleanup;
|
||||
outrom=SelectRom(NULL, "Select NEW MODIFIED File to Use", false);
|
||||
if (!outrom) goto cleanup;
|
||||
if (!strcmp(inrom, outrom))
|
||||
{
|
||||
ShowMessage((struct errorinfo){ el_broken, "That's the same file! You should really use two different files." });
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
struct {
|
||||
const char * filter;
|
||||
const char * description;
|
||||
} static const typeinfo[]={
|
||||
{ "*.bps", "BPS Patch File" },
|
||||
{ "*.ips", "IPS Patch File" },
|
||||
};
|
||||
static const size_t numtypeinfo = sizeof(typeinfo)/sizeof(*typeinfo);
|
||||
|
||||
{
|
||||
char * defpatchname=g_strndup(outrom, strlen(outrom)+4+1);
|
||||
char * ext=GetExtension(defpatchname);
|
||||
strcpy(ext, typeinfo[state.lastPatchType-1].filter+1);
|
||||
|
||||
GtkWidget* dialog=gtk_file_chooser_dialog_new("Select File to Save As", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defpatchname);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), true);
|
||||
|
||||
GtkFileFilter* filters[numtypeinfo];
|
||||
for (size_t i=0;i<numtypeinfo;i++)
|
||||
{
|
||||
GtkFileFilter* filter=gtk_file_filter_new();
|
||||
filters[i]=filter;
|
||||
gtk_file_filter_set_name(filter, typeinfo[i].description);
|
||||
gtk_file_filter_add_pattern(filter, typeinfo[i].filter);
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
}
|
||||
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filters[state.lastPatchType-1]);
|
||||
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
patchname=gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
|
||||
}
|
||||
|
||||
GtkFileFilter* filter=gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog));
|
||||
for (size_t i=0;i<numtypeinfo;i++)
|
||||
{
|
||||
if (filter==filters[i])
|
||||
{
|
||||
if (state.lastPatchType!=i && !strcmp(GetExtension(patchname), typeinfo[state.lastPatchType-1].filter+1))
|
||||
{
|
||||
strcpy(GetExtension(patchname), typeinfo[i].filter+1);
|
||||
}
|
||||
state.lastPatchType=i+1;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
if (!patchname) goto cleanup;
|
||||
|
||||
bpsdCancel=false;
|
||||
struct errorinfo errinf;
|
||||
errinf=CreatePatch(inrom, outrom, (patchtype)state.lastPatchType, NULL, patchname);
|
||||
if (!bpsdCancel) ShowMessage(errinf);
|
||||
|
||||
cleanup:
|
||||
g_free(inrom);
|
||||
g_free(outrom);
|
||||
g_free(patchname);
|
||||
}
|
||||
|
||||
void a_SetEmulator(GtkButton* widget, gpointer user_data);
|
||||
void a_ApplyRun(GtkButton* widget, gpointer user_data)
|
||||
{
|
||||
gchar * patchname=(gchar*)user_data;
|
||||
if (!patchname)
|
||||
{
|
||||
GSList * patchnames=SelectPatches(false, true);
|
||||
if (!patchnames) return;
|
||||
patchname=(gchar*)patchnames->data;
|
||||
g_slist_free(patchnames);
|
||||
}
|
||||
|
||||
file* patchfile = file::create(patchname);
|
||||
gchar * romname=NULL;
|
||||
{
|
||||
if (!patchfile)
|
||||
{
|
||||
ShowMessage((struct errorinfo){ el_broken, "Couldn't read input patch. What exactly are you doing?" });
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (state.autoSelectRom) romname=g_strdup(FindRomForPatch(patchfile, NULL)); // g_strdup(NULL) is NULL
|
||||
if (!romname) romname=SelectRom(NULL, "Select Base File", false);
|
||||
if (!romname) goto cleanup;
|
||||
|
||||
if (!state.emulator) a_SetEmulator(NULL, NULL);
|
||||
if (!state.emulator) goto cleanup;
|
||||
|
||||
//gchar * outromname;
|
||||
//gint fd=g_file_open_tmp("flipsXXXXXX.smc", &outromname, NULL);
|
||||
|
||||
gchar * outromname_rel=g_strndup(patchname, strlen(patchname)+4+1);
|
||||
strcpy(GetExtension(outromname_rel), GetExtension(romname));
|
||||
|
||||
GFile* outrom_file=g_file_new_for_commandline_arg(outromname_rel);
|
||||
g_free(outromname_rel);
|
||||
gchar * outromname;
|
||||
if (g_file_is_native(outrom_file)) outromname=g_file_get_path(outrom_file);
|
||||
else outromname=g_file_get_uri(outrom_file);
|
||||
g_object_unref(outrom_file);
|
||||
|
||||
struct errorinfo errinf=ApplyPatchMem(patchfile, romname, true, outromname, NULL, state.autoSelectRom);
|
||||
if (errinf.level!=el_ok) ShowMessage(errinf);
|
||||
if (errinf.level>=el_notthis) goto cleanup;
|
||||
|
||||
gchar * patchend=GetBaseName(patchname);
|
||||
*patchend='\0';
|
||||
|
||||
gchar * argv[3];
|
||||
argv[0]=state.emulator;
|
||||
argv[1]=outromname;
|
||||
argv[2]=NULL;
|
||||
|
||||
GPid pid;
|
||||
GError* error=NULL;
|
||||
if (!g_spawn_async(patchname, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, &error))
|
||||
{
|
||||
//g_unlink(tempname);//apparently this one isn't in the headers.
|
||||
ShowMessage((struct errorinfo){ el_broken, error->message });
|
||||
g_error_free(error);
|
||||
}
|
||||
else g_spawn_close_pid(pid);
|
||||
g_free(outromname);
|
||||
//close(fd);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
delete patchfile;
|
||||
g_free(patchname);
|
||||
g_free(romname);
|
||||
}
|
||||
|
||||
void a_ShowSettings(GtkButton* widget, gpointer user_data)
|
||||
{
|
||||
//used mnemonics:
|
||||
//E - Select Emulator
|
||||
//M - Create ROM
|
||||
//U - Run in Emulator
|
||||
//A - Enable automatic ROM selector
|
||||
|
||||
GtkWidget* settingswindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(settingswindow), flipsversion);
|
||||
gtk_window_set_resizable(GTK_WINDOW(settingswindow), false);
|
||||
gtk_window_set_modal(GTK_WINDOW(settingswindow), true);
|
||||
gtk_window_set_transient_for(GTK_WINDOW(settingswindow), GTK_WINDOW(window));
|
||||
g_signal_connect(settingswindow, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
||||
|
||||
GtkGrid* grid=GTK_GRID(gtk_grid_new());
|
||||
gtk_grid_set_row_spacing(grid, 3);
|
||||
|
||||
GtkWidget* button=gtk_button_new_with_mnemonic("Select _Emulator");
|
||||
g_signal_connect(button, "clicked", G_CALLBACK(a_SetEmulator), NULL);
|
||||
gtk_grid_attach(grid, button, 0,0, 1,1);
|
||||
|
||||
GtkWidget* text=gtk_label_new("When opening through associations:");
|
||||
gtk_grid_attach(grid, text, 0,1, 1,1);
|
||||
|
||||
GtkGrid* radioGrid=GTK_GRID(gtk_grid_new());
|
||||
gtk_grid_set_column_homogeneous(radioGrid, true);
|
||||
GtkWidget* emuAssoc;
|
||||
emuAssoc=gtk_radio_button_new_with_mnemonic(NULL, "Create RO_M");
|
||||
gtk_grid_attach(radioGrid, emuAssoc, 0,0, 1,1);
|
||||
emuAssoc=gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(emuAssoc), "R_un in Emulator");
|
||||
gtk_grid_attach(radioGrid, emuAssoc, 1,0, 1,1);
|
||||
g_object_ref(emuAssoc);//otherwise it, and its value, gets eaten when I close the window, before I can save its value anywhere
|
||||
if (state.openInEmulatorOnAssoc) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(emuAssoc), true);
|
||||
gtk_grid_attach(grid, GTK_WIDGET(radioGrid), 0,2, 1,1);
|
||||
|
||||
GtkWidget* autoRom;
|
||||
autoRom=gtk_check_button_new_with_mnemonic("Enable _automatic ROM selector");
|
||||
if (state.autoSelectRom) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoRom), true);
|
||||
g_object_ref(autoRom);
|
||||
gtk_grid_attach(grid, autoRom, 0,3, 1,1);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(settingswindow), GTK_WIDGET(grid));
|
||||
|
||||
gtk_widget_show_all(settingswindow);
|
||||
gtk_main();
|
||||
|
||||
state.openInEmulatorOnAssoc=(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(emuAssoc)));
|
||||
g_object_unref(emuAssoc);
|
||||
state.autoSelectRom=(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(autoRom)));
|
||||
g_object_unref(autoRom);
|
||||
}
|
||||
|
||||
gboolean filterExecOnly(const GtkFileFilterInfo* filter_info, gpointer data)
|
||||
{
|
||||
GFile* file=g_file_new_for_uri(filter_info->uri);
|
||||
GFileInfo* info=g_file_query_info(file, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||
bool ret=g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);
|
||||
g_object_unref(file);
|
||||
g_object_unref(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void a_SetEmulator(GtkButton* widget, gpointer user_data)
|
||||
{
|
||||
GtkWidget* dialog=gtk_file_chooser_dialog_new("Select Emulator to Use", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
|
||||
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), true);
|
||||
|
||||
GtkFileFilter* filter=gtk_file_filter_new();
|
||||
gtk_file_filter_set_name(filter, "Executable files");
|
||||
gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_URI, filterExecOnly, NULL, NULL);
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
||||
|
||||
if (gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
g_free(state.emulator);
|
||||
state.emulator=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
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
|
||||
966
flips-w32.cpp
Normal file
966
flips-w32.cpp
Normal file
|
|
@ -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) anySuccess=true;
|
||||
else canUseFoundRom=false;
|
||||
}
|
||||
multi_auto_next:
|
||||
delete patch;
|
||||
thisPatchName=wcschr(thisPatchName, '\0')+1;
|
||||
}
|
||||
if (anySuccess)
|
||||
{
|
||||
if (worsterror==e_no_auto && foundRom && canUseFoundRom && !usingFoundRom)
|
||||
{
|
||||
usingFoundRom=true;
|
||||
goto redo;
|
||||
}
|
||||
int severity=(worsterror==e_none ? el_ok : el_warning);
|
||||
MessageBoxA(hwndMain, messages[worsterror], flipsversion, mboxtype[severity]);
|
||||
return severity;
|
||||
}
|
||||
}
|
||||
WCHAR inromname[MAX_PATH];
|
||||
inromname[0]='\0';
|
||||
if (!SelectRom(inromname, TEXT("Select Base File"), false)) return 0;
|
||||
WCHAR thisFileNameWithPath[MAX_PATH];
|
||||
wcscpy(thisFileNameWithPath, patchnames);
|
||||
LPWSTR thisFileName=wcschr(thisFileNameWithPath, '\0');
|
||||
*thisFileName='\\';
|
||||
thisFileName++;
|
||||
LPWSTR thisPatchName=wcschr(patchnames, '\0')+1;
|
||||
LPCWSTR romExtension=GetExtension(inromname);
|
||||
struct mem inrom=ReadWholeFile(inromname);
|
||||
bool anySuccess=false;
|
||||
enum { e_none, e_notice, e_warning, e_invalid_this, e_invalid, e_io_write, e_io_read, e_io_read_rom } worsterror=e_none;
|
||||
enum errorlevel severity[2][8]={
|
||||
{ el_ok, el_ok, el_warning,el_broken, el_broken, el_broken, el_broken, el_broken },
|
||||
{ el_ok, el_ok, el_warning,el_warning, el_warning, el_warning,el_warning,el_broken },
|
||||
};
|
||||
LPCSTR messages[2][8]={
|
||||
{
|
||||
//no error-free
|
||||
NULL,//e_none
|
||||
NULL,//e_notice
|
||||
NULL,//e_warning
|
||||
"None of these are valid patches for this ROM!",//e_invalid_this
|
||||
"None of these are valid patches!",//e_invalid
|
||||
"Couldn't write any ROMs. Are you on a read-only medium?",//e_io_write
|
||||
"Couldn't read any patches. What exactly are you doing?",//e_io_read
|
||||
"Couldn't read the input ROM. What exactly are you doing?",//e_io_read_rom
|
||||
},{
|
||||
//at least one error-free
|
||||
"The patches were applied successfully!",//e_none
|
||||
"The patches were applied successfully!",//e_notice
|
||||
"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 for this ROM...",//e_invalid_this
|
||||
"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_io_write
|
||||
"Some patches were applied, but not all of the given patches could be read...",//e_io_read
|
||||
NULL,//e_io_read_rom
|
||||
},
|
||||
};
|
||||
if (inrom.ptr)
|
||||
{
|
||||
bool removeheaders=shouldRemoveHeader(inromname, inrom.len);
|
||||
while (*thisPatchName)
|
||||
{
|
||||
wcscpy(thisFileName, thisPatchName);
|
||||
file* patch = file::create(thisFileNameWithPath);
|
||||
if (patch)
|
||||
{
|
||||
LPWSTR patchExtension=GetExtension(thisFileName);
|
||||
wcscpy(patchExtension, romExtension);
|
||||
struct errorinfo errinf=ApplyPatchMem2(patch, inrom, removeheaders, true, thisFileNameWithPath, NULL);
|
||||
|
||||
if (errinf.level==el_broken) worsterror=max(worsterror, e_invalid);
|
||||
if (errinf.level==el_notthis) worsterror=max(worsterror, e_invalid_this);
|
||||
if (errinf.level==el_warning) worsterror=max(worsterror, e_warning);
|
||||
if (errinf.level<el_notthis)
|
||||
{
|
||||
if (state.enableAutoRomSelector && !anySuccess) AddToRomList(patch, inromname);
|
||||
anySuccess=true;
|
||||
}
|
||||
delete patch;
|
||||
}
|
||||
else worsterror=max(worsterror, e_io_read);
|
||||
thisPatchName=wcschr(thisPatchName, '\0')+1;
|
||||
}
|
||||
}
|
||||
else worsterror=e_io_read_rom;
|
||||
FreeFileMemory(inrom);
|
||||
MessageBoxA(hwndMain, messages[anySuccess][worsterror], flipsversion, mboxtype[severity[anySuccess][worsterror]]);
|
||||
return severity[anySuccess][worsterror];
|
||||
#undef max
|
||||
}
|
||||
}
|
||||
|
||||
void a_CreatePatch()
|
||||
{
|
||||
//pick roms
|
||||
WCHAR romnames[2][MAX_PATH];
|
||||
WCHAR patchname[MAX_PATH];
|
||||
|
||||
romnames[0][0]='\0';
|
||||
romnames[1][0]='\0';
|
||||
if (!SelectRom(romnames[0], TEXT("Select ORIGINAL UNMODIFIED File to Use"), false)) return;
|
||||
if (!SelectRom(romnames[1], TEXT("Select NEW MODIFIED File to Use"), false)) return;
|
||||
|
||||
if (!wcsicmp(romnames[0], romnames[1]))
|
||||
{
|
||||
MessageBoxA(hwndMain, "That's the same file! You should really use two different files.", flipsversion, mboxtype[el_broken]);
|
||||
return;
|
||||
}
|
||||
|
||||
//pick patch name and type
|
||||
wcscpy(patchname, romnames[1]);
|
||||
LPWSTR extension=GetExtension(patchname);
|
||||
if (extension) *extension='\0';//wcscpy(extension+1, extensions[state.lastPatchType]);
|
||||
OPENFILENAME ofn;
|
||||
ZeroMemory(&ofn, sizeof(ofn));
|
||||
ofn.lStructSize=sizeof(ofn);
|
||||
ofn.hwndOwner=hwndMain;
|
||||
ofn.lpstrFilter =
|
||||
TEXT("BPS Patch File (*.bps)\0*.bps\0")
|
||||
//TEXT("BPS Patch File (Favor Creation Speed) (*.bps)\0*.bps\0")
|
||||
TEXT("IPS Patch File (*.ips)\0*.ips\0");
|
||||
ofn.lpstrFile=patchname;
|
||||
ofn.nMaxFile=MAX_PATH;
|
||||
ofn.nFilterIndex=state.lastPatchType;
|
||||
ofn.lpstrTitle=TEXT("Select File to Save As");
|
||||
ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT;
|
||||
ofn.lpstrDefExt=patchextensions[state.lastPatchType];
|
||||
if (!GetSaveFileName(&ofn))
|
||||
{
|
||||
state.lastPatchType=(enum patchtype)ofn.nFilterIndex;
|
||||
return;
|
||||
}
|
||||
state.lastPatchType=(enum patchtype)ofn.nFilterIndex;
|
||||
|
||||
bpsdCancel=false;
|
||||
struct errorinfo errinf=CreatePatch(romnames[0], romnames[1], (enum patchtype)ofn.nFilterIndex, NULL, patchname);
|
||||
if (!bpsdCancel) MessageBoxA(hwndMain, errinf.description, flipsversion, mboxtype[errinf.level]);
|
||||
}
|
||||
|
||||
bool a_SetEmulator()
|
||||
{
|
||||
WCHAR newemupath[MAX_PATH];
|
||||
*newemupath='\0';
|
||||
OPENFILENAME ofn;
|
||||
ZeroMemory(&ofn, sizeof(ofn));
|
||||
ofn.lStructSize=sizeof(ofn);
|
||||
ofn.hwndOwner=hwndMain;
|
||||
ofn.lpstrFilter=TEXT("Emulator Files (*.exe)\0*.exe\0All files (*.*)\0*.*\0");
|
||||
ofn.lpstrFile=newemupath;
|
||||
ofn.nMaxFile=MAX_PATH;
|
||||
ofn.lpstrTitle=TEXT("Select Emulator to Use");
|
||||
ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
|
||||
ofn.lpstrDefExt=TEXT("exe");
|
||||
if (!GetOpenFileName(&ofn)) return false;
|
||||
set_st_emulator(newemupath);
|
||||
return true;
|
||||
}
|
||||
|
||||
int a_ApplyRun(LPCWSTR clipatchname)
|
||||
{
|
||||
struct mem rommem={NULL,0};
|
||||
struct mem patchedmem={NULL,0};
|
||||
|
||||
WCHAR patchpath[MAX_PATH];
|
||||
*patchpath='\0';
|
||||
if (clipatchname)
|
||||
{
|
||||
wcscpy(patchpath, clipatchname);
|
||||
}
|
||||
else
|
||||
{
|
||||
OPENFILENAME ofn;
|
||||
ZeroMemory(&ofn, sizeof(ofn));
|
||||
ofn.lStructSize=sizeof(ofn);
|
||||
ofn.hwndOwner=hwndMain;
|
||||
ofn.lpstrFilter=TEXT("All supported patches (*.bps, *.ips)\0*.bps;*.ips;*.ups\0All files (*.*)\0*.*\0");
|
||||
ofn.lpstrFile=patchpath;
|
||||
ofn.nMaxFile=MAX_PATH;
|
||||
ofn.lpstrTitle=TEXT("Select Patch to Use");
|
||||
ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
|
||||
ofn.lpstrDefExt=TEXT("bps");
|
||||
if (!GetOpenFileName(&ofn)) return 0;
|
||||
}
|
||||
|
||||
struct errorinfo errinf;
|
||||
file* patch = file::create(patchpath);
|
||||
if (!patch)
|
||||
{
|
||||
errinf=error(el_broken, "Couldn't read input patch. What exactly are you doing?");
|
||||
goto error;
|
||||
}
|
||||
|
||||
LPCWSTR romname;
|
||||
romname=NULL;
|
||||
if (state.enableAutoRomSelector) romname=FindRomForPatch(patch, NULL);
|
||||
WCHAR romname_base[MAX_PATH];
|
||||
if (!romname)
|
||||
{
|
||||
romname_base[0]='\0';
|
||||
if (!SelectRom(romname_base, TEXT("Select Base File"), false))
|
||||
{
|
||||
delete patch;
|
||||
return 0;
|
||||
}
|
||||
romname=romname_base;
|
||||
}
|
||||
|
||||
if (!*st_emulator && !a_SetEmulator())
|
||||
{
|
||||
delete patch;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//WCHAR tempfilepath[MAX_PATH];
|
||||
//WCHAR tempfilename[MAX_PATH];
|
||||
//if (!GetTempPath(MAX_PATH, tempfilepath)) wcscpy(tempfilepath, TEXT("."));
|
||||
//if (!GetTempFileName(tempfilepath, TEXT("rom"), 0, tempfilename)) wcscpy(tempfilename, TEXT("temprom.tmp"));
|
||||
|
||||
WCHAR outfilename_rel[MAX_PATH];
|
||||
wcscpy(outfilename_rel, patchpath);
|
||||
wcscpy(GetExtension(outfilename_rel), GetExtension(romname));
|
||||
WCHAR outfilename[MAX_PATH];
|
||||
GetFullPathName(outfilename_rel, MAX_PATH, outfilename, NULL);
|
||||
|
||||
errinf=ApplyPatchMem(patch, romname, true, outfilename, NULL, state.enableAutoRomSelector);
|
||||
error:
|
||||
|
||||
if (errinf.level!=el_ok) MessageBoxA(hwndMain, errinf.description, flipsversion, mboxtype[errinf.level]);
|
||||
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
|
||||
1
flips.Manifest
Normal file
1
flips.Manifest
Normal file
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/></dependentAssembly></dependency></assembly>
|
||||
813
flips.cpp
Normal file
813
flips.cpp
Normal file
|
|
@ -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;i<checkmap_len[type];i++)
|
||||
{
|
||||
if (!memcmp(checkmap[type][i].sum, sum, checkmap_sum_size[type]))
|
||||
{
|
||||
return checkmap[type][i].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void AddRomForSum(int type, void* sum, LPCWSTR filename)
|
||||
{
|
||||
//printf("AD.CRC=%.8X:%ls\n",*(uint32_t*)sum,filename);
|
||||
if (FindRomForSum(type, sum)) return;
|
||||
|
||||
int ch_pos=(checkmap_len[type]++);
|
||||
|
||||
if (!(ch_pos&(ch_pos+1)))
|
||||
{
|
||||
checkmap[type]=(struct checkmap*)realloc(checkmap[type], sizeof(struct checkmap)*((ch_pos+1)*2));
|
||||
}
|
||||
|
||||
struct checkmap* item=&checkmap[type][ch_pos];
|
||||
item->sum=(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<ch_last;type++)
|
||||
{
|
||||
out.len+=sizeof(uint32_t);
|
||||
for (uint32_t i=0;i<checkmap_len[type];i++)
|
||||
{
|
||||
out.len+=sizeof(uint8_t);
|
||||
out.len+=sizeof(uint16_t);
|
||||
out.len+=checkmap_sum_size[type];
|
||||
out.len+=sizeof(WCHAR)*wcslen(checkmap[type][i].name);
|
||||
}
|
||||
}
|
||||
out.ptr=(uint8_t*)malloc(out.len);
|
||||
uint8_t* data=out.ptr;
|
||||
for (unsigned int type=0;type<ch_last;type++)
|
||||
{
|
||||
#define write(ptr, size) \
|
||||
memcpy(data, ptr, size); \
|
||||
data+=size
|
||||
#define write_obj(obj) write(&obj, sizeof(obj))
|
||||
write_obj(checkmap_len[type]);
|
||||
for (uint32_t i=0;i<checkmap_len[type];i++)
|
||||
{
|
||||
write_obj(checkmap_sum_size[type]);
|
||||
uint16_t len=sizeof(WCHAR)*wcslen(checkmap[type][i].name);
|
||||
write_obj(len);
|
||||
|
||||
write(checkmap[type][i].sum, checkmap_sum_size[type]);
|
||||
write(checkmap[type][i].name, len);
|
||||
}
|
||||
#undef write
|
||||
#undef write_obj
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void FreeRomList(struct mem data)
|
||||
{
|
||||
free(data.ptr);
|
||||
}
|
||||
|
||||
void SetRomList(struct mem data)
|
||||
{
|
||||
for (int type=0;type<ch_last;type++)
|
||||
{
|
||||
#define read(target, bytes) \
|
||||
if (bytes > 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;type<ch_last;type++)
|
||||
{
|
||||
for (unsigned int i=0;i<checkmap_len[type];i++)
|
||||
{
|
||||
if (!wcscmp(checkmap[type][i].name, path))
|
||||
{
|
||||
free(checkmap[type][i].name);
|
||||
free(checkmap[type][i].sum);
|
||||
memmove(&checkmap[type][i], &checkmap[type][i+1], sizeof(struct checkmap)*(checkmap_len[type]-1 - i));
|
||||
i--;
|
||||
checkmap_len[type]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct errorinfo error(errorlevel level, const char * text)
|
||||
{
|
||||
struct errorinfo errinf = { level, text };
|
||||
return errinf;
|
||||
}
|
||||
|
||||
struct errorinfo ApplyPatchMem2(file* patch, struct mem inrom, bool verifyinput, bool removeheader,
|
||||
LPCWSTR outromname, struct manifestinfo * manifestinfo)
|
||||
{
|
||||
struct mem patchmem = patch->read(); // 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<el_notthis)
|
||||
{
|
||||
if (!WriteWholeFileWithHeader(outromname, inrom, outrom)) errinf=error(el_broken, "Couldn't write ROM. Are you on a read-only medium?");
|
||||
}
|
||||
}
|
||||
else if (errinf.level<el_notthis)
|
||||
{
|
||||
if (!WriteWholeFile(outromname, outrom)) errinf=error(el_broken, "Couldn't write ROM. Are you on a read-only medium?");
|
||||
}
|
||||
free(outrom.ptr);
|
||||
free(patchmem.ptr);
|
||||
|
||||
if (errinf.level==el_notthis && removeheader)
|
||||
{
|
||||
errinf=ApplyPatchMem2(patch, inrom, verifyinput, false, outromname, manifestinfo);
|
||||
if (errinf.level==el_ok)
|
||||
{
|
||||
errinf=error(el_warning, "The patch was applied, but it was created from a headered ROM, which may not work for everyone.");
|
||||
}
|
||||
}
|
||||
return errinf;
|
||||
}
|
||||
|
||||
bool shouldRemoveHeader(LPCWSTR romname, size_t romlen)
|
||||
{
|
||||
LPWSTR romext=GetExtension(romname);
|
||||
return ((romlen&0x7FFF)==512 &&
|
||||
(!wcsicmp(romext, TEXT(".smc")) || !wcsicmp(romext, TEXT(".sfc"))) &&
|
||||
!forceKeepHeader);
|
||||
}
|
||||
|
||||
struct errorinfo ApplyPatchMem(file* patch, LPCWSTR inromname, bool verifyinput,
|
||||
LPCWSTR outromname, struct manifestinfo * manifestinfo, bool update_rom_list)
|
||||
{
|
||||
struct mem inrom=ReadWholeFile(inromname);
|
||||
if (!inrom.ptr)
|
||||
{
|
||||
if (update_rom_list) DeleteRomFromList(inromname);
|
||||
return error(el_broken, "Couldn't read ROM. What exactly are you doing?");
|
||||
}
|
||||
struct errorinfo errinf=ApplyPatchMem2(patch, inrom, verifyinput, shouldRemoveHeader(inromname, inrom.len), outromname, manifestinfo);
|
||||
if (update_rom_list && errinf.level==el_ok) AddToRomList(patch, inromname);
|
||||
FreeFileMemory(inrom);
|
||||
return errinf;
|
||||
}
|
||||
|
||||
struct errorinfo ApplyPatch(LPCWSTR patchname, LPCWSTR inromname, bool verifyinput,
|
||||
LPCWSTR outromname, struct manifestinfo * manifestinfo, bool update_rom_list)
|
||||
{
|
||||
file* patch = file::create(patchname);
|
||||
if (!patch)
|
||||
{
|
||||
return error(el_broken, "Couldn't read input patch. What exactly are you doing?");
|
||||
}
|
||||
struct errorinfo errinf=ApplyPatchMem(patch, inromname, verifyinput, outromname, manifestinfo, update_rom_list);
|
||||
delete patch;
|
||||
return errinf;
|
||||
}
|
||||
|
||||
|
||||
char bpsdProgStr[24];
|
||||
int bpsdLastPromille=-1;
|
||||
|
||||
bool bpsdeltaGetProgress(size_t done, size_t total)
|
||||
{
|
||||
if (total<1000) total=1000;//avoid div by zero
|
||||
int promille=done/(total/1000);//don't set this to done*1000/total, it'd just give overflows on huge stuff. 100% is handled later
|
||||
if (promille==bpsdLastPromille) return false;
|
||||
bpsdLastPromille=promille;
|
||||
if (promille>=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<el_notthis)
|
||||
{
|
||||
if (!WriteWholeFile(patchname, patch)) errinf=error(el_broken, "Couldn't write ROM. Are you on a read-only medium?");
|
||||
}
|
||||
if (patch.ptr) free(patch.ptr);
|
||||
return errinf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void usage()
|
||||
{
|
||||
ClaimConsole();
|
||||
puts(
|
||||
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
"usage:\n"
|
||||
" "
|
||||
#ifndef FLIPS_CLI
|
||||
"flips\n"
|
||||
"or flips patch.ips\n"
|
||||
"or "
|
||||
#endif
|
||||
"flips [--apply] [--exact] patch.bps rom.smc [outrom.smc]\n"
|
||||
"or flips [--create] [--exact] [--ips | --bps | --bps-delta] clean.smc\n"
|
||||
" hack.smc [patch.bps]\n"
|
||||
#ifndef FLIPS_CLI
|
||||
"(for scripting, only the latter two are sensible)\n"
|
||||
#endif
|
||||
"(patch.ips is valid in all cases patch.bps is)\n"
|
||||
"\n"
|
||||
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
"options:\n"
|
||||
"-a --apply: apply patch (default if given two arguments)\n"
|
||||
"-c --create: create patch (default if given three arguments)\n"
|
||||
"-i --ips, -b -B --bps --bps-delta, --bps-linear, --bps-delta-moremem:\n"
|
||||
" create this patch format instead of guessing based on file extension\n"
|
||||
" ignored when applying\n"
|
||||
" bps formats:\n"
|
||||
" delta is the recommended one; it's a good balance between creation time and\n"
|
||||
" patch size\n"
|
||||
" -b and -B both refer to this, for backwards compatibility reasons\n"
|
||||
" delta-moremem is usually slightly faster than delta, but uses about twice\n"
|
||||
" as much memory; it gives identical patches to delta\n"
|
||||
" linear is the fastest, but tends to give pretty big patches\n"
|
||||
"--exact: do not remove SMC headers when applying or creating a BPS patch\n"
|
||||
" (ignored for IPS)\n"
|
||||
"--ignore-checksum: accept checksum mismatches when applying a BPS patch\n"
|
||||
"-m or --manifest: emit or insert a manifest file as romname.bml\n"
|
||||
" (valid only for BPS)\n"
|
||||
"-mfilename or --manifest=filename: emit or insert a manifest file exactly here\n"
|
||||
"-h -? --help: show this information\n"
|
||||
"-v --version: show application version\n"
|
||||
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
int flipsmain(int argc, WCHAR * argv[])
|
||||
{
|
||||
enum patchtype patchtype=ty_null;
|
||||
enum { a_default, a_apply_filepicker, a_apply_given, a_create } action=a_default;
|
||||
int numargs=0;
|
||||
LPCWSTR arg[3]={NULL,NULL,NULL};
|
||||
bool hasFlags=false;
|
||||
|
||||
bool ignoreChecksum=false;
|
||||
|
||||
struct manifestinfo manifestinfo={false, false, NULL};
|
||||
// {
|
||||
// bool use;
|
||||
// bool required;
|
||||
// LPCWSTR name;
|
||||
// bool success;
|
||||
//};
|
||||
for (int i=1;i<argc;i++)
|
||||
{
|
||||
if (argv[i][0]=='-')
|
||||
{
|
||||
hasFlags=true;
|
||||
if(0);
|
||||
else if (!wcscmp(argv[i], TEXT("--apply")) || !wcscmp(argv[i], TEXT("-a")))
|
||||
{
|
||||
if (action==a_default) action=a_apply_given;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--create")) || !wcscmp(argv[i], TEXT("-c")))
|
||||
{
|
||||
if (action==a_default) action=a_create;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--ips")) || !wcscmp(argv[i], TEXT("-i")))
|
||||
{
|
||||
if (patchtype==ty_null) patchtype=ty_ips;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--bps")) || !wcscmp(argv[i], TEXT("--bps-delta")) ||
|
||||
!wcscmp(argv[i], TEXT("-b")) || !wcscmp(argv[i], TEXT("-B")))
|
||||
{
|
||||
if (patchtype==ty_null) patchtype=ty_bps;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--bps-delta-moremem")))
|
||||
{
|
||||
if (patchtype==ty_null) patchtype=ty_bps_moremem;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--bps-linear")))
|
||||
{
|
||||
if (patchtype==ty_null) patchtype=ty_bps_linear;
|
||||
else usage();
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--exact"))) // no short form
|
||||
{
|
||||
if (forceKeepHeader) usage();
|
||||
forceKeepHeader=true;
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--ignore-checksum")))
|
||||
{
|
||||
if (ignoreChecksum) usage();
|
||||
ignoreChecksum=true;
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--manifest")) || !wcscmp(argv[i], TEXT("-m")))
|
||||
{
|
||||
manifestinfo.use=true;
|
||||
manifestinfo.required=true;
|
||||
}
|
||||
else if (!wcsncmp(argv[i], TEXT("--manifest="), wcslen(TEXT("--manifest="))))
|
||||
{
|
||||
manifestinfo.use=true;
|
||||
manifestinfo.required=true;
|
||||
manifestinfo.name=argv[i]+wcslen(TEXT("--manifest="));
|
||||
}
|
||||
else if (!wcsncmp(argv[i], TEXT("-m"), wcslen(TEXT("-m"))))
|
||||
{
|
||||
manifestinfo.use=true;
|
||||
manifestinfo.required=true;
|
||||
manifestinfo.name=argv[i]+wcslen(TEXT("--m"));
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--version")) || !wcscmp(argv[i], TEXT("-v")))
|
||||
{
|
||||
ClaimConsole();
|
||||
puts(flipsversion);
|
||||
return 0;
|
||||
}
|
||||
else if (!wcscmp(argv[i], TEXT("--help")) || !wcscmp(argv[i], TEXT("-h")) || !wcscmp(argv[i], TEXT("-?"))) usage();
|
||||
else usage();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (numargs==3) usage();
|
||||
arg[numargs++]=argv[i];
|
||||
}
|
||||
}
|
||||
if (action==a_default)
|
||||
{
|
||||
if (numargs==0) action=a_default;
|
||||
if (numargs==1) action=a_apply_filepicker;
|
||||
if (numargs==2) action=a_apply_given;
|
||||
if (numargs==3) action=a_create;
|
||||
}
|
||||
switch (action)
|
||||
{
|
||||
case a_default:
|
||||
{
|
||||
if (numargs!=0 || hasFlags) usage();
|
||||
#ifndef FLIPS_CLI
|
||||
guiActive=true;
|
||||
return ShowGUI(NULL);
|
||||
#else
|
||||
usage();
|
||||
#endif
|
||||
}
|
||||
case a_apply_filepicker:
|
||||
{
|
||||
if (numargs!=1 || hasFlags) usage();
|
||||
#ifndef FLIPS_CLI
|
||||
guiActive=true;
|
||||
return ShowGUI(arg[0]);
|
||||
#else
|
||||
usage();
|
||||
#endif
|
||||
}
|
||||
case a_apply_given:
|
||||
{
|
||||
if (numargs!=2 && numargs!=3) usage();
|
||||
ClaimConsole();
|
||||
struct errorinfo errinf=ApplyPatch(arg[0], arg[1], !ignoreChecksum, arg[2]?arg[2]:arg[1], &manifestinfo, false);
|
||||
puts(errinf.description);
|
||||
return errinf.level;
|
||||
}
|
||||
case a_create:
|
||||
{
|
||||
if (numargs!=2 && numargs!=3) usage();
|
||||
ClaimConsole();
|
||||
if (!arg[2])
|
||||
{
|
||||
if (patchtype==ty_null)
|
||||
{
|
||||
puts("Error: Unknown patch type.");
|
||||
return el_broken;
|
||||
}
|
||||
LPWSTR arg2=(WCHAR*)malloc(sizeof(WCHAR)*(wcslen(arg[1])+4+1));
|
||||
arg[2]=arg2;
|
||||
wcscpy(arg2, arg[1]);
|
||||
GetExtension(arg2)[0]='\0';
|
||||
if (patchtype==ty_ips) wcscat(arg2, TEXT(".ips"));
|
||||
if (patchtype==ty_bps) wcscat(arg2, TEXT(".bps"));
|
||||
if (patchtype==ty_bps_linear) wcscat(arg2, TEXT(".bps"));
|
||||
}
|
||||
if (patchtype==ty_null)
|
||||
{
|
||||
LPCWSTR patchext=GetExtension(arg[2]);
|
||||
if (!*patchext)
|
||||
{
|
||||
puts("Error: Unknown patch type.");
|
||||
return el_broken;
|
||||
}
|
||||
else if (!wcsicmp(patchext, TEXT(".ips"))) patchtype=ty_ips;
|
||||
else if (!wcsicmp(patchext, TEXT(".bps"))) patchtype=ty_bps;
|
||||
else
|
||||
{
|
||||
wprintf(TEXT("Error: Unknown patch type (%s)\n"), patchext);
|
||||
return el_broken;
|
||||
}
|
||||
}
|
||||
struct errorinfo errinf=CreatePatch(arg[0], arg[1], patchtype, &manifestinfo, arg[2]);
|
||||
puts(errinf.description);
|
||||
return errinf.level;
|
||||
}
|
||||
}
|
||||
return 99;//doesn't happen
|
||||
}
|
||||
205
flips.h
Normal file
205
flips.h
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
//Module name: Floating IPS, header for all frontends
|
||||
//Author: Alcaro
|
||||
//Date: June 18, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
//Preprocessor switch documentation:
|
||||
//
|
||||
//FLIPS_WINDOWS
|
||||
//FLIPS_GTK
|
||||
//FLIPS_CLI
|
||||
// Picks which frontend to use for Flips. You can pick one manually, or let Flips choose
|
||||
// automatically depending on the platform (Windows -> 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 <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <shlobj.h>
|
||||
#include <wchar.h>
|
||||
#include <stdio.h>
|
||||
#include <commctrl.h>
|
||||
|
||||
#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 <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//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 <stdbool.h>//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()
|
||||
33
flips.rc
Normal file
33
flips.rc
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include <windows.h>
|
||||
|
||||
|
||||
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
|
||||
52
global.h
Normal file
52
global.h
Normal file
|
|
@ -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 <stddef.h>//size_t, SIZE_MAX
|
||||
#include <stdint.h>//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
|
||||
891
libbps-suf.cpp
Normal file
891
libbps-suf.cpp
Normal file
|
|
@ -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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
//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 <stdio.h>
|
||||
#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<typename sais_index_type>
|
||||
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<sais_index_type>(T, SA, 0, n, 256);
|
||||
}
|
||||
|
||||
//According to <https://code.google.com/p/libdivsufsort/wiki/SACA_Benchmarks>, 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<typename T> static T min(T a, T b) { return a<b ? a : b; }
|
||||
template<typename T> static T max(T a, T b) { return a<b ? b : a; }
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
struct bps_creator {
|
||||
uint8_t* out;
|
||||
size_t outlen;
|
||||
size_t outbuflen;
|
||||
|
||||
void reserve(size_t len)
|
||||
{
|
||||
if (outlen+len > 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<prev);
|
||||
size_t offset = negative ? prev-next : next-prev;
|
||||
return (negative?1:0) | (offset<<1);
|
||||
}
|
||||
|
||||
void append_delta(size_t prev, size_t next)
|
||||
{
|
||||
appendnum(encode_delta(prev, next));
|
||||
}
|
||||
|
||||
void append_cmd(bpscmd command, size_t count)
|
||||
{
|
||||
appendnum((count-1)<<2 | command);
|
||||
}
|
||||
|
||||
void flush_target_read()
|
||||
{
|
||||
if (!numtargetread) return;
|
||||
append_cmd(TargetRead, numtargetread);
|
||||
append(targetmem+outpos-numtargetread, numtargetread);
|
||||
numtargetread = 0;
|
||||
}
|
||||
|
||||
size_t emit_source_copy(size_t location, size_t count)
|
||||
{
|
||||
if (location == outpos) return emit_source_read(location, count);
|
||||
flush_target_read();
|
||||
append_cmd(SourceCopy, count);
|
||||
append_delta(sourcecopypos, location);
|
||||
sourcecopypos = location+count;
|
||||
outpos += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t emit_source_read(size_t location, size_t count)
|
||||
{
|
||||
flush_target_read();
|
||||
#ifdef TEST_CORRECT
|
||||
if (location != outpos)
|
||||
puts("ERROR: SourceRead not from source pointer"),abort();
|
||||
#endif
|
||||
append_cmd(SourceRead, count);
|
||||
outpos+=count;
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t emit_target_copy(size_t location, size_t count)
|
||||
{
|
||||
flush_target_read();
|
||||
append_cmd(TargetCopy, count);
|
||||
append_delta(targetcopypos, location);
|
||||
targetcopypos = location+count;
|
||||
outpos += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t emit_target_read()
|
||||
{
|
||||
numtargetread++;
|
||||
outpos++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
size_t abs_diff(size_t a, size_t b)
|
||||
{
|
||||
return (b<a) ? (a-b) : (b-a);
|
||||
}
|
||||
size_t num_cost(size_t num)
|
||||
{
|
||||
if (num<128) return 1;
|
||||
if (num<128*128) return 2; // 32KB
|
||||
if (num<128*128*128) return 3; // 2MB
|
||||
if (num<128*128*128*128) return 4; // 256MB
|
||||
return 5; // 128^5 is 32GB, let's just assume the sizes don't go any higher...
|
||||
}
|
||||
|
||||
bool use_match(bool hastargetread, size_t cost, size_t len)
|
||||
{
|
||||
//numbers calculated via trial and error; checking for each cost, optimizing 'len' for each, and checking what happens
|
||||
//then a pattern was identified and used
|
||||
//yes, it looks weird
|
||||
return len >= 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<typename off_t>
|
||||
static off_t match_len(const uint8_t* a, const uint8_t* b, off_t len)
|
||||
{
|
||||
off_t i;
|
||||
for (i=0;i<len && a[i]==b[i];i++) {}
|
||||
#ifdef TEST_PERF
|
||||
match_len_n++;
|
||||
match_len_tot+=i;
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
|
||||
//This one assumes that the longest common prefix of 'a' and 'b' is shared also by 'search'.
|
||||
//In practice, lexographically, a < search < b, which is a stronger guarantee.
|
||||
template<typename off_t>
|
||||
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<datalen && search[commonlen]==data[a+commonlen])
|
||||
{
|
||||
// a is better
|
||||
*bestlen = commonlen + match_len(search+commonlen, data+a+commonlen, min(searchlen, datalen-a)-commonlen);
|
||||
return a;
|
||||
}
|
||||
else
|
||||
{
|
||||
// b is better, or they're equal
|
||||
*bestlen = commonlen + match_len(search+commonlen, data+b+commonlen, min(searchlen, datalen-b)-commonlen);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
//This one takes a match, which is assumed optimal, and looks for the lexographically closest one
|
||||
// that either starts before 'maxstart', or starts at or after 'minstart'.
|
||||
template<typename off_t>
|
||||
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]<minstart) match_up--;
|
||||
while (match_dn<sortedlen && sorted[match_dn]>=maxstart && sorted[match_dn]<minstart) match_dn++;
|
||||
if (match_up<0 || 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<typename off_t>
|
||||
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<typename off_t>
|
||||
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<high;i++)
|
||||
{
|
||||
if (read2(data+index[i], len-index[i])!=n)
|
||||
{
|
||||
printf("e: buckets suck, %i != (%i)[%i]%i [%i-%i]", n, i,index[i],read2(data+index[i],len-index[i]),low,high);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
//printf("%i:[%i]%i\n",n,low,read2(data+index[low],len-low));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename off_t>
|
||||
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<typename off_t>
|
||||
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<len;i++) reverse[index[i]]=i;
|
||||
}
|
||||
|
||||
template<typename off_t>
|
||||
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<typename off_t>
|
||||
off_t lerp(off_t x, off_t y, float frac)
|
||||
{
|
||||
return x + (y-x)*frac;
|
||||
}
|
||||
|
||||
template<typename off_t>
|
||||
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<typename T> static bpserror bps_create_suf_pick(file* source, file* target, bool moremem, struct bps_creator * bps);
|
||||
template<> bpserror bps_create_suf_pick<uint32_t>(file* source, file* target, bool moremem, struct bps_creator * bps)
|
||||
{
|
||||
return bps_create_suf_core<int32_t>(source, target, moremem, bps);
|
||||
}
|
||||
template<> bpserror bps_create_suf_pick<uint64_t>(file* source, file* target, bool moremem, struct bps_creator * bps)
|
||||
{
|
||||
bpserror err = bps_create_suf_core<int32_t>(source, target, moremem, bps);
|
||||
if (err==bps_too_big) err = bps_create_suf_core<int64_t>(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<size_t>(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 <stdio.h>
|
||||
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<n;i++)
|
||||
//printf("%i/%i\n",i,n),
|
||||
bps_create_delta(in,out,null,&p, NULL,NULL);
|
||||
printf("len=%lu \n",p.len);
|
||||
printf("check=%.8X\n",*(uint32_t*)(p.ptr+p.len-4));
|
||||
WriteWholeFile(argv[3], p);
|
||||
free(in.ptr);
|
||||
free(out.ptr);
|
||||
free(p.ptr);
|
||||
|
||||
#ifdef TEST_PERF
|
||||
printf("%i/%i=%f\n",match_len_tot,match_len_n,(float)match_len_tot/match_len_n);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
521
libbps.cpp
Normal file
521
libbps.cpp
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
//Module name: libbps
|
||||
//Author: Alcaro
|
||||
//Date: December 20, 2014
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#include "libbps.h"
|
||||
|
||||
#include <stdlib.h>//malloc, realloc, free
|
||||
#include <string.h>//memcpy, memset
|
||||
#include <stdint.h>//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)<<shift; \
|
||||
assert_sum(var, addthis); \
|
||||
var+=addthis; \
|
||||
if (next&0x80) break; \
|
||||
shift+=7; \
|
||||
assert_sum(var, 1U<<shift); \
|
||||
var+=1<<shift; \
|
||||
} \
|
||||
} while(false)
|
||||
#define write8(byte) (*(outat++)=byte)
|
||||
|
||||
const uint8_t * patchat=patch.ptr;
|
||||
const uint8_t * patchend=patch.ptr+patch.len-12;
|
||||
|
||||
if (read8()!='B') error(bps_broken);
|
||||
if (read8()!='P') error(bps_broken);
|
||||
if (read8()!='S') error(bps_broken);
|
||||
if (read8()!='1') error(bps_broken);
|
||||
|
||||
uint32_t crc_in_e = read32(patch.ptr+patch.len-12);
|
||||
uint32_t crc_out_e = read32(patch.ptr+patch.len-8);
|
||||
uint32_t crc_patch_e = read32(patch.ptr+patch.len-4);
|
||||
|
||||
uint32_t crc_in_a = crc32(in.ptr, in.len);
|
||||
uint32_t crc_patch_a = crc32(patch.ptr, patch.len-4);
|
||||
|
||||
if (crc_patch_a != crc_patch_e) error(bps_broken);
|
||||
|
||||
size_t inlen;
|
||||
decodeto(inlen);
|
||||
|
||||
size_t outlen;
|
||||
decodeto(outlen);
|
||||
|
||||
if (inlen!=in.len || crc_in_a!=crc_in_e)
|
||||
{
|
||||
if (in.len==outlen && crc_in_a==crc_out_e) error=bps_to_output;
|
||||
else error=bps_not_this;
|
||||
if (!accept_wrong_input) goto exit;
|
||||
}
|
||||
|
||||
out->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;i<metadatalen;i++) metadata->ptr[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<metadatalen;i++) (void)read8();
|
||||
}
|
||||
|
||||
while (patchat<patchend)
|
||||
{
|
||||
size_t thisinstr;
|
||||
decodeto(thisinstr);
|
||||
size_t length=(thisinstr>>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;i<length;i++)
|
||||
{
|
||||
size_t pos = outat-outstart; // don't inline, write8 changes outat
|
||||
write8(instart[pos]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TargetRead:
|
||||
{
|
||||
if (patchat+length>patchend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(read8());
|
||||
}
|
||||
break;
|
||||
case SourceCopy:
|
||||
{
|
||||
size_t encodeddistance;
|
||||
decodeto(encodeddistance);
|
||||
size_t distance=encodeddistance>>1;
|
||||
if ((encodeddistance&1)==0) inreadat+=distance;
|
||||
else inreadat-=distance;
|
||||
|
||||
if (inreadat<instart || inreadat+length>inend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(*inreadat++);
|
||||
}
|
||||
break;
|
||||
case TargetCopy:
|
||||
{
|
||||
size_t encodeddistance;
|
||||
decodeto(encodeddistance);
|
||||
size_t distance=encodeddistance>>1;
|
||||
if ((encodeddistance&1)==0) outreadat+=distance;
|
||||
else outreadat-=distance;
|
||||
|
||||
if (outreadat<outstart || outreadat>=outat || outreadat+length>outend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(*outreadat++);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patchat!=patchend) error(bps_broken);
|
||||
if (outat!=outend) error(bps_broken);
|
||||
|
||||
uint32_t crc_out_a = crc32(out->ptr, 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;i<metadata.len;i++) write(metadata.ptr[i]);
|
||||
|
||||
size_t mainContentPos=outlen;
|
||||
|
||||
const uint8_t * lastknownchange=targetbegin;
|
||||
while (target<targetend)
|
||||
{
|
||||
size_t numunchanged=0;
|
||||
while (source+numunchanged<sourceend && source[numunchanged]==target[numunchanged]) numunchanged++;
|
||||
if (numunchanged>1)
|
||||
{
|
||||
//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<targetend)
|
||||
{
|
||||
numchanged++;
|
||||
if (source+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;i<numchanged;i++)
|
||||
{
|
||||
write(target[i]);
|
||||
}
|
||||
source+=numchanged;
|
||||
target+=numchanged;
|
||||
}
|
||||
if (target[-2]==target[0] && target[-1]==target[1] && target[0]==target[2])
|
||||
{
|
||||
//two-byte RLE
|
||||
size_t rlelen=0;
|
||||
while (target+rlelen<targetend && target[0]==target[rlelen+0] && target[1]==target[rlelen+1]) rlelen+=2;
|
||||
writenum((rlelen-1)<<2 | TargetCopy);
|
||||
writenum((target-targetcopypos-2)<<1);
|
||||
source+=rlelen;
|
||||
target+=rlelen;
|
||||
targetcopypos=target-2;
|
||||
}
|
||||
else if (target[-1]==target[0] && target[0]==target[1])
|
||||
{
|
||||
//one-byte RLE
|
||||
size_t rlelen=0;
|
||||
while (target+rlelen<targetend && target[0]==target[rlelen]) rlelen++;
|
||||
writenum((rlelen-1)<<2 | TargetCopy);
|
||||
writenum((target-targetcopypos-1)<<1);
|
||||
source+=rlelen;
|
||||
target+=rlelen;
|
||||
targetcopypos=target-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write32(crc32(sourcemem.ptr, sourcemem.len));
|
||||
write32(crc32(targetmem.ptr, targetmem.len));
|
||||
write32(crc32(out, outlen));
|
||||
|
||||
patchmem->ptr=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 <stdio.h>
|
||||
|
||||
//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)<<shift; \
|
||||
var+=addthis; \
|
||||
if (next&0x80) break; \
|
||||
shift+=7; \
|
||||
var+=1<<shift; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
size_t lastmatch=0;
|
||||
size_t patchposatmatch[2]={0,0};
|
||||
|
||||
size_t outlen;
|
||||
patch[0]+=4; patch[1]+=4;//BPS1
|
||||
size_t tempuint;
|
||||
decodeto(0, tempuint); decodeto(1, tempuint);//source-size
|
||||
decodeto(0, outlen); decodeto(1, outlen);//target-size
|
||||
decodeto(0, tempuint); patch[0]+=tempuint;//metadata
|
||||
decodeto(1, tempuint); patch[1]+=tempuint;//metadata
|
||||
|
||||
bool show=false;
|
||||
while (patchpos[0]<patchlen[0] && patchpos[1]<patchlen[1])
|
||||
{
|
||||
bool step[2]={(patchoutpos[0]<=patchoutpos[1]), (patchoutpos[0]>=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 (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
|
||||
70
libbps.h
Normal file
70
libbps.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//Module name: libbps
|
||||
//Author: Alcaro
|
||||
//Date: June 18, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#include "global.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>//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
|
||||
401
libips.cpp
Normal file
401
libips.cpp
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
//Module name: libips
|
||||
//Author: Alcaro
|
||||
//Date: July 11, 2013
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>//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 <stdlib.h>//malloc, realloc, free
|
||||
#include <string.h>//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() ((patchat<patchend)?(*patchat++):0)
|
||||
#define read16() ((patchat+1<patchend)?(patchat+=2,((patchat[-2]<<8)|patchat[-1])):0)
|
||||
#define read24() ((patchat+2<patchend)?(patchat+=3,((patchat[-3]<<16)|(patchat[-2]<<8)|patchat[-1])):0)
|
||||
if (read8()!='P' ||
|
||||
read8()!='A' ||
|
||||
read8()!='T' ||
|
||||
read8()!='C' ||
|
||||
read8()!='H')
|
||||
{
|
||||
return ips_invalid;
|
||||
}
|
||||
unsigned int offset=read24();
|
||||
unsigned int outlen=0;
|
||||
unsigned int thisout=0;
|
||||
unsigned int lastoffset=0;
|
||||
bool w_scrambled=false;
|
||||
while (offset!=0x454F46)//454F46=EOF
|
||||
{
|
||||
unsigned int size=read16();
|
||||
if (size==0)
|
||||
{
|
||||
size=read16();
|
||||
if (!size) w_scrambled=true;
|
||||
thisout=offset+size;
|
||||
read8();
|
||||
}
|
||||
else
|
||||
{
|
||||
thisout=offset+size;
|
||||
patchat+=size;
|
||||
}
|
||||
if (offset<lastoffset) w_scrambled=true;
|
||||
lastoffset=offset;
|
||||
if (thisout>outlen) 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<targetlen)
|
||||
{
|
||||
while (offset<sourcelen && (offset<sourcelen?source[offset]:0)==target[offset]) offset++;
|
||||
//check how much we need to edit until it starts getting similar
|
||||
int thislen=0;
|
||||
int consecutiveunchanged=0;
|
||||
thislen=lastknownchange-offset;
|
||||
if (thislen<0) thislen=0;
|
||||
while (true)
|
||||
{
|
||||
unsigned int thisbyte=offset+thislen+consecutiveunchanged;
|
||||
if (thisbyte<sourcelen && (thisbyte<sourcelen?source[thisbyte]:0)==target[thisbyte]) consecutiveunchanged++;
|
||||
else
|
||||
{
|
||||
thislen+=consecutiveunchanged+1;
|
||||
consecutiveunchanged=0;
|
||||
}
|
||||
if (consecutiveunchanged>=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<thislen && target[offset]==target[offset+byteshere];byteshere++) {}
|
||||
if (byteshere==thislen)
|
||||
{
|
||||
int thisbyte=target[offset];
|
||||
int i=0;
|
||||
while (true)
|
||||
{
|
||||
unsigned int pos=offset+byteshere+i-1;
|
||||
if (pos>=targetlen || target[pos]!=thisbyte || byteshere+i>65535) break;
|
||||
if (pos>=sourcelen || (pos<sourcelen?source[pos]:0)!=thisbyte)
|
||||
{
|
||||
byteshere+=i;
|
||||
thislen+=i;
|
||||
i=0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if ((byteshere>8-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+byteshere<thislen)
|
||||
{
|
||||
if (target[offset+stopat]==target[offset+stopat+byteshere]) byteshere++;
|
||||
else
|
||||
{
|
||||
stopat+=byteshere;
|
||||
byteshere=0;
|
||||
}
|
||||
if (byteshere>8+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-1<sourcelen && target[offset+thislen-1]==(offset+thislen-1<sourcelen?source[offset+thislen-1]:0))
|
||||
{
|
||||
thislen--;
|
||||
}
|
||||
}
|
||||
|
||||
if (thislen>3 && !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;i<thislen;i++)
|
||||
{
|
||||
write8(target[offset+i]);
|
||||
}
|
||||
}
|
||||
offset+=thislen;
|
||||
}
|
||||
}
|
||||
write8('E');
|
||||
write8('O');
|
||||
write8('F');
|
||||
if (sourcelen>targetlen) 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 <stdio.h>
|
||||
|
||||
//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() ((patchat<patchend)?(*patchat++):0)
|
||||
#define read16() ((patchat+1<patchend)?(patchat+=2,((patchat[-2]<<8)|patchat[-1])):0)
|
||||
#define read24() ((patchat+2<patchend)?(patchat+=3,((patchat[-3]<<16)|(patchat[-2]<<8)|patchat[-1])):0)
|
||||
if (read8()!='P' ||
|
||||
read8()!='A' ||
|
||||
read8()!='T' ||
|
||||
read8()!='C' ||
|
||||
read8()!='H')
|
||||
{
|
||||
puts("Invalid");
|
||||
return;
|
||||
}
|
||||
int blockstart=patchat-patch.ptr;
|
||||
int offset=read24();
|
||||
int outlen=0;
|
||||
int thisout=0;
|
||||
while (offset!=0x454F46)//454F46=EOF
|
||||
{
|
||||
int size=read16();
|
||||
if (size==0)
|
||||
{
|
||||
int rlelen=read16();
|
||||
thisout=offset+rlelen;
|
||||
printf("[%X] %X: %i (RLE)\n", blockstart, offset, rlelen);
|
||||
read8();
|
||||
}
|
||||
else
|
||||
{
|
||||
thisout=offset+size;
|
||||
printf("[%X] %X: %i\n", blockstart, offset, size);
|
||||
patchat+=size;
|
||||
}
|
||||
if (thisout>outlen) 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
|
||||
41
libips.h
Normal file
41
libips.h
Normal file
|
|
@ -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);
|
||||
178
libups.cpp
Normal file
178
libups.cpp
Normal file
|
|
@ -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 <stdbool.h>//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 <stdint.h>//uint8_t, uint32_t
|
||||
#include <stdlib.h>//malloc, realloc, free
|
||||
#include <string.h>//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)<<shift; \
|
||||
assert_sum(var, addthis); \
|
||||
var+=addthis; \
|
||||
if (next&0x80) break; \
|
||||
shift+=7; \
|
||||
assert_sum(var, 1U<<shift); \
|
||||
var+=1<<shift; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
bool backwards=false;
|
||||
|
||||
uint8_t * patchat=patch.ptr;
|
||||
uint8_t * patchend=patch.ptr+patch.len-12;
|
||||
|
||||
if (readpatch8()!='U') error(ups_broken);
|
||||
if (readpatch8()!='P') error(ups_broken);
|
||||
if (readpatch8()!='S') error(ups_broken);
|
||||
if (readpatch8()!='1') error(ups_broken);
|
||||
|
||||
size_t inlen;
|
||||
size_t outlen;
|
||||
decodeto(inlen);
|
||||
decodeto(outlen);
|
||||
if (inlen!=in.len)
|
||||
{
|
||||
size_t tmp=inlen;
|
||||
inlen=outlen;
|
||||
outlen=tmp;
|
||||
backwards=true;
|
||||
}
|
||||
if (inlen!=in.len) error(ups_not_this);
|
||||
|
||||
out->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 (patchat<patchend)
|
||||
{
|
||||
size_t skip;
|
||||
decodeto(skip);
|
||||
while (skip>0)
|
||||
{
|
||||
uint8_t out;
|
||||
if (inat>=inend) out=0;
|
||||
else out=readin8();
|
||||
if (outat<outend) writeout8(out);
|
||||
skip--;
|
||||
}
|
||||
uint8_t tmp;
|
||||
do
|
||||
{
|
||||
tmp=readpatch8();
|
||||
uint8_t out;
|
||||
if (inat>=inend) out=0;
|
||||
else out=readin8();
|
||||
if (outat<outend) writeout8(out^tmp);
|
||||
}
|
||||
while (tmp);
|
||||
}
|
||||
if (patchat!=patchend) error(ups_broken);
|
||||
while (outat<outend) writeout8(0);
|
||||
while (inat<inend) (void)readin8();
|
||||
|
||||
uint32_t crc_in_expected=read32(patchat);
|
||||
uint32_t crc_out_expected=read32(patchat+4);
|
||||
uint32_t crc_patch_expected=read32(patchat+8);
|
||||
|
||||
uint32_t crc_in=crc32(in.ptr, in.len);
|
||||
uint32_t crc_out=crc32(out->ptr, 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
|
||||
34
libups.h
Normal file
34
libups.h
Normal file
|
|
@ -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);
|
||||
674
license-gpl.txt
Normal file
674
license-gpl.txt
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
11
license.txt
Normal file
11
license.txt
Normal file
|
|
@ -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.
|
||||
56
make.sh
Executable file
56
make.sh
Executable file
|
|
@ -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
|
||||
16
profile/profile.sh
Executable file
16
profile/profile.sh
Executable file
|
|
@ -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
|
||||
9
profile/profile1.sh
Executable file
9
profile/profile1.sh
Executable file
|
|
@ -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
|
||||
479
sais.cpp
Normal file
479
sais.cpp
Normal file
|
|
@ -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 <https://sites.google.com/site/yuta256/sais>;
|
||||
//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 <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#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<typename sais_index_type, typename TT>
|
||||
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<typename sais_index_type>
|
||||
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<typename sais_index_type, typename TT>
|
||||
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<typename sais_index_type, typename TT>
|
||||
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<typename sais_index_type, typename TT>
|
||||
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<typename sais_index_type>
|
||||
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<typename sais_index_type, typename TT>
|
||||
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<typename sais_index_type, typename TT>
|
||||
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;
|
||||
}
|
||||
7
special.sh
Executable file
7
special.sh
Executable file
|
|
@ -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
|
||||
Loading…
Reference in New Issue
Block a user