mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-07 10:06:09 -05:00
More updates
This commit is contained in:
@@ -11,21 +11,15 @@ protected:
|
||||
T * items; // not const, despite not necessarily being writable; this makes arrayvieww/array a lot simpler
|
||||
size_t count;
|
||||
|
||||
//void clone(const arrayview<T>& other)
|
||||
//{
|
||||
// this->count=other.count;
|
||||
// this->items=other.items;
|
||||
//}
|
||||
|
||||
protected:
|
||||
static const bool trivial_cons = std::is_trivial<T>::value; // constructor is memset(0)
|
||||
#if __GNUC__ >= 5
|
||||
static const bool trivial_copy = std::is_trivially_copyable<T>::value;
|
||||
static const bool trivial_copy = std::is_trivially_copyable<T>::value; // copy constructor is memcpy
|
||||
#else
|
||||
static const bool trivial_copy = trivial_cons; // copy constructor is memcpy
|
||||
static const bool trivial_copy = trivial_cons;
|
||||
#endif
|
||||
//static const bool trivial_comp = std::has_unique_object_representations<T>::value;
|
||||
static const bool trivial_comp = std::is_integral<T>::value;
|
||||
static const bool trivial_comp = std::is_integral<T>::value; // comparison operator is memcmp
|
||||
|
||||
public:
|
||||
const T& operator[](size_t n) const { return items[n]; }
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
//}
|
||||
|
||||
|
||||
static long pagesize;
|
||||
static long pagesize = sysconf(_SC_PAGESIZE);
|
||||
|
||||
namespace {
|
||||
class file_unix : public file::impl {
|
||||
@@ -205,8 +205,5 @@ bool file::unlink(cstring filename)
|
||||
}
|
||||
//#endif
|
||||
|
||||
void _window_init_file()
|
||||
{
|
||||
pagesize = sysconf(_SC_PAGESIZE);
|
||||
}
|
||||
void _window_init_file() {}
|
||||
#endif
|
||||
|
||||
@@ -97,6 +97,7 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
||||
#ifdef _MSC_VER
|
||||
//this version doesn't work on GCC, it makes PPFE_MAP0 not get expanded the second time and quite effectively stops everything.
|
||||
//but completely unknown guy says it's required on MSVC, so I'll trust that and ifdef it
|
||||
//pretty sure one of them violate the C99/C++ specifications, but I have no idea which of them, nor what Clang does
|
||||
#define PPFE_MAP_NEXT1(test, next) PPFE_EVAL0(PPFE_MAP_NEXT0 (test, next, 0))
|
||||
#else
|
||||
#define PPFE_MAP_NEXT1(test, next) PPFE_MAP_NEXT0 (test, next, 0)
|
||||
@@ -106,7 +107,7 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
||||
#define PPFE_MAP1(f, x, peek, ...) f(x) PPFE_MAP_NEXT (peek, PPFE_MAP0) (f, peek, __VA_ARGS__)
|
||||
#define PPFOREACH(f, ...) PPFE_EVAL (PPFE_MAP1 (f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
|
||||
//usage:
|
||||
//#define STRING(x) char const *x##_string = #x;
|
||||
//#define STRING(x) const char * x##_string = #x;
|
||||
//PPFOREACH(STRING, foo, bar, baz)
|
||||
//limited to 365 entries, but that's enough.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
struct window_x11_info window_x11;
|
||||
#endif
|
||||
|
||||
void window_init(int * argc, char * * argv[])
|
||||
static bool window_init(bool require, int * argc, char * * argv[])
|
||||
{
|
||||
//struct rlimit core_limits;core_limits.rlim_cur=core_limits.rlim_max=64*1024*1024;setrlimit(RLIMIT_CORE,&core_limits);
|
||||
#ifdef DEBUG
|
||||
@@ -59,8 +59,14 @@ g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING
|
||||
#ifdef ARGUIPROT_X11
|
||||
XInitThreads();
|
||||
#endif
|
||||
gtk_init(argc, argv);
|
||||
_window_init_file();
|
||||
if (require)
|
||||
{
|
||||
gtk_init(argc, argv);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!gtk_init_check(argc, argv)) return false;
|
||||
}
|
||||
//gdk_window_add_filter(NULL,scanfilter,NULL);
|
||||
//#ifndef NO_ICON
|
||||
// struct image img;
|
||||
@@ -74,6 +80,23 @@ g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING
|
||||
window_x11.root=gdk_x11_get_default_root_xwindow();//alternatively XRootWindow(window_x11.display, window_x11.screen)
|
||||
#endif
|
||||
errno=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void window_init(int * argc, char * * argv[])
|
||||
{
|
||||
window_init(true, argc, argv);
|
||||
}
|
||||
|
||||
bool window_try_init(int * argc, char * * argv[])
|
||||
{
|
||||
return window_init(false, argc, argv);
|
||||
}
|
||||
|
||||
bool window_attach_console()
|
||||
{
|
||||
//nothing to do
|
||||
return getenv("TERM");
|
||||
}
|
||||
|
||||
//file* file::create(const char * filename)
|
||||
|
||||
@@ -10,13 +10,17 @@
|
||||
#include "../wutf/wutf.h"
|
||||
#endif
|
||||
|
||||
//Number of ugly hacks: 5
|
||||
//Number of ugly hacks: 6
|
||||
//If a status bar item is right-aligned, a space is appended.
|
||||
//The status bar is created with WS_DISABLED.
|
||||
//WM_SYSCOMMAND is sometimes ignored.
|
||||
//I have to keep track of the mouse position so I can ignore various bogus instances of WM_MOUSEMOVE.
|
||||
//I have to undefine 'bind' before including any Windows header. I suspect something is including winsock.
|
||||
//Console handling under Windows is a mess. (But launching a GUI app from a Linux console isn't much better...)
|
||||
|
||||
//Microsoft dropped Windows XP at April 8, 2014, after an unusually long support period. That is well above two years ago.
|
||||
//Vista will die on April 11, 2017. But its user count is so low I don't care about dropping that either.
|
||||
//Therefore, I have no reason to continue caring about it working.
|
||||
//Incompatibility levels:
|
||||
//Level 0 - a feature works as intended
|
||||
//Level 1 - a feature is usable, but behaves weirdly
|
||||
@@ -40,7 +44,7 @@
|
||||
//Level 4: printf dislikes z (size_t) size specifiers; they must be behind #ifdef DEBUG, or turned into "I" via #define
|
||||
// NOTE: This is present on Vista too. z requires 7 or higher.
|
||||
//Level 5: 64-bit programs dislike XP (there are 32bit Vista/7/8, but Vista is practically dead, as is 32bit 7+)
|
||||
//Level 5: SRWLOCK is Vista+.
|
||||
//Level 5: SRWLOCK is Vista+
|
||||
|
||||
//static LARGE_INTEGER timer_freq;
|
||||
|
||||
@@ -62,6 +66,35 @@ void window_init(int * argc, char * * argv[])
|
||||
//QueryPerformanceFrequency(&timer_freq);
|
||||
}
|
||||
|
||||
bool window_try_init(int * argc, char * * argv[])
|
||||
{
|
||||
window_init(argc, argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool window_attach_console()
|
||||
{
|
||||
//doesn't create a new console if not launched from one, it'd go away on app exit anyways
|
||||
//doesn't like being launched from cmd; cmd wants to run a new command if spawning a gui app
|
||||
// I can't make it not be a gui app, that flashes a console; it acts sanely from batch files
|
||||
//windows consoles are, like so much else, a massive mess
|
||||
|
||||
bool claimstdin=(GetFileType(GetStdHandle(STD_INPUT_HANDLE))==FILE_TYPE_UNKNOWN);
|
||||
bool claimstdout=(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE))==FILE_TYPE_UNKNOWN);
|
||||
bool claimstderr=(GetFileType(GetStdHandle(STD_ERROR_HANDLE))==FILE_TYPE_UNKNOWN);
|
||||
|
||||
if (claimstdin || claimstdout || claimstderr) AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
|
||||
if (claimstdin) freopen("CONIN$", "rt", stdin);
|
||||
if (claimstdout) freopen("CONOUT$", "wt", stdout);
|
||||
if (claimstderr) freopen("CONOUT$", "wt", stderr);
|
||||
|
||||
if (claimstdout) fputc('\r', stdout);
|
||||
if (claimstderr) fputc('\r', stderr);
|
||||
|
||||
return GetConsoleWindow();
|
||||
}
|
||||
|
||||
#if 0
|
||||
file* file::create(const char * filename)
|
||||
{
|
||||
|
||||
@@ -13,11 +13,18 @@ class widget_base;
|
||||
//This must be called before calling any other window_*, before creating any interface that does any I/O, before calling anything from
|
||||
// the malloc() family, and before using argc/argv; basically, before doing anything else. It should be the first thing main() does.
|
||||
//It does the following actions, in whatever order makes sense:
|
||||
//- Initialize the window system, if needed
|
||||
//- Initialize the window system, if needed; on failure, terminates the process
|
||||
//- Read off any arguments it recognizes (if any), and delete them; for example, it takes care of --display and a few others on GTK+
|
||||
//- Convert argv[0] to the standard path format, if needed (hi Windows)
|
||||
void window_init(int * argc, char * * argv[]);
|
||||
|
||||
//Returns false if the window system couldn't be initialized, rather than terminating.
|
||||
bool window_try_init(int * argc, char * * argv[]);
|
||||
|
||||
//On Windows, attaches stdout/stderr to the console of the launching process. On Linux, does nothing.
|
||||
//On both, returns whether the process is currently in a terminal. Returns true if I/O is redirected.
|
||||
bool window_attach_console();
|
||||
|
||||
//window toolkit is not choosable at runtime
|
||||
//It is safe to interact with this window while inside its callbacks, with the exception that you may not free it.
|
||||
//You may also not use window_run_*().
|
||||
|
||||
@@ -70,20 +70,21 @@ test()
|
||||
|
||||
bool aropengl::hasExtension(const char * ext)
|
||||
{
|
||||
int major = strtol((char*)this->GetString(GL_VERSION), NULL, 0);
|
||||
aropengl& gl = *this;
|
||||
int major = strtol((char*)gl.GetString(GL_VERSION), NULL, 0);
|
||||
if (major >= 3)
|
||||
{
|
||||
GLint n;
|
||||
this->GetIntegerv(GL_NUM_EXTENSIONS, &n);
|
||||
for (GLint i=0;i<n;i++)
|
||||
{
|
||||
if (!strcmp((char*)this->GetStringi(GL_EXTENSIONS, i), ext)) return true;
|
||||
if (!strcmp((char*)gl.GetStringi(GL_EXTENSIONS, i), ext)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return strtoken((char*)this->GetString(GL_EXTENSIONS), ext);
|
||||
return strtoken((char*)gl.GetString(GL_EXTENSIONS), ext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,10 +138,11 @@ static void APIENTRY debug_cb(GLenum source, GLenum type, GLuint id, GLenum seve
|
||||
|
||||
void aropengl::enableDefaultDebugger(FILE* out)
|
||||
{
|
||||
aropengl& gl = *this;
|
||||
if (!out) out = stderr;
|
||||
|
||||
this->DebugMessageCallback((GLDEBUGPROC)debug_cb, out);//some headers have 'const' on the userdata, some don't
|
||||
gl.DebugMessageCallback((GLDEBUGPROC)debug_cb, out);//some headers have 'const' on the userdata, some don't
|
||||
//https://www.opengl.org/sdk/docs/man/html/glDebugMessageCallback.xhtml says it shouldn't be const
|
||||
this->DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
|
||||
this->Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
gl.DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
|
||||
gl.Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
|
||||
}
|
||||
|
||||
@@ -107,4 +107,31 @@ test()
|
||||
assert_eq(b, "[123]");
|
||||
assert_eq(c, "[123]");
|
||||
}
|
||||
|
||||
{
|
||||
string a = "baaaaaaaaaaaaaaa";
|
||||
array<string> b;
|
||||
|
||||
b = a.split("a");
|
||||
assert_eq(b[0], "b");
|
||||
assert_eq(b[1], "");
|
||||
assert_eq(b[15], "");
|
||||
assert_eq(b.size(), 16);
|
||||
|
||||
b = a.split("aa");
|
||||
assert_eq(b.size(), 8);
|
||||
assert_eq(b[0], "b");
|
||||
assert_eq(b[1], "");
|
||||
assert_eq(b[6], "");
|
||||
assert_eq(b[7], "a");
|
||||
|
||||
b = a.split<1>("aa");
|
||||
assert_eq(b.size(), 2);
|
||||
assert_eq(b[0], "b");
|
||||
assert_eq(b[1], "aaaaaaaaaaaaa");
|
||||
|
||||
b = a.split<1>("c");
|
||||
assert_eq(b.size(), 1);
|
||||
assert_eq(b[0], "baaaaaaaaaaaaaaa");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +429,29 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
//can't create csplit without things blowing up
|
||||
//limit is maximum number of cuts
|
||||
array<string> split(const string& sep, size_t limit) const
|
||||
{
|
||||
array<string> ret;
|
||||
const uint8_t * data = ptr();
|
||||
const uint8_t * dataend = ptr()+length();
|
||||
|
||||
while (ret.size() < limit)
|
||||
{
|
||||
const uint8_t * next = (uint8_t*)memmem(data, dataend-data, sep.ptr(), sep.length());
|
||||
if (!next) break;
|
||||
ret.append(arrayview<uint8_t>(data, next-data));
|
||||
data = next+sep.length();
|
||||
}
|
||||
ret.append(arrayview<uint8_t>(data, dataend-data));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t limit>
|
||||
array<string> split(const string& sep) const { return split(sep, limit); }
|
||||
array<string> split(const string& sep) const { return split(sep, SIZE_MAX); }
|
||||
|
||||
private:
|
||||
class noinit {};
|
||||
string(noinit) {}
|
||||
@@ -440,6 +463,10 @@ public:
|
||||
string(const char * str) { init_from(str); }
|
||||
//string(const uint8_t * str, uint32_t len) { init_from(str, len); }
|
||||
string(arrayview<uint8_t> bytes) { init_from(bytes); }
|
||||
string(arrayview<char> chars)
|
||||
{
|
||||
init_from_nocopy(arrayview<uint8_t>((uint8_t*)chars.ptr(), chars.size()));
|
||||
}
|
||||
string& operator=(const string& other) { release(); init_from(other); return *this; }
|
||||
string& operator=(const char * str) { release(); init_from(str); return *this; }
|
||||
~string() { release(); }
|
||||
@@ -509,16 +536,10 @@ public:
|
||||
else return "\xEF\xBF\xBD";
|
||||
return ret;
|
||||
}
|
||||
|
||||
//Implementation detail of the equality operators. Don't use.
|
||||
static inline bool s_eq(arrayview<byte> left, arrayview<byte> right)
|
||||
{
|
||||
return (left.size()==right.size() && !memcmp(left.ptr(), right.ptr(), left.size()));
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const string& left, const char * right ) { return string::s_eq(left.bytes(), arrayview<byte>((uint8_t*)right,strlen(right))); }
|
||||
inline bool operator==(const string& left, const string& right) { return string::s_eq(left.bytes(), right.bytes()); }
|
||||
inline bool operator==(const string& left, const char * right ) { return left.bytes() == arrayview<byte>((uint8_t*)right,strlen(right)); }
|
||||
inline bool operator==(const string& left, const string& right) { return left.bytes() == right.bytes(); }
|
||||
inline bool operator==(const char * left, const string& right) { return operator==(right, left); }
|
||||
inline bool operator!=(const string& left, const char * right ) { return !operator==(left, right); }
|
||||
inline bool operator!=(const string& left, const string& right) { return !operator==(left, right); }
|
||||
@@ -545,9 +566,17 @@ public:
|
||||
cstring(const char * str) : string(noinit()) { init_from_nocopy(str); }
|
||||
//cstring(const uint8_t * str, uint32_t len) : string(noinit()) { init_from_nocopy(str, len); }
|
||||
cstring(arrayview<uint8_t> bytes) : string(noinit()) { init_from_nocopy(bytes); }
|
||||
cstring(arrayview<char> chars) : string(noinit())
|
||||
{
|
||||
init_from_nocopy(arrayview<uint8_t>((uint8_t*)chars.ptr(), chars.size()));
|
||||
}
|
||||
private:
|
||||
//don't use arrayview, if (nul) then it uses len+1 bytes
|
||||
cstring(const uint8_t * str, uint32_t len, bool nul) : string(noinit()) { init_from_nocopy(arrayview<byte>(str, len)); if (!inlined()) m_nul=nul; }
|
||||
cstring(const uint8_t * str, uint32_t len, bool nul) : string(noinit())
|
||||
{
|
||||
init_from_nocopy(arrayview<byte>(str, len));
|
||||
if (!inlined()) m_nul=nul;
|
||||
}
|
||||
public:
|
||||
|
||||
cstring& operator=(const cstring& other) { release(); init_from_nocopy(other); return *this; }
|
||||
|
||||
@@ -96,6 +96,7 @@ extern "C" {
|
||||
|
||||
#ifdef _WIN32
|
||||
//Main function; this one does the actual magic. Call this as early as possible.
|
||||
//If you're using WuTF as part of Arlib, there's no need to call this manually; window_init() does.
|
||||
void WuTF_enable();
|
||||
|
||||
//Converts argc/argv to UTF-8. Uses only documented functions, so it has zero chance of blowing up.
|
||||
|
||||
335
flips.cpp
335
flips.cpp
@@ -6,15 +6,19 @@ a/a.smc b/b.bps -> -a b/b.bps a/a.smc b/b.smc
|
||||
a/a.bps b/b.smc c/c.sfc -> -a a/a.bps b/b.smc c/c.sfc
|
||||
-c a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
|
||||
a/a.smc b/b.smc c/c.bps -> -c a/a.smc b/b.smc c/c.bps
|
||||
a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
|
||||
a/a.smc -> error
|
||||
a/a.smc b/b.smc -> error
|
||||
a/a.bps b/b.bps -> error
|
||||
(anything) -m b/b.txt -> extract or insert manifest here
|
||||
a/a.bps -m b/b.txt -> extract manifest only
|
||||
a/a.bps . -> query database
|
||||
a/a.bps $ -> query database
|
||||
-c . a/a.smc a/a.bps -> pick best match from database
|
||||
-c . a/a.smc -> -c . a/a.smc a/a.bps
|
||||
-c a/a.smc -> -c . a/a.smc
|
||||
(null) -> launch gui
|
||||
a/a.bps -> launch patch wizard
|
||||
anything else -> error
|
||||
flips-c a/a.smc -> -c . a/a.smc a/a.bps
|
||||
|
||||
--db -> print database
|
||||
--db a/a.smc b/b.smc -> add to database
|
||||
@@ -28,6 +32,7 @@ a/a.smc --db -> error
|
||||
-s --silent - don't print anything on success, also silence BPS create progress (but do print on failure)
|
||||
-h -? --help -v --version - exists
|
||||
--ips --bps - removed, use the correct extensions
|
||||
--ignore-checksum - allow applying patch to wrong files (DANGEROUS)
|
||||
|
||||
replace --exact:
|
||||
--inhead=512 - discard 512 leading bytes in the infile before sending to patcher
|
||||
@@ -46,10 +51,21 @@ on failed application, or successful IPS or UPS application, do nothing
|
||||
~/.config/flips.cfg
|
||||
#Floating IPS configuration
|
||||
#Version 2.00
|
||||
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.sfc
|
||||
database crc32=a31bead4 size=524800 path=/home/alcaro/smw.smc
|
||||
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.smc # SMCs have two entries
|
||||
database crc32=b19ed489 size=524288 path=/home/alcaro/smw.sfc # duplicates are fine
|
||||
assoc-target=ask # or auto or auto-exec
|
||||
create-show-all=true # affects whether Create Patch (GUI) defaults to all files, or only common ROMs
|
||||
create-show-all=true # affects whether Create Patch (GUI) defaults to all files, or only common ROMs; both in and out
|
||||
create-auto-source=true # if source rom can't be found, asks for that after target
|
||||
|
||||
auto pick source rom:
|
||||
load first 1MB from source
|
||||
for each file in database, except duplicates:
|
||||
load first 1MB
|
||||
check how many bytes are the same
|
||||
if exactly one file has >1/8 matching, and the rest are <1/32, use that
|
||||
otherwise error
|
||||
behind off-by-default flag
|
||||
|
||||
GUI is same as Flips 1.31, except
|
||||
- no IPS creation
|
||||
@@ -78,72 +94,261 @@ bps spec:
|
||||
http://wayback.archive.org/web/20110911111128/http://byuu.org/programming/bps/
|
||||
*/
|
||||
|
||||
//static void usage()
|
||||
//{
|
||||
// //GUIClaimConsole();
|
||||
// puts(
|
||||
// // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
// "usage:\n"
|
||||
// " "
|
||||
//#ifndef FLIPS_CLI
|
||||
// "flips\n"
|
||||
// "or flips patch.ips\n"
|
||||
// "or "
|
||||
//#endif
|
||||
// "flips [--apply] [--exact] patch.bps rom.smc [outrom.smc]\n"
|
||||
// "or flips [--create] [--exact] [--bps | --bps-linear | --ips] clean.smc\n"
|
||||
// " hack.smc [patch.bps]\n"
|
||||
//#ifndef FLIPS_CLI
|
||||
// "(for scripting, only the latter two are sensible)\n"
|
||||
//#endif
|
||||
// "(patch.ips is valid in all cases patch.bps is)\n"
|
||||
// "\n"
|
||||
// // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
// "options:\n"
|
||||
// "-a --apply: apply patch (default if given two arguments)\n"
|
||||
// "-c --create: create patch (default if given three arguments)\n"
|
||||
// "-I --info: BPSes contain information about input and output roms, print it\n"
|
||||
// //" also estimates how much of the source file is retained\n"
|
||||
// //" anything under 400 is fine, anything over 600 should be treated with suspicion\n"
|
||||
// "-i --ips, -b -B --bps --bps-delta, --bps-linear, --bps-delta-moremem:\n"
|
||||
// " create this patch format instead of guessing based on file extension\n"
|
||||
// " ignored when applying\n"
|
||||
// " bps formats:\n"
|
||||
// " delta is the recommended one; it's a good balance between creation time and\n"
|
||||
// " patch size\n"
|
||||
// " -b and -B both refer to this, for backwards compatibility reasons\n"
|
||||
// " delta-moremem is usually slightly faster than delta, but uses about twice\n"
|
||||
// " as much memory; it gives identical patches to delta\n"
|
||||
// " linear is the fastest, but tends to give pretty big patches\n"
|
||||
// "--exact: do not remove SMC headers when applying or creating a BPS patch\n"
|
||||
// " (ignored for IPS)\n"
|
||||
// "--ignore-checksum: accept checksum mismatches when applying a BPS patch\n"
|
||||
// "-m or --manifest: emit or insert a manifest file as romname.bml\n"
|
||||
// " (valid only for BPS)\n"
|
||||
// "-mfilename or --manifest=filename: emit or insert a manifest file exactly here\n"
|
||||
// "-h -? --help: show this information\n"
|
||||
// "-v --version: show application version\n"
|
||||
// // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
// );
|
||||
// exit(0);
|
||||
//}
|
||||
|
||||
static void a_apply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
class flipsargs {
|
||||
public:
|
||||
enum { m_default, m_apply, m_create, m_info, m_db } mode = m_default;
|
||||
array<string> fnames;
|
||||
|
||||
string manifest;
|
||||
bool silent = false;
|
||||
bool ignorechecksum = false;
|
||||
|
||||
bool autohead = true;
|
||||
size_t inhead = 0;
|
||||
size_t patchhead = 0;
|
||||
size_t outhead = 0;
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
string errormsg;
|
||||
#endif
|
||||
|
||||
void usage(cstring error)
|
||||
{
|
||||
#ifndef ARLIB_TEST
|
||||
window_attach_console();
|
||||
if (error) puts("error: "+error);
|
||||
puts(R"(command line usage:
|
||||
flips -a|--apply a.bps b.smc [c.sfc]
|
||||
apply patch; default output file is a.smc
|
||||
flips -c|--create a.smc b.smc [c.bps]
|
||||
create patch; default output file is b.bps
|
||||
flips -i|--info a.bps
|
||||
print some information about this patch, such as its expected input file
|
||||
with only filenames, guess either --apply or --create
|
||||
bps, ips and ups patches can be applied, bps and ips can be created
|
||||
|
||||
database:
|
||||
if the source file has been used before, it can be shortened to . (bps only)
|
||||
usable both when creating and applying
|
||||
the database can be manipulated with
|
||||
flips --db
|
||||
print list of known inputs
|
||||
flips --db [-]a.smc [[-]b.smc]
|
||||
add or remove files from database
|
||||
|
||||
additional options:
|
||||
-m foo.xml or --manifest=foo.xml: extract or insert bps manifest here
|
||||
-s --silent: remain silent on success
|
||||
--ignore-checksum: allow applying a bps patch to wrong input file
|
||||
--inhead=512: discard the first 512 bytes of the input file
|
||||
--patchhead=512: prepend 512 bytes before patching, discard afterwards
|
||||
--outhead=512: prepend 512 bytes to the patch output
|
||||
--head=512: shortcut for --inhead=512 --outhead=512
|
||||
prepended bytes are either copied from a previous header, or 00)");
|
||||
exit(error ? 1 : 0);
|
||||
#else
|
||||
if (!errormsg) errormsg = error;
|
||||
#endif
|
||||
}
|
||||
|
||||
//returns whether 'next' was used
|
||||
//calls usage() if arg is unknown
|
||||
bool parse(cstring arg, cstring next)
|
||||
{
|
||||
if(0);
|
||||
else if (arg=="help") usage("");
|
||||
else if (arg=="version")
|
||||
{
|
||||
window_attach_console();
|
||||
puts("?");
|
||||
exit(0);
|
||||
}
|
||||
else if (arg=="apply") mode=m_apply;
|
||||
else if (arg=="create") mode=m_create;
|
||||
else if (arg=="info") mode=m_info;
|
||||
else if (arg=="db") mode=m_db;
|
||||
else if (arg=="manifest")
|
||||
{
|
||||
manifest=next;
|
||||
return true;
|
||||
}
|
||||
else if (arg=="silent") silent=true;
|
||||
else if (arg=="ignorechecksum") ignorechecksum=true;
|
||||
else if (arg=="head" || arg=="inhead" || arg=="patchhead" || arg=="outhead")
|
||||
{
|
||||
size_t size;
|
||||
if (!fromstring(next, size)) usage("invalid argument to --"+arg);
|
||||
if (arg=="head") outhead=inhead=size;
|
||||
if (arg=="inhead") inhead=size;
|
||||
if (arg=="patchhead") patchhead=size;
|
||||
if (arg=="outhead") outhead=size;
|
||||
return true;
|
||||
}
|
||||
else usage("unknown option --"+arg);
|
||||
return false;
|
||||
}
|
||||
|
||||
string longname(char arg)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
case 'h': return "help";
|
||||
case '?': return "help";
|
||||
case 'v': return "version";
|
||||
|
||||
case 'a': return "apply";
|
||||
case 'c': return "create";
|
||||
case 'i': return "info";
|
||||
case 'm': return "manifest";
|
||||
case 's': return "silent";
|
||||
default: usage("unknown option -"+string(arrayview<char>(&arg, 1))); return "";
|
||||
}
|
||||
}
|
||||
|
||||
//if -f is --foo, all of these yield arg="foo" next="bar":
|
||||
//-fbar | -f bar | --foo=bar
|
||||
//and these yield next="":
|
||||
//-f -b | -f --bar | -f | --foo bar
|
||||
void parse(const char * const * argv)
|
||||
{
|
||||
array<string> args;
|
||||
for (int i=1;argv[i];i++) args.append(argv[i]);
|
||||
|
||||
for (size_t i=0;i<args.size();i++)
|
||||
{
|
||||
string arg = args[i];
|
||||
if (arg[0]=='-')
|
||||
{
|
||||
if (arg[1]=='-')
|
||||
{
|
||||
//long
|
||||
array<string> parts = arg.substr(2, ~0).split<1>("=");
|
||||
|
||||
bool hasarg = (parts.size()==2);
|
||||
bool argused = parse(parts[0], parts[1]);
|
||||
if (hasarg && !argused) usage("--"+parts[0]+" does not take an argument");
|
||||
}
|
||||
else
|
||||
{
|
||||
//short
|
||||
string rest = arg.substr(1, ~0);
|
||||
|
||||
next:
|
||||
char opt = rest[0];
|
||||
rest = rest.substr(1, ~0);
|
||||
|
||||
if (rest)
|
||||
{
|
||||
bool argused = parse(longname(opt), rest);
|
||||
if (!argused) goto next;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool argused = parse(longname(opt), args[i+1]);
|
||||
if (argused) i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//not option
|
||||
fnames.append(arg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setmode()
|
||||
{
|
||||
|
||||
//a/a.bps b/b.smc -> -a a/a.bps b/b.smc a/a.smc ("default path", patch path + patch basename + infile extension)
|
||||
//a/a.smc b/b.bps -> -a b/b.bps a/a.smc b/b.smc
|
||||
//a/a.bps b/b.smc c/c.sfc -> -a a/a.bps b/b.smc c/c.sfc
|
||||
//-c a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
|
||||
//a/a.smc b/b.smc c/c.bps -> -c a/a.smc b/b.smc c/c.bps
|
||||
//a/a.smc b/b.smc -> -c a/a.smc b/b.smc b/b.bps
|
||||
//a/a.smc -> error
|
||||
//a/a.bps b/b.bps -> error
|
||||
//(anything) -m b/b.txt -> extract or insert manifest here
|
||||
//a/a.bps -m b/b.txt -> extract manifest only
|
||||
//a/a.bps . -> query database
|
||||
//-c . b/b.smc b/b.bps -> pick best match from database
|
||||
//-c . b/b.smc -> -c . b/b.smc b/b.bps
|
||||
//-c b/b.smc -> -c . b/b.smc b/b.bps
|
||||
//(null) -> launch gui
|
||||
//a/a.bps -> launch patch wizard
|
||||
//anything else -> error
|
||||
}
|
||||
};
|
||||
|
||||
test()
|
||||
{{
|
||||
//macros are great to wipe out copypasted boilerplate
|
||||
#define PARSE(x) } { const char * argv[] = { "flips", x, NULL }; flipsargs args; args.parse(argv)
|
||||
PARSE(NULL);
|
||||
assert_eq(args.errormsg, "");
|
||||
|
||||
PARSE("--foo");
|
||||
assert(args.errormsg != "");
|
||||
|
||||
PARSE("--apply");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
|
||||
PARSE("-a");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
|
||||
PARSE("-a", "-m", "foo.smc");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
assert_eq(args.manifest, "foo.smc");
|
||||
|
||||
PARSE("-a", "--manifest=foo.smc");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
assert_eq(args.manifest, "foo.smc");
|
||||
|
||||
PARSE("-a", "--manifest", "foo.smc");
|
||||
assert(args.errormsg != ""); // invalid way to specify --manifest
|
||||
|
||||
PARSE("-am", "foo.smc");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
assert_eq(args.manifest, "foo.smc");
|
||||
|
||||
PARSE("-amfoo.smc");
|
||||
assert_eq(args.errormsg, "");
|
||||
assert(args.mode==flipsargs::m_apply);
|
||||
assert_eq(args.manifest, "foo.smc");
|
||||
#undef PARSE
|
||||
}}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
window_init(&argc, &argv);
|
||||
window* wnd = window_create(
|
||||
widget_create_layout_grid(2,2, false,
|
||||
widget_create_button("Apply Patch")->set_onclick(bind(a_apply)),
|
||||
widget_create_button("Create Patch"),
|
||||
widget_create_button("Apply and Run"),
|
||||
widget_create_button("Settings")));
|
||||
wnd->set_title("Flips v" FLIPSVER);
|
||||
wnd->set_visible(true);
|
||||
while (wnd->is_visible()) window_run_wait();
|
||||
return 0;
|
||||
bool guiavail = window_try_init(&argc, &argv);
|
||||
|
||||
flipsargs args;
|
||||
args.parse(argv);
|
||||
|
||||
if (guiavail)
|
||||
{
|
||||
window* wnd = window_create(
|
||||
widget_create_layout_grid(2,2, false,
|
||||
widget_create_button("Apply Patch")->set_onclick(bind(a_apply)),
|
||||
widget_create_button("Create Patch"),
|
||||
widget_create_button("Apply and Run"),
|
||||
widget_create_button("Settings")));
|
||||
wnd->set_title("Flips v" FLIPSVER);
|
||||
wnd->set_visible(true);
|
||||
while (wnd->is_visible()) window_run_wait();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
21
global.h
21
global.h
@@ -1,21 +0,0 @@
|
||||
//Module name: Floating IPS, global header
|
||||
//Author: Alcaro
|
||||
//Date: June 18, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#ifndef struct_mem
|
||||
#define struct_mem
|
||||
|
||||
//the standard library can be assumed to exist
|
||||
#include <stddef.h>//size_t, SIZE_MAX
|
||||
#include <stdint.h>//uint8_t
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX ((size_t)-1)
|
||||
#endif
|
||||
|
||||
struct mem {
|
||||
uint8_t * ptr;
|
||||
size_t len;
|
||||
};
|
||||
#endif
|
||||
@@ -1,15 +1,5 @@
|
||||
#include "patch.h"
|
||||
|
||||
//Deprecated
|
||||
//struct mem {
|
||||
//mem() : ptr(NULL), len(0) {}
|
||||
//mem(uint8_t* ptr, size_t len) : ptr(ptr), len(len) {}
|
||||
//mem(arrayview<byte> v) : ptr((byte*)v.ptr()), len(v.size()) {}
|
||||
//arrayvieww<byte> v() { return arrayvieww<byte>(ptr, len); }
|
||||
//uint8_t * ptr;
|
||||
//size_t len;
|
||||
//};
|
||||
|
||||
//These two give minor performance penalties and will print some random stuff to stdout.
|
||||
//The former will verify the correctness of the output patch, the latter will print some performance data.
|
||||
//Can be useful for debugging, but should be disabled for release builds.
|
||||
@@ -36,6 +26,9 @@
|
||||
// called O(n) times, with O(log n) comparisons per iteration. Each comparison is potentially O(n),
|
||||
// but for each matched byte, another iteration is removed from the outer loop, so the comparisons
|
||||
// can be considered O(1) each; the sum is O(n log n).
|
||||
//It could be replaced with a reverse index, reverse[sorted[x]]==x for all x, but that would cost a
|
||||
// lot of memory, and due to the cost of creating said index and only a few entries being used, it
|
||||
// doesn't save any time in practice.
|
||||
//
|
||||
//After it's found sortpos, it scans sorted[] up and down for the closest entry that either starts
|
||||
// before the current output position, or is somewhere in the source file.
|
||||
@@ -53,14 +46,15 @@
|
||||
// This gives O(log n) calls to the suffix sorter.
|
||||
//Given O(n log n) for one sorting step, the time taken is O(n/1 log n/1 + n/2 log n/2 +
|
||||
// n/4 log n/4 + ...), which is strictly less than O(n/1 log n + n/2 log n + n/4 log n + ...), which
|
||||
// equals O(2n log n), which is O(n log n). (The exact value of that infinite sum is 2n*log(n/2).)
|
||||
// equals O(2n log n), which is O(n log n).
|
||||
//
|
||||
//Many details were omitted from the above, but that's the basic setup.
|
||||
//
|
||||
//Thus, the program is O(max(n log n, n log n, n, n) = n log n) average and O(max(n log n, n log n,
|
||||
// n^2, n) = n^2) worst case.
|
||||
//Thus, the program is O(n log n) + O(n log n) + O(n) + O(n) = O(n log n) average and O(n log n) +
|
||||
// O(n log n) + O(n^2) + O(n) = O(n^2) worst case.
|
||||
//
|
||||
//I conclude that the task of finding, understanding and implementing a sub-O(n^2) algorithm for
|
||||
//As the quadratic worst case is not hit for random data or any other plausible output file, I
|
||||
// conclude that the task of finding, understanding and implementing a sub-quadratic algorithm for
|
||||
// delta patching is resolved.
|
||||
|
||||
|
||||
@@ -73,9 +67,10 @@
|
||||
// Penalty: Likely O(n) or O(n log log n), with low constants. I'd guess ~1.4% for my 48MB test file.
|
||||
//However, due to better heuristics and others' performance optimizations, this one still beats its
|
||||
// competitors.
|
||||
//Heuristics are likely somewhat mistuned.
|
||||
|
||||
//TODO: test multiple same-length matches
|
||||
// but only for lengths <= 64,
|
||||
// but only for lengths <= 16 or something, otherwise it'd take too long
|
||||
|
||||
|
||||
//Possible optimizations:
|
||||
@@ -84,30 +79,32 @@
|
||||
//If each iteration takes 4 times as long as the previous one, then the last one takes 3/4 of the total time.
|
||||
//Since divsufsort doesn't depend on anything else, the last iteration can be split off to its own thread.
|
||||
//This would split it to
|
||||
//Search, non-final: 1/2 * 1/4 = 1/8
|
||||
//Search, final: 1/2 * 3/4 = 3/8
|
||||
//Sort+rev, non-final: 1/2 * 1/4 = 1/8
|
||||
//Sort+rev, final: 1/2 * 3/4 = 3/8
|
||||
//Search, non-final: 1/2 * 1/4 = 1/8
|
||||
//Search, final: 1/2 * 3/4 = 3/8
|
||||
//Sort, non-final: 1/2 * 1/4 = 1/8
|
||||
//Sort, final: 1/2 * 3/4 = 3/8
|
||||
//All non-final must be done sequentially. Both Sort Final and non-final must be done before Search Final can start.
|
||||
//This means the final time, if Sort Final is split off, is
|
||||
//max(1/8+1/8, 3/8) + 3/8 = 6/8 = 3/4
|
||||
//of the original time.
|
||||
//Due to
|
||||
//- the considerable complexity costs (OpenMP doesn't seem able to represent the "insert a wait in
|
||||
// the middle of this while loop" I would need)
|
||||
// the middle of this while loop" operation I would need)
|
||||
//- the added memory use, approximately 25% higher - it's already high enough
|
||||
//- libdivsufsort already using threads, which would make the gains lower
|
||||
// and would increase complexity, as I have to ensure the big one remains threaded -
|
||||
// and that the small ones are not, as that'd starve the big one
|
||||
//I deem a possible 25% boost not worthwhile.
|
||||
|
||||
//Both sorting algorithms claim O(1) memory use, in addition to the in/outputs. For most hardware,
|
||||
// this is 5*(source.len+target.len).
|
||||
//If the output is stored to disk, that's all this algorithm needs as well.
|
||||
//Another optimization would be if a faster suffix sorting algorithm available.
|
||||
|
||||
//Both SA-IS and libdivsufsort claim O(1) memory use, in addition to the in/outputs. For most
|
||||
// hardware, this is 5*(source.len+target.len).
|
||||
//The output file is also stored in memory, which is potentially slightly more than the output file
|
||||
// size. This could be changed without too much trouble, but is unlikely to be worth it.
|
||||
|
||||
|
||||
namespace patch { namespace bps {
|
||||
//TODO: HEAVY cleanups needed here
|
||||
#include "sais.cpp"
|
||||
template<typename sais_index_type>
|
||||
static void sufsort(sais_index_type* SA, const uint8_t* T, sais_index_type n) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include "patch.h"
|
||||
|
||||
//Things I would've done differently if I had a chance to redesign BPS:
|
||||
//Things I would've done differently if I had a chance to redesign the BPS format:
|
||||
//- Don't allow encoding -0 in Source/TargetCopy
|
||||
//- Ditch metadata, it goes in a separate file
|
||||
//- Ditch metadata, it goes in a separate file (and it's already used in nonstandard ways, spec says XML but bsnes disagrees)
|
||||
//- Reconsider SourceRead; maybe patches would be smaller if of the three others was one bit rather than two, or maybe a new command
|
||||
// or maybe only Read/Copy commands? Read is TargetRead, Copy treats target as concatenated to source
|
||||
//- Invert 0x80 bit in the encoded numbers, set means continue; it would simplify the decoder
|
||||
//- Replace BPS1 signature with something not containing an 1
|
||||
// while DWORD alignment sounds nice, it's useless for a byte-oriented format like this; even the checksums aren't aligned
|
||||
// four-byte signatures are nicer than three, but '1' is the wrong choice for the last byte; PNG's \x89 would work
|
||||
// four-byte signatures are nicer than three, but '1' is the wrong choice for the last byte; PNGs \x89 would work
|
||||
//- Make the checksums mandatory
|
||||
// (1) Ignoring them allows all files of that size, including ones that are clearly not the proper source
|
||||
// (2) Even if a ROM hacker is careful to only change a few bytes, BPS likes copying stuff around,
|
||||
@@ -34,10 +34,7 @@ result apply(arrayview<byte> patchmem, arrayview<byte> inmem, array<byte>& outme
|
||||
if (!patch.bpsnum(&var)) error(e_too_big); \
|
||||
} while(false)
|
||||
|
||||
if (patch.u8()!='B') error(e_broken);
|
||||
if (patch.u8()!='P') error(e_broken);
|
||||
if (patch.u8()!='S') error(e_broken);
|
||||
if (patch.u8()!='1') error(e_broken);
|
||||
if (!patch.signature("BPS1")) error(e_broken);
|
||||
|
||||
memstream checks = patchmem.slice(patchmem.size()-12, 12);
|
||||
uint32_t crc_in_e = checks.u32();
|
||||
@@ -191,7 +188,7 @@ result info::parse(arrayview<byte> data, bool changefrac)
|
||||
size_t outpos=0; // position in the output file
|
||||
size_t changeamt=0; // change score
|
||||
|
||||
while (patch.remaining())
|
||||
while (patch.remaining() && outpos<this->size_in)
|
||||
{
|
||||
size_t thisinstr;
|
||||
patch.bpsnum(&thisinstr);
|
||||
@@ -223,7 +220,6 @@ result info::parse(arrayview<byte> data, bool changefrac)
|
||||
}
|
||||
outpos+=length;
|
||||
}
|
||||
if (outpos>this->size_out) return e_broken;
|
||||
this->change_num = (changeamt<this->size_in ? changeamt : this->size_in);
|
||||
this->change_denom = this->size_in;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ enum result {
|
||||
e_ok,
|
||||
|
||||
//You may get an output file along with some of these errors.
|
||||
//This is indistinguishable from zero-size output, but such patches are useless and rare anyways.
|
||||
e_to_output,//You attempted to apply a patch to its output.
|
||||
e_not_this, //This is not the intended input file for this patch.
|
||||
e_damaged, //The patch is technically valid, but seems scrambled or malformed.
|
||||
@@ -28,7 +29,7 @@ enum result {
|
||||
|
||||
//All of these functions can be called with arrayview inputs and array& outputs, but
|
||||
// they give lower memory use and/or better performance if you follow the listed types.
|
||||
//For example, IPS and UPS application start with copying the input file to the output;
|
||||
//For example, applying an IPS or UPS starts with copying the input file to the output;
|
||||
// if you give them a file object directly, they'll read it straight from disk to the target buffer.
|
||||
|
||||
namespace ips {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "patch.h"
|
||||
|
||||
#if 0
|
||||
|
||||
/*
|
||||
make clean; rm callgrind.out.*; make test -j8 TESTRUNNER='time valgrind --tool=callgrind' CFLAGS='-Os -g' && kcachegrind callgrind.out.*
|
||||
*/
|
||||
@@ -169,7 +171,7 @@ test("BPS")
|
||||
test("the big ones")
|
||||
{
|
||||
testips=true;
|
||||
testips=false;
|
||||
//testips=false;
|
||||
testbps=true;
|
||||
//testbps=false;
|
||||
|
||||
@@ -180,30 +182,31 @@ test("the big ones")
|
||||
array<byte> sm64 = file::read("patch/test/sm64.z64");
|
||||
array<byte> sm64_bps = file::read("patch/test/star.bps");
|
||||
if (!smw || !smw_bps || !dl || !dl_ups || !sm64 || !sm64_bps) test_skip("test files not present; see patch/test/readme.txt");
|
||||
result r;
|
||||
|
||||
array<byte> smwhack;
|
||||
r = bps::apply(smw_bps, smw, smwhack);
|
||||
assert_eq(r, e_ok);
|
||||
result smwr = bps::apply(smw_bps, smw, smwhack);
|
||||
assert_eq(smwr, e_ok);
|
||||
assert_eq(smwhack.size(), 4194304);
|
||||
testcall(createtest(smw, smwhack, 3302746, 2077386));
|
||||
|
||||
//this is the only UPS test, UPS is pretty much an easter egg in Flips
|
||||
array<byte> dlhack;
|
||||
r = ups::apply(dl_ups, dl, dlhack);
|
||||
assert_eq(r, e_ok);
|
||||
result dlr = ups::apply(dl_ups, dl, dlhack);
|
||||
assert_eq(dlr, e_ok);
|
||||
assert_eq(dlhack.size(), 3145728);
|
||||
array<byte> dl2;
|
||||
r = ups::apply(dl_ups, dlhack, dl2);
|
||||
assert_eq(r, e_ok);
|
||||
dlr = ups::apply(dl_ups, dlhack, dl2);
|
||||
assert_eq(dlr, e_ok);
|
||||
assert(dl == dl2);
|
||||
testcall(createtest(dl, dlhack, 852124, 817190));
|
||||
|
||||
array<byte> sm64hack;
|
||||
r = bps::apply(sm64_bps, sm64, sm64hack);
|
||||
assert_eq(r, e_ok);
|
||||
result sm64r = bps::apply(sm64_bps, sm64, sm64hack);
|
||||
assert_eq(sm64r, e_ok);
|
||||
assert_eq(sm64hack.size(), 50331648);
|
||||
testbps=false; // too slow
|
||||
//removing this makes that entire createtest useless
|
||||
//testbps=false; // too slow
|
||||
testcall(createtest(sm64, sm64hack, -1, 6788133));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "patch.h"
|
||||
|
||||
namespace patch { namespace ups {
|
||||
//TODO: HEAVY cleanups needed here
|
||||
|
||||
result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
|
||||
{
|
||||
@@ -20,10 +19,7 @@ result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
|
||||
|
||||
bool backwards=false;
|
||||
|
||||
if (patch.u8()!='U') error(e_broken);
|
||||
if (patch.u8()!='P') error(e_broken);
|
||||
if (patch.u8()!='S') error(e_broken);
|
||||
if (patch.u8()!='1') error(e_broken);
|
||||
if (!patch.signature("UPS1")) error(e_broken);
|
||||
|
||||
size_t inlen;
|
||||
size_t outlen;
|
||||
|
||||
Reference in New Issue
Block a user