UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

647 lines (646 loc) 25.1 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 { assert } from '@awayjs/graphics'; import { release } from '@awayfl/swf-loader'; import { ByteArray as AwayByteArray } from '@awayjs/core'; import { StringUtilities, isNumeric } from '@awayfl/swf-loader'; import { AXBasePrototype } from './run/initializeAXBasePrototype'; import { forEachPublicProperty } from './run/forEachPublicProperty'; var AMF3ReferenceTables = /** @class */ (function () { function AMF3ReferenceTables() { this.strings = []; this.objects = []; this.traits = []; /** * Trait names are kept in sync with |traits| and are used to optimize fetching public trait names. */ this.traitNames = []; this.dynamic = []; } return AMF3ReferenceTables; }()); var ClassAliases = /** @class */ (function () { function ClassAliases() { this._classMap = new WeakMap(); this._nameMap = Object.create(null); } ClassAliases.prototype.getAliasByClass = function (axClass) { return this._classMap.get(axClass); }; ClassAliases.prototype.getClassByAlias = function (alias) { return this._nameMap[alias]; }; ClassAliases.prototype.registerClassAlias = function (alias, axClass) { this._classMap.set(axClass, alias); release || assert(!this._nameMap[alias] || (this._nameMap[alias] === axClass)); this._nameMap[alias] = axClass; }; return ClassAliases; }()); export { ClassAliases }; function writeString(ba, s) { if (s.length > 0xFFFF) { throw 'AMF short string exceeded'; } if (!s.length) { ba.writeByte(0x00); ba.writeByte(0x00); return; } var bytes = StringUtilities.utf8decode(s); ba.writeByte((bytes.length >> 8) & 255); ba.writeByte(bytes.length & 255); for (var i = 0; i < bytes.length; i++) { ba.writeByte(bytes[i]); } } function readString(ba) { var byteLength = (ba.readByte() << 8) | ba.readByte(); if (!byteLength) { return ''; } var buffer = new Uint8Array(byteLength); for (var i = 0; i < byteLength; i++) { buffer[i] = ba.readByte(); } return StringUtilities.utf8encode(buffer); } function writeDouble(ba, value) { var buffer = new ArrayBuffer(8); var view = new DataView(buffer); view.setFloat64(0, value, false); for (var i = 0; i < buffer.byteLength; i++) { ba.writeByte(view.getUint8(i)); } } function readDouble(ba) { var buffer = new ArrayBuffer(8); var view = new DataView(buffer); for (var i = 0; i < buffer.byteLength; i++) { view.setUint8(i, ba.readByte()); } return view.getFloat64(0, false); } var AMF0 = /** @class */ (function () { function AMF0() { } AMF0.write = function (ba, value) { switch (typeof value) { case 'boolean': ba.writeByte(1 /* AMF0Marker.BOOLEAN */); ba.writeByte(value ? 0x01 : 0x00); break; case 'number': ba.writeByte(0 /* AMF0Marker.NUMBER */); writeDouble(ba, value); break; case 'undefined': ba.writeByte(6 /* AMF0Marker.UNDEFINED */); break; case 'string': ba.writeByte(2 /* AMF0Marker.STRING */); writeString(ba, value); break; case 'object': { var object = value; release || assert(object === null || AXBasePrototype.isPrototypeOf(object)); if (object === null) { ba.writeByte(5 /* AMF0Marker.NULL */); } else if (ba.sec.AXArray.axIsType(object)) { var array = object.value; ba.writeByte(8 /* AMF0Marker.ECMA_ARRAY */); ba.writeByte((array.length >>> 24) & 255); ba.writeByte((array.length >> 16) & 255); ba.writeByte((array.length >> 8) & 255); ba.writeByte(array.length & 255); // REDUX: What about sparse arrays? forEachPublicProperty(object, function (key, value) { writeString(ba, key); this.write(ba, value); }, this); ba.writeByte(0x00); ba.writeByte(0x00); ba.writeByte(9 /* AMF0Marker.OBJECT_END */); } else { ba.writeByte(3 /* AMF0Marker.OBJECT */); forEachPublicProperty(object, function (key, value) { writeString(ba, key); this.write(ba, value); }, this); ba.writeByte(0x00); ba.writeByte(0x00); ba.writeByte(9 /* AMF0Marker.OBJECT_END */); } return; } } }; AMF0.read = function (ba) { var marker = ba.readByte(); switch (marker) { case 0 /* AMF0Marker.NUMBER */: return readDouble(ba); case 1 /* AMF0Marker.BOOLEAN */: return !!ba.readByte(); case 2 /* AMF0Marker.STRING */: return readString(ba); case 3 /* AMF0Marker.OBJECT */: { var object = ba.sec.createObject(); var key = void 0; while ((key = readString(ba)).length) { object.axSetPublicProperty(key, this.read(ba)); } if (ba.readByte() !== 9 /* AMF0Marker.OBJECT_END */) { throw 'AMF0 End marker is not found'; } return object; } case 5 /* AMF0Marker.NULL */: return null; case 6 /* AMF0Marker.UNDEFINED */: return undefined; case 8 /* AMF0Marker.ECMA_ARRAY */: { var array = ba.sec.createArray([]); array.length = (ba.readByte() << 24) | (ba.readByte() << 16) | (ba.readByte() << 8) | ba.readByte(); var key = void 0; while ((key = readString(ba)).length) { array.axSetPublicProperty(key, this.read(ba)); } if (ba.readByte() !== 9 /* AMF0Marker.OBJECT_END */) { throw 'AMF0 End marker is not found'; } return array; } case 10 /* AMF0Marker.STRICT_ARRAY */: { var array = ba.sec.createArray([]); var length_1 = array.length = (ba.readByte() << 24) | (ba.readByte() << 16) | (ba.readByte() << 8) | ba.readByte(); for (var i = 0; i < length_1; i++) { array.axSetPublicProperty(i, this.read(ba)); } return array; } case 17 /* AMF0Marker.AVMPLUS */: return readAMF3Value(ba, new AMF3ReferenceTables()); default: throw 'AMF0 Unknown marker ' + marker; } }; return AMF0; }()); export { AMF0 }; function readU29(ba) { var b1 = ba.readByte(); if ((b1 & 0x80) === 0) { return (b1 & 0x7F); } var b2 = ba.readByte(); if ((b2 & 0x80) === 0) { return ((b1 & 0x7F) << 7) | (b2 & 0x7F); } var b3 = ba.readByte(); if ((b3 & 0x80) === 0) { return ((b1 & 0x7F) << 14) | ((b2 & 0x7F) << 7) | (b3 & 0x7F); } var b4 = ba.readByte(); var val = ((b1 & 0x7f) << 22) | ((b2 & 0x7f) << 15) | ((b3 & 0x7f) << 8) | (b4 & 0xFF); // handle negative // https://github.com/Ventero/amf-cpp/blob/master/src/types/amfinteger.cpp#L92 // https://github.com/yzh44yzh/scala-amf/blob/master/scala-amf-lib/src/com/yzh44yzh/scalaAmf/AmfInt.scala#L35 if ((val & 0x10000000) !== 0) { val |= 0xe0000000; } return val; } function writeU29(ba, value) { // C++ version // https://github.com/Ventero/amf-cpp/blob/master/src/types/amfinteger.cpp#L13 if (value < -0x10000000 || value >= 0x10000000) { throw 'AMF3 U29 range'; } if ((value & 0xFFFFFF80) === 0) { ba.writeByte(value & 0x7F); } else if ((value & 0xFFFFC000) === 0) { ba.writeByte(0x80 | ((value >> 7) & 0x7F)); ba.writeByte(value & 0x7F); } else if ((value & 0xFFE00000) === 0) { ba.writeByte(0x80 | ((value >> 14) & 0x7F)); ba.writeByte(0x80 | ((value >> 7) & 0x7F)); ba.writeByte(value & 0x7F); } else /*if ((value & 0xC0000000) === 0)*/ { // handle negative ba.writeByte(0x80 | ((value >> 22) & 0x7F)); ba.writeByte(0x80 | ((value >> 15) & 0x7F)); ba.writeByte(0x80 | ((value >> 8) & 0x7F)); ba.writeByte(value & 0xFF); } } function readUTF8(ba, references) { var u29s = readU29(ba); if (u29s === 0x01) { return ''; } var strings = references.strings; if ((u29s & 1) === 0) { return strings[u29s >> 1]; } var byteLength = u29s >> 1; var buffer = new Uint8Array(byteLength); for (var i = 0; i < byteLength; i++) { buffer[i] = ba.readByte(); } var value = StringUtilities.utf8encode(buffer); strings.push(value); return value; } function writeUTF8(ba, s, references) { if (s === '') { ba.writeByte(0x01); // empty string return; } var strings = references.strings; var index = strings.indexOf(s); if (index >= 0) { writeU29(ba, index << 1); return; } strings.push(s); var bytes = StringUtilities.utf8decode(s); writeU29(ba, 1 | (bytes.length << 1)); // we can write faster if (ba instanceof AwayByteArray) { // view can look on biggest array, we should check this ba.writeArrayBuffer(bytes.byteLength !== bytes.buffer.byteLength ? new Uint8Array(bytes).buffer : bytes.buffer); return; } for (var i = 0; i < bytes.length; i++) { ba.writeByte(bytes[i]); } } function readAMF3Value(ba, references) { var marker = ba.readByte(); switch (marker) { case 1 /* AMF3Marker.NULL */: return null; case 0 /* AMF3Marker.UNDEFINED */: return undefined; case 2 /* AMF3Marker.FALSE */: return false; case 3 /* AMF3Marker.TRUE */: return true; case 4 /* AMF3Marker.INTEGER */: return readU29(ba); case 5 /* AMF3Marker.DOUBLE */: return readDouble(ba); case 6 /* AMF3Marker.STRING */: return readUTF8(ba, references); case 8 /* AMF3Marker.DATE */: { var u29o = readU29(ba); release || assert((u29o & 1) === 1); return ba.sec.AXDate.axConstruct([readDouble(ba)]); } case 11 /* AMF3Marker.XML */: return ba.sec.AXXML.axConstruct([readUTF8(ba, references)]); case 10 /* AMF3Marker.OBJECT */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var axClass = void 0; var traits = void 0; var isDynamic = true; var traitNames = void 0; if ((u29o & 2) === 0) { traits = references.traits[u29o >> 2]; traitNames = references.traitNames[u29o >> 2]; isDynamic = references.dynamic[u29o >> 2]; } else { var alias = readUTF8(ba, references); if (alias) { traits = axClass = ba.sec.classAliases.getClassByAlias(alias); } isDynamic = (u29o & 8) !== 0; traitNames = []; for (var i = 0, j = u29o >> 4; i < j; i++) { traitNames.push(readUTF8(ba, references)); } references.traits.push(traits); references.traitNames.push(traitNames); references.dynamic.push(isDynamic); } var object = axClass ? axClass.axConstruct([]) : ba.sec.createObject(); references.objects.push(object); // Read trait properties. for (var i = 0; i < traitNames.length; i++) { var value = readAMF3Value(ba, references); object.axSetPublicProperty(traitNames[i], value); } // Read dynamic properties. if (isDynamic) { var key = void 0; while ((key = readUTF8(ba, references)) !== '') { var value = readAMF3Value(ba, references); object.axSetPublicProperty(key, value); } } return object; } case 9 /* AMF3Marker.ARRAY */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var array = ba.sec.createArray([]); references.objects.push(array); var densePortionLength = u29o >> 1; var key = void 0; while ((key = readUTF8(ba, references)).length) { var value = readAMF3Value(ba, references); array.axSetPublicProperty(key, value); } for (var i = 0; i < densePortionLength; i++) { var value = readAMF3Value(ba, references); array.axSetPublicProperty(i, value); } return array; } case 13 /* AMF3Marker.VECTOR_INT */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var length_2 = u29o >> 1; var fixed = ba.readUnsignedInt(); var vector = ba.sec.Int32Vector.axClass.axConstruct([length_2, fixed]); references.objects.push(vector); for (var i = 0; i < length_2; i++) { vector.axSetPublicProperty(i, readU29(ba)); } return vector; } case 14 /* AMF3Marker.VECTOR_UINT */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var length_3 = u29o >> 1; var fixed = ba.readUnsignedInt(); var vector = ba.sec.Uint32Vector.axClass.axConstruct([length_3, fixed]); references.objects.push(vector); for (var i = 0; i < length_3; i++) { vector.axSetPublicProperty(i, readU29(ba)); } return vector; } case 15 /* AMF3Marker.VECTOR_DOUBLE */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var length_4 = u29o >> 1; var fixed = ba.readUnsignedInt(); var vector = ba.sec.Float64Vector.axClass.axConstruct([length_4, fixed]); references.objects.push(vector); for (var i = 0; i < length_4; i++) { vector.axSetPublicProperty(i, readDouble(ba)); } return vector; } case 16 /* AMF3Marker.VECTOR_OBJECT */: { var u29o = readU29(ba); if ((u29o & 1) === 0) { return references.objects[u29o >> 1]; } var length_5 = u29o >> 1; var fixed = ba.readUnsignedInt(); var type = ba.sec.classAliases.getClassByAlias(readUTF8(ba, references)); var vector = ba.sec.getVectorClass(type).axConstruct([length_5, fixed]); references.objects.push(vector); for (var i = 0; i < length_5; i++) { vector.axSetPublicProperty(i, readAMF3Value(ba, references)); } return vector; } default: throw 'AMF3 Unknown marker ' + marker; } } /** * Tries to write a reference to a previously written object. */ function tryWriteAndStartTrackingReference(ba, object, references) { var objects = references.objects; var index = objects.indexOf(object); if (index < 0) { objects.push(object); return false; } writeU29(ba, index << 1); return true; } var MAX_INT = 268435456 - 1; // 2^28 - 1 var MIN_INT = -268435456; // -2^28 function writeAMF3Value(ba, value, references) { switch (typeof value) { case 'boolean': ba.writeByte(value ? 3 /* AMF3Marker.TRUE */ : 2 /* AMF3Marker.FALSE */); break; case 'number': { var useInteger = value === (value | 0); if (useInteger) { if (value > MAX_INT || value < MIN_INT) { useInteger = false; } } if (useInteger) { ba.writeByte(4 /* AMF3Marker.INTEGER */); writeU29(ba, value); } else { ba.writeByte(5 /* AMF3Marker.DOUBLE */); writeDouble(ba, value); } break; } case 'undefined': ba.writeByte(0 /* AMF3Marker.UNDEFINED */); break; case 'string': ba.writeByte(6 /* AMF3Marker.STRING */); writeUTF8(ba, value, references); break; case 'object': if (value === null) { ba.writeByte(1 /* AMF3Marker.NULL */); } else if (ba.sec.AXArray.axIsType(value)) { var array = value; ba.writeByte(9 /* AMF3Marker.ARRAY */); if (tryWriteAndStartTrackingReference(ba, array, references)) { break; } var densePortionLength_1 = 0; while (array.axHasPublicProperty(densePortionLength_1)) { ++densePortionLength_1; } writeU29(ba, (densePortionLength_1 << 1) | 1); forEachPublicProperty(array, function (i, value) { if (isNumeric(i) && i >= 0 && i < densePortionLength_1) { return; } writeUTF8(ba, i, references); writeAMF3Value(ba, value, references); }); writeUTF8(ba, '', references); for (var j = 0; j < densePortionLength_1; j++) { writeAMF3Value(ba, array.axGetPublicProperty(j), references); } } else if (ba.sec.AXDate.axIsType(value)) { ba.writeByte(8 /* AMF3Marker.DATE */); if (tryWriteAndStartTrackingReference(ba, value, references)) break; writeU29(ba, 1); writeDouble(ba, value.valueOf()); } else if (ba.sec.AXXML.axIsType(value)) { ba.writeByte(11 /* AMF3Marker.XML */); writeUTF8(ba, value.toString(), references); } else if (ba.sec.Int32Vector.axIsType(value)) { var vector = value; ba.writeByte(13 /* AMF3Marker.VECTOR_INT */); if (tryWriteAndStartTrackingReference(ba, vector, references)) { break; } writeU29(ba, (vector.length << 1) | 1); ba.writeUnsignedInt(+vector.fixed); for (var i = 0; i < vector.length; i++) { writeU29(ba, vector.axGetPublicProperty(i)); } } else if (ba.sec.Uint32Vector.axIsType(value)) { var vector = value; ba.writeByte(14 /* AMF3Marker.VECTOR_UINT */); if (tryWriteAndStartTrackingReference(ba, vector, references)) { break; } writeU29(ba, (vector.length << 1) | 1); ba.writeUnsignedInt(+vector.fixed); for (var i = 0; i < vector.length; i++) { writeU29(ba, vector.axGetPublicProperty(i)); } } else if (ba.sec.Float64Vector.axIsType(value)) { var vector = value; ba.writeByte(15 /* AMF3Marker.VECTOR_DOUBLE */); if (tryWriteAndStartTrackingReference(ba, vector, references)) { break; } writeU29(ba, (vector.length << 1) | 1); ba.writeUnsignedInt(+vector.fixed); for (var i = 0; i < vector.length; i++) { writeDouble(ba, vector.axGetPublicProperty(i)); } } else if (ba.sec.ObjectVector.axIsType(value)) { var vector = value; ba.writeByte(16 /* AMF3Marker.VECTOR_OBJECT */); if (tryWriteAndStartTrackingReference(ba, vector, references)) { break; } writeU29(ba, (vector.length << 1) | 1); ba.writeUnsignedInt(+vector.fixed); writeUTF8(ba, ba.sec.classAliases.getAliasByClass(value.axClass.type) || '*', references); for (var i = 0; i < vector.length; i++) { writeAMF3Value(ba, vector.axGetPublicProperty(i), references); } } else { var object = value; // TODO Dictionary, ByteArray ba.writeByte(10 /* AMF3Marker.OBJECT */); if (tryWriteAndStartTrackingReference(ba, object, references)) { break; } var isDynamic = true; var axClass = object.axClass; if (axClass) { var classInfo = axClass.classInfo; isDynamic = !classInfo.instanceInfo.isSealed(); var alias = ba.sec.classAliases.getAliasByClass(axClass) || ''; var traitsRef = references.traits.indexOf(axClass); var traitNames = null; if (traitsRef < 0) { // Write traits since we haven't done so yet. traitNames = classInfo.instanceInfo.runtimeTraits.getPublicTraitNames(); references.traits.push(axClass); references.traitNames.push(traitNames); writeU29(ba, (isDynamic ? 0x0B : 0x03) + (traitNames.length << 4)); writeUTF8(ba, alias, references); // Write trait names. for (var i = 0; i < traitNames.length; i++) { writeUTF8(ba, traitNames[i], references); } } else { // Write a reference to the previously written traits. traitNames = references.traitNames[traitsRef]; writeU29(ba, 0x01 + (traitsRef << 2)); } // Write the actual trait values. for (var i = 0; i < traitNames.length; i++) { writeAMF3Value(ba, object.axGetPublicProperty(traitNames[i]), references); } } else { // REDUX: I don't understand in what situations we wouldn't have a class definition, ask Yury. // object with no class definition writeU29(ba, 0x0B); writeUTF8(ba, '', references); // empty alias name } // Write dynamic properties. if (isDynamic) { forEachPublicProperty(object, function (i, value) { writeUTF8(ba, i, references); writeAMF3Value(ba, value, references); }); writeUTF8(ba, '', references); } } return; } } var AMF3 = /** @class */ (function () { function AMF3() { } AMF3.write = function (ba, object) { writeAMF3Value(ba, object, new AMF3ReferenceTables()); }; AMF3.read = function (ba) { return readAMF3Value(ba, new AMF3ReferenceTables()); }; return AMF3; }()); export { AMF3 };