Update Arlib

This commit is contained in:
Alcaro 2016-08-18 02:34:41 +02:00
parent cb2ab4e9e6
commit 9908250790
17 changed files with 937 additions and 238 deletions

1
arlib.h Normal file
View File

@ -0,0 +1 @@
#include "arlib/arlib.h"

View File

@ -63,6 +63,9 @@ endif
ifneq (,$(findstring test,$(MAKECMDGOALS)))
SELFTEST = 1
endif
ifneq (,$(findstring check,$(MAKECMDGOALS)))
SELFTEST = 1
endif
OPTFLAGS := -Os -fomit-frame-pointer -fmerge-all-constants -fvisibility=hidden
OPTFLAGS += -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
@ -191,7 +194,9 @@ TRUE_LFLAGS = $(LFLAGS) -fvisibility=hidden $(CONF_LFLAGS)
#// bar(a) \
#// bar(b) \
#// bar(c)
#(2) https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 says '#pragma GCC diagnostic ignored "-Wcomment"' does nothing
# to my knowledge unreported
#(2) '#pragma GCC diagnostic ignored "-Wcomment"' does nothing
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431
TRUE_CFLAGS += -Wno-comment
TRUE_CXXFLAGS += -Wno-comment
@ -233,4 +238,7 @@ obj/arlibtest$(EXESUFFIX): $(OBJS)
test: obj/arlibtest$(EXESUFFIX)
$(TESTRUNNER) obj/arlibtest$(EXESUFFIX)
check: test
endif
#todo: replace with ./test shell script (make test/check should still exist)

View File

