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 //Handle to the graphics system.
78 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
79 enum advance_mode amode
;
80 //Mode and filename of pending load, one of LOAD_* constants.
82 std::string pending_load
;
83 //Queued saves (all savestates).
84 std::set
<std::string
> queued_saves
;
85 bool stepping_into_save
;
87 controls_t curcontrols
;
88 controls_t autoheld_controls
;
89 //Emulator status area.
90 std::map
<std::string
, std::string
>* status
;
91 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
92 long pending_reset_cycles
= -1;
93 //Set by every video refresh.
94 bool video_refresh_done
;
95 //Special subframe location. One of SPECIAL_* constants.
98 numeric_setting
advance_timeout_first("advance-timeout", 0, 999999999, 500);
100 void send_analog_input(int32_t x
, int32_t y
, unsigned index
)
102 if(controller_ismouse_by_analog(index
)) {
104 y
-= (framebuffer
.height
/ 2);
109 int aindex
= controller_index_by_analog(index
);
111 out(win
) << "No analog controller in slot #" << (index
+ 1) << std::endl
;
114 curcontrols(aindex
>> 2, aindex
& 3, 0) = x
;
115 curcontrols(aindex
>> 2, aindex
& 3, 1) = y
;
120 class firmware_path_setting
: public setting
123 firmware_path_setting() : setting("firmwarepath") { _firmwarepath
= "./"; default_firmware
= true; }
124 void blank() throw(std::bad_alloc
, std::runtime_error
)
126 _firmwarepath
= "./";
127 default_firmware
= true;
130 bool is_set() throw()
132 return !default_firmware
;
135 void set(const std::string
& value
) throw(std::bad_alloc
, std::runtime_error
)
137 _firmwarepath
= value
;
138 default_firmware
= false;
141 std::string
get() throw(std::bad_alloc
)
143 return _firmwarepath
;
146 operator std::string() throw(std::bad_alloc
)
148 return _firmwarepath
;
151 std::string _firmwarepath
;
152 bool default_firmware
;
153 } firmwarepath_setting
;
155 controls_t
movie_logic::update_controls(bool subframe
) throw(std::bad_alloc
, std::runtime_error
)
157 if(lua_requests_subframe_paint
)
158 redraw_framebuffer(win
);
161 if(amode
== ADVANCE_SUBFRAME
) {
162 if(!cancel_advance
&& !advanced_once
) {
163 win
->wait_msec(advance_timeout_first
);
164 advanced_once
= true;
167 amode
= ADVANCE_PAUSE
;
168 cancel_advance
= false;
170 win
->paused(amode
== ADVANCE_PAUSE
);
171 } else if(amode
== ADVANCE_FRAME
) {
174 win
->paused(amode
== ADVANCE_SKIPLAG
|| amode
== ADVANCE_PAUSE
);
175 cancel_advance
= false;
177 if(amode
== ADVANCE_SKIPLAG
)
178 amode
= ADVANCE_AUTO
;
179 location_special
= SPECIAL_NONE
;
180 update_movie_state();
182 if(amode
== ADVANCE_SKIPLAG_PENDING
)
183 amode
= ADVANCE_SKIPLAG
;
184 if(amode
== ADVANCE_FRAME
|| amode
== ADVANCE_SUBFRAME
) {
185 if(!cancel_advance
) {
186 win
->wait_msec(advanced_once
? to_wait_frame(get_ticks_msec()) :
187 advance_timeout_first
);
188 advanced_once
= true;
191 amode
= ADVANCE_PAUSE
;
192 cancel_advance
= false;
194 win
->paused(amode
== ADVANCE_PAUSE
);
196 win
->paused((amode
== ADVANCE_PAUSE
));
197 cancel_advance
= false;
199 location_special
= SPECIAL_FRAME_START
;
200 update_movie_state();
202 win
->notify_screen_update();
204 if(!subframe
&& pending_reset_cycles
>= 0) {
205 curcontrols(CONTROL_SYSTEM_RESET
) = 1;
206 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = pending_reset_cycles
/ 10000;
207 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = pending_reset_cycles
% 10000;
208 } else if(!subframe
) {
209 curcontrols(CONTROL_SYSTEM_RESET
) = 0;
210 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI
) = 0;
211 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO
) = 0;
213 controls_t tmp
= curcontrols
^ autoheld_controls
;
214 lua_callback_do_input(tmp
, subframe
, win
);
220 std::map
<std::string
, std::pair
<unsigned, unsigned>> buttonmap
;
222 const char* buttonnames
[] = {
223 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
227 void init_buttonmap()
232 for(unsigned i
= 0; i
< 8; i
++)
233 for(unsigned j
= 0; j
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); j
++) {
234 std::ostringstream x
;
235 x
<< (i
+ 1) << buttonnames
[j
];
236 buttonmap
[x
.str()] = std::make_pair(i
, j
);
242 void do_button_action(unsigned ui_id
, unsigned button
, short newstate
, bool do_xor
= false)
244 enum devicetype_t p
= controller_type_by_logical(ui_id
);
245 int x
= controller_index_by_logical(ui_id
);
249 out(win
) << "No such controller #" << (ui_id
+ 1) << std::endl
;
253 case BUTTON_UP
: bid
= SNES_DEVICE_ID_JOYPAD_UP
; break;
254 case BUTTON_DOWN
: bid
= SNES_DEVICE_ID_JOYPAD_DOWN
; break;
255 case BUTTON_LEFT
: bid
= SNES_DEVICE_ID_JOYPAD_LEFT
; break;
256 case BUTTON_RIGHT
: bid
= SNES_DEVICE_ID_JOYPAD_RIGHT
; break;
257 case BUTTON_A
: bid
= SNES_DEVICE_ID_JOYPAD_A
; break;
258 case BUTTON_B
: bid
= SNES_DEVICE_ID_JOYPAD_B
; break;
259 case BUTTON_X
: bid
= SNES_DEVICE_ID_JOYPAD_X
; break;
260 case BUTTON_Y
: bid
= SNES_DEVICE_ID_JOYPAD_Y
; break;
261 case BUTTON_L
: bid
= SNES_DEVICE_ID_JOYPAD_L
; break;
262 case BUTTON_R
: bid
= SNES_DEVICE_ID_JOYPAD_R
; break;
263 case BUTTON_SELECT
: bid
= SNES_DEVICE_ID_JOYPAD_SELECT
; break;
264 case BUTTON_START
: bid
= SNES_DEVICE_ID_JOYPAD_START
; break;
266 out(win
) << "Invalid button for gamepad" << std::endl
;
272 case BUTTON_L
: bid
= SNES_DEVICE_ID_MOUSE_LEFT
; break;
273 case BUTTON_R
: bid
= SNES_DEVICE_ID_MOUSE_RIGHT
; break;
275 out(win
) << "Invalid button for mouse" << std::endl
;
281 case BUTTON_START
: bid
= SNES_DEVICE_ID_JUSTIFIER_START
; break;
282 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_JUSTIFIER_TRIGGER
; break;
284 out(win
) << "Invalid button for justifier" << std::endl
;
290 case BUTTON_TRIGGER
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
; break;
291 case BUTTON_CURSOR
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
; break;
292 case BUTTON_PAUSE
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
; break;
293 case BUTTON_TURBO
: bid
= SNES_DEVICE_ID_SUPER_SCOPE_TURBO
; break;
295 out(win
) << "Invalid button for superscope" << std::endl
;
301 autoheld_controls((x
& 4) ? 1 : 0, x
& 3, bid
) ^= newstate
;
303 curcontrols((x
& 4) ? 1 : 0, x
& 3, bid
) = newstate
;
307 //Do pending load (automatically unpauses).
308 void mark_pending_load(const std::string
& filename
, int lmode
)
311 pending_load
= filename
;
312 amode
= ADVANCE_AUTO
;
317 //Mark pending save (movies save immediately).
318 void mark_pending_save(const std::string
& filename
, int smode
)
320 if(smode
== SAVE_MOVIE
) {
321 //Just do this immediately.
322 do_save_movie(win
, filename
);
325 queued_saves
.insert(filename
);
326 win
->message("Pending save on '" + filename
+ "'");
329 class dump_watch
: public av_snooper::dump_notification
331 void dump_starting() throw()
333 update_movie_state();
335 void dump_ending() throw()
337 update_movie_state();
342 void update_movie_state()
344 auto& _status
= win
->get_emustatus();
346 std::ostringstream x
;
347 x
<< movb
.get_movie().get_current_frame() << "(";
348 if(location_special
== SPECIAL_FRAME_START
)
350 else if(location_special
== SPECIAL_SAVEPOINT
)
352 else if(location_special
== SPECIAL_FRAME_VIDEO
)
355 x
<< movb
.get_movie().next_poll_number();
356 x
<< ";" << movb
.get_movie().get_lag_frames() << ")/" << movb
.get_movie().get_frame_count();
357 _status
["Frame"] = x
.str();
360 std::ostringstream x
;
361 if(movb
.get_movie().readonly_mode())
365 if(av_snooper::dump_in_progress())
367 _status
["Flags"] = x
.str();
369 for(auto i
= memory_watches
.begin(); i
!= memory_watches
.end(); i
++) {
371 _status
["M[" + i
->first
+ "]"] = evaluate_watch(i
->second
);
376 if(movb
.get_movie().readonly_mode())
377 c
= movb
.get_movie().get_controls();
379 c
= curcontrols
^ autoheld_controls
;
380 for(unsigned i
= 0; i
< 8; i
++) {
381 unsigned pindex
= controller_index_by_logical(i
);
382 unsigned port
= pindex
>> 2;
383 unsigned dev
= pindex
& 3;
384 auto ctype
= controller_type_by_logical(i
);
385 std::ostringstream x
;
388 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_LEFT
) ? "l" : " ");
389 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_RIGHT
) ? "r" : " ");
390 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_UP
) ? "u" : " ");
391 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_DOWN
) ? "d" : " ");
392 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_A
) ? "A" : " ");
393 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_B
) ? "B" : " ");
394 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_X
) ? "X" : " ");
395 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_Y
) ? "Y" : " ");
396 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_L
) ? "L" : " ");
397 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_R
) ? "R" : " ");
398 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_START
) ? "S" : " ");
399 x
<< (c(port
, dev
, SNES_DEVICE_ID_JOYPAD_SELECT
) ? "s" : " ");
402 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_X
) << " ";
403 x
<< c(port
, dev
, SNES_DEVICE_ID_MOUSE_Y
) << " ";
404 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_LEFT
) ? "L" : " ");
405 x
<< (c(port
, dev
, SNES_DEVICE_ID_MOUSE_RIGHT
) ? "R" : " ");
408 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_X
) << " ";
409 x
<< c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_Y
) << " ";
410 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER
) ? "T" : " ");
411 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR
) ? "C" : " ");
412 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_TURBO
) ? "t" : " ");
413 x
<< (c(port
, dev
, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE
) ? "P" : " ");
416 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_X
) << " ";
417 x
<< c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_Y
) << " ";
418 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_START
) ? "T" : " ");
419 x
<< (c(port
, dev
, SNES_DEVICE_ID_JUSTIFIER_TRIGGER
) ? "S" : " ");
424 char y
[3] = {'P', 0, 0};
426 _status
[std::string(y
)] = x
.str();
431 class my_interface
: public SNES::Interface
433 string
path(SNES::Cartridge::Slot slot
, const string
&hint
)
435 return static_cast<std::string
>(firmwarepath_setting
).c_str();
438 void video_refresh(const uint16_t *data
, bool hires
, bool interlace
, bool overscan
)
440 if(stepping_into_save
)
441 win
->message("Got video refresh in runtosave, expect desyncs!");
442 video_refresh_done
= true;
443 bool region
= (SNES::system
.region() == SNES::System::Region::PAL
);
444 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
445 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
446 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
447 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
448 lcscreen
ls(data
, hires
, interlace
, overscan
, region
);
450 location_special
= SPECIAL_FRAME_VIDEO
;
451 update_movie_state();
452 redraw_framebuffer(win
);
453 uint32_t fps_n
, fps_d
;
461 av_snooper::frame(ls
, fps_n
, fps_d
, win
);
464 void audio_sample(int16_t l_sample
, int16_t r_sample
)
466 uint16_t _l
= l_sample
;
467 uint16_t _r
= r_sample
;
468 win
->play_audio_sample(_l
+ 32768, _r
+ 32768);
469 av_snooper::sample(_l
, _r
, win
);
472 void audio_sample(uint16_t l_sample
, uint16_t r_sample
)
474 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
475 win
->play_audio_sample(l_sample
+ 32768, r_sample
+ 32768);
476 av_snooper::sample(l_sample
, r_sample
, win
);
479 int16_t input_poll(bool port
, SNES::Input::Device device
, unsigned index
, unsigned id
)
482 x
= movb
.input_poll(port
, index
, id
);
483 //if(id == SNES_DEVICE_ID_JOYPAD_START)
484 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
485 lua_callback_snoop_input(port
? 1 : 0, index
, id
, x
, win
);
492 class quit_emulator_cmd
: public command
495 quit_emulator_cmd() throw(std::bad_alloc
) : command("quit-emulator") {}
496 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
498 if(args
== "/y" || win
->modal_message("Really quit?", true)) {
499 amode
= ADVANCE_QUIT
;
504 std::string
get_short_help() throw(std::bad_alloc
) { return "Quit the emulator"; }
505 std::string
get_long_help() throw(std::bad_alloc
)
507 return "Syntax: quit-emulator [/y]\n"
508 "Quits emulator (/y => don't ask for confirmation).\n";
512 class pause_emulator_cmd
: public command
515 pause_emulator_cmd() throw(std::bad_alloc
) : command("pause-emulator") {}
516 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
519 throw std::runtime_error("This command does not take parameters");
520 if(amode
!= ADVANCE_AUTO
) {
521 amode
= ADVANCE_AUTO
;
524 win
->message("Unpaused");
527 cancel_advance
= false;
528 amode
= ADVANCE_PAUSE
;
529 win
->message("Paused");
532 std::string
get_short_help() throw(std::bad_alloc
) { return "(Un)pause the emulator"; }
533 std::string
get_long_help() throw(std::bad_alloc
)
535 return "Syntax: pause-emulator\n"
536 "(Un)pauses the emulator.\n";
540 class padvance_frame_cmd
: public command
543 padvance_frame_cmd() throw(std::bad_alloc
) : command("+advance-frame") {}
544 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
547 throw std::runtime_error("This command does not take parameters");
548 amode
= ADVANCE_FRAME
;
549 cancel_advance
= false;
550 advanced_once
= false;
554 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one frame"; }
555 std::string
get_long_help() throw(std::bad_alloc
)
557 return "Syntax: +advance-frame\n"
558 "Advances the emulation by one frame.\n";
562 class nadvance_frame_cmd
: public command
565 nadvance_frame_cmd() throw(std::bad_alloc
) : command("-advance-frame") {}
566 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
569 throw std::runtime_error("This command does not take parameters");
570 cancel_advance
= true;
576 class padvance_poll_cmd
: public command
579 padvance_poll_cmd() throw(std::bad_alloc
) : command("+advance-poll") {}
580 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
583 throw std::runtime_error("This command does not take parameters");
584 amode
= ADVANCE_SUBFRAME
;
585 cancel_advance
= false;
586 advanced_once
= false;
590 std::string
get_short_help() throw(std::bad_alloc
) { return "Advance one subframe"; }
591 std::string
get_long_help() throw(std::bad_alloc
)
593 return "Syntax: +advance-poll\n"
594 "Advances the emulation by one subframe.\n";
598 class nadvance_poll_cmd
: public command
601 nadvance_poll_cmd() throw(std::bad_alloc
) : command("-advance-poll") {}
603 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
606 throw std::runtime_error("This command does not take parameters");
607 cancel_advance
= true;
613 class advance_skiplag_cmd
: public command
616 advance_skiplag_cmd() throw(std::bad_alloc
) : command("advance-skiplag") {}
617 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
620 throw std::runtime_error("This command does not take parameters");
621 amode
= ADVANCE_SKIPLAG
;
625 std::string
get_short_help() throw(std::bad_alloc
) { return "Skip to next poll"; }
626 std::string
get_long_help() throw(std::bad_alloc
)
628 return "Syntax: advance-skiplag\n"
629 "Advances the emulation to the next poll.\n";
633 class reset_cmd
: public command
636 reset_cmd() throw(std::bad_alloc
) : command("reset") {}
637 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
640 throw std::runtime_error("This command does not take parameters");
641 pending_reset_cycles
= 0;
643 std::string
get_short_help() throw(std::bad_alloc
) { return "Reset the SNES"; }
644 std::string
get_long_help() throw(std::bad_alloc
)
646 return "Syntax: reset\n"
647 "Resets the SNES in beginning of the next frame.\n";
651 class load_state_cmd
: public command
654 load_state_cmd() throw(std::bad_alloc
) : command("load-state") {}
655 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
658 throw std::runtime_error("Filename required");
659 mark_pending_load(args
, LOAD_STATE_RW
);
661 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
662 std::string
get_long_help() throw(std::bad_alloc
)
664 return "Syntax: load-state <file>\n"
665 "Loads SNES state from <file> in Read/Write mode\n";
669 class load_readonly_cmd
: public command
672 load_readonly_cmd() throw(std::bad_alloc
) : command("load-readonly") {}
673 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
676 throw std::runtime_error("Filename required");
677 mark_pending_load(args
, LOAD_STATE_RO
);
679 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
680 std::string
get_long_help() throw(std::bad_alloc
)
682 return "Syntax: load-readonly <file>\n"
683 "Loads SNES state from <file> in Read-only mode\n";
687 class load_preserve_cmd
: public command
690 load_preserve_cmd() throw(std::bad_alloc
) : command("load-preserve") {}
691 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
694 throw std::runtime_error("Filename required");
695 mark_pending_load(args
, LOAD_STATE_PRESERVE
);
697 std::string
get_short_help() throw(std::bad_alloc
) { return "Load state"; }
698 std::string
get_long_help() throw(std::bad_alloc
)
700 return "Syntax: load-preserve <file>\n"
701 "Loads SNES state from <file> preserving input\n";
705 class load_movie_cmd
: public command
708 load_movie_cmd() throw(std::bad_alloc
) : command("load-movie") {}
709 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
712 throw std::runtime_error("Filename required");
713 mark_pending_load(args
, LOAD_STATE_MOVIE
);
715 std::string
get_short_help() throw(std::bad_alloc
) { return "Load movie"; }
716 std::string
get_long_help() throw(std::bad_alloc
)
718 return "Syntax: load-movie <file>\n"
719 "Loads movie from <file>\n";
723 class save_state_cmd
: public command
726 save_state_cmd() throw(std::bad_alloc
) : command("save-state") {}
727 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
730 throw std::runtime_error("Filename required");
731 mark_pending_save(args
, SAVE_STATE
);
734 std::string
get_short_help() throw(std::bad_alloc
) { return "Save state"; }
735 std::string
get_long_help() throw(std::bad_alloc
)
737 return "Syntax: save-state <file>\n"
738 "Saves SNES state to <file>\n";
742 class save_movie_cmd
: public command
745 save_movie_cmd() throw(std::bad_alloc
) : command("save-movie") {}
746 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
749 throw std::runtime_error("Filename required");
750 mark_pending_save(args
, SAVE_MOVIE
);
752 std::string
get_short_help() throw(std::bad_alloc
) { return "Save movie"; }
753 std::string
get_long_help() throw(std::bad_alloc
)
755 return "Syntax: save-movie <file>\n"
756 "Saves movie to <file>\n";
760 class set_rwmode_cmd
: public command
763 set_rwmode_cmd() throw(std::bad_alloc
) : command("set-rwmode") {}
764 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
767 throw std::runtime_error("This command does not take parameters");
768 movb
.get_movie().readonly_mode(false);
769 lua_callback_do_readwrite(win
);
770 update_movie_state();
771 win
->notify_screen_update();
773 std::string
get_short_help() throw(std::bad_alloc
) { return "Switch to read/write mode"; }
774 std::string
get_long_help() throw(std::bad_alloc
)
776 return "Syntax: set-rwmode\n"
777 "Switches to read/write mode\n";
781 class repainter
: public command
784 repainter() throw(std::bad_alloc
) : command("repaint") {}
785 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
788 throw std::runtime_error("This command does not take parameters");
789 redraw_framebuffer(win
);
791 std::string
get_short_help() throw(std::bad_alloc
) { return "Redraw the screen"; }
792 std::string
get_long_help() throw(std::bad_alloc
)
794 return "Syntax: repaint\n"
795 "Redraws the screen\n";
799 class set_gamename_cmd
: public command
802 set_gamename_cmd() throw(std::bad_alloc
) : command("set-gamename") {}
803 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
805 our_movie
.gamename
= args
;
806 out(win
) << "Game name changed to '" << our_movie
.gamename
<< "'" << std::endl
;
808 std::string
get_short_help() throw(std::bad_alloc
) { return "Set the game name"; }
809 std::string
get_long_help() throw(std::bad_alloc
)
811 return "Syntax: set-gamename <name>\n"
812 "Sets the game name to <name>\n";
816 class add_watch_command
: public command
819 add_watch_command() throw(std::bad_alloc
) : command("add-watch") {}
820 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
822 tokensplitter
t(args
);
823 std::string name
= t
;
824 if(name
== "" || t
.tail() == "")
825 throw std::runtime_error("syntax: add-watch <name> <expr>");
826 std::cerr
<< "Add watch: '" << name
<< "'" << std::endl
;
827 memory_watches
[name
] = t
.tail();
828 update_movie_state();
830 std::string
get_short_help() throw(std::bad_alloc
) { return "Add a memory watch"; }
831 std::string
get_long_help() throw(std::bad_alloc
)
833 return "Syntax: add-watch <name> <expression>\n"
834 "Adds a new memory watch\n";
838 class remove_watch_command
: public command
841 remove_watch_command() throw(std::bad_alloc
) : command("remove-watch") {}
842 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
844 tokensplitter
t(args
);
845 std::string name
= t
;
846 if(name
== "" || t
.tail() != "") {
847 out(win
) << "syntax: remove-watch <name>" << std::endl
;
850 std::cerr
<< "Erase watch: '" << name
<< "'" << std::endl
;
851 memory_watches
.erase(name
);
852 auto& _status
= win
->get_emustatus();
853 _status
.erase("M[" + name
+ "]");
854 update_movie_state(); }
855 std::string
get_short_help() throw(std::bad_alloc
) { return "Remove a memory watch"; }
856 std::string
get_long_help() throw(std::bad_alloc
)
858 return "Syntax: remove-watch <name>\n"
859 "Removes a memory watch\n";
863 class test_1
: public command
866 test_1() throw(std::bad_alloc
) : command("test-1") {}
867 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
869 framebuffer
= screen_nosignal
;
870 redraw_framebuffer(win
);
874 class test_2
: public command
877 test_2() throw(std::bad_alloc
) : command("test-2") {}
878 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
880 framebuffer
= screen_corrupt
;
881 redraw_framebuffer(win
);
885 class test_3
: public command
888 test_3() throw(std::bad_alloc
) : command("test-3") {}
889 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
895 class screenshot_command
: public command
898 screenshot_command() throw(std::bad_alloc
) : command("take-screenshot") {}
899 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
902 throw std::runtime_error("Filename required");
903 framebuffer
.save_png(args
);
904 out(win
) << "Saved PNG screenshot" << std::endl
;
906 std::string
get_short_help() throw(std::bad_alloc
) { return "Takes a screenshot"; }
907 std::string
get_long_help() throw(std::bad_alloc
)
909 return "Syntax: take-screenshot <file>\n"
910 "Saves screenshot to PNG file <file>\n";
914 class mouse_button_handler
: public command
917 mouse_button_handler() throw(std::bad_alloc
) : command("mouse_button") {}
918 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
920 tokensplitter
t(args
);
924 int _x
= atoi(x
.c_str());
925 int _y
= atoi(y
.c_str());
926 int _b
= atoi(b
.c_str());
927 if(_b
& ~prev_mouse_mask
& 1)
928 send_analog_input(_x
, _y
, 0);
929 if(_b
& ~prev_mouse_mask
& 2)
930 send_analog_input(_x
, _y
, 1);
931 if(_b
& ~prev_mouse_mask
& 4)
932 send_analog_input(_x
, _y
, 2);
933 prev_mouse_mask
= _b
;
937 class button_action
: public command
940 button_action(const std::string
& cmd
, int _type
, unsigned _controller
, std::string _button
)
941 throw(std::bad_alloc
)
946 controller
= _controller
;
949 ~button_action() throw() {}
950 void invoke(const std::string
& args
, window
* win
) throw(std::bad_alloc
, std::runtime_error
)
953 throw std::runtime_error("This command does not take parameters");
955 if(!buttonmap
.count(button
))
957 auto i
= buttonmap
[button
];
958 do_button_action(i
.first
, i
.second
, (type
!= 1) ? 1 : 0, (type
== 2));
959 update_movie_state();
960 win
->notify_screen_update();
962 std::string
get_short_help() throw(std::bad_alloc
)
964 return "Press/Unpress button";
966 std::string
get_long_help() throw(std::bad_alloc
)
968 return "Syntax: " + commandn
+ "\n"
969 "Presses/Unpresses button\n";
971 std::string commandn
;
977 class button_action_helper
980 button_action_helper()
982 for(size_t i
= 0; i
< sizeof(buttonnames
) / sizeof(buttonnames
[0]); ++i
)
983 for(int j
= 0; j
< 3; ++j
)
984 for(unsigned k
= 0; k
< 8; ++k
) {
985 std::ostringstream x
, y
;
1000 y
<< buttonnames
[i
];
1001 new button_action(x
.str(), j
, k
, y
.str());
1006 //If there is a pending load, perform it.
1009 if(pending_load
!= "") {
1010 do_load_state(win
, pending_load
, loadmode
);
1011 redraw_framebuffer(win
);
1013 pending_reset_cycles
= -1;
1014 amode
= ADVANCE_AUTO
;
1017 if(!system_corrupt
) {
1018 location_special
= SPECIAL_SAVEPOINT
;
1019 update_movie_state();
1020 win
->notify_screen_update();
1028 //If there are pending saves, perform them.
1031 if(!queued_saves
.empty()) {
1032 stepping_into_save
= true;
1033 SNES::system
.runtosave();
1034 stepping_into_save
= false;
1035 for(auto i
= queued_saves
.begin(); i
!= queued_saves
.end(); i
++)
1036 do_save_state(win
, *i
);
1038 queued_saves
.clear();
1041 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1042 bool handle_reset(long cycles
)
1045 win
->message("SNES reset");
1046 SNES::system
.reset();
1047 framebuffer
= screen_nosignal
;
1048 lua_callback_do_reset(win
);
1049 redraw_framebuffer(win
);
1050 } else if(cycles
> 0) {
1051 video_refresh_done
= false;
1052 long cycles_executed
= 0;
1053 out(win
) << "Executing delayed reset... This can take some time!" << std::endl
;
1054 while(cycles_executed
< cycles
&& !video_refresh_done
) {
1055 SNES::cpu
.op_step();
1058 if(!video_refresh_done
)
1059 out(win
) << "SNES reset (delayed " << cycles_executed
<< ")" << std::endl
;
1061 out(win
) << "SNES reset (forced at " << cycles_executed
<< ")" << std::endl
;
1062 SNES::system
.reset();
1063 framebuffer
= screen_nosignal
;
1064 lua_callback_do_reset(win
);
1065 redraw_framebuffer(win
);
1066 if(video_refresh_done
) {
1067 to_wait_frame(get_ticks_msec());
1074 bool handle_corrupt()
1078 while(system_corrupt
) {
1079 redraw_framebuffer(win
);
1084 if(amode
== ADVANCE_QUIT
)
1090 void print_controller_mappings()
1092 for(unsigned i
= 0; i
< 8; i
++) {
1093 std::string type
= "unknown";
1094 if(controller_type_by_logical(i
) == DT_NONE
)
1095 type
= "disconnected";
1096 if(controller_type_by_logical(i
) == DT_GAMEPAD
)
1098 if(controller_type_by_logical(i
) == DT_MOUSE
)
1100 if(controller_type_by_logical(i
) == DT_SUPERSCOPE
)
1101 type
= "superscope";
1102 if(controller_type_by_logical(i
) == DT_JUSTIFIER
)
1104 out(win
) << "Physical controller mapping: Logical " << (i
+ 1) << " is physical " <<
1105 controller_index_by_logical(i
) << " (" << type
<< ")" << std::endl
;
1110 void main_loop(window
* _win
, struct loaded_rom
& rom
, struct moviefile
& initial
) throw(std::bad_alloc
,
1113 //Basic initialization.
1115 init_special_screens();
1118 auto old_inteface
= SNES::system
.interface
;
1119 SNES::system
.interface
= &intrf
;
1120 status
= &win
->get_emustatus();
1122 //Load our given movie.
1123 bool first_round
= false;
1124 bool just_did_loadstate
= false;
1126 do_load_state(win
, initial
, LOAD_STATE_DEFAULT
);
1127 first_round
= our_movie
.is_savestate
;
1128 just_did_loadstate
= first_round
;
1129 } catch(std::bad_alloc
& e
) {
1131 } catch(std::exception
& e
) {
1132 win
->message(std::string("FATAL: Can't load initial state: ") + e
.what());
1137 lua_callback_startup(win
);
1139 //print_controller_mappings();
1140 av_snooper::add_dump_notifier(dumpwatch
);
1141 win
->set_main_surface(main_screen
);
1142 redraw_framebuffer(win
);
1144 amode
= ADVANCE_PAUSE
;
1145 while(amode
!= ADVANCE_QUIT
) {
1146 if(handle_corrupt()) {
1147 first_round
= our_movie
.is_savestate
;
1148 just_did_loadstate
= true;
1151 long resetcycles
= -1;
1152 ack_frame_tick(get_ticks_msec());
1153 if(amode
== ADVANCE_SKIPLAG_PENDING
)
1154 amode
= ADVANCE_SKIPLAG
;
1157 resetcycles
= movb
.new_frame_starting(amode
== ADVANCE_SKIPLAG
);
1158 if(amode
== ADVANCE_QUIT
)
1160 bool delayed_reset
= (resetcycles
> 0);
1161 pending_reset_cycles
= -1;
1162 if(!handle_reset(resetcycles
)) {
1165 if(!delayed_reset
) {
1169 first_round
= our_movie
.is_savestate
;
1170 amode
= ADVANCE_PAUSE
;
1171 just_did_loadstate
= first_round
;
1175 if(just_did_loadstate
) {
1176 if(amode
== ADVANCE_QUIT
)
1178 amode
= ADVANCE_PAUSE
;
1179 redraw_framebuffer(win
);
1183 just_did_loadstate
= false;
1186 if(amode
== ADVANCE_AUTO
)
1187 win
->wait_msec(to_wait_frame(get_ticks_msec()));
1188 first_round
= false;
1190 av_snooper::end(win
);
1191 SNES::system
.interface
= old_inteface
;