Fix desync on loading savestate in readonly mode after movie
[lsnes.git] / src / core / moviedata.cpp
blobf8e0e02acdc8f894e35915f534709ecb2178242c
1 #include "lsnes.hpp"
2 #include "core/emucore.hpp"
4 #include "core/command.hpp"
5 #include "core/controller.hpp"
6 #include "core/dispatch.hpp"
7 #include "core/framebuffer.hpp"
8 #include "core/framerate.hpp"
9 #include "lua/lua.hpp"
10 #include "core/misc.hpp"
11 #include "core/moviedata.hpp"
12 #include "core/rrdata.hpp"
13 #include "core/settings.hpp"
14 #include "library/string.hpp"
16 #include <iomanip>
17 #include <fstream>
19 #include <boost/filesystem.hpp>
21 #ifdef BOOST_FILESYSTEM3
22 namespace boost_fs = boost::filesystem3;
23 #else
24 namespace boost_fs = boost::filesystem;
25 #endif
27 struct moviefile our_movie;
28 struct loaded_rom* our_rom;
29 bool system_corrupt;
30 movie_logic movb;
31 std::string last_save;
33 extern "C"
35 time_t __wrap_time(time_t* t)
37 time_t v = static_cast<time_t>(our_movie.rtc_second);
38 if(t)
39 *t = v;
40 return v;
44 std::vector<char>& get_host_memory()
46 return our_movie.host_memory;
49 movie& get_movie()
51 return movb.get_movie();
54 namespace
56 numeric_setting savecompression("savecompression", 0, 9, 7);
57 boolean_setting readonly_load_preserves("preserve_on_readonly_load", true);
59 path_setting slotpath_setting("slotpath");
61 class projectprefix_setting : public setting
63 public:
64 bool _set;
65 std::string prefix;
66 projectprefix_setting() throw(std::bad_alloc)
67 : setting("$project")
69 _set = false;
71 bool blank(bool really) throw(std::bad_alloc, std::runtime_error)
73 if(!really)
74 return true;
75 _set = false;
76 return true;
78 bool is_set() throw()
80 return _set;
82 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
84 prefix = value;
85 _set = true;
87 std::string get() throw(std::bad_alloc)
89 return prefix;
91 operator std::string() throw()
93 lock_holder lck(this);
94 if(_set)
95 return prefix + "-";
96 else
97 return "movieslot";
99 } mprefix;
101 function_ptr_command<> get_gamename("get-gamename", "Get the game name",
102 "Syntax: get-gamename\nPrints the game name\n",
103 []() throw(std::bad_alloc, std::runtime_error) {
104 messages << "Game name is '" << our_movie.gamename << "'" << std::endl;
107 function_ptr_command<const std::string&> set_gamename("set-gamename", "Set the game name",
108 "Syntax: set-gamename <name>\nSets the game name to <name>\n",
109 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
110 our_movie.gamename = args;
111 messages << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
114 function_ptr_command<> show_authors("show-authors", "Show the run authors",
115 "Syntax: show-authors\nShows the run authors\n",
116 []() throw(std::bad_alloc, std::runtime_error)
118 size_t idx = 0;
119 for(auto i : our_movie.authors) {
120 messages << (idx++) << ": " << i.first << "|" << i.second << std::endl;
122 messages << "End of authors list" << std::endl;
125 function_ptr_command<const std::string&> add_author("add-author", "Add an author",
126 "Syntax: add-author <fullname>\nSyntax: add-author |<nickname>\n"
127 "Syntax: add-author <fullname>|<nickname>\nAdds a new author\n",
128 [](const std::string& t) throw(std::bad_alloc, std::runtime_error) {
129 auto g = split_author(t);
130 our_movie.authors.push_back(g);
131 messages << (our_movie.authors.size() - 1) << ": " << g.first << "|" << g.second << std::endl;
134 function_ptr_command<const std::string&> remove_author("remove-author", "Remove an author",
135 "Syntax: remove-author <id>\nRemoves author with ID <id>\n",
136 [](const std::string& t) throw(std::bad_alloc, std::runtime_error) {
137 uint64_t index = parse_value<uint64_t>(t);
138 if(index >= our_movie.authors.size())
139 throw std::runtime_error("No such author");
140 our_movie.authors.erase(our_movie.authors.begin() + index);
143 function_ptr_command<const std::string&> edit_author("edit-author", "Edit an author",
144 "Syntax: edit-author <authorid> <fullname>\nSyntax: edit-author <authorid> |<nickname>\n"
145 "Syntax: edit-author <authorid> <fullname>|<nickname>\nEdits author name\n",
146 [](const std::string& t) throw(std::bad_alloc, std::runtime_error) {
147 auto r = regex("([^ \t]+)[ \t]+(|[^ \t].*)", t, "Index and author required.");
148 uint64_t index = parse_value<uint64_t>(r[1]);
149 if(index >= our_movie.authors.size())
150 throw std::runtime_error("No such author");
151 auto g = split_author(r[2]);
152 our_movie.authors[index] = g;
155 function_ptr_command<const std::string&> dump_coresave("dump-coresave", "Dump bsnes core state",
156 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
157 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
158 auto x = save_core_state();
159 x.resize(x.size() - 32);
160 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
161 y.write(&x[0], x.size());
162 y.close();
163 messages << "Saved core state to " << name << std::endl;
166 bool warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
167 const std::string& name, bool fatal)
169 if(mhash == slot.sha256)
170 return true;
171 if(!fatal) {
172 messages << "WARNING: " << name << " hash mismatch!" << std::endl
173 << "\tMovie: " << mhash << std::endl
174 << "\tOur ROM: " << slot.sha256 << std::endl;
175 return true;
176 } else {
177 messages << "ERROR: " << name << " hash mismatch!" << std::endl
178 << "\tMovie: " << mhash << std::endl
179 << "\tOur ROM: " << slot.sha256 << std::endl;
180 return false;
185 std::string translate_name_mprefix(std::string original, bool forio)
187 size_t prefixloc = original.find("${project}");
188 if(prefixloc < original.length()) {
189 std::string pprf = forio ? (slotpath_setting.get() + "/") : std::string("");
190 if(prefixloc == 0)
191 return pprf + static_cast<std::string>(mprefix) + original.substr(prefixloc + 10);
192 else
193 return original.substr(0, prefixloc) + static_cast<std::string>(mprefix) +
194 original.substr(prefixloc + 10);
195 } else
196 return original;
199 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
200 std::runtime_error)
202 std::string _author = author;
203 std::string fullname;
204 std::string nickname;
205 size_t split = _author.find_first_of("|");
206 if(split >= _author.length()) {
207 fullname = _author;
208 } else {
209 fullname = _author.substr(0, split);
210 nickname = _author.substr(split + 1);
212 if(fullname == "" && nickname == "")
213 throw std::runtime_error("Bad author name");
214 return std::make_pair(fullname, nickname);
218 //Save state.
219 void do_save_state(const std::string& filename) throw(std::bad_alloc,
220 std::runtime_error)
222 if(!our_movie.gametype) {
223 messages << "Can't save movie without a ROM" << std::endl;
224 return;
226 std::string filename2 = translate_name_mprefix(filename, true);
227 lua_callback_pre_save(filename2, true);
228 try {
229 uint64_t origtime = get_utime();
230 if(mprefix._set)
231 our_movie.prefix = sanitize_prefix(mprefix.prefix);
232 our_movie.is_savestate = true;
233 our_movie.sram = save_sram();
234 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
235 our_movie.romimg_sha256[i] = our_rom->romimg[i].sha256;
236 our_movie.romxml_sha256[i] = our_rom->romxml[i].sha256;
238 our_movie.savestate = save_core_state();
239 get_framebuffer().save(our_movie.screenshot);
240 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
241 our_movie.pollcounters);
242 our_movie.input = movb.get_movie().save();
243 our_movie.save(filename2, savecompression);
244 uint64_t took = get_utime() - origtime;
245 messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
246 lua_callback_post_save(filename2, true);
247 } catch(std::bad_alloc& e) {
248 throw;
249 } catch(std::exception& e) {
250 messages << "Save failed: " << e.what() << std::endl;
251 lua_callback_err_save(filename2);
253 last_save = boost_fs::absolute(boost_fs::path(filename2)).string();
256 //Save movie.
257 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
259 if(!our_movie.gametype) {
260 messages << "Can't save movie without a ROM" << std::endl;
261 return;
263 std::string filename2 = translate_name_mprefix(filename, true);
264 lua_callback_pre_save(filename2, false);
265 try {
266 uint64_t origtime = get_utime();
267 if(mprefix._set)
268 our_movie.prefix = sanitize_prefix(static_cast<std::string>(mprefix.prefix));
269 our_movie.is_savestate = false;
270 our_movie.input = movb.get_movie().save();
271 our_movie.save(filename2, savecompression);
272 uint64_t took = get_utime() - origtime;
273 messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
274 lua_callback_post_save(filename2, false);
275 } catch(std::bad_alloc& e) {
276 OOM_panic();
277 } catch(std::exception& e) {
278 messages << "Save failed: " << e.what() << std::endl;
279 lua_callback_err_save(filename2);
281 last_save = boost_fs::absolute(boost_fs::path(filename2)).string();
284 extern time_t random_seed_value;
286 void do_load_beginning(bool reload) throw(std::bad_alloc, std::runtime_error)
288 if(!our_movie.gametype && !reload) {
289 messages << "Can't load movie without a ROM" << std::endl;
290 return;
292 set_preload_settings();
294 //Negative return.
295 if(!reload) {
296 //Force unlazy rrdata.
297 rrdata::read_base(our_movie.projectid, false);
298 rrdata::add_internal();
300 try {
301 bool ro = movb.get_movie().readonly_mode();
302 movb.get_movie().reset_state();
303 random_seed_value = our_movie.movie_rtc_second;
304 our_rom->load(our_movie.movie_rtc_second, our_movie.movie_rtc_subsecond);
305 if(our_rom->rtype)
306 our_movie.gametype = &our_rom->rtype->combine_region(*our_rom->region);
307 else
308 our_movie.gametype = NULL;
309 if(reload)
310 movb.get_movie().readonly_mode(ro);
312 load_sram(our_movie.movie_sram);
313 our_movie.rtc_second = our_movie.movie_rtc_second;
314 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
315 if(!our_movie.anchor_savestate.empty())
316 load_core_state(our_movie.anchor_savestate);
317 redraw_framebuffer(screen_nosignal);
318 lua_callback_do_rewind();
319 } catch(std::bad_alloc& e) {
320 OOM_panic();
321 } catch(std::exception& e) {
322 system_corrupt = true;
323 redraw_framebuffer(screen_corrupt, true);
324 throw;
326 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
327 our_movie.is_savestate = false;
328 our_movie.host_memory.clear();
329 if(reload)
330 messages << "ROM reloaded." << std::endl;
331 else
332 messages << "Movie rewound to beginning." << std::endl;
335 //Load state from loaded movie file (does not catch errors).
336 void do_load_state(struct moviefile& _movie, int lmode)
338 bool current_mode = movb.get_movie().readonly_mode();
339 if(_movie.force_corrupt)
340 throw std::runtime_error("Movie file invalid");
341 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
342 if(our_rom->rtype && &(_movie.gametype->get_type()) != our_rom->rtype)
343 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
344 if(our_rom->orig_region && !our_rom->orig_region->compatible_with(_movie.gametype->get_region()))
345 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
347 if(our_rom->rtype && _movie.coreversion != bsnes_core_version) {
348 if(will_load_state) {
349 std::ostringstream x;
350 x << "ERROR: Emulator core version mismatch!" << std::endl
351 << "\tThis version: " << bsnes_core_version << std::endl
352 << "\tFile is from: " << _movie.coreversion << std::endl;
353 throw std::runtime_error(x.str());
354 } else
355 messages << "WARNING: Emulator core version mismatch!" << std::endl
356 << "\tThis version: " << bsnes_core_version << std::endl
357 << "\tFile is from: " << _movie.coreversion << std::endl;
359 bool rom_ok = true;
360 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
361 rom_ok = rom_ok & warn_hash_mismatch(_movie.romimg_sha256[i], our_rom->romimg[i],
362 (stringfmt() << "ROM #" << (i + 1)).str(), will_load_state);
363 rom_ok = rom_ok & warn_hash_mismatch(_movie.romxml_sha256[i], our_rom->romxml[i],
364 (stringfmt() << "XML #" << (i + 1)).str(), will_load_state);
366 if(!rom_ok)
367 throw std::runtime_error("Incorrect ROM");
369 set_preload_settings();
371 if(lmode == LOAD_STATE_CURRENT && movb.get_movie().readonly_mode() && readonly_load_preserves)
372 lmode = LOAD_STATE_PRESERVE;
374 movie newmovie;
375 if(lmode == LOAD_STATE_PRESERVE)
376 newmovie = movb.get_movie();
377 else
378 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
380 if(will_load_state)
381 newmovie.restore_state(_movie.save_frame, _movie.lagged_frames, _movie.pollcounters, true,
382 (lmode == LOAD_STATE_PRESERVE) ? &_movie.input : NULL, _movie.projectid);
384 //Negative return.
385 rrdata::read_base(_movie.projectid, _movie.lazy_project_create);
386 rrdata::read(_movie.c_rrdata);
387 rrdata::add_internal();
388 try {
389 our_rom->region = _movie.gametype ? &(_movie.gametype->get_region()) : NULL;
390 random_seed_value = _movie.movie_rtc_second;
391 our_rom->load(_movie.movie_rtc_second, _movie.movie_rtc_subsecond);
393 if(will_load_state) {
394 //Load the savestate and movie state.
395 //Set the core ports in order to avoid port state being reinitialized when loading.
396 controls.set_port(0, *_movie.port1, true);
397 controls.set_port(1, *_movie.port2, true);
398 load_core_state(_movie.savestate);
399 } else {
400 load_sram(_movie.movie_sram);
401 controls.set_port(0, *_movie.port1, true);
402 controls.set_port(1, *_movie.port2, true);
403 _movie.rtc_second = _movie.movie_rtc_second;
404 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
405 if(!_movie.anchor_savestate.empty())
406 load_core_state(_movie.anchor_savestate);
408 } catch(std::bad_alloc& e) {
409 OOM_panic();
410 } catch(std::exception& e) {
411 system_corrupt = true;
412 redraw_framebuffer(screen_corrupt, true);
413 throw;
416 //Okay, copy the movie data.
417 if(lmode != LOAD_STATE_PRESERVE)
418 our_movie = _movie;
419 else {
420 //The is_savestate MUST be taken from movie (except LOAD_STATE_MOVIE), or one gets desyncs.
421 our_movie.is_savestate = _movie.is_savestate;
423 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
424 our_movie.is_savestate = false;
425 our_movie.host_memory.clear();
427 if(our_movie.prefix != "" && lmode != LOAD_STATE_PRESERVE) {
428 mprefix.prefix = our_movie.prefix;
429 mprefix._set = true;
431 movb.get_movie() = newmovie;
432 //Paint the screen.
434 framebuffer_raw tmp;
435 if(will_load_state) {
436 tmp.load(_movie.screenshot);
437 redraw_framebuffer(tmp);
438 } else
439 redraw_framebuffer(screen_nosignal);
441 //Activate RW mode if needed.
442 if(lmode == LOAD_STATE_RW)
443 movb.get_movie().readonly_mode(false);
444 if(lmode == LOAD_STATE_DEFAULT && !current_mode &&
445 movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
446 movb.get_movie().readonly_mode(false);
447 if(lmode == LOAD_STATE_CURRENT && !current_mode)
448 movb.get_movie().readonly_mode(false);
449 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
450 if(our_rom->rtype)
451 messages << "ROM Type " << our_rom->rtype->get_hname() << " region " << our_rom->region->get_hname()
452 << std::endl;
453 uint64_t mlength = _movie.get_movie_length();
455 mlength += 999999;
456 std::ostringstream x;
457 if(mlength > 3600000000000) {
458 x << mlength / 3600000000000 << ":";
459 mlength %= 3600000000000;
461 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
462 mlength %= 60000000000;
463 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
464 mlength %= 1000000000;
465 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
466 std::string rerecords = _movie.rerecords;
467 if(lmode != LOAD_STATE_MOVIE)
468 rerecords = (stringfmt() << rrdata::count()).str();
469 messages << "Rerecords " << rerecords << " length " << x.str() << " ("
470 << _movie.get_frame_count() << " frames)" << std::endl;
472 if(_movie.gamename != "")
473 messages << "Game Name: " << _movie.gamename << std::endl;
474 for(size_t i = 0; i < _movie.authors.size(); i++)
475 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
476 << std::endl;
479 //Load state
480 bool do_load_state(const std::string& filename, int lmode)
482 std::string filename2 = translate_name_mprefix(filename, true);
483 uint64_t origtime = get_utime();
484 lua_callback_pre_load(filename2);
485 struct moviefile mfile;
486 try {
487 mfile = moviefile(filename2);
488 } catch(std::bad_alloc& e) {
489 OOM_panic();
490 } catch(std::exception& e) {
491 messages << "Can't read movie/savestate '" << filename2 << "': " << e.what() << std::endl;
492 lua_callback_err_load(filename2);
493 return false;
495 try {
496 do_load_state(mfile, lmode);
497 uint64_t took = get_utime() - origtime;
498 messages << "Loaded '" << filename2 << "' in " << took << " microseconds." << std::endl;
499 lua_callback_post_load(filename2, our_movie.is_savestate);
500 } catch(std::bad_alloc& e) {
501 OOM_panic();
502 } catch(std::exception& e) {
503 messages << "Can't load movie/savestate '" << filename2 << "': " << e.what() << std::endl;
504 lua_callback_err_load(filename2);
505 return false;
507 return true;
510 void mainloop_restore_state(const std::vector<char>& state, uint64_t secs, uint64_t ssecs)
512 //Force unlazy rrdata.
513 rrdata::read_base(our_movie.projectid, false);
514 rrdata::add_internal();
515 our_movie.rtc_second = secs;
516 our_movie.rtc_subsecond = ssecs;
517 load_core_state(state, true);