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"
19 #include <boost/filesystem.hpp>
21 struct moviefile our_movie
;
22 struct loaded_rom
* our_rom
;
25 std::string last_save
;
29 time_t __wrap_time(time_t* t
)
31 time_t v
= static_cast<time_t>(our_movie
.rtc_second
);
38 std::vector
<char>& get_host_memory()
40 return our_movie
.host_memory
;
45 return movb
.get_movie();
50 numeric_setting
savecompression("savecompression", 0, 9, 7);
52 class projectprefix_setting
: public setting
57 projectprefix_setting() throw(std::bad_alloc
)
62 void blank() throw(std::bad_alloc
, std::runtime_error
)
70 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
75 std::string
get() throw(std::bad_alloc
)
79 operator std::string() throw()
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
;
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
;
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
)
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());
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
)
158 messages
<< "WARNING: " << name
<< " hash mismatch!" << std::endl
159 << "\tMovie: " << mhash
<< std::endl
160 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
163 messages
<< "ERROR: " << name
<< " hash mismatch!" << std::endl
164 << "\tMovie: " << mhash
<< std::endl
165 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
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);
181 std::pair
<std::string
, std::string
> split_author(const std::string
& author
) throw(std::bad_alloc
,
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()) {
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
);
201 void do_save_state(const std::string
& filename
) throw(std::bad_alloc
,
204 std::string filename2
= translate_name_mprefix(filename
);
205 lua_callback_pre_save(filename2
, true);
207 uint64_t origtime
= get_utime();
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
) {
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();
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);
242 uint64_t origtime
= get_utime();
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
) {
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
;
268 rrdata::add_internal();
270 movb
.get_movie().reset_state();
271 random_seed_value
= our_movie
.movie_rtc_second
;
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
) {
280 } catch(std::exception
& e
) {
281 system_corrupt
= true;
282 redraw_framebuffer(screen_corrupt
, true);
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());
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
;
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
);
326 throw std::runtime_error("Incorrect ROM");
328 SNES::config
.random
= false;
329 SNES::config
.expansion_port
= SNES::System::ExpansionPortDevice::None
;
332 if(lmode
== LOAD_STATE_PRESERVE
)
333 newmovie
= movb
.get_movie();
335 newmovie
.load(_movie
.rerecords
, _movie
.projectid
, _movie
.input
);
338 newmovie
.restore_state(_movie
.save_frame
, _movie
.lagged_frames
, _movie
.pollcounters
, true,
339 (lmode
== LOAD_STATE_PRESERVE
) ? &_movie
.input
: NULL
, _movie
.projectid
);
342 rrdata::read_base(_movie
.projectid
);
343 rrdata::read(_movie
.c_rrdata
);
344 rrdata::add_internal();
346 our_rom
->region
= gtype::toromregion(_movie
.gametype
);
347 random_seed_value
= _movie
.rtc_second
;
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
);
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
) {
364 } catch(std::exception
& e
) {
365 system_corrupt
= true;
366 redraw_framebuffer(screen_corrupt
, true);
370 //Okay, copy the movie data.
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
;
380 movb
.get_movie() = newmovie
;
384 if(will_load_state
) {
385 tmp
.load(_movie
.screenshot
);
386 redraw_framebuffer(tmp
);
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
) {
406 case ROMTYPE_BSXSLOTTED
:
407 messages
<< "BS-X slotted";
409 case ROMTYPE_SUFAMITURBO
:
410 messages
<< "Sufami Turbo";
413 messages
<< "Super Game Boy";
416 messages
<< "Unknown";
419 messages
<< " region ";
420 switch(our_rom
->region
) {
428 messages
<< "Unknown";
431 messages
<< std::endl
;
432 uint64_t mlength
= _movie
.get_movie_length();
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
<< ")"
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
;
463 mfile
= moviefile(filename2
);
464 } catch(std::bad_alloc
& e
) {
466 } catch(std::exception
& e
) {
467 messages
<< "Can't read movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
468 lua_callback_err_load(filename2
);
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
) {
478 } catch(std::exception
& e
) {
479 messages
<< "Can't load movie/savestate '" << filename2
<< "': " << e
.what() << std::endl
;
480 lua_callback_err_load(filename2
);