UNPKG

miijs

Version:

Read, Edit, Write, and make Special Miis from a Wiimote binary file or 3DS QR Code to a binary file or QR code

1,141 lines (1,067 loc) 43.4 kB
// @ts-check /*! * struct-fu library: https://github.com/natevw/struct-fu * forked by ariankordi: https://github.com/ariankordi/struct-fu * @author Nathan Vander Wilt <https://github.com/natevw> * @license Apache-2.0 * @license BSD-2-Clause */ // // --------------------------------------------------------------------- // // Exported JSDoc Type Definitions // // --------------------------------------------------------------------- /** * A type representing either a raw ArrayBuffer or a typed view of it. * @typedef {ArrayBuffer | Uint8Array} BufferSource */ /** * A type for the offset/"cursor" used to track the byte and bit offsets. * @typedef {Object} Offset * @property {number} bytes - Current byte offset. * @property {number} [bits] - Current bit offset. */ /** * A transform interface for bitfields, converting between raw number bits and user values. * Notably, the input/output can be number or boolean. (Used by `b2v` and `v2b` methods.) * @typedef {Object} BitTransform * @property {function(this: {width:number}, number): number|boolean} b2v - Called when reading bits from buffer. * @property {function(this: {width:number}, number|boolean): number} v2b - Called when writing bits into buffer. * @property {number} [width=1] - The width (in bits) of this bitfield. * @property {boolean|null} [littleEndian] - Whether or not the bitfield is little-endian. */ /** * A transform interface for byte fields, converting between raw bytes and user values. * (Used by `b2v` and `vTb` methods.) * @typedef {Object} ByteTransform * @property {function(BufferSource): *} b2v - Called when reading bytes from buffer. * @property {function(*, Uint8Array): number} vTb - Called when writing values into buffer. * @property {number} [size=1] - The size (in bytes) of this field. */ /** * Represents a padding field that ensures proper byte alignment in a struct. * It does not hold a value but affects struct layout. * @typedef {Object} PaddingField * @property {number} _padTo - The byte offset to pad to. * @property {string} [_id] - Internal unique ID. */ /** * A single field definition, which must define how to read (unpack) and write (pack) data. * This is the base type that all specialized fields (bitfields, bytefields, etc.) implement. * @typedef {Object} Field * @property {string} [name] - The name of the field (if any). * @property {number} size - The size of the field in bytes (for non-bitfield types). * @property {number} [width] - The size of the field in bits (for bitfields). * @property {number | Offset} [offset] - Byte or (byte, bits) offset. * @property {function(BufferSource, Offset=): *} unpack - Unpacks the field value from a buffer (`valueFromBytes`). * @property {function(*, BufferSource=, Offset=): Uint8Array} pack - Packs a value into a buffer (`bytesFromValue`). * @property {Object<string, Field>|null} [_hoistFields] - If this is a nested struct, this maps sub-fields to their definitions. * @property {Object<string, Field>} [fields] - An object mapping field names to their definitions. */ /** * Template for the return type of _.struct(), representing an instance of a structure with pack/unpack methods. * It is generic in case you want to define a typed object for the data. * @template T * @typedef StructInstance * @property {function(BufferSource, Offset=): T} unpack - Deserialize from a BufferSource into the structured object. * @property {function(T, BufferSource=, Offset=): Uint8Array} pack - Serialize the structured object into a Uint8Array. * @property {Object<string, Field>} fields - Field definitions keyed by field name. * @property {number} size - The total size in bytes of the packed structure. * @property {string} [name] - The name of the struct (if provided). * @property {Object<string, Field>|null} [_hoistFields] - If this is a nested struct, this maps sub-fields to their definitions. */ // // --------------------------------------------------------------------- // // UMD / factory setup // // --------------------------------------------------------------------- (function (root, factory) { // @ts-ignore - cannot find name define if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. // @ts-ignore define([], factory); } else if (typeof module === 'object' && module.exports) { // Node.js/CommonJS // In Node (in versions earlier than 11, released 2018), TextEn/Decoder // are only available through the "util" package, however bundlers // building for browser will see that and always try to include it. // Therefore, this will assume TextEncoder/TextDecoder are in globalThis (not root). module.exports = factory(globalThis.TextEncoder, globalThis.TextDecoder); // NOTE: Uncomment below if you are using versions of Node earlier than 11. // module.exports = factory(require('util').TextEncoder, require('util').TextDecoder); } else { // Browser globals (root is window) // Assume TextEncoder/TextDecoder are either defined or undefined in window. /** @type {*} */ (root)._ = factory(/** @type {*} */ (root).TextEncoder, /** @type {*} */ (root).TextDecoder); } }(typeof self !== 'undefined' ? self : this, // It seems that TypeScript is predicting a type for the whole // namespace that includes all functions, and defining any custom // returns type ruins that, leaving the .d.ts empty and dry of functions. /* eslint-disable jsdoc/require-returns-type */ /** * @param {typeof TextEncoder} _TextEncoder * @param {typeof TextDecoder} _TextDecoder * @returns Returns the exported namespace. */ function (_TextEncoder, _TextDecoder) { /* eslint-enable jsdoc/require-returns-type */ 'use strict'; /** * A library for defining structs to convert between JSON and binary. * Supports numbers, bytes, strings, and bitfields. * Compatible with browsers down to Safari 5.1. * @namespace _ */ var _ = {}; // Add ECMA262-5 method binding if not supported natively // https://github.com/ReactNativeNews/react-native-newsletter/blob/93016f62af32d97cc009f991d4f7c3c7155a4f26/ie.js#L8 if (!('bind' in Function.prototype)) { /** * @param {Object|null|undefined} owner * @returns {Function} * @this {Function} */ // @ts-ignore - Property bind does not exist on never? Function.prototype.bind = function (owner) { var that = this; if (arguments.length <= 1) { return function () { return that.apply(owner, arguments); }; } var args = Array.prototype.slice.call(arguments, 1); return function () { return that.apply( owner, arguments.length === 0 ? args : args.concat(Array.prototype.slice.call(arguments)) ); }; }; } // // --------------------------------------------------------------------- // // Helper Methods // // --------------------------------------------------------------------- /** * Creates a new Uint8Array of given size backed by an ArrayBuffer. * @param {number} size - The size of the buffer in bytes. * @returns {Uint8Array} A new Uint8Array of the specified size. */ function newBuffer(size) { return new Uint8Array(new ArrayBuffer(size)); } /** * Extends an object with properties from subsequent objects. * @param {Object<string, *>} obj - The target object to extend. * @returns {Object} The extended target object. */ function extend(obj) { var args = /** @type {Array<Object<string, *>>} */ (Array.prototype.slice.call(arguments, 1)); args.forEach(function (ext) { Object.keys(ext).forEach(function (key) { obj[key] = ext[key]; }); }); return obj; } /** * Adds a field's size to the current offset cursor (bytes/bits). * @param {Offset} ctr - The current offset (bytes, bits). * @param {Field} f - The field whose size to add. * @returns {Offset} The updated offset cursor. * @throws {Error} Improperly aligned bitfield before field */ function addField(ctr, f) { if ('width' in f && typeof f.width === 'number') { ctr.bits = (ctr.bits || 0) + f.width; while ((ctr.bits || 0) > 7) { ctr.bytes += 1; ctr.bits -= 8; } } else if (!ctr.bits) { // Not a bitfield, add full size in bytes. ctr.bytes += f.size || 0; } else { // We have leftover bits that aren't aligned, can't add a normal field. throw Error('Improperly aligned bitfield before field: ' + (f.name || '')); } return ctr; } /** * Converts a field into an array field if a count is provided. * @param {Field} f - The field to arrayize. * @param {number} [count] - The number of elements in the array, if needed. * @returns {Field} The arrayized field. */ function arrayizeField(f, count) { var field = (typeof count === 'number') ? /** @type {Field} */ (extend({ name: f.name, field: f, /** * Unpacks an array of values from bytes. * @param {BufferSource} buf - The buffer to read from. * @param {Offset} off - The offset object with bytes and bits. * @returns {Array<*>} The unpacked array of values. */ unpack: function (buf, off) { off || (off = { bytes: 0, bits: 0 }); var arr = new Array(count); for (var idx = 0, len = arr.length; idx < len; idx += 1) { arr[idx] = f.unpack(buf, off); } return arr; }, /** * Packs an array of values into bytes. * @param {Array<*>} arr * @param {BufferSource} [buf] * @param {Offset} [off] * @returns {Uint8Array} * @this {Field} */ pack: function (arr, buf, off) { arr || (arr = new Array(count)); buf || (buf = newBuffer(this.size)); off || (off = { bytes: 0, bits: 0 }); for (var idx = 0, len = Math.min(arr.length, count); idx < len; idx += 1) { f.pack(arr[idx], buf, off); } while (idx++ < count) addField(off, f); return /** @type {Uint8Array} */ (buf); } }, (f.width !== undefined) ? { width: f.width * count } : { size: f.size * count }) ) : f; return field; } // // --------------------------------------------------------------------- // // _.struct Definition // // --------------------------------------------------------------------- /** * Defines a new structure with the given fields. * Overloads: * _.struct(fields, count?) * _.struct(name, fields, count?) * @param {string|Array<Field>} name - The structure name or the array of fields if no name. * @param {Array<Field|StructInstance<*>>|number} [fields] - The array of fields OR the count if the first param was fields. * @param {number} [count] - The number of array elements if making an array of structs. * @returns {StructInstance<*>} The resulting struct definition (and array, if count was provided). * @throws {Error} Invalid .padTo..., Improperly aligned bitfield at end of struct */ _.struct = function (name, fields, count) { /** @type {string|undefined} */ var structName; /** @type {Array<Field>} */ var fieldDefs; if (typeof name !== 'string') { // Overload 1: (fields[, count]) count = /** @type {number|undefined} */ (fields); fieldDefs = /** @type {Array<Field>} */ (name); } else { // Overload 2: (name, fields[, count]) structName = name; fieldDefs = /** @type {Array<Field>} */ (fields); } var _size = { bytes: 0, bits: 0 }; var _padsById = Object.create(null); /** * @param {Object<string, Field>} obj * @param {Field} f * @returns {Object<string, Field>} * @throws {Error} Invalid .padTo... */ function reduceFunc(obj, f) { // Handle _padTo: if ('_padTo' in f) { var padField = /** @type {PaddingField} */ (f); // Cast to this type. // Generate a "pad field" for alignment. if (!padField._id) { padField._id = 'id' + Math.random().toFixed(20).slice(2); // WORKAROUND: https://github.com/tessel/runtime/issues/716 } var neededPadBytes = padField._padTo - _size.bytes; if (_size.bits) { // This is a bitfield alignment. var bitsNeeded = 8 * neededPadBytes - _size.bits; if (bitsNeeded < 0) { throw Error( 'Invalid .padTo(' + padField._padTo + ') field, struct is already ' + _size.bytes + ' byte(s) and ' + _size.bits + ' bits!' ); } _padsById[padField._id] = { width: bitsNeeded }; f.width = bitsNeeded; } else { if (neededPadBytes < 0) { throw Error( 'Invalid .padTo(' + padField._padTo + ') field, struct is already ' + _size.bytes + ' bytes!' ); } _padsById[padField._id] = { size: neededPadBytes }; f.size = neededPadBytes; } } // Handle sub-struct hoisting: if (f._hoistFields) { var hoistFields = f._hoistFields; // Otherwise it thinks it's null or undefined below Object.keys(hoistFields).forEach(function (subName) { var _f = Object.create(hoistFields[subName]); if ('width' in _f && typeof _f.width === 'number') { _f.offset = { bytes: _f.offset.bytes + _size.bytes, bits: _f.offset.bits }; } else { _f.offset = /** @type {number} */ (_f.offset) + _size.bytes; } obj[subName] = _f; }); } else if (f.name) { // Create a local copy, assign offset. var localCopy = Object.create(f); if ('width' in localCopy && typeof localCopy.width === 'number') { localCopy.offset = { bytes: _size.bytes, bits: _size.bits }; } else { localCopy.offset = _size.bytes; } obj[f.name] = localCopy; } addField(_size, f); return obj; } /** @type {Object<string, Field>} */ var fieldsObj = fieldDefs.reduce(reduceFunc, {}); if (_size.bits) { throw Error('Improperly aligned bitfield at end of struct: ' + (structName || '')); } // Now build the main field definition for the struct. var structField = { /** * Reads (unpacks) a struct from the buffer at the given offset. * @param {BufferSource} buf * @param {Offset} [off] * @returns {*} */ unpack: function (buf, off) { off = off || { bytes: 0, bits: 0 }; /** @type {Object<string, Field>} */ var obj = {}; fieldDefs.forEach(function (f) { if ('_padTo' in f && /** @type {PaddingField} */ (f)._id !== undefined) { // It's a pad spec; retrieve the pad field from _padsById. addField(/** @type {Offset} */ (off), _padsById[/** @type {PaddingField} */ (f)._id]); return; } var value = f.unpack(buf, off); if (f.name) { obj[f.name] = value; } else if (typeof value === 'object') { extend(obj, value); } }); return obj; }, /** * Writes (packs) the given object into a buffer at the given offset. * @param {*} obj * @param {BufferSource} [buf] * @param {Offset} [off] * @returns {Uint8Array} */ pack: function (obj, buf, off) { obj = obj || {}; if (!buf) { buf = newBuffer(this.size); } off = off || { bytes: 0, bits: 0 }; fieldDefs.forEach(function (f) { if ('_padTo' in f && /** @type {PaddingField} */ (f)._id !== undefined) { addField(/** @type {Offset} */ (off), _padsById[/** @type {PaddingField} */ (f)._id]); return; } var value = f.name ? obj[f.name] : obj; f.pack(value, /** @type {BufferSource} */ (buf), off); }); return /** @type {Uint8Array} */ (buf); }, _hoistFields: structName ? null : fieldsObj, fields: fieldsObj, size: _size.bytes, name: structName }; // Cast to StructInstance for type compatibility. return /** @type {StructInstance<*>} */ (arrayizeField(structField, count)); }; // // --------------------------------------------------------------------- // // Begin Bitfield Helpers // // --------------------------------------------------------------------- /** * Reads a 32-bit unsigned int from buffer, but handles short lengths by not reading beyond the buffer. * Used in valueFromBytes by bitfield logic. * @param {BufferSource} buffer - The buffer to read from. * @param {number} offset - The byte offset to start reading. * @param {boolean} littleEndian - Indicates whether to read little-endian. * @returns {number} The read unsigned 32-bit integer. */ function truncatedReadUInt32(buffer, offset, littleEndian) { var bytes = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer; var availableBytes = bytes.length - offset; var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); if (availableBytes >= 4) { return view.getUint32(offset, littleEndian); } else if (availableBytes === 3) { var first = view.getUint16(offset, littleEndian); var second = view.getUint8(offset + 2); return littleEndian ? ((second << 16) + first) >>> 0 : ((first << 8) + second) << 8 >>> 0; } else if (availableBytes === 2) { return view.getUint16(offset, littleEndian) << (littleEndian ? 0 : 16) >>> 0; } else if (availableBytes === 1) { return view.getUint8(offset) << (littleEndian ? 0 : 24) >>> 0; } return 0x0; } /** * Writes a 32-bit unsigned int to buffer, but handles short lengths by not writing beyond the buffer. * Used in bytesFromValue/pack by bitfield logic. * @param {BufferSource} buffer - The buffer to write to. * @param {number} offset - The byte offset to start writing. * @param {number} data - The unsigned 32-bit integer to write. * @param {boolean} littleEndian - Indicates whether to write little-endian. */ function truncatedWriteUInt32(buffer, offset, data, littleEndian) { var bytes = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer; var availableBytes = bytes.length - offset; var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); if (availableBytes >= 4) { view.setUint32(offset, data, littleEndian); } else if (availableBytes === 3) { if (littleEndian) { view.setUint8(offset, data & 0xff); view.setUint16(offset + 1, data >>> 8, littleEndian); } else { view.setUint16(offset, data >>> 16, littleEndian); view.setUint8(offset + 2, (data >>> 8) & 0xff); } } else if (availableBytes === 2) { view.setUint16(offset, littleEndian ? data & 0xffff : data >>> 16, littleEndian); } else if (availableBytes === 1) { view.setUint8(offset, littleEndian ? data & 0xff : data >>> 24); } } /** * Defines a padding field up to the specified offset in bytes. * @param {number} off - The byte offset to pad to. * @returns {Field & {_padTo: number, _id?: string}} The padding field definition. */ _.padTo = function (off) { var field = /** @type {Field & {_padTo: number}} */ ({ _padTo: off, // Dummy implementations to satisfy Field: size: 0, unpack: function () { return null; }, pack: function () { return newBuffer(0); } }); return field; }; // NOTE: bitfields must be embedded in a struct (C/C++ share this limitation) var FULL = 0xFFFFFFFF; /** * A helper for big and little endian bitfields. * @param {string} [name] - The name of the bitfield. * @param {number} [width=1] - The width of the bitfield in bits. * @param {number} [count] - The number of bitfields in an array. * @returns {Field} The defined bitfield. * @throws {Error} Bitfields support a maximum width of 24 bits * @this {BitTransform} */ function bitfield(name, width, count) { if (width === undefined) { width = 1; } // NOTE: width limitation is so all values will align *within* a 4-byte word if (width > 24) { throw Error('Bitfields support a maximum width of 24 bits.'); } var mask = FULL >>> (32 - width); var littleEndian = this.littleEndian; if (littleEndian) { mask >>>= 0; } var impl = this; return arrayizeField({ /** * @param {BufferSource} buf * @param {Offset} [off] * @returns {number|boolean} * @this {Field & {width: number}} */ unpack: function (buf, off) { off = off || { bytes: 0, bits: 0 }; var over; var end; var word; if (littleEndian) { end = off.bits || 0; word = truncatedReadUInt32(buf, off.bytes, true) >>> 0; over = (word >>> end); } else { end = (off.bits || 0) + /** @type {number} */ (width); word = truncatedReadUInt32(buf, off.bytes, false) || 0; over = word >>> (32 - end); } addField(off, this); return impl.b2v.call(this, over & mask); }, /** * @param {number} val * @param {BufferSource} [buf] * @param {Offset} [off] * @returns {Uint8Array} * @this {Field & {width: number}} */ pack: function (val, buf, off) { val = impl.v2b.call(this, val || 0); if (!buf) { buf = newBuffer(/** @type {number} */(this.size)); } off = off || { bytes: 0, bits: 0 }; var word; var zero; var over; if (littleEndian) { word = truncatedReadUInt32(buf, off.bytes, true) >>> 0; var shift = off.bits || 0; zero = (mask << shift) >>> 0; word &= ~zero; over = (val & mask) << shift; word = (word | over) >>> 0; // WORKAROUND: https://github.com/tessel/runtime/issues/644 truncatedWriteUInt32(buf, off.bytes, word, true); } else { var end = (off.bits || 0) + /** @type {number} */ (width); word = truncatedReadUInt32(buf, off.bytes, false) || 0; zero = mask << (32 - end); over = (val & mask) << (32 - end); word &= ~zero; word = (word | over) >>> 0; // WORKAROUND: https://github.com/tessel/runtime/issues/644 truncatedWriteUInt32(buf, off.bytes, word, false); } addField(off, this); return new Uint8Array(buf); }, width: width, size: 0, // Placeholder for bitfields. name: name }, count); } // // --------------------------------------------------------------------- // // Begin Bitfield Definitions // // --------------------------------------------------------------------- /** * Boolean field: 1-bit big-endian bitfield that interprets 1/0 as true/false. * @param {string} [name] * @param {number} [count] * @returns {Field} */ _.bool = function (name, count) { return bitfield.call({ /** * Converts a bitfield to a boolean. * @param {number} b - The bitfield value. * @returns {boolean} The boolean representation. */ b2v: function (b) { return Boolean(b); }, /** * Converts a boolean to a bitfield. * @param {number|boolean} v - The boolean value. * @returns {number} The bitfield representation. */ v2b: function (v) { return v ? FULL : 0; } }, name, 1, count); }; /** * Unsigned bitfield (big-endian). */ _.ubit = bitfield.bind({ /** * Converts a bitfield to a value. * @param {number} b - The bitfield value. * @returns {number} The numeric value. */ b2v: function (b) { return b; }, /** * Converts a value to a bitfield. * @param {number|boolean} v - The numeric value. * @returns {number} The bitfield representation. */ v2b: function (v) { return Number(v); } }); /** * Unsigned bitfield (little-endian). * @param {string} name - The name of the bitfield. * @param {number} [width=1] - The width of the bitfield in bits. * @param {number} [count] - The number of bitfields in an array. * @returns {Object} The defined little-endian bitfield. */ _.ubitLE = bitfield.bind({ /** * Converts a bitfield to a little-endian value. * @param {number} b - The bitfield value. * @returns {number} The little-endian numeric value. */ b2v: function (b) { return b; }, /** * Converts a little-endian value to a bitfield. * @param {number|boolean} v - The little-endian numeric value. * @returns {number} The bitfield representation. */ v2b: function (v) { return Number(v); }, littleEndian: true }); /** * Signed bitfield (big-endian). * @param {string} name - The name of the bitfield. * @param {number} [width=1] - The width of the bitfield in bits. * @param {number} [count] - The number of bitfields in an array. * @returns {Object} The defined signed bitfield. */ _.sbit = bitfield.bind({ /** * Converts a bitfield to a signed value. * @param {number} b - The bitfield value. * @returns {number} The signed numeric value. */ b2v: function (b) { var m = 1 << ((this.width || 1) - 1); var s = b & m; return (s) ? -(b &= ~m) : b; }, /** * Converts a signed value to a bitfield. * @param {number|boolean} v - The signed numeric value. * @returns {number} The bitfield representation. */ v2b: function (v) { v = Number(v); var m = 1 << ((this.width || 1) - 1); var s = (v < 0); return (s) ? (-v | m) : v; } }); /** * Creates a simple "byte field" for reading/writing raw bytes. * @param {string|number} name - The name of the bytefield or its size if name is omitted. * @param {number} [size=1] - The size of the bytefield in bytes. * @param {number} [count] - The number of bytefields in an array. * @returns {Field & ByteTransform} The defined bytefield. * @this {ByteTransform} */ function bytefield(name, size, count) { /** @type {string|undefined} */ var fieldName; /** @type {number} */ var fieldSize = 1; if (typeof name === 'string') { fieldName = name; if (typeof size === 'number') { fieldSize = size; } } else { // Shift params. fieldSize = name; count = size; // pass along } var impl = this; return /** @type {Field & ByteTransform} */ (arrayizeField(/** @type {Field & ByteTransform} */ ({ name: fieldName, size: fieldSize, /** * @param {BufferSource} buf * @param {Offset} [off] * @returns {Uint8Array} */ unpack: function (buf, off) { off = off || { bytes: 0, bits: 0 }; var bytes = (buf instanceof ArrayBuffer) ? new Uint8Array(buf) : buf; var val = bytes.subarray(off.bytes, off.bytes + /** @type {number} */ (this.size)); addField(off, this); return impl.b2v.call(this, val); }, /** * @param {Array<number>|Uint8Array|ArrayBuffer} val * @param {BufferSource} [buf] * @param {Offset} [off] * @returns {Uint8Array} */ pack: function (val, buf, off) { if (!buf) { buf = newBuffer(/** @type {number} */ (this.size)); } off = off || { bytes: 0, bits: 0 }; var bytes = (buf instanceof ArrayBuffer) ? new Uint8Array(buf) : buf; var blk = bytes.subarray(off.bytes, off.bytes + /** @type {number} */ (this.size)); impl.vTb.call(this, val, blk); addField(off, this); return /** @type {Uint8Array} */ (buf); }, // Default transforms: /** * @param {Uint8Array} b * @returns {Uint8Array} */ b2v: function (b) { return b; }, /** * @param {Array<number>|Uint8Array|ArrayBuffer} v * @param {Uint8Array} b * @returns {number} */ vTb: function (v, b) { if (!v) return 0; var src = new Uint8Array(v); b.set(src.subarray(0, b.length)); return b.length; } }), count)); } /** * Swaps adjacent byte pairs in a buffer, used for big-endian <-> little-endian char16 manipulations. * http://stackoverflow.com/a/7460958/72637 * @param {Uint8Array} fromBuffer - The source buffer. * @param {Uint8Array} [toBuffer] - The destination buffer. If not provided, fromBuffer is modified. * @returns {Uint8Array} The buffer with swapped byte pairs. */ function swapBytesPairs(fromBuffer, toBuffer) { toBuffer = toBuffer || fromBuffer; var l = fromBuffer.length; for (var i = 1; i < l; i += 2) { var a = fromBuffer[i - 1]; toBuffer[i - 1] = fromBuffer[i]; toBuffer[i] = a; } return toBuffer; } /** Basic raw byte field. */ _.byte = bytefield.bind({ /** * Converts bytes to a value. * @param {BufferSource} b - The bytes to convert. * @returns {Uint8Array} The byte value. */ b2v: function (b) { return new Uint8Array(b); }, /** * Converts a value to bytes. * @param {BufferSource} v - The value to convert. * @param {Uint8Array} b - The buffer to write to. * @returns {number} The number of bytes written. */ vTb: function (v, b) { if (!v) return 0; b.set(new Uint8Array(v)); return v.byteLength; } }); // // --------------------------------------------------------------------- // // Character Field Definitions // // --------------------------------------------------------------------- /** Null-terminated UTF-8 string field. */ _.char = bytefield.bind({ /** * Converts bytes to a UTF-8 string. * @param {BufferSource} b - The bytes to convert. * @returns {string} The resulting string. */ b2v: function (b) { var decoder; if (typeof _TextDecoder !== 'undefined' && _TextDecoder) { decoder = new _TextDecoder('utf-8'); } else { // Fallback minimal polyfill decoder = { /** * @param {BufferSource} buf * @returns {string} */ decode: function (buf) { var bytes = new Uint8Array(buf); var str = ''; for (var i = 0; i < bytes.length; i++) { str += String.fromCharCode(bytes[i]); } return str; } }; } var v = decoder.decode(b); var z = v.indexOf('\0'); return (~z) ? v.slice(0, z) : v; }, /** * Converts a string to UTF-8 bytes. * @param {string} v - The string to convert. * @param {Uint8Array} b - The buffer to write to. * @returns {number} The number of bytes written. */ vTb: function (v, b) { v || (v = ''); var encoder; if (typeof _TextEncoder !== 'undefined' && _TextEncoder) { encoder = new _TextEncoder(); } else { // Fallback minimal polyfill encoder = { /** * @param {string} str * @returns {Uint8Array} */ encode: function (str) { var arr = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { arr[i] = str.charCodeAt(i) & 0xff; } return arr; } }; } var encoded = encoder.encode(v); for (var i = 0; i < encoded.length && i < b.length; i++) { b[i] = encoded[i]; } return encoded.length; } }); /** Null-terminated UTF-16LE string field. */ _.char16le = bytefield.bind({ /** * Converts bytes to a UTF-16LE string. * @param {BufferSource} b - The bytes to convert. * @returns {string} The resulting string. */ b2v: function (b) { var decoder; if (typeof _TextDecoder !== 'undefined' && _TextDecoder) { decoder = new _TextDecoder('utf-16le'); } else { // fallback decoder = { /** * @param {BufferSource} buf * @returns {string} */ decode: function (buf) { var bytes = new Uint8Array(buf); var str = ''; for (var i = 0; i < bytes.length; i += 2) { var charCode = bytes[i] | (bytes[i + 1] << 8); str += String.fromCharCode(charCode); } return str; } }; } var v = decoder.decode(b); var z = v.indexOf('\0'); return (~z) ? v.slice(0, z) : v; }, /** * Converts a string to UTF-16LE bytes. * @param {string} v - The string to convert. * @param {Uint8Array} b - The buffer to write to. * @returns {number} The number of bytes written. */ vTb: function (v, b) { v || (v = ''); var bytesWritten = 0; for (var i = 0; i < v.length && bytesWritten + 1 < b.length; i++) { var charCode = v.charCodeAt(i); b[bytesWritten++] = charCode & 0xFF; b[bytesWritten++] = (charCode >> 8) & 0xFF; } return bytesWritten; } }); /** Null-terminated UTF-16BE string field. */ _.char16be = bytefield.bind({ /** * Converts bytes to a UTF-16BE string. * @param {BufferSource} b - The bytes to convert. * @returns {string} The resulting string. */ b2v: function (b) { var temp = new Uint8Array(b); swapBytesPairs(temp); var decoder; if (typeof _TextDecoder !== 'undefined' && _TextDecoder) { decoder = new _TextDecoder('utf-16le'); } else { // fallback decoder = { /** * @param {BufferSource} buf * @returns {string} */ decode: function (buf) { var bytes = new Uint8Array(buf); var str = ''; for (var i = 0; i < bytes.length; i += 2) { var charCode = bytes[i] | (bytes[i + 1] << 8); str += String.fromCharCode(charCode); } return str; } }; } var v = decoder.decode(/** @type {ArrayBuffer} */ (temp.buffer)); var z = v.indexOf('\0'); return (~z) ? v.slice(0, z) : v; }, /** * Converts a string to UTF-16BE bytes. * @param {string} v - The string to convert. * @param {Uint8Array} b - The buffer to write to. * @returns {number} The number of bytes written. */ vTb: function (v, b) { v || (v = ''); var temp = new Uint8Array(b.length); var bytesWritten = 0; for (var i = 0; i < v.length && bytesWritten + 1 < temp.length; i++) { var charCode = v.charCodeAt(i); temp[bytesWritten++] = charCode & 0xFF; temp[bytesWritten++] = (charCode >> 8) & 0xFF; } swapBytesPairs(temp, b); return bytesWritten; } }); // // --------------------------------------------------------------------- // // dataViewField Definition // // --------------------------------------------------------------------- /** * Defines a standard field based on read/write methods for a DataView. * @param {function(this:DataView, number, boolean):number} getFn - The DataView getter. * @param {function(this:DataView, number, number, boolean):void} setFn - The DataView setter. * @param {number} size - The size of the field in bytes (4 for Uint32, etc.). * @param {boolean} littleEndian - Indicates whether or not the field is little endian. * @returns {function((string|number), number=): Field} A function to create the field. */ function dataViewField(getFn, setFn, size, littleEndian) { /** * @param {string|number} name * @param {number} [count] * @returns {Field} */ var func = function (name, count) { /** @type {string|undefined} */ var fieldName; if (typeof name === 'string') { fieldName = name; } else { // shift count = name; } return arrayizeField({ name: fieldName, size: size, /** * @param {BufferSource} buf * @param {Offset} [off] * @returns {number} */ unpack: function (buf, off) { off = off || { bytes: 0 }; var bytes = (buf instanceof ArrayBuffer) ? new Uint8Array(buf) : buf; var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); var val = getFn.call(view, off.bytes, littleEndian); addField(off, this); return val; }, /** * @param {number} val * @param {BufferSource} [buf] * @param {Offset} [off] * @returns {Uint8Array} */ pack: function (val, buf, off) { if (val === undefined || val === null) { val = 0; } if (!buf) { buf = newBuffer(this.size); } off = off || { bytes: 0 }; var bytes = (buf instanceof ArrayBuffer) ? new Uint8Array(buf) : buf; var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); setFn.call(view, off.bytes, val, littleEndian); addField(off, this); return new Uint8Array(buf); } }, count); }; return func; } // // --------------------------------------------------------------------- // // DataView Field Definitions // // --------------------------------------------------------------------- var DV = DataView.prototype; // Alias for brevity. // Unsigned integers _.uint8 = dataViewField(DV.getUint8, DV.setUint8, 1, false); _.uint16 = dataViewField(DV.getUint16, DV.setUint16, 2, false); _.uint32 = dataViewField(DV.getUint32, DV.setUint32, 4, false); _.uint16le = dataViewField(DV.getUint16, DV.setUint16, 2, true); _.uint32le = dataViewField(DV.getUint32, DV.setUint32, 4, true); // Signed integers _.int8 = dataViewField(DV.getInt8, DV.setInt8, 1, false); _.int16 = dataViewField(DV.getInt16, DV.setInt16, 2, false); _.int32 = dataViewField(DV.getInt32, DV.setInt32, 4, false); _.int16le = dataViewField(DV.getInt16, DV.setInt16, 2, true); _.int32le = dataViewField(DV.getInt32, DV.setInt32, 4, true); // Floating-point numbers _.float32 = dataViewField(DV.getFloat32, DV.setFloat32, 4, false); _.float64 = dataViewField(DV.getFloat64, DV.setFloat64, 8, false); _.float32le = dataViewField(DV.getFloat32, DV.setFloat32, 4, true); _.float64le = dataViewField(DV.getFloat64, DV.setFloat64, 8, true); /** * Derives a new field based on an existing one with custom pack and unpack functions. * The types are intentionally any. * @param {Field} orig - The original field to derive from. * @param {function(*): *} pack - The function to pack the derived value. * @param {function(*): *} unpack - The function to unpack the derived value. * @returns {function((string|number)=, number=): Field} A function to create the derived field. */ _.derive = function (orig, pack, unpack) { /** * @param {string|number} [name] * @param {number} [count] * @returns {Field} */ var func = function (name, count) { /** @type {string|null} */ var fieldName = null; if (typeof name === 'string') { fieldName = name; } else { count = name; } var derived = /** @type {Field} */ (extend({ /** * @param {BufferSource} buf * @param {Offset} [off] * @returns {*} */ unpack: function (buf, off) { var rawVal = orig.unpack(buf, off); return unpack(rawVal); }, /** * @param {*} val * @param {BufferSource} buf * @param {Offset} [off] * @returns {Uint8Array} */ pack: function (val, buf, off) { var packed = pack(val); return orig.pack(packed, buf, off); }, name: fieldName }, ('width' in orig) ? { width: orig.width } : { size: orig.size } )); return arrayizeField(derived, count); }; return func; }; // Return the `_` object for whichever loader environment we're in. return _; }));