1 // DefineButtonTag.cpp:  Mouse-sensitive SWF buttons, for Gnash.
2 //
3 //   Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 //
20 
21 #include "DefineButtonTag.h"
22 
23 #include <string>
24 #include <boost/functional.hpp>
25 
26 #include "TypesParser.h"
27 #include "RunResources.h"
28 #include "DisplayObject.h"
29 #include "Button.h" // for createDisplayObject()
30 #include "DefineButtonCxformTag.h"
31 #include "SWF.h"
32 #include "SWFStream.h" // for read()
33 #include "movie_definition.h"
34 #include "action_buffer.h"
35 #include "filter_factory.h"
36 #include "GnashKey.h" // for gnash::key::codeMap
37 #include "GnashAlgorithm.h"
38 #include "Global_as.h"
39 #include "namedStrings.h"
40 #include "as_function.h"
41 
42 namespace gnash {
43 namespace SWF {
44 
45 // Forward declarations
46 namespace {
47     std::string computeButtonStatesString(int flags);
48 }
49 
DefineButtonTag(SWFStream & in,movie_definition & m,TagType tag,std::uint16_t id)50 DefineButtonTag::DefineButtonTag(SWFStream& in, movie_definition& m,
51         TagType tag, std::uint16_t id)
52     :
53     DefinitionTag(id),
54     _trackAsMenu(false),
55     _movieDef(m)
56 {
57     switch (tag) {
58         default:
59             std::abort();
60             break;
61         case DEFINEBUTTON:
62             readDefineButtonTag(in, m);
63             break;
64         case DEFINEBUTTON2:
65             readDefineButton2Tag(in, m);
66             break;
67     }
68 }
69 
~DefineButtonTag()70 DefineButtonTag::~DefineButtonTag()
71 {
72 }
73 
74 
75 void
loader(SWFStream & in,TagType tag,movie_definition & m,const RunResources &)76 DefineButtonTag::loader(SWFStream& in, TagType tag, movie_definition& m,
77             const RunResources& /*r*/)
78 {
79     assert(tag == DEFINEBUTTON);
80     in.ensureBytes(2);
81     const std::uint16_t id = in.read_u16();
82 
83     IF_VERBOSE_PARSE(
84         log_parse(_("  DefineButton loader: character id = %d"), id);
85     );
86 
87     std::unique_ptr<DefineButtonTag> bt(new DefineButtonTag(in, m, tag, id));
88 
89     m.addDisplayObject(id, bt.release());
90 }
91 
92 void
loader(SWFStream & in,TagType tag,movie_definition & m,const RunResources &)93 DefineButton2Tag::loader(SWFStream& in, TagType tag, movie_definition& m,
94             const RunResources& /*r*/)
95 {
96     assert(tag == DEFINEBUTTON2);
97     in.ensureBytes(2);
98     const std::uint16_t id = in.read_u16();
99 
100     IF_VERBOSE_PARSE(
101         log_parse(_("  DefineButton2 loader: chararacter id = %d"), id);
102     );
103 
104     std::unique_ptr<DefineButtonTag> bt(new DefineButtonTag(in, m, tag, id));
105 
106     m.addDisplayObject(id, bt.release());
107 }
108 
109 
110 void
readDefineButtonTag(SWFStream & in,movie_definition & m)111 DefineButtonTag::readDefineButtonTag(SWFStream& in, movie_definition& m)
112 {
113 
114     // Old button tag.
115 
116     unsigned long endTagPos = in.get_tag_end_position();
117 
118     // Read button DisplayObject records.
119     for (;;) {
120         ButtonRecord r;
121         if (r.read(in, SWF::DEFINEBUTTON, m, endTagPos) == false) {
122             // Null record; marks the end of button records.
123             break;
124         }
125 
126         // SAFETY CHECK:
127         // if the ButtonRecord is corrupted, discard it
128         if (r.valid()) _buttonRecords.push_back(std::move(r));
129     }
130 
131     if (in.tell() >= endTagPos) {
132         IF_VERBOSE_MALFORMED_SWF(
133         log_swferror(_("Premature end of DEFINEBUTTON tag, "
134                 "won't read actions"));
135         );
136         return;
137     }
138 
139     // Read actions.
140     _buttonActions.push_back(new ButtonAction(in, SWF::DEFINEBUTTON,
141                 endTagPos, m));
142 
143 }
144 
145 void
readDefineButton2Tag(SWFStream & in,movie_definition & m)146 DefineButtonTag::readDefineButton2Tag(SWFStream& in, movie_definition& m)
147 {
148     // Character ID has been read already
149 
150     in.ensureBytes(1 + 2); // flags + actions offset
151 
152     // Read the menu flag
153     // (this is a single bit, the first 7 bits are reserved)
154     const std::uint8_t flags = in.read_u8();
155     _trackAsMenu = flags & (1 << 0);
156     if (_trackAsMenu) {
157         LOG_ONCE(log_unimpl("DefineButton2: trackAsMenu"));
158     }
159 
160     // Read the action offset
161     unsigned button_2_action_offset = in.read_u16();
162 
163     unsigned long tagEndPosition = in.get_tag_end_position();
164     unsigned next_action_pos = in.tell() + button_2_action_offset - 2;
165 
166     if ( next_action_pos > tagEndPosition )
167     {
168         IF_VERBOSE_MALFORMED_SWF(
169         log_swferror(_("Next Button2 actionOffset (%u) points past "
170                 "the end of tag (%lu)"),
171             button_2_action_offset, tagEndPosition);
172         );
173         return;
174     }
175 
176     unsigned long endOfButtonRecords = tagEndPosition;
177     if ( ! button_2_action_offset  ) endOfButtonRecords = tagEndPosition;
178 
179     // Read button records.
180     // takes at least 1 byte for the end mark button record, so
181     // we don't attempt to parse at all unless we have at least 1 byte left
182     while ( in.tell() < endOfButtonRecords )
183     {
184         ButtonRecord r;
185         if (r.read(in, SWF::DEFINEBUTTON2, m, endOfButtonRecords) == false) {
186             // Null record marks the end of button records.
187             break;
188         }
189 
190         // SAFETY CHECK:
191         // if the ButtonRecord is corrupted, discard it
192         if (r.valid()) {
193             _buttonRecords.push_back(std::move(r));
194         }
195     }
196 
197     if (button_2_action_offset) {
198 
199         in.seek(next_action_pos);
200 
201         // Read Button2ActionConditions
202         // Don't read past tag end
203         while (in.tell() < tagEndPosition) {
204             in.ensureBytes(2);
205             unsigned next_action_offset = in.read_u16();
206             if (next_action_offset) {
207                 next_action_pos = in.tell() + next_action_offset - 2;
208                 if (next_action_pos > tagEndPosition) {
209                     IF_VERBOSE_MALFORMED_SWF(
210                         log_swferror(_("Next action offset (%u) in "
211                                 "Button2ActionConditions points past "
212                                 "the end of tag"), next_action_offset);
213                     );
214                     next_action_pos = tagEndPosition;
215                 }
216             }
217 
218             const size_t endActionPos = next_action_offset ?
219                 next_action_pos : tagEndPosition;
220 
221             _buttonActions.push_back(new ButtonAction(in, SWF::DEFINEBUTTON2,
222                         endActionPos, m));
223 
224             if (next_action_offset == 0 ) {
225                 // done.
226                 break;
227             }
228 
229             // seek to next action.
230             in.seek(next_action_pos);
231         }
232     }
233 }
234 
235 DisplayObject*
createDisplayObject(Global_as & gl,DisplayObject * parent) const236 DefineButtonTag::createDisplayObject(Global_as& gl, DisplayObject* parent)
237     const
238 {
239     as_object* obj = getObjectWithPrototype(gl, NSV::CLASS_BUTTON);
240     DisplayObject* ch = new Button(obj, this, parent);
241     return ch;
242 }
243 
244 int
getSWFVersion() const245 DefineButtonTag::getSWFVersion() const
246 {
247     return _movieDef.get_version();
248 }
249 
250 bool
hasKeyPressHandler() const251 DefineButtonTag::hasKeyPressHandler() const
252 {
253     return std::find_if(_buttonActions.begin(), _buttonActions.end(),
254             std::mem_fn(&ButtonAction::triggeredByKeyPress)) !=
255             _buttonActions.end();
256 }
257 
258 //
259 // ButtonAction
260 //
261 
ButtonAction(SWFStream & in,TagType t,unsigned long endPos,movie_definition & mdef)262 ButtonAction::ButtonAction(SWFStream& in, TagType t, unsigned long endPos,
263         movie_definition& mdef)
264     :
265     _actions(mdef),
266     _conditions(OVER_DOWN_TO_OVER_UP)
267 {
268     // Read condition flags.
269     if (t != SWF::DEFINEBUTTON) {
270         assert(t == SWF::DEFINEBUTTON2);
271 
272         if ( in.tell()+2 > endPos )
273         {
274             IF_VERBOSE_MALFORMED_SWF(
275             log_swferror(_("Premature end of button action input: "
276                     "can't read conditions"));
277             );
278             return;
279         }
280         in.ensureBytes(2);
281         _conditions = in.read_u16();
282     }
283 
284     IF_VERBOSE_PARSE (
285         log_parse(_("   button actions for conditions 0x%x"),
286             _conditions); // @@ need more info about which actions
287     );
288 
289     // Read actions.
290     _actions.read(in, endPos);
291 }
292 
293 bool
triggeredBy(const event_id & ev) const294 ButtonAction::triggeredBy(const event_id& ev) const
295 {
296     switch (ev.id()) {
297         case event_id::ROLL_OVER: return _conditions & IDLE_TO_OVER_UP;
298         case event_id::ROLL_OUT: return _conditions & OVER_UP_TO_IDLE;
299         case event_id::PRESS: return _conditions & OVER_UP_TO_OVER_DOWN;
300         case event_id::RELEASE: return _conditions & OVER_DOWN_TO_OVER_UP;
301         case event_id::DRAG_OUT: return _conditions & OVER_DOWN_TO_OUT_DOWN;
302         case event_id::DRAG_OVER: return _conditions & OUT_DOWN_TO_OVER_DOWN;
303         case event_id::RELEASE_OUTSIDE: return _conditions & OUT_DOWN_TO_IDLE;
304         case event_id::KEY_PRESS:
305         {
306             int keycode = getKeyCode();
307             if (!keycode) return false; // not a keypress event
308             return key::codeMap[ev.keyCode()][key::SWF] == keycode;
309         }
310         default: return false;
311     }
312 }
313 
314 //
315 // ButtonRecord
316 //
317 
318 DisplayObject*
instantiate(Button * button,bool name) const319 ButtonRecord::instantiate(Button* button, bool name) const
320 {
321     assert(button);
322     assert(_definitionTag);
323 
324     Global_as& gl = getGlobal(*getObject(button));
325 
326     DisplayObject* o = _definitionTag->createDisplayObject(gl, button);
327 
328     o->setMatrix(_matrix, true);
329     o->setCxForm(_cxform);
330     o->set_depth(_buttonLayer + DisplayObject::staticDepthOffset + 1);
331     if (name && isReferenceable(*o)) {
332         o->set_name(button->getNextUnnamedInstanceName());
333     }
334     return o;
335 }
336 
337 bool
hasState(Button::MouseState st) const338 ButtonRecord::hasState(Button::MouseState st) const
339 {
340     switch (st)
341     {
342         case Button::MOUSESTATE_UP: return _up;
343         case Button::MOUSESTATE_DOWN: return _down;
344         case Button::MOUSESTATE_OVER: return _over;
345         case Button::MOUSESTATE_HIT: return _hitTest;
346         default: return false;
347     }
348 }
349 
350 bool
read(SWFStream & in,TagType t,movie_definition & m,unsigned long endPos)351 ButtonRecord::read(SWFStream& in, TagType t,
352         movie_definition& m, unsigned long endPos)
353 {
354     // caller should check this
355     if (in.tell() + 1 > endPos)
356     {
357         IF_VERBOSE_MALFORMED_SWF(
358         log_swferror(_("   premature end of button record input stream, "
359                 "can't read flags"));
360         );
361         return false;
362     }
363 
364     in.ensureBytes(1);
365     std::uint8_t flags = in.read_u8();
366     if (!flags) return false;
367 
368     // Upper 4 bits are:
369     //
370     bool buttonHasBlendMode = flags & (1 << 5);
371     bool buttonHasFilterList = flags & (1 << 4);
372     _hitTest = flags & (1 << 3);
373     _down = flags & (1 << 2);
374     _over = flags & (1 << 1);
375     _up = flags & (1 << 0);
376 
377     if (in.tell() + 2 > endPos) {
378         IF_VERBOSE_MALFORMED_SWF(
379         log_swferror(_("   premature end of button record input stream, "
380                 "can't read DisplayObject id"));
381         );
382         return false;
383     }
384     in.ensureBytes(2);
385     const std::uint16_t id = in.read_u16();
386 
387     // Get DisplayObject definition now (safer)
388     _definitionTag = m.getDefinitionTag(id);
389 
390     // If no DisplayObject with given ID is found in the movie
391     // definition, we print an error, but keep parsing.
392     if (!_definitionTag) {
393         IF_VERBOSE_MALFORMED_SWF(
394         log_swferror(_("   button record for states [%s] refer to "
395             "DisplayObject with id %d, which is not found "
396             "in the chars dictionary"), computeButtonStatesString(flags), id);
397         );
398     }
399     else {
400         IF_VERBOSE_PARSE(
401         log_parse(_("   button record for states [%s] contain "
402             "DisplayObject %d (%s)"), computeButtonStatesString(flags),
403             id, typeName(*_definitionTag));
404         );
405     }
406 
407     if (in.tell() + 2 > endPos) {
408         IF_VERBOSE_MALFORMED_SWF(
409         log_swferror(_("   premature end of button record input stream, "
410                 "can't read button layer (depth?)"));
411         );
412         return false;
413     }
414     in.ensureBytes(2);
415     _buttonLayer = in.read_u16();
416 
417     _matrix = readSWFMatrix(in);
418 
419     if (t == SWF::DEFINEBUTTON2) {
420         _cxform = readCxFormRGBA(in);
421     }
422 
423     if (buttonHasFilterList) {
424         filter_factory::read(in, true, &_filters);
425         LOG_ONCE(
426             log_unimpl("Button filters");
427         );
428     }
429 
430     if (buttonHasBlendMode) {
431         in.ensureBytes(1);
432         _blendMode = in.read_u8();
433         LOG_ONCE(
434             log_unimpl("Button blend mode");
435         );
436     }
437 
438     return true;
439 }
440 
441 namespace {
442 
443 std::string
computeButtonStatesString(int flags)444 computeButtonStatesString(int flags)
445 {
446     std::string ret;
447     if (flags & (1 << 3)) ret += "hit";
448     if (flags & (1 << 2)) { if (!ret.empty()) ret += ","; ret += "down"; }
449     if (flags & (1 << 1)) { if (!ret.empty()) ret += ","; ret += "over"; }
450     if (flags & (1 << 0)) { if (!ret.empty()) ret += ","; ret += "up"; }
451     return ret;
452 }
453 
454 } // anonymous namespace
455 
456 } // namespace SWF
457 } // namespace gnash
458 
459 // Local Variables:
460 // mode: C++
461 // c-basic-offset: 8
462 // tab-width: 8
463 // indent-tabs-mode: t
464 // End:
465