diff --git a/arlib/Makefile b/arlib/Makefile index 429ef61..f7adcc3 100644 --- a/arlib/Makefile +++ b/arlib/Makefile @@ -5,6 +5,10 @@ #PATH is the source filename, including extension, relative to project root, with slashes replaced with double underscore #example: obj/DEFAULT___linux___arlib__file-posix.cpp.o +ifneq (,$(wildcard string-test.cpp)) + $(error wrong build directory, go up one level) +endif + SPACE := SPACE += diff --git a/arlib/array.h b/arlib/array.h index 95327ee..17a54f9 100644 --- a/arlib/array.h +++ b/arlib/array.h @@ -87,7 +87,7 @@ public: // return *this; //} - bool operator==(arrayview other) + bool operator==(arrayview other) const { if (size() != other.size()) return false; if (this->trivial_comp) @@ -104,13 +104,13 @@ public: } } - bool operator!=(arrayview other) + bool operator!=(arrayview other) const { return !(*this == other); } - const T* begin() { return this->items; } - const T* end() { return this->items+this->count; } + const T* begin() const { return this->items; } + const T* end() const { return this->items+this->count; } }; //size: two pointers @@ -165,6 +165,8 @@ public: arrayvieww slice(size_t first, size_t count) { return arrayvieww(this->items+first, count); } + const T* begin() const { return this->items; } + const T* end() const { return this->items+this->count; } T* begin() { return this->items; } T* end() { return this->items+this->count; } }; @@ -268,7 +270,22 @@ public: else resize_grow(len); } + void insert(size_t index, const T& item) + { + resize_grow(this->count+1); + memmove(this->items+index+1, this->items+index, sizeof(T)*(this->count-1-index)); + new(&this->items[index]) T(item); + } + T& insert(size_t index) + { + resize_grow(this->count+1); + memmove(this->items+index+1, this->items+index, sizeof(T)*(this->count-1-index)); + new(&this->items[index]) T(); + return this->items[index]; + } + void append(const T& item) { size_t pos = this->count; resize_grow(pos+1); this->items[pos] = item; } + T& append() { size_t pos = this->count; resize_grow(pos+1); return this->items[pos]; } void reset() { resize_shrink(0); } void remove(size_t index) @@ -491,7 +508,7 @@ public: void append(bool item) { set(this->nbits, item); } - array slice(size_t first, size_t count) + array slice(size_t first, size_t count) const { if ((first&7) == 0) { @@ -519,4 +536,28 @@ public: { if (nbits >= n_inline) free(this->bits_outline); } + + //missing features from the other arrays (some don't make sense here): + //operator bool() { return count; } + //T join() const + //template decltype(T() + T2()) join(T2 between) const + //bool operator==(arrayview other) + //bool operator!=(arrayview other) + //const T* begin() { return this->items; } + //const T* end() { return this->items+this->count; } + //T* ptr() { return this->items; } + //const T* ptr() const { return this->items; } + //T* begin() { return this->items; } + //T* end() { return this->items+this->count; } + //void reserve(size_t len) { resize_grow(len); } + //void reserve_noinit(size_t len) + //void remove(size_t index) + // + //array(null_t) + //array(const array& other) + //array(const arrayview& other) + //array(array&& other) + //array operator=(array other) + //array operator=(arrayview other) + //array& operator+=(arrayview other) }; diff --git a/arlib/gui/gtk3-misc.cpp b/arlib/gui/gtk3-misc.cpp index 4d012e7..5f423d3 100644 --- a/arlib/gui/gtk3-misc.cpp +++ b/arlib/gui/gtk3-misc.cpp @@ -93,10 +93,15 @@ bool window_try_init(int * argc, char * * argv[]) return window_init(false, argc, argv); } -bool window_attach_console() +bool window_console_avail() +{ + return getenv("TERM"); +} + +bool window_console_attach() { //nothing to do - return getenv("TERM"); + return window_console_avail(); } //file* file::create(const char * filename) diff --git a/arlib/gui/win32-misc.cpp b/arlib/gui/win32-misc.cpp index f4d962a..acad9bd 100644 --- a/arlib/gui/win32-misc.cpp +++ b/arlib/gui/win32-misc.cpp @@ -72,6 +72,12 @@ bool window_try_init(int * argc, char * * argv[]) return true; } +bool window_console_avail() +{ + AttachConsole(ATTACH_PARENT_PROCESS); + return GetConsoleWindow(); +} + bool window_attach_console() { //doesn't create a new console if not launched from one, it'd go away on app exit anyways @@ -79,11 +85,13 @@ bool window_attach_console() // 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 +#error check whether AttachConsole attaches stdin + 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); + AttachConsole(ATTACH_PARENT_PROCESS); if (claimstdin) freopen("CONIN$", "rt", stdin); if (claimstdout) freopen("CONOUT$", "wt", stdout); @@ -92,7 +100,7 @@ bool window_attach_console() if (claimstdout) fputc('\r', stdout); if (claimstderr) fputc('\r', stderr); - return GetConsoleWindow(); + return window_console_avail(); } #if 0 diff --git a/arlib/gui/window.h b/arlib/gui/window.h index 417d888..20c564d 100644 --- a/arlib/gui/window.h +++ b/arlib/gui/window.h @@ -23,7 +23,11 @@ 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(); + +//Returns whether the process was launched from a console. +//If yes, calling window_console_attach will connect stdout/stderr to something (stdin not guaranteed to work). +bool window_console_avail(); +bool window_console_attach(); // Returns whether it worked. //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. diff --git a/arlib/test.cpp b/arlib/test.cpp index 8d5ed1c..37c9b77 100644 --- a/arlib/test.cpp +++ b/arlib/test.cpp @@ -32,6 +32,8 @@ void _teststack_push(int line) { callstack.append(line); } void _teststack_pop() { callstack.resize(callstack.size()-1); } static string stack(int top) { + if (top<0) return ""; + string ret = " (line "+tostring(top); for (int i=callstack.size()-1;i>=0;i--) diff --git a/arlib/test.h b/arlib/test.h index a408bf1..8ce353b 100644 --- a/arlib/test.h +++ b/arlib/test.h @@ -45,8 +45,11 @@ void _test_skip(cstring why); return; \ } \ } while(0) +#define assert_fail(msg) do { _testfail((string)"\n"+msg, __LINE__); return; } while(0) +#define assert_fail_nostack(msg) do { _testfail((string)"\n"+msg, -1); return; } while(0) #define testcall(x) do { _teststack_push(__LINE__); x; _teststack_pop(); if (_test_result) return; } while(0) #define test_skip(x) do { _test_skip(x); return; } while(0) +#define main not_quite_main #else diff --git a/flips.cpp b/flips.cpp index 60f12fe..b9951c2 100644 --- a/flips.cpp +++ b/flips.cpp @@ -2,23 +2,23 @@ /* 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.smc b/b.bps -> -a b/b.bps a/a.smc b/b.smc (if no console then auto-gui) 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 -> -c a/a.smc b/b.smc b/b.bps (if no console then auto-gui, sorting files by mod date) 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 . a/a.smc a/a.bps -> pick best match from database --c . a/a.smc -> -c . a/a.smc a/a.bps +-a a/a.bps -> query database +. a/a.smc a/a.bps -> pick best match from database +. 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 +(null) -> (gui) main window +a/a.bps -> (gui) apply patch +a/a.smc -> (gui) create patch, per assoc-can-create 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 @@ -46,36 +46,33 @@ if the above happened and patching fails, retry with all headers 0; if success, database: on successful BPS application, add without asking, even if that size/sum is already known (but don't add the same file twice, of course) -if file disappears or its checksum changes, delete and try another, if any +if file disappears or its checksum changes, silently delete and try another, if any on failed application, or successful IPS or UPS application, do nothing +if create-auto-source, creating a patch also adds source file to database ~/.config/flips.cfg #Floating IPS configuration #Version 2.00 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; both in and out -create-auto-source=true # if source rom can't be found, asks for that after target +assoc-apply-exec=false # if true, dropping a bps on flips.exe will suppress success messages and instead launch emulator +assoc-can-create=unset # true -> dropping rom on exe creates; false -> not a patch error; unset -> error but ask, with don't ask again +create-show-all=false # affects whether Create Patch (GUI) defaults to all files, or only common ROMs; both in and out +create-auto-source=false # 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 + if exactly one file has >1/3 matching, and the rest are <1/16, use that otherwise error behind off-by-default flag -GUI is same as Flips 1.31, except -- no IPS creation -- publish size/checksum of infile info in failure message -- no manifests -- replace settings window with list of db files (including add/delete buttons), and assoc-target config (don't allow disabling db) -- rewrite using Arlib -which means these limits compared to CLI: +GUI limits compared to CLI: - can't create IPS - no manifests +- checksum is mandatory patch wizard: first, get infile from DB or user @@ -94,15 +91,21 @@ bps spec: http://wayback.archive.org/web/20110911111128/http://byuu.org/programming/bps/ */ -static void a_apply() -{ - -} - class flipsargs { public: - enum { m_default, m_apply, m_create, m_info, m_db } mode = m_default; - array fnames; + //most of these are set by parse() + //flipsfile::{f, exists} are set by fillfiles + enum mode_t { m_default, m_apply, m_create, m_info, m_db } mode = m_default; + bool canusegui; // window_try_init + bool usegui; // !window_console_avail && no other args + bool forcegui = false; // --gui + + struct flipsfile { + string path; + file f; + enum { t_auto, t_patch, t_file } type; + }; + array files; string manifest; bool silent = false; @@ -117,6 +120,17 @@ public: string errormsg; #endif + void error(cstring error) + { +#ifndef ARLIB_TEST + window_attach_console(); + puts("error: "+error); + exit(1); +#else + if (!errormsg) errormsg = "error: "+error; +#endif + } + void usage(cstring error) { #ifndef ARLIB_TEST @@ -145,6 +159,7 @@ 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 +--gui: use GUI even if launched from command line --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 @@ -152,7 +167,7 @@ additional options: prepended bytes are either copied from a previous header, or 00)"); exit(error ? 1 : 0); #else - if (!errormsg) errormsg = error; + if (!errormsg) errormsg = "error: "+error; #endif } @@ -164,14 +179,14 @@ prepended bytes are either copied from a previous header, or 00)"); else if (arg=="help") usage(""); else if (arg=="version") { - window_attach_console(); - puts("?"); + window_console_attach(); + puts("Flips v" FLIPSVER); 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=="db") error("--db must be first if present"); else if (arg=="manifest") { manifest=next; @@ -179,6 +194,7 @@ prepended bytes are either copied from a previous header, or 00)"); } else if (arg=="silent") silent=true; else if (arg=="ignorechecksum") ignorechecksum=true; + else if (arg=="gui") forcegui=true; else if (arg=="head" || arg=="inhead" || arg=="patchhead" || arg=="outhead") { size_t size; @@ -219,11 +235,23 @@ prepended bytes are either copied from a previous header, or 00)"); array args; for (int i=1;argv[i];i++) args.append(argv[i]); + if (args[0]=="--db") + { + mode = m_db; + for (size_t i=1;i i+1 && args[i+1][0]!='-'); + bool argused = parse(longname(opt), (hasarg ? args[i+1] : "")); + if (!hasarg && argused) usage("--"+longname(opt)+" requires an argument"); if (argused) i++; } } @@ -257,98 +289,237 @@ prepended bytes are either copied from a previous header, or 00)"); else { //not option - fnames.append(arg); + files.append().path = arg; continue; } } } - void setmode() + void setfiles() + { + if (forcegui) usegui=true; + if (!canusegui) usegui=false; + if (forcegui && !canusegui) error("gui requested but not available"); + + if (files.size() > 3) usage("too many files"); + for (flipsfile& f : files) + { + if (f.path==".") f.type = flipsfile::t_auto; + else + { + f.f.open(f.path); + + bool ispatch; + if (f.f) ispatch = (patch::identify(f.f) != patch::t_unknown); + else ispatch = (patch::identify_ext(f.path) != patch::t_unknown); + + if (ispatch) f.type = flipsfile::t_patch; + else f.type = flipsfile::t_file; + } + } + + if (!usegui && files.size()==0) usage(""); + if (usegui && files.size() > 2) usage("too many files"); + if (mode == m_default) + { + if (files.size()>=2 && files[0].type!=flipsfile::t_patch && files[1].type==flipsfile::t_file) mode = m_create; + else if (usegui && files.size()==2 && (files[0].type==flipsfile::t_file || files[1].type==flipsfile::t_file)) mode = m_create; + else mode = m_apply; + } + if (mode == m_apply) + { + if (files.size()==1) files.append().path = "."; + if (files.size()==2) files.append().path = "."; + if (files[1].type==flipsfile::t_patch && files[0].type!=flipsfile::t_patch) + { + std::swap(files[0], files[1]); + } + if (files[0].type != flipsfile::t_patch) usage("unknown patch format"); + + //applying a patch to a patch requires explicit output filename + //applying a patch to an auto can't have a patch as output + bool haspatchpatch = (files[1].type==flipsfile::t_patch || files[2].type==flipsfile::t_patch); + bool hasauto = (files[1].type==flipsfile::t_auto || files[2].type==flipsfile::t_auto); + if (haspatchpatch && hasauto) usage("attempt to apply a patch to a patch\n if you want that, give three filenames"); + } + if (mode == m_create) + { + if (files.size()==1) files.insert(0).path = "."; + if (files.size()==2) files.append().path = "."; + if (files[1].type==flipsfile::t_auto) usage("can't auto detect target file for patch"); + + //creating a patch using patches as input requires three filenames + bool haspatchsrc = (files[0].type==flipsfile::t_patch || files[1].type==flipsfile::t_patch); + bool hasauto = (files[0].type==flipsfile::t_auto || files[1].type==flipsfile::t_auto || files[2].type==flipsfile::t_auto); + if (haspatchsrc && hasauto) usage("attempt to create a patch that applies to a patch\n if you want that, give three filenames"); + //if (hasauto && files[1].type!=flipsfile::t_file) usage("moo"); + if (files[2].type==flipsfile::t_file) usage("unknown patch format"); + + if (usegui) + { + error("fixme: sort files by date"); + } + } + } + + void execute() { -//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() +test("flipsargs::parse") {{ -//macros are great to wipe out copypasted boilerplate -#define PARSE(x) } { const char * argv[] = { "flips", x, NULL }; flipsargs args; args.parse(argv) +//macros are great for wiping out copypasted boilerplate +#define PARSE_BASE(...) } { const char * argv[] = { "flips", __VA_ARGS__, NULL }; flipsargs args; args.parse(argv) +#define PARSE(...) PARSE_BASE(__VA_ARGS__); assert_eq(args.errormsg, "") +#define PARSE_ERR(...) PARSE_BASE(__VA_ARGS__); assert(args.errormsg != "") PARSE(NULL); - assert_eq(args.errormsg, ""); - PARSE("--foo"); - assert(args.errormsg != ""); + PARSE_ERR("--foo"); PARSE("--apply"); - assert_eq(args.errormsg, ""); - assert(args.mode==flipsargs::m_apply); + assert_eq(args.mode, flipsargs::m_apply); PARSE("-a"); - assert_eq(args.errormsg, ""); - assert(args.mode==flipsargs::m_apply); + assert_eq(args.mode, flipsargs::m_apply); PARSE("-a", "-m", "foo.smc"); - assert_eq(args.errormsg, ""); - assert(args.mode==flipsargs::m_apply); + assert_eq(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.mode, flipsargs::m_apply); assert_eq(args.manifest, "foo.smc"); - PARSE("-a", "--manifest", "foo.smc"); - assert(args.errormsg != ""); // invalid way to specify --manifest + PARSE_ERR("-a", "--manifest", "foo.smc"); // --manifest doesn't work that way PARSE("-am", "foo.smc"); - assert_eq(args.errormsg, ""); - assert(args.mode==flipsargs::m_apply); + assert_eq(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.mode, flipsargs::m_apply); assert_eq(args.manifest, "foo.smc"); +#undef PARSE_BASE #undef PARSE +#undef PARSE_ERR }} +static void testsetfiles(int numfiles, int fnames, char exp_a, char exp_c) +{ + static const char * fnamebase[]={".", "a.bps", "a.smc"}; + const char * f1 = fnamebase[fnames/3/3%3]; + const char * f2 = fnamebase[fnames/3%3]; + const char * f3 = fnamebase[fnames%3]; + + for (int pass=0;pass<3;pass++) + { + flipsargs args; + args.canusegui=false; + if (numfiles>=3) args.files.append().path = f1; + if (numfiles>=2) args.files.append().path = f2; + if (numfiles>=1) args.files.append().path = f3; + args.mode = (flipsargs::mode_t)pass; + args.setfiles(); + + int result; + if (args.errormsg!="") result = 0; + else + { + assert(args.mode != flipsargs::m_default); + result = args.mode; + } + + if (pass==flipsargs::m_default) + { + if (result==0 && (exp_a=='A' || exp_c=='C')) goto fail; + if (exp_a=='A' && result!=flipsargs::m_apply) goto fail; + if (exp_c=='C' && result!=flipsargs::m_create) goto fail; + } + if (pass==flipsargs::m_apply) + { + if (result==0 && exp_a!='-') goto fail; + if (result!=0 && exp_a=='-') goto fail; + } + if (pass==flipsargs::m_create) + { + if (result==0 && exp_c!='-') goto fail; + if (result!=0 && exp_c=='-') goto fail; + } + continue; + + fail: + string exp_s; + exp_s[0]=exp_a; + exp_s[1]=exp_c; + if (numfiles<3) f1=""; + if (numfiles<2) f2=""; + if (numfiles<1) f3=""; + string err = (string)f1+" "+f2+" "+f3+" pass "+tostring(pass)+" exp "+exp_s+": unexpected "; + if (result!=0) err += tostring(result); + if (result==0) err += args.errormsg; + assert_fail_nostack(err); + } +} + +test("flipsargs::setfiles") +{ + //each combination is 2 chars; first is apply, second is create + //- = reject this, ac = allow if -a/-c is given, AC = auto select this mode + static const char * expected1 = + "--A--c" // auto, patch, rom + ; + static const char * expected2 = + "--A--C" // auto, * + "A---A-" // patch, * + "--A--C" // rom, * + ; + static const char * expected3 = + "------" // auto, auto, * + "A---A-" // auto, patch, * + "-C-C--" // auto, rom, * + "A---A-" // patch, auto, * + "--aca-" // patch, patch, * + "A-acA-" // patch, rom, * + "------" // rom, auto, * + "A-acA-" // rom, patch, * + "-C-C--" // rom, rom, * + ; + for (int i=0;i<3 ;i++) testcall(testsetfiles(1, i, expected1[i*2], expected1[i*2+1])); + for (int i=0;i<3*3 ;i++) testcall(testsetfiles(2, i, expected2[i*2], expected2[i*2+1])); + for (int i=0;i<3*3*3;i++) testcall(testsetfiles(3, i, expected3[i*2], expected3[i*2+1])); +} + int main(int argc, char* argv[]) { bool guiavail = window_try_init(&argc, &argv); flipsargs args; + args.canusegui = guiavail; + args.usegui = !window_console_avail(); args.parse(argv); - - if (guiavail) + if (args.mode == flipsargs::m_db) { - 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; + //args.executedb(); } + //args.setfiles(); + //args.querydb(); + //args.execute(); + + //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/patch/patch.cpp b/patch/patch.cpp new file mode 100644 index 0000000..64662b5 --- /dev/null +++ b/patch/patch.cpp @@ -0,0 +1,59 @@ +#include "patch.h" + +namespace patch { +type identify(const file& patch) +{ + byte head[16]; + size_t len = patch.read(arrayview(head)); + if (len>=5 && !memcmp(head, "PATCH", 5)) return t_ips; + if (len>=4 && !memcmp(head, "UPS1", 4)) return t_ups; + if (len>=4 && !memcmp(head, "BPS1", 4)) return t_bps; + return t_unknown; +} + +type identify_ext(cstring path) +{ + if (path.endswith(".ips")) return t_ips; + if (path.endswith(".ups")) return t_ups; + if (path.endswith(".bps")) return t_bps; + return t_unknown; +} + +bool memstream::bpsnum(size_t* out) +{ + //similar to uleb128, but bpsnum adds another 1<::lslov(next^0x80, shift, &tmp)) return false; + if (safeint::addov(ret, tmp, &ret)) return false; + if (next&0x80) break; + shift+=7; + } + *out = ret; + return true; +} +} diff --git a/patch/patch.h b/patch/patch.h index a23417a..6c75fa0 100644 --- a/patch/patch.h +++ b/patch/patch.h @@ -9,6 +9,7 @@ enum type { t_bps }; type identify(const file& patch); +type identify_ext(cstring path); enum result { e_ok, @@ -183,43 +184,8 @@ public: size_t remaining() { return end-at; } //if the bpsnum is too big, number of read bytes is undefined - //does not do bounds checks, there must be at least 10 unread bytes in the buffer - bool bpsnum(size_t* out) - { - //similar to uleb128, but bpsnum adds another 1<::lslov(next^0x80, shift, &tmp)) return false; - if (safeint::addov(ret, tmp, &ret)) return false; - if (next&0x80) break; - shift+=7; - } - *out = ret; - return true; - } + //does not do bounds checks, there must be at least 10 unread bytes in the buffer (possibly trimmed off from checksums) + bool bpsnum(size_t* out); }; class membufwriter {