Since window is singleton anyway, get rid of window* parameters
[lsnes.git] / mainloop.cpp
blobf4d1620ab835f3bb4cbbee7ce0927d81c01fd709
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 //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.
79 int loadmode;
80 std::string pending_load;
81 //Queued saves (all savestates).
82 std::set<std::string> queued_saves;
83 bool stepping_into_save;
84 //Current controls.
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.
94 int location_special;
95 //Few settings.
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)) {
101 x -= 256;
102 y -= (framebuffer.height / 2);
103 } else {
104 x /= 2;
105 y /= 2;
107 int aindex = controller_index_by_analog(index);
108 if(aindex < 0) {
109 window::out() << "No analog controller in slot #" << (index + 1) << std::endl;
110 return;
112 curcontrols(aindex >> 2, aindex & 3, 0) = x;
113 curcontrols(aindex >> 2, aindex & 3, 1) = y;
118 class firmware_path_setting : public setting
120 public:
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;
148 private:
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();
158 if(subframe) {
159 if(amode == ADVANCE_SUBFRAME) {
160 if(!cancel_advance && !advanced_once) {
161 window::wait_msec(advance_timeout_first);
162 advanced_once = true;
164 if(cancel_advance) {
165 amode = ADVANCE_PAUSE;
166 cancel_advance = false;
168 window::paused(amode == ADVANCE_PAUSE);
169 } else if(amode == ADVANCE_FRAME) {
171 } else {
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();
179 } else {
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;
188 if(cancel_advance) {
189 amode = ADVANCE_PAUSE;
190 cancel_advance = false;
192 window::paused(amode == ADVANCE_PAUSE);
193 } else {
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);
213 return tmp;
216 namespace
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",
222 "pause", "turbo"
225 void init_buttonmap()
227 static int done = 0;
228 if(done)
229 return;
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);
236 done = 1;
239 //Do button action.
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);
244 int bid = -1;
245 switch(p) {
246 case DT_NONE:
247 window::out() << "No such controller #" << (ui_id + 1) << std::endl;
248 return;
249 case DT_GAMEPAD:
250 switch(button) {
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;
263 default:
264 window::out() << "Invalid button for gamepad" << std::endl;
265 return;
267 break;
268 case DT_MOUSE:
269 switch(button) {
270 case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
271 case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
272 default:
273 window::out() << "Invalid button for mouse" << std::endl;
274 return;
276 break;
277 case DT_JUSTIFIER:
278 switch(button) {
279 case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
280 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
281 default:
282 window::out() << "Invalid button for justifier" << std::endl;
283 return;
285 break;
286 case DT_SUPERSCOPE:
287 switch(button) {
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;
292 default:
293 window::out() << "Invalid button for superscope" << std::endl;
294 return;
296 break;
298 if(do_xor)
299 autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
300 else
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)
308 loadmode = 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);
321 return;
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();
337 } dumpwatch;
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)
347 x << "0";
348 else if(location_special == SPECIAL_SAVEPOINT)
349 x << "S";
350 else if(location_special == SPECIAL_FRAME_VIDEO)
351 x << "V";
352 else
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())
360 x << "PLAY ";
361 else
362 x << "REC ";
363 if(av_snooper::dump_in_progress())
364 x << "CAP ";
365 _status["Flags"] = x.str();
367 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
368 try {
369 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
370 } catch(...) {
373 controls_t c;
374 if(movb.get_movie().readonly_mode())
375 c = movb.get_movie().get_controls();
376 else
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;
384 switch(ctype) {
385 case DT_GAMEPAD:
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" : " ");
398 break;
399 case DT_MOUSE:
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" : " ");
404 break;
405 case DT_SUPERSCOPE:
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" : " ");
412 break;
413 case DT_JUSTIFIER:
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" : " ");
418 break;
419 case DT_NONE:
420 continue;
422 char y[3] = {'P', 0, 0};
423 y[1] = 49 + i;
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);
447 framebuffer = ls;
448 location_special = SPECIAL_FRAME_VIDEO;
449 update_movie_state();
450 redraw_framebuffer();
451 uint32_t fps_n, fps_d;
452 if(region) {
453 fps_n = 322445;
454 fps_d = 6448;
455 } else {
456 fps_n = 10738636;
457 fps_d = 178683;
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)
479 int16_t x;
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);
484 return x;
488 namespace
490 class quit_emulator_cmd : public command
492 public:
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";
508 } quitemu;
510 class pause_emulator_cmd : public command
512 public:
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)
516 if(args != "")
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");
523 } else {
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";
536 } pauseemu;
538 class padvance_frame_cmd : public command
540 public:
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)
544 if(args != "")
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";
558 } padvancef;
560 class nadvance_frame_cmd : public command
562 public:
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)
566 if(args != "")
567 throw std::runtime_error("This command does not take parameters");
568 cancel_advance = true;
569 window::cancel_wait();
570 window::paused(false);
572 } nadvancef;
574 class padvance_poll_cmd : public command
576 public:
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)
580 if(args != "")
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";
594 } padvancep;
596 class nadvance_poll_cmd : public command
598 public:
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)
603 if(args != "")
604 throw std::runtime_error("This command does not take parameters");
605 cancel_advance = true;
606 window::cancel_wait();
607 window::paused(false);
609 } nadvancep;
611 class advance_skiplag_cmd : public command
613 public:
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)
617 if(args != "")
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";
629 } skiplagc;
631 class reset_cmd : public command
633 public:
634 reset_cmd() throw(std::bad_alloc) : command("reset") {}
635 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
637 if(args != "")
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";
647 } resetc;
649 class load_state_cmd : public command
651 public:
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)
655 if(args == "")
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";
665 } loadstatec;
667 class load_readonly_cmd : public command
669 public:
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)
673 if(args == "")
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";
683 } loadreadonlyc;
685 class load_preserve_cmd : public command
687 public:
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)
691 if(args == "")
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";
701 } loadpreservec;
703 class load_movie_cmd : public command
705 public:
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)
709 if(args == "")
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";
719 } loadmoviec;
721 class save_state_cmd : public command
723 public:
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)
727 if(args == "")
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";
738 } savestatec;
740 class save_movie_cmd : public command
742 public:
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)
746 if(args == "")
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";
756 } savemoviec;
758 class set_rwmode_cmd : public command
760 public:
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)
764 if(args != "")
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";
777 } setrwc;
779 class repainter : public command
781 public:
782 repainter() throw(std::bad_alloc) : command("repaint") {}
783 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
785 if(args != "")
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";
795 } repaintc;
797 class set_gamename_cmd : public command
799 public:
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";
812 } setnamec;
814 class add_watch_command : public command
816 public:
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";
834 } addwatchc;
836 class remove_watch_command : public command
838 public:
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;
846 return;
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";
859 } removewatchc;
861 class test_1 : public command
863 public:
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();
870 } test1c;
872 class test_2 : public command
874 public:
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();
881 } test2c;
883 class test_3 : public command
885 public:
886 test_3() throw(std::bad_alloc) : command("test-3") {}
887 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
889 while(1);
891 } test3c;
893 class screenshot_command : public command
895 public:
896 screenshot_command() throw(std::bad_alloc) : command("take-screenshot") {}
897 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
899 if(args == "")
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";
910 } screenshotc;
912 class mouse_button_handler : public command
914 public:
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);
919 std::string x = t;
920 std::string y = t;
921 std::string b = t;
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;
933 } mousebuttonh;
935 class button_action : public command
937 public:
938 button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
939 throw(std::bad_alloc)
940 : command(cmd)
942 commandn = cmd;
943 type = _type;
944 controller = _controller;
945 button = _button;
947 ~button_action() throw() {}
948 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
950 if(args != "")
951 throw std::runtime_error("This command does not take parameters");
952 init_buttonmap();
953 if(!buttonmap.count(button))
954 return;
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;
970 unsigned controller;
971 int type;
972 std::string button;
975 class button_action_helper
977 public:
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;
984 switch(j) {
985 case 0:
986 x << "+controller";
987 break;
988 case 1:
989 x << "-controller";
990 break;
991 case 2:
992 x << "controllerh";
993 break;
995 x << (k + 1);
996 x << buttonnames[i];
997 y << (k + 1);
998 y << buttonnames[i];
999 new button_action(x.str(), j, k, y.str());
1002 } bah;
1004 //If there is a pending load, perform it.
1005 bool handle_load()
1007 if(pending_load != "") {
1008 do_load_state(pending_load, loadmode);
1009 redraw_framebuffer();
1010 pending_load = "";
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();
1021 return true;
1023 return false;
1026 //If there are pending saves, perform them.
1027 void handle_saves()
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++)
1034 do_save_state(*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)
1042 if(cycles == 0) {
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();
1054 cycles_executed++;
1056 if(!video_refresh_done)
1057 window::out() << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
1058 else
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());
1066 return false;
1069 return true;
1072 bool handle_corrupt()
1074 if(!system_corrupt)
1075 return false;
1076 while(system_corrupt) {
1077 redraw_framebuffer();
1078 window::cancel_wait();
1079 window::paused(true);
1080 window::poll_inputs();
1081 handle_load();
1082 if(amode == ADVANCE_QUIT)
1083 return true;
1085 return true;
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)
1095 type = "gamepad";
1096 if(controller_type_by_logical(i) == DT_MOUSE)
1097 type = "mouse";
1098 if(controller_type_by_logical(i) == DT_SUPERSCOPE)
1099 type = "superscope";
1100 if(controller_type_by_logical(i) == DT_JUSTIFIER)
1101 type = "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,
1109 std::runtime_error)
1111 //Basic initialization.
1112 init_special_screens();
1113 our_rom = &rom;
1114 my_interface intrf;
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;
1122 try {
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) {
1127 OOM_panic();
1128 } catch(std::exception& e) {
1129 window::message(std::string("FATAL: Can't load initial state: ") + e.what());
1130 window::fatal_error();
1131 return;
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;
1146 continue;
1148 long resetcycles = -1;
1149 ack_frame_tick(get_ticks_msec());
1150 if(amode == ADVANCE_SKIPLAG_PENDING)
1151 amode = ADVANCE_SKIPLAG;
1153 if(!first_round) {
1154 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
1155 if(amode == ADVANCE_QUIT)
1156 break;
1157 bool delayed_reset = (resetcycles > 0);
1158 pending_reset_cycles = -1;
1159 if(!handle_reset(resetcycles)) {
1160 continue;
1162 if(!delayed_reset) {
1163 handle_saves();
1165 if(handle_load()) {
1166 first_round = our_movie.is_savestate;
1167 amode = ADVANCE_PAUSE;
1168 just_did_loadstate = first_round;
1169 continue;
1172 if(just_did_loadstate) {
1173 if(amode == ADVANCE_QUIT)
1174 break;
1175 amode = ADVANCE_PAUSE;
1176 redraw_framebuffer();
1177 window::cancel_wait();
1178 window::paused(true);
1179 window::poll_inputs();
1180 just_did_loadstate = false;
1182 SNES::system.run();
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;