UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

191 lines (167 loc) 5.36 kB
/* * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { StringUtilities } from '@awayfl/swf-loader'; declare let TextDecoder; let textDecoder: any = null; if (typeof TextDecoder !== 'undefined') { textDecoder = new TextDecoder(); } export class AbcStream { private static _resultBuffer = new Uint32Array(256); private _bytes: Uint8Array; private _view: DataView; private _position: number; constructor (bytes: Uint8Array) { this._bytes = bytes; this._view = new DataView(bytes.buffer, bytes.byteOffset); this._position = 0; } private static _getResultBuffer(length: number) { if (!AbcStream._resultBuffer || AbcStream._resultBuffer.length < length) { AbcStream._resultBuffer = new Uint32Array(length * 2); } return AbcStream._resultBuffer; } get position(): number { return this._position; } remaining(): number { return this._bytes.length - this._position; } seek(position: number) { this._position = position; } advance(length: number) { this._position += length; } readU8(): number { return this._bytes[this._position++]; } readU8s(count: number) { const b = new Uint8Array(count); b.set(this._bytes.subarray(this._position, this._position + count), 0); this._position += count; return b; } viewU8s(count: number) { const view = this._bytes.subarray(this._position, this._position + count); this._position += count; return view; } readS8(): number { return this._bytes[this._position++] << 24 >> 24; } readU32(): number { return this.readS32() >>> 0; } readU30(): number { const result = this.readU32(); if (result & 0xc0000000) { // TODO: Spec says this is a corrupt ABC file, but it seems that some content // has this, e.g. 1000-0.abc // error("Corrupt ABC File"); return result; } return result; } readU30Unsafe(): number { return this.readU32(); } readS16(): number { return (this.readU30Unsafe() << 16) >> 16; } /** * Read a variable-length encoded 32-bit signed integer. The value may use one to five bytes (little endian), * each contributing 7 bits. The most significant bit of each byte indicates that the next byte is part of * the value. The spec indicates that the most significant bit of the last byte to be read is sign extended * but this turns out not to be the case in the real implementation, for instance 0x7f should technically be * -1, but instead it's 127. Moreover, what happens to the remaining 4 high bits of the fifth byte that is * read? Who knows, here we'll just stay true to the Tamarin implementation. */ readS32(): number { let result = this.readU8(); if (result & 0x80) { result = result & 0x7f | this.readU8() << 7; if (result & 0x4000) { result = result & 0x3fff | this.readU8() << 14; if (result & 0x200000) { result = result & 0x1fffff | this.readU8() << 21; if (result & 0x10000000) { result = result & 0x0fffffff | this.readU8() << 28; result = result & 0xffffffff; } } } } return result; } readWord(): number { const result = this._view.getUint32(this._position, true); this._position += 4; return result; } readS24(): number { const u = this.readU8() | (this.readU8() << 8) | (this.readU8() << 16); return (u << 8) >> 8; } readDouble(): number { const result = this._view.getFloat64(this._position, true); this._position += 8; return result; } readUTFString(length): string { /** * Use the TextDecoder API whenever available. * http://encoding.spec.whatwg.org/#concept-encoding-get */ if (textDecoder) { const position = this._position; this._position += length; return textDecoder.decode(this._bytes.subarray(position, position + length)); } let pos = this._position; const end = pos + length; const bytes = this._bytes; let i = 0; const result = AbcStream._getResultBuffer(length * 2); while (pos < end) { const c = bytes[pos++]; if (c <= 0x7f) { result[i++] = c; } else if (c >= 0xc0) { // multibyte let code = 0; if (c < 0xe0) { // 2 bytes code = ((c & 0x1f) << 6) | (bytes[pos++] & 0x3f); } else if (c < 0xf0) { // 3 bytes code = ((c & 0x0f) << 12) | ((bytes[pos++] & 0x3f) << 6) | (bytes[pos++] & 0x3f); } else { // 4 bytes // turned into two characters in JS as surrogate pair code = (((c & 0x07) << 18) | ((bytes[pos++] & 0x3f) << 12) | ((bytes[pos++] & 0x3f) << 6) | (bytes[pos++] & 0x3f)) - 0x10000; // High surrogate result[i++] = ((code & 0xffc00) >>> 10) + 0xd800; // Low surrogate code = (code & 0x3ff) + 0xdc00; } result[i++] = code; } // Otherwise it's an invalid UTF8, skipped. } this._position = pos; return StringUtilities.fromCharCodeArray(result.subarray(0, i)); } }