More updates

This commit is contained in:
Alcaro
2016-12-30 01:25:09 +01:00
parent 56cd2b4ca6
commit a6b0176344
17 changed files with 463 additions and 172 deletions

View File

@@ -11,21 +11,15 @@ protected:
T * items; // not const, despite not necessarily being writable; this makes arrayvieww/array a lot simpler
size_t count;
//void clone(const arrayview<T>& other)
//{
// this->count=other.count;
// this->items=other.items;
//}
protected:
static const bool trivial_cons = std::is_trivial<T>::value; // constructor is memset(0)
#if __GNUC__ >= 5
static const bool trivial_copy = std::is_trivially_copyable<T>::value;
static const bool trivial_copy = std::is_trivially_copyable<T>::value; // copy constructor is memcpy
#else
static const bool trivial_copy = trivial_cons; // copy constructor is memcpy
static const bool trivial_copy = trivial_cons;
#endif
//static const bool trivial_comp = std::has_unique_object_representations<T>::value;
static const bool trivial_comp = std::is_integral<T>::value;
static const bool trivial_comp = std::is_integral<T>::value; // comparison operator is memcmp
public:
const T& operator[](size_t n) const { return items[n]; }

View File

@@ -126,7 +126,7 @@
//}
static long pagesize;
static long pagesize = sysconf(_SC_PAGESIZE);
namespace {
class file_unix : public file::impl {
@@ -205,8 +205,5 @@ bool file::unlink(cstring filename)
}
//#endif
void _window_init_file()
{
pagesize = sysconf(_SC_PAGESIZE);
}
void _window_init_file() {}
#endif

View File

@@ -97,6 +97,7 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#ifdef _MSC_VER
//this version doesn't work on GCC, it makes PPFE_MAP0 not get expanded the second time and quite effectively stops everything.
//but completely unknown guy says it's required on MSVC, so I'll trust that and ifdef it
//pretty sure one of them violate the C99/C++ specifications, but I have no idea which of them, nor what Clang does
#define PPFE_MAP_NEXT1(test, next) PPFE_EVAL0(PPFE_MAP_NEXT0 (test, next, 0))
#else
#define PPFE_MAP_NEXT1(test, next) PPFE_MAP_NEXT0 (test, next, 0)
@@ -106,7 +107,7 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#define PPFE_MAP1(f, x, peek, ...) f(x) PPFE_MAP_NEXT (peek, PPFE_MAP0) (f, peek, __VA_ARGS__)
#define PPFOREACH(f, ...) PPFE_EVAL (PPFE_MAP1 (f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
//usage:
//#define STRING(x) char const *x##_string = #x;
//#define STRING(x) const char * x##_string = #x;
//PPFOREACH(STRING, foo, bar, baz)
//limited to 365 entries, but that's enough.

View File

@@ -50,7 +50,7 @@
struct window_x11_info window_x11;
#endif
void window_init(int * argc, char * * argv[])
static bool window_init(bool require, int * argc, char * * argv[])
{
//struct rlimit core_limits;core_limits.rlim_cur=core_limits.rlim_max=64*1024*1024;setrlimit(RLIMIT_CORE,&core_limits);
#ifdef DEBUG
@@ -59,8 +59,14 @@ g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING
#ifdef ARGUIPROT_X11
XInitThreads();
#endif
gtk_init(argc, argv);
_window_init_file();
if (require)
{
gtk_init(argc, argv);
}
else
{
if (!gtk_init_check(argc, argv)) return false;
}
//gdk_window_add_filter(NULL,scanfilter,NULL);
//#ifndef NO_ICON
// struct image img;
@@ -74,6 +80,23 @@ g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING
window_x11.root=gdk_x11_get_default_root_xwindow();//alternatively XRootWindow(window_x11.display, window_x11.screen)
#endif
errno=0;
return true;
}
void window_init(int * argc, char * * argv[])
{
window_init(true, argc, argv);
}
bool window_try_init(int * argc, char * * argv[])
{
return window_init(false, argc, argv);
}
bool window_attach_console()
{
//nothing to do
return getenv("TERM");
}
//file* file::create(const char * filename)

View File

@@ -10,13 +10,17 @@
#include "../wutf/wutf.h"
#endif
//Number of ugly hacks: 5
//Number of ugly hacks: 6
//If a status bar item is right-aligned, a space is appended.
//The status bar is created with WS_DISABLED.
//WM_SYSCOMMAND is sometimes ignored.
//I have to keep track of the mouse position so I can ignore various bogus instances of WM_MOUSEMOVE.
//I have to undefine 'bind' before including any Windows header. I suspect something is including winsock.
//Console handling under Windows is a mess. (But launching a GUI app from a Linux console isn't much better...)
//Microsoft dropped Windows XP at April 8, 2014, after an unusually long support period. That is well above two years ago.
//Vista will die on April 11, 2017. But its user count is so low I don't care about dropping that either.
//Therefore, I have no reason to continue caring about it working.
//Incompatibility levels:
//Level 0 - a feature works as intended
//Level 1 - a feature is usable, but behaves weirdly
@@ -40,7 +44,7 @@
//Level 4: printf dislikes z (size_t) size specifiers; they must be behind #ifdef DEBUG, or turned into "I" via #define
// NOTE: This is present on Vista too. z requires 7 or higher.
//Level 5: 64-bit programs dislike XP (there are 32bit Vista/7/8, but Vista is practically dead, as is 32bit 7+)
//Level 5: SRWLOCK is Vista+.
//Level 5: SRWLOCK is Vista+
//static LARGE_INTEGER timer_freq;
@@ -62,6 +66,35 @@ void window_init(int * argc, char * * argv[])
//QueryPerformanceFrequency(&timer_freq);
}
bool window_try_init(int * argc, char * * argv[])
{
window_init(argc, argv);
return true;
}
bool window_attach_console()
{
//doesn't create a new console if not launched from one, it'd go away on app exit anyways
//doesn't like being launched from cmd; cmd wants to run a new command if spawning a gui app
// I can't make it not be a gui app, that flashes a console; it acts sanely from batch files
//windows consoles are, like so much else, a massive mess
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);
if (claimstdout) fputc('\r', stdout);
if (claimstderr) fputc('\r', stderr);
return GetConsoleWindow();
}
#if 0
file* file::create(const char * filename)
{

View File

@@ -13,11 +13,18 @@ class widget_base;
//This must be called before calling any other window_*, before creating any interface that does any I/O, before calling anything from
// the malloc() family, and before using argc/argv; basically, before doing anything else. It should be the first thing main() does.
//It does the following actions, in whatever order makes sense:
//- Initialize the window system, if needed
//- Initialize the window system, if needed; on failure, terminates the process
//- Read off any arguments it recognizes (if any), and delete them; for example, it takes care of --display and a few others on GTK+
//- Convert argv[0] to the standard path format, if needed (hi Windows)
void window_init(int * argc, char * * argv[]);
//Returns false if the window system couldn't be initialized, rather than terminating.
bool window_try_init(int * argc, char * * argv[]);
//On Windows, attaches stdout/stderr to the console of the launching process. On Linux, does nothing.
//On both, returns whether the process is currently in a terminal. Returns true if I/O is redirected.
bool window_attach_console();
//window toolkit is not choosable at runtime
//It is safe to interact with this window while inside its callbacks, with the exception that you may not free it.
//You may also not use window_run_*().

View File

@@ -70,20 +70,21 @@ test()
bool aropengl::hasExtension(const char * ext)
{
int major = strtol((char*)this->GetString(GL_VERSION), NULL, 0);
aropengl& gl = *this;
int major = strtol((char*)gl.GetString(GL_VERSION), NULL, 0);
if (major >= 3)
{
GLint n;
this->GetIntegerv(GL_NUM_EXTENSIONS, &n);
for (GLint i=0;i<n;i++)
{
if (!strcmp((char*)this->GetStringi(GL_EXTENSIONS, i), ext)) return true;
if (!strcmp((char*)gl.GetStringi(GL_EXTENSIONS, i), ext)) return true;
}
return false;
}
else
{
return strtoken((char*)this->GetString(GL_EXTENSIONS), ext);
return strtoken((char*)gl.GetString(GL_EXTENSIONS), ext);
}
}
@@ -137,10 +138,11 @@ static void APIENTRY debug_cb(GLenum source, GLenum type, GLuint id, GLenum seve
void aropengl::enableDefaultDebugger(FILE* out)
{
aropengl& gl = *this;
if (!out) out = stderr;
this->DebugMessageCallback((GLDEBUGPROC)debug_cb, out);//some headers have 'const' on the userdata, some don't
gl.DebugMessageCallback((GLDEBUGPROC)debug_cb, out);//some headers have 'const' on the userdata, some don't
//https://www.opengl.org/sdk/docs/man/html/glDebugMessageCallback.xhtml says it shouldn't be const
this->DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
this->Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
gl.DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
gl.Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
}

View File

@@ -107,4 +107,31 @@ test()
assert_eq(b, "[123]");
assert_eq(c, "[123]");
}
{
string a = "baaaaaaaaaaaaaaa";
array<string> b;
b = a.split("a");
assert_eq(b[0], "b");
assert_eq(b[1], "");
assert_eq(b[15], "");
assert_eq(b.size(), 16);
b = a.split("aa");
assert_eq(b.size(), 8);
assert_eq(b[0], "b");
assert_eq(b[1], "");
assert_eq(b[6], "");
assert_eq(b[7], "a");
b = a.split<1>("aa");
assert_eq(b.size(), 2);
assert_eq(b[0], "b");
assert_eq(b[1], "aaaaaaaaaaaaa");
b = a.split<1>("c");
assert_eq(b.size(), 1);
assert_eq(b[0], "baaaaaaaaaaaaaaa");
}
}

