1 #include "core/misc.hpp"
2 #include "core/moviedata.hpp"
3 #include "core/moviefile.hpp"
4 #include "core/rrdata.hpp"
5 #include "library/zip.hpp"
6 #include "library/string.hpp"
7 #include "library/minmax.hpp"
8 #include "library/serialization.hpp"
9 #include "library/binarystream.hpp"
10 #include "interface/romtype.hpp"
17 #include <boost/iostreams/copy.hpp>
18 #include <boost/iostreams/device/back_inserter.hpp>
19 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
23 #define DEFAULT_RTC_SECOND 1000000000ULL
24 #define DEFAULT_RTC_SUBSECOND 0ULL
28 std::map
<std::string
, moviefile
> memory_saves
;
33 TAG_ANCHOR_SAVE
= 0xf5e0fad7,
34 TAG_AUTHOR
= 0xafff97b4,
35 TAG_CORE_VERSION
= 0xe4344c7e,
36 TAG_GAMENAME
= 0xe80d6970,
37 TAG_HOSTMEMORY
= 0x3bf9d187,
38 TAG_MACRO
= 0xd261338f,
39 TAG_MOVIE
= 0xf3dca44b,
40 TAG_MOVIE_SRAM
= 0xbbc824b7,
41 TAG_MOVIE_TIME
= 0x18c3a975,
42 TAG_PROJECT_ID
= 0x359bfbab,
43 TAG_ROMHASH
= 0x0428acfc,
44 TAG_RRDATA
= 0xa3a07f71,
45 TAG_SAVE_SRAM
= 0xae9bfb2f,
46 TAG_SAVESTATE
= 0x2e5bc2ac,
47 TAG_SCREENSHOT
= 0xc6760d0e,
48 TAG_SUBTITLE
= 0x6a7054d3,
49 TAG_RAMCONTENT
= 0xd3ec3770,
50 TAG_ROMHINT
= 0x6f715830
55 std::map
<std::string
, std::string
> read_settings(zip::reader
& r
)
57 std::map
<std::string
, std::string
> x
;
59 if(!regex_match("port[0-9]+|setting\\..+", i
))
63 if(i
.substr(0, 4) == "port")
67 if(r
.read_linefile(i
, v
, true))
73 template<typename target
>
74 void write_settings(target
& w
, const std::map
<std::string
, std::string
>& settings
,
75 core_setting_group
& sgroup
, std::function
<void(target
& w
, const std::string
& name
,
76 const std::string
& value
)> writefn
)
78 for(auto i
: settings
) {
79 if(!sgroup
.settings
.count(i
.first
))
81 if(sgroup
.settings
.find(i
.first
)->second
.dflt
== i
.second
)
83 writefn(w
, i
.first
, i
.second
);
87 std::map
<std::string
, uint64_t> read_active_macros(zip::reader
& r
, const std::string
& member
)
89 std::map
<std::string
, uint64_t> x
;
90 if(!r
.has_member(member
))
92 std::istream
& m
= r
[member
];
100 regex_results rx
= regex("([0-9]+) +(.*)", out
);
102 messages
<< "Warning: Bad macro state: '" << out
<< "'" << std::endl
;
106 uint64_t f
= parse_value
<uint64_t>(rx
[1]);
119 void write_active_macros(zip::writer
& w
, const std::string
& member
, const std::map
<std::string
, uint64_t>& ma
)
123 std::ostream
& m
= w
.create_file(member
);
126 m
<< i
.second
<< " " << i
.first
<< std::endl
;
128 throw std::runtime_error("Can't write ZIP file member");
137 void read_authors_file(zip::reader
& r
, std::vector
<std::pair
<std::string
, std::string
>>& authors
)
138 throw(std::bad_alloc
, std::runtime_error
)
140 std::istream
& m
= r
["authors"];
143 while(std::getline(m
, x
)) {
145 auto g
= split_author(x
);
146 authors
.push_back(g
);
155 std::string
read_rrdata(zip::reader
& r
, std::vector
<char>& out
) throw(std::bad_alloc
, std::runtime_error
)
157 r
.read_raw_file("rrdata", out
);
158 uint64_t count
= rrdata
.count(out
);
159 std::ostringstream x
;
164 void write_rrdata(zip::writer
& w
) throw(std::bad_alloc
, std::runtime_error
)
167 std::vector
<char> out
;
168 count
= rrdata
.write(out
);
169 w
.write_raw_file("rrdata", out
);
170 std::ostream
& m2
= w
.create_file("rerecords");
172 m2
<< count
<< std::endl
;
174 throw std::runtime_error("Can't write ZIP file member");
182 void write_authors_file(zip::writer
& w
, std::vector
<std::pair
<std::string
, std::string
>>& authors
)
183 throw(std::bad_alloc
, std::runtime_error
)
185 std::ostream
& m
= w
.create_file("authors");
187 for(auto i
: authors
)
189 m
<< i
.first
<< std::endl
;
191 m
<< i
.first
<< "|" << i
.second
<< std::endl
;
193 throw std::runtime_error("Can't write ZIP file member");
201 void write_input(zip::writer
& w
, controller_frame_vector
& input
)
202 throw(std::bad_alloc
, std::runtime_error
)
204 std::ostream
& m
= w
.create_file("input");
206 char buffer
[MAX_SERIALIZED_SIZE
];
207 for(size_t i
= 0; i
< input
.size(); i
++) {
208 input
[i
].serialize(buffer
);
209 m
<< buffer
<< std::endl
;
212 throw std::runtime_error("Can't write ZIP file member");
220 void read_subtitles(zip::reader
& r
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
223 if(!r
.has_member(file
))
225 std::istream
& m
= r
[file
];
229 std::getline(m
, out
);
231 auto r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out
);
234 x
[moviefile_subtiming(parse_value
<uint64_t>(r
[1]), parse_value
<uint64_t>(r
[2]))] =
244 void write_subtitles(zip::writer
& w
, const std::string
& file
, std::map
<moviefile_subtiming
, std::string
>& x
)
246 std::ostream
& m
= w
.create_file(file
);
249 m
<< i
.first
.get_frame() << " " << i
.first
.get_length() << " " << s_escape(i
.second
)
252 throw std::runtime_error("Can't write ZIP file member");
260 void read_input(zip::reader
& r
, controller_frame_vector
& input
, unsigned version
) throw(std::bad_alloc
,
263 controller_frame tmp
= input
.blank_frame(false);
264 std::istream
& m
= r
["input"];
267 while(std::getline(m
, x
)) {
270 tmp
.deserialize(x
.c_str());
281 void read_pollcounters(zip::reader
& r
, const std::string
& file
, std::vector
<uint32_t>& pctr
)
283 std::istream
& m
= r
[file
];
286 while(std::getline(m
, x
)) {
289 int32_t y
= parse_value
<int32_t>(x
);
307 void write_pollcounters(zip::writer
& w
, const std::string
& file
, const std::vector
<uint32_t>& pctr
)
309 std::ostream
& m
= w
.create_file(file
);
312 int32_t x
= i
& 0x7FFFFFFFUL
;
313 if((i
& 0x80000000UL
) == 0)
318 throw std::runtime_error("Can't write ZIP file member");
326 moviefile::brief_info::brief_info(const std::string
& filename
)
329 if(rr
= regex("\\$MEMORY:(.*)", filename
)) {
330 if(!memory_saves
.count(rr
[1]))
331 throw std::runtime_error("No such memory save");
332 moviefile
& mv
= memory_saves
[rr
[1]];
333 sysregion
= mv
.gametype
->get_name();
334 corename
= mv
.coreversion
;
335 projectid
= mv
.projectid
;
336 current_frame
= mv
.is_savestate
? mv
.save_frame
: 0;
337 rerecords
= mv
.rerecords_mem
;
338 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
339 hash
[i
] = mv
.romimg_sha256
[i
];
340 hashxml
[i
] = mv
.romxml_sha256
[i
];
341 hint
[i
] = mv
.namehint
[i
];
346 std::istream
& s
= zip::openrel(filename
, "");
349 if(!strcmp(buf
, "lsmv\x1A")) {
356 zip::reader
r(filename
);
358 r
.read_linefile("systemid", tmp
);
359 if(tmp
.substr(0, 8) != "lsnes-rr")
360 throw std::runtime_error("Not lsnes movie");
361 r
.read_linefile("gametype", sysregion
);
362 r
.read_linefile("coreversion", corename
);
363 r
.read_linefile("projectid", projectid
);
364 if(r
.has_member("savestate"))
365 r
.read_numeric_file("saveframe", current_frame
);
368 r
.read_numeric_file("rerecords", rerecords
);
369 r
.read_linefile("rom.sha256", hash
[0], true);
370 r
.read_linefile("romxml.sha256", hashxml
[0], true);
371 r
.read_linefile("rom.hint", hint
[0], true);
373 if(r
.has_member("slot`.sha256"))
375 for(size_t i
= 1; i
< ROM_SLOT_COUNT
; i
++) {
376 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << ".sha256").str(), hash
[i
],
378 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << "xml.sha256").str(),
380 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << ".hint").str(), hint
[i
],
385 void moviefile::brief_info::binary_io(std::istream
& _stream
)
387 binarystream::input
in(_stream
);
388 sysregion
= in
.string();
389 //Discard the settings.
395 {TAG_CORE_VERSION
, [this](binarystream::input
& s
) {
396 this->corename
= s
.string_implicit();
397 }},{TAG_PROJECT_ID
, [this](binarystream::input
& s
) {
398 this->projectid
= s
.string_implicit();
399 }},{TAG_SAVESTATE
, [this](binarystream::input
& s
) {
400 this->current_frame
= s
.number();
401 }},{TAG_RRDATA
, [this](binarystream::input
& s
) {
402 std::vector
<char> c_rrdata
;
403 s
.blob_implicit(c_rrdata
);
404 this->rerecords
= rrdata
.count(c_rrdata
);
405 }},{TAG_ROMHASH
, [this](binarystream::input
& s
) {
406 uint8_t n
= s
.byte();
407 std::string h
= s
.string_implicit();
408 if(n
> 2 * ROM_SLOT_COUNT
)
411 this->hashxml
[n
>> 1] = h
;
413 this->hash
[n
>> 1] = h
;
414 }},{TAG_ROMHINT
, [this](binarystream::input
& s
) {
415 uint8_t n
= s
.byte();
416 std::string h
= s
.string_implicit();
417 if(n
> ROM_SLOT_COUNT
)
421 }, binarystream::null_default
);
424 moviefile::moviefile() throw(std::bad_alloc
)
426 static port_type_set dummy_types
;
427 force_corrupt
= false;
432 is_savestate
= false;
433 movie_rtc_second
= rtc_second
= DEFAULT_RTC_SECOND
;
434 movie_rtc_subsecond
= rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
435 start_paused
= false;
436 lazy_project_create
= true;
440 moviefile::moviefile(const std::string
& movie
, core_type
& romtype
) throw(std::bad_alloc
, std::runtime_error
)
443 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
444 if(!memory_saves
.count(rr
[1]))
445 throw std::runtime_error("No such memory save");
446 *this = memory_saves
[rr
[1]];
450 start_paused
= false;
451 force_corrupt
= false;
452 is_savestate
= false;
453 lazy_project_create
= false;
456 std::istream
& s
= zip::openrel(movie
, "");
459 if(!strcmp(buf
, "lsmv\x1A")) {
460 binary_io(s
, romtype
);
466 zip::reader
r(movie
);
467 r
.read_linefile("systemid", tmp
);
468 if(tmp
.substr(0, 8) != "lsnes-rr")
469 throw std::runtime_error("Not lsnes movie");
470 r
.read_linefile("controlsversion", tmp
);
472 throw std::runtime_error("Can't decode movie data");
473 r
.read_linefile("gametype", tmp
);
475 gametype
= &romtype
.lookup_sysregion(tmp
);
476 } catch(std::bad_alloc
& e
) {
478 } catch(std::exception
& e
) {
479 throw std::runtime_error("Illegal game type '" + tmp
+ "'");
481 settings
= read_settings(r
);
482 auto ctrldata
= gametype
->get_type().controllerconfig(settings
);
483 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
486 r
.read_linefile("gamename", gamename
, true);
487 r
.read_linefile("projectid", projectid
);
488 rerecords
= read_rrdata(r
, c_rrdata
);
489 r
.read_linefile("coreversion", coreversion
);
490 r
.read_linefile("rom.sha256", romimg_sha256
[0], true);
491 r
.read_linefile("romxml.sha256", romxml_sha256
[0], true);
492 r
.read_linefile("rom.hint", namehint
[0], true);
494 if(r
.has_member("slot`.sha256"))
496 for(size_t i
= 1; i
< ROM_SLOT_COUNT
; i
++) {
497 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << ".sha256").str(), romimg_sha256
[i
],
499 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << "xml.sha256").str(),
500 romxml_sha256
[i
], true);
501 r
.read_linefile((stringfmt() << "slot" << (char)(base
+ i
- 1) << ".hint").str(), namehint
[i
],
504 read_subtitles(r
, "subtitles", subtitles
);
505 movie_rtc_second
= DEFAULT_RTC_SECOND
;
506 movie_rtc_subsecond
= DEFAULT_RTC_SUBSECOND
;
507 r
.read_numeric_file("starttime.second", movie_rtc_second
, true);
508 r
.read_numeric_file("starttime.subsecond", movie_rtc_subsecond
, true);
509 rtc_second
= movie_rtc_second
;
510 rtc_subsecond
= movie_rtc_subsecond
;
511 if(r
.has_member("savestate.anchor"))
512 r
.read_raw_file("savestate.anchor", anchor_savestate
);
513 if(r
.has_member("savestate")) {
515 r
.read_numeric_file("saveframe", save_frame
, true);
516 r
.read_numeric_file("lagcounter", lagged_frames
, true);
517 read_pollcounters(r
, "pollcounters", pollcounters
);
518 if(r
.has_member("hostmemory"))
519 r
.read_raw_file("hostmemory", host_memory
);
520 r
.read_raw_file("savestate", savestate
);
522 if(name
.length() >= 5 && name
.substr(0, 5) == "sram.")
523 r
.read_raw_file(name
, sram
[name
.substr(5)]);
524 r
.read_raw_file("screenshot", screenshot
);
525 //If these can't be read, just use some (wrong) values.
526 r
.read_numeric_file("savetime.second", rtc_second
, true);
527 r
.read_numeric_file("savetime.subsecond", rtc_subsecond
, true);
528 uint64_t _poll_flag
= 2; //Legacy behaviour is the default.
529 r
.read_numeric_file("pollflag", _poll_flag
, true);
530 poll_flag
= _poll_flag
;
531 active_macros
= read_active_macros(r
, "macros");
534 if(name
.length() >= 8 && name
.substr(0, 8) == "initram.")
535 r
.read_raw_file(name
, ramcontent
[name
.substr(8)]);
536 if(rtc_subsecond
< 0 || movie_rtc_subsecond
< 0)
537 throw std::runtime_error("Invalid RTC subsecond value");
538 std::string name
= r
.find_first();
540 if(name
.length() >= 10 && name
.substr(0, 10) == "moviesram.")
541 r
.read_raw_file(name
, movie_sram
[name
.substr(10)]);
542 read_authors_file(r
, authors
);
543 read_input(r
, input
, 0);
546 void moviefile::save(const std::string
& movie
, unsigned compression
, bool binary
) throw(std::bad_alloc
,
550 if(rr
= regex("\\$MEMORY:(.*)", movie
)) {
551 memory_saves
[rr
[1]] = *this;
555 std::string tmp
= movie
+ ".tmp";
556 std::ofstream
strm(tmp
.c_str(), std::ios_base::binary
);
558 throw std::runtime_error("Can't open output file");
559 char buf
[5] = {'l', 's', 'm', 'v', 0x1A};
562 throw std::runtime_error("Failed to write to output file");
565 throw std::runtime_error("Failed to write to output file");
567 std::string backup
= movie
+ ".backup";
568 zip::rename_overwrite(movie
.c_str(), backup
.c_str());
569 if(zip::rename_overwrite(tmp
.c_str(), movie
.c_str()) < 0)
570 throw std::runtime_error("Can't rename '" + tmp
+ "' -> '" + movie
+ "'");
573 zip::writer
w(movie
, compression
);
577 void moviefile::save(std::ostream
& stream
) throw(std::bad_alloc
, std::runtime_error
)
579 zip::writer
w(stream
, 0);
583 void moviefile::save(zip::writer
& w
) throw(std::bad_alloc
, std::runtime_error
)
585 w
.write_linefile("gametype", gametype
->get_name());
586 write_settings
<zip::writer
>(w
, settings
, gametype
->get_type().get_settings(), [](zip::writer
& w
,
587 const std::string
& name
, const std::string
& value
) -> void {
588 if(regex_match("port[0-9]+", name
))
589 w
.write_linefile(name
, value
);
591 w
.write_linefile("setting." + name
, value
);
593 w
.write_linefile("gamename", gamename
, true);
594 w
.write_linefile("systemid", "lsnes-rr1");
595 w
.write_linefile("controlsversion", "0");
596 coreversion
= gametype
->get_type().get_core_identifier();
597 w
.write_linefile("coreversion", coreversion
);
598 w
.write_linefile("projectid", projectid
);
600 w
.write_linefile("rom.sha256", romimg_sha256
[0], true);
601 w
.write_linefile("romxml.sha256", romxml_sha256
[0], true);
602 w
.write_linefile("rom.hint", namehint
[0], true);
603 for(size_t i
= 1; i
< ROM_SLOT_COUNT
; i
++) {
604 w
.write_linefile((stringfmt() << "slot" << (char)(96 + i
) << ".sha256").str(), romimg_sha256
[i
],
606 w
.write_linefile((stringfmt() << "slot" << (char)(96 + i
) << "xml.sha256").str(), romxml_sha256
[i
],
608 w
.write_linefile((stringfmt() << "slot" << (char)(96 + i
) << ".hint").str(), namehint
[i
],
611 write_subtitles(w
, "subtitles", subtitles
);
612 for(auto i
: movie_sram
)
613 w
.write_raw_file("moviesram." + i
.first
, i
.second
);
614 w
.write_numeric_file("starttime.second", movie_rtc_second
);
615 w
.write_numeric_file("starttime.subsecond", movie_rtc_subsecond
);
616 if(!anchor_savestate
.empty())
617 w
.write_raw_file("savestate.anchor", anchor_savestate
);
619 w
.write_numeric_file("saveframe", save_frame
);
620 w
.write_numeric_file("lagcounter", lagged_frames
);
621 write_pollcounters(w
, "pollcounters", pollcounters
);
622 w
.write_raw_file("hostmemory", host_memory
);
623 w
.write_raw_file("savestate", savestate
);
624 w
.write_raw_file("screenshot", screenshot
);
626 w
.write_raw_file("sram." + i
.first
, i
.second
);
627 w
.write_numeric_file("savetime.second", rtc_second
);
628 w
.write_numeric_file("savetime.subsecond", rtc_subsecond
);
629 w
.write_numeric_file("pollflag", poll_flag
);
630 write_active_macros(w
, "macros", active_macros
);
632 for(auto i
: ramcontent
)
633 w
.write_raw_file("initram." + i
.first
, i
.second
);
634 write_authors_file(w
, authors
);
635 write_input(w
, input
);
640 Following need to be saved:
642 - settings (string name, value pairs)
643 - gamename (optional string)
644 - core version (string)
647 - ROM hashes (2*27 table of optional strings)
648 - Subtitles (list of number,number,string)
649 - SRAMs (dictionary string->blob.)
650 - Starttime (number,number)
651 - Anchor savestate (optional blob)
652 - Save frame (savestate-only, numeric).
653 - Lag counter (savestate-only, numeric).
654 - pollcounters (savestate-only, vector of numbers).
655 - hostmemory (savestate-only, blob).
656 - screenshot (savestate-only, blob).
657 - Save SRAMs (savestate-only, dictionary string->blob.)
658 - Save time (savestate-only, number,number)
659 - Poll flag (savestate-only, boolean)
660 - Macros (savestate-only, ???)
661 - Authors (list of string,string).
665 void moviefile::binary_io(std::ostream
& _stream
) throw(std::bad_alloc
, std::runtime_error
)
667 binarystream::output
out(_stream
);
668 out
.string(gametype
->get_name());
669 write_settings
<binarystream::output
>(out
, settings
, gametype
->get_type().get_settings(),
670 [](binarystream::output
& s
, const std::string
& name
, const std::string
& value
) -> void {
677 out
.extension(TAG_MOVIE_TIME
, [this](binarystream::output
& s
) {
678 s
.number(this->movie_rtc_second
);
679 s
.number(this->movie_rtc_subsecond
);
682 out
.extension(TAG_PROJECT_ID
, [this](binarystream::output
& s
) {
683 s
.string_implicit(this->projectid
);
686 out
.extension(TAG_CORE_VERSION
, [this](binarystream::output
& s
) {
687 this->coreversion
= this->gametype
->get_type().get_core_identifier();
688 s
.string_implicit(this->coreversion
);
691 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
692 out
.extension(TAG_ROMHASH
, [this, i
](binarystream::output
& s
) {
693 if(!this->romimg_sha256
[i
].length()) return;
695 s
.string_implicit(this->romimg_sha256
[i
]);
697 out
.extension(TAG_ROMHASH
, [this, i
](binarystream::output
& s
) {
698 if(!this->romxml_sha256
[i
].length()) return;
700 s
.string_implicit(this->romxml_sha256
[i
]);
702 out
.extension(TAG_ROMHINT
, [this, i
](binarystream::output
& s
) {
703 if(!this->namehint
[i
].length()) return;
705 s
.string_implicit(this->namehint
[i
]);
709 out
.extension(TAG_RRDATA
, [this](binarystream::output
& s
) {
711 std::vector
<char> rrd
;
712 count
= rrdata
.write(rrd
);
713 s
.blob_implicit(rrd
);
716 for(auto i
: movie_sram
)
717 out
.extension(TAG_MOVIE_SRAM
, [&i
](binarystream::output
& s
) {
719 s
.blob_implicit(i
.second
);
722 out
.extension(TAG_ANCHOR_SAVE
, [this](binarystream::output
& s
) {
723 s
.blob_implicit(this->anchor_savestate
);
726 out
.extension(TAG_SAVESTATE
, [this](binarystream::output
& s
) {
727 s
.number(this->save_frame
);
728 s
.number(this->lagged_frames
);
729 s
.number(this->rtc_second
);
730 s
.number(this->rtc_subsecond
);
731 s
.number(this->pollcounters
.size());
732 for(auto i
: this->pollcounters
)
734 s
.byte(this->poll_flag
? 0x01 : 0x00);
735 s
.blob_implicit(this->savestate
);
736 }, true, out
.numberbytes(save_frame
) + out
.numberbytes(lagged_frames
) + out
.numberbytes(rtc_second
) +
737 out
.numberbytes(rtc_subsecond
) + out
.numberbytes(pollcounters
.size()) +
738 4 * pollcounters
.size() + 1 + savestate
.size());
740 out
.extension(TAG_HOSTMEMORY
, [this](binarystream::output
& s
) {
741 s
.blob_implicit(this->host_memory
);
744 out
.extension(TAG_SCREENSHOT
, [this](binarystream::output
& s
) {
745 s
.blob_implicit(this->screenshot
);
746 }, true, screenshot
.size());
749 out
.extension(TAG_SAVE_SRAM
, [&i
](binarystream::output
& s
) {
751 s
.blob_implicit(i
.second
);
756 out
.extension(TAG_GAMENAME
, [this](binarystream::output
& s
) {
757 s
.string_implicit(this->gamename
);
760 for(auto i
: subtitles
)
761 out
.extension(TAG_SUBTITLE
, [&i
](binarystream::output
& s
) {
762 s
.number(i
.first
.get_frame());
763 s
.number(i
.first
.get_length());
764 s
.string_implicit(i
.second
);
767 for(auto i
: authors
)
768 out
.extension(TAG_AUTHOR
, [&i
](binarystream::output
& s
) {
770 s
.string_implicit(i
.second
);
773 for(auto i
: active_macros
)
774 out
.extension(TAG_MACRO
, [&i
](binarystream::output
& s
) {
776 s
.string_implicit(i
.first
);
779 for(auto i
: ramcontent
) {
780 out
.extension(TAG_RAMCONTENT
, [&i
](binarystream::output
& s
) {
782 s
.blob_implicit(i
.second
);
786 out
.extension(TAG_MOVIE
, [this](binarystream::output
& s
) {
787 input
.save_binary(s
);
788 }, true, input
.binary_size());
791 void moviefile::binary_io(std::istream
& _stream
, core_type
& romtype
) throw(std::bad_alloc
, std::runtime_error
)
793 binarystream::input
in(_stream
);
794 std::string tmp
= in
.string();
796 gametype
= &romtype
.lookup_sysregion(tmp
);
797 } catch(std::bad_alloc
& e
) {
799 } catch(std::exception
& e
) {
800 throw std::runtime_error("Illegal game type '" + tmp
+ "'");
803 std::string name
= in
.string();
804 settings
[name
] = in
.string();
806 auto ctrldata
= gametype
->get_type().controllerconfig(settings
);
807 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
811 {TAG_ANCHOR_SAVE
, [this](binarystream::input
& s
) {
812 s
.blob_implicit(this->anchor_savestate
);
813 }},{TAG_AUTHOR
, [this](binarystream::input
& s
) {
814 std::string a
= s
.string();
815 std::string b
= s
.string_implicit();
816 this->authors
.push_back(std::make_pair(a
, b
));
817 }},{TAG_CORE_VERSION
, [this](binarystream::input
& s
) {
818 this->coreversion
= s
.string_implicit();
819 }},{TAG_GAMENAME
, [this](binarystream::input
& s
) {
820 this->gamename
= s
.string_implicit();
821 }},{TAG_HOSTMEMORY
, [this](binarystream::input
& s
) {
822 s
.blob_implicit(this->host_memory
);
823 }},{TAG_MACRO
, [this](binarystream::input
& s
) {
824 uint64_t n
= s
.number();
825 this->active_macros
[s
.string_implicit()] = n
;
826 }},{TAG_MOVIE
, [this](binarystream::input
& s
) {
827 input
.load_binary(s
);
828 }},{TAG_MOVIE_SRAM
, [this](binarystream::input
& s
) {
829 std::string a
= s
.string();
830 s
.blob_implicit(this->movie_sram
[a
]);
831 }},{TAG_RAMCONTENT
, [this](binarystream::input
& s
) {
832 std::string a
= s
.string();
833 s
.blob_implicit(this->ramcontent
[a
]);
834 }},{TAG_MOVIE_TIME
, [this](binarystream::input
& s
) {
835 this->movie_rtc_second
= s
.number();
836 this->movie_rtc_subsecond
= s
.number();
837 }},{TAG_PROJECT_ID
, [this](binarystream::input
& s
) {
838 this->projectid
= s
.string_implicit();
839 }},{TAG_ROMHASH
, [this](binarystream::input
& s
) {
840 uint8_t n
= s
.byte();
841 std::string h
= s
.string_implicit();
842 if(n
> 2 * ROM_SLOT_COUNT
)
845 romxml_sha256
[n
>> 1] = h
;
847 romimg_sha256
[n
>> 1] = h
;
848 }},{TAG_ROMHINT
, [this](binarystream::input
& s
) {
849 uint8_t n
= s
.byte();
850 std::string h
= s
.string_implicit();
851 if(n
> ROM_SLOT_COUNT
)
854 }},{TAG_RRDATA
, [this](binarystream::input
& s
) {
855 s
.blob_implicit(this->c_rrdata
);
856 this->rerecords
= (stringfmt() << rrdata
.count(c_rrdata
)).str();
857 }},{TAG_SAVE_SRAM
, [this](binarystream::input
& s
) {
858 std::string a
= s
.string();
859 s
.blob_implicit(this->sram
[a
]);
860 }},{TAG_SAVESTATE
, [this](binarystream::input
& s
) {
861 this->is_savestate
= true;
862 this->save_frame
= s
.number();
863 this->lagged_frames
= s
.number();
864 this->rtc_second
= s
.number();
865 this->rtc_subsecond
= s
.number();
866 this->pollcounters
.resize(s
.number());
867 for(auto& i
: this->pollcounters
)
869 this->poll_flag
= (s
.byte() != 0);
870 s
.blob_implicit(this->savestate
);
871 }},{TAG_SCREENSHOT
, [this](binarystream::input
& s
) {
872 s
.blob_implicit(this->screenshot
);
873 }},{TAG_SUBTITLE
, [this](binarystream::input
& s
) {
874 uint64_t f
= s
.number();
875 uint64_t l
= s
.number();
876 std::string x
= s
.string_implicit();
877 this->subtitles
[moviefile_subtiming(f
, l
)] = x
;
879 }, binarystream::null_default
);
882 uint64_t moviefile::get_frame_count() throw()
884 return input
.count_frames();
889 const int BLOCK_SECONDS
= 0;
890 const int BLOCK_FRAMES
= 1;
891 const int STEP_W
= 2;
892 const int STEP_N
= 3;
895 uint64_t moviefile::get_movie_length() throw()
897 uint64_t frames
= get_frame_count();
899 return 100000000ULL * frames
/ 6;
902 gametype
->fill_framerate_magic(_magic
);
903 uint64_t t
= _magic
[BLOCK_SECONDS
] * 1000000000ULL * (frames
/ _magic
[BLOCK_FRAMES
]);
904 frames
%= _magic
[BLOCK_FRAMES
];
905 t
+= frames
* _magic
[STEP_W
] + (frames
* _magic
[STEP_N
] / _magic
[BLOCK_FRAMES
]);
909 moviefile
& moviefile::memref(const std::string
& slot
)
911 return memory_saves
[slot
];
916 void emerg_write_bytes(int handle
, const uint8_t* d
, size_t dsize
)
919 ssize_t r
= write(handle
, d
, dsize
);
926 void emerg_write_number(int handle
, uint64_t num
)
931 bool cont
= (num
> 127);
932 data
[len
++] = (cont
? 0x80 : 0x00) | (num
& 0x7F);
935 emerg_write_bytes(handle
, data
, len
);
937 size_t number_size(uint64_t num
)
946 void emerg_write_number32(int handle
, uint32_t num
)
949 serialization::u32b(buf
, num
);
950 emerg_write_bytes(handle
, (const uint8_t*)buf
, 4);
952 void emerg_write_member(int handle
, uint32_t tag
, uint64_t size
)
954 emerg_write_number32(handle
, 0xaddb2d86);
955 emerg_write_number32(handle
, tag
);
956 emerg_write_number(handle
, size
);
958 void emerg_write_blob_implicit(int handle
, const std::vector
<char>& v
)
960 emerg_write_bytes(handle
, (const uint8_t*)&v
[0], v
.size());
962 void emerg_write_byte(int handle
, uint8_t byte
)
964 emerg_write_bytes(handle
, &byte
, 1);
966 size_t string_size(const std::string
& str
)
968 return number_size(str
.length()) + str
.length();
970 void emerg_write_string_implicit(int handle
, const std::string
& str
)
972 for(size_t i
= 0; i
< str
.length(); i
++)
973 emerg_write_byte(handle
, str
[i
]);
975 void emerg_write_string(int handle
, const std::string
& str
)
977 emerg_write_number(handle
, str
.length());
978 emerg_write_string_implicit(handle
, str
);
980 uint64_t append_number(char* ptr
, uint64_t n
)
988 for(unsigned i
= digits
; i
> 0; i
--) {
989 ptr
[i
- 1] = (n
% 10) + '0';
996 void emerg_save_movie(const moviefile
& mv
)
998 //Whee, assume state of the emulator is totally busted.
999 const controller_frame_vector
& v
= mv
.input
;
1001 return; //No valid movie. Trying to save would segfault.
1002 char header
[] = {'l', 's', 'm', 'v', '\x1a'};
1004 char filename_buf
[512];
1007 filename_buf
[0] = 0;
1008 strcpy(filename_buf
+ strlen(filename_buf
), "crashsave-");
1009 append_number(filename_buf
+ strlen(filename_buf
), time(NULL
));
1010 strcpy(filename_buf
+ strlen(filename_buf
), "-");
1011 append_number(filename_buf
+ strlen(filename_buf
), number
++);
1012 strcpy(filename_buf
+ strlen(filename_buf
), ".lsmv");
1013 fd
= open(filename_buf
, O_WRONLY
| O_CREAT
| O_EXCL
, 0666);
1014 if(fd
< 0 && errno
== EEXIST
) goto name_again
;
1015 if(fd
< 0) return; //Can't open.
1017 emerg_write_bytes(fd
, (const uint8_t*)header
, sizeof(header
));
1018 emerg_write_string(fd
, mv
.gametype
->get_name());
1019 for(auto& i
: mv
.settings
) {
1020 emerg_write_byte(fd
, 1);
1021 emerg_write_string(fd
, i
.first
);
1022 emerg_write_string(fd
, i
.second
);
1024 emerg_write_byte(fd
, 0);
1026 uint64_t pages
= v
.get_page_count();
1027 uint64_t stride
= v
.get_stride();
1028 uint64_t pageframes
= v
.get_frames_per_page();
1029 uint64_t vsize
= v
.size();
1030 emerg_write_member(fd
, TAG_MOVIE
, vsize
* stride
);
1033 uint64_t count
= (vsize
> pageframes
) ? pageframes
: vsize
;
1034 size_t bytes
= count
* stride
;
1035 const unsigned char* content
= v
.get_page_buffer(pagenum
++);
1036 emerg_write_bytes(fd
, content
, bytes
);
1039 //Movie starting time.
1040 emerg_write_member(fd
, TAG_MOVIE_TIME
, number_size(mv
.movie_rtc_second
) +
1041 number_size(mv
.movie_rtc_subsecond
));
1042 emerg_write_number(fd
, mv
.movie_rtc_second
);
1043 emerg_write_number(fd
, mv
.movie_rtc_subsecond
);
1045 emerg_write_member(fd
, TAG_PROJECT_ID
, mv
.projectid
.length());
1046 emerg_write_string_implicit(fd
, mv
.projectid
);
1048 for(auto& i
: mv
.movie_sram
) {
1049 emerg_write_member(fd
, TAG_MOVIE_SRAM
, string_size(i
.first
) + i
.second
.size());
1050 emerg_write_string(fd
, i
.first
);
1051 emerg_write_blob_implicit(fd
, i
.second
);
1054 emerg_write_member(fd
, TAG_ANCHOR_SAVE
, mv
.anchor_savestate
.size());
1055 emerg_write_blob_implicit(fd
, mv
.anchor_savestate
);
1057 emerg_write_member(fd
, TAG_RRDATA
, rrdata
.size_emerg());
1058 rrdata_set::esave_state estate
;
1061 size_t w
= rrdata
.write_emerg(estate
, buf
, sizeof(buf
));
1063 emerg_write_bytes(fd
, (const uint8_t*)buf
, w
);
1066 emerg_write_member(fd
, TAG_CORE_VERSION
, mv
.coreversion
.length());
1067 emerg_write_string_implicit(fd
, mv
.coreversion
);
1069 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
1070 if(mv
.romimg_sha256
[i
].length()) {
1071 emerg_write_member(fd
, TAG_ROMHASH
, mv
.romimg_sha256
[i
].length() + 1);
1072 emerg_write_byte(fd
, 2 * i
);
1073 emerg_write_string_implicit(fd
, mv
.romimg_sha256
[i
]);
1075 if(mv
.romxml_sha256
[i
].length()) {
1076 emerg_write_member(fd
, TAG_ROMHASH
, mv
.romxml_sha256
[i
].length() + 1);
1077 emerg_write_byte(fd
, 2 * i
+ 1);
1078 emerg_write_string_implicit(fd
, mv
.romxml_sha256
[i
]);
1080 if(mv
.namehint
[i
].length()) {
1081 emerg_write_member(fd
, TAG_ROMHINT
, mv
.namehint
[i
].length() + 1);
1082 emerg_write_byte(fd
, i
);
1083 emerg_write_string_implicit(fd
, mv
.namehint
[i
]);
1087 emerg_write_member(fd
, TAG_GAMENAME
, mv
.gamename
.size());
1088 emerg_write_string_implicit(fd
, mv
.gamename
);
1090 for(auto& i
: mv
.subtitles
) {
1091 emerg_write_member(fd
, TAG_SUBTITLE
, number_size(i
.first
.get_frame()) +
1092 number_size(i
.first
.get_length()) + i
.second
.length());
1093 emerg_write_number(fd
, i
.first
.get_frame());
1094 emerg_write_number(fd
, i
.first
.get_length());
1095 emerg_write_string_implicit(fd
, i
.second
);
1098 for(auto& i
: mv
.authors
) {
1099 emerg_write_member(fd
, TAG_AUTHOR
, string_size(i
.first
) + i
.second
.size());
1100 emerg_write_string(fd
, i
.first
);
1101 emerg_write_string_implicit(fd
, i
.second
);
1105 for(auto& i
: mv
.ramcontent
) {
1106 emerg_write_member(fd
, TAG_RAMCONTENT
, string_size(i
.first
) + i
.second
.size());
1107 emerg_write_string(fd
, i
.first
);
1108 emerg_write_blob_implicit(fd
, i
.second
);