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