// ASHandlers.cpp: ActionScript handlers, for Gnash. // // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 // Free Software Foundation, Inc // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // #ifdef HAVE_CONFIG_H #include "gnashconfig.h" #endif #include "ASHandlers.h" #include #include #include #include #include "log.h" #include "SWF.h" #include "rc.h" #include "NativeFunction.h" #include "Function.h" #include "as_function.h" #include "Function2.h" #include "fn_call.h" #include "ActionExec.h" #include "MovieClip.h" #include "as_environment.h" #include "URL.h" #include "action_buffer.h" #include "as_object.h" #include "DragState.h" #include "VM.h" // for getting the root #include "movie_root.h" #include "sound_handler.h" #include "namedStrings.h" #include "utf8.h" #include "StringPredicates.h" #include "GnashNumeric.h" #include "Global_as.h" #include "DisplayObject.h" #include "as_environment.h" #include "as_value.h" #include "RunResources.h" #include "ObjectURI.h" // GNASH_PARANOIA_LEVEL: // 0 : no assertions // 1 : quick assertions // 2 : check that handlers are called on correct tag #ifndef GNASH_PARANOIA_LEVEL # define GNASH_PARANOIA_LEVEL 1 #endif namespace gnash { namespace { as_object* construct_object(as_function* ctor_as_func, as_environment& env, unsigned int nargs); /// Convert to object without an exception being thrown. // /// @return null if the value cannot be converted to an object. as_object* safeToObject(VM& vm, const as_value& val); /// Common code for ActionGetUrl and ActionGetUrl2 // /// @param target the target window or _level1 to _level10 /// @param method 0:NONE, 1:GET, 2:POST void commonGetURL(as_environment& env, as_value target, const std::string& url, std::uint8_t method); /// Common code for SetTarget and SetTargetExpression /// /// @param target_name The target name. If empty new target will /// be the main movie. /// @param thread The current execution thread. void commonSetTarget(ActionExec& thread, const std::string& target_name); void ActionEnd(ActionExec& thread); void ActionNextFrame(ActionExec& thread); void ActionPrevFrame(ActionExec& thread); void ActionPlay(ActionExec& thread); void ActionStop(ActionExec& thread); void ActionToggleQuality(ActionExec& thread); void ActionStopSounds(ActionExec& thread); void ActionGotoFrame(ActionExec& thread); void ActionGetUrl(ActionExec& thread); void ActionWaitForFrame(ActionExec& thread); void ActionSetTarget(ActionExec& thread); void ActionGotoLabel(ActionExec& thread); void ActionAdd(ActionExec& thread); void ActionSubtract(ActionExec& thread); void ActionMultiply(ActionExec& thread); void ActionDivide(ActionExec& thread); void ActionEqual(ActionExec& thread); void ActionLessThan(ActionExec& thread); void ActionLogicalAnd(ActionExec& thread); void ActionLogicalOr(ActionExec& thread); void ActionLogicalNot(ActionExec& thread); void ActionStringEq(ActionExec& thread); void ActionStringLength(ActionExec& thread); void ActionSubString(ActionExec& thread); void ActionPop(ActionExec& thread); void ActionInt(ActionExec& thread); void ActionGetVariable(ActionExec& thread); void ActionSetVariable(ActionExec& thread); void ActionSetTargetExpression(ActionExec& thread); void ActionStringConcat(ActionExec& thread); void ActionGetProperty(ActionExec& thread); void ActionSetProperty(ActionExec& thread); void ActionDuplicateClip(ActionExec& thread); void ActionRemoveClip(ActionExec& thread); void ActionTrace(ActionExec& thread); void ActionStartDragMovie(ActionExec& thread); void ActionStopDragMovie(ActionExec& thread); void ActionStringCompare(ActionExec& thread); void ActionThrow(ActionExec& thread); void ActionCastOp(ActionExec& thread); void ActionImplementsOp(ActionExec& thread); void ActionFscommand2(ActionExec& thread); void ActionRandom(ActionExec& thread); void ActionMbLength(ActionExec& thread); void ActionOrd(ActionExec& thread); void ActionChr(ActionExec& thread); void ActionGetTimer(ActionExec& thread); void ActionMbSubString(ActionExec& thread); void ActionMbOrd(ActionExec& thread); void ActionMbChr(ActionExec& thread); void ActionStrictMode(ActionExec& thread); void ActionWaitForFrameExpression(ActionExec& thread); void ActionPushData(ActionExec& thread); void ActionBranchAlways(ActionExec& thread); void ActionGetUrl2(ActionExec& thread); void ActionBranchIfTrue(ActionExec& thread); void ActionCallFrame(ActionExec& thread); void ActionGotoExpression(ActionExec& thread); void ActionDelete(ActionExec& thread); void ActionDelete2(ActionExec& thread); void ActionVarEquals(ActionExec& thread); void ActionCallFunction(ActionExec& thread); void ActionReturn(ActionExec& thread); void ActionModulo(ActionExec& thread); void ActionNew(ActionExec& thread); void ActionVar(ActionExec& thread); void ActionInitArray(ActionExec& thread); void ActionInitObject(ActionExec& thread); void ActionTypeOf(ActionExec& thread); void ActionTargetPath(ActionExec& thread); void ActionEnumerate(ActionExec& thread); void ActionNewAdd(ActionExec& thread); void ActionNewLessThan(ActionExec& thread); void ActionNewEquals(ActionExec& thread); void ActionToNumber(ActionExec& thread); void ActionToString(ActionExec& thread); void ActionDup(ActionExec& thread); void ActionSwap(ActionExec& thread); void ActionGetMember(ActionExec& thread); void ActionSetMember(ActionExec& thread); void ActionIncrement(ActionExec& thread); void ActionDecrement(ActionExec& thread); void ActionCallMethod(ActionExec& thread); void ActionNewMethod(ActionExec& thread); void ActionInstanceOf(ActionExec& thread); void ActionEnum2(ActionExec& thread); void ActionBitwiseAnd(ActionExec& thread); void ActionBitwiseOr(ActionExec& thread); void ActionBitwiseXor(ActionExec& thread); void ActionShiftLeft(ActionExec& thread); void ActionShiftRight(ActionExec& thread); void ActionShiftRight2(ActionExec& thread); void ActionStrictEq(ActionExec& thread); void ActionGreater(ActionExec& thread); void ActionStringGreater(ActionExec& thread); void ActionExtends(ActionExec& thread); void ActionConstantPool(ActionExec& thread); void ActionDefineFunction2(ActionExec& thread); void ActionTry(ActionExec& thread); void ActionWith(ActionExec& thread); void ActionDefineFunction(ActionExec& thread); void ActionSetRegister(ActionExec& thread); void ActionUnsupported(ActionExec& thread); } namespace { class Enumerator : public KeyVisitor { public: explicit Enumerator(as_environment& env) : _env(env) {} virtual void operator()(const ObjectURI& uri) { _env.push(uri.toString(getStringTable(_env))); } private: as_environment& _env; }; } namespace SWF { ActionHandler::ActionHandler() : _type(ACTION_END), // initialized to zero to have well known value _callback(ActionUnsupported), _arg_format(ARG_NONE) { } ActionHandler::ActionHandler(ActionType type, ActionCallback func, ArgumentType format) : _type(type), _callback(func), _arg_format(format) { } void ActionHandler::execute(ActionExec& thread) const { return _callback(thread); } SWFHandlers::SWFHandlers() : _handlers(256) { _handlers[ACTION_END] = ActionHandler(ACTION_END, ActionEnd); _handlers[ACTION_NEXTFRAME] = ActionHandler(ACTION_NEXTFRAME, ActionNextFrame); _handlers[ACTION_PREVFRAME] = ActionHandler(ACTION_PREVFRAME, ActionPrevFrame); _handlers[ACTION_PLAY] = ActionHandler(ACTION_PLAY, ActionPlay); _handlers[ACTION_STOP] = ActionHandler(ACTION_STOP, ActionStop); _handlers[ACTION_TOGGLEQUALITY] = ActionHandler(ACTION_TOGGLEQUALITY, ActionToggleQuality); _handlers[ACTION_STOPSOUNDS] = ActionHandler(ACTION_STOPSOUNDS, ActionStopSounds); _handlers[ACTION_GOTOFRAME] = ActionHandler(ACTION_GOTOFRAME, ActionGotoFrame, ARG_U16); _handlers[ACTION_GETURL] = ActionHandler(ACTION_GETURL, ActionGetUrl, ARG_STR); _handlers[ACTION_WAITFORFRAME] = ActionHandler(ACTION_WAITFORFRAME, ActionWaitForFrame, ARG_HEX); _handlers[ACTION_SETTARGET] = ActionHandler(ACTION_SETTARGET, ActionSetTarget, ARG_STR); _handlers[ACTION_GOTOLABEL] = ActionHandler(ACTION_GOTOLABEL, ActionGotoLabel, ARG_STR); _handlers[ACTION_ADD] = ActionHandler(ACTION_ADD, ActionAdd); _handlers[ACTION_SUBTRACT] = ActionHandler(ACTION_SUBTRACT, ActionSubtract); _handlers[ACTION_MULTIPLY] = ActionHandler(ACTION_MULTIPLY, ActionMultiply); _handlers[ACTION_DIVIDE] = ActionHandler(ACTION_DIVIDE, ActionDivide); _handlers[ACTION_EQUAL] = ActionHandler(ACTION_EQUAL, ActionEqual); _handlers[ACTION_LESSTHAN] = ActionHandler(ACTION_LESSTHAN, ActionLessThan); _handlers[ACTION_LOGICALAND] = ActionHandler(ACTION_LOGICALAND, ActionLogicalAnd); _handlers[ACTION_LOGICALOR] = ActionHandler(ACTION_LOGICALOR, ActionLogicalOr); _handlers[ACTION_LOGICALNOT] = ActionHandler(ACTION_LOGICALNOT, ActionLogicalNot); _handlers[ACTION_STRINGEQ] = ActionHandler(ACTION_STRINGEQ, ActionStringEq); _handlers[ACTION_STRINGLENGTH] = ActionHandler(ACTION_STRINGLENGTH, ActionStringLength); _handlers[ACTION_SUBSTRING] = ActionHandler(ACTION_SUBSTRING, ActionSubString); _handlers[ACTION_POP] = ActionHandler(ACTION_POP, ActionPop); _handlers[ACTION_INT] = ActionHandler(ACTION_INT, ActionInt); _handlers[ACTION_GETVARIABLE] = ActionHandler(ACTION_GETVARIABLE, ActionGetVariable); _handlers[ACTION_SETVARIABLE] = ActionHandler(ACTION_SETVARIABLE, ActionSetVariable); _handlers[ACTION_SETTARGETEXPRESSION] = ActionHandler(ACTION_SETTARGETEXPRESSION, ActionSetTargetExpression); _handlers[ACTION_STRINGCONCAT] = ActionHandler(ACTION_STRINGCONCAT, ActionStringConcat); _handlers[ACTION_GETPROPERTY] = ActionHandler(ACTION_GETPROPERTY, ActionGetProperty); _handlers[ACTION_SETPROPERTY] = ActionHandler(ACTION_SETPROPERTY, ActionSetProperty); _handlers[ACTION_DUPLICATECLIP] = ActionHandler(ACTION_DUPLICATECLIP, ActionDuplicateClip); _handlers[ACTION_REMOVECLIP] = ActionHandler(ACTION_REMOVECLIP, ActionRemoveClip); _handlers[ACTION_TRACE] = ActionHandler(ACTION_TRACE, ActionTrace); _handlers[ACTION_STARTDRAGMOVIE] = ActionHandler(ACTION_STARTDRAGMOVIE, ActionStartDragMovie); _handlers[ACTION_STOPDRAGMOVIE] = ActionHandler(ACTION_STOPDRAGMOVIE, ActionStopDragMovie); _handlers[ACTION_STRINGCOMPARE] = ActionHandler(ACTION_STRINGCOMPARE, ActionStringCompare); _handlers[ACTION_THROW] = ActionHandler(ACTION_THROW, ActionThrow); _handlers[ACTION_CASTOP] = ActionHandler(ACTION_CASTOP, ActionCastOp); _handlers[ACTION_IMPLEMENTSOP] = ActionHandler(ACTION_IMPLEMENTSOP, ActionImplementsOp); _handlers[ACTION_FSCOMMAND2] = ActionHandler(ACTION_FSCOMMAND2, ActionFscommand2); _handlers[ACTION_RANDOM] = ActionHandler(ACTION_RANDOM, ActionRandom); _handlers[ACTION_MBLENGTH] = ActionHandler(ACTION_MBLENGTH, ActionMbLength); _handlers[ACTION_ORD] = ActionHandler(ACTION_ORD, ActionOrd); _handlers[ACTION_CHR] = ActionHandler(ACTION_CHR, ActionChr); _handlers[ACTION_GETTIMER] = ActionHandler(ACTION_GETTIMER, ActionGetTimer); _handlers[ACTION_MBSUBSTRING] = ActionHandler(ACTION_MBSUBSTRING, ActionMbSubString); _handlers[ACTION_MBORD] = ActionHandler(ACTION_MBORD, ActionMbOrd); _handlers[ACTION_MBCHR] = ActionHandler(ACTION_MBCHR, ActionMbChr); _handlers[ACTION_STRICTMODE] = ActionHandler(ACTION_STRICTMODE, ActionStrictMode, ARG_U8); _handlers[ACTION_WAITFORFRAMEEXPRESSION] = ActionHandler(ACTION_WAITFORFRAMEEXPRESSION, ActionWaitForFrameExpression, ARG_HEX); _handlers[ACTION_PUSHDATA] = ActionHandler(ACTION_PUSHDATA, ActionPushData, ARG_PUSH_DATA); _handlers[ACTION_BRANCHALWAYS] = ActionHandler(ACTION_BRANCHALWAYS, ActionBranchAlways, ARG_S16); _handlers[ACTION_GETURL2] = ActionHandler(ACTION_GETURL2, ActionGetUrl2, ARG_HEX); _handlers[ACTION_BRANCHIFTRUE] = ActionHandler(ACTION_BRANCHIFTRUE, ActionBranchIfTrue, ARG_S16); _handlers[ACTION_CALLFRAME] = ActionHandler(ACTION_CALLFRAME, ActionCallFrame, ARG_HEX); _handlers[ACTION_GOTOEXPRESSION] = ActionHandler(ACTION_GOTOEXPRESSION, ActionGotoExpression, ARG_HEX); _handlers[ACTION_DELETE] = ActionHandler(ACTION_DELETE, ActionDelete); _handlers[ACTION_DELETE2] = ActionHandler(ACTION_DELETE2, ActionDelete2); _handlers[ACTION_VAREQUALS] = ActionHandler(ACTION_VAREQUALS, ActionVarEquals); _handlers[ACTION_CALLFUNCTION] = ActionHandler(ACTION_CALLFUNCTION, ActionCallFunction); _handlers[ACTION_RETURN] = ActionHandler(ACTION_RETURN, ActionReturn); _handlers[ACTION_MODULO] = ActionHandler(ACTION_MODULO, ActionModulo); _handlers[ACTION_NEW] = ActionHandler(ACTION_NEW, ActionNew); _handlers[ACTION_VAR] = ActionHandler(ACTION_VAR, ActionVar); _handlers[ACTION_INITARRAY] = ActionHandler(ACTION_INITARRAY, ActionInitArray); _handlers[ACTION_INITOBJECT] = ActionHandler(ACTION_INITOBJECT, ActionInitObject); _handlers[ACTION_TYPEOF] = ActionHandler(ACTION_TYPEOF, ActionTypeOf); _handlers[ACTION_TARGETPATH] = ActionHandler(ACTION_TARGETPATH, ActionTargetPath); _handlers[ACTION_ENUMERATE] = ActionHandler(ACTION_ENUMERATE, ActionEnumerate); _handlers[ACTION_NEWADD] = ActionHandler(ACTION_NEWADD, ActionNewAdd); _handlers[ACTION_NEWLESSTHAN] = ActionHandler(ACTION_NEWLESSTHAN, ActionNewLessThan); _handlers[ACTION_NEWEQUALS] = ActionHandler(ACTION_NEWEQUALS, ActionNewEquals); _handlers[ACTION_TONUMBER] = ActionHandler(ACTION_TONUMBER, ActionToNumber); _handlers[ACTION_TOSTRING] = ActionHandler(ACTION_TOSTRING, ActionToString); _handlers[ACTION_DUP] = ActionHandler(ACTION_DUP, ActionDup); _handlers[ACTION_SWAP] = ActionHandler(ACTION_SWAP, ActionSwap); _handlers[ACTION_GETMEMBER] = ActionHandler(ACTION_GETMEMBER, ActionGetMember); _handlers[ACTION_SETMEMBER] = ActionHandler(ACTION_SETMEMBER, ActionSetMember); _handlers[ACTION_INCREMENT] = ActionHandler(ACTION_INCREMENT, ActionIncrement); _handlers[ACTION_DECREMENT] = ActionHandler(ACTION_DECREMENT, ActionDecrement); _handlers[ACTION_CALLMETHOD] = ActionHandler(ACTION_CALLMETHOD, ActionCallMethod); _handlers[ACTION_NEWMETHOD] = ActionHandler(ACTION_NEWMETHOD, ActionNewMethod); _handlers[ACTION_INSTANCEOF] = ActionHandler(ACTION_INSTANCEOF, ActionInstanceOf); _handlers[ACTION_ENUM2] = ActionHandler(ACTION_ENUM2, ActionEnum2); _handlers[ACTION_BITWISEAND] = ActionHandler(ACTION_BITWISEAND, ActionBitwiseAnd); _handlers[ACTION_BITWISEOR] = ActionHandler(ACTION_BITWISEOR, ActionBitwiseOr); _handlers[ACTION_BITWISEXOR] = ActionHandler(ACTION_BITWISEXOR, ActionBitwiseXor); _handlers[ACTION_SHIFTLEFT] = ActionHandler(ACTION_SHIFTLEFT, ActionShiftLeft); _handlers[ACTION_SHIFTRIGHT] = ActionHandler(ACTION_SHIFTRIGHT, ActionShiftRight); _handlers[ACTION_SHIFTRIGHT2] = ActionHandler(ACTION_SHIFTRIGHT2, ActionShiftRight2); _handlers[ACTION_STRICTEQ] = ActionHandler(ACTION_STRICTEQ, ActionStrictEq); _handlers[ACTION_GREATER] = ActionHandler(ACTION_GREATER, ActionGreater); _handlers[ACTION_STRINGGREATER] = ActionHandler(ACTION_STRINGGREATER, ActionStringGreater); _handlers[ACTION_EXTENDS] = ActionHandler(ACTION_EXTENDS, ActionExtends); _handlers[ACTION_CONSTANTPOOL] = ActionHandler(ACTION_CONSTANTPOOL, ActionConstantPool, ARG_DECL_DICT); _handlers[ACTION_DEFINEFUNCTION2] = ActionHandler(ACTION_DEFINEFUNCTION2, ActionDefineFunction2, ARG_FUNCTION2); _handlers[ACTION_TRY] = ActionHandler(ACTION_TRY, ActionTry, ARG_FUNCTION2); _handlers[ACTION_WITH] = ActionHandler(ACTION_WITH, ActionWith, ARG_U16); _handlers[ACTION_DEFINEFUNCTION] = ActionHandler(ACTION_DEFINEFUNCTION, ActionDefineFunction, ARG_HEX); _handlers[ACTION_SETREGISTER] = ActionHandler(ACTION_SETREGISTER, ActionSetRegister, ARG_U8); } SWFHandlers::~SWFHandlers() { } const SWFHandlers& SWFHandlers::instance() { static const SWFHandlers instance; return instance; } void SWFHandlers::execute(ActionType type, ActionExec& thread) const { try { _handlers[type].execute(thread); } catch (const ActionParserException& e) { log_swferror(_("Malformed action code: %s"), e.what()); } } } // namespace SWF namespace { void ActionEnd(ActionExec& thread) { #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_END)); #endif log_error (_("%s: CHECKME: was broken"), __PRETTY_FUNCTION__); thread.skipRemainingBuffer(); } void ActionNextFrame(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_NEXTFRAME)); #endif DisplayObject* tgtch = env.target(); MovieClip* tgt = tgtch ? tgtch->to_movie() : nullptr; if (tgt) tgt->goto_frame(tgt->get_current_frame() + 1); else { log_debug("ActionNextFrame: as_environment target is null " "or not a sprite"); } } void ActionPrevFrame(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_PREVFRAME)); #endif DisplayObject* tgtch = env.target(); MovieClip* tgt = tgtch ? tgtch->to_movie() : nullptr; if (tgt) tgt->goto_frame(tgt->get_current_frame() - 1); else log_debug("ActionPrevFrame: as_environment target is null or not a sprite"); } void ActionPlay(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_PLAY)); #endif DisplayObject* tgtch = env.target(); MovieClip* tgt = tgtch ? tgtch->to_movie() : nullptr; if (tgt) tgt->setPlayState(MovieClip::PLAYSTATE_PLAY); else log_debug("ActionPlay: as_environment target is null or not a sprite"); } void ActionStop(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_STOP)); #endif DisplayObject* tgtch = env.target(); MovieClip* tgt = tgtch ? tgtch->to_movie() : nullptr; if (tgt) tgt->setPlayState(MovieClip::PLAYSTATE_STOP); else log_debug("ActionStop: as_environment target is null or not a sprite"); } void ActionToggleQuality(ActionExec& thread) { movie_root& mr = getRoot(thread.env); if (mr.getQuality() != QUALITY_HIGH) { mr.setQuality(QUALITY_HIGH); return; } mr.setQuality(QUALITY_LOW); } void ActionStopSounds(ActionExec& thread) { #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_STOPSOUNDS)); #endif VM& vm = getVM(thread.env); sound::sound_handler* s = vm.getRoot().runResources().soundHandler(); if (s) { s->stop_all_sounds(); } } void ActionGotoFrame(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_GOTOFRAME)); #endif size_t frame = code.read_int16(thread.getCurrentPC() + 3); DisplayObject* tgtch = env.target(); MovieClip* tgt = tgtch ? tgtch->to_movie() : nullptr; // frame number within this tag is hard-coded and 0-based if (tgt) tgt->goto_frame(frame); else { log_debug("ActionGotoFrame: as_environment target is null " "or not a sprite"); } } void ActionGetUrl(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_GETURL)); #endif size_t pc = thread.getCurrentPC(); // If this is an FSCommand, then call the callback // handler, if any. // Two strings as args. // TODO: make sure the NULL terminations are there // we could implement a safe_read_string(pc, maxlen) // and use tag length as maxlen //size_t tag_length = code.read_int16(pc+1); const char* url = code.read_string(pc+3); size_t urlLength = strlen(url)+1; // Will abort if code.read_string returns 0, but action // buffer should always have a null terminator at the // end. This replaces an assertion in commonGetURL. const std::string target(code.read_string(pc + 3 + urlLength)); IF_VERBOSE_ACTION ( log_action(_("GetUrl: target=%s URL=%s"), target, url); ); commonGetURL(env, target, url, 0u); } void ActionWaitForFrame(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_WAITFORFRAME)); #endif // SWF integrity check const size_t tag_len = code.read_int16(thread.getCurrentPC() + 1); if (tag_len != 3) { IF_VERBOSE_MALFORMED_SWF ( log_swferror(_("ActionWaitForFrame (0x%X) tag length == %d " "(expected 3)"), SWF::ACTION_WAITFORFRAME, tag_len); ); } // If we haven't loaded a specified frame yet, then // skip the specified number of actions. // unsigned int framenum = code.read_int16(thread.getCurrentPC()+3); std::uint8_t skip = code[thread.getCurrentPC()+5]; DisplayObject* target = env.target(); MovieClip* target_sprite = target ? target->to_movie() : nullptr; if (!target_sprite) { log_error(_("%s: environment target is null or not a MovieClip"), __FUNCTION__); return; } const size_t totframes = target_sprite->get_frame_count(); if (framenum > totframes) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionWaitForFrame(%d): " "target (%s) has only %d frames"), framenum, totframes); ); framenum = totframes; } // Actually *wait* for target frame, and never skip any action const size_t lastloaded = target_sprite->get_loaded_frames(); if (lastloaded < framenum) { // better delegate this to ActionExec thread.skip_actions(skip); } } void ActionSetTarget(ActionExec& thread) { const action_buffer& code = thread.code; size_t pc = thread.getCurrentPC(); #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_SETTARGET)); // 0x8B #endif // Change the movie we're working on. const std::string target_name(code.read_string(pc+3)); commonSetTarget(thread, target_name); } void ActionGotoLabel(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; const char* frame_label = code.read_string(thread.getCurrentPC()+3); DisplayObject *target = env.target(); MovieClip *target_sprite = target ? target->to_movie() : nullptr; if (!target_sprite) { log_error(_("GotoLabel: environment target is null or not a " "MovieClip")); } else { target_sprite->goto_labeled_frame(frame_label); } } void ActionAdd(ActionExec& thread) { as_environment& env = thread.env; const double operand2 = toNumber(env.top(0), getVM(env)); const double operand1 = toNumber(env.top(1), getVM(env)); env.top(1) = operand1 + operand2; env.drop(1); } void ActionSubtract(ActionExec& thread) { as_environment& env = thread.env; subtract(env.top(1), env.top(0), getVM(env)); env.drop(1); } void ActionMultiply(ActionExec& thread) { as_environment& env = thread.env; const double operand2 = toNumber(env.top(0), getVM(env)); const double operand1 = toNumber(env.top(1), getVM(env)); env.top(1) = operand1 * operand2; env.drop(1); } // Negative number / 0: -infinity // Positive number / 0: infinity // 0 / 0 : NaN // Either operand is NaN: NaN void ActionDivide(ActionExec& thread) { as_environment& env = thread.env; const double operand2 = toNumber(env.top(0), getVM(env)); const double operand1 = toNumber(env.top(1), getVM(env)); if (operand2 == 0) { if (env.get_version() < 5) { env.top(1).set_string("#ERROR#"); } else if (operand1 == 0 || isNaN(operand1) || isNaN(operand2)) { setNaN(env.top(1)); } else { // Division by -0.0 is not possible in AS, so // the sign of the resulting infinity should match the // sign of operand1. Division by 0 in C++ is undefined // behaviour. env.top(1) = operand1 < 0 ? - std::numeric_limits::infinity() : std::numeric_limits::infinity(); } } else { env.top(1) = operand1 / operand2; } env.drop(1); } void ActionEqual(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_EQUAL)); // 0x0E #endif const double op1 = toNumber(env.top(0), getVM(env)); const double op2 = toNumber(env.top(1), getVM(env)); env.top(1).set_bool(op2 == op1); // Flash4 used 1 and 0 as return from this tag if (env.get_version() < 5) convertToNumber(env.top(1), getVM(env)); env.drop(1); } void ActionLessThan(ActionExec& thread) { as_environment& env = thread.env; // NB: this unusual order is correct! const double d2 = toNumber(env.top(1), getVM(env)); const double d1 = toNumber(env.top(0), getVM(env)); env.top(1).set_bool(d2 < d1); // Flash4 used 1 and 0 as return from this tag if (env.get_version() < 5) convertToNumber(env.top(1), getVM(env)); env.drop(1); } void ActionLogicalAnd(ActionExec& thread) { as_environment& env = thread.env; // Note: the order of evaluation of the && operands is specified. env.top(1).set_bool(toBool(env.top(1), getVM(env)) && toBool(env.top(0), getVM(env))); env.drop(1); } void ActionLogicalOr(ActionExec& thread) { as_environment& env = thread.env; // Note: the order of evaluation of the || operands is specified. env.top(1).set_bool(toBool(env.top(1), getVM(env)) || toBool(env.top(0), getVM(env))); env.drop(1); } void ActionLogicalNot(ActionExec& thread) { as_environment& env = thread.env; env.top(0).set_bool(!toBool(env.top(0), getVM(env))); // Flash4 used 1 and 0 as return from this tag if (env.get_version() < 5) convertToNumber(env.top(0), getVM(env)); } void ActionStringEq(ActionExec& thread) { as_environment& env = thread.env; const int version = env.get_version(); const std::string& str0 = env.top(0).to_string(version); const std::string& str1 = env.top(1).to_string(version); env.top(1).set_bool(str0 == str1); env.drop(1); } void ActionStringLength(ActionExec& thread) { as_environment& env = thread.env; // NOTE: I've tested that we should change behaviour // based on code definition version, not top-level // SWF version. Just not automated yet. // const int version = thread.code.getDefinitionVersion(); if (version > 5) { // when SWF version is > 5 we compute the multi-byte length ActionMbLength(thread); } else { env.top(0).set_double(env.top(0).to_string(version).size()); } } void ActionSubString(ActionExec& thread) { // substring("string", base, size) // SWF4 function, deprecated in favour of String.substring. // 1-based (String object methods are 0-based). as_environment& env = thread.env; const as_value& strval = env.top(2); // Undefined values should resolve to 0. int size = toInt(env.top(0), getVM(env)); int start = toInt(env.top(1), getVM(env)); const int version = env.get_version(); const std::wstring wstr = utf8::decodeCanonicalString( strval.to_string(version), version); if (size < 0) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Negative size passed to ActionSubString, " "taking as whole length")); ); size = wstr.length(); } if (size == 0 || wstr.empty()) { env.drop(2); env.top(0).set_string(""); return; } // TODO: if 'start' or 'size' do not evaluate to numbers return // the empty string (how do we check if they evaluate ??) if (start < 1) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("Start is less then 1 in ActionSubString, " "setting to 1.")); ); start = 1; } // If start is longer than the string length, return empty // string else if (static_cast(start) > wstr.length()) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("Start goes beyond input string in ActionSubString, " "returning the empty string.")); ); env.drop(2); env.top(0).set_string(""); return; } // Adjust the start for our own use. --start; if (static_cast(start + size) > wstr.length()) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("start + size goes beyond input string in " "ActionSubString, adjusting size")); ); size = wstr.length() - start; } #if GNASH_PARANOIA_LEVEL > 1 assert(start >= 0); assert(static_cast(start) < wstr.length() ); assert(size >= 0); #endif env.drop(2); env.top(0).set_string(utf8::encodeCanonicalString( wstr.substr(start, size), version)); } void ActionPop(ActionExec& thread) { as_environment& env = thread.env; env.drop(1); } void ActionInt(ActionExec& thread) { as_environment& env = thread.env; env.top(0).set_double(toInt(env.top(0), getVM(env))); } void ActionGetVariable(ActionExec& thread) { as_environment& env = thread.env; as_value& top_value = env.top(0); const std::string& var_string = top_value.to_string(); if (var_string.empty()) { top_value.set_undefined(); return; } top_value = thread.getVariable(var_string); if (env.get_version() < 5 && top_value.is_sprite()) { // See http://www.ferryhalim.com/orisinal/g2/penguin.htm IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Can't assign a sprite/DisplayObject to a " "variable in SWF%d. We'll return undefined instead of %s."), env.get_version(), top_value); ); top_value.set_undefined(); } IF_VERBOSE_ACTION( log_action(_("-- get var: %s=%s"), var_string, top_value); ); } void ActionSetVariable(ActionExec& thread) { as_environment& env = thread.env; const std::string& name = env.top(1).to_string(); if (name.empty()) { IF_VERBOSE_ASCODING_ERRORS ( // Invalid object, can't set. log_aserror(_("ActionSetVariable: %s=%s: variable name " "evaluates to invalid (empty) string"), env.top(1), env.top(0)); ); } thread.setVariable(name, env.top(0)); IF_VERBOSE_ACTION( log_action(_("-- set var: %s = %s"), name, env.top(0)); ); env.drop(2); } void ActionSetTargetExpression(ActionExec& thread) { as_environment& env = thread.env; // we don't ues the target sprite directly, instead we fetch the // _target(string type) of that sprite first and then search the // final target(might be a different one). See tests in // opcode_guard_test2.sc // // For _versioned, see swfdec's settarget2-tostring.as (swf 7 and 8) // std::string target_name = env.top(0).to_string(env.get_version()); commonSetTarget(thread, target_name); env.drop(1); // pop the target sprite off the stack } void ActionStringConcat(ActionExec& thread) { as_environment& env = thread.env; const int version = getSWFVersion(env); const std::string& op1 = env.top(0).to_string(version); const std::string& op2 = env.top(1).to_string(version); env.top(1).set_string(op2 + op1); env.drop(1); } void ActionGetProperty(ActionExec& thread) { as_environment& env = thread.env; as_value& tgt_val = env.top(1); std::string tgt_str = tgt_val.to_string(); DisplayObject* target; if (tgt_str.empty()) { target = get(thread.getTarget()); if (!target) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("ActionGetProperty() called, but " "current target is not a DisplayObject")); ); } } else { target = findTarget(env, tgt_str); } // FIXME: what happens when it's an invalid number? This will cause // undefined behaviour on overflow. unsigned int prop_number = toNumber(env.top(0), getVM(env)); if (target) { getIndexedProperty(prop_number, *target, env.top(1)); } else { // ASCODING error ? (well, last time it was a gnash error ;) IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Could not find GetProperty target (%s)"), tgt_val); ); env.top(1) = as_value(); } env.drop(1); } void ActionSetProperty(ActionExec& thread) { as_environment& env = thread.env; DisplayObject *target = findTarget(env, env.top(2).to_string()); // FIXME: what happens when it's an invalid number? This will cause // undefined behaviour on overflow. unsigned int prop_number = toNumber(env.top(1), getVM(env)); as_value prop_val = env.top(0); if (target) { setIndexedProperty(prop_number, *target, prop_val); } else { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionSetProperty: can't find target %s for " "setting property %s"), env.top(2), prop_number); ) } env.drop(3); } void ActionDuplicateClip(ActionExec& thread) { as_environment& env = thread.env; // Movies should be attachable from -16384 to 2130690044. See // Tests in misc-ming.all/DepthLimitsTest.c. const double depth = toNumber(env.top(0), getVM(env)) + DisplayObject::staticDepthOffset; // This also checks for overflow, as both numbers are expressible as // std::int32_t. if (depth < DisplayObject::lowerAccessibleBound || depth > DisplayObject::upperAccessibleBound) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("duplicateMovieClip: invalid depth %d passed; " "not duplicating"), depth); ); env.drop(3); return; } std::int32_t depthValue = static_cast(depth); const std::string& newname = env.top(1).to_string(); const std::string& path = env.top(2).to_string(); DisplayObject* ch = findTarget(env, path); if (!ch) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Path given to duplicateMovieClip(%s) doesn't " "point to a DisplayObject"), path); ); env.drop(3); return; } MovieClip* sprite = ch->to_movie(); if (!sprite) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Path given to duplicateMovieClip(%s) is not a sprite"), path); ); env.drop(3); return; } sprite->duplicateMovieClip(newname, depthValue); env.drop(3); } void ActionRemoveClip(ActionExec& thread) { as_environment& env = thread.env; const std::string path = env.pop().to_string(); DisplayObject* ch = findTarget(env, path); if (!ch) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Path given to removeMovieClip(%s) doesn't " "point to a DisplayObject"), path); ); return; } MovieClip* sprite = ch->to_movie(); if (!sprite) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Path given to removeMovieClip(%s) is not a sprite"), path); ); return; } sprite->removeMovieClip(); } /// \brief Trace messages from the Flash movie using trace(); void ActionTrace(ActionExec& thread) { as_environment& env = thread.env; const std::string val = env.pop().to_string(); // Logging with a std::string here fails the swfdec testsuite, // probably because the first 0 character terminates the output // with a c_str, whereas a std::string outputs the entire length // of the string. log_trace("%s", val.c_str()); } void ActionStartDragMovie(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_STARTDRAGMOVIE)); #endif DisplayObject* tgt = findTarget(env, env.top(0).to_string()); if (tgt) { // mark this DisplayObject as script transformed. tgt->transformedByScript(); } else { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("startDrag: unknown target '%s'"), env.top(0)); ); } const bool lock = toBool(env.top(1), getVM(env)); DragState st(tgt, lock); // toNumber because we found out that ming writes "0" for the third // argument, and this converts to true when converted to a bool when // it should actually convert to false! if (toNumber(env.top(2), getVM(env))) { std::int32_t y1 = pixelsToTwips(toNumber(env.top(3), getVM(env))); std::int32_t x1 = pixelsToTwips(toNumber(env.top(4), getVM(env))); std::int32_t y0 = pixelsToTwips(toNumber(env.top(5), getVM(env))); std::int32_t x0 = pixelsToTwips(toNumber(env.top(6), getVM(env))); // check for swapped values if (y1 < y0) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Y values in ActionStartDrag swapped, fixing")); ); std::swap(y1, y0); } if (x1 < x0) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("X values in ActionStartDrag swapped, fixing")); ); std::swap(x1, x0); } const SWFRect bounds(x0, y0, x1, y1); st.setBounds(bounds); env.drop(4); } env.drop(3); if (tgt) { VM& vm = getVM(env); vm.getRoot().setDragState(st); } } void ActionStopDragMovie(ActionExec& thread) { as_environment& env = thread.env; // Previously this checked the target, but manual tests show no // need to do that; the drag is always stopped. getRoot(env).stop_drag(); } void ActionStringCompare(ActionExec& thread) { as_environment& env = thread.env; const int ver = env.get_version(); const std::string& op1 = env.top(0).to_string(ver); const std::string& op2 = env.top(1).to_string(ver); env.top(1).set_bool(op2 < op1); env.drop(1); } void ActionThrow(ActionExec& thread) { as_environment& env = thread.env; // Throw the value on the top of the stack. env.top(0).flag_exception(); // Proceed to the end of the code block to throw. thread.skipRemainingBuffer(); } void ActionCastOp(ActionExec& thread) { as_environment& env = thread.env; // Get the "instance" as_object* instance = safeToObject(getVM(thread.env), env.top(0)); // Get the "super" function as_object* super = safeToObject(getVM(thread.env), env.top(1)); // Invalid args! if (!super || !instance) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("-- %s cast_to %s (invalid args?)"), env.top(1), env.top(0)); ); env.drop(1); env.top(0).set_null(); return; } env.drop(1); if (instance->instanceOf(super)) { env.top(0) = as_value(instance); } else { env.top(0).set_null(); } } /// Implements the "implements" opcode // /// Example AS code: "C implements B;" // /// The above code makes an instance of C ("var c = new C();") return true /// for "c instanceOf B);". That seems to be the end of its usefulness, as /// c inherits no properties from B. void ActionImplementsOp(ActionExec& thread) { as_environment& env = thread.env; as_value objval = env.pop(); as_object* obj = safeToObject(getVM(thread.env), objval); int count = toNumber(env.pop(), getVM(env)); if (!obj) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Stack value on IMPLEMENTSOP is not an object: %s."), objval); ); return; } as_value protoval; if (!obj->get_member(NSV::PROP_PROTOTYPE, &protoval)) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Target object for IMPLEMENTSOP has no prototype.")); ); return; } obj = safeToObject(getVM(thread.env), protoval); if (!obj) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("IMPLEMENTSOP target object's prototype is not " "an object (%s)"), protoval); ); return; } if (count <= 0) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Invalid interfaces count (%d) on IMPLEMENTSOP"), count); ); return; } while (count--) { as_value ctorval = env.pop(); as_object* ctor = safeToObject(getVM(thread.env), ctorval); if (!ctor) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("class found on stack on IMPLEMENTSOP is " "not an object: %s"), ctorval); ); continue; } if (!ctor->get_member(NSV::PROP_PROTOTYPE, &protoval)) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Interface object for IMPLEMENTSOP has no " "prototype.")); ); continue; } as_object *inter = safeToObject(getVM(thread.env), protoval); if (!inter) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Prototype of interface object for " "IMPLEMENTSOP is not an object (%s)."), protoval); ); continue; } IF_VERBOSE_ACTION( log_action(_("%s (with .prototype %p) implements %s (with " ".prototype %p)"), objval, (void*)obj, ctorval, (void*)inter); ); obj->addInterface(inter); } } /// FsCommand2 is not part of the Flash spec. It belongs to /// Flash Lite (from 1.1 onwards) and is used to control /// devices (backlight, vibrate etc). void ActionFscommand2(ActionExec& thread) { #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_FSCOMMAND2)); // 0x0E #endif as_environment& env = thread.env; unsigned int off=0; const unsigned int nargs = toInt(env.top(off++), getVM(env)); std::string cmd = env.top(off++).to_string(); std::ostringstream ss; ss << cmd << "("; for (unsigned int i = 1; i < nargs; ++i) { as_value arg = env.top(off++); if ( i > 1 ) ss << ", "; ss << arg; } ss << ")"; LOG_ONCE(log_unimpl(_("fscommand2:%s"), ss.str()) ); // TODO: check wheter or not we should drop anything from // the stack, some reports and the Canonical tests // suggest we shoudn't } void ActionRandom(ActionExec& thread) { as_environment& env = thread.env; // Action random(n) should return an integer from 0 up to (not // including) n. // It was introduced in SWF4 and deprecated in favour of // Math.random() in SWF5. int max = toInt(env.top(0), getVM(env)); if (max < 1) max = 1; // Get pointer to static random generator in VM VM::RNG& rnd = getVM(env).randomNumberGenerator(); // Produces int (0 <= n <= max - 1) boost::uniform_int<> uni_dist(0, max - 1); boost::variate_generator > uni(rnd, uni_dist); env.top(0).set_double(uni()); } void ActionMbLength(ActionExec& thread) { as_environment& env = thread.env; std::string str = env.top(0).to_string(); if (str.empty()) { env.top(0).set_double(0); } else { int length; std::vector unused; unused.resize(str.length()+1); utf8::guessEncoding(str, length, unused); env.top(0).set_double(length); } } void ActionOrd(ActionExec& thread) { as_environment& env = thread.env; // Should return 0 const int swfVersion = thread.code.getDefinitionVersion(); std::string str = env.top(0).to_string(); if (str.empty()) { env.top(0).set_double(0); return; } std::wstring wstr = utf8::decodeCanonicalString(str, swfVersion); // decodeCanonicalString should correctly work out what the first // character is according to version. env.top(0).set_double(wstr.at(0)); } void ActionChr(ActionExec& thread) { as_environment& env = thread.env; // This is UB: const std::uint16_t c = toInt(env.top(0), getVM(env)); // If the argument to chr() is '0', we return // nothing, not NULL if (c == 0) { env.top(0).set_string(""); return; } const int swfVersion = thread.code.getDefinitionVersion(); if (swfVersion > 5) { env.top(0).set_string(utf8::encodeUnicodeCharacter(c)); return; } // SWF 5 only: // This casts to unsigned char to a string, giving // IS0-8859-1 8-bit characters. // Values above 256 evaluate to value % 256, // through the cast, which is expected behaviour. const unsigned char uc = static_cast(c); if (uc == 0) { env.top(0).set_string(""); return; } env.top(0).set_string(std::string(1, uc)); } void ActionGetTimer(ActionExec& thread) { as_environment& env = thread.env; const VM& vm = getVM(env); env.push(vm.getTime()); } void ActionMbSubString(ActionExec& thread) { as_environment& env = thread.env; const as_value& arg0 = env.top(0); const as_value& arg1 = env.top(1); // Undefined values should resolve to 0. int size = toInt(env.top(0), getVM(env)); int start = toInt(env.top(1), getVM(env)); as_value& string_val = env.top(2); IF_VERBOSE_ACTION( log_action(_(" ActionMbSubString(%s, %d, %d)"), string_val, arg0, arg1); ); env.drop(2); const int version = env.get_version(); std::string str = string_val.to_string(version); int length = 0; std::vector offsets; utf8::EncodingGuess encoding = utf8::guessEncoding(str, length, offsets); if (size < 0) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Negative size passed to ActionSubString, " "taking as whole length")); ); size = length; } if (start < 1) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Base is less then 1 in ActionMbSubString, " "setting to 1.")); ); start = 1; } else if ( start > length) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("base goes beyond input string in ActionMbSubString, " "returning the empty string.")); ); env.top(0).set_string(""); return; } // Adjust the start for our own use. --start; if (size + start > length) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("base+size goes beyond input string in ActionMbSubString, " "adjusting size based on length:%d and start:%d"), length, start); ); size = length - start; } if (encoding == utf8::ENCGUESS_OTHER) { env.top(0).set_string(str.substr(start, size)); } else { env.top(0).set_string(str.substr(offsets.at(start), offsets.at(start + size) - offsets.at(start))); } } void ActionMbOrd(ActionExec& thread) { /// This only deals with UTF-8 characters. /// TODO: what else is possible? /// TODO: fix for SWF5 as_environment& env = thread.env; if (env.get_version() == 5) { log_unimpl(_("Not properly implemented for SWF5")); // No need to return - it works a bit. } const std::string s = env.top(0).to_string(); std::string::const_iterator it = s.begin(), e = s.end(); std::uint32_t out = utf8::decodeNextUnicodeCharacter(it, e); /// Always valid, or can it be undefined? env.top(0).set_double(out); } void ActionMbChr(ActionExec& thread) { /// This only generates UTF-8 characters. No idea /// what difference user locale might make, but UTF-8 /// is generally GOOD. /// TODO: fix for SWF5 as_environment& env = thread.env; if (env.get_version() == 5) { log_unimpl(_("Not properly implemented for SWF5")); // No need to return. } // This is UB const std::uint16_t i = toInt(env.top(0), getVM(env)); std::string out = utf8::encodeUnicodeCharacter(i); /// Always valid, or can it be undefined? env.top(0).set_string(out); } /// Sets strict mode in the compiler. // /// This is irrelevant for execution, but included for completeness. void ActionStrictMode(ActionExec& thread) { const action_buffer& code = thread.code; // off if 0, on for anything else. const bool on = code[thread.getCurrentPC() + 3]; IF_VERBOSE_ACTION( log_action(_("ActionStrictMode set to %1%"), on); ); } // also known as WaitForFrame2 void ActionWaitForFrameExpression(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; // how many actions to skip if frame has not been loaded const std::uint8_t skip = code[thread.getCurrentPC() + 3]; // env.top(0) contains frame specification, // evaluated as for ActionGotoExpression as_value framespec = env.pop(); DisplayObject* tgtch = env.target(); MovieClip* target_sprite = tgtch ? tgtch->to_movie() : nullptr; if (!target_sprite) { log_error(_("%s: environment target is null or not a MovieClip"), __FUNCTION__); return; } size_t framenum; if (!target_sprite->get_frame_number(framespec, framenum)) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Frame spec found on stack " "at ActionWaitForFrame doesn't evaluate " "to a valid frame: %s"), framespec); ); return; } const size_t lastloaded = target_sprite->get_loaded_frames(); if (lastloaded < framenum) { // better delegate this to ActionExec thread.skip_actions(skip); } } void pushConstant(ActionExec& thread, unsigned int id) { as_environment& env = thread.env; /* const action_buffer& code = thread.code; */ const ConstantPool *pool = getVM(env).getConstantPool(); if ( ! pool ) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Unknown constant '%1%' (no pool registered with VM)"), id); ); env.push(as_value()); return; } if ( id >= pool->size() ) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Unknown constant '%1%' (registered pool has %2% entries)"), id, pool->size()); ); env.push(as_value()); return; } env.push( (*pool)[id] ); } void ActionPushData(ActionExec& thread) { as_environment& env = thread.env; enum { pushString, pushFloat, pushNull, pushUndefined, pushRegister, pushBool, pushDouble, pushInt32, pushDict8, pushDict16 }; const char* pushType[] = { "string", "float", "null", "undefined", "register", "bool", "double", "int", "dict8", "dict16" }; const action_buffer& code = thread.code; const size_t pc = thread.getCurrentPC(); const std::uint16_t length = code.read_uint16(pc + 1); //--------------- size_t i = pc; size_t count = 0; while (i - pc < length) { const std::uint8_t type = code[3 + i]; ++i; switch (type) { default: { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Unknown push type %d. Execution will " "continue but it is likely to fail due to lost " "sync."), +type); ); continue; } case pushString: // 0 { const std::string str(code.read_string(i + 3)); i += str.size() + 1; env.push(str); break; } case pushFloat: // 1 { const float f = code.read_float_little(i + 3); i += 4; env.push(f); break; } case pushNull: // 2 { as_value nullvalue; nullvalue.set_null(); env.push(nullvalue); break; } case pushUndefined: // 3 env.push(as_value()); break; case pushRegister: // 4 { const size_t reg = code[3 + i]; ++i; const as_value* v = getVM(env).getRegister(reg); if (!v) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Invalid register %d in ActionPush"), reg); ); env.push(as_value()); } else env.push(*v); break; } case pushBool: // 5 { const bool bool_val = code[i + 3]; ++i; env.push(bool_val); break; } case pushDouble: // 6 { const double d = code.read_double_wacky(i + 3); i += 8; env.push(d); break; } case pushInt32: // 7 { const std::int32_t val = code.read_int32(i + 3); i += 4; env.push(val); break; } case pushDict8: // 8 { const std::uint8_t id = code[3 + i]; ++i; pushConstant(thread, id); break; } case pushDict16: // 9 { const std::uint16_t id = code.read_int16(i + 3); i += 2; pushConstant(thread, id); break; } } IF_VERBOSE_ACTION( log_action(_("\t%d) type=%s, value=%s"), count, pushType[type], env.top(0)); ++count; ); } } void ActionBranchAlways(ActionExec& thread) { std::int16_t offset = thread.code.read_int16(thread.getCurrentPC()+3); thread.adjustNextPC(offset); // @@ TODO range checks } void ActionGetUrl2(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_GETURL2)); #endif const std::uint8_t method = code[thread.getCurrentPC() + 3]; as_value url_val = env.top(1); if (url_val.is_undefined()) { log_error(_("Undefined GetUrl2 URL on stack, skipping")); } else { const std::string& url = url_val.to_string(); commonGetURL(env, env.top(0), url, method); } env.drop(2); } void ActionBranchIfTrue(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; size_t pc = thread.getCurrentPC(); size_t nextPC = thread.getNextPC(); size_t stopPC = thread.getStopPC(); #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_BRANCHIFTRUE)); #endif std::int16_t offset = code.read_int16(pc+3); const bool test = toBool(env.pop(), getVM(env)); if (test) { thread.adjustNextPC(offset); if (nextPC > stopPC) { IF_VERBOSE_MALFORMED_SWF ( log_swferror(_("branch to offset %d -- " " this section only runs to %d"), nextPC, stopPC); ) } } } void ActionCallFrame(ActionExec& thread) { as_environment& env = thread.env; const std::string& target_frame = env.top(0).to_string(); std::string target_path; std::string frame_var; DisplayObject* target = nullptr; if (parsePath(target_frame, target_path, frame_var)) { target = findTarget(env, target_path); } else { frame_var = target_frame; target = env.target(); } env.drop(1); MovieClip* target_sprite = target ? target->to_movie() : nullptr; if (target_sprite) { target_sprite->call_frame_actions(frame_var); } else { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("Couldn't find target_sprite \"%s\" in ActionCallFrame!" " target frame actions will not be called..."), target_path); ) } } void ActionGotoExpression(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; size_t pc = thread.getCurrentPC(); // From Alexis SWF ref: // // Pop a value or a string and jump to the specified // frame. When a string is specified, it can include a // path to a sprite as in: // // /Test:55 // // When f_play is ON, the action is to play as soon as // that frame is reached. Otherwise, the // frame is shown in stop mode. unsigned char play_flag = code[pc + 3]; const MovieClip::PlayState state = play_flag ? MovieClip::PLAYSTATE_PLAY : MovieClip::PLAYSTATE_STOP; std::string target_frame = env.pop().to_string(); std::string target_path; std::string frame_var; DisplayObject* target = nullptr; if (parsePath(target_frame, target_path, frame_var)) { target = findTarget(env, target_path); } // 4.11 would make parsePath above return true, // we should check if a sprite named '4' is supposed to work // in that case if (!target) { target = env.target(); frame_var = target_frame; } MovieClip *target_sprite = target ? target->to_movie() : nullptr; if (target_sprite) { size_t frame_number; if (!target_sprite->get_frame_number(frame_var, frame_number)) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Frame spec found on stack " "at ActionGotoExpression doesn't evaluate " "to a valid frame: %s"), target_frame); ); return; } target_sprite->goto_frame(frame_number); target_sprite->setPlayState(state); } else { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("Couldn't find target sprite \"%s\" in " "ActionGotoExpression. Will not go to target frame..."), target_frame); ) } } void ActionDelete(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_DELETE)); // 0x3A #endif // Stack Result // 'a' | nothing // 'a.b' | a.b deleted. // 'b' | a [obj] a.b deleted (normal use). // 'a.b' | a [obj] nothing. // 'a.b' | 'string' nothing. // 'c' | a.b [obj] a.b.c deleted (normal use). // 'a.b.c' | a.b.c deleted // 'b.c' | a [obj] nothing // 'a.b.c' | string nothing const size_t stackSize = env.stack_size(); const int version = getSWFVersion(env); std::string propertyname = env.top(0).to_string(); as_object* obj(nullptr); // Behaviour is different according to version. For SWF7 and above, // the delete fails if there aren't two items on the stack. For SWF6 // and below, a single item should be parsed to see if it's a path, // then we try to delete it. If it's not a path, we try to delete it as // a variable. // // In both cases, if there are two or more items on the stack, they // have to be property and object. if (stackSize < 2) { if (version > 6) { env.top(1).set_bool(false); env.drop(1); return; } std::string path, var; if (!parsePath(propertyname, path, var)) { // It's not a path. For SWF 7 and above, don't delete. Otherwise // assume it's a variable and try to delete. env.top(1).set_bool(thread.delVariable(propertyname)); env.drop(1); return; } else { as_value target = thread.getVariable(path); // Don't create an object! Only get the value if it is an object // already. if (target.is_object()) { obj = safeToObject(getVM(thread.env), target); propertyname = var; } } } else { // Don't create an object! Only get the value if it is an object // already. if (env.top(1).is_object()) { obj = safeToObject(getVM(thread.env), env.top(1)); } } if (!obj) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("delete %s.%s: no object found to delete"), env.top(1), env.top(0)); ); env.top(1).set_bool(false); env.drop(1); return; } VM& vm = getVM(env); const std::pair ret = obj->delProperty(getURI(vm, propertyname)); env.top(1).set_bool(ret.second); env.drop(1); } void ActionDelete2(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_DELETE2)); // 0x3B #endif const std::string& propertyname = env.top(0).to_string(); // If it's not a path, delete it as a variable. std::string path, var; if (!parsePath(propertyname, path, var)) { // See bug #18482, this works fine now (assuming the bug // report is correct) env.top(0) = thread.delVariable(propertyname); return; } // Otherwise see if it's an object and delete it. as_value target = thread.getVariable(path); if (!target.is_object()) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("delete2 called with a path that does not resolve " "to an object"), env.top(1), env.top(0)); ); env.top(1).set_bool(false); env.drop(1); return; } as_object* obj = safeToObject(getVM(thread.env), target); VM& vm = getVM(env); const std::pair ret = obj->delProperty(getURI(vm, var)); env.top(1).set_bool(ret.second); } void ActionVarEquals(ActionExec& thread) { as_environment& env = thread.env; as_value& value = env.top(0); as_value& varname = env.top(1); thread.setLocalVariable(varname.to_string(), value); IF_VERBOSE_ACTION( log_action(_("-- set local var: %s = %s"), varname.to_string(), value); ); env.drop(2); } void ActionCallFunction(ActionExec& thread) { as_environment& env = thread.env; // Let's consider it a as a string and lookup the function. // // Note: this produces the correct behaviour: Any as_value, including // null or numbers, are converted to a string and the corresponding // function is called. If it is undefined, nothing happens, even if // there is a function called 'undefined'. // // In all cases, even undefined, the specified number of arguments // is dropped from the stack. const std::string& funcname = env.pop().to_string(); as_object* super(nullptr); as_object* this_ptr; as_value function = thread.getVariable(funcname, &this_ptr); if (!function.is_object()) { // In this case the call to invoke() will fail. We won't return // because we still need to handle the stack, and the attempt to // convert the value to an object may have effects in AS (haven't // checked). IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("ActionCallFunction: %s is not an object"), funcname); ) } else if (!function.is_function()) { as_object* obj = toObject(function, getVM(thread.env)); super = obj->get_super(); this_ptr = thread.getThisPointer(); } // Get number of args, modifying it if not enough values are on the stack. // TODO: this may cause undefined behaviour if the number on the stack // is too large. Fix it. size_t nargs = toNumber(env.pop(), getVM(env)); const size_t available_args = env.stack_size(); if (available_args < nargs) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Attempt to call a function with %u arguments " "while only %u are available on the stack."), nargs, available_args); ); nargs = available_args; } fn_call::Args args; for (size_t i = 0; i < nargs; ++i) { args += env.pop(); } as_value result = invoke(function, env, this_ptr, args, super, &(thread.code.getMovieDefinition())); env.push(result); // If the function threw an exception, do so here. if (result.is_exception()) { thread.skipRemainingBuffer(); } } void ActionReturn(ActionExec& thread) { as_environment& env = thread.env; // Put top of stack in the provided return slot, if // it's not NULL. thread.pushReturn(env.top(0)); env.drop(1); // Skip the rest of this buffer (return from this action_buffer). thread.skipRemainingBuffer(); } void ActionModulo(ActionExec& thread) { as_environment& env = thread.env; const double y = toNumber(env.pop(), getVM(env)); const double x = toNumber(env.pop(), getVM(env)); // Don't need to check for y being 0 here - if it's zero, // fmod returns NaN, which is what flash would do too as_value result = std::fmod(x, y); env.push(result); } void ActionNew(ActionExec& thread) { as_environment& env = thread.env; as_value val = env.pop(); const std::string& classname = val.to_string(); IF_VERBOSE_ACTION ( log_action(_("---new object: %s"), classname); ); unsigned nargs = toNumber(env.pop(), getVM(env)); as_value constructorval = thread.getVariable(classname); as_function* constructor = constructorval.to_function(); if (!constructor) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionNew: " "'%s' is not a constructor"), classname); ); env.drop(nargs); env.push(as_value()); // should we push an object anyway ? return; } // It is possible for constructors to fail, for instance if a // conversion to object calls a built-in constructor that has been // deleted. BitmapData also fails to construct anything under // some circumstances. try { as_object* newobj = construct_object(constructor, env, nargs); env.push(newobj); return; } catch (const GnashException& ) { env.push(as_value()); return; } } void ActionVar(ActionExec& thread) { as_environment& env = thread.env; const std::string& varname = env.top(0).to_string(); const ObjectURI& name = getURI(getVM(env), varname); VM& vm = getVM(env); if (vm.calling()) { declareLocal(vm.currentCall(), name); } else { // See https://savannah.gnu.org/patch/?8721 as_object* this_ptr = thread.getThisPointer(); if (!hasOwnProperty(*this_ptr, name)) { this_ptr->set_member(name, as_value()); } } env.drop(1); } void ActionInitArray(ActionExec& thread) { as_environment& env = thread.env; const int array_size = toInt(env.pop(), getVM(env)); assert(array_size >= 0); // TODO: trigger this !! Global_as& gl = getGlobal(env); as_object* ao = gl.createArray(); VM& vm = getVM(env); // Fill the elements with the initial values from the stack. for (int i = 0; i < array_size; i++) { const ObjectURI& k = getURI(vm, std::to_string(i)); ao->set_member(k, env.pop()); } env.push(ao); } void ActionInitObject(ActionExec& thread) { as_environment& env = thread.env; // // SWFACTION_PUSH // [000] Constant: 1 "obj" // [001] Constant: 0 "member" <-- we handle up to here // [002] Integer: 1 // [003] Integer: 1 // SWFACTION_INITOBJECT const int nmembers = toInt(env.pop(), getVM(env)); // TODO: see if this could call the ASnative function(101, 9). Global_as& gl = getGlobal(env); as_object* obj = createObject(gl); obj->init_member(NSV::PROP_CONSTRUCTOR, getMember(gl, NSV::CLASS_OBJECT)); VM& vm = getVM(env); // Set provided members for (int i = 0; i < nmembers; ++i) { const as_value& member_value = env.top(0); std::string member_name = env.top(1).to_string(); obj->set_member(getURI(vm, member_name), member_value); env.drop(2); } as_value new_obj; new_obj.set_as_object(obj); env.push(new_obj); } void ActionTypeOf(ActionExec& thread) { as_environment& env = thread.env; env.top(0).set_string(env.top(0).typeOf()); } void ActionTargetPath(ActionExec& thread) { as_environment& env = thread.env; DisplayObject* sp = env.top(0).toDisplayObject(); if (sp) { env.top(0).set_string(sp->getTarget()); return; } IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Argument to TargetPath(%s) doesn't cast " "to a DisplayObject"), env.top(0)); ); env.top(0).set_undefined(); } // Push a each object's member value on the stack // This is an utility function for use by ActionEnumerate // and ActionEnum2. The caller is expected to have // already set the top-of-stack to undefined (as an optimization) static void enumerateObject(as_environment& env, const as_object& obj) { assert(env.top(0).is_undefined()); Enumerator en(env); obj.visitKeys(en); } void ActionEnumerate(ActionExec& thread) { as_environment& env = thread.env; // Get the object as_value var_name = env.top(0); std::string var_string = var_name.to_string(); as_value variable = thread.getVariable(var_string); env.top(0).set_undefined(); const as_object* obj = safeToObject(getVM(thread.env), variable); if ( !obj || !variable.is_object() ) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Top of stack doesn't evaluate to an object (%s) at " "ActionEnumerate execution"), var_name); ); return; } enumerateObject(env, *obj); } void ActionNewAdd(ActionExec& thread) { as_environment& env = thread.env; const as_value& op2 = env.pop(); as_value op1 = env.pop(); newAdd(op1, op2, getVM(env)); env.push(op1); } void ActionNewLessThan(ActionExec& thread) { as_environment& env = thread.env; env.top(1) = newLessThan(env.top(1), env.top(0), getVM(env)); env.drop(1); } void ActionNewEquals(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_NEWEQUALS)); #endif VM& vm = getVM(env); int swfVersion = vm.getSWFVersion(); if (swfVersion <= 5) { as_value op1 = env.top(0); try { convertToPrimitive(op1, vm); } catch (const ActionTypeError& e) { log_debug("to_primitive(%s) threw an ActionTypeError %s", op1, e.what()); } as_value op2 = env.top(1); try { convertToPrimitive(op2, vm); } catch (const ActionTypeError& e) { log_debug("to_primitive(%s) threw an ActionTypeError %s", op2, e.what()); } env.top(1).set_bool(equals(op1, op2, getVM(env))); } else { /// ECMA-262 abstract equality comparison (sect 11.9.3) env.top(1).set_bool(equals(env.top(1), env.top(0), getVM(env))); } env.drop(1); } void ActionToNumber(ActionExec& thread) { as_environment& env = thread.env; convertToNumber(env.top(0), getVM(env)); } void ActionToString(ActionExec& thread) { as_environment& env = thread.env; convertToString(env.top(0), getVM(env)); } void ActionDup(ActionExec& thread) { as_environment& env = thread.env; env.push(env.top(0)); } void ActionSwap(ActionExec& thread) { as_environment& env = thread.env; std::swap(env.top(1), env.top(0)); } void ActionGetMember(ActionExec& thread) { as_environment& env = thread.env; as_value member_name = env.top(0); as_value target = env.top(1); as_object* obj = safeToObject(getVM(thread.env), target); if (!obj) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("getMember called against a value that does not " "cast to an as_object: %s"), target) ); env.top(1).set_undefined(); env.drop(1); return; } IF_VERBOSE_ACTION( log_action(_(" ActionGetMember: target: %s (object %p)"), target, static_cast(obj)); ); const ObjectURI& k = getURI(getVM(env), member_name.to_string()); if (!obj->get_member(k, &env.top(1))) { IF_VERBOSE_ASCODING_ERRORS( log_aserror("Reference to undefined member %s of object %s", member_name, target); ); env.top(1).set_undefined(); } IF_VERBOSE_ACTION ( log_action(_("-- get_member %s.%s=%s"), target, member_name, env.top(1)); ); env.drop(1); } void ActionSetMember(ActionExec& thread) { as_environment& env = thread.env; as_object* obj = safeToObject(getVM(thread.env), env.top(2)); const std::string& member_name = env.top(1).to_string(); const as_value& member_value = env.top(0); if (member_name.empty()) { IF_VERBOSE_ASCODING_ERRORS ( // Invalid object, can't set. log_aserror(_("ActionSetMember: %s.%s=%s: member name " "evaluates to invalid (empty) string"), env.top(2), env.top(1), env.top(0)); ); } else if (obj) { obj->set_member(getURI(getVM(env), member_name), member_value); IF_VERBOSE_ACTION ( log_action(_("-- set_member %s.%s=%s"), env.top(2), member_name, member_value); ); } else { // Malformed SWF ? (don't think this is possible to do with // ActionScript syntax) // FIXME, should this be log_swferror? IF_VERBOSE_ASCODING_ERRORS( // Invalid object, can't set. log_aserror(_("-- set_member %s.%s=%s on invalid object!"), env.top(2), member_name, member_value); ); } env.drop(3); } void ActionIncrement(ActionExec& thread) { as_environment& env = thread.env; env.top(0).set_double(toNumber(env.top(0), getVM(env)) + 1); } void ActionDecrement(ActionExec& thread) { as_environment& env = thread.env; env.top(0).set_double(toNumber(env.top(0), getVM(env)) - 1); } /// Call a method of an object // /// Stack: method, object, argc, arg0 ... argn // /// The standard use of this opcode is: /// 1. First stack value converted to a string (method name). /// 2. Second stack value converted to an object (this pointer). /// 3. Arg count and arguments parsed. /// 4. The method name must be a property of the object and is called with /// the object as its 'this'. // /// But it can also be used in a different way under some circumstances. // /// 1. If the method name is defined and not empty, the object value must /// be an object and the method name must be a property of the object /// (may be inherited). Otherwise the call fails and returns undefined. /// 2. If the method name is undefined or empty, the second stack value is /// called, and call's 'this' pointer is undefined. // /// In both usages the arguments are passed. void ActionCallMethod(ActionExec& thread) { as_environment& env = thread.env; // Get name function of the method as_value method_name = env.pop(); std::string method_string = method_name.to_string(); // Get an object as_value obj_value = env.pop(); // Get number of args, modifying it if not enough values are on the stack. size_t nargs = toNumber(env.pop(), getVM(env)); const size_t available_args = env.stack_size(); if (available_args < nargs) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Attempt to call a method with %u arguments " "while only %u are available on the stack."), nargs, available_args); ); nargs = available_args; } IF_VERBOSE_ACTION ( log_action(_(" method name: %s"), method_name); log_action(_(" method object/func: %s"), obj_value); log_action(_(" method nargs: %d"), nargs); ); as_object* obj = safeToObject(getVM(thread.env), obj_value); if (!obj) { // If this value is not an object, it can neither have any members // nor be called as a function, so neither opcode usage is possible. IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionCallMethod invoked with " "non-object object/func (%s)"), obj_value); ); env.drop(nargs); env.push(as_value()); return; } const bool noMeth = (method_name.is_undefined() || method_string.empty()); as_object* method_obj; // The method to call, as an object // The object to be the 'this' pointer during the call. as_object* this_ptr(nullptr); // Will be used to find super later ObjectURI methURI; // If the method name is undefined or evaluates to an empty string, // the first argument is used as the method name and the 'this' pointer // is undefined. We can signify this by leaving the 'this' pointer as // null.a if (noMeth) { method_obj = obj; } else { methURI = getURI(getVM(env), method_string); // The method value as_value method_value; if (!obj->get_member(methURI, &method_value) ) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionCallMethod: " "Can't find method %s of object %s"), method_name, obj_value); ); env.drop(nargs); env.push(as_value()); return; } method_obj = safeToObject(getVM(thread.env), method_value); if ( ! method_obj ) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionCallMethod: " "property %d of object %d is not callable (%s)"), method_name, obj_value, method_value); ); env.drop(nargs); env.push(as_value()); return; } this_ptr = obj; } assert(method_obj); // or we would should have returned already by now // If we are calling a method of a super object, the 'this' pointer // for the call is always the this pointer of the function that called // super(). if (obj->isSuper()) { if (thread.isFunction()) this_ptr = thread.getThisPointer(); } fn_call::Args args; for (size_t i = 0; i < nargs; ++i) { args += env.pop(); } as_object* super; as_function* func = method_obj->to_function(); if (func && func->isBuiltin()) { // Do not construct super if method is a builtin // TODO: check if this is correct!! super = nullptr; } else { super = obj->get_super(methURI); } fn_call call(this_ptr, env, args); call.super = super; call.callerDef = &(thread.code.getMovieDefinition()); as_value result; try { result = method_obj->call(call); } catch (const ActionTypeError& e) { IF_VERBOSE_ASCODING_ERRORS( log_aserror("ActionCallMethod: %s", e.what()); ); } env.push(result); // Now, if there was an exception, proceed to the end of the block. if (result.is_exception()) thread.skipRemainingBuffer(); } void ActionNewMethod(ActionExec& thread) { as_environment& env = thread.env; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_NEWMETHOD)); #endif const as_value method_name = env.pop(); const as_value obj_val = env.pop(); // Get number of args, modifying it if not enough values are on the stack. unsigned nargs = toNumber(env.pop(), getVM(env)); const size_t available_args = env.stack_size(); // previous 3 entries popped if (available_args < nargs) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("Attempt to call a constructor with %u arguments " "while only %u are available on the stack."), nargs, available_args); ); nargs = available_args; } as_object* obj = safeToObject(getVM(thread.env), obj_val); if (!obj) { // SWF integrity check // FIXME, should this be log_swferror? Or log_aserror? IF_VERBOSE_MALFORMED_SWF( log_swferror(_("On ActionNewMethod: " "no object found on stack on ActionMethod")); ); env.drop(nargs); env.push(as_value()); return; } const std::string& method_string = method_name.to_string(); as_value method_val; if (method_name.is_undefined() || method_string.empty()) { method_val = obj_val; } else { const ObjectURI& k = getURI(getVM(env), method_string); if (!obj->get_member(k, &method_val)) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("ActionNewMethod: can't find method %s of " "object %s"), method_string, obj_val); ); env.drop(nargs); env.push(as_value()); return; } } as_function* method = method_val.to_function(); if (!method) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("ActionNewMethod: method name is undefined " "and object is not a function")); ); env.drop(nargs); env.push(as_value()); return; } // Construct the object // It is possible for constructors to fail, for instance if a // conversion to object calls a built-in constructor that has been // deleted. BitmapData also fails to construct anything under // some circumstances. try { as_object* newobj = construct_object(method, env, nargs); env.push(newobj); return; } catch (const GnashException&) { env.push(as_value()); return; } } void ActionInstanceOf(ActionExec& thread) { as_environment& env = thread.env; // Get the "super" function as_object* super = safeToObject(getVM(thread.env), env.top(0)); // Get the "instance" (but avoid implicit conversion of primitive values!) as_object* instance = env.top(1).is_object() ? safeToObject(getVM(thread.env), env.top(1)) : nullptr; // Invalid args! if (!super || ! instance) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("-- %s instanceof %s (invalid args?)"), env.top(1), env.top(0)); ); env.drop(1); env.top(0) = as_value(false); return; } env.drop(1); env.top(0) = as_value(instance->instanceOf(super)); } void ActionEnum2(ActionExec& thread) { as_environment& env = thread.env; // Get the object. // Copy it so we can override env.top(0) as_value obj_val = env.top(0); // End of the enumeration. Won't override the object // as we copied that as_value. env.top(0).set_undefined(); as_object* obj = safeToObject(getVM(thread.env), obj_val); if (!obj || !obj_val.is_object()) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Top of stack not an object %s at ActionEnum2" " execution"), obj_val); ); return; } enumerateObject(env, *obj); } void ActionBitwiseAnd(ActionExec& thread) { as_environment& env = thread.env; const int operand1 = toInt(env.top(1), getVM(env)); const int operand2 = toInt(env.top(0), getVM(env)); env.top(1) = operand1 & operand2; env.drop(1); } void ActionBitwiseOr(ActionExec& thread) { as_environment& env = thread.env; const int operand1 = toInt(env.top(1), getVM(env)); const int operand2 = toInt(env.top(0), getVM(env)); env.top(1) = operand1|operand2; env.drop(1); } void ActionBitwiseXor(ActionExec& thread) { as_environment& env = thread.env; const int operand1 = toInt(env.top(1), getVM(env)); const int operand2 = toInt(env.top(0), getVM(env)); env.top(1) = operand1^operand2; env.drop(1); } inline std::uint32_t saneShiftParam(std::int32_t value) { // NOTE: ISO-IEC 14882:2003 5.8.1: "The behavior is undefined if the right // operand is negative, or greater than or equal to the length in bits of // the promoted left operand." std::uint32_t rv = value; return rv % 32; } void ActionShiftLeft(ActionExec& thread) { as_environment& env = thread.env; std::uint32_t amount = saneShiftParam(toInt(env.top(0), getVM(env))); std::int32_t value = toInt(env.top(1), getVM(env)); value = static_cast(value) << amount; env.top(1) = value; env.drop(1); } void ActionShiftRight(ActionExec& thread) { as_environment& env = thread.env; std::uint32_t amount = saneShiftParam(toInt(env.top(0), getVM(env))); std::int32_t value = toInt(env.top(1), getVM(env)); value = value >> amount; env.top(1) = value; env.drop(1); } void ActionShiftRight2(ActionExec& thread) { as_environment& env = thread.env; std::uint32_t amount = saneShiftParam(toInt(env.top(0), getVM(env))); std::int32_t value = toInt(env.top(1), getVM(env)); value = static_cast(value) >> amount; env.top(1) = value; env.drop(1); } void ActionStrictEq(ActionExec& thread) { as_environment& env = thread.env; env.top(1).set_bool(env.top(1).strictly_equals(env.top(0))); env.drop(1); } void ActionGreater(ActionExec& thread) { // Just swap the operator and invoke ActionNewLessThan as_environment& env = thread.env; std::swap(env.top(1), env.top(0)); ActionNewLessThan(thread); } void ActionStringGreater(ActionExec& thread) { as_environment& env = thread.env; const std::string& op1 = env.top(0).to_string(); const std::string& op2 = env.top(1).to_string(); env.top(1).set_bool(op2 > op1); env.drop(1); } void ActionExtends(ActionExec& thread) { as_environment& env = thread.env; as_object* super = toObject(env.top(0), getVM(thread.env)); as_function* sub = env.top(1).to_function(); if (!super ||!sub) { IF_VERBOSE_ASCODING_ERRORS( if (!super) { log_aserror(_("ActionExtends: Super is not an object (%s)"), env.top(0)); } if (!sub) { log_aserror(_("ActionExtends: Sub is not a function (%s)"), env.top(1)); } ); env.drop(2); return; } env.drop(2); as_object* newproto = new as_object(getGlobal(thread.env)); as_object* p = toObject(getMember(*super, NSV::PROP_PROTOTYPE), getVM(thread.env)); newproto->set_prototype(p); if (getSWFVersion(*super) > 5) { const int flags = PropFlags::dontEnum; newproto->init_member(NSV::PROP_uuCONSTRUCTORuu, super, flags); } sub->init_member(NSV::PROP_PROTOTYPE, as_value(newproto)); } void ActionConstantPool(ActionExec& thread) { /* Register the so-read ConstantPool into the VM */ getVM(thread.env).setConstantPool( &thread.code.readConstantPool(thread.getCurrentPC(), thread.getNextPC()) ); } void ActionDefineFunction2(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; // Code starts at thread.getNextPC() as the DefineFunction tag // contains name and args, while next tag is first tag // of the function body. Function2* func = new Function2(code, env, thread.getNextPC(), thread.getScopeStack()); // We're stuck initializing our own prototype at the moment. as_object* proto = createObject(getGlobal(env)); proto->init_member(NSV::PROP_CONSTRUCTOR, func); func->init_member(NSV::PROP_PROTOTYPE, proto); Global_as& gl = getGlobal(env); as_function* f = getOwnProperty(gl, NSV::CLASS_FUNCTION).to_function(); if (f) { const int flags = as_object::DefaultFlags | PropFlags::onlySWF6Up; func->init_member(NSV::PROP_uuPROTOuu, getMember(*f, NSV::PROP_PROTOTYPE), flags); func->init_member(NSV::PROP_CONSTRUCTOR, f); } size_t i = thread.getCurrentPC() + 3; // skip tag id and length // Extract name. // @@ security: watch out for possible missing terminator here! const std::string name = code.read_string(i); i += name.length() + 1; // add NULL-termination // Get number of arguments. const std::uint16_t nargs = code.read_uint16(i); i += 2; // Get the count of local registers used by this function. const std::uint8_t register_count = code[i]; ++i; func->setRegisterCount(register_count); // Flags, for controlling register assignment of implicit args. const std::uint16_t flags = code.read_uint16(i); i += 2; func->setFlags(flags); // Get the register assignments and names of the arguments. for (size_t n = 0; n < nargs; ++n) { std::uint8_t arg_register = code[i]; ++i; // @@ security: watch out for possible missing terminator here! const std::string arg(code.read_string(i)); func->add_arg(arg_register, getURI(getVM(env), arg)); i += arg.size() + 1; } // Get the length of the actual function code. std::uint16_t code_size = code.read_int16(i); // Check code_size value consistency const size_t actionbuf_size = thread.code.size(); if (thread.getNextPC() + code_size > actionbuf_size) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("function2 code len (%u) " "overflows DOACTION tag boundaries " "(DOACTION tag len=%d" ", function2 code offset=%d). " "Forcing code len to eat the whole buffer " "(would this work?)."), code_size, actionbuf_size, thread.getNextPC()); ); code_size = actionbuf_size-thread.getNextPC(); } i += 2; func->setLength(code_size); // Skip the function body (don't interpret it now). thread.adjustNextPC(code_size); // If we have a name, then save the function in this // environment under that name. as_value function_value(func); if (!name.empty()) { IF_VERBOSE_ACTION( log_action(_("DefineFunction2: named function '%s' " "starts at PC %d"), name, func->getStartPC()); ); thread.setVariable(name, function_value); } // Otherwise push the function literal on the stack else { IF_VERBOSE_ACTION( log_action(_("DefineFunction2: anonymous function starts at " "PC %d"), func->getStartPC()); ); env.push(function_value); } } void ActionTry(ActionExec& thread) { const action_buffer& code = thread.code; #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_TRY)); #endif size_t i = thread.getCurrentPC() + 3; // skip tag id and length std::uint8_t flags = code[i]; ++i; bool doCatch = flags & 1; bool doFinally = flags & (1<<1); bool catchInRegister = flags&(1<<2); std::uint8_t reserved = flags&0xE0; std::uint16_t trySize = code.read_uint16(i); i += 2; std::uint16_t catchSize = code.read_uint16(i); i += 2; std::uint16_t finallySize = code.read_uint16(i); i += 2; const char* catchName = nullptr; std::uint8_t catchRegister = 0; if (!doFinally) finallySize = 0; if (!doCatch) catchSize = 0; if (!catchInRegister) { catchName = code.read_string(i); i += strlen(catchName) + 1; thread.pushTryBlock( TryBlock(i, trySize, catchSize, finallySize, catchName)); } else { catchRegister = code[i]; ++i; thread.pushTryBlock( TryBlock(i, trySize, catchSize, finallySize, catchRegister)); } thread.setNextPC(i); // Proceed into the try block. IF_VERBOSE_ACTION( log_action(_("ActionTry: reserved:%x doFinally:%d doCatch:%d trySize:%u " "catchSize:%u finallySize:%u catchName:%s catchRegister:%u"), static_cast(reserved), doFinally, doCatch, trySize, catchSize, finallySize, catchName ? catchName : "(null)", catchRegister); ); } /// See: http://sswf.sourceforge.net/SWFalexref.html#action_with void ActionWith(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; size_t pc = thread.getCurrentPC(); #if GNASH_PARANOIA_LEVEL > 1 assert(thread.atActionTag(SWF::ACTION_WITH)); #endif const as_value& val = env.pop(); as_object* with_obj = toObject(val, getVM(thread.env)); ++pc; // skip tag code int tag_length = code.read_int16(pc); // read tag len (should be 2) if ( tag_length != 2 ) { IF_VERBOSE_MALFORMED_SWF( log_swferror(_("ActionWith tag length != 2; skipping")); ); return; } pc += 2; // skip tag len unsigned block_length = code.read_int16(pc); // read 'with' body size if ( block_length == 0 ) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Empty with() block...")); ); return; } pc += 2; // skip with body size // now we should be on the first action of the 'with' body assert(thread.getNextPC() == pc); if (!with_obj) { IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("with(%s) : first argument doesn't cast to an object!"), val); ); // skip the full block thread.adjustNextPC(block_length); return; } // where does the 'with' block end? const size_t block_end = thread.getNextPC() + block_length; if (!thread.pushWith(With(with_obj, block_end))) { // skip the full block thread.adjustNextPC(block_length); } } void ActionDefineFunction(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; #ifndef NDEBUG // TODO: check effects of the following 'length' std::int16_t length = code.read_int16(thread.getCurrentPC()+1); assert( length >= 0 ); #endif // Create a new Function // Code starts at thread.getNextPC() as the DefineFunction tag // contains name and args, while next tag is first tag // of the function body. Function* func = new Function(code, env, thread.getNextPC(), thread.getScopeStack()); // We're stuck initializing our own prototype at the moment. as_object* proto = createObject(getGlobal(env)); proto->init_member(NSV::PROP_CONSTRUCTOR, func); func->init_member(NSV::PROP_PROTOTYPE, proto); Global_as& gl = getGlobal(env); as_function* f = getOwnProperty(gl, NSV::CLASS_FUNCTION).to_function(); if (f) { const int flags = as_object::DefaultFlags | PropFlags::onlySWF6Up; func->init_member(NSV::PROP_uuPROTOuu, getMember(*f, NSV::PROP_PROTOTYPE), flags); func->init_member(NSV::PROP_CONSTRUCTOR, f); } size_t i = thread.getCurrentPC() + 3; // Extract name. // @@ security: watch out for possible missing terminator here! const std::string name = code.read_string(i); i += name.length() + 1; // Get number of arguments. const size_t nargs = code.read_uint16(i); i += 2; // Get the names of the arguments. for (size_t n = 0; n < nargs; ++n) { const std::string arg(code.read_string(i)); func->add_arg(0, getURI(getVM(env), arg)); i += arg.size() + 1; } // Get the length of the actual function code. const std::uint16_t code_size = code.read_uint16(i); func->setLength(code_size); // Skip the function body (don't interpret it now). // getNextPC() is assumed to point to first action of // the function body (one-past the current tag, whic // is DefineFunction). We add code_size to it. thread.adjustNextPC(code_size); // If we have a name, then save the function in this // environment under that name. as_value function_value(func); if (!name.empty()) { IF_VERBOSE_ACTION( log_action("DefineFunction: named function '%s' starts at " "PC %d", name, func->getStartPC()); ); thread.setVariable(name, function_value); } else { // Otherwise push the function literal on the stack IF_VERBOSE_ACTION( log_action("DefineFunction: anonymous function starts at " "PC %d", func->getStartPC()); ); env.push(function_value); } } void ActionSetRegister(ActionExec& thread) { as_environment& env = thread.env; const action_buffer& code = thread.code; const size_t reg = code[thread.getCurrentPC() + 3]; // Save top of stack in specified register. getVM(env).setRegister(reg, env.top(0)); } void ActionUnsupported(ActionExec& thread) { log_error(_("Unsupported action handler invoked, code at pc is %#x"), static_cast(thread.code[thread.getCurrentPC()])); } as_object* safeToObject(VM& vm, const as_value& val) { try { return toObject(val, vm); } catch (const GnashException&) { return nullptr; } } // Utility: construct an object using given constructor. // This is used by both ActionNew and ActionNewMethod and // hides differences between builtin and actionscript-defined // constructors. as_object* construct_object(as_function* ctor_as_func, as_environment& env, unsigned int nargs) { assert(ctor_as_func); fn_call::Args args; for (size_t i = 0; i < nargs; ++i) { args += env.pop(); } return constructInstance(*ctor_as_func, env, args); } // Common code for GetUrl and GetUrl2. See: // http://sswf.sourceforge.net/SWFalexref.html#action_get_url // http://sswf.sourceforge.net/SWFalexref.html#action_get_url2 // // Testcases: // // - http://www.garfield.com/comics/comics_todays.html // lower_todayscomic.swf should render four flash files in its canvas // // - http://www.voiptalk.org // pressing 'My Account' button should open // https://www.voiptalk.org/products/login.php // NOTE: this is affected by the GetUrl bug reported with an excerpt // from Colin Moock book, see below. (won't work, and won't fix) // // - http://www.uptoten.com // Should load in _level0, with loadTargetFlag set. // // Method isbBit-packed as follows: // SendVarsMethod:2 (0:NONE 1:GET 2:POST) // Reserved:4 // LoadTargetFlag:1 // LoadVariableFlag:1 /// @param target the target window, or _level1..10 void commonGetURL(as_environment& env, as_value target, const std::string& url, std::uint8_t method) { if (url.empty()) { log_error(_("Bogus empty GetUrl URL in SWF file, skipping")); return; } // Parse the method bitfield bool loadTargetFlag = method & 64; bool loadVariableFlag = method & 128; MovieClip::VariablesMethod sendVarsMethod; // handle malformed sendVarsMethod if ((method & 3) == 3) { log_error(_("Bogus GetUrl2 send vars method" " in SWF file (both GET and POST requested). Using GET")); sendVarsMethod = MovieClip::METHOD_GET; } else { sendVarsMethod = static_cast(method & 3); } std::string target_string; if (!target.is_undefined() && !target.is_null()) { target_string = target.to_string(); } VM& vm = getVM(env); movie_root& m = vm.getRoot(); // If the url starts with "FSCommand:", then this is // a message for the host app. StringNoCaseEqual noCaseCompare; if (noCaseCompare(url.substr(0, 10), "FSCommand:")) { m.handleFsCommand(url.substr(10), target_string); return; } // If the url starts with "print:", then this is // a print request. if (noCaseCompare(url.substr(0, 6), "print:")) { log_unimpl("print: URL"); return; } // // From "ActionScript: The Definitive Guide" by Colin Moock p. 470 // --------8<------------------------------------------------------ // In most browsers, getURL() relative links are resolved relative // to the HTML file that contains the .swf file. In IE 4.5 and older // versions on Macintosh, relative links are resolved relative to // the location of the .swf file, not the HTML file, which causes // problems when the two are in different directories. To avoid // the problem, either place the .swf and the .html file in the // same directory or use absolute URLs when invoking getURL(). // --------8<------------------------------------------------------ // // We'll resolve relative to our "base url". // The base url must be set with the set_base_url() command. // log_debug("get url: target=%s, URL=%s, method=%x " "(sendVars:%X, loadTarget:%d, loadVariable:%d)", target_string, url, static_cast(method), sendVarsMethod, loadTargetFlag, loadVariableFlag); DisplayObject* target_ch = findTarget(env, target_string); MovieClip* target_movie = target_ch ? target_ch->to_movie() : nullptr; if (loadVariableFlag) { log_debug("getURL2 loadVariable"); if (!target_ch) { log_error(_("getURL: target %s not found"), target_string); // might want to invoke the external url opener here... return; } if (!target_movie) { log_error(_("getURL: target %s is not a sprite"), target_string); // might want to invoke the external url opener here... return; } target_movie->loadVariables(url, sendVarsMethod); return; } std::string varsToSend; if (sendVarsMethod != MovieClip::METHOD_NONE) { // TESTED: variables sent are those in current target, // no matter the target found on stack (which // is the target to load the resource into). // as_object* curtgt = getObject(env.target()); if (!curtgt) { log_error(_("commonGetURL: current target is undefined")); return; } varsToSend = getURLEncodedVars(*curtgt); } if (loadTargetFlag) { log_debug("getURL2 target load"); if (!target_ch) { unsigned int levelno; if (isLevelTarget(getSWFVersion(env), target_string, levelno)) { log_debug("Testing _level loading (level %u)", levelno); m.loadMovie(url, target_string, varsToSend, sendVarsMethod); return; } IF_VERBOSE_ASCODING_ERRORS( log_aserror(_("Unknown loadMovie target: %s"), target_string); ); // TESTED: Even if the target is created right-after // the load request, the player won't load // into it. In other words, the target MUST // exist at time of interpreting the GETURL2 // tag with loadTarget flag. return; } if (!target_movie) { log_error(_("get URL: target %s is not a sprite"), target_string); return; } const std::string s = target_movie->getTarget(); // or getOrigTarget ? if (s != target_movie->getOrigTarget()) { log_debug("TESTME: target of a loadMovie changed its target path"); } // To trigger: https://savannah.gnu.org/bugs/?32506 if ( m.findCharacterByTarget(s) != target_movie ) { log_error("FIXME: " "getURL target %1% is resolved by findTarget(env) " "to sprite %2%. Sprite %2% has " "target %3%. Target %3% will be resolved " "by movie_root::findCharacterByTarget() to %4%", target_string, target_movie, s, m.findCharacterByTarget(s)); } // We might probably use target_string here rather than // ``s'' (which is the _official_ target) but at time // of writing the MovieLoader will use // movie_root::findCharacterByTarget to resolve paths, // and that one, in turn, won't support /slash/notation // which may be found in target_string (see also loadMovieTest.swf // under misc-ming.all) // m.loadMovie(url, s, varsToSend, sendVarsMethod); return; } unsigned int levelno; if (isLevelTarget(getSWFVersion(env), target_string, levelno)) { log_debug("Testing _level loading (level %u)", levelno); m.loadMovie(url, target_string, varsToSend, sendVarsMethod); return; } // Just plain getURL // This should be the original URL string, as the hosting application // will decide how to resolve the URL. If there is no hosting // application, movie_root::getURL will resolve the URL. m.getURL(url, target_string, varsToSend, sendVarsMethod); } // Common code for SetTarget and SetTargetExpression. See: // http://sswf.sourceforge.net/SWFalexref.html#action_set_target // http://sswf.sourceforge.net/SWFalexref.html#action_get_dynamic void commonSetTarget(ActionExec& thread, const std::string& target_name) { as_environment& env = thread.env; // see swfdec's settarget-relative-*.swf env.reset_target(); // if the string is blank, we reset the target to its original value if (target_name.empty()) return; // TODO: pass thread.getScopeStack() DisplayObject* new_target = findTarget(env, target_name); if (!new_target) { IF_VERBOSE_ASCODING_ERRORS ( log_aserror(_("Couldn't find movie \"%s\" to set target to!" " Setting target to NULL..."), target_name); ); } env.set_target(new_target); } } } // namespace gnash