Clean up IPS patcher

This commit is contained in:
Alcaro
2016-12-25 13:02:09 +01:00
parent e6459a0af4
commit 14ea216a2b
5 changed files with 81 additions and 146 deletions

View File

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

View File

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

View File

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

View File

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

View File

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