More cleanup via initializer lists
[lsnes.git] / src / core / moviedata.cpp
blobadb0ecc2523a857863db677b5594331f0f68e8f0
1 #include "lsnes.hpp"
3 #include "core/command.hpp"
4 #include "core/controller.hpp"
5 #include "core/dispatch.hpp"
6 #include "core/framebuffer.hpp"
7 #include "core/framerate.hpp"
8 #include "lua/lua.hpp"
9 #include "core/misc.hpp"
10 #include "core/moviedata.hpp"
11 #include "core/project.hpp"
12 #include "core/rrdata.hpp"
13 #include "core/settings.hpp"
14 #include "library/string.hpp"
15 #include "interface/romtype.hpp"
17 #include <iomanip>
18 #include <fstream>
20 #include <boost/filesystem.hpp>
22 #ifdef BOOST_FILESYSTEM3
23 namespace boost_fs = boost::filesystem3;
24 #else
25 namespace boost_fs = boost::filesystem;
26 #endif
28 struct moviefile our_movie;
29 struct loaded_rom* our_rom;
30 bool system_corrupt;
31 movie_logic movb;
32 std::string last_save;
33 void update_movie_state();
35 extern "C"
37 time_t __wrap_time(time_t* t)
39 time_t v = static_cast<time_t>(our_movie.rtc_second);
40 if(t)
41 *t = v;
42 return v;
46 std::vector<char>& get_host_memory()
48 return our_movie.host_memory;
51 movie& get_movie()
53 return movb.get_movie();
56 namespace
58 setting_var<setting_var_model_int<0, 9>> savecompression(lsnes_vset, "savecompression", "Movie‣Compression",
59 7);
60 setting_var<setting_var_model_bool<setting_yes_no>> readonly_load_preserves(lsnes_vset,
61 "preserve_on_readonly_load", "Movie‣Preserve on readonly load", true);
62 mutex_class mprefix_lock;
63 std::string mprefix;
64 bool mprefix_valid;
66 std::string get_mprefix()
68 umutex_class h(mprefix_lock);
69 if(!mprefix_valid)
70 return "movieslot";
71 else
72 return mprefix + "-";
75 function_ptr_command<const std::string&> dump_coresave(lsnes_cmd, "dump-coresave", "Dump bsnes core state",
76 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
77 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
78 auto x = our_rom->save_core_state();
79 x.resize(x.size() - 32);
80 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
81 y.write(&x[0], x.size());
82 y.close();
83 messages << "Saved core state to " << name << std::endl;
84 });
86 bool warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
87 const std::string& name, bool fatal)
89 if(mhash == slot.sha_256)
90 return true;
91 if(!fatal) {
92 messages << "WARNING: " << name << " hash mismatch!" << std::endl
93 << "\tMovie: " << mhash << std::endl
94 << "\tOur ROM: " << slot.sha_256 << std::endl;
95 return true;
96 } else {
97 messages << "ERROR: " << name << " hash mismatch!" << std::endl
98 << "\tMovie: " << mhash << std::endl
99 << "\tOur ROM: " << slot.sha_256 << std::endl;
100 return false;
104 void set_mprefix(const std::string& pfx)
107 umutex_class h(mprefix_lock);
108 mprefix_valid = (pfx != "");
109 mprefix = pfx;
111 update_movie_state();
112 information_dispatch::do_status_update();
115 std::string get_mprefix_for_project(const std::string& prjid)
117 std::string filename = get_config_path() + "/" + safe_filename(prjid) + ".pfx";
118 std::ifstream strm(filename);
119 if(!strm)
120 return "";
121 std::string pfx;
122 std::getline(strm, pfx);
123 return strip_CR(pfx);
127 std::string get_mprefix_for_project()
129 return get_mprefix_for_project(our_movie.projectid);
132 void set_mprefix_for_project(const std::string& prjid, const std::string& pfx)
134 std::string filename = get_config_path() + "/" + safe_filename(prjid) + ".pfx";
135 std::ofstream strm(filename);
136 strm << pfx << std::endl;
139 void set_mprefix_for_project(const std::string& pfx)
141 set_mprefix_for_project(our_movie.projectid, pfx);
142 set_mprefix(pfx);
145 std::string translate_name_mprefix(std::string original)
147 auto p = project_get();
148 regex_results r;
149 if(p && (r = regex("\\$\\{project\\}([0-9]+).lsmv", original))) {
150 return p->directory + "/" + p->prefix + "-" + r[1] + ".lss";
152 size_t prefixloc = original.find("${project}");
153 if(prefixloc < original.length()) {
154 std::string pprf = lsnes_vset["slotpath"].str() + "/";
155 if(prefixloc == 0)
156 return pprf + get_mprefix() + original.substr(prefixloc + 10);
157 else
158 return original.substr(0, prefixloc) + get_mprefix() + original.substr(prefixloc + 10);
159 } else
160 return original;
163 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
164 std::runtime_error)
166 std::string _author = author;
167 std::string fullname;
168 std::string nickname;
169 size_t split = _author.find_first_of("|");
170 if(split >= _author.length()) {
171 fullname = _author;
172 } else {
173 fullname = _author.substr(0, split);
174 nickname = _author.substr(split + 1);
176 if(fullname == "" && nickname == "")
177 throw std::runtime_error("Bad author name");
178 return std::make_pair(fullname, nickname);
181 //Resolve relative path.
182 std::string resolve_relative_path(const std::string& path)
184 try {
185 return boost_fs::absolute(boost_fs::path(path)).string();
186 } catch(...) {
187 return path;
191 //Save state.
192 void do_save_state(const std::string& filename) throw(std::bad_alloc,
193 std::runtime_error)
195 if(!our_movie.gametype) {
196 messages << "Can't save movie without a ROM" << std::endl;
197 return;
199 std::string filename2 = translate_name_mprefix(filename);
200 lua_callback_pre_save(filename2, true);
201 try {
202 uint64_t origtime = get_utime();
203 our_movie.is_savestate = true;
204 our_movie.sram = our_rom->rtype->save_sram();
205 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
206 our_movie.romimg_sha256[i] = our_rom->romimg[i].sha_256;
207 our_movie.romxml_sha256[i] = our_rom->romxml[i].sha_256;
209 our_movie.savestate = our_rom->save_core_state();
210 get_framebuffer().save(our_movie.screenshot);
211 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
212 our_movie.pollcounters);
213 our_movie.input = movb.get_movie().save();
214 our_movie.poll_flag = our_rom->rtype->get_pflag();
215 auto prj = project_get();
216 if(prj) {
217 our_movie.gamename = prj->gamename;
218 our_movie.authors = prj->authors;
220 our_movie.active_macros = controls.get_macro_frames();
221 our_movie.save(filename2, savecompression);
222 uint64_t took = get_utime() - origtime;
223 messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
224 lua_callback_post_save(filename2, true);
225 } catch(std::bad_alloc& e) {
226 throw;
227 } catch(std::exception& e) {
228 messages << "Save failed: " << e.what() << std::endl;
229 lua_callback_err_save(filename2);
231 last_save = resolve_relative_path(filename2);
232 auto p = project_get();
233 if(p) {
234 p->last_save = last_save;
235 project_flush(p);
239 //Save movie.
240 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
242 if(!our_movie.gametype) {
243 messages << "Can't save movie without a ROM" << std::endl;
244 return;
246 std::string filename2 = translate_name_mprefix(filename);
247 lua_callback_pre_save(filename2, false);
248 try {
249 uint64_t origtime = get_utime();
250 our_movie.is_savestate = false;
251 our_movie.input = movb.get_movie().save();
252 auto prj = project_get();
253 if(prj) {
254 our_movie.gamename = prj->gamename;
255 our_movie.authors = prj->authors;
257 our_movie.active_macros.clear();
258 our_movie.save(filename2, savecompression);
259 uint64_t took = get_utime() - origtime;
260 messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
261 lua_callback_post_save(filename2, false);
262 } catch(std::bad_alloc& e) {
263 OOM_panic();
264 } catch(std::exception& e) {
265 messages << "Save failed: " << e.what() << std::endl;
266 lua_callback_err_save(filename2);
268 last_save = resolve_relative_path(filename2);
269 auto p = project_get();
270 if(p) {
271 p->last_save = last_save;
272 project_flush(p);
276 extern time_t random_seed_value;
278 void do_load_beginning(bool reload) throw(std::bad_alloc, std::runtime_error)
280 bool force_rw = false;
281 if(!our_movie.gametype && !reload) {
282 messages << "Can't load movie without a ROM" << std::endl;
283 return;
286 //Negative return.
287 if(!reload) {
288 //Force unlazy rrdata.
289 rrdata::read_base(our_movie.projectid, false);
290 rrdata::add_internal();
291 } else {
292 auto ctrldata = our_rom->rtype->controllerconfig(our_movie.settings);
293 port_type_set& portset = port_type_set::make(ctrldata.ports, ctrldata.portindex);
294 controls.set_ports(portset);
295 if(our_movie.input.get_types() != portset) {
296 //The input type changes, so set the types.
297 force_rw = true;
298 our_movie.input.clear(portset);
299 movb.get_movie().load(our_movie.rerecords, our_movie.projectid, our_movie.input);
302 try {
303 bool ro = movb.get_movie().readonly_mode() && !force_rw;
304 movb.get_movie().reset_state();
305 random_seed_value = our_movie.movie_rtc_second;
306 our_rom->load(our_movie.settings, our_movie.movie_rtc_second, our_movie.movie_rtc_subsecond);
307 our_movie.gametype = &our_rom->rtype->combine_region(*our_rom->region);
308 if(reload)
309 movb.get_movie().readonly_mode(ro);
311 our_rom->rtype->load_sram(our_movie.movie_sram);
312 our_movie.rtc_second = our_movie.movie_rtc_second;
313 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
314 if(!our_movie.anchor_savestate.empty())
315 our_rom->load_core_state(our_movie.anchor_savestate);
316 our_rom->rtype->set_pflag(0);
317 controls.set_macro_frames(std::map<std::string, uint64_t>());
318 redraw_framebuffer(our_rom->rtype->draw_cover());
319 lua_callback_do_rewind();
320 } catch(std::bad_alloc& e) {
321 OOM_panic();
322 } catch(std::exception& e) {
323 system_corrupt = true;
324 redraw_framebuffer(screen_corrupt, true);
325 throw;
327 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
328 our_movie.is_savestate = false;
329 our_movie.host_memory.clear();
330 if(reload)
331 messages << "ROM reloaded." << std::endl;
332 else
333 messages << "Movie rewound to beginning." << std::endl;
336 //Load state from loaded movie file (does not catch errors).
337 void do_load_state(struct moviefile& _movie, int lmode)
339 bool current_mode = movb.get_movie().readonly_mode();
340 if(_movie.force_corrupt)
341 throw std::runtime_error("Movie file invalid");
342 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
343 if(our_rom->rtype && &(_movie.gametype->get_type()) != our_rom->rtype)
344 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
345 if(our_rom->orig_region && !our_rom->orig_region->compatible_with(_movie.gametype->get_region()))
346 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
347 auto _hostmemory = _movie.host_memory;
349 if(our_rom->rtype && _movie.coreversion != our_rom->rtype->get_core_identifier()) {
350 if(will_load_state) {
351 std::ostringstream x;
352 x << "ERROR: Emulator core version mismatch!" << std::endl
353 << "\tThis version: " << our_rom->rtype->get_core_identifier() << std::endl
354 << "\tFile is from: " << _movie.coreversion << std::endl;
355 throw std::runtime_error(x.str());
356 } else
357 messages << "WARNING: Emulator core version mismatch!" << std::endl
358 << "\tThis version: " << our_rom->rtype->get_core_identifier() << std::endl
359 << "\tFile is from: " << _movie.coreversion << std::endl;
361 bool rom_ok = true;
362 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
363 rom_ok = rom_ok & warn_hash_mismatch(_movie.romimg_sha256[i], our_rom->romimg[i],
364 (stringfmt() << "ROM #" << (i + 1)).str(), will_load_state);
365 rom_ok = rom_ok & warn_hash_mismatch(_movie.romxml_sha256[i], our_rom->romxml[i],
366 (stringfmt() << "XML #" << (i + 1)).str(), will_load_state);
368 if(!rom_ok)
369 throw std::runtime_error("Incorrect ROM");
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 auto ctrldata = our_rom->rtype->controllerconfig(_movie.settings);
385 port_type_set& portset = port_type_set::make(ctrldata.ports, ctrldata.portindex);
387 //Negative return.
388 rrdata::read_base(_movie.projectid, _movie.lazy_project_create);
389 rrdata::read(_movie.c_rrdata);
390 rrdata::add_internal();
391 try {
392 our_rom->region = _movie.gametype ? &(_movie.gametype->get_region()) : NULL;
393 random_seed_value = _movie.movie_rtc_second;
394 our_rom->load(_movie.settings, _movie.movie_rtc_second, _movie.movie_rtc_subsecond);
396 if(will_load_state) {
397 //Load the savestate and movie state.
398 //Set the core ports in order to avoid port state being reinitialized when loading.
399 controls.set_ports(portset);
400 our_rom->load_core_state(_movie.savestate);
401 our_rom->rtype->set_pflag(_movie.poll_flag);
402 controls.set_macro_frames(_movie.active_macros);
403 } else {
404 our_rom->rtype->load_sram(_movie.movie_sram);
405 controls.set_ports(portset);
406 _movie.rtc_second = _movie.movie_rtc_second;
407 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
408 if(!_movie.anchor_savestate.empty())
409 our_rom->load_core_state(_movie.anchor_savestate);
410 our_rom->rtype->set_pflag(0);
411 controls.set_macro_frames(std::map<std::string, uint64_t>());
413 } catch(std::bad_alloc& e) {
414 OOM_panic();
415 } catch(std::exception& e) {
416 system_corrupt = true;
417 redraw_framebuffer(screen_corrupt, true);
418 throw;
421 //Okay, copy the movie data.
422 if(lmode != LOAD_STATE_PRESERVE)
423 our_movie = _movie;
424 else {
425 //Some fields MUST be taken from movie or one gets desyncs.
426 our_movie.is_savestate = _movie.is_savestate;
427 our_movie.rtc_second = _movie.rtc_second;
428 our_movie.rtc_subsecond = _movie.rtc_subsecond;
430 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
431 our_movie.is_savestate = false;
432 our_movie.host_memory.clear();
433 } else {
434 //Hostmemory must be unconditionally reloaded, even in preserve mode.
435 our_movie.host_memory = _hostmemory;
437 if(lmode != LOAD_STATE_PRESERVE) {
438 set_mprefix(get_mprefix_for_project(our_movie.projectid));
440 movb.get_movie() = newmovie;
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_INITIAL && movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
448 movb.get_movie().readonly_mode(false);
449 if(lmode == LOAD_STATE_CURRENT && !current_mode)
450 movb.get_movie().readonly_mode(false);
451 //Paint the screen.
453 framebuffer_raw tmp;
454 if(will_load_state) {
455 tmp.load(_movie.screenshot);
456 redraw_framebuffer(tmp);
457 } else
458 redraw_framebuffer(our_rom->rtype->draw_cover());
460 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
461 if(our_rom->rtype)
462 messages << "ROM Type " << our_rom->rtype->get_hname() << " region " << our_rom->region->get_hname()
463 << std::endl;
464 uint64_t mlength = _movie.get_movie_length();
466 mlength += 999999;
467 std::ostringstream x;
468 if(mlength > 3600000000000) {
469 x << mlength / 3600000000000 << ":";
470 mlength %= 3600000000000;
472 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
473 mlength %= 60000000000;
474 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
475 mlength %= 1000000000;
476 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
477 std::string rerecords = _movie.rerecords;
478 if(our_movie.is_savestate)
479 rerecords = (stringfmt() << rrdata::count()).str();
480 messages << "Rerecords " << rerecords << " length " << x.str() << " ("
481 << _movie.get_frame_count() << " frames)" << std::endl;
483 if(_movie.gamename != "")
484 messages << "Game Name: " << _movie.gamename << std::endl;
485 for(size_t i = 0; i < _movie.authors.size(); i++)
486 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
487 << std::endl;
490 //Load state
491 bool do_load_state(const std::string& filename, int lmode)
493 std::string filename2 = translate_name_mprefix(filename);
494 uint64_t origtime = get_utime();
495 lua_callback_pre_load(filename2);
496 struct moviefile mfile;
497 try {
498 mfile = moviefile(filename2, *our_rom->rtype);
499 } catch(std::bad_alloc& e) {
500 OOM_panic();
501 } catch(std::exception& e) {
502 messages << "Can't read movie/savestate '" << filename2 << "': " << e.what() << std::endl;
503 lua_callback_err_load(filename2);
504 return false;
506 try {
507 do_load_state(mfile, lmode);
508 uint64_t took = get_utime() - origtime;
509 messages << "Loaded '" << filename2 << "' in " << took << " microseconds." << std::endl;
510 lua_callback_post_load(filename2, our_movie.is_savestate);
511 } catch(std::bad_alloc& e) {
512 OOM_panic();
513 } catch(std::exception& e) {
514 messages << "Can't load movie/savestate '" << filename2 << "': " << e.what() << std::endl;
515 lua_callback_err_load(filename2);
516 return false;
518 return true;
521 void mainloop_restore_state(const std::vector<char>& state, uint64_t secs, uint64_t ssecs)
523 //Force unlazy rrdata.
524 rrdata::read_base(our_movie.projectid, false);
525 rrdata::add_internal();
526 our_movie.rtc_second = secs;
527 our_movie.rtc_subsecond = ssecs;
528 our_rom->load_core_state(state, true);