Back movie data off movie file structure
[lsnes.git] / src / core / moviefile.cpp
blobecf94f3253565cef4c2dc83b379d509912c50588
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"
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <iostream>
15 #include <algorithm>
16 #include <sstream>
17 #include <boost/iostreams/copy.hpp>
18 #include <boost/iostreams/device/back_inserter.hpp>
19 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
20 #include <windows.h>
21 #endif
23 #define DEFAULT_RTC_SECOND 1000000000ULL
24 #define DEFAULT_RTC_SUBSECOND 0ULL
26 namespace
28 std::map<std::string, moviefile> memory_saves;
31 enum lsnes_movie_tags
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
53 namespace
55 std::map<std::string, std::string> read_settings(zip::reader& r)
57 std::map<std::string, std::string> x;
58 for(auto i : r) {
59 if(!regex_match("port[0-9]+|setting\\..+", i))
60 continue;
61 std::string s;
62 std::string v;
63 if(i.substr(0, 4) == "port")
64 s = i;
65 else
66 s = i.substr(8);
67 if(r.read_linefile(i, v, true))
68 x[s] = v;
70 return x;
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))
80 continue;
81 if(sgroup.settings.find(i.first)->second.dflt == i.second)
82 continue;
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))
91 return x;
92 std::istream& m = r[member];
93 try {
94 while(m) {
95 std::string out;
96 std::getline(m, out);
97 istrip_CR(out);
98 if(out == "")
99 continue;
100 regex_results rx = regex("([0-9]+) +(.*)", out);
101 if(!rx) {
102 messages << "Warning: Bad macro state: '" << out << "'" << std::endl;
103 continue;
105 try {
106 uint64_t f = parse_value<uint64_t>(rx[1]);
107 x[rx[2]] = f;
108 } catch(...) {
111 delete &m;
112 } catch(...) {
113 delete &m;
114 throw;
116 return x;
119 void write_active_macros(zip::writer& w, const std::string& member, const std::map<std::string, uint64_t>& ma)
121 if(ma.empty())
122 return;
123 std::ostream& m = w.create_file(member);
124 try {
125 for(auto i : ma)
126 m << i.second << " " << i.first << std::endl;
127 if(!m)
128 throw std::runtime_error("Can't write ZIP file member");
129 w.close_file();
130 } catch(...) {
131 w.close_file();
132 throw;
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"];
141 try {
142 std::string x;
143 while(std::getline(m, x)) {
144 istrip_CR(x);
145 auto g = split_author(x);
146 authors.push_back(g);
148 delete &m;
149 } catch(...) {
150 delete &m;
151 throw;
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;
160 x << count;
161 return x.str();
164 void write_rrdata(zip::writer& w) throw(std::bad_alloc, std::runtime_error)
166 uint64_t count;
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");
171 try {
172 m2 << count << std::endl;
173 if(!m2)
174 throw std::runtime_error("Can't write ZIP file member");
175 w.close_file();
176 } catch(...) {
177 w.close_file();
178 throw;
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");
186 try {
187 for(auto i : authors)
188 if(i.second == "")
189 m << i.first << std::endl;
190 else
191 m << i.first << "|" << i.second << std::endl;
192 if(!m)
193 throw std::runtime_error("Can't write ZIP file member");
194 w.close_file();
195 } catch(...) {
196 w.close_file();
197 throw;
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");
205 try {
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;
211 if(!m)
212 throw std::runtime_error("Can't write ZIP file member");
213 w.close_file();
214 } catch(...) {
215 w.close_file();
216 throw;
220 void read_subtitles(zip::reader& r, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
222 x.clear();
223 if(!r.has_member(file))
224 return;
225 std::istream& m = r[file];
226 try {
227 while(m) {
228 std::string out;
229 std::getline(m, out);
230 istrip_CR(out);
231 auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out);
232 if(!r)
233 continue;
234 x[moviefile_subtiming(parse_value<uint64_t>(r[1]), parse_value<uint64_t>(r[2]))] =
235 s_unescape(r[3]);
237 delete &m;
238 } catch(...) {
239 delete &m;
240 throw;
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);
247 try {
248 for(auto i : x)
249 m << i.first.get_frame() << " " << i.first.get_length() << " " << s_escape(i.second)
250 << std::endl;
251 if(!m)
252 throw std::runtime_error("Can't write ZIP file member");
253 w.close_file();
254 } catch(...) {
255 w.close_file();
256 throw;
260 void read_input(zip::reader& r, controller_frame_vector& input, unsigned version) throw(std::bad_alloc,
261 std::runtime_error)
263 controller_frame tmp = input.blank_frame(false);
264 std::istream& m = r["input"];
265 try {
266 std::string x;
267 while(std::getline(m, x)) {
268 istrip_CR(x);
269 if(x != "") {
270 tmp.deserialize(x.c_str());
271 input.append(tmp);
274 delete &m;
275 } catch(...) {
276 delete &m;
277 throw;
281 void read_pollcounters(zip::reader& r, const std::string& file, std::vector<uint32_t>& pctr)
283 std::istream& m = r[file];
284 try {
285 std::string x;
286 while(std::getline(m, x)) {
287 istrip_CR(x);
288 if(x != "") {
289 int32_t y = parse_value<int32_t>(x);
290 uint32_t z = 0;
291 if(y < 0)
292 z = -(y + 1);
293 else {
294 z = y;
295 z |= 0x80000000UL;
297 pctr.push_back(z);
300 delete &m;
301 } catch(...) {
302 delete &m;
303 throw;
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);
310 try {
311 for(auto i : pctr) {
312 int32_t x = i & 0x7FFFFFFFUL;
313 if((i & 0x80000000UL) == 0)
314 x = -x - 1;
315 m << x << std::endl;
317 if(!m)
318 throw std::runtime_error("Can't write ZIP file member");
319 w.close_file();
320 } catch(...) {
321 w.close_file();
322 throw;
326 moviefile::brief_info::brief_info(const std::string& filename)
328 regex_results rr;
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];
343 return;
346 std::istream& s = zip::openrel(filename, "");
347 char buf[6] = {0};
348 s.read(buf, 5);
349 if(!strcmp(buf, "lsmv\x1A")) {
350 binary_io(s);
351 delete &s;
352 return;
354 delete &s;
356 zip::reader r(filename);
357 std::string tmp;
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);
366 else
367 current_frame = 0;
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);
372 unsigned base = 97;
373 if(r.has_member("slot`.sha256"))
374 base = 96;
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],
377 true);
378 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << "xml.sha256").str(),
379 hashxml[i], true);
380 r.read_linefile((stringfmt() << "slot" << (char)(base + i - 1) << ".hint").str(), hint[i],
381 true);
385 void moviefile::brief_info::binary_io(std::istream& _stream)
387 binarystream::input in(_stream);
388 sysregion = in.string();
389 //Discard the settings.
390 while(in.byte()) {
391 in.string();
392 in.string();
394 in.extension({
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)
409 return;
410 if(n & 1)
411 this->hashxml[n >> 1] = h;
412 else
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)
418 return;
419 this->hint[n] = h;
421 }, binarystream::null_default);
424 moviefile::moviefile() throw(std::bad_alloc)
426 static port_type_set dummy_types;
427 force_corrupt = false;
428 gametype = NULL;
429 coreversion = "";
430 projectid = "";
431 rerecords = "0";
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;
437 poll_flag = 0;
440 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
442 regex_results rr;
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]];
447 return;
449 poll_flag = false;
450 start_paused = false;
451 force_corrupt = false;
452 is_savestate = false;
453 lazy_project_create = false;
454 std::string tmp;
456 std::istream& s = zip::openrel(movie, "");
457 char buf[6] = {0};
458 s.read(buf, 5);
459 if(!strcmp(buf, "lsmv\x1A")) {
460 binary_io(s, romtype);
461 delete &s;
462 return;
464 delete &s;
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);
471 if(tmp != "0")
472 throw std::runtime_error("Can't decode movie data");
473 r.read_linefile("gametype", tmp);
474 try {
475 gametype = &romtype.lookup_sysregion(tmp);
476 } catch(std::bad_alloc& e) {
477 throw;
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());
485 input.clear(ports);
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);
493 unsigned base = 97;
494 if(r.has_member("slot`.sha256"))
495 base = 96;
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],
498 true);
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],
502 true);
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")) {
514 is_savestate = true;
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);
521 for(auto name : r)
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");
533 for(auto name : r)
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();
539 for(auto name : r)
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,
547 std::runtime_error)
549 regex_results rr;
550 if(rr = regex("\\$MEMORY:(.*)", movie)) {
551 memory_saves[rr[1]] = *this;
552 return;
554 if(binary) {
555 std::string tmp = movie + ".tmp";
556 std::ofstream strm(tmp.c_str(), std::ios_base::binary);
557 if(!strm)
558 throw std::runtime_error("Can't open output file");
559 char buf[5] = {'l', 's', 'm', 'v', 0x1A};
560 strm.write(buf, 5);
561 if(!strm)
562 throw std::runtime_error("Failed to write to output file");
563 binary_io(strm);
564 if(!strm)
565 throw std::runtime_error("Failed to write to output file");
566 strm.close();
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 + "'");
571 return;
573 zip::writer w(movie, compression);
574 save(w);
577 void moviefile::save(std::ostream& stream) throw(std::bad_alloc, std::runtime_error)
579 zip::writer w(stream, 0);
580 save(w);
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);
590 else
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);
599 write_rrdata(w);
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],
605 true);
606 w.write_linefile((stringfmt() << "slot" << (char)(96 + i) << "xml.sha256").str(), romxml_sha256[i],
607 true);
608 w.write_linefile((stringfmt() << "slot" << (char)(96 + i) << ".hint").str(), namehint[i],
609 true);
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);
618 if(is_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);
625 for(auto i : sram)
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);
636 w.commit();
640 Following need to be saved:
641 - gametype (string)
642 - settings (string name, value pairs)
643 - gamename (optional string)
644 - core version (string)
645 - project id (string
646 - rrdata (blob)
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).
662 - Input (blob).
663 - Extensions (???)
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 {
671 s.byte(0x01);
672 s.string(name);
673 s.string(value);
675 out.byte(0x00);
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;
694 s.byte(2 * i);
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;
699 s.byte(2 * i + 1);
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;
704 s.byte(i);
705 s.string_implicit(this->namehint[i]);
709 out.extension(TAG_RRDATA, [this](binarystream::output& s) {
710 uint64_t count;
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) {
718 s.string(i.first);
719 s.blob_implicit(i.second);
722 out.extension(TAG_ANCHOR_SAVE, [this](binarystream::output& s) {
723 s.blob_implicit(this->anchor_savestate);
725 if(is_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)
733 s.number32(i);
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());
748 for(auto i : sram) {
749 out.extension(TAG_SAVE_SRAM, [&i](binarystream::output& s) {
750 s.string(i.first);
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) {
769 s.string(i.first);
770 s.string_implicit(i.second);
773 for(auto i : active_macros)
774 out.extension(TAG_MACRO, [&i](binarystream::output& s) {
775 s.number(i.second);
776 s.string_implicit(i.first);
779 for(auto i : ramcontent) {
780 out.extension(TAG_RAMCONTENT, [&i](binarystream::output& s) {
781 s.string(i.first);
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();
795 try {
796 gametype = &romtype.lookup_sysregion(tmp);
797 } catch(std::bad_alloc& e) {
798 throw;
799 } catch(std::exception& e) {
800 throw std::runtime_error("Illegal game type '" + tmp + "'");
802 while(in.byte()) {
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());
808 input.clear(ports);
810 in.extension({
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)
843 return;
844 if(n & 1)
845 romxml_sha256[n >> 1] = h;
846 else
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)
852 return;
853 namehint[n] = h;
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)
868 i = s.number32();
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();
887 namespace
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();
898 if(!gametype) {
899 return 100000000ULL * frames / 6;
901 uint64_t _magic[4];
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]);
906 return t;
909 moviefile& moviefile::memref(const std::string& slot)
911 return memory_saves[slot];
914 namespace
916 void emerg_write_bytes(int handle, const uint8_t* d, size_t dsize)
918 while(dsize > 0) {
919 ssize_t r = write(handle, d, dsize);
920 if(r > 0) {
921 d += r;
922 dsize -= r;
926 void emerg_write_number(int handle, uint64_t num)
928 uint8_t data[10];
929 size_t len = 0;
930 do {
931 bool cont = (num > 127);
932 data[len++] = (cont ? 0x80 : 0x00) | (num & 0x7F);
933 num >>= 7;
934 } while(num);
935 emerg_write_bytes(handle, data, len);
937 size_t number_size(uint64_t num)
939 unsigned len = 0;
940 do {
941 num >>= 7;
942 len++;
943 } while(num);
944 return len;
946 void emerg_write_number32(int handle, uint32_t num)
948 char buf[4];
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)
982 unsigned digits = 0;
983 uint64_t n2 = n;
984 do {
985 digits++;
986 n2 /= 10;
987 } while(n2);
988 for(unsigned i = digits; i > 0; i--) {
989 ptr[i - 1] = (n % 10) + '0';
990 n /= 10;
992 ptr[digits] = 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;
1000 if(!mv.gametype)
1001 return; //No valid movie. Trying to save would segfault.
1002 char header[] = {'l', 's', 'm', 'v', '\x1a'};
1003 int fd;
1004 char filename_buf[512];
1005 int number = 1;
1006 name_again:
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.
1016 //Headers.
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);
1025 //The actual movie.
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);
1031 size_t pagenum = 0;
1032 while(vsize > 0) {
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);
1037 vsize -= count;
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);
1044 //Project id.
1045 emerg_write_member(fd, TAG_PROJECT_ID, mv.projectid.length());
1046 emerg_write_string_implicit(fd, mv.projectid);
1047 //starting SRAM.
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);
1053 //Anchor save.
1054 emerg_write_member(fd, TAG_ANCHOR_SAVE, mv.anchor_savestate.size());
1055 emerg_write_blob_implicit(fd, mv.anchor_savestate);
1056 //RRDATA.
1057 emerg_write_member(fd, TAG_RRDATA, rrdata.size_emerg());
1058 rrdata_set::esave_state estate;
1059 while(true) {
1060 char buf[4096];
1061 size_t w = rrdata.write_emerg(estate, buf, sizeof(buf));
1062 if(!w) break;
1063 emerg_write_bytes(fd, (const uint8_t*)buf, w);
1065 //Core version.
1066 emerg_write_member(fd, TAG_CORE_VERSION, mv.coreversion.length());
1067 emerg_write_string_implicit(fd, mv.coreversion);
1068 //ROM slots data.
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]);
1086 //Game name.
1087 emerg_write_member(fd, TAG_GAMENAME, mv.gamename.size());
1088 emerg_write_string_implicit(fd, mv.gamename);
1089 //Subtitles.
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);
1097 //Authors.
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);
1104 //RAM contents.
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);
1110 close(fd);