@awayfl/avm2
Version:
Virtual machine for executing AS3 code
647 lines (646 loc) • 25.1 kB
JavaScript
/*
* 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 };