From 56722324efdf8d5c9675f7658a35b0d05e9f8594 Mon Sep 17 00:00:00 2001
From: Jeffrey Pfau <jeffrey@endrift.com>
Date: Sun, 28 Aug 2016 20:38:24 -0700
Subject: [PATCH] Core: Put back rewind

---
 src/core/config.c                    |  2 -
 src/core/config.h                    |  1 -
 src/core/core.h                      |  3 +
 src/core/rewind.c                    | 88 ++++++++++++++++++++++++++++
 src/core/rewind.h                    | 31 ++++++++++
 src/core/thread.c                    | 55 ++++++++++++++++-
 src/core/thread.h                    | 10 ++++
 src/gb/video.c                       |  6 +-
 src/gba/gba.c                        | 11 +---
 src/gba/serialize.c                  |  2 -
 src/platform/qt/ConfigController.cpp |  3 +-
 src/platform/qt/GameController.cpp   | 39 +++++-------
 src/platform/qt/GameController.h     |  3 +-
 src/platform/qt/SettingsView.cpp     | 14 -----
 src/platform/qt/SettingsView.h       |  1 -
 src/platform/qt/SettingsView.ui      | 83 ++++++--------------------
 src/platform/qt/Window.cpp           |  9 +--
 src/platform/sdl/main.c              |  1 +
 src/platform/sdl/sdl-events.c        |  6 +-
 19 files changed, 232 insertions(+), 136 deletions(-)
 create mode 100644 src/core/rewind.c
 create mode 100644 src/core/rewind.h

diff --git a/src/core/config.c b/src/core/config.c
index 17de20d46..6dfb6ea8c 100644
--- a/src/core/config.c
+++ b/src/core/config.c
@@ -307,7 +307,6 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts)
 	_lookupIntValue(config, "frameskip", &opts->frameskip);
 	_lookupIntValue(config, "volume", &opts->volume);
 	_lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
-	_lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval);
 	_lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
 	unsigned audioBuffers;
 	if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) {
@@ -363,7 +362,6 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio
 	ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
 	ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
 	ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity);
-	ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval);
 	ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);
 	ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers);
 	ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate);
diff --git a/src/core/config.h b/src/core/config.h
index 27d12ca5e..806fadde3 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -25,7 +25,6 @@ struct mCoreOptions {
 	int frameskip;
 	bool rewindEnable;
 	int rewindBufferCapacity;
-	int rewindBufferInterval;
 	float fpsTarget;
 	size_t audioBuffers;
 	unsigned sampleRate;
diff --git a/src/core/core.h b/src/core/core.h
index 78dc2ae6a..38655362c 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -145,6 +145,9 @@ void mCoreTakeScreenshot(struct mCore* core);
 struct mCore* mCoreFindVF(struct VFile* vf);
 enum mPlatform mCoreIsCompatible(struct VFile* vf);
 
+bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags);
+bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags);
+
 void mCoreInitConfig(struct mCore* core, const char* port);
 void mCoreLoadConfig(struct mCore* core);
 void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config);
