Reorganize the window code a bit
[lsnes.git] / generic / mainloop.cpp
blob72f903c668f894ad5815417cc008a46f12642ea1
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 "sdump.hpp"
8 #include <iomanip>
9 #include "framerate.hpp"
10 #include "memorywatch.hpp"
11 #include "lua.hpp"
12 #include "rrdata.hpp"
13 #include "rom.hpp"
14 #include "movie.hpp"
15 #include "moviefile.hpp"
16 #include "render.hpp"
17 #include "window.hpp"
18 #include "settings.hpp"
19 #include "rom.hpp"
20 #include "movie.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 void update_movie_state();
39 namespace
41 enum advance_mode
43 ADVANCE_QUIT, //Quit the emulator.
44 ADVANCE_AUTO, //Normal (possibly slowed down play).
45 ADVANCE_FRAME, //Frame advance.
46 ADVANCE_SUBFRAME, //Subframe advance.
47 ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
48 ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
49 ADVANCE_PAUSE, //Unconditional pause.
52 //Memory watches.
53 std::map<std::string, std::string> memory_watches;
54 //Previous mouse mask.
55 int prev_mouse_mask = 0;
56 //Flags related to repeating advance.
57 bool advanced_once;
58 bool cancel_advance;
59 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
60 enum advance_mode amode;
61 //Mode and filename of pending load, one of LOAD_* constants.
62 int loadmode;
63 std::string pending_load;
64 //Queued saves (all savestates).
65 std::set<std::string> queued_saves;
66 bool stepping_into_save;
67 //Save jukebox.
68 std::vector<std::string> save_jukebox;
69 size_t save_jukebox_pointer;
70 //Emulator status area.
71 std::map<std::string, std::string>* status;
72 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
73 long pending_reset_cycles = -1;
74 //Set by every video refresh.
75 bool video_refresh_done;
76 //Special subframe location. One of SPECIAL_* constants.
77 int location_special;
78 //Few settings.
79 numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500);
80 //Last frame params.
81 bool last_hires = false;
82 bool last_interlace = false;
85 class firmware_path_setting : public setting
87 public:
88 firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "."; default_firmware = true; }
89 void blank() throw(std::bad_alloc, std::runtime_error)
91 _firmwarepath = ".";
92 default_firmware = true;
95 bool is_set() throw()
97 return !default_firmware;
100 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
102 _firmwarepath = value;
103 default_firmware = false;
106 std::string get() throw(std::bad_alloc)
108 return _firmwarepath;
111 operator std::string() throw(std::bad_alloc)
113 return _firmwarepath;
115 private:
116 std::string _firmwarepath;
117 bool default_firmware;
118 } firmwarepath_setting;
120 controls_t movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
122 if(lua_requests_subframe_paint)
123 redraw_framebuffer();
125 if(subframe) {
126 if(amode == ADVANCE_SUBFRAME) {
127 if(!cancel_advance && !advanced_once) {
128 window::wait_usec(advance_timeout_first * 1000);
129 advanced_once = true;
131 if(cancel_advance) {
132 amode = ADVANCE_PAUSE;
133 cancel_advance = false;
135 window::paused(amode == ADVANCE_PAUSE);
136 } else if(amode == ADVANCE_FRAME) {
138 } else {
139 window::paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE);
140 cancel_advance = false;
142 if(amode == ADVANCE_SKIPLAG)
143 amode = ADVANCE_AUTO;
144 location_special = SPECIAL_NONE;
145 update_movie_state();
146 } else {
147 if(amode == ADVANCE_SKIPLAG_PENDING)
148 amode = ADVANCE_SKIPLAG;
149 if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
150 if(!cancel_advance) {
151 window::wait_usec(advanced_once ? to_wait_frame(get_utime()) :
152 (advance_timeout_first * 1000));
153 advanced_once = true;
155 if(cancel_advance) {
156 amode = ADVANCE_PAUSE;
157 cancel_advance = false;
159 window::paused(amode == ADVANCE_PAUSE);
160 } else {
161 window::paused((amode == ADVANCE_PAUSE));
162 cancel_advance = false;
164 location_special = SPECIAL_FRAME_START;
165 update_movie_state();
168 window::notify_screen_update();
169 window::poll_inputs();
170 if(!subframe && pending_reset_cycles >= 0)
171 set_curcontrols_reset(pending_reset_cycles);
172 else if(!subframe)
173 set_curcontrols_reset(-1);
174 controls_t tmp = get_current_controls(movb.get_movie().get_current_frame());
175 lua_callback_do_input(tmp, subframe);
176 return tmp;
179 namespace
181 //Do pending load (automatically unpauses).
182 void mark_pending_load(const std::string& filename, int lmode)
184 loadmode = lmode;
185 pending_load = filename;
186 amode = ADVANCE_AUTO;
187 window::cancel_wait();
188 window::paused(false);
191 void mark_pending_save(const std::string& filename, int smode)
193 if(smode == SAVE_MOVIE) {
194 //Just do this immediately.
195 do_save_movie(filename);
196 return;
198 queued_saves.insert(filename);
199 window::message("Pending save on '" + filename + "'");
202 class dump_watch : public av_snooper::dump_notification
204 void dump_starting() throw()
206 update_movie_state();
208 void dump_ending() throw()
210 update_movie_state();
212 } dumpwatch;
214 uint32_t lpalette[0x80000];
215 void init_palette()
217 static bool palette_init = false;
218 if(palette_init)
219 return;
220 palette_init = true;
221 for(unsigned i = 0; i < 0x80000; i++) {
222 unsigned l = (i >> 15) & 0xF;
223 unsigned r = (i >> 0) & 0x1F;
224 unsigned g = (i >> 5) & 0x1F;
225 unsigned b = (i >> 10) & 0x1F;
226 double _l = static_cast<double>(l);
227 double m = 17.0 / 31.0;
228 r = floor(m * r * _l + 0.5);
229 g = floor(m * g * _l + 0.5);
230 b = floor(m * b * _l + 0.5);
231 lpalette[i] = r * 65536 + g * 256 + b;
236 void update_movie_state()
238 auto& _status = window::get_emustatus();
239 if(!system_corrupt) {
240 std::ostringstream x;
241 x << movb.get_movie().get_current_frame() << "(";
242 if(location_special == SPECIAL_FRAME_START)
243 x << "0";
244 else if(location_special == SPECIAL_SAVEPOINT)
245 x << "S";
246 else if(location_special == SPECIAL_FRAME_VIDEO)
247 x << "V";
248 else
249 x << movb.get_movie().next_poll_number();
250 x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count();
251 _status["Frame"] = x.str();
252 } else
253 _status["Frame"] = "N/A";
254 #ifndef NO_TIME_INTERCEPT
255 if(!system_corrupt) {
256 time_t timevalue = static_cast<time_t>(our_movie.rtc_second);
257 struct tm* time_decompose = gmtime(&timevalue);
258 char datebuffer[512];
259 strftime(datebuffer, 511, "%Y%m%d(%a)T%H%M%S", time_decompose);
260 _status["RTC"] = datebuffer;
261 } else {
262 _status["RTC"] = "N/A";
264 #endif
266 std::ostringstream x;
267 x << (system_corrupt ? "C" : "-");
268 x << (av_snooper::dump_in_progress() ? "D" : "-");
269 x << (last_hires ? "H" : "-");
270 x << (last_interlace ? "I" : "-");
271 if(!system_corrupt)
272 x << (movb.get_movie().readonly_mode() ? "P" : "R");
273 else
274 x << "-";
275 _status["Flags"] = x.str();
277 if(save_jukebox.size() > 0)
278 _status["Saveslot"] = save_jukebox[save_jukebox_pointer];
279 else
280 _status.erase("Saveslot");
281 for(auto i : memory_watches) {
282 try {
283 _status["M[" + i.first + "]"] = evaluate_watch(i.second);
284 } catch(...) {
288 controls_t c;
289 if(movb.get_movie().readonly_mode())
290 c = movb.get_movie().get_controls();
291 else
292 c = get_current_controls(movb.get_movie().get_current_frame());
293 for(unsigned i = 0; i < 8; i++) {
294 unsigned pindex = controller_index_by_logical(i);
295 unsigned port = pindex >> 2;
296 unsigned dev = pindex & 3;
297 auto ctype = controller_type_by_logical(i);
298 std::ostringstream x;
299 switch(ctype) {
300 case DT_GAMEPAD:
301 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " ");
302 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " ");
303 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " ");
304 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " ");
305 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " ");
306 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " ");
307 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " ");
308 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " ");
309 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " ");
310 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " ");
311 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " ");
312 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " ");
313 break;
314 case DT_MOUSE:
315 x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " ";
316 x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " ";
317 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " ");
318 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " ");
319 break;
320 case DT_SUPERSCOPE:
321 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " ";
322 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " ";
323 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " ");
324 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " ");
325 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " ");
326 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " ");
327 break;
328 case DT_JUSTIFIER:
329 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " ";
330 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " ";
331 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " ");
332 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " ");
333 break;
334 case DT_NONE:
335 continue;
337 char y[3] = {'P', 0, 0};
338 y[1] = 49 + i;
339 _status[std::string(y)] = x.str();
344 class my_interface : public SNES::Interface
346 string path(SNES::Cartridge::Slot slot, const string &hint)
348 const char* _hint = hint;
349 std::string _hint2 = _hint;
350 std::string fwp = firmwarepath_setting;
351 std::string finalpath = fwp + "/" + _hint2;
352 return finalpath.c_str();
355 void videoRefresh(const uint32_t* data, bool hires, bool interlace, bool overscan)
357 last_hires = hires;
358 last_interlace = interlace;
359 init_palette();
360 if(stepping_into_save)
361 window::message("Got video refresh in runtosave, expect desyncs!");
362 video_refresh_done = true;
363 bool region = (SNES::system.region() == SNES::System::Region::PAL);
364 try {
365 sdump_frame(data, (hires ? SDUMP_FLAG_HIRES : 0) | (interlace ? SDUMP_FLAG_INTERLACED : 0) |
366 (overscan ? SDUMP_FLAG_OVERSCAN : 0) | (region ? SDUMP_FLAG_PAL : 0));
367 } catch(std::exception& e) {
368 messages << "Error dumping frame: " << e.what() << std::endl;
370 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
371 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
372 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
373 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
374 lcscreen ls(data, hires, interlace, overscan, region);
375 framebuffer = ls;
376 location_special = SPECIAL_FRAME_VIDEO;
377 update_movie_state();
378 redraw_framebuffer();
379 uint32_t fps_n, fps_d;
380 uint32_t fclocks;
381 if(region)
382 fclocks = interlace ? DURATION_PAL_FIELD : DURATION_PAL_FRAME;
383 else
384 fclocks = interlace ? DURATION_NTSC_FIELD : DURATION_NTSC_FRAME;
385 fps_n = SNES::system.cpu_frequency();
386 fps_d = fclocks;
387 uint32_t g = gcd(fps_n, fps_d);
388 fps_n /= g;
389 fps_d /= g;
390 av_snooper::frame(ls, fps_n, fps_d, true);
393 void audioSample(int16_t l_sample, int16_t r_sample)
395 try {
396 sdump_sample(l_sample, r_sample);
397 } catch(std::exception& e) {
398 messages << "Error dumping sample: " << e.what() << std::endl;
401 uint16_t _l = l_sample;
402 uint16_t _r = r_sample;
403 window::play_audio_sample(_l + 32768, _r + 32768);
404 av_snooper::sample(_l, _r, true);
405 //The SMP emits a sample every 768 ticks of its clock. Use this in order to keep track of time.
406 our_movie.rtc_subsecond += 768;
407 while(our_movie.rtc_subsecond >= SNES::system.apu_frequency()) {
408 our_movie.rtc_second++;
409 our_movie.rtc_subsecond -= SNES::system.apu_frequency();
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)
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;