Allow immediate saving at point of save
[lsnes.git] / src / core / moviefile.cpp
blob75cfadafd43bdd969d4dca05a9d28860d650bd50
1 #include "core/emucore.hpp"
2 #include "core/misc.hpp"
3 #include "core/moviedata.hpp"
4 #include "core/moviefile.hpp"
5 #include "core/rrdata.hpp"
6 #include "library/zip.hpp"
7 #include "library/string.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
17 void read_linefile(zip_reader& r, const std::string& member, std::string& out, bool conditional = false)
18 throw(std::bad_alloc, std::runtime_error)
20 if(conditional && !r.has_member(member))
21 return;
22 std::istream& m = r[member];
23 try {
24 std::getline(m, out);
25 istrip_CR(out);
26 delete &m;
27 } catch(...) {
28 delete &m;
29 throw;
33 template<typename T>
34 void read_numeric_file(zip_reader& r, const std::string& member, T& out, bool conditional = false)
35 throw(std::bad_alloc, std::runtime_error)
37 std::string _out;
38 read_linefile(r, member, _out, conditional);
39 if(conditional && _out == "")
40 return;
41 out = parse_value<int64_t>(_out);
44 void write_linefile(zip_writer& w, const std::string& member, const std::string& value, bool conditional = false)
45 throw(std::bad_alloc, std::runtime_error)
47 if(conditional && value == "")
48 return;
49 std::ostream& m = w.create_file(member);
50 try {
51 m << value << std::endl;
52 w.close_file();
53 } catch(...) {
54 w.close_file();
55 throw;
59 template<typename T>
60 void write_numeric_file(zip_writer& w, const std::string& member, T value) throw(std::bad_alloc,
61 std::runtime_error)
63 std::ostringstream x;
64 x << value;
65 write_linefile(w, member, x.str());
68 void write_raw_file(zip_writer& w, const std::string& member, std::vector<char>& content) throw(std::bad_alloc,
69 std::runtime_error)
71 std::ostream& m = w.create_file(member);
72 try {
73 m.write(&content[0], content.size());
74 if(!m)
75 throw std::runtime_error("Can't write ZIP file member");
76 w.close_file();
77 } catch(...) {
78 w.close_file();
79 throw;
83 std::vector<char> read_raw_file(zip_reader& r, const std::string& member) throw(std::bad_alloc, std::runtime_error)
85 std::vector<char> out;
86 std::istream& m = r[member];
87 try {
88 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
89 boost::iostreams::copy(m, rd);
90 delete &m;
91 } catch(...) {
92 delete &m;
93 throw;
95 return out;
98 uint64_t decode_uint64(unsigned char* buf)
100 return ((uint64_t)buf[0] << 56) |
101 ((uint64_t)buf[1] << 48) |
102 ((uint64_t)buf[2] << 40) |
103 ((uint64_t)buf[3] << 32) |
104 ((uint64_t)buf[4] << 24) |
105 ((uint64_t)buf[5] << 16) |
106 ((uint64_t)buf[6] << 8) |
107 ((uint64_t)buf[7]);
110 uint32_t decode_uint32(unsigned char* buf)
112 return ((uint32_t)buf[0] << 24) |
113 ((uint32_t)buf[1] << 16) |
114 ((uint32_t)buf[2] << 8) |
115 ((uint32_t)buf[3]);
119 void read_moviestate_file(zip_reader& r, const std::string& file, uint64_t& save_frame, uint64_t& lagged_frames,
120 std::vector<uint32_t>& pollcounters) throw(std::bad_alloc, std::runtime_error)
122 unsigned char buf[512];
123 auto s = read_raw_file(r, file);
124 if(s.size() != sizeof(buf))
125 throw std::runtime_error("Invalid moviestate file");
126 memcpy(buf, &s[0], sizeof(buf));
127 //Interesting offsets: 32-39: Current frame, 40-439: Poll counters, 440-447 lagged frames. All bigendian.
128 save_frame = decode_uint64(buf + 32);
129 lagged_frames = decode_uint64(buf + 440);
130 pollcounters.resize(100);
131 for(unsigned i = 0; i < 100; i++)
132 pollcounters[i] = decode_uint32(buf + 40 + 4 * i);
135 void read_authors_file(zip_reader& r, std::vector<std::pair<std::string, std::string>>& authors) throw(std::bad_alloc,
136 std::runtime_error)
138 std::istream& m = r["authors"];
139 try {
140 std::string x;
141 while(std::getline(m, x)) {
142 istrip_CR(x);
143 auto g = split_author(x);
144 authors.push_back(g);
146 delete &m;
147 } catch(...) {
148 delete &m;
149 throw;
153 std::string read_rrdata(zip_reader& r, std::vector<char>& out) throw(std::bad_alloc, std::runtime_error)
155 out = read_raw_file(r, "rrdata");
156 uint64_t count = rrdata::count(out);
157 std::ostringstream x;
158 x << count;
159 return x.str();
162 void write_rrdata(zip_writer& w) throw(std::bad_alloc, std::runtime_error)
164 uint64_t count;
165 std::vector<char> out;
166 count = rrdata::write(out);
167 write_raw_file(w, "rrdata", out);
168 std::ostream& m2 = w.create_file("rerecords");
169 try {
170 m2 << count << std::endl;
171 if(!m2)
172 throw std::runtime_error("Can't write ZIP file member");
173 w.close_file();
174 } catch(...) {
175 w.close_file();
176 throw;
180 void write_authors_file(zip_writer& w, std::vector<std::pair<std::string, std::string>>& authors)
181 throw(std::bad_alloc, std::runtime_error)
183 std::ostream& m = w.create_file("authors");
184 try {
185 for(auto i : authors)
186 if(i.second == "")
187 m << i.first << std::endl;
188 else
189 m << i.first << "|" << i.second << std::endl;
190 if(!m)
191 throw std::runtime_error("Can't write ZIP file member");
192 w.close_file();
193 } catch(...) {
194 w.close_file();
195 throw;
199 void write_input(zip_writer& w, controller_frame_vector& input)
200 throw(std::bad_alloc, std::runtime_error)
202 std::ostream& m = w.create_file("input");
203 try {
204 char buffer[MAX_SERIALIZED_SIZE];
205 for(size_t i = 0; i < input.size(); i++) {
206 input[i].serialize(buffer);
207 m << buffer << std::endl;
209 if(!m)
210 throw std::runtime_error("Can't write ZIP file member");
211 w.close_file();
212 } catch(...) {
213 w.close_file();
214 throw;
218 void read_subtitles(zip_reader& r, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
220 x.clear();
221 if(!r.has_member(file))
222 return;
223 std::istream& m = r[file];
224 try {
225 while(m) {
226 std::string out;
227 std::getline(m, out);
228 istrip_CR(out);
229 auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.*)", out);
230 if(!r)
231 continue;
232 x[moviefile_subtiming(parse_value<uint64_t>(r[1]), parse_value<uint64_t>(r[2]))] =
233 s_unescape(r[3]);
235 delete &m;
236 } catch(...) {
237 delete &m;
238 throw;
243 void write_subtitles(zip_writer& w, const std::string& file, std::map<moviefile_subtiming, std::string>& x)
245 std::ostream& m = w.create_file(file);
246 try {
247 for(auto i : x)
248 m << i.first.get_frame() << " " << i.first.get_length() << " " << s_escape(i.second)
249 << std::endl;
250 if(!m)
251 throw std::runtime_error("Can't write ZIP file member");
252 w.close_file();
253 } catch(...) {
254 w.close_file();
255 throw;
259 void read_input(zip_reader& r, controller_frame_vector& input, unsigned version) throw(std::bad_alloc,
260 std::runtime_error)
262 controller_frame tmp = input.blank_frame(false);
263 std::istream& m = r["input"];
264 try {
265 std::string x;
266 while(std::getline(m, x)) {
267 istrip_CR(x);
268 if(x != "") {
269 tmp.deserialize(x.c_str());
270 input.append(tmp);
273 delete &m;
274 } catch(...) {
275 delete &m;
276 throw;
280 void read_pollcounters(zip_reader& r, const std::string& file, std::vector<uint32_t>& pctr)
282 std::istream& m = r[file];
283 try {
284 std::string x;
285 while(std::getline(m, x)) {
286 istrip_CR(x);
287 if(x != "") {
288 int32_t y = parse_value<int32_t>(x);
289 uint32_t z = 0;
290 if(y < 0)
291 z = -(y + 1);
292 else {
293 z = y;
294 z |= 0x80000000UL;
296 pctr.push_back(z);
299 delete &m;
300 } catch(...) {
301 delete &m;
302 throw;
306 void write_pollcounters(zip_writer& w, const std::string& file, const std::vector<uint32_t>& pctr)
308 std::ostream& m = w.create_file(file);
309 try {
310 for(auto i : pctr) {
311 int32_t x = i & 0x7FFFFFFFUL;
312 if((i & 0x80000000UL) == 0)
313 x = -x - 1;
314 m << x << std::endl;
316 if(!m)
317 throw std::runtime_error("Can't write ZIP file member");
318 w.close_file();
319 } catch(...) {
320 w.close_file();
321 throw;
325 porttype_info& parse_controller_type(const std::string& type, unsigned port) throw(std::bad_alloc, std::runtime_error)
327 try {
328 porttype_info& i = porttype_info::lookup(type);
329 if(!i.legal || !(i.legal(port)))
330 throw 42;
331 return i;
332 } catch(...) {
333 (stringfmt() << "Illegal port " << (port + 1) << " device '" << type << "'").throwex();
337 moviefile::moviefile() throw(std::bad_alloc)
339 force_corrupt = false;
340 gametype = NULL;
341 port1 = &porttype_info::default_type();
342 port2 = &porttype_info::default_type();
343 coreversion = "";
344 projectid = "";
345 rerecords = "0";
346 is_savestate = false;
347 movie_rtc_second = rtc_second = DEFAULT_RTC_SECOND;
348 movie_rtc_subsecond = rtc_subsecond = DEFAULT_RTC_SUBSECOND;
349 start_paused = false;
350 lazy_project_create = true;
351 poll_flag = 0;
354 moviefile::moviefile(const std::string& movie) throw(std::bad_alloc, std::runtime_error)
356 poll_flag = false;
357 start_paused = false;
358 force_corrupt = false;
359 is_savestate = false;
360 lazy_project_create = false;
361 std::string tmp;
362 zip_reader r(movie);
363 read_linefile(r, "systemid", tmp);
364 if(tmp.substr(0, 8) != "lsnes-rr")
365 throw std::runtime_error("Not lsnes movie");
366 read_linefile(r, "controlsversion", tmp);
367 if(tmp != "0")
368 throw std::runtime_error("Can't decode movie data");
369 read_linefile(r, "gametype", tmp);
370 try {
371 gametype = &core_sysregion::lookup(tmp);
372 } catch(std::bad_alloc& e) {
373 throw;
374 } catch(std::exception& e) {
375 throw std::runtime_error("Illegal game type '" + tmp + "'");
377 tmp = porttype_info::port_default(0).name;
378 read_linefile(r, "port1", tmp, true);
379 port1 = &porttype_info::lookup(tmp);
380 tmp = porttype_info::port_default(1).name;
381 read_linefile(r, "port2", tmp, true);
382 port2 = &porttype_info::lookup(tmp);
383 input.clear(*port1, *port2);
384 read_linefile(r, "gamename", gamename, true);
385 read_linefile(r, "projectid", projectid);
386 rerecords = read_rrdata(r, c_rrdata);
387 read_linefile(r, "coreversion", coreversion);
388 read_linefile(r, "rom.sha256", romimg_sha256[0], true);
389 read_linefile(r, "romxml.sha256", romxml_sha256[0], true);
390 unsigned base = 97;
391 if(r.has_member("slot`.sha256"))
392 base = 96;
393 for(size_t i = 0; i < 26; i++) {
394 read_linefile(r, (stringfmt() << "slot" << (char)(base + i) << ".sha256").str(), romimg_sha256[i + 1],
395 true);
396 read_linefile(r, (stringfmt() << "slot" << (char)(base + i) << "xml.sha256").str(),
397 romxml_sha256[i + 1], true);
399 read_linefile(r, "prefix", prefix, true);
400 read_subtitles(r, "subtitles", subtitles);
401 prefix = sanitize_prefix(prefix);
402 movie_rtc_second = DEFAULT_RTC_SECOND;
403 movie_rtc_subsecond = DEFAULT_RTC_SUBSECOND;
404 read_numeric_file(r, "starttime.second", movie_rtc_second, true);
405 read_numeric_file(r, "starttime.subsecond", movie_rtc_subsecond, true);
406 rtc_second = movie_rtc_second;
407 rtc_subsecond = movie_rtc_subsecond;
408 if(r.has_member("savestate.anchor"))
409 anchor_savestate = read_raw_file(r, "savestate.anchor");
410 if(r.has_member("savestate")) {
411 is_savestate = true;
412 if(r.has_member("moviestate"))
413 //Backwards compat stuff.
414 read_moviestate_file(r, "moviestate", save_frame, lagged_frames, pollcounters);
415 else {
416 read_numeric_file(r, "saveframe", save_frame, true);
417 read_numeric_file(r, "lagcounter", lagged_frames, true);
418 read_pollcounters(r, "pollcounters", pollcounters);
420 if(r.has_member("hostmemory"))
421 host_memory = read_raw_file(r, "hostmemory");
422 savestate = read_raw_file(r, "savestate");
423 for(auto name : r)
424 if(name.length() >= 5 && name.substr(0, 5) == "sram.")
425 sram[name.substr(5)] = read_raw_file(r, name);
426 screenshot = read_raw_file(r, "screenshot");
427 //If these can't be read, just use some (wrong) values.
428 read_numeric_file(r, "savetime.second", rtc_second, true);
429 read_numeric_file(r, "savetime.subsecond", rtc_subsecond, true);
430 uint64_t _poll_flag = 2; //Legacy behaviour is the default.
431 read_numeric_file(r, "pollflag", _poll_flag, true);
432 poll_flag = _poll_flag;
434 if(rtc_subsecond < 0 || movie_rtc_subsecond < 0)
435 throw std::runtime_error("Invalid RTC subsecond value");
436 std::string name = r.find_first();
437 for(auto name : r)
438 if(name.length() >= 10 && name.substr(0, 10) == "moviesram.")
439 movie_sram[name.substr(10)] = read_raw_file(r, name);
440 read_authors_file(r, authors);
441 read_input(r, input, 0);
444 void moviefile::save(const std::string& movie, unsigned compression) throw(std::bad_alloc, std::runtime_error)
446 zip_writer w(movie, compression);
447 write_linefile(w, "gametype", gametype->get_name());
448 if(port1->name != porttype_info::port_default(0).name)
449 write_linefile(w, "port1", port1->name);
450 if(port2->name != porttype_info::port_default(1).name)
451 write_linefile(w, "port2", port2->name);
452 write_linefile(w, "gamename", gamename, true);
453 write_linefile(w, "systemid", "lsnes-rr1");
454 write_linefile(w, "controlsversion", "0");
455 coreversion = get_core_identifier();
456 write_linefile(w, "coreversion", coreversion);
457 write_linefile(w, "projectid", projectid);
458 write_rrdata(w);
459 write_linefile(w, "rom.sha256", romimg_sha256[0], true);
460 write_linefile(w, "romxml.sha256", romxml_sha256[0], true);
461 for(size_t i = 0; i < 26; i++) {
462 write_linefile(w, (stringfmt() << "slot" << (char)(97 + i) << ".sha256").str(), romimg_sha256[i + 1],
463 true);
464 write_linefile(w, (stringfmt() << "slot" << (char)(97 + i) << "xml.sha256").str(),
465 romxml_sha256[i + 1], true);
467 write_subtitles(w, "subtitles", subtitles);
468 write_linefile(w, "prefix", prefix, true);
469 for(auto i : movie_sram)
470 write_raw_file(w, "moviesram." + i.first, i.second);
471 write_numeric_file(w, "starttime.second", movie_rtc_second);
472 write_numeric_file(w, "starttime.subsecond", movie_rtc_subsecond);
473 if(!anchor_savestate.empty())
474 write_raw_file(w, "savestate.anchor", anchor_savestate);
475 if(is_savestate) {
476 write_numeric_file(w, "saveframe", save_frame);
477 write_numeric_file(w, "lagcounter", lagged_frames);
478 write_pollcounters(w, "pollcounters", pollcounters);
479 write_raw_file(w, "hostmemory", host_memory);
480 write_raw_file(w, "savestate", savestate);
481 write_raw_file(w, "screenshot", screenshot);
482 for(auto i : sram)
483 write_raw_file(w, "sram." + i.first, i.second);
484 write_numeric_file(w, "savetime.second", rtc_second);
485 write_numeric_file(w, "savetime.subsecond", rtc_subsecond);
486 write_numeric_file(w, "pollflag", poll_flag);
488 write_authors_file(w, authors);
489 write_input(w, input);
491 w.commit();
494 uint64_t moviefile::get_frame_count() throw()
496 return input.count_frames();
499 namespace
501 const int BLOCK_SECONDS = 0;
502 const int BLOCK_FRAMES = 1;
503 const int STEP_W = 2;
504 const int STEP_N = 3;
507 uint64_t moviefile::get_movie_length() throw()
509 uint64_t frames = get_frame_count();
510 if(!gametype) {
511 return 100000000ULL * frames / 6;
513 uint64_t _magic[4];
514 gametype->fill_framerate_magic(_magic);
515 uint64_t t = _magic[BLOCK_SECONDS] * 1000000000ULL * (frames / _magic[BLOCK_FRAMES]);
516 frames %= _magic[BLOCK_FRAMES];
517 t += frames * _magic[STEP_W] + (frames * _magic[STEP_N] / _magic[BLOCK_FRAMES]);
518 return t;
521 std::string sanitize_prefix(const std::string& in) throw(std::bad_alloc)
523 std::ostringstream s;
524 bool any = false;
525 for(size_t i = 0; i < in.length(); i++) {
526 char ch = in[i];
527 if(ch < 33 || ch == '$' || ch == ':' || ch == '/' || ch == '\\')
528 continue; //Always disallowed.
529 if(ch == '.' && !any)
530 continue; //Sometimes disallowed.
531 any = true;
532 s << ch;
534 return s.str();