diff --git a/archive/archive.js b/archive/archive.js index 5137c25..8acc476 100644 --- a/archive/archive.js +++ b/archive/archive.js @@ -11,65 +11,22 @@ var bitjs = bitjs || {}; bitjs.archive = bitjs.archive || {}; -(function() { - -// =========================================================================== -// Stolen from Closure because it's the best way to do Java-like inheritance. -bitjs.base = function(me, opt_methodName, var_args) { - const caller = arguments.callee.caller; - if (caller.superClass_) { - // This is a constructor. Call the superclass constructor. - return caller.superClass_.constructor.apply( - me, Array.prototype.slice.call(arguments, 1)); - } - - const args = Array.prototype.slice.call(arguments, 2); - let foundCaller = false; - for (let ctor = me.constructor; - ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { - if (ctor.prototype[opt_methodName] === caller) { - foundCaller = true; - } else if (foundCaller) { - return ctor.prototype[opt_methodName].apply(me, args); - } - } - - // If we did not find the caller in the prototype chain, - // then one of two things happened: - // 1) The caller is an instance method. - // 2) This method was not called by the right caller. - if (me[opt_methodName] === caller) { - return me.constructor.prototype[opt_methodName].apply(me, args); - } else { - throw Error( - 'goog.base called from a method of one name ' + - 'to a method of a different name'); - } -}; -bitjs.inherits = function(childCtor, parentCtor) { - /** @constructor */ - function tempCtor() {}; - tempCtor.prototype = parentCtor.prototype; - childCtor.superClass_ = parentCtor.prototype; - childCtor.prototype = new tempCtor(); - childCtor.prototype.constructor = childCtor; -}; -// =========================================================================== - /** * An unarchive event. - * - * @param {string} type The event type. - * @constructor */ -bitjs.archive.UnarchiveEvent = function(type) { +bitjs.archive.UnarchiveEvent = class { /** - * The event type. - * - * @type {string} + * @param {string} type The event type. */ - this.type = type; -}; + constructor(type) { + /** + * The event type. + * + * @type {string} + */ + this.type = type; + } +} /** * The UnarchiveEvent types. @@ -85,78 +42,101 @@ bitjs.archive.UnarchiveEvent.Type = { /** * Useful for passing info up to the client (for debugging). - * - * @param {string} msg The info message. */ -bitjs.archive.UnarchiveInfoEvent = function(msg) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO); - +bitjs.archive.UnarchiveInfoEvent = class extends bitjs.archive.UnarchiveEvent { /** - * The information message. - * - * @type {string} + * @param {string} msg The info message. */ - this.msg = msg; -}; -bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent); + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.INFO); + + /** + * The information message. + * + * @type {string} + */ + this.msg = msg; + } +} /** * An unrecoverable error has occured. - * - * @param {string} msg The error message. */ -bitjs.archive.UnarchiveErrorEvent = function(msg) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR); - +bitjs.archive.UnarchiveErrorEvent = class extends bitjs.archive.UnarchiveEvent { /** - * The information message. - * - * @type {string} + * @param {string} msg The error message. */ - this.msg = msg; -}; -bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent); + constructor(msg) { + super(bitjs.archive.UnarchiveEvent.Type.ERROR); + + /** + * The information message. + * + * @type {string} + */ + this.msg = msg; + } +} /** * Start event. - * - * @param {string} msg The info message. */ -bitjs.archive.UnarchiveStartEvent = function() { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START); -}; -bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent); +bitjs.archive.UnarchiveStartEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.START); + } +} /** * Finish event. - * - * @param {string} msg The info message. */ -bitjs.archive.UnarchiveFinishEvent = function() { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH); -}; -bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent); +bitjs.archive.UnarchiveFinishEvent = class extends bitjs.archive.UnarchiveEvent { + constructor() { + super(bitjs.archive.UnarchiveEvent.Type.FINISH); + } +} /** * Progress event. */ -bitjs.archive.UnarchiveProgressEvent = function( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS); +bitjs.archive.UnarchiveProgressEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {string} currentFilename + * @param {number} currentFileNumber + * @param {number} currentBytesUnarchivedInFile + * @param {number} currentBytesUnarchived + * @param {number} totalUncompressedBytesInArchive + * @param {number} totalFilesInArchive + */ + constructor(currentFilename, currentFileNumber, currentBytesUnarchivedInFile, + currentBytesUnarchived, totalUncompressedBytesInArchive, totalFilesInArchive) { + super(bitjs.archive.UnarchiveEvent.Type.PROGRESS); - this.currentFilename = currentFilename; - this.currentFileNumber = currentFileNumber; - this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; - this.totalFilesInArchive = totalFilesInArchive; - this.currentBytesUnarchived = currentBytesUnarchived; - this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; -}; -bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent); + this.currentFilename = currentFilename; + this.currentFileNumber = currentFileNumber; + this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile; + this.totalFilesInArchive = totalFilesInArchive; + this.currentBytesUnarchived = currentBytesUnarchived; + this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive; + } +} + +/** + * Extract event. + */ +bitjs.archive.UnarchiveExtractEvent = class extends bitjs.archive.UnarchiveEvent { + /** + * @param {UnarchivedFile} unarchivedFile + */ + constructor(unarchivedFile) { + super(bitjs.archive.UnarchiveEvent.Type.EXTRACT); + + /** + * @type {UnarchivedFile} + */ + this.unarchivedFile = unarchivedFile; + } +} /** * All extracted files returned by an Unarchiver will implement @@ -169,186 +149,177 @@ bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEven * */ -/** - * Extract event. - */ -bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) { - bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT); - - /** - * @type {UnarchivedFile} - */ - this.unarchivedFile = unarchivedFile; -}; -bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent); - - /** * Base class for all Unarchivers. - * - * @param {ArrayBuffer} arrayBuffer The Array Buffer. - * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located. - * @constructor */ -bitjs.archive.Unarchiver = function(arrayBuffer, opt_pathToBitJS) { +bitjs.archive.Unarchiver = class { /** - * The ArrayBuffer object. - * @type {ArrayBuffer} - * @protected + * @param {ArrayBuffer} arrayBuffer The Array Buffer. + * @param {string} opt_pathToBitJS Optional string for where the BitJS files are located. */ - this.ab = arrayBuffer; + constructor(arrayBuffer, opt_pathToBitJS) { + /** + * The ArrayBuffer object. + * @type {ArrayBuffer} + * @protected + */ + this.ab = arrayBuffer; + + /** + * The path to the BitJS files. + * @type {string} + * @private + */ + this.pathToBitJS_ = opt_pathToBitJS || '/'; + + /** + * A map from event type to an array of listeners. + * @type {Map.} + */ + this.listeners_ = {}; + for (let type in bitjs.archive.UnarchiveEvent.Type) { + this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; + } + + /** + * Private web worker initialized during start(). + * @type {Worker} + * @private + */ + this.worker_ = null; + } /** - * The path to the BitJS files. - * @type {string} + * This method must be overridden by the subclass to return the script filename. + * @return {string} The script filename. + * @protected. + */ + getScriptFileName() { + throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()'; + } + + /** + * Adds an event listener for UnarchiveEvents. + * + * @param {string} Event type. + * @param {function} An event handler function. + */ + addEventListener(type, listener) { + if (type in this.listeners_) { + if (this.listeners_[type].indexOf(listener) == -1) { + this.listeners_[type].push(listener); + } + } + } + + /** + * Removes an event listener. + * + * @param {string} Event type. + * @param {EventListener|function} An event listener or handler function. + */ + removeEventListener(type, listener) { + if (type in this.listeners_) { + const index = this.listeners_[type].indexOf(listener); + if (index != -1) { + this.listeners_[type].splice(index, 1); + } + } + } + + /** + * Receive an event and pass it to the listener functions. + * + * @param {bitjs.archive.UnarchiveEvent} e * @private */ - this.pathToBitJS_ = opt_pathToBitJS || '/'; + handleWorkerEvent_(e) { + if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && + this.listeners_[e.type] instanceof Array) { + this.listeners_[e.type].forEach(function (listener) { listener(e) }); + if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { + this.worker_.terminate(); + } + } else { + console.log(e); + } + } /** - * A map from event type to an array of listeners. - * @type {Map.} + * Starts the unarchive in a separate Web Worker thread and returns immediately. */ - this.listeners_ = {}; - for (let type in bitjs.archive.UnarchiveEvent.Type) { - this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = []; - } -}; + start() { + const me = this; + const scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); + if (scriptFileName) { + this.worker_ = new Worker(scriptFileName); -/** - * Private web worker initialized during start(). - * @type {Worker} - * @private - */ -bitjs.archive.Unarchiver.prototype.worker_ = null; + this.worker_.onerror = function(e) { + console.log('Worker error: message = ' + e.message); + throw e; + }; -/** - * This method must be overridden by the subclass to return the script filename. - * @return {string} The script filename. - * @protected. - */ -bitjs.archive.Unarchiver.prototype.getScriptFileName = function() { - throw 'Subclasses of AbstractUnarchiver must overload getScriptFileName()'; -}; + this.worker_.onmessage = function(e) { + if (typeof e.data == 'string') { + // Just log any strings the workers pump our way. + console.log(e.data); + } else { + // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' + // so that instanceof UnarchiveEvent returns true, but others do not. + me.handleWorkerEvent_(e.data); + } + }; -/** - * Adds an event listener for UnarchiveEvents. - * - * @param {string} Event type. - * @param {function} An event handler function. - */ -bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) { - if (type in this.listeners_) { - if (this.listeners_[type].indexOf(listener) == -1) { - this.listeners_[type].push(listener); + this.worker_.postMessage({file: this.ab}); } } -}; -/** - * Removes an event listener. - * - * @param {string} Event type. - * @param {EventListener|function} An event listener or handler function. - */ -bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) { - if (type in this.listeners_) { - const index = this.listeners_[type].indexOf(listener); - if (index != -1) { - this.listeners_[type].splice(index, 1); + /** + * Terminates the Web Worker for this Unarchiver and returns immediately. + */ + stop() { + if (this.worker_) { + this.worker_.terminate(); } } -}; - -/** - * Receive an event and pass it to the listener functions. - * - * @param {bitjs.archive.UnarchiveEvent} e - * @private - */ -bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) { - if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) && - this.listeners_[e.type] instanceof Array) { - this.listeners_[e.type].forEach(function (listener) { listener(e) }); - if (e.type == bitjs.archive.UnarchiveEvent.Type.FINISH) { - this.worker_.terminate(); - } - } else { - console.log(e); - } -}; - -/** - * Starts the unarchive in a separate Web Worker thread and returns immediately. - */ - bitjs.archive.Unarchiver.prototype.start = function() { - const me = this; - const scriptFileName = this.pathToBitJS_ + this.getScriptFileName(); - if (scriptFileName) { - this.worker_ = new Worker(scriptFileName); - - this.worker_.onerror = function(e) { - console.log('Worker error: message = ' + e.message); - throw e; - }; - - this.worker_.onmessage = function(e) { - if (typeof e.data == 'string') { - // Just log any strings the workers pump our way. - console.log(e.data); - } else { - // Assume that it is an UnarchiveEvent. Some browsers preserve the 'type' - // so that instanceof UnarchiveEvent returns true, but others do not. - me.handleWorkerEvent_(e.data); - } - }; - - this.worker_.postMessage({file: this.ab}); - } -}; - -/** - * Terminates the Web Worker for this Unarchiver and returns immediately. - */ -bitjs.archive.Unarchiver.prototype.stop = function() { - if (this.worker_) { - this.worker_.terminate(); - } -}; +} /** * Unzipper - * @extends {bitjs.archive.Unarchiver} - * @constructor */ -bitjs.archive.Unzipper = function(arrayBuffer, opt_pathToBitJS) { - bitjs.base(this, arrayBuffer, opt_pathToBitJS); -}; -bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver); -bitjs.archive.Unzipper.prototype.getScriptFileName = function() { return 'archive/unzip.js' }; +bitjs.archive.Unzipper = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unzip.js'; } +} + /** * Unrarrer - * @extends {bitjs.archive.Unarchiver} - * @constructor */ -bitjs.archive.Unrarrer = function(arrayBuffer, opt_pathToBitJS) { - bitjs.base(this, arrayBuffer, opt_pathToBitJS); -}; -bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver); -bitjs.archive.Unrarrer.prototype.getScriptFileName = function() { return 'archive/unrar.js' }; +bitjs.archive.Unrarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/unrar.js'; } +} /** * Untarrer * @extends {bitjs.archive.Unarchiver} * @constructor */ -bitjs.archive.Untarrer = function(arrayBuffer, opt_pathToBitJS) { - bitjs.base(this, arrayBuffer, opt_pathToBitJS); -}; -bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver); -bitjs.archive.Untarrer.prototype.getScriptFileName = function() { return 'archive/untar.js' }; +bitjs.archive.Untarrer = class extends bitjs.archive.Unarchiver { + constructor(arrayBuffer, opt_pathToBitJS) { + super(arrayBuffer, opt_pathToBitJS); + } + + getScriptFileName() { return 'archive/untar.js'; }; +} /** * Factory method that creates an unarchiver based on the byte signature found @@ -371,5 +342,3 @@ bitjs.archive.GetUnarchiver = function(ab, opt_pathToBitJS) { } return unarchiver; }; - -})(); \ No newline at end of file diff --git a/archive/rarvm.js b/archive/rarvm.js index c86c6a6..f2411e9 100644 --- a/archive/rarvm.js +++ b/archive/rarvm.js @@ -201,132 +201,132 @@ function getDebugString(obj, val) { } /** - * @struct - * @constructor */ -const VM_PreparedOperand = function() { - /** @type {VM_OpType} */ - this.Type; +class VM_PreparedOperand { + constructor() { + /** @type {VM_OpType} */ + this.Type; - /** @type {number} */ - this.Data = 0; + /** @type {number} */ + this.Data = 0; - /** @type {number} */ - this.Base = 0; + /** @type {number} */ + this.Base = 0; - // TODO: In C++ this is a uint* - /** @type {Array} */ - this.Addr = null; -}; + // TODO: In C++ this is a uint* + /** @type {Array} */ + this.Addr = null; + }; -/** @return {string} */ -VM_PreparedOperand.prototype.toString = function() { - if (this.Type === null) { - return 'Error: Type was null in VM_PreparedOperand'; + /** @return {string} */ + toString() { + if (this.Type === null) { + return 'Error: Type was null in VM_PreparedOperand'; + } + return '{ ' + + 'Type: ' + getDebugString(VM_OpType, this.Type) + + ', Data: ' + this.Data + + ', Base: ' + this.Base + + ' }'; } - return '{ ' - + 'Type: ' + getDebugString(VM_OpType, this.Type) - + ', Data: ' + this.Data - + ', Base: ' + this.Base - + ' }'; -}; +} /** - * @struct - * @constructor */ -const VM_PreparedCommand = function() { - /** @type {VM_Commands} */ - this.OpCode; +class VM_PreparedCommand { + constructor() { + /** @type {VM_Commands} */ + this.OpCode; - /** @type {boolean} */ - this.ByteMode = false; + /** @type {boolean} */ + this.ByteMode = false; - /** @type {VM_PreparedOperand} */ - this.Op1 = new VM_PreparedOperand(); + /** @type {VM_PreparedOperand} */ + this.Op1 = new VM_PreparedOperand(); - /** @type {VM_PreparedOperand} */ - this.Op2 = new VM_PreparedOperand(); -}; - -/** @return {string} */ -VM_PreparedCommand.prototype.toString = function(indent) { - if (this.OpCode === null) { - return 'Error: OpCode was null in VM_PreparedCommand'; + /** @type {VM_PreparedOperand} */ + this.Op2 = new VM_PreparedOperand(); } - indent = indent || ''; - return indent + '{\n' - + indent + ' OpCode: ' + getDebugString(VM_Commands, this.OpCode) + ',\n' - + indent + ' ByteMode: ' + this.ByteMode + ',\n' - + indent + ' Op1: ' + this.Op1.toString() + ',\n' - + indent + ' Op2: ' + this.Op2.toString() + ',\n' - + indent + '}'; -}; + + /** @return {string} */ + toString(indent) { + if (this.OpCode === null) { + return 'Error: OpCode was null in VM_PreparedCommand'; + } + indent = indent || ''; + return indent + '{\n' + + indent + ' OpCode: ' + getDebugString(VM_Commands, this.OpCode) + ',\n' + + indent + ' ByteMode: ' + this.ByteMode + ',\n' + + indent + ' Op1: ' + this.Op1.toString() + ',\n' + + indent + ' Op2: ' + this.Op2.toString() + ',\n' + + indent + '}'; + } +} /** - * @struct - * @constructor */ -const VM_PreparedProgram = function() { - /** @type {Array} */ - this.Cmd = []; +class VM_PreparedProgram { + constructor() { + /** @type {Array} */ + this.Cmd = []; - /** @type {Array} */ - this.AltCmd = null; + /** @type {Array} */ + this.AltCmd = null; - /** @type {Uint8Array} */ - this.GlobalData = new Uint8Array(); + /** @type {Uint8Array} */ + this.GlobalData = new Uint8Array(); - /** @type {Uint8Array} */ - this.StaticData = new Uint8Array(); // static data contained in DB operators + /** @type {Uint8Array} */ + this.StaticData = new Uint8Array(); // static data contained in DB operators - /** @type {Uint32Array} */ - this.InitR = new Uint32Array(7); + /** @type {Uint32Array} */ + this.InitR = new Uint32Array(7); - /** - * A pointer to bytes that have been filtered by a program. - * @type {Uint8Array} - */ - this.FilteredData = null; -}; - -/** @return {string} */ -VM_PreparedProgram.prototype.toString = function() { - let s = '{\n Cmd: [\n'; - for (let i = 0; i < this.Cmd.length; ++i) { - s += this.Cmd[i].toString(' ') + ',\n'; + /** + * A pointer to bytes that have been filtered by a program. + * @type {Uint8Array} + */ + this.FilteredData = null; } - s += '],\n'; - // TODO: Dump GlobalData, StaticData, InitR? - s += ' }\n'; - return s; -}; + + /** @return {string} */ + toString() { + let s = '{\n Cmd: [\n'; + for (let i = 0; i < this.Cmd.length; ++i) { + s += this.Cmd[i].toString(' ') + ',\n'; + } + s += '],\n'; + // TODO: Dump GlobalData, StaticData, InitR? + s += ' }\n'; + return s; + } +} /** - * @struct - * @constructor */ -const UnpackFilter = function() { - /** @type {number} */ - this.BlockStart = 0; +class UnpackFilter { + constructor() { + /** @type {number} */ + this.BlockStart = 0; - /** @type {number} */ - this.BlockLength = 0; + /** @type {number} */ + this.BlockLength = 0; - /** @type {number} */ - this.ExecCount = 0; + /** @type {number} */ + this.ExecCount = 0; - /** @type {boolean} */ - this.NextWindow = false; + /** @type {boolean} */ + this.NextWindow = false; - // position of parent filter in Filters array used as prototype for filter - // in PrgStack array. Not defined for filters in Filters array. - /** @type {number} */ - this.ParentFilter = null; + // position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + /** @type {number} */ + this.ParentFilter = null; - /** @type {VM_PreparedProgram} */ - this.Prg = new VM_PreparedProgram(); -}; + /** @type {VM_PreparedProgram} */ + this.Prg = new VM_PreparedProgram(); + } +} const VMCF_OP0 = 0; const VMCF_OP1 = 1; @@ -383,22 +383,24 @@ const VM_CmdFlags = [ /** - * @param {number} length - * @param {number} crc - * @param {VM_StandardFilters} type - * @struct - * @constructor */ -const StandardFilterSignature = function(length, crc, type) { - /** @type {number} */ - this.Length = length; +class StandardFilterSignature { + /** + * @param {number} length + * @param {number} crc + * @param {VM_StandardFilters} type + */ + constructor(length, crc, type) { + /** @type {number} */ + this.Length = length; - /** @type {number} */ - this.CRC = crc; + /** @type {number} */ + this.CRC = crc; - /** @type {VM_StandardFilters} */ - this.Type = type; -}; + /** @type {VM_StandardFilters} */ + this.Type = type; + } +} /** * @type {Array} @@ -416,402 +418,404 @@ const StdList = [ /** * @constructor */ -const RarVM = function() { - /** @private {Uint8Array} */ - this.mem_ = null; +class RarVM { + constructor() { + /** @private {Uint8Array} */ + this.mem_ = null; - /** @private {Uint32Array} */ - this.R_ = new Uint32Array(8); + /** @private {Uint32Array} */ + this.R_ = new Uint32Array(8); - /** @private {number} */ - this.flags_ = 0; -}; - -/** - * Initializes the memory of the VM. - */ -RarVM.prototype.init = function() { - if (!this.mem_) { - this.mem_ = new Uint8Array(VM_MEMSIZE); - } -}; - -/** - * @param {Uint8Array} code - * @return {VM_StandardFilters} - */ -RarVM.prototype.isStandardFilter = function(code) { - const codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0; - for (let i = 0; i < StdList.length; ++i) { - if (StdList[i].CRC == codeCRC && StdList[i].Length == code.length) - return StdList[i].Type; + /** @private {number} */ + this.flags_ = 0; } - return VM_StandardFilters.VMSF_NONE; -}; + /** + * Initializes the memory of the VM. + */ + init() { + if (!this.mem_) { + this.mem_ = new Uint8Array(VM_MEMSIZE); + } + } -/** - * @param {VM_PreparedOperand} op - * @param {boolean} byteMode - * @param {bitjs.io.BitStream} bstream A rtl bit stream. - */ -RarVM.prototype.decodeArg = function(op, byteMode, bstream) { - const data = bstream.peekBits(16); - if (data & 0x8000) { - op.Type = VM_OpType.VM_OPREG; // Operand is register (R[0]..R[7]) - bstream.readBits(1); // 1 flag bit and... - op.Data = bstream.readBits(3); // ... 3 register number bits - op.Addr = [this.R_[op.Data]] // TODO &R[Op.Data] // Register address - } else { - if ((data & 0xc000) == 0) { - op.Type = VM_OpType.VM_OPINT; // Operand is integer - bstream.readBits(2); // 2 flag bits - if (byteMode) { - op.Data = bstream.readBits(8); // Byte integer. - } else { - op.Data = RarVM.readData(bstream); // 32 bit integer. - } + /** + * @param {Uint8Array} code + * @return {VM_StandardFilters} + */ + isStandardFilter(code) { + const codeCRC = (CRC(0xffffffff, code, code.length) ^ 0xffffffff) >>> 0; + for (let i = 0; i < StdList.length; ++i) { + if (StdList[i].CRC == codeCRC && StdList[i].Length == code.length) + return StdList[i].Type; + } + + return VM_StandardFilters.VMSF_NONE; + } + + /** + * @param {VM_PreparedOperand} op + * @param {boolean} byteMode + * @param {bitjs.io.BitStream} bstream A rtl bit stream. + */ + decodeArg(op, byteMode, bstream) { + const data = bstream.peekBits(16); + if (data & 0x8000) { + op.Type = VM_OpType.VM_OPREG; // Operand is register (R[0]..R[7]) + bstream.readBits(1); // 1 flag bit and... + op.Data = bstream.readBits(3); // ... 3 register number bits + op.Addr = [this.R_[op.Data]] // TODO &R[Op.Data] // Register address } else { - // Operand is data addressed by register data, base address or both. - op.Type = VM_OpType.VM_OPREGMEM; - if ((data & 0x2000) == 0) { - bstream.readBits(3); // 3 flag bits - // Base address is zero, just use the address from register. - op.Data = bstream.readBits(3); // (Data>>10)&7 - op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] - op.Base = 0; - } else { - bstream.readBits(4); // 4 flag bits - if ((data & 0x1000) == 0) { - // Use both register and base address. - op.Data = bstream.readBits(3); - op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + if ((data & 0xc000) == 0) { + op.Type = VM_OpType.VM_OPINT; // Operand is integer + bstream.readBits(2); // 2 flag bits + if (byteMode) { + op.Data = bstream.readBits(8); // Byte integer. } else { - // Use base address only. Access memory by fixed address. - op.Data = 0; + op.Data = RarVM.readData(bstream); // 32 bit integer. + } + } else { + // Operand is data addressed by register data, base address or both. + op.Type = VM_OpType.VM_OPREGMEM; + if ((data & 0x2000) == 0) { + bstream.readBits(3); // 3 flag bits + // Base address is zero, just use the address from register. + op.Data = bstream.readBits(3); // (Data>>10)&7 + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + op.Base = 0; + } else { + bstream.readBits(4); // 4 flag bits + if ((data & 0x1000) == 0) { + // Use both register and base address. + op.Data = bstream.readBits(3); + op.Addr = [this.R_[op.Data]]; // TODO &R[op.Data] + } else { + // Use base address only. Access memory by fixed address. + op.Data = 0; + } + op.Base = RarVM.readData(bstream); // Read base address. } - op.Base = RarVM.readData(bstream); // Read base address. } } } -}; -/** - * @param {VM_PreparedProgram} prg - */ -RarVM.prototype.execute = function(prg) { - this.R_.set(prg.InitR); + /** + * @param {VM_PreparedProgram} prg + */ + execute(prg) { + this.R_.set(prg.InitR); - const globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE); - if (globalSize) { - this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR); + const globalSize = Math.min(prg.GlobalData.length, VM_GLOBALMEMSIZE); + if (globalSize) { + this.mem_.set(prg.GlobalData.subarray(0, globalSize), VM_GLOBALMEMADDR); + } + + const staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize); + if (staticSize) { + this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize); + } + + this.R_[7] = VM_MEMSIZE; + this.flags_ = 0; + + const preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd; + if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) { + // Invalid VM program. Let's replace it with 'return' command. + preparedCode.OpCode = VM_Commands.VM_RET; + } + + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + let newBlockPos = dataView.getUint32(0x20, true /* little endian */) & VM_MEMMASK; + const newBlockSize = dataView.getUint32(0x1c, true /* little endian */) & VM_MEMMASK; + if (newBlockPos + newBlockSize >= VM_MEMSIZE) { + newBlockPos = newBlockSize = 0; + } + prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize); + + prg.GlobalData = new Uint8Array(0); + + const dataSize = Math.min(dataView.getUint32(0x30), (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)); + if (dataSize != 0) { + const len = dataSize + VM_FIXEDGLOBALSIZE; + prg.GlobalData = new Uint8Array(len); + prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len)); + } } - const staticSize = Math.min(prg.StaticData.length, VM_GLOBALMEMSIZE - globalSize); - if (staticSize) { - this.mem_.set(prg.StaticData.subarray(0, staticSize), VM_GLOBALMEMADDR + globalSize); + /** + * @param {Array} preparedCodes + * @return {boolean} + */ + executeCode(preparedCodes) { + let codeIndex = 0; + let cmd = preparedCodes[codeIndex]; + // TODO: Why is this an infinite loop instead of just returning + // when a VM_RET is hit? + while (1) { + switch (cmd.OpCode) { + case VM_Commands.VM_RET: + if (this.R_[7] >= VM_MEMSIZE) { + return true; + } + //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); + this.R_[7] += 4; + continue; + + case VM_Commands.VM_STANDARD: + this.executeStandardFilter(cmd.Op1.Data); + break; + + default: + console.error('RarVM OpCode not supported: ' + getDebugString(VM_Commands, cmd.OpCode)); + break; + } // switch (cmd.OpCode) + codeIndex++; + cmd = preparedCodes[codeIndex]; + } } - this.R_[7] = VM_MEMSIZE; - this.flags_ = 0; + /** + * @param {number} filterType + */ + executeStandardFilter(filterType) { + switch (filterType) { + case VM_StandardFilters.VMSF_DELTA: + const dataSize = this.R_[4]; + const channels = this.R_[0]; + let srcPos = 0; + const border = dataSize * 2; - const preparedCodes = prg.AltCmd ? prg.AltCmd : prg.Cmd; - if (prg.Cmd.length > 0 && !this.executeCode(preparedCodes)) { - // Invalid VM program. Let's replace it with 'return' command. - preparedCode.OpCode = VM_Commands.VM_RET; - } + //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); + const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); + dataView.setUint32(0x20, dataSize, true /* little endian */); - const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); - let newBlockPos = dataView.getUint32(0x20, true /* little endian */) & VM_MEMMASK; - const newBlockSize = dataView.getUint32(0x1c, true /* little endian */) & VM_MEMMASK; - if (newBlockPos + newBlockSize >= VM_MEMSIZE) { - newBlockPos = newBlockSize = 0; - } - prg.FilteredData = this.mem_.subarray(newBlockPos, newBlockPos + newBlockSize); - - prg.GlobalData = new Uint8Array(0); - - const dataSize = Math.min(dataView.getUint32(0x30), (VM_GLOBALMEMSIZE - VM_FIXEDGLOBALSIZE)); - if (dataSize != 0) { - const len = dataSize + VM_FIXEDGLOBALSIZE; - prg.GlobalData = new Uint8Array(len); - prg.GlobalData.set(mem.subarray(VM_GLOBALMEMADDR, VM_GLOBALMEMADDR + len)); - } -}; - -/** - * @param {Array} preparedCodes - * @return {boolean} - */ -RarVM.prototype.executeCode = function(preparedCodes) { - let codeIndex = 0; - let cmd = preparedCodes[codeIndex]; - // TODO: Why is this an infinite loop instead of just returning - // when a VM_RET is hit? - while (1) { - switch (cmd.OpCode) { - case VM_Commands.VM_RET: - if (this.R_[7] >= VM_MEMSIZE) { - return true; + if (dataSize >= VM_GLOBALMEMADDR / 2) { + break; + } + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (let curChannel = 0; curChannel < channels; ++curChannel) { + let prevByte = 0; + for (let destPos = dataSize + curChannel; destPos < border; destPos += channels) { + prevByte = (prevByte - this.mem_[srcPos++]) & 0xff; + this.mem_[destPos] = prevByte; + } } - //SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); - this.R_[7] += 4; - continue; - case VM_Commands.VM_STANDARD: - this.executeStandardFilter(cmd.Op1.Data); break; default: - console.error('RarVM OpCode not supported: ' + getDebugString(VM_Commands, cmd.OpCode)); + console.error('RarVM Standard Filter not supported: ' + getDebugString(VM_StandardFilters, filterType)); break; - } // switch (cmd.OpCode) - codeIndex++; - cmd = preparedCodes[codeIndex]; + } } -}; -/** - * @param {number} filterType - */ -RarVM.prototype.executeStandardFilter = function(filterType) { - switch (filterType) { - case VM_StandardFilters.VMSF_DELTA: - const dataSize = this.R_[4]; - const channels = this.R_[0]; - let srcPos = 0; - const border = dataSize * 2; + /** + * @param {Uint8Array} code + * @param {VM_PreparedProgram} prg + */ + prepare(code, prg) { + let codeSize = code.length; - //SET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20],DataSize); - const dataView = new DataView(this.mem_.buffer, VM_GLOBALMEMADDR); - dataView.setUint32(0x20, dataSize, true /* little endian */); + //InitBitInput(); + //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); + const bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */); - if (dataSize >= VM_GLOBALMEMADDR / 2) { - break; + // Calculate the single byte XOR checksum to check validity of VM code. + let xorSum = 0; + for (let i = 1; i < codeSize; ++i) { + xorSum ^= code[i]; + } + + bstream.readBits(8); + + prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp. + + // VM code is valid if equal. + if (xorSum == code[0]) { + const filterType = this.isStandardFilter(code); + if (filterType != VM_StandardFilters.VMSF_NONE) { + // VM code is found among standard filters. + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); + + curCmd.OpCode = VM_Commands.VM_STANDARD; + curCmd.Op1.Data = filterType; + // TODO: Addr=&CurCmd->Op1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + codeSize = 0; } - // Bytes from same channels are grouped to continual data blocks, - // so we need to place them back to their interleaving positions. - for (let curChannel = 0; curChannel < channels; ++curChannel) { - let prevByte = 0; - for (let destPos = dataSize + curChannel; destPos < border; destPos += channels) { - prevByte = (prevByte - this.mem_[srcPos++]) & 0xff; - this.mem_[destPos] = prevByte; + const dataFlag = bstream.readBits(1); + + // Read static data contained in DB operators. This data cannot be + // changed, it is a part of VM code, not a filter parameter. + + if (dataFlag & 0x8000) { + const dataSize = RarVM.readData(bstream) + 1; + // TODO: This accesses the byte pointer of the bstream directly. Is that ok? + for (let i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) { + // Append a byte to the program's static data. + const newStaticData = new Uint8Array(prg.StaticData.length + 1); + newStaticData.set(prg.StaticData); + newStaticData[newStaticData.length - 1] = bstream.readBits(8); + prg.StaticData = newStaticData; } } - break; - - default: - console.error('RarVM Standard Filter not supported: ' + getDebugString(VM_StandardFilters, filterType)); - break; - } -}; - -/** - * @param {Uint8Array} code - * @param {VM_PreparedProgram} prg - */ -RarVM.prototype.prepare = function(code, prg) { - let codeSize = code.length; - - //InitBitInput(); - //memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); - const bstream = new bitjs.io.BitStream(code.buffer, true /* rtl */); - - // Calculate the single byte XOR checksum to check validity of VM code. - let xorSum = 0; - for (let i = 1; i < codeSize; ++i) { - xorSum ^= code[i]; - } - - bstream.readBits(8); - - prg.Cmd = []; // TODO: Is this right? I don't see it being done in rarvm.cpp. - - // VM code is valid if equal. - if (xorSum == code[0]) { - const filterType = this.isStandardFilter(code); - if (filterType != VM_StandardFilters.VMSF_NONE) { - // VM code is found among standard filters. - const curCmd = new VM_PreparedCommand(); - prg.Cmd.push(curCmd); - - curCmd.OpCode = VM_Commands.VM_STANDARD; - curCmd.Op1.Data = filterType; - // TODO: Addr=&CurCmd->Op1.Data - curCmd.Op1.Addr = [curCmd.Op1.Data]; - curCmd.Op2.Addr = [null]; // &CurCmd->Op2.Data; - curCmd.Op1.Type = VM_OpType.VM_OPNONE; - curCmd.Op2.Type = VM_OpType.VM_OPNONE; - codeSize = 0; - } - - const dataFlag = bstream.readBits(1); - - // Read static data contained in DB operators. This data cannot be - // changed, it is a part of VM code, not a filter parameter. - - if (dataFlag & 0x8000) { - const dataSize = RarVM.readData(bstream) + 1; - // TODO: This accesses the byte pointer of the bstream directly. Is that ok? - for (let i = 0; i < bstream.bytePtr < codeSize && i < dataSize; ++i) { - // Append a byte to the program's static data. - const newStaticData = new Uint8Array(prg.StaticData.length + 1); - newStaticData.set(prg.StaticData); - newStaticData[newStaticData.length - 1] = bstream.readBits(8); - prg.StaticData = newStaticData; - } - } - - while (bstream.bytePtr < codeSize) { - const curCmd = new VM_PreparedCommand(); - prg.Cmd.push(curCmd); // Prg->Cmd.Add(1) - const flag = bstream.peekBits(1); - if (!flag) { // (Data&0x8000)==0 - curCmd.OpCode = bstream.readBits(4); - } else { - curCmd.OpCode = (bstream.readBits(6) - 24); - } - - if (VM_CmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) { - curCmd.ByteMode = (bstream.readBits(1) != 0); - } else { - curCmd.ByteMode = 0; - } - curCmd.Op1.Type = VM_OpType.VM_OPNONE; - curCmd.Op2.Type = VM_OpType.VM_OPNONE; - const opNum = (VM_CmdFlags[curCmd.OpCode] & VMCF_OPMASK); - curCmd.Op1.Addr = null; - curCmd.Op2.Addr = null; - if (opNum > 0) { - this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand - if (opNum == 2) { - this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand + while (bstream.bytePtr < codeSize) { + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); // Prg->Cmd.Add(1) + const flag = bstream.peekBits(1); + if (!flag) { // (Data&0x8000)==0 + curCmd.OpCode = bstream.readBits(4); } else { - if (curCmd.Op1.Type == VM_OpType.VM_OPINT && (VM_CmdFlags[curCmd.OpCode] & (VMCF_JUMP|VMCF_PROC))) { - // Calculating jump distance. - let distance = curCmd.Op1.Data; - if (distance >= 256) { - distance -= 256; - } else { - if (distance >= 136) { - distance -= 264; + curCmd.OpCode = (bstream.readBits(6) - 24); + } + + if (VM_CmdFlags[curCmd.OpCode] & VMCF_BYTEMODE) { + curCmd.ByteMode = (bstream.readBits(1) != 0); + } else { + curCmd.ByteMode = 0; + } + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; + const opNum = (VM_CmdFlags[curCmd.OpCode] & VMCF_OPMASK); + curCmd.Op1.Addr = null; + curCmd.Op2.Addr = null; + if (opNum > 0) { + this.decodeArg(curCmd.Op1, curCmd.ByteMode, bstream); // reading the first operand + if (opNum == 2) { + this.decodeArg(curCmd.Op2, curCmd.ByteMode, bstream); // reading the second operand + } else { + if (curCmd.Op1.Type == VM_OpType.VM_OPINT && (VM_CmdFlags[curCmd.OpCode] & (VMCF_JUMP|VMCF_PROC))) { + // Calculating jump distance. + let distance = curCmd.Op1.Data; + if (distance >= 256) { + distance -= 256; } else { - if (distance >= 16) { - distance -= 8; + if (distance >= 136) { + distance -= 264; } else { - if (distance >= 8) { - distance -= 16; + if (distance >= 16) { + distance -= 8; + } else { + if (distance >= 8) { + distance -= 16; + } } } + distance += prg.Cmd.length; } - distance += prg.Cmd.length; + curCmd.Op1.Data = distance; } - curCmd.Op1.Data = distance; } - } - } // if (OpNum>0) - } // while ((uint)InAddr0) + } // while ((uint)InAddrOp1.Data - curCmd.Op1.Addr = [curCmd.Op1.Data]; - curCmd.Op2.Addr = [curCmd.Op2.Data]; - curCmd.Op1.Type = VM_OpType.VM_OPNONE; - curCmd.Op2.Type = VM_OpType.VM_OPNONE; + const curCmd = new VM_PreparedCommand(); + prg.Cmd.push(curCmd); + curCmd.OpCode = VM_Commands.VM_RET; + // TODO: Addr=&CurCmd->Op1.Data + curCmd.Op1.Addr = [curCmd.Op1.Data]; + curCmd.Op2.Addr = [curCmd.Op2.Data]; + curCmd.Op1.Type = VM_OpType.VM_OPNONE; + curCmd.Op2.Type = VM_OpType.VM_OPNONE; - // If operand 'Addr' field has not been set by DecodeArg calls above, - // let's set it to point to operand 'Data' field. It is necessary for - // VM_OPINT type operands (usual integers) or maybe if something was - // not set properly for other operands. 'Addr' field is required - // for quicker addressing of operand data. - for (let i = 0; i < prg.Cmd.length; ++i) { - const cmd = prg.Cmd[i]; - if (cmd.Op1.Addr == null) { - cmd.Op1.Addr = [cmd.Op1.Data]; - } - if (cmd.Op2.Addr == null) { - cmd.Op2.Addr = [cmd.Op2.Data]; - } - } - -/* -#ifdef VM_OPTIMIZE - if (CodeSize!=0) - Optimize(Prg); -#endif - */ -}; - -/** - * @param {Uint8Array} arr The byte array to set a value in. - * @param {number} value The unsigned 32-bit value to set. - * @param {number} offset Offset into arr to start setting the value, defaults to 0. - */ -RarVM.prototype.setLowEndianValue = function(arr, value, offset) { - const i = offset || 0; - arr[i] = value & 0xff; - arr[i + 1] = (value >>> 8) & 0xff; - arr[i + 2] = (value >>> 16) & 0xff; - arr[i + 3] = (value >>> 24) & 0xff; -}; - -/** - * Sets a number of bytes of the VM memory at the given position from a - * source buffer of bytes. - * @param {number} pos The position in the VM memory to start writing to. - * @param {Uint8Array} buffer The source buffer of bytes. - * @param {number} dataSize The number of bytes to set. - */ -RarVM.prototype.setMemory = function(pos, buffer, dataSize) { - if (pos < VM_MEMSIZE) { - const numBytes = Math.min(dataSize, VM_MEMSIZE - pos); - for (let i = 0; i < numBytes; ++i) { - this.mem_[pos + i] = buffer[i]; - } - } -}; - -/** - * Static function that reads in the next set of bits for the VM - * (might return 4, 8, 16 or 32 bits). - * @param {bitjs.io.BitStream} bstream A RTL bit stream. - * @return {number} The value of the bits read. - */ -RarVM.readData = function(bstream) { - // Read in the first 2 bits. - const flags = bstream.readBits(2); - switch (flags) { // Data&0xc000 - // Return the next 4 bits. - case 0: - return bstream.readBits(4); // (Data>>10)&0xf - - case 1: // 0x4000 - // 0x3c00 => 0011 1100 0000 0000 - if (bstream.peekBits(4) == 0) { // (Data&0x3c00)==0 - // Skip the 4 zero bits. - bstream.readBits(4); - // Read in the next 8 and pad with 1s to 32 bits. - return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff) + // If operand 'Addr' field has not been set by DecodeArg calls above, + // let's set it to point to operand 'Data' field. It is necessary for + // VM_OPINT type operands (usual integers) or maybe if something was + // not set properly for other operands. 'Addr' field is required + // for quicker addressing of operand data. + for (let i = 0; i < prg.Cmd.length; ++i) { + const cmd = prg.Cmd[i]; + if (cmd.Op1.Addr == null) { + cmd.Op1.Addr = [cmd.Op1.Data]; } + if (cmd.Op2.Addr == null) { + cmd.Op2.Addr = [cmd.Op2.Data]; + } + } - // Else, read in the next 8. - return bstream.readBits(8); - - // Read in the next 16. - case 2: // 0x8000 - const val = bstream.getBits(); - bstream.readBits(16); - return val; //bstream.readBits(16); - - // case 3 - default: - return (bstream.readBits(16) << 16) | bstream.readBits(16); + /* + #ifdef VM_OPTIMIZE + if (CodeSize!=0) + Optimize(Prg); + #endif + */ } -}; + + /** + * @param {Uint8Array} arr The byte array to set a value in. + * @param {number} value The unsigned 32-bit value to set. + * @param {number} offset Offset into arr to start setting the value, defaults to 0. + */ + setLowEndianValue(arr, value, offset) { + const i = offset || 0; + arr[i] = value & 0xff; + arr[i + 1] = (value >>> 8) & 0xff; + arr[i + 2] = (value >>> 16) & 0xff; + arr[i + 3] = (value >>> 24) & 0xff; + } + + /** + * Sets a number of bytes of the VM memory at the given position from a + * source buffer of bytes. + * @param {number} pos The position in the VM memory to start writing to. + * @param {Uint8Array} buffer The source buffer of bytes. + * @param {number} dataSize The number of bytes to set. + */ + setMemory(pos, buffer, dataSize) { + if (pos < VM_MEMSIZE) { + const numBytes = Math.min(dataSize, VM_MEMSIZE - pos); + for (let i = 0; i < numBytes; ++i) { + this.mem_[pos + i] = buffer[i]; + } + } + } + + /** + * Static function that reads in the next set of bits for the VM + * (might return 4, 8, 16 or 32 bits). + * @param {bitjs.io.BitStream} bstream A RTL bit stream. + * @return {number} The value of the bits read. + */ + static readData(bstream) { + // Read in the first 2 bits. + const flags = bstream.readBits(2); + switch (flags) { // Data&0xc000 + // Return the next 4 bits. + case 0: + return bstream.readBits(4); // (Data>>10)&0xf + + case 1: // 0x4000 + // 0x3c00 => 0011 1100 0000 0000 + if (bstream.peekBits(4) == 0) { // (Data&0x3c00)==0 + // Skip the 4 zero bits. + bstream.readBits(4); + // Read in the next 8 and pad with 1s to 32 bits. + return (0xffffff00 | bstream.readBits(8)) >>> 0; // ((Data>>2)&0xff) + } + + // Else, read in the next 8. + return bstream.readBits(8); + + // Read in the next 16. + case 2: // 0x8000 + const val = bstream.getBits(); + bstream.readBits(16); + return val; //bstream.readBits(16); + + // case 3 + default: + return (bstream.readBits(16) << 16) | bstream.readBits(16); + } + } +} // ============================================================================================== // diff --git a/archive/unrar.js b/archive/unrar.js index f7e93a2..687cd58 100644 --- a/archive/unrar.js +++ b/archive/unrar.js @@ -63,156 +63,158 @@ const ENDARC_HEAD = 0x7b; // ============================================================================================== // /** - * @param {bitjs.io.BitStream} bstream - * @constructor */ -const RarVolumeHeader = function(bstream) { - const headPos = bstream.bytePtr; - // byte 1,2 - info("Rar Volume Header @"+bstream.bytePtr); - - this.crc = bstream.readBits(16); - info(" crc=" + this.crc); +class RarVolumeHeader { + /** + * @param {bitjs.io.BitStream} bstream + */ + constructor(bstream) { + const headPos = bstream.bytePtr; + // byte 1,2 + info("Rar Volume Header @"+bstream.bytePtr); - // byte 3 - this.headType = bstream.readBits(8); - info(" headType=" + this.headType); + this.crc = bstream.readBits(16); + info(" crc=" + this.crc); - // Get flags - // bytes 4,5 - this.flags = {}; - this.flags.value = bstream.peekBits(16); - - info(" flags=" + twoByteValueToHexString(this.flags.value)); - switch (this.headType) { - case MAIN_HEAD: - this.flags.MHD_VOLUME = !!bstream.readBits(1); - this.flags.MHD_COMMENT = !!bstream.readBits(1); - this.flags.MHD_LOCK = !!bstream.readBits(1); - this.flags.MHD_SOLID = !!bstream.readBits(1); - this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1); - this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT; - this.flags.MHD_AV = !!bstream.readBits(1); - this.flags.MHD_PROTECT = !!bstream.readBits(1); - this.flags.MHD_PASSWORD = !!bstream.readBits(1); - this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1); - this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1); - bstream.readBits(6); // unused - break; - case FILE_HEAD: - this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001 - this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002 - this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004 - this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008 - this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010 - bstream.readBits(3); // unused - this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100 - this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200 - this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400 - this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800 - this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000 - this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000 - bstream.readBits(2); // unused - info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE); - break; - default: - bstream.readBits(16); - } - - // byte 6,7 - this.headSize = bstream.readBits(16); - info(" headSize=" + this.headSize); - switch (this.headType) { - case MAIN_HEAD: - this.highPosAv = bstream.readBits(16); - this.posAv = bstream.readBits(32); - if (this.flags.MHD_ENCRYPTVER) { - this.encryptVer = bstream.readBits(8); - } - info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv); - break; - case FILE_HEAD: - this.packSize = bstream.readBits(32); - this.unpackedSize = bstream.readBits(32); - this.hostOS = bstream.readBits(8); - this.fileCRC = bstream.readBits(32); - this.fileTime = bstream.readBits(32); - this.unpVer = bstream.readBits(8); - this.method = bstream.readBits(8); - this.nameSize = bstream.readBits(16); - this.fileAttr = bstream.readBits(32); + // byte 3 + this.headType = bstream.readBits(8); + info(" headType=" + this.headType); + + // Get flags + // bytes 4,5 + this.flags = {}; + this.flags.value = bstream.peekBits(16); - if (this.flags.LHD_LARGE) { - info("Warning: Reading in LHD_LARGE 64-bit size values"); - this.HighPackSize = bstream.readBits(32); - this.HighUnpSize = bstream.readBits(32); - } else { - this.HighPackSize = 0; - this.HighUnpSize = 0; - if (this.unpackedSize == 0xffffffff) { - this.HighUnpSize = 0x7fffffff - this.unpackedSize = 0xffffffff; + info(" flags=" + twoByteValueToHexString(this.flags.value)); + switch (this.headType) { + case MAIN_HEAD: + this.flags.MHD_VOLUME = !!bstream.readBits(1); + this.flags.MHD_COMMENT = !!bstream.readBits(1); + this.flags.MHD_LOCK = !!bstream.readBits(1); + this.flags.MHD_SOLID = !!bstream.readBits(1); + this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1); + this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT; + this.flags.MHD_AV = !!bstream.readBits(1); + this.flags.MHD_PROTECT = !!bstream.readBits(1); + this.flags.MHD_PASSWORD = !!bstream.readBits(1); + this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1); + this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1); + bstream.readBits(6); // unused + break; + case FILE_HEAD: + this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001 + this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002 + this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004 + this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008 + this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010 + bstream.readBits(3); // unused + this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100 + this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200 + this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400 + this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800 + this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000 + this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000 + bstream.readBits(2); // unused + info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE); + break; + default: + bstream.readBits(16); + } + + // byte 6,7 + this.headSize = bstream.readBits(16); + info(" headSize=" + this.headSize); + switch (this.headType) { + case MAIN_HEAD: + this.highPosAv = bstream.readBits(16); + this.posAv = bstream.readBits(32); + if (this.flags.MHD_ENCRYPTVER) { + this.encryptVer = bstream.readBits(8); } - } - this.fullPackSize = 0; - this.fullUnpackSize = 0; - this.fullPackSize |= this.HighPackSize; - this.fullPackSize <<= 32; - this.fullPackSize |= this.packSize; - - // read in filename - - this.filename = bstream.readBytes(this.nameSize); - let _s = ''; - for (let _i = 0; _i < this.filename.length; _i++) { - _s += String.fromCharCode(this.filename[_i]); - } - - this.filename = _s; - - if (this.flags.LHD_SALT) { - info("Warning: Reading in 64-bit salt value"); - this.salt = bstream.readBits(64); // 8 bytes - } - - if (this.flags.LHD_EXTTIME) { - // 16-bit flags - const extTimeFlags = bstream.readBits(16); + info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv); + break; + case FILE_HEAD: + this.packSize = bstream.readBits(32); + this.unpackedSize = bstream.readBits(32); + this.hostOS = bstream.readBits(8); + this.fileCRC = bstream.readBits(32); + this.fileTime = bstream.readBits(32); + this.unpVer = bstream.readBits(8); + this.method = bstream.readBits(8); + this.nameSize = bstream.readBits(16); + this.fileAttr = bstream.readBits(32); - // this is adapted straight out of arcread.cpp, Archive::ReadHeader() - for (let I = 0; I < 4; ++I) { - const rmode = extTimeFlags >> ((3 - I) * 4); - if ((rmode & 8) == 0) { - continue; + if (this.flags.LHD_LARGE) { + info("Warning: Reading in LHD_LARGE 64-bit size values"); + this.HighPackSize = bstream.readBits(32); + this.HighUnpSize = bstream.readBits(32); + } else { + this.HighPackSize = 0; + this.HighUnpSize = 0; + if (this.unpackedSize == 0xffffffff) { + this.HighUnpSize = 0x7fffffff + this.unpackedSize = 0xffffffff; } - if (I != 0) - bstream.readBits(16); - const count = (rmode & 3); - for (let J = 0; J < count; ++J) { - bstream.readBits(8); - } } + this.fullPackSize = 0; + this.fullUnpackSize = 0; + this.fullPackSize |= this.HighPackSize; + this.fullPackSize <<= 32; + this.fullPackSize |= this.packSize; + + // read in filename + + this.filename = bstream.readBytes(this.nameSize); + let _s = ''; + for (let _i = 0; _i < this.filename.length; _i++) { + _s += String.fromCharCode(this.filename[_i]); + } + + this.filename = _s; + + if (this.flags.LHD_SALT) { + info("Warning: Reading in 64-bit salt value"); + this.salt = bstream.readBits(64); // 8 bytes + } + + if (this.flags.LHD_EXTTIME) { + // 16-bit flags + const extTimeFlags = bstream.readBits(16); + + // this is adapted straight out of arcread.cpp, Archive::ReadHeader() + for (let I = 0; I < 4; ++I) { + const rmode = extTimeFlags >> ((3 - I) * 4); + if ((rmode & 8) == 0) { + continue; + } + if (I != 0) + bstream.readBits(16); + const count = (rmode & 3); + for (let J = 0; J < count; ++J) { + bstream.readBits(8); + } + } + } + + if (this.flags.LHD_COMMENT) { + info("Found a LHD_COMMENT"); + } + + while (headPos + this.headSize > bstream.bytePtr) { + bstream.readBits(1); + } + + info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename); + + break; + default: + info("Found a header of type 0x" + byteValueToHexString(this.headType)); + // skip the rest of the header bytes (for now) + bstream.readBytes(this.headSize - 7); + break; } - - if (this.flags.LHD_COMMENT) { - info("Found a LHD_COMMENT"); - } - - - while (headPos + this.headSize > bstream.bytePtr) { - bstream.readBits(1); - } - - info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename); - - break; - default: - info("Found a header of type 0x" + byteValueToHexString(this.headType)); - // skip the rest of the header bytes (for now) - bstream.readBytes(this.headSize - 7); - break; } -}; +} const BLOCK_LZ = 0; const BLOCK_PPM = 1; @@ -1212,7 +1214,7 @@ function unpack(v) { const Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer; const Solid = v.header.LHD_SOLID; const bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); - + rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); info("Unpacking " + v.filename + " RAR v" + Ver); @@ -1237,43 +1239,49 @@ function unpack(v) { return rBuffer.data; } -// bstream is a bit stream -const RarLocalFile = function(bstream) { - this.header = new RarVolumeHeader(bstream); - this.filename = this.header.filename; - - if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { - this.isValid = false; - info("Error! RAR Volume did not include a FILE_HEAD header "); - } - else { - // read in the compressed data - this.fileData = null; - if (this.header.packSize > 0) { - this.fileData = bstream.readBytes(this.header.packSize); - this.isValid = true; +/** + */ +class RarLocalFile { + /** + * @param {bitjs.io.BitStream} bstream + */ + constructor(bstream) { + this.header = new RarVolumeHeader(bstream); + this.filename = this.header.filename; + + if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { + this.isValid = false; + info("Error! RAR Volume did not include a FILE_HEAD header "); + } + else { + // read in the compressed data + this.fileData = null; + if (this.header.packSize > 0) { + this.fileData = bstream.readBytes(this.header.packSize); + this.isValid = true; + } } } -}; -RarLocalFile.prototype.unrar = function() { - if (!this.header.flags.LHD_SPLIT_BEFORE) { - // unstore file - if (this.header.method == 0x30) { - info("Unstore "+this.filename); - this.isValid = true; - - currentBytesUnarchivedInFile += this.fileData.length; - currentBytesUnarchived += this.fileData.length; + unrar() { + if (!this.header.flags.LHD_SPLIT_BEFORE) { + // unstore file + if (this.header.method == 0x30) { + info("Unstore "+this.filename); + this.isValid = true; - // Create a new buffer and copy it over. - const len = this.header.packSize; - const newBuffer = new bitjs.io.ByteBuffer(len); - newBuffer.insertBytes(this.fileData); - this.fileData = newBuffer.data; - } else { - this.isValid = true; - this.fileData = unpack(this); + currentBytesUnarchivedInFile += this.fileData.length; + currentBytesUnarchived += this.fileData.length; + + // Create a new buffer and copy it over. + const len = this.header.packSize; + const newBuffer = new bitjs.io.ByteBuffer(len); + newBuffer.insertBytes(this.fileData); + this.fileData = newBuffer.data; + } else { + this.isValid = true; + this.fileData = unpack(this); + } } } } diff --git a/archive/untar.js b/archive/untar.js index 3360cd8..76c83f3 100644 --- a/archive/untar.js +++ b/archive/untar.js @@ -46,66 +46,68 @@ const readCleanString = function(bstr, numBytes) { return zIndex != -1 ? str.substr(0, zIndex) : str; }; -// takes a ByteStream and parses out the local file information -const TarLocalFile = function(bstream) { - this.isValid = false; +class TarLocalFile { + // takes a ByteStream and parses out the local file information + constructor(bstream) { + this.isValid = false; - // Read in the header block - this.name = readCleanString(bstream, 100); - this.mode = readCleanString(bstream, 8); - this.uid = readCleanString(bstream, 8); - this.gid = readCleanString(bstream, 8); - this.size = parseInt(readCleanString(bstream, 12), 8); - this.mtime = readCleanString(bstream, 12); - this.chksum = readCleanString(bstream, 8); - this.typeflag = readCleanString(bstream, 1); - this.linkname = readCleanString(bstream, 100); - this.maybeMagic = readCleanString(bstream, 6); + // Read in the header block + this.name = readCleanString(bstream, 100); + this.mode = readCleanString(bstream, 8); + this.uid = readCleanString(bstream, 8); + this.gid = readCleanString(bstream, 8); + this.size = parseInt(readCleanString(bstream, 12), 8); + this.mtime = readCleanString(bstream, 12); + this.chksum = readCleanString(bstream, 8); + this.typeflag = readCleanString(bstream, 1); + this.linkname = readCleanString(bstream, 100); + this.maybeMagic = readCleanString(bstream, 6); - if (this.maybeMagic == "ustar") { - this.version = readCleanString(bstream, 2); - this.uname = readCleanString(bstream, 32); - this.gname = readCleanString(bstream, 32); - this.devmajor = readCleanString(bstream, 8); - this.devminor = readCleanString(bstream, 8); - this.prefix = readCleanString(bstream, 155); + if (this.maybeMagic == "ustar") { + this.version = readCleanString(bstream, 2); + this.uname = readCleanString(bstream, 32); + this.gname = readCleanString(bstream, 32); + this.devmajor = readCleanString(bstream, 8); + this.devminor = readCleanString(bstream, 8); + this.prefix = readCleanString(bstream, 155); - if (this.prefix.length) { - this.name = this.prefix + this.name; + if (this.prefix.length) { + this.name = this.prefix + this.name; + } + bstream.readBytes(12); // 512 - 500 + } else { + bstream.readBytes(255); // 512 - 257 + } + + // Done header, now rest of blocks are the file contents. + this.filename = this.name; + this.fileData = null; + + info("Untarring file '" + this.filename + "'"); + info(" size = " + this.size); + info(" typeflag = " + this.typeflag); + + // A regular file. + if (this.typeflag == 0) { + info(" This is a regular file."); + const sizeInBytes = parseInt(this.size); + this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.size); + if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) { + this.isValid = true; + } + + bstream.readBytes(this.size); + + // Round up to 512-byte blocks. + const remaining = 512 - bstream.ptr % 512; + if (remaining > 0 && remaining < 512) { + bstream.readBytes(remaining); + } + } else if (this.typeflag == 5) { + info(" This is a directory.") } - bstream.readBytes(12); // 512 - 500 - } else { - bstream.readBytes(255); // 512 - 257 } - - // Done header, now rest of blocks are the file contents. - this.filename = this.name; - this.fileData = null; - - info("Untarring file '" + this.filename + "'"); - info(" size = " + this.size); - info(" typeflag = " + this.typeflag); - - // A regular file. - if (this.typeflag == 0) { - info(" This is a regular file."); - const sizeInBytes = parseInt(this.size); - this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.size); - if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) { - this.isValid = true; - } - - bstream.readBytes(this.size); - - // Round up to 512-byte blocks. - const remaining = 512 - bstream.ptr % 512; - if (remaining > 0 && remaining < 512) { - bstream.readBytes(remaining); - } - } else if (this.typeflag == 5) { - info(" This is a directory.") - } -}; +} // Takes an ArrayBuffer of a tar file in // returns null on error diff --git a/archive/unzip.js b/archive/unzip.js index 40da6b3..4d4d231 100644 --- a/archive/unzip.js +++ b/archive/unzip.js @@ -50,8 +50,16 @@ const zDigitalSignatureSignature = 0x05054b50; const zEndOfCentralDirSignature = 0x06064b50; const zEndOfCentralDirLocatorSignature = 0x07064b50; -// takes a ByteStream and parses out the local file information -const ZipLocalFile = function(bstream) { +// mask for getting the Nth bit (zero-based) +const BIT = [ 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80, + 0x100, 0x200, 0x400, 0x800, + 0x1000, 0x2000, 0x4000, 0x8000]; + + +class ZipLocalFile { + // takes a ByteStream and parses out the local file information + constructor(bstream) { if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) { return null; } @@ -103,32 +111,32 @@ const ZipLocalFile = function(bstream) { // "This descriptor exists only if bit 3 of the general purpose bit flag is set" // But how do you figure out how big the file data is if you don't know the compressedSize // from the header?!? - if ((this.generalPurpose & bitjs.BIT[3]) != 0) { + if ((this.generalPurpose & BIT[3]) != 0) { this.crc32 = bstream.readNumber(4); this.compressedSize = bstream.readNumber(4); this.uncompressedSize = bstream.readNumber(4); } -}; + } -// determine what kind of compressed data we have and decompress -ZipLocalFile.prototype.unzip = function() { - // Zip Version 1.0, no compression (store only) - if (this.compressionMethod == 0 ) { - info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); - currentBytesUnarchivedInFile = this.compressedSize; - currentBytesUnarchived += this.compressedSize; + // determine what kind of compressed data we have and decompress + unzip() { + // Zip Version 1.0, no compression (store only) + if (this.compressionMethod == 0 ) { + info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); + currentBytesUnarchivedInFile = this.compressedSize; + currentBytesUnarchived += this.compressedSize; + } + // version == 20, compression method == 8 (DEFLATE) + else if (this.compressionMethod == 8) { + info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); + this.fileData = inflate(this.fileData, this.uncompressedSize); + } + else { + err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); + this.fileData = null; + } } - // version == 20, compression method == 8 (DEFLATE) - else if (this.compressionMethod == 8) { - info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)"); - this.fileData = inflate(this.fileData, this.uncompressedSize); - } - else { - err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)"); - this.fileData = null; - } -}; - +} // Takes an ArrayBuffer of a zip file in // returns null on error @@ -374,21 +382,22 @@ function decodeSymbol(bstream, hcTable) { const CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; - /* - Extra Extra Extra - Code Bits Length(s) Code Bits Lengths Code Bits Length(s) - ---- ---- ------ ---- ---- ------- ---- ---- ------- - 257 0 3 267 1 15,16 277 4 67-82 - 258 0 4 268 1 17,18 278 4 83-98 - 259 0 5 269 2 19-22 279 4 99-114 - 260 0 6 270 2 23-26 280 4 115-130 - 261 0 7 271 2 27-30 281 5 131-162 - 262 0 8 272 2 31-34 282 5 163-194 - 263 0 9 273 3 35-42 283 5 195-226 - 264 0 10 274 3 43-50 284 5 227-257 - 265 1 11,12 275 3 51-58 285 0 258 - 266 1 13,14 276 3 59-66 - */ + +/* + Extra Extra Extra +Code Bits Length(s) Code Bits Lengths Code Bits Length(s) +---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 +*/ const LengthLookupTable = [ [0,3], [0,4], [0,5], [0,6], [0,7], [0,8], [0,9], [0,10], @@ -399,21 +408,22 @@ const LengthLookupTable = [ [5,131], [5,163], [5,195], [5,227], [0,258] ]; - /* - Extra Extra Extra - Code Bits Dist Code Bits Dist Code Bits Distance - ---- ---- ---- ---- ---- ------ ---- ---- -------- - 0 0 1 10 4 33-48 20 9 1025-1536 - 1 0 2 11 4 49-64 21 9 1537-2048 - 2 0 3 12 5 65-96 22 10 2049-3072 - 3 0 4 13 5 97-128 23 10 3073-4096 - 4 1 5,6 14 6 129-192 24 11 4097-6144 - 5 1 7,8 15 6 193-256 25 11 6145-8192 - 6 2 9-12 16 7 257-384 26 12 8193-12288 - 7 2 13-16 17 7 385-512 27 12 12289-16384 - 8 3 17-24 18 8 513-768 28 13 16385-24576 - 9 3 25-32 19 8 769-1024 29 13 24577-32768 - */ + +/* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 +*/ const DistLookupTable = [ [0,1], [0,2], [0,3], [0,4], [1,5], [1,7], diff --git a/io/bitstream.js b/io/bitstream.js index 79b14c0..6c7c8e3 100644 --- a/io/bitstream.js +++ b/io/bitstream.js @@ -12,224 +12,212 @@ var bitjs = bitjs || {}; bitjs.io = bitjs.io || {}; -(function() { - -// mask for getting the Nth bit (zero-based) -bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08, - 0x10, 0x20, 0x40, 0x80, - 0x100, 0x200, 0x400, 0x800, - 0x1000, 0x2000, 0x4000, 0x8000]; - -// mask for getting N number of bits (0-8) -const BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; - /** * This bit stream peeks and consumes bits out of a binary stream. - * - * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. - * @param {boolean} rtl Whether the stream reads bits from the byte starting - * from bit 7 to 0 (true) or bit 0 to 7 (false). - * @param {Number} opt_offset The offset into the ArrayBuffer - * @param {Number} opt_length The length of this BitStream */ -bitjs.io.BitStream = function(ab, rtl, opt_offset, opt_length) { - if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { - throw "Error! BitArray constructed with an invalid ArrayBuffer object"; - } - - const offset = opt_offset || 0; - const length = opt_length || ab.byteLength; - this.bytes = new Uint8Array(ab, offset, length); - this.bytePtr = 0; // tracks which byte we are on - this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) - this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; -}; - - -/** - * byte0 byte1 byte2 byte3 - * 7......0 | 7......0 | 7......0 | 7......0 - * - * The bit pointer starts at bit0 of byte0 and moves left until it reaches - * bit7 of byte0, then jumps to bit0 of byte1, etc. - * @param {number} n The number of bits to peek. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {number} The peeked bits, as an unsigned number. - */ -bitjs.io.BitStream.prototype.peekBits_ltr = function(n, opt_movePointers) { - if (n <= 0 || typeof n != typeof 1) { - return 0; - } - - const movePointers = opt_movePointers || false; - const bytes = this.bytes; - let bytePtr = this.bytePtr; - let bitPtr = this.bitPtr; - let result = 0; - let bitsIn = 0; - - // keep going until we have no more bits left to peek at - // TODO: Consider putting all bits from bytes we will need into a variable and then - // shifting/masking it to just extract the bits we want. - // This could be considerably faster when reading more than 3 or 4 bits at a time. - while (n > 0) { - if (bytePtr >= bytes.length) { - throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + - bytes.length + ", bitPtr=" + bitPtr; - return -1; +bitjs.io.BitStream = class { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") { + throw "Error! BitArray constructed with an invalid ArrayBuffer object"; } - const numBitsLeftInThisByte = (8 - bitPtr); - if (n >= numBitsLeftInThisByte) { - const mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); - - bytePtr++; - bitPtr = 0; - bitsIn += numBitsLeftInThisByte; - n -= numBitsLeftInThisByte; - } - else { - const mask = (BITMASK[n] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); - - bitPtr += n; - bitsIn += n; - n = 0; - } + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.bytePtr = 0; // tracks which byte we are on + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; } - if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; - } - - return result; -}; - - -/** - * byte0 byte1 byte2 byte3 - * 7......0 | 7......0 | 7......0 | 7......0 - * - * The bit pointer starts at bit7 of byte0 and moves right until it reaches - * bit0 of byte0, then goes to bit7 of byte1, etc. - * @param {number} n The number of bits to peek. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {number} The peeked bits, as an unsigned number. - */ -bitjs.io.BitStream.prototype.peekBits_rtl = function(n, opt_movePointers) { - if (n <= 0 || typeof n != typeof 1) { - return 0; - } - - const movePointers = opt_movePointers || false; - const bytes = this.bytes; - let bytePtr = this.bytePtr; - let bitPtr = this.bitPtr; - let result = 0; - - // keep going until we have no more bits left to peek at - // TODO: Consider putting all bits from bytes we will need into a variable and then - // shifting/masking it to just extract the bits we want. - // This could be considerably faster when reading more than 3 or 4 bits at a time. - while (n > 0) { - - if (bytePtr >= bytes.length) { - throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + - bytes.length + ", bitPtr=" + bitPtr; - return -1; + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; } - const numBitsLeftInThisByte = (8 - bitPtr); - if (n >= numBitsLeftInThisByte) { - result <<= numBitsLeftInThisByte; - result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); - bytePtr++; - bitPtr = 0; - n -= numBitsLeftInThisByte; + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + const mask = (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + n -= numBitsLeftInThisByte; + } + else { + const mask = (bitjs.io.BitStream.BITMASK[n] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += n; + bitsIn += n; + n = 0; + } } - else { - result <<= n; - result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); - bitPtr += n; - n = 0; + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; } + + return result; } - if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } + + const movePointers = opt_movePointers || false; + const bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + // TODO: Consider putting all bits from bytes we will need into a variable and then + // shifting/masking it to just extract the bits we want. + // This could be considerably faster when reading more than 3 or 4 bits at a time. + while (n > 0) { + + if (bytePtr >= bytes.length) { + throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" + + bytes.length + ", bitPtr=" + bitPtr; + return -1; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (n >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (bitjs.io.BitStream.BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + n -= numBitsLeftInThisByte; + } + else { + result <<= n; + result |= ((bytes[bytePtr] & (bitjs.io.BitStream.BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr)); + + bitPtr += n; + n = 0; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + } + + return result; } - return result; -}; - - -/** - * Peek at 16 bits from current position in the buffer. - * Bit at (bytePtr,bitPtr) has the highest position in returning data. - * Taken from getbits.hpp in unrar. - * TODO: Move this out of BitStream and into unrar. - */ -bitjs.io.BitStream.prototype.getBits = function() { - return (((((this.bytes[this.bytePtr] & 0xff) << 16) + - ((this.bytes[this.bytePtr+1] & 0xff) << 8) + - ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); -}; - - -/** - * Reads n bits out of the stream, consuming them (moving the bit pointer). - * @param {number} n The number of bits to read. - * @return {number} The read bits, as an unsigned number. - */ -bitjs.io.BitStream.prototype.readBits = function(n) { - return this.peekBits(n, true); -}; - - -/** - * This returns n bytes as a sub-array, advancing the pointer if movePointers - * is true. Only use this for uncompressed blocks as this throws away remaining - * bits in the current byte. - * @param {number} n The number of bytes to peek. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {Uint8Array} The subarray. - */ -bitjs.io.BitStream.prototype.peekBytes = function(n, opt_movePointers) { - if (n <= 0 || typeof n != typeof 1) { - return 0; + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr+1] & 0xff) << 8) + + ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); } - // from http://tools.ietf.org/html/rfc1951#page-11 - // "Any bits of input up to the next byte boundary are ignored." - while (this.bitPtr != 0) { - this.readBits(1); + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); } - const movePointers = opt_movePointers || false; - let bytePtr = this.bytePtr; - let bitPtr = this.bitPtr; + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return 0; + } - const result = this.bytes.subarray(bytePtr, bytePtr + n); + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } - if (movePointers) { - this.bytePtr += n; + const movePointers = opt_movePointers || false; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + + const result = this.bytes.subarray(bytePtr, bytePtr + n); + + if (movePointers) { + this.bytePtr += n; + } + + return result; } - return result; -}; + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } +} +// mask for getting N number of bits (0-8) +bitjs.io.BitStream.BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; -/** - * @param {number} n The number of bytes to read. - * @return {Uint8Array} The subarray. - */ -bitjs.io.BitStream.prototype.readBytes = function(n) { - return this.peekBytes(n, true); -}; - -})(); diff --git a/io/bytebuffer.js b/io/bytebuffer.js index 937d43b..444c9e2 100644 --- a/io/bytebuffer.js +++ b/io/bytebuffer.js @@ -12,111 +12,106 @@ var bitjs = bitjs || {}; bitjs.io = bitjs.io || {}; -(function() { - /** * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. - * @param {number} numBytes The number of bytes to allocate. - * @constructor */ -bitjs.io.ByteBuffer = function(numBytes) { - if (typeof numBytes != typeof 1 || numBytes <= 0) { - throw "Error! ByteBuffer initialized with '" + numBytes + "'"; - } - this.data = new Uint8Array(numBytes); - this.ptr = 0; -}; - - -/** - * @param {number} b The byte to insert. - */ -bitjs.io.ByteBuffer.prototype.insertByte = function(b) { - // TODO: throw if byte is invalid? - this.data[this.ptr++] = b; -}; - - -/** - * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. - */ -bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) { - // TODO: throw if bytes is invalid? - this.data.set(bytes, this.ptr); - this.ptr += bytes.length; -}; - - -/** - * Writes an unsigned number into the next n bytes. If the number is too large - * to fit into n bytes or is negative, an error is thrown. - * @param {number} num The unsigned number to write. - * @param {number} numBytes The number of bytes to write the number into. - */ -bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) { - if (numBytes < 1) { - throw 'Trying to write into too few bytes: ' + numBytes; - } - if (num < 0) { - throw 'Trying to write a negative number (' + num + - ') as an unsigned number to an ArrayBuffer'; - } - if (num > (Math.pow(2, numBytes * 8) - 1)) { - throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; - } - - // Roll 8-bits at a time into an array of bytes. - const bytes = []; - while (numBytes-- > 0) { - const eightBits = num & 255; - bytes.push(eightBits); - num >>= 8; - } - - this.insertBytes(bytes); -}; - - -/** - * Writes a signed number into the next n bytes. If the number is too large - * to fit into n bytes, an error is thrown. - * @param {number} num The signed number to write. - * @param {number} numBytes The number of bytes to write the number into. - */ -bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) { - if (numBytes < 1) { - throw 'Trying to write into too few bytes: ' + numBytes; - } - - const HALF = Math.pow(2, (numBytes * 8) - 1); - if (num >= HALF || num < -HALF) { - throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; - } - - // Roll 8-bits at a time into an array of bytes. - const bytes = []; - while (numBytes-- > 0) { - const eightBits = num & 255; - bytes.push(eightBits); - num >>= 8; - } - - this.insertBytes(bytes); -}; - - -/** - * @param {string} str The ASCII string to write. - */ -bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) { - for (let i = 0; i < str.length; ++i) { - const curByte = str.charCodeAt(i); - if (curByte < 0 || curByte > 255) { - throw 'Trying to write a non-ASCII string!'; +bitjs.io.ByteBuffer = class { + /** + * @param {number} numBytes The number of bytes to allocate. + */ + constructor(numBytes) { + if (typeof numBytes != typeof 1 || numBytes <= 0) { + throw "Error! ByteBuffer initialized with '" + numBytes + "'"; } - this.insertByte(curByte); + this.data = new Uint8Array(numBytes); + this.ptr = 0; } -}; -})(); + + /** + * @param {number} b The byte to insert. + */ + insertByte(b) { + // TODO: throw if byte is invalid? + this.data[this.ptr++] = b; + } + + /** + * @param {Array.|Uint8Array|Int8Array} bytes The bytes to insert. + */ + insertBytes(bytes) { + // TODO: throw if bytes is invalid? + this.data.set(bytes, this.ptr); + this.ptr += bytes.length; + } + + /** + * Writes an unsigned number into the next n bytes. If the number is too large + * to fit into n bytes or is negative, an error is thrown. + * @param {number} num The unsigned number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + if (num < 0) { + throw 'Trying to write a negative number (' + num + + ') as an unsigned number to an ArrayBuffer'; + } + if (num > (Math.pow(2, numBytes * 8) - 1)) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * Writes a signed number into the next n bytes. If the number is too large + * to fit into n bytes, an error is thrown. + * @param {number} num The signed number to write. + * @param {number} numBytes The number of bytes to write the number into. + */ + writeSignedNumber(num, numBytes) { + if (numBytes < 1) { + throw 'Trying to write into too few bytes: ' + numBytes; + } + + const HALF = Math.pow(2, (numBytes * 8) - 1); + if (num >= HALF || num < -HALF) { + throw 'Trying to write ' + num + ' into only ' + numBytes + ' bytes'; + } + + // Roll 8-bits at a time into an array of bytes. + const bytes = []; + while (numBytes-- > 0) { + const eightBits = num & 255; + bytes.push(eightBits); + num >>= 8; + } + + this.insertBytes(bytes); + } + + /** + * @param {string} str The ASCII string to write. + */ + writeASCIIString(str) { + for (let i = 0; i < str.length; ++i) { + const curByte = str.charCodeAt(i); + if (curByte < 0 || curByte > 255) { + throw 'Trying to write a non-ASCII string!'; + } + this.insertByte(curByte); + } + }; +} diff --git a/io/bytestream.js b/io/bytestream.js index 570aed1..86d916e 100644 --- a/io/bytestream.js +++ b/io/bytestream.js @@ -12,153 +12,147 @@ var bitjs = bitjs || {}; bitjs.io = bitjs.io || {}; -(function() { - /** * This object allows you to peek and consume bytes as numbers and strings * out of an ArrayBuffer. In this buffer, everything must be byte-aligned. - * - * @param {ArrayBuffer} ab The ArrayBuffer object. - * @param {number=} opt_offset The offset into the ArrayBuffer - * @param {number=} opt_length The length of this BitStream - * @constructor */ -bitjs.io.ByteStream = function(ab, opt_offset, opt_length) { - const offset = opt_offset || 0; - const length = opt_length || ab.byteLength; - this.bytes = new Uint8Array(ab, offset, length); - this.ptr = 0; -}; - - -/** - * Peeks at the next n bytes as an unsigned number but does not advance the - * pointer - * TODO: This apparently cannot read more than 4 bytes as a number? - * @param {number} n The number of bytes to peek at. - * @return {number} The n bytes interpreted as an unsigned number. - */ -bitjs.io.ByteStream.prototype.peekNumber = function(n) { - // TODO: return error if n would go past the end of the stream? - if (n <= 0 || typeof n != typeof 1) - return -1; - - let result = 0; - // read from last byte to first byte and roll them in - let curByte = this.ptr + n - 1; - while (curByte >= this.ptr) { - result <<= 8; - result |= this.bytes[curByte]; - --curByte; - } - return result; -}; - - -/** - * Returns the next n bytes as an unsigned number (or -1 on error) - * and advances the stream pointer n bytes. - * @param {number} n The number of bytes to read. - * @return {number} The n bytes interpreted as an unsigned number. - */ -bitjs.io.ByteStream.prototype.readNumber = function(n) { - const num = this.peekNumber( n ); - this.ptr += n; - return num; -}; - - -/** - * Returns the next n bytes as a signed number but does not advance the - * pointer. - * @param {number} n The number of bytes to read. - * @return {number} The bytes interpreted as a signed number. - */ -bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) { - let num = this.peekNumber(n); - const HALF = Math.pow(2, (n * 8) - 1); - const FULL = HALF * 2; - - if (num >= HALF) num -= FULL; - - return num; -}; - - -/** - * Returns the next n bytes as a signed number and advances the stream pointer. - * @param {number} n The number of bytes to read. - * @return {number} The bytes interpreted as a signed number. - */ -bitjs.io.ByteStream.prototype.readSignedNumber = function(n) { - const num = this.peekSignedNumber(n); - this.ptr += n; - return num; -}; - - -/** - * This returns n bytes as a sub-array, advancing the pointer if movePointers - * is true. - * @param {number} n The number of bytes to read. - * @param {boolean} movePointers Whether to move the pointers. - * @return {Uint8Array} The subarray. - */ -bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) { - if (n <= 0 || typeof n != typeof 1) { - return null; +bitjs.io.ByteStream = class { + /** + * @param {ArrayBuffer} ab The ArrayBuffer object. + * @param {number=} opt_offset The offset into the ArrayBuffer + * @param {number=} opt_length The length of this BitStream + */ + constructor(ab, opt_offset, opt_length) { + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + this.bytes = new Uint8Array(ab, offset, length); + this.ptr = 0; } - const result = this.bytes.subarray(this.ptr, this.ptr + n); - if (movePointers) { + /** + * Peeks at the next n bytes as an unsigned number but does not advance the + * pointer + * TODO: This apparently cannot read more than 4 bytes as a number? + * @param {number} n The number of bytes to peek at. + * @return {number} The n bytes interpreted as an unsigned number. + */ + peekNumber(n) { + // TODO: return error if n would go past the end of the stream? + if (n <= 0 || typeof n != typeof 1) + return -1; + + let result = 0; + // read from last byte to first byte and roll them in + let curByte = this.ptr + n - 1; + while (curByte >= this.ptr) { + result <<= 8; + result |= this.bytes[curByte]; + --curByte; + } + return result; + } + + + /** + * Returns the next n bytes as an unsigned number (or -1 on error) + * and advances the stream pointer n bytes. + * @param {number} n The number of bytes to read. + * @return {number} The n bytes interpreted as an unsigned number. + */ + readNumber(n) { + const num = this.peekNumber( n ); this.ptr += n; + return num; } - return result; -}; + /** + * Returns the next n bytes as a signed number but does not advance the + * pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + peekSignedNumber(n) { + let num = this.peekNumber(n); + const HALF = Math.pow(2, (n * 8) - 1); + const FULL = HALF * 2; -/** - * Reads the next n bytes as a sub-array. - * @param {number} n The number of bytes to read. - * @return {Uint8Array} The subarray. - */ -bitjs.io.ByteStream.prototype.readBytes = function(n) { - return this.peekBytes(n, true); -}; + if (num >= HALF) num -= FULL; - -/** - * Peeks at the next n bytes as a string but does not advance the pointer. - * @param {number} n The number of bytes to peek at. - * @return {string} The next n bytes as a string. - */ -bitjs.io.ByteStream.prototype.peekString = function(n) { - if (n <= 0 || typeof n != typeof 1) { - return ""; + return num; } - let result = ""; - for (let p = this.ptr, end = this.ptr + n; p < end; ++p) { - result += String.fromCharCode(this.bytes[p]); + + /** + * Returns the next n bytes as a signed number and advances the stream pointer. + * @param {number} n The number of bytes to read. + * @return {number} The bytes interpreted as a signed number. + */ + readSignedNumber(n) { + const num = this.peekSignedNumber(n); + this.ptr += n; + return num; } - return result; -}; -/** - * Returns the next n bytes as an ASCII string and advances the stream pointer - * n bytes. - * @param {number} n The number of bytes to read. - * @return {string} The next n bytes as a string. - */ -bitjs.io.ByteStream.prototype.readString = function(n) { - const strToReturn = this.peekString(n); - this.ptr += n; - return strToReturn; -}; + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. + * @param {number} n The number of bytes to read. + * @param {boolean} movePointers Whether to move the pointers. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, movePointers) { + if (n <= 0 || typeof n != typeof 1) { + return null; + } + const result = this.bytes.subarray(this.ptr, this.ptr + n); -})(); + if (movePointers) { + this.ptr += n; + } + + return result; + } + + /** + * Reads the next n bytes as a sub-array. + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + + /** + * Peeks at the next n bytes as a string but does not advance the pointer. + * @param {number} n The number of bytes to peek at. + * @return {string} The next n bytes as a string. + */ + peekString(n) { + if (n <= 0 || typeof n != typeof 1) { + return ""; + } + + let result = ""; + for (let p = this.ptr, end = this.ptr + n; p < end; ++p) { + result += String.fromCharCode(this.bytes[p]); + } + return result; + } + + /** + * Returns the next n bytes as an ASCII string and advances the stream pointer + * n bytes. + * @param {number} n The number of bytes to read. + * @return {string} The next n bytes as a string. + */ + readString(n) { + const strToReturn = this.peekString(n); + this.ptr += n; + return strToReturn; + } +}