Allow forcing maximum number of frames per segment
[lsnes.git] / mainloop.cpp
blobd483fb5bfb87bb4ac33ffc331fcce407811a311e
1 #include "mainloop.hpp"
2 #include "avsnoop.hpp"
3 #include "command.hpp"
4 #include "controller.hpp"
5 #include "framebuffer.hpp"
6 #include "moviedata.hpp"
7 #include <iomanip>
8 #include "framerate.hpp"
9 #include "memorywatch.hpp"
10 #include "lua.hpp"
11 #include "rrdata.hpp"
12 #include "rom.hpp"
13 #include "movie.hpp"
14 #include "moviefile.hpp"
15 #include "render.hpp"
16 #include "window.hpp"
17 #include "settings.hpp"
18 #include "rom.hpp"
19 #include "movie.hpp"
20 #include "window.hpp"
21 #include <cassert>
22 #include <sstream>
23 #include "memorymanip.hpp"
24 #include <iostream>
25 #include <set>
26 #include "lsnes.hpp"
27 #include <sys/time.h>
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();
56 namespace
58 enum advance_mode
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.
69 //Memory watches.
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.
74 bool advanced_once;
75 bool cancel_advance;
76 //Handle to the graphics system.
77 window* win;
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.
81 int loadmode;
82 std::string pending_load;
83 //Queued saves (all savestates).
84 std::set<std::string> queued_saves;
85 bool stepping_into_save;
86 //Current controls.
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.
96 int location_special;
97 //Few settings.
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)) {
103 x -= 256;
104 y -= (framebuffer.height / 2);
105 } else {
106 x /= 2;
107 y /= 2;
109 int aindex = controller_index_by_analog(index);
110 if(aindex < 0) {
111 out(win) << "No analog controller in slot #" << (index + 1) << std::endl;
112 return;
114 curcontrols(aindex >> 2, aindex & 3, 0) = x;
115 curcontrols(aindex >> 2, aindex & 3, 1) = y;
120 class firmware_path_setting : public setting
122 public:
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;
150 private:
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);
160 if(subframe) {
161 if(amode == ADVANCE_SUBFRAME) {
162 if(!cancel_advance && !advanced_once) {
163 win->wait_msec(advance_timeout_first);
164 advanced_once = true;
166 if(cancel_advance) {
167 amode = ADVANCE_PAUSE;
168 cancel_advance = false;
170 win->paused(amode == ADVANCE_PAUSE);
171 } else if(amode == ADVANCE_FRAME) {
173 } else {
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();
181 } else {
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;
190 if(cancel_advance) {
191 amode = ADVANCE_PAUSE;
192 cancel_advance = false;
194 win->paused(amode == ADVANCE_PAUSE);
195 } else {
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();
203 win->poll_inputs();
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);
215 return tmp;
218 namespace
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",
224 "pause", "turbo"
227 void init_buttonmap()
229 static int done = 0;
230 if(done)
231 return;
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);
238 done = 1;
241 //Do button action.
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);
246 int bid = -1;
247 switch(p) {
248 case DT_NONE:
249 out(win) << "No such controller #" << (ui_id + 1) << std::endl;
250 return;
251 case DT_GAMEPAD:
252 switch(button) {
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;
265 default:
266 out(win) << "Invalid button for gamepad" << std::endl;
267 return;
269 break;
270 case DT_MOUSE:
271 switch(button) {
272 case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
273 case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
274 default:
275 out(win) << "Invalid button for mouse" << std::endl;
276 return;
278 break;
279 case DT_JUSTIFIER:
280 switch(button) {
281 case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
282 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
283 default:
284 out(win) << "Invalid button for justifier" << std::endl;
285 return;
287 break;
288 case DT_SUPERSCOPE:
289 switch(button) {
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;
294 default:
295 out(win) << "Invalid button for superscope" << std::endl;
296 return;
298 break;
300 if(do_xor)
301 autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
302 else
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)
310 loadmode = lmode;
311 pending_load = filename;
312 amode = ADVANCE_AUTO;
313 win->cancel_wait();
314 win->paused(false);
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);
323 return;
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();
339 } dumpwatch;
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)
349 x << "0";
350 else if(location_special == SPECIAL_SAVEPOINT)
351 x << "S";
352 else if(location_special == SPECIAL_FRAME_VIDEO)
353 x << "V";
354 else
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())
362 x << "PLAY ";
363 else
364 x << "REC ";
365 if(av_snooper::dump_in_progress())
366 x << "CAP ";
367 _status["Flags"] = x.str();
369 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
370 try {
371 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
372 } catch(...) {
375 controls_t c;
376 if(movb.get_movie().readonly_mode())
377 c = movb.get_movie().get_controls();
378 else
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;
386 switch(ctype) {
387 case DT_GAMEPAD:
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" : " ");
400 break;
401 case DT_MOUSE:
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" : " ");
406 break;
407 case DT_SUPERSCOPE:
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" : " ");
414 break;
415 case DT_JUSTIFIER:
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" : " ");
420 break;
421 case DT_NONE:
422 continue;
424 char y[3] = {'P', 0, 0};
425 y[1] = 49 + i;
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);
449 framebuffer = ls;
450 location_special = SPECIAL_FRAME_VIDEO;
451 update_movie_state();
452 redraw_framebuffer(win);
453 uint32_t fps_n, fps_d;
454 if(region) {
455 fps_n = 322445;
456 fps_d = 6448;
457 } else {
458 fps_n = 10738636;
459 fps_d = 178683;
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)
481 int16_t x;
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);
486 return x;
490 namespace
492 class quit_emulator_cmd : public command
494 public:
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;
500 win->paused(false);
501 win->cancel_wait();
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";
510 } quitemu;
512 class pause_emulator_cmd : public command
514 public:
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)
518 if(args != "")
519 throw std::runtime_error("This command does not take parameters");
520 if(amode != ADVANCE_AUTO) {
521 amode = ADVANCE_AUTO;
522 win->paused(false);
523 win->cancel_wait();
524 win->message("Unpaused");
525 } else {
526 win->cancel_wait();
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";
538 } pauseemu;
540 class padvance_frame_cmd : public command
542 public:
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)
546 if(args != "")
547 throw std::runtime_error("This command does not take parameters");
548 amode = ADVANCE_FRAME;
549 cancel_advance = false;
550 advanced_once = false;
551 win->cancel_wait();
552 win->paused(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";
560 } padvancef;
562 class nadvance_frame_cmd : public command
564 public:
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)
568 if(args != "")
569 throw std::runtime_error("This command does not take parameters");
570 cancel_advance = true;
571 win->cancel_wait();
572 win->paused(false);
574 } nadvancef;
576 class padvance_poll_cmd : public command
578 public:
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)
582 if(args != "")
583 throw std::runtime_error("This command does not take parameters");
584 amode = ADVANCE_SUBFRAME;
585 cancel_advance = false;
586 advanced_once = false;
587 win->cancel_wait();
588 win->paused(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";
596 } padvancep;
598 class nadvance_poll_cmd : public command
600 public:
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)
605 if(args != "")
606 throw std::runtime_error("This command does not take parameters");
607 cancel_advance = true;
608 win->cancel_wait();
609 win->paused(false);
611 } nadvancep;
613 class advance_skiplag_cmd : public command
615 public:
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)
619 if(args != "")
620 throw std::runtime_error("This command does not take parameters");
621 amode = ADVANCE_SKIPLAG;
622 win->cancel_wait();
623 win->paused(false);
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";
631 } skiplagc;
633 class reset_cmd : public command
635 public:
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)
639 if(args != "")
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";
649 } resetc;
651 class load_state_cmd : public command
653 public:
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)
657 if(args == "")
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";
667 } loadstatec;
669 class load_readonly_cmd : public command
671 public:
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)
675 if(args == "")
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";
685 } loadreadonlyc;
687 class load_preserve_cmd : public command
689 public:
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)
693 if(args == "")
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";
703 } loadpreservec;
705 class load_movie_cmd : public command
707 public:
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)
711 if(args == "")
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";
721 } loadmoviec;
723 class save_state_cmd : public command
725 public:
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)
729 if(args == "")
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";
740 } savestatec;
742 class save_movie_cmd : public command
744 public:
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)
748 if(args == "")
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";
758 } savemoviec;
760 class set_rwmode_cmd : public command
762 public:
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)
766 if(args != "")
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";
779 } setrwc;
781 class repainter : public command
783 public:
784 repainter() throw(std::bad_alloc) : command("repaint") {}
785 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
787 if(args != "")
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";
797 } repaintc;
799 class set_gamename_cmd : public command
801 public:
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";
814 } setnamec;
816 class add_watch_command : public command
818 public:
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";
836 } addwatchc;
838 class remove_watch_command : public command
840 public:
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;
848 return;
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";
861 } removewatchc;
863 class test_1 : public command
865 public:
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);
872 } test1c;
874 class test_2 : public command
876 public:
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);
883 } test2c;
885 class test_3 : public command
887 public:
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)
891 while(1);
893 } test3c;
895 class screenshot_command : public command
897 public:
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)
901 if(args == "")
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";
912 } screenshotc;
914 class mouse_button_handler : public command
916 public:
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);
921 std::string x = t;
922 std::string y = t;
923 std::string b = t;
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;
935 } mousebuttonh;
937 class button_action : public command
939 public:
940 button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
941 throw(std::bad_alloc)
942 : command(cmd)
944 commandn = cmd;
945 type = _type;
946 controller = _controller;
947 button = _button;
949 ~button_action() throw() {}
950 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
952 if(args != "")
953 throw std::runtime_error("This command does not take parameters");
954 init_buttonmap();
955 if(!buttonmap.count(button))
956 return;
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;
972 unsigned controller;
973 int type;
974 std::string button;
977 class button_action_helper
979 public:
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;
986 switch(j) {
987 case 0:
988 x << "+controller";
989 break;
990 case 1:
991 x << "-controller";
992 break;
993 case 2:
994 x << "controllerh";
995 break;
997 x << (k + 1);
998 x << buttonnames[i];
999 y << (k + 1);
1000 y << buttonnames[i];
1001 new button_action(x.str(), j, k, y.str());
1004 } bah;
1006 //If there is a pending load, perform it.
1007 bool handle_load()
1009 if(pending_load != "") {
1010 do_load_state(win, pending_load, loadmode);
1011 redraw_framebuffer(win);
1012 pending_load = "";
1013 pending_reset_cycles = -1;
1014 amode = ADVANCE_AUTO;
1015 win->cancel_wait();
1016 win->paused(false);
1017 if(!system_corrupt) {
1018 location_special = SPECIAL_SAVEPOINT;
1019 update_movie_state();
1020 win->notify_screen_update();
1021 win->poll_inputs();
1023 return true;
1025 return false;
1028 //If there are pending saves, perform them.
1029 void handle_saves()
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)
1044 if(cycles == 0) {
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();
1056 cycles_executed++;
1058 if(!video_refresh_done)
1059 out(win) << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
1060 else
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());
1068 return false;
1071 return true;
1074 bool handle_corrupt()
1076 if(!system_corrupt)
1077 return false;
1078 while(system_corrupt) {
1079 redraw_framebuffer(win);
1080 win->cancel_wait();
1081 win->paused(true);
1082 win->poll_inputs();
1083 handle_load();
1084 if(amode == ADVANCE_QUIT)
1085 return true;
1087 return true;
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)
1097 type = "gamepad";
1098 if(controller_type_by_logical(i) == DT_MOUSE)
1099 type = "mouse";
1100 if(controller_type_by_logical(i) == DT_SUPERSCOPE)
1101 type = "superscope";
1102 if(controller_type_by_logical(i) == DT_JUSTIFIER)
1103 type = "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,
1111 std::runtime_error)
1113 //Basic initialization.
1114 win = _win;
1115 init_special_screens();
1116 our_rom = &rom;
1117 my_interface intrf;
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;
1125 try {
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) {
1130 OOM_panic(win);
1131 } catch(std::exception& e) {
1132 win->message(std::string("FATAL: Can't load initial state: ") + e.what());
1133 win->fatal_error();
1134 return;
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);
1143 win->paused(false);
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;
1149 continue;
1151 long resetcycles = -1;
1152 ack_frame_tick(get_ticks_msec());
1153 if(amode == ADVANCE_SKIPLAG_PENDING)
1154 amode = ADVANCE_SKIPLAG;
1156 if(!first_round) {
1157 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
1158 if(amode == ADVANCE_QUIT)
1159 break;
1160 bool delayed_reset = (resetcycles > 0);
1161 pending_reset_cycles = -1;
1162 if(!handle_reset(resetcycles)) {
1163 continue;
1165 if(!delayed_reset) {
1166 handle_saves();
1168 if(handle_load()) {
1169 first_round = our_movie.is_savestate;
1170 amode = ADVANCE_PAUSE;
1171 just_did_loadstate = first_round;
1172 continue;
1175 if(just_did_loadstate) {
1176 if(amode == ADVANCE_QUIT)
1177 break;
1178 amode = ADVANCE_PAUSE;
1179 redraw_framebuffer(win);
1180 win->cancel_wait();
1181 win->paused(true);
1182 win->poll_inputs();
1183 just_did_loadstate = false;
1185 SNES::system.run();
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;