#include "libbps.h"
#include "crc32.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef BPS_STANDALONE
#endif
#define EOF_IS_LAST false
#if defined(TEST_CORRECT) || defined(TEST_PERF)
#include <stdio.h>
#endif
#include "divsufsort.h"
static void sufsort(int32_t* SA, uint8_t* T, int32_t n)
{
divsufsort(T, SA, n);
}
#ifdef USE_DIVSUFSORT64
#include "divsufsort64.h"
static void sufsort(int64_t* SA, uint8_t* T, int64_t n)
{
divsufsort(T, SA, n);
}
#endif
template<typename T> static T min(T a, T b) { return a<b ? a : b; }
template<typename T> static T max(T a, T b) { return a<b ? b : a; }
namespace {
struct bps_creator {
uint8_t* out;
size_t outlen;
size_t outbuflen;
void reserve(size_t len)
{
if (outlen+len > outbuflen)
{
if (!outbuflen) outbuflen = 128;
while (outlen+len > outbuflen) outbuflen *= 2;
out = (uint8_t*)realloc(out, outbuflen);
}
}
void append(const uint8_t * data, size_t len)
{
reserve(len);
memcpy(out+outlen, data, len);
outlen+=len;
}
void appendnum(size_t num)
{
#ifdef TEST_CORRECT
if (num > 1000000000)
printf("ERROR: Attempt to write %.8lX\n",(unsigned long)num),abort();
#endif
reserve(sizeof(size_t)*8/7+1);
while (num >= 128)
{
out[outlen++]=(num&0x7F);
num>>=7;
num--;
}
out[outlen++]=num|0x80;
}
void appendnum32(uint32_t num)
{
reserve(4);
out[outlen++] = num>>0;
out[outlen++] = num>>8;
out[outlen++] = num>>16;
out[outlen++] = num>>24;
}
static size_t maxsize()
{
return SIZE_MAX>>2; }
size_t sourcelen;
size_t targetlen;
const uint8_t* targetmem;
enum bpscmd { SourceRead, TargetRead, SourceCopy, TargetCopy };
size_t outpos;
size_t sourcecopypos;
size_t targetcopypos;
size_t numtargetread;
bps_creator(file* source, file* target, struct mem metadata)
{
outlen = 0;
outbuflen = 128;
out = (uint8_t*)malloc(outbuflen);
outpos = 0;
sourcelen = source->len();
targetlen = target->len();
sourcecopypos = 0;
targetcopypos = 0;
numtargetread = 0;
append((const uint8_t*)"BPS1", 4);
appendnum(sourcelen);
appendnum(targetlen);
appendnum(metadata.len);
append(metadata.ptr, metadata.len);
setProgress(NULL, NULL);
}
void move_target(const uint8_t* ptr)
{
targetmem = ptr;
}
size_t encode_delta(size_t prev, size_t next)
{
bool negative = (next<prev);
size_t offset = negative ? prev-next : next-prev;
return (negative?1:0) | (offset<<1);
}
void append_delta(size_t prev, size_t next)
{
appendnum(encode_delta(prev, next));
}
void append_cmd(bpscmd command, size_t count)
{
appendnum((count-1)<<2 | command);
}
void flush_target_read()
{
if (!numtargetread) return;
append_cmd(TargetRead, numtargetread);
append(targetmem+outpos-numtargetread, numtargetread);
numtargetread = 0;
}
size_t emit_source_copy(size_t location, size_t count)
{
if (location == outpos) return emit_source_read(location, count);
flush_target_read();
append_cmd(SourceCopy, count);
append_delta(sourcecopypos, location);
sourcecopypos = location+count;
outpos += count;
return count;
}
size_t emit_source_read(size_t location, size_t count)
{
flush_target_read();
#ifdef TEST_CORRECT
if (location != outpos)
puts("ERROR: SourceRead not from source pointer"),abort();
#endif
append_cmd(SourceRead, count);
outpos+=count;
return count;
}
size_t emit_target_copy(size_t location, size_t count)
{
flush_target_read();
append_cmd(TargetCopy, count);
append_delta(targetcopypos, location);
targetcopypos = location+count;
outpos += count;
return count;
}
size_t emit_target_read()
{
numtargetread++;
outpos++;
return 1;
}
size_t abs_diff(size_t a, size_t b)
{
return (b<a) ? (a-b) : (b-a);
}
size_t num_cost(size_t num)
{
if (num<128) return 1;
if (num<128*128) return 2; if (num<128*128*128) return 3; if (num<128*128*128*128) return 4; return 5; }
bool use_match(bool hastargetread, size_t cost, size_t len)
{
return len >= 1+cost+hastargetread+(len==1);
}
size_t match(bool is_target, size_t pos, size_t len)
{
if (!use_match(
numtargetread,
(!is_target && pos==outpos) ? 1 : (num_cost(abs_diff(pos, (is_target ? targetcopypos : sourcecopypos)))+1),
len
))
{
return emit_target_read();
}
if (is_target) return emit_target_copy(pos, len);
else return emit_source_copy(pos, len);
}
bool (*prog_func)(void* userdata, size_t done, size_t total);
void* prog_dat;
static bool prog_func_null(void* userdata, size_t done, size_t total) { return true; }
void setProgress(bool (*progress)(void* userdata, size_t done, size_t total), void* userdata)
{
if (!progress) progress = prog_func_null;
prog_func=progress;
prog_dat=userdata;
}
bool progress(size_t done, size_t total)
{
return prog_func(prog_dat, done, total);
}
void finish(const uint8_t* source, const uint8_t* target)
{
flush_target_read();
#ifdef TEST_CORRECT
if (outpos != targetlen)
puts("ERROR: patch creates wrong ROM size"),abort();
#endif
appendnum32(crc32(source, sourcelen));
appendnum32(crc32(target, targetlen));
appendnum32(crc32(out, outlen));
}
struct mem getpatch()
{
struct mem ret = { out, outlen };
out = NULL;
return ret;
}
~bps_creator() { free(out); }
};
}
#ifdef TEST_PERF
static int match_len_n=0;
static int match_len_tot=0;
#endif
template<typename off_t>
static off_t match_len(const uint8_t* a, const uint8_t* b, off_t len)
{
off_t i;
for (i=0;i<len && a[i]==b[i];i++) {}
#ifdef TEST_PERF
match_len_n++;
match_len_tot+=i;
#endif
return i;
}
template<typename off_t>
static off_t pick_best_of_two(const uint8_t* search, off_t searchlen,
const uint8_t* data, off_t datalen,
off_t a, off_t b,
off_t* bestlen)
{
off_t commonlen = match_len(data+a, data+b, min(datalen-a, datalen-b));
if (commonlen>=searchlen)
{
*bestlen=searchlen;
return a;
}
if (a+commonlen<datalen && search[commonlen]==data[a+commonlen])
{
*bestlen = commonlen + match_len(search+commonlen, data+a+commonlen, min(searchlen, datalen-a)-commonlen);
return a;
}
else
{
*bestlen = commonlen + match_len(search+commonlen, data+b+commonlen, min(searchlen, datalen-b)-commonlen);
return b;
}
}
template<typename off_t>
static off_t adjust_match(off_t match, const uint8_t* search, off_t searchlen,
const uint8_t* data,off_t datalen, off_t maxstart,off_t minstart,
const off_t* sorted, off_t sortedlen,
off_t* bestlen)
{
off_t match_up = match;
off_t match_dn = match;
while (match_up>=0 && sorted[match_up]>=maxstart && sorted[match_up]<minstart) match_up--;
while (match_dn<sortedlen && sorted[match_dn]>=maxstart && sorted[match_dn]<minstart) match_dn++;
if (match_up<0 || match_dn>=sortedlen)
{
if (match_up<0 && match_dn>=sortedlen)
{
*bestlen=0;
return 0;
}
off_t pos = sorted[match_up<0 ? match_dn : match_up];
*bestlen = match_len(search, data+pos, min(searchlen, datalen-pos));
return pos;
}
return pick_best_of_two(search,searchlen, data,datalen, sorted[match_up],sorted[match_dn], bestlen);
}
static uint16_t read2_uc(const uint8_t* data)
{
return data[0]<<8 | data[1];
}
template<typename off_t>
static uint16_t read2(const uint8_t* data, off_t len)
{
if (len>=2) return read2_uc(data);
else
{
uint16_t out = (EOF_IS_LAST ? 0xFFFF : 0x0000);
if (len==1) out = (data[0]<<8) | (out&0x00FF);
return out;
}
}
template<typename off_t>
static void create_buckets(const uint8_t* data, off_t* index, off_t len, off_t* buckets)
{
off_t low = 0;
off_t high;
for (int n=0;n<65536;n++)
{
high = low+(len/131072)+1; while (true)
{
if (high > len-1) break;
off_t pos = index[high];
uint16_t here = read2(data+pos, len-pos);
if (here >= n) break;
else
{
off_t diff = high-low;
low = high;
high = high+diff*2;
}
}
if (high > len-1) high = len-1;
while (low < high)
{
off_t mid = low + (high-low)/2;
off_t midpos = index[mid];
uint16_t here = read2(data+midpos, len-midpos);
if (here < n) low = mid+1;
else high = mid;
}
buckets[n] = low;
}
buckets[65536] = len;
#ifdef TEST_CORRECT
if (buckets[0]!=0)
{
printf("e: buckets suck, [0]=%i\n", buckets[0]);
abort();
}
for (int n=0;n<65536;n++)
{
off_t low = buckets[n];
off_t high = buckets[n+1];
for (off_t i=low;i<high;i++)
{
if (read2(data+index[i], len-index[i])!=n)
{
printf("e: buckets suck, %i != (%i)[%i]%i [%i-%i]", n, i,index[i],read2(data+index[i],len-index[i]),low,high);
abort();
}
}
}
#endif
}
template<typename off_t>
static off_t find_index(off_t pos, const uint8_t* data, off_t datalen, const off_t* index, const off_t* reverse, off_t* buckets)
{
if (reverse) return reverse[pos];
uint16_t bucket = read2(data+pos, datalen-pos);
off_t low = buckets[bucket];
off_t high = buckets[bucket+1]-1;
off_t lowmatch = 2;
off_t highmatch = 2;
while (true)
{
off_t mid = low + (high-low)/2;
off_t midpos = index[mid];
if (midpos == pos) return mid;
#ifdef TEST_CORRECT
if (low >= high)
{
printf("E: [%i](%i): stuck at %i(%i)-%i(%i)\n", pos, read2_uc(data+pos),
low, read2_uc(data+index[low]), high, read2_uc(data+index[high]));
int n=0;
while (index[n]!=pos) n++;
printf("correct one is %i(%i)\n",n, read2_uc(data+index[n]));
abort();
}
#endif
off_t matchlenstart = min(lowmatch, highmatch);
off_t len = datalen - max(pos, midpos) - matchlenstart;
const uint8_t* search = data+pos+matchlenstart;
const uint8_t* here = data+midpos+matchlenstart;
while (len>0 && *search==*here)
{
search++;
here++;
len--;
}
off_t matchlen = search-data-pos;
bool less;
if (len > 0) less = (*here<*search);
else less = (here > search) ^ EOF_IS_LAST;
if (less)
{
low = mid+1;
lowmatch = matchlen;
}
else
{
high = mid-1;
highmatch = matchlen;
}
if (low+256 > high)
{
off_t i=low;
while (true)
{
if (index[i]==pos) return i;
i++;
}
}
}
}
template<typename off_t>
static void create_reverse_index(off_t* index, off_t* reverse, off_t len)
{
for (off_t i=0;i<len;i++) reverse[index[i]]=i;
}
template<typename off_t>
static off_t nextsize(off_t outpos, off_t sortedsize, off_t targetlen)
{
while (outpos >= sortedsize-256 && sortedsize < targetlen)
sortedsize = min(sortedsize*4+3, targetlen);
return sortedsize;
}
template<typename off_t>
off_t lerp(off_t x, off_t y, float frac)
{
return x + (y-x)*frac;
}
template<typename off_t>
static bpserror bps_create_suf_core(file* source, file* target, bool moremem, struct bps_creator * out)
{
#define error(which) do { err = which; goto error; } while(0)
bpserror err;
size_t realsourcelen = source->len();
size_t realtargetlen = target->len();
size_t overflowtest = realsourcelen + realtargetlen;
if (overflowtest < realsourcelen) return bps_too_big;
if ((size_t)(off_t)overflowtest != overflowtest) return bps_too_big;
if ((off_t)overflowtest < 0) return bps_too_big;
if (realsourcelen+realtargetlen >= SIZE_MAX/sizeof(off_t)) return bps_too_big;
if (realsourcelen+realtargetlen >= out->maxsize()) return bps_too_big;
off_t sourcelen = realsourcelen;
off_t targetlen = realtargetlen;
uint8_t* mem_joined = (uint8_t*)malloc(sizeof(uint8_t)*(realsourcelen+realtargetlen));
off_t* sorted = (off_t*)malloc(sizeof(off_t)*(realsourcelen+realtargetlen));
off_t* sorted_inverse = NULL;
if (moremem) sorted_inverse = (off_t*)malloc(sizeof(off_t)*(realsourcelen+realtargetlen));
off_t* buckets = NULL;
if (!sorted_inverse) buckets = (off_t*)malloc(sizeof(off_t)*65537);
if (!sorted || !mem_joined || (!sorted_inverse && !buckets))
{
free(mem_joined);
free(sorted);
free(sorted_inverse);
free(buckets);
return bps_out_of_mem;
}
off_t sortedsize = targetlen;
while (sortedsize/4 > sourcelen && sortedsize > 1024) sortedsize >>= 2;
off_t prevsortedsize = 0;
off_t outpos = 0;
goto reindex;
while (outpos < targetlen)
{
if (outpos >= sortedsize-256 && sortedsize < targetlen)
{
sortedsize = nextsize(outpos, sortedsize, targetlen);
reindex:
const float percSort = sorted_inverse ? 0.67 : 0.50;
const float percInv = sorted_inverse ? 0.11 : 0.10;
const size_t progPreSort = lerp(prevsortedsize, sortedsize, 0);
const size_t progPreInv = lerp(prevsortedsize, sortedsize, percSort);
const size_t progPreFind = lerp(prevsortedsize, sortedsize, percSort+percInv);
prevsortedsize = sortedsize;
if (!out->progress(progPreSort, targetlen)) error(bps_canceled);
if (!target->read(mem_joined, 0, sortedsize)) error(bps_io);
if (!source->read(mem_joined+sortedsize, 0, sourcelen)) error(bps_io);
out->move_target(mem_joined);
sufsort(sorted, mem_joined, sortedsize+sourcelen);
if (!out->progress(progPreInv, targetlen)) error(bps_canceled);
if (sorted_inverse)
create_reverse_index(sorted, sorted_inverse, sortedsize+sourcelen);
else
create_buckets(mem_joined, sorted, sortedsize+sourcelen, buckets);
if (!out->progress(progPreFind, targetlen)) error(bps_canceled);
}
off_t matchlen = 0;
off_t matchpos = adjust_match(find_index(outpos, mem_joined, sortedsize+sourcelen, sorted, sorted_inverse, buckets),
mem_joined+outpos, sortedsize-outpos,
mem_joined,sortedsize+sourcelen, outpos,sortedsize,
sorted, sortedsize+sourcelen,
&matchlen);
#ifdef TEST_CORRECT
if (matchlen && matchpos >= outpos && matchpos < sortedsize) puts("ERROR: found match in invalid location"),abort();
if (memcmp(mem_joined+matchpos, mem_joined+outpos, matchlen)) puts("ERROR: found match doesn't match"),abort();
#endif
off_t taken;
if (matchpos >= sortedsize) taken = out->match(false, matchpos-sortedsize, matchlen);
else taken = out->match(true, matchpos, matchlen);
#ifdef TEST_CORRECT
if (taken < 0) puts("ERROR: match() returned negative"),abort();
if (matchlen >= 7 && taken < matchlen) printf("ERROR: match() took %i bytes, offered %i\n", taken, matchlen),abort();
#endif
outpos += taken;
}
out->finish(mem_joined+sortedsize, mem_joined);
err = bps_ok;
error:
free(buckets);
free(sorted_inverse);
free(sorted);
free(mem_joined);
return err;
}
bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patchmem,
bool (*progress)(void* userdata, size_t done, size_t total), void* userdata, bool moremem)
{
bps_creator bps(source, target, metadata);
bps.setProgress(progress, userdata);
size_t maindata = bps.outlen;
bpserror err = bps_create_suf_core<int32_t>(source, target, moremem, &bps);
if (err!=bps_ok) return err;
*patchmem = bps.getpatch();
while ((patchmem->ptr[maindata]&0x80) == 0x00) maindata++;
if (maindata==patchmem->len-12-1) return bps_identical;
return bps_ok;
}
enum bpserror bps_create_delta_inmem(struct mem source, struct mem target, struct mem metadata, struct mem * patch,
bool (*progress)(void* userdata, size_t done, size_t total), void* userdata,
bool moremem)
{
class memfile : public file {
public:
const uint8_t * m_ptr;
size_t m_len;
size_t len() { return m_len; }
bool read(uint8_t* target, size_t start, size_t len) { memcpy(target, m_ptr+start, len); return true; }
memfile(const uint8_t * ptr, size_t len) : m_ptr(ptr), m_len(len) {}
};
memfile sourcef(source.ptr, source.len);
memfile targetf(target.ptr, target.len);
return bps_create_delta(&sourcef, &targetf, metadata, patch, progress, userdata, moremem);
}
#ifdef BPS_STANDALONE
#include <stdio.h>
static struct mem ReadWholeFile(const char * filename)
{
struct mem null = {NULL, 0};
FILE * file=fopen(filename, "rb");
if (!file) return null;
fseek(file, 0, SEEK_END);
size_t len=ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char * data=(unsigned char*)malloc(len);
size_t truelen=fread(data, 1,len, file);
fclose(file);
if (len!=truelen)
{
free(data);
return null;
}
struct mem ret = { (unsigned char*)data, len };
return ret;
}
static bool WriteWholeFile(const char * filename, struct mem data)
{
FILE * file=fopen(filename, "wb");
if (!file) return false;
unsigned int truelen=fwrite(data.ptr, 1,data.len, file);
fclose(file);
return (truelen==data.len);
}
int main(int argc, char * argv[])
{
struct mem in = ReadWholeFile(argv[1]);
struct mem out = ReadWholeFile(argv[2]);
struct mem null = {NULL, 0};
struct mem p={NULL,0};
bps_create_delta(in,out,null,&p, NULL,NULL);
printf("len=%lu \n",p.len);
printf("check=%.8X\n",*(uint32_t*)(p.ptr+p.len-4));
WriteWholeFile(argv[3], p);
free(in.ptr);
free(out.ptr);
free(p.ptr);
#ifdef TEST_PERF
printf("%i/%i=%f\n",match_len_tot,match_len_n,(float)match_len_tot/match_len_n);
#endif
}
#endif