diff --git a/src/core/rewind.c b/src/core/rewind.c
new file mode 100644
index 000000000..e48b2eb8b
--- /dev/null
+++ b/src/core/rewind.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "rewind.h"
+
+#include "core/core.h"
+#include "util/patch-fast.h"
+#include "util/vfs.h"
+
+DEFINE_VECTOR(mCoreRewindPatches, struct PatchFast);
+
+void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries) {
+	mCoreRewindPatchesInit(&context->patchMemory, entries);
+	size_t e;
+	for (e = 0; e < entries; ++e) {
+		initPatchFast(mCoreRewindPatchesAppend(&context->patchMemory));
+	}
+	context->previousState = VFileMemChunk(0, 0);
+	context->currentState = VFileMemChunk(0, 0);
+	context->size = 0;
+}
+
+void mCoreRewindContextDeinit(struct mCoreRewindContext* context) {
+	context->previousState->close(context->previousState);
+	context->currentState->close(context->currentState);
+	size_t s;
+	for (s = 0; s < mCoreRewindPatchesSize(&context->patchMemory); ++s) {
+		deinitPatchFast(mCoreRewindPatchesGetPointer(&context->patchMemory, s));
+	}
+	mCoreRewindPatchesDeinit(&context->patchMemory);
+}
+
+void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) {
+	struct VFile* nextState = context->previousState;
+	++context->current;
+	if (context->size < mCoreRewindPatchesSize(&context->patchMemory)) {
+		++context->size;
+	}
+	if (context->current >= mCoreRewindPatchesSize(&context->patchMemory)) {
+		context->current = 0;
+	}
+	mCoreSaveStateNamed(core, nextState, 0);
+	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
+	size_t size2 = nextState->size(nextState);
+	size_t size = context->currentState->size(context->currentState);
+	if (size2 > size) {
+		context->currentState->truncate(context->currentState, size2);
+		size = size2;
+	}
+	void* current = context->currentState->map(context->currentState, size, MAP_READ);
+	void* next = nextState->map(nextState, size, MAP_READ);
+	diffPatchFast(patch, current, next, size);
+	context->currentState->unmap(context->currentState, current, size);
+	nextState->unmap(next, nextState, size);
+	context->previousState = context->currentState;
+	context->currentState = nextState;
+}
+
+bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core) {
+	if (!context->size) {
+		return false;
+	}
+	--context->size;
+
+	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
+	size_t size2 = context->previousState->size(context->previousState);
+	size_t size = context->currentState->size(context->currentState);
+	if (size2 < size) {
+		size = size2;
+	}
+	void* current = context->currentState->map(context->currentState, size, MAP_READ);
+	void* previous = context->previousState->map(context->previousState, size, MAP_WRITE);
+	patch->d.applyPatch(&patch->d, current, size, previous, size);
+	context->currentState->unmap(context->currentState, current, size);
+	context->previousState->unmap(context->previousState, previous, size);
+	mCoreLoadStateNamed(core, context->previousState, 0);
+	struct VFile* nextState = context->previousState;
+	context->previousState = context->currentState;
+	context->currentState = nextState;
+
+	if (context->current == 0) {
+		context->current = mCoreRewindPatchesSize(&context->patchMemory);
+	} 
+	--context->current;
+	return true;
+}
diff --git a/src/core/rewind.h b/src/core/rewind.h
new file mode 100644
index 000000000..50844c545
--- /dev/null
+++ b/src/core/rewind.h
@@ -0,0 +1,31 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef M_CORE_REWIND_H
+#define M_CORE_REWIND_H
+
+#include "util/common.h"
+
+#include "util/vector.h"
+
+DECLARE_VECTOR(mCoreRewindPatches, struct PatchFast);
+
+struct VFile;
+struct mCoreRewindContext {
+	struct mCoreRewindPatches patchMemory;
+	size_t current;
+	size_t size;
+	struct VFile* previousState;
+	struct VFile* currentState;
+};
+
+void mCoreRewindContextInit(struct mCoreRewindContext*, size_t entries);
+void mCoreRewindContextDeinit(struct mCoreRewindContext*);
+
+struct mCore;
+void mCoreRewindAppend(struct mCoreRewindContext*, struct mCore*);
+bool mCoreRewindRestore(struct mCoreRewindContext*, struct mCore*);
+
+#endif
diff --git a/src/core/thread.c b/src/core/thread.c
index 177d94eed..27d5f835c 100644
--- a/src/core/thread.c
+++ b/src/core/thread.c
@@ -109,6 +109,10 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
 	core->setSync(core, &threadContext->sync);
 	core->reset(core);
 
