Factor stuff related to our_movie into separate file
[lsnes.git] / mainloop.cpp
blob97a0286664168bcc88234f6e033b2963873d57d5
1 #include "mainloop.hpp"
2 #include "avsnoop.hpp"
3 #include "command.hpp"
4 #include "moviedata.hpp"
5 #include <iomanip>
6 #include "framerate.hpp"
7 #include "memorywatch.hpp"
8 #include "lua.hpp"
9 #include "rrdata.hpp"
10 #include "rom.hpp"
11 #include "movie.hpp"
12 #include "moviefile.hpp"
13 #include "render.hpp"
14 #include "window.hpp"
15 #include "settings.hpp"
16 #include "rom.hpp"
17 #include "movie.hpp"
18 #include "window.hpp"
19 #include <cassert>
20 #include <sstream>
21 #include "memorymanip.hpp"
22 #include <iostream>
23 #include <set>
24 #include "lsnes.hpp"
25 #include <sys/time.h>
26 #include <snes/snes.hpp>
27 #include <ui-libsnes/libsnes.hpp>
28 #include "framerate.hpp"
30 #define LOAD_STATE_RW 0
31 #define LOAD_STATE_RO 1
32 #define LOAD_STATE_PRESERVE 2
33 #define LOAD_STATE_MOVIE 3
34 #define LOAD_STATE_DEFAULT 4
35 #define SAVE_STATE 0
36 #define SAVE_MOVIE 1
37 #define SPECIAL_FRAME_START 0
38 #define SPECIAL_FRAME_VIDEO 1
39 #define SPECIAL_SAVEPOINT 2
40 #define SPECIAL_NONE 3
42 #define BUTTON_LEFT 0 //Gamepad
43 #define BUTTON_RIGHT 1 //Gamepad
44 #define BUTTON_UP 2 //Gamepad
45 #define BUTTON_DOWN 3 //Gamepad
46 #define BUTTON_A 4 //Gamepad
47 #define BUTTON_B 5 //Gamepad
48 #define BUTTON_X 6 //Gamepad
49 #define BUTTON_Y 7 //Gamepad
50 #define BUTTON_L 8 //Gamepad & Mouse
51 #define BUTTON_R 9 //Gamepad & Mouse
52 #define BUTTON_SELECT 10 //Gamepad
53 #define BUTTON_START 11 //Gamepad & Justifier
54 #define BUTTON_TRIGGER 12 //Superscope.
55 #define BUTTON_CURSOR 13 //Superscope & Justifier
56 #define BUTTON_PAUSE 14 //Superscope
57 #define BUTTON_TURBO 15 //Superscope
59 void update_movie_state();
60 void draw_nosignal(uint16_t* target);
61 void draw_corrupt(uint16_t* target);
64 namespace
66 enum advance_mode
68 ADVANCE_QUIT, //Quit the emulator.
69 ADVANCE_AUTO, //Normal (possibly slowed down play).
70 ADVANCE_FRAME, //Frame advance.
71 ADVANCE_SUBFRAME, //Subframe advance.
72 ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
73 ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
74 ADVANCE_PAUSE, //Unconditional pause.
77 //Analog input physical controller IDs and types.
78 int analog[4] = {-1, -1, -1};
79 bool analog_is_mouse[4] = {false, false, false};
80 //Memory watches.
81 std::map<std::string, std::string> memory_watches;
82 //Previous mouse mask.
83 int prev_mouse_mask = 0;
84 //Flags related to repeating advance.
85 bool advanced_once;
86 bool cancel_advance;
87 //Our ROM.
88 struct loaded_rom* our_rom;
89 //Handle to the graphics system.
90 window* win;
91 //The SNES screen.
92 struct screen scr;
93 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
94 enum advance_mode amode;
95 //Mode and filename of pending load, one of LOAD_* constants.
96 int loadmode;
97 std::string pending_load;
98 //Queued saves (all savestates).
99 std::set<std::string> queued_saves;
100 bool stepping_into_save;
101 //Current controls.
102 controls_t curcontrols;
103 controls_t autoheld_controls;
104 //Emulator status area.
105 std::map<std::string, std::string>* status;
106 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
107 long pending_reset_cycles = -1;
108 //Set by every video refresh.
109 bool video_refresh_done;
110 //Special subframe location. One of SPECIAL_* constants.
111 int location_special;
112 //Types of connected controllers.
113 enum porttype_t porttype1 = PT_GAMEPAD;
114 enum porttype_t porttype2 = PT_NONE;
115 //System corrupt flag.
116 bool system_corrupt;
117 //Current screen, no signal screen and corrupt screen.
118 lcscreen framebuffer;
119 lcscreen nosignal_screen;
120 lcscreen corrupt_screen;
121 //Few settings.
122 numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500);
123 numeric_setting savecompression("savecompression", 0, 9, 7);
125 void send_analog_input(int32_t x, int32_t y, unsigned index)
127 if(analog_is_mouse[index]) {
128 x -= 256;
129 y -= (framebuffer.height / 2);
130 } else {
131 x /= 2;
132 y /= 2;
134 if(analog[index] < 0) {
135 out(win) << "No analog controller in slot #" << (index + 1) << std::endl;
136 return;
138 curcontrols(analog[index] >> 2, analog[index] & 3, 0) = x;
139 curcontrols(analog[index] >> 2, analog[index] & 3, 1) = y;
142 void redraw_framebuffer()
144 uint32_t hscl = 1, vscl = 1;
145 if(framebuffer.width < 512)
146 hscl = 2;
147 if(framebuffer.height < 400)
148 vscl = 2;
149 render_queue rq;
150 struct lua_render_context lrc;
151 lrc.left_gap = 0;
152 lrc.right_gap = 0;
153 lrc.bottom_gap = 0;
154 lrc.top_gap = 0;
155 lrc.queue = &rq;
156 lrc.width = framebuffer.width * hscl;
157 lrc.height = framebuffer.height * vscl;
158 lrc.rshift = scr.active_rshift;
159 lrc.gshift = scr.active_gshift;
160 lrc.bshift = scr.active_bshift;
161 lua_callback_do_paint(&lrc, win);
162 scr.reallocate(framebuffer.width * hscl + lrc.left_gap + lrc.right_gap, framebuffer.height * vscl +
163 lrc.top_gap + lrc.bottom_gap, lrc.left_gap, lrc.top_gap);
164 scr.copy_from(framebuffer, hscl, vscl);
165 //We would want divide by 2, but we'll do it ourselves in order to do mouse.
166 win->set_window_compensation(lrc.left_gap, lrc.top_gap, 1, 1);
167 rq.run(scr);
168 win->notify_screen_update();
171 void fill_special_frames()
173 uint16_t buf[512*448];
174 draw_nosignal(buf);
175 nosignal_screen = lcscreen(buf, 512, 448);
176 draw_corrupt(buf);
177 corrupt_screen = lcscreen(buf, 512, 448);
181 class firmware_path_setting : public setting
183 public:
184 firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "./"; default_firmware = true; }
185 void blank() throw(std::bad_alloc, std::runtime_error)
187 _firmwarepath = "./";
188 default_firmware = true;
191 bool is_set() throw()
193 return !default_firmware;
196 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
198 _firmwarepath = value;
199 default_firmware = false;
202 std::string get() throw(std::bad_alloc)
204 return _firmwarepath;
207 operator std::string() throw(std::bad_alloc)
209 return _firmwarepath;
211 private:
212 std::string _firmwarepath;
213 bool default_firmware;
214 } firmwarepath_setting;
216 class mymovielogic : public movie_logic
218 public:
219 mymovielogic() : movie_logic(dummy_movie) {}
221 controls_t update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
223 if(lua_requests_subframe_paint)
224 redraw_framebuffer();
226 if(subframe) {
227 if(amode == ADVANCE_SUBFRAME) {
228 if(!cancel_advance && !advanced_once) {
229 win->wait_msec(advance_timeout_first);
230 advanced_once = true;
232 if(cancel_advance) {
233 amode = ADVANCE_PAUSE;
234 cancel_advance = false;
236 win->paused(amode == ADVANCE_PAUSE);
237 } else if(amode == ADVANCE_FRAME) {
239 } else {
240 win->paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE);
241 cancel_advance = false;
243 if(amode == ADVANCE_SKIPLAG)
244 amode = ADVANCE_AUTO;
245 location_special = SPECIAL_NONE;
246 update_movie_state();
247 } else {
248 if(amode == ADVANCE_SKIPLAG_PENDING)
249 amode = ADVANCE_SKIPLAG;
250 if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
251 if(!cancel_advance) {
252 win->wait_msec(advanced_once ? to_wait_frame(get_ticks_msec()) :
253 advance_timeout_first);
254 advanced_once = true;
256 if(cancel_advance) {
257 amode = ADVANCE_PAUSE;
258 cancel_advance = false;
260 win->paused(amode == ADVANCE_PAUSE);
261 } else {
262 win->paused((amode == ADVANCE_PAUSE));
263 cancel_advance = false;
265 location_special = SPECIAL_FRAME_START;
266 update_movie_state();
268 win->notify_screen_update();
269 win->poll_inputs();
270 if(!subframe && pending_reset_cycles >= 0) {
271 curcontrols(CONTROL_SYSTEM_RESET) = 1;
272 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = pending_reset_cycles / 10000;
273 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = pending_reset_cycles % 10000;
274 } else if(!subframe) {
275 curcontrols(CONTROL_SYSTEM_RESET) = 0;
276 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = 0;
277 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = 0;
279 controls_t tmp = curcontrols ^ autoheld_controls;
280 lua_callback_do_input(tmp, subframe, win);
281 return tmp;
283 private:
284 movie dummy_movie;
287 namespace
289 mymovielogic movb;
291 //Lookup physical controller id based on UI controller id and given types (-1 if invalid).
292 int lookup_physical_controller(unsigned ui_id)
294 bool p1multitap = (porttype1 == PT_MULTITAP);
295 unsigned p1devs = port_types[porttype1].devices;
296 unsigned p2devs = port_types[porttype2].devices;
297 if(ui_id >= p1devs + p2devs)
298 return -1;
299 if(!p1multitap)
300 if(ui_id < p1devs)
301 return ui_id;
302 else
303 return 4 + ui_id - p1devs;
304 else
305 if(ui_id == 0)
306 return 0;
307 else if(ui_id < 5)
308 return ui_id + 3;
309 else
310 return ui_id - 4;
313 //Look up controller type given UI controller id (note: Non-present controllers give PT_NONE, not the type
314 //of port, multitap controllers give PT_GAMEPAD, not PT_MULTITAP, and justifiers give PT_JUSTIFIER, not
315 //PT_JUSTIFIERS).
316 enum devicetype_t lookup_controller_type(unsigned ui_id)
318 int x = lookup_physical_controller(ui_id);
319 if(x < 0)
320 return DT_NONE;
321 enum porttype_t rawtype = (x & 4) ? porttype2 : porttype1;
322 if((x & 3) < port_types[rawtype].devices)
323 return port_types[rawtype].dtype;
324 else
325 return DT_NONE;
328 void set_analog_controllers()
330 unsigned index = 0;
331 for(unsigned i = 0; i < 8; i++) {
332 enum devicetype_t t = lookup_controller_type(i);
333 analog_is_mouse[index] = (t == DT_MOUSE);
334 if(t == DT_MOUSE || t == DT_SUPERSCOPE || t == DT_JUSTIFIER) {
335 analog[index++] = lookup_physical_controller(i);
336 } else
337 analog[index] = -1;
339 for(; index < 3; index++)
340 analog[index] = -1;
343 std::map<std::string, std::pair<unsigned, unsigned>> buttonmap;
345 const char* buttonnames[] = {
346 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
347 "pause", "turbo"
350 void init_buttonmap()
352 static int done = 0;
353 if(done)
354 return;
355 for(unsigned i = 0; i < 8; i++)
356 for(unsigned j = 0; j < sizeof(buttonnames) / sizeof(buttonnames[0]); j++) {
357 std::ostringstream x;
358 x << (i + 1) << buttonnames[j];
359 buttonmap[x.str()] = std::make_pair(i, j);
361 done = 1;
364 //Do button action.
365 void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor = false)
367 enum devicetype_t p = lookup_controller_type(ui_id);
368 int x = lookup_physical_controller(ui_id);
369 int bid = -1;
370 switch(p) {
371 case DT_NONE:
372 out(win) << "No such controller #" << (ui_id + 1) << std::endl;
373 return;
374 case DT_GAMEPAD:
375 switch(button) {
376 case BUTTON_UP: bid = SNES_DEVICE_ID_JOYPAD_UP; break;
377 case BUTTON_DOWN: bid = SNES_DEVICE_ID_JOYPAD_DOWN; break;
378 case BUTTON_LEFT: bid = SNES_DEVICE_ID_JOYPAD_LEFT; break;
379 case BUTTON_RIGHT: bid = SNES_DEVICE_ID_JOYPAD_RIGHT; break;
380 case BUTTON_A: bid = SNES_DEVICE_ID_JOYPAD_A; break;
381 case BUTTON_B: bid = SNES_DEVICE_ID_JOYPAD_B; break;
382 case BUTTON_X: bid = SNES_DEVICE_ID_JOYPAD_X; break;
383 case BUTTON_Y: bid = SNES_DEVICE_ID_JOYPAD_Y; break;
384 case BUTTON_L: bid = SNES_DEVICE_ID_JOYPAD_L; break;
385 case BUTTON_R: bid = SNES_DEVICE_ID_JOYPAD_R; break;
386 case BUTTON_SELECT: bid = SNES_DEVICE_ID_JOYPAD_SELECT; break;
387 case BUTTON_START: bid = SNES_DEVICE_ID_JOYPAD_START; break;
388 default:
389 out(win) << "Invalid button for gamepad" << std::endl;
390 return;
392 break;
393 case DT_MOUSE:
394 switch(button) {
395 case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
396 case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
397 default:
398 out(win) << "Invalid button for mouse" << std::endl;
399 return;
401 break;
402 case DT_JUSTIFIER:
403 switch(button) {
404 case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
405 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
406 default:
407 out(win) << "Invalid button for justifier" << std::endl;
408 return;
410 break;
411 case DT_SUPERSCOPE:
412 switch(button) {
413 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER; break;
414 case BUTTON_CURSOR: bid = SNES_DEVICE_ID_SUPER_SCOPE_CURSOR; break;
415 case BUTTON_PAUSE: bid = SNES_DEVICE_ID_SUPER_SCOPE_PAUSE; break;
416 case BUTTON_TURBO: bid = SNES_DEVICE_ID_SUPER_SCOPE_TURBO; break;
417 default:
418 out(win) << "Invalid button for superscope" << std::endl;
419 return;
421 break;
423 if(do_xor)
424 autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
425 else
426 curcontrols((x & 4) ? 1 : 0, x & 3, bid) = newstate;
429 //Save state.
430 void do_save_state(const std::string& filename) throw(std::bad_alloc,
431 std::runtime_error)
433 lua_callback_pre_save(filename, true, win);
434 try {
435 uint64_t origtime = get_ticks_msec();
436 our_movie.is_savestate = true;
437 our_movie.sram = save_sram();
438 our_movie.savestate = save_core_state();
439 framebuffer.save(our_movie.screenshot);
440 auto s = movb.get_movie().save_state();
441 our_movie.movie_state.resize(s.size());
442 memcpy(&our_movie.movie_state[0], &s[0], s.size());
443 our_movie.input = movb.get_movie().save();
444 our_movie.save(filename, savecompression);
445 uint64_t took = get_ticks_msec() - origtime;
446 out(win) << "Saved state '" << filename << "' in " << took << "ms." << std::endl;
447 lua_callback_post_save(filename, true, win);
448 } catch(std::bad_alloc& e) {
449 OOM_panic(win);
450 } catch(std::exception& e) {
451 win->message(std::string("Save failed: ") + e.what());
452 lua_callback_err_save(filename, win);
456 //Save movie.
457 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
459 lua_callback_pre_save(filename, false, win);
460 try {
461 uint64_t origtime = get_ticks_msec();
462 our_movie.is_savestate = false;
463 our_movie.input = movb.get_movie().save();
464 our_movie.save(filename, savecompression);
465 uint64_t took = get_ticks_msec() - origtime;
466 out(win) << "Saved movie '" << filename << "' in " << took << "ms." << std::endl;
467 lua_callback_post_save(filename, false, win);
468 } catch(std::bad_alloc& e) {
469 OOM_panic(win);
470 } catch(std::exception& e) {
471 win->message(std::string("Save failed: ") + e.what());
472 lua_callback_err_save(filename, win);
476 void warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot, const std::string& name)
478 if(mhash != slot.sha256) {
479 out(win) << "WARNING: " << name << " hash mismatch!" << std::endl
480 << "\tMovie: " << mhash << std::endl
481 << "\tOur ROM: " << slot.sha256 << std::endl;
485 void set_dev(bool port, porttype_t t, bool set_core = true)
487 //return;
488 switch(set_core ? t : PT_INVALID) {
489 case PT_NONE:
490 snes_set_controller_port_device(port, SNES_DEVICE_NONE);
491 break;
492 case PT_GAMEPAD:
493 snes_set_controller_port_device(port, SNES_DEVICE_JOYPAD);
494 break;
495 case PT_MULTITAP:
496 snes_set_controller_port_device(port, SNES_DEVICE_MULTITAP);
497 break;
498 case PT_MOUSE:
499 snes_set_controller_port_device(port, SNES_DEVICE_MOUSE);
500 break;
501 case PT_SUPERSCOPE:
502 snes_set_controller_port_device(port, SNES_DEVICE_SUPER_SCOPE);
503 break;
504 case PT_JUSTIFIER:
505 snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIER);
506 break;
507 case PT_JUSTIFIERS:
508 snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIERS);
509 break;
510 case PT_INVALID:
513 if(port)
514 porttype2 = t;
515 else
516 porttype1 = t;
517 set_analog_controllers();
520 //Load state from loaded movie file (does not catch errors).
521 void do_load_state(struct moviefile& _movie, int lmode)
523 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
524 if(gtype::toromtype(_movie.gametype) != our_rom->rtype)
525 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
526 if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO)
527 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
529 if(_movie.coreversion != bsnes_core_version) {
530 if(will_load_state) {
531 std::ostringstream x;
532 x << "ERROR: Emulator core version mismatch!" << std::endl
533 << "\tThis version: " << bsnes_core_version << std::endl
534 << "\tFile is from: " << _movie.coreversion << std::endl;
535 throw std::runtime_error(x.str());
536 } else
537 out(win) << "WARNING: Emulator core version mismatch!" << std::endl
538 << "\tThis version: " << bsnes_core_version << std::endl
539 << "\tFile is from: " << _movie.coreversion << std::endl;
541 warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1");
542 warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1");
543 warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2");
544 warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2");
545 warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3");
546 warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3");
548 SNES::config.random = false;
549 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
551 movie newmovie;
552 if(lmode == LOAD_STATE_PRESERVE)
553 newmovie = movb.get_movie();
554 else
555 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
557 if(will_load_state) {
558 std::vector<unsigned char> tmp;
559 tmp.resize(_movie.movie_state.size());
560 memcpy(&tmp[0], &_movie.movie_state[0], tmp.size());
561 newmovie.restore_state(tmp, true);
564 //Negative return.
565 rrdata::read_base(_movie.projectid);
566 rrdata::add_internal();
567 try {
568 our_rom->region = gtype::toromregion(_movie.gametype);
569 our_rom->load();
571 if(_movie.is_savestate && lmode != LOAD_STATE_MOVIE) {
572 //Load the savestate and movie state.
573 set_dev(false, _movie.port1);
574 set_dev(true, _movie.port2);
575 load_core_state(_movie.savestate);
576 framebuffer.load(_movie.screenshot);
577 } else {
578 load_sram(_movie.movie_sram, win);
579 set_dev(false, _movie.port1);
580 set_dev(true, _movie.port2);
581 framebuffer = nosignal_screen;
583 } catch(std::bad_alloc& e) {
584 OOM_panic(win);
585 } catch(std::exception& e) {
586 system_corrupt = true;
587 throw;
590 //Okay, copy the movie data.
591 our_movie = _movie;
592 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
593 our_movie.is_savestate = false;
594 our_movie.host_memory.clear();
596 movb.get_movie() = newmovie;
597 //Activate RW mode if needed.
598 if(lmode == LOAD_STATE_RW)
599 movb.get_movie().readonly_mode(false);
600 if(lmode == LOAD_STATE_DEFAULT && !(movb.get_movie().get_frame_count()))
601 movb.get_movie().readonly_mode(false);
602 out(win) << "ROM Type ";
603 switch(our_rom->rtype) {
604 case ROMTYPE_SNES:
605 out(win) << "SNES";
606 break;
607 case ROMTYPE_BSX:
608 out(win) << "BS-X";
609 break;
610 case ROMTYPE_BSXSLOTTED:
611 out(win) << "BS-X slotted";
612 break;
613 case ROMTYPE_SUFAMITURBO:
614 out(win) << "Sufami Turbo";
615 break;
616 case ROMTYPE_SGB:
617 out(win) << "Super Game Boy";
618 break;
619 default:
620 out(win) << "Unknown";
621 break;
623 out(win) << " region ";
624 switch(our_rom->region) {
625 case REGION_PAL:
626 out(win) << "PAL";
627 break;
628 case REGION_NTSC:
629 out(win) << "NTSC";
630 break;
631 default:
632 out(win) << "Unknown";
633 break;
635 out(win) << std::endl;
636 uint64_t mlength = _movie.get_movie_length();
638 mlength += 999999;
639 std::ostringstream x;
640 if(mlength > 3600000000000) {
641 x << mlength / 3600000000000 << ":";
642 mlength %= 3600000000000;
644 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
645 mlength %= 60000000000;
646 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
647 mlength %= 1000000000;
648 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
649 out(win) << "Rerecords " << _movie.rerecords << " length " << x.str() << " ("
650 << _movie.get_frame_count() << " frames)" << std::endl;
653 if(_movie.gamename != "")
654 out(win) << "Game Name: " << _movie.gamename << std::endl;
655 for(size_t i = 0; i < _movie.authors.size(); i++)
656 out(win) << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
657 << std::endl;
660 //Load state
661 void do_load_state(const std::string& filename, int lmode)
663 uint64_t origtime = get_ticks_msec();
664 lua_callback_pre_load(filename, win);
665 struct moviefile mfile;
666 try {
667 mfile = moviefile(filename);
668 } catch(std::bad_alloc& e) {
669 OOM_panic(win);
670 } catch(std::exception& e) {
671 win->message("Can't read movie/savestate '" + filename + "': " + e.what());
672 lua_callback_err_load(filename, win);
673 return;
675 try {
676 do_load_state(mfile, lmode);
677 uint64_t took = get_ticks_msec() - origtime;
678 out(win) << "Loaded '" << filename << "' in " << took << "ms." << std::endl;
679 lua_callback_post_load(filename, our_movie.is_savestate, win);
680 } catch(std::bad_alloc& e) {
681 OOM_panic(win);
682 } catch(std::exception& e) {
683 win->message("Can't load movie/savestate '" + filename + "': " + e.what());
684 lua_callback_err_load(filename, win);
685 return;
689 //Do pending load (automatically unpauses).
690 void mark_pending_load(const std::string& filename, int lmode)
692 loadmode = lmode;
693 pending_load = filename;
694 amode = ADVANCE_AUTO;
695 win->cancel_wait();
696 win->paused(false);
699 //Mark pending save (movies save immediately).
700 void mark_pending_save(const std::string& filename, int smode)
702 if(smode == SAVE_MOVIE) {
703 //Just do this immediately.
704 do_save_movie(filename);
705 return;
707 queued_saves.insert(filename);
708 win->message("Pending save on '" + filename + "'");
711 class dump_watch : public av_snooper::dump_notification
713 void dump_starting() throw()
715 update_movie_state();
717 void dump_ending() throw()
719 update_movie_state();
721 } dumpwatch;
724 movie& get_movie()
726 return movb.get_movie();
729 void update_movie_state()
731 auto& _status = win->get_emustatus();
733 std::ostringstream x;
734 x << movb.get_movie().get_current_frame() << "(";
735 if(location_special == SPECIAL_FRAME_START)
736 x << "0";
737 else if(location_special == SPECIAL_SAVEPOINT)
738 x << "S";
739 else if(location_special == SPECIAL_FRAME_VIDEO)
740 x << "V";
741 else
742 x << movb.get_movie().next_poll_number();
743 x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count();
744 _status["Frame"] = x.str();
747 std::ostringstream x;
748 if(movb.get_movie().readonly_mode())
749 x << "PLAY ";
750 else
751 x << "REC ";
752 if(av_snooper::dump_in_progress())
753 x << "CAP ";
754 _status["Flags"] = x.str();
756 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
757 try {
758 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
759 } catch(...) {
762 controls_t c;
763 if(movb.get_movie().readonly_mode())
764 c = movb.get_movie().get_controls();
765 else
766 c = curcontrols ^ autoheld_controls;
767 for(unsigned i = 0; i < 8; i++) {
768 unsigned pindex = lookup_physical_controller(i);
769 unsigned port = pindex >> 2;
770 unsigned dev = pindex & 3;
771 auto ctype = lookup_controller_type(i);
772 std::ostringstream x;
773 switch(ctype) {
774 case DT_GAMEPAD:
775 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " ");
776 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " ");
777 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " ");
778 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " ");
779 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " ");
780 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " ");
781 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " ");
782 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " ");
783 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " ");
784 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " ");
785 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " ");
786 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " ");
787 break;
788 case DT_MOUSE:
789 x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " ";
790 x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " ";
791 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " ");
792 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " ");
793 break;
794 case DT_SUPERSCOPE:
795 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " ";
796 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " ";
797 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " ");
798 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " ");
799 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " ");
800 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " ");
801 break;
802 case DT_JUSTIFIER:
803 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " ";
804 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " ";
805 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " ");
806 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " ");
807 break;
808 case DT_NONE:
809 continue;
811 char y[3] = {'P', 0, 0};
812 y[1] = 49 + i;
813 _status[std::string(y)] = x.str();
818 class my_interface : public SNES::Interface
820 string path(SNES::Cartridge::Slot slot, const string &hint)
822 return static_cast<std::string>(firmwarepath_setting).c_str();
825 void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan)
827 if(stepping_into_save)
828 win->message("Got video refresh in runtosave, expect desyncs!");
829 video_refresh_done = true;
830 bool region = (SNES::system.region() == SNES::System::Region::PAL);
831 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
832 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
833 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
834 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
835 lcscreen ls(data, hires, interlace, overscan, region);
836 framebuffer = ls;
837 location_special = SPECIAL_FRAME_VIDEO;
838 update_movie_state();
839 redraw_framebuffer();
840 uint32_t fps_n, fps_d;
841 if(region) {
842 fps_n = 322445;
843 fps_d = 6448;
844 } else {
845 fps_n = 10738636;
846 fps_d = 178683;
848 av_snooper::frame(ls, fps_n, fps_d, win);
851 void audio_sample(int16_t l_sample, int16_t r_sample)
853 uint16_t _l = l_sample;
854 uint16_t _r = r_sample;
855 win->play_audio_sample(_l + 32768, _r + 32768);
856 av_snooper::sample(_l, _r, win);
859 void audio_sample(uint16_t l_sample, uint16_t r_sample)
861 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
862 win->play_audio_sample(l_sample + 32768, r_sample + 32768);
863 av_snooper::sample(l_sample, r_sample, win);
866 int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
868 int16_t x;
869 x = movb.input_poll(port, index, id);
870 //if(id == SNES_DEVICE_ID_JOYPAD_START)
871 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
872 lua_callback_snoop_input(port ? 1 : 0, index, id, x, win);
873 return x;
877 namespace
879 class quit_emulator_cmd : public command
881 public:
882 quit_emulator_cmd() throw(std::bad_alloc) : command("quit-emulator") {}
883 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
885 if(args == "/y" || win->modal_message("Really quit?", true)) {
886 amode = ADVANCE_QUIT;
887 win->paused(false);
888 win->cancel_wait();
891 std::string get_short_help() throw(std::bad_alloc) { return "Quit the emulator"; }
892 std::string get_long_help() throw(std::bad_alloc)
894 return "Syntax: quit-emulator [/y]\n"
895 "Quits emulator (/y => don't ask for confirmation).\n";
897 } quitemu;
899 class pause_emulator_cmd : public command
901 public:
902 pause_emulator_cmd() throw(std::bad_alloc) : command("pause-emulator") {}
903 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
905 if(args != "")
906 throw std::runtime_error("This command does not take parameters");
907 if(amode != ADVANCE_AUTO) {
908 amode = ADVANCE_AUTO;
909 win->paused(false);
910 win->cancel_wait();
911 win->message("Unpaused");
912 } else {
913 win->cancel_wait();
914 cancel_advance = false;
915 amode = ADVANCE_PAUSE;
916 win->message("Paused");
919 std::string get_short_help() throw(std::bad_alloc) { return "(Un)pause the emulator"; }
920 std::string get_long_help() throw(std::bad_alloc)
922 return "Syntax: pause-emulator\n"
923 "(Un)pauses the emulator.\n";
925 } pauseemu;
927 class padvance_frame_cmd : public command
929 public:
930 padvance_frame_cmd() throw(std::bad_alloc) : command("+advance-frame") {}
931 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
933 if(args != "")
934 throw std::runtime_error("This command does not take parameters");
935 amode = ADVANCE_FRAME;
936 cancel_advance = false;
937 advanced_once = false;
938 win->cancel_wait();
939 win->paused(false);
941 std::string get_short_help() throw(std::bad_alloc) { return "Advance one frame"; }
942 std::string get_long_help() throw(std::bad_alloc)
944 return "Syntax: +advance-frame\n"
945 "Advances the emulation by one frame.\n";
947 } padvancef;
949 class nadvance_frame_cmd : public command
951 public:
952 nadvance_frame_cmd() throw(std::bad_alloc) : command("-advance-frame") {}
953 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
955 if(args != "")
956 throw std::runtime_error("This command does not take parameters");
957 cancel_advance = true;
958 win->cancel_wait();
959 win->paused(false);
961 } nadvancef;
963 class padvance_poll_cmd : public command
965 public:
966 padvance_poll_cmd() throw(std::bad_alloc) : command("+advance-poll") {}
967 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
969 if(args != "")
970 throw std::runtime_error("This command does not take parameters");
971 amode = ADVANCE_SUBFRAME;
972 cancel_advance = false;
973 advanced_once = false;
974 win->cancel_wait();
975 win->paused(false);
977 std::string get_short_help() throw(std::bad_alloc) { return "Advance one subframe"; }
978 std::string get_long_help() throw(std::bad_alloc)
980 return "Syntax: +advance-poll\n"
981 "Advances the emulation by one subframe.\n";
983 } padvancep;
985 class nadvance_poll_cmd : public command
987 public:
988 nadvance_poll_cmd() throw(std::bad_alloc) : command("-advance-poll") {}
990 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
992 if(args != "")
993 throw std::runtime_error("This command does not take parameters");
994 cancel_advance = true;
995 win->cancel_wait();
996 win->paused(false);
998 } nadvancep;
1000 class advance_skiplag_cmd : public command
1002 public:
1003 advance_skiplag_cmd() throw(std::bad_alloc) : command("advance-skiplag") {}
1004 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1006 if(args != "")
1007 throw std::runtime_error("This command does not take parameters");
1008 amode = ADVANCE_SKIPLAG;
1009 win->cancel_wait();
1010 win->paused(false);
1012 std::string get_short_help() throw(std::bad_alloc) { return "Skip to next poll"; }
1013 std::string get_long_help() throw(std::bad_alloc)
1015 return "Syntax: advance-skiplag\n"
1016 "Advances the emulation to the next poll.\n";
1018 } skiplagc;
1020 class reset_cmd : public command
1022 public:
1023 reset_cmd() throw(std::bad_alloc) : command("reset") {}
1024 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1026 if(args != "")
1027 throw std::runtime_error("This command does not take parameters");
1028 pending_reset_cycles = 0;
1030 std::string get_short_help() throw(std::bad_alloc) { return "Reset the SNES"; }
1031 std::string get_long_help() throw(std::bad_alloc)
1033 return "Syntax: reset\n"
1034 "Resets the SNES in beginning of the next frame.\n";
1036 } resetc;
1038 class load_state_cmd : public command
1040 public:
1041 load_state_cmd() throw(std::bad_alloc) : command("load-state") {}
1042 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1044 if(args == "")
1045 throw std::runtime_error("Filename required");
1046 mark_pending_load(args, LOAD_STATE_RW);
1048 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1049 std::string get_long_help() throw(std::bad_alloc)
1051 return "Syntax: load-state <file>\n"
1052 "Loads SNES state from <file> in Read/Write mode\n";
1054 } loadstatec;
1056 class load_readonly_cmd : public command
1058 public:
1059 load_readonly_cmd() throw(std::bad_alloc) : command("load-readonly") {}
1060 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1062 if(args == "")
1063 throw std::runtime_error("Filename required");
1064 mark_pending_load(args, LOAD_STATE_RO);
1066 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1067 std::string get_long_help() throw(std::bad_alloc)
1069 return "Syntax: load-readonly <file>\n"
1070 "Loads SNES state from <file> in Read-only mode\n";
1072 } loadreadonlyc;
1074 class load_preserve_cmd : public command
1076 public:
1077 load_preserve_cmd() throw(std::bad_alloc) : command("load-preserve") {}
1078 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1080 if(args == "")
1081 throw std::runtime_error("Filename required");
1082 mark_pending_load(args, LOAD_STATE_PRESERVE);
1084 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1085 std::string get_long_help() throw(std::bad_alloc)
1087 return "Syntax: load-preserve <file>\n"
1088 "Loads SNES state from <file> preserving input\n";
1090 } loadpreservec;
1092 class load_movie_cmd : public command
1094 public:
1095 load_movie_cmd() throw(std::bad_alloc) : command("load-movie") {}
1096 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1098 if(args == "")
1099 throw std::runtime_error("Filename required");
1100 mark_pending_load(args, LOAD_STATE_MOVIE);
1102 std::string get_short_help() throw(std::bad_alloc) { return "Load movie"; }
1103 std::string get_long_help() throw(std::bad_alloc)
1105 return "Syntax: load-movie <file>\n"
1106 "Loads movie from <file>\n";
1108 } loadmoviec;
1110 class save_state_cmd : public command
1112 public:
1113 save_state_cmd() throw(std::bad_alloc) : command("save-state") {}
1114 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1116 if(args == "")
1117 throw std::runtime_error("Filename required");
1118 mark_pending_save(args, SAVE_STATE);
1121 std::string get_short_help() throw(std::bad_alloc) { return "Save state"; }
1122 std::string get_long_help() throw(std::bad_alloc)
1124 return "Syntax: save-state <file>\n"
1125 "Saves SNES state to <file>\n";
1127 } savestatec;
1129 class save_movie_cmd : public command
1131 public:
1132 save_movie_cmd() throw(std::bad_alloc) : command("save-movie") {}
1133 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1135 if(args == "")
1136 throw std::runtime_error("Filename required");
1137 mark_pending_save(args, SAVE_MOVIE);
1139 std::string get_short_help() throw(std::bad_alloc) { return "Save movie"; }
1140 std::string get_long_help() throw(std::bad_alloc)
1142 return "Syntax: save-movie <file>\n"
1143 "Saves movie to <file>\n";
1145 } savemoviec;
1147 class set_rwmode_cmd : public command
1149 public:
1150 set_rwmode_cmd() throw(std::bad_alloc) : command("set-rwmode") {}
1151 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1153 if(args != "")
1154 throw std::runtime_error("This command does not take parameters");
1155 movb.get_movie().readonly_mode(false);
1156 lua_callback_do_readwrite(win);
1157 update_movie_state();
1158 win->notify_screen_update();
1160 std::string get_short_help() throw(std::bad_alloc) { return "Switch to read/write mode"; }
1161 std::string get_long_help() throw(std::bad_alloc)
1163 return "Syntax: set-rwmode\n"
1164 "Switches to read/write mode\n";
1166 } setrwc;
1168 class repainter : public command
1170 public:
1171 repainter() throw(std::bad_alloc) : command("repaint") {}
1172 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1174 if(args != "")
1175 throw std::runtime_error("This command does not take parameters");
1176 redraw_framebuffer();
1178 std::string get_short_help() throw(std::bad_alloc) { return "Redraw the screen"; }
1179 std::string get_long_help() throw(std::bad_alloc)
1181 return "Syntax: repaint\n"
1182 "Redraws the screen\n";
1184 } repaintc;
1186 class set_gamename_cmd : public command
1188 public:
1189 set_gamename_cmd() throw(std::bad_alloc) : command("set-gamename") {}
1190 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1192 our_movie.gamename = args;
1193 out(win) << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
1195 std::string get_short_help() throw(std::bad_alloc) { return "Set the game name"; }
1196 std::string get_long_help() throw(std::bad_alloc)
1198 return "Syntax: set-gamename <name>\n"
1199 "Sets the game name to <name>\n";
1201 } setnamec;
1203 class add_watch_command : public command
1205 public:
1206 add_watch_command() throw(std::bad_alloc) : command("add-watch") {}
1207 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1209 tokensplitter t(args);
1210 std::string name = t;
1211 if(name == "" || t.tail() == "")
1212 throw std::runtime_error("syntax: add-watch <name> <expr>");
1213 std::cerr << "Add watch: '" << name << "'" << std::endl;
1214 memory_watches[name] = t.tail();
1215 update_movie_state();
1217 std::string get_short_help() throw(std::bad_alloc) { return "Add a memory watch"; }
1218 std::string get_long_help() throw(std::bad_alloc)
1220 return "Syntax: add-watch <name> <expression>\n"
1221 "Adds a new memory watch\n";
1223 } addwatchc;
1225 class remove_watch_command : public command
1227 public:
1228 remove_watch_command() throw(std::bad_alloc) : command("remove-watch") {}
1229 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1231 tokensplitter t(args);
1232 std::string name = t;
1233 if(name == "" || t.tail() != "") {
1234 out(win) << "syntax: remove-watch <name>" << std::endl;
1235 return;
1237 std::cerr << "Erase watch: '" << name << "'" << std::endl;
1238 memory_watches.erase(name);
1239 auto& _status = win->get_emustatus();
1240 _status.erase("M[" + name + "]");
1241 update_movie_state(); }
1242 std::string get_short_help() throw(std::bad_alloc) { return "Remove a memory watch"; }
1243 std::string get_long_help() throw(std::bad_alloc)
1245 return "Syntax: remove-watch <name>\n"
1246 "Removes a memory watch\n";
1248 } removewatchc;
1250 class test_1 : public command
1252 public:
1253 test_1() throw(std::bad_alloc) : command("test-1") {}
1254 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1256 framebuffer = nosignal_screen;
1257 redraw_framebuffer();
1259 } test1c;
1261 class test_2 : public command
1263 public:
1264 test_2() throw(std::bad_alloc) : command("test-2") {}
1265 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1267 framebuffer = corrupt_screen;
1268 redraw_framebuffer();
1270 } test2c;
1272 class test_3 : public command
1274 public:
1275 test_3() throw(std::bad_alloc) : command("test-3") {}
1276 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1278 while(1);
1280 } test3c;
1282 class screenshot_command : public command
1284 public:
1285 screenshot_command() throw(std::bad_alloc) : command("take-screenshot") {}
1286 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1288 if(args == "")
1289 throw std::runtime_error("Filename required");
1290 framebuffer.save_png(args);
1291 out(win) << "Saved PNG screenshot" << std::endl;
1293 std::string get_short_help() throw(std::bad_alloc) { return "Takes a screenshot"; }
1294 std::string get_long_help() throw(std::bad_alloc)
1296 return "Syntax: take-screenshot <file>\n"
1297 "Saves screenshot to PNG file <file>\n";
1299 } screenshotc;
1301 class mouse_button_handler : public command
1303 public:
1304 mouse_button_handler() throw(std::bad_alloc) : command("mouse_button") {}
1305 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1307 tokensplitter t(args);
1308 std::string x = t;
1309 std::string y = t;
1310 std::string b = t;
1311 int _x = atoi(x.c_str());
1312 int _y = atoi(y.c_str());
1313 int _b = atoi(b.c_str());
1314 if(_b & ~prev_mouse_mask & 1)
1315 send_analog_input(_x, _y, 0);
1316 if(_b & ~prev_mouse_mask & 2)
1317 send_analog_input(_x, _y, 1);
1318 if(_b & ~prev_mouse_mask & 4)
1319 send_analog_input(_x, _y, 2);
1320 prev_mouse_mask = _b;
1322 } mousebuttonh;
1324 class button_action : public command
1326 public:
1327 button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
1328 throw(std::bad_alloc)
1329 : command(cmd)
1331 commandn = cmd;
1332 type = _type;
1333 controller = _controller;
1334 button = _button;
1336 ~button_action() throw() {}
1337 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1339 if(args != "")
1340 throw std::runtime_error("This command does not take parameters");
1341 init_buttonmap();
1342 if(!buttonmap.count(button))
1343 return;
1344 auto i = buttonmap[button];
1345 do_button_action(i.first, i.second, (type != 1) ? 1 : 0, (type == 2));
1346 update_movie_state();
1347 win->notify_screen_update();
1349 std::string get_short_help() throw(std::bad_alloc)
1351 return "Press/Unpress button";
1353 std::string get_long_help() throw(std::bad_alloc)
1355 return "Syntax: " + commandn + "\n"
1356 "Presses/Unpresses button\n";
1358 std::string commandn;
1359 unsigned controller;
1360 int type;
1361 std::string button;
1364 class button_action_helper
1366 public:
1367 button_action_helper()
1369 for(size_t i = 0; i < sizeof(buttonnames) / sizeof(buttonnames[0]); ++i)
1370 for(int j = 0; j < 3; ++j)
1371 for(unsigned k = 0; k < 8; ++k) {
1372 std::ostringstream x, y;
1373 switch(j) {
1374 case 0:
1375 x << "+controller";
1376 break;
1377 case 1:
1378 x << "-controller";
1379 break;
1380 case 2:
1381 x << "controllerh";
1382 break;
1384 x << (k + 1);
1385 x << buttonnames[i];
1386 y << (k + 1);
1387 y << buttonnames[i];
1388 new button_action(x.str(), j, k, y.str());
1391 } bah;
1393 //If there is a pending load, perform it.
1394 bool handle_load()
1396 if(pending_load != "") {
1397 do_load_state(pending_load, loadmode);
1398 redraw_framebuffer();
1399 pending_load = "";
1400 pending_reset_cycles = -1;
1401 amode = ADVANCE_AUTO;
1402 win->cancel_wait();
1403 win->paused(false);
1404 if(!system_corrupt) {
1405 location_special = SPECIAL_SAVEPOINT;
1406 update_movie_state();
1407 win->notify_screen_update();
1408 win->poll_inputs();
1410 return true;
1412 return false;
1415 //If there are pending saves, perform them.
1416 void handle_saves()
1418 if(!queued_saves.empty()) {
1419 stepping_into_save = true;
1420 SNES::system.runtosave();
1421 stepping_into_save = false;
1422 for(auto i = queued_saves.begin(); i != queued_saves.end(); i++)
1423 do_save_state(*i);
1425 queued_saves.clear();
1428 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1429 bool handle_reset(long cycles)
1431 if(cycles == 0) {
1432 win->message("SNES reset");
1433 SNES::system.reset();
1434 framebuffer = nosignal_screen;
1435 lua_callback_do_reset(win);
1436 redraw_framebuffer();
1437 } else if(cycles > 0) {
1438 video_refresh_done = false;
1439 long cycles_executed = 0;
1440 out(win) << "Executing delayed reset... This can take some time!" << std::endl;
1441 while(cycles_executed < cycles && !video_refresh_done) {
1442 SNES::cpu.op_step();
1443 cycles_executed++;
1445 if(!video_refresh_done)
1446 out(win) << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
1447 else
1448 out(win) << "SNES reset (forced at " << cycles_executed << ")" << std::endl;
1449 SNES::system.reset();
1450 framebuffer = nosignal_screen;
1451 lua_callback_do_reset(win);
1452 redraw_framebuffer();
1453 if(video_refresh_done) {
1454 to_wait_frame(get_ticks_msec());
1455 return false;
1458 return true;
1461 bool handle_corrupt()
1463 if(!system_corrupt)
1464 return false;
1465 while(system_corrupt) {
1466 framebuffer = corrupt_screen;
1467 redraw_framebuffer();
1468 win->cancel_wait();
1469 win->paused(true);
1470 win->poll_inputs();
1471 handle_load();
1472 if(amode == ADVANCE_QUIT)
1473 return true;
1475 return true;
1478 void print_controller_mappings()
1480 for(unsigned i = 0; i < 8; i++) {
1481 std::string type = "unknown";
1482 if(lookup_controller_type(i) == DT_NONE)
1483 type = "disconnected";
1484 if(lookup_controller_type(i) == DT_GAMEPAD)
1485 type = "gamepad";
1486 if(lookup_controller_type(i) == DT_MOUSE)
1487 type = "mouse";
1488 if(lookup_controller_type(i) == DT_SUPERSCOPE)
1489 type = "superscope";
1490 if(lookup_controller_type(i) == DT_JUSTIFIER)
1491 type = "justifier";
1492 out(win) << "Physical controller mapping: Logical " << (i + 1) << " is physical " <<
1493 lookup_physical_controller(i) << " (" << type << ")" << std::endl;
1498 void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& initial) throw(std::bad_alloc,
1499 std::runtime_error)
1501 //Basic initialization.
1502 win = _win;
1503 our_rom = &rom;
1504 my_interface intrf;
1505 auto old_inteface = SNES::system.interface;
1506 SNES::system.interface = &intrf;
1507 status = &win->get_emustatus();
1508 fill_special_frames();
1510 //Load our given movie.
1511 bool first_round = false;
1512 bool just_did_loadstate = false;
1513 try {
1514 do_load_state(initial, LOAD_STATE_DEFAULT);
1515 first_round = our_movie.is_savestate;
1516 just_did_loadstate = first_round;
1517 } catch(std::bad_alloc& e) {
1518 OOM_panic(win);
1519 } catch(std::exception& e) {
1520 win->message(std::string("FATAL: Can't load initial state: ") + e.what());
1521 win->fatal_error();
1522 return;
1525 lua_callback_startup(win);
1527 //print_controller_mappings();
1528 av_snooper::add_dump_notifier(dumpwatch);
1529 win->set_main_surface(scr);
1530 redraw_framebuffer();
1531 win->paused(false);
1532 amode = ADVANCE_PAUSE;
1533 while(amode != ADVANCE_QUIT) {
1534 if(handle_corrupt()) {
1535 first_round = our_movie.is_savestate;
1536 just_did_loadstate = true;
1537 continue;
1539 long resetcycles = -1;
1540 ack_frame_tick(get_ticks_msec());
1541 if(amode == ADVANCE_SKIPLAG_PENDING)
1542 amode = ADVANCE_SKIPLAG;
1544 if(!first_round) {
1545 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
1546 if(amode == ADVANCE_QUIT)
1547 break;
1548 bool delayed_reset = (resetcycles > 0);
1549 pending_reset_cycles = -1;
1550 if(!handle_reset(resetcycles)) {
1551 continue;
1553 if(!delayed_reset) {
1554 handle_saves();
1556 if(handle_load()) {
1557 first_round = our_movie.is_savestate;
1558 amode = ADVANCE_PAUSE;
1559 just_did_loadstate = first_round;
1560 continue;
1563 if(just_did_loadstate) {
1564 if(amode == ADVANCE_QUIT)
1565 break;
1566 amode = ADVANCE_PAUSE;
1567 redraw_framebuffer();
1568 win->cancel_wait();
1569 win->paused(true);
1570 win->poll_inputs();
1571 just_did_loadstate = false;
1573 SNES::system.run();
1574 if(amode == ADVANCE_AUTO)
1575 win->wait_msec(to_wait_frame(get_ticks_msec()));
1576 first_round = false;
1578 av_snooper::end(win);
1579 SNES::system.interface = old_inteface;