2 #include "core/emucore.hpp"
4 #include "core/command.hpp"
5 #include "core/controller.hpp"
6 #include "core/dispatch.hpp"
7 #include "core/framebuffer.hpp"
8 #include "core/framerate.hpp"
10 #include "core/misc.hpp"
11 #include "core/moviedata.hpp"
12 #include "core/rrdata.hpp"
13 #include "core/settings.hpp"
14 #include "library/string.hpp"
19 #include <boost/filesystem.hpp>
21 #ifdef BOOST_FILESYSTEM3
22 namespace boost_fs
= boost::filesystem3
;
24 namespace boost_fs
= boost::filesystem
;
27 struct moviefile our_movie
;
28 struct loaded_rom
* our_rom
;
31 std::string last_save
;
35 time_t __wrap_time(time_t* t
)
37 time_t v
= static_cast<time_t>(our_movie
.rtc_second
);
44 std::vector
<char>& get_host_memory()
46 return our_movie
.host_memory
;
51 return movb
.get_movie();
56 numeric_setting
savecompression("savecompression", 0, 9, 7);
57 boolean_setting
readonly_load_preserves("preserve_on_readonly_load", true);
59 path_setting
slotpath_setting("slotpath");
61 class projectprefix_setting
: public setting
66 projectprefix_setting() throw(std::bad_alloc
)
71 bool blank(bool really
) throw(std::bad_alloc
, std::runtime_error
)
82 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
87 std::string
get() throw(std::bad_alloc
)
91 operator std::string() throw()
93 lock_holder
lck(this);
101 function_ptr_command
<> get_gamename("get-gamename", "Get the game name",
102 "Syntax: get-gamename\nPrints the game name\n",
103 []() throw(std::bad_alloc
, std::runtime_error
) {
104 messages
<< "Game name is '" << our_movie
.gamename
<< "'" << std::endl
;
107 function_ptr_command
<const std::string
&> set_gamename("set-gamename", "Set the game name",
108 "Syntax: set-gamename <name>\nSets the game name to <name>\n",
109 [](const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
) {
110 our_movie
.gamename
= args
;
111 messages
<< "Game name changed to '" << our_movie
.gamename
<< "'" << std::endl
;
114 function_ptr_command
<> show_authors("show-authors", "Show the run authors",
115 "Syntax: show-authors\nShows the run authors\n",
116 []() throw(std::bad_alloc
, std::runtime_error
)
119 for(auto i
: our_movie
.authors
) {
120 messages
<< (idx
++) << ": " << i
.first
<< "|" << i
.second
<< std::endl
;
122 messages
<< "End of authors list" << std::endl
;
125 function_ptr_command
<const std::string
&> add_author("add-author", "Add an author",
126 "Syntax: add-author <fullname>\nSyntax: add-author |<nickname>\n"
127 "Syntax: add-author <fullname>|<nickname>\nAdds a new author\n",
128 [](const std::string
& t
) throw(std::bad_alloc
, std::runtime_error
) {
129 auto g
= split_author(t
);
130 our_movie
.authors
.push_back(g
);
131 messages
<< (our_movie
.authors
.size() - 1) << ": " << g
.first
<< "|" << g
.second
<< std::endl
;
134 function_ptr_command
<const std::string
&> remove_author("remove-author", "Remove an author",
135 "Syntax: remove-author <id>\nRemoves author with ID <id>\n",
136 [](const std::string
& t
) throw(std::bad_alloc
, std::runtime_error
) {
137 uint64_t index
= parse_value
<uint64_t>(t
);
138 if(index
>= our_movie
.authors
.size())
139 throw std::runtime_error("No such author");
140 our_movie
.authors
.erase(our_movie
.authors
.begin() + index
);
143 function_ptr_command
<const std::string
&> edit_author("edit-author", "Edit an author",
144 "Syntax: edit-author <authorid> <fullname>\nSyntax: edit-author <authorid> |<nickname>\n"
145 "Syntax: edit-author <authorid> <fullname>|<nickname>\nEdits author name\n",
146 [](const std::string
& t
) throw(std::bad_alloc
, std::runtime_error
) {
147 auto r
= regex("([^ \t]+)[ \t]+(|[^ \t].*)", t
, "Index and author required.");
148 uint64_t index
= parse_value
<uint64_t>(r
[1]);
149 if(index
>= our_movie
.authors
.size())
150 throw std::runtime_error("No such author");
151 auto g
= split_author(r
[2]);
152 our_movie
.authors
[index
] = g
;
155 function_ptr_command
<const std::string
&> dump_coresave("dump-coresave", "Dump bsnes core state",
156 "Syntax: dump-coresave <name>\nDumps core save to <name>\n",
157 [](const std::string
& name
) throw(std::bad_alloc
, std::runtime_error
) {
158 auto x
= save_core_state();
159 x
.resize(x
.size() - 32);
160 std::ofstream
y(name
.c_str(), std::ios::out
| std::ios::binary
);
161 y
.write(&x
[0], x
.size());
163 messages
<< "Saved core state to " << name
<< std::endl
;
166 bool warn_hash_mismatch(const std::string
& mhash
, const loaded_slot
& slot
,
167 const std::string
& name
, bool fatal
)
169 if(mhash
== slot
.sha256
)
172 messages
<< "WARNING: " << name
<< " hash mismatch!" << std::endl
173 << "\tMovie: " << mhash
<< std::endl
174 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
177 messages
<< "ERROR: " << name
<< " hash mismatch!" << std::endl
178 << "\tMovie: " << mhash
<< std::endl
179 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
185 std::string
translate_name_mprefix(std::string original
, bool forio
)
187 size_t prefixloc
= original
.find("${project}");
188 if(prefixloc
< original
.length()) {
189 std::string pprf
= forio
? (slotpath_setting
.get() + "/") : std::string("");
191 return pprf
+ static_cast<std::string
>(mprefix
) + original
.substr(prefixloc
+ 10);
193 return original
.substr(0, prefixloc
) + static_cast<std::string
>(mprefix
) +
194 original
.substr(prefixloc
+ 10);
199 std::pair
<std::string
, std::string
> split_author(const std::string
& author
) throw(std::bad_alloc
,
202 std::string _author
= author
;
203 std::string fullname
;
204 std::string nickname
;
205 size_t split
= _author
.find_first_of("|");
206 if(split
>= _author
.length()) {
209 fullname
= _author
.substr(0, split
);
210 nickname
= _author
.substr(split
+ 1);
212 if(fullname
== "" && nickname
== "")
213 throw std::runtime_error("Bad author name");
214 return std::make_pair(fullname
, nickname
);
219 void do_save_state(const std::string
& filename
) throw(std::bad_alloc
,
222 if(!our_movie
.gametype
) {
223 messages
<< "Can't save movie without a ROM" << std::endl
;
226 std::string filename2
= translate_name_mprefix(filename
, true);
227 lua_callback_pre_save(filename2
, true);
229 uint64_t origtime
= get_utime();
231 our_movie
.prefix
= sanitize_prefix(mprefix
.prefix
);
232 our_movie
.is_savestate
= true;
233 our_movie
.sram
= save_sram();
234 for(size_t i
= 0; i
< sizeof(our_rom
->romimg
)/sizeof(our_rom
->romimg
[0]); i
++) {
235 our_movie
.romimg_sha256
[i
] = our_rom
->romimg
[i
].sha256
;
236 our_movie
.romxml_sha256
[i
] = our_rom
->romxml
[i
].sha256
;
238 our_movie
.savestate
= save_core_state();
239 get_framebuffer().save(our_movie
.screenshot
);
240 movb
.get_movie().save_state(our_movie
.projectid
, our_movie
.save_frame
, our_movie
.lagged_frames
,
241 our_movie
.pollcounters
);
242 our_movie
.input
= movb
.get_movie().save();
243 our_movie
.save(filename2
, savecompression
);
244 uint64_t took
= get_utime() - origtime
;
245 messages
<< "Saved state '" << filename2
<< "' in " << took
<< " microseconds." << std::endl
;
246 lua_callback_post_save(filename2
, true);
247 } catch(std::bad_alloc
& e
) {
249 } catch(std::exception
& e
) {
250 messages
<< "Save failed: " << e
.what() << std::endl
;
251 lua_callback_err_save(filename2
);
253 last_save
= boost_fs::absolute(boost_fs::path(filename2
)).string();
257 void do_save_movie(const std::string
& filename
) throw(std::bad_alloc
, std::runtime_error
)
259 if(!our_movie
.gametype
) {
260 messages
<< "Can't save movie without a ROM" << std::endl
;
263 std::string filename2
= translate_name_mprefix(filename
, true);
264 lua_callback_pre_save(filename2
, false);
266 uint64_t origtime
= get_utime();
268 our_movie
.prefix
= sanitize_prefix(static_cast<std::string
>(mprefix
.prefix
));
269 our_movie
.is_savestate
= false;
270 our_movie
.input
= movb
.get_movie().save();
271 our_movie
.save(filename2
, savecompression
);
272 uint64_t took
= get_utime() - origtime
;
273 messages
<< "Saved movie '" << filename2
<< "' in " << took
<< " microseconds." << std::endl
;
274 lua_callback_post_save(filename2
, false);
275 } catch(std::bad_alloc
& e
) {
277 } catch(std::exception
& e
) {
278 messages
<< "Save failed: " << e
.what() << std::endl
;
279 lua_callback_err_save(filename2
);
281 last_save
= boost_fs::absolute(boost_fs::path(filename2
)).string();
284 extern time_t random_seed_value
;
286 void do_load_beginning(bool reload
) throw(std::bad_alloc
, std::runtime_error
)
288 if(!our_movie
.gametype
&& !reload
) {
289 messages
<< "Can't load movie without a ROM" << std::endl
;
292 set_preload_settings();
296 //Force unlazy rrdata.
297 rrdata::read_base(our_movie
.projectid
, false);
298 rrdata::add_internal();
301 bool ro
= movb
.get_movie().readonly_mode();
302 movb
.get_movie().reset_state();
303 random_seed_value
= our_movie
.movie_rtc_second
;
304 our_rom
->load(our_movie
.movie_rtc_second
, our_movie
.movie_rtc_subsecond
);
306 our_movie
.gametype
= &our_rom
->rtype
->combine_region(*our_rom
->region
);
308 our_movie
.gametype
= NULL
;
310 movb
.get_movie().readonly_mode(ro
);
312 load_sram(our_movie
.movie_sram
);
313 our_movie
.rtc_second
= our_movie
.movie_rtc_second
;
314 our_movie
.rtc_subsecond
= our_movie
.movie_rtc_subsecond
;
315 if(!our_movie
.anchor_savestate
.empty())
316 load_core_state(our_movie
.anchor_savestate
);
317 redraw_framebuffer(screen_nosignal
);
318 lua_callback_do_rewind();
319 } catch(std::bad_alloc
& e
) {
321 } catch(std::exception
& e
) {
322 system_corrupt
= true;
323 redraw_framebuffer(screen_corrupt
, true);
326 information_dispatch::do_mode_change(movb
.get_movie().readonly_mode());
327 our_movie
.is_savestate
= false;
328 our_movie
.host_memory
.clear();
330 messages
<< "ROM reloaded." << std::endl
;
332 messages
<< "Movie rewound to beginning." << std::endl
;
335 //Load state from loaded movie file (does not catch errors).
336 void do_load_state(struct moviefile
& _movie
, int lmode
)
338 bool current_mode
= movb
.get_movie().readonly_mode();
339 if(_movie
.force_corrupt
)
340 throw std::runtime_error("Movie file invalid");
341 bool will_load_state
= _movie
.is_savestate
&& lmode
!= LOAD_STATE_MOVIE
;
342 if(our_rom
->rtype
&& &(_movie
.gametype
->get_type()) != our_rom
->rtype
)
343 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
344 if(our_rom
->orig_region
&& !our_rom
->orig_region
->compatible_with(_movie
.gametype
->get_region()))
345 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
347 if(our_rom
->rtype
&& _movie
.coreversion
!= bsnes_core_version
) {
348 if(will_load_state
) {
349 std::ostringstream x
;
350 x
<< "ERROR: Emulator core version mismatch!" << std::endl
351 << "\tThis version: " << bsnes_core_version
<< std::endl
352 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
353 throw std::runtime_error(x
.str());
355 messages
<< "WARNING: Emulator core version mismatch!" << std::endl
356 << "\tThis version: " << bsnes_core_version
<< std::endl
357 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
360 for(size_t i
= 0; i
< sizeof(our_rom
->romimg
)/sizeof(our_rom
->romimg
[0]); i
++) {
361 rom_ok
= rom_ok
& warn_hash_mismatch(_movie
.romimg_sha256
[i
], our_rom
->romimg
[i
],
362 (stringfmt() << "ROM #" << (i
+ 1)).str(), will_load_state
);
363 rom_ok
= rom_ok
& warn_hash_mismatch(_movie
.romxml_sha256
[i
], our_rom
->romxml
[i
],
364 (stringfmt() << "XML #" << (i
+ 1)).str(), will_load_state
);
367 throw std::runtime_error("Incorrect ROM");
369 set_preload_settings();
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
);
385 rrdata::read_base(_movie
.projectid
, _movie
.lazy_project_create
);
386 rrdata::read(_movie
.c_rrdata
);
387 rrdata::add_internal();
389 our_rom
->region
= _movie
.gametype
? &(_movie
.gametype
->get_region()) : NULL
;
390 random_seed_value
= _movie
.movie_rtc_second
;
391 our_rom
->load(_movie
.movie_rtc_second
, _movie
.movie_rtc_subsecond
);
393 if(will_load_state
) {
394 //Load the savestate and movie state.
395 //Set the core ports in order to avoid port state being reinitialized when loading.
396 controls
.set_port(0, *_movie
.port1
, true);
397 controls
.set_port(1, *_movie
.port2
, true);
398 load_core_state(_movie
.savestate
);
400 load_sram(_movie
.movie_sram
);
401 controls
.set_port(0, *_movie
.port1
, true);
402 controls
.set_port(1, *_movie
.port2
, true);
403 _movie
.rtc_second
= _movie
.movie_rtc_second
;
404 _movie
.rtc_subsecond
= _movie
.movie_rtc_subsecond
;
405 if(!_movie
.anchor_savestate
.empty())
406 load_core_state(_movie
.anchor_savestate
);
408 } catch(std::bad_alloc
& e
) {
410 } catch(std::exception
& e
) {
411 system_corrupt
= true;
412 redraw_framebuffer(screen_corrupt
, true);
416 //Okay, copy the movie data.
417 if(lmode
!= LOAD_STATE_PRESERVE
)
420 //The is_savestate MUST be taken from movie (except LOAD_STATE_MOVIE), or one gets desyncs.
421 our_movie
.is_savestate
= _movie
.is_savestate
;
423 if(!our_movie
.is_savestate
|| lmode
== LOAD_STATE_MOVIE
) {
424 our_movie
.is_savestate
= false;
425 our_movie
.host_memory
.clear();
427 if(our_movie
.prefix
!= "" && lmode
!= LOAD_STATE_PRESERVE
) {
428 mprefix
.prefix
= our_movie
.prefix
;
431 movb
.get_movie() = newmovie
;
435 if(will_load_state
) {
436 tmp
.load(_movie
.screenshot
);
437 redraw_framebuffer(tmp
);
439 redraw_framebuffer(screen_nosignal
);
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_CURRENT
&& !current_mode
)
448 movb
.get_movie().readonly_mode(false);
449 information_dispatch::do_mode_change(movb
.get_movie().readonly_mode());
451 messages
<< "ROM Type " << our_rom
->rtype
->get_hname() << " region " << our_rom
->region
->get_hname()
453 uint64_t mlength
= _movie
.get_movie_length();
456 std::ostringstream x
;
457 if(mlength
> 3600000000000) {
458 x
<< mlength
/ 3600000000000 << ":";
459 mlength
%= 3600000000000;
461 x
<< std::setfill('0') << std::setw(2) << mlength
/ 60000000000 << ":";
462 mlength
%= 60000000000;
463 x
<< std::setfill('0') << std::setw(2) << mlength
/ 1000000000 << ".";
464 mlength
%= 1000000000;
465 x
<< std::setfill('0') << std::setw(3) << mlength
/ 1000000;
466 std::string rerecords
= _movie
.rerecords
;
467 if(lmode
!= LOAD_STATE_MOVIE
)
468 rerecords
= (stringfmt() << rrdata::count()).str();
469 messages
<< "Rerecords " << rerecords
<< " length " << x
.str() << " ("
470 << _movie
.get_frame_count() << " frames)" << std::endl
;
472 if(_movie
.gamename
!= "")
473 messages
<< "Game Name: " << _movie
.gamename
<< std::endl
;
474 for(size_t i
= 0; i
< _movie
.authors
.size(); i
++)
475 messages
<< "Author: " << _movie
.authors
[i
].first
<< "(" << _movie
.authors
[i
].second
<< ")"
480 bool do_load_state(const std::string
& filename
, int lmode
)
482 std::string filename2
= translate_name_mprefix(filename
, true);
483 uint64_t origtime
= get_utime();
484 lua_callback_pre_load(filename2
);
485 struct moviefile mfile
;
487 mfile
= moviefile(filename2
);
488 } catch(std::bad_alloc
& e
) {
490 } catch(std::exception
& e
) {
491 messages
<< "Can't read movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
492 lua_callback_err_load(filename2
);
496 do_load_state(mfile
, lmode
);
497 uint64_t took
= get_utime() - origtime
;
498 messages
<< "Loaded '" << filename2
<< "' in " << took
<< " microseconds." << std::endl
;
499 lua_callback_post_load(filename2
, our_movie
.is_savestate
);
500 } catch(std::bad_alloc
& e
) {
502 } catch(std::exception
& e
) {
503 messages
<< "Can't load movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
504 lua_callback_err_load(filename2
);
510 void mainloop_restore_state(const std::vector
<char>& state
, uint64_t secs
, uint64_t ssecs
)
512 //Force unlazy rrdata.
513 rrdata::read_base(our_movie
.projectid
, false);
514 rrdata::add_internal();
515 our_movie
.rtc_second
= secs
;
516 our_movie
.rtc_subsecond
= ssecs
;
517 load_core_state(state
, true);