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"
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"
20 #include <boost/filesystem.hpp>
22 #ifdef BOOST_FILESYSTEM3
23 namespace boost_fs
= boost::filesystem3
;
25 namespace boost_fs
= boost::filesystem
;
28 struct moviefile our_movie
;
29 struct loaded_rom
* our_rom
;
32 std::string last_save
;
33 void update_movie_state();
37 time_t __wrap_time(time_t* t
)
39 time_t v
= static_cast<time_t>(our_movie
.rtc_second
);
46 std::vector
<char>& get_host_memory()
48 return our_movie
.host_memory
;
53 return movb
.get_movie();
58 setting_var
<setting_var_model_int
<0, 9>> savecompression(lsnes_vset
, "savecompression", "Movie‣Compression",
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
;
66 std::string
get_mprefix()
68 umutex_class
h(mprefix_lock
);
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());
83 messages
<< "Saved core state to " << name
<< std::endl
;
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
)
92 messages
<< "WARNING: " << name
<< " hash mismatch!" << std::endl
93 << "\tMovie: " << mhash
<< std::endl
94 << "\tOur ROM: " << slot
.sha_256
<< std::endl
;
97 messages
<< "ERROR: " << name
<< " hash mismatch!" << std::endl
98 << "\tMovie: " << mhash
<< std::endl
99 << "\tOur ROM: " << slot
.sha_256
<< std::endl
;
104 void set_mprefix(const std::string
& pfx
)
107 umutex_class
h(mprefix_lock
);
108 mprefix_valid
= (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
);
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
);
145 std::string
translate_name_mprefix(std::string original
)
147 auto p
= project_get();
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() + "/";
156 return pprf
+ get_mprefix() + original
.substr(prefixloc
+ 10);
158 return original
.substr(0, prefixloc
) + get_mprefix() + original
.substr(prefixloc
+ 10);
163 std::pair
<std::string
, std::string
> split_author(const std::string
& author
) throw(std::bad_alloc
,
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()) {
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
)
185 return boost_fs::absolute(boost_fs::path(path
)).string();
192 void do_save_state(const std::string
& filename
) throw(std::bad_alloc
,
195 if(!our_movie
.gametype
) {
196 messages
<< "Can't save movie without a ROM" << std::endl
;
199 std::string filename2
= translate_name_mprefix(filename
);
200 lua_callback_pre_save(filename2
, true);
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();
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
) {
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();
234 p
->last_save
= last_save
;
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
;
246 std::string filename2
= translate_name_mprefix(filename
);
247 lua_callback_pre_save(filename2
, false);
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();
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
) {
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();
271 p
->last_save
= last_save
;
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
;
288 //Force unlazy rrdata.
289 rrdata::read_base(our_movie
.projectid
, false);
290 rrdata::add_internal();
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.
298 our_movie
.input
.clear(portset
);
299 movb
.get_movie().load(our_movie
.rerecords
, our_movie
.projectid
, our_movie
.input
);
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
);
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
) {
322 } catch(std::exception
& e
) {
323 system_corrupt
= true;
324 redraw_framebuffer(screen_corrupt
, true);
327 information_dispatch::do_mode_change(movb
.get_movie().readonly_mode());
328 our_movie
.is_savestate
= false;
329 our_movie
.host_memory
.clear();
331 messages
<< "ROM reloaded." << std::endl
;
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());
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
;
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
);
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
;
375 if(lmode
== LOAD_STATE_PRESERVE
)
376 newmovie
= movb
.get_movie();
378 newmovie
.load(_movie
.rerecords
, _movie
.projectid
, _movie
.input
);
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
);
388 rrdata::read_base(_movie
.projectid
, _movie
.lazy_project_create
);
389 rrdata::read(_movie
.c_rrdata
);
390 rrdata::add_internal();
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
);
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
) {
415 } catch(std::exception
& e
) {
416 system_corrupt
= true;
417 redraw_framebuffer(screen_corrupt
, true);
421 //Okay, copy the movie data.
422 if(lmode
!= LOAD_STATE_PRESERVE
)
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();
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);
454 if(will_load_state
) {
455 tmp
.load(_movie
.screenshot
);
456 redraw_framebuffer(tmp
);
458 redraw_framebuffer(our_rom
->rtype
->draw_cover());
460 information_dispatch::do_mode_change(movb
.get_movie().readonly_mode());
462 messages
<< "ROM Type " << our_rom
->rtype
->get_hname() << " region " << our_rom
->region
->get_hname()
464 uint64_t mlength
= _movie
.get_movie_length();
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
<< ")"
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
;
498 mfile
= moviefile(filename2
, *our_rom
->rtype
);
499 } catch(std::bad_alloc
& e
) {
501 } catch(std::exception
& e
) {
502 messages
<< "Can't read movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
503 lua_callback_err_load(filename2
);
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
) {
513 } catch(std::exception
& e
) {
514 messages
<< "Can't load movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
515 lua_callback_err_load(filename2
);
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);