1 #include "mainloop.hpp"
4 #include "controller.hpp"
5 #include "framebuffer.hpp"
6 #include "moviedata.hpp"
8 #include "framerate.hpp"
9 #include "memorywatch.hpp"
14 #include "moviefile.hpp"
17 #include "settings.hpp"
23 #include "memorymanip.hpp"
28 #include <snes/snes.hpp>
29 #include <ui-libsnes/libsnes.hpp>
30 #include "framerate.hpp"
32 #define SPECIAL_FRAME_START 0
33 #define SPECIAL_FRAME_VIDEO 1
34 #define SPECIAL_SAVEPOINT 2
35 #define SPECIAL_NONE 3
37 #define BUTTON_LEFT 0 //Gamepad
38 #define BUTTON_RIGHT 1 //Gamepad
39 #define BUTTON_UP 2 //Gamepad
40 #define BUTTON_DOWN 3 //Gamepad
41 #define BUTTON_A 4 //Gamepad
42 #define BUTTON_B 5 //Gamepad
43 #define BUTTON_X 6 //Gamepad
44 #define BUTTON_Y 7 //Gamepad
45 #define BUTTON_L 8 //Gamepad & Mouse
46 #define BUTTON_R 9 //Gamepad & Mouse
47 #define BUTTON_SELECT 10 //Gamepad
48 #define BUTTON_START 11 //Gamepad & Justifier
49 #define BUTTON_TRIGGER 12 //Superscope.
50 #define BUTTON_CURSOR 13 //Superscope & Justifier
51 #define BUTTON_PAUSE 14 //Superscope
52 #define BUTTON_TURBO 15 //Superscope
54 void update_movie_state();
60 ADVANCE_QUIT
, //Quit the emulator.
61 ADVANCE_AUTO
, //Normal (possibly slowed down play).
62 ADVANCE_FRAME
, //Frame advance.
63 ADVANCE_SUBFRAME
, //Subframe advance.
64 ADVANCE_SKIPLAG
, //Skip lag (oneshot, reverts to normal).
65 ADVANCE_SKIPLAG_PENDING
, //Activate skip lag mode at next frame.
66 ADVANCE_PAUSE
, //Unconditional pause.
70 std::map
<std::string
, std::string
> memory_watches
;
71 //Previous mouse mask.
72 int prev_mouse_mask
= 0;
73 //Flags related to repeating advance.
76 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
77 enum advance_mode amode
;
78 //Mode and filename of pending load, one of LOAD_* constants.
80 std::string pending_load
;
81 //Queued saves (all savestates).
82 std::set
<std::string
> queued_saves
;
83 bool stepping_into_save
;
85 controls_t curcontrols
;
86 controls_t autoheld_controls
;
87 //Emulator status area.
88 std::map
<std::string
, std::string
>* status
;
89 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
90 long pending_reset_cycles
= -1;
91 //Set by every video refresh.
92 bool video_refresh_done
;
93 //Special subframe location. One of SPECIAL_* constants.
96 numeric_setting
advance_timeout_first("advance-timeout", 0, 999999999, 500);
98 void send_analog_input(int32_t x
, int32_t y
, unsigned index
)
100 if(controller_ismouse_by_analog(index
)) {
102 y
-= (framebuffer
.height
/ 2);
107 int aindex
= controller_index_by_analog(index
);
109 window::out() << "No analog controller in slot #" << (index
+ 1) << std::endl
;
112 curcontrols(aindex
>> 2, aindex
& 3, 0) = x
;
113 curcontrols(aindex
>> 2, aindex
& 3, 1) = y
;
118 class firmware_path_setting
: public setting
121 firmware_path_setting() : setting("firmwarepath") { _firmwarepath
= "./"; default_firmware
= true; }
122 void blank() throw(std::bad_alloc
, std::runtime_error
)
124 _firmwarepath
= "./";
125 default_firmware
= true;
128 bool is_set() throw()
130 return !default_firmware
;
133 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
135 _firmwarepath
= value
;
136 default_firmware
= false;
139 std::string
get() throw(std::bad_alloc
)
141 return _firmwarepath
;
144 operator std::string() throw(std::bad_alloc
)
146 return _firmwarepath
;
149 std::string _firmwarepath
;
150 bool default_firmware
;
151 } firmwarepath_setting
;
153 controls_t
movie_logic::update_controls(bool subframe
) throw(std::bad_alloc
, std::runtime_error
)
155 if(lua_requests_subframe_paint
)
156 redraw_framebuffer();
159 if(amode
== ADVANCE_SUBFRAME
) {
160 if(!cancel_advance
&& !advanced_once
) {
161 window::wait_msec(advance_timeout_first
);
162 advanced_once
= true;
165 amode
= ADVANCE_PAUSE
;
166 cancel_advance
= false;
168 window::paused(amode
== ADVANCE_PAUSE
);
169 } else if(amode
== ADVANCE_FRAME
) {
172 window::paused(amode
== ADVANCE_SKIPLAG
|| amode
== ADVANCE_PAUSE
);
173 cancel_advance
= false;
175 if(amode
== ADVANCE_SKIPLAG
)
176 amode
= ADVANCE_AUTO
;
177 location_special
= SPECIAL_NONE
;
178 update_movie_state();
180 if(amode
== ADVANCE_SKIPLAG_PENDING
)
181 amode
= ADVANCE_SKIPLAG
;
182 if(amode
== ADVANCE_FRAME
|| amode
== ADVANCE_SUBFRAME
) {
183 if(!cancel_advance
) {
184 window::wait_msec(advanced_once
? to_wait_frame(get_ticks_msec()) :
185 advance_timeout_first
);
186 advanced_once
= true;
189 amode
= ADVANCE_PAUSE
;
190 cancel_advance
= false;
192 window::paused(amode
== ADVANCE_PAUSE
);
194 window::paused((amode
== ADVANCE_PAUSE
));
195 cancel_advance
= false;
197 location_special
= SPECIAL_FRAME_START
;
198 update_movie_state();
200 window::notify_screen_update();
201 window::poll_inputs();
202 if(!subframe
&& pending_reset_cycles
>= 0) {
203 curcontrols(CONTROL_SYSTEM_RESET
) = 1;
204 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = pending_reset_cycles
/ 10000;
205 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = pending_reset_cycles
% 10000;
206 } else if(!subframe
) {
207 curcontrols(CONTROL_SYSTEM_RESET
) = 0;
208 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = 0;
209 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = 0;
211 controls_t tmp
= curcontrols
^ autoheld_controls
;
212 lua_callback_do_input(tmp
, subframe
);
218 std::map
<std::string
, std::pair
<unsigned, unsigned>> buttonmap
;
220 const char* buttonnames
[] = {
221 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
225 void init_buttonmap()
230 for(unsigned i
= 0; i
< 8; i
++)
231 for(unsigned j
= 0; j
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); j
++) {
232 std::ostringstream x
;
233 x
<< (i
+ 1) << buttonnames
[j
];
234 buttonmap
[x
.str()] = std::make_pair(i
, j
);
240 void do_button_action(unsigned ui_id
, unsigned button
, short newstate
, bool do_xor
= false)
242 enum devicetype_t p
= controller_type_by_logical(ui_id
);
243 int x
= controller_index_by_logical(ui_id
);
247 window::out() << "No such controller #" << (ui_id
+ 1) << std::endl
;
251 case BUTTON_UP
: bid
= SNES_DEVICE_ID_JOYPAD_UP
; break;
252 case BUTTON_DOWN
: bid
= SNES_DEVICE_ID_JOYPAD_DOWN
; break;
253 case BUTTON_LEFT
: bid
= SNES_DEVICE_ID_JOYPAD_LEFT
; break;
254 case BUTTON_RIGHT
: bid
= SNES_DEVICE_ID_JOYPAD_RIGHT
; break;
255 case BUTTON_A
: bid
= SNES_DEVICE_ID_JOYPAD_A
; break;
256 case BUTTON_B
: bid
= SNES_DEVICE_ID_JOYPAD_B
; break;
257 case BUTTON_X
: bid
= SNES_DEVICE_ID_JOYPAD_X
; break;
258 case BUTTON_Y
: bid
= SNES_DEVICE_ID_JOYPAD_Y
; break;
259 case BUTTON_L
: bid
= SNES_DEVICE_ID_JOYPAD_L
; break;
260 case BUTTON_R
: bid
= SNES_DEVICE_ID_JOYPAD_R
; break;
261 case BUTTON_SELECT
: bid
= SNES_DEVICE_ID_JOYPAD_SELECT
; break;
262 case BUTTON_START
: bid
= SNES_DEVICE_ID_JOYPAD_START
; break;
264 window::out() << "Invalid button for gamepad" << std::endl
;
270 case BUTTON_L
: bid
= SNES_DEVICE_ID_MOUSE_LEFT
; break;
271 case BUTTON_R
: bid
= SNES_DEVICE_ID_MOUSE_RIGHT
; break;
273 window::out() << "Invalid button for mouse" << std::endl
;
279 case BUTTON_START
: bid
= SNES_DEVICE_ID_JUSTIFIER_START
; break;
280 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_JUSTIFIER_TRIGGER
; break;
282 window::out() << "Invalid button for justifier" << std::endl
;
288 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
; break;
289 case BUTTON_CURSOR
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
; break;
290 case BUTTON_PAUSE
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
; break;
291 case BUTTON_TURBO
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TURBO
; break;
293 window::out() << "Invalid button for superscope" << std::endl
;
299 autoheld_controls((x
& 4) ? 1 : 0, x
& 3, bid
) ^= newstate
;
301 curcontrols((x
& 4) ? 1 : 0, x
& 3, bid
) = newstate
;
305 //Do pending load (automatically unpauses).
306 void mark_pending_load(const std::string
& filename
, int lmode
)
309 pending_load
= filename
;
310 amode
= ADVANCE_AUTO
;
311 window::cancel_wait();
312 window::paused(false);
315 //Mark pending save (movies save immediately).
316 void mark_pending_save(const std::string
& filename
, int smode
)
318 if(smode
== SAVE_MOVIE
) {
319 //Just do this immediately.
320 do_save_movie(filename
);
323 queued_saves
.insert(filename
);
324 window::message("Pending save on '" + filename
+ "'");
327 class dump_watch
: public av_snooper::dump_notification
329 void dump_starting() throw()
331 update_movie_state();
333 void dump_ending() throw()
335 update_movie_state();
340 void update_movie_state()
342 auto& _status
= window::get_emustatus();
344 std::ostringstream x
;
345 x
<< movb
.get_movie().get_current_frame() << "(";
346 if(location_special
== SPECIAL_FRAME_START
)
348 else if(location_special
== SPECIAL_SAVEPOINT
)
350 else if(location_special
== SPECIAL_FRAME_VIDEO
)
353 x
<< movb
.get_movie().next_poll_number();
354 x
<< ";" << movb
.get_movie().get_lag_frames() << ")/" << movb
.get_movie().get_frame_count();
355 _status
["Frame"] = x
.str();
358 std::ostringstream x
;
359 if(movb
.get_movie().readonly_mode())
363 if(av_snooper::dump_in_progress())
365 _status
["Flags"] = x
.str();
367 for(auto i
= memory_watches
.begin(); i
!= memory_watches
.end(); i
++) {
369 _status
["M[" + i
->first
+ "]"] = evaluate_watch(i
->second
);
374 if(movb
.get_movie().readonly_mode())
375 c
= movb
.get_movie().get_controls();
377 c
= curcontrols
^ autoheld_controls
;
378 for(unsigned i
= 0; i
< 8; i
++) {
379 unsigned pindex
= controller_index_by_logical(i
);
380 unsigned port
= pindex
>> 2;
381 unsigned dev
= pindex
& 3;
382 auto ctype
= controller_type_by_logical(i
);
383 std::ostringstream x
;
386 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_LEFT
) ? "l" : " ");
387 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_RIGHT
) ? "r" : " ");
388 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_UP
) ? "u" : " ");
389 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_DOWN
) ? "d" : " ");
390 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_A
) ? "A" : " ");
391 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_B
) ? "B" : " ");
392 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_X
) ? "X" : " ");
393 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_Y
) ? "Y" : " ");
394 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_L
) ? "L" : " ");
395 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_R
) ? "R" : " ");
396 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_START
) ? "S" : " ");
397 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_SELECT
) ? "s" : " ");
400 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_X
) << " ";
401 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_Y
) << " ";
402 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_LEFT
) ? "L" : " ");
403 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_RIGHT
) ? "R" : " ");
406 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_X
) << " ";
407 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_Y
) << " ";
408 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
) ? "T" : " ");
409 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
) ? "C" : " ");
410 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TURBO
) ? "t" : " ");
411 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
) ? "P" : " ");
414 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_X
) << " ";
415 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_Y
) << " ";
416 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_START
) ? "T" : " ");
417 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_TRIGGER
) ? "S" : " ");
422 char y
[3] = {'P', 0, 0};
424 _status
[std::string(y
)] = x
.str();
429 class my_interface
: public SNES::Interface
431 string
path(SNES::Cartridge::Slot slot
, const string
&hint
)
433 return static_cast<std::string
>(firmwarepath_setting
).c_str();
436 void video_refresh(const uint16_t *data
, bool hires
, bool interlace
, bool overscan
)
438 if(stepping_into_save
)
439 window::message("Got video refresh in runtosave, expect desyncs!");
440 video_refresh_done
= true;
441 bool region
= (SNES::system
.region() == SNES::System::Region::PAL
);
442 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
443 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
444 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
445 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
446 lcscreen
ls(data
, hires
, interlace
, overscan
, region
);
448 location_special
= SPECIAL_FRAME_VIDEO
;
449 update_movie_state();
450 redraw_framebuffer();
451 uint32_t fps_n
, fps_d
;
459 av_snooper::frame(ls
, fps_n
, fps_d
, true);
462 void audio_sample(int16_t l_sample
, int16_t r_sample
)
464 uint16_t _l
= l_sample
;
465 uint16_t _r
= r_sample
;
466 window::play_audio_sample(_l
+ 32768, _r
+ 32768);
467 av_snooper::sample(_l
, _r
, true);
470 void audio_sample(uint16_t l_sample
, uint16_t r_sample
)
472 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
473 window::play_audio_sample(l_sample
+ 32768, r_sample
+ 32768);
474 av_snooper::sample(l_sample
, r_sample
, true);
477 int16_t input_poll(bool port
, SNES::Input::Device device
, unsigned index
, unsigned id
)
480 x
= movb
.input_poll(port
, index
, id
);
481 //if(id == SNES_DEVICE_ID_JOYPAD_START)
482 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
483 lua_callback_snoop_input(port
? 1 : 0, index
, id
, x
);
490 class quit_emulator_cmd
: public command
493 quit_emulator_cmd() throw(std::bad_alloc
) : command("quit-emulator") {}
494 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
496 if(args
== "/y" || window::modal_message("Really quit?", true)) {
497 amode
= ADVANCE_QUIT
;
498 window::paused(false);
499 window::cancel_wait();
502 std::string
get_short_help() throw(std::bad_alloc
) { return "Quit the emulator"; }
503 std::string
get_long_help() throw(std::bad_alloc
)
505 return "Syntax: quit-emulator [/y]\n"
506 "Quits emulator (/y => don't ask for confirmation).\n";
510 class pause_emulator_cmd
: public command
513 pause_emulator_cmd() throw(std::bad_alloc
) : command("pause-emulator") {}
514 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
517 throw std::runtime_error("This command does not take parameters");
518 if(amode
!= ADVANCE_AUTO
) {
519 amode
= ADVANCE_AUTO
;
520 window::paused(false);
521 window::cancel_wait();
522 window::message("Unpaused");
524 window::cancel_wait();
525 cancel_advance
= false;
526 amode
= ADVANCE_PAUSE
;
527 window::message("Paused");
530 std::string
get_short_help() throw(std::bad_alloc
) { return "(Un)pause the emulator"; }
531 std::string
get_long_help() throw(std::bad_alloc
)
533 return "Syntax: pause-emulator\n"
534 "(Un)pauses the emulator.\n";
538 class padvance_frame_cmd
: public command
541 padvance_frame_cmd() throw(std::bad_alloc
) : command("+advance-frame") {}
542 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
545 throw std::runtime_error("This command does not take parameters");
546 amode
= ADVANCE_FRAME
;
547 cancel_advance
= false;
548 advanced_once
= false;
549 window::cancel_wait();
550 window::paused(false);
552 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one frame"; }
553 std::string
get_long_help() throw(std::bad_alloc
)
555 return "Syntax: +advance-frame\n"
556 "Advances the emulation by one frame.\n";
560 class nadvance_frame_cmd
: public command
563 nadvance_frame_cmd() throw(std::bad_alloc
) : command("-advance-frame") {}
564 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
567 throw std::runtime_error("This command does not take parameters");
568 cancel_advance
= true;
569 window::cancel_wait();
570 window::paused(false);
574 class padvance_poll_cmd
: public command
577 padvance_poll_cmd() throw(std::bad_alloc
) : command("+advance-poll") {}
578 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
581 throw std::runtime_error("This command does not take parameters");
582 amode
= ADVANCE_SUBFRAME
;
583 cancel_advance
= false;
584 advanced_once
= false;
585 window::cancel_wait();
586 window::paused(false);
588 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one subframe"; }
589 std::string
get_long_help() throw(std::bad_alloc
)
591 return "Syntax: +advance-poll\n"
592 "Advances the emulation by one subframe.\n";
596 class nadvance_poll_cmd
: public command
599 nadvance_poll_cmd() throw(std::bad_alloc
) : command("-advance-poll") {}
601 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
604 throw std::runtime_error("This command does not take parameters");
605 cancel_advance
= true;
606 window::cancel_wait();
607 window::paused(false);
611 class advance_skiplag_cmd
: public command
614 advance_skiplag_cmd() throw(std::bad_alloc
) : command("advance-skiplag") {}
615 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
618 throw std::runtime_error("This command does not take parameters");
619 amode
= ADVANCE_SKIPLAG
;
620 window::cancel_wait();
621 window::paused(false);
623 std::string
get_short_help() throw(std::bad_alloc
) { return "Skip to next poll"; }
624 std::string
get_long_help() throw(std::bad_alloc
)
626 return "Syntax: advance-skiplag\n"
627 "Advances the emulation to the next poll.\n";
631 class reset_cmd
: public command
634 reset_cmd() throw(std::bad_alloc
) : command("reset") {}
635 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
638 throw std::runtime_error("This command does not take parameters");
639 pending_reset_cycles
= 0;
641 std::string
get_short_help() throw(std::bad_alloc
) { return "Reset the SNES"; }
642 std::string
get_long_help() throw(std::bad_alloc
)
644 return "Syntax: reset\n"
645 "Resets the SNES in beginning of the next frame.\n";
649 class load_state_cmd
: public command
652 load_state_cmd() throw(std::bad_alloc
) : command("load-state") {}
653 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
656 throw std::runtime_error("Filename required");
657 mark_pending_load(args
, LOAD_STATE_RW
);
659 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
660 std::string
get_long_help() throw(std::bad_alloc
)
662 return "Syntax: load-state <file>\n"
663 "Loads SNES state from <file> in Read/Write mode\n";
667 class load_readonly_cmd
: public command
670 load_readonly_cmd() throw(std::bad_alloc
) : command("load-readonly") {}
671 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
674 throw std::runtime_error("Filename required");
675 mark_pending_load(args
, LOAD_STATE_RO
);
677 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
678 std::string
get_long_help() throw(std::bad_alloc
)
680 return "Syntax: load-readonly <file>\n"
681 "Loads SNES state from <file> in Read-only mode\n";
685 class load_preserve_cmd
: public command
688 load_preserve_cmd() throw(std::bad_alloc
) : command("load-preserve") {}
689 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
692 throw std::runtime_error("Filename required");
693 mark_pending_load(args
, LOAD_STATE_PRESERVE
);
695 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
696 std::string
get_long_help() throw(std::bad_alloc
)
698 return "Syntax: load-preserve <file>\n"
699 "Loads SNES state from <file> preserving input\n";
703 class load_movie_cmd
: public command
706 load_movie_cmd() throw(std::bad_alloc
) : command("load-movie") {}
707 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
710 throw std::runtime_error("Filename required");
711 mark_pending_load(args
, LOAD_STATE_MOVIE
);
713 std::string
get_short_help() throw(std::bad_alloc
) { return "Load movie"; }
714 std::string
get_long_help() throw(std::bad_alloc
)
716 return "Syntax: load-movie <file>\n"
717 "Loads movie from <file>\n";
721 class save_state_cmd
: public command
724 save_state_cmd() throw(std::bad_alloc
) : command("save-state") {}
725 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
728 throw std::runtime_error("Filename required");
729 mark_pending_save(args
, SAVE_STATE
);
732 std::string
get_short_help() throw(std::bad_alloc
) { return "Save state"; }
733 std::string
get_long_help() throw(std::bad_alloc
)
735 return "Syntax: save-state <file>\n"
736 "Saves SNES state to <file>\n";
740 class save_movie_cmd
: public command
743 save_movie_cmd() throw(std::bad_alloc
) : command("save-movie") {}
744 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
747 throw std::runtime_error("Filename required");
748 mark_pending_save(args
, SAVE_MOVIE
);
750 std::string
get_short_help() throw(std::bad_alloc
) { return "Save movie"; }
751 std::string
get_long_help() throw(std::bad_alloc
)
753 return "Syntax: save-movie <file>\n"
754 "Saves movie to <file>\n";
758 class set_rwmode_cmd
: public command
761 set_rwmode_cmd() throw(std::bad_alloc
) : command("set-rwmode") {}
762 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
765 throw std::runtime_error("This command does not take parameters");
766 movb
.get_movie().readonly_mode(false);
767 lua_callback_do_readwrite();
768 update_movie_state();
769 window::notify_screen_update();
771 std::string
get_short_help() throw(std::bad_alloc
) { return "Switch to read/write mode"; }
772 std::string
get_long_help() throw(std::bad_alloc
)
774 return "Syntax: set-rwmode\n"
775 "Switches to read/write mode\n";
779 class repainter
: public command
782 repainter() throw(std::bad_alloc
) : command("repaint") {}
783 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
786 throw std::runtime_error("This command does not take parameters");
787 redraw_framebuffer();
789 std::string
get_short_help() throw(std::bad_alloc
) { return "Redraw the screen"; }
790 std::string
get_long_help() throw(std::bad_alloc
)
792 return "Syntax: repaint\n"
793 "Redraws the screen\n";
797 class set_gamename_cmd
: public command
800 set_gamename_cmd() throw(std::bad_alloc
) : command("set-gamename") {}
801 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
803 our_movie
.gamename
= args
;
804 window::out() << "Game name changed to '" << our_movie
.gamename
<< "'" << std::endl
;
806 std::string
get_short_help() throw(std::bad_alloc
) { return "Set the game name"; }
807 std::string
get_long_help() throw(std::bad_alloc
)
809 return "Syntax: set-gamename <name>\n"
810 "Sets the game name to <name>\n";
814 class add_watch_command
: public command
817 add_watch_command() throw(std::bad_alloc
) : command("add-watch") {}
818 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
820 tokensplitter
t(args
);
821 std::string name
= t
;
822 if(name
== "" || t
.tail() == "")
823 throw std::runtime_error("syntax: add-watch <name> <expr>");
824 std::cerr
<< "Add watch: '" << name
<< "'" << std::endl
;
825 memory_watches
[name
] = t
.tail();
826 update_movie_state();
828 std::string
get_short_help() throw(std::bad_alloc
) { return "Add a memory watch"; }
829 std::string
get_long_help() throw(std::bad_alloc
)
831 return "Syntax: add-watch <name> <expression>\n"
832 "Adds a new memory watch\n";
836 class remove_watch_command
: public command
839 remove_watch_command() throw(std::bad_alloc
) : command("remove-watch") {}
840 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
842 tokensplitter
t(args
);
843 std::string name
= t
;
844 if(name
== "" || t
.tail() != "") {
845 window::out() << "syntax: remove-watch <name>" << std::endl
;
848 std::cerr
<< "Erase watch: '" << name
<< "'" << std::endl
;
849 memory_watches
.erase(name
);
850 auto& _status
= window::get_emustatus();
851 _status
.erase("M[" + name
+ "]");
852 update_movie_state(); }
853 std::string
get_short_help() throw(std::bad_alloc
) { return "Remove a memory watch"; }
854 std::string
get_long_help() throw(std::bad_alloc
)
856 return "Syntax: remove-watch <name>\n"
857 "Removes a memory watch\n";
861 class test_1
: public command
864 test_1() throw(std::bad_alloc
) : command("test-1") {}
865 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
867 framebuffer
= screen_nosignal
;
868 redraw_framebuffer();
872 class test_2
: public command
875 test_2() throw(std::bad_alloc
) : command("test-2") {}
876 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
878 framebuffer
= screen_corrupt
;
879 redraw_framebuffer();
883 class test_3
: public command
886 test_3() throw(std::bad_alloc
) : command("test-3") {}
887 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
893 class screenshot_command
: public command
896 screenshot_command() throw(std::bad_alloc
) : command("take-screenshot") {}
897 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
900 throw std::runtime_error("Filename required");
901 framebuffer
.save_png(args
);
902 window::out() << "Saved PNG screenshot" << std::endl
;
904 std::string
get_short_help() throw(std::bad_alloc
) { return "Takes a screenshot"; }
905 std::string
get_long_help() throw(std::bad_alloc
)
907 return "Syntax: take-screenshot <file>\n"
908 "Saves screenshot to PNG file <file>\n";
912 class mouse_button_handler
: public command
915 mouse_button_handler() throw(std::bad_alloc
) : command("mouse_button") {}
916 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
918 tokensplitter
t(args
);
922 int _x
= atoi(x
.c_str());
923 int _y
= atoi(y
.c_str());
924 int _b
= atoi(b
.c_str());
925 if(_b
& ~prev_mouse_mask
& 1)
926 send_analog_input(_x
, _y
, 0);
927 if(_b
& ~prev_mouse_mask
& 2)
928 send_analog_input(_x
, _y
, 1);
929 if(_b
& ~prev_mouse_mask
& 4)
930 send_analog_input(_x
, _y
, 2);
931 prev_mouse_mask
= _b
;
935 class button_action
: public command
938 button_action(const std::string
& cmd
, int _type
, unsigned _controller
, std::string _button
)
939 throw(std::bad_alloc
)
944 controller
= _controller
;
947 ~button_action() throw() {}
948 void invoke(const std::string
& args
) throw(std::bad_alloc
, std::runtime_error
)
951 throw std::runtime_error("This command does not take parameters");
953 if(!buttonmap
.count(button
))
955 auto i
= buttonmap
[button
];
956 do_button_action(i
.first
, i
.second
, (type
!= 1) ? 1 : 0, (type
== 2));
957 update_movie_state();
958 window::notify_screen_update();
960 std::string
get_short_help() throw(std::bad_alloc
)
962 return "Press/Unpress button";
964 std::string
get_long_help() throw(std::bad_alloc
)
966 return "Syntax: " + commandn
+ "\n"
967 "Presses/Unpresses button\n";
969 std::string commandn
;
975 class button_action_helper
978 button_action_helper()
980 for(size_t i
= 0; i
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); ++i
)
981 for(int j
= 0; j
< 3; ++j
)
982 for(unsigned k
= 0; k
< 8; ++k
) {
983 std::ostringstream x
, y
;
999 new button_action(x
.str(), j
, k
, y
.str());
1004 //If there is a pending load, perform it.
1007 if(pending_load
!= "") {
1008 do_load_state(pending_load
, loadmode
);
1009 redraw_framebuffer();
1011 pending_reset_cycles
= -1;
1012 amode
= ADVANCE_AUTO
;
1013 window::cancel_wait();
1014 window::paused(false);
1015 if(!system_corrupt
) {
1016 location_special
= SPECIAL_SAVEPOINT
;
1017 update_movie_state();
1018 window::notify_screen_update();
1019 window::poll_inputs();
1026 //If there are pending saves, perform them.
1029 if(!queued_saves
.empty()) {
1030 stepping_into_save
= true;
1031 SNES::system
.runtosave();
1032 stepping_into_save
= false;
1033 for(auto i
= queued_saves
.begin(); i
!= queued_saves
.end(); i
++)
1036 queued_saves
.clear();
1039 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1040 bool handle_reset(long cycles
)
1043 window::message("SNES reset");
1044 SNES::system
.reset();
1045 framebuffer
= screen_nosignal
;
1046 lua_callback_do_reset();
1047 redraw_framebuffer();
1048 } else if(cycles
> 0) {
1049 video_refresh_done
= false;
1050 long cycles_executed
= 0;
1051 window::out() << "Executing delayed reset... This can take some time!" << std::endl
;
1052 while(cycles_executed
< cycles
&& !video_refresh_done
) {
1053 SNES::cpu
.op_step();
1056 if(!video_refresh_done
)
1057 window::out() << "SNES reset (delayed " << cycles_executed
<< ")" << std::endl
;
1059 window::out() << "SNES reset (forced at " << cycles_executed
<< ")" << std::endl
;
1060 SNES::system
.reset();
1061 framebuffer
= screen_nosignal
;
1062 lua_callback_do_reset();
1063 redraw_framebuffer();
1064 if(video_refresh_done
) {
1065 to_wait_frame(get_ticks_msec());
1072 bool handle_corrupt()
1076 while(system_corrupt
) {
1077 redraw_framebuffer();
1078 window::cancel_wait();
1079 window::paused(true);
1080 window::poll_inputs();
1082 if(amode
== ADVANCE_QUIT
)
1088 void print_controller_mappings()
1090 for(unsigned i
= 0; i
< 8; i
++) {
1091 std::string type
= "unknown";
1092 if(controller_type_by_logical(i
) == DT_NONE
)
1093 type
= "disconnected";
1094 if(controller_type_by_logical(i
) == DT_GAMEPAD
)
1096 if(controller_type_by_logical(i
) == DT_MOUSE
)
1098 if(controller_type_by_logical(i
) == DT_SUPERSCOPE
)
1099 type
= "superscope";
1100 if(controller_type_by_logical(i
) == DT_JUSTIFIER
)
1102 window::out() << "Physical controller mapping: Logical " << (i
+ 1) << " is physical " <<
1103 controller_index_by_logical(i
) << " (" << type
<< ")" << std::endl
;
1108 void main_loop(struct loaded_rom
& rom
, struct moviefile
& initial
) throw(std::bad_alloc
,
1111 //Basic initialization.
1112 init_special_screens();
1115 auto old_inteface
= SNES::system
.interface
;
1116 SNES::system
.interface
= &intrf
;
1117 status
= &window::get_emustatus();
1119 //Load our given movie.
1120 bool first_round
= false;
1121 bool just_did_loadstate
= false;
1123 do_load_state(initial
, LOAD_STATE_DEFAULT
);
1124 first_round
= our_movie
.is_savestate
;
1125 just_did_loadstate
= first_round
;
1126 } catch(std::bad_alloc
& e
) {
1128 } catch(std::exception
& e
) {
1129 window::message(std::string("FATAL: Can't load initial state: ") + e
.what());
1130 window::fatal_error();
1134 lua_callback_startup();
1136 //print_controller_mappings();
1137 av_snooper::add_dump_notifier(dumpwatch
);
1138 window::set_main_surface(main_screen
);
1139 redraw_framebuffer();
1140 window::paused(false);
1141 amode
= ADVANCE_PAUSE
;
1142 while(amode
!= ADVANCE_QUIT
) {
1143 if(handle_corrupt()) {
1144 first_round
= our_movie
.is_savestate
;
1145 just_did_loadstate
= true;
1148 long resetcycles
= -1;
1149 ack_frame_tick(get_ticks_msec());
1150 if(amode
== ADVANCE_SKIPLAG_PENDING
)
1151 amode
= ADVANCE_SKIPLAG
;
1154 resetcycles
= movb
.new_frame_starting(amode
== ADVANCE_SKIPLAG
);
1155 if(amode
== ADVANCE_QUIT
)
1157 bool delayed_reset
= (resetcycles
> 0);
1158 pending_reset_cycles
= -1;
1159 if(!handle_reset(resetcycles
)) {
1162 if(!delayed_reset
) {
1166 first_round
= our_movie
.is_savestate
;
1167 amode
= ADVANCE_PAUSE
;
1168 just_did_loadstate
= first_round
;
1172 if(just_did_loadstate
) {
1173 if(amode
== ADVANCE_QUIT
)
1175 amode
= ADVANCE_PAUSE
;
1176 redraw_framebuffer();
1177 window::cancel_wait();
1178 window::paused(true);
1179 window::poll_inputs();
1180 just_did_loadstate
= false;
1183 if(amode
== ADVANCE_AUTO
)
1184 window::wait_msec(to_wait_frame(get_ticks_msec()));
1185 first_round
= false;
1187 av_snooper::end(true);
1188 SNES::system
.interface
= old_inteface
;