1 // SWFMovieDefinition.cpp: load a SWF definition
2 //
3 //   Copyright (C) 2005, 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 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h" // USE_SWFTREE
23 #endif
24 
25 #include "SWFMovieDefinition.h"
26 
27 #include <functional>
28 #include <boost/version.hpp>
29 #include <thread>
30 #include <mutex>
31 #include <iomanip>
32 #include <memory>
33 #include <string>
34 #include <algorithm>
35 
36 #include "GnashSleep.h"
37 #include "movie_definition.h"
38 #include "zlib_adapter.h"
39 #include "IOChannel.h"
40 #include "SWFStream.h"
41 #include "RunResources.h"
42 #include "Font.h"
43 #include "VM.h"
44 #include "log.h"
45 #include "SWFMovie.h"
46 #include "GnashException.h" // for parser exception
47 #include "ControlTag.h"
48 #include "sound_definition.h" // for sound_sample
49 #include "GnashAlgorithm.h"
50 #include "SWFParser.h"
51 #include "Global_as.h"
52 #include "namedStrings.h"
53 #include "as_function.h"
54 #include "CachedBitmap.h"
55 #include "TypesParser.h"
56 #include "GnashImageJpeg.h"
57 
58 // Debug frames load
59 #undef DEBUG_FRAMES_LOAD
60 
61 // Define this this to load movies using a separate thread
62 // (undef and it will fully load a movie before starting to play it)
63 #define LOAD_MOVIES_IN_A_SEPARATE_THREAD 1
64 
65 // Debug threads locking
66 //#undef DEBUG_THREADS_LOCKING
67 
68 // Define this to get debugging output for symbol library use
69 //#define DEBUG_EXPORTS
70 
71 namespace gnash
72 {
73 
SWFMovieLoader(SWFMovieDefinition & md)74 SWFMovieLoader::SWFMovieLoader(SWFMovieDefinition& md)
75     : _movie_def(md)
76 {
77 }
78 
~SWFMovieLoader()79 SWFMovieLoader::~SWFMovieLoader()
80 {
81     // we should assert _movie_def._loadingCanceled
82     // but we're not friend yet (anyone introduce us ?)
83     if ( _thread.joinable() )
84     {
85         //cout << "Joining thread.." << endl;
86         _thread.join();
87     }
88 }
89 
90 bool
started() const91 SWFMovieLoader::started() const
92 {
93     std::lock_guard<std::mutex> lock(_mutex);
94 
95     return _thread.joinable();
96 }
97 
98 bool
isSelfThread() const99 SWFMovieLoader::isSelfThread() const
100 {
101     std::lock_guard<std::mutex> lock(_mutex);
102 
103     if (!_thread.joinable()) {
104         return false;
105     }
106     return std::this_thread::get_id() == _thread.get_id();
107 }
108 
109 bool
start()110 SWFMovieLoader::start()
111 {
112 #ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
113     std::abort();
114 #endif
115     // don't start SWFMovieLoader thread() which rely
116     // on std::thread() returning before they are executed. Therefore,
117     // we must employ locking.
118     // Those tests do seem a bit redundant, though...
119     std::lock_guard<std::mutex> lock(_mutex);
120 
121     _thread = std::thread(&SWFMovieDefinition::read_all_swf, &_movie_def);
122 
123     return true;
124 }
125 
126 
127 //
128 // SWFMovieDefinition
129 //
130 
SWFMovieDefinition(const RunResources & runResources)131 SWFMovieDefinition::SWFMovieDefinition(const RunResources& runResources)
132     :
133     m_frame_rate(30.0f),
134     m_frame_count(0u),
135     m_version(0),
136     _frames_loaded(0u),
137     _waiting_for_frame(0),
138     _bytes_loaded(0),
139     m_loading_sound_stream(-1),
140     m_file_length(0),
141     m_jpeg_in(),
142     _swf_end_pos(0),
143     _loader(*this),
144     _loadingCanceled(false),
145     _runResources(runResources),
146     _as3(false)
147 {
148 }
149 
~SWFMovieDefinition()150 SWFMovieDefinition::~SWFMovieDefinition()
151 {
152     // Request cancellation of the loading thread
153     std::lock_guard<std::mutex> lock(_loadingCanceledMutex);
154     _loadingCanceled = true;
155 }
156 
157 void
addDisplayObject(std::uint16_t id,SWF::DefinitionTag * c)158 SWFMovieDefinition::addDisplayObject(std::uint16_t id, SWF::DefinitionTag* c)
159 {
160     assert(c);
161     std::lock_guard<std::mutex> lock(_dictionaryMutex);
162     _dictionary.addDisplayObject(id, c);
163     addControlTag(c);
164 }
165 
166 SWF::DefinitionTag*
getDefinitionTag(std::uint16_t id) const167 SWFMovieDefinition::getDefinitionTag(std::uint16_t id) const
168 {
169     std::lock_guard<std::mutex> lock(_dictionaryMutex);
170     boost::intrusive_ptr<SWF::DefinitionTag> ch =
171         _dictionary.getDisplayObject(id);
172     return ch.get();
173 }
174 
175 void
add_font(int font_id,boost::intrusive_ptr<Font> f)176 SWFMovieDefinition::add_font(int font_id, boost::intrusive_ptr<Font> f)
177 {
178     assert(f);
179     m_fonts.insert(std::make_pair(font_id, f));
180 }
181 
182 Font*
get_font(int font_id) const183 SWFMovieDefinition::get_font(int font_id) const
184 {
185 
186     FontMap::const_iterator it = m_fonts.find(font_id);
187     if ( it == m_fonts.end() ) return nullptr;
188     boost::intrusive_ptr<Font> f = it->second;
189     assert(f->get_ref_count() > 1);
190     return f.get();
191 }
192 
193 Font*
get_font(const std::string & name,bool bold,bool italic) const194 SWFMovieDefinition::get_font(const std::string& name, bool bold, bool italic)
195     const
196 {
197 
198     for (const auto& elem : m_fonts)
199     {
200        Font* f = elem.second.get();
201        if ( f->matches(name, bold, italic) ) return f;
202     }
203     return nullptr;
204 }
205 
206 CachedBitmap*
getBitmap(int id) const207 SWFMovieDefinition::getBitmap(int id) const
208 {
209     const Bitmaps::const_iterator it = _bitmaps.find(id);
210     if (it == _bitmaps.end()) return nullptr;
211     return it->second.get();
212 }
213 
214 void
addBitmap(int id,boost::intrusive_ptr<CachedBitmap> im)215 SWFMovieDefinition::addBitmap(int id, boost::intrusive_ptr<CachedBitmap> im)
216 {
217     assert(im);
218     _bitmaps.insert(std::make_pair(id, im));
219 }
220 
221 sound_sample*
get_sound_sample(int id) const222 SWFMovieDefinition::get_sound_sample(int id) const
223 {
224     SoundSampleMap::const_iterator it = m_sound_samples.find(id);
225     if (it == m_sound_samples.end()) return nullptr;
226 
227     boost::intrusive_ptr<sound_sample> ch = it->second;
228 
229     return ch.get();
230 }
231 
232 void
add_sound_sample(int id,sound_sample * sam)233 SWFMovieDefinition::add_sound_sample(int id, sound_sample* sam)
234 {
235     assert(sam);
236     IF_VERBOSE_PARSE(
237     log_parse(_("Add sound sample %d assigning id %d"),
238         id, sam->m_sound_handler_id);
239     )
240     m_sound_samples.insert(std::make_pair(id,
241                 boost::intrusive_ptr<sound_sample>(sam)));
242 }
243 
244 // Read header and assign url
245 bool
readHeader(std::unique_ptr<IOChannel> in,const std::string & url)246 SWFMovieDefinition::readHeader(std::unique_ptr<IOChannel> in,
247         const std::string& url)
248 {
249 
250     _in = std::move(in);
251 
252     // we only read a movie once
253     assert(!_str.get());
254 
255     _url = url.empty() ? "<anonymous>" : url;
256 
257     std::uint32_t file_start_pos = _in->tell();
258     std::uint32_t header = _in->read_le32();
259     m_file_length = _in->read_le32();
260     _swf_end_pos = file_start_pos + m_file_length;
261 
262     m_version = (header >> 24) & 255;
263     if ((header & 0x0FFFFFF) != 0x00535746
264         && (header & 0x0FFFFFF) != 0x00535743) {
265         // ERROR
266         log_error(_("gnash::SWFMovieDefinition::read() -- "
267             "file does not start with a SWF header"));
268         return false;
269     }
270     const bool compressed = (header & 255) == 'C';
271 
272     IF_VERBOSE_PARSE(
273         log_parse(_("version: %d, file_length: %d"), m_version, m_file_length);
274     );
275 
276     if (compressed) {
277 #ifndef HAVE_ZLIB_H
278         log_error(_("SWFMovieDefinition::read(): unable to read "
279             "zipped SWF data; Gnash was compiled without zlib support"));
280         return false;
281 #else
282         IF_VERBOSE_PARSE(
283             log_parse(_("file is compressed"));
284         );
285 
286         // Uncompress the input as we read it.
287         _in = zlib_adapter::make_inflater(std::move(_in));
288 #endif
289     }
290 
291     assert(_in.get());
292 
293     _str.reset(new SWFStream(_in.get()));
294 
295     m_frame_size = readRect(*_str);
296 
297     // If the SWFRect is malformed, SWFRect::read would already
298     // print an error. We check again here just to give
299     // the error are better context.
300     if (m_frame_size.is_null()) {
301         IF_VERBOSE_MALFORMED_SWF(
302             log_swferror(_("non-finite movie bounds"));
303         );
304     }
305 
306     _str->ensureBytes(2 + 2); // frame rate, frame count.
307     m_frame_rate = _str->read_u16() / 256.0f;
308     if (!m_frame_rate) {
309         m_frame_rate = std::numeric_limits<std::uint16_t>::max();
310     }
311 
312     m_frame_count = _str->read_u16();
313 
314     // TODO: This seems dangerous, check closely
315     if (!m_frame_count) ++m_frame_count;
316 
317     IF_VERBOSE_PARSE(
318         log_parse(_("frame size = %s, frame rate = %f, frames = %d"),
319             m_frame_size, m_frame_rate, m_frame_count);
320     );
321 
322     setBytesLoaded(_str->tell());
323     return true;
324 }
325 
326 // Fire up the loading thread
327 bool
completeLoad()328 SWFMovieDefinition::completeLoad()
329 {
330 
331     // should call this only once
332     assert( ! _loader.started() );
333 
334     // should call readHeader before this
335     assert(_str.get());
336 
337 #ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
338 
339     // Start the loading frame
340     if ( ! _loader.start() )
341     {
342         log_error(_("Could not start loading thread"));
343         return false;
344     }
345 
346     // Wait until 'startup_frames' have been loaded
347     size_t startup_frames = 0;
348     ensure_frame_loaded(startup_frames);
349 
350 #else // undef LOAD_MOVIES_IN_A_SEPARATE_THREAD
351 
352     read_all_swf();
353 #endif
354 
355     return true;
356 }
357 
358 
359 // 1-based frame number
360 bool
ensure_frame_loaded(size_t framenum) const361 SWFMovieDefinition::ensure_frame_loaded(size_t framenum) const
362 {
363 #ifndef LOAD_MOVIES_IN_A_SEPARATE_THREAD
364     return (framenum <= _frames_loaded.load());
365 #endif
366     if ( framenum <= _frames_loaded.load() ) {
367         return true;
368     }
369 
370     _waiting_for_frame = framenum;
371 
372     std::unique_lock<std::mutex> lock(_loadingCanceledMutex);
373 
374     // TODO: return false on timeout
375 
376     // Make sure we don't wait here if the frame has been loaded, or the
377     // loading thread has finished.
378     _frame_reached_condition.wait(lock, [&] () {
379             return framenum <= _frames_loaded.load() || _loadingCanceled;
380         });
381 
382     return ( framenum <= _frames_loaded.load() );
383 }
384 
385 Movie*
createMovie(Global_as & gl,DisplayObject * parent)386 SWFMovieDefinition::createMovie(Global_as& gl, DisplayObject* parent)
387 {
388     as_object* o = getObjectWithPrototype(gl, NSV::CLASS_MOVIE_CLIP);
389     return new SWFMovie(o, this, parent);
390 }
391 
392 
393 //
394 // CharacterDictionary
395 //
396 
397 std::ostream&
operator <<(std::ostream & o,const CharacterDictionary & cd)398 operator<<(std::ostream& o, const CharacterDictionary& cd)
399 {
400 
401        for (const auto& elem : cd)
402        {
403            o << std::endl
404              << "Character: " << elem.first
405              << " at address: " << static_cast<void*>(elem.second.get());
406        }
407 
408        return o;
409 }
410 
411 boost::intrusive_ptr<SWF::DefinitionTag>
getDisplayObject(int id) const412 CharacterDictionary::getDisplayObject(int id) const
413 {
414     CharacterConstIterator it = _map.find(id);
415     if ( it == _map.end() )
416     {
417         IF_VERBOSE_PARSE(
418             log_parse(_("Could not find char %d, dump is: %s"), id, *this);
419         );
420         return boost::intrusive_ptr<SWF::DefinitionTag>();
421     }
422 
423     return it->second;
424 }
425 
426 void
addDisplayObject(int id,boost::intrusive_ptr<SWF::DefinitionTag> c)427 CharacterDictionary::addDisplayObject(int id,
428         boost::intrusive_ptr<SWF::DefinitionTag> c)
429 {
430     _map[id] = c;
431 }
432 
433 
434 void
read_all_swf()435 SWFMovieDefinition::read_all_swf()
436 {
437     assert(_str.get());
438 
439 #ifdef LOAD_MOVIES_IN_A_SEPARATE_THREAD
440     assert( _loader.isSelfThread() );
441     assert( _loader.started() );
442 #else
443     assert( ! _loader.started() );
444     assert( ! _loader.isSelfThread() );
445 #endif
446 
447     SWFParser parser(*_str, this, _runResources);
448 
449     const size_t startPos = _str->tell();
450     assert (startPos <= _swf_end_pos);
451 
452     size_t left = _swf_end_pos - startPos;
453 
454     const size_t chunkSize = 65535;
455 
456     try {
457         while (left) {
458 
459             {
460                 std::lock_guard<std::mutex> lock(_loadingCanceledMutex);
461                 if (_loadingCanceled) {
462                     log_debug("Loading thread cancellation requested, "
463                               "returning from read_all_swf");
464                     return;
465                 }
466             }
467             if (!parser.read(std::min<size_t>(left, chunkSize))) break;
468 
469             left -= parser.bytesRead();
470             setBytesLoaded(startPos + parser.bytesRead());
471         }
472 
473         // Make sure we won't leave any pending writers
474         // on any eventual fd-based IOChannel.
475         _str->consumeInput();
476 
477     }
478     catch (const ParserException& e) {
479         // This is a fatal parser error.
480         log_error(_("Error while parsing SWF stream."));
481     }
482 
483     // Set bytesLoaded to the current stream position unless it's greater
484     // than the reported length. TODO: should we be trying to continue
485     // parsing after an exception?
486     setBytesLoaded(std::min<size_t>(_str->tell(), _swf_end_pos));
487 
488     size_t floaded = get_loading_frame();
489     if (!m_playlist[floaded].empty())
490     {
491         IF_VERBOSE_MALFORMED_SWF(
492         log_swferror(_("%d control tags are NOT followed by"
493             " a SHOWFRAME tag"), m_playlist[floaded].size());
494         );
495     }
496 
497     if ( m_frame_count > floaded )
498     {
499         IF_VERBOSE_MALFORMED_SWF(
500         log_swferror(_("%d frames advertised in header, but only %d "
501                 "SHOWFRAME tags found in stream. Pretending we loaded "
502                 "all advertised frames"), m_frame_count, floaded);
503         );
504         _frames_loaded = m_frame_count;
505         // Notify any thread waiting on frame reached condition
506     }
507     {
508         std::lock_guard<std::mutex> lock(_loadingCanceledMutex);
509         _loadingCanceled = true;
510     }
511     _frame_reached_condition.notify_all();
512 }
513 
514 size_t
get_loading_frame() const515 SWFMovieDefinition::get_loading_frame() const
516 {
517     return _frames_loaded.load();
518 }
519 
520 void
incrementLoadedFrames()521 SWFMovieDefinition::incrementLoadedFrames()
522 {
523     ++_frames_loaded;
524 
525     if ( _frames_loaded.load() > m_frame_count )
526     {
527         IF_VERBOSE_MALFORMED_SWF(
528             log_swferror(_("number of SHOWFRAME tags "
529                 "in SWF stream '%s' (%d) exceeds "
530                 "the advertised number in header (%d)."),
531                 get_url(), _frames_loaded.load(),
532                 m_frame_count);
533         )
534     }
535 
536 #ifdef DEBUG_FRAMES_LOAD
537     log_debug("Loaded frame %u/%u", _frames_loaded.load(), m_frame_count);
538 #endif
539 
540     // signal load of frame if anyone requested it
541     // FIXME: _waiting_for_frame needs mutex ?
542     if (_frames_loaded.load() >= _waiting_for_frame.load() )
543     {
544         // or should we notify_one ?
545         // See: http://boost.org/doc/html/condition.html
546         _frame_reached_condition.notify_all();
547     }
548 
549 }
550 
551 void
registerExport(const std::string & symbol,std::uint16_t id)552 SWFMovieDefinition::registerExport(const std::string& symbol,
553         std::uint16_t id)
554 {
555     assert(id);
556 
557     std::lock_guard<std::mutex> lock(_exportedResourcesMutex);
558 #ifdef DEBUG_EXPORTS
559     log_debug("%s registering export %s, %s", get_url(), symbol, id);
560 #endif
561     _exportTable[symbol] = id;
562 }
563 
564 
565 void
add_frame_name(const std::string & n)566 SWFMovieDefinition::add_frame_name(const std::string& n)
567 {
568     std::lock_guard<std::mutex> lock1(_namedFramesMutex);
569 
570     _namedFrames.insert(std::make_pair(n, _frames_loaded.load()));
571 }
572 
573 bool
get_labeled_frame(const std::string & label,size_t & frame_number) const574 SWFMovieDefinition::get_labeled_frame(const std::string& label,
575         size_t& frame_number) const
576 {
577     std::lock_guard<std::mutex> lock(_namedFramesMutex);
578     NamedFrameMap::const_iterator it = _namedFrames.find(label);
579     if (it == _namedFrames.end()) return false;
580     frame_number = it->second;
581     return true;
582 }
583 
584 void
set_jpeg_loader(std::unique_ptr<image::JpegInput> j_in)585 SWFMovieDefinition::set_jpeg_loader(std::unique_ptr<image::JpegInput> j_in)
586 {
587     if (m_jpeg_in.get()) {
588         /// There should be only one JPEGTABLES tag in an SWF (see:
589         /// http://www.m2osw.com/en/swf_alexref.html#tag_jpegtables)
590         /// Discard any subsequent attempts to set the jpeg loader
591         /// to avoid crashing on very malformed SWFs. (No conclusive tests
592         /// for pp behaviour, though one version also crashes out on the
593         /// malformed SWF that triggers this assert in Gnash).
594         log_swferror(_("More than one JPEGTABLES tag found: not "
595                     "resetting JPEG loader"));
596         return;
597     }
598     m_jpeg_in = std::move(j_in);
599 }
600 
601 std::uint16_t
exportID(const std::string & symbol) const602 SWFMovieDefinition::exportID(const std::string& symbol) const
603 {
604     std::lock_guard<std::mutex> lock(_exportedResourcesMutex);
605     Exports::const_iterator it = _exportTable.find(symbol);
606     return (it == _exportTable.end()) ? 0 : it->second;
607 }
608 
609 
610 void
importResources(boost::intrusive_ptr<movie_definition> source,const Imports & imports)611 SWFMovieDefinition::importResources(
612         boost::intrusive_ptr<movie_definition> source, const Imports& imports)
613 {
614     size_t importedSyms = 0;
615 
616     // Mutex scope.
617 
618     for (const auto& import : imports) {
619 
620         size_t new_loading_frame = source->get_loading_frame();
621 
622         // 0.1 seconds.
623         const size_t naptime = 100000;
624 
625         // Timeout after two seconds of NO frames progress
626         const size_t timeout_ms = 2000000;
627         const size_t def_timeout = timeout_ms / naptime;
628 
629         size_t timeout = def_timeout;
630         size_t loading_frame = (size_t)-1; // used to keep track of advancements
631 
632         const int id = import.first;
633         const std::string& symbolName = import.second;
634 
635 #ifdef DEBUG_EXPORTS
636         log_debug("%s importing %s from %s", get_url(), symbolName,
637                 source->get_url());
638 #endif
639         std::uint16_t targetID;
640 
641         while(!(targetID = source->exportID(symbolName))) {
642 
643             // We checked last (or past-last) advertised frame.
644             // TODO: this check should really be for a parser
645             //       process being active or not, as SWF
646             //       might advertise less frames then actually
647             //       found in it...
648             //
649             if (new_loading_frame >= source->get_frame_count()) {
650                 // Update of loading_frame is
651                 // really just for the latter debugging output
652                 loading_frame = new_loading_frame;
653                 break;
654             }
655 
656             // There's more frames to parse, go ahead
657             // TODO: this is still based on *advertised*
658             //       number of frames, if SWF advertises
659             //       more then actually found we'd be
660             //       keep trying till timeout, see the
661             //       other TODO above.
662 
663             // We made frame progress since last iteration
664             // so sleep some and try again
665             if (new_loading_frame != loading_frame) {
666 #ifdef DEBUG_EXPORTS
667                 log_debug("looking for exported resource: frame load "
668                             "advancement (from %d to %d)",
669                     loading_frame, new_loading_frame);
670 #endif
671                 loading_frame = new_loading_frame;
672                 timeout = def_timeout+1;
673             }
674             else if (!--timeout) {
675                 // no progress since last run, and
676                 // timeout reached: give up
677                 break;
678             }
679 
680             // take a breath to give other threads more time to advance
681             gnashSleep(naptime);
682 
683         }
684 
685         if ( ! targetID ) {
686             // timed out
687             if (!timeout) {
688                 log_error("Timeout (%d milliseconds) seeking export "
689                     "symbol %s in movie %s. Frames loaded %d/%d",
690                     timeout_ms / 1000, symbolName,
691                     source->get_url(),
692                     loading_frame, source->get_frame_count());
693             }
694             else {
695                 // eof
696                 //assert(loading_frame >= m_frame_count);
697                 log_error("No export symbol %s found in movie %s. "
698                     "Frames loaded %d/%d",
699                     symbolName, source->get_url(), loading_frame,
700                     source->get_frame_count());
701             }
702             continue;
703         }
704 
705 #ifdef DEBUG_EXPORTS
706         log_debug("Export symbol %s found in movie %s with targetID %d. "
707                     "Frames loaded %d/%d",
708                     symbolName, source->get_url(),
709                     targetID,
710                     loading_frame,
711                     source->get_frame_count());
712 #endif
713 
714         boost::intrusive_ptr<SWF::DefinitionTag> res =
715             source->getDefinitionTag(targetID);
716         if (res) {
717             // It's a character import.
718             addDisplayObject(id, res.get());
719             registerExport(symbolName, id);
720             ++importedSyms;
721             continue;
722         }
723 
724         Font* f = source->get_font(id);
725         if (f) {
726             // It's a font import
727             add_font(id, f);
728             registerExport(symbolName, id);
729             ++importedSyms;
730             continue;
731         }
732 
733         log_error(_("import error: could not find resource '%s' in "
734                     "movie '%s'"), symbolName, source->get_url());
735     }
736 
737     if (importedSyms) {
738         _importSources.insert(source);
739     }
740 }
741 
742 } // namespace gnash
743