diff --git a/arlib/array.h b/arlib/array.h index a709fa2..95327ee 100644 --- a/arlib/array.h +++ b/arlib/array.h @@ -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& other) - //{ - // this->count=other.count; - // this->items=other.items; - //} - protected: static const bool trivial_cons = std::is_trivial::value; // constructor is memset(0) #if __GNUC__ >= 5 - static const bool trivial_copy = std::is_trivially_copyable::value; + static const bool trivial_copy = std::is_trivially_copyable::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::value; - static const bool trivial_comp = std::is_integral::value; + static const bool trivial_comp = std::is_integral::value; // comparison operator is memcmp public: const T& operator[](size_t n) const { return items[n]; } diff --git a/arlib/file-unix.cpp b/arlib/file-unix.cpp index 93e6449..8ad7cc4 100644 --- a/arlib/file-unix.cpp +++ b/arlib/file-unix.cpp @@ -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 diff --git a/arlib/global.h b/arlib/global.h index 6d540a2..8e0a4f7 100644 --- a/arlib/global.h +++ b/arlib/global.h @@ -97,6 +97,7 @@ template 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 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. diff --git a/arlib/gui/gtk3-misc.cpp b/arlib/gui/gtk3-misc.cpp index 3481782..4d012e7 100644 --- a/arlib/gui/gtk3-misc.cpp +++ b/arlib/gui/gtk3-misc.cpp @@ -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) diff --git a/arlib/gui/win32-misc.cpp b/arlib/gui/win32-misc.cpp index 4ba97be..f4d962a 100644 --- a/arlib/gui/win32-misc.cpp +++ b/arlib/gui/win32-misc.cpp @@ -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) { diff --git a/arlib/gui/window.h b/arlib/gui/window.h index ad9b817..417d888 100644 --- a/arlib/gui/window.h +++ b/arlib/gui/window.h @@ -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_*(). diff --git a/arlib/opengl/helpers.cpp b/arlib/opengl/helpers.cpp index a725006..4920bff 100644 --- a/arlib/opengl/helpers.cpp +++ b/arlib/opengl/helpers.cpp @@ -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;iGetStringi(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); } diff --git a/arlib/string-test.cpp b/arlib/string-test.cpp index adc3668..a16d283 100644 --- a/arlib/string-test.cpp +++ b/arlib/string-test.cpp @@ -107,4 +107,31 @@ test() assert_eq(b, "[123]"); assert_eq(c, "[123]"); } + + { + string a = "baaaaaaaaaaaaaaa"; + array 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"); + } } diff --git a/arlib/string.h b/arlib/string.h index 74ebbae..f026fb9 100644 --- a/arlib/string.h +++ b/arlib/string.h @@ -429,6 +429,29 @@ public: return *this; } + //can't create csplit without things blowing up + //limit is maximum number of cuts + array split(const string& sep, size_t limit) const + { + array 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(data, next-data)); + data = next+sep.length(); + } + ret.append(arrayview(data, dataend-data)); + return ret; + } + + template + array split(const string& sep) const { return split(sep, limit); } + array 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 bytes) { init_from(bytes); } + string(arrayview chars) + { + init_from_nocopy(arrayview((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 left, arrayview 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((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((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 bytes) : string(noinit()) { init_from_nocopy(bytes); } + cstring(arrayview chars) : string(noinit()) + { + init_from_nocopy(arrayview((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(str, len)); if (!inlined()) m_nul=nul; } + cstring(const uint8_t * str, uint32_t len, bool nul) : string(noinit()) + { + init_from_nocopy(arrayview(str, len)); + if (!inlined()) m_nul=nul; + } public: cstring& operator=(const cstring& other) { release(); init_from_nocopy(other); return *this; } diff --git a/arlib/wutf/wutf.h b/arlib/wutf/wutf.h index bee7acd..600aef0 100644 --- a/arlib/wutf/wutf.h +++ b/arlib/wutf/wutf.h @@ -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. diff --git a/flips.cpp b/flips.cpp index edba54d..60f12fe 100644 --- a/flips.cpp +++ b/flips.cpp @@ -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 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(&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 args; + for (int i=1;argv[i];i++) args.append(argv[i]); + + for (size_t i=0;i 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; } diff --git a/global.h b/global.h deleted file mode 100644 index 3a29b99..0000000 --- a/global.h +++ /dev/null @@ -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 //size_t, SIZE_MAX -#include //uint8_t - -#ifndef SIZE_MAX -#define SIZE_MAX ((size_t)-1) -#endif - -struct mem { - uint8_t * ptr; - size_t len; -}; -#endif diff --git a/patch/bps-create.cpp b/patch/bps-create.cpp index 1ac1481..2570cac 100644 --- a/patch/bps-create.cpp +++ b/patch/bps-create.cpp @@ -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 v) : ptr((byte*)v.ptr()), len(v.size()) {} - //arrayvieww v() { return arrayvieww(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 static void sufsort(sais_index_type* SA, const uint8_t* T, sais_index_type n) { diff --git a/patch/bps.cpp b/patch/bps.cpp index 09cd3d8..1a5a487 100644 --- a/patch/bps.cpp +++ b/patch/bps.cpp @@ -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 patchmem, arrayview inmem, array& 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 data, bool changefrac) size_t outpos=0; // position in the output file size_t changeamt=0; // change score - while (patch.remaining()) + while (patch.remaining() && outpossize_in) { size_t thisinstr; patch.bpsnum(&thisinstr); @@ -223,7 +220,6 @@ result info::parse(arrayview data, bool changefrac) } outpos+=length; } - if (outpos>this->size_out) return e_broken; this->change_num = (changeamtsize_in ? changeamt : this->size_in); this->change_denom = this->size_in; } diff --git a/patch/patch.h b/patch/patch.h index a5ffda9..a23417a 100644 --- a/patch/patch.h +++ b/patch/patch.h @@ -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 { diff --git a/patch/test.cpp b/patch/test.cpp index 5bb203d..b3e958e 100644 --- a/patch/test.cpp +++ b/patch/test.cpp @@ -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 sm64 = file::read("patch/test/sm64.z64"); array 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 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 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 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 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 diff --git a/patch/ups.cpp b/patch/ups.cpp index 0756900..c6f2f2a 100644 --- a/patch/ups.cpp +++ b/patch/ups.cpp @@ -1,7 +1,6 @@ #include "patch.h" namespace patch { namespace ups { -//TODO: HEAVY cleanups needed here result apply(arrayview patchmem, const file& in, array& outmem) { @@ -20,10 +19,7 @@ result apply(arrayview patchmem, const file& in, array& 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;