diff --git a/src/System.h b/src/System.h index 110e3162..ec8c7e62 100644 --- a/src/System.h +++ b/src/System.h @@ -31,13 +31,13 @@ struct EmulatedSystem { // save state bool (*emuWriteState)(const char *); #endif - // load memory state (rewind) - bool (*emuReadMemState)(char *, int); - // write memory state (rewind) - bool (*emuWriteMemState)(char *, int); - // write PNG file - bool (*emuWritePNG)(const char *); - // write BMP file + // load memory state (rewind) + bool (*emuReadMemState)(char *, int); + // write memory state (rewind) + bool (*emuWriteMemState)(char *, int, long&); + // write PNG file + bool (*emuWritePNG)(const char *); + // write BMP file bool (*emuWriteBMP)(const char *); // emulator update CPSR (ARM only) void (*emuUpdateCPSR)(); diff --git a/src/common/memgzio.c b/src/common/memgzio.c index 529cc832..8d225d3d 100644 --- a/src/common/memgzio.c +++ b/src/common/memgzio.c @@ -691,6 +691,7 @@ int ZEXPORT memgzclose (file) long ZEXPORT memtell(file) gzFile file; { + do_flush (file, Z_FULL_FLUSH); // makes memtell to tell truth mem_stream *s = (mem_stream*)file; if (s == NULL) return Z_STREAM_ERROR; diff --git a/src/gb/GB.cpp b/src/gb/GB.cpp index c532379c..34df6281 100644 --- a/src/gb/GB.cpp +++ b/src/gb/GB.cpp @@ -3929,7 +3929,7 @@ static bool gbWriteSaveState(gzFile gzFile) return true; } -bool gbWriteMemSaveState(char *memory, int available) +bool gbWriteMemSaveState(char *memory, int available, long& reserved) { gzFile gzFile = utilMemGzOpen(memory, available, "w"); @@ -3939,9 +3939,9 @@ bool gbWriteMemSaveState(char *memory, int available) bool res = gbWriteSaveState(gzFile); - long pos = utilGzMemTell(gzFile)+8; + reserved = utilGzMemTell(gzFile)+8; - if(pos >= (available)) + if(reserved >= (available)) res = false; utilGzClose(gzFile); diff --git a/src/gb/gb.h b/src/gb/gb.h index c2556144..d14a0e53 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -35,7 +35,7 @@ bool gbWriteBatteryFile(const char *); bool gbWriteBatteryFile(const char *, bool); bool gbReadBatteryFile(const char *); bool gbWriteSaveState(const char *); -bool gbWriteMemSaveState(char *, int); +bool gbWriteMemSaveState(char *, int, long&); bool gbReadSaveState(const char *); bool gbReadMemSaveState(char *, int); void gbSgbRenderBorder(); diff --git a/src/gba/GBA.cpp b/src/gba/GBA.cpp index 6b491ef0..a5f32600 100644 --- a/src/gba/GBA.cpp +++ b/src/gba/GBA.cpp @@ -621,7 +621,7 @@ unsigned int CPUWriteState(u8* data, unsigned size) return (ptrdiff_t)data - (ptrdiff_t)orig; } -bool CPUWriteMemState(char *memory, int available) +bool CPUWriteMemState(char *memory, int available, long& reserved) { return false; } @@ -684,7 +684,7 @@ bool CPUWriteState(const char *file) } -bool CPUWriteMemState(char *memory, int available) +bool CPUWriteMemState(char *memory, int available, long& reserved) { gzFile gzFile = utilMemGzOpen(memory, available, "w"); @@ -694,9 +694,9 @@ bool CPUWriteMemState(char *memory, int available) bool res = CPUWriteState(gzFile); - long pos = utilGzMemTell(gzFile)+8; + reserved = utilGzMemTell(gzFile)+8; - if(pos >= (available)) + if(reserved >= (available)) res = false; utilGzClose(gzFile); diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index fc69903f..f1d09a6e 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -104,7 +104,8 @@ Window::Window(GtkWindow * _pstWindow, const Glib::RefPtr & _poXml m_iJoypadMax (PAD_4), m_iVideoOutputMin (OutputCairo), m_iVideoOutputMax (OutputOpenGL), - m_bFullscreen (false) + m_bFullscreen (false), + m_psavestate (NULL) { m_poXml = _poXml; m_poFileOpenDialog = NULL; @@ -526,7 +527,12 @@ void Window::vInitConfig() m_poCoreConfig->vSetKey("emulator_type", EmulatorAuto ); m_poCoreConfig->vSetKey("pause_when_inactive", true ); m_poCoreConfig->vSetKey("show_speed", ShowPercentage ); - + + // Rewind + // + m_poCoreConfig->vSetKey("rewind_count_max", STATE_MAX_DEFAULT); + m_poCoreConfig->vSetKey("rewind_interval", STATE_INTERVAL_DEFAULT); + // Display section // m_poDisplayConfig = m_oConfig.poAddSection("Display"); @@ -569,6 +575,7 @@ void Window::vCheckConfig() { int iValue; int iAdjusted; + unsigned short i16Value; float fValue; float fAdjusted; std::string sValue; @@ -665,6 +672,22 @@ void Window::vCheckConfig() m_poCoreConfig->vSetKey("emulator_type", iAdjusted); } + // Rewind feature + // move to value change cb + i16Value = m_poCoreConfig->oGetKey("rewind_count_max"); + if (i16Value > 65535u) + { + m_poCoreConfig->vSetKey("rewind_count_max", STATE_MAX_DEFAULT); + } + m_state_count_max = m_poCoreConfig->oGetKey("rewind_count_max"); + + iValue = m_poCoreConfig->oGetKey("rewind_interval"); + if (i16Value > 65535u) + { + m_poCoreConfig->vSetKey("rewind_interval", STATE_INTERVAL_DEFAULT); + } + m_rewind_interval = m_poCoreConfig->oGetKey("rewind_interval"); + // Display section // iValue = m_poDisplayConfig->oGetKey("scale"); @@ -1108,6 +1131,15 @@ bool Window::bLoadROM(const std::string & _rsFile) vOnLoadGameMostRecent(); } + // reserve rewind space for write operation + // this is used as work space + // actual state blocks are reserved in bOnEmuSaveStateRewind() + // when resulted size is known + // + if (m_state_count_max > 0 && m_psavestate == NULL) { + m_psavestate = new char[SZSTATE]; + } + vStartEmu(); return true; @@ -1414,6 +1446,9 @@ void Window::vSaveCheats() void Window::vStartEmu() { + m_oEmuRewindSig.disconnect(); + m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuSaveStateRewind), m_rewind_interval); + if (m_oEmuSig.connected()) { return; @@ -1426,6 +1461,7 @@ void Window::vStartEmu() void Window::vStopEmu() { m_oEmuSig.disconnect(); + m_oEmuRewindSig.disconnect(); m_bWasEmulating = false; } diff --git a/src/gtk/window.h b/src/gtk/window.h index 77652499..ad0a50cc 100644 --- a/src/gtk/window.h +++ b/src/gtk/window.h @@ -166,6 +166,8 @@ protected: virtual void vOnCheatDisableToggled(Gtk::CheckMenuItem * _poCMI); virtual void vOnHelpAbout(); virtual bool bOnEmuIdle(); + virtual bool bOnEmuSaveStateRewind(); + virtual bool bOnEmuRewind(); virtual bool on_focus_in_event(GdkEventFocus * _pstEvent); virtual bool on_focus_out_event(GdkEventFocus * _pstEvent); @@ -242,7 +244,7 @@ private: std::list m_listSensitiveWhenPlaying; - sigc::connection m_oEmuSig; + sigc::connection m_oEmuSig, m_oEmuRewindSig; int m_bFullscreen; int m_iScreenWidth; @@ -257,6 +259,16 @@ private: bool m_bAutoFrameskip; EShowSpeed m_eShowSpeed; + + /* State saving into memory & rewind to saved state */ + u16 m_state_count_max; + u16 m_rewind_interval; + static const u32 SZSTATE = 1024*512; + static const u16 STATE_MAX_DEFAULT = 180u; + static const u16 STATE_INTERVAL_DEFAULT = 165u; + std::deque m_rewind_load_q; + char *m_psavestate; + void vInitSystem(); void vUnInitSystem(); void vInitSDL(); diff --git a/src/gtk/windowcallbacks.cpp b/src/gtk/windowcallbacks.cpp index e504d352..09558c06 100644 --- a/src/gtk/windowcallbacks.cpp +++ b/src/gtk/windowcallbacks.cpp @@ -18,6 +18,8 @@ #include "window.h" +#include + #include #include #include @@ -341,6 +343,13 @@ void Window::vOnFileClose() m_eCartridge = CartridgeNone; emulating = 0; + while (!m_rewind_load_q.empty()) { + delete[] m_rewind_load_q.front(); + m_rewind_load_q.pop_front(); + } + delete[] m_psavestate; + m_psavestate = NULL; + vUpdateGameSlots(); for (std::list::iterator it = m_listSensitiveWhenPlaying.begin(); @@ -534,6 +543,57 @@ void Window::vOnHelpAbout() oAboutDialog.run(); } +bool Window::bOnEmuRewind() +{ + if( !m_rewind_load_q.empty() ) { + // load a rewind save state + char *psavestate = m_rewind_load_q.front(); + //memset(m_psavestate, 0x0, SZSTATE); + long szstate = *((long*)psavestate); // first there is size + //memmove(m_psavestate, psavestate+sizeof(szstate), szstate-sizeof(szstate)); + if (m_stEmulator.emuReadMemState(psavestate+sizeof(szstate), szstate)) { + // the save state is now used so delete it and we have more space + m_rewind_load_q.pop_front(); + delete[] psavestate; + //printf ("Restored %p! (%li bytes)\n", psavestate, szstate); + } + return true; + } else { + // no more save states; either disabled, too early, or rewinded all the way to start + return false; + } +} + +bool Window::bOnEmuSaveStateRewind() { + // check if we're disabled + char *psavestate; + if (m_state_count_max == 0u) { + return false; + } else if (m_rewind_load_q.size() >= m_state_count_max) { // check if we can reserve more memory for save states + // if we can't reserve more memory let's take away used save states starting from oldest + psavestate = m_rewind_load_q.back(); + m_rewind_load_q.pop_back(); + delete[] psavestate; + } // otherwise we can reserve more memory + + // Do the actual saving + long ressize; + if (m_stEmulator.emuWriteMemState(m_psavestate, SZSTATE, ressize)) { + /*ressize*=2; // if tell does not return correct size this leverage factor is needed + if (ressize > SZSTATE) ressize = SZSTATE;*/ + g_assert( ressize <= SZSTATE ); + ressize+=(sizeof(ressize)*8); // some leverage + psavestate = new char[ressize]; + memmove(psavestate, &ressize, sizeof(ressize)); // pack size first + memmove(psavestate+sizeof(ressize), m_psavestate, ressize-sizeof(ressize)); // then actual save data + //printf("Wrote %p (%li bytes %i %i)\n", psavestate, ressize, *((long*)psavestate), sizeof(ressize)); + m_rewind_load_q.push_front(psavestate); + return true; + } else { + return false; + } +} + bool Window::bOnEmuIdle() { vSDLPollEvents(); @@ -573,6 +633,17 @@ bool Window::on_key_press_event(GdkEventKey * _pstEvent) return true; } + // Rewind key CTRL+B + if (m_state_count_max > 0u && (_pstEvent->state & GDK_CONTROL_MASK) && _pstEvent->keyval == GDK_b) { + // disable saves first and then connect new handler + if (m_oEmuRewindSig.connected()) m_oEmuRewindSig.disconnect(); + m_state_count_max = 0u; + //return this->bOnEmuRewind(); + m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuRewind), + 65u); + return true; + } + // Forward the keyboard event to the input module by faking a SDL event SDL_Event event; event.type = SDL_KEYDOWN; @@ -584,6 +655,15 @@ bool Window::on_key_press_event(GdkEventKey * _pstEvent) bool Window::on_key_release_event(GdkEventKey * _pstEvent) { + // Rewind key CTRL+B + if (_pstEvent->keyval == GDK_b /*&& !(_pstEvent->state & GDK_CONTROL_MASK)*/) { + // connect save handler back + if (m_oEmuRewindSig.connected()) m_oEmuRewindSig.disconnect(); + m_state_count_max = m_poCoreConfig->oGetKey("rewind_count_max"); + m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuSaveStateRewind), m_rewind_interval); + return true; + } + // Forward the keyboard event to the input module by faking a SDL event SDL_Event event; event.type = SDL_KEYUP; diff --git a/src/sdl/SDL.cpp b/src/sdl/SDL.cpp index b0aee232..14534a5a 100644 --- a/src/sdl/SDL.cpp +++ b/src/sdl/SDL.cpp @@ -1438,13 +1438,14 @@ void handleRewinds() rewindCount = REWIND_NUM; curSavePos = (rewindTopPos + 1) % rewindCount; // [1] depends on previous - + long ressize; if( emulator.emuWriteMemState && emulator.emuWriteMemState( &rewindMemory[curSavePos*REWIND_SIZE], - REWIND_SIZE + REWIND_SIZE, /* available*/ + ressize /* actual size */ ) ) { char rewMsgBuf[100]; @@ -2048,6 +2049,16 @@ void systemScreenCapture(int a) systemScreenMessage("Screen capture"); } +void systemSaveOldest() +{ + // I need to be implemented +} + +void systemLoadRecent() +{ + // I need to be implemented +} + u32 systemGetClock() { return SDL_GetTicks(); diff --git a/src/win32/VBA.cpp b/src/win32/VBA.cpp index 3c0ea6e0..0ef8fd4c 100644 --- a/src/win32/VBA.cpp +++ b/src/win32/VBA.cpp @@ -1316,8 +1316,9 @@ BOOL VBA::OnIdle(LONG lCount) rewindCount++; if(rewindCount > 8) rewindCount = 8; + long ressize; if(emulator.emuWriteMemState(&rewindMemory[rewindPos*REWIND_SIZE], - REWIND_SIZE)) { + REWIND_SIZE, ressize)) { /* available and actual size */ rewindPos = ++rewindPos & 7; if(rewindCount == 8) rewindTopPos = ++rewindTopPos & 7; diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index c5b98468..ba6b089d 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -1081,8 +1081,10 @@ void GameArea::OnIdle(wxIdleEvent &event) return; } + long ressize; + if (!emusys->emuWriteMemState(&rewind_mem[REWIND_SIZE * next_rewind_state], - REWIND_SIZE)) + REWIND_SIZE, ressize /* actual size */)) // if you see a lot of these, maybe increase REWIND_SIZE wxLogInfo(_("Error writing rewind state")); else diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index ff3de061..6efda6ce 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -494,6 +494,17 @@ void systemScreenCapture(int num) systemScreenMessage(msg); } +void systemSaveOldest() +{ + // I need to be implemented +} + +void systemLoadRecent() +{ + // I need to be implemented +} + + u32 systemGetClock() { return wxGetApp().timer.Time(); diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index c6f476ac..6b0e9556 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -452,9 +452,9 @@ public: u32 rom_size; // FIXME: size this properly -#define REWIND_SIZE 400000 - // FIXME: make this a config option #define NUM_REWINDS 8 +#define REWIND_SIZE 1024*512*NUM_REWINDS + // FIXME: make this a config option void ShowFullScreen(bool full); bool IsFullScreen() { return fullscreen; }