Handle conflicting sysregions
[lsnes.git] / src / core / moviedata.cpp
blobb72cee46d11626fbd843289328861c9825b4576b
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/rrdata.hpp"
12 #include "core/settings.hpp"
13 #include "library/string.hpp"
14 #include "interface/romtype.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(lsnes_set, "savecompression", 0, 9, 7);
57 boolean_setting readonly_load_preserves(lsnes_set, "preserve_on_readonly_load", true);
59 path_setting slotpath_setting(lsnes_set, "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(lsnes_set, "$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<const std::string&> dump_coresave(lsnes_cmd, "dump-coresave", "Dump bsnes core state",
102 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
103 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
104 auto x = our_rom->save_core_state();
105 x.resize(x.size() - 32);
106 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
107 y.write(&x[0], x.size());
108 y.close();
109 messages << "Saved core state to " << name << std::endl;
112 bool warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
113 const std::string& name, bool fatal)
115 if(mhash == slot.sha_256)
116 return true;
117 if(!fatal) {
118 messages << "WARNING: " << name << " hash mismatch!" << std::endl
119 << "\tMovie: " << mhash << std::endl
120 << "\tOur ROM: " << slot.sha_256 << std::endl;
121 return true;
122 } else {
123 messages << "ERROR: " << name << " hash mismatch!" << std::endl
124 << "\tMovie: " << mhash << std::endl
125 << "\tOur ROM: " << slot.sha_256 << std::endl;
126 return false;
131 std::string translate_name_mprefix(std::string original, bool forio)
133 size_t prefixloc = original.find("${project}");
134 if(prefixloc < original.length()) {
135 std::string pprf = forio ? (slotpath_setting.get() + "/") : std::string("");
136 if(prefixloc == 0)
137 return pprf + static_cast<std::string>(mprefix) + original.substr(prefixloc + 10);
138 else
139 return original.substr(0, prefixloc) + static_cast<std::string>(mprefix) +
140 original.substr(prefixloc + 10);
141 } else
142 return original;
145 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
146 std::runtime_error)
148 std::string _author = author;
149 std::string fullname;
150 std::string nickname;
151 size_t split = _author.find_first_of("|");
152 if(split >= _author.length()) {
153 fullname = _author;
154 } else {
155 fullname = _author.substr(0, split);
156 nickname = _author.substr(split + 1);
158 if(fullname == "" && nickname == "")
159 throw std::runtime_error("Bad author name");
160 return std::make_pair(fullname, nickname);
164 //Save state.
165 void do_save_state(const std::string& filename) throw(std::bad_alloc,
166 std::runtime_error)
168 if(!our_movie.gametype) {
169 messages << "Can't save movie without a ROM" << std::endl;
170 return;
172 std::string filename2 = translate_name_mprefix(filename, true);
173 lua_callback_pre_save(filename2, true);
174 try {
175 uint64_t origtime = get_utime();
176 if(mprefix._set)
177 our_movie.prefix = sanitize_prefix(mprefix.prefix);
178 our_movie.is_savestate = true;
179 our_movie.sram = our_rom->rtype->save_sram();
180 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
181 our_movie.romimg_sha256[i] = our_rom->romimg[i].sha_256;
182 our_movie.romxml_sha256[i] = our_rom->romxml[i].sha_256;
184 our_movie.savestate = our_rom->save_core_state();
185 get_framebuffer().save(our_movie.screenshot);
186 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
187 our_movie.pollcounters);
188 our_movie.input = movb.get_movie().save();
189 our_movie.save(filename2, savecompression);
190 our_movie.poll_flag = our_rom->rtype->get_pflag();
191 uint64_t took = get_utime() - origtime;
192 messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
193 lua_callback_post_save(filename2, true);
194 } catch(std::bad_alloc& e) {
195 throw;
196 } catch(std::exception& e) {
197 messages << "Save failed: " << e.what() << std::endl;
198 lua_callback_err_save(filename2);
200 last_save = boost_fs::absolute(boost_fs::path(filename2)).string();
203 //Save movie.
204 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
206 if(!our_movie.gametype) {
207 messages << "Can't save movie without a ROM" << std::endl;
208 return;
210 std::string filename2 = translate_name_mprefix(filename, true);
211 lua_callback_pre_save(filename2, false);
212 try {
213 uint64_t origtime = get_utime();
214 if(mprefix._set)
215 our_movie.prefix = sanitize_prefix(static_cast<std::string>(mprefix.prefix));
216 our_movie.is_savestate = false;
217 our_movie.input = movb.get_movie().save();
218 our_movie.save(filename2, savecompression);
219 uint64_t took = get_utime() - origtime;
220 messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
221 lua_callback_post_save(filename2, false);
222 } catch(std::bad_alloc& e) {
223 OOM_panic();
224 } catch(std::exception& e) {
225 messages << "Save failed: " << e.what() << std::endl;
226 lua_callback_err_save(filename2);
228 last_save = boost_fs::absolute(boost_fs::path(filename2)).string();
231 extern time_t random_seed_value;
233 void do_load_beginning(bool reload) throw(std::bad_alloc, std::runtime_error)
235 bool force_rw = false;
236 if(!our_movie.gametype && !reload) {
237 messages << "Can't load movie without a ROM" << std::endl;
238 return;
241 //Negative return.
242 if(!reload) {
243 //Force unlazy rrdata.
244 rrdata::read_base(our_movie.projectid, false);
245 rrdata::add_internal();
246 } else {
247 auto ctrldata = our_rom->rtype->controllerconfig(our_movie.settings);
248 port_type_set& portset = port_type_set::make(ctrldata.ports, ctrldata.portindex);
249 controls.set_ports(portset);
250 if(our_movie.input.get_types() != portset) {
251 //The input type changes, so set the types.
252 force_rw = true;
253 our_movie.input.clear(portset);
254 movb.get_movie().load(our_movie.rerecords, our_movie.projectid, our_movie.input);
257 try {
258 bool ro = movb.get_movie().readonly_mode() && !force_rw;
259 movb.get_movie().reset_state();
260 random_seed_value = our_movie.movie_rtc_second;
261 our_rom->load(our_movie.settings, our_movie.movie_rtc_second, our_movie.movie_rtc_subsecond);
262 our_movie.gametype = &our_rom->rtype->combine_region(*our_rom->region);
263 if(reload)
264 movb.get_movie().readonly_mode(ro);
266 our_rom->rtype->load_sram(our_movie.movie_sram);
267 our_movie.rtc_second = our_movie.movie_rtc_second;
268 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
269 if(!our_movie.anchor_savestate.empty())
270 our_rom->load_core_state(our_movie.anchor_savestate);
271 our_rom->rtype->set_pflag(0);
272 redraw_framebuffer(our_rom->rtype->draw_cover());
273 lua_callback_do_rewind();
274 } catch(std::bad_alloc& e) {
275 OOM_panic();
276 } catch(std::exception& e) {
277 system_corrupt = true;
278 redraw_framebuffer(screen_corrupt, true);
279 throw;
281 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
282 our_movie.is_savestate = false;
283 our_movie.host_memory.clear();
284 if(reload)
285 messages << "ROM reloaded." << std::endl;
286 else
287 messages << "Movie rewound to beginning." << std::endl;
290 //Load state from loaded movie file (does not catch errors).
291 void do_load_state(struct moviefile& _movie, int lmode)
293 bool current_mode = movb.get_movie().readonly_mode();
294 if(_movie.force_corrupt)
295 throw std::runtime_error("Movie file invalid");
296 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
297 if(our_rom->rtype && &(_movie.gametype->get_type()) != our_rom->rtype)
298 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
299 if(our_rom->orig_region && !our_rom->orig_region->compatible_with(_movie.gametype->get_region()))
300 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
302 if(our_rom->rtype && _movie.coreversion != our_rom->rtype->get_core_identifier()) {
303 if(will_load_state) {
304 std::ostringstream x;
305 x << "ERROR: Emulator core version mismatch!" << std::endl
306 << "\tThis version: " << our_rom->rtype->get_core_identifier() << std::endl
307 << "\tFile is from: " << _movie.coreversion << std::endl;
308 throw std::runtime_error(x.str());
309 } else
310 messages << "WARNING: Emulator core version mismatch!" << std::endl
311 << "\tThis version: " << our_rom->rtype->get_core_identifier() << std::endl
312 << "\tFile is from: " << _movie.coreversion << std::endl;
314 bool rom_ok = true;
315 for(size_t i = 0; i < sizeof(our_rom->romimg)/sizeof(our_rom->romimg[0]); i++) {
316 rom_ok = rom_ok & warn_hash_mismatch(_movie.romimg_sha256[i], our_rom->romimg[i],
317 (stringfmt() << "ROM #" << (i + 1)).str(), will_load_state);
318 rom_ok = rom_ok & warn_hash_mismatch(_movie.romxml_sha256[i], our_rom->romxml[i],
319 (stringfmt() << "XML #" << (i + 1)).str(), will_load_state);
321 if(!rom_ok)
322 throw std::runtime_error("Incorrect ROM");
324 if(lmode == LOAD_STATE_CURRENT && movb.get_movie().readonly_mode() && readonly_load_preserves)
325 lmode = LOAD_STATE_PRESERVE;
327 movie newmovie;
328 if(lmode == LOAD_STATE_PRESERVE)
329 newmovie = movb.get_movie();
330 else
331 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
333 if(will_load_state)
334 newmovie.restore_state(_movie.save_frame, _movie.lagged_frames, _movie.pollcounters, true,
335 (lmode == LOAD_STATE_PRESERVE) ? &_movie.input : NULL, _movie.projectid);
337 auto ctrldata = our_rom->rtype->controllerconfig(_movie.settings);
338 port_type_set& portset = port_type_set::make(ctrldata.ports, ctrldata.portindex);
340 //Negative return.
341 rrdata::read_base(_movie.projectid, _movie.lazy_project_create);
342 rrdata::read(_movie.c_rrdata);
343 rrdata::add_internal();
344 try {
345 our_rom->region = _movie.gametype ? &(_movie.gametype->get_region()) : NULL;
346 random_seed_value = _movie.movie_rtc_second;
347 our_rom->load(_movie.settings, _movie.movie_rtc_second, _movie.movie_rtc_subsecond);
349 if(will_load_state) {
350 //Load the savestate and movie state.
351 //Set the core ports in order to avoid port state being reinitialized when loading.
352 controls.set_ports(portset);
353 our_rom->load_core_state(_movie.savestate);
354 our_rom->rtype->set_pflag(_movie.poll_flag);
355 } else {
356 our_rom->rtype->load_sram(_movie.movie_sram);
357 controls.set_ports(portset);
358 _movie.rtc_second = _movie.movie_rtc_second;
359 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
360 if(!_movie.anchor_savestate.empty())
361 our_rom->load_core_state(_movie.anchor_savestate);
362 our_rom->rtype->set_pflag(0);
364 } catch(std::bad_alloc& e) {
365 OOM_panic();
366 } catch(std::exception& e) {
367 system_corrupt = true;
368 redraw_framebuffer(screen_corrupt, true);
369 throw;
372 //Okay, copy the movie data.
373 if(lmode != LOAD_STATE_PRESERVE)
374 our_movie = _movie;
375 else {
376 //The is_savestate MUST be taken from movie (except LOAD_STATE_MOVIE), or one gets desyncs.
377 our_movie.is_savestate = _movie.is_savestate;
379 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
380 our_movie.is_savestate = false;
381 our_movie.host_memory.clear();
383 if(our_movie.prefix != "" && lmode != LOAD_STATE_PRESERVE) {
384 mprefix.prefix = our_movie.prefix;
385 mprefix._set = true;
387 movb.get_movie() = newmovie;
388 //Activate RW mode if needed.
389 if(lmode == LOAD_STATE_RW)
390 movb.get_movie().readonly_mode(false);
391 if(lmode == LOAD_STATE_DEFAULT && !current_mode &&
392 movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
393 movb.get_movie().readonly_mode(false);
394 if(lmode == LOAD_STATE_INITIAL && movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
395 movb.get_movie().readonly_mode(false);
396 if(lmode == LOAD_STATE_CURRENT && !current_mode)
397 movb.get_movie().readonly_mode(false);
398 //Paint the screen.
400 framebuffer_raw tmp;
401 if(will_load_state) {
402 tmp.load(_movie.screenshot);
403 redraw_framebuffer(tmp);
404 } else
405 redraw_framebuffer(our_rom->rtype->draw_cover());
407 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
408 if(our_rom->rtype)
409 messages << "ROM Type " << our_rom->rtype->get_hname() << " region " << our_rom->region->get_hname()
410 << std::endl;
411 uint64_t mlength = _movie.get_movie_length();
413 mlength += 999999;
414 std::ostringstream x;
415 if(mlength > 3600000000000) {
416 x << mlength / 3600000000000 << ":";
417 mlength %= 3600000000000;
419 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
420 mlength %= 60000000000;
421 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
422 mlength %= 1000000000;
423 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
424 std::string rerecords = _movie.rerecords;
425 if(lmode != LOAD_STATE_MOVIE)
426 rerecords = (stringfmt() << rrdata::count()).str();
427 messages << "Rerecords " << rerecords << " length " << x.str() << " ("
428 << _movie.get_frame_count() << " frames)" << std::endl;
430 if(_movie.gamename != "")
431 messages << "Game Name: " << _movie.gamename << std::endl;
432 for(size_t i = 0; i < _movie.authors.size(); i++)
433 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
434 << std::endl;
437 //Load state
438 bool do_load_state(const std::string& filename, int lmode)
440 std::string filename2 = translate_name_mprefix(filename, true);
441 uint64_t origtime = get_utime();
442 lua_callback_pre_load(filename2);
443 struct moviefile mfile;
444 try {
445 mfile = moviefile(filename2, *our_rom->rtype);
446 } catch(std::bad_alloc& e) {
447 OOM_panic();
448 } catch(std::exception& e) {
449 messages << "Can't read movie/savestate '" << filename2 << "': " << e.what() << std::endl;
450 lua_callback_err_load(filename2);
451 return false;
453 try {
454 do_load_state(mfile, lmode);
455 uint64_t took = get_utime() - origtime;
456 messages << "Loaded '" << filename2 << "' in " << took << " microseconds." << std::endl;
457 lua_callback_post_load(filename2, our_movie.is_savestate);
458 } catch(std::bad_alloc& e) {
459 OOM_panic();
460 } catch(std::exception& e) {
461 messages << "Can't load movie/savestate '" << filename2 << "': " << e.what() << std::endl;
462 lua_callback_err_load(filename2);
463 return false;
465 return true;
468 void mainloop_restore_state(const std::vector<char>& state, uint64_t secs, uint64_t ssecs)
470 //Force unlazy rrdata.
471 rrdata::read_base(our_movie.projectid, false);
472 rrdata::add_internal();
473 our_movie.rtc_second = secs;
474 our_movie.rtc_subsecond = ssecs;
475 our_rom->load_core_state(state, true);