diff --git a/include/core/mainloop.hpp b/include/core/mainloop.hpp index beaed31c..08ec9b89 100644 --- a/include/core/mainloop.hpp +++ b/include/core/mainloop.hpp @@ -13,5 +13,14 @@ void main_loop(struct loaded_rom& rom, struct moviefile& settings, bool load_has std::vector get_jukebox_names(); void set_jukebox_names(const std::vector& newj); void update_movie_state(); +extern std::string msu1_base_path; -#endif \ No newline at end of file +/** + * Signal that fast rewind operation is needed. + * + * Parameter ptr: If NULL, saves a state and calls lua_callback_do_unsafe_rewind() with quicksave, movie and NULL. + * If non-NULL, calls lua_callback_do_unsafe_rewind() with movie and this parameter, but without quicksave. + */ +void mainloop_signal_need_rewind(void* ptr); + +#endif diff --git a/include/core/movie.hpp b/include/core/movie.hpp index 05fbcf3b..c560f494 100644 --- a/include/core/movie.hpp +++ b/include/core/movie.hpp @@ -230,6 +230,15 @@ public: * movie, reads all released. */ controller_frame read_subframe(uint64_t frame, uint64_t subframe) throw(); +/** + * Fast save. + */ + void fast_save(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector& counters); +/** + * Fast load. + */ + void fast_load(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector& counters); + private: //TRUE if readonly mode is active. bool readonly; diff --git a/include/core/moviedata.hpp b/include/core/moviedata.hpp index e17e5856..6eadfe2b 100644 --- a/include/core/moviedata.hpp +++ b/include/core/moviedata.hpp @@ -33,4 +33,14 @@ std::string translate_name_mprefix(std::string original, bool forio = false); extern std::string last_save; extern movie_logic movb; +/** + * Restore the actual core state from quicksave. Only call in rewind callback. + * + * Parameter state: The state to restore. + * Parameter secs: The seconds counter. + * Parameter ssecs: The subsecond counter. + */ +void mainloop_restore_state(const std::vector& state, uint64_t secs, uint64_t ssecs); + + #endif diff --git a/include/core/rom.hpp b/include/core/rom.hpp index 03f8750b..2e5591e9 100644 --- a/include/core/rom.hpp +++ b/include/core/rom.hpp @@ -176,7 +176,11 @@ struct loaded_rom */ std::vector markup_slots; /** - * Patch the ROMs. + * MSU-1 base. + */ + std::string msu1_base; +/** + * Patch the ROM. * * parameter cmdline: The command line. * throws std::bad_alloc: Not enough memory. @@ -222,7 +226,27 @@ std::map> load_sram_commandline(const std::vector throw(std::bad_alloc, std::runtime_error); /** +<<<<<<< HEAD * Given commandline arguments, load a ROM. +======= + * Saves core state into buffer. WARNING: This takes emulated time. + * + * returns: The saved state. + * throws std::bad_alloc: Not enough memory. + */ +std::vector save_core_state(bool nochecksum = false) throw(std::bad_alloc); + +/** + * Loads core state from buffer. + * + * parameter buf: The buffer containing the state. + * throws std::runtime_error: Loading state failed. + */ +void load_core_state(const std::vector& buf, bool nochecksum = false) throw(std::runtime_error); + +/** + * Read index of ROMs and add ROMs found to content-searchable storage. +>>>>>>> rr1-maint * * parameter cmdline: The command line. * returns: The loaded ROM set. diff --git a/include/interface/core.hpp b/include/interface/core.hpp index 49b36c5b..83082320 100644 --- a/include/interface/core.hpp +++ b/include/interface/core.hpp @@ -89,8 +89,8 @@ struct sram_slot_structure* emucore_sram_slot(size_t index); size_t emucore_vma_slots(); struct vma_structure* emucore_vma_slot(size_t index); void emucore_refresh_cart(); -std::vector emucore_serialize(); -void emucore_unserialize(const std::vector& data); +std::vector emucore_serialize(bool nochecksum = false); +void emucore_unserialize(const std::vector& data, bool nochecksum = false); void emucore_load_rom(systype_info_structure* rtype, region_info_structure* region, const std::vector>& romslots, const std::vector>& markslots); struct region_info_structure* emucore_current_region(); diff --git a/include/lua/lua.hpp b/include/lua/lua.hpp index 086ee4ff..c481856c 100644 --- a/include/lua/lua.hpp +++ b/include/lua/lua.hpp @@ -4,6 +4,7 @@ #include "core/controllerframe.hpp" #include "core/keymapper.hpp" #include "core/render.hpp" +#include "core/movie.hpp" struct lua_State; @@ -88,6 +89,7 @@ void lua_callback_post_save(const std::string& name, bool is_state) throw(); void lua_callback_snoop_input(uint32_t port, uint32_t controller, uint32_t index, short value) throw(); void lua_callback_quit() throw(); void lua_callback_keyhook(const std::string& key, const struct keygroup::parameters& p) throw(); +void lua_callback_do_unsafe_rewind(const std::vector& save, uint64_t secs, uint64_t ssecs, movie& mov, void* u); #define LUA_TIMED_HOOK_IDLE 0 #define LUA_TIMED_HOOK_TIMER 1 diff --git a/include/lua/unsaferewind.hpp b/include/lua/unsaferewind.hpp new file mode 100644 index 00000000..f8cf109d --- /dev/null +++ b/include/lua/unsaferewind.hpp @@ -0,0 +1,15 @@ +#ifndef _lua__unsaferewind__hpp__included__ +#define _lua__unsaferewind__hpp__included__ + +struct lua_unsaferewind +{ + std::vector state; + uint64_t frame; + uint64_t lag; + uint64_t ptr; + uint64_t secs; + uint64_t ssecs; + std::vector pollcounters; +}; + +#endif \ No newline at end of file diff --git a/manual.lyx b/manual.lyx index 92d9c59f..51fc6a2a 100644 --- a/manual.lyx +++ b/manual.lyx @@ -2918,6 +2918,51 @@ Read specifed subframe in specified frame and return data as array (100 elements, numbered 0-99 currently). \end_layout +\begin_layout Subsubsection +movie.unsafe_rewind([UNSAFEREWIND state]) +\end_layout + +\begin_layout Standard +Start setting point for unsafe rewind or jump to point of unsafe rewind. +\end_layout + +\begin_layout Itemize +If called without argument, causes emulator to start process of setting + unsafe rewind point. + When this has finished, callback on_set_rewind occurs, passing the rewind + state to lua script. +\end_layout + +\begin_layout Itemize +If called with argument, causes emulator rewind to passed rewind point as + soon as possible. + Readwrite mode is implicitly activated. +\end_layout + +\begin_layout Standard +The following warnings apply to unsafe rewinding: +\end_layout + +\begin_layout Itemize +There are no safety checks against misuse (that's what +\begin_inset Quotes eld +\end_inset + +unsafe +\begin_inset Quotes erd +\end_inset + + comes from)! +\end_layout + +\begin_layout Itemize +Only call rewind from timeline rewind point was set from. +\end_layout + +\begin_layout Itemize +Only call rewind from after the rewind point was set. +\end_layout + \begin_layout Subsection Table settings \end_layout @@ -3341,6 +3386,30 @@ Called when requested by set_idle_timeout() and the timeout has expired (regardless if emulator is waiting). \end_layout +\begin_layout Subsubsection +Callback: on_set_rewind(UNSAFEREWIND r) +\end_layout + +\begin_layout Standard +Called when unsafe rewind object has been constructed. +\end_layout + +\begin_layout Subsubsection +Callback: on_pre_rewind() +\end_layout + +\begin_layout Standard +Called just before unsafe rewind is about to occur. +\end_layout + +\begin_layout Subsubsection +Callback: on_post_rewind() +\end_layout + +\begin_layout Standard +Called just after unsafe rewind has occured. +\end_layout + \begin_layout Section Memory watch expression syntax \end_layout diff --git a/manual.txt b/manual.txt index 34de1642..49769fd5 100644 --- a/manual.txt +++ b/manual.txt @@ -1476,6 +1476,28 @@ Count number of subframes in specified frame (frame numbers are Read specifed subframe in specified frame and return data as array (100 elements, numbered 0-99 currently). +8.6.7 movie.unsafe_rewind([UNSAFEREWIND state]) + +Start setting point for unsafe rewind or jump to point of unsafe +rewind. + +• If called without argument, causes emulator to start process of + setting unsafe rewind point. When this has finished, callback + on_set_rewind occurs, passing the rewind state to lua script. + +• If called with argument, causes emulator rewind to passed + rewind point as soon as possible. Readwrite mode is implicitly + activated. + +The following warnings apply to unsafe rewinding: + +• There are no safety checks against misuse (that's what “unsafe” + comes from)! + +• Only call rewind from timeline rewind point was set from. + +• Only call rewind from after the rewind point was set. + 8.7 Table settings Routines for settings manipulation @@ -1712,6 +1734,18 @@ expired and emulator is waiting. Called when requested by set_idle_timeout() and the timeout has expired (regardless if emulator is waiting). +8.10.21 Callback: on_set_rewind(UNSAFEREWIND r) + +Called when unsafe rewind object has been constructed. + +8.10.22 Callback: on_pre_rewind() + +Called just before unsafe rewind is about to occur. + +8.10.23 Callback: on_post_rewind() + +Called just after unsafe rewind has occured. + 9 Memory watch expression syntax Memory watch expressions are in RPN (Reverse Polish Notation). At diff --git a/src/core/mainloop.cpp b/src/core/mainloop.cpp index ad518c9a..4908f2c2 100644 --- a/src/core/mainloop.cpp +++ b/src/core/mainloop.cpp @@ -80,6 +80,11 @@ namespace //Delay reset. unsigned long long delayreset_cycles_run; unsigned long long delayreset_cycles_target; + //Unsafe rewind. + bool do_unsafe_rewind = false; + void* unsafe_rewind_obj = NULL; + + enum advance_mode old_mode; bool delayreset_fn() { @@ -97,6 +102,16 @@ namespace path_setting firmwarepath_setting("firmwarepath"); +void mainloop_signal_need_rewind(void* ptr) +{ + if(ptr) { + old_mode = amode; + amode = ADVANCE_LOAD; + } + do_unsafe_rewind = true; + unsafe_rewind_obj = ptr; +} + controller_frame movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error) { if(lua_requests_subframe_paint) @@ -165,7 +180,6 @@ controller_frame movie_logic::update_controls(bool subframe) throw(std::bad_allo namespace { - enum advance_mode old_mode; //Do pending load (automatically unpauses). void mark_pending_load(const std::string& filename, int lmode) @@ -286,7 +300,7 @@ void update_movie_state() uint64_t audio_irq_time; uint64_t controller_irq_time; uint64_t frame_irq_time; - +std::string msu1_base_path; class my_interface : public SNES::Interface { @@ -295,6 +309,23 @@ class my_interface : public SNES::Interface const char* _hint = hint; std::string _hint2 = _hint; std::string fwp = firmwarepath_setting; + regex_results r; + std::string msubase = msu1_base_path; + if(regex_match(".*\\.sfc", msu1_base_path)) + msubase = msu1_base_path.substr(0, msu1_base_path.length() - 4); + + if(_hint2 == "msu1.rom" || _hint2 == ".msu") { + //MSU-1 main ROM. + std::string x = msubase + ".msu"; + messages << "MSU main data file: " << x << std::endl; + return x.c_str(); + } + if(r = regex("(track)?(-([0-9])+\\.pcm)", _hint2)) { + //MSU track. + std::string x = msubase + r[2]; + messages << "MSU track " << r[3] << "': " << x << std::endl; + return x.c_str(); + } std::string finalpath = fwp + "/" + _hint2; return finalpath.c_str(); } @@ -736,6 +767,18 @@ namespace //failing. int handle_load() { + if(do_unsafe_rewind && unsafe_rewind_obj) { + uint64_t t = get_utime(); + std::vector s; + lua_callback_do_unsafe_rewind(s, 0, 0, movb.get_movie(), unsafe_rewind_obj); + information_dispatch::do_mode_change(false); + do_unsafe_rewind = false; + our_movie.is_savestate = true; + location_special = SPECIAL_SAVEPOINT; + update_movie_state(); + messages << "Rewind done in " << (get_utime() - t) << " usec." << std::endl; + return 1; + } if(pending_load != "") { system_corrupt = false; if(loadmode != LOAD_STATE_BEGINNING && !do_load_state(pending_load, loadmode)) { @@ -763,12 +806,21 @@ namespace //If there are pending saves, perform them. void handle_saves() { - if(!queued_saves.empty()) { + if(!queued_saves.empty() || (do_unsafe_rewind && !unsafe_rewind_obj)) { stepping_into_save = true; SNES::system.runtosave(); stepping_into_save = false; for(auto i : queued_saves) do_save_state(i); + if(do_unsafe_rewind && !unsafe_rewind_obj) { + uint64_t t = get_utime(); + std::vector s = emucore_serialize(true); + uint64_t secs = our_movie.rtc_second; + uint64_t ssecs = our_movie.rtc_subsecond; + lua_callback_do_unsafe_rewind(s, secs, ssecs, movb.get_movie(), NULL); + do_unsafe_rewind = false; + messages << "Rewind point set in " << (get_utime() - t) << " usec." << std::endl; + } } queued_saves.clear(); } diff --git a/src/core/movie.cpp b/src/core/movie.cpp index d4e8db3f..0a759fe1 100644 --- a/src/core/movie.cpp +++ b/src/core/movie.cpp @@ -459,6 +459,25 @@ void movie::reset_state() throw() clear_caches(); } +void movie::fast_save(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector& _counters) +{ + pollcounters.save_state(_counters); + _frame = current_frame; + _ptr = current_frame_first_subframe; + _lagc = lag_frames; +} + +void movie::fast_load(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector& _counters) +{ + readonly = true; + current_frame = _frame; + current_frame_first_subframe = (_ptr <= movie_data.size()) ? _ptr : movie_data.size(); + lag_frames = _lagc; + pollcounters.load_state(_counters); + readonly_mode(false); +} + + movie_logic::movie_logic() throw() { } diff --git a/src/core/moviedata.cpp b/src/core/moviedata.cpp index 31025320..cc97a17c 100644 --- a/src/core/moviedata.cpp +++ b/src/core/moviedata.cpp @@ -291,6 +291,7 @@ void do_load_beginning() throw(std::bad_alloc, std::runtime_error) redraw_framebuffer(screen_corrupt, true); throw; } + information_dispatch::do_mode_change(movb.get_movie().readonly_mode()); our_movie.is_savestate = false; our_movie.host_memory.clear(); messages << "Movie rewound to beginning." << std::endl; @@ -469,3 +470,11 @@ bool do_load_state(const std::string& filename, int lmode) } return true; } + +void mainloop_restore_state(const std::vector& state, uint64_t secs, uint64_t ssecs) +{ + rrdata::add_internal(); + our_movie.rtc_second = secs; + our_movie.rtc_subsecond = ssecs; + emucore_unserialize(state, true); +} diff --git a/src/core/rom.cpp b/src/core/rom.cpp index 38b15b16..f50a268d 100644 --- a/src/core/rom.cpp +++ b/src/core/rom.cpp @@ -1,6 +1,7 @@ #include "core/command.hpp" #include "core/dispatch.hpp" #include "core/framerate.hpp" +#include "core/mainloop.hpp" #include "core/memorymanip.hpp" #include "core/misc.hpp" #include "core/rom.hpp" @@ -210,6 +211,10 @@ loaded_rom::loaded_rom(const rom_files& files) throw(std::bad_alloc, std::runtim markup_slots[i] = loaded_slot(files.markup_slots[i], files.base_file, *rtype->rom_slot(i), true); orig_region = region = files.region; + if(main_slots.size() > 1) + msu1_base = resolve_file_relative(files.main_slots[1], files.base_file); + else + msu1_base = resolve_file_relative(files.main_slots[0], files.base_file); } void loaded_rom::load() throw(std::bad_alloc, std::runtime_error) @@ -234,6 +239,7 @@ void loaded_rom::load() throw(std::bad_alloc, std::runtime_error) auto soundrate = emucore_get_audio_rate(); set_nominal_framerate(1.0 * framerate.first / framerate.second); information_dispatch::do_sound_rate(soundrate.first, soundrate.second); + msu1_base_path = msu1_base; refresh_cart_mappings(); } diff --git a/src/interface/bsnes.cpp b/src/interface/bsnes.cpp index 9771dfd7..522d372a 100644 --- a/src/interface/bsnes.cpp +++ b/src/interface/bsnes.cpp @@ -446,12 +446,14 @@ void emucore_refresh_cart() delete i; } -std::vector emucore_serialize() +std::vector emucore_serialize(bool nochecksum) { std::vector ret; serializer s = SNES::system.serialize(); ret.resize(s.size()); memcpy(&ret[0], s.data(), s.size()); + if(nochecksum) + return ret; size_t offset = ret.size(); unsigned char tmp[32]; sha256::hash(tmp, ret); @@ -460,8 +462,14 @@ std::vector emucore_serialize() return ret; } -void emucore_unserialize(const std::vector& buf) +void emucore_unserialize(const std::vector& buf, bool nochecksum) { + if(nochecksum) { + serializer s(reinterpret_cast(&buf[0]), buf.size()); + if(!SNES::system.unserialize(s)) + throw std::runtime_error("SNES core rejected savestate"); + return; + } if(buf.size() < 32) throw std::runtime_error("Savestate corrupt"); unsigned char tmp[32]; diff --git a/src/lua/dummy.cpp b/src/lua/dummy.cpp index 107c2674..38bd91b4 100644 --- a/src/lua/dummy.cpp +++ b/src/lua/dummy.cpp @@ -27,6 +27,8 @@ void lua_callback_keyhook(const std::string& key, const struct keygroup::paramet void init_lua() throw() {} void quit_lua() throw() {} uint64_t lua_timed_hook(int timer) throw() { return 0x7EFFFFFFFFFFFFFFULL; } +void lua_callback_do_unsafe_rewind(const std::vector& save, movie& mov, void* u) {} + bool lua_requests_repaint = false; bool lua_requests_subframe_paint = false; diff --git a/src/lua/lua.cpp b/src/lua/lua.cpp index 2aa25164..93b318ed 100644 --- a/src/lua/lua.cpp +++ b/src/lua/lua.cpp @@ -2,8 +2,10 @@ #include "core/globalwrap.hpp" #include "lua/internal.hpp" #include "lua/lua.hpp" +#include "lua/unsaferewind.hpp" #include "core/mainloop.hpp" #include "core/memorymanip.hpp" +#include "core/moviedata.hpp" #include "core/misc.hpp" #include @@ -555,7 +557,38 @@ uint64_t lua_timed_hook(int timer) throw() } } +void lua_callback_do_unsafe_rewind(const std::vector& save, uint64_t secs, uint64_t ssecs, movie& mov, void* u) +{ + if(u) { + lua_unsaferewind* u2 = reinterpret_cast*>(u)->object(); + //Load. + try { + if(callback_exists("on_pre_rewind")) + run_lua_cb(0); + mainloop_restore_state(u2->state, u2->secs, u2->ssecs); + mov.fast_load(u2->frame, u2->ptr, u2->lag, u2->pollcounters); + if(callback_exists("on_post_rewind")) + run_lua_cb(0); + } catch(...) { + return; + } + } else { + //Save + if(callback_exists("on_set_rewind")) { + lua_unsaferewind* u2 = lua_class::create(L); + u2->state = save; + u2->secs = secs, + u2->ssecs = ssecs; + mov.fast_save(u2->frame, u2->ptr, u2->lag, u2->pollcounters); + run_lua_cb(1); + } + + } +} + bool lua_requests_repaint = false; bool lua_requests_subframe_paint = false; bool lua_supported = true; + +DECLARE_LUACLASS(lua_unsaferewind, "UNSAFEREWIND"); diff --git a/src/lua/movie.cpp b/src/lua/movie.cpp index c7540f76..58db24fd 100644 --- a/src/lua/movie.cpp +++ b/src/lua/movie.cpp @@ -1,6 +1,8 @@ #include "lua/internal.hpp" +#include "lua/unsaferewind.hpp" #include "core/movie.hpp" #include "core/moviedata.hpp" +#include "core/mainloop.hpp" namespace { @@ -68,4 +70,20 @@ namespace lua_pushnumber(LS, our_movie.rtc_subsecond); return 2; }); + + function_ptr_luafun musv("movie.unsafe_rewind", [](lua_State* LS, const std::string& fname) -> int { + if(lua_isnoneornil(LS, 1)) { + //Start process to mark save. + mainloop_signal_need_rewind(NULL); + } else if(lua_class::is(LS, 1)) { + //Load the save. + lua_obj_pin* u = lua_class::pin(LS, 1, fname.c_str()); + mainloop_signal_need_rewind(u); + } else { + lua_pushstring(LS, "movie.unsafe_rewind: Expected nil or UNSAFEREWIND as 1st argument"); + lua_error(LS); + return 0; + } + }); } + diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index 18de7d01..70875e31 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -473,7 +473,7 @@ void boot_emulator(loaded_rom& rom, moviefile& movie) } wxwin_mainwindow::panel::panel(wxWindow* win) - : wxPanel(win) + : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS) { this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this); this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this); diff --git a/src/platform/wxwidgets/settings.cpp b/src/platform/wxwidgets/settings.cpp index 253ac939..dfa587e8 100644 --- a/src/platform/wxwidgets/settings.cpp +++ b/src/platform/wxwidgets/settings.cpp @@ -100,11 +100,11 @@ namespace int horiz_padding = 60; wxdialog_pressbutton::wxdialog_pressbutton(wxWindow* parent, const std::string& title) - : wxDialog(parent, wxID_ANY, towxstring(title), wxDefaultPosition, wxSize(-1, -1)) + : wxDialog(parent, wxID_ANY, towxstring(title)) { wxStaticText* t; wxBoxSizer* s2 = new wxBoxSizer(wxVERTICAL); - wxPanel* p = new wxPanel(this, wxID_ANY); + wxPanel* p = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, -1), wxWANTS_CHARS); s2->Add(p, 1, wxGROW); lastkbdkey = -1; mouseflag = 0; @@ -116,7 +116,8 @@ namespace s->Add(0, 0); s->Add(0, 0); s->Add(0, 0); - s->Add(t = new wxStaticText(p, wxID_ANY, wxT("Press the key to assign")), 1, wxGROW); + s->Add(t = new wxStaticText(p, wxID_ANY, wxT("Press the key to assign"), wxDefaultPosition, + wxSize(-1, -1), wxWANTS_CHARS), 1, wxGROW); s->Add(0, 0); s->Add(0, 0); s->Add(0, 0);