View File

@@ -429,6 +429,29 @@ public:
return *this;
}
//can't create csplit without things blowing up
//limit is maximum number of cuts
array<string> split(const string& sep, size_t limit) const
{
array<string> ret;
const uint8_t * data = ptr();
const uint8_t * dataend = ptr()+length();
while (ret.size() < limit)
{
const uint8_t * next = (uint8_t*)memmem(data, dataend-data, sep.ptr(), sep.length());
if (!next) break;
ret.append(arrayview<uint8_t>(data, next-data));
data = next+sep.length();
}
ret.append(arrayview<uint8_t>(data, dataend-data));
return ret;
}
template<size_t limit>
array<string> split(const string& sep) const { return split(sep, limit); }
array<string> split(const string& sep) const { return split(sep, SIZE_MAX); }
private:
class noinit {};
string(noinit) {}
@@ -440,6 +463,10 @@ public:
string(const char * str) { init_from(str); }
//string(const uint8_t * str, uint32_t len) { init_from(str, len); }
string(arrayview<uint8_t> bytes) { init_from(bytes); }
string(arrayview<char> chars)
{
init_from_nocopy(arrayview<uint8_t>((uint8_t*)chars.ptr(), chars.size()));
}
string& operator=(const string& other) { release(); init_from(other); return *this; }
string& operator=(const char * str) { release(); init_from(str); return *this; }
~string() { release(); }
@@ -509,16 +536,10 @@ public:
else return "\xEF\xBF\xBD";
return ret;
}
//Implementation detail of the equality operators. Don't use.
static inline bool s_eq(arrayview<byte> left, arrayview<byte> right)
{
return (left.size()==right.size() && !memcmp(left.ptr(), right.ptr(), left.size()));
}
};
inline bool operator==(const string& left, const char * right ) { return string::s_eq(left.bytes(), arrayview<byte>((uint8_t*)right,strlen(right))); }
inline bool operator==(const string& left, const string& right) { return string::s_eq(left.bytes(), right.bytes()); }
inline bool operator==(const string& left, const char * right ) { return left.bytes() == arrayview<byte>((uint8_t*)right,strlen(right)); }
inline bool operator==(const string& left, const string& right) { return left.bytes() == right.bytes(); }
inline bool operator==(const char * left, const string& right) { return operator==(right, left); }
inline bool operator!=(const string& left, const char * right ) { return !operator==(left, right); }
inline bool operator!=(const string& left, const string& right) { return !operator==(left, right); }
@@ -545,9 +566,17 @@ public:
cstring(const char * str) : string(noinit()) { init_from_nocopy(str); }
//cstring(const uint8_t * str, uint32_t len) : string(noinit()) { init_from_nocopy(str, len); }
cstring(arrayview<uint8_t> bytes) : string(noinit()) { init_from_nocopy(bytes); }
cstring(arrayview<char> chars) : string(noinit())
{
init_from_nocopy(arrayview<uint8_t>((uint8_t*)chars.ptr(), chars.size()));
}
private:
//don't use arrayview, if (nul) then it uses len+1 bytes
cstring(const uint8_t * str, uint32_t len, bool nul) : string(noinit()) { init_from_nocopy(arrayview<byte>(str, len)); if (!inlined()) m_nul=nul; }
cstring(const uint8_t * str, uint32_t len, bool nul) : string(noinit())
{
init_from_nocopy(arrayview<byte>(str, len));
if (!inlined()) m_nul=nul;
}
public:
cstring& operator=(const cstring& other) { release(); init_from_nocopy(other); return *this; }

