mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-07 10:06:09 -05:00
Clean up IPS patcher
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
namespace patch { namespace bps {
|
||||
enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
result apply(arrayview<byte> patchmem, arrayview<byte> in, array<byte>& out, bool accept_wrong_input)
|
||||
{
|
||||
if (patchmem.size()<4+3+12) return e_broken;
|
||||
@@ -29,6 +28,7 @@ result apply(arrayview<byte> patchmem, arrayview<byte> in, array<byte>& out, boo
|
||||
|
||||
if (true)
|
||||
{
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
#define decodeto(var) \
|
||||
do { \
|
||||
if (!patch.bpsnum(var)) error(e_too_big); \
|
||||
@@ -150,13 +150,13 @@ result apply(arrayview<byte> patchmem, arrayview<byte> in, array<byte>& out, boo
|
||||
|
||||
return error;
|
||||
#undef decodeto
|
||||
#undef error
|
||||
}
|
||||
|
||||
exit:
|
||||
out.resize(0);
|
||||
return error;
|
||||
}
|
||||
#undef error
|
||||
|
||||
|
||||
|
||||
|
||||
187
patch/ips.cpp
187
patch/ips.cpp
@@ -1,155 +1,68 @@
|
||||
#include "patch.h"
|
||||
|
||||
namespace patch { namespace ips {
|
||||
//TODO: HEAVY cleanups needed here
|
||||
#define min(a,b) ((a)<(b)?(a):(b))
|
||||
#define max(a,b) ((a)>(b)?(a):(b))
|
||||
#define clamp(a,b,c) max(a,min(b,c))
|
||||
|
||||
struct ipsstudy {
|
||||
result error;
|
||||
unsigned int outlen_min;
|
||||
unsigned int outlen_max;
|
||||
unsigned int outlen_min_mem;
|
||||
};
|
||||
|
||||
static result ips_study(struct mem patch, struct ipsstudy * study)
|
||||
result apply(arrayview<byte> patchmem, const file& in, array<byte>& out)
|
||||
{
|
||||
study->error=e_broken;
|
||||
if (patch.len<8) return e_broken;
|
||||
const unsigned char * patchat=patch.ptr;
|
||||
const unsigned char * patchend=patchat+patch.len;
|
||||
#define read8() ((patchat<patchend)?(*patchat++):0)
|
||||
#define read16() ((patchat+1<patchend)?(patchat+=2,((patchat[-2]<<8)|patchat[-1])):0)
|
||||
#define read24() ((patchat+2<patchend)?(patchat+=3,((patchat[-3]<<16)|(patchat[-2]<<8)|patchat[-1])):0)
|
||||
if (read8()!='P' ||
|
||||
read8()!='A' ||
|
||||
read8()!='T' ||
|
||||
read8()!='C' ||
|
||||
read8()!='H')
|
||||
result error = e_ok;
|
||||
memstream patch = patchmem;
|
||||
|
||||
if (patch.size()<8 || !patch.signature("PATCH")) return e_broken;
|
||||
out = in.read();
|
||||
|
||||
bool anychanges = false;
|
||||
uint32_t lastoffset = 0;
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
while (true)
|
||||
{
|
||||
return e_broken;
|
||||
}
|
||||
unsigned int offset=read24();
|
||||
unsigned int outlen=0;
|
||||
unsigned int thisout=0;
|
||||
unsigned int lastoffset=0;
|
||||
bool w_scrambled=false;
|
||||
while (offset!=0x454F46)//454F46=EOF
|
||||
{
|
||||
unsigned int size=read16();
|
||||
uint32_t offset = patch.u24be();
|
||||
if (offset == 0x454F46) break;
|
||||
|
||||
if (offset < lastoffset) error = e_damaged;
|
||||
lastoffset = offset;
|
||||
|
||||
if (patch.remaining() < 2+1+3) error(e_broken);
|
||||
uint32_t size = patch.u16be();
|
||||
|
||||
if (size==0)
|
||||
{
|
||||
size=read16();
|
||||
if (!size) w_scrambled=true;
|
||||
thisout=offset+size;
|
||||
read8();
|
||||
if (patch.remaining() < 2+1+3) error(e_broken);
|
||||
size = patch.u16be();
|
||||
uint8_t b = patch.u8();
|
||||
if (!size) error(e_broken); // is this defined?
|
||||
|
||||
out.reserve(offset+size);
|
||||
if (!anychanges &&
|
||||
(out[offset]!=b || out.slice(offset, size-1).ptr()!=out.slice(offset+1, size-1).ptr()))
|
||||
{
|
||||
anychanges = true;
|
||||
}
|
||||
|
||||
memset(out.slice(offset, size).ptr(), b, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
thisout=offset+size;
|
||||
patchat+=size;
|
||||
}
|
||||
if (offset<lastoffset) w_scrambled=true;
|
||||
lastoffset=offset;
|
||||
if (thisout>outlen) outlen=thisout;
|
||||
if (patchat>=patchend) return e_broken;
|
||||
offset=read24();
|
||||
}
|
||||
study->outlen_min_mem=outlen;
|
||||
study->outlen_max=0xFFFFFFFF;
|
||||
if (patchat+3==patchend)
|
||||
{
|
||||
unsigned int truncate=read24();
|
||||
study->outlen_max=truncate;
|
||||
if (outlen>truncate)
|
||||
{
|
||||
outlen=truncate;
|
||||
w_scrambled=true;
|
||||
}
|
||||
}
|
||||
if (patchat!=patchend) return e_broken;
|
||||
study->outlen_min=outlen;
|
||||
#undef read8
|
||||
#undef read16
|
||||
#undef read24
|
||||
study->error=e_ok;
|
||||
if (w_scrambled) study->error=e_damaged;
|
||||
return study->error;
|
||||
}
|
||||
|
||||
static result ips_apply_study(struct mem patch, struct ipsstudy * study, struct mem in, struct mem * out)
|
||||
{
|
||||
out->ptr=NULL;
|
||||
out->len=0;
|
||||
if (study->error==e_broken) return study->error;
|
||||
#define read8() (*patchat++)//guaranteed to not overflow at this point, we already checked the patch
|
||||
#define read16() (patchat+=2,((patchat[-2]<<8)|patchat[-1]))
|
||||
#define read24() (patchat+=3,((patchat[-3]<<16)|(patchat[-2]<<8)|patchat[-1]))
|
||||
unsigned int outlen=clamp(study->outlen_min, in.len, study->outlen_max);
|
||||
out->ptr=(uint8_t*)malloc(max(outlen, study->outlen_min_mem));
|
||||
out->len=outlen;
|
||||
|
||||
bool anychanges=false;
|
||||
if (outlen!=in.len) anychanges=true;
|
||||
|
||||
if (out->len>in.len)
|
||||
{
|
||||
memcpy(out->ptr, in.ptr, in.len);
|
||||
memset(out->ptr+in.len, 0, out->len-in.len);
|
||||
}
|
||||
else memcpy(out->ptr, in.ptr, outlen);
|
||||
const unsigned char * patchat=patch.ptr+5;
|
||||
unsigned int offset=read24();
|
||||
while (offset!=0x454F46)
|
||||
{
|
||||
unsigned int size=read16();
|
||||
if (size==0)
|
||||
{
|
||||
size=read16();
|
||||
if (!size) {}//no clue (fix the change detector if changing this)
|
||||
unsigned char b=read8();
|
||||
if (patch.remaining() < size+3) error(e_broken);
|
||||
|
||||
if (size && (out->ptr[offset]!=b || memcmp(out->ptr+offset, out->ptr+offset, size-1))) anychanges=true;
|
||||
|
||||
memset(out->ptr+offset, b, size);
|
||||
out.reserve(offset+size);
|
||||
arrayview<byte> newdat = patch.bytes(size);
|
||||
if (!anychanges && newdat!=out.slice(offset, size)) anychanges = true;
|
||||
memcpy(out.slice(offset, size).ptr(), newdat.ptr(), newdat.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (memcmp(out->ptr+offset, patchat, size)) anychanges=true;
|
||||
|
||||
memcpy(out->ptr+offset, patchat, size);
|
||||
patchat+=size;
|
||||
}
|
||||
offset=read24();
|
||||
}
|
||||
#undef read8
|
||||
#undef read16
|
||||
#undef read24
|
||||
if (patch.remaining()==3)
|
||||
{
|
||||
uint32_t newsize = patch.u24();
|
||||
if (newsize <= out.size() && !error) error = e_not_this;
|
||||
out.resize(newsize);
|
||||
}
|
||||
if (patch.remaining()!=0) error = e_damaged;
|
||||
if (!anychanges && in.size()==out.size() && error != e_damaged) error = e_to_output;
|
||||
return error;
|
||||
|
||||
if (study->outlen_max!=0xFFFFFFFF && in.len<=study->outlen_max) study->error=e_not_this;//truncate data without this being needed is a poor idea
|
||||
if (!anychanges) study->error=e_to_output;
|
||||
return study->error;
|
||||
}
|
||||
|
||||
static result apply(struct mem patch, struct mem in, struct mem * out)
|
||||
{
|
||||
struct ipsstudy study;
|
||||
ips_study(patch, &study);
|
||||
return ips_apply_study(patch, &study, in, out);
|
||||
}
|
||||
|
||||
result apply(const file& patch, const file& source, file& target)
|
||||
{
|
||||
struct mem patchmem = patch.mmap();
|
||||
struct mem inmem = source.mmap();
|
||||
struct mem outmem;
|
||||
result r = apply(patchmem, inmem, &outmem);
|
||||
patch.unmap(patchmem.v());
|
||||
source.unmap(inmem.v());
|
||||
target.write(outmem.v());
|
||||
free(outmem.ptr);
|
||||
return r;
|
||||
exit:
|
||||
out.resize(0);
|
||||
return error;
|
||||
}
|
||||
|
||||
//Known situations where this function does not generate an optimal patch:
|
||||
|
||||
@@ -27,8 +27,12 @@ enum result {
|
||||
};
|
||||
|
||||
namespace ips {
|
||||
result apply(const file& patch, const file& source, file& target);
|
||||
static inline result apply(const file& patch, const file& source, file&& target) { return apply(patch, source, (file&)target); }
|
||||
result apply(arrayview<byte> patch, const file& in, array<byte>& out);
|
||||
static inline result apply(arrayview<byte> patch, arrayview<byte> in, array<byte>& out)
|
||||
{
|
||||
file inf = file::mem(in);
|
||||
return apply(patch, inf, out);
|
||||
}
|
||||
result create(const file& source, const file& target, file& patch);
|
||||
static inline result create(const file& source, const file& target, file&& patch) { return create(source, target, (file&)patch); }
|
||||
}
|
||||
@@ -107,6 +111,12 @@ class memstream {
|
||||
public:
|
||||
memstream(arrayview<byte> buf) : start(buf.ptr()), at(buf.ptr()), end(buf.ptr()+buf.size()) {}
|
||||
arrayview<byte> bytes(size_t n) { arrayview<byte> ret = arrayview<byte>(at, n); at+=n; return ret; }
|
||||
bool signature(cstring sig)
|
||||
{
|
||||
bool ok = (memcmp(at, sig.bytes().ptr(), sig.length())==0);
|
||||
at+=sig.length();
|
||||
return ok;
|
||||
}
|
||||
uint8_t u8()
|
||||
{
|
||||
return *(at++);
|
||||
@@ -121,11 +131,21 @@ public:
|
||||
arrayview<byte> b = bytes(2);
|
||||
return b[0] | b[1]<<8;
|
||||
}
|
||||
uint16_t u16be()
|
||||
{
|
||||
arrayview<byte> b = bytes(2);
|
||||
return b[0]<<8 | b[1];
|
||||
}
|
||||
uint32_t u24()
|
||||
{
|
||||
arrayview<byte> b = bytes(3);
|
||||
return b[0] | b[1]<<8 | b[2]<<16;
|
||||
}
|
||||
uint32_t u24be()
|
||||
{
|
||||
arrayview<byte> b = bytes(3);
|
||||
return b[0]<<16 | b[1]<<8 | b[2];
|
||||
}
|
||||
uint32_t u32()
|
||||
{
|
||||
arrayview<byte> b = bytes(4);
|
||||
@@ -136,6 +156,7 @@ public:
|
||||
const byte* b = start+pos;
|
||||
return b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24;
|
||||
}
|
||||
size_t pos() { return at-start; }
|
||||
size_t size() { return end-start; }
|
||||
size_t remaining() { return end-at; }
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ static void createtest(arrayview<byte> a, arrayview<byte> b, size_t ipssize, siz
|
||||
result r = ips::create(file::mem(a), file::mem(b), file::mem(patch));
|
||||
if (r!=e_identical) assert_eq(r, e_ok);
|
||||
array<byte> b2;
|
||||
r = ips::apply(file::mem(patch), file::mem(a), file::mem(b2));
|
||||
r = ips::apply(patch, a, b2);
|
||||
if (r!=e_to_output) assert_eq(r, e_ok);
|
||||
assert(b == b2);
|
||||
|
||||
@@ -179,9 +179,9 @@ test("the big ones")
|
||||
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);
|
||||
//array<byte> smwhack;
|
||||
//r = bps::apply(smw_bps, smw, smwhack);
|
||||
//assert_eq(r, e_ok);
|
||||
//testcall(createtest(smw, smwhack, 3302980, 2077386));
|
||||
|
||||
//array<byte> sm64hack;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace patch { namespace ups {
|
||||
//TODO: HEAVY cleanups needed here
|
||||
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
|
||||
{
|
||||
if (patchmem.size()<4+2+12) return e_broken;
|
||||
@@ -13,6 +12,7 @@ result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
|
||||
|
||||
if (true)
|
||||
{
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
#define decodeto(var) \
|
||||
do { \
|
||||
if (!patch.bpsnum(var)) error(e_too_big); \
|
||||
@@ -94,6 +94,7 @@ result apply(arrayview<byte> patchmem, const file& in, array<byte>& outmem)
|
||||
|
||||
return e_ok;
|
||||
#undef decodeto
|
||||
#undef error
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
Reference in New Issue
Block a user