@ -4,14 +4,13 @@
#include "string.h"
#include "serialize.h"
//This is a streaming parser. For each node, { enter } then { exit } is returned; more enter/exit pairs may be present between them.
//For example, the document
/*
parent child=1
parent2
*/
//would yield { enter, parent, "" }, { enter, child, 1 }, { exit } { exit } { enter, parent2, "" } { exit }.
//would yield { enter, parent, "" } { enter, child, 1 } { exit } { exit } { enter, parent2, "" } { exit }.
//The parser keeps trying after an { error }, giving you a partial view of the damaged document; however,
// there are no guarantees on how much you can see, and it is likely for one error to cause many more, or misplaced nodes.
//enter/exit is always paired, even in the presense of errors.
@ -23,9 +22,12 @@ public:
int action;
cstring name;
cstring value; // or error message
// putting error first would be cleaner in the parser, but reader clarity is more important, and this name is better
};
//Since this takes a cstring, the string must be kept alive until the object is disposed.
//Remember the cstring rules: If this cstring doesn't hold a reference, don't touch its buffer until the object is disposed.
//If it's originally a string, don't worry about it.
//It is not allowed to try to stream data into this object.
bmlparser(cstring bml) : m_orig_data(bml), m_data(bml), m_exit(false) {}
event next();
@ -42,30 +44,50 @@ private:
inline bool getline();
};
//This is also streaming. It may disobey the mode if the value is not supported; for example, val!="" on bml_anon won't help you.
//It also disobeys mode <= bml_inl_col on enter(), you need node().
//This is also streaming.
//Calling exit() without a matching enter(), or finish() without closing every enter(), is undefined behavior.
class bmlwriterx {
class bmlwriter {
string m_data;
int m_indent;
int m_indent = 0;
bool m_caninline = false;
inline string indent();
public:
enum mode {
anon, // parent node
inl_eq, // parent node=value
inl_col, // parent node: value
eq, // node=value
col, // node: value
ianon, // parent node
ieq, // parent node=value
iquote, // parent node="value"
icol, // parent node: value
anon, // node
eq, // node=value
quote, // node="value"
col, // node: value
multiline // node\n :value
};
bmlwriterx() { m_indent = 0; }
void enter(cstring name, cstring val, mode m);
void exit() { m_indent--; }
//If you pass in data that's not valid for that mode (for example, val="foo bar" and mode=eq won't work),
// then it silently switches to the lowest working mode (in the above example, quote).
//Since enter() implies the tag has children, it will also disobey the inline modes; use node() for that.
void enter(cstring name, cstring val, mode m = anon);
void exit();
void linebreak();
void comment(cstring text);
void node(cstring name, cstring val, mode m);
void node(cstring name, cstring val, mode m = ianon); // Equivalent to enter()+exit(), except supports inline nodes.
//TODO: inline nodes on multilines? They're ridiculously ugly, but there are a few cases where they're useful.
//I'll decide what to do once I actually need a multiline with children. Maybe add mode multiline_i or something.
//Tells what mode will actually be used if node() is called with these parameters and in this context.
//To ask what enter() would do, call this with a non-inline mode.
mode typeof(cstring val, mode m) const;
private:
void node(cstring name, cstring val, mode m, bool enter);
static mode typeof_core(cstring val);
public:
string finish() { return m_data; }
};

View File

@ -161,6 +161,8 @@ static bool bml_parse_inline_node(cstring& data, cstring& node, bool& hasvalue,
if (nodestart == nodelen)
{
value = "Invalid node name";
while (data[nodelen]!='\n' && data[nodelen]!='\0') nodelen++;
data = data.csubstr(nodelen, ~0);
return false;
}
node = cut(data, nodestart, nodelen, 0);
@ -216,6 +218,7 @@ static bool bml_parse_inline_node(cstring& data, cstring& node, bool& hasvalue,
static bool isendl(char ch)
{
//this 32 is also a perf hack
if (ch>=32) return false;
return (ch=='\r' || ch=='\n' || ch=='\0');
}
@ -225,8 +228,7 @@ static cstring cutline(cstring& input)
//pointers are generally bad ideas, but this is such a hotspot it's worth it
const char * inputraw = input.nt();
size_t nlpos = 0;
//that 32 is also a perf hack
if (input.ntterm())
if (input.hasnt())
{
while (!isendl(inputraw[nlpos])) nlpos++;
}
@ -299,9 +301,16 @@ bmlparser::event bmlparser::next()
if (m_indent_step.size() > m_indent.length())
{
handle_indent:
if (!m_indent_step[m_indent.length()]) return (event){ error, "", "Invalid indentation depth" };
if (!m_indent_step[m_indent.length()])
{
//this may throw random mix-tab-space errors that weren't present in the original,
// but only if the document contains mix-tab-space already.
if (m_indent_step.size() > m_indent.length()) m_indent += m_indent[0];
else m_indent = m_indent.csubstr(0, ~1);
return (event){ error, "", "Invalid indentation depth" };
}
int lasttrue = m_indent_step.size()-2;
int lasttrue = m_indent_step.size()-2; // -1 for [size()] being OOB, -1 to skip the true at [size()-1] and discard it
while (lasttrue>=0 && m_indent_step[lasttrue]==false) lasttrue--;
m_indent_step.resize(lasttrue+1);
@ -493,7 +502,7 @@ bmlparser::event test4e[]={
{ e_finish }
};
static bool testbml(const char * bml, bmlparser::event* expected)
static void testbml(const char * bml, bmlparser::event* expected)
{
bmlparser parser(bml);
while (true)
@ -506,47 +515,57 @@ static bool testbml(const char * bml, bmlparser::event* expected)
assert_eq(actual.name, expected->name);
assert_eq(actual.value, expected->value);
if (expected->action == e_finish || actual.action == e_finish) return true;
if (expected->action == e_finish || actual.action == e_finish) return;
expected++;
}
}
static bool testbml_error(const char * bml)
static void testbml_error(const char * bml)
{
bmlparser parser(bml);
for (int i=0;i<100;i++)
int depth = 0;
bool error = false;
int events = 0;
while (true)
{
bmlparser::event ev = parser.next();
//printf("a=%i [%s] [%s]\n\n", ev.action, ev.name.data(), ev.value.data());
if (ev.action == e_error) return true;
if (events==999)
printf("a=%i [%s] [%s]\n\n", ev.action, ev.name.data(), ev.value.data());
if (ev.action == e_error) error = true; // any error is fine, really
if (ev.action == e_enter) depth++;
if (ev.action == e_exit) depth--;
if (ev.action == e_finish) break;
assert(depth >= 0);
events++;
assert(events < 1000); // fail on infinite error loops
}
assert(!"expected error");
assert_eq(error, true);
assert_eq(depth, 0);
}
test()
{
assert(testbml(test1, test1e));
assert(testbml(test2, test2e));
assert(testbml(test3, test3e));
assert(testbml(test4, test4e));
testcall(testbml(test1, test1e));
testcall(testbml(test2, test2e));
testcall(testbml(test3, test3e));
testcall(testbml(test4, test4e));
assert(testbml_error("*")); // invalid node name
assert(testbml_error("a=\"")); // unclosed quote
assert(testbml_error("a=\"b\"c")); // no space after closing quote
assert(testbml_error("a=\"b\"c\"")); // no space after closing quote
assert(testbml_error("a\n b\n c")); // derpy indentation
assert(testbml_error("a\n b\n\tc")); // mixed tabs and spaces
assert(testbml_error("a=b\n :c")); // two values
testcall(testbml_error("*")); // invalid node name
testcall(testbml_error("a=\"")); // unclosed quote
testcall(testbml_error("a=\"b\"c")); // no space after closing quote
testcall(testbml_error("a=\"b\"c\"")); // quote in quoted element
testcall(testbml_error("a\n b\n c")); // derpy indentation
testcall(testbml_error("a\n b\n\tc")); // mixed tabs and spaces
testcall(testbml_error("a=b\n :c")); // two values
//derpy indentation with multilines
assert(testbml_error("a\n :b\n :c"));
assert(testbml_error("a\n :b\n :c"));
assert(testbml_error("a\n :b\n c"));
assert(testbml_error("a\n :b\n c"));
assert(testbml_error("a\n :b\n\t:c"));
assert(testbml_error("a\n :b\n\tc"));
return true;
testcall(testbml_error("a\n :b\n :c"));
testcall(testbml_error("a\n :b\n :c"));
testcall(testbml_error("a\n :b\n c"));
testcall(testbml_error("a\n :b\n c"));
testcall(testbml_error("a\n :b\n\t:c"));
testcall(testbml_error("a\n :b\n\tc"));
}
#endif

View File

@ -1,38 +1,168 @@
#include "bml.h"
#include "test.h"
//This is also streaming. It may disobey the mode if the value is not supported; for example, val!="" on mode=anon won't work.
//It also disobeys mode <= inl_col on enter(), you need node() for that.
//Calling exit() without a matching enter(), or finish() without closing every enter(), is undefined behavior.
class bmlwriter {
string m_data;
int m_indent;
inline string bmlwriter::indent()
{
string ret;
char* ptr = ret.construct(m_indent*2);
memset(ptr, ' ', m_indent*2);
return ret;
}
void bmlwriter::node(cstring name, cstring val, mode m, bool enter)
{
m = typeof(val, m);
bool inlined = (m <= icol && m_caninline && !enter);
if (m_data)
{
if (inlined) m_data += " ";
else m_data += "\n"+indent();
}
public:
enum mode {
anon, // parent node
inl_eq, // parent node=value
inl_col, // parent node: value
eq, // node=value
col, // node: value
multiline // node\n :value
};
m_indent++;
bmlwriter() { m_indent = 0; }
if ((m==anon || m==eq || m==quote) && enter) m_caninline = true;
if (m==icol || m==col || m==multiline) m_caninline = false;
//for other modes, inlinability isn't affected
void enter(cstring name, cstring val, mode m); // Always uses mode=eq or higher.
void exit() { m_indent--; }
void linebreak();
void comment(cstring text);
void node(cstring name, cstring val, mode m);
switch (m)
{
case ianon:
case anon:
m_data += name;
break;
case ieq:
case eq:
m_data += name+"="+val;
break;
case iquote:
case quote:
m_data += name+"=\""+val+"\"";
break;
case icol:
case col:
m_data += name+": "+val;
break;
case multiline:
string prefix = "\n"+indent()+":";
m_data += name + prefix + val.replace("\n", prefix);
break;
}
if (!enter) m_indent--;
}
void bmlwriter::enter(cstring name, cstring val, mode m) { node(name, val, m, true); }
void bmlwriter::node(cstring name, cstring val, mode m) { node(name, val, m, false); }
void bmlwriter::exit() { m_indent--; m_caninline = false; }
bmlwriter::mode bmlwriter::typeof_core(cstring val)
{
if (val.contains("\n") || val[0]==' ' || val[0]=='\t' || val[~0]==' ' || val[~0]=='\t') return multiline;
if (val.contains("\"")) return col;
if (val.contains(" ") || val.contains("\t")) return quote;
if (val!="") return eq;
return anon;
}
bmlwriter::mode bmlwriter::typeof(cstring val, mode m) const
{
bool inlined = (m <= icol);
if (m <= icol) m = (mode)(m+4);
//Tells what mode will actually be used if node() is called with these parameters.
mode typeof(cstring val, mode m) const;
string finish() { return m_data; }
};
m = max(typeof_core(val), m);
if (inlined && m != multiline && m_caninline) return (mode)(m-4);
else return m;
}
#ifdef ARLIB_TEST
test()
{
{
bmlwriter w;
w.enter("a", "");
w.node("b", "1");
w.node("c", "");
w.exit();
assert_eq(w.finish(), "a b=1 c");
}
{
bmlwriter w;
w.enter("a", "");
w.enter("b", "");
w.enter("c", "");
w.enter("d", "");
w.exit();
w.exit();
w.exit();
w.exit();
assert_eq(w.finish(), "a\n b\n c\n d");
}
{
bmlwriter w;
w.enter("a", "foo bar");
w.node("b", "1");
w.node("c", "foo \"bar\"");
w.node("d", "");
w.exit();
assert_eq(w.finish(), "a=\"foo bar\" b=1 c: foo \"bar\"\n d");
}
{
bmlwriter w;
w.enter("a", "foo bar");
w.node("b", "1");
w.enter("c", "foo \"bar\"");
w.node("d", "");
w.exit();
w.node("e", "");
w.exit();
assert_eq(w.finish(), "a=\"foo bar\" b=1\n c: foo \"bar\"\n d\n e");
}
{
bmlwriter w;
w.enter("a", "foo\nbar");
w.node("b", "");
w.node("c", "foo\nbar");
w.enter("d", "foo\nbar");
w.exit();
w.exit();
assert_eq(w.finish(), "a\n :foo\n :bar\n b\n c\n :foo\n :bar\n d\n :foo\n :bar");
}
{
bmlwriter w;
w.node("a", "");
w.enter("b", "");
w.exit();
assert_eq(w.finish(), "a\nb");
}
{
bmlwriter w;
w.node("a", "1");
w.node("b", "2");
assert_eq(w.finish(), "a=1\nb=2"); // these must not be children of each other
}
{
bmlwriter w;
w.enter("a", "");
w.node("b", "c\nd");
w.exit();
assert_eq(w.finish(), "a\n b\n :c\n :d"); // ensure this is properly non-inlined
}
}
#endif

View File

@ -38,6 +38,7 @@ typedef void(*funcptr)();
//No attempt is made to keep any kind of backwards or forwards compatibility.
#define using(obj) for(bool FIRST=true;FIRST;FIRST=false)for(obj;FIRST;FIRST=false)
//in C++17, this becomes if(obj;true)
#define JOIN_(x, y) x ## y
#define JOIN(x, y) JOIN_(x, y)
@ -45,11 +46,6 @@ typedef void(*funcptr)();
#define STR_(x) #x
#define STR(x) STR_(x)
//some magic stolen from http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx
//C++ can be so messy sometimes...
template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#define ARRAY_SIZE(x) (sizeof(ARRAY_SIZE_CORE(x)))
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
@ -62,6 +58,42 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#define UNLIKELY(expr) (expr)
#endif
//some magic stolen from http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx
//C++ can be so messy sometimes...
template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#define ARRAY_SIZE(x) (sizeof(ARRAY_SIZE_CORE(x)))
//yep, C++ is definitely a mess. based on https://github.com/swansontec/map-macro with some changes:
//- namespaced all child macros, renamed main one
//- merged https://github.com/swansontec/map-macro/pull/3
//- merged http://stackoverflow.com/questions/6707148/foreach-macro-on-macros-arguments#comment62878935_13459454, plus ifdef
#define PPFE_EVAL0(...) __VA_ARGS__
#define PPFE_EVAL1(...) PPFE_EVAL0 (PPFE_EVAL0 (PPFE_EVAL0 (__VA_ARGS__)))
#define PPFE_EVAL2(...) PPFE_EVAL1 (PPFE_EVAL1 (PPFE_EVAL1 (__VA_ARGS__)))
#define PPFE_EVAL3(...) PPFE_EVAL2 (PPFE_EVAL2 (PPFE_EVAL2 (__VA_ARGS__)))
#define PPFE_EVAL4(...) PPFE_EVAL3 (PPFE_EVAL3 (PPFE_EVAL3 (__VA_ARGS__)))
#define PPFE_EVAL(...) PPFE_EVAL4 (PPFE_EVAL4 (PPFE_EVAL4 (__VA_ARGS__)))
#define PPFE_MAP_END(...)
#define PPFE_MAP_OUT
#define PPFE_MAP_GET_END2() 0, PPFE_MAP_END
#define PPFE_MAP_GET_END1(...) PPFE_MAP_GET_END2
#define PPFE_MAP_GET_END(...) PPFE_MAP_GET_END1
#define PPFE_MAP_NEXT0(test, next, ...) next PPFE_MAP_OUT
#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.
#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)
#endif
#define PPFE_MAP_NEXT(test, next) PPFE_MAP_NEXT1 (PPFE_MAP_GET_END test, next)
#define PPFE_MAP0(f, x, peek, ...) f(x) PPFE_MAP_NEXT (peek, PPFE_MAP1) (f, peek, __VA_ARGS__)
#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;
//PPFOREACH(STRING, foo, bar, baz)
//limited to 365 entries, but that's enough.
//requirements:
//- static_assert(false) throws something at compile time
@ -102,7 +134,7 @@ template<> struct static_assert_t<false> {};
#define static_assert(expr) static_assert(expr, #expr)
#endif
//almost C version (fails inside structs):
//almost C version (fails inside structs)
//#define static_assert(expr) \
// typedef char JOIN(static_assertion_, __COUNTER__)[(expr)?1:-1]
@ -206,6 +238,16 @@ protected:
nocopy& operator=(nocopy&&) = default;
};
class nomove : empty {
protected:
nomove() {}
~nomove() {}
nomove(const nomove&) = delete;
const nomove& operator=(const nomove&) = delete;
nomove(nomove&&) = delete;
nomove& operator=(nomove&&) = delete;
};
template<typename T>
class autoptr : nocopy {
T* ptr;
@ -228,6 +270,9 @@ public:
#else
void asprintf(char * * ptr, const char * fmt, ...);
#endif
#ifdef _WIN32
void* memmem(const void * haystack, size_t haystacklen, const void * needle, size_t needlelen);
#endif
//msvc:

182
arlib/memmem.cpp Normal file
View File

@ -0,0 +1,182 @@
#include "global.h"
#ifdef _WIN32
//from musl, http://git.musl-libc.org/cgit/musl/tree/src/string/memmem.c commit 4be6a310a7618e791615fcb8543eb2f23d47370b
//with a few C++ and warning fixes
/*
musl as a whole is licensed under the following standard MIT license:
----------------------------------------------------------------------
Copyright © 2005-2014 Rich Felker, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define _GNU_SOURCE
#include <string.h>
#include <stdint.h>
static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
{
uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1];
for (h++, k--; k; k--, hw = hw<<8 | *++h)
if (hw == nw) return (char *)h-1;
return 0;
}
static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
{
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8;
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8;
for (h+=2, k-=2; k; k--, hw = (hw|*++h)<<8)
if (hw == nw) return (char *)h-2;
return 0;
}
static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
{
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3];
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3];
for (h+=3, k-=3; k; k--, hw = hw<<8 | *++h)
if (hw == nw) return (char *)h-3;
return 0;
}
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))
#define BITOP(a,b,op) \
((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a))))
static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l)
{
size_t i, ip, jp, k, p, ms, p0, mem, mem0;
size_t byteset[32 / sizeof(size_t)] = { 0 };
size_t shift[256];
/* Computing length of needle and fill shift table */
for (i=0; i<l; i++)
BITOP(byteset, n[i], |=), shift[n[i]] = i+1;
/* Compute maximal suffix */
ip = -1; jp = 0; k = p = 1;
while (jp+k<l) {
if (n[ip+k] == n[jp+k]) {
if (k == p) {
jp += p;
k = 1;
} else k++;
} else if (n[ip+k] > n[jp+k]) {
jp += k;
k = 1;
p = jp - ip;
} else {
ip = jp++;
k = p = 1;
}
}
ms = ip;
p0 = p;
/* And with the opposite comparison */
ip = -1; jp = 0; k = p = 1;
while (jp+k<l) {
if (n[ip+k] == n[jp+k]) {
if (k == p) {
jp += p;
k = 1;
} else k++;
} else if (n[ip+k] < n[jp+k]) {
jp += k;
k = 1;
p = jp - ip;
} else {
ip = jp++;
k = p = 1;
}
}
if (ip+1 > ms+1) ms = ip;
else p = p0;
/* Periodic needle? */
if (memcmp(n, n+p, ms+1)) {
mem0 = 0;
p = MAX(ms, l-ms-1) + 1;
} else mem0 = l-p;
mem = 0;
/* Search loop */
for (;;) {
/* If remainder of haystack is shorter than needle, done */
if (z-h < (ptrdiff_t)l) return 0;
/* Check last byte first; advance by shift on mismatch */
if (BITOP(byteset, h[l-1], &)) {
k = l-shift[h[l-1]];
if (k) {
if (mem0 && mem && k < p) k = l-p;
h += k;
mem = 0;
continue;
}
} else {
h += l;
mem = 0;
continue;
}
/* Compare right half */
for (k=MAX(ms+1,mem); k<l && n[k] == h[k]; k++);
if (k < l) {
h += k-ms;
mem = 0;
continue;
}
/* Compare left half */
for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);
if (k <= mem) return (char *)h;
h += p;
mem = mem0;
}
}
void *memmem(const void *h0, size_t k, const void *n0, size_t l)
{
const unsigned char *h = (const unsigned char*)h0, *n = (const unsigned char*)n0;
/* Return immediately on empty needle */
if (!l) return (void *)h;
/* Return immediately when needle is longer than haystack */
if (k<l) return 0;
/* Use faster algorithms for short needles */
h = (const unsigned char*)memchr(h0, *n, k);
if (!h || l==1) return (void *)h;
k -= h - (const unsigned char *)h0;
if (k<l) return 0;
if (l==2) return twobyte_memmem(h, k, n);
if (l==3) return threebyte_memmem(h, k, n);
if (l==4) return fourbyte_memmem(h, k, n);
return twoway_memmem(h, h+k, n, l);
}
#endif

View File

@ -10,6 +10,10 @@
#include <sys/wait.h>
#include <linux/futex.h>
//TODO: merge sh_fd[], one is enough for all plausible purposes
//TODO: figure out where O_BENEATH went, it's not in the manpages
//TODO: split sandbox::impl to impl_parent, impl_child, impl_shared, to ensure the right one is always used
#include<errno.h>
struct sandbox::impl {

View File

@ -32,9 +32,9 @@
// are well documented, and if I miss something, strace quickly tells me what.
//
//On Linux, this requires exclusive control over SIGSYS in the child process.
//<http://lxr.free-electrons.com/ident?i=SIGSYS> (as of kernel version 4.6) shows me about fifty references to SIGSYS,
// but they're all seccomp-related (mostly in seccomp tests), #define SIGSYS 123, or otherwise uninteresting to handle,
// so it's safe to claim this signal for myself.
//All uses (according to <http://lxr.free-electrons.com/ident?i=SIGSYS>, kernel version 4.6) are either seccomp,
// hardware events on rare platforms that won't be delivered to me, or catching / passing on the signal (as opposed to raising it).
//Therefore, this requirement is safe.
//
//Chrome sandbox entry points: http://stackoverflow.com/questions/1590337/using-the-google-chrome-sandbox
@ -111,9 +111,9 @@ public:
// old shared area is deleted.
// The implementation must ensure the parent does not crash even if the child's internal
// structures are corrupt, including but not limited to size mismatch.
// This function is not thread safe.
// This function is not thread safe. Only one thread per side may enter.
// It is implementation defined which processes are charged for these bytes. It could be parent,
// child, a little of each, both, or something weirder.
// child, a little of each, both, neither, or something weirder.
void* shalloc(int index, size_t bytes);
//Convenience function, just calls the above.
@ -126,7 +126,7 @@ public:
void set_fopen_fallback(function<intptr_t(const char * path, bool write)> callback); // Child only.
//Clones a file handle into the child. The handle remains open in the parent. The child may get another ID.
//Like shalloc(), neither process returns until the other enters.
//Like shalloc(), both processes are allowed to sleep until the other enters.
void give_fd(intptr_t fd); // Parent only.
intptr_t accept_fd(); // Child only.

View File

@ -1,50 +1,251 @@
#include "serialize.h"
#include "test.h"
#include <string.h>
#include "bml.h"
#ifdef ARLIB_TEST
struct serializable_test {
int a;
int b;
/*
onserialize() {
SER(a);
SER(b) SER_HEX;
}
{ a; b; }
a=1
b=2
{ a={ b; c; } d; }
a
b=1
c=2
d=3
*/
#define SERIALIZE_CORE(member) s(STR(member), member);
#define SERIALIZE(...) template<typename T> void serialize(T& s) { PPFOREACH(SERIALIZE_CORE, __VA_ARGS__); }
class bmlserialize_impl {
bmlwriter w;
template<typename T> friend string bmlserialize(T& item);
public:
static const bool serializing = true;
template<typename T> void operator()(cstring name, T& item)
{
w.enter(name, "");
item.serialize(*this);
w.exit();
}
#define LEAF(T) void operator()(cstring name, T& item) { w.node(name, tostring(item)); }
LEAF(char);
LEAF(int);
LEAF(unsigned int);
LEAF(bool);
LEAF(float);
LEAF(time_t);
#undef LEAF
};
class serializer_test : public serializer_base<serializer_test> {
public:
int phase;
template<typename T> string bmlserialize(T& item)
{
bmlserialize_impl s;
item.serialize(s);
return s.w.finish();
}
class bmlunserialize_impl {
bmlparser p;
int pdepth = 0;
template<typename T>
void serialize(const char * name, T& member, const serialize_opts& opts)
int thisdepth = 0;
cstring thisnode;
cstring thisval;
bool matchagain;
bmlparser::event event()
{
if(0);
else if (phase==0 && !strcmp(name, "a") && member==16 && opts.hex==false) phase++;
else if (phase==1 && !strcmp(name, "b") && member==32 && opts.hex==true) phase++;
else phase=-1;
member++;
bmlparser::event ret = p.next();
if (ret.action == bmlparser::enter) pdepth++;
if (ret.action == bmlparser::exit) pdepth--;
if (ret.action == bmlparser::finish) pdepth=-2;
return ret;
}
void skipchildren()
{
while (pdepth > thisdepth) event();
}
bmlunserialize_impl(cstring bml) : p(bml) {}
template<typename T> friend T bmlunserialize(cstring bml);
template<typename T> void item(T& out)
{
while (pdepth >= thisdepth)
{
bmlparser::event ev = event();
if (ev.action == bmlparser::enter)
{
thisdepth++;
thisnode = ev.name;
thisval = ev.value;
do {
matchagain = false;
out.serialize(*this);
} while (matchagain);
thisdepth--;
skipchildren();
}
}
}
void next()
{
matchagain = false;
if (pdepth >= thisdepth)
{
thisdepth--;
skipchildren();
bmlparser::event ev = event();
if (ev.action == bmlparser::enter)
{
matchagain = true;
thisnode = ev.name;
thisval = ev.value;
}
thisdepth++;
}
}
#define LEAF(T) void item(T& out) { out = fromstring<T>(thisval); }
LEAF(char);
LEAF(int);
LEAF(unsigned int);
LEAF(bool);
LEAF(float);
LEAF(time_t);
#undef LEAF
public:
static const bool serializing = false;
template<typename T> void operator()(cstring name, T& out)
{
while (thisnode == name) // this should be a loop, in case of documents like 'foo bar=1 bar=2 bar=3'
{
item(out);
thisnode = "";
next();
}
}
};
test()
template<typename T> T bmlunserialize(cstring bml)
{
serializer_test s;
s.phase = 0;
serializable_test item;
item.a = 16;
item.b = 32;
item.serialize(s);
return s.phase==2 && item.a==17 && item.b==33;
T out{};
bmlunserialize_impl s(bml);
s.item(out);
return out;
}
//test BML serialization - not sure which file that belongs in, so just pick one
#include "bml.h"
#ifdef ARLIB_TEST
struct ser1 {
int a;
int b;
SERIALIZE(a, b);
};
struct ser2 {
ser1 c;
ser1 d;
SERIALIZE(c, d);
};
struct ser3 {
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
SERIALIZE(a, b, c, d, e, f, g, h);
};
struct ser4 {
ser3 mem;
int count = 0;
template<typename T> void serialize(T& s) { mem.serialize(s); count++; }
};
test()
{
{
ser1 item;
item.a = 1;
item.b = 2;
assert_eq(bmlserialize(item), "a=1\nb=2");
}
{
ser2 item;
item.c.a = 1;
item.c.b = 2;
item.d.a = 3;
item.d.b = 4;
assert_eq(bmlserialize(item), "c a=1 b=2\nd a=3 b=4");
}
}
test()
{
{
ser1 item = bmlunserialize<ser1>("a=1\nb=2");
assert_eq(item.a, 1);
assert_eq(item.b, 2);
}
{
ser2 item = bmlunserialize<ser2>("c a=1 b=2\nd a=3 b=4");
assert_eq(item.c.a, 1);
assert_eq(item.c.b, 2);
assert_eq(item.d.a, 3);
assert_eq(item.d.b, 4);
}
//the system should not be order-sensitive
{
ser2 item = bmlunserialize<ser2>("d b=4 a=3\nc a=1 b=2");
assert_eq(item.c.a, 1);
assert_eq(item.c.b, 2);
assert_eq(item.d.a, 3);
assert_eq(item.d.b, 4);
}
//in case of dupes, last one should win; extraneous nodes should be cleanly ignored
{
ser1 item = bmlunserialize<ser1>("a=1\nb=2\nq=0\na=3\na=4");
assert_eq(item.a, 4);
assert_eq(item.b, 2);
}
//the system is allowed to loop, but only if there's bogus or extraneous nodes
//we want O(n) runtime for a clean document, so ensure no looping
//this includes missing and duplicate elements, both of which are possible for serialized arrays
{
ser4 item = bmlunserialize<ser4>("a=1\nb=2\nd=4\ne=5\ne=5\nf=6");
assert_eq(item.count, 1);
}
}
#endif

View File

@ -1,87 +1,3 @@
#pragma once
#include "global.h"
//public API:
//for serializable classes:
#define onserialize() \
template<typename _T> \
void serialize(_T& _s)
#define SER(member) _s.execute_base(#member, member)+=serialize_opts()
//these are optional and go in front of SER, in any order
//it would be better to do this via reflection and attributes, but it doesn't seem like C++17 will have that, and Arlib is C++11 anyways.
#define SER_HEX SER_OPT(hex)
#define SER_BML(x) SER_OPT(bml, x)
//serializer options:
#define SER_OPTS(na, a) /* na = no argument to SER_name, a = has argument*/ \
na(bool, hex) \
a(int8_t, bml) \
//example:
//onserialize() { SER(width) SER_HEX; }
//for serializers:
//implement template<typename T> void serialize(const char * name, T& member, const serialize_opts& opts)
//and inherit from serializer_base<your class>, both public
//then poke opts.hex
//for an example of everything, see serialize.cpp
//implementation follows (serialize.cpp is just a test). warning: ugly as fuck
struct serialize_opts {
#define X(t, n) t n;
SER_OPTS(X, X)
#undef X
serialize_opts()
{
#define X(t, n) n=t();
SER_OPTS(X, X)
#undef X
}
#define Xn(t, n) static serialize_opts opt_##n() { serialize_opts ret; ret.n=1; return ret; }
#define Xa(t, n) static serialize_opts opt_##n(t v) { serialize_opts ret; ret.n=v; return ret; }
SER_OPTS(Xn, Xa)
#undef Xn
#undef Xa
serialize_opts operator+(const serialize_opts& right)
{
#define X(t, n) n |= right.n;
SER_OPTS(X, X)
#undef X
return *this;
}
};
template<typename Tser, typename Tmem>
struct serialize_execute {
Tser* parent;
const char * name;
Tmem& member;
serialize_execute(Tser* parent, const char * name, Tmem& member) : parent(parent), name(name), member(member) {}
};
template<typename Tser, typename Tmem>
void operator+=(const serialize_execute<Tser,Tmem>& exec, const serialize_opts& opts)
{
exec.parent->serialize(exec.name, exec.member, opts);
}
template<typename real>
class serializer_base {
public:
template<typename T>
serialize_execute<real,T> execute_base(const char * name, T& val)
{
return serialize_execute<real,T>((real*)this, name, val);
}
};
#define SER_OPT(name, ...) +serialize_opts::opt_##name(__VA_ARGS__)

View File

@ -49,6 +49,26 @@ test()
assert_eq(a, "1-78-12345678");
}
{
//ensure outline->outline also works
string a = "123456789012345";
a += "678";
assert_eq(a, "123456789012345678");
a += (const char*)a;
string b = a;
assert_eq(a, "123456789012345678123456789012345678");
assert_eq(a.substr(1,3), "23");
assert_eq(b, "123456789012345678123456789012345678");
assert_eq(a.substr(1,21), "23456789012345678123");
assert_eq(a.substr(1,~1), "2345678901234567812345678901234567");
assert_eq(a.substr(2,2), "");
assert_eq(a.substr(22,22), "");
a.replace(1,5, "-");
assert_eq(a, "1-789012345678123456789012345678");
a.replace(4,20, "-");
assert_eq(a, "1-78-12345678");
}
{
string a = "12345678";
a += a;
@ -58,5 +78,25 @@ test()
assert_eq(b, "12345678123456781234567812345678");
}
return true;
{
string a = "1abc1de1fgh1";
assert_eq(a.replace("1", ""), "abcdefgh");
assert_eq(a.replace("1", "@"), "@abc@de@fgh@");
assert_eq(a.replace("1", "@@"), "@@abc@@de@@fgh@@");
}
{
//this has thrown valgrind errors due to derpy allocations
string a = "abcdefghijklmnopqrstuvwxyz";
string b = a; // needs an extra reference
a += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
assert_eq(a, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
{
//this has also crashed, due to unshare() not respecting m_owning=false
cstring a = "aaaaaaaaaaaaaaaa";
a[0] = 'b';
assert_eq(a, "baaaaaaaaaaaaaaa");
}
}

View File

@ -171,7 +171,8 @@ public:
}
//the sizes can be 0 if you want to
//sizes are how many characters fit in the string, including the NUL
//sizes are how many characters fit in the string, excluding the NUL
//always allocates, doesn't try to inline
static char* alloc(char* prev, uint32_t prevsize, uint32_t newsize)
{
if (prevsize==0)
@ -200,7 +201,7 @@ public:
--*refcount;
char* ptr = malloc(bytes_for(newsize));
memcpy(ptr, prev, min(prevsize, newsize));
memcpy(ptr, prev-sizeof(int), min(prevsize, newsize));
*(int*)ptr = 1;
return ptr+sizeof(int);
}
@ -212,9 +213,14 @@ public:
if (inlined()) return;
if (m_owning && *(int*)(m_data-sizeof(int))==1) return;
m_owning = true;
m_data = alloc(m_data,m_len, m_len);
char* prevdat = m_data;
m_data = alloc(NULL,0, m_len);
memcpy(m_data, prevdat, m_len);
m_data[m_len] = '\0';
if (m_owning) alloc(prevdat,m_len, 0);
m_owning = true;
m_nul = true;
}
@ -233,7 +239,7 @@ public:
break;
case 1: // small->big
{
char* newptr = alloc(NULL,0, newlen+1);
char* newptr = alloc(NULL,0, newlen);
memcpy(newptr, m_inline, max_inline);
newptr[newlen] = '\0';
m_data = newptr;
@ -257,7 +263,7 @@ public:
break;
case 3: // big->big
{
m_data = alloc(m_data,m_len, newlen+1);
m_data = alloc(m_data,m_len, newlen);
m_data[newlen] = '\0';
m_len = newlen;
}
@ -286,7 +292,7 @@ public:
{
return ptr();
}
bool ntterm() const
bool hasnt() const // has nul terminator, no missing apostrophe here
{
return (inlined() || m_nul);
}
@ -448,7 +454,7 @@ public:
return ptr();
}
void replace(int32_t pos, int32_t len, const string& newdat)
void replace(int32_t pos, int32_t len, const string& newdat) // const string& is ugly, but cstring isn't declared yet.
{
//if newdat is a cstring backed by this, then modifying this invalidates that string, so it's illegal
//if newdat equals this, then the memmoves will mess things up
@ -481,6 +487,47 @@ public:
memcpy(ptr()+pos, newdat.ptr(), newlength);
}
string replace(const string& in, const string& out)
{
size_t outlen = length();
if (in.length() != out.length())
{
char* haystack = ptr();
char* haystackend = ptr()+length();
while (true)
{
haystack = (char*)memmem(haystack, haystackend-haystack, in.ptr(), in.length());
if (!haystack) break;
haystack += in.length();
outlen += out.length(); // outlen-inlen is type uint - bad idea
outlen -= in.length();
}
}
string ret;
char* retptr = ret.construct(outlen);
char* prev = ptr();
char* myend = ptr()+length();
while (true)
{
char* match = (char*)memmem(prev, myend-prev, in.ptr(), in.length());
if (!match) break;
memcpy(retptr, prev, match-prev);
retptr += match-prev;
prev = match + in.length();
memcpy(retptr, out.ptr(), out.length());
retptr += out.length();
}
memcpy(retptr, prev, myend-prev);
return ret;
}
string& operator+=(const char * right)
{
append(right, strlen(right));
@ -492,6 +539,12 @@ public:
append(right.ptr(), right.length());
return *this;
}
string& operator+=(char right)
{
append(&right, 1);
return *this;
}
#endif
//Shared between all string implementations.
@ -513,15 +566,15 @@ public:
operator const char * () const { return data(); }
private:
class charref {
class charref : nocopy {
friend class string;
string* parent;
uint32_t index;
charref(string* parent, uint32_t index) : parent(parent), index(index) {}
public:
charref& operator=(char ch) { parent->setchar(index, ch); return *this; }
operator char() { return parent->getchar(index); }
charref(string* parent, uint32_t index) : parent(parent), index(index) {}
};
friend class charref;
@ -541,6 +594,7 @@ public:
return string(data()+start, end-start);
}
inline cstring csubstr(int32_t start, int32_t end) const;
inline bool contains(cstring other) const;
};
static inline bool string_eq(const char * left, uint32_t leftlen, const char * right, uint32_t rightlen)
@ -561,6 +615,10 @@ inline string operator+(string&& left, const string& right) { left+=right; retur
inline string operator+(const string& left, const string& right) { string ret=left; ret+=right; return ret; }
inline string operator+(const char * left, const string& right) { string ret=left; ret+=right; return ret; }
inline string operator+(string&& left, char right) { left+=right; return left; }
inline string operator+(const string& left, char right) { string ret=left; ret+=right; return ret; }
inline string operator+(char left, const string& right) { string ret; ret[0]=left; ret+=right; return ret; }
class cstring : public string {
friend class string;
public:
@ -586,6 +644,11 @@ inline cstring string::csubstr(int32_t start, int32_t end) const
else return cstring(nt()+start, end-start, (m_nul && (uint32_t)end == m_len));
}
inline bool string::contains(cstring other) const
{
return memmem(this->ptr(), this->length(), other.ptr(), other.length());
}
//TODO
class wstring : public string {
mutable uint32_t pos_bytes;

View File

@ -6,9 +6,15 @@ inline string tostring(cstring s) { return s; }
inline string tostring(const char * s) { return s; }
inline string tostring(int val) { char ret[16]; sprintf(ret, "%i", val); return ret; }
template<typename T> inline T fromstring(string s);
template<> inline string fromstring<string>(string s) { return s; }
template<> inline cstring fromstring<cstring>(string s) { return s; }
template<typename T> inline T fromstring(cstring s);
template<> inline string fromstring<string>(cstring s) { return s; }
template<> inline cstring fromstring<cstring>(cstring s) { return s; }
//no const char *, their lifetime is unknowable
template<> inline int fromstring<int>(string s) { return atoi(s); }
template<> inline int fromstring<int>(cstring s) { return strtol(s, NULL, 0); }
template<> inline long int fromstring<long int>(cstring s) { return strtol(s, NULL, 0); }
template<> inline unsigned int fromstring<unsigned int>(cstring s) { return strtoul(s, NULL, 0); }
template<> inline float fromstring<float>(cstring s) { return strtod(s, NULL); }
template<> inline char fromstring<char>(cstring s) { return s[0]; }
template<> inline bool fromstring<bool>(cstring s) { return s=="true"; }

View File

@ -1,15 +1,16 @@
#ifdef ARLIB_TEST
#include "test.h"
#include "array.h"
struct testlist {
bool(*func)();
void(*func)();
const char * name;
testlist* next;
};
static testlist* g_testlist;
_testdecl::_testdecl(bool(*func)(), const char * name)
_testdecl::_testdecl(void(*func)(), const char * name)
{
testlist* next = malloc(sizeof(testlist));
next->func = func;
@ -18,27 +19,80 @@ _testdecl::_testdecl(bool(*func)(), const char * name)
g_testlist = next;
}
static bool thisfail;
static array<int> callstack;
void _teststack_push(int line) { callstack.append(line); }
void _teststack_pop() { callstack.resize(callstack.size()-1); }
static string stack(int top)
{
string ret = " (line "+tostring(top);
for (int i=callstack.size();i>=0;i--)
{
ret += " from "+tostring(callstack[i]);
}
return ret+")";
}
static void _testfail(cstring why)
{
if (!thisfail) puts(why); // discard multiple failures from same test, they're probably caused by same thing
thisfail = true;
}
void _testfail(cstring why, int line)
{
_testfail(why+stack(line));
}
void _testeqfail(cstring name, int line, cstring expected, cstring actual)
{
if (expected.contains("\n") || actual.contains("\n"))
{
_testfail("\nFailed assertion "+name+stack(line)+"\nexpected:\n"+expected+"\nactual:\n"+actual);
}
else
{
_testfail("\nFailed assertion "+name+stack(line)+": expected "+expected+", got "+actual);
}
}
#undef main // the real main is #define'd to something stupid on test runs
int main(int argc, char* argv[])
{
int count[2]={0,0};
//flip list backwards
//order of static initializers is implementation defined, but this makes output better under gcc
testlist* test = g_testlist;
g_testlist = NULL;
while (test)
{
testlist* next = test->next;
test->next = g_testlist;
g_testlist = test;
test = next;
}
test = g_testlist;
while (test)
{
testlist* next = test->next;
printf("Testing %s...", test->name);
bool pass = test->func();
count[pass]++;
if (pass) puts(" pass");
fflush(stdout);
thisfail = false;
test->func();
count[thisfail]++;
if (!thisfail) puts(" pass");
free(test);
test = next;
}
printf("Passed %i, failed %i\n", count[1], count[0]);
printf("Passed %i, failed %i\n", count[0], count[1]);
return 0;
}
test()
{
return true;
}
test() {}
test() {}
#endif

View File

@ -8,27 +8,35 @@
class _testdecl {
public:
_testdecl(bool(*func)(), const char * name);
_testdecl(void(*func)(), const char * name);
};
void _testfail(cstring name, int line);
void _testeqfail(cstring name, int line, cstring expected, cstring actual);
void _teststack_push(int line);
void _teststack_pop();
#define TESTFUNCNAME JOIN(_testfunc, __LINE__)
#define test() \
static bool _testfunc##__LINE__(); \
static _testdecl _testdeclv(_testfunc##__LINE__, __FILE__ ":" STR(__LINE__)); \
static bool _testfunc##__LINE__()
#define assert(x) do { if (!(x)) { puts("\nFailed assertion " #x); return false; } } while(0)
static void TESTFUNCNAME(); \
static _testdecl JOIN(_testdecl, __LINE__)(TESTFUNCNAME, __FILE__ ":" STR(__LINE__)); \
static void TESTFUNCNAME()
#define assert(x) do { if (!(x)) { _testfail("\nFailed assertion " #x, __LINE__); return; } } while(0)
#define assert_eq(x,y) do { \
if ((x) != (y)) \
{ \
printf("\nFailed assertion " #x " == " #y " (line " STR(__LINE__) "): " \
"expected %s, got %s\n", (const char*)tostring(y), (const char*)tostring(x)); \
return false; \
_testeqfail(#x " == " #y, __LINE__, tostring(y), tostring(x)); \
return; \
} \
} while(0)
#define testcall(x) _teststack_push(__LINE__),x,_teststack_pop()
#else
#define test() static bool MAYBE_UNUSED _testfunc_##__LINE__()
#define test() static void MAYBE_UNUSED JOIN(_testfunc_, __LINE__)()
#define assert(x)
#define assert_eq(x,y)
#define testcall(x) x
#endif

View File

@ -18,17 +18,17 @@ multiple patches -> error
anything else -> error
allow -a and -c for overriding this
allow applying IPS, UPS and BPS, but only create BPS
allow applying IPS, UPS and BPS, but only create BPS in GUI; CLI supports everything (maybe an advanced mode that exposes everything?)
only use the delta creator; moremem is worthless, remove it
also need this feature:
also need this new feature:
--romhead=512 - discard 512 leading bytes in the ROM before sending to patcher
--patchhead=512 - prepend 512 leading 00s before patching, discard afterwards
--outhead=512 - prepend 512 leading 00s after patching
if both romhead and patchhead are nonzero, replace the leading min(romhead,patchhead) 00s with data from the rom; same for outhead
if patchhead or romhead isn't set, it's 0
if outhead is not set, it's romhead
if none are set, romsize modulo 32768 is 512, and file extension is sfc or smc, set romhead to 512
if none are set, romsize modulo 32768 is 512, and file extension is sfc or smc, set rom/outhead to 512 and patchhead to 0
if patching fails, retry with all headers 0 and throw a warning
*/