diff --git a/_cache_service_worker.js b/_cache_service_worker.js index fce3417..efe9ed6 100644 --- a/_cache_service_worker.js +++ b/_cache_service_worker.js @@ -12,7 +12,7 @@ limitations under the License. mod by marcrobledo, original from: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/basic/service-worker.js */ -const PRECACHE_ID='v20180830'; +const PRECACHE_ID='v20180918'; const PRECACHE_FILES=[ 'index.html','./', 'RomPatcher.css', diff --git a/index.html b/index.html index 46bdaf2..86dbd84 100644 --- a/index.html +++ b/index.html @@ -16,8 +16,7 @@ - - +
@@ -54,7 +53,7 @@
-
+
@@ -104,7 +103,8 @@ diff --git a/xdelta.js b/xdelta.js new file mode 100644 index 0000000..0c1e7b8 --- /dev/null +++ b/xdelta.js @@ -0,0 +1,973 @@ +/* xdelta3 module for RomPatcher.js v20180918 - Marc Robledo 2016-2018 - http://www.marcrobledo.com/license */ + +var XDELTA_MAGIC=0xd6c3c400; + +function XDelta(patchData){ + this.patchData=patchData; +} +XDelta.prototype.toString=function(){ + return '' +} + +/* code borrowed from https://github.com/google/xdelta3-decoder-js/blob/master/tests/testA.html */ +XDelta.prototype.apply=function(romFile){ + var target; + try{ + target=XDelta3Decoder.decode(this.patchData, new Uint8Array(romFile.fileReader.result)); + }catch(e){ + alert('EXCEPTION: ' + e.message); + return false; + } + + var tempFile=new MarcBinFile(target.byteLength); + var targetUint8Array=new Uint8Array(target); + for(var i=0; i 0) { + this.acache.near_array = allocArray(this.acache.s_near, 0); + } + if (this.acache.s_same > 0) { + this.acache.same_array = allocArray(this.acache.s_same * 256, 0); + } + }; + + /** + * Parses the delta file data and produces the targetWindow data. + * @return {!Uint8Array} + */ + _XDelta3Decoder.prototype.xd3_decode_input = function() { + + if (this.delta[0] != 0xD6 || // 'V' with MSB set + this.delta[1] != 0xC3 || // 'C' with MSB set + this.delta[2] != 0xC4 || // 'D' with MSB set + this.delta[3] != 0) { // unused but be set to zero + throw new Error('XD3_INVALID_INPUT invalid magic'); + } + this.position = 4; + + this.dec_hdr_ind = this.delta[this.position++]; + if (this.dec_hdr_ind & VCD_INVHDR) { + throw new Error('VCD_INVHDR unrecognized header indicator bits set'); + } + + if (this.dec_hdr_ind & VCD_SECONDARY) { + throw new Error('VCD_SECONDARY not implemented'); + } + + if (this.dec_hdr_ind & VCD_CODETABLE) { + throw new Error('VCD_CODETABLE support was removed'); + } else { + /* Use the default table. */ + this.acache.s_near = __rfc3284_code_table_desc.near_modes; + this.acache.s_same = __rfc3284_code_table_desc.same_modes; + this.code_table = xd3_rfc3284_code_table(); + } + + this.xd3_alloc_cache(); + + if (this.dec_hdr_ind & VCD_APPHEADER) { + this.dec_appheadsz = this.getInteger(); + // Note: appHeader does not have a 0-termination. + this.dec_apphead = this.xd3_alloc(this.dec_appheadsz + 1); + this.xd3_decode_bytes(this.dec_apphead, 0, this.dec_appheadsz); + this.dec_apphead[this.dec_appheadsz + 1] = 0; + } + + //var targetLength = 0; + while (true) { + if (this.position >= this.delta.length) { + break; + } + //targetLength += + this.handleWindow(); + } + return this.dec_buffer.bytes; + }; + + _XDelta3Decoder.prototype.xd3_decode_init_window = function() { + this.dec_cpylen = 0; + this.dec_cpyoff = 0; + // this.dec_cksumbytes = 0; + + xd3_init_cache(this.acache); + } + + _XDelta3Decoder.prototype.handleWindow = function() { + this.dec_win_ind = this.delta[this.position++]; // DEC_WININD + + if (this.dec_win_ind & ~7) { + throw new Error('VCD_INVWIN unexpected bits set'); + } + + this.current_window = this.dec_window_count; + + this.dec_winstart += this.dec_tgtlen; + + this.xd3_decode_init_window(); + var SRCORTGT = VCD_SOURCE | VCD_TARGET; + var srcortgt = SRCORTGT & this.dec_win_ind; + + // If using a source or target data segment: read the lenght and position + // integers. + if (srcortgt) { + this.dec_cpylen = this.getInteger(); // DEC_CPYLEN + } + this.dec_position = this.dec_cpylen; + if (srcortgt) { + var sourcePosition = this.getInteger(); // DEC_CPYOFF + this.dec_cpyoff = sourcePosition; + } + + this.dec_enclen = this.getInteger(); // DEC_ENCLEN + + // Calculate the position if the delta was actually read. + // var positionAfterDelta = this.position + this.dec_enclen; + + // Get the target window length. + this.dec_tgtlen = this.getInteger(); // DEC_TGTLEN + + this.dec_del_ind = this.getByte(); // DEC_DELIND + + this.data_sect.size = this.getInteger(); // DEC_DATALEN + this.inst_sect.size = this.getInteger(); // DEC_INSTLEN + this.addr_sect.size = this.getInteger(); // DEC_ADDRLEN + + if (this.dec_win_ind & VCD_ADLER32) { // DEC_CKSUM + this.dec_cksum = this.xd3_decode_allocate(4); + for (var i = 0; i < 4; i += 1) { + this.dec_adler32 = (this.dec_adler32 << 8) | this.dec_cksum[i]; + } + } + + this.xd3_decode_sections(); + + /* In the C++ code: + * To speed VCD_SOURCE block-address calculations, the source + * cpyoff_blocks and cpyoff_blkoff are pre-computed. + * However, in this Javascript code there is no 'blocks'. + */ + if (this.dec_win_ind & VCD_SOURCE) { + this.src.cpyoff_blkoff = this.dec_cpyoff; + } + this.xd3_decode_emit(); + + return this.dec_tgtlen; + }; + + /** + * This function only has code if the preprocessor statement + * "#if SECONDARY_ANY" is set. SECONDARY_ANY does not seem to be set. + */ + _XDelta3Decoder.prototype.xd3_decode_secondary_sections = function() { // + }; + + /** + * @param {!xd3_desect} sect + */ + _XDelta3Decoder.prototype.xd3_decode_section = function(sect) { + // It is possible to just point into the buffer but perhaps that can be done + // later. + sect.bytes = this.xd3_decode_allocate(sect.size); + }; + + _XDelta3Decoder.prototype.xd3_decode_sections = function() { + this.xd3_decode_section(this.data_sect); + this.xd3_decode_section(this.inst_sect); + this.xd3_decode_section(this.addr_sect); + + this.xd3_decode_secondary_sections(); + + this.xd3_decode_setup_buffers(); + }; + + _XDelta3Decoder.prototype.xd3_decode_setup_buffers = function() { + this.dec_buffer = new DataObject(new Uint8Array(this.dec_tgtlen)); + }; + + var VCD_SELF = 0; + var VCD_HERE = 1; + + /** + * xd3_decode_address + * @param {number} here + * @param {number} mode + * @param {!xd3_desect} sect + */ + _XDelta3Decoder.prototype.xd3_decode_address = function(here, mode, sect) { + var val; + var same_start = 2 + this.acache.s_near; + + if (mode < same_start) { + val = sect.getInteger(); + switch (mode) { + case VCD_SELF: + break; + case VCD_HERE: + // var old_val = val; + val = here - val; + break; + default: + val += this.acache.near_array[mode - 2]; + } + } else { + mode -= same_start; + var offset = sect.getByte(); + val = this.acache.same_array[mode * 256 + offset]; + } + + this.xd3_update_cache(this.acache, val); + + return val; + }; + + /** + * @param {!xd3_addr_cache} acache + * @param {number} addr + */ + _XDelta3Decoder.prototype.xd3_update_cache = function(acache, addr) { + if (acache.s_near > 0) { + acache.near_array[acache.next_slot] = addr; + acache.next_slot = (acache.next_slot + 1) % acache.s_near; + } + + if (acache.s_same > 0) { + acache.same_array[addr % (acache.s_same * 256)] = addr; + } + }; + + /** + * @param {!xd3_hinst} inst + */ + _XDelta3Decoder.prototype.xd3_decode_output_halfinst = function(inst) { + var take = inst.size; + var blkoff; + + switch (inst.type) { + case XD3_RUN: + var val = this.data_sect.getByte(); + this.dec_buffer.fill(val, take); + break; + + case XD3_ADD: + this.dec_buffer.copySect(this.data_sect, take); + break; + + default: + var overlap; + var overlap_pos; + if (inst.addr < this.dec_cpylen) { + overlap = 0; + if (this.dec_win_ind & VCD_TARGET) { + throw new Error('VCD_TARGET not supported'); + } else { + blkoff = this.src.cpyoff_blkoff; + blkoff = this.dec_cpyoff + inst.addr; + } + } else { + overlap = 1; + overlap_pos = inst.addr - this.dec_cpylen; + } + if (overlap) { + this.dec_buffer.copyBytes(this.dec_buffer.bytes, overlap_pos, take); + } else { + this.dec_buffer.copyBytes(this.source.bytes, blkoff, take); + } + } + }; + + /** + * xref: xd3_decode_parse_halfinst + * @param {!xd3_hinst} inst + */ + _XDelta3Decoder.prototype.xd3_decode_parse_halfinst = function(inst) { + // Get size and address if necessary. + if (inst.size == 0) { + inst.size = this.inst_sect.getInteger(); + } + + /* For copy instructions, read address. */ + if (inst.type >= XD3_CPY) { + var mode = inst.type - XD3_CPY; + inst.addr = + this.xd3_decode_address(this.dec_position, mode, this.addr_sect); + } + + this.dec_position += inst.size; + }; + + /** + * xref: xd3_decode_instruction + */ + _XDelta3Decoder.prototype.xd3_decode_instruction = function() { + var code_table = this.code_table; + var instPair = this.inst_sect.getByte(); + + this.dec_current1.type = code_table.tableRows[instPair].type1; + this.dec_current1.size = code_table.tableRows[instPair].size1; + // dec_current1.addr keeps it previous value. + + this.dec_current2.type = code_table.tableRows[instPair].type2; + this.dec_current2.size = code_table.tableRows[instPair].size2; + // dec_current2.addr keeps it previous value. + + + /* For each instruction with a real operation, decode the + * corresponding size and addresses if necessary. Assume a + * code-table may have NOOP in either position, although this is + * unlikely. */ + if (this.dec_current1.type != XD3_NOOP) { + this.xd3_decode_parse_halfinst(this.dec_current1); + } + if (this.dec_current2.type != XD3_NOOP) { + this.xd3_decode_parse_halfinst(this.dec_current2); + } + }; + + _XDelta3Decoder.prototype.xd3_decode_finish_window = function() { + // stream->dec_winbytes = 0; + // stream->dec_state = DEC_FINISH; + this.data_sect.pos = 0; + this.inst_sect.pos = 0; + this.addr_sect.pos = 0; + }; + + _XDelta3Decoder.prototype.xd3_decode_emit = function() { + + var instLength = this.inst_sect.bytes.byteLength; + /* Decode next instruction pair. */ + while (this.inst_sect.pos < instLength) { + this.xd3_decode_instruction(); + + /* Output dec_current1 */ + if (this.dec_current1.type != XD3_NOOP) { + this.xd3_decode_output_halfinst(this.dec_current1); + } + /* Output dec_current2 */ + if (this.dec_current2.type != XD3_NOOP) { + this.xd3_decode_output_halfinst(this.dec_current2); + } + } + if (this.dec_win_ind & VCD_ADLER32) { + var a32 = adler32(1, this.dec_buffer.bytes, 0, this.dec_tgtlen); + if (a32 != this.dec_adler32) { + throw new Error('target window checksum mismatch'); + } + } + + /* Finished with a window. */ + this.xd3_decode_finish_window(); + }; + + _XDelta3Decoder.prototype.xd3_alloc = function(length) { + return new Uint8Array(length); + }; + + _XDelta3Decoder.prototype.xd3_decode_bytes = function(bytes, pos, length) { + for (var i = 0; i < length; i++) { + bytes[pos + i] = this.delta[this.position++]; + } + }; + + _XDelta3Decoder.prototype.xd3_decode_allocate = function(length) { + var bytes = + new Uint8Array(this.delta.slice(this.position, this.position + length)); + this.position += length; + return bytes; + }; + + _XDelta3Decoder.prototype.getByte = function() { + return this.delta[this.position++]; + }; + + _XDelta3Decoder.prototype.getInteger = function() { + var maxBytes = Math.min(20, this.delta.length - this.position); + var integer = 0; + for (var i = 0; i < maxBytes; i++) { + var aPart = this.delta[this.position++]; + integer += aPart & 0x7F; + if (!(aPart & 0x80)) { + return integer; + } + integer <<= 7; + } + throw new Error('delta integer too long'); + }; + + /** + * The code table. + * @param {!Array} tableRows + * @constructor + * @struct + */ + var xd3_dinst_table = function(tableRows) { + /** @type {!Array} */ + this.tableRows = tableRows; + }; + + /** + * xd3_hinst + * @constructor + */ + function xd3_hinst() { + this.type = XD3_NOOP; + this.size = 0; + this.addr = 0; + } + + /** + * The code-table double instruction. + * @constructor + */ + function xd3_dinst() { + /** @type {number} */ + this.type1 = XD3_NOOP; + /** @type {number} */ + this.size1 = 0; + /** @type {number} */ + this.type2 = XD3_NOOP; + /** @type {number} */ + this.size2 = 0; + } + + /** + * @param {!xd3_code_table_desc} desc + * @return {!xd3_dinst_table} + */ + function xd3_build_code_table(desc) { + var row = 0; + var tableRows = new Array(256); + for (var i = 0; i < 256; i++) { + tableRows[i] = new xd3_dinst(); + } + var cpyModes = 2 + desc.near_modes + desc.same_modes; + + // The single RUN command. + tableRows[row++].type1 = XD3_RUN; + + // The ADD only commands. + tableRows[row++].type1 = XD3_ADD; + for (var size1 = 1; size1 <= desc.add_sizes; size1++) { + tableRows[row].type1 = XD3_ADD; + tableRows[row++].size1 = size1; + } + + // The Copy only commands. + for (var mode = 0; mode < cpyModes; mode++) { + tableRows[row++].type1 = XD3_CPY + mode; + + for (var size1 = MIN_MATCH; size1 < MIN_MATCH + desc.cpy_sizes; size1++) { + tableRows[row].type1 = XD3_CPY + mode; + tableRows[row++].size1 = size1; + } + } + + // The Add/Copy commands. + for (var mode = 0; mode < cpyModes; mode++) { + for (var size1 = 1; size1 <= desc.addcopy_add_max; size1++) { + var max = (mode < 2 + desc.near_modes) ? // + desc.addcopy_near_cpy_max : + desc.addcopy_same_cpy_max; + for (var size2 = MIN_MATCH; size2 <= max; size2++) { + tableRows[row].type1 = XD3_ADD; + tableRows[row].size1 = size1; + tableRows[row].type2 = XD3_CPY + mode; + tableRows[row++].size2 = size2; + } + } + } + + // The Copy/Add commands. + for (var mode = 0; mode < cpyModes; mode++) { + var max = (mode < 2 + desc.near_modes) ? // + desc.copyadd_near_cpy_max : + desc.copyadd_same_cpy_max; + for (var size1 = MIN_MATCH; size1 <= max; size1++) { + for (var size2 = 1; size2 <= desc.copyadd_add_max; size2++) { + tableRows[row].type1 = XD3_CPY + mode; + tableRows[row].size1 = size1; + tableRows[row].type2 = XD3_ADD; + tableRows[row++].size2 = size2; + } + } + } + + return new xd3_dinst_table(tableRows); + } + + + /** + * @constructor + */ + function xd3_code_table_desc() { + this.add_sizes = 0; + this.near_modes = 0; + this.same_modes = 0; + this.cpy_sizes = 0; + + this.addcopy_add_max = 0; + this.addcopy_near_cpy_max = 0; + this.addcopy_same_cpy_max = 0; + + this.copyadd_add_max = 0; + this.copyadd_near_cpy_max = 0; + this.copyadd_same_cpy_max = 0; + } + + + /** + * This builds the __rfc3284_code_table_desc + * Assumes a single RUN instruction + * Assumes that MIN_MATCH is 4. + * @return {!xd3_code_table_desc} + */ + function build_rfc3284_code_table_desc() { + var desc = new xd3_code_table_desc(); + desc.add_sizes = 17; + desc.near_modes = 4; + desc.same_modes = 3; + desc.cpy_sizes = 15; + + desc.addcopy_add_max = 4; + desc.addcopy_near_cpy_max = 6; + desc.addcopy_same_cpy_max = 4; + + desc.copyadd_add_max = 1; + desc.copyadd_near_cpy_max = 4; + desc.copyadd_same_cpy_max = 4; + + // xd3_code_table_sizes addcopy_max_sizes[MAX_MODES]; + // { {6,163,3},{6,175,3},{6,187,3},{6,199,3},{6,211,3},{6,223,3}, + // {4,235,1},{4,239,1},{4,243,1} }, + + // xd3_code_table_sizes copyadd_max_sizes[MAX_MODES]; + // { {4,247,1},{4,248,1},{4,249,1},{4,250,1},{4,251,1},{4,252,1}, + // {4,253,1},{4,254,1},{4,255,1} }, + + return desc; + } + + var __rfc3284_code_table_desc = build_rfc3284_code_table_desc(); + + var A32_BASE = 65521; /* Largest prime smaller than 2^16 */ + var A32_NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + + (n+1)(BASE-1) <= 2^32-1 */ + + // 1140 #define A32_DO1(buf,i) {s1 += buf[i]; s2 += s1;} + // 1141 #define A32_DO2(buf,i) A32_DO1(buf,i); A32_DO1(buf,i+1); + // 1142 #define A32_DO4(buf,i) A32_DO2(buf,i); A32_DO2(buf,i+2); + // 1143 #define A32_DO8(buf,i) A32_DO4(buf,i); A32_DO4(buf,i+4); + // 1144 #define A32_DO16(buf) A32_DO8(buf,0); A32_DO8(buf,8); + + + /** + * Calculated the Adler32 checksum. + * @param {number} adler I'm not sure what this is. + * @param {!Uint8Array} buf + * @param {number} pos + * @param {number} len + * @return {number} + */ + function adler32(adler, buf, pos, len) { + var s1 = adler & 0xffff; + var s2 = (adler >> 16) & 0xffff; + var k; + + while (len > 0) { + k = (len < A32_NMAX) ? len : A32_NMAX; + len -= k; + + if (k != 0) { + do { + s1 += buf[pos++]; + s2 += s1; + } while (--k); + } + + s1 %= A32_BASE; + s2 %= A32_BASE; + } + + return (s2 << 16) | s1; + } + + + /** + * @constructor + */ + function xd3_addr_cache(s_near, s_same) { + this.s_near = s_near; + this.s_same = s_same; + this.next_slot = 0; /* the circular index for near */ + this.near_array = null; /* array of size s_near */ + this.same_array = null; /* array of size s_same*256 */ + } + + + /** + * @param {!xd3_addr_cache} acache + */ + function xd3_init_cache(acache) { + if (acache.s_near > 0) { + for (var i = 0; i < acache.near_array.length; i++) { + acache.near_array[i] = 0; + } + acache.next_slot = 0; + } + + if (acache.s_same > 0) { + for (var i = 0; i < acache.same_array.length; i++) { + acache.same_array[i] = 0; + } + } + } + + /** + * Used by the decoder to buffer input in sections. + * XDelta3 C++ struct. + * @constructor + * @struct + */ + function xd3_desect() { + /** + * The buffer as a slice of the backingBuffer; + * @type {?Uint8Array} + */ + this.bytes = null; + + /** @type {number} */ + this.size = 0; + + /** @type {number} */ + this.pos = 0; + } + + /** + * Gets a byte from the section. + * @return {number} + */ + xd3_desect.prototype.getByte = function() { + if (!this.bytes) { + throw new Error('bytes not set'); + } + return this.bytes[this.pos++]; + }; + + /** + * Gets an integer from the section. + * XDelta3 integers are encodes as a variable number of 7 bit bytes. Bit 8, the + * most significant bit is used to indicate more bytes needed. + * @return {number} + */ + xd3_desect.prototype.getInteger = function() { + if (!this.bytes) { + throw new Error('bytes not set'); + } + var val = 0; + for (var i = 0; i < 10; i++) { + var aByte = this.bytes[this.pos++]; + val += aByte & 0x7F; + if (!(aByte & 0x80)) { + return val; + } + val <<= 7; + } + throw new Error('invalid number'); + }; + + + /** + * Builds a default code table. + * @return {!xd3_dinst_table} + */ + function xd3_rfc3284_code_table() { + return xd3_build_code_table(__rfc3284_code_table_desc); + } + + /** + * Allocates and initializes a Javascript Array. + * @return {!Array} + */ + function allocArray(len, val) { + var arr = new Array(len); + for (var i = 0; i < len; i++) { + arr[i] = val; + } + return arr; + } + + /** + * @constructor + */ + function xd3_source() { + /** @type {number} */ + this.cpyoff_blkoff = -1; + } + + /** + * @param {!Uint8Array} bytes + * @constructor + */ + function DataObject(bytes) { + this.pos = 0; + this.bytes = bytes; + }; + + DataObject.prototype.getByte = function() { + return this.bytes[this.pos++]; + }; + + DataObject.prototype.getInteger = function() { + var val = 0; + for (var i = 0; i < 10; i++) { + var aByte = this.bytes[this.pos++]; + val += aByte & 0x7F; + if (!(aByte & 0x80)) { + return val; + } + val <<= 7; + } + throw new Error('invalid number'); + }; + + DataObject.prototype.fill = function(val, length) { + // TODO(bstell): see if there is a function for this. + for (var i = 0; i < length; i++) { + this.bytes[this.pos++] = val; + } + }; + + /** + * @param {!xd3_desect} sect + * @param {number} length + */ + DataObject.prototype.copySect = function(sect, length) { + // TODO(bstell): see if there is a function for this. + for (var i = 0; i < length; i++) { + this.bytes[this.pos++] = sect.bytes[sect.pos++]; + } + }; + + DataObject.prototype.copyBytes = function(bytes, offset, length) { + // TODO(bstell): see if there is a function for this. + for (var i = 0; i < length; i++) { + this.bytes[this.pos++] = bytes[offset++]; + } + }; + +})();