UNPKG

@awayfl/avm1

Version:

Virtual machine for executing AS1 and AS2 code

1,202 lines 117 kB
import { __extends } from "tslib"; import { avm1CompilerEnabled, avm1ErrorsEnabled, avm1TimeoutDisabled, avm1TraceEnabled, avm1WarningsEnabled, } from './settings'; import { AVM1Context } from './context'; import { ActionsDataAnalyzer } from './analyze'; import { ActionsDataParser, ParsedPushConstantAction, ParsedPushRegisterAction } from './parser'; import { ActionsDataCompiler } from './baseline'; import { alCoerceString, alDefineObjectProperties, alForEachProperty, alIsArray, alIsFunction, alIsName, alNewObject, alToBoolean, alToInt32, alToNumber, alToObject, alToPrimitive, alToString, AVM1EvalFunction, AVM1NativeFunction, bToRuntimeBool } from './runtime'; import { AVM1Globals, AVM1NativeActions } from './lib/AVM1Globals'; import { Telemetry, isNullOrUndefined, Debug, release, assert } from '@awayfl/swf-loader'; import { hasAwayJSAdaptee } from './lib/AVM1Utils'; import { AVM1MovieClip } from './lib/AVM1MovieClip'; import { AVM1ArrayNative } from './natives'; import { AVM1Object } from './runtime/AVM1Object'; import { AVM1Function } from './runtime/AVM1Function'; import { AVM1PropertyDescriptor } from './runtime/AVM1PropertyDescriptor'; import { MovieClipProperties } from './interpreter/MovieClipProperties'; import { TextField, FrameScriptManager } from '@awayjs/scene'; var noVarGetDebug = true; export var Debugger = { pause: false, breakpoints: {} }; function avm1Warn(message, arg1, arg2, arg3, arg4) { if (avm1ErrorsEnabled.value) { try { throw new Error(message); // using throw as a way to break in browsers debugger } catch (e) { /* ignoring since handled */ } } if (avm1WarningsEnabled.value) { /* eslint-disable-next-line */ Debug.warning.apply(console, arguments); } } export var MAX_AVM1_HANG_TIMEOUT = 1000; export var CHECK_AVM1_HANG_EVERY = 1000; var MAX_AVM1_ERRORS_LIMIT = 1000; var MAX_AVM1_STACK_LIMIT = 256; export var AVM1ScopeListItemFlags; (function (AVM1ScopeListItemFlags) { AVM1ScopeListItemFlags[AVM1ScopeListItemFlags["DEFAULT"] = 0] = "DEFAULT"; AVM1ScopeListItemFlags[AVM1ScopeListItemFlags["TARGET"] = 1] = "TARGET"; AVM1ScopeListItemFlags[AVM1ScopeListItemFlags["REPLACE_TARGET"] = 2] = "REPLACE_TARGET"; })(AVM1ScopeListItemFlags || (AVM1ScopeListItemFlags = {})); var AVM1ScopeListItem = /** @class */ (function () { function AVM1ScopeListItem(scope, previousScopeItem) { this.scope = scope; this.previousScopeItem = previousScopeItem; this.flags = AVM1ScopeListItemFlags.DEFAULT; } return AVM1ScopeListItem; }()); export { AVM1ScopeListItem }; // Similar to function scope, mostly for 'this'. var GlobalPropertiesScope = /** @class */ (function (_super) { __extends(GlobalPropertiesScope, _super); function GlobalPropertiesScope(context, thisArg) { var _this = _super.call(this, context) || this; _this.alSetOwnProperty('this', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ | 1 /* AVM1PropertyFlags.DONT_ENUM */ | 2 /* AVM1PropertyFlags.DONT_DELETE */ | 4 /* AVM1PropertyFlags.READ_ONLY */, thisArg)); _this.alSetOwnProperty('_global', new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ | 1 /* AVM1PropertyFlags.DONT_ENUM */ | 2 /* AVM1PropertyFlags.DONT_DELETE */ | 4 /* AVM1PropertyFlags.READ_ONLY */, context.globals)); return _this; } return GlobalPropertiesScope; }(AVM1Object)); export { GlobalPropertiesScope }; var AVM1CallFrame = /** @class */ (function () { function AVM1CallFrame(previousFrame, currentThis, fn, args, ectx) { this.previousFrame = previousFrame; this.currentThis = currentThis; this.fn = fn; this.args = args; this.ectx = ectx; this.inSequence = !previousFrame ? false : (previousFrame.calleeThis === currentThis && previousFrame.calleeFn === fn); this.resetCallee(); } AVM1CallFrame.prototype.setCallee = function (thisArg, superArg, fn, args) { this.calleeThis = thisArg; this.calleeSuper = superArg; this.calleeFn = fn; if (!release) { this.calleeArgs = args; } }; AVM1CallFrame.prototype.resetCallee = function () { this.calleeThis = null; this.calleeSuper = null; this.calleeFn = null; }; return AVM1CallFrame; }()); export { AVM1CallFrame }; var AVM1RuntimeUtilsImpl = /** @class */ (function () { function AVM1RuntimeUtilsImpl(context) { this._context = context; } AVM1RuntimeUtilsImpl.prototype.hasProperty = function (obj, name) { return as2HasProperty(this._context, obj, name); }; AVM1RuntimeUtilsImpl.prototype.getProperty = function (obj, name) { return as2GetProperty(this._context, obj, name); }; AVM1RuntimeUtilsImpl.prototype.setProperty = function (obj, name, value) { return as2SetProperty(this._context, obj, name, value); }; AVM1RuntimeUtilsImpl.prototype.warn = function (msg) { /* eslint-disable-next-line */ avm1Warn.apply(null, arguments); }; return AVM1RuntimeUtilsImpl; }()); var AVM1ContextImpl = /** @class */ (function (_super) { __extends(AVM1ContextImpl, _super); function AVM1ContextImpl(swfVersion) { var _this = _super.call(this, swfVersion) || this; _this.globals = AVM1Globals.createGlobalsObject(_this); _this.actions = new AVM1NativeActions(_this); _this.initialScope = new AVM1ScopeListItem(_this.globals, null); _this.utils = new AVM1RuntimeUtilsImpl(_this); _this.isActive = false; _this.executionProhibited = false; _this.actionTracer = avm1TraceEnabled.value ? new ActionTracer() : null; _this.abortExecutionAt = 0; _this.stackDepth = 0; _this.frame = null; _this.isTryCatchListening = false; _this.errorsIgnored = 0; _this.deferScriptExecution = true; return _this; } AVM1ContextImpl.prototype._getExecutionContext = function () { // We probably entering this function from some native function, // so faking execution context. Let's reuse last created context. return this.frame.ectx; }; AVM1ContextImpl.prototype.resolveTarget = function (target) { var ectx = this._getExecutionContext(); return avm1ResolveTarget(ectx, target, true); }; AVM1ContextImpl.prototype.resolveRoot = function () { var ectx = this._getExecutionContext(); return avm1ResolveRoot(ectx); }; AVM1ContextImpl.prototype.checkTimeout = function () { if (Date.now() >= this.abortExecutionAt) { //80pro - this fires even for short scripts: //throw new AVM1CriticalError('long running script -- AVM1 instruction hang timeout'); } }; AVM1ContextImpl.prototype.pushCallFrame = function (thisArg, fn, args, ectx) { var nextFrame = new AVM1CallFrame(this.frame, thisArg, fn, args, ectx); this.frame = nextFrame; return nextFrame; }; AVM1ContextImpl.prototype.popCallFrame = function () { var previousFrame = this.frame.previousFrame; this.frame = previousFrame; return previousFrame; }; AVM1ContextImpl.prototype.executeActions = function (actionsData, scopeObj) { if (this.executionProhibited) { return; // no more avm1 for this context } var savedIsActive = this.isActive; if (!savedIsActive) { this.isActive = true; this.abortExecutionAt = avm1TimeoutDisabled.value ? Number.MAX_VALUE : Date.now() + MAX_AVM1_HANG_TIMEOUT; this.errorsIgnored = 0; } var caughtError; //console.log("executeActions", scopeObj.aCount); try { executeActionsData(this, actionsData, scopeObj); } catch (e) { caughtError = e; } this.isActive = savedIsActive; if (caughtError) { // Note: this doesn't use `finally` because that's a no-go for performance. console.error('error in framescripts', caughtError); //throw caughtError; } }; AVM1ContextImpl.prototype.executeFunction = function (fn, thisArg, args) { if (this.executionProhibited) { return; // no more avm1 for this context } var savedIsActive = this.isActive; if (!savedIsActive) { this.isActive = true; this.abortExecutionAt = avm1TimeoutDisabled.value ? Number.MAX_VALUE : Date.now() + MAX_AVM1_HANG_TIMEOUT; this.errorsIgnored = 0; } var caughtError; var result; result = fn.alCall(thisArg, args); this.isActive = savedIsActive; return result; }; return AVM1ContextImpl; }(AVM1Context)); export { AVM1ContextImpl }; AVM1Context.create = function (swfVersion) { return new AVM1ContextImpl(swfVersion); }; var AVM1Error = /** @class */ (function () { function AVM1Error(error) { this.error = error; } return AVM1Error; }()); var AVM1CriticalError = /** @class */ (function (_super) { __extends(AVM1CriticalError, _super); function AVM1CriticalError(message, error) { var _this = _super.call(this, message) || this; _this.error = error; return _this; } return AVM1CriticalError; }(Error)); function isAVM1MovieClip(obj) { return typeof obj === 'object' && obj && obj instanceof AVM1MovieClip; } // function stopIfClipRemoved(ectx: ExecutionContext, clip: AVM1Object | AVM1Function) { // if (isAVM1MovieClip(clip) && clip.isGhost) { // ectx.isEndOfActions = true; // } // } function as2GetType(v) { if (v === null) { return 'null'; } var type = typeof v; if (typeof v === 'object') { if (v instanceof AVM1MovieClip) { return 'movieclip'; } if (v instanceof AVM1Function) { return 'function'; } } return type; } /** * Performs "less" comparison of two arugments. * @returns {boolean} Returns true if x is less than y, otherwise false */ function as2Compare(context, x, y) { var x2 = alToPrimitive(context, x); var y2 = alToPrimitive(context, y); if (typeof x2 === 'string' && typeof y2 === 'string') { var xs = alToString(context, x2), ys = alToString(context, y2); return xs < ys; } else { var xn = alToNumber(context, x2), yn = alToNumber(context, y2); return isNaN(xn) || isNaN(yn) ? undefined : xn < yn; } } /** * Performs equality comparison of two arugments. The equality comparison * algorithm from EcmaScript 3, Section 11.9.3 is applied. * @see http://ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf#page=67 * @returns {boolean} Coerces x and y to the same type and returns true if they're equal, false otherwise. */ function as2Equals(context, x, y) { // Spec steps 1 through 13 can be condensed to ... if (typeof x === typeof y) { if (typeof x === 'number') { // Calculate the difference. var ma = Math.abs(x), mb = Math.abs(y); var larges = ma > mb ? ma : mb; var eps = (1e-6) * larges; return Math.abs(x - y) <= eps; } return x === y; } // Spec steps 14 and 15. if (x == null && y == null) { return true; } /* if (typeof x === 'undefined' && typeof y === 'string' && y=="") { // Unfolding the recursion for `as2Equals(context, x, alToNumber(y))` return true; // in AVM1, ToNumber('') === NaN } if (typeof y === 'undefined' && typeof x === 'string' && x=="") { // Unfolding the recursion for `as2Equals(context, x, alToNumber(y))` return true; // in AVM1, ToNumber('') === NaN } */ // Spec steps 16 and 17. if (typeof x === 'number' && typeof y === 'string') { // Unfolding the recursion for `as2Equals(context, x, alToNumber(y))` return y === '' ? false : x === +y; // in AVM1, ToNumber('') === NaN } if (typeof x === 'string' && typeof y === 'number') { // Unfolding the recursion for `as2Equals(context, alToNumber(x), y)` return x === '' ? false : +x === y; // in AVM1, ToNumber('') === NaN } // Spec step 18. if (typeof x === 'boolean') { // Unfolding the recursion for `as2Equals(context, alToNumber(x), y)` x = +x; // typeof x === 'number' if (typeof y === 'number' || typeof y === 'string') { return y === '' ? false : x === +y; } // Fall through for typeof y === 'object', 'boolean', 'undefined' cases } // Spec step 19. if (typeof y === 'boolean') { // Unfolding the recursion for `as2Equals(context, x, alToNumber(y))` y = +y; // typeof y === 'number' if (typeof x === 'number' || typeof x === 'string') { return x === '' ? false : +x === y; } // Fall through for typeof x === 'object', 'undefined' cases } // Spec step 20. if ((typeof x === 'number' || typeof x === 'string') && typeof y === 'object' && y !== null) { y = alToPrimitive(context, y); if (typeof y === 'object') { return false; // avoiding infinite recursion } return as2Equals(context, x, y); } // Spec step 21. if (typeof x === 'object' && x !== null && (typeof y === 'number' || typeof y === 'string')) { x = alToPrimitive(context, x); if (typeof x === 'object') { return false; // avoiding infinite recursion } return as2Equals(context, x, y); } return false; } function as2InstanceOf(obj, constructor) { // TODO refactor this -- quick and dirty hack for now if (isNullOrUndefined(obj) || isNullOrUndefined(constructor)) { return false; } // if (constructor === ASString) { // return typeof obj === 'string'; // } else if (constructor === ASNumber) { // return typeof obj === 'number'; // } else if (constructor === ASBoolean) { // return typeof obj === 'boolean'; // } else if (constructor === ASArray) { // return Array.isArray(obj); // } else if (constructor === ASFunction) { // return typeof obj === 'function'; // } else if (constructor === ASObject) { // return typeof obj === 'object'; // } var baseProto = constructor.alGetPrototypeProperty(); if (!baseProto) { return false; } var proto = obj; while (proto) { if (proto === baseProto) { return true; // found the type if the chain } proto = proto.alPrototype; } // TODO interface check return false; } function as2HasProperty(context, obj, name) { var avm1Obj = alToObject(context, obj); name = context.normalizeName(name); return avm1Obj.alHasProperty(name); } function as2GetProperty(context, obj, name) { var avm1Obj = alToObject(context, obj); if (!avm1Obj) return undefined; var value = avm1Obj.alGet(name); //if(typeof name==="string" && name.toLowerCase()=="ox"){ // console.log("get ox", avm1Obj.adaptee.id, avm1Obj.adaptee.name, value); //} return value; } function as2SetProperty(context, obj, name, value) { var avm1Obj = alToObject(context, obj); if (!avm1Obj) return; //if(typeof name==="string" && name.toLowerCase()=="ox"){ // console.log("set ox", avm1Obj.adaptee.id, avm1Obj.adaptee.name, value); //} if (name == '__proto__') { if (value) { var allKeys = value.alGetKeys(); for (var i = 0; i < allKeys.length; i++) { var key = allKeys[i]; if (key != '') { avm1Obj.alPut(key, value.alGet(key)); as2SyncEvents(context, key, avm1Obj); } } avm1Obj.protoTypeChanged = !(value instanceof AVM1MovieClip); } } else { avm1Obj.alPut(name, value); if (avm1Obj.adaptee) { // todo: this might not be the best way // the goal is to not call as2SyncEvents when avm1Obj is a prototype object // but idk how to identify if avm1Obj is prototype. // for now i just use the adaptee to check, because a prototype should not have adaptee set as2SyncEvents(context, name, avm1Obj); } } } function as2DeleteProperty(context, obj, name) { var avm1Obj = alToObject(context, obj); name = context.normalizeName(name); var result = avm1Obj.alDeleteProperty(name); as2SyncEvents(context, name, avm1Obj); return result; } function as2SyncEvents(context, name, avm1Obj) { if (typeof name === 'undefined') return; name = alCoerceString(context, name); name = context.normalizeName(name); if (name[0] !== 'o' || name[1] !== 'n') { // TODO check case? return; } if (avm1Obj && avm1Obj.updateEventByPropName) avm1Obj.updateEventByPropName(name); // Maybe an event property, trying to broadcast change. //(<AVM1ContextImpl>context).broadcastEventPropertyChange(name); } function as2CastError(ex) { if (typeof InternalError !== 'undefined' && ex instanceof InternalError && ex.message === 'too much recursion') { // HACK converting too much recursion into AVM1CriticalError //console.log('long running script -- AVM1 recursion limit is reached'); return new AVM1CriticalError('long running script -- AVM1 recursion limit is reached'); } return ex; } function as2Construct(ctor, args) { var result; if (alIsFunction(ctor)) { result = ctor.alConstruct(args); } else { // AVM1 simply ignores attempts to invoke non-methods. return undefined; } return result; } function as2Enumerate(obj, fn, thisArg) { // todo: better just whitelist "typeof === object" instead of blacklisting ? if (typeof obj === 'boolean' || typeof obj === 'string' || typeof obj === 'number') { return; } alForEachProperty(obj, function (name) { var _a; if (typeof name == 'string' && name.indexOf('_internal_TF') != -1) return; var avmObj = obj.alGet(name); if (((_a = avmObj === null || avmObj === void 0 ? void 0 : avmObj.adaptee) === null || _a === void 0 ? void 0 : _a.isAsset(TextField)) && avmObj.adaptee.isStatic) return; fn.call(thisArg, name); }, thisArg); /* let i = props.length; let avmObj = null; while (i > 0) { i--; fn.call(thisArg, props[i]); }*/ } function avm1FindSuperPropertyOwner(context, frame, propertyName) { if (context.swfVersion < 6) { return null; } var proto = (frame.inSequence && frame.previousFrame.calleeSuper); if (!proto) { // Finding first object in prototype chain link that has the property. proto = frame.currentThis; while (proto && !proto.alHasOwnProperty(propertyName)) { proto = proto.alPrototype; } if (!proto) { return null; } } // Skipping one chain link proto = proto.alPrototype; return proto; } var DEFAULT_REGISTER_COUNT = 4; function executeActionsData(context, actionsData, scope) { var actionTracer = context.actionTracer; var globalPropertiesScopeList = new AVM1ScopeListItem(new GlobalPropertiesScope(context, scope), context.initialScope); var scopeList = new AVM1ScopeListItem(scope, globalPropertiesScopeList); scopeList.flags |= AVM1ScopeListItemFlags.TARGET; var caughtError; release || (actionTracer && actionTracer.message('ActionScript Execution Starts')); release || (actionTracer && actionTracer.indent()); var ectx = ExecutionContext.create(context, scopeList, [], DEFAULT_REGISTER_COUNT); context.pushCallFrame(scope, null, null, ectx); try { interpretActionsData(ectx, actionsData); } catch (e) { caughtError = as2CastError(e); } ectx.dispose(); if (caughtError instanceof AVM1CriticalError) { context.executionProhibited = true; console.error('Disabling AVM1 execution'); } context.popCallFrame(); release || (actionTracer && actionTracer.unindent()); release || (actionTracer && actionTracer.message('ActionScript Execution Stops')); if (caughtError) { // Note: this doesn't use `finally` because that's a no-go for performance. throw caughtError; // TODO shall we just ignore it? } } function createBuiltinType(context, cls, args) { var builtins = context.builtins; var obj = undefined; if (cls === builtins.Array || cls === builtins.Object || cls === builtins.Date || cls === builtins.String || cls === builtins.Function) { obj = cls.alConstruct(args); } if (cls === builtins.Boolean || cls === builtins.Number) { obj = cls.alConstruct(args).value; } if (obj instanceof AVM1Object) { var desc = new AVM1PropertyDescriptor(64 /* AVM1PropertyFlags.DATA */ | 1 /* AVM1PropertyFlags.DONT_ENUM */, cls); obj.alSetOwnProperty('__constructor__', desc); } return obj; } var AVM1SuperWrapper = /** @class */ (function (_super) { __extends(AVM1SuperWrapper, _super); function AVM1SuperWrapper(context, callFrame) { var _this = _super.call(this, context) || this; _this.callFrame = callFrame; _this.alPrototype = context.builtins.Object.alGetPrototypeProperty(); return _this; } return AVM1SuperWrapper; }(AVM1Object)); var AVM1Arguments = /** @class */ (function (_super) { __extends(AVM1Arguments, _super); function AVM1Arguments(context, args, callee, caller) { var _this = _super.call(this, context, args) || this; alDefineObjectProperties(_this, { callee: { value: callee }, caller: { value: caller } }); return _this; } return AVM1Arguments; }(AVM1ArrayNative)); var ExecutionContext = /** @class */ (function () { function ExecutionContext(context, scopeList, constantPool, registerCount) { this.framescriptmanager = FrameScriptManager; this.context = context; this.actions = context.actions; this.isSwfVersion5 = context.swfVersion >= 5; this.isSwfVersion7 = context.swfVersion >= 7; this.registers = []; this.stack = []; this.frame = null; this.recoveringFromError = false; this.isEndOfActions = false; this.reset(scopeList, constantPool, registerCount); } ExecutionContext.alInitStatic = function () { this.cache = []; }; ExecutionContext.prototype.reset = function (scopeList, constantPool, registerCount) { this.scopeList = scopeList; this.constantPool = constantPool; this.registers.length = registerCount; }; ExecutionContext.prototype.clean = function () { this.scopeList = null; this.constantPool = null; this.registers.length = 0; this.stack.length = 0; this.frame = null; this.recoveringFromError = false; this.isEndOfActions = false; }; ExecutionContext.prototype.pushScope = function (newScopeList) { var newContext = Object.create(this); newContext.stack = []; if (!isNullOrUndefined(newScopeList)) { newContext.scopeList = newScopeList; } return newContext; }; ExecutionContext.prototype.dispose = function () { this.clean(); var state = this.context.getStaticState(ExecutionContext); if (state.cache.length < ExecutionContext.MAX_CACHED_EXECUTIONCONTEXTS) { state.cache.push(this); } }; /* eslint-disable-next-line */ ExecutionContext.create = function (context, scopeList, constantPool, registerCount) { var state = context.getStaticState(ExecutionContext); var ectx; if (state.cache.length > 0) { ectx = state.cache.pop(); ectx.reset(scopeList, constantPool, registerCount); } else { ectx = new ExecutionContext(context, scopeList, constantPool, registerCount); } return ectx; }; ExecutionContext.MAX_CACHED_EXECUTIONCONTEXTS = 20; return ExecutionContext; }()); export { ExecutionContext }; /** * Interpreted function closure. */ var AVM1InterpreterScope = /** @class */ (function (_super) { __extends(AVM1InterpreterScope, _super); function AVM1InterpreterScope(context) { var _this = _super.call(this, context) || this; _this.alPut('toString', new AVM1NativeFunction(context, _this._toString)); return _this; } AVM1InterpreterScope.prototype._toString = function () { // It shall return 'this' return this; }; return AVM1InterpreterScope; }(AVM1Object)); var AVM1InterpretedFunction = /** @class */ (function (_super) { __extends(AVM1InterpretedFunction, _super); function AVM1InterpretedFunction(context, ectx, actionsData, functionName, parametersNames, registersCount, registersAllocation, suppressArguments) { var _this = _super.call(this, context) || this; _this.functionName = functionName; _this.actionsData = actionsData; _this.parametersNames = parametersNames; _this.registersAllocation = registersAllocation; _this.suppressArguments = suppressArguments; _this.scopeList = ectx.scopeList; _this.constantPool = ectx.constantPool; var skipArguments = null; var registersAllocationCount = !registersAllocation ? 0 : registersAllocation.length; for (var i = 0; i < registersAllocationCount; i++) { var registerAllocation = registersAllocation[i]; if (registerAllocation && registerAllocation.type === 1 /* ArgumentAssignmentType.Argument */) { if (!skipArguments) { skipArguments = []; } skipArguments[registersAllocation[i].index] = true; } } _this.skipArguments = skipArguments; var registersLength = Math.min(registersCount, 255); // max allowed for DefineFunction2 registersLength = Math.max(registersLength, registersAllocationCount + 1); _this.registersLength = registersLength; return _this; } AVM1InterpretedFunction.prototype.alCall = function (thisArg, args) { var _a, _b; var currentContext = this.context; if (currentContext.executionProhibited) { return; // no more avm1 execution, ever } var newScope = new AVM1InterpreterScope(currentContext); var newScopeList = new AVM1ScopeListItem(newScope, this.scopeList); var oldScope = this.scopeList.scope; //thisArg = thisArg || oldScope; // REDUX no isGlobalObject check? args = args || []; var ectx = ExecutionContext.create(currentContext, newScopeList, this.constantPool, this.registersLength); var caller = currentContext.frame ? currentContext.frame.fn : undefined; var frame = currentContext.pushCallFrame(thisArg, this, args, ectx); var supperWrapper; var suppressArguments = this.suppressArguments; if (!(suppressArguments & 4 /* ArgumentAssignmentType.Arguments */)) { newScope.alPut('arguments', new AVM1Arguments(currentContext, args, this, caller)); } if (!(suppressArguments & 2 /* ArgumentAssignmentType.This */)) { newScope.alPut('this', thisArg); } if (!(suppressArguments & 8 /* ArgumentAssignmentType.Super */)) { supperWrapper = new AVM1SuperWrapper(currentContext, frame); newScope.alPut('super', supperWrapper); } var i; var registers = ectx.registers; var registersAllocation = this.registersAllocation; var registersAllocationCount = !registersAllocation ? 0 : registersAllocation.length; for (i = 0; i < registersAllocationCount; i++) { var registerAllocation = registersAllocation[i]; if (registerAllocation) { switch (registerAllocation.type) { case 1 /* ArgumentAssignmentType.Argument */: registers[i] = args[registerAllocation.index]; break; case 2 /* ArgumentAssignmentType.This */: registers[i] = thisArg; break; case 4 /* ArgumentAssignmentType.Arguments */: registers[i] = new AVM1Arguments(currentContext, args, this, caller); break; case 8 /* ArgumentAssignmentType.Super */: supperWrapper = supperWrapper || new AVM1SuperWrapper(currentContext, frame); registers[i] = supperWrapper; break; case 16 /* ArgumentAssignmentType.Global */: registers[i] = currentContext.globals; break; case 32 /* ArgumentAssignmentType.Parent */: { var parentObj = null; if (oldScope) { parentObj = oldScope.alGet('_parent'); if (!parentObj) { parentObj = oldScope.alGet('this'); if (parentObj) { parentObj = parentObj.alGet('_parent'); } } } if (!parentObj) { // if the _parent was not set from oldScope, we get it from thisArg parentObj = thisArg; if (parentObj) { parentObj = parentObj.alGet('_parent'); } // if this is a onEnter, and the _parent was not set from oldScope, // we need to go up another parent if possible if (parentObj && this.isOnEnter && parentObj.alGet('_parent')) { parentObj = parentObj.alGet('_parent'); } // for setInterval: if its still not has a parent found // we look back at the previous-scopes until we find a scope that can provide a _parent if (!parentObj) { if ((_b = (_a = this.scopeList) === null || _a === void 0 ? void 0 : _a.previousScopeItem) === null || _b === void 0 ? void 0 : _b.scope) { var currentScope = this.scopeList.previousScopeItem; while (currentScope) { if (currentScope.scope && currentScope.scope instanceof AVM1MovieClip) { parentObj = currentScope.scope; } else if (currentScope.scope) { parentObj = currentScope.scope.alGet('this'); } if (parentObj) { parentObj = parentObj.alGet('_parent'); } if (currentScope.previousScopeItem) currentScope = currentScope.previousScopeItem; else currentScope = null; } } } } /*if(this.isOnEnter){ console.log("prepare on enter"); console.log("oldScope parent", oldScope.alGet("_parent")); console.log("oldScope this", oldScope.alGet("this")); console.log("newscope this", newScope.alGet("_parent")); console.log("thisArg", thisArg); }*/ if (parentObj) { registers[i] = parentObj; } else { //console.log("_parent not defined"); } break; } case 64 /* ArgumentAssignmentType.Root */: registers[i] = avm1ResolveRoot(ectx); break; } } } var parametersNames = this.parametersNames; var skipArguments = this.skipArguments; for (i = 0; i < args.length || i < parametersNames.length; i++) { if (skipArguments && skipArguments[i]) { continue; } newScope.alPut(parametersNames[i], args[i]); } var result; var caughtError; var actionTracer = currentContext.actionTracer; var actionsData = this.actionsData; release || (actionTracer && actionTracer.indent()); if (++currentContext.stackDepth >= MAX_AVM1_STACK_LIMIT) { throw new AVM1CriticalError('long running script -- AVM1 recursion limit is reached'); } result = interpretActionsData(ectx, actionsData); currentContext.stackDepth--; currentContext.popCallFrame(); ectx.dispose(); release || (actionTracer && actionTracer.unindent()); return result; }; return AVM1InterpretedFunction; }(AVM1EvalFunction)); export { AVM1InterpretedFunction }; function fixArgsCount(numArgs /* int */, maxAmount) { if (isNaN(numArgs) || numArgs < 0) { avm1Warn('Invalid amount of arguments: ' + numArgs); return 0; } numArgs |= 0; if (numArgs > maxAmount) { avm1Warn('Truncating amount of arguments: from ' + numArgs + ' to ' + maxAmount); return maxAmount; } return numArgs; } function avm1ReadFunctionArgs(stack) { var numArgs = +stack.pop(); numArgs = fixArgsCount(numArgs, stack.length); var args = []; for (var i = 0; i < numArgs; i++) { args.push(stack.pop()); } return args; } function avm1SetTarget(ectx, targetPath) { var newTarget = null; if (targetPath) { if (typeof targetPath === 'string') { while (targetPath.length && targetPath[targetPath.length - 1] == '.') { targetPath = targetPath.substring(0, targetPath.length - 1); } } try { newTarget = avm1ResolveTarget(ectx, targetPath, false); if (!avm1IsTarget(newTarget)) { avm1Warn('Invalid AVM1 target object: ' + targetPath); newTarget = undefined; } } catch (e) { avm1Warn('Unable to set target: ' + e); } } if (newTarget) { ectx.scopeList.flags |= AVM1ScopeListItemFlags.REPLACE_TARGET; ectx.scopeList.replaceTargetBy = newTarget; } else { ectx.scopeList.flags &= ~AVM1ScopeListItemFlags.REPLACE_TARGET; ectx.scopeList.replaceTargetBy = null; } } function avm1DefineFunction(ectx, actionsData, functionName, parametersNames, registersCount, registersAllocation, suppressArguments) { return new AVM1InterpretedFunction(ectx.context, ectx, actionsData, functionName, parametersNames, registersCount, registersAllocation, suppressArguments); } function avm1VariableNameHasPath(variableName) { return variableName && (variableName.indexOf('.') >= 0 || variableName.indexOf(':') >= 0 || variableName.indexOf('/') >= 0); } var cachedResolvedVariableResult = { scope: null, propertyName: null, value: undefined }; function avm1IsTarget(target) { // TODO refactor return target instanceof AVM1Object && hasAwayJSAdaptee(target); } /* eslint-disable-next-line */ function avm1ResolveSimpleVariable(scopeList, variableName, flags, additionalName) { if (additionalName === void 0) { additionalName = null; } release || Debug.assert(alIsName(scopeList.scope.context, variableName)); var currentTarget; var resolved = cachedResolvedVariableResult; for (var p = scopeList; p; p = p.previousScopeItem) { if ((p.flags & AVM1ScopeListItemFlags.REPLACE_TARGET) && !(flags & 64 /* AVM1ResolveVariableFlags.DISALLOW_TARGET_OVERRIDE */) && !currentTarget) { currentTarget = p.replaceTargetBy; } if ((p.flags & AVM1ScopeListItemFlags.TARGET)) { if ((flags & 2 /* AVM1ResolveVariableFlags.WRITE */)) { // last scope/target we can modify (exclude globals) resolved.scope = currentTarget || p.scope; resolved.propertyName = variableName; resolved.value = (flags & 32 /* AVM1ResolveVariableFlags.GET_VALUE */) ? resolved.scope.alGet(variableName) : undefined; return resolved; } if ((flags & 1 /* AVM1ResolveVariableFlags.READ */) && currentTarget) { if (currentTarget.alHasProperty(variableName)) { resolved.scope = currentTarget; resolved.propertyName = variableName; resolved.value = (flags & 32 /* AVM1ResolveVariableFlags.GET_VALUE */) ? currentTarget.alGet(variableName) : undefined; return resolved; } continue; } } //console.log("scope :", p.scope.aCount); if (p.scope.alHasProperty(variableName)) { var value = p.scope.alGet(variableName); if (additionalName && (!value || typeof value !== 'object' || !value.alHasProperty || !value.alHasProperty(additionalName))) { continue; } resolved.scope = p.scope; resolved.propertyName = variableName; resolved.value = (flags & 32 /* AVM1ResolveVariableFlags.GET_VALUE */) ? p.scope.alGet(variableName) : undefined; return resolved; } //80pro: in some cases we are trying to find a mc by name, but it is only registered as "this" within the scope // in this cases, we check if the "this" object actually has the name that we are searching for /* if(p.scope.alHasProperty("this")) { var thisValue = (flags & AVM1ResolveVariableFlags.GET_VALUE) ? p.scope.alGet("this") : undefined; if(thisValue && thisValue.adaptee && thisValue.adaptee.name && thisValue.adaptee.name==variableName){ resolved.scope = p.scope; resolved.propertyName = variableName; resolved.value = thisValue; return resolved; } }*/ } noVarGetDebug || console.log('avm1ResolveSimpleVariable variableName', variableName); release || Debug.assert(!(flags & 2 /* AVM1ResolveVariableFlags.WRITE */)); return undefined; } /* eslint-disable-next-line */ function avm1ResolveVariable(ectx, variableName, flags) { // For now it is just very much magical -- designed to pass some of the swfdec tests // FIXME refactor release || Debug.assert(variableName); var len = variableName.length; var i = 0; var markedAsTarget = true; var resolved, ch, needsScopeResolution; var propertyName = null; var scope = null; var obj = undefined; // Canonicalizing the name here is ok even for paths: the only thing that (potentially) // happens is that the name is converted to lower-case, which is always valid for paths. // The original name is saved because the final property name needs to be extracted from // it for property name paths. var originalName = variableName; if (!avm1VariableNameHasPath(variableName)) { variableName = ectx.context.normalizeName(variableName); if (typeof variableName === 'string' && variableName.startsWith('_level')) { resolved = cachedResolvedVariableResult; resolved.scope = scope; resolved.propertyName = variableName; resolved.value = ectx.context.resolveLevel(+variableName[6]); return resolved; } //noVarGetDebug || console.log("simple variableName", variableName); var resolvedVar = avm1ResolveSimpleVariable(ectx.scopeList, variableName, flags); noVarGetDebug || console.log('resolved', resolvedVar); return resolvedVar; } noVarGetDebug || console.log('originalName', originalName); // if this is a path, and the last item is a "." flash will not find anything if (variableName[variableName.length - 1] == '.') { return null; } if (variableName[0] === '/') { noVarGetDebug || console.log('originalName starts with a \'/\''); resolved = avm1ResolveSimpleVariable(ectx.scopeList, '_root', 1 /* AVM1ResolveVariableFlags.READ */ | 32 /* AVM1ResolveVariableFlags.GET_VALUE */); if (resolved) { noVarGetDebug || console.log('resolved', resolved); propertyName = resolved.propertyName; scope = resolved.scope; obj = resolved.value; } i++; needsScopeResolution = false; } else { resolved = null; needsScopeResolution = true; } noVarGetDebug || console.log('needsScopeResolution', needsScopeResolution); if (i >= len) { return resolved; } var q = i; while (i < len) { if (!needsScopeResolution && !(obj instanceof AVM1Object)) { /* eslint-disable-next-line */ noVarGetDebug || console.log('Unable to resolve variable on invalid object ' + variableName.substring(q, i - 1) + ' (expr ' + variableName + ')'); /* eslint-disable-next-line */ avm1Warn('Unable to resolve variable on invalid object ' + variableName.substring(q, i - 1) + ' (expr ' + variableName + ')'); return null; } q = i; if (variableName[i] === '.' && variableName[i + 1] === '.') { i += 2; propertyName = '_parent'; } else { while (i < len && ((ch = variableName[i]) !== '/' && ch !== '.' && ch !== ':')) { i++; } propertyName = variableName.substring(q, i); } if (propertyName === '' && i < len) { // Ignoring double delimiters in the middle of the path i++; continue; } scope = obj; var valueFound = false; if (markedAsTarget) { // Trying movie clip children first var child = obj instanceof AVM1MovieClip ? obj._lookupChildByName(propertyName) : void 0; if (child) { valueFound = true; obj = child; } } if (!valueFound) { if (needsScopeResolution) { // 80pro: // if we need to resolve the scope, we want to know the next property name // if a next property name exists, we pass it as extra argument to avm1ResolveSimpleVariable // this will make sure that avm1ResolveSimpleVariable // returns the scope that has the property name available q = i + 1; var k = i + 1; var nextPropName = ''; if (variableName[k] === '.' && variableName[k + 1] === '.') { k += 2; nextPropName = '_parent'; } else { while (k < len && ((ch = variableName[k]) !== '/' && ch !== '.' && ch !== ':')) { k++; } nextPropName = variableName.substring(q, k); } if (nextPropName == '') nextPropName = null; resolved = avm1ResolveSimpleVariable(ectx.scopeList, propertyName, flags, nextPropName); if (!resolved && nextPropName) { // if we tried to get with a nextPropName, // and got nothing returned, we try again without any nextpropName resolved = avm1ResolveSimpleVariable(ectx.scopeList, propertyName, flags); } if (resolved) { valueFound = true; propertyName = resolved.propertyName; scope = resolved.scope; obj = resolved.value; if (i < len && !obj && scope) { obj = scope; } } needsScopeResolution = false; } else if (obj.alHasProperty(propertyName)) { obj = obj.alGet(propertyName); valueFound = true; } } if (!valueFound && propertyName[0] === '_') { // FIXME hacking to pass some swfdec test cases if (propertyName.startsWith('_level')) { obj = ectx.context.resolveLevel(+propertyName[6]); valueFound = true; } else if (propertyName === '_root') { obj = avm1ResolveRoot(ectx); valueFound = true; } } if (!valueFound && !(flags & 2 /* AVM1ResolveVariableFlags.WRITE */)) { /* eslint-disable-next-line */ avm1Warn('Unable to resolve ' + propertyName + ' on ' + variableName.substring(q, i - 1) + ' (expr ' + variableName + ')'); return null; } if (i >= len) { break; } var delimiter = variableName[i++]; if (delimiter === '/' && ((ch = variableName[i]) === ':' || ch === '.')) { delimiter = variableName[i++]; } markedAsTarget = delimiter === '/'; } resolved = cachedResolvedVariableResult; resolved.scope = scope; resolved.propertyName = originalName.substring(q, i); resolved.value = (flags & 32 /* AVM1ResolveVariableFlags.GET_VALUE */) ? obj : undefined; return resolved; } function avm1GetTarget(ectx, allowOverride) { var scopeList = ectx.scopeList; for (var p = scopeList; p.previousScopeItem; p = p.previousScopeItem) { if ((p.flags & AVM1ScopeListItemFlags.REPLACE_TARGET) && allowOverride) { return p.replaceTargetBy; } if ((p.flags & AVM1ScopeListItemFlags.TARGET)) { return p.scope; } } release || Debug.assert(false, 'Shall not reach this statement'); return undefined; } function avm1ResolveTarget(ectx, target, fromCurrentTarget) { var result; if (avm1IsTarget(target)) { result = target; } else { target = isNullOrUndefined(target) ? '' : alToString(this, target); if (target) { var targetPath = alToString(ectx.context, target); var resolved = avm1ResolveVariable(ectx, targetPath, 1 /* AVM1ResolveVariableFlags.READ */ | 128 /* AVM1ResolveVariableFlags.ONLY_TARGETS */ | 32 /* AVM1ResolveVariableFlags.GET_VALUE */ | (fromCurrentTarget ? 0 : 64 /* AVM1ResolveVariableFlags.DISALLOW_TARGET_OVERRIDE */)); if (!resolved || !avm1IsTarget(resolved.value)) { avm1Warn('Invalid AVM1 target object: ' + targetPath); result = undefined; } else { result = resolved.value; } } else { result = avm1GetTarget(ectx, true); } } return result; } function avm1ResolveRoot(ectx) { var target = avm1GetTarget(ectx, true); return target.get_root(); } function avm1ProcessWith(ectx, obj, withBlock) { if (isNullOrUndefined(obj)) { // Not executing anything in the block. avm1Warn('The with statement object cannot be undefined.'); return; } var context = ectx.context; var scopeList = ectx.scopeList; var newScopeList = new AVM1ScopeListItem(alToObject(context, obj), scopeList); var newEctx = ectx.pushScope(newScopeList); interpretActionsData(newEctx, withBlock); } function avm1ProcessTry(ectx, catchIsRegisterFlag, finallyBlockFlag, catchBlockFlag, catchTarget, tryBlock, catchBlock, finallyBlock) { var currentContext = ectx.context; var scopeList = ectx.scopeList; var registers = ectx.registers; var savedTryCatchState = currentContext.isTryCatchListening; var caughtError; try { currentContext.isTryCatchListening = true; interpretActionsData(ectx.pushScope(), tryBlock); } catch (e) { currentContext.isTryCatchListening = savedTryCatchState; if (!catchBlockFlag || !(e instanceof AVM1Error)) { caughtError = e; } else { if (typeof catchTarget === 'string') { // TODO catchIsRegisterFlag? var scope = scopeList.scope; scope