+	if (core->opts.rewindEnable) {
+		 mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity);
+	}
+
 	_changeState(threadContext, THREAD_RUNNING, true);
 
 	if (threadContext->startCallback) {
@@ -126,14 +130,14 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
 				_changeState(threadContext, THREAD_EXITING, false);
 			}
 		} else {
-			while (threadContext->state == THREAD_RUNNING) {
+			while (threadContext->state <= THREAD_MAX_RUNNING) {
 				core->runLoop(core);
 			}
 		}
 
 		int resetScheduled = 0;
 		MutexLock(&threadContext->stateMutex);
-		while (threadContext->state > THREAD_RUNNING && threadContext->state < THREAD_EXITING) {
+		while (threadContext->state > THREAD_MAX_RUNNING && threadContext->state < THREAD_EXITING) {
 			if (threadContext->state == THREAD_PAUSING) {
 				threadContext->state = THREAD_PAUSED;
 				ConditionWake(&threadContext->stateCond);
@@ -170,6 +174,10 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
 		_changeState(threadContext, THREAD_SHUTDOWN, false);
 	}
 
+	if (core->opts.rewindEnable) {
+		 mCoreRewindContextDeinit(&threadContext->rewind);
+	}
+
 	if (threadContext->cleanCallback) {
 		threadContext->cleanCallback(threadContext);
 	}
@@ -422,6 +430,18 @@ void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
 	mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
 }
 
+void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
+	MutexLock(&threadContext->stateMutex);
+	_waitOnInterrupt(threadContext);
+	if (rewinding && threadContext->state == THREAD_RUNNING) {
+		threadContext->state = THREAD_REWINDING;
+	}
+	if (!rewinding && threadContext->state == THREAD_REWINDING) {
+		threadContext->state = THREAD_RUNNING;
+	}
+	MutexUnlock(&threadContext->stateMutex);
+}
+
 #ifdef USE_PTHREADS
 struct mCoreThread* mCoreThreadGet(void) {
 	pthread_once(&_contextOnce, _createTLS);
@@ -438,10 +458,41 @@ struct mCoreThread* mCoreThreadGet(void) {
 }
 #endif
 
+void mCoreThreadFrameStarted(struct mCoreThread* thread) {
+	if (!thread) {
+		return;
+	}
+	if (thread->core->opts.rewindEnable && thread->state != THREAD_REWINDING) {
+		mCoreRewindAppend(&thread->rewind, thread->core);
+	} else if (thread->state == THREAD_REWINDING) {
+		if (!mCoreRewindRestore(&thread->rewind, thread->core)) {
+			mCoreRewindAppend(&thread->rewind, thread->core);
+		}
+	}
+}
+
+void mCoreThreadFrameEnded(struct mCoreThread* thread) {
+	if (!thread) {
+		return;
+	}
+	if (thread->frameCallback) {
+		thread->frameCallback(thread);
+	}
+}
+
 #else
 struct mCoreThread* mCoreThreadGet(void) {
 	return NULL;
 }
+
+void mCoreThreadFrameStarted(struct mCoreThread* thread) {
+	UNUSED(thread);
+}
+
+void mCoreThreadFrameEnded(struct mCoreThread* thread) {
+	UNUSED(thread);
+}
+
 #endif
 
 static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
diff --git a/src/core/thread.h b/src/core/thread.h
index 9540c841b..c965ab8d2 100644
--- a/src/core/thread.h
+++ b/src/core/thread.h
@@ -9,6 +9,7 @@
 #include "util/common.h"
 
 #include "core/log.h"
+#include "core/rewind.h"
 #include "core/sync.h"
 #include "util/threading.h"
 
@@ -20,6 +21,8 @@ typedef void (*ThreadCallback)(struct mCoreThread* threadContext);
 enum mCoreThreadState {
 	THREAD_INITIALIZED = -1,
 	THREAD_RUNNING = 0,
+	THREAD_REWINDING,
+	THREAD_MAX_RUNNING = THREAD_REWINDING,
 	THREAD_INTERRUPTED,
 	THREAD_INTERRUPTING,
 	THREAD_PAUSED,
@@ -61,6 +64,7 @@ struct mCoreThread {
 	void (*run)(struct mCoreThread*);
 
 	struct mCoreSync sync;
+	struct mCoreRewindContext rewind;
 };
 
 bool mCoreThreadStart(struct mCoreThread* threadContext);
@@ -84,7 +88,13 @@ bool mCoreThreadIsPaused(struct mCoreThread* threadContext);
 void mCoreThreadTogglePause(struct mCoreThread* threadContext);
 void mCoreThreadPauseFromThread(struct mCoreThread* threadContext);
 
+void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool);
+
 struct mCoreThread* mCoreThreadGet(void);
+
+void mCoreThreadFrameStarted(struct mCoreThread*);
+void mCoreThreadFrameEnded(struct mCoreThread*);
+
 struct mLogger* mCoreThreadLogger(void);
 
 #endif
diff --git a/src/gb/video.c b/src/gb/video.c
index a38efce4d..91b857e22 100644
--- a/src/gb/video.c
+++ b/src/gb/video.c
@@ -175,6 +175,8 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
 				if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) {
 					video->p->memory.rotation->sample(video->p->memory.rotation);
 				}
+				struct mCoreThread* thread = mCoreThreadGet();
+				mCoreThreadFrameStarted(thread);
 				break;
 			case 2:
 				_cleanOAM(video, video->ly);
@@ -204,9 +206,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) {
 			if (video->p->cpu->executionState == LR35902_CORE_FETCH) {
 				GBFrameEnded(video->p);
 				struct mCoreThread* thread = mCoreThreadGet();
-				if (thread && thread->frameCallback) {
-					thread->frameCallback(thread);
-				}
+				mCoreThreadFrameEnded(thread);
 				video->nextFrame = GB_VIDEO_TOTAL_LENGTH;
 			} else {
 				video->nextFrame = 4 - ((video->p->cpu->executionState + 1) & 3);
diff --git a/src/gba/gba.c b/src/gba/gba.c
index ccdf28dd6..458f722f9 100644
--- a/src/gba/gba.c
+++ b/src/gba/gba.c
@@ -800,7 +800,8 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) {
 void GBAFrameStarted(struct GBA* gba) {
 	UNUSED(gba);
 
-	// TODO: Put back rewind
+	struct mCoreThread* thread = mCoreThreadGet();
+	mCoreThreadFrameStarted(thread);
 }
 
 void GBAFrameEnded(struct GBA* gba) {
@@ -831,13 +832,7 @@ void GBAFrameEnded(struct GBA* gba) {
 	}
 
 	struct mCoreThread* thread = mCoreThreadGet();
-	if (!thread) {
-		return;
-	}
-
-	if (thread->frameCallback) {
-		thread->frameCallback(thread);
-	}
+	mCoreThreadFrameEnded(thread);
 
 	// TODO: Put back RR
 }
diff --git a/src/gba/serialize.c b/src/gba/serialize.c
index a2dd3e0de..8130fd00c 100644
--- a/src/gba/serialize.c
+++ b/src/gba/serialize.c
@@ -225,5 +225,3 @@ struct GBASerializedState* GBAAllocateState(void) {
 void GBADeallocateState(struct GBASerializedState* state) {
 	mappedMemoryFree(state, sizeof(struct GBASerializedState));
 }
-
-// TODO: Put back rewind
diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp
index ee7beb890..62b209bfc 100644
--- a/src/platform/qt/ConfigController.cpp
+++ b/src/platform/qt/ConfigController.cpp
@@ -112,8 +112,7 @@ ConfigController::ConfigController(QObject* parent)
 	m_opts.volume = 0x100;
 	m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL;
 	m_opts.rewindEnable = false;
-	m_opts.rewindBufferInterval = 0;
-	m_opts.rewindBufferCapacity = 0;
+	m_opts.rewindBufferCapacity = 60;
 	m_opts.useBios = true;
 	m_opts.suspendScreensaver = true;
 	mCoreConfigLoad(&m_config);
diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp
index 4fc8764b4..7b48a84a9 100644
--- a/src/platform/qt/GameController.cpp
+++ b/src/platform/qt/GameController.cpp
@@ -234,13 +234,6 @@ GameController::GameController(QObject* parent)
 
 	m_threadContext.userData = this;
 
-	connect(&m_rewindTimer, &QTimer::timeout, [this]() {
-		// TODO: Put rewind back
-		emit frameAvailable(m_drawContext);
-		emit rewound(&m_threadContext);
-	});
-	m_rewindTimer.setInterval(100);
-
 	m_audioThread->setObjectName("Audio Thread");
 	m_audioThread->start(QThread::TimeCriticalPriority);
 	m_audioProcessor->moveToThread(m_audioThread);
@@ -529,7 +522,6 @@ void GameController::closeGame() {
 	}
 	m_gameOpen = false;
 
-	m_rewindTimer.stop();
 	if (mCoreThreadIsPaused(&m_threadContext)) {
 		mCoreThreadUnpause(&m_threadContext);
 	}
@@ -578,7 +570,7 @@ QSize GameController::screenDimensions() const {
 }
 
 void GameController::setPaused(bool paused) {
-	if (!isLoaded() || m_rewindTimer.isActive() || paused == mCoreThreadIsPaused(&m_threadContext)) {
+	if (!isLoaded() || paused == mCoreThreadIsPaused(&m_threadContext)) {
 		return;
 	}
 	if (paused) {
@@ -617,15 +609,12 @@ void GameController::threadContinue() {
 }
 
 void GameController::frameAdvance() {
-	if (m_rewindTimer.isActive()) {
-		return;
-	}
 	if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
 		setPaused(false);
 	}
 }
 
-void GameController::setRewind(bool enable, int capacity, int interval) {
+void GameController::setRewind(bool enable, int capacity) {
 	if (m_gameOpen) {
 		threadInterrupt();
 		// TODO: Put back rewind
@@ -638,9 +627,12 @@ void GameController::setRewind(bool enable, int capacity, int interval) {
 void GameController::rewind(int states) {
 	threadInterrupt();
 	if (!states) {
-		// TODO: Put back rewind
-	} else {
-		// TODO: Put back rewind
+		states = INT_MAX;
+	}
+	for (int i = 0; i < states; ++i) {
+		if (!mCoreRewindRestore(&m_threadContext.rewind, m_threadContext.core)) {
+			break;
+		}
 	}
 	threadContinue();
 	emit frameAvailable(m_drawContext);
@@ -648,24 +640,21 @@ void GameController::rewind(int states) {
 }
 
 void GameController::startRewinding() {
-	if (!m_gameOpen || m_rewindTimer.isActive()) {
+	if (!m_gameOpen) {
 		return;
 	}
 	if (m_multiplayer && m_multiplayer->attached() > 1) {
 		return;
 	}
-	m_wasPaused = isPaused();
-	if (!mCoreThreadIsPaused(&m_threadContext)) {
-		mCoreThreadPause(&m_threadContext);
+	m_wasPaused = mCoreThreadIsPaused(&m_threadContext);
+	if (m_wasPaused) {
+		mCoreThreadUnpause(&m_threadContext);
 	}
-	m_rewindTimer.start();
+	mCoreThreadSetRewinding(&m_threadContext, true);
 }
 
 void GameController::stopRewinding() {
-	if (!m_rewindTimer.isActive()) {
-		return;
-	}
-	m_rewindTimer.stop();
+	mCoreThreadSetRewinding(&m_threadContext, false);
 	bool signalsBlocked = blockSignals(true);
 	setPaused(m_wasPaused);
 	blockSignals(signalsBlocked);
diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h
index 3747fa895..e7a0638ef 100644
--- a/src/platform/qt/GameController.h
+++ b/src/platform/qt/GameController.h
@@ -116,7 +116,7 @@ public slots:
 	void setPaused(bool paused);
 	void reset();
 	void frameAdvance();
-	void setRewind(bool enable, int capacity, int interval);
+	void setRewind(bool enable, int capacity);
 	void rewind(int states = 0);
 	void startRewinding();
 	void stopRewinding();
@@ -202,7 +202,6 @@ private:
 	bool m_turbo;
 	bool m_turboForced;
 	float m_turboSpeed;
-	QTimer m_rewindTimer;
 	bool m_wasPaused;
 
 	bool m_audioChannels[6];
diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp
index 488e09bbc..ae5167512 100644
--- a/src/platform/qt/SettingsView.cpp
+++ b/src/platform/qt/SettingsView.cpp
@@ -165,14 +165,6 @@ void SettingsView::selectBios() {
 	}
 }
 
-void SettingsView::recalculateRewind() {
-	int interval = m_ui.rewindInterval->value();
-	int capacity = m_ui.rewindCapacity->value();
-	double duration = m_ui.fpsTarget->value();
-	m_ui.rewindDuration->setValue(interval * capacity / duration);
-
-}
-
 void SettingsView::updateConfig() {
 	saveSetting("bios", m_ui.bios);
 	saveSetting("useBios", m_ui.useBios);
@@ -187,7 +179,6 @@ void SettingsView::updateConfig() {
 	saveSetting("volume", m_ui.volume);
 	saveSetting("mute", m_ui.mute);
 	saveSetting("rewindEnable", m_ui.rewind);
-	saveSetting("rewindBufferInterval", m_ui.rewindInterval);
 	saveSetting("rewindBufferCapacity", m_ui.rewindCapacity);
 	saveSetting("resampleVideo", m_ui.resampleVideo);
 	saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
@@ -262,7 +253,6 @@ void SettingsView::reloadConfig() {
 	loadSetting("volume", m_ui.volume);
 	loadSetting("mute", m_ui.mute);
 	loadSetting("rewindEnable", m_ui.rewind);
-	loadSetting("rewindBufferInterval", m_ui.rewindInterval);
 	loadSetting("rewindBufferCapacity", m_ui.rewindCapacity);
 	loadSetting("resampleVideo", m_ui.resampleVideo);
 	loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
@@ -283,10 +273,6 @@ void SettingsView::reloadConfig() {
 		m_ui.fastForwardRatio->setValue(fastForwardRatio);
 	}
 
-	connect(m_ui.rewindInterval, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind()));
-	connect(m_ui.rewindCapacity, SIGNAL(valueChanged(int)), this, SLOT(recalculateRewind()));
-	connect(m_ui.fpsTarget, SIGNAL(valueChanged(double)), this, SLOT(recalculateRewind()));
-
 	QString idleOptimization = loadSetting("idleOptimization");
 	if (idleOptimization == "ignore") {
 		m_ui.idleOptimization->setCurrentIndex(0);
diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h
index 77d672bdf..352604b32 100644
--- a/src/platform/qt/SettingsView.h
+++ b/src/platform/qt/SettingsView.h
@@ -30,7 +30,6 @@ signals:
 
 private slots:
 	void selectBios();
-	void recalculateRewind();
 	void updateConfig();
 	void reloadConfig();
 
diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui
index 0b1b45333..804cfffc4 100644
--- a/src/platform/qt/SettingsView.ui
+++ b/src/platform/qt/SettingsView.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>544</width>
-    <height>425</height>
+    <width>548</width>
+    <height>431</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -72,7 +72,7 @@
    <item row="1" column="1">
     <widget class="QStackedWidget" name="stackedWidget">
      <property name="currentIndex">
-      <number>0</number>
+      <number>2</number>
      </property>
      <widget class="QWidget" name="stackedWidgetPage1">
       <layout class="QFormLayout" name="formLayout">
@@ -110,7 +110,7 @@
            <property name="editable">
             <bool>true</bool>
            </property>
-           <property name="currentText" stdset="0">
+           <property name="currentText">
             <string>1536</string>
            </property>
            <property name="currentIndex">
@@ -176,7 +176,7 @@
            <property name="editable">
             <bool>true</bool>
            </property>
-           <property name="currentText" stdset="0">
+           <property name="currentText">
             <string>44100</string>
            </property>
            <property name="currentIndex">
@@ -487,13 +487,6 @@
          </property>
         </widget>
        </item>
-       <item row="7" column="1">
-        <widget class="QCheckBox" name="allowOpposingDirections">
-         <property name="text">
-          <string>Allow opposing input directions</string>
-         </property>
-        </widget>
-       </item>
        <item row="8" column="1">
         <widget class="QCheckBox" name="suspendScreensaver">
          <property name="text">
@@ -537,6 +530,13 @@
          </item>
         </widget>
        </item>
+       <item row="7" column="1">
+        <widget class="QCheckBox" name="allowOpposingDirections">
+         <property name="text">
+          <string>Allow opposing input directions</string>
+         </property>
+        </widget>
+       </item>
       </layout>
      </widget>
      <widget class="QWidget" name="page_2">
@@ -634,70 +634,25 @@
         </widget>
        </item>
        <item row="10" column="0">
-        <widget class="QLabel" name="label_4">
-         <property name="text">
-          <string>Create rewind state:</string>
-         </property>
-        </widget>
-       </item>
-       <item row="10" column="1">
-        <layout class="QHBoxLayout" name="horizontalLayout_12">
-         <item>
-          <widget class="QLabel" name="label_5">
-           <property name="text">
-            <string>Every</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QSpinBox" name="rewindInterval"/>
-         </item>
-         <item>
-          <widget class="QLabel" name="label_6">
-           <property name="text">
-            <string>frames</string>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="11" column="0">
         <widget class="QLabel" name="label_8">
          <property name="text">
           <string>Rewind history:</string>
          </property>
         </widget>
        </item>
-       <item row="11" column="1">
+       <item row="10" column="1">
         <layout class="QHBoxLayout" name="horizontalLayout_13">
          <item>
-          <widget class="QSpinBox" name="rewindCapacity"/>
+          <widget class="QSpinBox" name="rewindCapacity">
+           <property name="maximum">
+            <number>3600</number>
+           </property>
+          </widget>
          </item>
          <item>
           <widget class="QLabel" name="label_7">
            <property name="text">
-            <string>states</string>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="12" column="1">
-        <layout class="QHBoxLayout" name="horizontalLayout_5">
-         <item>
-          <widget class="QDoubleSpinBox" name="rewindDuration">
-           <property name="enabled">
-            <bool>false</bool>
-           </property>
-           <property name="maximum">
-            <double>999.990000000000009</double>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QLabel" name="label_26">
-           <property name="text">
-            <string>seconds</string>
+            <string>frames</string>
            </property>
           </widget>
          </item>
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index 318f16b68..41ae3ce7e 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -1375,17 +1375,12 @@ void Window::setupMenu(QMenuBar* menubar) {
 
 	ConfigOption* rewindEnable = m_config->addOption("rewindEnable");
 	rewindEnable->connect([this](const QVariant& value) {
-		m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt());
+		m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt());
 	}, this);
 
 	ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity");
 	rewindBufferCapacity->connect([this](const QVariant& value) {
-		m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt());
-	}, this);
-
-	ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval");
-	rewindBufferInterval->connect([this](const QVariant& value) {
-		m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt());
+		m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt());
 	}, this);
 
 	ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections");
diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c
index 83a4c51a2..0e89c368f 100644
--- a/src/platform/sdl/main.c
+++ b/src/platform/sdl/main.c
@@ -51,6 +51,7 @@ int main(int argc, char** argv) {
 	struct mCoreOptions opts = {
 		.useBios = true,
 		.rewindEnable = true,
+		.rewindBufferCapacity = 600,
 		.audioBuffers = 512,
 		.videoSync = false,
 		.audioSync = true,
diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c
index 651ccc885..224ccf5f5 100644
--- a/src/platform/sdl/sdl-events.c
+++ b/src/platform/sdl/sdl-events.c
@@ -406,6 +406,9 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
 		context->sync.audioWait = event->type != SDL_KEYDOWN;
 		return;
 	}
+	if (event->keysym.sym == SDLK_BACKQUOTE) {
+		mCoreThreadSetRewinding(context, event->type == SDL_KEYDOWN);
+	}
 	if (event->type == SDL_KEYDOWN) {
 		switch (event->keysym.sym) {
 		case SDLK_F11:
@@ -423,9 +426,6 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
 			context->frameCallback = _pauseAfterFrame;
 			mCoreThreadUnpause(context);
 			return;
-		case SDLK_BACKQUOTE:
-			// TODO: Put back rewind
-			return;
 #ifdef BUILD_PANDORA
 		case SDLK_ESCAPE:
 			mCoreThreadEnd(context);