View File

@@ -96,6 +96,7 @@ extern "C" {
#ifdef _WIN32
//Main function; this one does the actual magic. Call this as early as possible.
//If you're using WuTF as part of Arlib, there's no need to call this manually; window_init() does.
void WuTF_enable();
//Converts argc/argv to UTF-8. Uses only documented functions, so it has zero chance of blowing up.

335
flips.cpp
View File

@@ -6,15 +6,19 @@ a/a.smc b/b.bps -> -a b/b.bps a/a.smc b/b.smc
a/a.bps b/b.smc c/c.sfc -> -a a/a.bps b/b.smc c/c.sfc
-c a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
a/a.smc b/b.smc c/c.bps -> -c a/a.smc b/b.smc c/c.bps
a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
a/a.smc -> error
a/a.smc b/b.smc -> error
a/a.bps b/b.bps -> error
(anything) -m b/b.txt -> extract or insert manifest here
a/a.bps -m b/b.txt -> extract manifest only
a/a.bps . -> query database
a/a.bps $ -> query database
-c . a/a.smc a/a.bps -> pick best match from database
-c . a/a.smc -> -c . a/a.smc a/a.bps
-c a/a.smc -> -c . a/a.smc
(null) -> launch gui
a/a.bps -> launch patch wizard
anything else -> error
flips-c a/a.smc -> -c . a/a.smc a/a.bps
--db -> print database
--db a/a.smc b/b.smc -> add to database
@@ -28,6 +32,7 @@ a/a.smc --db -> error
-s --silent - don't print anything on success, also silence BPS create progress (but do print on failure)
-h -? --help -v --version - exists
--ips --bps - removed, use the correct extensions
--ignore-checksum - allow applying patch to wrong files (DANGEROUS)
replace --exact:
--inhead=512 - discard 512 leading bytes in the infile before sending to patcher
@@ -46,10 +51,21 @@ on failed application, or successful IPS or UPS application, do nothing
~/.config/flips.cfg
#Floating IPS configuration
#Version 2.00
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.sfc
database crc32=a31bead4 size=524800 path=/home/alcaro/smw.smc
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.smc # SMCs have two entries
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.sfc # duplicates are fine
assoc-target=ask # or auto or auto-exec
create-show-all=true # affects whether Create Patch (GUI) defaults to all files, or only common ROMs
create-show-all=true # affects whether Create Patch (GUI) defaults to all files, or only common ROMs; both in and out
create-auto-source=true # if source rom can't be found, asks for that after target
auto pick source rom:
load first 1MB from source
for each file in database, except duplicates:
load first 1MB
check how many bytes are the same
if exactly one file has >1/8 matching, and the rest are <1/32, use that
otherwise error
behind off-by-default flag
GUI is same as Flips 1.31, except
- no IPS creation
@@ -78,72 +94,261 @@ bps spec:
http://wayback.archive.org/web/20110911111128/http://byuu.org/programming/bps/
*/
//static void usage()
//{
// //GUIClaimConsole();
// 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] [--bps | --bps-linear | --ips] 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 --info: BPSes contain information about input and output roms, print it\n"
// //" also estimates how much of the source file is retained\n"
// //" anything under 400 is fine, anything over 600 should be treated with suspicion\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);
//}
static void a_apply()
{
}
class flipsargs {
public:
enum { m_default, m_apply, m_create, m_info, m_db } mode = m_default;
array<string> fnames;
string manifest;
bool silent = false;
bool ignorechecksum = false;
bool autohead = true;
size_t inhead = 0;
size_t patchhead = 0;
size_t outhead = 0;
#ifdef ARLIB_TEST
string errormsg;
#endif
void usage(cstring error)
{
#ifndef ARLIB_TEST
window_attach_console();
if (error) puts("error: "+error);
puts(R"(command line usage:
flips -a|--apply a.bps b.smc [c.sfc]
apply patch; default output file is a.smc
flips -c|--create a.smc b.smc [c.bps]
create patch; default output file is b.bps
flips -i|--info a.bps
print some information about this patch, such as its expected input file
with only filenames, guess either --apply or --create
bps, ips and ups patches can be applied, bps and ips can be created
database:
if the source file has been used before, it can be shortened to . (bps only)
usable both when creating and applying
the database can be manipulated with
flips --db
print list of known inputs
flips --db [-]a.smc [[-]b.smc]
add or remove files from database
additional options:
-m foo.xml or --manifest=foo.xml: extract or insert bps manifest here
-s --silent: remain silent on success
--ignore-checksum: allow applying a bps patch to wrong input file
--inhead=512: discard the first 512 bytes of the input file
--patchhead=512: prepend 512 bytes before patching, discard afterwards
--outhead=512: prepend 512 bytes to the patch output
--head=512: shortcut for --inhead=512 --outhead=512
prepended bytes are either copied from a previous header, or 00)");
exit(error ? 1 : 0);
#else
if (!errormsg) errormsg = error;
#endif
}
//returns whether 'next' was used
//calls usage() if arg is unknown
bool parse(cstring arg, cstring next)
{
if(0);
else if (arg=="help") usage("");
else if (arg=="version")
{
window_attach_console();
puts("?");
exit(0);
}
else if (arg=="apply") mode=m_apply;
else if (arg=="create") mode=m_create;
else if (arg=="info") mode=m_info;
else if (arg=="db") mode=m_db;
else if (arg=="manifest")
{
manifest=next;
return true;
}
else if (arg=="silent") silent=true;
else if (arg=="ignorechecksum") ignorechecksum=true;
else if (arg=="head" || arg=="inhead" || arg=="patchhead" || arg=="outhead")
{
size_t size;
if (!fromstring(next, size)) usage("invalid argument to --"+arg);
if (arg=="head") outhead=inhead=size;
if (arg=="inhead") inhead=size;
if (arg=="patchhead") patchhead=size;
if (arg=="outhead") outhead=size;
return true;
}
else usage("unknown option --"+arg);
return false;
}
string longname(char arg)
{
switch (arg)
{
case 'h': return "help";
case '?': return "help";
case 'v': return "version";
case 'a': return "apply";
case 'c': return "create";
case 'i': return "info";
case 'm': return "manifest";
case 's': return "silent";
default: usage("unknown option -"+string(arrayview<char>(&arg, 1))); return "";
}
}
//if -f is --foo, all of these yield arg="foo" next="bar":
//-fbar | -f bar | --foo=bar
//and these yield next="":
//-f -b | -f --bar | -f | --foo bar
void parse(const char * const * argv)
{
array<string> args;
for (int i=1;argv[i];i++) args.append(argv[i]);
for (size_t i=0;i<args.size();i++)
{
string arg = args[i];
if (arg[0]=='-')
{
if (arg[1]=='-')
{
//long
array<string> parts = arg.substr(2, ~0).split<1>("=");
bool hasarg = (parts.size()==2);
bool argused = parse(parts[0], parts[1]);
if (hasarg && !argused) usage("--"+parts[0]+" does not take an argument");
}
else
{
//short
string rest = arg.substr(1, ~0);
next:
char opt = rest[0];
rest = rest.substr(1, ~0);
if (rest)
{
bool argused = parse(longname(opt), rest);
if (!argused) goto next;
}
else
{
bool argused = parse(longname(opt), args[i+1]);
if (argused) i++;
}
}
}
else
{
//not option
fnames.append(arg);
continue;
}
}
}
void setmode()
{
//a/a.bps b/b.smc -> -a a/a.bps b/b.smc a/a.smc ("default path", patch path + patch basename + infile extension)
//a/a.smc b/b.bps -> -a b/b.bps a/a.smc b/b.smc
//a/a.bps b/b.smc c/c.sfc -> -a a/a.bps b/b.smc c/c.sfc
//-c a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
//a/a.smc b/b.smc c/c.bps -> -c a/a.smc b/b.smc c/c.bps
//a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
//a/a.smc -> error
//a/a.bps b/b.bps -> error
//(anything) -m b/b.txt -> extract or insert manifest here
//a/a.bps -m b/b.txt -> extract manifest only
//a/a.bps . -> query database
//-c . b/b.smc b/b.bps -> pick best match from database
//-c . b/b.smc -> -c . b/b.smc b/b.bps
//-c b/b.smc -> -c . b/b.smc b/b.bps
//(null) -> launch gui
//a/a.bps -> launch patch wizard
//anything else -> error
}
};
test()
{{
//macros are great to wipe out copypasted boilerplate
#define PARSE(x) } { const char * argv[] = { "flips", x, NULL }; flipsargs args; args.parse(argv)
PARSE(NULL);
assert_eq(args.errormsg, "");
PARSE("--foo");
assert(args.errormsg != "");
PARSE("--apply");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
PARSE("-a");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
PARSE("-a", "-m", "foo.smc");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
assert_eq(args.manifest, "foo.smc");
PARSE("-a", "--manifest=foo.smc");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
assert_eq(args.manifest, "foo.smc");
PARSE("-a", "--manifest", "foo.smc");
assert(args.errormsg != ""); // invalid way to specify --manifest
PARSE("-am", "foo.smc");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
assert_eq(args.manifest, "foo.smc");
PARSE("-amfoo.smc");
assert_eq(args.errormsg, "");
assert(args.mode==flipsargs::m_apply);
assert_eq(args.manifest, "foo.smc");
#undef PARSE
}}
int main(int argc, char* argv[])
{
window_init(&argc, &argv);
window* wnd = window_create(
widget_create_layout_grid(2,2, false,
widget_create_button("Apply Patch")->set_onclick(bind(a_apply)),
widget_create_button("Create Patch"),
widget_create_button("Apply and Run"),
widget_create_button("Settings")));
wnd->set_title("Flips v" FLIPSVER);
wnd->set_visible(true);
while (wnd->is_visible()) window_run_wait();
return 0;
bool guiavail = window_try_init(&argc, &argv);
flipsargs args;
args.parse(argv);
if (guiavail)
{
window* wnd = window_create(
widget_create_layout_grid(2,2, false,
widget_create_button("Apply Patch")->set_onclick(bind(a_apply)),
widget_create_button("Create Patch"),
widget_create_button("Apply and Run"),
widget_create_button("Settings")));
wnd->set_title("Flips v" FLIPSVER);
wnd->set_visible(true);
while (wnd->is_visible()) window_run_wait();
return 0;
}
return -1;
}

View File

@@ -1,21 +0,0 @@
//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 {
uint8_t * ptr;
size_t len;
};
#endif

