Handle conflicting sysregions
[lsnes.git] / src / core / moviefile.cpp
bloba8af06bde5316bbadd28c24c951deb0a769ac514
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 "interface/romtype.hpp"
9 #include <sstream>
10 #include <boost/iostreams/copy.hpp>
11 #include <boost/iostreams/device/back_inserter.hpp>
13 #define DEFAULT_RTC_SECOND 1000000000ULL
14 #define DEFAULT_RTC_SUBSECOND 0ULL
16 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
17 throw(std::bad_alloc, std::runtime_error)
19 if(conditional && !r.has_member(member))
20 return;
21 std::istream& m = r[member];
22 try {
23 std::getline(m, out);
24 istrip_CR(out);
25 delete &m;
26 } catch(...) {
27 delete &m;
28 throw;
32 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
33 throw(std::bad_alloc, std::runtime_error)
35 if(conditional && value == "")
36 return;
37 std::ostream& m = w.create_file(member);
38 try {
39 m << value << std::endl;
40 w.close_file();
41 } catch(...) {
42 w.close_file();
43 throw;
48 namespace
50 std::map<std::string, std::string> read_settings(zip_reader& r)
52 std::map<std::string, std::string> x;
53 for(auto i : r) {
54 if(!regex_match("port[0-9]+|setting\\..+", i))
55 continue;
56 std::string s;
57 if(i.substr(0, 4) == "port")
58 s = i;
59 else
60 s = i.substr(8);
61 read_linefile(r, i, x[s], true);
63 return x;
66 void write_settings(zip_writer& w, const std::map<std::string, std::string>& settings,
67 core_setting_group& sgroup)
69 for(auto i : settings) {
70 if(!sgroup.settings.count(i.first))
71 continue;
72 if(sgroup.settings[i.first]->dflt == i.second)
73 continue;
74 if(regex_match("port[0-9]+", i.first))
75 write_linefile(w, i.first, i.second);
76 else
77 write_linefile(w, "setting." + i.first, i.second);
83 template<typename T>
84 void read_numeric_file(zip_reader& r, const std::string& member, T& out, bool conditional = false)
85 throw(std::bad_alloc, std::runtime_error)
87 std::string _out;
88 read_linefile(r, member, _out, conditional);
89 if(conditional && _out == "")
90 return;
91 out = parse_value<int64_t>(_out);
94 template<typename T>
95 void write_numeric_file(zip_writer& w, const std::string& member, T value) throw(std::bad_alloc,
96 std::runtime_error)
98 std::ostringstream x;
99 x << value;
100 write_linefile(w, member, x.str());
103 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
104 std::runtime_error)
106 std::ostream& m = w.create_file(member);
107 try {
108 m.write(&content[0], content.size());
109 if(!m)
110 throw std::runtime_error("Can't write ZIP file member");
111 w.close_file();
112 } catch(...) {
113 w.close_file();
114 throw;
118 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
120 std::vector<char> out;
121 std::istream& m = r[member];
122 try {
123 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
124 boost::iostreams::copy(m, rd);
125 delete &m;
126 } catch(...) {
127 delete &m;
128 throw;
130 return out;
133 uint64_t decode_uint64(unsigned char* buf)
135 return ((uint64_t)buf[0] << 56) |
136 ((uint64_t)buf[1] << 48) |
137 ((uint64_t)buf[2] << 40) |
138 ((uint64_t)buf[3] << 32) |
139 ((uint64_t)buf[4] << 24) |
140 ((uint64_t)buf[5] << 16) |
141 ((uint64_t)buf[6] << 8) |
142 ((uint64_t)buf[7]);
145 uint32_t decode_uint32(unsigned char* buf)
147 return ((uint32_t)buf[0] << 24) |
148 ((uint32_t)buf[1] << 16) |
149 ((uint32_t)buf[2] << 8) |
150 ((uint32_t)buf[3]);
154 void read_moviestate_file(zip_reader& r, const std::string& file, uint64_t& save_frame, uint64_t& lagged_frames,
155 std::vector<uint32_t>& pollcounters) throw(std::bad_alloc, std::runtime_error)
157 unsigned char buf[512];
158 auto s = read_raw_file(r, file);
159 if(s.size() != sizeof(buf))
160 throw std::runtime_error("Invalid moviestate file");
161 memcpy(buf, &s[0], sizeof(buf));
162 //Interesting offsets: 32-39: Current frame, 40-439: Poll counters, 440-447 lagged frames. All bigendian.
163 save_frame = decode_uint64(buf + 32);
164 lagged_frames = decode_uint64(buf + 440);
165 pollcounters.resize(100);
166 for(unsigned i = 0; i < 100; i++)
167 pollcounters[i] = decode_uint32(buf + 40 + 4 * i);
170 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
171 std::runtime_error)
173 std::istream& m = r["authors"];
174 try {
175 std::string x;
176 while(std::getline(m, x)) {
177 istrip_CR(x);
178 auto g = split_author(x);
179 authors.push_back(g);
181 delete &m;
182 } catch(...) {
183 delete &m;
184 throw;
188 std::string read_rrdata(zip_reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
190 out = read_raw_file(r, "rrdata");
191 uint64_t count = rrdata::count(out);
192 std::ostringstream x;
193 x << count;
194 return x.str();
197 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
199 uint64_t count;
200 std::vector<char> out;
201 count = rrdata::write(out);
202 write_raw_file(w, "rrdata", out);
203 std::ostream& m2 = w.create_file("rerecords");
204 try {
205 m2 << count << std::endl;
206 if(!m2)
207 throw std::runtime_error("Can't write ZIP file member");
208 w.close_file();
209 } catch(...) {
210 w.close_file();
211 throw;
215 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
216 throw(std::bad_alloc, std::runtime_error)
218 std::ostream& m = w.create_file("authors");
219 try {
220 for(auto i : authors)
221 if(i.second == "")
222 m << i.first << std::endl;
223 else
224 m << i.first << "|" << i.second << std::endl;
225 if(!m)
226 throw std::runtime_error("Can't write ZIP file member");
227 w.close_file();
228 } catch(...) {
229 w.close_file();
230 throw;
234 void write_input(zip_writer& w, controller_frame_vector& input)
235 throw(std::bad_alloc, std::runtime_error)
237 std::ostream& m = w.create_file("input");
238 try {
239 char buffer[MAX_SERIALIZED_SIZE];
240 for(size_t i = 0; i < input.size(); i++) {
241 input[i].serialize(buffer);
242 m << buffer << std::endl;
244 if(!m)
245 throw std::runtime_error("Can't write ZIP file member");
246 w.close_file();
247 } catch(...) {
248 w.close_file();
249 throw;
253 void read_subtitles(zip_reader& r, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
255 x.clear();
256 if(!r.has_member(file))
257 return;
258 std::istream& m = r[file];
259 try {
260 while(m) {
261 std::string out;
262 std::getline(m, out);
263 istrip_CR(out);
264 auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out);
265 if(!r)
266 continue;
267 x[moviefile_subtiming(parse_value<uint64_t>(r[1]), parse_value<uint64_t>(r[2]))] =
268 s_unescape(r[3]);
270 delete &m;
271 } catch(...) {
272 delete &m;
273 throw;
278 void write_subtitles(zip_writer& w, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
280 std::ostream& m = w.create_file(file);
281 try {
282 for(auto i : x)
283 m << i.first.get_frame() << " " << i.first.get_length() << " " << s_escape(i.second)
284 << std::endl;
285 if(!m)
286 throw std::runtime_error("Can't write ZIP file member");
287 w.close_file();
288 } catch(...) {
289 w.close_file();
290 throw;
294 void read_input(zip_reader& r, controller_frame_vector& input, unsigned version) throw(std::bad_alloc,
295 std::runtime_error)
297 controller_frame tmp = input.blank_frame(false);
298 std::istream& m = r["input"];
299 try {
300 std::string x;
301 while(std::getline(m, x)) {
302 istrip_CR(x);
303 if(x != "") {
304 tmp.deserialize(x.c_str());
305 input.append(tmp);
308 delete &m;
309 } catch(...) {
310 delete &m;
311 throw;
315 void read_pollcounters(zip_reader& r, const std::string& file, std::vector<uint32_t>& pctr)
317 std::istream& m = r[file];
318 try {
319 std::string x;
320 while(std::getline(m, x)) {
321 istrip_CR(x);
322 if(x != "") {
323 int32_t y = parse_value<int32_t>(x);
324 uint32_t z = 0;
325 if(y < 0)
326 z = -(y + 1);
327 else {
328 z = y;
329 z |= 0x80000000UL;
331 pctr.push_back(z);
334 delete &m;
335 } catch(...) {
336 delete &m;
337 throw;
341 void write_pollcounters(zip_writer& w, const std::string& file, const std::vector<uint32_t>& pctr)
343 std::ostream& m = w.create_file(file);
344 try {
345 for(auto i : pctr) {
346 int32_t x = i & 0x7FFFFFFFUL;
347 if((i & 0x80000000UL) == 0)
348 x = -x - 1;
349 m << x << std::endl;
351 if(!m)
352 throw std::runtime_error("Can't write ZIP file member");
353 w.close_file();
354 } catch(...) {
355 w.close_file();
356 throw;
360 moviefile::moviefile() throw(std::bad_alloc)
362 static port_type_set dummy_types;
363 force_corrupt = false;
364 gametype = NULL;
365 coreversion = "";
366 projectid = "";
367 rerecords = "0";
368 is_savestate = false;
369 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
370 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
371 start_paused = false;
372 lazy_project_create = true;
373 poll_flag = 0;
376 moviefile::moviefile(const std::string& movie, core_type& romtype) throw(std::bad_alloc, std::runtime_error)
378 poll_flag = false;
379 start_paused = false;
380 force_corrupt = false;
381 is_savestate = false;
382 lazy_project_create = false;
383 std::string tmp;
384 zip_reader r(movie);
385 read_linefile(r, "systemid", tmp);
386 if(tmp.substr(0, 8) != "lsnes-rr")
387 throw std::runtime_error("Not lsnes movie");
388 read_linefile(r, "controlsversion", tmp);
389 if(tmp != "0")
390 throw std::runtime_error("Can't decode movie data");
391 read_linefile(r, "gametype", tmp);
392 try {
393 gametype = &romtype.lookup_sysregion(tmp);
394 } catch(std::bad_alloc& e) {
395 throw;
396 } catch(std::exception& e) {
397 throw std::runtime_error("Illegal game type '" + tmp + "'");
399 settings = read_settings(r);
400 auto ctrldata = gametype->get_type().controllerconfig(settings);
401 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex);
403 input.clear(ports);
404 read_linefile(r, "gamename", gamename, true);
405 read_linefile(r, "projectid", projectid);
406 rerecords = read_rrdata(r, c_rrdata);
407 read_linefile(r, "coreversion", coreversion);
408 read_linefile(r, "rom.sha256", romimg_sha256[0], true);
409 read_linefile(r, "romxml.sha256", romxml_sha256[0], true);
410 unsigned base = 97;
411 if(r.has_member("slot`.sha256"))
412 base = 96;
413 for(size_t i = 0; i < 26; i++) {
414 read_linefile(r, (stringfmt() << "slot" << (char)(base + i) << ".sha256").str(), romimg_sha256[i + 1],
415 true);
416 read_linefile(r, (stringfmt() << "slot" << (char)(base + i) << "xml.sha256").str(),
417 romxml_sha256[i + 1], true);
419 read_linefile(r, "prefix", prefix, true);
420 read_subtitles(r, "subtitles", subtitles);
421 prefix = sanitize_prefix(prefix);
422 movie_rtc_second = DEFAULT_RTC_SECOND;
423 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
424 read_numeric_file(r, "starttime.second", movie_rtc_second, true);
425 read_numeric_file(r, "starttime.subsecond", movie_rtc_subsecond, true);
426 rtc_second = movie_rtc_second;
427 rtc_subsecond = movie_rtc_subsecond;
428 if(r.has_member("savestate.anchor"))
429 anchor_savestate = read_raw_file(r, "savestate.anchor");
430 if(r.has_member("savestate")) {
431 is_savestate = true;
432 if(r.has_member("moviestate"))
433 //Backwards compat stuff.
434 read_moviestate_file(r, "moviestate", save_frame, lagged_frames, pollcounters);
435 else {
436 read_numeric_file(r, "saveframe", save_frame, true);
437 read_numeric_file(r, "lagcounter", lagged_frames, true);
438 read_pollcounters(r, "pollcounters", pollcounters);
440 if(r.has_member("hostmemory"))
441 host_memory = read_raw_file(r, "hostmemory");
442 savestate = read_raw_file(r, "savestate");
443 for(auto name : r)
444 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
445 sram[name.substr(5)] = read_raw_file(r, name);
446 screenshot = read_raw_file(r, "screenshot");
447 //If these can't be read, just use some (wrong) values.
448 read_numeric_file(r, "savetime.second", rtc_second, true);
449 read_numeric_file(r, "savetime.subsecond", rtc_subsecond, true);
450 uint64_t _poll_flag = 2; //Legacy behaviour is the default.
451 read_numeric_file(r, "pollflag", _poll_flag, true);
452 poll_flag = _poll_flag;
454 if(rtc_subsecond < 0 || movie_rtc_subsecond < 0)
455 throw std::runtime_error("Invalid RTC subsecond value");
456 std::string name = r.find_first();
457 for(auto name : r)
458 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
459 movie_sram[name.substr(10)] = read_raw_file(r, name);
460 read_authors_file(r, authors);
461 read_input(r, input, 0);
464 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
466 zip_writer w(movie, compression);
467 write_linefile(w, "gametype", gametype->get_name());
468 write_settings(w, settings, gametype->get_type().get_settings());
469 write_linefile(w, "gamename", gamename, true);
470 write_linefile(w, "systemid", "lsnes-rr1");
471 write_linefile(w, "controlsversion", "0");
472 coreversion = gametype->get_type().get_core_identifier();
473 write_linefile(w, "coreversion", coreversion);
474 write_linefile(w, "projectid", projectid);
475 write_rrdata(w);
476 write_linefile(w, "rom.sha256", romimg_sha256[0], true);
477 write_linefile(w, "romxml.sha256", romxml_sha256[0], true);
478 for(size_t i = 0; i < 26; i++) {
479 write_linefile(w, (stringfmt() << "slot" << (char)(97 + i) << ".sha256").str(), romimg_sha256[i + 1],
480 true);
481 write_linefile(w, (stringfmt() << "slot" << (char)(97 + i) << "xml.sha256").str(),
482 romxml_sha256[i + 1], true);
484 write_subtitles(w, "subtitles", subtitles);
485 write_linefile(w, "prefix", prefix, true);
486 for(auto i : movie_sram)
487 write_raw_file(w, "moviesram." + i.first, i.second);
488 write_numeric_file(w, "starttime.second", movie_rtc_second);
489 write_numeric_file(w, "starttime.subsecond", movie_rtc_subsecond);
490 if(!anchor_savestate.empty())
491 write_raw_file(w, "savestate.anchor", anchor_savestate);
492 if(is_savestate) {
493 write_numeric_file(w, "saveframe", save_frame);
494 write_numeric_file(w, "lagcounter", lagged_frames);
495 write_pollcounters(w, "pollcounters", pollcounters);
496 write_raw_file(w, "hostmemory", host_memory);
497 write_raw_file(w, "savestate", savestate);
498 write_raw_file(w, "screenshot", screenshot);
499 for(auto i : sram)
500 write_raw_file(w, "sram." + i.first, i.second);
501 write_numeric_file(w, "savetime.second", rtc_second);
502 write_numeric_file(w, "savetime.subsecond", rtc_subsecond);
503 write_numeric_file(w, "pollflag", poll_flag);
505 write_authors_file(w, authors);
506 write_input(w, input);
508 w.commit();
511 uint64_t moviefile::get_frame_count() throw()
513 return input.count_frames();
516 namespace
518 const int BLOCK_SECONDS = 0;
519 const int BLOCK_FRAMES = 1;
520 const int STEP_W = 2;
521 const int STEP_N = 3;
524 uint64_t moviefile::get_movie_length() throw()
526 uint64_t frames = get_frame_count();
527 if(!gametype) {
528 return 100000000ULL * frames / 6;
530 uint64_t _magic[4];
531 gametype->fill_framerate_magic(_magic);
532 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
533 frames %= _magic[BLOCK_FRAMES];
534 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
535 return t;
538 std::string sanitize_prefix(const std::string& in) throw(std::bad_alloc)
540 std::ostringstream s;
541 bool any = false;
542 for(size_t i = 0; i < in.length(); i++) {
543 char ch = in[i];
544 if(ch < 33 || ch == '$' || ch == ':' || ch == '/' || ch == '\\')
545 continue; //Always disallowed.
546 if(ch == '.' && !any)
547 continue; //Sometimes disallowed.
548 any = true;
549 s << ch;
551 return s.str();