mirror of
https://github.com/Alcaro/Flips.git
synced 2026-04-05 00:44:51 -05:00
Fill in flipsargs::setfiles
This commit is contained in:
parent
a6b0176344
commit
3512a31797
|
|
@ -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 +=
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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--)
|
||||
|
|
|
|||
|
|
@ -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
337
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<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
59
patch/patch.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user