View File

@@ -1,15 +1,5 @@
#include "patch.h"
//Deprecated
//struct mem {
//mem() : ptr(NULL), len(0) {}
//mem(uint8_t* ptr, size_t len) : ptr(ptr), len(len) {}
//mem(arrayview<byte> v) : ptr((byte*)v.ptr()), len(v.size()) {}
//arrayvieww<byte> v() { return arrayvieww<byte>(ptr, len); }
//uint8_t * ptr;
//size_t len;
//};
//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.
@@ -36,6 +26,9 @@
// called O(n) times, with O(log n) comparisons per iteration. Each comparison is potentially O(n),
// but for each matched byte, another iteration is removed from the outer loop, so the comparisons
// can be considered O(1) each; the sum is O(n log n).
//It could be replaced with a reverse index, reverse[sorted[x]]==x for all x, but that would cost a
// lot of memory, and due to the cost of creating said index and only a few entries being used, it
// doesn't save any time in practice.
//
//After it's found sortpos, it scans sorted[] up and down for the closest entry that either starts
// before the current output position, or is somewhere in the source file.
@@ -53,14 +46,15 @@
// 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).)
// equals O(2n log n), which is O(n log n).
//
//Many details were omitted from the above, but that's the basic setup.
//
//Thus, the program is O(max(n log n, n log n, n, n) = n log n) average and O(max(n log n, n log n,
// n^2, n) = n^2) worst case.
//Thus, the program is O(n log n) + O(n log n) + O(n) + O(n) = O(n log n) average and O(n log n) +
// O(n log n) + O(n^2) + O(n) = O(n^2) worst case.
//
//I conclude that the task of finding, understanding and implementing a sub-O(n^2) algorithm for
//As the quadratic worst case is not hit for random data or any other plausible output file, I
// conclude that the task of finding, understanding and implementing a sub-quadratic algorithm for
// delta patching is resolved.
@@ -73,9 +67,10 @@
// 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.
//Heuristics are likely somewhat mistuned.
//TODO: test multiple same-length matches
// but only for lengths <= 64,
// but only for lengths <= 16 or something, otherwise it'd take too long
//Possible optimizations:
@@ -84,30 +79,32 @@
//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 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: 1/2 * 1/4 = 1/8
//Search, final: 1/2 * 3/4 = 3/8
//Sort+rev, non-final: 1/2 * 1/4 = 1/8
//Sort+rev, final: 1/2 * 3/4 = 3/8
//Search, non-final: 1/2 * 1/4 = 1/8
//Search, final: 1/2 * 3/4 = 3/8
//Sort, non-final: 1/2 * 1/4 = 1/8
//Sort, final: 1/2 * 3/4 = 3/8
//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(1/8+1/8, 3/8) + 3/8 = 6/8 = 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 middle of this while loop" operation 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 in/outputs. For most hardware,
// this is 5*(source.len+target.len).
//If the output is stored to disk, that's all this algorithm needs as well.
//Another optimization would be if a faster suffix sorting algorithm available.
//Both SA-IS and libdivsufsort claim O(1) memory use, in addition to the in/outputs. For most
// hardware, this is 5*(source.len+target.len).
//The output file is also stored in memory, which is potentially slightly more than the output file
// size. This could be changed without too much trouble, but is unlikely to be worth it.
namespace patch { namespace bps {
//TODO: HEAVY cleanups needed here
#include "sais.cpp"
template<typename sais_index_type>
static void sufsort(sais_index_type* SA, const uint8_t* T, sais_index_type n) {

View File

@@ -1,14 +1,14 @@
#include "patch.h"
//Things I would've done differently if I had a chance to redesign BPS:
//Things I would've done differently if I had a chance to redesign the BPS format:
//- Don't allow encoding -0 in Source/TargetCopy
//- Ditch metadata, it goes in a separate file
//- Ditch metadata, it goes in a separate file (and it's already used in nonstandard ways, spec says XML but bsnes disagrees)
//- Reconsider SourceRead; maybe patches would be smaller if of the three others was one bit rather than two, or maybe a new command
// or maybe only Read/Copy commands? Read is TargetRead, Copy treats target as concatenated to source
//- Invert 0x80 bit in the encoded numbers, set means continue; it would simplify the decoder
//- Replace BPS1 signature with something not containing an 1
// while DWORD alignment sounds nice, it's useless for a byte-oriented format like this; even the checksums aren't aligned
// four-byte signatures are nicer than three, but '1' is the wrong choice for the last byte; PNG's \x89 would work
// four-byte signatures are nicer than three, but '1' is the wrong choice for the last byte; PNGs \x89 would work
//- Make the checksums mandatory
// (1) Ignoring them allows all files of that size, including ones that are clearly not the proper source
// (2) Even if a ROM hacker is careful to only change a few bytes, BPS likes copying stuff around,
@@ -34,10 +34,7 @@ result apply(arrayview<byte> patchmem, arrayview<byte> inmem, array<byte>& outme
if (!patch.bpsnum(&var)) error(e_too_big); \
} while(false)
if (patch.u8()!='B') error(e_broken);
if (patch.u8()!='P') error(e_broken);
if (patch.u8()!='S') error(e_broken);
if (patch.u8()!='1') error(e_broken);
if (!patch.signature("BPS1")) error(e_broken);
memstream checks = patchmem.slice(patchmem.size()-12, 12);
uint32_t crc_in_e = checks.u32();
@@ -191,7 +188,7 @@ result info::parse(arrayview<byte> data, bool changefrac)
size_t outpos=0; // position in the output file
size_t changeamt=0; // change score
while (patch.remaining())
while (patch.remaining() && outpos<this->size_in)
{
size_t thisinstr;
patch.bpsnum(&thisinstr);
@@ -223,7 +220,6 @@ result info::parse(arrayview<byte> data, bool changefrac)
}
outpos+=length;
}
if (outpos>this->size_out) return e_broken;
this->change_num = (changeamt<this->size_in ? changeamt : this->size_in);
this->change_denom = this->size_in;
}

View File

@@ -14,6 +14,7 @@ enum result {
e_ok,
//You may get an output file along with some of these errors.
//This is indistinguishable from zero-size output, but such patches are useless and rare anyways.
e_to_output,//You attempted to apply a patch to its output.
e_not_this, //This is not the intended input file for this patch.
e_damaged, //The patch is technically valid, but seems scrambled or malformed.
@@ -28,7 +29,7 @@ enum result {
//All of these functions can be called with arrayview inputs and array& outputs, but
// they give lower memory use and/or better performance if you follow the listed types.
//For example, IPS and UPS application start with copying the input file to the output;
//For example, applying an IPS or UPS starts with copying the input file to the output;
// if you give them a file object directly, they'll read it straight from disk to the target buffer.
namespace ips {

View File

@@ -1,5 +1,7 @@
#include "patch.h"
#if 0
/*
make clean; rm callgrind.out.*; make test -j8 TESTRUNNER='time valgrind --tool=callgrind' CFLAGS='-Os -g' && kcachegrind callgrind.out.*
*/
@@ -169,7 +171,7 @@ test("BPS")
test("the big ones")
{
testips=true;
testips=false;
//testips=false;
testbps=true;
//testbps=false;
@@ -180,30 +182,31 @@ test("the big ones")
array<byte> sm64 = file::read("patch/test/sm64.z64");
array<byte> sm64_bps = file::read("patch/test/star.bps");
if (!smw || !smw_bps || !dl || !dl_ups || !sm64 || !sm64_bps) test_skip("test files not present; see patch/test/readme.txt");
result r;
array<byte> smwhack;
r = bps::apply(smw_bps, smw, smwhack);
assert_eq(r, e_ok);
result smwr = bps::apply(smw_bps, smw, smwhack);
assert_eq(smwr, e_ok);
assert_eq(smwhack.size(), 4194304);
testcall(createtest(smw, smwhack, 3302746, 2077386));
//this is the only UPS test, UPS is pretty much an easter egg in Flips
array<byte> dlhack;
r = ups::apply(dl_ups, dl, dlhack);
assert_eq(r, e_ok);
result dlr = ups::apply(dl_ups, dl, dlhack);
assert_eq(dlr, e_ok);
assert_eq(dlhack.size(), 3145728);
array<byte> dl2;
r = ups::apply(dl_ups, dlhack, dl2);
assert_eq(r, e_ok);
dlr = ups::apply(dl_ups, dlhack, dl2);
assert_eq(dlr, e_ok);
assert(dl == dl2);
testcall(createtest(dl, dlhack, 852124, 817190));
array<byte> sm64hack;
r = bps::apply(sm64_bps, sm64, sm64hack);
assert_eq(r, e_ok);
result sm64r = bps::apply(sm64_bps, sm64, sm64hack);
assert_eq(sm64r, e_ok);
assert_eq(sm64hack.size(), 50331648);
testbps=false; // too slow
//removing this makes that entire createtest useless
//testbps=false; // too slow
testcall(createtest(sm64, sm64hack, -1, 6788133));
}
}
#endif

View File

@@ -1,7 +1,6 @@
#include "patch.h"
namespace patch { namespace ups {
//TODO: HEAVY cleanups needed here
result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
{
@@ -20,10 +19,7 @@ result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
bool backwards=false;
if (patch.u8()!='U') error(e_broken);
if (patch.u8()!='P') error(e_broken);
if (patch.u8()!='S') error(e_broken);
if (patch.u8()!='1') error(e_broken);
if (!patch.signature("UPS1")) error(e_broken);
size_t inlen;
size_t outlen;