Bump minimum bsnes version to v083.
[lsnes.git] / generic / mainloop.cpp
blobbfc5b93945711345027d13ca89deefc85a90abb3
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 RTC_SUBSECOND_INCREMENT_PAL 69242724928ULL
37 #define RTC_SUBSECOND_INCREMENT_NTSC 57615439935ULL
38 #define RTC_SUBSECOND_INCREMENT_NTSC_I 57615762380ULL
39 #define RTC_SUBSECONDS_PER_SECOND 3462619485020ULL
42 void update_movie_state();
44 namespace
46 enum advance_mode
48 ADVANCE_QUIT, //Quit the emulator.
49 ADVANCE_AUTO, //Normal (possibly slowed down play).
50 ADVANCE_FRAME, //Frame advance.
51 ADVANCE_SUBFRAME, //Subframe advance.
52 ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
53 ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
54 ADVANCE_PAUSE, //Unconditional pause.
57 //Memory watches.
58 std::map<std::string, std::string> memory_watches;
59 //Previous mouse mask.
60 int prev_mouse_mask = 0;
61 //Flags related to repeating advance.
62 bool advanced_once;
63 bool cancel_advance;
64 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
65 enum advance_mode amode;
66 //Mode and filename of pending load, one of LOAD_* constants.
67 int loadmode;
68 std::string pending_load;
69 //Queued saves (all savestates).
70 std::set<std::string> queued_saves;
71 bool stepping_into_save;
72 //Save jukebox.
73 std::vector<std::string> save_jukebox;
74 size_t save_jukebox_pointer;
75 //Emulator status area.
76 std::map<std::string, std::string>* status;
77 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
78 long pending_reset_cycles = -1;
79 //Set by every video refresh.
80 bool video_refresh_done;
81 //Special subframe location. One of SPECIAL_* constants.
82 int location_special;
83 //Few settings.
84 numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500);
89 class firmware_path_setting : public setting
91 public:
92 firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "."; default_firmware = true; }
93 void blank() throw(std::bad_alloc, std::runtime_error)
95 _firmwarepath = ".";
96 default_firmware = true;
99 bool is_set() throw()
101 return !default_firmware;
104 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
106 _firmwarepath = value;
107 default_firmware = false;
110 std::string get() throw(std::bad_alloc)
112 return _firmwarepath;
115 operator std::string() throw(std::bad_alloc)
117 return _firmwarepath;
119 private:
120 std::string _firmwarepath;
121 bool default_firmware;
122 } firmwarepath_setting;
124 controls_t movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
126 if(lua_requests_subframe_paint)
127 redraw_framebuffer();
129 if(subframe) {
130 if(amode == ADVANCE_SUBFRAME) {
131 if(!cancel_advance && !advanced_once) {
132 window::wait_usec(advance_timeout_first * 1000);
133 advanced_once = true;
135 if(cancel_advance) {
136 amode = ADVANCE_PAUSE;
137 cancel_advance = false;
139 window::paused(amode == ADVANCE_PAUSE);
140 } else if(amode == ADVANCE_FRAME) {
142 } else {
143 window::paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE);
144 cancel_advance = false;
146 if(amode == ADVANCE_SKIPLAG)
147 amode = ADVANCE_AUTO;
148 location_special = SPECIAL_NONE;
149 update_movie_state();
150 } else {
151 if(amode == ADVANCE_SKIPLAG_PENDING)
152 amode = ADVANCE_SKIPLAG;
153 if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
154 if(!cancel_advance) {
155 window::wait_usec(advanced_once ? to_wait_frame(get_utime()) :
156 (advance_timeout_first * 1000));
157 advanced_once = true;
159 if(cancel_advance) {
160 amode = ADVANCE_PAUSE;
161 cancel_advance = false;
163 window::paused(amode == ADVANCE_PAUSE);
164 } else {
165 window::paused((amode == ADVANCE_PAUSE));
166 cancel_advance = false;
168 location_special = SPECIAL_FRAME_START;
169 update_movie_state();
172 window::notify_screen_update();
173 window::poll_inputs();
174 if(!subframe && pending_reset_cycles >= 0)
175 set_curcontrols_reset(pending_reset_cycles);
176 else if(!subframe)
177 set_curcontrols_reset(-1);
178 controls_t tmp = get_current_controls(movb.get_movie().get_current_frame());
179 lua_callback_do_input(tmp, subframe);
180 return tmp;
183 namespace
187 //Do pending load (automatically unpauses).
188 void mark_pending_load(const std::string& filename, int lmode)
190 loadmode = lmode;
191 pending_load = filename;
192 amode = ADVANCE_AUTO;
193 window::cancel_wait();
194 window::paused(false);
197 void mark_pending_save(const std::string& filename, int smode)
199 if(smode == SAVE_MOVIE) {
200 //Just do this immediately.
201 do_save_movie(filename);
202 return;
204 queued_saves.insert(filename);
205 window::message("Pending save on '" + filename + "'");
208 class dump_watch : public av_snooper::dump_notification
210 void dump_starting() throw()
212 update_movie_state();
214 void dump_ending() throw()
216 update_movie_state();
218 } dumpwatch;
220 uint16_t lpalette[0x80000];
221 void init_palette()
223 static bool palette_init = false;
224 if(palette_init)
225 return;
226 palette_init = true;
227 for(unsigned i = 0; i < 0x80000; i++) {
228 unsigned l = (i >> 15) & 0xF;
229 unsigned b = (i >> 10) & 0x1F;
230 unsigned g = (i >> 5) & 0x1F;
231 unsigned r = (i >> 0) & 0x1F;
232 double _l = static_cast<double>(l) / 15;
233 r = (r * _l + 0.5);
234 g = (g * _l + 0.5);
235 b = (b * _l + 0.5);
236 lpalette[i] = (r << 10) | (g << 5) | b;
241 void update_movie_state()
243 auto& _status = window::get_emustatus();
244 if(!system_corrupt) {
245 std::ostringstream x;
246 x << movb.get_movie().get_current_frame() << "(";
247 if(location_special == SPECIAL_FRAME_START)
248 x << "0";
249 else if(location_special == SPECIAL_SAVEPOINT)
250 x << "S";
251 else if(location_special == SPECIAL_FRAME_VIDEO)
252 x << "V";
253 else
254 x << movb.get_movie().next_poll_number();
255 x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count();
256 _status["Frame"] = x.str();
257 } else
258 _status["Frame"] = "N/A";
259 #ifndef NO_TIME_INTERCEPT
260 if(!system_corrupt) {
261 time_t timevalue = static_cast<time_t>(our_movie.rtc_second);
262 struct tm* time_decompose = gmtime(&timevalue);
263 char datebuffer[512];
264 char timebuffer[512];
265 strftime(datebuffer, 511, "%a %d %b %Y", time_decompose);
266 strftime(timebuffer, 511, "%H:%M:%S", time_decompose);
267 _status["RTCdate"] = datebuffer;
268 _status["RTCtime"] = timebuffer;
269 } else {
270 _status["RTCdate"] = "N/A";
271 _status["RTCtime"] = "N/A";
273 #endif
275 std::ostringstream x;
276 if(system_corrupt)
277 x << "CORRUPT ";
278 else if(movb.get_movie().readonly_mode())
279 x << "PLAY ";
280 else
281 x << "REC ";
282 if(av_snooper::dump_in_progress())
283 x << "CAP ";
284 _status["Flags"] = x.str();
286 if(save_jukebox.size() > 0)
287 _status["Saveslot"] = save_jukebox[save_jukebox_pointer];
288 else
289 _status.erase("Saveslot");
290 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
291 try {
292 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
293 } catch(...) {
297 controls_t c;
298 if(movb.get_movie().readonly_mode())
299 c = movb.get_movie().get_controls();
300 else
301 c = get_current_controls(movb.get_movie().get_current_frame());
302 for(unsigned i = 0; i < 8; i++) {
303 unsigned pindex = controller_index_by_logical(i);
304 unsigned port = pindex >> 2;
305 unsigned dev = pindex & 3;
306 auto ctype = controller_type_by_logical(i);
307 std::ostringstream x;
308 switch(ctype) {
309 case DT_GAMEPAD:
310 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " ");
311 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " ");
312 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " ");
313 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " ");
314 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " ");
315 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " ");
316 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " ");
317 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " ");
318 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " ");
319 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " ");
320 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " ");
321 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " ");
322 break;
323 case DT_MOUSE:
324 x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " ";
325 x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " ";
326 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " ");
327 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " ");
328 break;
329 case DT_SUPERSCOPE:
330 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " ";
331 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " ";
332 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " ");
333 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " ");
334 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " ");
335 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " ");
336 break;
337 case DT_JUSTIFIER:
338 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " ";
339 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " ";
340 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " ");
341 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " ");
342 break;
343 case DT_NONE:
344 continue;
346 char y[3] = {'P', 0, 0};
347 y[1] = 49 + i;
348 _status[std::string(y)] = x.str();
353 class my_interface : public SNES::Interface
355 string path(SNES::Cartridge::Slot slot, const string &hint)
357 const char* _hint = hint;
358 std::string _hint2 = _hint;
359 std::string fwp = firmwarepath_setting;
360 std::string finalpath = fwp + "/" + _hint2;
361 return finalpath.c_str();
364 void videoRefresh(const uint32_t* data, bool hires, bool interlace, bool overscan)
366 init_palette();
367 static uint16_t _data[512 * 512];
368 if(stepping_into_save)
369 window::message("Got video refresh in runtosave, expect desyncs!");
370 video_refresh_done = true;
371 bool region = (SNES::system.region() == SNES::System::Region::PAL);
372 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
373 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
374 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
375 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
376 for(size_t i = 0; i < 512 * 512; i++)
377 _data[i] = lpalette[data[i] & 0x7FFFF];
378 lcscreen ls(_data, hires, interlace, overscan, region);
379 framebuffer = ls;
380 location_special = SPECIAL_FRAME_VIDEO;
381 update_movie_state();
382 redraw_framebuffer();
383 uint32_t fps_n, fps_d;
384 if(region) {
385 fps_n = 322445;
386 fps_d = 6448;
387 our_movie.rtc_subsecond += RTC_SUBSECOND_INCREMENT_PAL;
388 } else if(!interlace) {
389 fps_n = 10738636;
390 fps_d = 178683;
391 our_movie.rtc_subsecond += RTC_SUBSECOND_INCREMENT_NTSC;
392 } else {
393 //Yes, interlace makes difference with NTSC but not on PAL.
394 fps_n = 2684659;
395 fps_d = 44671;
396 our_movie.rtc_subsecond += RTC_SUBSECOND_INCREMENT_NTSC_I;
398 if(our_movie.rtc_subsecond >= RTC_SUBSECONDS_PER_SECOND) {
399 our_movie.rtc_second++;
400 our_movie.rtc_subsecond -= RTC_SUBSECONDS_PER_SECOND;
402 av_snooper::frame(ls, fps_n, fps_d, true);
405 void audioSample(int16_t l_sample, int16_t r_sample)
407 uint16_t _l = l_sample;
408 uint16_t _r = r_sample;
409 window::play_audio_sample(_l + 32768, _r + 32768);
410 av_snooper::sample(_l, _r, true);
413 int16_t inputPoll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
415 int16_t x;
416 x = movb.input_poll(port, index, id);
417 //if(id == SNES_DEVICE_ID_JOYPAD_START)
418 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
419 lua_callback_snoop_input(port ? 1 : 0, index, id, x);
420 return x;
424 namespace
426 function_ptr_command<> count_rerecords("count-rerecords", "Count rerecords",
427 "Syntax: count-rerecords\nCounts rerecords.\n",
428 []() throw(std::bad_alloc, std::runtime_error) {
429 std::vector<char> tmp;
430 uint64_t x = rrdata::write(tmp);
431 messages << x << " rerecord(s)" << std::endl;
434 function_ptr_command<const std::string&> quit_emulator("quit-emulator", "Quit the emulator",
435 "Syntax: quit-emulator [/y]\nQuits emulator (/y => don't ask for confirmation).\n",
436 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
437 if(args == "/y" || window::modal_message("Really quit?", true)) {
438 amode = ADVANCE_QUIT;
439 window::paused(false);
440 window::cancel_wait();
444 function_ptr_command<> pause_emulator("pause-emulator", "(Un)pause the emulator",
445 "Syntax: pause-emulator\n(Un)pauses the emulator.\n",
446 []() throw(std::bad_alloc, std::runtime_error) {
447 if(amode != ADVANCE_AUTO) {
448 amode = ADVANCE_AUTO;
449 window::paused(false);
450 window::cancel_wait();
451 window::message("Unpaused");
452 } else {
453 window::cancel_wait();
454 cancel_advance = false;
455 amode = ADVANCE_PAUSE;
456 window::message("Paused");
460 function_ptr_command<> save_jukebox_prev("cycle-jukebox-backward", "Cycle save jukebox backwards",
461 "Syntax: cycle-jukebox-backwards\nCycle save jukebox backwards\n",
462 []() throw(std::bad_alloc, std::runtime_error) {
463 if(save_jukebox_pointer == 0)
464 save_jukebox_pointer = save_jukebox.size() - 1;
465 else
466 save_jukebox_pointer--;
467 if(save_jukebox_pointer >= save_jukebox.size())
468 save_jukebox_pointer = 0;
469 update_movie_state();
470 window::notify_screen_update();
473 function_ptr_command<> save_jukebox_next("cycle-jukebox-forward", "Cycle save jukebox forwards",
474 "Syntax: cycle-jukebox-forwards\nCycle save jukebox forwards\n",
475 []() throw(std::bad_alloc, std::runtime_error) {
476 if(save_jukebox_pointer == save_jukebox.size() - 1)
477 save_jukebox_pointer = 0;
478 else
479 save_jukebox_pointer++;
480 if(save_jukebox_pointer >= save_jukebox.size())
481 save_jukebox_pointer = 0;
482 update_movie_state();
483 window::notify_screen_update();
486 function_ptr_command<arg_filename> add_jukebox("add-jukebox-save", "Add save to jukebox",
487 "Syntax: add-jukebox-save\nAdd save to jukebox\n",
488 [](arg_filename filename) throw(std::bad_alloc, std::runtime_error) {
489 save_jukebox.push_back(filename);
490 update_movie_state();
491 window::notify_screen_update();
494 function_ptr_command<> load_jukebox("load-jukebox", "Load save from jukebox",
495 "Syntax: load-jukebox\nLoad save from jukebox\n",
496 []() throw(std::bad_alloc, std::runtime_error) {
497 if(!save_jukebox.size())
498 throw std::runtime_error("No saves in jukebox");
499 mark_pending_load(save_jukebox[save_jukebox_pointer], LOAD_STATE_CURRENT);
502 function_ptr_command<> save_jukebox_c("save-jukebox", "Save save to jukebox",
503 "Syntax: save-jukebox\nSave save to jukebox\n",
504 []() throw(std::bad_alloc, std::runtime_error) {
505 if(!save_jukebox.size())
506 throw std::runtime_error("No saves in jukebox");
507 mark_pending_save(save_jukebox[save_jukebox_pointer], SAVE_STATE);
510 function_ptr_command<> padvance_frame("+advance-frame", "Advance one frame",
511 "Syntax: +advance-frame\nAdvances the emulation by one frame.\n",
512 []() throw(std::bad_alloc, std::runtime_error) {
513 amode = ADVANCE_FRAME;
514 cancel_advance = false;
515 advanced_once = false;
516 window::cancel_wait();
517 window::paused(false);
520 function_ptr_command<> nadvance_frame("-advance-frame", "Advance one frame",
521 "No help available\n",
522 []() throw(std::bad_alloc, std::runtime_error) {
523 cancel_advance = true;
524 window::cancel_wait();
525 window::paused(false);
528 function_ptr_command<> padvance_poll("+advance-poll", "Advance one subframe",
529 "Syntax: +advance-poll\nAdvances the emulation by one subframe.\n",
530 []() throw(std::bad_alloc, std::runtime_error) {
531 amode = ADVANCE_SUBFRAME;
532 cancel_advance = false;
533 advanced_once = false;
534 window::cancel_wait();
535 window::paused(false);
538 function_ptr_command<> nadvance_poll("-advance-poll", "Advance one subframe",
539 "No help available\n",
540 []() throw(std::bad_alloc, std::runtime_error) {
541 cancel_advance = true;
542 window::cancel_wait();
543 window::paused(false);
546 function_ptr_command<> advance_skiplag("advance-skiplag", "Skip to next poll",
547 "Syntax: advance-skiplag\nAdvances the emulation to the next poll.\n",
548 []() throw(std::bad_alloc, std::runtime_error) {
549 amode = ADVANCE_SKIPLAG;
550 window::cancel_wait();
551 window::paused(false);
554 function_ptr_command<> reset_c("reset", "Reset the SNES",
555 "Syntax: reset\nResets the SNES in beginning of the next frame.\n",
556 []() throw(std::bad_alloc, std::runtime_error) {
557 pending_reset_cycles = 0;
560 function_ptr_command<arg_filename> load_c("load", "Load savestate (current mode)",
561 "Syntax: load <file>\nLoads SNES state from <file> in current mode\n",
562 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
563 mark_pending_load(args, LOAD_STATE_CURRENT);
566 function_ptr_command<arg_filename> load_state_c("load-state", "Load savestate (R/W)",
567 "Syntax: load-state <file>\nLoads SNES state from <file> in Read/Write mode\n",
568 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
569 mark_pending_load(args, LOAD_STATE_RW);
572 function_ptr_command<arg_filename> load_readonly("load-readonly", "Load savestate (RO)",
573 "Syntax: load-readonly <file>\nLoads SNES state from <file> in read-only mode\n",
574 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
575 mark_pending_load(args, LOAD_STATE_RO);
578 function_ptr_command<arg_filename> load_preserve("load-preserve", "Load savestate (preserve input)",
579 "Syntax: load-preserve <file>\nLoads SNES state from <file> preserving input\n",
580 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
581 mark_pending_load(args, LOAD_STATE_PRESERVE);
584 function_ptr_command<arg_filename> load_movie_c("load-movie", "Load movie",
585 "Syntax: load-movie <file>\nLoads SNES movie from <file>\n",
586 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
587 mark_pending_load(args, LOAD_STATE_MOVIE);
591 function_ptr_command<arg_filename> save_state("save-state", "Save state",
592 "Syntax: save-state <file>\nSaves SNES state to <file>\n",
593 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
594 mark_pending_save(args, SAVE_STATE);
597 function_ptr_command<arg_filename> save_movie("save-movie", "Save movie",
598 "Syntax: save-movie <file>\nSaves SNES movie to <file>\n",
599 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
600 mark_pending_save(args, SAVE_MOVIE);
603 function_ptr_command<> set_rwmode("set-rwmode", "Switch to read/write mode",
604 "Syntax: set-rwmode\nSwitches to read/write mode\n",
605 []() throw(std::bad_alloc, std::runtime_error) {
606 movb.get_movie().readonly_mode(false);
607 lua_callback_do_readwrite();
608 update_movie_state();
609 window::notify_screen_update();
612 function_ptr_command<> set_romode("set-romode", "Switch to read-only mode",
613 "Syntax: set-romode\nSwitches to read-only mode\n",
614 []() throw(std::bad_alloc, std::runtime_error) {
615 movb.get_movie().readonly_mode(true);
616 update_movie_state();
617 window::notify_screen_update();
620 function_ptr_command<> toggle_rwmode("toggle-rwmode", "Toggle read/write mode",
621 "Syntax: toggle-rwmode\nToggles read/write mode\n",
622 []() throw(std::bad_alloc, std::runtime_error) {
623 bool c = movb.get_movie().readonly_mode();
624 movb.get_movie().readonly_mode(!c);
625 if(c)
626 lua_callback_do_readwrite();
627 update_movie_state();
628 window::notify_screen_update();
631 function_ptr_command<> repaint("repaint", "Redraw the screen",
632 "Syntax: repaint\nRedraws the screen\n",
633 []() throw(std::bad_alloc, std::runtime_error) {
634 redraw_framebuffer();
637 function_ptr_command<tokensplitter&> add_watch("add-watch", "Add a memory watch",
638 "Syntax: add-watch <name> <expression>\nAdds a new memory watch\n",
639 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
640 std::string name = t;
641 if(name == "" || t.tail() == "")
642 throw std::runtime_error("syntax: add-watch <name> <expr>");
643 std::cerr << "Add watch: '" << name << "'" << std::endl;
644 memory_watches[name] = t.tail();
645 update_movie_state();
648 function_ptr_command<tokensplitter&> remove_watch("remove-watch", "Remove a memory watch",
649 "Syntax: remove-watch <name>\nRemoves a memory watch\n",
650 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
651 std::string name = t;
652 if(name == "" || t.tail() != "") {
653 messages << "syntax: remove-watch <name>" << std::endl;
654 return;
656 std::cerr << "Erase watch: '" << name << "'" << std::endl;
657 memory_watches.erase(name);
658 auto& _status = window::get_emustatus();
659 _status.erase("M[" + name + "]");
660 update_movie_state();
664 function_ptr_command<> test1("test-1", "no description available", "No help available\n",
665 []() throw(std::bad_alloc, std::runtime_error) {
666 framebuffer = screen_nosignal;
667 redraw_framebuffer();
670 function_ptr_command<> test2("test-2", "no description available", "No help available\n",
671 []() throw(std::bad_alloc, std::runtime_error) {
672 framebuffer = screen_corrupt;
673 redraw_framebuffer();
676 function_ptr_command<> test3("test-3", "no description available", "No help available\n",
677 []() throw(std::bad_alloc, std::runtime_error) {
678 while(1);
682 bool on_quit_prompt = false;
683 class mywindowcallbacks : public window_callback
685 public:
686 void on_close() throw()
688 if(on_quit_prompt) {
689 amode = ADVANCE_QUIT;
690 window::paused(false);
691 window::cancel_wait();
692 return;
694 on_quit_prompt = true;
695 try {
696 if(window::modal_message("Really quit?", true)) {
697 amode = ADVANCE_QUIT;
698 window::paused(false);
699 window::cancel_wait();
701 } catch(...) {
703 on_quit_prompt = false;
706 void on_click(int32_t x, int32_t y, uint32_t buttonmask) throw()
708 if(buttonmask & ~prev_mouse_mask & 1)
709 send_analog_input(x, y, 0);
710 if(buttonmask & ~prev_mouse_mask & 2)
711 send_analog_input(x, y, 1);
712 if(buttonmask & ~prev_mouse_mask & 4)
713 send_analog_input(x, y, 2);
714 prev_mouse_mask = buttonmask;
716 } mywcb;
718 //If there is a pending load, perform it. Return 1 on successful load, 0 if nothing to load, -1 on load
719 //failing.
720 int handle_load()
722 if(pending_load != "") {
723 system_corrupt = false;
724 if(!do_load_state(pending_load, loadmode)) {
725 pending_load = "";
726 return -1;
728 redraw_framebuffer();
729 pending_load = "";
730 pending_reset_cycles = -1;
731 amode = ADVANCE_AUTO;
732 window::cancel_wait();
733 window::paused(false);
734 if(!system_corrupt) {
735 location_special = SPECIAL_SAVEPOINT;
736 update_movie_state();
737 window::notify_screen_update();
738 window::poll_inputs();
740 return 1;
742 return 0;
745 //If there are pending saves, perform them.
746 void handle_saves()
748 if(!queued_saves.empty()) {
749 stepping_into_save = true;
750 SNES::system.runtosave();
751 stepping_into_save = false;
752 for(auto i = queued_saves.begin(); i != queued_saves.end(); i++)
753 do_save_state(*i);
755 queued_saves.clear();
758 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
759 bool handle_reset(long cycles)
761 if(cycles < 0)
762 return true;
763 video_refresh_done = false;
764 if(cycles == 0)
765 window::message("SNES reset");
766 else if(cycles > 0) {
767 window::message("SNES delayed reset not implemented (doing immediate reset)");
768 /* ... This code is just too buggy.
769 long cycles_executed = 0;
770 messages << "Executing delayed reset... This can take some time!" << std::endl;
771 while(cycles_executed < cycles && !video_refresh_done) {
772 //Poll inputs once in a while to prevent activating watchdog.
773 if(cycles_executed % 100 == 0)
774 window::poll_inputs();
775 SNES::cpu.op_step();
776 cycles_executed++;
778 if(!video_refresh_done)
779 messages << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
780 else
781 messages << "SNES reset (forced at " << cycles_executed << ")" << std::endl;
784 SNES::system.reset();
785 framebuffer = screen_nosignal;
786 lua_callback_do_reset();
787 redraw_framebuffer();
788 if(video_refresh_done) {
789 to_wait_frame(get_utime());
790 return false;
792 return true;
795 bool handle_corrupt()
797 if(!system_corrupt)
798 return false;
799 while(system_corrupt) {
800 redraw_framebuffer();
801 window::cancel_wait();
802 window::paused(true);
803 window::poll_inputs();
804 handle_load();
805 if(amode == ADVANCE_QUIT)
806 return true;
808 return true;
811 void print_controller_mappings()
813 for(unsigned i = 0; i < 8; i++) {
814 std::string type = "unknown";
815 if(controller_type_by_logical(i) == DT_NONE)
816 type = "disconnected";
817 if(controller_type_by_logical(i) == DT_GAMEPAD)
818 type = "gamepad";
819 if(controller_type_by_logical(i) == DT_MOUSE)
820 type = "mouse";
821 if(controller_type_by_logical(i) == DT_SUPERSCOPE)
822 type = "superscope";
823 if(controller_type_by_logical(i) == DT_JUSTIFIER)
824 type = "justifier";
825 messages << "Physical controller mapping: Logical " << (i + 1) << " is physical " <<
826 controller_index_by_logical(i) << " (" << type << ")" << std::endl;
831 void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_to_succeed) throw(std::bad_alloc,
832 std::runtime_error)
834 //Basic initialization.
835 init_special_screens();
836 our_rom = &rom;
837 my_interface intrf;
838 auto old_inteface = SNES::interface;
839 SNES::interface = &intrf;
840 intrf.initialize(&intrf);
841 status = &window::get_emustatus();
842 window_callback::set_callback_handler(mywcb);
844 //Load our given movie.
845 bool first_round = false;
846 bool just_did_loadstate = false;
847 try {
848 do_load_state(initial, LOAD_STATE_DEFAULT);
849 location_special = SPECIAL_SAVEPOINT;
850 update_movie_state();
851 first_round = our_movie.is_savestate;
852 just_did_loadstate = first_round;
853 } catch(std::bad_alloc& e) {
854 OOM_panic();
855 } catch(std::exception& e) {
856 messages << "ERROR: Can't load initial state: " << e.what() << std::endl;
857 if(load_has_to_succeed) {
858 messages << "FATAL: Can't load movie" << std::endl;
859 window::fatal_error();
861 system_corrupt = true;
862 update_movie_state();
863 framebuffer = screen_corrupt;
864 redraw_framebuffer();
867 lua_callback_startup();
869 //print_controller_mappings();
870 av_snooper::add_dump_notifier(dumpwatch);
871 window::set_main_surface(main_screen);
872 redraw_framebuffer();
873 window::paused(false);
874 amode = ADVANCE_PAUSE;
875 while(amode != ADVANCE_QUIT) {
876 if(handle_corrupt()) {
877 first_round = our_movie.is_savestate;
878 just_did_loadstate = first_round;
879 continue;
881 long resetcycles = -1;
882 ack_frame_tick(get_utime());
883 if(amode == ADVANCE_SKIPLAG_PENDING)
884 amode = ADVANCE_SKIPLAG;
886 if(!first_round) {
887 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
888 if(amode == ADVANCE_QUIT)
889 break;
890 bool delayed_reset = (resetcycles > 0);
891 pending_reset_cycles = -1;
892 if(!handle_reset(resetcycles)) {
893 continue;
895 if(!delayed_reset) {
896 handle_saves();
898 int r = handle_load();
899 if(r > 0 || system_corrupt) {
900 first_round = our_movie.is_savestate;
901 amode = ADVANCE_PAUSE;
902 just_did_loadstate = first_round;
903 continue;
904 } else if(r < 0) {
905 //Not exactly desriable, but this at least won't desync.
906 amode = ADVANCE_PAUSE;
909 if(just_did_loadstate) {
910 if(amode == ADVANCE_QUIT)
911 break;
912 amode = ADVANCE_PAUSE;
913 redraw_framebuffer();
914 window::cancel_wait();
915 window::paused(true);
916 window::poll_inputs();
917 //We already have done the reset this frame if we are going to do one at all.
918 movb.get_movie().set_controls(get_current_controls(movb.get_movie().get_current_frame()));
919 just_did_loadstate = false;
921 SNES::system.run();
922 if(amode == ADVANCE_AUTO)
923 window::wait_usec(to_wait_frame(get_utime()));
924 first_round = false;
926 av_snooper::end(true);
927 SNES::interface = old_inteface;