Fix crash on invalid Lua function arguments
[lsnes.git] / mainloop.cpp
blob4eb6f2d3bb10684a6ee32ac2cbb2e09e52d4c016
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 <cassert>
21 #include <sstream>
22 #include "memorymanip.hpp"
23 #include <iostream>
24 #include <set>
25 #include "lsnes.hpp"
26 #include <sys/time.h>
27 #include <snes/snes.hpp>
28 #include <ui-libsnes/libsnes.hpp>
29 #include "framerate.hpp"
31 #define SPECIAL_FRAME_START 0
32 #define SPECIAL_FRAME_VIDEO 1
33 #define SPECIAL_SAVEPOINT 2
34 #define SPECIAL_NONE 3
36 #define BUTTON_LEFT 0 //Gamepad
37 #define BUTTON_RIGHT 1 //Gamepad
38 #define BUTTON_UP 2 //Gamepad
39 #define BUTTON_DOWN 3 //Gamepad
40 #define BUTTON_A 4 //Gamepad
41 #define BUTTON_B 5 //Gamepad
42 #define BUTTON_X 6 //Gamepad
43 #define BUTTON_Y 7 //Gamepad
44 #define BUTTON_L 8 //Gamepad & Mouse
45 #define BUTTON_R 9 //Gamepad & Mouse
46 #define BUTTON_SELECT 10 //Gamepad
47 #define BUTTON_START 11 //Gamepad & Justifier
48 #define BUTTON_TRIGGER 12 //Superscope.
49 #define BUTTON_CURSOR 13 //Superscope & Justifier
50 #define BUTTON_PAUSE 14 //Superscope
51 #define BUTTON_TURBO 15 //Superscope
53 void update_movie_state();
55 namespace
57 enum advance_mode
59 ADVANCE_QUIT, //Quit the emulator.
60 ADVANCE_AUTO, //Normal (possibly slowed down play).
61 ADVANCE_FRAME, //Frame advance.
62 ADVANCE_SUBFRAME, //Subframe advance.
63 ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
64 ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
65 ADVANCE_PAUSE, //Unconditional pause.
68 //Memory watches.
69 std::map<std::string, std::string> memory_watches;
70 //Previous mouse mask.
71 int prev_mouse_mask = 0;
72 //Flags related to repeating advance.
73 bool advanced_once;
74 bool cancel_advance;
75 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
76 enum advance_mode amode;
77 //Mode and filename of pending load, one of LOAD_* constants.
78 int loadmode;
79 std::string pending_load;
80 //Queued saves (all savestates).
81 std::set<std::string> queued_saves;
82 bool stepping_into_save;
83 //Current controls.
84 controls_t curcontrols;
85 controls_t autoheld_controls;
86 //Emulator status area.
87 std::map<std::string, std::string>* status;
88 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
89 long pending_reset_cycles = -1;
90 //Set by every video refresh.
91 bool video_refresh_done;
92 //Special subframe location. One of SPECIAL_* constants.
93 int location_special;
94 //Few settings.
95 numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500);
97 void send_analog_input(int32_t x, int32_t y, unsigned index)
99 if(controller_ismouse_by_analog(index)) {
100 x -= 256;
101 y -= (framebuffer.height / 2);
102 } else {
103 x /= 2;
104 y /= 2;
106 int aindex = controller_index_by_analog(index);
107 if(aindex < 0) {
108 messages << "No analog controller in slot #" << (index + 1) << std::endl;
109 return;
111 curcontrols(aindex >> 2, aindex & 3, 0) = x;
112 curcontrols(aindex >> 2, aindex & 3, 1) = y;
117 class firmware_path_setting : public setting
119 public:
120 firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "./"; default_firmware = true; }
121 void blank() throw(std::bad_alloc, std::runtime_error)
123 _firmwarepath = "./";
124 default_firmware = true;
127 bool is_set() throw()
129 return !default_firmware;
132 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
134 _firmwarepath = value;
135 default_firmware = false;
138 std::string get() throw(std::bad_alloc)
140 return _firmwarepath;
143 operator std::string() throw(std::bad_alloc)
145 return _firmwarepath;
147 private:
148 std::string _firmwarepath;
149 bool default_firmware;
150 } firmwarepath_setting;
152 controls_t movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
154 if(lua_requests_subframe_paint)
155 redraw_framebuffer();
157 if(subframe) {
158 if(amode == ADVANCE_SUBFRAME) {
159 if(!cancel_advance && !advanced_once) {
160 window::wait_msec(advance_timeout_first);
161 advanced_once = true;
163 if(cancel_advance) {
164 amode = ADVANCE_PAUSE;
165 cancel_advance = false;
167 window::paused(amode == ADVANCE_PAUSE);
168 } else if(amode == ADVANCE_FRAME) {
170 } else {
171 window::paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE);
172 cancel_advance = false;
174 if(amode == ADVANCE_SKIPLAG)
175 amode = ADVANCE_AUTO;
176 location_special = SPECIAL_NONE;
177 update_movie_state();
178 } else {
179 if(amode == ADVANCE_SKIPLAG_PENDING)
180 amode = ADVANCE_SKIPLAG;
181 if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
182 if(!cancel_advance) {
183 window::wait_msec(advanced_once ? to_wait_frame(get_ticks_msec()) :
184 advance_timeout_first);
185 advanced_once = true;
187 if(cancel_advance) {
188 amode = ADVANCE_PAUSE;
189 cancel_advance = false;
191 window::paused(amode == ADVANCE_PAUSE);
192 } else {
193 window::paused((amode == ADVANCE_PAUSE));
194 cancel_advance = false;
196 location_special = SPECIAL_FRAME_START;
197 update_movie_state();
199 window::notify_screen_update();
200 window::poll_inputs();
201 if(!subframe && pending_reset_cycles >= 0) {
202 curcontrols(CONTROL_SYSTEM_RESET) = 1;
203 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = pending_reset_cycles / 10000;
204 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = pending_reset_cycles % 10000;
205 } else if(!subframe) {
206 curcontrols(CONTROL_SYSTEM_RESET) = 0;
207 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = 0;
208 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = 0;
210 controls_t tmp = curcontrols ^ autoheld_controls;
211 lua_callback_do_input(tmp, subframe);
212 return tmp;
215 namespace
217 std::map<std::string, std::pair<unsigned, unsigned>> buttonmap;
219 const char* buttonnames[] = {
220 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
221 "pause", "turbo"
224 void init_buttonmap()
226 static int done = 0;
227 if(done)
228 return;
229 for(unsigned i = 0; i < 8; i++)
230 for(unsigned j = 0; j < sizeof(buttonnames) / sizeof(buttonnames[0]); j++) {
231 std::ostringstream x;
232 x << (i + 1) << buttonnames[j];
233 buttonmap[x.str()] = std::make_pair(i, j);
235 done = 1;
238 //Do button action.
239 void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor = false)
241 enum devicetype_t p = controller_type_by_logical(ui_id);
242 int x = controller_index_by_logical(ui_id);
243 int bid = -1;
244 switch(p) {
245 case DT_NONE:
246 messages << "No such controller #" << (ui_id + 1) << std::endl;
247 return;
248 case DT_GAMEPAD:
249 switch(button) {
250 case BUTTON_UP: bid = SNES_DEVICE_ID_JOYPAD_UP; break;
251 case BUTTON_DOWN: bid = SNES_DEVICE_ID_JOYPAD_DOWN; break;
252 case BUTTON_LEFT: bid = SNES_DEVICE_ID_JOYPAD_LEFT; break;
253 case BUTTON_RIGHT: bid = SNES_DEVICE_ID_JOYPAD_RIGHT; break;
254 case BUTTON_A: bid = SNES_DEVICE_ID_JOYPAD_A; break;
255 case BUTTON_B: bid = SNES_DEVICE_ID_JOYPAD_B; break;
256 case BUTTON_X: bid = SNES_DEVICE_ID_JOYPAD_X; break;
257 case BUTTON_Y: bid = SNES_DEVICE_ID_JOYPAD_Y; break;
258 case BUTTON_L: bid = SNES_DEVICE_ID_JOYPAD_L; break;
259 case BUTTON_R: bid = SNES_DEVICE_ID_JOYPAD_R; break;
260 case BUTTON_SELECT: bid = SNES_DEVICE_ID_JOYPAD_SELECT; break;
261 case BUTTON_START: bid = SNES_DEVICE_ID_JOYPAD_START; break;
262 default:
263 messages << "Invalid button for gamepad" << std::endl;
264 return;
266 break;
267 case DT_MOUSE:
268 switch(button) {
269 case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
270 case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
271 default:
272 messages << "Invalid button for mouse" << std::endl;
273 return;
275 break;
276 case DT_JUSTIFIER:
277 switch(button) {
278 case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
279 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
280 default:
281 messages << "Invalid button for justifier" << std::endl;
282 return;
284 break;
285 case DT_SUPERSCOPE:
286 switch(button) {
287 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER; break;
288 case BUTTON_CURSOR: bid = SNES_DEVICE_ID_SUPER_SCOPE_CURSOR; break;
289 case BUTTON_PAUSE: bid = SNES_DEVICE_ID_SUPER_SCOPE_PAUSE; break;
290 case BUTTON_TURBO: bid = SNES_DEVICE_ID_SUPER_SCOPE_TURBO; break;
291 default:
292 messages << "Invalid button for superscope" << std::endl;
293 return;
295 break;
297 if(do_xor)
298 autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
299 else
300 curcontrols((x & 4) ? 1 : 0, x & 3, bid) = newstate;
304 //Do pending load (automatically unpauses).
305 void mark_pending_load(const std::string& filename, int lmode)
307 loadmode = lmode;
308 pending_load = filename;
309 amode = ADVANCE_AUTO;
310 window::cancel_wait();
311 window::paused(false);
314 //Mark pending save (movies save immediately).
315 void mark_pending_save(const std::string& filename, int smode)
317 if(smode == SAVE_MOVIE) {
318 //Just do this immediately.
319 do_save_movie(filename);
320 return;
322 queued_saves.insert(filename);
323 window::message("Pending save on '" + filename + "'");
326 class dump_watch : public av_snooper::dump_notification
328 void dump_starting() throw()
330 update_movie_state();
332 void dump_ending() throw()
334 update_movie_state();
336 } dumpwatch;
339 void update_movie_state()
341 auto& _status = window::get_emustatus();
343 std::ostringstream x;
344 x << movb.get_movie().get_current_frame() << "(";
345 if(location_special == SPECIAL_FRAME_START)
346 x << "0";
347 else if(location_special == SPECIAL_SAVEPOINT)
348 x << "S";
349 else if(location_special == SPECIAL_FRAME_VIDEO)
350 x << "V";
351 else
352 x << movb.get_movie().next_poll_number();
353 x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count();
354 _status["Frame"] = x.str();
357 std::ostringstream x;
358 if(movb.get_movie().readonly_mode())
359 x << "PLAY ";
360 else
361 x << "REC ";
362 if(av_snooper::dump_in_progress())
363 x << "CAP ";
364 _status["Flags"] = x.str();
366 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
367 try {
368 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
369 } catch(...) {
372 controls_t c;
373 if(movb.get_movie().readonly_mode())
374 c = movb.get_movie().get_controls();
375 else
376 c = curcontrols ^ autoheld_controls;
377 for(unsigned i = 0; i < 8; i++) {
378 unsigned pindex = controller_index_by_logical(i);
379 unsigned port = pindex >> 2;
380 unsigned dev = pindex & 3;
381 auto ctype = controller_type_by_logical(i);
382 std::ostringstream x;
383 switch(ctype) {
384 case DT_GAMEPAD:
385 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " ");
386 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " ");
387 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " ");
388 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " ");
389 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " ");
390 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " ");
391 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " ");
392 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " ");
393 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " ");
394 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " ");
395 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " ");
396 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " ");
397 break;
398 case DT_MOUSE:
399 x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " ";
400 x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " ";
401 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " ");
402 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " ");
403 break;
404 case DT_SUPERSCOPE:
405 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " ";
406 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " ";
407 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " ");
408 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " ");
409 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " ");
410 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " ");
411 break;
412 case DT_JUSTIFIER:
413 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " ";
414 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " ";
415 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " ");
416 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " ");
417 break;
418 case DT_NONE:
419 continue;
421 char y[3] = {'P', 0, 0};
422 y[1] = 49 + i;
423 _status[std::string(y)] = x.str();
428 class my_interface : public SNES::Interface
430 string path(SNES::Cartridge::Slot slot, const string &hint)
432 return static_cast<std::string>(firmwarepath_setting).c_str();
435 void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan)
437 if(stepping_into_save)
438 window::message("Got video refresh in runtosave, expect desyncs!");
439 video_refresh_done = true;
440 bool region = (SNES::system.region() == SNES::System::Region::PAL);
441 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
442 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
443 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
444 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
445 lcscreen ls(data, hires, interlace, overscan, region);
446 framebuffer = ls;
447 location_special = SPECIAL_FRAME_VIDEO;
448 update_movie_state();
449 redraw_framebuffer();
450 uint32_t fps_n, fps_d;
451 if(region) {
452 fps_n = 322445;
453 fps_d = 6448;
454 } else {
455 fps_n = 10738636;
456 fps_d = 178683;
458 av_snooper::frame(ls, fps_n, fps_d, true);
461 void audio_sample(int16_t l_sample, int16_t r_sample)
463 uint16_t _l = l_sample;
464 uint16_t _r = r_sample;
465 window::play_audio_sample(_l + 32768, _r + 32768);
466 av_snooper::sample(_l, _r, true);
469 void audio_sample(uint16_t l_sample, uint16_t r_sample)
471 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
472 window::play_audio_sample(l_sample + 32768, r_sample + 32768);
473 av_snooper::sample(l_sample, r_sample, true);
476 int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
478 int16_t x;
479 x = movb.input_poll(port, index, id);
480 //if(id == SNES_DEVICE_ID_JOYPAD_START)
481 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
482 lua_callback_snoop_input(port ? 1 : 0, index, id, x);
483 return x;
487 namespace
489 function_ptr_command<const std::string&> quit_emulator("quit-emulator", "Quit the emulator",
490 "Syntax: quit-emulator [/y]\nQuits emulator (/y => don't ask for confirmation).\n",
491 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
492 if(args == "/y" || window::modal_message("Really quit?", true)) {
493 amode = ADVANCE_QUIT;
494 window::paused(false);
495 window::cancel_wait();
499 function_ptr_command<> pause_emulator("pause-emulator", "(Un)pause the emulator",
500 "Syntax: pause-emulator\n(Un)pauses the emulator.\n",
501 []() throw(std::bad_alloc, std::runtime_error) {
502 if(amode != ADVANCE_AUTO) {
503 amode = ADVANCE_AUTO;
504 window::paused(false);
505 window::cancel_wait();
506 window::message("Unpaused");
507 } else {
508 window::cancel_wait();
509 cancel_advance = false;
510 amode = ADVANCE_PAUSE;
511 window::message("Paused");
515 function_ptr_command<> padvance_frame("+advance-frame", "Advance one frame",
516 "Syntax: +advance-frame\nAdvances the emulation by one frame.\n",
517 []() throw(std::bad_alloc, std::runtime_error) {
518 amode = ADVANCE_FRAME;
519 cancel_advance = false;
520 advanced_once = false;
521 window::cancel_wait();
522 window::paused(false);
525 function_ptr_command<> nadvance_frame("-advance-frame", "Advance one frame",
526 "No help available\n",
527 []() throw(std::bad_alloc, std::runtime_error) {
528 cancel_advance = true;
529 window::cancel_wait();
530 window::paused(false);
533 function_ptr_command<> padvance_poll("+advance-poll", "Advance one subframe",
534 "Syntax: +advance-poll\nAdvances the emulation by one subframe.\n",
535 []() throw(std::bad_alloc, std::runtime_error) {
536 amode = ADVANCE_SUBFRAME;
537 cancel_advance = false;
538 advanced_once = false;
539 window::cancel_wait();
540 window::paused(false);
543 function_ptr_command<> nadvance_poll("-advance-poll", "Advance one subframe",
544 "No help available\n",
545 []() throw(std::bad_alloc, std::runtime_error) {
546 cancel_advance = true;
547 window::cancel_wait();
548 window::paused(false);
551 function_ptr_command<> advance_skiplag("advance-skiplag", "Skip to next poll",
552 "Syntax: advance-skiplag\nAdvances the emulation to the next poll.\n",
553 []() throw(std::bad_alloc, std::runtime_error) {
554 amode = ADVANCE_SKIPLAG;
555 window::cancel_wait();
556 window::paused(false);
559 function_ptr_command<> reset_c("reset", "Reset the SNES",
560 "Syntax: reset\nResets the SNES in beginning of the next frame.\n",
561 []() throw(std::bad_alloc, std::runtime_error) {
562 pending_reset_cycles = 0;
565 function_ptr_command<arg_filename> load_state_c("load-state", "Load savestate (R/W)",
566 "Syntax: load-state <file>\nLoads SNES state from <file> in Read/Write mode\n",
567 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
568 mark_pending_load(args, LOAD_STATE_RW);
571 function_ptr_command<arg_filename> load_readonly("load-readonly", "Load savestate (RO)",
572 "Syntax: load-readonly <file>\nLoads SNES state from <file> in read-only mode\n",
573 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
574 mark_pending_load(args, LOAD_STATE_RO);
577 function_ptr_command<arg_filename> load_preserve("load-preserve", "Load savestate (preserve input)",
578 "Syntax: load-preserve <file>\nLoads SNES state from <file> preserving input\n",
579 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
580 mark_pending_load(args, LOAD_STATE_PRESERVE);
583 function_ptr_command<arg_filename> load_movie_c("load-movie", "Load movie",
584 "Syntax: load-movie <file>\nLoads SNES movie from <file>\n",
585 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
586 mark_pending_load(args, LOAD_STATE_MOVIE);
590 function_ptr_command<arg_filename> save_state("save-state", "Save state",
591 "Syntax: save-state <file>\nSaves SNES state to <file>\n",
592 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
593 mark_pending_save(args, SAVE_STATE);
596 function_ptr_command<arg_filename> save_movie("save-movie", "Save movie",
597 "Syntax: save-movie <file>\nSaves SNES movie to <file>\n",
598 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
599 mark_pending_save(args, SAVE_MOVIE);
602 function_ptr_command<> set_rwmode("set-rwmode", "Switch to read/write mode",
603 "Syntax: set-rwmode\nSwitches to read/write mode\n",
604 []() throw(std::bad_alloc, std::runtime_error) {
605 movb.get_movie().readonly_mode(false);
606 lua_callback_do_readwrite();
607 update_movie_state();
608 window::notify_screen_update();
611 function_ptr_command<> repaint("repaint", "Redraw the screen",
612 "Syntax: repaint\nRedraws the screen\n",
613 []() throw(std::bad_alloc, std::runtime_error) {
614 redraw_framebuffer();
617 function_ptr_command<const std::string&> add_watch("add-watch", "Add a memory watch",
618 "Syntax: add-watch <name> <expression>\nAdds a new memory watch\n",
619 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
620 tokensplitter t(args);
621 std::string name = t;
622 if(name == "" || t.tail() == "")
623 throw std::runtime_error("syntax: add-watch <name> <expr>");
624 std::cerr << "Add watch: '" << name << "'" << std::endl;
625 memory_watches[name] = t.tail();
626 update_movie_state();
629 function_ptr_command<const std::string&> remove_watch("remove-watch", "Remove a memory watch",
630 "Syntax: remove-watch <name>\nRemoves a memory watch\n",
631 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
632 tokensplitter t(args);
633 std::string name = t;
634 if(name == "" || t.tail() != "") {
635 messages << "syntax: remove-watch <name>" << std::endl;
636 return;
638 std::cerr << "Erase watch: '" << name << "'" << std::endl;
639 memory_watches.erase(name);
640 auto& _status = window::get_emustatus();
641 _status.erase("M[" + name + "]");
642 update_movie_state();
645 function_ptr_command<> test1("test-1", "no description available", "No help available\n",
646 []() throw(std::bad_alloc, std::runtime_error) {
647 framebuffer = screen_nosignal;
648 redraw_framebuffer();
651 function_ptr_command<> test2("test-2", "no description available", "No help available\n",
652 []() throw(std::bad_alloc, std::runtime_error) {
653 framebuffer = screen_corrupt;
654 redraw_framebuffer();
657 function_ptr_command<> test3("test-3", "no description available", "No help available\n",
658 []() throw(std::bad_alloc, std::runtime_error) {
659 while(1);
662 function_ptr_command<const std::string&> mouse_button_handler("mouse_button", "no description available",
663 "No help available\n",
664 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
665 tokensplitter t(args);
666 std::string x = t;
667 std::string y = t;
668 std::string b = t;
669 int _x = atoi(x.c_str());
670 int _y = atoi(y.c_str());
671 int _b = atoi(b.c_str());
672 if(_b & ~prev_mouse_mask & 1)
673 send_analog_input(_x, _y, 0);
674 if(_b & ~prev_mouse_mask & 2)
675 send_analog_input(_x, _y, 1);
676 if(_b & ~prev_mouse_mask & 4)
677 send_analog_input(_x, _y, 2);
678 prev_mouse_mask = _b;
681 class button_action : public command
683 public:
684 button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
685 throw(std::bad_alloc)
686 : command(cmd)
688 commandn = cmd;
689 type = _type;
690 controller = _controller;
691 button = _button;
693 ~button_action() throw() {}
694 void invoke(const std::string& args) throw(std::bad_alloc, std::runtime_error)
696 if(args != "")
697 throw std::runtime_error("This command does not take parameters");
698 init_buttonmap();
699 if(!buttonmap.count(button))
700 return;
701 auto i = buttonmap[button];
702 do_button_action(i.first, i.second, (type != 1) ? 1 : 0, (type == 2));
703 update_movie_state();
704 window::notify_screen_update();
706 std::string get_short_help() throw(std::bad_alloc)
708 return "Press/Unpress button";
710 std::string get_long_help() throw(std::bad_alloc)
712 return "Syntax: " + commandn + "\n"
713 "Presses/Unpresses button\n";
715 std::string commandn;
716 unsigned controller;
717 int type;
718 std::string button;
721 class button_action_helper
723 public:
724 button_action_helper()
726 for(size_t i = 0; i < sizeof(buttonnames) / sizeof(buttonnames[0]); ++i)
727 for(int j = 0; j < 3; ++j)
728 for(unsigned k = 0; k < 8; ++k) {
729 std::ostringstream x, y;
730 switch(j) {
731 case 0:
732 x << "+controller";
733 break;
734 case 1:
735 x << "-controller";
736 break;
737 case 2:
738 x << "controllerh";
739 break;
741 x << (k + 1);
742 x << buttonnames[i];
743 y << (k + 1);
744 y << buttonnames[i];
745 new button_action(x.str(), j, k, y.str());
748 } bah;
750 //If there is a pending load, perform it.
751 bool handle_load()
753 if(pending_load != "") {
754 do_load_state(pending_load, loadmode);
755 redraw_framebuffer();
756 pending_load = "";
757 pending_reset_cycles = -1;
758 amode = ADVANCE_AUTO;
759 window::cancel_wait();
760 window::paused(false);
761 if(!system_corrupt) {
762 location_special = SPECIAL_SAVEPOINT;
763 update_movie_state();
764 window::notify_screen_update();
765 window::poll_inputs();
767 return true;
769 return false;
772 //If there are pending saves, perform them.
773 void handle_saves()
775 if(!queued_saves.empty()) {
776 stepping_into_save = true;
777 SNES::system.runtosave();
778 stepping_into_save = false;
779 for(auto i = queued_saves.begin(); i != queued_saves.end(); i++)
780 do_save_state(*i);
782 queued_saves.clear();
785 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
786 bool handle_reset(long cycles)
788 if(cycles == 0) {
789 window::message("SNES reset");
790 SNES::system.reset();
791 framebuffer = screen_nosignal;
792 lua_callback_do_reset();
793 redraw_framebuffer();
794 } else if(cycles > 0) {
795 video_refresh_done = false;
796 long cycles_executed = 0;
797 messages << "Executing delayed reset... This can take some time!" << std::endl;
798 while(cycles_executed < cycles && !video_refresh_done) {
799 SNES::cpu.op_step();
800 cycles_executed++;
802 if(!video_refresh_done)
803 messages << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
804 else
805 messages << "SNES reset (forced at " << cycles_executed << ")" << std::endl;
806 SNES::system.reset();
807 framebuffer = screen_nosignal;
808 lua_callback_do_reset();
809 redraw_framebuffer();
810 if(video_refresh_done) {
811 to_wait_frame(get_ticks_msec());
812 return false;
815 return true;
818 bool handle_corrupt()
820 if(!system_corrupt)
821 return false;
822 while(system_corrupt) {
823 redraw_framebuffer();
824 window::cancel_wait();
825 window::paused(true);
826 window::poll_inputs();
827 handle_load();
828 if(amode == ADVANCE_QUIT)
829 return true;
831 return true;
834 void print_controller_mappings()
836 for(unsigned i = 0; i < 8; i++) {
837 std::string type = "unknown";
838 if(controller_type_by_logical(i) == DT_NONE)
839 type = "disconnected";
840 if(controller_type_by_logical(i) == DT_GAMEPAD)
841 type = "gamepad";
842 if(controller_type_by_logical(i) == DT_MOUSE)
843 type = "mouse";
844 if(controller_type_by_logical(i) == DT_SUPERSCOPE)
845 type = "superscope";
846 if(controller_type_by_logical(i) == DT_JUSTIFIER)
847 type = "justifier";
848 messages << "Physical controller mapping: Logical " << (i + 1) << " is physical " <<
849 controller_index_by_logical(i) << " (" << type << ")" << std::endl;
854 void main_loop(struct loaded_rom& rom, struct moviefile& initial) throw(std::bad_alloc,
855 std::runtime_error)
857 //Basic initialization.
858 init_special_screens();
859 our_rom = &rom;
860 my_interface intrf;
861 auto old_inteface = SNES::system.interface;
862 SNES::system.interface = &intrf;
863 status = &window::get_emustatus();
865 //Load our given movie.
866 bool first_round = false;
867 bool just_did_loadstate = false;
868 try {
869 do_load_state(initial, LOAD_STATE_DEFAULT);
870 first_round = our_movie.is_savestate;
871 just_did_loadstate = first_round;
872 } catch(std::bad_alloc& e) {
873 OOM_panic();
874 } catch(std::exception& e) {
875 messages << "FATAL: Can't load initial state: " << e.what() << std::endl;
876 fatal_error();
877 return;
880 lua_callback_startup();
882 //print_controller_mappings();
883 av_snooper::add_dump_notifier(dumpwatch);
884 window::set_main_surface(main_screen);
885 redraw_framebuffer();
886 window::paused(false);
887 amode = ADVANCE_PAUSE;
888 while(amode != ADVANCE_QUIT) {
889 if(handle_corrupt()) {
890 first_round = our_movie.is_savestate;
891 just_did_loadstate = true;
892 continue;
894 long resetcycles = -1;
895 ack_frame_tick(get_ticks_msec());
896 if(amode == ADVANCE_SKIPLAG_PENDING)
897 amode = ADVANCE_SKIPLAG;
899 if(!first_round) {
900 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
901 if(amode == ADVANCE_QUIT)
902 break;
903 bool delayed_reset = (resetcycles > 0);
904 pending_reset_cycles = -1;
905 if(!handle_reset(resetcycles)) {
906 continue;
908 if(!delayed_reset) {
909 handle_saves();
911 if(handle_load()) {
912 first_round = our_movie.is_savestate;
913 amode = ADVANCE_PAUSE;
914 just_did_loadstate = first_round;
915 continue;
918 if(just_did_loadstate) {
919 if(amode == ADVANCE_QUIT)
920 break;
921 amode = ADVANCE_PAUSE;
922 redraw_framebuffer();
923 window::cancel_wait();
924 window::paused(true);
925 window::poll_inputs();
926 just_did_loadstate = false;
928 SNES::system.run();
929 if(amode == ADVANCE_AUTO)
930 window::wait_msec(to_wait_frame(get_ticks_msec()));
931 first_round = false;
933 av_snooper::end(true);
934 SNES::system.interface = old_inteface;