#include "mainloop.hpp" #include "avsnoop.hpp" #include "command.hpp" #include #include "framerate.hpp" #include "memorywatch.hpp" #include "lua.hpp" #include "rrdata.hpp" #include "rom.hpp" #include "movie.hpp" #include "moviefile.hpp" #include "render.hpp" #include "keymapper.hpp" #include "window.hpp" #include "settings.hpp" #include "rom.hpp" #include "movie.hpp" #include "window.hpp" #include #include #include "memorymanip.hpp" #include "keymapper.hpp" #include "render.hpp" #include #include "lsnes.hpp" #include #include #include #include "framerate.hpp" #define LOAD_STATE_RW 0 #define LOAD_STATE_RO 1 #define LOAD_STATE_PRESERVE 2 #define LOAD_STATE_MOVIE 3 #define LOAD_STATE_DEFAULT 4 #define SAVE_STATE 0 #define SAVE_MOVIE 1 #define SPECIAL_FRAME_START 0 #define SPECIAL_FRAME_VIDEO 1 #define SPECIAL_SAVEPOINT 2 #define SPECIAL_NONE 3 #define BUTTON_LEFT 0 //Gamepad #define BUTTON_RIGHT 1 //Gamepad #define BUTTON_UP 2 //Gamepad #define BUTTON_DOWN 3 //Gamepad #define BUTTON_A 4 //Gamepad #define BUTTON_B 5 //Gamepad #define BUTTON_X 6 //Gamepad #define BUTTON_Y 7 //Gamepad #define BUTTON_L 8 //Gamepad & Mouse #define BUTTON_R 9 //Gamepad & Mouse #define BUTTON_SELECT 10 //Gamepad #define BUTTON_START 11 //Gamepad & Justifier #define BUTTON_TRIGGER 12 //Superscope. #define BUTTON_CURSOR 13 //Superscope & Justifier #define BUTTON_PAUSE 14 //Superscope #define BUTTON_TURBO 15 //Superscope void update_movie_state(); void draw_nosignal(uint16_t* target); void draw_corrupt(uint16_t* target); namespace { enum advance_mode { ADVANCE_QUIT, //Quit the emulator. ADVANCE_AUTO, //Normal (possibly slowed down play). ADVANCE_FRAME, //Frame advance. ADVANCE_SUBFRAME, //Subframe advance. ADVANCE_SKIPLAG, //Skip lag (oneshot, reverts to normal). ADVANCE_SKIPLAG_PENDING, //Activate skip lag mode at next frame. ADVANCE_PAUSE, //Unconditional pause. }; //Analog input physical controller IDs and types. int analog[4] = {-1, -1, -1}; bool analog_is_mouse[4] = {false, false, false}; //Memory watches. std::map memory_watches; //Previous mouse mask. int prev_mouse_mask = 0; //Flags related to repeating advance. bool advanced_once; bool cancel_advance; //Our ROM. struct loaded_rom* our_rom; //Our movie file. struct moviefile our_movie; //Handle to the graphics system. window* win; //The SNES screen. struct screen scr; //Emulator advance mode. Detemines pauses at start of frame / subframe, etc.. enum advance_mode amode; //Mode and filename of pending load, one of LOAD_* constants. int loadmode; std::string pending_load; //Queued saves (all savestates). std::set queued_saves; bool stepping_into_save; //Current controls. controls_t curcontrols; controls_t autoheld_controls; //Emulator status area. std::map* status; //Pending reset cycles. -1 if no reset pending, otherwise, cycle count for reset. long pending_reset_cycles = -1; //Set by every video refresh. bool video_refresh_done; //Special subframe location. One of SPECIAL_* constants. int location_special; //Types of connected controllers. enum porttype_t porttype1 = PT_GAMEPAD; enum porttype_t porttype2 = PT_NONE; //System corrupt flag. bool system_corrupt; //Current screen, no signal screen and corrupt screen. lcscreen framebuffer; lcscreen nosignal_screen; lcscreen corrupt_screen; //Few settings. numeric_setting advance_timeout_first("advance-timeout", 0, 999999999, 500); numeric_setting savecompression("savecompression", 0, 9, 7); void send_analog_input(int32_t x, int32_t y, unsigned index) { if(analog_is_mouse[index]) { x -= 256; y -= (framebuffer.height / 2); } else { x /= 2; y /= 2; } if(analog[index] < 0) { out(win) << "No analog controller in slot #" << (index + 1) << std::endl; return; } curcontrols(analog[index] >> 2, analog[index] & 3, 0) = x; curcontrols(analog[index] >> 2, analog[index] & 3, 1) = y; } void redraw_framebuffer() { uint32_t hscl = 1, vscl = 1; if(framebuffer.width < 512) hscl = 2; if(framebuffer.height < 400) vscl = 2; render_queue rq; struct lua_render_context lrc; lrc.left_gap = 0; lrc.right_gap = 0; lrc.bottom_gap = 0; lrc.top_gap = 0; lrc.queue = &rq; lrc.width = framebuffer.width * hscl; lrc.height = framebuffer.height * vscl; lrc.rshift = scr.active_rshift; lrc.gshift = scr.active_gshift; lrc.bshift = scr.active_bshift; lua_callback_do_paint(&lrc, win); scr.reallocate(framebuffer.width * hscl + lrc.left_gap + lrc.right_gap, framebuffer.height * vscl + lrc.top_gap + lrc.bottom_gap, lrc.left_gap, lrc.top_gap); scr.copy_from(framebuffer, hscl, vscl); //We would want divide by 2, but we'll do it ourselves in order to do mouse. win->set_window_compensation(lrc.left_gap, lrc.top_gap, 1, 1); rq.run(scr); win->notify_screen_update(); } void fill_special_frames() { uint16_t buf[512*448]; draw_nosignal(buf); nosignal_screen = lcscreen(buf, 512, 448); draw_corrupt(buf); corrupt_screen = lcscreen(buf, 512, 448); } } class firmware_path_setting : public setting { public: firmware_path_setting() : setting("firmwarepath") { _firmwarepath = "./"; default_firmware = true; } void blank() throw(std::bad_alloc, std::runtime_error) { _firmwarepath = "./"; default_firmware = true; } bool is_set() throw() { return !default_firmware; } void set(const std::string& value) throw(std::bad_alloc, std::runtime_error) { _firmwarepath = value; default_firmware = false; } std::string get() throw(std::bad_alloc) { return _firmwarepath; } operator std::string() throw(std::bad_alloc) { return _firmwarepath; } private: std::string _firmwarepath; bool default_firmware; } firmwarepath_setting; class mymovielogic : public movie_logic { public: mymovielogic() : movie_logic(dummy_movie) {} controls_t update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error) { if(lua_requests_subframe_paint) redraw_framebuffer(); if(subframe) { if(amode == ADVANCE_SUBFRAME) { if(!cancel_advance && !advanced_once) { win->wait_msec(advance_timeout_first); advanced_once = true; } if(cancel_advance) { amode = ADVANCE_PAUSE; cancel_advance = false; } win->paused(amode == ADVANCE_PAUSE); } else if(amode == ADVANCE_FRAME) { ; } else { win->paused(amode == ADVANCE_SKIPLAG || amode == ADVANCE_PAUSE); cancel_advance = false; } if(amode == ADVANCE_SKIPLAG) amode = ADVANCE_AUTO; location_special = SPECIAL_NONE; update_movie_state(); } else { if(amode == ADVANCE_SKIPLAG_PENDING) amode = ADVANCE_SKIPLAG; if(amode == ADVANCE_FRAME || amode == ADVANCE_SUBFRAME) { if(!cancel_advance) { win->wait_msec(advanced_once ? to_wait_frame(get_ticks_msec()) : advance_timeout_first); advanced_once = true; } if(cancel_advance) { amode = ADVANCE_PAUSE; cancel_advance = false; } win->paused(amode == ADVANCE_PAUSE); } else { win->paused((amode == ADVANCE_PAUSE)); cancel_advance = false; } location_special = SPECIAL_FRAME_START; update_movie_state(); } win->notify_screen_update(); win->poll_inputs(); if(!subframe && pending_reset_cycles >= 0) { curcontrols(CONTROL_SYSTEM_RESET) = 1; curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = pending_reset_cycles / 10000; curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = pending_reset_cycles % 10000; } else if(!subframe) { curcontrols(CONTROL_SYSTEM_RESET) = 0; curcontrols(CONTROL_SYSTEM_RESET_CYCLES_HI) = 0; curcontrols(CONTROL_SYSTEM_RESET_CYCLES_LO) = 0; } controls_t tmp = curcontrols ^ autoheld_controls; lua_callback_do_input(tmp, subframe, win); return tmp; } private: movie dummy_movie; }; namespace { mymovielogic movb; //Lookup physical controller id based on UI controller id and given types (-1 if invalid). int lookup_physical_controller(unsigned ui_id) { bool p1multitap = (porttype1 == PT_MULTITAP); unsigned p1devs = port_types[porttype1].devices; unsigned p2devs = port_types[porttype2].devices; if(ui_id >= p1devs + p2devs) return -1; if(!p1multitap) if(ui_id < p1devs) return ui_id; else return 4 + ui_id - p1devs; else if(ui_id == 0) return 0; else if(ui_id < 5) return ui_id + 3; else return ui_id - 4; } //Look up controller type given UI controller id (note: Non-present controllers give PT_NONE, not the type //of port, multitap controllers give PT_GAMEPAD, not PT_MULTITAP, and justifiers give PT_JUSTIFIER, not //PT_JUSTIFIERS). enum devicetype_t lookup_controller_type(unsigned ui_id) { int x = lookup_physical_controller(ui_id); if(x < 0) return DT_NONE; enum porttype_t rawtype = (x & 4) ? porttype2 : porttype1; if((x & 3) < port_types[rawtype].devices) return port_types[rawtype].dtype; else return DT_NONE; } void set_analog_controllers() { unsigned index = 0; for(unsigned i = 0; i < 8; i++) { enum devicetype_t t = lookup_controller_type(i); analog_is_mouse[index] = (t == DT_MOUSE); if(t == DT_MOUSE || t == DT_SUPERSCOPE || t == DT_JUSTIFIER) { analog[index++] = lookup_physical_controller(i); } else analog[index] = -1; } for(; index < 3; index++) analog[index] = -1; } std::map> buttonmap; const char* buttonnames[] = { "left", "right", "up", "down", "A", "B", "X", "Y", "L", "R", "select", "start", "trigger", "cursor", "pause", "turbo" }; void init_buttonmap() { static int done = 0; if(done) return; for(unsigned i = 0; i < 8; i++) for(unsigned j = 0; j < sizeof(buttonnames) / sizeof(buttonnames[0]); j++) { std::ostringstream x; x << (i + 1) << buttonnames[j]; buttonmap[x.str()] = std::make_pair(i, j); } done = 1; } //Do button action. void do_button_action(unsigned ui_id, unsigned button, short newstate, bool do_xor = false) { enum devicetype_t p = lookup_controller_type(ui_id); int x = lookup_physical_controller(ui_id); int bid = -1; switch(p) { case DT_NONE: out(win) << "No such controller #" << (ui_id + 1) << std::endl; return; case DT_GAMEPAD: switch(button) { case BUTTON_UP: bid = SNES_DEVICE_ID_JOYPAD_UP; break; case BUTTON_DOWN: bid = SNES_DEVICE_ID_JOYPAD_DOWN; break; case BUTTON_LEFT: bid = SNES_DEVICE_ID_JOYPAD_LEFT; break; case BUTTON_RIGHT: bid = SNES_DEVICE_ID_JOYPAD_RIGHT; break; case BUTTON_A: bid = SNES_DEVICE_ID_JOYPAD_A; break; case BUTTON_B: bid = SNES_DEVICE_ID_JOYPAD_B; break; case BUTTON_X: bid = SNES_DEVICE_ID_JOYPAD_X; break; case BUTTON_Y: bid = SNES_DEVICE_ID_JOYPAD_Y; break; case BUTTON_L: bid = SNES_DEVICE_ID_JOYPAD_L; break; case BUTTON_R: bid = SNES_DEVICE_ID_JOYPAD_R; break; case BUTTON_SELECT: bid = SNES_DEVICE_ID_JOYPAD_SELECT; break; case BUTTON_START: bid = SNES_DEVICE_ID_JOYPAD_START; break; default: out(win) << "Invalid button for gamepad" << std::endl; return; }; break; case DT_MOUSE: switch(button) { case BUTTON_L: bid = SNES_DEVICE_ID_MOUSE_LEFT; break; case BUTTON_R: bid = SNES_DEVICE_ID_MOUSE_RIGHT; break; default: out(win) << "Invalid button for mouse" << std::endl; return; }; break; case DT_JUSTIFIER: switch(button) { case BUTTON_START: bid = SNES_DEVICE_ID_JUSTIFIER_START; break; case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_JUSTIFIER_TRIGGER; break; default: out(win) << "Invalid button for justifier" << std::endl; return; }; break; case DT_SUPERSCOPE: switch(button) { case BUTTON_TRIGGER: bid = SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER; break; case BUTTON_CURSOR: bid = SNES_DEVICE_ID_SUPER_SCOPE_CURSOR; break; case BUTTON_PAUSE: bid = SNES_DEVICE_ID_SUPER_SCOPE_PAUSE; break; case BUTTON_TURBO: bid = SNES_DEVICE_ID_SUPER_SCOPE_TURBO; break; default: out(win) << "Invalid button for superscope" << std::endl; return; }; break; }; if(do_xor) autoheld_controls((x & 4) ? 1 : 0, x & 3, bid) ^= newstate; else curcontrols((x & 4) ? 1 : 0, x & 3, bid) = newstate; } //Save state. void do_save_state(const std::string& filename) throw(std::bad_alloc, std::runtime_error) { lua_callback_pre_save(filename, true, win); try { uint64_t origtime = get_ticks_msec(); our_movie.is_savestate = true; our_movie.sram = save_sram(); our_movie.savestate = save_core_state(); framebuffer.save(our_movie.screenshot); auto s = movb.get_movie().save_state(); our_movie.movie_state.resize(s.size()); memcpy(&our_movie.movie_state[0], &s[0], s.size()); our_movie.input = movb.get_movie().save(); our_movie.save(filename, savecompression); uint64_t took = get_ticks_msec() - origtime; out(win) << "Saved state '" << filename << "' in " << took << "ms." << std::endl; lua_callback_post_save(filename, true, win); } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { win->message(std::string("Save failed: ") + e.what()); lua_callback_err_save(filename, win); } } //Save movie. void do_save_movie(const std::string& filename) throw(std::bad_alloc, std::runtime_error) { lua_callback_pre_save(filename, false, win); try { uint64_t origtime = get_ticks_msec(); our_movie.is_savestate = false; our_movie.input = movb.get_movie().save(); our_movie.save(filename, savecompression); uint64_t took = get_ticks_msec() - origtime; out(win) << "Saved movie '" << filename << "' in " << took << "ms." << std::endl; lua_callback_post_save(filename, false, win); } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { win->message(std::string("Save failed: ") + e.what()); lua_callback_err_save(filename, win); } } void warn_hash_mismatch(const std::string& mhash, const loaded_slot& slot, const std::string& name) { if(mhash != slot.sha256) { out(win) << "WARNING: " << name << " hash mismatch!" << std::endl << "\tMovie: " << mhash << std::endl << "\tOur ROM: " << slot.sha256 << std::endl; } } void set_dev(bool port, porttype_t t, bool set_core = true) { //return; switch(set_core ? t : PT_INVALID) { case PT_NONE: snes_set_controller_port_device(port, SNES_DEVICE_NONE); break; case PT_GAMEPAD: snes_set_controller_port_device(port, SNES_DEVICE_JOYPAD); break; case PT_MULTITAP: snes_set_controller_port_device(port, SNES_DEVICE_MULTITAP); break; case PT_MOUSE: snes_set_controller_port_device(port, SNES_DEVICE_MOUSE); break; case PT_SUPERSCOPE: snes_set_controller_port_device(port, SNES_DEVICE_SUPER_SCOPE); break; case PT_JUSTIFIER: snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIER); break; case PT_JUSTIFIERS: snes_set_controller_port_device(port, SNES_DEVICE_JUSTIFIERS); break; case PT_INVALID: ; }; if(port) porttype2 = t; else porttype1 = t; set_analog_controllers(); } //Load state from loaded movie file (does not catch errors). void do_load_state(struct moviefile& _movie, int lmode) { bool will_load_state = _movie.is_savestate && lmode != LOAD_STATE_MOVIE; if(gtype::toromtype(_movie.gametype) != our_rom->rtype) throw std::runtime_error("ROM types of movie and loaded ROM don't match"); if(gtype::toromregion(_movie.gametype) != our_rom->orig_region && our_rom->orig_region != REGION_AUTO) throw std::runtime_error("NTSC/PAL select of movie and loaded ROM don't match"); if(_movie.coreversion != bsnes_core_version) { if(will_load_state) { std::ostringstream x; x << "ERROR: Emulator core version mismatch!" << std::endl << "\tThis version: " << bsnes_core_version << std::endl << "\tFile is from: " << _movie.coreversion << std::endl; throw std::runtime_error(x.str()); } else out(win) << "WARNING: Emulator core version mismatch!" << std::endl << "\tThis version: " << bsnes_core_version << std::endl << "\tFile is from: " << _movie.coreversion << std::endl; } warn_hash_mismatch(_movie.rom_sha256, our_rom->rom, "ROM #1"); warn_hash_mismatch(_movie.romxml_sha256, our_rom->rom_xml, "XML #1"); warn_hash_mismatch(_movie.slota_sha256, our_rom->slota, "ROM #2"); warn_hash_mismatch(_movie.slotaxml_sha256, our_rom->slota_xml, "XML #2"); warn_hash_mismatch(_movie.slotb_sha256, our_rom->slotb, "ROM #3"); warn_hash_mismatch(_movie.slotbxml_sha256, our_rom->slotb_xml, "XML #3"); SNES::config.random = false; SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None; movie newmovie; if(lmode == LOAD_STATE_PRESERVE) newmovie = movb.get_movie(); else newmovie.load(_movie.rerecords, _movie.projectid, _movie.input); if(will_load_state) { std::vector tmp; tmp.resize(_movie.movie_state.size()); memcpy(&tmp[0], &_movie.movie_state[0], tmp.size()); newmovie.restore_state(tmp, true); } //Negative return. rrdata::read_base(_movie.projectid); rrdata::add_internal(); try { our_rom->region = gtype::toromregion(_movie.gametype); our_rom->load(); if(_movie.is_savestate && lmode != LOAD_STATE_MOVIE) { //Load the savestate and movie state. set_dev(false, _movie.port1); set_dev(true, _movie.port2); load_core_state(_movie.savestate); framebuffer.load(_movie.screenshot); } else { load_sram(_movie.movie_sram, win); set_dev(false, _movie.port1); set_dev(true, _movie.port2); framebuffer = nosignal_screen; } } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { system_corrupt = true; throw; } //Okay, copy the movie data. our_movie = _movie; if(!our_movie.is_savestate || lmode == LOAD_STATE_MOVIE) { our_movie.is_savestate = false; our_movie.host_memory.clear(); } movb.get_movie() = newmovie; //Activate RW mode if needed. if(lmode == LOAD_STATE_RW) movb.get_movie().readonly_mode(false); if(lmode == LOAD_STATE_DEFAULT && !(movb.get_movie().get_frame_count())) movb.get_movie().readonly_mode(false); out(win) << "ROM Type "; switch(our_rom->rtype) { case ROMTYPE_SNES: out(win) << "SNES"; break; case ROMTYPE_BSX: out(win) << "BS-X"; break; case ROMTYPE_BSXSLOTTED: out(win) << "BS-X slotted"; break; case ROMTYPE_SUFAMITURBO: out(win) << "Sufami Turbo"; break; case ROMTYPE_SGB: out(win) << "Super Game Boy"; break; default: out(win) << "Unknown"; break; } out(win) << " region "; switch(our_rom->region) { case REGION_PAL: out(win) << "PAL"; break; case REGION_NTSC: out(win) << "NTSC"; break; default: out(win) << "Unknown"; break; } out(win) << std::endl; uint64_t mlength = _movie.get_movie_length(); { mlength += 999999; std::ostringstream x; if(mlength > 3600000000000) { x << mlength / 3600000000000 << ":"; mlength %= 3600000000000; } x << std::setfill('0') << std::setw(2) << mlength / 60000000000 << ":"; mlength %= 60000000000; x << std::setfill('0') << std::setw(2) << mlength / 1000000000 << "."; mlength %= 1000000000; x << std::setfill('0') << std::setw(3) << mlength / 1000000; out(win) << "Rerecords " << _movie.rerecords << " length " << x.str() << " (" << _movie.get_frame_count() << " frames)" << std::endl; } if(_movie.gamename != "") out(win) << "Game Name: " << _movie.gamename << std::endl; for(size_t i = 0; i < _movie.authors.size(); i++) out(win) << "Author: " << _movie.authors[i].first << "(" << _movie.authors[i].second << ")" << std::endl; } //Load state void do_load_state(const std::string& filename, int lmode) { uint64_t origtime = get_ticks_msec(); lua_callback_pre_load(filename, win); struct moviefile mfile; try { mfile = moviefile(filename); } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { win->message("Can't read movie/savestate '" + filename + "': " + e.what()); lua_callback_err_load(filename, win); return; } try { do_load_state(mfile, lmode); uint64_t took = get_ticks_msec() - origtime; out(win) << "Loaded '" << filename << "' in " << took << "ms." << std::endl; lua_callback_post_load(filename, our_movie.is_savestate, win); } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { win->message("Can't load movie/savestate '" + filename + "': " + e.what()); lua_callback_err_load(filename, win); return; } } //Do pending load (automatically unpauses). void mark_pending_load(const std::string& filename, int lmode) { loadmode = lmode; pending_load = filename; amode = ADVANCE_AUTO; win->cancel_wait(); win->paused(false); } //Mark pending save (movies save immediately). void mark_pending_save(const std::string& filename, int smode) { if(smode == SAVE_MOVIE) { //Just do this immediately. do_save_movie(filename); return; } queued_saves.insert(filename); win->message("Pending save on '" + filename + "'"); } class dump_watch : public av_snooper::dump_notification { void dump_starting() throw() { update_movie_state(); } void dump_ending() throw() { update_movie_state(); } } dumpwatch; } std::vector& get_host_memory() { return our_movie.host_memory; } movie& get_movie() { return movb.get_movie(); } void update_movie_state() { auto& _status = win->get_emustatus(); { std::ostringstream x; x << movb.get_movie().get_current_frame() << "("; if(location_special == SPECIAL_FRAME_START) x << "0"; else if(location_special == SPECIAL_SAVEPOINT) x << "S"; else if(location_special == SPECIAL_FRAME_VIDEO) x << "V"; else x << movb.get_movie().next_poll_number(); x << ";" << movb.get_movie().get_lag_frames() << ")/" << movb.get_movie().get_frame_count(); _status["Frame"] = x.str(); } { std::ostringstream x; if(movb.get_movie().readonly_mode()) x << "PLAY "; else x << "REC "; if(av_snooper::dump_in_progress()) x << "CAP "; _status["Flags"] = x.str(); } for(auto i = memory_watches.begin(); i != memory_watches.end(); i++) { try { _status["M[" + i->first + "]"] = evaluate_watch(i->second); } catch(...) { } } controls_t c; if(movb.get_movie().readonly_mode()) c = movb.get_movie().get_controls(); else c = curcontrols ^ autoheld_controls; for(unsigned i = 0; i < 8; i++) { unsigned pindex = lookup_physical_controller(i); unsigned port = pindex >> 2; unsigned dev = pindex & 3; auto ctype = lookup_controller_type(i); std::ostringstream x; switch(ctype) { case DT_GAMEPAD: x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_LEFT) ? "l" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_RIGHT) ? "r" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_UP) ? "u" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_DOWN) ? "d" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_A) ? "A" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_B) ? "B" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_X) ? "X" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_Y) ? "Y" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_L) ? "L" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_R) ? "R" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_START) ? "S" : " "); x << (c(port, dev, SNES_DEVICE_ID_JOYPAD_SELECT) ? "s" : " "); break; case DT_MOUSE: x << c(port, dev, SNES_DEVICE_ID_MOUSE_X) << " "; x << c(port, dev, SNES_DEVICE_ID_MOUSE_Y) << " "; x << (c(port, dev, SNES_DEVICE_ID_MOUSE_LEFT) ? "L" : " "); x << (c(port, dev, SNES_DEVICE_ID_MOUSE_RIGHT) ? "R" : " "); break; case DT_SUPERSCOPE: x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_X) << " "; x << c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_Y) << " "; x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER) ? "T" : " "); x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_CURSOR) ? "C" : " "); x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_TURBO) ? "t" : " "); x << (c(port, dev, SNES_DEVICE_ID_SUPER_SCOPE_PAUSE) ? "P" : " "); break; case DT_JUSTIFIER: x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_X) << " "; x << c(port, dev, SNES_DEVICE_ID_JUSTIFIER_Y) << " "; x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_START) ? "T" : " "); x << (c(port, dev, SNES_DEVICE_ID_JUSTIFIER_TRIGGER) ? "S" : " "); break; case DT_NONE: continue; } char y[3] = {'P', 0, 0}; y[1] = 49 + i; _status[std::string(y)] = x.str(); } } class my_interface : public SNES::Interface { string path(SNES::Cartridge::Slot slot, const string &hint) { return static_cast(firmwarepath_setting).c_str(); } void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan) { if(stepping_into_save) win->message("Got video refresh in runtosave, expect desyncs!"); video_refresh_done = true; bool region = (SNES::system.region() == SNES::System::Region::PAL); //std::cerr << "Frame: hires flag is " << (hires ? " " : "un") << "set." << std::endl; //std::cerr << "Frame: interlace flag is " << (interlace ? " " : "un") << "set." << std::endl; //std::cerr << "Frame: overscan flag is " << (overscan ? " " : "un") << "set." << std::endl; //std::cerr << "Frame: region flag is " << (region ? " " : "un") << "set." << std::endl; lcscreen ls(data, hires, interlace, overscan, region); framebuffer = ls; location_special = SPECIAL_FRAME_VIDEO; update_movie_state(); redraw_framebuffer(); uint32_t fps_n, fps_d; if(region) { fps_n = 322445; fps_d = 6448; } else { fps_n = 10738636; fps_d = 178683; } av_snooper::frame(ls, fps_n, fps_d, win); } void audio_sample(int16_t l_sample, int16_t r_sample) { uint16_t _l = l_sample; uint16_t _r = r_sample; win->play_audio_sample(_l + 32768, _r + 32768); av_snooper::sample(_l, _r, win); } void audio_sample(uint16_t l_sample, uint16_t r_sample) { //Yes, this interface is broken. The samples are signed but are passed as unsigned! win->play_audio_sample(l_sample + 32768, r_sample + 32768); av_snooper::sample(l_sample, r_sample, win); } int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id) { int16_t x; x = movb.input_poll(port, index, id); //if(id == SNES_DEVICE_ID_JOYPAD_START) // std::cerr << "bsnes polling for start on (" << port << "," << index << ")=" << x << std::endl; lua_callback_snoop_input(port ? 1 : 0, index, id, x, win); return x; } }; namespace { class quit_emulator_cmd : public command { public: quit_emulator_cmd() throw(std::bad_alloc) : command("quit-emulator") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "/y" || win->modal_message("Really quit?", true)) { amode = ADVANCE_QUIT; win->paused(false); win->cancel_wait(); } } std::string get_short_help() throw(std::bad_alloc) { return "Quit the emulator"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: quit-emulator [/y]\n" "Quits emulator (/y => don't ask for confirmation).\n"; } } quitemu; class pause_emulator_cmd : public command { public: pause_emulator_cmd() throw(std::bad_alloc) : command("pause-emulator") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); if(amode != ADVANCE_AUTO) { amode = ADVANCE_AUTO; win->paused(false); win->cancel_wait(); win->message("Unpaused"); } else { win->cancel_wait(); cancel_advance = false; amode = ADVANCE_PAUSE; win->message("Paused"); } } std::string get_short_help() throw(std::bad_alloc) { return "(Un)pause the emulator"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: pause-emulator\n" "(Un)pauses the emulator.\n"; } } pauseemu; class padvance_frame_cmd : public command { public: padvance_frame_cmd() throw(std::bad_alloc) : command("+advance-frame") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); amode = ADVANCE_FRAME; cancel_advance = false; advanced_once = false; win->cancel_wait(); win->paused(false); } std::string get_short_help() throw(std::bad_alloc) { return "Advance one frame"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: +advance-frame\n" "Advances the emulation by one frame.\n"; } } padvancef; class nadvance_frame_cmd : public command { public: nadvance_frame_cmd() throw(std::bad_alloc) : command("-advance-frame") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); cancel_advance = true; win->cancel_wait(); win->paused(false); } } nadvancef; class padvance_poll_cmd : public command { public: padvance_poll_cmd() throw(std::bad_alloc) : command("+advance-poll") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); amode = ADVANCE_SUBFRAME; cancel_advance = false; advanced_once = false; win->cancel_wait(); win->paused(false); } std::string get_short_help() throw(std::bad_alloc) { return "Advance one subframe"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: +advance-poll\n" "Advances the emulation by one subframe.\n"; } } padvancep; class nadvance_poll_cmd : public command { public: nadvance_poll_cmd() throw(std::bad_alloc) : command("-advance-poll") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); cancel_advance = true; win->cancel_wait(); win->paused(false); } } nadvancep; class advance_skiplag_cmd : public command { public: advance_skiplag_cmd() throw(std::bad_alloc) : command("advance-skiplag") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); amode = ADVANCE_SKIPLAG; win->cancel_wait(); win->paused(false); } std::string get_short_help() throw(std::bad_alloc) { return "Skip to next poll"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: advance-skiplag\n" "Advances the emulation to the next poll.\n"; } } skiplagc; class reset_cmd : public command { public: reset_cmd() throw(std::bad_alloc) : command("reset") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); pending_reset_cycles = 0; } std::string get_short_help() throw(std::bad_alloc) { return "Reset the SNES"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: reset\n" "Resets the SNES in beginning of the next frame.\n"; } } resetc; class load_state_cmd : public command { public: load_state_cmd() throw(std::bad_alloc) : command("load-state") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_load(args, LOAD_STATE_RW); } std::string get_short_help() throw(std::bad_alloc) { return "Load state"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: load-state \n" "Loads SNES state from in Read/Write mode\n"; } } loadstatec; class load_readonly_cmd : public command { public: load_readonly_cmd() throw(std::bad_alloc) : command("load-readonly") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_load(args, LOAD_STATE_RO); } std::string get_short_help() throw(std::bad_alloc) { return "Load state"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: load-readonly \n" "Loads SNES state from in Read-only mode\n"; } } loadreadonlyc; class load_preserve_cmd : public command { public: load_preserve_cmd() throw(std::bad_alloc) : command("load-preserve") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_load(args, LOAD_STATE_PRESERVE); } std::string get_short_help() throw(std::bad_alloc) { return "Load state"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: load-preserve \n" "Loads SNES state from preserving input\n"; } } loadpreservec; class load_movie_cmd : public command { public: load_movie_cmd() throw(std::bad_alloc) : command("load-movie") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_load(args, LOAD_STATE_MOVIE); } std::string get_short_help() throw(std::bad_alloc) { return "Load movie"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: load-movie \n" "Loads movie from \n"; } } loadmoviec; class save_state_cmd : public command { public: save_state_cmd() throw(std::bad_alloc) : command("save-state") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_save(args, SAVE_STATE); } std::string get_short_help() throw(std::bad_alloc) { return "Save state"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: save-state \n" "Saves SNES state to \n"; } } savestatec; class save_movie_cmd : public command { public: save_movie_cmd() throw(std::bad_alloc) : command("save-movie") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); mark_pending_save(args, SAVE_MOVIE); } std::string get_short_help() throw(std::bad_alloc) { return "Save movie"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: save-movie \n" "Saves movie to \n"; } } savemoviec; class set_rwmode_cmd : public command { public: set_rwmode_cmd() throw(std::bad_alloc) : command("set-rwmode") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); movb.get_movie().readonly_mode(false); lua_callback_do_readwrite(win); update_movie_state(); win->notify_screen_update(); } std::string get_short_help() throw(std::bad_alloc) { return "Switch to read/write mode"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: set-rwmode\n" "Switches to read/write mode\n"; } } setrwc; class set_gamename_cmd : public command { public: set_gamename_cmd() throw(std::bad_alloc) : command("set-gamename") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { our_movie.gamename = args; out(win) << "Game name changed to '" << our_movie.gamename << "'" << std::endl; } std::string get_short_help() throw(std::bad_alloc) { return "Set the game name"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: set-gamename \n" "Sets the game name to \n"; } } setnamec; class get_gamename_cmd : public command { public: get_gamename_cmd() throw(std::bad_alloc) : command("get-gamename") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); out(win) << "Game name is '" << our_movie.gamename << "'" << std::endl; } std::string get_short_help() throw(std::bad_alloc) { return "Get the game name"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: get-gamename\n" "Prints the game name\n"; } } getnamec; class print_authors_cmd : public command { public: print_authors_cmd() throw(std::bad_alloc) : command("show-authors") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); size_t idx = 0; for(auto i = our_movie.authors.begin(); i != our_movie.authors.end(); i++) { out(win) << (idx++) << ": " << i->first << "|" << i->second << std::endl; } out(win) << "End of authors list" << std::endl; } std::string get_short_help() throw(std::bad_alloc) { return "Show the run authors"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: show-authors\n" "Shows the run authors\n"; } } getauthorc; class repainter : public command { public: repainter() throw(std::bad_alloc) : command("repaint") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); redraw_framebuffer(); } std::string get_short_help() throw(std::bad_alloc) { return "Redraw the screen"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: repaint\n" "Redraws the screen\n"; } } repaintc; class add_author_command : public command { public: add_author_command() throw(std::bad_alloc) : command("add-author") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); fieldsplitter f(t.tail()); std::string full = f; std::string nick = f; if(full == "" && nick == "") throw std::runtime_error("Bad author name"); our_movie.authors.push_back(std::make_pair(full, nick)); out(win) << (our_movie.authors.size() - 1) << ": " << full << "|" << nick << std::endl; } std::string get_short_help() throw(std::bad_alloc) { return "Add an author"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: add-author \n" "Syntax: add-author |\n" "Syntax: add-author |\n" "Adds a new author\n"; } } addauthorc; class remove_author_command : public command { public: remove_author_command() throw(std::bad_alloc) : command("remove-author") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); uint64_t index = parse_value(t.tail()); if(index >= our_movie.authors.size()) throw std::runtime_error("No such author"); our_movie.authors.erase(our_movie.authors.begin() + index); } std::string get_short_help() throw(std::bad_alloc) { return "Remove an author"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: remove-author \n" "Removes author with ID \n"; } } removeauthorc; class edit_author_command : public command { public: edit_author_command() throw(std::bad_alloc) : command("edit-author") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); uint64_t index = parse_value(t); if(index >= our_movie.authors.size()) throw std::runtime_error("No such author"); fieldsplitter f(t.tail()); std::string full = f; std::string nick = f; if(full == "" && nick == "") { out(win) << "syntax: edit-author " << std::endl; return; } our_movie.authors[index] = std::make_pair(full, nick); } std::string get_short_help() throw(std::bad_alloc) { return "Edit an author"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: edit-author \n" "Syntax: edit-author |\n" "Syntax: edit-author |\n" "Edits author name\n"; } } editauthorc; class add_watch_command : public command { public: add_watch_command() throw(std::bad_alloc) : command("add-watch") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); std::string name = t; if(name == "" || t.tail() == "") throw std::runtime_error("syntax: add-watch "); std::cerr << "Add watch: '" << name << "'" << std::endl; memory_watches[name] = t.tail(); update_movie_state(); } std::string get_short_help() throw(std::bad_alloc) { return "Add a memory watch"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: add-watch \n" "Adds a new memory watch\n"; } } addwatchc; class remove_watch_command : public command { public: remove_watch_command() throw(std::bad_alloc) : command("remove-watch") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); std::string name = t; if(name == "" || t.tail() != "") { out(win) << "syntax: remove-watch " << std::endl; return; } std::cerr << "Erase watch: '" << name << "'" << std::endl; memory_watches.erase(name); auto& _status = win->get_emustatus(); _status.erase("M[" + name + "]"); update_movie_state(); } std::string get_short_help() throw(std::bad_alloc) { return "Remove a memory watch"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: remove-watch \n" "Removes a memory watch\n"; } } removewatchc; class test_1 : public command { public: test_1() throw(std::bad_alloc) : command("test-1") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { framebuffer = nosignal_screen; redraw_framebuffer(); } } test1c; class test_2 : public command { public: test_2() throw(std::bad_alloc) : command("test-2") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { framebuffer = corrupt_screen; redraw_framebuffer(); } } test2c; class test_3 : public command { public: test_3() throw(std::bad_alloc) : command("test-3") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { while(1); } } test3c; class screenshot_command : public command { public: screenshot_command() throw(std::bad_alloc) : command("take-screenshot") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args == "") throw std::runtime_error("Filename required"); framebuffer.save_png(args); out(win) << "Saved PNG screenshot" << std::endl; } std::string get_short_help() throw(std::bad_alloc) { return "Takes a screenshot"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: take-screenshot \n" "Saves screenshot to PNG file \n"; } } screenshotc; class mouse_button_handler : public command { public: mouse_button_handler() throw(std::bad_alloc) : command("mouse_button") {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { tokensplitter t(args); std::string x = t; std::string y = t; std::string b = t; int _x = atoi(x.c_str()); int _y = atoi(y.c_str()); int _b = atoi(b.c_str()); if(_b & ~prev_mouse_mask & 1) send_analog_input(_x, _y, 0); if(_b & ~prev_mouse_mask & 2) send_analog_input(_x, _y, 1); if(_b & ~prev_mouse_mask & 4) send_analog_input(_x, _y, 2); prev_mouse_mask = _b; } } mousebuttonh; class button_action : public command { public: button_action(const std::string& cmd, int _type, unsigned _controller, std::string _button) throw(std::bad_alloc) : command(cmd) { commandn = cmd; type = _type; controller = _controller; button = _button; } ~button_action() throw() {} void invoke(const std::string& args, window* win) throw(std::bad_alloc, std::runtime_error) { if(args != "") throw std::runtime_error("This command does not take parameters"); init_buttonmap(); if(!buttonmap.count(button)) return; auto i = buttonmap[button]; do_button_action(i.first, i.second, (type != 1) ? 1 : 0, (type == 2)); update_movie_state(); win->notify_screen_update(); } std::string get_short_help() throw(std::bad_alloc) { return "Press/Unpress button"; } std::string get_long_help() throw(std::bad_alloc) { return "Syntax: " + commandn + "\n" "Presses/Unpresses button\n"; } std::string commandn; unsigned controller; int type; std::string button; }; class button_action_helper { public: button_action_helper() { for(size_t i = 0; i < sizeof(buttonnames) / sizeof(buttonnames[0]); ++i) for(int j = 0; j < 3; ++j) for(unsigned k = 0; k < 8; ++k) { std::ostringstream x, y; switch(j) { case 0: x << "+controller"; break; case 1: x << "-controller"; break; case 2: x << "controllerh"; break; }; x << (k + 1); x << buttonnames[i]; y << (k + 1); y << buttonnames[i]; new button_action(x.str(), j, k, y.str()); } } } bah; //If there is a pending load, perform it. bool handle_load() { if(pending_load != "") { do_load_state(pending_load, loadmode); redraw_framebuffer(); pending_load = ""; pending_reset_cycles = -1; amode = ADVANCE_AUTO; win->cancel_wait(); win->paused(false); if(!system_corrupt) { location_special = SPECIAL_SAVEPOINT; update_movie_state(); win->notify_screen_update(); win->poll_inputs(); } return true; } return false; } //If there are pending saves, perform them. void handle_saves() { if(!queued_saves.empty()) { stepping_into_save = true; SNES::system.runtosave(); stepping_into_save = false; for(auto i = queued_saves.begin(); i != queued_saves.end(); i++) do_save_state(*i); } queued_saves.clear(); } //Do (delayed) reset. Return true if proper, false if forced at frame boundary. bool handle_reset(long cycles) { if(cycles == 0) { win->message("SNES reset"); SNES::system.reset(); framebuffer = nosignal_screen; lua_callback_do_reset(win); redraw_framebuffer(); } else if(cycles > 0) { video_refresh_done = false; long cycles_executed = 0; out(win) << "Executing delayed reset... This can take some time!" << std::endl; while(cycles_executed < cycles && !video_refresh_done) { SNES::cpu.op_step(); cycles_executed++; } if(!video_refresh_done) out(win) << "SNES reset (delayed " << cycles_executed << ")" << std::endl; else out(win) << "SNES reset (forced at " << cycles_executed << ")" << std::endl; SNES::system.reset(); framebuffer = nosignal_screen; lua_callback_do_reset(win); redraw_framebuffer(); if(video_refresh_done) { to_wait_frame(get_ticks_msec()); return false; } } return true; } bool handle_corrupt() { if(!system_corrupt) return false; while(system_corrupt) { framebuffer = corrupt_screen; redraw_framebuffer(); win->cancel_wait(); win->paused(true); win->poll_inputs(); handle_load(); if(amode == ADVANCE_QUIT) return true; } return true; } void print_controller_mappings() { for(unsigned i = 0; i < 8; i++) { std::string type = "unknown"; if(lookup_controller_type(i) == DT_NONE) type = "disconnected"; if(lookup_controller_type(i) == DT_GAMEPAD) type = "gamepad"; if(lookup_controller_type(i) == DT_MOUSE) type = "mouse"; if(lookup_controller_type(i) == DT_SUPERSCOPE) type = "superscope"; if(lookup_controller_type(i) == DT_JUSTIFIER) type = "justifier"; out(win) << "Physical controller mapping: Logical " << (i + 1) << " is physical " << lookup_physical_controller(i) << " (" << type << ")" << std::endl; } } } void main_loop(window* _win, struct loaded_rom& rom, struct moviefile& initial) throw(std::bad_alloc, std::runtime_error) { //Basic initialization. win = _win; our_rom = &rom; my_interface intrf; auto old_inteface = SNES::system.interface; SNES::system.interface = &intrf; status = &win->get_emustatus(); fill_special_frames(); //Load our given movie. bool first_round = false; bool just_did_loadstate = false; try { do_load_state(initial, LOAD_STATE_DEFAULT); first_round = our_movie.is_savestate; just_did_loadstate = first_round; } catch(std::bad_alloc& e) { OOM_panic(win); } catch(std::exception& e) { win->message(std::string("FATAL: Can't load initial state: ") + e.what()); win->fatal_error(); return; } lua_callback_startup(win); //print_controller_mappings(); av_snooper::add_dump_notifier(dumpwatch); win->set_main_surface(scr); redraw_framebuffer(); win->paused(false); amode = ADVANCE_PAUSE; while(amode != ADVANCE_QUIT) { if(handle_corrupt()) { first_round = our_movie.is_savestate; just_did_loadstate = true; continue; } long resetcycles = -1; ack_frame_tick(get_ticks_msec()); if(amode == ADVANCE_SKIPLAG_PENDING) amode = ADVANCE_SKIPLAG; if(!first_round) { resetcycles = movb.new_frame_starting(amode == ADVANCE_SKIPLAG); if(amode == ADVANCE_QUIT) break; bool delayed_reset = (resetcycles > 0); pending_reset_cycles = -1; if(!handle_reset(resetcycles)) { continue; } if(!delayed_reset) { handle_saves(); } if(handle_load()) { first_round = our_movie.is_savestate; amode = ADVANCE_PAUSE; just_did_loadstate = first_round; continue; } } if(just_did_loadstate) { if(amode == ADVANCE_QUIT) break; amode = ADVANCE_PAUSE; redraw_framebuffer(); win->cancel_wait(); win->paused(true); win->poll_inputs(); just_did_loadstate = false; } SNES::system.run(); if(amode == ADVANCE_AUTO) win->wait_msec(to_wait_frame(get_ticks_msec())); first_round = false; } av_snooper::end(win); SNES::system.interface = old_inteface; }