JSON-based controller descriptions
[lsnes.git] / src / core / moviefile.cpp
blobeb4860166033f7e2cdd8b76d9c5c39c97f7aa76c
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/binarystream.hpp"
9 #include "interface/romtype.hpp"
11 #include <iostream>
12 #include <algorithm>
13 #include <sstream>
14 #include <boost/iostreams/copy.hpp>
15 #include <boost/iostreams/device/back_inserter.hpp>
16 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
17 #include <windows.h>
18 #endif
20 #define DEFAULT_RTC_SECOND 1000000000ULL
21 #define DEFAULT_RTC_SUBSECOND 0ULL
23 enum lsnes_movie_tags
25 TAG_ANCHOR_SAVE = 0xf5e0fad7,
26 TAG_AUTHOR = 0xafff97b4,
27 TAG_CORE_VERSION = 0xe4344c7e,
28 TAG_GAMENAME = 0xe80d6970,
29 TAG_HOSTMEMORY = 0x3bf9d187,
30 TAG_MACRO = 0xd261338f,
31 TAG_MOVIE = 0xf3dca44b,
32 TAG_MOVIE_SRAM = 0xbbc824b7,
33 TAG_MOVIE_TIME = 0x18c3a975,
34 TAG_PROJECT_ID = 0x359bfbab,
35 TAG_ROMHASH = 0x0428acfc,
36 TAG_RRDATA = 0xa3a07f71,
37 TAG_SAVE_SRAM = 0xae9bfb2f,
38 TAG_SAVESTATE = 0x2e5bc2ac,
39 TAG_SCREENSHOT = 0xc6760d0e,
40 TAG_SUBTITLE = 0x6a7054d3,
41 TAG_RAMCONTENT = 0xd3ec3770,
42 TAG_ROMHINT = 0x6f715830
45 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
46 throw(std::bad_alloc, std::runtime_error)
48 if(conditional && !r.has_member(member))
49 return;
50 std::istream& m = r[member];
51 try {
52 std::getline(m, out);
53 istrip_CR(out);
54 delete &m;
55 } catch(...) {
56 delete &m;
57 throw;
61 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
62 throw(std::bad_alloc, std::runtime_error)
64 if(conditional && value == "")
65 return;
66 std::ostream& m = w.create_file(member);
67 try {
68 m << value << std::endl;
69 w.close_file();
70 } catch(...) {
71 w.close_file();
72 throw;
76 namespace
78 void binary_read_movie(binary_input_stream& in, controller_frame_vector& v)
80 uint64_t stride = v.get_stride();
81 uint64_t pageframes = v.get_frames_per_page();
82 uint64_t vsize = 0;
83 size_t pagenum = 0;
84 uint64_t pagesize = stride * pageframes;
85 while(in.get_left()) {
86 v.resize(vsize + pageframes);
87 unsigned char* contents = v.get_page_buffer(pagenum++);
88 uint64_t gcount = min(pagesize, in.get_left());
89 in.raw(contents, gcount);
90 vsize += (gcount / stride);
92 v.resize(vsize);
95 void binary_write_movie(binary_output_stream& out, controller_frame_vector& v)
97 uint64_t pages = v.get_page_count();
98 uint64_t stride = v.get_stride();
99 uint64_t pageframes = v.get_frames_per_page();
100 uint64_t vsize = v.size();
101 out.write_extension_tag(TAG_MOVIE, vsize * stride);
102 size_t pagenum = 0;
103 while(vsize > 0) {
104 uint64_t count = (vsize > pageframes) ? pageframes : vsize;
105 size_t bytes = count * stride;
106 unsigned char* content = v.get_page_buffer(pagenum++);
107 out.raw(content, bytes);
108 vsize -= count;
112 std::map<std::string, std::string> read_settings(zip_reader& r)
114 std::map<std::string, std::string> x;
115 for(auto i : r) {
116 if(!regex_match("port[0-9]+|setting\\..+", i))
117 continue;
118 std::string s;
119 if(i.substr(0, 4) == "port")
120 s = i;
121 else
122 s = i.substr(8);
123 read_linefile(r, i, x[s], true);
125 return x;
128 template<typename target>
129 void write_settings(target& w, const std::map<std::string, std::string>& settings,
130 core_setting_group& sgroup, std::function<void(target& w, const std::string& name,
131 const std::string& value)> writefn)
133 for(auto i : settings) {
134 if(!sgroup.settings.count(i.first))
135 continue;
136 if(sgroup.settings.find(i.first)->second.dflt == i.second)
137 continue;
138 writefn(w, i.first, i.second);
142 std::map<std::string, uint64_t> read_active_macros(zip_reader& r, const std::string& member)
144 std::map<std::string, uint64_t> x;
145 if(!r.has_member(member))
146 return x;
147 std::istream& m = r[member];
148 try {
149 while(m) {
150 std::string out;
151 std::getline(m, out);
152 istrip_CR(out);
153 if(out == "")
154 continue;
155 regex_results rx = regex("([0-9]+) +(.*)", out);
156 if(!rx) {
157 messages << "Warning: Bad macro state: '" << out << "'" << std::endl;
158 continue;
160 try {
161 uint64_t f = parse_value<uint64_t>(rx[1]);
162 x[rx[2]] = f;
163 } catch(...) {
166 delete &m;
167 } catch(...) {
168 delete &m;
169 throw;
171 return x;
174 void write_active_macros(zip_writer& w, const std::string& member, const std::map<std::string, uint64_t>& ma)
176 if(ma.empty())
177 return;
178 std::ostream& m = w.create_file(member);
179 try {
180 for(auto i : ma)
181 m << i.second << " " << i.first << std::endl;
182 if(!m)
183 throw std::runtime_error("Can't write ZIP file member");
184 w.close_file();
185 } catch(...) {
186 w.close_file();
187 throw;
193 template<typename T>
194 void read_numeric_file(zip_reader& r, const std::string& member, T& out, bool conditional = false)
195 throw(std::bad_alloc, std::runtime_error)
197 std::string _out;
198 read_linefile(r, member, _out, conditional);
199 if(conditional && _out == "")
200 return;
201 out = parse_value<int64_t>(_out);
204 template<typename T>
205 void write_numeric_file(zip_writer& w, const std::string& member, T value) throw(std::bad_alloc,
206 std::runtime_error)
208 std::ostringstream x;
209 x << value;
210 write_linefile(w, member, x.str());
213 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
214 std::runtime_error)
216 std::ostream& m = w.create_file(member);
217 try {
218 m.write(&content[0], content.size());
219 if(!m)
220 throw std::runtime_error("Can't write ZIP file member");
221 w.close_file();
222 } catch(...) {
223 w.close_file();
224 throw;
228 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
230 std::vector<char> out;
231 std::istream& m = r[member];
232 try {
233 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
234 boost::iostreams::copy(m, rd);
235 delete &m;
236 } catch(...) {
237 delete &m;
238 throw;
240 return out;
243 uint64_t decode_uint64(unsigned char* buf)
245 return ((uint64_t)buf[0] << 56) |
246 ((uint64_t)buf[1] << 48) |
247 ((uint64_t)buf[2] << 40) |
248 ((uint64_t)buf[3] << 32) |
249 ((uint64_t)buf[4] << 24) |
250 ((uint64_t)buf[5] << 16) |
251 ((uint64_t)buf[6] << 8) |
252 ((uint64_t)buf[7]);
255 uint32_t decode_uint32(unsigned char* buf)
257 return ((uint32_t)buf[0] << 24) |
258 ((uint32_t)buf[1] << 16) |
259 ((uint32_t)buf[2] << 8) |
260 ((uint32_t)buf[3]);
263 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
264 std::runtime_error)
266 std::istream& m = r["authors"];
267 try {
268 std::string x;
269 while(std::getline(m, x)) {
270 istrip_CR(x);
271 auto g = split_author(x);
272 authors.push_back(g);
274 delete &m;
275 } catch(...) {
276 delete &m;
277 throw;
281 std::string read_rrdata(zip_reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
283 out = read_raw_file(r, "rrdata");
284 uint64_t count = rrdata.count(out);
285 std::ostringstream x;
286 x << count;
287 return x.str();
290 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
292 uint64_t count;
293 std::vector<char> out;
294 count = rrdata.write(out);
295 write_raw_file(w, "rrdata", out);
296 std::ostream& m2 = w.create_file("rerecords");
297 try {
298 m2 << count << std::endl;
299 if(!m2)
300 throw std::runtime_error("Can't write ZIP file member");
301 w.close_file();
302 } catch(...) {
303 w.close_file();
304 throw;
308 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
309 throw(std::bad_alloc, std::runtime_error)
311 std::ostream& m = w.create_file("authors");
312 try {
313 for(auto i : authors)
314 if(i.second == "")
315 m << i.first << std::endl;
316 else
317 m << i.first << "|" << i.second << std::endl;
318 if(!m)
319 throw std::runtime_error("Can't write ZIP file member");
320 w.close_file();
321 } catch(...) {
322 w.close_file();
323 throw;
327 void write_input(zip_writer& w, controller_frame_vector& input)
328 throw(std::bad_alloc, std::runtime_error)
330 std::ostream& m = w.create_file("input");
331 try {
332 char buffer[MAX_SERIALIZED_SIZE];
333 for(size_t i = 0; i < input.size(); i++) {
334 input[i].serialize(buffer);
335 m << buffer << std::endl;
337 if(!m)
338 throw std::runtime_error("Can't write ZIP file member");
339 w.close_file();
340 } catch(...) {
341 w.close_file();
342 throw;
346 void read_subtitles(zip_reader& r, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
348 x.clear();
349 if(!r.has_member(file))
350 return;
351 std::istream& m = r[file];
352 try {
353 while(m) {
354 std::string out;
355 std::getline(m, out);
356 istrip_CR(out);
357 auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out);
358 if(!r)
359 continue;
360 x[moviefile_subtiming(parse_value<uint64_t>(r[1]), parse_value<uint64_t>(r[2]))] =
361 s_unescape(r[3]);
363 delete &m;
364 } catch(...) {
365 delete &m;
366 throw;
371 void write_subtitles(zip_writer& w, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
373 std::ostream& m = w.create_file(file);
374 try {
375 for(auto i : x)
376 m << i.first.get_frame() << " " << i.first.get_length() << " " << s_escape(i.second)
377 << std::endl;
378 if(!m)
379 throw std::runtime_error("Can't write ZIP file member");
380 w.close_file();
381 } catch(...) {
382 w.close_file();
383 throw;
387 void read_input(zip_reader& r, controller_frame_vector& input, unsigned version) throw(std::bad_alloc,
388 std::runtime_error)
390 controller_frame tmp = input.blank_frame(false);
391 std::istream& m = r["input"];
392 try {
393 std::string x;
394 while(std::getline(m, x)) {
395 istrip_CR(x);
396 if(x != "") {
397 tmp.deserialize(x.c_str());
398 input.append(tmp);
401 delete &m;
402 } catch(...) {
403 delete &m;
404 throw;
408 void read_pollcounters(zip_reader& r, const std::string& file, std::vector<uint32_t>& pctr)
410 std::istream& m = r[file];
411 try {
412 std::string x;
413 while(std::getline(m, x)) {
414 istrip_CR(x);
415 if(x != "") {
416 int32_t y = parse_value<int32_t>(x);
417 uint32_t z = 0;
418 if(y < 0)
419 z = -(y + 1);
420 else {
421 z = y;
422 z |= 0x80000000UL;
424 pctr.push_back(z);
427 delete &m;
428 } catch(...) {
429 delete &m;
430 throw;
434 void write_pollcounters(zip_writer& w, const std::string& file, const std::vector<uint32_t>& pctr)
436 std::ostream& m = w.create_file(file);
437 try {
438 for(auto i : pctr) {
439 int32_t x = i & 0x7FFFFFFFUL;
440 if((i & 0x80000000UL) == 0)
441 x = -x - 1;
442 m << x << std::endl;
444 if(!m)
445 throw std::runtime_error("Can't write ZIP file member");
446 w.close_file();
447 } catch(...) {
448 w.close_file();
449 throw;
453 moviefile::brief_info::brief_info(const std::string& filename)
456 std::istream& s = open_file_relative(filename, "");
457 char buf[6] = {0};
458 s.read(buf, 5);
459 if(!strcmp(buf, "lsmv\x1A")) {
460 binary_io(s);
461 delete &s;
462 return;
464 delete &s;
466 zip_reader r(filename);
467 std::string tmp;
468 read_linefile(r, "systemid", tmp);
469 if(tmp.substr(0, 8) != "lsnes-rr")
470 throw std::runtime_error("Not lsnes movie");
471 read_linefile(r, "gametype", sysregion);
472 read_linefile(r, "coreversion", corename);
473 read_linefile(r, "projectid", projectid);
474 if(r.has_member("savestate"))
475 read_numeric_file(r, "saveframe", current_frame);
476 else
477 current_frame = 0;
478 read_numeric_file(r, "rerecords", rerecords);
479 read_linefile(r, "rom.sha256", hash[0], true);
480 read_linefile(r, "romxml.sha256", hashxml[0], true);
481 read_linefile(r, "rom.hint", hint[0], true);
482 unsigned base = 97;
483 if(r.has_member("slot`.sha256"))
484 base = 96;
485 for(size_t i = 1; i < ROM_SLOT_COUNT; i++) {
486 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << ".sha256").str(), hash[i],
487 true);
488 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << "xml.sha256").str(),
489 hashxml[i], true);
490 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << ".hint").str(), hint[i],
491 true);
495 void moviefile::brief_info::binary_io(std::istream& _stream)
497 binary_input_stream in(_stream);
498 sysregion = in.string();
499 //Discard the settings.
500 while(in.byte()) {
501 in.string();
502 in.string();
504 in.extension({
505 {TAG_CORE_VERSION, [this](binary_input_stream& s) {
506 this->corename = s.string_implicit();
507 }},{TAG_PROJECT_ID, [this](binary_input_stream& s) {
508 this->projectid = s.string_implicit();
509 }},{TAG_SAVESTATE, [this](binary_input_stream& s) {
510 this->current_frame = s.number();
511 }},{TAG_RRDATA, [this](binary_input_stream& s) {
512 std::vector<char> c_rrdata;
513 s.blob_implicit(c_rrdata);
514 this->rerecords = rrdata.count(c_rrdata);
515 }},{TAG_ROMHASH, [this](binary_input_stream& s) {
516 uint8_t n = s.byte();
517 std::string h = s.string_implicit();
518 if(n > 2 * ROM_SLOT_COUNT)
519 return;
520 if(n & 1)
521 this->hashxml[n >> 1] = h;
522 else
523 this->hash[n >> 1] = h;
524 }},{TAG_ROMHINT, [this](binary_input_stream& s) {
525 uint8_t n = s.byte();
526 std::string h = s.string_implicit();
527 if(n > ROM_SLOT_COUNT)
528 return;
529 this->hint[n] = h;
531 }, binary_null_default);
534 moviefile::moviefile() throw(std::bad_alloc)
536 static port_type_set dummy_types;
537 force_corrupt = false;
538 gametype = NULL;
539 coreversion = "";
540 projectid = "";
541 rerecords = "0";
542 is_savestate = false;
543 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
544 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
545 start_paused = false;
546 lazy_project_create = true;
547 poll_flag = 0;
550 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
552 poll_flag = false;
553 start_paused = false;
554 force_corrupt = false;
555 is_savestate = false;
556 lazy_project_create = false;
557 std::string tmp;
559 std::istream& s = open_file_relative(movie, "");
560 char buf[6] = {0};
561 s.read(buf, 5);
562 if(!strcmp(buf, "lsmv\x1A")) {
563 binary_io(s, romtype);
564 delete &s;
565 return;
567 delete &s;
569 zip_reader r(movie);
570 read_linefile(r, "systemid", tmp);
571 if(tmp.substr(0, 8) != "lsnes-rr")
572 throw std::runtime_error("Not lsnes movie");
573 read_linefile(r, "controlsversion", tmp);
574 if(tmp != "0")
575 throw std::runtime_error("Can't decode movie data");
576 read_linefile(r, "gametype", tmp);
577 try {
578 gametype = &romtype.lookup_sysregion(tmp);
579 } catch(std::bad_alloc& e) {
580 throw;
581 } catch(std::exception& e) {
582 throw std::runtime_error("Illegal game type '" + tmp + "'");
584 settings = read_settings(r);
585 auto ctrldata = gametype->get_type().controllerconfig(settings);
586 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
588 input.clear(ports);
589 read_linefile(r, "gamename", gamename, true);
590 read_linefile(r, "projectid", projectid);
591 rerecords = read_rrdata(r, c_rrdata);
592 read_linefile(r, "coreversion", coreversion);
593 read_linefile(r, "rom.sha256", romimg_sha256[0], true);
594 read_linefile(r, "romxml.sha256", romxml_sha256[0], true);
595 read_linefile(r, "rom.hint", namehint[0], true);
596 unsigned base = 97;
597 if(r.has_member("slot`.sha256"))
598 base = 96;
599 for(size_t i = 1; i < ROM_SLOT_COUNT; i++) {
600 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << ".sha256").str(), romimg_sha256[i],
601 true);
602 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << "xml.sha256").str(),
603 romxml_sha256[i], true);
604 read_linefile(r, (stringfmt() << "slot" << (char)(base + i - 1) << ".hint").str(), namehint[i],
605 true);
607 read_subtitles(r, "subtitles", subtitles);
608 movie_rtc_second = DEFAULT_RTC_SECOND;
609 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
610 read_numeric_file(r, "starttime.second", movie_rtc_second, true);
611 read_numeric_file(r, "starttime.subsecond", movie_rtc_subsecond, true);
612 rtc_second = movie_rtc_second;
613 rtc_subsecond = movie_rtc_subsecond;
614 if(r.has_member("savestate.anchor"))
615 anchor_savestate = read_raw_file(r, "savestate.anchor");
616 if(r.has_member("savestate")) {
617 is_savestate = true;
618 read_numeric_file(r, "saveframe", save_frame, true);
619 read_numeric_file(r, "lagcounter", lagged_frames, true);
620 read_pollcounters(r, "pollcounters", pollcounters);
621 if(r.has_member("hostmemory"))
622 host_memory = read_raw_file(r, "hostmemory");
623 savestate = read_raw_file(r, "savestate");
624 for(auto name : r)
625 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
626 sram[name.substr(5)] = read_raw_file(r, name);
627 screenshot = read_raw_file(r, "screenshot");
628 //If these can't be read, just use some (wrong) values.
629 read_numeric_file(r, "savetime.second", rtc_second, true);
630 read_numeric_file(r, "savetime.subsecond", rtc_subsecond, true);
631 uint64_t _poll_flag = 2; //Legacy behaviour is the default.
632 read_numeric_file(r, "pollflag", _poll_flag, true);
633 poll_flag = _poll_flag;
634 active_macros = read_active_macros(r, "macros");
636 for(auto name : r)
637 if(name.length() >= 8 && name.substr(0, 8) == "initram.")
638 ramcontent[name.substr(8)] = read_raw_file(r, name);
639 if(rtc_subsecond < 0 || movie_rtc_subsecond < 0)
640 throw std::runtime_error("Invalid RTC subsecond value");
641 std::string name = r.find_first();
642 for(auto name : r)
643 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
644 movie_sram[name.substr(10)] = read_raw_file(r, name);
645 read_authors_file(r, authors);
646 read_input(r, input, 0);
649 void moviefile::save(const std::string& movie, unsigned compression, bool binary) throw(std::bad_alloc,
650 std::runtime_error)
652 if(binary) {
653 std::string tmp = movie + ".tmp";
654 std::ofstream strm(tmp.c_str(), std::ios_base::binary);
655 if(!strm)
656 throw std::runtime_error("Can't open output file");
657 char buf[5] = {'l', 's', 'm', 'v', 0x1A};
658 strm.write(buf, 5);
659 if(!strm)
660 throw std::runtime_error("Failed to write to output file");
661 binary_io(strm);
662 if(!strm)
663 throw std::runtime_error("Failed to write to output file");
664 strm.close();
665 std::string backup = movie + ".backup";
666 rename_file_overwrite(movie.c_str(), backup.c_str());
667 if(rename_file_overwrite(tmp.c_str(), movie.c_str()) < 0)
668 throw std::runtime_error("Can't rename '" + tmp + "' -> '" + movie + "'");
669 return;
671 zip_writer w(movie, compression);
672 write_linefile(w, "gametype", gametype->get_name());
673 write_settings<zip_writer>(w, settings, gametype->get_type().get_settings(), [](zip_writer& w,
674 const std::string& name, const std::string& value) -> void {
675 if(regex_match("port[0-9]+", name))
676 write_linefile(w, name, value);
677 else
678 write_linefile(w, "setting." + name, value);
680 write_linefile(w, "gamename", gamename, true);
681 write_linefile(w, "systemid", "lsnes-rr1");
682 write_linefile(w, "controlsversion", "0");
683 coreversion = gametype->get_type().get_core_identifier();
684 write_linefile(w, "coreversion", coreversion);
685 write_linefile(w, "projectid", projectid);
686 write_rrdata(w);
687 write_linefile(w, "rom.sha256", romimg_sha256[0], true);
688 write_linefile(w, "romxml.sha256", romxml_sha256[0], true);
689 write_linefile(w, "rom.hint", namehint[0], true);
690 for(size_t i = 1; i < ROM_SLOT_COUNT; i++) {
691 write_linefile(w, (stringfmt() << "slot" << (char)(96 + i) << ".sha256").str(), romimg_sha256[i],
692 true);
693 write_linefile(w, (stringfmt() << "slot" << (char)(96 + i) << "xml.sha256").str(), romxml_sha256[i],
694 true);
695 write_linefile(w, (stringfmt() << "slot" << (char)(96 + i) << ".hint").str(), namehint[i],
696 true);
698 write_subtitles(w, "subtitles", subtitles);
699 for(auto i : movie_sram)
700 write_raw_file(w, "moviesram." + i.first, i.second);
701 write_numeric_file(w, "starttime.second", movie_rtc_second);
702 write_numeric_file(w, "starttime.subsecond", movie_rtc_subsecond);
703 if(!anchor_savestate.empty())
704 write_raw_file(w, "savestate.anchor", anchor_savestate);
705 if(is_savestate) {
706 write_numeric_file(w, "saveframe", save_frame);
707 write_numeric_file(w, "lagcounter", lagged_frames);
708 write_pollcounters(w, "pollcounters", pollcounters);
709 write_raw_file(w, "hostmemory", host_memory);
710 write_raw_file(w, "savestate", savestate);
711 write_raw_file(w, "screenshot", screenshot);
712 for(auto i : sram)
713 write_raw_file(w, "sram." + i.first, i.second);
714 write_numeric_file(w, "savetime.second", rtc_second);
715 write_numeric_file(w, "savetime.subsecond", rtc_subsecond);
716 write_numeric_file(w, "pollflag", poll_flag);
717 write_active_macros(w, "macros", active_macros);
719 for(auto i : ramcontent)
720 write_raw_file(w, "initram." + i.first, i.second);
721 write_authors_file(w, authors);
722 write_input(w, input);
724 w.commit();
728 Following need to be saved:
729 - gametype (string)
730 - settings (string name, value pairs)
731 - gamename (optional string)
732 - core version (string)
733 - project id (string
734 - rrdata (blob)
735 - ROM hashes (2*27 table of optional strings)
736 - Subtitles (list of number,number,string)
737 - SRAMs (dictionary string->blob.)
738 - Starttime (number,number)
739 - Anchor savestate (optional blob)
740 - Save frame (savestate-only, numeric).
741 - Lag counter (savestate-only, numeric).
742 - pollcounters (savestate-only, vector of numbers).
743 - hostmemory (savestate-only, blob).
744 - screenshot (savestate-only, blob).
745 - Save SRAMs (savestate-only, dictionary string->blob.)
746 - Save time (savestate-only, number,number)
747 - Poll flag (savestate-only, boolean)
748 - Macros (savestate-only, ???)
749 - Authors (list of string,string).
750 - Input (blob).
751 - Extensions (???)
753 void moviefile::binary_io(std::ostream& _stream) throw(std::bad_alloc, std::runtime_error)
755 binary_output_stream out(_stream);
756 out.string(gametype->get_name());
757 write_settings<binary_output_stream>(out, settings, gametype->get_type().get_settings(),
758 [](binary_output_stream& s, const std::string& name, const std::string& value) -> void {
759 s.byte(0x01);
760 s.string(name);
761 s.string(value);
763 out.byte(0x00);
765 out.extension(TAG_MOVIE_TIME, [this](binary_output_stream& s) {
766 s.number(this->movie_rtc_second);
767 s.number(this->movie_rtc_subsecond);
770 out.extension(TAG_PROJECT_ID, [this](binary_output_stream& s) {
771 s.string_implicit(this->projectid);
774 out.extension(TAG_CORE_VERSION, [this](binary_output_stream& s) {
775 this->coreversion = this->gametype->get_type().get_core_identifier();
776 s.string_implicit(this->coreversion);
779 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
780 out.extension(TAG_ROMHASH, [this, i](binary_output_stream& s) {
781 if(!this->romimg_sha256[i].length()) return;
782 s.byte(2 * i);
783 s.string_implicit(this->romimg_sha256[i]);
785 out.extension(TAG_ROMHASH, [this, i](binary_output_stream& s) {
786 if(!this->romxml_sha256[i].length()) return;
787 s.byte(2 * i + 1);
788 s.string_implicit(this->romxml_sha256[i]);
790 out.extension(TAG_ROMHINT, [this, i](binary_output_stream& s) {
791 if(!this->namehint[i].length()) return;
792 s.byte(i);
793 s.string_implicit(this->namehint[i]);
797 out.extension(TAG_RRDATA, [this](binary_output_stream& s) {
798 uint64_t count;
799 std::vector<char> rrd;
800 count = rrdata.write(rrd);
801 s.blob_implicit(rrd);
804 for(auto i : movie_sram)
805 out.extension(TAG_MOVIE_SRAM, [&i](binary_output_stream& s) {
806 s.string(i.first);
807 s.blob_implicit(i.second);
810 out.extension(TAG_ANCHOR_SAVE, [this](binary_output_stream& s) {
811 s.blob_implicit(this->anchor_savestate);
813 if(is_savestate) {
814 out.extension(TAG_SAVESTATE, [this](binary_output_stream& s) {
815 s.number(this->save_frame);
816 s.number(this->lagged_frames);
817 s.number(this->rtc_second);
818 s.number(this->rtc_subsecond);
819 s.number(this->pollcounters.size());
820 for(auto i : this->pollcounters)
821 s.number32(i);
822 s.byte(this->poll_flag ? 0x01 : 0x00);
823 s.blob_implicit(this->savestate);
824 }, true, out.numberbytes(save_frame) + out.numberbytes(lagged_frames) + out.numberbytes(rtc_second) +
825 out.numberbytes(rtc_subsecond) + out.numberbytes(pollcounters.size()) +
826 4 * pollcounters.size() + 1 + savestate.size());
828 out.extension(TAG_HOSTMEMORY, [this](binary_output_stream& s) {
829 s.blob_implicit(this->host_memory);
832 out.extension(TAG_SCREENSHOT, [this](binary_output_stream& s) {
833 s.blob_implicit(this->screenshot);
834 }, true, screenshot.size());
836 for(auto i : sram) {
837 out.extension(TAG_SAVE_SRAM, [&i](binary_output_stream& s) {
838 s.string(i.first);
839 s.blob_implicit(i.second);
844 out.extension(TAG_GAMENAME, [this](binary_output_stream& s) {
845 s.string_implicit(this->gamename);
848 for(auto i : subtitles)
849 out.extension(TAG_SUBTITLE, [&i](binary_output_stream& s) {
850 s.number(i.first.get_frame());
851 s.number(i.first.get_length());
852 s.string_implicit(i.second);
855 for(auto i : authors)
856 out.extension(TAG_AUTHOR, [&i](binary_output_stream& s) {
857 s.string(i.first);
858 s.string_implicit(i.second);
861 for(auto i : active_macros)
862 out.extension(TAG_MACRO, [&i](binary_output_stream& s) {
863 s.number(i.second);
864 s.string_implicit(i.first);
867 for(auto i : ramcontent) {
868 out.extension(TAG_RAMCONTENT, [&i](binary_output_stream& s) {
869 s.string(i.first);
870 s.blob_implicit(i.second);
874 binary_write_movie(out, input);
877 void moviefile::binary_io(std::istream& _stream, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
879 binary_input_stream in(_stream);
880 std::string tmp = in.string();
881 try {
882 gametype = &romtype.lookup_sysregion(tmp);
883 } catch(std::bad_alloc& e) {
884 throw;
885 } catch(std::exception& e) {
886 throw std::runtime_error("Illegal game type '" + tmp + "'");
888 while(in.byte()) {
889 std::string name = in.string();
890 settings[name] = in.string();
892 auto ctrldata = gametype->get_type().controllerconfig(settings);
893 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
894 input.clear(ports);
896 in.extension({
897 {TAG_ANCHOR_SAVE, [this](binary_input_stream& s) {
898 s.blob_implicit(this->anchor_savestate);
899 }},{TAG_AUTHOR, [this](binary_input_stream& s) {
900 std::string a = s.string();
901 std::string b = s.string_implicit();
902 this->authors.push_back(std::make_pair(a, b));
903 }},{TAG_CORE_VERSION, [this](binary_input_stream& s) {
904 this->coreversion = s.string_implicit();
905 }},{TAG_GAMENAME, [this](binary_input_stream& s) {
906 this->gamename = s.string_implicit();
907 }},{TAG_HOSTMEMORY, [this](binary_input_stream& s) {
908 s.blob_implicit(this->host_memory);
909 }},{TAG_MACRO, [this](binary_input_stream& s) {
910 uint64_t n = s.number();
911 this->active_macros[s.string_implicit()] = n;
912 }},{TAG_MOVIE, [this](binary_input_stream& s) {
913 binary_read_movie(s, input);
914 }},{TAG_MOVIE_SRAM, [this](binary_input_stream& s) {
915 std::string a = s.string();
916 s.blob_implicit(this->movie_sram[a]);
917 }},{TAG_RAMCONTENT, [this](binary_input_stream& s) {
918 std::string a = s.string();
919 s.blob_implicit(this->ramcontent[a]);
920 }},{TAG_MOVIE_TIME, [this](binary_input_stream& s) {
921 this->movie_rtc_second = s.number();
922 this->movie_rtc_subsecond = s.number();
923 }},{TAG_PROJECT_ID, [this](binary_input_stream& s) {
924 this->projectid = s.string_implicit();
925 }},{TAG_ROMHASH, [this](binary_input_stream& s) {
926 uint8_t n = s.byte();
927 std::string h = s.string_implicit();
928 if(n > 2 * ROM_SLOT_COUNT)
929 return;
930 if(n & 1)
931 romxml_sha256[n >> 1] = h;
932 else
933 romimg_sha256[n >> 1] = h;
934 }},{TAG_ROMHINT, [this](binary_input_stream& s) {
935 uint8_t n = s.byte();
936 std::string h = s.string_implicit();
937 if(n > ROM_SLOT_COUNT)
938 return;
939 namehint[n] = h;
940 }},{TAG_RRDATA, [this](binary_input_stream& s) {
941 s.blob_implicit(this->c_rrdata);
942 this->rerecords = (stringfmt() << rrdata.count(c_rrdata)).str();
943 }},{TAG_SAVE_SRAM, [this](binary_input_stream& s) {
944 std::string a = s.string();
945 s.blob_implicit(this->sram[a]);
946 }},{TAG_SAVESTATE, [this](binary_input_stream& s) {
947 this->is_savestate = true;
948 this->save_frame = s.number();
949 this->lagged_frames = s.number();
950 this->rtc_second = s.number();
951 this->rtc_subsecond = s.number();
952 this->pollcounters.resize(s.number());
953 for(auto& i : this->pollcounters)
954 i = s.number32();
955 this->poll_flag = (s.byte() != 0);
956 s.blob_implicit(this->savestate);
957 }},{TAG_SCREENSHOT, [this](binary_input_stream& s) {
958 s.blob_implicit(this->screenshot);
959 }},{TAG_SUBTITLE, [this](binary_input_stream& s) {
960 uint64_t f = s.number();
961 uint64_t l = s.number();
962 std::string x = s.string_implicit();
963 this->subtitles[moviefile_subtiming(f, l)] = x;
965 }, binary_null_default);
968 uint64_t moviefile::get_frame_count() throw()
970 return input.count_frames();
973 namespace
975 const int BLOCK_SECONDS = 0;
976 const int BLOCK_FRAMES = 1;
977 const int STEP_W = 2;
978 const int STEP_N = 3;
981 uint64_t moviefile::get_movie_length() throw()
983 uint64_t frames = get_frame_count();
984 if(!gametype) {
985 return 100000000ULL * frames / 6;
987 uint64_t _magic[4];
988 gametype->fill_framerate_magic(_magic);
989 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
990 frames %= _magic[BLOCK_FRAMES];
991 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
992 return t;