UNPKG

@awayfl/avm1

Version:

Virtual machine for executing AS1 and AS2 code

331 lines (289 loc) 10.2 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. */ //module Shumway.AVM1 { //import flash = flash; import { AVM1Movie } from './AVM1Movie'; import { AnalyzerResults } from './analyze'; import { alCoerceString, alToString, IAVM1Builtins, IAVM1Context } from './runtime'; import { AVM1Globals } from './lib/AVM1Globals'; import { installBuiltins } from './natives'; import { Debug, release, assert, AVMStage } from '@awayfl/swf-loader'; import { AVM1Key } from './lib/AVM1Key'; import { AVM1Mouse } from './lib/AVM1Mouse'; import { AVM1Stage } from './lib/AVM1Stage'; import { AVM1MovieClip } from './lib/AVM1MovieClip'; import { getAVM1Object } from './lib/AVM1Utils'; import { AVM1Object } from './runtime/AVM1Object'; import { AVM1Function } from './runtime/AVM1Function'; import { AssetLibrary } from '@awayjs/core'; import { SecurityDomain } from './SecurityDomain'; import { AVM1Handler } from './AVM1Handler'; //import {WeakMap} from "es6-weak-map"; //import {Map} from "es6-map"; interface IEncryptedActionData { data: Uint8Array, size: ui32, bytePos: ui32, rawTagId: ui8 } export class AVM1ActionsData { public ir: AnalyzerResults; public compiled: Function; public debugPath: string = this.id; constructor( public bytes: Uint8Array, public id: string, public parent: AVM1ActionsData = null, public encryptedData?: IEncryptedActionData) { this.ir = null; this.compiled = null; } } export interface AVM1ExportedSymbol { symbolId: number; symbolProps; } export interface IAVM1RuntimeUtils { hasProperty(obj, name): boolean; getProperty(obj, name): any; setProperty(obj, name, value): void; warn(msg: string): void; } export interface IAVM1EventPropertyObserver { onEventPropertyModified(name: string); } interface IActonBlock { actionsData: Uint8Array; encryptedData: any; } export class ActionsDataFactory { private _cache: WeakMap<Uint8Array, AVM1ActionsData> = new WeakMap<Uint8Array, AVM1ActionsData>(); public createActionsData(actionData: Uint8Array | IActonBlock, id: string, parent: AVM1ActionsData = null): AVM1ActionsData { const isArray = (actionData instanceof Uint8Array); const bytes: Uint8Array = isArray ? <Uint8Array>actionData : (<IActonBlock>actionData).actionsData; const encryptedData = isArray ? undefined : (<IActonBlock>actionData).encryptedData; let actionsData = this._cache.get(bytes); if (!actionsData) { actionsData = new AVM1ActionsData(bytes, id, parent, encryptedData); this._cache.set(bytes, actionsData); } release || assert(actionsData.bytes === actionData && actionsData.id === id && actionsData.parent === parent); return actionsData; } } export class AVM1Context implements IAVM1Context { public sec: SecurityDomain; public globals: AVM1Globals; public builtins: IAVM1Builtins; public isPropertyCaseSensitive: boolean; public actionsDataFactory: ActionsDataFactory; public swfVersion: number; public levelsContainer: AVM1Movie; private eventObservers: Record<string, IAVM1EventPropertyObserver[]> = {}; private assets: Record<string, number> = {}; private awayAssets: any = {}; private assetsSymbols: Array<any> = []; private assetsClasses: Array<any> = []; private staticStates: WeakMap<typeof AVM1Object, any> = new WeakMap<typeof AVM1Object, any>(); constructor(swfVersion: number) { this.swfVersion = swfVersion; this.globals = null; this.actionsDataFactory = new ActionsDataFactory(); if (swfVersion > 6) { this.isPropertyCaseSensitive = true; this.normalizeName = this.normalizeNameCaseSensitive; } else { this.isPropertyCaseSensitive = false; this._nameCache = Object.create(null); this.normalizeName = this.normalizeNameCaseInsensitive; } this.builtins = <any>{}; installBuiltins(this); } public utils: IAVM1RuntimeUtils; public static create: (swfVersion: number) => AVM1Context; public resolveTarget(target): any { } public resolveRoot(): any { } public checkTimeout() { } public executeActions(actionsData: AVM1ActionsData, scopeObj): void { } public executeFunction(fn: AVM1Function, thisArg, args: any): any { } /** * Normalize the name according to the current AVM1Context's settings. * * This entails coercing it to number or string. For SWF versions < 7, it also means converting * it to lower-case. * To avoid runtime checks, the implementation is set during context initialization based on * the SWF version. */ public normalizeName: (name) => string; private normalizeNameCaseSensitive(name: any): string { switch (typeof name) { case 'number': case 'string': return <string>name; default: return alToString(this, name); } } private _nameCache: Map<string, string>; private normalizeNameCaseInsensitive(name: any): string { switch (typeof name) { case 'number': return name.toString(); case 'string': break; default: name = alToString(this, name); } let normalizedName = this._nameCache[name]; if (normalizedName) { return normalizedName; } normalizedName = name.toLowerCase(); this._nameCache[name] = normalizedName; return normalizedName; } private _getEventPropertyObservers(propertyName: string, create: boolean): IAVM1EventPropertyObserver[] { if (!this.isPropertyCaseSensitive) { propertyName = propertyName.toLowerCase(); } let observers = this.eventObservers[propertyName]; if (observers) { return observers; } if (create) { observers = []; this.eventObservers[propertyName] = observers; return observers; } return null; } public registerEventPropertyObserver(propertyName: string, observer: IAVM1EventPropertyObserver): void { const observers = this._getEventPropertyObservers(propertyName, true); observers.push(observer); } public unregisterEventPropertyObserver(propertyName: string, observer: IAVM1EventPropertyObserver): void { const observers = this._getEventPropertyObservers(propertyName, false); if (!observers) { return; } const j = observers.indexOf(observer); if (j < 0) { return; } observers.splice(j, 1); } public broadcastEventPropertyChange(propertyName: string): void { const observers = this._getEventPropertyObservers(propertyName, false); if (!observers) { return; } observers.forEach((observer: IAVM1EventPropertyObserver) => observer.onEventPropertyModified(propertyName)); } public addAsset(className: string, symbolId: number, symbolProps: any): void { //console.log("addAsset", className, symbolId, symbolProps); release || Debug.assert(typeof className === 'string' && !isNaN(symbolId)); this.assets[className.toLowerCase()] = symbolId; this.assetsSymbols[symbolId] = symbolProps; //80pro: directly store assets in dictionary this.awayAssets[className.toLowerCase()] = symbolProps; } public registerClass(className: string, theClass: AVM1Object): void { className = alCoerceString(this, className); if (className === null) { this.utils.warn('Cannot register class for symbol: className is missing'); return; } const myAsset: any = AssetLibrary.getAsset(className, AVM1MovieClip.currentMCAssetNameSpace); if (!myAsset || !myAsset.adaptee) { console.warn('can not find symbol to register class ' + className); return; } //console.log("register", myAsset.adaptee.name, myAsset.adaptee.id); (<any>myAsset.adaptee).avm1Symbol = theClass; /* var symbolId = this.assets[className.toLowerCase()]; if (symbolId === undefined) { this.utils.warn('Cannot register ' + className + ' class for symbol'); return; } this.assetsClasses[symbolId] = theClass;*/ } public getSymbolClass(symbolId: number): AVM1Object { return this.assetsClasses[symbolId] || null; } public getAsset(className: string): AVM1ExportedSymbol { className = alCoerceString(this, className); if (className === null) { return undefined; } const symbolId = this.assets[className.toLowerCase()]; if (symbolId === undefined) { return undefined; } const symbol = this.awayAssets[className.toLowerCase()]; if (!symbol) { console.log('error in getAsset. not implemented to grab assets from loaderInfo'); /*symbol = this.loaderInfo.getSymbolById(symbolId); if (!symbol) { Debug.warning("Symbol " + symbolId + " is not defined."); return undefined; } this.assetsSymbols[symbolId] = symbol;*/ } return { symbolId: symbolId, symbolProps: symbol }; } public reset(): void { this.eventObservers = Object.create(null); this.assets = {}; this.assetsSymbols = []; this.assetsClasses = []; this.awayAssets = {}; this.staticStates = new WeakMap<typeof AVM1Object, any>(); AVM1Stage.bindStage(this, this.globals.Stage, null, null, null); } private htmlElement: any; public setStage(avmStage: AVMStage, avm1Handler: AVM1Handler, htmlElement: any): void { AVM1Key.bindStage(this, this.globals.Key, avmStage, htmlElement); AVM1Mouse.bindStage(this, this.globals.Mouse, avmStage, htmlElement); AVM1Stage.bindStage(this, this.globals.Stage, avmStage, avm1Handler, htmlElement); } public getStaticState(cls): any { let state = this.staticStates.get(cls); if (!state) { state = Object.create(null); const initStatic: Function = (<any>cls).alInitStatic; if (initStatic) { initStatic.call(state, this); } this.staticStates.set(cls, state); } return state; } public resolveLevel(level: number): AVM1MovieClip { release || Debug.assert(typeof level === 'number'); const as3Root = this.globals._getRootForLevel(level); if (!as3Root) { this.utils.warn('Unable to resolve level ' + level + ' root'); return undefined; } return <AVM1MovieClip>getAVM1Object(as3Root, this); } }