//Module name: libbps //Author: Alcaro //Date: December 20, 2014 //Licence: GPL v3.0 or higher #include "libbps.h" #include //malloc, realloc, free #include //memcpy, memset #include //uint8_t, uint32_t #include "crc32.h"//crc32 static uint32_t read32(uint8_t * ptr) { uint32_t out; out =ptr[0]; out|=ptr[1]<<8; out|=ptr[2]<<16; out|=ptr[3]<<24; return out; } enum { SourceRead, TargetRead, SourceCopy, TargetCopy }; #define error(which) do { error=which; goto exit; } while(0) #define assert_sum(a,b) do { if (SIZE_MAX-(a)<(b)) error(bps_too_big); } while(0) #define assert_shift(a,b) do { if (SIZE_MAX>>(b)<(a)) error(bps_too_big); } while(0) enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input) { enum bpserror error = bps_ok; out->len=0; out->ptr=NULL; if (metadata) { metadata->len=0; metadata->ptr=NULL; } if (patch.len<4+3+12) return bps_broken; if (true) { #define read8() (*(patchat++)) #define decodeto(var) \ do { \ var=0; \ unsigned int shift=0; \ while (true) \ { \ uint8_t next=read8(); \ assert_shift(next&0x7F, shift); \ size_t addthis=(next&0x7F)<len=outlen; out->ptr=(uint8_t*)malloc(outlen); const uint8_t * instart=in.ptr; const uint8_t * inreadat=in.ptr; const uint8_t * inend=in.ptr+in.len; uint8_t * outstart=out->ptr; uint8_t * outreadat=out->ptr; uint8_t * outat=out->ptr; uint8_t * outend=out->ptr+out->len; size_t metadatalen; decodeto(metadatalen); if (metadata && metadatalen) { metadata->len=metadatalen; metadata->ptr=(uint8_t*)malloc(metadatalen+1); for (size_t i=0;iptr[i]=read8(); metadata->ptr[metadatalen]='\0';//just to be on the safe side - that metadata is assumed to be text, might as well terminate it } else { for (size_t i=0;i>2)+1; int action=(thisinstr&3); if (outat+length>outend) error(bps_broken); switch (action) { case SourceRead: { if (outat-outstart+length > in.len) error(bps_broken); for (size_t i=0;ipatchend) error(bps_broken); for (size_t i=0;i>1; if ((encodeddistance&1)==0) inreadat+=distance; else inreadat-=distance; if (inreadatinend) error(bps_broken); for (size_t i=0;i>1; if ((encodeddistance&1)==0) outreadat+=distance; else outreadat-=distance; if (outreadat=outat || outreadat+length>outend) error(bps_broken); for (size_t i=0;iptr, out->len); if (crc_out_a!=crc_out_e) { error=bps_not_this; if (!accept_wrong_input) goto exit; } return error; #undef read8 #undef decodeto #undef write8 } exit: free(out->ptr); out->len=0; out->ptr=NULL; if (metadata) { free(metadata->ptr); metadata->len=0; metadata->ptr=NULL; } return error; } #define write(val) \ do { \ out[outlen++]=(val); \ if (outlen==outbuflen) \ { \ outbuflen*=2; \ out=(uint8_t*)realloc(out, outbuflen); \ } \ } while(0) #define write32(val) \ do { \ uint32_t tmp=(val); \ write(tmp); \ write(tmp>>8); \ write(tmp>>16); \ write(tmp>>24); \ } while(0) #define writenum(val) \ do { \ size_t tmpval=(val); \ while (true) \ { \ uint8_t tmpbyte=(tmpval&0x7F); \ tmpval>>=7; \ if (!tmpval) \ { \ write(tmpbyte|0x80); \ break; \ } \ write(tmpbyte); \ tmpval--; \ } \ } while(0) enum bpserror bps_create_linear(struct mem sourcemem, struct mem targetmem, struct mem metadata, struct mem * patchmem) { if (sourcemem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;//the 16 is just to be on the safe side, I don't think it's needed. if (targetmem.len>=(SIZE_MAX>>2) - 16) return bps_too_big; const uint8_t * source=sourcemem.ptr; const uint8_t * sourceend=sourcemem.ptr+sourcemem.len; if (sourcemem.len>targetmem.len) sourceend=sourcemem.ptr+targetmem.len; const uint8_t * targetbegin=targetmem.ptr; const uint8_t * target=targetmem.ptr; const uint8_t * targetend=targetmem.ptr+targetmem.len; const uint8_t * targetcopypos=targetbegin; size_t outbuflen=4096; uint8_t * out=(uint8_t*)malloc(outbuflen); size_t outlen=0; write('B'); write('P'); write('S'); write('1'); writenum(sourcemem.len); writenum(targetmem.len); writenum(metadata.len); for (size_t i=0;i1) { //assert_shift((numunchanged-1), 2); writenum((numunchanged-1)<<2 | 0);//SourceRead source+=numunchanged; target+=numunchanged; } size_t numchanged=0; if (lastknownchange>target) numchanged=lastknownchange-target; while ((source+numchanged>=sourceend || source[numchanged]!=target[numchanged] || source[numchanged+1]!=target[numchanged+1] || source[numchanged+2]!=target[numchanged+2]) && target+numchanged=sourceend) numchanged=targetend-target; } lastknownchange=target+numchanged; if (numchanged) { //assert_shift((numchanged-1), 2); size_t rle1start=(target==targetbegin); while (true) { if ( target[rle1start-1]==target[rle1start+0] && target[rle1start+0]==target[rle1start+1] && target[rle1start+1]==target[rle1start+2] && target[rle1start+2]==target[rle1start+3]) { numchanged=rle1start; break; } if ( target[rle1start-2]==target[rle1start+0] && target[rle1start-1]==target[rle1start+1] && target[rle1start+0]==target[rle1start+2] && target[rle1start+1]==target[rle1start+3] && target[rle1start+2]==target[rle1start+4]) { numchanged=rle1start; break; } if (rle1start+3>=numchanged) break; rle1start++; } if (numchanged) { writenum((numchanged-1)<<2 | TargetRead); for (size_t i=0;iptr=out; patchmem->len=outlen; //while this may look like it can be fooled by a patch containing one of any other command, it // can't, because the ones that aren't SourceRead requires an argument. size_t i; for (i=mainContentPos;(out[i]&0x80)==0x00;i++) {} if (i==outlen-12-1) return bps_identical; return bps_ok; } #undef write_nocrc #undef write #undef writenum enum bpserror bps_get_checksums(file* patch, uint32_t * inromsum, uint32_t * outromsum, uint32_t * patchsum) { size_t len = patch->len(); if (len<4+3+12) return bps_broken; uint8_t verify[4]; if (!patch->read(verify, 0, 4) || memcmp(verify, "BPS1", 4)) return bps_broken; uint8_t checksums[12]; if (!patch->read(checksums, len-12, 12)) return bps_broken; if (inromsum) *inromsum =read32(checksums+0); if (outromsum) *outromsum=read32(checksums+4); if (patchsum) *patchsum =read32(checksums+8); return bps_ok; } void bps_free(struct mem mem) { free(mem.ptr); } #if 0 #warning Disable this in release versions. #include //Congratulations, you found the undocumented feature! It compares two equivalent BPS patches and // tells where each one is more compact. (It crashes or gives bogus answers on invalid or // non-equivalent patches.) Have fun. void bps_compare(struct mem patch1mem, struct mem patch2mem) { const uint8_t * patch[2]={patch1mem.ptr, patch2mem.ptr}; size_t patchpos[2]={0,0}; size_t patchlen[2]={patch1mem.len-12, patch2mem.len-12}; size_t patchoutpos[2]={0,0}; size_t patchcopypos[2][4]={0,0};//[0] and [1] are unused, but this is just debug code, it doesn't need to be neat. #define read8(id) (patch[id][patchpos[id]++]) #define decodeto(id, var) \ do { \ var=0; \ int shift=0; \ while (true) \ { \ uint8_t next=read8(id); \ size_t addthis=(next&0x7F)<=patchoutpos[1])}; char describe[2][256]; for (int i=0;i<2;i++) { if (step[i]) { size_t patchposstart=patchpos[i]; decodeto(i, tempuint); size_t len=(tempuint>>2)+1; patchoutpos[i]+=len; int action=(tempuint&3); //enum { SourceRead, TargetRead, SourceCopy, TargetCopy }; const char * actionnames[]={"SourceRead", "TargetRead", "SourceCopy", "TargetCopy"}; if (action==TargetRead) patchpos[i]+=len; if (action==SourceCopy || action==TargetCopy) { decodeto(i, tempuint); int delta = tempuint>>1; if (tempuint&1) delta=-delta; patchcopypos[i][action]+=delta; sprintf(describe[i], "%s from %i (%+i) for %i in %i", actionnames[action], patchcopypos[i][action], delta, len, patchpos[i]-patchposstart); patchcopypos[i][action]+=len; } else sprintf(describe[i], "%s from %i for %i in %i", actionnames[action], patchoutpos[i], len, patchpos[i]-patchposstart); if (!step[i^1]) { printf("%i: %s\n", i+1, describe[i]); show=true; } } } if (step[0] && step[1]) { if (!strcmp(describe[0], describe[1])) /*printf("3: %s\n", describe[0])*/; else { printf("1: %s\n2: %s\n", describe[0], describe[1]); show=true; } } if (patchoutpos[0]==patchoutpos[1]) { size_t used[2]={patchpos[0]-patchposatmatch[0], patchpos[1]-patchposatmatch[1]}; char which='='; if (used[0]used[1]) which='-'; if (show) { printf("%c: %i,%i bytes since last match (%i)\n", which, used[0], used[1], patchoutpos[0]); show=false; } patchposatmatch[0]=patchpos[0]; patchposatmatch[1]=patchpos[1]; lastmatch=patchoutpos[0]; } } } 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; } int main(int argc,char**argv) { bps_compare(ReadWholeFile(argv[1]),ReadWholeFile(argv[2])); } #endif