UNPKG

@awayfl/avm1

Version:

Virtual machine for executing AS1 and AS2 code

611 lines (564 loc) 16.8 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 { ActionsDataStream } from './stream'; import { AVM1ActionsData } from './context'; export const enum ActionCode { None = 0x00, ActionGotoFrame = 0x81, ActionGetURL = 0x83, ActionNextFrame = 0x04, ActionPreviousFrame = 0x05, ActionPlay = 0x06, ActionStop = 0x07, ActionToggleQuality = 0x08, ActionStopSounds = 0x09, ActionWaitForFrame = 0x8A, ActionSetTarget = 0x8B, ActionGoToLabel = 0x8C, ActionPush = 0x96, ActionPop = 0x17, ActionAdd = 0x0A, ActionSubtract = 0x0B, ActionMultiply = 0x0C, ActionDivide = 0x0D, ActionEquals = 0x0E, ActionLess = 0x0F, ActionAnd = 0x10, ActionOr = 0x11, ActionNot = 0x12, ActionStringEquals = 0x13, ActionStringLength = 0x14, ActionMBStringLength = 0x31, ActionStringAdd = 0x21, ActionStringExtract = 0x15, ActionMBStringExtract = 0x35, ActionStringLess = 0x29, ActionToInteger = 0x18, ActionCharToAscii = 0x32, ActionMBCharToAscii = 0x36, ActionAsciiToChar = 0x33, ActionMBAsciiToChar = 0x37, ActionJump = 0x99, ActionIf = 0x9D, ActionCall = 0x9E, ActionGetVariable = 0x1C, ActionSetVariable = 0x1D, ActionGetURL2 = 0x9A, ActionGotoFrame2 = 0x9F, ActionSetTarget2 = 0x20, ActionGetProperty = 0x22, ActionSetProperty = 0x23, ActionCloneSprite = 0x24, ActionRemoveSprite = 0x25, ActionStartDrag = 0x27, ActionEndDrag = 0x28, ActionWaitForFrame2 = 0x8D, ActionTrace = 0x26, ActionGetTime = 0x34, ActionRandomNumber = 0x30, ActionCallFunction = 0x3D, ActionCallMethod = 0x52, ActionConstantPool = 0x88, ActionDefineFunction = 0x9B, ActionDefineLocal = 0x3C, ActionDefineLocal2 = 0x41, ActionDelete = 0x3A, ActionDelete2 = 0x3B, ActionEnumerate = 0x46, ActionEquals2 = 0x49, ActionGetMember = 0x4E, ActionInitArray = 0x42, ActionInitObject = 0x43, ActionNewMethod = 0x53, ActionNewObject = 0x40, ActionSetMember = 0x4F, ActionTargetPath = 0x45, ActionWith = 0x94, ActionToNumber = 0x4A, ActionToString = 0x4B, ActionTypeOf = 0x44, ActionAdd2 = 0x47, ActionLess2 = 0x48, ActionModulo = 0x3F, ActionBitAnd = 0x60, ActionBitLShift = 0x63, ActionBitOr = 0x61, ActionBitRShift = 0x64, ActionBitURShift = 0x65, ActionBitXor = 0x62, ActionDecrement = 0x51, ActionIncrement = 0x50, ActionPushDuplicate = 0x4C, ActionReturn = 0x3E, ActionStackSwap = 0x4D, ActionStoreRegister = 0x87, ActionInstanceOf = 0x54, ActionEnumerate2 = 0x55, ActionStrictEquals = 0x66, ActionGreater = 0x67, ActionStringGreater = 0x68, ActionDefineFunction2 = 0x8E, ActionExtends = 0x69, ActionCastOp = 0x2B, ActionImplementsOp = 0x2C, ActionTry = 0x8F, ActionThrow = 0x2A, ActionFSCommand2 = 0x2D, ActionStrictMode = 0x89 } const IS_INVALID_NAME = /[^A-Za-z0-9_]+/g; export class ParsedPushRegisterAction { constructor(public registerNumber: number) {} } export class ParsedPushConstantAction { constructor(public constantIndex: number) {} } export interface ParsedAction { position: number; actionCode: number; actionName: string; args: any[]; knownAction?: boolean; } export interface ArgumentAssignment { type: ArgumentAssignmentType; name?: string; index?: number; } export const enum ArgumentAssignmentType { None = 0, Argument = 1, This = 2, Arguments = 4, Super = 8, Global = 16, Parent = 32, Root = 64 } export class ActionsDataParser { public dataId: string; private _stream: ActionsDataStream; private _actionsData: AVM1ActionsData; private _lastPushedValue: any = null; private _lastDefinedConstantPool: any[] = null; private _initialPosition: number = 0; private _initialLen: number = 0; constructor(actionsData: AVM1ActionsData, public swfVersion: number) { this._actionsData = actionsData; this.dataId = actionsData.id; const bytes = actionsData.bytes; const buffer = new Uint8Array(bytes.buffer, 0); this._initialPosition = bytes.byteOffset; this._initialLen = bytes.length; /** * This is important, because apps with encryption is problem, * We should parse ALL bytes of SWF, because tag253 contains part of bytecodes */ this._stream = new ActionsDataStream(buffer, swfVersion); this._stream.position = this._initialPosition; } get position(): number { return this._stream.position - this._initialPosition; } set position(value: number) { this._stream.position = value + this._initialPosition; } get eof(): boolean { return this.position >= this._initialLen; } get length(): number { return this._initialLen; } readNext(): ParsedAction { const stream = this._stream; const currentPosition = stream.position; const actionCode = stream.readUI8(); const length = actionCode >= 0x80 ? stream.readUI16() : 0; let nextPosition = stream.position + length; let args: any[] = null; if ( actionCode !== ActionCode.ActionDefineFunction && actionCode !== ActionCode.ActionDefineFunction2) { this._lastPushedValue = null; } switch (actionCode | 0) { case ActionCode.ActionGotoFrame: var frame = stream.readUI16(); var nextActionCode = stream.readUI8(); var play = false; if (nextActionCode !== 0x06 && nextActionCode !== 0x07) { stream.position--; } else { nextPosition++; play = nextActionCode === 0x06; } args = [frame, play]; break; case ActionCode.ActionGetURL: var urlString = stream.readString(); var targetString = stream.readString(); args = [urlString, targetString]; break; case ActionCode.ActionWaitForFrame: var frame = stream.readUI16(); var count = stream.readUI8(); args = [frame, count]; break; case ActionCode.ActionSetTarget: var targetName = stream.readString(); args = [targetName]; break; case ActionCode.ActionGoToLabel: var label = stream.readString(); var nextActionCode = stream.readUI8(); var play = false; if (nextActionCode !== 0x06 && nextActionCode !== 0x07) { stream.position--; } else { nextPosition++; play = nextActionCode === 0x06; } args = [label, play]; break; case ActionCode.ActionPush: var type, value; args = []; while (stream.position < nextPosition) { type = stream.readUI8(); switch (type | 0) { case 0: // STRING value = stream.readString(); this._lastPushedValue = value; break; case 1: // FLOAT value = stream.readFloat(); break; case 2: // null value = null; break; case 3: // undefined value = void (0); break; case 4: // Register number value = new ParsedPushRegisterAction(stream.readUI8()); break; case 5: // Boolean value = stream.readBoolean(); break; case 6: // Double value = stream.readDouble(); break; case 7: // Integer value = stream.readInteger(); break; case 8: // Constant8 value = new ParsedPushConstantAction(stream.readUI8()); break; case 9: // Constant16 value = new ParsedPushConstantAction(stream.readUI16()); break; default: console.error('Unknown value type: ' + type, stream.position); stream.position = nextPosition; continue; } this._lastPushedValue = value; args.push(value); } break; case ActionCode.ActionJump: var offset = stream.readSI16(); args = [offset]; break; case ActionCode.ActionIf: var offset = stream.readSI16(); args = [offset]; break; case ActionCode.ActionGetURL2: var flags = stream.readUI8(); args = [flags]; break; case ActionCode.ActionGotoFrame2: var flags = stream.readUI8(); args = [flags]; if (flags & 2) { args.push(stream.readUI16()); } break; case ActionCode.ActionWaitForFrame2: var count = stream.readUI8(); args = [count]; break; case ActionCode.ActionConstantPool: var count = stream.readUI16(); var constantPool = []; for (var i = 0; i < count; i++) { constantPool.push(stream.readString()); } this._lastDefinedConstantPool = constantPool; args = [constantPool]; break; case ActionCode.ActionDefineFunction: var functionName = stream.readString(); var count = stream.readUI16(); var functionParams = []; for (var i = 0; i < count; i++) { functionParams.push(stream.readString()); } var codeSize = stream.readUI16(); nextPosition += codeSize; var functionBody = new AVM1ActionsData(stream.readBytes(codeSize), this.dataId + '_f' + stream.position, this._actionsData); args = [functionBody, functionName, functionParams]; break; case ActionCode.ActionWith: var codeSize = stream.readUI16(); nextPosition += codeSize; var withBody = new AVM1ActionsData(stream.readBytes(codeSize), this.dataId + '_w' + stream.position, this._actionsData); args = [withBody]; break; case ActionCode.ActionStoreRegister: var register = stream.readUI8(); args = [register]; break; case ActionCode.ActionDefineFunction2: let methodName: string = null; if (this._lastPushedValue instanceof ParsedPushConstantAction && this._lastDefinedConstantPool) { methodName = this._lastDefinedConstantPool[this._lastPushedValue.constantIndex]; } if (typeof this._lastPushedValue === 'string') { methodName = this._lastPushedValue; } if (methodName && IS_INVALID_NAME.test(methodName)) { methodName = null; } var functionName = stream.readString(); var count = stream.readUI16(); var registerCount = stream.readUI8(); var flags = stream.readUI16(); var registerAllocation: ArgumentAssignment[] = []; var functionParams = []; for (var i = 0; i < count; i++) { var register = stream.readUI8(); const paramName = stream.readString(); functionParams.push(paramName); if (register) { registerAllocation[register] = { type: ArgumentAssignmentType.Argument, name: paramName, index: i }; } } var j = 1; // order this, arguments, super, _root, _parent, and _global if (flags & 0x0001) { // preloadThis registerAllocation[j++] = { type: ArgumentAssignmentType.This }; } if (flags & 0x0004) { // preloadArguments registerAllocation[j++] = { type: ArgumentAssignmentType.Arguments }; } if (flags & 0x0010) { // preloadSuper registerAllocation[j++] = { type: ArgumentAssignmentType.Super }; } if (flags & 0x0040) { // preloadRoot registerAllocation[j++] = { type: ArgumentAssignmentType.Root }; } if (flags & 0x0080) { // preloadParent registerAllocation[j++] = { type: ArgumentAssignmentType.Parent }; } if (flags & 0x0100) { // preloadGlobal registerAllocation[j++] = { type: ArgumentAssignmentType.Global }; } var suppressArguments: ArgumentAssignmentType = 0; if (flags & 0x0002) { // suppressThis suppressArguments |= ArgumentAssignmentType.This; } if (flags & 0x0008) { // suppressArguments suppressArguments |= ArgumentAssignmentType.Arguments; } if (flags & 0x0020) { // suppressSuper suppressArguments |= ArgumentAssignmentType.Super; } var codeSize = stream.readUI16(); nextPosition += codeSize; let name = this.dataId + '_f' + stream.position; if (methodName) { name = methodName + '__' + name; } var functionBody = new AVM1ActionsData(stream.readBytes(codeSize), name, this._actionsData); functionBody.debugPath = this.dataId + '/' + (methodName ? methodName : ('f' + stream.position)); args = [functionBody, functionName, functionParams, registerCount, registerAllocation, suppressArguments]; break; case ActionCode.ActionTry: var flags = stream.readUI8(); var catchIsRegisterFlag = !!(flags & 4); var finallyBlockFlag = !!(flags & 2); var catchBlockFlag = !!(flags & 1); var trySize = stream.readUI16(); var catchSize = stream.readUI16(); var finallySize = stream.readUI16(); var catchTarget: any = catchIsRegisterFlag ? stream.readUI8() : stream.readString(); nextPosition += trySize + catchSize + finallySize; const thisPath = this._actionsData.debugPath; var tryBody = new AVM1ActionsData(stream.readBytes(trySize), this.dataId + '_t' + stream.position, this._actionsData); if (thisPath) tryBody.debugPath = thisPath + '/try_' + this._stream.position; var catchBody = new AVM1ActionsData(stream.readBytes(catchSize), this.dataId + '_c' + stream.position, this._actionsData); if (thisPath) { catchBody.debugPath = thisPath + '/catch_' + this._stream.position; } var finallyBody = new AVM1ActionsData(stream.readBytes(finallySize), this.dataId + '_z' + stream.position, this._actionsData); if (thisPath) { finallyBody.debugPath = thisPath + '/finaly_' + this._stream.position; } args = [catchIsRegisterFlag, catchTarget, tryBody, catchBlockFlag, catchBody, finallyBlockFlag, finallyBody]; break; case ActionCode.ActionStrictMode: var mode = stream.readUI8(); args = [mode]; break; } stream.position = nextPosition; return { position: currentPosition - this._initialPosition, actionCode: actionCode, actionName: ActionNamesMap[actionCode], knownAction: !!(ActionNamesMap[actionCode]), args: args }; } skip(count) { const stream = this._stream; while (count > 0 && this.position < this._initialLen) { const actionCode = stream.readUI8(); const length = actionCode >= 0x80 ? stream.readUI16() : 0; stream.position += length; count--; } } } const ActionNamesMap = { 0x00: 'EOA', 0x04: 'ActionNextFrame', 0x05: 'ActionPreviousFrame', 0x06: 'ActionPlay', 0x07: 'ActionStop', 0x08: 'ActionToggleQuality', 0x09: 'ActionStopSounds', 0x0A: 'ActionAdd', 0x0B: 'ActionSubtract', 0x0C: 'ActionMultiply', 0x0D: 'ActionDivide', 0x0E: 'ActionEquals', 0x0F: 'ActionLess', 0x10: 'ActionAnd', 0x11: 'ActionOr', 0x12: 'ActionNot', 0x13: 'ActionStringEquals', 0x14: 'ActionStringLength', 0x15: 'ActionStringExtract', 0x17: 'ActionPop', 0x18: 'ActionToInteger', 0x1C: 'ActionGetVariable', 0x1D: 'ActionSetVariable', 0x20: 'ActionSetTarget2', 0x21: 'ActionStringAdd', 0x22: 'ActionGetProperty', 0x23: 'ActionSetProperty', 0x24: 'ActionCloneSprite', 0x25: 'ActionRemoveSprite', 0x26: 'ActionTrace', 0x27: 'ActionStartDrag', 0x28: 'ActionEndDrag', 0x29: 'ActionStringLess', 0x2A: 'ActionThrow', 0x2B: 'ActionCastOp', 0x2C: 'ActionImplementsOp', 0x2D: 'ActionFSCommand2', 0x30: 'ActionRandomNumber', 0x31: 'ActionMBStringLength', 0x32: 'ActionCharToAscii', 0x33: 'ActionAsciiToChar', 0x34: 'ActionGetTime', 0x35: 'ActionMBStringExtract', 0x36: 'ActionMBCharToAscii', 0x37: 'ActionMBAsciiToChar', 0x3A: 'ActionDelete', 0x3B: 'ActionDelete2', 0x3C: 'ActionDefineLocal', 0x3D: 'ActionCallFunction', 0x3E: 'ActionReturn', 0x3F: 'ActionModulo', 0x40: 'ActionNewObject', 0x41: 'ActionDefineLocal2', 0x42: 'ActionInitArray', 0x43: 'ActionInitObject', 0x44: 'ActionTypeOf', 0x45: 'ActionTargetPath', 0x46: 'ActionEnumerate', 0x47: 'ActionAdd2', 0x48: 'ActionLess2', 0x49: 'ActionEquals2', 0x4A: 'ActionToNumber', 0x4B: 'ActionToString', 0x4C: 'ActionPushDuplicate', 0x4D: 'ActionStackSwap', 0x4E: 'ActionGetMember', 0x4F: 'ActionSetMember', 0x50: 'ActionIncrement', 0x51: 'ActionDecrement', 0x52: 'ActionCallMethod', 0x53: 'ActionNewMethod', 0x54: 'ActionInstanceOf', 0x55: 'ActionEnumerate2', 0x60: 'ActionBitAnd', 0x61: 'ActionBitOr', 0x62: 'ActionBitXor', 0x63: 'ActionBitLShift', 0x64: 'ActionBitRShift', 0x65: 'ActionBitURShift', 0x66: 'ActionStrictEquals', 0x67: 'ActionGreater', 0x68: 'ActionStringGreater', 0x69: 'ActionExtends', 0x81: 'ActionGotoFrame', 0x83: 'ActionGetURL', 0x87: 'ActionStoreRegister', 0x88: 'ActionConstantPool', 0x89: 'ActionStrictMode', 0x8A: 'ActionWaitForFrame', 0x8B: 'ActionSetTarget', 0x8C: 'ActionGoToLabel', 0x8D: 'ActionWaitForFrame2', 0x8E: 'ActionDefineFunction2', 0x8F: 'ActionTry', 0x94: 'ActionWith', 0x96: 'ActionPush', 0x99: 'ActionJump', 0x9A: 'ActionGetURL2', 0x9B: 'ActionDefineFunction', 0x9D: 'ActionIf', 0x9E: 'ActionCall', 0x9F: 'ActionGotoFrame2' };