Fill in flipsargs::setfiles

This commit is contained in:
Alcaro 2017-01-03 11:55:09 +01:00
parent a6b0176344
commit 3512a31797
10 changed files with 393 additions and 130 deletions

View File

@ -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 +=

View File

@ -87,7 +87,7 @@ public:
// return *this;
//}
bool operator==(arrayview<T> other)
bool operator==(arrayview<T> other) const
{
if (size() != other.size()) return false;
if (this->trivial_comp)
@ -104,13 +104,13 @@ public:
}
}
bool operator!=(arrayview<T> other)
bool operator!=(arrayview<T> 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<T> slice(size_t first, size_t count) { return arrayvieww<T>(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<bool> slice(size_t first, size_t count)
array<bool> 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<typename T2> decltype(T() + T2()) join(T2 between) const
//bool operator==(arrayview<T> other)
//bool operator!=(arrayview<T> 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<T>& other)
//array(const arrayview<T>& other)
//array(array<T>&& other)
//array<T> operator=(array<T> other)
//array<T> operator=(arrayview<T> other)
//array<T>& operator+=(arrayview<T> other)
};

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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--)

View File

@ -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

337
flips.cpp
View File

@ -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<string> 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<flipsfile> 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<string> 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<args.size();i++)
{
files[i-1].path = args[i];
}
return;
}
for (size_t i=0;i<args.size();i++)
{
string arg = args[i];
if (arg[0]=='-')
{
usegui = false;
if (arg[1]=='-')
{
//long
@ -231,7 +259,9 @@ prepended bytes are either copied from a previous header, or 00)");
bool hasarg = (parts.size()==2);
bool argused = parse(parts[0], parts[1]);
//this breaks for optional arguments, but I don't have any of those
if (hasarg && !argused) usage("--"+parts[0]+" does not take an argument");
if (!hasarg && argused) usage("--"+parts[0]+" requires an argument");
}
else
{
@ -249,7 +279,9 @@ prepended bytes are either copied from a previous header, or 00)");
}
else
{
bool argused = parse(longname(opt), args[i+1]);
bool hasarg = (args.size() > 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;
}

59
patch/patch.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "patch.h"
namespace patch {
type identify(const file& patch)
{
byte head[16];
size_t len = patch.read(arrayview<byte>(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<<shift for every byte except the first
//this ensures there's only one way to encode an integer
//(other than -0, but this one decodes only unsigned)
//heavily optimized, so it looks a bit weird
uint8_t b = *(at++);
if (LIKELY(b&0x80))
{
*out = b ^ (1<<7);
return true;
}
size_t tmp = b;
b = *(at++);
tmp |= b<<7;
if (LIKELY(b&0x80))
{
*out = tmp + (1<<7) - (1<<7<<7);
return true;
}
//these weird subtractions and additions wouldn't be needed if the 0x80 bits were inverted
//but I can't change the BPS spec, so they'll have to stay
size_t ret = tmp + (1<<7) + (1<<7<<7);
size_t shift = 7+7;
while (true)
{
uint8_t next = *(at++);
if (safeint<size_t>::lslov(next^0x80, shift, &tmp)) return false;
if (safeint<size_t>::addov(ret, tmp, &ret)) return false;
if (next&0x80) break;
shift+=7;
}
*out = ret;
return true;
}
}

View File

@ -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<<shift for every byte except the first
//this ensures there's only one way to encode an integer
//really heavily optimized, so it looks a bit weird
uint8_t b = *(at++);
if (LIKELY(b&0x80))
{
*out = b ^ (1<<7);
return true;
}
size_t tmp = b;
b = *(at++);
tmp |= b<<7;
if (LIKELY(b&0x80))
{
*out = tmp + (1<<7) - (1<<7<<7);
return true;
}
//these weird subtractions and additions wouldn't be needed if the 0x80 bits were inverted
//but I can't change the BPS spec, so they'll have to stay
size_t ret = tmp + (1<<7) + (1<<7<<7);
size_t shift = 7+7;
while (true)
{
uint8_t next = *(at++);
if (safeint<size_t>::lslov(next^0x80, shift, &tmp)) return false;
if (safeint<size_t>::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 {