Add lua functions to manipulate emulator settings
[lsnes.git] / mainloop.cpp
blob617e8b728bdecc298f74ab90f9bca37b49ec208e
1 #include "mainloop.hpp"
2 #include "avsnoop.hpp"
3 #include "command.hpp"
4 #include <iomanip>
5 #include "framerate.hpp"
6 #include "memorywatch.hpp"
7 #include "lua.hpp"
8 #include "rrdata.hpp"
9 #include "rom.hpp"
10 #include "movie.hpp"
11 #include "moviefile.hpp"
12 #include "render.hpp"
13 #include "keymapper.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 "keymapper.hpp"
23 #include "render.hpp"
24 #include <iostream>
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 LOAD_STATE_RW 0
32 #define LOAD_STATE_RO 1
33 #define LOAD_STATE_PRESERVE 2
34 #define LOAD_STATE_MOVIE 3
35 #define LOAD_STATE_DEFAULT 4
36 #define SAVE_STATE 0
37 #define SAVE_MOVIE 1
38 #define SPECIAL_FRAME_START 0
39 #define SPECIAL_FRAME_VIDEO 1
40 #define SPECIAL_SAVEPOINT 2
41 #define SPECIAL_NONE 3
43 #define BUTTON_LEFT 0 //Gamepad
44 #define BUTTON_RIGHT 1 //Gamepad
45 #define BUTTON_UP 2 //Gamepad
46 #define BUTTON_DOWN 3 //Gamepad
47 #define BUTTON_A 4 //Gamepad
48 #define BUTTON_B 5 //Gamepad
49 #define BUTTON_X 6 //Gamepad
50 #define BUTTON_Y 7 //Gamepad
51 #define BUTTON_L 8 //Gamepad & Mouse
52 #define BUTTON_R 9 //Gamepad & Mouse
53 #define BUTTON_SELECT 10 //Gamepad
54 #define BUTTON_START 11 //Gamepad & Justifier
55 #define BUTTON_TRIGGER 12 //Superscope.
56 #define BUTTON_CURSOR 13 //Superscope & Justifier
57 #define BUTTON_PAUSE 14 //Superscope
58 #define BUTTON_TURBO 15 //Superscope
60 void update_movie_state();
61 void draw_nosignal(uint16_t* target);
62 void draw_corrupt(uint16_t* target);
65 namespace
67 enum advance_mode
69 ADVANCE_QUIT, //Quit the emulator.
70 ADVANCE_AUTO, //Normal (possibly slowed down play).
71 ADVANCE_FRAME, //Frame advance.
72 ADVANCE_SUBFRAME, //Subframe advance.
73 ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal).
74 ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame.
75 ADVANCE_PAUSE, //Unconditional pause.
78 //Analog input physical controller IDs and types.
79 int analog[4] = {-1, -1, -1};
80 bool analog_is_mouse[4] = {false, false, false};
81 //Memory watches.
82 std::map<std::string, std::string> memory_watches;
83 //Previous mouse mask.
84 int prev_mouse_mask = 0;
85 //Flags related to repeating advance.
86 bool advanced_once;
87 bool cancel_advance;
88 //Our ROM.
89 struct loaded_rom* our_rom;
90 //Our movie file.
91 struct moviefile our_movie;
92 //Handle to the graphics system.
93 window* win;
94 //The SNES screen.
95 struct screen scr;
96 //Emulator advance mode. Detemines pauses at start of frame / subframe, etc..
97 enum advance_mode amode;
98 //Mode and filename of pending load, one of LOAD_* constants.
99 int loadmode;
100 std::string pending_load;
101 //Queued saves (all savestates).
102 std::set<std::string> queued_saves;
103 bool stepping_into_save;
104 //Current controls.
105 controls_t curcontrols;
106 controls_t autoheld_controls;
107 //Emulator status area.
108 std::map<std::string, std::string>* status;
109 //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset.
110 long pending_reset_cycles = -1;
111 //Set by every video refresh.
112 bool video_refresh_done;
113 //Special subframe location. One of SPECIAL_* constants.
114 int location_special;
115 //Types of connected controllers.
116 enum porttype_t porttype1 = PT_GAMEPAD;
117 enum porttype_t porttype2 = PT_NONE;
118 //System corrupt flag.
119 bool system_corrupt;
120 //Current screen, no signal screen and corrupt screen.
121 lcscreen framebuffer;
122 lcscreen nosignal_screen;
123 lcscreen corrupt_screen;
124 //Few settings.
125 numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500);
126 numeric_setting savecompression("savecompression", 0, 9, 7);
128 void send_analog_input(int32_t x, int32_t y, unsigned index)
130 if(analog_is_mouse[index]) {
131 x -= 256;
132 y -= (framebuffer.height / 2);
133 } else {
134 x /= 2;
135 y /= 2;
137 if(analog[index] < 0) {
138 out(win) << "No analog controller in slot #" << (index + 1) << std::endl;
139 return;
141 curcontrols(analog[index] >> 2, analog[index] & 3, 0) = x;
142 curcontrols(analog[index] >> 2, analog[index] & 3, 1) = y;
145 void redraw_framebuffer()
147 uint32_t hscl = 1, vscl = 1;
148 if(framebuffer.width < 512)
149 hscl = 2;
150 if(framebuffer.height < 400)
151 vscl = 2;
152 render_queue rq;
153 struct lua_render_context lrc;
154 lrc.left_gap = 0;
155 lrc.right_gap = 0;
156 lrc.bottom_gap = 0;
157 lrc.top_gap = 0;
158 lrc.queue = &rq;
159 lrc.width = framebuffer.width * hscl;
160 lrc.height = framebuffer.height * vscl;
161 lrc.rshift = scr.active_rshift;
162 lrc.gshift = scr.active_gshift;
163 lrc.bshift = scr.active_bshift;
164 lua_callback_do_paint(&lrc, win);
165 scr.reallocate(framebuffer.width * hscl + lrc.left_gap + lrc.right_gap, framebuffer.height * vscl +
166 lrc.top_gap + lrc.bottom_gap, lrc.left_gap, lrc.top_gap);
167 scr.copy_from(framebuffer, hscl, vscl);
168 //We would want divide by 2, but we'll do it ourselves in order to do mouse.
169 win->set_window_compensation(lrc.left_gap, lrc.top_gap, 1, 1);
170 rq.run(scr);
171 win->notify_screen_update();
174 void fill_special_frames()
176 uint16_t buf[512*448];
177 draw_nosignal(buf);
178 nosignal_screen = lcscreen(buf, 512, 448);
179 draw_corrupt(buf);
180 corrupt_screen = lcscreen(buf, 512, 448);
184 class firmware_path_setting : public setting
186 public:
187 firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "./"; default_firmware = true; }
188 void blank() throw(std::bad_alloc, std::runtime_error)
190 _firmwarepath = "./";
191 default_firmware = true;
194 bool is_set() throw()
196 return !default_firmware;
199 void set(const std::string& value) throw(std::bad_alloc, std::runtime_error)
201 _firmwarepath = value;
202 default_firmware = false;
205 std::string get() throw(std::bad_alloc)
207 return _firmwarepath;
210 operator std::string() throw(std::bad_alloc)
212 return _firmwarepath;
214 private:
215 std::string _firmwarepath;
216 bool default_firmware;
217 } firmwarepath_setting;
219 class mymovielogic : public movie_logic
221 public:
222 mymovielogic() : movie_logic(dummy_movie) {}
224 controls_t update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
226 if(lua_requests_subframe_paint)
227 redraw_framebuffer();
229 if(subframe) {
230 if(amode == ADVANCE_SUBFRAME) {
231 if(!cancel_advance && !advanced_once) {
232 win->wait_msec(advance_timeout_first);
233 advanced_once = true;
235 if(cancel_advance) {
236 amode = ADVANCE_PAUSE;
237 cancel_advance = false;
239 win->paused(amode == ADVANCE_PAUSE);
240 } else if(amode == ADVANCE_FRAME) {
242 } else {
243 win->paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE);
244 cancel_advance = false;
246 if(amode == ADVANCE_SKIPLAG)
247 amode = ADVANCE_AUTO;
248 location_special = SPECIAL_NONE;
249 update_movie_state();
250 } else {
251 if(amode == ADVANCE_SKIPLAG_PENDING)
252 amode = ADVANCE_SKIPLAG;
253 if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) {
254 if(!cancel_advance) {
255 win->wait_msec(advanced_once ? to_wait_frame(get_ticks_msec()) :
256 advance_timeout_first);
257 advanced_once = true;
259 if(cancel_advance) {
260 amode = ADVANCE_PAUSE;
261 cancel_advance = false;
263 win->paused(amode == ADVANCE_PAUSE);
264 } else {
265 win->paused((amode == ADVANCE_PAUSE));
266 cancel_advance = false;
268 location_special = SPECIAL_FRAME_START;
269 update_movie_state();
271 win->notify_screen_update();
272 win->poll_inputs();
273 if(!subframe && pending_reset_cycles >= 0) {
274 curcontrols(CONTROL_SYSTEM_RESET) = 1;
275 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = pending_reset_cycles / 10000;
276 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = pending_reset_cycles % 10000;
277 } else if(!subframe) {
278 curcontrols(CONTROL_SYSTEM_RESET) = 0;
279 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = 0;
280 curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = 0;
282 controls_t tmp = curcontrols ^ autoheld_controls;
283 lua_callback_do_input(tmp, subframe, win);
284 return tmp;
286 private:
287 movie dummy_movie;
290 namespace
292 mymovielogic movb;
294 //Lookup physical controller id based on UI controller id and given types (-1 if invalid).
295 int lookup_physical_controller(unsigned ui_id)
297 bool p1multitap = (porttype1 == PT_MULTITAP);
298 unsigned p1devs = port_types[porttype1].devices;
299 unsigned p2devs = port_types[porttype2].devices;
300 if(ui_id >= p1devs + p2devs)
301 return -1;
302 if(!p1multitap)
303 if(ui_id < p1devs)
304 return ui_id;
305 else
306 return 4 + ui_id - p1devs;
307 else
308 if(ui_id == 0)
309 return 0;
310 else if(ui_id < 5)
311 return ui_id + 3;
312 else
313 return ui_id - 4;
316 //Look up controller type given UI controller id (note: Non-present controllers give PT_NONE, not the type
317 //of port, multitap controllers give PT_GAMEPAD, not PT_MULTITAP, and justifiers give PT_JUSTIFIER, not
318 //PT_JUSTIFIERS).
319 enum devicetype_t lookup_controller_type(unsigned ui_id)
321 int x = lookup_physical_controller(ui_id);
322 if(x < 0)
323 return DT_NONE;
324 enum porttype_t rawtype = (x & 4) ? porttype2 : porttype1;
325 if((x & 3) < port_types[rawtype].devices)
326 return port_types[rawtype].dtype;
327 else
328 return DT_NONE;
331 void set_analog_controllers()
333 unsigned index = 0;
334 for(unsigned i = 0; i < 8; i++) {
335 enum devicetype_t t = lookup_controller_type(i);
336 analog_is_mouse[index] = (t == DT_MOUSE);
337 if(t == DT_MOUSE || t == DT_SUPERSCOPE || t == DT_JUSTIFIER) {
338 analog[index++] = lookup_physical_controller(i);
339 } else
340 analog[index] = -1;
342 for(; index < 3; index++)
343 analog[index] = -1;
346 std::map<std::string, std::pair<unsigned, unsigned>> buttonmap;
348 const char* buttonnames[] = {
349 "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor",
350 "pause", "turbo"
353 void init_buttonmap()
355 static int done = 0;
356 if(done)
357 return;
358 for(unsigned i = 0; i < 8; i++)
359 for(unsigned j = 0; j < sizeof(buttonnames) / sizeof(buttonnames[0]); j++) {
360 std::ostringstream x;
361 x << (i + 1) << buttonnames[j];
362 buttonmap[x.str()] = std::make_pair(i, j);
364 done = 1;
367 //Do button action.
368 void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor = false)
370 enum devicetype_t p = lookup_controller_type(ui_id);
371 int x = lookup_physical_controller(ui_id);
372 int bid = -1;
373 switch(p) {
374 case DT_NONE:
375 out(win) << "No such controller #" << (ui_id + 1) << std::endl;
376 return;
377 case DT_GAMEPAD:
378 switch(button) {
379 case BUTTON_UP: bid = SNES_DEVICE_ID_JOYPAD_UP; break;
380 case BUTTON_DOWN: bid = SNES_DEVICE_ID_JOYPAD_DOWN; break;
381 case BUTTON_LEFT: bid = SNES_DEVICE_ID_JOYPAD_LEFT; break;
382 case BUTTON_RIGHT: bid = SNES_DEVICE_ID_JOYPAD_RIGHT; break;
383 case BUTTON_A: bid = SNES_DEVICE_ID_JOYPAD_A; break;
384 case BUTTON_B: bid = SNES_DEVICE_ID_JOYPAD_B; break;
385 case BUTTON_X: bid = SNES_DEVICE_ID_JOYPAD_X; break;
386 case BUTTON_Y: bid = SNES_DEVICE_ID_JOYPAD_Y; break;
387 case BUTTON_L: bid = SNES_DEVICE_ID_JOYPAD_L; break;
388 case BUTTON_R: bid = SNES_DEVICE_ID_JOYPAD_R; break;
389 case BUTTON_SELECT: bid = SNES_DEVICE_ID_JOYPAD_SELECT; break;
390 case BUTTON_START: bid = SNES_DEVICE_ID_JOYPAD_START; break;
391 default:
392 out(win) << "Invalid button for gamepad" << std::endl;
393 return;
395 break;
396 case DT_MOUSE:
397 switch(button) {
398 case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break;
399 case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break;
400 default:
401 out(win) << "Invalid button for mouse" << std::endl;
402 return;
404 break;
405 case DT_JUSTIFIER:
406 switch(button) {
407 case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break;
408 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break;
409 default:
410 out(win) << "Invalid button for justifier" << std::endl;
411 return;
413 break;
414 case DT_SUPERSCOPE:
415 switch(button) {
416 case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER; break;
417 case BUTTON_CURSOR: bid = SNES_DEVICE_ID_SUPER_SCOPE_CURSOR; break;
418 case BUTTON_PAUSE: bid = SNES_DEVICE_ID_SUPER_SCOPE_PAUSE; break;
419 case BUTTON_TURBO: bid = SNES_DEVICE_ID_SUPER_SCOPE_TURBO; break;
420 default:
421 out(win) << "Invalid button for superscope" << std::endl;
422 return;
424 break;
426 if(do_xor)
427 autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate;
428 else
429 curcontrols((x & 4) ? 1 : 0, x & 3, bid) = newstate;
432 //Save state.
433 void do_save_state(const std::string& filename) throw(std::bad_alloc,
434 std::runtime_error)
436 lua_callback_pre_save(filename, true, win);
437 try {
438 uint64_t origtime = get_ticks_msec();
439 our_movie.is_savestate = true;
440 our_movie.sram = save_sram();
441 our_movie.savestate = save_core_state();
442 framebuffer.save(our_movie.screenshot);
443 auto s = movb.get_movie().save_state();
444 our_movie.movie_state.resize(s.size());
445 memcpy(&our_movie.movie_state[0], &s[0], s.size());
446 our_movie.input = movb.get_movie().save();
447 our_movie.save(filename, savecompression);
448 uint64_t took = get_ticks_msec() - origtime;
449 out(win) << "Saved state '" << filename << "' in " << took << "ms." << std::endl;
450 lua_callback_post_save(filename, true, win);
451 } catch(std::bad_alloc& e) {
452 OOM_panic(win);
453 } catch(std::exception& e) {
454 win->message(std::string("Save failed: ") + e.what());
455 lua_callback_err_save(filename, win);
459 //Save movie.
460 void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
462 lua_callback_pre_save(filename, false, win);
463 try {
464 uint64_t origtime = get_ticks_msec();
465 our_movie.is_savestate = false;
466 our_movie.input = movb.get_movie().save();
467 our_movie.save(filename, savecompression);
468 uint64_t took = get_ticks_msec() - origtime;
469 out(win) << "Saved movie '" << filename << "' in " << took << "ms." << std::endl;
470 lua_callback_post_save(filename, false, win);
471 } catch(std::bad_alloc& e) {
472 OOM_panic(win);
473 } catch(std::exception& e) {
474 win->message(std::string("Save failed: ") + e.what());
475 lua_callback_err_save(filename, win);
479 void warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot, const std::string& name)
481 if(mhash != slot.sha256) {
482 out(win) << "WARNING: " << name << " hash mismatch!" << std::endl
483 << "\tMovie: " << mhash << std::endl
484 << "\tOur ROM: " << slot.sha256 << std::endl;
488 void set_dev(bool port, porttype_t t, bool set_core = true)
490 //return;
491 switch(set_core ? t : PT_INVALID) {
492 case PT_NONE:
493 snes_set_controller_port_device(port, SNES_DEVICE_NONE);
494 break;
495 case PT_GAMEPAD:
496 snes_set_controller_port_device(port, SNES_DEVICE_JOYPAD);
497 break;
498 case PT_MULTITAP:
499 snes_set_controller_port_device(port, SNES_DEVICE_MULTITAP);
500 break;
501 case PT_MOUSE:
502 snes_set_controller_port_device(port, SNES_DEVICE_MOUSE);
503 break;
504 case PT_SUPERSCOPE:
505 snes_set_controller_port_device(port, SNES_DEVICE_SUPER_SCOPE);
506 break;
507 case PT_JUSTIFIER:
508 snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIER);
509 break;
510 case PT_JUSTIFIERS:
511 snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIERS);
512 break;
513 case PT_INVALID:
516 if(port)
517 porttype2 = t;
518 else
519 porttype1 = t;
520 set_analog_controllers();
523 //Load state from loaded movie file (does not catch errors).
524 void do_load_state(struct moviefile& _movie, int lmode)
526 bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE;
527 if(gtype::toromtype(_movie.gametype) != our_rom->rtype)
528 throw std::runtime_error("ROM types of movie and loaded ROM don't match");
529 if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO)
530 throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match");
532 if(_movie.coreversion != bsnes_core_version) {
533 if(will_load_state) {
534 std::ostringstream x;
535 x << "ERROR: Emulator core version mismatch!" << std::endl
536 << "\tThis version: " << bsnes_core_version << std::endl
537 << "\tFile is from: " << _movie.coreversion << std::endl;
538 throw std::runtime_error(x.str());
539 } else
540 out(win) << "WARNING: Emulator core version mismatch!" << std::endl
541 << "\tThis version: " << bsnes_core_version << std::endl
542 << "\tFile is from: " << _movie.coreversion << std::endl;
544 warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1");
545 warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1");
546 warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2");
547 warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2");
548 warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3");
549 warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3");
551 SNES::config.random = false;
552 SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
554 movie newmovie;
555 if(lmode == LOAD_STATE_PRESERVE)
556 newmovie = movb.get_movie();
557 else
558 newmovie.load(_movie.rerecords, _movie.projectid, _movie.input);
560 if(will_load_state) {
561 std::vector<unsigned char> tmp;
562 tmp.resize(_movie.movie_state.size());
563 memcpy(&tmp[0], &_movie.movie_state[0], tmp.size());
564 newmovie.restore_state(tmp, true);
567 //Negative return.
568 rrdata::read_base(_movie.projectid);
569 rrdata::add_internal();
570 try {
571 our_rom->region = gtype::toromregion(_movie.gametype);
572 our_rom->load();
574 if(_movie.is_savestate && lmode != LOAD_STATE_MOVIE) {
575 //Load the savestate and movie state.
576 set_dev(false, _movie.port1);
577 set_dev(true, _movie.port2);
578 load_core_state(_movie.savestate);
579 framebuffer.load(_movie.screenshot);
580 } else {
581 load_sram(_movie.movie_sram, win);
582 set_dev(false, _movie.port1);
583 set_dev(true, _movie.port2);
584 framebuffer = nosignal_screen;
586 } catch(std::bad_alloc& e) {
587 OOM_panic(win);
588 } catch(std::exception& e) {
589 system_corrupt = true;
590 throw;
593 //Okay, copy the movie data.
594 our_movie = _movie;
595 if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) {
596 our_movie.is_savestate = false;
597 our_movie.host_memory.clear();
599 movb.get_movie() = newmovie;
600 //Activate RW mode if needed.
601 if(lmode == LOAD_STATE_RW)
602 movb.get_movie().readonly_mode(false);
603 if(lmode == LOAD_STATE_DEFAULT && !(movb.get_movie().get_frame_count()))
604 movb.get_movie().readonly_mode(false);
605 out(win) << "ROM Type ";
606 switch(our_rom->rtype) {
607 case ROMTYPE_SNES:
608 out(win) << "SNES";
609 break;
610 case ROMTYPE_BSX:
611 out(win) << "BS-X";
612 break;
613 case ROMTYPE_BSXSLOTTED:
614 out(win) << "BS-X slotted";
615 break;
616 case ROMTYPE_SUFAMITURBO:
617 out(win) << "Sufami Turbo";
618 break;
619 case ROMTYPE_SGB:
620 out(win) << "Super Game Boy";
621 break;
622 default:
623 out(win) << "Unknown";
624 break;
626 out(win) << " region ";
627 switch(our_rom->region) {
628 case REGION_PAL:
629 out(win) << "PAL";
630 break;
631 case REGION_NTSC:
632 out(win) << "NTSC";
633 break;
634 default:
635 out(win) << "Unknown";
636 break;
638 out(win) << std::endl;
639 uint64_t mlength = _movie.get_movie_length();
641 mlength += 999999;
642 std::ostringstream x;
643 if(mlength > 3600000000000) {
644 x << mlength / 3600000000000 << ":";
645 mlength %= 3600000000000;
647 x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":";
648 mlength %= 60000000000;
649 x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << ".";
650 mlength %= 1000000000;
651 x << std::setfill('0') << std::setw(3) << mlength / 1000000;
652 out(win) << "Rerecords " << _movie.rerecords << " length " << x.str() << " ("
653 << _movie.get_frame_count() << " frames)" << std::endl;
656 if(_movie.gamename != "")
657 out(win) << "Game Name: " << _movie.gamename << std::endl;
658 for(size_t i = 0; i < _movie.authors.size(); i++)
659 out(win) << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")"
660 << std::endl;
663 //Load state
664 void do_load_state(const std::string& filename, int lmode)
666 uint64_t origtime = get_ticks_msec();
667 lua_callback_pre_load(filename, win);
668 struct moviefile mfile;
669 try {
670 mfile = moviefile(filename);
671 } catch(std::bad_alloc& e) {
672 OOM_panic(win);
673 } catch(std::exception& e) {
674 win->message("Can't read movie/savestate '" + filename + "': " + e.what());
675 lua_callback_err_load(filename, win);
676 return;
678 try {
679 do_load_state(mfile, lmode);
680 uint64_t took = get_ticks_msec() - origtime;
681 out(win) << "Loaded '" << filename << "' in " << took << "ms." << std::endl;
682 lua_callback_post_load(filename, our_movie.is_savestate, win);
683 } catch(std::bad_alloc& e) {
684 OOM_panic(win);
685 } catch(std::exception& e) {
686 win->message("Can't load movie/savestate '" + filename + "': " + e.what());
687 lua_callback_err_load(filename, win);
688 return;
692 //Do pending load (automatically unpauses).
693 void mark_pending_load(const std::string& filename, int lmode)
695 loadmode = lmode;
696 pending_load = filename;
697 amode = ADVANCE_AUTO;
698 win->cancel_wait();
699 win->paused(false);
702 //Mark pending save (movies save immediately).
703 void mark_pending_save(const std::string& filename, int smode)
705 if(smode == SAVE_MOVIE) {
706 //Just do this immediately.
707 do_save_movie(filename);
708 return;
710 queued_saves.insert(filename);
711 win->message("Pending save on '" + filename + "'");
714 class dump_watch : public av_snooper::dump_notification
716 void dump_starting() throw()
718 update_movie_state();
720 void dump_ending() throw()
722 update_movie_state();
724 } dumpwatch;
727 std::vector<char>& get_host_memory()
729 return our_movie.host_memory;
732 movie& get_movie()
734 return movb.get_movie();
737 void update_movie_state()
739 auto& _status = win->get_emustatus();
741 std::ostringstream x;
742 x << movb.get_movie().get_current_frame() << "(";
743 if(location_special == SPECIAL_FRAME_START)
744 x << "0";
745 else if(location_special == SPECIAL_SAVEPOINT)
746 x << "S";
747 else if(location_special == SPECIAL_FRAME_VIDEO)
748 x << "V";
749 else
750 x << movb.get_movie().next_poll_number();
751 x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count();
752 _status["Frame"] = x.str();
755 std::ostringstream x;
756 if(movb.get_movie().readonly_mode())
757 x << "PLAY ";
758 else
759 x << "REC ";
760 if(av_snooper::dump_in_progress())
761 x << "CAP ";
762 _status["Flags"] = x.str();
764 for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) {
765 try {
766 _status["M[" + i->first + "]"] = evaluate_watch(i->second);
767 } catch(...) {
770 controls_t c;
771 if(movb.get_movie().readonly_mode())
772 c = movb.get_movie().get_controls();
773 else
774 c = curcontrols ^ autoheld_controls;
775 for(unsigned i = 0; i < 8; i++) {
776 unsigned pindex = lookup_physical_controller(i);
777 unsigned port = pindex >> 2;
778 unsigned dev = pindex & 3;
779 auto ctype = lookup_controller_type(i);
780 std::ostringstream x;
781 switch(ctype) {
782 case DT_GAMEPAD:
783 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " ");
784 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " ");
785 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " ");
786 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " ");
787 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " ");
788 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " ");
789 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " ");
790 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " ");
791 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " ");
792 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " ");
793 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " ");
794 x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " ");
795 break;
796 case DT_MOUSE:
797 x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " ";
798 x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " ";
799 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " ");
800 x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " ");
801 break;
802 case DT_SUPERSCOPE:
803 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " ";
804 x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " ";
805 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " ");
806 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " ");
807 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " ");
808 x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " ");
809 break;
810 case DT_JUSTIFIER:
811 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " ";
812 x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " ";
813 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " ");
814 x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " ");
815 break;
816 case DT_NONE:
817 continue;
819 char y[3] = {'P', 0, 0};
820 y[1] = 49 + i;
821 _status[std::string(y)] = x.str();
826 class my_interface : public SNES::Interface
828 string path(SNES::Cartridge::Slot slot, const string &hint)
830 return static_cast<std::string>(firmwarepath_setting).c_str();
833 void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan)
835 if(stepping_into_save)
836 win->message("Got video refresh in runtosave, expect desyncs!");
837 video_refresh_done = true;
838 bool region = (SNES::system.region() == SNES::System::Region::PAL);
839 //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl;
840 //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl;
841 //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl;
842 //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl;
843 lcscreen ls(data, hires, interlace, overscan, region);
844 framebuffer = ls;
845 location_special = SPECIAL_FRAME_VIDEO;
846 update_movie_state();
847 redraw_framebuffer();
848 uint32_t fps_n, fps_d;
849 if(region) {
850 fps_n = 322445;
851 fps_d = 6448;
852 } else {
853 fps_n = 10738636;
854 fps_d = 178683;
856 av_snooper::frame(ls, fps_n, fps_d, win);
859 void audio_sample(int16_t l_sample, int16_t r_sample)
861 uint16_t _l = l_sample;
862 uint16_t _r = r_sample;
863 win->play_audio_sample(_l + 32768, _r + 32768);
864 av_snooper::sample(_l, _r, win);
867 void audio_sample(uint16_t l_sample, uint16_t r_sample)
869 //Yes, this interface is broken. The samples are signed but are passed as unsigned!
870 win->play_audio_sample(l_sample + 32768, r_sample + 32768);
871 av_snooper::sample(l_sample, r_sample, win);
874 int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
876 int16_t x;
877 x = movb.input_poll(port, index, id);
878 //if(id == SNES_DEVICE_ID_JOYPAD_START)
879 // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl;
880 lua_callback_snoop_input(port ? 1 : 0, index, id, x, win);
881 return x;
885 namespace
887 class quit_emulator_cmd : public command
889 public:
890 quit_emulator_cmd() throw(std::bad_alloc) : command("quit-emulator") {}
891 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
893 if(args == "/y" || win->modal_message("Really quit?", true)) {
894 amode = ADVANCE_QUIT;
895 win->paused(false);
896 win->cancel_wait();
899 std::string get_short_help() throw(std::bad_alloc) { return "Quit the emulator"; }
900 std::string get_long_help() throw(std::bad_alloc)
902 return "Syntax: quit-emulator [/y]\n"
903 "Quits emulator (/y => don't ask for confirmation).\n";
905 } quitemu;
907 class pause_emulator_cmd : public command
909 public:
910 pause_emulator_cmd() throw(std::bad_alloc) : command("pause-emulator") {}
911 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
913 if(args != "")
914 throw std::runtime_error("This command does not take parameters");
915 if(amode != ADVANCE_AUTO) {
916 amode = ADVANCE_AUTO;
917 win->paused(false);
918 win->cancel_wait();
919 win->message("Unpaused");
920 } else {
921 win->cancel_wait();
922 cancel_advance = false;
923 amode = ADVANCE_PAUSE;
924 win->message("Paused");
927 std::string get_short_help() throw(std::bad_alloc) { return "(Un)pause the emulator"; }
928 std::string get_long_help() throw(std::bad_alloc)
930 return "Syntax: pause-emulator\n"
931 "(Un)pauses the emulator.\n";
933 } pauseemu;
935 class padvance_frame_cmd : public command
937 public:
938 padvance_frame_cmd() throw(std::bad_alloc) : command("+advance-frame") {}
939 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
941 if(args != "")
942 throw std::runtime_error("This command does not take parameters");
943 amode = ADVANCE_FRAME;
944 cancel_advance = false;
945 advanced_once = false;
946 win->cancel_wait();
947 win->paused(false);
949 std::string get_short_help() throw(std::bad_alloc) { return "Advance one frame"; }
950 std::string get_long_help() throw(std::bad_alloc)
952 return "Syntax: +advance-frame\n"
953 "Advances the emulation by one frame.\n";
955 } padvancef;
957 class nadvance_frame_cmd : public command
959 public:
960 nadvance_frame_cmd() throw(std::bad_alloc) : command("-advance-frame") {}
961 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
963 if(args != "")
964 throw std::runtime_error("This command does not take parameters");
965 cancel_advance = true;
966 win->cancel_wait();
967 win->paused(false);
969 } nadvancef;
971 class padvance_poll_cmd : public command
973 public:
974 padvance_poll_cmd() throw(std::bad_alloc) : command("+advance-poll") {}
975 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
977 if(args != "")
978 throw std::runtime_error("This command does not take parameters");
979 amode = ADVANCE_SUBFRAME;
980 cancel_advance = false;
981 advanced_once = false;
982 win->cancel_wait();
983 win->paused(false);
985 std::string get_short_help() throw(std::bad_alloc) { return "Advance one subframe"; }
986 std::string get_long_help() throw(std::bad_alloc)
988 return "Syntax: +advance-poll\n"
989 "Advances the emulation by one subframe.\n";
991 } padvancep;
993 class nadvance_poll_cmd : public command
995 public:
996 nadvance_poll_cmd() throw(std::bad_alloc) : command("-advance-poll") {}
998 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1000 if(args != "")
1001 throw std::runtime_error("This command does not take parameters");
1002 cancel_advance = true;
1003 win->cancel_wait();
1004 win->paused(false);
1006 } nadvancep;
1008 class advance_skiplag_cmd : public command
1010 public:
1011 advance_skiplag_cmd() throw(std::bad_alloc) : command("advance-skiplag") {}
1012 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1014 if(args != "")
1015 throw std::runtime_error("This command does not take parameters");
1016 amode = ADVANCE_SKIPLAG;
1017 win->cancel_wait();
1018 win->paused(false);
1020 std::string get_short_help() throw(std::bad_alloc) { return "Skip to next poll"; }
1021 std::string get_long_help() throw(std::bad_alloc)
1023 return "Syntax: advance-skiplag\n"
1024 "Advances the emulation to the next poll.\n";
1026 } skiplagc;
1028 class reset_cmd : public command
1030 public:
1031 reset_cmd() throw(std::bad_alloc) : command("reset") {}
1032 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1034 if(args != "")
1035 throw std::runtime_error("This command does not take parameters");
1036 pending_reset_cycles = 0;
1038 std::string get_short_help() throw(std::bad_alloc) { return "Reset the SNES"; }
1039 std::string get_long_help() throw(std::bad_alloc)
1041 return "Syntax: reset\n"
1042 "Resets the SNES in beginning of the next frame.\n";
1044 } resetc;
1046 class load_state_cmd : public command
1048 public:
1049 load_state_cmd() throw(std::bad_alloc) : command("load-state") {}
1050 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1052 if(args == "")
1053 throw std::runtime_error("Filename required");
1054 mark_pending_load(args, LOAD_STATE_RW);
1056 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1057 std::string get_long_help() throw(std::bad_alloc)
1059 return "Syntax: load-state <file>\n"
1060 "Loads SNES state from <file> in Read/Write mode\n";
1062 } loadstatec;
1064 class load_readonly_cmd : public command
1066 public:
1067 load_readonly_cmd() throw(std::bad_alloc) : command("load-readonly") {}
1068 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1070 if(args == "")
1071 throw std::runtime_error("Filename required");
1072 mark_pending_load(args, LOAD_STATE_RO);
1074 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1075 std::string get_long_help() throw(std::bad_alloc)
1077 return "Syntax: load-readonly <file>\n"
1078 "Loads SNES state from <file> in Read-only mode\n";
1080 } loadreadonlyc;
1082 class load_preserve_cmd : public command
1084 public:
1085 load_preserve_cmd() throw(std::bad_alloc) : command("load-preserve") {}
1086 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1088 if(args == "")
1089 throw std::runtime_error("Filename required");
1090 mark_pending_load(args, LOAD_STATE_PRESERVE);
1092 std::string get_short_help() throw(std::bad_alloc) { return "Load state"; }
1093 std::string get_long_help() throw(std::bad_alloc)
1095 return "Syntax: load-preserve <file>\n"
1096 "Loads SNES state from <file> preserving input\n";
1098 } loadpreservec;
1100 class load_movie_cmd : public command
1102 public:
1103 load_movie_cmd() throw(std::bad_alloc) : command("load-movie") {}
1104 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1106 if(args == "")
1107 throw std::runtime_error("Filename required");
1108 mark_pending_load(args, LOAD_STATE_MOVIE);
1110 std::string get_short_help() throw(std::bad_alloc) { return "Load movie"; }
1111 std::string get_long_help() throw(std::bad_alloc)
1113 return "Syntax: load-movie <file>\n"
1114 "Loads movie from <file>\n";
1116 } loadmoviec;
1118 class save_state_cmd : public command
1120 public:
1121 save_state_cmd() throw(std::bad_alloc) : command("save-state") {}
1122 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1124 if(args == "")
1125 throw std::runtime_error("Filename required");
1126 mark_pending_save(args, SAVE_STATE);
1129 std::string get_short_help() throw(std::bad_alloc) { return "Save state"; }
1130 std::string get_long_help() throw(std::bad_alloc)
1132 return "Syntax: save-state <file>\n"
1133 "Saves SNES state to <file>\n";
1135 } savestatec;
1137 class save_movie_cmd : public command
1139 public:
1140 save_movie_cmd() throw(std::bad_alloc) : command("save-movie") {}
1141 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1143 if(args == "")
1144 throw std::runtime_error("Filename required");
1145 mark_pending_save(args, SAVE_MOVIE);
1147 std::string get_short_help() throw(std::bad_alloc) { return "Save movie"; }
1148 std::string get_long_help() throw(std::bad_alloc)
1150 return "Syntax: save-movie <file>\n"
1151 "Saves movie to <file>\n";
1153 } savemoviec;
1155 class set_rwmode_cmd : public command
1157 public:
1158 set_rwmode_cmd() throw(std::bad_alloc) : command("set-rwmode") {}
1159 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1161 if(args != "")
1162 throw std::runtime_error("This command does not take parameters");
1163 movb.get_movie().readonly_mode(false);
1164 lua_callback_do_readwrite(win);
1165 update_movie_state();
1166 win->notify_screen_update();
1168 std::string get_short_help() throw(std::bad_alloc) { return "Switch to read/write mode"; }
1169 std::string get_long_help() throw(std::bad_alloc)
1171 return "Syntax: set-rwmode\n"
1172 "Switches to read/write mode\n";
1174 } setrwc;
1176 class set_gamename_cmd : public command
1178 public:
1179 set_gamename_cmd() throw(std::bad_alloc) : command("set-gamename") {}
1180 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1182 our_movie.gamename = args;
1183 out(win) << "Game name changed to '" << our_movie.gamename << "'" << std::endl;
1185 std::string get_short_help() throw(std::bad_alloc) { return "Set the game name"; }
1186 std::string get_long_help() throw(std::bad_alloc)
1188 return "Syntax: set-gamename <name>\n"
1189 "Sets the game name to <name>\n";
1191 } setnamec;
1193 class get_gamename_cmd : public command
1195 public:
1196 get_gamename_cmd() throw(std::bad_alloc) : command("get-gamename") {}
1197 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1199 if(args != "")
1200 throw std::runtime_error("This command does not take parameters");
1201 out(win) << "Game name is '" << our_movie.gamename << "'" << std::endl;
1203 std::string get_short_help() throw(std::bad_alloc) { return "Get the game name"; }
1204 std::string get_long_help() throw(std::bad_alloc)
1206 return "Syntax: get-gamename\n"
1207 "Prints the game name\n";
1209 } getnamec;
1211 class print_authors_cmd : public command
1213 public:
1214 print_authors_cmd() throw(std::bad_alloc) : command("show-authors") {}
1215 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1217 if(args != "")
1218 throw std::runtime_error("This command does not take parameters");
1219 size_t idx = 0;
1220 for(auto i = our_movie.authors.begin(); i != our_movie.authors.end(); i++) {
1221 out(win) << (idx++) << ": " << i->first << "|" << i->second << std::endl;
1223 out(win) << "End of authors list" << std::endl;
1225 std::string get_short_help() throw(std::bad_alloc) { return "Show the run authors"; }
1226 std::string get_long_help() throw(std::bad_alloc)
1228 return "Syntax: show-authors\n"
1229 "Shows the run authors\n";
1231 } getauthorc;
1233 class repainter : public command
1235 public:
1236 repainter() throw(std::bad_alloc) : command("repaint") {}
1237 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1239 if(args != "")
1240 throw std::runtime_error("This command does not take parameters");
1241 redraw_framebuffer();
1243 std::string get_short_help() throw(std::bad_alloc) { return "Redraw the screen"; }
1244 std::string get_long_help() throw(std::bad_alloc)
1246 return "Syntax: repaint\n"
1247 "Redraws the screen\n";
1249 } repaintc;
1251 class add_author_command : public command
1253 public:
1254 add_author_command() throw(std::bad_alloc) : command("add-author") {}
1255 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1257 tokensplitter t(args);
1258 fieldsplitter f(t.tail());
1259 std::string full = f;
1260 std::string nick = f;
1261 if(full == "" && nick == "")
1262 throw std::runtime_error("Bad author name");
1263 our_movie.authors.push_back(std::make_pair(full, nick));
1264 out(win) << (our_movie.authors.size() - 1) << ": " << full << "|" << nick << std::endl;
1266 std::string get_short_help() throw(std::bad_alloc) { return "Add an author"; }
1267 std::string get_long_help() throw(std::bad_alloc)
1269 return "Syntax: add-author <fullname>\n"
1270 "Syntax: add-author |<nickname>\n"
1271 "Syntax: add-author <fullname>|<nickname>\n"
1272 "Adds a new author\n";
1274 } addauthorc;
1276 class remove_author_command : public command
1278 public:
1279 remove_author_command() throw(std::bad_alloc) : command("remove-author") {}
1280 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1282 tokensplitter t(args);
1283 uint64_t index = parse_value<uint64_t>(t.tail());
1284 if(index >= our_movie.authors.size())
1285 throw std::runtime_error("No such author");
1286 our_movie.authors.erase(our_movie.authors.begin() + index);
1288 std::string get_short_help() throw(std::bad_alloc) { return "Remove an author"; }
1289 std::string get_long_help() throw(std::bad_alloc)
1291 return "Syntax: remove-author <id>\n"
1292 "Removes author with ID <id>\n";
1294 } removeauthorc;
1296 class edit_author_command : public command
1298 public:
1299 edit_author_command() throw(std::bad_alloc) : command("edit-author") {}
1300 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1302 tokensplitter t(args);
1303 uint64_t index = parse_value<uint64_t>(t);
1304 if(index >= our_movie.authors.size())
1305 throw std::runtime_error("No such author");
1306 fieldsplitter f(t.tail());
1307 std::string full = f;
1308 std::string nick = f;
1309 if(full == "" && nick == "") {
1310 out(win) << "syntax: edit-author <authornum> <author>" << std::endl;
1311 return;
1313 our_movie.authors[index] = std::make_pair(full, nick);
1315 std::string get_short_help() throw(std::bad_alloc) { return "Edit an author"; }
1316 std::string get_long_help() throw(std::bad_alloc)
1318 return "Syntax: edit-author <authorid> <fullname>\n"
1319 "Syntax: edit-author <authorid> |<nickname>\n"
1320 "Syntax: edit-author <authorid> <fullname>|<nickname>\n"
1321 "Edits author name\n";
1323 } editauthorc;
1325 class add_watch_command : public command
1327 public:
1328 add_watch_command() throw(std::bad_alloc) : command("add-watch") {}
1329 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1331 tokensplitter t(args);
1332 std::string name = t;
1333 if(name == "" || t.tail() == "")
1334 throw std::runtime_error("syntax: add-watch <name> <expr>");
1335 std::cerr << "Add watch: '" << name << "'" << std::endl;
1336 memory_watches[name] = t.tail();
1337 update_movie_state();
1339 std::string get_short_help() throw(std::bad_alloc) { return "Add a memory watch"; }
1340 std::string get_long_help() throw(std::bad_alloc)
1342 return "Syntax: add-watch <name> <expression>\n"
1343 "Adds a new memory watch\n";
1345 } addwatchc;
1347 class remove_watch_command : public command
1349 public:
1350 remove_watch_command() throw(std::bad_alloc) : command("remove-watch") {}
1351 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1353 tokensplitter t(args);
1354 std::string name = t;
1355 if(name == "" || t.tail() != "") {
1356 out(win) << "syntax: remove-watch <name>" << std::endl;
1357 return;
1359 std::cerr << "Erase watch: '" << name << "'" << std::endl;
1360 memory_watches.erase(name);
1361 auto& _status = win->get_emustatus();
1362 _status.erase("M[" + name + "]");
1363 update_movie_state(); }
1364 std::string get_short_help() throw(std::bad_alloc) { return "Remove a memory watch"; }
1365 std::string get_long_help() throw(std::bad_alloc)
1367 return "Syntax: remove-watch <name>\n"
1368 "Removes a memory watch\n";
1370 } removewatchc;
1372 class test_1 : public command
1374 public:
1375 test_1() throw(std::bad_alloc) : command("test-1") {}
1376 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1378 framebuffer = nosignal_screen;
1379 redraw_framebuffer();
1381 } test1c;
1383 class test_2 : public command
1385 public:
1386 test_2() throw(std::bad_alloc) : command("test-2") {}
1387 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1389 framebuffer = corrupt_screen;
1390 redraw_framebuffer();
1392 } test2c;
1394 class test_3 : public command
1396 public:
1397 test_3() throw(std::bad_alloc) : command("test-3") {}
1398 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1400 while(1);
1402 } test3c;
1404 class screenshot_command : public command
1406 public:
1407 screenshot_command() throw(std::bad_alloc) : command("take-screenshot") {}
1408 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1410 if(args == "")
1411 throw std::runtime_error("Filename required");
1412 framebuffer.save_png(args);
1413 out(win) << "Saved PNG screenshot" << std::endl;
1415 std::string get_short_help() throw(std::bad_alloc) { return "Takes a screenshot"; }
1416 std::string get_long_help() throw(std::bad_alloc)
1418 return "Syntax: take-screenshot <file>\n"
1419 "Saves screenshot to PNG file <file>\n";
1421 } screenshotc;
1423 class mouse_button_handler : public command
1425 public:
1426 mouse_button_handler() throw(std::bad_alloc) : command("mouse_button") {}
1427 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1429 tokensplitter t(args);
1430 std::string x = t;
1431 std::string y = t;
1432 std::string b = t;
1433 int _x = atoi(x.c_str());
1434 int _y = atoi(y.c_str());
1435 int _b = atoi(b.c_str());
1436 if(_b & ~prev_mouse_mask & 1)
1437 send_analog_input(_x, _y, 0);
1438 if(_b & ~prev_mouse_mask & 2)
1439 send_analog_input(_x, _y, 1);
1440 if(_b & ~prev_mouse_mask & 4)
1441 send_analog_input(_x, _y, 2);
1442 prev_mouse_mask = _b;
1444 } mousebuttonh;
1446 class button_action : public command
1448 public:
1449 button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button)
1450 throw(std::bad_alloc)
1451 : command(cmd)
1453 commandn = cmd;
1454 type = _type;
1455 controller = _controller;
1456 button = _button;
1458 ~button_action() throw() {}
1459 void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error)
1461 if(args != "")
1462 throw std::runtime_error("This command does not take parameters");
1463 init_buttonmap();
1464 if(!buttonmap.count(button))
1465 return;
1466 auto i = buttonmap[button];
1467 do_button_action(i.first, i.second, (type != 1) ? 1 : 0, (type == 2));
1468 update_movie_state();
1469 win->notify_screen_update();
1471 std::string get_short_help() throw(std::bad_alloc)
1473 return "Press/Unpress button";
1475 std::string get_long_help() throw(std::bad_alloc)
1477 return "Syntax: " + commandn + "\n"
1478 "Presses/Unpresses button\n";
1480 std::string commandn;
1481 unsigned controller;
1482 int type;
1483 std::string button;
1486 class button_action_helper
1488 public:
1489 button_action_helper()
1491 for(size_t i = 0; i < sizeof(buttonnames) / sizeof(buttonnames[0]); ++i)
1492 for(int j = 0; j < 3; ++j)
1493 for(unsigned k = 0; k < 8; ++k) {
1494 std::ostringstream x, y;
1495 switch(j) {
1496 case 0:
1497 x << "+controller";
1498 break;
1499 case 1:
1500 x << "-controller";
1501 break;
1502 case 2:
1503 x << "controllerh";
1504 break;
1506 x << (k + 1);
1507 x << buttonnames[i];
1508 y << (k + 1);
1509 y << buttonnames[i];
1510 new button_action(x.str(), j, k, y.str());
1513 } bah;
1515 //If there is a pending load, perform it.
1516 bool handle_load()
1518 if(pending_load != "") {
1519 do_load_state(pending_load, loadmode);
1520 redraw_framebuffer();
1521 pending_load = "";
1522 pending_reset_cycles = -1;
1523 amode = ADVANCE_AUTO;
1524 win->cancel_wait();
1525 win->paused(false);
1526 if(!system_corrupt) {
1527 location_special = SPECIAL_SAVEPOINT;
1528 update_movie_state();
1529 win->notify_screen_update();
1530 win->poll_inputs();
1532 return true;
1534 return false;
1537 //If there are pending saves, perform them.
1538 void handle_saves()
1540 if(!queued_saves.empty()) {
1541 stepping_into_save = true;
1542 SNES::system.runtosave();
1543 stepping_into_save = false;
1544 for(auto i = queued_saves.begin(); i != queued_saves.end(); i++)
1545 do_save_state(*i);
1547 queued_saves.clear();
1550 //Do (delayed) reset. Return true if proper, false if forced at frame boundary.
1551 bool handle_reset(long cycles)
1553 if(cycles == 0) {
1554 win->message("SNES reset");
1555 SNES::system.reset();
1556 framebuffer = nosignal_screen;
1557 lua_callback_do_reset(win);
1558 redraw_framebuffer();
1559 } else if(cycles > 0) {
1560 video_refresh_done = false;
1561 long cycles_executed = 0;
1562 out(win) << "Executing delayed reset... This can take some time!" << std::endl;
1563 while(cycles_executed < cycles && !video_refresh_done) {
1564 SNES::cpu.op_step();
1565 cycles_executed++;
1567 if(!video_refresh_done)
1568 out(win) << "SNES reset (delayed " << cycles_executed << ")" << std::endl;
1569 else
1570 out(win) << "SNES reset (forced at " << cycles_executed << ")" << std::endl;
1571 SNES::system.reset();
1572 framebuffer = nosignal_screen;
1573 lua_callback_do_reset(win);
1574 redraw_framebuffer();
1575 if(video_refresh_done) {
1576 to_wait_frame(get_ticks_msec());
1577 return false;
1580 return true;
1583 bool handle_corrupt()
1585 if(!system_corrupt)
1586 return false;
1587 while(system_corrupt) {
1588 framebuffer = corrupt_screen;
1589 redraw_framebuffer();
1590 win->cancel_wait();
1591 win->paused(true);
1592 win->poll_inputs();
1593 handle_load();
1594 if(amode == ADVANCE_QUIT)
1595 return true;
1597 return true;
1600 void print_controller_mappings()
1602 for(unsigned i = 0; i < 8; i++) {
1603 std::string type = "unknown";
1604 if(lookup_controller_type(i) == DT_NONE)
1605 type = "disconnected";
1606 if(lookup_controller_type(i) == DT_GAMEPAD)
1607 type = "gamepad";
1608 if(lookup_controller_type(i) == DT_MOUSE)
1609 type = "mouse";
1610 if(lookup_controller_type(i) == DT_SUPERSCOPE)
1611 type = "superscope";
1612 if(lookup_controller_type(i) == DT_JUSTIFIER)
1613 type = "justifier";
1614 out(win) << "Physical controller mapping: Logical " << (i + 1) << " is physical " <<
1615 lookup_physical_controller(i) << " (" << type << ")" << std::endl;
1620 void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& initial) throw(std::bad_alloc,
1621 std::runtime_error)
1623 //Basic initialization.
1624 win = _win;
1625 our_rom = &rom;
1626 my_interface intrf;
1627 auto old_inteface = SNES::system.interface;
1628 SNES::system.interface = &intrf;
1629 status = &win->get_emustatus();
1630 fill_special_frames();
1632 //Load our given movie.
1633 bool first_round = false;
1634 bool just_did_loadstate = false;
1635 try {
1636 do_load_state(initial, LOAD_STATE_DEFAULT);
1637 first_round = our_movie.is_savestate;
1638 just_did_loadstate = first_round;
1639 } catch(std::bad_alloc& e) {
1640 OOM_panic(win);
1641 } catch(std::exception& e) {
1642 win->message(std::string("FATAL: Can't load initial state: ") + e.what());
1643 win->fatal_error();
1644 return;
1647 lua_callback_startup(win);
1649 //print_controller_mappings();
1650 av_snooper::add_dump_notifier(dumpwatch);
1651 win->set_main_surface(scr);
1652 redraw_framebuffer();
1653 win->paused(false);
1654 amode = ADVANCE_PAUSE;
1655 while(amode != ADVANCE_QUIT) {
1656 if(handle_corrupt()) {
1657 first_round = our_movie.is_savestate;
1658 just_did_loadstate = true;
1659 continue;
1661 long resetcycles = -1;
1662 ack_frame_tick(get_ticks_msec());
1663 if(amode == ADVANCE_SKIPLAG_PENDING)
1664 amode = ADVANCE_SKIPLAG;
1666 if(!first_round) {
1667 resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG);
1668 if(amode == ADVANCE_QUIT)
1669 break;
1670 bool delayed_reset = (resetcycles > 0);
1671 pending_reset_cycles = -1;
1672 if(!handle_reset(resetcycles)) {
1673 continue;
1675 if(!delayed_reset) {
1676 handle_saves();
1678 if(handle_load()) {
1679 first_round = our_movie.is_savestate;
1680 amode = ADVANCE_PAUSE;
1681 just_did_loadstate = first_round;
1682 continue;
1685 if(just_did_loadstate) {
1686 if(amode == ADVANCE_QUIT)
1687 break;
1688 amode = ADVANCE_PAUSE;
1689 redraw_framebuffer();
1690 win->cancel_wait();
1691 win->paused(true);
1692 win->poll_inputs();
1693 just_did_loadstate = false;
1695 SNES::system.run();
1696 if(amode == ADVANCE_AUTO)
1697 win->wait_msec(to_wait_frame(get_ticks_msec()));
1698 first_round = false;
1700 av_snooper::end(win);
1701 SNES::system.interface = old_inteface;