ROM/savestate handling fixes
[lsnes.git] / src / core / moviedata.cpp
blobcc77625b874ac14648b83fb0f63bb0e0a924fe44
1 #include "lsnes.hpp"
2 #include <snes/snes.hpp>
3 #include <ui-libsnes/libsnes.hpp>
5 #include "core/command.hpp"
6 #include "core/controller.hpp"
7 #include "core/dispatch.hpp"
8 #include "core/framebuffer.hpp"
9 #include "core/framerate.hpp"
10 #include "core/lua.hpp"
11 #include "core/misc.hpp"
12 #include "core/moviedata.hpp"
13 #include "core/rrdata.hpp"
14 #include "core/settings.hpp"
16 #include <iomanip>
17 #include <fstream>
19 #include <boost/filesystem.hpp>
21 struct moviefile our_movie;
22 struct loaded_rom* our_rom;
23 bool system_corrupt;
24 movie_logic movb;
25 std::string last_save;
27 extern "C"
29 time_t __wrap_time(time_t* t)
31 time_t v = static_cast<time_t>(our_movie.rtc_second);
32 if(t)
33 *t = v;
34 return v;
38 std::vector<char>& get_host_memory()
40 return our_movie.host_memory;
43 movie& get_movie()
45 return movb.get_movie();
48 namespace
50 numeric_setting savecompression("savecompression", 0, 9, 7);
52 class projectprefix_setting : public setting
54 public:
55 bool _set;
56 std::string prefix;
57 projectprefix_setting() throw(std::bad_alloc)
58 : setting("$project")
60 _set = false;
62 void blank() throw(std::bad_alloc, std::runtime_error)
64 _set = false;
66 bool is_set() throw()
68 return _set;
70 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
72 prefix = value;
73 _set = true;
75 std::string get() throw(std::bad_alloc)
77 return prefix;
79 operator std::string() throw()
81 if(_set)
82 return prefix + "-";
83 else
84 return "movieslot";
86 } mprefix;
88 function_ptr_command<> get_gamename("get-gamename", "Get the game name",
89 "Syntax: get-gamename\nPrints the game name\n",
90 []() throw(std::bad_alloc, std::runtime_error) {
91 messages << "Game name is '" << our_movie.gamename << "'" << std::endl;
92 });
94 function_ptr_command<const std::string&> set_gamename("set-gamename", "Set the game name",
95 "Syntax: set-gamename <name>\nSets the game name to <name>\n",
96 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
97 our_movie.gamename = args;
98 messages << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
99 });
101 function_ptr_command<> show_authors("show-authors", "Show the run authors",
102 "Syntax: show-authors\nShows the run authors\n",
103 []() throw(std::bad_alloc, std::runtime_error)
105 size_t idx = 0;
106 for(auto i : our_movie.authors) {
107 messages << (idx++) << ": " << i.first << "|" << i.second << std::endl;
109 messages << "End of authors list" << std::endl;
112 function_ptr_command<tokensplitter&> add_author("add-author", "Add an author",
113 "Syntax: add-author <fullname>\nSyntax: add-author |<nickname>\n"
114 "Syntax: add-author <fullname>|<nickname>\nAdds a new author\n",
115 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
116 auto g = split_author(t.tail());
117 our_movie.authors.push_back(g);
118 messages << (our_movie.authors.size() - 1) << ": " << g.first << "|" << g.second << std::endl;
121 function_ptr_command<tokensplitter&> remove_author("remove-author", "Remove an author",
122 "Syntax: remove-author <id>\nRemoves author with ID <id>\n",
123 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
124 uint64_t index = parse_value<uint64_t>(t.tail());
125 if(index >= our_movie.authors.size())
126 throw std::runtime_error("No such author");
127 our_movie.authors.erase(our_movie.authors.begin() + index);
130 function_ptr_command<tokensplitter&> edit_author("edit-author", "Edit an author",
131 "Syntax: edit-author <authorid> <fullname>\nSyntax: edit-author <authorid> |<nickname>\n"
132 "Syntax: edit-author <authorid> <fullname>|<nickname>\nEdits author name\n",
133 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
134 uint64_t index = parse_value<uint64_t>(t);
135 if(index >= our_movie.authors.size())
136 throw std::runtime_error("No such author");
137 auto g = split_author(t.tail());
138 our_movie.authors[index] = g;
141 function_ptr_command<const std::string&> dump_coresave("dump-coresave", "Dump bsnes core state",
142 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
143 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
144 auto x = save_core_state();
145 x.resize(x.size() - 32);
146 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
147 y.write(&x[0], x.size());
148 y.close();
149 messages << "Saved core state to " << name << std::endl;
152 bool warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
153 const std::string& name, bool fatal)
155 if(mhash == slot.sha256)
156 return true;
157 if(!fatal) {
158 messages << "WARNING: " << name << " hash mismatch!" << std::endl
159 << "\tMovie: " << mhash << std::endl
160 << "\tOur ROM: " << slot.sha256 << std::endl;
161 return true;
162 } else {
163 messages << "ERROR: " << name << " hash mismatch!" << std::endl
164 << "\tMovie: " << mhash << std::endl
165 << "\tOur ROM: " << slot.sha256 << std::endl;
166 return false;
171 std::string translate_name_mprefix(std::string original)
173 size_t prefixloc = original.find("${project}");
174 if(prefixloc < original.length())
175 return original.substr(0, prefixloc) + static_cast<std::string>(mprefix) +
176 original.substr(prefixloc + 10);
177 else
178 return original;
181 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
182 std::runtime_error)
184 std::string _author = author;
185 std::string fullname;
186 std::string nickname;
187 size_t split = _author.find_first_of("|");
188 if(split >= _author.length()) {
189 fullname = _author;
190 } else {
191 fullname = _author.substr(0, split);
192 nickname = _author.substr(split + 1);
194 if(fullname == "" && nickname == "")
195 throw std::runtime_error("Bad author name");
196 return std::make_pair(fullname, nickname);
200 //Save state.
201 void do_save_state(const std::string& filename) throw(std::bad_alloc,
202 std::runtime_error)
204 std::string filename2 = translate_name_mprefix(filename);
205 lua_callback_pre_save(filename2, true);
206 try {
207 uint64_t origtime = get_utime();
208 if(mprefix._set)
209 our_movie.prefix = sanitize_prefix(mprefix.prefix);
210 our_movie.is_savestate = true;
211 our_movie.sram = save_sram();
212 our_movie.rom_sha256 = our_rom->rom.sha256;
213 our_movie.romxml_sha256 = our_rom->rom_xml.sha256;
214 our_movie.slota_sha256 = our_rom->slota.sha256;
215 our_movie.slotaxml_sha256 = our_rom->slota_xml.sha256;
216 our_movie.slotb_sha256 = our_rom->slotb.sha256;
217 our_movie.slotbxml_sha256 = our_rom->slotb_xml.sha256;
218 our_movie.savestate = save_core_state();
219 get_framebuffer().save(our_movie.screenshot);
220 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
221 our_movie.pollcounters);
222 our_movie.input = movb.get_movie().save();
223 our_movie.save(filename2, savecompression);
224 uint64_t took = get_utime() - origtime;
225 messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
226 lua_callback_post_save(filename2, true);
227 } catch(std::bad_alloc& e) {
228 throw;
229 } catch(std::exception& e) {
230 messages << "Save failed: " << e.what() << std::endl;
231 lua_callback_err_save(filename2);
233 last_save = boost::filesystem3::absolute(boost::filesystem3::path(filename2)).string();
236 //Save movie.
237 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
239 std::string filename2 = translate_name_mprefix(filename);
240 lua_callback_pre_save(filename2, false);
241 try {
242 uint64_t origtime = get_utime();
243 if(mprefix._set)
244 our_movie.prefix = sanitize_prefix(static_cast<std::string>(mprefix.prefix));
245 our_movie.is_savestate = false;
246 our_movie.input = movb.get_movie().save();
247 our_movie.save(filename2, savecompression);
248 uint64_t took = get_utime() - origtime;
249 messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
250 lua_callback_post_save(filename2, false);
251 } catch(std::bad_alloc& e) {
252 OOM_panic();
253 } catch(std::exception& e) {
254 messages << "Save failed: " << e.what() << std::endl;
255 lua_callback_err_save(filename2);
257 last_save = boost::filesystem3::absolute(boost::filesystem3::path(filename2)).string();
260 extern time_t random_seed_value;
262 void do_load_beginning() throw(std::bad_alloc, std::runtime_error)
264 SNES::config.random = false;
265 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
267 //Negative return.
268 rrdata::add_internal();
269 try {
270 movb.get_movie().reset_state();
271 random_seed_value = our_movie.movie_rtc_second;
272 our_rom->load();
274 load_sram(our_movie.movie_sram);
275 our_movie.rtc_second = our_movie.movie_rtc_second;
276 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
277 redraw_framebuffer(screen_nosignal);
278 } catch(std::bad_alloc& e) {
279 OOM_panic();
280 } catch(std::exception& e) {
281 system_corrupt = true;
282 redraw_framebuffer(screen_corrupt, true);
283 throw;
285 our_movie.is_savestate = false;
286 our_movie.host_memory.clear();
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(gtype::toromtype(_movie.gametype) != our_rom->rtype) {
298 messages << "_movie.gametype = " << _movie.gametype << std::endl;
299 messages << "gtype::toromtype(_movie.gametype) = " << gtype::toromtype(_movie.gametype) << std::endl;
300 messages << "our_rom->rtype = " << our_rom->rtype << std::endl;
301 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
303 if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO)
304 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
306 if(_movie.coreversion != bsnes_core_version) {
307 if(will_load_state) {
308 std::ostringstream x;
309 x << "ERROR: Emulator core version mismatch!" << std::endl
310 << "\tThis version: " << bsnes_core_version << std::endl
311 << "\tFile is from: " << _movie.coreversion << std::endl;
312 throw std::runtime_error(x.str());
313 } else
314 messages << "WARNING: Emulator core version mismatch!" << std::endl
315 << "\tThis version: " << bsnes_core_version << std::endl
316 << "\tFile is from: " << _movie.coreversion << std::endl;
318 bool rom_ok = true;
319 rom_ok = rom_ok & warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1", will_load_state);
320 rom_ok = rom_ok & warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1", will_load_state);
321 rom_ok = rom_ok & warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2", will_load_state);
322 rom_ok = rom_ok & warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2", will_load_state);
323 rom_ok = rom_ok & warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3", will_load_state);
324 rom_ok = rom_ok & warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3", will_load_state);
325 if(!rom_ok)
326 throw std::runtime_error("Incorrect ROM");
328 SNES::config.random = false;
329 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
331 movie newmovie;
332 if(lmode == LOAD_STATE_PRESERVE)
333 newmovie = movb.get_movie();
334 else
335 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
337 if(will_load_state)
338 newmovie.restore_state(_movie.save_frame, _movie.lagged_frames, _movie.pollcounters, true,
339 (lmode == LOAD_STATE_PRESERVE) ? &_movie.input : NULL, _movie.projectid);
341 //Negative return.
342 rrdata::read_base(_movie.projectid);
343 rrdata::read(_movie.c_rrdata);
344 rrdata::add_internal();
345 try {
346 our_rom->region = gtype::toromregion(_movie.gametype);
347 random_seed_value = _movie.rtc_second;
348 our_rom->load();
350 if(will_load_state) {
351 //Load the savestate and movie state.
352 controls.set_port(0, _movie.port1, false);
353 controls.set_port(1, _movie.port2, false);
354 load_core_state(_movie.savestate);
355 } else {
356 load_sram(_movie.movie_sram);
357 controls.set_port(0, _movie.port1, true);
358 controls.set_port(1, _movie.port2, true);
359 _movie.rtc_second = _movie.movie_rtc_second;
360 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
362 } catch(std::bad_alloc& e) {
363 OOM_panic();
364 } catch(std::exception& e) {
365 system_corrupt = true;
366 redraw_framebuffer(screen_corrupt, true);
367 throw;
370 //Okay, copy the movie data.
371 our_movie = _movie;
372 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
373 our_movie.is_savestate = false;
374 our_movie.host_memory.clear();
376 if(our_movie.prefix != "") {
377 mprefix.prefix = our_movie.prefix;
378 mprefix._set = true;
380 movb.get_movie() = newmovie;
381 //Paint the screen.
383 lcscreen tmp;
384 if(will_load_state) {
385 tmp.load(_movie.screenshot);
386 redraw_framebuffer(tmp);
387 } else
388 redraw_framebuffer(screen_nosignal);
390 //Activate RW mode if needed.
391 if(lmode == LOAD_STATE_RW)
392 movb.get_movie().readonly_mode(false);
393 if(lmode == LOAD_STATE_DEFAULT && movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
394 movb.get_movie().readonly_mode(false);
395 if(lmode == LOAD_STATE_CURRENT && !current_mode)
396 movb.get_movie().readonly_mode(false);
397 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
398 messages << "ROM Type ";
399 switch(our_rom->rtype) {
400 case ROMTYPE_SNES:
401 messages << "SNES";
402 break;
403 case ROMTYPE_BSX:
404 messages << "BS-X";
405 break;
406 case ROMTYPE_BSXSLOTTED:
407 messages << "BS-X slotted";
408 break;
409 case ROMTYPE_SUFAMITURBO:
410 messages << "Sufami Turbo";
411 break;
412 case ROMTYPE_SGB:
413 messages << "Super Game Boy";
414 break;
415 default:
416 messages << "Unknown";
417 break;
419 messages << " region ";
420 switch(our_rom->region) {
421 case REGION_PAL:
422 messages << "PAL";
423 break;
424 case REGION_NTSC:
425 messages << "NTSC";
426 break;
427 default:
428 messages << "Unknown";
429 break;
431 messages << std::endl;
432 uint64_t mlength = _movie.get_movie_length();
434 mlength += 999999;
435 std::ostringstream x;
436 if(mlength > 3600000000000) {
437 x << mlength / 3600000000000 << ":";
438 mlength %= 3600000000000;
440 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
441 mlength %= 60000000000;
442 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
443 mlength %= 1000000000;
444 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
445 messages << "Rerecords " << _movie.rerecords << " length " << x.str() << " ("
446 << _movie.get_frame_count() << " frames)" << std::endl;
448 if(_movie.gamename != "")
449 messages << "Game Name: " << _movie.gamename << std::endl;
450 for(size_t i = 0; i < _movie.authors.size(); i++)
451 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
452 << std::endl;
455 //Load state
456 bool do_load_state(const std::string& filename, int lmode)
458 std::string filename2 = translate_name_mprefix(filename);
459 uint64_t origtime = get_utime();
460 lua_callback_pre_load(filename2);
461 struct moviefile mfile;
462 try {
463 mfile = moviefile(filename2);
464 } catch(std::bad_alloc& e) {
465 OOM_panic();
466 } catch(std::exception& e) {
467 messages << "Can't read movie/savestate '" << filename2 << "': " << e.what() << std::endl;
468 lua_callback_err_load(filename2);
469 return false;
471 try {
472 do_load_state(mfile, lmode);
473 uint64_t took = get_utime() - origtime;
474 messages << "Loaded '" << filename2 << "' in " << took << " microseconds." << std::endl;
475 lua_callback_post_load(filename2, our_movie.is_savestate);
476 } catch(std::bad_alloc& e) {
477 OOM_panic();
478 } catch(std::exception& e) {
479 messages << "Can't load movie/savestate '" << filename2 << "': " << e.what() << std::endl;
480 lua_callback_err_load(filename2);
481 return false;
483 return true;