Fix frame number reported to Lua in repaint after load
[lsnes.git] / src / core / moviedata.cpp
blob1082e1db0f61d0fff8ef1111ab9f6e256e3375eb
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;
24 extern "C"
26 time_t __wrap_time(time_t* t)
28 time_t v = static_cast<time_t>(our_movie.rtc_second);
29 if(t)
30 *t = v;
31 return v;
35 std::vector<char>& get_host_memory()
37 return our_movie.host_memory;
40 movie& get_movie()
42 return movb.get_movie();
45 namespace
47 numeric_setting savecompression("savecompression", 0, 9, 7);
50 function_ptr_command<> get_gamename("get-gamename", "Get the game name",
51 "Syntax: get-gamename\nPrints the game name\n",
52 []() throw(std::bad_alloc, std::runtime_error) {
53 messages << "Game name is '" << our_movie.gamename << "'" << std::endl;
54 });
56 function_ptr_command<const std::string&> set_gamename("set-gamename", "Set the game name",
57 "Syntax: set-gamename <name>\nSets the game name to <name>\n",
58 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
59 our_movie.gamename = args;
60 messages << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
61 });
63 function_ptr_command<> show_authors("show-authors", "Show the run authors",
64 "Syntax: show-authors\nShows the run authors\n",
65 []() throw(std::bad_alloc, std::runtime_error)
67 size_t idx = 0;
68 for(auto i : our_movie.authors) {
69 messages << (idx++) << ": " << i.first << "|" << i.second << std::endl;
71 messages << "End of authors list" << std::endl;
72 });
74 function_ptr_command<tokensplitter&> add_author("add-author", "Add an author",
75 "Syntax: add-author <fullname>\nSyntax: add-author |<nickname>\n"
76 "Syntax: add-author <fullname>|<nickname>\nAdds a new author\n",
77 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
78 auto g = split_author(t.tail());
79 our_movie.authors.push_back(g);
80 messages << (our_movie.authors.size() - 1) << ": " << g.first << "|" << g.second << std::endl;
81 });
83 function_ptr_command<tokensplitter&> remove_author("remove-author", "Remove an author",
84 "Syntax: remove-author <id>\nRemoves author with ID <id>\n",
85 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
86 uint64_t index = parse_value<uint64_t>(t.tail());
87 if(index >= our_movie.authors.size())
88 throw std::runtime_error("No such author");
89 our_movie.authors.erase(our_movie.authors.begin() + index);
90 });
92 function_ptr_command<tokensplitter&> edit_author("edit-author", "Edit an author",
93 "Syntax: edit-author <authorid> <fullname>\nSyntax: edit-author <authorid> |<nickname>\n"
94 "Syntax: edit-author <authorid> <fullname>|<nickname>\nEdits author name\n",
95 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
96 uint64_t index = parse_value<uint64_t>(t);
97 if(index >= our_movie.authors.size())
98 throw std::runtime_error("No such author");
99 auto g = split_author(t.tail());
100 our_movie.authors[index] = g;
103 function_ptr_command<const std::string&> dump_coresave("dump-coresave", "Dump bsnes core state",
104 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
105 [](const std::string& name) throw(std::bad_alloc, std::runtime_error) {
106 auto x = save_core_state();
107 x.resize(x.size() - 32);
108 std::ofstream y(name.c_str(), std::ios::out | std::ios::binary);
109 y.write(&x[0], x.size());
110 y.close();
111 messages << "Saved core state to " << name << std::endl;
114 void warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot,
115 const std::string& name)
117 if(mhash != slot.sha256) {
118 messages << "WARNING: " << name << " hash mismatch!" << std::endl
119 << "\tMovie: " << mhash << std::endl
120 << "\tOur ROM: " << slot.sha256 << std::endl;
125 std::pair<std::string, std::string> split_author(const std::string& author) throw(std::bad_alloc,
126 std::runtime_error)
128 std::string _author = author;
129 std::string fullname;
130 std::string nickname;
131 size_t split = _author.find_first_of("|");
132 if(split >= _author.length()) {
133 fullname = _author;
134 } else {
135 fullname = _author.substr(0, split);
136 nickname = _author.substr(split + 1);
138 if(fullname == "" && nickname == "")
139 throw std::runtime_error("Bad author name");
140 return std::make_pair(fullname, nickname);
144 //Save state.
145 void do_save_state(const std::string& filename) throw(std::bad_alloc,
146 std::runtime_error)
148 lua_callback_pre_save(filename, true);
149 try {
150 uint64_t origtime = get_utime();
151 our_movie.is_savestate = true;
152 our_movie.sram = save_sram();
153 our_movie.savestate = save_core_state();
154 get_framebuffer().save(our_movie.screenshot);
155 movb.get_movie().save_state(our_movie.projectid, our_movie.save_frame, our_movie.lagged_frames,
156 our_movie.pollcounters);
157 our_movie.input = movb.get_movie().save();
158 our_movie.save(filename, savecompression);
159 uint64_t took = get_utime() - origtime;
160 messages << "Saved state '" << filename << "' in " << took << " microseconds." << std::endl;
161 lua_callback_post_save(filename, true);
162 } catch(std::bad_alloc& e) {
163 throw;
164 } catch(std::exception& e) {
165 messages << "Save failed: " << e.what() << std::endl;
166 lua_callback_err_save(filename);
170 //Save movie.
171 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
173 lua_callback_pre_save(filename, false);
174 try {
175 uint64_t origtime = get_utime();
176 our_movie.is_savestate = false;
177 our_movie.input = movb.get_movie().save();
178 our_movie.save(filename, savecompression);
179 uint64_t took = get_utime() - origtime;
180 messages << "Saved movie '" << filename << "' in " << took << " microseconds." << std::endl;
181 lua_callback_post_save(filename, false);
182 } catch(std::bad_alloc& e) {
183 OOM_panic();
184 } catch(std::exception& e) {
185 messages << "Save failed: " << e.what() << std::endl;
186 lua_callback_err_save(filename);
190 extern time_t random_seed_value;
192 void do_load_beginning() throw(std::bad_alloc, std::runtime_error)
194 SNES::config.random = false;
195 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
197 //Negative return.
198 rrdata::add_internal();
199 try {
200 movb.get_movie().reset_state();
201 random_seed_value = our_movie.movie_rtc_second;
202 our_rom->load();
204 load_sram(our_movie.movie_sram);
205 our_movie.rtc_second = our_movie.movie_rtc_second;
206 our_movie.rtc_subsecond = our_movie.movie_rtc_subsecond;
207 redraw_framebuffer(screen_nosignal);
208 } catch(std::bad_alloc& e) {
209 OOM_panic();
210 } catch(std::exception& e) {
211 system_corrupt = true;
212 redraw_framebuffer(screen_corrupt, true);
213 throw;
217 //Load state from loaded movie file (does not catch errors).
218 void do_load_state(struct moviefile& _movie, int lmode)
220 bool current_mode = movb.get_movie().readonly_mode();
221 if(_movie.force_corrupt)
222 throw std::runtime_error("Movie file invalid");
223 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
224 if(gtype::toromtype(_movie.gametype) != our_rom->rtype) {
225 messages << "_movie.gametype = " << _movie.gametype << std::endl;
226 messages << "gtype::toromtype(_movie.gametype) = " << gtype::toromtype(_movie.gametype) << std::endl;
227 messages << "our_rom->rtype = " << our_rom->rtype << std::endl;
228 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
230 if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO)
231 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
233 if(_movie.coreversion != bsnes_core_version) {
234 if(will_load_state) {
235 std::ostringstream x;
236 x << "ERROR: Emulator core version mismatch!" << std::endl
237 << "\tThis version: " << bsnes_core_version << std::endl
238 << "\tFile is from: " << _movie.coreversion << std::endl;
239 throw std::runtime_error(x.str());
240 } else
241 messages << "WARNING: Emulator core version mismatch!" << std::endl
242 << "\tThis version: " << bsnes_core_version << std::endl
243 << "\tFile is from: " << _movie.coreversion << std::endl;
245 warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1");
246 warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1");
247 warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2");
248 warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2");
249 warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3");
250 warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3");
252 SNES::config.random = false;
253 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
255 movie newmovie;
256 if(lmode == LOAD_STATE_PRESERVE)
257 newmovie = movb.get_movie();
258 else
259 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
261 if(will_load_state)
262 newmovie.restore_state(_movie.save_frame, _movie.lagged_frames, _movie.pollcounters, true,
263 (lmode == LOAD_STATE_PRESERVE) ? &_movie.input : NULL, _movie.projectid);
265 //Negative return.
266 rrdata::read_base(_movie.projectid);
267 rrdata::read(_movie.c_rrdata);
268 rrdata::add_internal();
269 try {
270 our_rom->region = gtype::toromregion(_movie.gametype);
271 random_seed_value = _movie.rtc_second;
272 our_rom->load();
274 if(will_load_state) {
275 //Load the savestate and movie state.
276 controls.set_port(0, _movie.port1, false);
277 controls.set_port(1, _movie.port2, false);
278 load_core_state(_movie.savestate);
279 } else {
280 load_sram(_movie.movie_sram);
281 controls.set_port(0, _movie.port1, true);
282 controls.set_port(1, _movie.port2, true);
283 _movie.rtc_second = _movie.movie_rtc_second;
284 _movie.rtc_subsecond = _movie.movie_rtc_subsecond;
286 } catch(std::bad_alloc& e) {
287 OOM_panic();
288 } catch(std::exception& e) {
289 system_corrupt = true;
290 redraw_framebuffer(screen_corrupt, true);
291 throw;
294 //Okay, copy the movie data.
295 our_movie = _movie;
296 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
297 our_movie.is_savestate = false;
298 our_movie.host_memory.clear();
300 movb.get_movie() = newmovie;
301 //Paint the screen.
303 lcscreen tmp;
304 if(will_load_state) {
305 tmp.load(_movie.screenshot);
306 redraw_framebuffer(tmp);
307 } else
308 redraw_framebuffer(screen_nosignal);
310 //Activate RW mode if needed.
311 if(lmode == LOAD_STATE_RW)
312 movb.get_movie().readonly_mode(false);
313 if(lmode == LOAD_STATE_DEFAULT && movb.get_movie().get_frame_count() <= movb.get_movie().get_current_frame())
314 movb.get_movie().readonly_mode(false);
315 if(lmode == LOAD_STATE_CURRENT && !current_mode)
316 movb.get_movie().readonly_mode(false);
317 information_dispatch::do_mode_change(movb.get_movie().readonly_mode());
318 messages << "ROM Type ";
319 switch(our_rom->rtype) {
320 case ROMTYPE_SNES:
321 messages << "SNES";
322 break;
323 case ROMTYPE_BSX:
324 messages << "BS-X";
325 break;
326 case ROMTYPE_BSXSLOTTED:
327 messages << "BS-X slotted";
328 break;
329 case ROMTYPE_SUFAMITURBO:
330 messages << "Sufami Turbo";
331 break;
332 case ROMTYPE_SGB:
333 messages << "Super Game Boy";
334 break;
335 default:
336 messages << "Unknown";
337 break;
339 messages << " region ";
340 switch(our_rom->region) {
341 case REGION_PAL:
342 messages << "PAL";
343 break;
344 case REGION_NTSC:
345 messages << "NTSC";
346 break;
347 default:
348 messages << "Unknown";
349 break;
351 messages << std::endl;
352 uint64_t mlength = _movie.get_movie_length();
354 mlength += 999999;
355 std::ostringstream x;
356 if(mlength > 3600000000000) {
357 x << mlength / 3600000000000 << ":";
358 mlength %= 3600000000000;
360 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
361 mlength %= 60000000000;
362 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
363 mlength %= 1000000000;
364 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
365 messages << "Rerecords " << _movie.rerecords << " length " << x.str() << " ("
366 << _movie.get_frame_count() << " frames)" << std::endl;
368 if(_movie.gamename != "")
369 messages << "Game Name: " << _movie.gamename << std::endl;
370 for(size_t i = 0; i < _movie.authors.size(); i++)
371 messages << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
372 << std::endl;
375 //Load state
376 bool do_load_state(const std::string& filename, int lmode)
378 uint64_t origtime = get_utime();
379 lua_callback_pre_load(filename);
380 struct moviefile mfile;
381 try {
382 mfile = moviefile(filename);
383 } catch(std::bad_alloc& e) {
384 OOM_panic();
385 } catch(std::exception& e) {
386 messages << "Can't read movie/savestate '" << filename << "': " << e.what() << std::endl;
387 lua_callback_err_load(filename);
388 return false;
390 try {
391 do_load_state(mfile, lmode);
392 uint64_t took = get_utime() - origtime;
393 messages << "Loaded '" << filename << "' in " << took << " microseconds." << std::endl;
394 lua_callback_post_load(filename, our_movie.is_savestate);
395 } catch(std::bad_alloc& e) {
396 OOM_panic();
397 } catch(std::exception& e) {
398 messages << "Can't load movie/savestate '" << filename << "': " << e.what() << std::endl;
399 lua_callback_err_load(filename);
400 return false;
402 return true;