Remember last saved file when populating file to load
[lsnes.git] / src / core / moviedata.cpp
blobd0f523584e009084b5bd52f87ad48c43857b25ef
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 struct moviefile our_movie;
20 struct loaded_rom* our_rom;
21 bool system_corrupt;
22 movie_logic movb;
23 std::string last_save;
25 extern "C"
27 time_t __wrap_time(time_t* t)
29 time_t v = static_cast<time_t>(our_movie.rtc_second);
30 if(t)
31 *t = v;
32 return v;
36 std::vector<char>& get_host_memory()
38 return our_movie.host_memory;
41 movie& get_movie()
43 return movb.get_movie();
46 namespace
48 numeric_setting savecompression("savecompression", 0, 9, 7);
50 class projectprefix_setting : public setting
52 std::string prefix;
53 bool _set;
54 public:
55 projectprefix_setting() throw(std::bad_alloc)
56 : setting("$project")
58 _set = false;
60 void blank() throw(std::bad_alloc, std::runtime_error)
62 _set = false;
64 bool is_set() throw()
66 return _set;
68 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
70 prefix = value;
71 _set = true;
73 std::string get() throw(std::bad_alloc)
75 return prefix;
77 operator std::string() throw()
79 if(_set)
80 return prefix + "-";
81 else
82 return "movieslot";
84 } mprefix;
86 function_ptr_command<> get_gamename("get-gamename", "Get the game name",
87 "Syntax: get-gamename\nPrints the game name\n",
88 []() throw(std::bad_alloc, std::runtime_error) {
89 messages << "Game name is '" << our_movie.gamename << "'" << std::endl;
90 });
92 function_ptr_command<const std::string&> set_gamename("set-gamename", "Set the game name",
93 "Syntax: set-gamename <name>\nSets the game name to <name>\n",
94 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
95 our_movie.gamename = args;
96 messages << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
97 });
99 function_ptr_command<> show_authors("show-authors", "Show the run authors",
100 "Syntax: show-authors\nShows the run authors\n",
101 []() throw(std::bad_alloc, std::runtime_error)
103 size_t idx = 0;
104 for(auto i : our_movie.authors) {
105 messages << (idx++) << ": " << i.first << "|" << i.second << std::endl;
107 messages << "End of authors list" << std::endl;
110 function_ptr_command<tokensplitter&> add_author("add-author", "Add an author",
111 "Syntax: add-author <fullname>\nSyntax: add-author |<nickname>\n"
112 "Syntax: add-author <fullname>|<nickname>\nAdds a new author\n",
113 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
114 auto g = split_author(t.tail());
115 our_movie.authors.push_back(g);
116 messages << (our_movie.authors.size() - 1) << ": " << g.first << "|" << g.second << std::endl;
119 function_ptr_command<tokensplitter&> remove_author("remove-author", "Remove an author",
120 "Syntax: remove-author <id>\nRemoves author with ID <id>\n",
121 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
122 uint64_t index = parse_value<uint64_t>(t.tail());
123 if(index >= our_movie.authors.size())
124 throw std::runtime_error("No such author");
125 our_movie.authors.erase(our_movie.authors.begin() + index);
128 function_ptr_command<tokensplitter&> edit_author("edit-author", "Edit an author",
129 "Syntax: edit-author <authorid> <fullname>\nSyntax: edit-author <authorid> |<nickname>\n"
130 "Syntax: edit-author <authorid> <fullname>|<nickname>\nEdits author name\n",
131 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
132 uint64_t index = parse_value<uint64_t>(t);
133 if(index >= our_movie.authors.size())
134 throw std::runtime_error("No such author");
135 auto g = split_author(t.tail());
136 our_movie.authors[index] = g;
139 function_ptr_command<const std::string&> dump_coresave("dump-coresave", "Dump bsnes core state",
140 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
141 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
142 auto x = save_core_state();
143 x.resize(x.size() - 32);
144 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
145 y.write(&x[0], x.size());
146 y.close();
147 messages << "Saved core state to " << name << std::endl;
150 void warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
151 const std::string& name)
153 if(mhash != slot.sha256) {
154 messages << "WARNING: " << name << " hash mismatch!" << std::endl
155 << "\tMovie: " << mhash << std::endl
156 << "\tOur ROM: " << slot.sha256 << std::endl;
161 std::string translate_name_mprefix(std::string original)
163 size_t prefixloc = original.find("${project}");
164 if(prefixloc < original.length())
165 return original.substr(0, prefixloc) + static_cast<std::string>(mprefix) +
166 original.substr(prefixloc + 10);
167 else
168 return original;
171 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
172 std::runtime_error)
174 std::string _author = author;
175 std::string fullname;
176 std::string nickname;
177 size_t split = _author.find_first_of("|");
178 if(split >= _author.length()) {
179 fullname = _author;
180 } else {
181 fullname = _author.substr(0, split);
182 nickname = _author.substr(split + 1);
184 if(fullname == "" && nickname == "")
185 throw std::runtime_error("Bad author name");
186 return std::make_pair(fullname, nickname);
190 //Save state.
191 void do_save_state(const std::string& filename) throw(std::bad_alloc,
192 std::runtime_error)
194 std::string filename2 = translate_name_mprefix(filename);
195 lua_callback_pre_save(filename2, true);
196 try {
197 uint64_t origtime = get_utime();
198 our_movie.is_savestate = true;
199 our_movie.sram = save_sram();
200 our_movie.savestate = save_core_state();
201 get_framebuffer().save(our_movie.screenshot);
202 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
203 our_movie.pollcounters);
204 our_movie.input = movb.get_movie().save();
205 our_movie.save(filename2, savecompression);
206 uint64_t took = get_utime() - origtime;
207 messages << "Saved state '" << filename2 << "' in " << took << " microseconds." << std::endl;
208 lua_callback_post_save(filename2, true);
209 } catch(std::bad_alloc& e) {
210 throw;
211 } catch(std::exception& e) {
212 messages << "Save failed: " << e.what() << std::endl;
213 lua_callback_err_save(filename2);
215 last_save = filename2;
218 //Save movie.
219 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
221 std::string filename2 = translate_name_mprefix(filename);
222 lua_callback_pre_save(filename2, false);
223 try {
224 uint64_t origtime = get_utime();
225 our_movie.is_savestate = false;
226 our_movie.input = movb.get_movie().save();
227 our_movie.save(filename2, savecompression);
228 uint64_t took = get_utime() - origtime;
229 messages << "Saved movie '" << filename2 << "' in " << took << " microseconds." << std::endl;
230 lua_callback_post_save(filename2, false);
231 } catch(std::bad_alloc& e) {
232 OOM_panic();
233 } catch(std::exception& e) {
234 messages << "Save failed: " << e.what() << std::endl;
235 lua_callback_err_save(filename2);
237 last_save = filename2;
240 extern time_t random_seed_value;
242 void do_load_beginning() throw(std::bad_alloc, std::runtime_error)
244 SNES::config.random = false;
245 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
247 //Negative return.
248 rrdata::add_internal();
249 try {
250 movb.get_movie().reset_state();
251 random_seed_value = our_movie.movie_rtc_second;
252 our_rom->load();
254 load_sram(our_movie.movie_sram);
255 our_movie.rtc_second = our_movie.movie_rtc_second;
256 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
257 redraw_framebuffer(screen_nosignal);
258 } catch(std::bad_alloc& e) {
259 OOM_panic();
260 } catch(std::exception& e) {
261 system_corrupt = true;
262 redraw_framebuffer(screen_corrupt, true);
263 throw;
265 our_movie.is_savestate = false;
266 our_movie.host_memory.clear();
267 messages << "Movie rewound to beginning." << std::endl;
270 //Load state from loaded movie file (does not catch errors).
271 void do_load_state(struct moviefile& _movie, int lmode)
273 bool current_mode = movb.get_movie().readonly_mode();
274 if(_movie.force_corrupt)
275 throw std::runtime_error("Movie file invalid");
276 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
277 if(gtype::toromtype(_movie.gametype) != our_rom->rtype) {
278 messages << "_movie.gametype = " << _movie.gametype << std::endl;
279 messages << "gtype::toromtype(_movie.gametype) = " << gtype::toromtype(_movie.gametype) << std::endl;
280 messages << "our_rom->rtype = " << our_rom->rtype << std::endl;
281 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
283 if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO)
284 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
286 if(_movie.coreversion != bsnes_core_version) {
287 if(will_load_state) {
288 std::ostringstream x;
289 x << "ERROR: Emulator core version mismatch!" << std::endl
290 << "\tThis version: " << bsnes_core_version << std::endl
291 << "\tFile is from: " << _movie.coreversion << std::endl;
292 throw std::runtime_error(x.str());
293 } else
294 messages << "WARNING: Emulator core version mismatch!" << std::endl
295 << "\tThis version: " << bsnes_core_version << std::endl
296 << "\tFile is from: " << _movie.coreversion << std::endl;
298 warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1");
299 warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1");
300 warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2");
301 warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2");
302 warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3");
303 warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3");
305 SNES::config.random = false;
306 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
308 movie newmovie;
309 if(lmode == LOAD_STATE_PRESERVE)
310 newmovie = movb.get_movie();
311 else
312 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
314 if(will_load_state)
315 newmovie.restore_state(_movie.save_frame, _movie.lagged_frames, _movie.pollcounters, true,
316 (lmode == LOAD_STATE_PRESERVE) ? &_movie.input : NULL, _movie.projectid);
318 //Negative return.
319 rrdata::read_base(_movie.projectid);
320 rrdata::read(_movie.c_rrdata);
321 rrdata::add_internal();
322 try {
323 our_rom->region = gtype::toromregion(_movie.gametype);
324 random_seed_value = _movie.rtc_second;
325 our_rom->load();
327 if(will_load_state) {
328 //Load the savestate and movie state.
329 controls.set_port(0, _movie.port1, false);
330 controls.set_port(1, _movie.port2, false);
331 load_core_state(_movie.savestate);
332 } else {
333 load_sram(_movie.movie_sram);
334 controls.set_port(0, _movie.port1, true);
335 controls.set_port(1, _movie.port2, true);
336 _movie.rtc_second = _movie.movie_rtc_second;
337 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
339 } catch(std::bad_alloc& e) {
340 OOM_panic();
341 } catch(std::exception& e) {
342 system_corrupt = true;
343 redraw_framebuffer(screen_corrupt, true);
344 throw;
347 //Okay, copy the movie data.
348 our_movie = _movie;
349 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
350 our_movie.is_savestate = false;
351 our_movie.host_memory.clear();
353 movb.get_movie() = newmovie;
354 //Paint the screen.
356 lcscreen tmp;
357 if(will_load_state) {
358 tmp.load(_movie.screenshot);
359 redraw_framebuffer(tmp);
360 } else
361 redraw_framebuffer(screen_nosignal);
363 //Activate RW mode if needed.
364 if(lmode == LOAD_STATE_RW)
365 movb.get_movie().readonly_mode(false);
366 if(lmode == LOAD_STATE_DEFAULT && movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
367 movb.get_movie().readonly_mode(false);
368 if(lmode == LOAD_STATE_CURRENT && !current_mode)
369 movb.get_movie().readonly_mode(false);
370 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
371 messages << "ROM Type ";
372 switch(our_rom->rtype) {
373 case ROMTYPE_SNES:
374 messages << "SNES";
375 break;
376 case ROMTYPE_BSX:
377 messages << "BS-X";
378 break;
379 case ROMTYPE_BSXSLOTTED:
380 messages << "BS-X slotted";
381 break;
382 case ROMTYPE_SUFAMITURBO:
383 messages << "Sufami Turbo";
384 break;
385 case ROMTYPE_SGB:
386 messages << "Super Game Boy";
387 break;
388 default:
389 messages << "Unknown";
390 break;
392 messages << " region ";
393 switch(our_rom->region) {
394 case REGION_PAL:
395 messages << "PAL";
396 break;
397 case REGION_NTSC:
398 messages << "NTSC";
399 break;
400 default:
401 messages << "Unknown";
402 break;
404 messages << std::endl;
405 uint64_t mlength = _movie.get_movie_length();
407 mlength += 999999;
408 std::ostringstream x;
409 if(mlength > 3600000000000) {
410 x << mlength / 3600000000000 << ":";
411 mlength %= 3600000000000;
413 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
414 mlength %= 60000000000;
415 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
416 mlength %= 1000000000;
417 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
418 messages << "Rerecords " << _movie.rerecords << " length " << x.str() << " ("
419 << _movie.get_frame_count() << " frames)" << std::endl;
421 if(_movie.gamename != "")
422 messages << "Game Name: " << _movie.gamename << std::endl;
423 for(size_t i = 0; i < _movie.authors.size(); i++)
424 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
425 << std::endl;
428 //Load state
429 bool do_load_state(const std::string& filename, int lmode)
431 std::string filename2 = translate_name_mprefix(filename);
432 uint64_t origtime = get_utime();
433 lua_callback_pre_load(filename2);
434 struct moviefile mfile;
435 try {
436 mfile = moviefile(filename2);
437 } catch(std::bad_alloc& e) {
438 OOM_panic();
439 } catch(std::exception& e) {
440 messages << "Can't read movie/savestate '" << filename2 << "': " << e.what() << std::endl;
441 lua_callback_err_load(filename2);
442 return false;
444 try {
445 do_load_state(mfile, lmode);
446 uint64_t took = get_utime() - origtime;
447 messages << "Loaded '" << filename2 << "' in " << took << " microseconds." << std::endl;
448 lua_callback_post_load(filename2, our_movie.is_savestate);
449 } catch(std::bad_alloc& e) {
450 OOM_panic();
451 } catch(std::exception& e) {
452 messages << "Can't load movie/savestate '" << filename2 << "': " << e.what() << std::endl;
453 lua_callback_err_load(filename2);
454 return false;
456 return true;