UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

393 lines (334 loc) 10.2 kB
import { ABCFile } from './ABCFile'; import { CONSTANT, getCONSTANTName } from './CONSTANT'; import { Namespace } from './Namespace'; import { NamespaceType } from './NamespaceType'; import { internNamespace } from './internNamespace'; import { release } from '@awayfl/swf-loader'; import { assert } from '@awayjs/graphics'; import { internPrefixedNamespace } from './internPrefixedNamespace'; import { axCoerceString } from '../../run/axCoerceString'; import { isNumeric } from '@awayfl/swf-loader'; import { AXObject } from '../../run/AXObject'; import { Settings } from '../../Settings'; import { IGlobalInfo } from './IGlobalInfo'; export class Multiname { private static _isWeak = self.WeakRef && Settings.USE_WEAK_REF; private static _nextID = 1; public id: number = Multiname._nextID++; private _mangledName: string = null; public globalInfo: IGlobalInfo = null; public numeric: boolean = false; public numericValue: any = 0; public resolved: object = {}; private _scope: AXObject | WeakRef<AXObject> = null; private _value: AXObject | WeakRef<AXObject> = null; private _key: string = null; constructor( public abc: ABCFile, public index: number, public kind: CONSTANT, public namespaces: Namespace [], public name: any, public parameterType: Multiname = null, public mutable: boolean = false ) { // ... } public set(name: string | number | any, namespace?: Namespace) { // try to cast name to numeric, required for objects with numeric key's // {0: 1} const isIndexator = ( typeof name === 'number' || ( typeof name === 'string' // name maybe as object for Map && !name.includes('.') // check case when name is `1.0` && Number.isInteger(+name) // check that real integer, same as isFinite ) ); if (typeof name === 'object') { //debugger; } this.namespaces = namespace ? [namespace] : []; this.name = name; this.numeric = false; if (isIndexator) { this.numericValue = +name; this.numeric = true; } } /** * Drop field for RT name * @see https://github.com/awayfl/avm2/issues/4 */ public drop() { if (!this.mutable || !this.isRuntimeName) { // throw "DROP allowed only foe runtime name"; return; } this.numeric = false; this.name = undefined; this.namespaces = []; this.numericValue = NaN; this.resolved = {}; this._scope = null; } public get scope(): AXObject { return (!this._scope || !Multiname._isWeak) ? <AXObject> this._scope : (<WeakRef<AXObject>> this._scope).deref(); } public set scope(v: AXObject) { if (Multiname._isWeak && v) { this._scope = new self.WeakRef<AXObject>(v); return; } this._scope = v; } public get value(): AXObject { return (!this._value || !Multiname._isWeak) ? <AXObject> this._value : (<WeakRef<AXObject>> this._value).deref(); } public set value(v: AXObject) { if (Multiname._isWeak && v) { this._value = new self.WeakRef<AXObject>(v); return; } this._value = v; } public key(): string { if (this._key) return this._key; const r = this.toString(); if (!this.mutable) this._key = r; return r; } public static FromFQNString(fqn: string, nsType: NamespaceType) { const lastDot = fqn.lastIndexOf('.'); const uri = lastDot === -1 ? '' : fqn.substr(0, lastDot); const name = lastDot === -1 ? fqn : fqn.substr(lastDot + 1); const ns = internNamespace(nsType, uri); return new Multiname(null, 0, CONSTANT.RTQName, [ns], name); } private _nameToString(): string { if (this.isAnyName()) { return '*'; } return this.isRuntimeName() ? '[' + this.name + ']' : this.name; } public isRuntime(): boolean { switch (this.kind) { case CONSTANT.QName: case CONSTANT.QNameA: case CONSTANT.Multiname: case CONSTANT.MultinameA: return false; } return true; } public isRuntimeName(): boolean { switch (this.kind) { case CONSTANT.RTQNameL: case CONSTANT.RTQNameLA: case CONSTANT.MultinameL: case CONSTANT.MultinameLA: return true; } return false; } public isRuntimeNamespace(): boolean { /* switch (this.kind) { case CONSTANT.RTQName: case CONSTANT.RTQNameA: case CONSTANT.RTQNameL: case CONSTANT.RTQNameLA: return true; }*/ return this.kind >= CONSTANT.RTQName && this.kind <= CONSTANT.RTQNameLA; } public isAnyName(): boolean { return this.name === null; } public isAnyNamespace(): boolean { if (this.isRuntimeNamespace() || this.namespaces.length > 1) { return false; } return this.namespaces.length === 0 || this.namespaces[0].uri === ''; // x.* has the same meaning as x.*::*, so look for the former case and give // it the same meaning of the latter. // return !this.isRuntimeNamespace() && // (this.namespaces.length === 0 || (this.isAnyName() && this.namespaces.length !== 1)); } public isQName(): boolean { const kind = this.kind; const result = kind === CONSTANT.TypeName || kind === CONSTANT.QName || kind === CONSTANT.QNameA || kind >= CONSTANT.RTQName && kind <= CONSTANT.RTQNameLA; release || assert(!(result && this.namespaces.length !== 1)); return result; } public get namespace(): Namespace { release || assert(this.isQName()); return this.namespaces[0]; } public get uri(): string { release || assert(this.isQName()); return this.namespaces[0].uri; } public get prefix(): string { release || assert(this.isQName()); return this.namespaces[0].prefix; } public set prefix(prefix: string) { release || assert(this.isQName()); const ns = this.namespaces[0]; if (ns.prefix === prefix) { return; } this.namespaces[0] = internPrefixedNamespace(ns.type, ns.uri, prefix); } public equalsQName(mn: Multiname): boolean { release || assert(this.isQName()); return this.name === mn.name && this.namespaces[0].uri === mn.namespaces[0].uri; } public matches(mn: Multiname): boolean { release || assert(this.isQName()); const anyName = mn.isAnyName(); if (anyName && !mn.isQName()) { return true; } if (!anyName && this.name !== mn.name) { return false; } const uri = this.namespaces[0].uri; // @todo: not sure about this. // seems like its needed to match for xml nodes that have uri=="" if (uri == '' || uri == 'default') return true; for (let i = mn.namespaces.length; i--;) { // @todo: not sure about this. needed for xml if (mn.namespaces[i].uri == '' || mn.namespaces[i].uri === uri) { return true; } } return false; } public isAttribute(): boolean { switch (this.kind) { case CONSTANT.QNameA: case CONSTANT.RTQNameA: case CONSTANT.RTQNameLA: case CONSTANT.MultinameA: // Why? I not found a reason for L, in any cases is only A is figured //case CONSTANT.MultinameL: // eslint-disable-next-line no-fallthrough case CONSTANT.MultinameLA: return true; } return false; } public getMangledName(): string { release || assert(this.isQName()); return this._mangledName || this._mangleName(); } private _mangleName() { release || assert(!this._mangledName); const mangledName = '$Bg' + axCoerceString(this.name); if (!this.isRuntime()) { this._mangledName = mangledName; } return mangledName; } public getPublicMangledName(): any { if (isNumeric(this.name)) { return this.name; } return '$Bg' + axCoerceString(this.name); } public static isPublicQualifiedName(value: any): boolean { return value.indexOf('$Bg') === 0; } public static getPublicMangledName(name: string): any { if (isNumeric(name)) { return name; } return '$Bg' + name; } public toFQNString(useColons: boolean) { release || assert(this.isQName()); let prefix = this.namespaces[0].uri; if (prefix.length) { prefix += (useColons ? '::' : '.'); } return prefix + this.name; } public toString() { let str = getCONSTANTName(this.kind) + ' '; str += this.isAttribute() ? '@' : ''; if (this.isRuntimeNamespace()) { const namespaces = this.namespaces ? this.namespaces.map(x => String(x)).join(', ') : null; str += '[' + namespaces + ']::' + this._nameToString(); } else if (this.isQName()) { str += this.namespaces[0] + '::'; str += this._nameToString(); } else if (this.namespaces) { str += '{' + this.namespaces.map(x => String(x)).join(', ') + '}'; str += '::' + this._nameToString(); } else { str += '{' + this.namespaces + '}'; str += '::' + this._nameToString(); } if (this.parameterType) { str += '<' + this.parameterType + '>'; } return str; } public toFlashlogString(): string { const namespaceUri = this.uri; return namespaceUri ? namespaceUri + '::' + this.name : this.name; } /** * Removes the public prefix, or returns undefined if the prefix doesn't exist. */ public static stripPublicMangledName(name: string): any { if (name.indexOf('$Bg') === 0) { return name.substring(3); } return undefined; } public static FromSimpleName(simpleName: string | any): Multiname { let realName = ''; // hack when simple-name is a XMLList returned from "attribute" getter // when working with xml if (typeof simpleName !== 'string' && simpleName._children?.length === 1) { simpleName = (<any>simpleName)._children[0]._value || ''; } else { realName = simpleName; } //case for `com.package.name::className` let nameIndex = realName.lastIndexOf('::'); if (nameIndex > 0) { // trim extra : realName = realName.replace('::', ':'); } else { //case for `com.package.name.className` nameIndex = realName.lastIndexOf('.'); } if (nameIndex <= 0) { // todo Fix multiname resolver for simple name // case for `com.package.name className` // bugged on BBR, game has sounds named as 'bison 8-bit', 'bison victory' //nameIndex = realName.lastIndexOf(' '); } let uri = ''; let name = realName; if (nameIndex > 0 && nameIndex < realName.length - 1) { name = realName.substring(nameIndex + 1).trim(); uri = realName.substring(0, nameIndex).trim(); } const ns = internNamespace(NamespaceType.Public, uri); return new Multiname(null, 0, CONSTANT.RTQName, [ns], name); } }