1 #include "mainloop.hpp"
4 #include "framerate.hpp"
5 #include "memorywatch.hpp"
10 #include "moviefile.hpp"
12 #include "keymapper.hpp"
14 #include "settings.hpp"
20 #include "memorymanip.hpp"
21 #include "keymapper.hpp"
23 #include "videodumper2.hpp"
27 #include <snes/snes.hpp>
28 #include <ui-libsnes/libsnes.hpp>
29 #include "framerate.hpp"
31 #define LOAD_STATE_RW 0
32 #define LOAD_STATE_RO 1
33 #define LOAD_STATE_PRESERVE 2
34 #define LOAD_STATE_MOVIE 3
35 #define LOAD_STATE_DEFAULT 4
38 #define SPECIAL_FRAME_START 0
39 #define SPECIAL_FRAME_VIDEO 1
40 #define SPECIAL_SAVEPOINT 2
41 #define SPECIAL_NONE 3
43 #define BUTTON_LEFT 0 //Gamepad
44 #define BUTTON_RIGHT 1 //Gamepad
45 #define BUTTON_UP 2 //Gamepad
46 #define BUTTON_DOWN 3 //Gamepad
47 #define BUTTON_A 4 //Gamepad
48 #define BUTTON_B 5 //Gamepad
49 #define BUTTON_X 6 //Gamepad
50 #define BUTTON_Y 7 //Gamepad
51 #define BUTTON_L 8 //Gamepad & Mouse
52 #define BUTTON_R 9 //Gamepad & Mouse
53 #define BUTTON_SELECT 10 //Gamepad
54 #define BUTTON_START 11 //Gamepad & Justifier
55 #define BUTTON_TRIGGER 12 //Superscope.
56 #define BUTTON_CURSOR 13 //Superscope & Justifier
57 #define BUTTON_PAUSE 14 //Superscope
58 #define BUTTON_TURBO 15 //Superscope
60 void update_movie_state();
61 void draw_nosignal(uint16_t* target
);
62 void draw_corrupt(uint16_t* target
);
69 ADVANCE_QUIT
, //Quit the emulator.
70 ADVANCE_AUTO
, //Normal (possibly slowed down play).
71 ADVANCE_FRAME
, //Frame advance.
72 ADVANCE_SUBFRAME
, //Subframe advance.
73 ADVANCE_SKIPLAG
, //Skip lag (oneshot, reverts to normal).
74 ADVANCE_SKIPLAG_PENDING
, //Activate skip lag mode at next frame.
75 ADVANCE_PAUSE
, //Unconditional pause.
78 //Analog input physical controller IDs and types.
79 int analog
[4] = {-1, -1, -1};
80 bool analog_is_mouse
[4] = {false, false, false};
82 std::map
<std::string
, std::string
> memory_watches
;
83 //Previous mouse mask.
84 int prev_mouse_mask
= 0;
85 //Flags related to repeating advance.
89 struct loaded_rom
* our_rom
;
91 struct moviefile our_movie
;
92 //Handle to the graphics system.
96 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
97 enum advance_mode amode
;
98 //Mode and filename of pending load, one of LOAD_* constants.
100 std::string pending_load
;
101 //Queued saves (all savestates).
102 std::set
<std::string
> queued_saves
;
103 bool stepping_into_save
;
105 controls_t curcontrols
;
106 controls_t autoheld_controls
;
107 //Emulator status area.
108 std::map
<std::string
, std::string
>* status
;
109 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
110 long pending_reset_cycles
= -1;
111 //Set by every video refresh.
112 bool video_refresh_done
;
113 //Special subframe location. One of SPECIAL_* constants.
114 int location_special
;
115 //Types of connected controllers.
116 enum porttype_t porttype1
= PT_GAMEPAD
;
117 enum porttype_t porttype2
= PT_NONE
;
118 //System corrupt flag.
120 //Current screen, no signal screen and corrupt screen.
121 lcscreen framebuffer
;
122 lcscreen nosignal_screen
;
123 lcscreen corrupt_screen
;
125 numeric_setting
advance_timeout_first("advance-timeout", 0, 999999999, 500);
126 numeric_setting
savecompression("savecompression", 0, 9, 7);
128 void send_analog_input(int32_t x
, int32_t y
, unsigned index
)
130 if(analog_is_mouse
[index
]) {
132 y
-= (framebuffer
.height
/ 2);
137 if(analog
[index
] < 0) {
138 out(win
) << "No analog controller in slot #" << (index
+ 1) << std::endl
;
141 curcontrols(analog
[index
] >> 2, analog
[index
] & 3, 0) = x
;
142 curcontrols(analog
[index
] >> 2, analog
[index
] & 3, 1) = y
;
145 void redraw_framebuffer()
147 uint32_t hscl
= 1, vscl
= 1;
148 if(framebuffer
.width
< 512)
150 if(framebuffer
.height
< 400)
153 struct lua_render_context lrc
;
159 lrc
.width
= framebuffer
.width
* hscl
;
160 lrc
.height
= framebuffer
.height
* vscl
;
161 lrc
.rshift
= scr
.active_rshift
;
162 lrc
.gshift
= scr
.active_gshift
;
163 lrc
.bshift
= scr
.active_bshift
;
164 lua_callback_do_paint(&lrc
, win
);
165 scr
.reallocate(framebuffer
.width
* hscl
+ lrc
.left_gap
+ lrc
.right_gap
, framebuffer
.height
* vscl
+
166 lrc
.top_gap
+ lrc
.bottom_gap
, lrc
.left_gap
, lrc
.top_gap
);
167 scr
.copy_from(framebuffer
, hscl
, vscl
);
168 //We would want divide by 2, but we'll do it ourselves in order to do mouse.
169 win
->set_window_compensation(lrc
.left_gap
, lrc
.top_gap
, 1, 1);
171 win
->notify_screen_update();
174 void fill_special_frames()
176 uint16_t buf
[512*448];
178 nosignal_screen
= lcscreen(buf
, 512, 448);
180 corrupt_screen
= lcscreen(buf
, 512, 448);
184 class firmware_path_setting
: public setting
187 firmware_path_setting() : setting("firmwarepath") { _firmwarepath
= "./"; default_firmware
= true; }
188 void blank() throw(std::bad_alloc
, std::runtime_error
)
190 _firmwarepath
= "./";
191 default_firmware
= true;
194 bool is_set() throw()
196 return !default_firmware
;
199 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
201 _firmwarepath
= value
;
202 default_firmware
= false;
205 std::string
get() throw(std::bad_alloc
)
207 return _firmwarepath
;
210 operator std::string() throw(std::bad_alloc
)
212 return _firmwarepath
;
215 std::string _firmwarepath
;
216 bool default_firmware
;
217 } firmwarepath_setting
;
219 class mymovielogic
: public movie_logic
222 mymovielogic() : movie_logic(dummy_movie
) {}
224 controls_t
update_controls(bool subframe
) throw(std::bad_alloc
, std::runtime_error
)
226 if(lua_requests_subframe_paint
)
227 redraw_framebuffer();
230 if(amode
== ADVANCE_SUBFRAME
) {
231 if(!cancel_advance
&& !advanced_once
) {
232 win
->wait_msec(advance_timeout_first
);
233 advanced_once
= true;
236 amode
= ADVANCE_PAUSE
;
237 cancel_advance
= false;
239 win
->paused(amode
== ADVANCE_PAUSE
);
240 } else if(amode
== ADVANCE_FRAME
) {
243 win
->paused(amode
== ADVANCE_SKIPLAG
|| amode
== ADVANCE_PAUSE
);
244 cancel_advance
= false;
246 if(amode
== ADVANCE_SKIPLAG
)
247 amode
= ADVANCE_AUTO
;
248 location_special
= SPECIAL_NONE
;
249 update_movie_state();
251 if(amode
== ADVANCE_SKIPLAG_PENDING
)
252 amode
= ADVANCE_SKIPLAG
;
253 if(amode
== ADVANCE_FRAME
|| amode
== ADVANCE_SUBFRAME
) {
254 if(!cancel_advance
) {
255 win
->wait_msec(advanced_once
? to_wait_frame(get_ticks_msec()) :
256 advance_timeout_first
);
257 advanced_once
= true;
260 amode
= ADVANCE_PAUSE
;
261 cancel_advance
= false;
263 win
->paused(amode
== ADVANCE_PAUSE
);
265 win
->paused((amode
== ADVANCE_PAUSE
));
266 cancel_advance
= false;
268 location_special
= SPECIAL_FRAME_START
;
269 update_movie_state();
271 win
->notify_screen_update();
273 if(!subframe
&& pending_reset_cycles
>= 0) {
274 curcontrols(CONTROL_SYSTEM_RESET
) = 1;
275 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = pending_reset_cycles
/ 10000;
276 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = pending_reset_cycles
% 10000;
277 } else if(!subframe
) {
278 curcontrols(CONTROL_SYSTEM_RESET
) = 0;
279 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = 0;
280 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = 0;
282 controls_t tmp
= curcontrols
^ autoheld_controls
;
283 lua_callback_do_input(tmp
, subframe
, win
);
294 //Lookup physical controller id based on UI controller id and given types (-1 if invalid).
295 int lookup_physical_controller(unsigned ui_id
)
297 bool p1multitap
= (porttype1
== PT_MULTITAP
);
298 unsigned p1devs
= port_types
[porttype1
].devices
;
299 unsigned p2devs
= port_types
[porttype2
].devices
;
300 if(ui_id
>= p1devs
+ p2devs
)
306 return 4 + ui_id
- p1devs
;
316 //Look up controller type given UI controller id (note: Non-present controllers give PT_NONE, not the type
317 //of port, multitap controllers give PT_GAMEPAD, not PT_MULTITAP, and justifiers give PT_JUSTIFIER, not
319 enum devicetype_t
lookup_controller_type(unsigned ui_id
)
321 int x
= lookup_physical_controller(ui_id
);
324 enum porttype_t rawtype
= (x
& 4) ? porttype2
: porttype1
;
325 if((x
& 3) < port_types
[rawtype
].devices
)
326 return port_types
[rawtype
].dtype
;
331 void set_analog_controllers()
334 for(unsigned i
= 0; i
< 8; i
++) {
335 enum devicetype_t t
= lookup_controller_type(i
);
336 analog_is_mouse
[index
] = (t
== DT_MOUSE
);
337 if(t
== DT_MOUSE
|| t
== DT_SUPERSCOPE
|| t
== DT_JUSTIFIER
) {
338 analog
[index
++] = lookup_physical_controller(i
);
342 for(; index
< 3; index
++)
346 std::map
<std::string
, std::pair
<unsigned, unsigned>> buttonmap
;
348 const char* buttonnames
[] = {
349 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
353 void init_buttonmap()
358 for(unsigned i
= 0; i
< 8; i
++)
359 for(unsigned j
= 0; j
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); j
++) {
360 std::ostringstream x
;
361 x
<< (i
+ 1) << buttonnames
[j
];
362 buttonmap
[x
.str()] = std::make_pair(i
, j
);
368 void do_button_action(unsigned ui_id
, unsigned button
, short newstate
, bool do_xor
= false)
370 enum devicetype_t p
= lookup_controller_type(ui_id
);
371 int x
= lookup_physical_controller(ui_id
);
375 out(win
) << "No such controller #" << (ui_id
+ 1) << std::endl
;
379 case BUTTON_UP
: bid
= SNES_DEVICE_ID_JOYPAD_UP
; break;
380 case BUTTON_DOWN
: bid
= SNES_DEVICE_ID_JOYPAD_DOWN
; break;
381 case BUTTON_LEFT
: bid
= SNES_DEVICE_ID_JOYPAD_LEFT
; break;
382 case BUTTON_RIGHT
: bid
= SNES_DEVICE_ID_JOYPAD_RIGHT
; break;
383 case BUTTON_A
: bid
= SNES_DEVICE_ID_JOYPAD_A
; break;
384 case BUTTON_B
: bid
= SNES_DEVICE_ID_JOYPAD_B
; break;
385 case BUTTON_X
: bid
= SNES_DEVICE_ID_JOYPAD_X
; break;
386 case BUTTON_Y
: bid
= SNES_DEVICE_ID_JOYPAD_Y
; break;
387 case BUTTON_L
: bid
= SNES_DEVICE_ID_JOYPAD_L
; break;
388 case BUTTON_R
: bid
= SNES_DEVICE_ID_JOYPAD_R
; break;
389 case BUTTON_SELECT
: bid
= SNES_DEVICE_ID_JOYPAD_SELECT
; break;
390 case BUTTON_START
: bid
= SNES_DEVICE_ID_JOYPAD_START
; break;
392 out(win
) << "Invalid button for gamepad" << std::endl
;
398 case BUTTON_L
: bid
= SNES_DEVICE_ID_MOUSE_LEFT
; break;
399 case BUTTON_R
: bid
= SNES_DEVICE_ID_MOUSE_RIGHT
; break;
401 out(win
) << "Invalid button for mouse" << std::endl
;
407 case BUTTON_START
: bid
= SNES_DEVICE_ID_JUSTIFIER_START
; break;
408 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_JUSTIFIER_TRIGGER
; break;
410 out(win
) << "Invalid button for justifier" << std::endl
;
416 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
; break;
417 case BUTTON_CURSOR
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
; break;
418 case BUTTON_PAUSE
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
; break;
419 case BUTTON_TURBO
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TURBO
; break;
421 out(win
) << "Invalid button for superscope" << std::endl
;
427 autoheld_controls((x
& 4) ? 1 : 0, x
& 3, bid
) ^= newstate
;
429 curcontrols((x
& 4) ? 1 : 0, x
& 3, bid
) = newstate
;
433 void do_save_state(const std::string
& filename
) throw(std::bad_alloc
,
436 lua_callback_pre_save(filename
, true, win
);
438 uint64_t origtime
= get_ticks_msec();
439 our_movie
.is_savestate
= true;
440 our_movie
.sram
= save_sram();
441 our_movie
.savestate
= save_core_state();
442 framebuffer
.save(our_movie
.screenshot
);
443 auto s
= movb
.get_movie().save_state();
444 our_movie
.movie_state
.resize(s
.size());
445 memcpy(&our_movie
.movie_state
[0], &s
[0], s
.size());
446 our_movie
.input
= movb
.get_movie().save();
447 our_movie
.save(filename
, savecompression
);
448 uint64_t took
= get_ticks_msec() - origtime
;
449 out(win
) << "Saved state '" << filename
<< "' in " << took
<< "ms." << std::endl
;
450 lua_callback_post_save(filename
, true, win
);
451 } catch(std::bad_alloc
& e
) {
453 } catch(std::exception
& e
) {
454 win
->message(std::string("Save failed: ") + e
.what());
455 lua_callback_err_save(filename
, win
);
460 void do_save_movie(const std::string
& filename
) throw(std::bad_alloc
, std::runtime_error
)
462 lua_callback_pre_save(filename
, false, win
);
464 uint64_t origtime
= get_ticks_msec();
465 our_movie
.is_savestate
= false;
466 our_movie
.input
= movb
.get_movie().save();
467 our_movie
.save(filename
, savecompression
);
468 uint64_t took
= get_ticks_msec() - origtime
;
469 out(win
) << "Saved movie '" << filename
<< "' in " << took
<< "ms." << std::endl
;
470 lua_callback_post_save(filename
, false, win
);
471 } catch(std::bad_alloc
& e
) {
473 } catch(std::exception
& e
) {
474 win
->message(std::string("Save failed: ") + e
.what());
475 lua_callback_err_save(filename
, win
);
479 void warn_hash_mismatch(const std::string
& mhash
, const loaded_slot
& slot
, const std::string
& name
)
481 if(mhash
!= slot
.sha256
) {
482 out(win
) << "WARNING: " << name
<< " hash mismatch!" << std::endl
483 << "\tMovie: " << mhash
<< std::endl
484 << "\tOur ROM: " << slot
.sha256
<< std::endl
;
488 void set_dev(bool port
, porttype_t t
, bool set_core
= true)
491 switch(set_core
? t
: PT_INVALID
) {
493 snes_set_controller_port_device(port
, SNES_DEVICE_NONE
);
496 snes_set_controller_port_device(port
, SNES_DEVICE_JOYPAD
);
499 snes_set_controller_port_device(port
, SNES_DEVICE_MULTITAP
);
502 snes_set_controller_port_device(port
, SNES_DEVICE_MOUSE
);
505 snes_set_controller_port_device(port
, SNES_DEVICE_SUPER_SCOPE
);
508 snes_set_controller_port_device(port
, SNES_DEVICE_JUSTIFIER
);
511 snes_set_controller_port_device(port
, SNES_DEVICE_JUSTIFIERS
);
520 set_analog_controllers();
523 //Load state from loaded movie file (does not catch errors).
524 void do_load_state(struct moviefile
& _movie
, int lmode
)
526 bool will_load_state
= _movie
.is_savestate
&& lmode
!= LOAD_STATE_MOVIE
;
527 if(gtype::toromtype(_movie
.gametype
) != our_rom
->rtype
)
528 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
529 if(gtype::toromregion(_movie
.gametype
) != our_rom
->orig_region
&& our_rom
->orig_region
!= REGION_AUTO
)
530 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
532 if(_movie
.coreversion
!= bsnes_core_version
) {
533 if(will_load_state
) {
534 std::ostringstream x
;
535 x
<< "ERROR: Emulator core version mismatch!" << std::endl
536 << "\tThis version: " << bsnes_core_version
<< std::endl
537 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
538 throw std::runtime_error(x
.str());
540 out(win
) << "WARNING: Emulator core version mismatch!" << std::endl
541 << "\tThis version: " << bsnes_core_version
<< std::endl
542 << "\tFile is from: " << _movie
.coreversion
<< std::endl
;
544 warn_hash_mismatch(_movie
.rom_sha256
, our_rom
->rom
, "ROM #1");
545 warn_hash_mismatch(_movie
.romxml_sha256
, our_rom
->rom_xml
, "XML #1");
546 warn_hash_mismatch(_movie
.slota_sha256
, our_rom
->slota
, "ROM #2");
547 warn_hash_mismatch(_movie
.slotaxml_sha256
, our_rom
->slota_xml
, "XML #2");
548 warn_hash_mismatch(_movie
.slotb_sha256
, our_rom
->slotb
, "ROM #3");
549 warn_hash_mismatch(_movie
.slotbxml_sha256
, our_rom
->slotb_xml
, "XML #3");
551 SNES::config
.random
= false;
552 SNES::config
.expansion_port
= SNES::System::ExpansionPortDevice::None
;
555 if(lmode
== LOAD_STATE_PRESERVE
)
556 newmovie
= movb
.get_movie();
558 newmovie
.load(_movie
.rerecords
, _movie
.projectid
, _movie
.input
);
560 if(will_load_state
) {
561 std::vector
<unsigned char> tmp
;
562 tmp
.resize(_movie
.movie_state
.size());
563 memcpy(&tmp
[0], &_movie
.movie_state
[0], tmp
.size());
564 newmovie
.restore_state(tmp
, true);
568 rrdata::read_base(_movie
.projectid
);
569 rrdata::add_internal();
571 our_rom
->region
= gtype::toromregion(_movie
.gametype
);
574 if(_movie
.is_savestate
&& lmode
!= LOAD_STATE_MOVIE
) {
575 //Load the savestate and movie state.
576 set_dev(false, _movie
.port1
);
577 set_dev(true, _movie
.port2
);
578 load_core_state(_movie
.savestate
);
579 framebuffer
.load(_movie
.screenshot
);
581 load_sram(_movie
.movie_sram
, win
);
582 set_dev(false, _movie
.port1
);
583 set_dev(true, _movie
.port2
);
584 framebuffer
= nosignal_screen
;
586 } catch(std::bad_alloc
& e
) {
588 } catch(std::exception
& e
) {
589 system_corrupt
= true;
593 //Okay, copy the movie data.
595 if(!our_movie
.is_savestate
|| lmode
== LOAD_STATE_MOVIE
) {
596 our_movie
.is_savestate
= false;
597 our_movie
.host_memory
.clear();
599 movb
.get_movie() = newmovie
;
600 //Activate RW mode if needed.
601 if(lmode
== LOAD_STATE_RW
)
602 movb
.get_movie().readonly_mode(false);
603 if(lmode
== LOAD_STATE_DEFAULT
&& !(movb
.get_movie().get_frame_count()))
604 movb
.get_movie().readonly_mode(false);
605 out(win
) << "ROM Type ";
606 switch(our_rom
->rtype
) {
613 case ROMTYPE_BSXSLOTTED
:
614 out(win
) << "BS-X slotted";
616 case ROMTYPE_SUFAMITURBO
:
617 out(win
) << "Sufami Turbo";
620 out(win
) << "Super Game Boy";
623 out(win
) << "Unknown";
626 out(win
) << " region ";
627 switch(our_rom
->region
) {
635 out(win
) << "Unknown";
638 out(win
) << std::endl
;
639 uint64_t mlength
= _movie
.get_movie_length();
642 std::ostringstream x
;
643 if(mlength
> 3600000000000) {
644 x
<< mlength
/ 3600000000000 << ":";
645 mlength
%= 3600000000000;
647 x
<< std::setfill('0') << std::setw(2) << mlength
/ 60000000000 << ":";
648 mlength
%= 60000000000;
649 x
<< std::setfill('0') << std::setw(2) << mlength
/ 1000000000 << ".";
650 mlength
%= 1000000000;
651 x
<< std::setfill('0') << std::setw(3) << mlength
/ 1000000;
652 out(win
) << "Rerecords " << _movie
.rerecords
<< " length " << x
.str() << " ("
653 << _movie
.get_frame_count() << " frames)" << std::endl
;
656 if(_movie
.gamename
!= "")
657 out(win
) << "Game Name: " << _movie
.gamename
<< std::endl
;
658 for(size_t i
= 0; i
< _movie
.authors
.size(); i
++)
659 out(win
) << "Author: " << _movie
.authors
[i
].first
<< "(" << _movie
.authors
[i
].second
<< ")"
664 void do_load_state(const std::string
& filename
, int lmode
)
666 uint64_t origtime
= get_ticks_msec();
667 lua_callback_pre_load(filename
, win
);
668 struct moviefile mfile
;
670 mfile
= moviefile(filename
);
671 } catch(std::bad_alloc
& e
) {
673 } catch(std::exception
& e
) {
674 win
->message("Can't read movie/savestate '" + filename
+ "': " + e
.what());
675 lua_callback_err_load(filename
, win
);
679 do_load_state(mfile
, lmode
);
680 uint64_t took
= get_ticks_msec() - origtime
;
681 out(win
) << "Loaded '" << filename
<< "' in " << took
<< "ms." << std::endl
;
682 lua_callback_post_load(filename
, our_movie
.is_savestate
, win
);
683 } catch(std::bad_alloc
& e
) {
685 } catch(std::exception
& e
) {
686 win
->message("Can't load movie/savestate '" + filename
+ "': " + e
.what());
687 lua_callback_err_load(filename
, win
);
692 //Do pending load (automatically unpauses).
693 void mark_pending_load(const std::string
& filename
, int lmode
)
696 pending_load
= filename
;
697 amode
= ADVANCE_AUTO
;
702 //Mark pending save (movies save immediately).
703 void mark_pending_save(const std::string
& filename
, int smode
)
705 if(smode
== SAVE_MOVIE
) {
706 //Just do this immediately.
707 do_save_movie(filename
);
710 queued_saves
.insert(filename
);
711 win
->message("Pending save on '" + filename
+ "'");
715 std::vector
<char>& get_host_memory()
717 return our_movie
.host_memory
;
722 return movb
.get_movie();
725 void update_movie_state()
727 auto& _status
= win
->get_emustatus();
729 std::ostringstream x
;
730 x
<< movb
.get_movie().get_current_frame() << "(";
731 if(location_special
== SPECIAL_FRAME_START
)
733 else if(location_special
== SPECIAL_SAVEPOINT
)
735 else if(location_special
== SPECIAL_FRAME_VIDEO
)
738 x
<< movb
.get_movie().next_poll_number();
739 x
<< ";" << movb
.get_movie().get_lag_frames() << ")/" << movb
.get_movie().get_frame_count();
740 _status
["Frame"] = x
.str();
743 std::ostringstream x
;
744 if(movb
.get_movie().readonly_mode())
748 if(dump_in_progress())
750 _status
["Flags"] = x
.str();
752 for(auto i
= memory_watches
.begin(); i
!= memory_watches
.end(); i
++) {
754 _status
["M[" + i
->first
+ "]"] = evaluate_watch(i
->second
);
759 if(movb
.get_movie().readonly_mode())
760 c
= movb
.get_movie().get_controls();
762 c
= curcontrols
^ autoheld_controls
;
763 for(unsigned i
= 0; i
< 8; i
++) {
764 unsigned pindex
= lookup_physical_controller(i
);
765 unsigned port
= pindex
>> 2;
766 unsigned dev
= pindex
& 3;
767 auto ctype
= lookup_controller_type(i
);
768 std::ostringstream x
;
771 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_LEFT
) ? "l" : " ");
772 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_RIGHT
) ? "r" : " ");
773 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_UP
) ? "u" : " ");
774 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_DOWN
) ? "d" : " ");
775 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_A
) ? "A" : " ");
776 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_B
) ? "B" : " ");
777 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_X
) ? "X" : " ");
778 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_Y
) ? "Y" : " ");
779 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_L
) ? "L" : " ");
780 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_R
) ? "R" : " ");
781 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_START
) ? "S" : " ");
782 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_SELECT
) ? "s" : " ");
785 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_X
) << " ";
786 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_Y
) << " ";
787 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_LEFT
) ? "L" : " ");
788 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_RIGHT
) ? "R" : " ");
791 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_X
) << " ";
792 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_Y
) << " ";
793 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
) ? "T" : " ");
794 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
) ? "C" : " ");
795 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TURBO
) ? "t" : " ");
796 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
) ? "P" : " ");
799 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_X
) << " ";
800 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_Y
) << " ";
801 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_START
) ? "T" : " ");
802 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_TRIGGER
) ? "S" : " ");
807 char y
[3] = {'P', 0, 0};
809 _status
[std::string(y
)] = x
.str();
814 class my_interface
: public SNES::Interface
816 string
path(SNES::Cartridge::Slot slot
, const string
&hint
)
818 return static_cast<std::string
>(firmwarepath_setting
).c_str();
821 void video_refresh(const uint16_t *data
, bool hires
, bool interlace
, bool overscan
)
823 if(stepping_into_save
)
824 win
->message("Got video refresh in runtosave, expect desyncs!");
825 video_refresh_done
= true;
826 bool region
= (SNES::system
.region() == SNES::System::Region::PAL
);
827 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
828 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
829 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
830 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
831 lcscreen
ls(data
, hires
, interlace
, overscan
, region
);
833 location_special
= SPECIAL_FRAME_VIDEO
;
834 update_movie_state();
835 redraw_framebuffer();
837 struct lua_render_context lrc
;
844 lrc
.width
= framebuffer
.width
;
845 lrc
.height
= framebuffer
.height
;
846 video_fill_shifts(lrc
.rshift
, lrc
.gshift
, lrc
.bshift
);
847 lua_callback_do_video(&lrc
, win
);
848 dump_frame(framebuffer
, &rq
, lrc
.left_gap
, lrc
.right_gap
, lrc
.top_gap
, lrc
.bottom_gap
, region
, win
);
851 void audio_sample(int16_t l_sample
, int16_t r_sample
)
853 uint16_t _l
= l_sample
;
854 uint16_t _r
= r_sample
;
855 win
->play_audio_sample(_l
+ 32768, _r
+ 32768);
856 dump_audio_sample(_l
, _r
, win
);
859 void audio_sample(uint16_t l_sample
, uint16_t r_sample
)
861 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
862 win
->play_audio_sample(l_sample
+ 32768, r_sample
+ 32768);
863 dump_audio_sample(l_sample
, r_sample
, win
);
866 int16_t input_poll(bool port
, SNES::Input::Device device
, unsigned index
, unsigned id
)
869 x
= movb
.input_poll(port
, index
, id
);
870 //if(id == SNES_DEVICE_ID_JOYPAD_START)
871 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
878 class quit_emulator_cmd
: public command
881 quit_emulator_cmd() throw(std::bad_alloc
) : command("quit-emulator") {}
882 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
884 if(args
== "/y" || win
->modal_message("Really quit?", true)) {
885 amode
= ADVANCE_QUIT
;
890 std::string
get_short_help() throw(std::bad_alloc
) { return "Quit the emulator"; }
891 std::string
get_long_help() throw(std::bad_alloc
)
893 return "Syntax: quit-emulator [/y]\n"
894 "Quits emulator (/y => don't ask for confirmation).\n";
898 class pause_emulator_cmd
: public command
901 pause_emulator_cmd() throw(std::bad_alloc
) : command("pause-emulator") {}
902 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
905 throw std::runtime_error("This command does not take parameters");
906 if(amode
!= ADVANCE_AUTO
) {
907 amode
= ADVANCE_AUTO
;
910 win
->message("Unpaused");
913 cancel_advance
= false;
914 amode
= ADVANCE_PAUSE
;
915 win
->message("Paused");
918 std::string
get_short_help() throw(std::bad_alloc
) { return "(Un)pause the emulator"; }
919 std::string
get_long_help() throw(std::bad_alloc
)
921 return "Syntax: pause-emulator\n"
922 "(Un)pauses the emulator.\n";
926 class padvance_frame_cmd
: public command
929 padvance_frame_cmd() throw(std::bad_alloc
) : command("+advance-frame") {}
930 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
933 throw std::runtime_error("This command does not take parameters");
934 amode
= ADVANCE_FRAME
;
935 cancel_advance
= false;
936 advanced_once
= false;
940 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one frame"; }
941 std::string
get_long_help() throw(std::bad_alloc
)
943 return "Syntax: +advance-frame\n"
944 "Advances the emulation by one frame.\n";
948 class nadvance_frame_cmd
: public command
951 nadvance_frame_cmd() throw(std::bad_alloc
) : command("-advance-frame") {}
952 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
955 throw std::runtime_error("This command does not take parameters");
956 cancel_advance
= true;
962 class padvance_poll_cmd
: public command
965 padvance_poll_cmd() throw(std::bad_alloc
) : command("+advance-poll") {}
966 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
969 throw std::runtime_error("This command does not take parameters");
970 amode
= ADVANCE_SUBFRAME
;
971 cancel_advance
= false;
972 advanced_once
= false;
976 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one subframe"; }
977 std::string
get_long_help() throw(std::bad_alloc
)
979 return "Syntax: +advance-poll\n"
980 "Advances the emulation by one subframe.\n";
984 class nadvance_poll_cmd
: public command
987 nadvance_poll_cmd() throw(std::bad_alloc
) : command("-advance-poll") {}
989 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
992 throw std::runtime_error("This command does not take parameters");
993 cancel_advance
= true;
999 class advance_skiplag_cmd
: public command
1002 advance_skiplag_cmd() throw(std::bad_alloc
) : command("advance-skiplag") {}
1003 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1006 throw std::runtime_error("This command does not take parameters");
1007 amode
= ADVANCE_SKIPLAG
;
1011 std::string
get_short_help() throw(std::bad_alloc
) { return "Skip to next poll"; }
1012 std::string
get_long_help() throw(std::bad_alloc
)
1014 return "Syntax: advance-skiplag\n"
1015 "Advances the emulation to the next poll.\n";
1019 class reset_cmd
: public command
1022 reset_cmd() throw(std::bad_alloc
) : command("reset") {}
1023 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1026 throw std::runtime_error("This command does not take parameters");
1027 pending_reset_cycles
= 0;
1029 std::string
get_short_help() throw(std::bad_alloc
) { return "Reset the SNES"; }
1030 std::string
get_long_help() throw(std::bad_alloc
)
1032 return "Syntax: reset\n"
1033 "Resets the SNES in beginning of the next frame.\n";
1037 class load_state_cmd
: public command
1040 load_state_cmd() throw(std::bad_alloc
) : command("load-state") {}
1041 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1044 throw std::runtime_error("Filename required");
1045 mark_pending_load(args
, LOAD_STATE_RW
);
1047 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
1048 std::string
get_long_help() throw(std::bad_alloc
)
1050 return "Syntax: load-state <file>\n"
1051 "Loads SNES state from <file> in Read/Write mode\n";
1055 class load_readonly_cmd
: public command
1058 load_readonly_cmd() throw(std::bad_alloc
) : command("load-readonly") {}
1059 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1062 throw std::runtime_error("Filename required");
1063 mark_pending_load(args
, LOAD_STATE_RO
);
1065 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
1066 std::string
get_long_help() throw(std::bad_alloc
)
1068 return "Syntax: load-readonly <file>\n"
1069 "Loads SNES state from <file> in Read-only mode\n";
1073 class load_preserve_cmd
: public command
1076 load_preserve_cmd() throw(std::bad_alloc
) : command("load-preserve") {}
1077 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1080 throw std::runtime_error("Filename required");
1081 mark_pending_load(args
, LOAD_STATE_PRESERVE
);
1083 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
1084 std::string
get_long_help() throw(std::bad_alloc
)
1086 return "Syntax: load-preserve <file>\n"
1087 "Loads SNES state from <file> preserving input\n";
1091 class load_movie_cmd
: public command
1094 load_movie_cmd() throw(std::bad_alloc
) : command("load-movie") {}
1095 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1098 throw std::runtime_error("Filename required");
1099 mark_pending_load(args
, LOAD_STATE_MOVIE
);
1101 std::string
get_short_help() throw(std::bad_alloc
) { return "Load movie"; }
1102 std::string
get_long_help() throw(std::bad_alloc
)
1104 return "Syntax: load-movie <file>\n"
1105 "Loads movie from <file>\n";
1109 class save_state_cmd
: public command
1112 save_state_cmd() throw(std::bad_alloc
) : command("save-state") {}
1113 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1116 throw std::runtime_error("Filename required");
1117 mark_pending_save(args
, SAVE_STATE
);
1120 std::string
get_short_help() throw(std::bad_alloc
) { return "Save state"; }
1121 std::string
get_long_help() throw(std::bad_alloc
)
1123 return "Syntax: save-state <file>\n"
1124 "Saves SNES state to <file>\n";
1128 class save_movie_cmd
: public command
1131 save_movie_cmd() throw(std::bad_alloc
) : command("save-movie") {}
1132 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1135 throw std::runtime_error("Filename required");
1136 mark_pending_save(args
, SAVE_MOVIE
);
1138 std::string
get_short_help() throw(std::bad_alloc
) { return "Save movie"; }
1139 std::string
get_long_help() throw(std::bad_alloc
)
1141 return "Syntax: save-movie <file>\n"
1142 "Saves movie to <file>\n";
1146 class set_rwmode_cmd
: public command
1149 set_rwmode_cmd() throw(std::bad_alloc
) : command("set-rwmode") {}
1150 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1153 throw std::runtime_error("This command does not take parameters");
1154 movb
.get_movie().readonly_mode(false);
1155 lua_callback_do_readwrite(win
);
1156 update_movie_state();
1157 win
->notify_screen_update();
1159 std::string
get_short_help() throw(std::bad_alloc
) { return "Switch to read/write mode"; }
1160 std::string
get_long_help() throw(std::bad_alloc
)
1162 return "Syntax: set-rwmode\n"
1163 "Switches to read/write mode\n";
1167 class set_gamename_cmd
: public command
1170 set_gamename_cmd() throw(std::bad_alloc
) : command("set-gamename") {}
1171 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1173 our_movie
.gamename
= args
;
1174 out(win
) << "Game name changed to '" << our_movie
.gamename
<< "'" << std::endl
;
1176 std::string
get_short_help() throw(std::bad_alloc
) { return "Set the game name"; }
1177 std::string
get_long_help() throw(std::bad_alloc
)
1179 return "Syntax: set-gamename <name>\n"
1180 "Sets the game name to <name>\n";
1184 class get_gamename_cmd
: public command
1187 get_gamename_cmd() throw(std::bad_alloc
) : command("get-gamename") {}
1188 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1191 throw std::runtime_error("This command does not take parameters");
1192 out(win
) << "Game name is '" << our_movie
.gamename
<< "'" << std::endl
;
1194 std::string
get_short_help() throw(std::bad_alloc
) { return "Get the game name"; }
1195 std::string
get_long_help() throw(std::bad_alloc
)
1197 return "Syntax: get-gamename\n"
1198 "Prints the game name\n";
1202 class print_authors_cmd
: public command
1205 print_authors_cmd() throw(std::bad_alloc
) : command("show-authors") {}
1206 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1209 throw std::runtime_error("This command does not take parameters");
1211 for(auto i
= our_movie
.authors
.begin(); i
!= our_movie
.authors
.end(); i
++) {
1212 out(win
) << (idx
++) << ": " << i
->first
<< "|" << i
->second
<< std::endl
;
1214 out(win
) << "End of authors list" << std::endl
;
1216 std::string
get_short_help() throw(std::bad_alloc
) { return "Show the run authors"; }
1217 std::string
get_long_help() throw(std::bad_alloc
)
1219 return "Syntax: show-authors\n"
1220 "Shows the run authors\n";
1224 class repainter
: public command
1227 repainter() throw(std::bad_alloc
) : command("repaint") {}
1228 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1231 throw std::runtime_error("This command does not take parameters");
1232 redraw_framebuffer();
1234 std::string
get_short_help() throw(std::bad_alloc
) { return "Redraw the screen"; }
1235 std::string
get_long_help() throw(std::bad_alloc
)
1237 return "Syntax: repaint\n"
1238 "Redraws the screen\n";
1242 class add_author_command
: public command
1245 add_author_command() throw(std::bad_alloc
) : command("add-author") {}
1246 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1248 tokensplitter
t(args
);
1249 fieldsplitter
f(t
.tail());
1250 std::string full
= f
;
1251 std::string nick
= f
;
1252 if(full
== "" && nick
== "")
1253 throw std::runtime_error("Bad author name");
1254 our_movie
.authors
.push_back(std::make_pair(full
, nick
));
1255 out(win
) << (our_movie
.authors
.size() - 1) << ": " << full
<< "|" << nick
<< std::endl
;
1257 std::string
get_short_help() throw(std::bad_alloc
) { return "Add an author"; }
1258 std::string
get_long_help() throw(std::bad_alloc
)
1260 return "Syntax: add-author <fullname>\n"
1261 "Syntax: add-author |<nickname>\n"
1262 "Syntax: add-author <fullname>|<nickname>\n"
1263 "Adds a new author\n";
1267 class remove_author_command
: public command
1270 remove_author_command() throw(std::bad_alloc
) : command("remove-author") {}
1271 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1273 tokensplitter
t(args
);
1274 uint64_t index
= parse_value
<uint64_t>(t
.tail());
1275 if(index
>= our_movie
.authors
.size())
1276 throw std::runtime_error("No such author");
1277 our_movie
.authors
.erase(our_movie
.authors
.begin() + index
);
1279 std::string
get_short_help() throw(std::bad_alloc
) { return "Remove an author"; }
1280 std::string
get_long_help() throw(std::bad_alloc
)
1282 return "Syntax: remove-author <id>\n"
1283 "Removes author with ID <id>\n";
1287 class edit_author_command
: public command
1290 edit_author_command() throw(std::bad_alloc
) : command("edit-author") {}
1291 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1293 tokensplitter
t(args
);
1294 uint64_t index
= parse_value
<uint64_t>(t
);
1295 if(index
>= our_movie
.authors
.size())
1296 throw std::runtime_error("No such author");
1297 fieldsplitter
f(t
.tail());
1298 std::string full
= f
;
1299 std::string nick
= f
;
1300 if(full
== "" && nick
== "") {
1301 out(win
) << "syntax: edit-author <authornum> <author>" << std::endl
;
1304 our_movie
.authors
[index
] = std::make_pair(full
, nick
);
1306 std::string
get_short_help() throw(std::bad_alloc
) { return "Edit an author"; }
1307 std::string
get_long_help() throw(std::bad_alloc
)
1309 return "Syntax: edit-author <authorid> <fullname>\n"
1310 "Syntax: edit-author <authorid> |<nickname>\n"
1311 "Syntax: edit-author <authorid> <fullname>|<nickname>\n"
1312 "Edits author name\n";
1316 class add_watch_command
: public command
1319 add_watch_command() throw(std::bad_alloc
) : command("add-watch") {}
1320 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1322 tokensplitter
t(args
);
1323 std::string name
= t
;
1324 if(name
== "" || t
.tail() == "")
1325 throw std::runtime_error("syntax: add-watch <name> <expr>");
1326 std::cerr
<< "Add watch: '" << name
<< "'" << std::endl
;
1327 memory_watches
[name
] = t
.tail();
1328 update_movie_state();
1330 std::string
get_short_help() throw(std::bad_alloc
) { return "Add a memory watch"; }
1331 std::string
get_long_help() throw(std::bad_alloc
)
1333 return "Syntax: add-watch <name> <expression>\n"
1334 "Adds a new memory watch\n";
1338 class remove_watch_command
: public command
1341 remove_watch_command() throw(std::bad_alloc
) : command("remove-watch") {}
1342 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1344 tokensplitter
t(args
);
1345 std::string name
= t
;
1346 if(name
== "" || t
.tail() != "") {
1347 out(win
) << "syntax: remove-watch <name>" << std::endl
;
1350 std::cerr
<< "Erase watch: '" << name
<< "'" << std::endl
;
1351 memory_watches
.erase(name
);
1352 auto& _status
= win
->get_emustatus();
1353 _status
.erase("M[" + name
+ "]");
1354 update_movie_state(); }
1355 std::string
get_short_help() throw(std::bad_alloc
) { return "Remove a memory watch"; }
1356 std::string
get_long_help() throw(std::bad_alloc
)
1358 return "Syntax: remove-watch <name>\n"
1359 "Removes a memory watch\n";
1363 class test_1
: public command
1366 test_1() throw(std::bad_alloc
) : command("test-1") {}
1367 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1369 framebuffer
= nosignal_screen
;
1370 redraw_framebuffer();
1374 class test_2
: public command
1377 test_2() throw(std::bad_alloc
) : command("test-2") {}
1378 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1380 framebuffer
= corrupt_screen
;
1381 redraw_framebuffer();
1385 class test_3
: public command
1388 test_3() throw(std::bad_alloc
) : command("test-3") {}
1389 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1395 class screenshot_command
: public command
1398 screenshot_command() throw(std::bad_alloc
) : command("take-screenshot") {}
1399 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1402 throw std::runtime_error("Filename required");
1403 framebuffer
.save_png(args
);
1404 out(win
) << "Saved PNG screenshot" << std::endl
;
1406 std::string
get_short_help() throw(std::bad_alloc
) { return "Takes a screenshot"; }
1407 std::string
get_long_help() throw(std::bad_alloc
)
1409 return "Syntax: take-screenshot <file>\n"
1410 "Saves screenshot to PNG file <file>\n";
1414 class mouse_button_handler
: public command
1417 mouse_button_handler() throw(std::bad_alloc
) : command("mouse_button") {}
1418 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1420 tokensplitter
t(args
);
1424 int _x
= atoi(x
.c_str());
1425 int _y
= atoi(y
.c_str());
1426 int _b
= atoi(b
.c_str());
1427 if(_b
& ~prev_mouse_mask
& 1)
1428 send_analog_input(_x
, _y
, 0);
1429 if(_b
& ~prev_mouse_mask
& 2)
1430 send_analog_input(_x
, _y
, 1);
1431 if(_b
& ~prev_mouse_mask
& 4)
1432 send_analog_input(_x
, _y
, 2);
1433 prev_mouse_mask
= _b
;
1437 class button_action
: public command
1440 button_action(const std::string
& cmd
, int _type
, unsigned _controller
, std::string _button
)
1441 throw(std::bad_alloc
)
1446 controller
= _controller
;
1449 ~button_action() throw() {}
1450 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
1453 throw std::runtime_error("This command does not take parameters");
1455 if(!buttonmap
.count(button
))
1457 auto i
= buttonmap
[button
];
1458 do_button_action(i
.first
, i
.second
, (type
!= 1) ? 1 : 0, (type
== 2));
1459 update_movie_state();
1460 win
->notify_screen_update();
1462 std::string
get_short_help() throw(std::bad_alloc
)
1464 return "Press/Unpress button";
1466 std::string
get_long_help() throw(std::bad_alloc
)
1468 return "Syntax: " + commandn
+ "\n"
1469 "Presses/Unpresses button\n";
1471 std::string commandn
;
1472 unsigned controller
;
1477 class button_action_helper
1480 button_action_helper()
1482 for(size_t i
= 0; i
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); ++i
)
1483 for(int j
= 0; j
< 3; ++j
)
1484 for(unsigned k
= 0; k
< 8; ++k
) {
1485 std::ostringstream x
, y
;
1498 x
<< buttonnames
[i
];
1500 y
<< buttonnames
[i
];
1501 new button_action(x
.str(), j
, k
, y
.str());
1506 //If there is a pending load, perform it.
1509 if(pending_load
!= "") {
1510 do_load_state(pending_load
, loadmode
);
1511 redraw_framebuffer();
1513 pending_reset_cycles
= -1;
1514 amode
= ADVANCE_AUTO
;
1517 if(!system_corrupt
) {
1518 location_special
= SPECIAL_SAVEPOINT
;
1519 update_movie_state();
1520 win
->notify_screen_update();
1528 //If there are pending saves, perform them.
1531 if(!queued_saves
.empty()) {
1532 stepping_into_save
= true;
1533 SNES::system
.runtosave();
1534 stepping_into_save
= false;
1535 for(auto i
= queued_saves
.begin(); i
!= queued_saves
.end(); i
++)
1538 queued_saves
.clear();
1541 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1542 bool handle_reset(long cycles
)
1545 win
->message("SNES reset");
1546 SNES::system
.reset();
1547 framebuffer
= nosignal_screen
;
1548 lua_callback_do_reset(win
);
1549 redraw_framebuffer();
1550 } else if(cycles
> 0) {
1551 video_refresh_done
= false;
1552 long cycles_executed
= 0;
1553 out(win
) << "Executing delayed reset... This can take some time!" << std::endl
;
1554 while(cycles_executed
< cycles
&& !video_refresh_done
) {
1555 SNES::cpu
.op_step();
1558 if(!video_refresh_done
)
1559 out(win
) << "SNES reset (delayed " << cycles_executed
<< ")" << std::endl
;
1561 out(win
) << "SNES reset (forced at " << cycles_executed
<< ")" << std::endl
;
1562 SNES::system
.reset();
1563 framebuffer
= nosignal_screen
;
1564 lua_callback_do_reset(win
);
1565 redraw_framebuffer();
1566 if(video_refresh_done
) {
1567 to_wait_frame(get_ticks_msec());
1574 bool handle_corrupt()
1578 while(system_corrupt
) {
1579 framebuffer
= corrupt_screen
;
1580 redraw_framebuffer();
1585 if(amode
== ADVANCE_QUIT
)
1591 void print_controller_mappings()
1593 for(unsigned i
= 0; i
< 8; i
++) {
1594 std::string type
= "unknown";
1595 if(lookup_controller_type(i
) == DT_NONE
)
1596 type
= "disconnected";
1597 if(lookup_controller_type(i
) == DT_GAMEPAD
)
1599 if(lookup_controller_type(i
) == DT_MOUSE
)
1601 if(lookup_controller_type(i
) == DT_SUPERSCOPE
)
1602 type
= "superscope";
1603 if(lookup_controller_type(i
) == DT_JUSTIFIER
)
1605 out(win
) << "Physical controller mapping: Logical " << (i
+ 1) << " is physical " <<
1606 lookup_physical_controller(i
) << " (" << type
<< ")" << std::endl
;
1611 void main_loop(window
* _win
, struct loaded_rom
& rom
, struct moviefile
& initial
) throw(std::bad_alloc
,
1614 //Basic initialization.
1618 auto old_inteface
= SNES::system
.interface
;
1619 SNES::system
.interface
= &intrf
;
1620 status
= &win
->get_emustatus();
1621 fill_special_frames();
1623 //Load our given movie.
1624 bool first_round
= false;
1625 bool just_did_loadstate
= false;
1627 do_load_state(initial
, LOAD_STATE_DEFAULT
);
1628 first_round
= our_movie
.is_savestate
;
1629 just_did_loadstate
= first_round
;
1630 } catch(std::bad_alloc
& e
) {
1632 } catch(std::exception
& e
) {
1633 win
->message(std::string("FATAL: Can't load initial state: ") + e
.what());
1638 lua_callback_startup(win
);
1640 //print_controller_mappings();
1642 win
->set_main_surface(scr
);
1643 redraw_framebuffer();
1645 amode
= ADVANCE_PAUSE
;
1646 while(amode
!= ADVANCE_QUIT
) {
1647 if(handle_corrupt()) {
1648 first_round
= our_movie
.is_savestate
;
1649 just_did_loadstate
= true;
1652 long resetcycles
= -1;
1653 ack_frame_tick(get_ticks_msec());
1654 if(amode
== ADVANCE_SKIPLAG_PENDING
)
1655 amode
= ADVANCE_SKIPLAG
;
1658 resetcycles
= movb
.new_frame_starting(amode
== ADVANCE_SKIPLAG
);
1659 if(amode
== ADVANCE_QUIT
)
1661 bool delayed_reset
= (resetcycles
> 0);
1662 pending_reset_cycles
= -1;
1663 if(!handle_reset(resetcycles
)) {
1666 if(!delayed_reset
) {
1670 first_round
= our_movie
.is_savestate
;
1671 amode
= ADVANCE_PAUSE
;
1672 just_did_loadstate
= first_round
;
1676 if(just_did_loadstate
) {
1677 if(amode
== ADVANCE_QUIT
)
1679 amode
= ADVANCE_PAUSE
;
1680 redraw_framebuffer();
1684 just_did_loadstate
= false;
1687 if(amode
== ADVANCE_AUTO
)
1688 win
->wait_msec(to_wait_frame(get_ticks_msec()));
1689 first_round
= false;
1692 SNES::system
.interface
= old_inteface
;