diff --git a/Core/APU.h b/Core/APU.h index 6275d28e..cdefe9db 100644 --- a/Core/APU.h +++ b/Core/APU.h @@ -42,7 +42,6 @@ class APU : public Snapshotable, public IMemoryHandler private: __forceinline bool NeedToRun(uint32_t currentCycle); void Run(); - void EndFrame(); static void FrameCounterTick(FrameType type); @@ -53,6 +52,8 @@ class APU : public Snapshotable, public IMemoryHandler APU(MemoryManager* memoryManager); ~APU(); + void EndFrame(); + void Reset(bool softReset); void SetNesModel(NesModel model, bool forceInit = false); diff --git a/Core/AviRecorder.cpp b/Core/AviRecorder.cpp index ec16f9f7..8bb23573 100644 --- a/Core/AviRecorder.cpp +++ b/Core/AviRecorder.cpp @@ -28,6 +28,8 @@ bool AviRecorder::StartRecording(string filename, VideoCodec codec, uint32_t wid if(!_recording) { _outputFile = filename; _sampleRate = audioSampleRate; + _width = width; + _height = height; _frameBufferLength = height * width * bpp; _frameBuffer = new uint8_t[_frameBufferLength]; @@ -71,12 +73,16 @@ void AviRecorder::StopRecording() } } -void AviRecorder::AddFrame(void* frameBuffer) +void AviRecorder::AddFrame(void* frameBuffer, uint32_t width, uint32_t height) { if(_recording) { - auto lock = _lock.AcquireSafe(); - memcpy(_frameBuffer, frameBuffer, _frameBufferLength); - _waitFrame.Signal(); + if(_width != width || _height != height) { + StopRecording(); + } else { + auto lock = _lock.AcquireSafe(); + memcpy(_frameBuffer, frameBuffer, _frameBufferLength); + _waitFrame.Signal(); + } } } diff --git a/Core/AviRecorder.h b/Core/AviRecorder.h index c61cec14..e9d4e786 100644 --- a/Core/AviRecorder.h +++ b/Core/AviRecorder.h @@ -22,6 +22,9 @@ private: uint32_t _frameBufferLength; uint32_t _sampleRate; + uint32_t _width; + uint32_t _height; + public: AviRecorder(); virtual ~AviRecorder(); @@ -29,9 +32,8 @@ public: bool StartRecording(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t fps, uint32_t audioSampleRate, uint32_t compressionLevel); void StopRecording(); - void AddFrame(void* frameBuffer); + void AddFrame(void* frameBuffer, uint32_t width, uint32_t height); void AddSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); - void SendAudio(); bool IsRecording(); }; \ No newline at end of file diff --git a/Core/BaseControlDevice.cpp b/Core/BaseControlDevice.cpp index 77d42e8c..4cd145e2 100644 --- a/Core/BaseControlDevice.cpp +++ b/Core/BaseControlDevice.cpp @@ -6,6 +6,7 @@ #include "GameClient.h" #include "GameServerConnection.h" #include "AutomaticRomTest.h" +#include "RewindManager.h" BaseControlDevice::BaseControlDevice(uint8_t port) { @@ -60,7 +61,9 @@ uint8_t BaseControlDevice::ProcessNetPlayState(uint32_t netplayState) uint8_t BaseControlDevice::GetControlState() { GameServerConnection* netPlayDevice = GameServerConnection::GetNetPlayDevice(_port); - if(MovieManager::Playing()) { + if(RewindManager::IsRewinding()) { + _currentState = RewindManager::GetInput(_port); + } else if(MovieManager::Playing()) { _currentState = MovieManager::GetState(_port); } else if(GameClient::Connected()) { _currentState = GameClient::GetControllerState(_port); @@ -79,5 +82,7 @@ uint8_t BaseControlDevice::GetControlState() //For NetPlay ControlManager::BroadcastInput(_port, _currentState); + RewindManager::RecordInput(_port, _currentState); + return _currentState; } \ No newline at end of file diff --git a/Core/CPU.cpp b/Core/CPU.cpp index 7656655c..9c92ef81 100644 --- a/Core/CPU.cpp +++ b/Core/CPU.cpp @@ -152,6 +152,30 @@ void CPU::IRQ() } } +void CPU::BRK() { + Push((uint16_t)(PC() + 1)); + + uint8_t flags = PS() | PSFlags::Break; + if(_state.NMIFlag) { + Push((uint8_t)flags); + SetFlags(PSFlags::Interrupt); + + SetPC(MemoryReadWord(CPU::NMIVector)); + + TraceLogger::LogStatic("NMI"); + } else { + Push((uint8_t)flags); + SetFlags(PSFlags::Interrupt); + + SetPC(MemoryReadWord(CPU::IRQVector)); + + TraceLogger::LogStatic("IRQ"); + } + + //Since we just set the flag to prevent interrupts, do not run one right away after this (fixes nmi_and_brk & nmi_and_irq tests) + _prevRunIrq = false; +} + uint16_t CPU::FetchOperand() { switch(_instAddrMode) { diff --git a/Core/CPU.h b/Core/CPU.h index 15e4b00f..7e09adae 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -3,7 +3,6 @@ #include "stdafx.h" #include "MemoryManager.h" #include "Snapshotable.h" -#include "TraceLogger.h" #include "EmulationSettings.h" #include "Types.h" @@ -600,29 +599,7 @@ private: void SED() { SetFlags(PSFlags::Decimal); } void SEI() { SetFlags(PSFlags::Interrupt); } - void BRK() { - Push((uint16_t)(PC() + 1)); - - uint8_t flags = PS() | PSFlags::Break; - if(_state.NMIFlag) { - Push((uint8_t)flags); - SetFlags(PSFlags::Interrupt); - - SetPC(MemoryReadWord(CPU::NMIVector)); - - TraceLogger::LogStatic("NMI"); - } else { - Push((uint8_t)flags); - SetFlags(PSFlags::Interrupt); - - SetPC(MemoryReadWord(CPU::IRQVector)); - - TraceLogger::LogStatic("IRQ"); - } - - //Since we just set the flag to prevent interrupts, do not run one right away after this (fixes nmi_and_brk & nmi_and_irq tests) - _prevRunIrq = false; - } + void BRK(); void RTI() { DummyRead(); diff --git a/Core/Console.cpp b/Core/Console.cpp index 8e143508..88acf187 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -18,6 +18,7 @@ #include "NsfMapper.h" #include "ShortcutKeyHandler.h" #include "MovieManager.h" +#include "RewindManager.h" shared_ptr Console::Instance(new Console()); @@ -95,6 +96,8 @@ bool Console::Initialize(string romFilename, stringstream *filestream, string pa ResetComponents(false); + _rewindManager.reset(new RewindManager()); + VideoDecoder::GetInstance()->StartThread(); FolderUtilities::AddKnownGameFolder(FolderUtilities::GetFolderName(romFilename)); @@ -337,10 +340,11 @@ void Console::Run() uint32_t currentFrameNumber = PPU::GetFrameCount(); if(currentFrameNumber != lastFrameNumber) { + _rewindManager->ProcessEndOfFrame(); EmulationSettings::DisableOverclocking(_disableOcNextFrame); _disableOcNextFrame = false; - lastFrameNumber = currentFrameNumber; + lastFrameNumber = PPU::GetFrameCount(); //Sleep until we're ready to start the next frame clockTimer.WaitUntil(targetTime); @@ -402,6 +406,7 @@ void Console::Run() } } } + _rewindManager.reset(); SoundMixer::StopAudio(); MovieManager::Stop(); SoundMixer::StopRecording(); @@ -509,6 +514,9 @@ void Console::LoadState(istream &loadStream) void Console::LoadState(uint8_t *buffer, uint32_t bufferSize) { + //Send any unprocessed sound to the SoundMixer - needed for rewind + Instance->_apu->EndFrame(); + stringstream stream; stream.write((char*)buffer, bufferSize); stream.seekg(0, ios::beg); diff --git a/Core/Console.h b/Core/Console.h index 1509d1ee..80f10e8b 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -13,6 +13,7 @@ class Debugger; class BaseMapper; +class RewindManager; class Console { @@ -22,6 +23,7 @@ class Console SimpleLock _runLock; SimpleLock _stopLock; + shared_ptr _rewindManager; shared_ptr _cpu; shared_ptr _ppu; unique_ptr _apu; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 43274c83..a7391386 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -449,6 +449,8 @@ + + @@ -807,6 +809,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 035de055..1085cacf 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -89,6 +89,9 @@ {16c0726b-0c54-4fbc-a602-7930348d7f44} + + {52b03b24-dd62-4daf-bac8-bb60a555d3d2} + @@ -1162,6 +1165,12 @@ Movies + + Rewinder + + + Rewinder + @@ -1377,5 +1386,11 @@ Movies + + Rewinder + + + Rewinder + \ No newline at end of file diff --git a/Core/EmulationSettings.cpp b/Core/EmulationSettings.cpp index d301a0fd..b008792a 100644 --- a/Core/EmulationSettings.cpp +++ b/Core/EmulationSettings.cpp @@ -33,6 +33,9 @@ PpuModel EmulationSettings::_ppuModel = PpuModel::Ppu2C02; uint32_t EmulationSettings::_emulationSpeed = 100; uint32_t EmulationSettings::_turboSpeed = 300; +uint32_t EmulationSettings::_rewindSpeed = 100; + +uint32_t EmulationSettings::_rewindBufferSize = 300; bool EmulationSettings::_hasOverclock = false; uint32_t EmulationSettings::_overclockRate = 100; diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index ab92a9ad..51e29190 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -242,6 +242,10 @@ struct KeyMappingSet struct EmulatorKeyMappings { uint32_t FastForward; + uint32_t Rewind; + uint32_t RewindTenSecs; + uint32_t RewindOneMin; + uint32_t Pause; uint32_t Reset; uint32_t Exit; @@ -356,6 +360,9 @@ private: static uint32_t _emulationSpeed; static uint32_t _turboSpeed; + static uint32_t _rewindSpeed; + + static uint32_t _rewindBufferSize; static bool _hasOverclock; static uint32_t _overclockRate; @@ -641,9 +648,25 @@ public: } } - static void SetTurboSpeed(uint32_t turboSpeed) + static void SetTurboRewindSpeed(uint32_t turboSpeed, uint32_t rewindSpeed) { _turboSpeed = turboSpeed; + _rewindSpeed = rewindSpeed; + } + + static uint32_t GetRewindSpeed() + { + return _rewindSpeed; + } + + static void SetRewindBufferSize(uint32_t seconds) + { + _rewindBufferSize = seconds; + } + + static uint32_t GetRewindBufferSize() + { + return _rewindBufferSize; } static uint32_t GetEmulationSpeed(bool ignoreTurbo = false) diff --git a/Core/HdPpu.h b/Core/HdPpu.h index 913abb35..8e9a0a77 100644 --- a/Core/HdPpu.h +++ b/Core/HdPpu.h @@ -3,6 +3,7 @@ #include "PPU.h" #include "HdNesPack.h" #include "VideoDecoder.h" +#include "RewindManager.h" class HdPpu : public PPU { @@ -86,7 +87,13 @@ public: void SendFrame() { - VideoDecoder::GetInstance()->UpdateFrame(_currentOutputBuffer, _screenTiles); + MessageManager::SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer); + + if(RewindManager::IsRewinding()) { + VideoDecoder::GetInstance()->UpdateFrameSync(_currentOutputBuffer, _screenTiles); + } else { + VideoDecoder::GetInstance()->UpdateFrame(_currentOutputBuffer, _screenTiles); + } _currentOutputBuffer = (_currentOutputBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0]; _screenTiles = (_screenTiles == _screenTileBuffers[0]) ? _screenTileBuffers[1] : _screenTileBuffers[0]; diff --git a/Core/IMessageManager.h b/Core/IMessageManager.h index f5d87fc8..59988d38 100644 --- a/Core/IMessageManager.h +++ b/Core/IMessageManager.h @@ -16,63 +16,23 @@ class ToastInfo private: string _title; string _message; - uint8_t* _icon; - uint32_t _iconSize; uint64_t _endTime; uint64_t _startTime; - uint8_t* ReadFile(string filename, uint32_t &fileSize) - { - ifstream file(filename, ios::in | ios::binary); - if(file) { - file.seekg(0, ios::end); - fileSize = (uint32_t)file.tellg(); - file.seekg(0, ios::beg); - - uint8_t* buffer = new uint8_t[fileSize]; - file.read((char*)buffer, fileSize); - return buffer; - } - return nullptr; - } - uint64_t GetCurrentTime() { return std::chrono::duration_cast(std::chrono::high_resolution_clock::now().time_since_epoch()).count(); } public: - ToastInfo(string title, string message, int displayDuration, string iconFile) + ToastInfo(string title, string message, int displayDuration) { _title = title; _message = message; - - _icon = ReadFile(iconFile, _iconSize); - _startTime = GetCurrentTime(); _endTime = _startTime + displayDuration; } - ToastInfo(string title, string message, int displayDuration, uint8_t* iconData, uint32_t iconSize) - { - _title = title; - _message = message; - - _iconSize = iconSize; - _icon = new uint8_t[iconSize]; - memcpy(_icon, iconData, iconSize); - - _startTime = GetCurrentTime(); - _endTime = _startTime + displayDuration; - } - - ~ToastInfo() - { - if(_icon) { - delete _icon; - } - } - string GetToastTitle() { return _title; @@ -83,21 +43,6 @@ public: return _message; } - uint8_t* GetToastIcon() - { - return _icon; - } - - uint32_t GetIconSize() - { - return _iconSize; - } - - bool HasIcon() - { - return _iconSize > 0; - } - float GetOpacity() { uint64_t currentTime = GetCurrentTime(); diff --git a/Core/PPU.cpp b/Core/PPU.cpp index f9dd0af8..8b31c986 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -5,6 +5,7 @@ #include "VideoDecoder.h" #include "Debugger.h" #include "BaseMapper.h" +#include "RewindManager.h" PPU* PPU::Instance = nullptr; @@ -968,7 +969,11 @@ void PPU::SendFrame() MessageManager::SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer); - VideoDecoder::GetInstance()->UpdateFrame(_currentOutputBuffer); + if(RewindManager::IsRewinding()) { + VideoDecoder::GetInstance()->UpdateFrameSync(_currentOutputBuffer); + } else { + VideoDecoder::GetInstance()->UpdateFrame(_currentOutputBuffer); + } //Switch output buffer. VideoDecoder will decode the last frame while we build the new one. //If VideoDecoder isn't fast enough, UpdateFrame will block until it is ready to accept a new frame. diff --git a/Core/RewindData.cpp b/Core/RewindData.cpp new file mode 100644 index 00000000..3152a837 --- /dev/null +++ b/Core/RewindData.cpp @@ -0,0 +1,35 @@ +#include "stdafx.h" +#include "RewindData.h" +#include "Console.h" +#include "../Utilities/miniz.h" + +void RewindData::LoadState() +{ + unsigned long length = OriginalSaveStateSize; + uint8_t* buffer = new uint8_t[length]; + uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size()); + Console::LoadState(buffer, length); + delete[] buffer; +} + +void RewindData::CompressState(string stateData, vector& compressedState) +{ + unsigned long compressedSize = compressBound((unsigned long)stateData.size()); + uint8_t* compressedData = new uint8_t[compressedSize]; + compress(compressedData, &compressedSize, (unsigned char*)stateData.c_str(), (unsigned long)stateData.size()); + compressedState = vector(compressedData, compressedData + compressedSize); + delete[] compressedData; +} + +void RewindData::SaveState() +{ + std::stringstream state; + Console::SaveState(state); + + string stateData = state.str(); + vector compressedState; + CompressState(stateData, compressedState); + SaveStateData = compressedState; + OriginalSaveStateSize = (uint32_t)stateData.size(); + FrameCount = 0; +} diff --git a/Core/RewindData.h b/Core/RewindData.h new file mode 100644 index 00000000..e372c863 --- /dev/null +++ b/Core/RewindData.h @@ -0,0 +1,19 @@ +#pragma once +#include "stdafx.h" +#include + +class RewindData +{ +private: + vector SaveStateData; + uint32_t OriginalSaveStateSize; + + void CompressState(string stateData, vector &compressedState); + +public: + std::deque InputLogs[4]; + int32_t FrameCount = 0; + + void LoadState(); + void SaveState(); +}; diff --git a/Core/RewindManager.cpp b/Core/RewindManager.cpp new file mode 100644 index 00000000..39a7a621 --- /dev/null +++ b/Core/RewindManager.cpp @@ -0,0 +1,285 @@ +#include "stdafx.h" +#include "RewindManager.h" +#include "MessageManager.h" +#include "Console.h" +#include "VideoRenderer.h" +#include "SoundMixer.h" + +RewindManager* RewindManager::_instance = nullptr; + +RewindManager::RewindManager() +{ + _instance = this; + _rewindState = RewindState::Stopped; + _framesToFastForward = 0; + AddHistoryBlock(); + + MessageManager::RegisterNotificationListener(this); +} + +RewindManager::~RewindManager() +{ + if(_instance == this) { + _instance = nullptr; + } + MessageManager::UnregisterNotificationListener(this); +} + +void RewindManager::ProcessNotification(ConsoleNotificationType type, void * parameter) +{ + if(type == ConsoleNotificationType::PpuFrameDone) { + if(_rewindState >= RewindState::Starting) { + _currentHistory.FrameCount--; + } else if(_rewindState == RewindState::Stopping) { + _framesToFastForward--; + _currentHistory.FrameCount++; + if(_framesToFastForward == 0) { + for(int i = 0; i < 4; i++) { + size_t numberToRemove = _currentHistory.InputLogs[i].size(); + _currentHistory.InputLogs[i] = _historyBackup.front().InputLogs[i]; + for(size_t j = 0; j < numberToRemove; j++) { + _currentHistory.InputLogs[i].pop_back(); + } + } + _historyBackup.clear(); + _rewindState = RewindState::Stopped; + EmulationSettings::SetEmulationSpeed(100); + } + } else { + _currentHistory.FrameCount++; + } + } +} + +void RewindManager::AddHistoryBlock() +{ + uint32_t maxHistorySize = EmulationSettings::GetRewindBufferSize() * 120; + if(maxHistorySize == 0) { + _history.clear(); + } else { + while(_history.size() > maxHistorySize) { + _history.pop_front(); + } + + if(_currentHistory.FrameCount > 0) { + _history.push_back(_currentHistory); + } + _currentHistory = RewindData(); + _currentHistory.SaveState(); + } +} + +void RewindManager::PopHistory() +{ + if(_history.empty() && _currentHistory.FrameCount <= 0) { + StopRewinding(); + } else { + if(_currentHistory.FrameCount <= 0) { + _currentHistory = _history.back(); + _history.pop_back(); + } + + _historyBackup.push_front(_currentHistory); + _currentHistory.LoadState(); + if(!_audioHistoryBuilder.empty()) { + _audioHistory.insert(_audioHistory.begin(), _audioHistoryBuilder.begin(), _audioHistoryBuilder.end()); + _audioHistoryBuilder.clear(); + } + } +} + +void RewindManager::Start() +{ + if(_rewindState == RewindState::Stopped && EmulationSettings::GetRewindBufferSize() > 0) { + Console::Pause(); + + _rewindState = RewindState::Starting; + _videoHistoryBuilder.clear(); + _videoHistory.clear(); + _audioHistoryBuilder.clear(); + _audioHistory.clear(); + _historyBackup.clear(); + + PopHistory(); + SoundMixer::StopAudio(true); + EmulationSettings::SetEmulationSpeed(0); + + Console::Resume(); + } +} + +void RewindManager::Stop() +{ + if(_rewindState >= RewindState::Starting) { + Console::Pause(); + if(_rewindState == RewindState::Started) { + //Move back to the save state containing the frame currently shown on the screen + _framesToFastForward = (uint32_t)_videoHistory.size() + _historyBackup.front().FrameCount; + do { + _history.push_back(_historyBackup.front()); + _framesToFastForward -= _historyBackup.front().FrameCount; + _historyBackup.pop_front(); + + _currentHistory = _historyBackup.front(); + } + while(_framesToFastForward > RewindManager::BufferSize); + } else { + //We started rewinding, but didn't actually visually rewind anything yet + //Move back to the save state containing the frame currently shown on the screen + while(_historyBackup.size() > 1) { + _history.push_back(_historyBackup.front()); + _historyBackup.pop_front(); + } + _currentHistory = _historyBackup.front(); + _framesToFastForward = _historyBackup.front().FrameCount; + } + + _currentHistory.LoadState(); + if(_framesToFastForward > 0) { + _rewindState = RewindState::Stopping; + _currentHistory.FrameCount = 0; + EmulationSettings::SetEmulationSpeed(0); + } else { + _rewindState = RewindState::Stopped; + _historyBackup.clear(); + EmulationSettings::SetEmulationSpeed(100); + } + + _videoHistoryBuilder.clear(); + _videoHistory.clear(); + _audioHistoryBuilder.clear(); + _audioHistory.clear(); + + Console::Resume(); + } +} + +void RewindManager::ProcessEndOfFrame() +{ + if(_rewindState >= RewindState::Starting) { + if(_currentHistory.FrameCount <= 0) { + PopHistory(); + } + } else if(_currentHistory.FrameCount >= RewindManager::BufferSize) { + AddHistoryBlock(); + } +} + +void RewindManager::ProcessFrame(void * frameBuffer, uint32_t width, uint32_t height) +{ + if(_rewindState >= RewindState::Starting) { + _videoHistoryBuilder.push_back(vector((uint32_t*)frameBuffer, (uint32_t*)frameBuffer + width*height)); + + if(_videoHistoryBuilder.size() == _historyBackup.front().FrameCount) { + for(int i = (int)_videoHistoryBuilder.size() - 1; i >= 0; i--) { + _videoHistory.push_front(_videoHistoryBuilder[i]); + } + _videoHistoryBuilder.clear(); + } + + if(_rewindState == RewindState::Started || _videoHistory.size() >= RewindManager::BufferSize) { + _rewindState = RewindState::Started; + EmulationSettings::SetEmulationSpeed(EmulationSettings::GetRewindSpeed()); + if(!_videoHistory.empty()) { + VideoRenderer::GetInstance()->UpdateFrame(_videoHistory.back().data(), width, height); + _videoHistory.pop_back(); + } + } + } else if(_rewindState == RewindState::Stopping) { + //Display nothing while resyncing + } else { + VideoRenderer::GetInstance()->UpdateFrame(frameBuffer, width, height); + } +} + +bool RewindManager::ProcessAudio(int16_t * soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + if(_rewindState >= RewindState::Starting) { + _audioHistoryBuilder.insert(_audioHistoryBuilder.end(), soundBuffer, soundBuffer + sampleCount * 2); + + if(_rewindState == RewindState::Started && _audioHistory.size() > sampleCount * 2) { + for(uint32_t i = 0; i < sampleCount * 2; i++) { + soundBuffer[i] = _audioHistory.back(); + _audioHistory.pop_back(); + } + + return true; + } else { + //Mute while we prepare to rewind + return false; + } + } else if(_rewindState == RewindState::Stopping) { + //Mute while we resync + return false; + } else { + return true; + } +} + +void RewindManager::RecordInput(uint8_t port, uint8_t input) +{ + if(_instance && _instance->_rewindState == RewindState::Stopped) { + _instance->_currentHistory.InputLogs[port].push_back(input); + } +} + +uint8_t RewindManager::GetInput(uint8_t port) +{ + uint8_t value = _instance->_currentHistory.InputLogs[port].front(); + _instance->_currentHistory.InputLogs[port].pop_front(); + return value; +} + +void RewindManager::StartRewinding() +{ + if(_instance) { + _instance->Start(); + } +} + +void RewindManager::StopRewinding() +{ + if(_instance) { + _instance->Stop(); + } +} + +bool RewindManager::IsRewinding() +{ + return _instance ? _instance->_rewindState != RewindState::Stopped : false; +} + +void RewindManager::RewindSeconds(uint32_t seconds) +{ + if(_instance && _instance->_rewindState == RewindState::Stopped) { + uint32_t removeCount = (seconds * 60 / RewindManager::BufferSize) + 1; + Console::Pause(); + for(uint32_t i = 0; i < removeCount; i++) { + if(!_instance->_history.empty()) { + _instance->_currentHistory = _instance->_history.back(); + _instance->_history.pop_back(); + } else { + break; + } + } + _instance->_currentHistory.LoadState(); + Console::Resume(); + } +} + +void RewindManager::SendFrame(void * frameBuffer, uint32_t width, uint32_t height) +{ + if(_instance) { + _instance->ProcessFrame(frameBuffer, width, height); + } else { + VideoRenderer::GetInstance()->UpdateFrame(frameBuffer, width, height); + } +} + +bool RewindManager::SendAudio(int16_t * soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + if(_instance) { + return _instance->ProcessAudio(soundBuffer, sampleCount, sampleRate); + } + return true; +} diff --git a/Core/RewindManager.h b/Core/RewindManager.h new file mode 100644 index 00000000..b773710c --- /dev/null +++ b/Core/RewindManager.h @@ -0,0 +1,58 @@ +#pragma once +#include "stdafx.h" +#include +#include "INotificationListener.h" +#include "RewindData.h" + +enum class RewindState +{ + Stopped = 0, + Stopping = 1, + Starting = 2, + Started = 3 +}; + +class RewindManager : public INotificationListener +{ +private: + static const uint32_t BufferSize = 30; //Number of frames between each save state + static RewindManager* _instance; + std::deque _history; + std::deque _historyBackup; + RewindData _currentHistory; + + RewindState _rewindState; + int32_t _framesToFastForward; + + std::deque> _videoHistory; + vector> _videoHistoryBuilder; + std::deque _audioHistory; + vector _audioHistoryBuilder; + + void AddHistoryBlock(); + void PopHistory(); + + void Start(); + void Stop(); + + void ProcessFrame(void *frameBuffer, uint32_t width, uint32_t height); + bool ProcessAudio(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + +public: + RewindManager(); + ~RewindManager(); + + void ProcessNotification(ConsoleNotificationType type, void* parameter) override; + void ProcessEndOfFrame(); + + static void RecordInput(uint8_t port, uint8_t input); + static uint8_t GetInput(uint8_t port); + + static void StartRewinding(); + static void StopRewinding(); + static bool IsRewinding(); + static void RewindSeconds(uint32_t seconds); + + static void SendFrame(void *frameBuffer, uint32_t width, uint32_t height); + static bool SendAudio(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate); +}; \ No newline at end of file diff --git a/Core/ShortcutKeyHandler.cpp b/Core/ShortcutKeyHandler.cpp index 28bb38f4..17a0d62d 100644 --- a/Core/ShortcutKeyHandler.cpp +++ b/Core/ShortcutKeyHandler.cpp @@ -6,6 +6,7 @@ #include "VsControlManager.h" #include "FDS.h" #include "SaveStateManager.h" +#include "RewindManager.h" ShortcutKeyHandler::ShortcutKeyHandler() { @@ -48,6 +49,9 @@ bool ShortcutKeyHandler::DetectKeyRelease(uint32_t keyCode) void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) { + bool isNetplayClient = GameClient::Connected(); + bool isMovieActive = MovieManager::Playing() || MovieManager::Recording(); + if(DetectKeyPress(mappings.FastForward)) { EmulationSettings::SetFlags(EmulationFlags::Turbo); } else if(DetectKeyRelease(mappings.FastForward)) { @@ -66,7 +70,7 @@ void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) VideoDecoder::GetInstance()->TakeScreenshot(); } - if(VsControlManager::GetInstance()) { + if(VsControlManager::GetInstance() && !isNetplayClient && !isMovieActive) { VsControlManager* manager = VsControlManager::GetInstance(); if(DetectKeyPress(mappings.InsertCoin1)) { manager->InsertCoin(0); @@ -82,11 +86,11 @@ void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) } } - if(DetectKeyPress(mappings.SwitchDiskSide)) { + if(DetectKeyPress(mappings.SwitchDiskSide) && !isNetplayClient && !isMovieActive) { FDS::SwitchDiskSide(); } - if(DetectKeyPress(mappings.InsertNextDisk)) { + if(DetectKeyPress(mappings.InsertNextDisk) && !isNetplayClient && !isMovieActive) { FDS::InsertNextDisk(); } @@ -102,15 +106,15 @@ void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) SaveStateManager::SaveState(); } - if(DetectKeyPress(mappings.LoadState)) { + if(DetectKeyPress(mappings.LoadState) && !isNetplayClient) { SaveStateManager::LoadState(); } - if(DetectKeyPress(mappings.Reset)) { + if(DetectKeyPress(mappings.Reset) && !isNetplayClient && !isMovieActive) { Console::Reset(true); } - if(DetectKeyPress(mappings.Pause)) { + if(DetectKeyPress(mappings.Pause) && !isNetplayClient) { if(EmulationSettings::CheckFlag(EmulationFlags::Paused)) { EmulationSettings::ClearFlags(EmulationFlags::Paused); } else { @@ -122,7 +126,7 @@ void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) MessageManager::SendNotification(ConsoleNotificationType::RequestExit); } - if(DetectKeyPress(mappings.ToggleCheats)) { + if(DetectKeyPress(mappings.ToggleCheats) && !isNetplayClient && !isMovieActive) { MessageManager::SendNotification(ConsoleNotificationType::ToggleCheats); } @@ -137,6 +141,18 @@ void ShortcutKeyHandler::CheckMappedKeys(EmulatorKeyMappings mappings) EmulationSettings::SetFlags(EmulationFlags::Paused); } } + + if(!isNetplayClient && !isMovieActive && !EmulationSettings::CheckFlag(NsfPlayerEnabled)) { + if(DetectKeyPress(mappings.Rewind)) { + RewindManager::StartRewinding(); + } else if(DetectKeyRelease(mappings.Rewind)) { + RewindManager::StopRewinding(); + } else if(DetectKeyPress(mappings.RewindTenSecs)) { + RewindManager::RewindSeconds(10); + } else if(DetectKeyPress(mappings.RewindOneMin)) { + RewindManager::RewindSeconds(60); + } + } } void ShortcutKeyHandler::ProcessKeys(EmulatorKeyMappingSet mappings) diff --git a/Core/Snapshotable.cpp b/Core/Snapshotable.cpp index 95eaf585..77af826f 100644 --- a/Core/Snapshotable.cpp +++ b/Core/Snapshotable.cpp @@ -65,7 +65,6 @@ void Snapshotable::Stream(Snapshotable* snapshotable) void Snapshotable::SaveSnapshot(ostream* file) { _stream = new uint8_t[0xFFFFF]; - memset((char*)_stream, 0, 0xFFFFF); _position = 0; _saving = true; diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index de6cb0b0..841048e2 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -1,9 +1,10 @@ #include "stdafx.h" +#include "../Utilities/orfanidis_eq.h" #include "SoundMixer.h" #include "APU.h" #include "CPU.h" -#include "VideoDecoder.h" -#include "../Utilities/orfanidis_eq.h" +#include "VideoRenderer.h" +#include "RewindManager.h" IAudioDevice* SoundMixer::AudioDevice = nullptr; unique_ptr SoundMixer::_waveRecorder; @@ -102,7 +103,7 @@ void SoundMixer::PlayAudioBuffer(uint32_t time) } //Apply low pass filter/volume reduction when in background (based on options) - if(!VideoDecoder::GetInstance()->IsRecording() && !_waveRecorder && !EmulationSettings::CheckFlag(EmulationFlags::NsfPlayerEnabled) && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { + if(!VideoRenderer::GetInstance()->IsRecording() && !_waveRecorder && !EmulationSettings::CheckFlag(EmulationFlags::NsfPlayerEnabled) && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { if(EmulationSettings::CheckFlag(EmulationFlags::MuteSoundInBackground)) { _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 0, 0); } else if(EmulationSettings::CheckFlag(EmulationFlags::ReduceSoundInBackground)) { @@ -125,20 +126,22 @@ void SoundMixer::PlayAudioBuffer(uint32_t time) _crossFeedFilter.ApplyFilter(_outputBuffer, sampleCount, EmulationSettings::GetCrossFeedRatio()); } - if(SoundMixer::AudioDevice && !EmulationSettings::IsPaused()) { - SoundMixer::AudioDevice->PlayBuffer(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true); - } - - if(_waveRecorder) { - auto lock = _waveRecorderLock.AcquireSafe(); + if(RewindManager::SendAudio(_outputBuffer, (uint32_t)sampleCount, _sampleRate)) { if(_waveRecorder) { - if(!_waveRecorder->WriteSamples(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true)) { - _waveRecorder.reset(); + auto lock = _waveRecorderLock.AcquireSafe(); + if(_waveRecorder) { + if(!_waveRecorder->WriteSamples(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true)) { + _waveRecorder.reset(); + } } } - } - VideoDecoder::GetInstance()->AddRecordingSound(_outputBuffer, (uint32_t)sampleCount, _sampleRate); + VideoRenderer::GetInstance()->AddRecordingSound(_outputBuffer, (uint32_t)sampleCount, _sampleRate); + + if(SoundMixer::AudioDevice && !EmulationSettings::IsPaused()) { + SoundMixer::AudioDevice->PlayBuffer(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true); + } + } if(EmulationSettings::NeedAudioSettingsUpdate()) { if(EmulationSettings::GetSampleRate() != _sampleRate) { diff --git a/Core/VideoDecoder.cpp b/Core/VideoDecoder.cpp index 670c9e3a..971a681e 100644 --- a/Core/VideoDecoder.cpp +++ b/Core/VideoDecoder.cpp @@ -1,5 +1,4 @@ #include "stdafx.h" -#include "AviRecorder.h" #include "IRenderingDevice.h" #include "VideoDecoder.h" #include "EmulationSettings.h" @@ -9,6 +8,7 @@ #include "HdVideoFilter.h" #include "ScaleFilter.h" #include "VideoRenderer.h" +#include "RewindManager.h" unique_ptr VideoDecoder::Instance; @@ -39,6 +39,11 @@ VideoDecoder::~VideoDecoder() StopThread(); } +FrameInfo VideoDecoder::GetFrameInfo() +{ + return _videoFilter->GetFrameInfo(); +} + void VideoDecoder::GetScreenSize(ScreenSize &size, bool ignoreScale) { if(_videoFilter) { @@ -94,10 +99,6 @@ void VideoDecoder::UpdateVideoFilter() case VideoFilterType::HdPack: _videoFilter.reset(new HdVideoFilter()); break; } - - if(_aviRecorder) { - StopRecording(); - } } } @@ -110,11 +111,6 @@ void VideoDecoder::DecodeFrame() } _videoFilter->SendFrame(_ppuOutputBuffer); - shared_ptr aviRecorder = _aviRecorder; - if(aviRecorder) { - aviRecorder->AddFrame(_videoFilter->GetOutputBuffer()); - } - ScreenSize screenSize; GetScreenSize(screenSize, true); if(_previousScale != EmulationSettings::GetVideoScale() || screenSize.Height != _previousScreenSize.Height || screenSize.Width != _previousScreenSize.Width) { @@ -127,7 +123,8 @@ void VideoDecoder::DecodeFrame() _frameChanged = false; - VideoRenderer::GetInstance()->UpdateFrame(_videoFilter->GetOutputBuffer(), frameInfo.Width, frameInfo.Height); + //Rewind manager will take care of sending the correct frame to the video renderer + RewindManager::SendFrame(_videoFilter->GetOutputBuffer(), frameInfo.Width, frameInfo.Height); } void VideoDecoder::DebugDecodeFrame(uint16_t* inputBuffer, uint32_t* outputBuffer, uint32_t length) @@ -158,16 +155,24 @@ uint32_t VideoDecoder::GetFrameCount() return _frameCount; } +void VideoDecoder::UpdateFrameSync(void *ppuOutputBuffer, HdPpuPixelInfo *hdPixelInfo) +{ + _hdScreenTiles = hdPixelInfo; + _ppuOutputBuffer = (uint16_t*)ppuOutputBuffer; + DecodeFrame(); + _frameCount++; +} + void VideoDecoder::UpdateFrame(void *ppuOutputBuffer, HdPpuPixelInfo *hdPixelInfo) { if(_frameChanged) { //Last frame isn't done decoding yet - sometimes Signal() introduces a 25-30ms delay - while(_frameChanged) { + while(_frameChanged) { //Spin until decode is done } //At this point, we are sure that the decode thread is no longer busy } - + _hdScreenTiles = hdPixelInfo; _ppuOutputBuffer = (uint16_t*)ppuOutputBuffer; _frameChanged = true; @@ -219,33 +224,3 @@ void VideoDecoder::TakeScreenshot() _videoFilter->TakeScreenshot(); } } - -void VideoDecoder::StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel) -{ - if(_videoFilter) { - shared_ptr recorder(new AviRecorder()); - - FrameInfo frameInfo = _videoFilter->GetFrameInfo(); - if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, frameInfo.BitsPerPixel, 60098814, EmulationSettings::GetSampleRate(), compressionLevel)) { - _aviRecorder = recorder; - } - } -} - -void VideoDecoder::AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate) -{ - shared_ptr aviRecorder = _aviRecorder; - if(aviRecorder) { - aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate); - } -} - -void VideoDecoder::StopRecording() -{ - _aviRecorder.reset(); -} - -bool VideoDecoder::IsRecording() -{ - return _aviRecorder != nullptr && _aviRecorder->IsRecording(); -} \ No newline at end of file diff --git a/Core/VideoDecoder.h b/Core/VideoDecoder.h index 73aa0f4a..6fe00dd2 100644 --- a/Core/VideoDecoder.h +++ b/Core/VideoDecoder.h @@ -5,12 +5,10 @@ using std::thread; #include "../Utilities/SimpleLock.h" #include "../Utilities/AutoResetEvent.h" -#include "../Utilities/AviWriter.h" #include "EmulationSettings.h" #include "HdNesPack.h" #include "FrameInfo.h" -class AviRecorder; class BaseVideoFilter; class IRenderingDevice; @@ -30,7 +28,6 @@ private: HdPpuPixelInfo *_hdScreenTiles = nullptr; unique_ptr _decodeThread; - shared_ptr _aviRecorder; AutoResetEvent _waitForFrame; @@ -60,18 +57,15 @@ public: uint32_t GetFrameCount(); + FrameInfo GetFrameInfo(); void GetScreenSize(ScreenSize &size, bool ignoreScale); void DebugDecodeFrame(uint16_t* inputBuffer, uint32_t* outputBuffer, uint32_t length); + void UpdateFrameSync(void* frameBuffer, HdPpuPixelInfo *screenTiles = nullptr); void UpdateFrame(void* frameBuffer, HdPpuPixelInfo *screenTiles = nullptr); bool IsRunning(); void StartThread(); void StopThread(); - - void StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel); - void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); - void StopRecording(); - bool IsRecording(); }; \ No newline at end of file diff --git a/Core/VideoRenderer.cpp b/Core/VideoRenderer.cpp index 05d55f64..99591e89 100644 --- a/Core/VideoRenderer.cpp +++ b/Core/VideoRenderer.cpp @@ -1,6 +1,8 @@ #include "stdafx.h" #include "IRenderingDevice.h" #include "VideoRenderer.h" +#include "AviRecorder.h" +#include "VideoDecoder.h" unique_ptr VideoRenderer::Instance; @@ -60,6 +62,11 @@ void VideoRenderer::RenderThread() void VideoRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height) { + shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddFrame(frameBuffer, width, height); + } + if(_renderer) { _renderer->UpdateFrame(frameBuffer, width, height); _waitForRender.Signal(); @@ -78,4 +85,32 @@ void VideoRenderer::UnregisterRenderingDevice(IRenderingDevice *renderer) StopThread(); _renderer = nullptr; } +} + +void VideoRenderer::StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel) +{ + shared_ptr recorder(new AviRecorder()); + + FrameInfo frameInfo = VideoDecoder::GetInstance()->GetFrameInfo(); + if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, frameInfo.BitsPerPixel, 60098814, EmulationSettings::GetSampleRate(), compressionLevel)) { + _aviRecorder = recorder; + } +} + +void VideoRenderer::AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate); + } +} + +void VideoRenderer::StopRecording() +{ + _aviRecorder.reset(); +} + +bool VideoRenderer::IsRecording() +{ + return _aviRecorder != nullptr && _aviRecorder->IsRecording(); } \ No newline at end of file diff --git a/Core/VideoRenderer.h b/Core/VideoRenderer.h index a15fc2ee..da460e62 100644 --- a/Core/VideoRenderer.h +++ b/Core/VideoRenderer.h @@ -2,8 +2,11 @@ #include "stdafx.h" #include #include "../Utilities/AutoResetEvent.h" +#include "FrameInfo.h" class IRenderingDevice; +class AviRecorder; +enum class VideoCodec; class VideoRenderer { @@ -15,6 +18,9 @@ private: IRenderingDevice* _renderer = nullptr; atomic _stopFlag; + shared_ptr _aviRecorder; + FrameInfo type_info; + void RenderThread(); public: @@ -28,4 +34,9 @@ public: void UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height); void RegisterRenderingDevice(IRenderingDevice *renderer); void UnregisterRenderingDevice(IRenderingDevice *renderer); + + void StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel); + void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + void StopRecording(); + bool IsRecording(); }; \ No newline at end of file diff --git a/GUI.NET/Config/EmulationInfo.cs b/GUI.NET/Config/EmulationInfo.cs index 07c3494b..b7447cca 100644 --- a/GUI.NET/Config/EmulationInfo.cs +++ b/GUI.NET/Config/EmulationInfo.cs @@ -37,6 +37,7 @@ namespace Mesen.GUI.Config [MinMax(0, 500)] public UInt32 EmulationSpeed = 100; [MinMax(0, 500)] public UInt32 TurboSpeed = 300; + [MinMax(0, 500)] public UInt32 RewindSpeed = 100; public EmulationInfo() { @@ -47,7 +48,7 @@ namespace Mesen.GUI.Config EmulationInfo emulationInfo = ConfigManager.Config.EmulationInfo; InteropEmu.SetEmulationSpeed(emulationInfo.EmulationSpeed); - InteropEmu.SetTurboSpeed(emulationInfo.TurboSpeed); + InteropEmu.SetTurboRewindSpeed(emulationInfo.TurboSpeed, emulationInfo.RewindSpeed); InteropEmu.SetFlag(EmulationFlags.Mmc3IrqAltBehavior, emulationInfo.UseAlternativeMmc3Irq); InteropEmu.SetFlag(EmulationFlags.AllowInvalidInput, emulationInfo.AllowInvalidInput); diff --git a/GUI.NET/Config/PreferenceInfo.cs b/GUI.NET/Config/PreferenceInfo.cs index c724d37d..a20d8158 100644 --- a/GUI.NET/Config/PreferenceInfo.cs +++ b/GUI.NET/Config/PreferenceInfo.cs @@ -51,6 +51,8 @@ namespace Mesen.GUI.Config public bool DisableGameDatabase = false; + public UInt32 RewindBufferSize = 300; + public PreferenceInfo() { } @@ -101,6 +103,8 @@ namespace Mesen.GUI.Config InteropEmu.NsfSetNsfConfig(preferenceInfo.NsfAutoDetectSilence ? preferenceInfo.NsfAutoDetectSilenceDelay : 0, preferenceInfo.NsfMoveToNextTrackAfterTime ? preferenceInfo.NsfMoveToNextTrackTime : -1, preferenceInfo.NsfDisableApuIrqs); InteropEmu.SetAutoSaveOptions(preferenceInfo.AutoSave ? (uint)preferenceInfo.AutoSaveDelay : 0, preferenceInfo.AutoSaveNotify); InteropEmu.SetEmulatorKeys(new EmulatorKeyMappingSet() { KeySet1 = preferenceInfo.EmulatorKeySet1.Value, KeySet2 = preferenceInfo.EmulatorKeySet2.Value }); + + InteropEmu.SetRewindBufferSize(preferenceInfo.RewindBufferSize); } } } diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index c8496b19..b8794e1e 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -59,6 +59,9 @@ The selected cheat file ({0}) contains no cheats that match the selected game. Fast Forward + Rewind + Rewind 10 seconds + Rewind 1 minute Increase Speed Decrease Speed Pause diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index 6be6d161..0a8f3961 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -286,7 +286,9 @@ % (0 = Velocidad máxima) Velocidad de avance rápido: Velocidad de emulación: - + % (0 = Velocidad máxima) + Rewind Speed: + Avanzado Utilizar la versión alternativa de componentes de IRQs de MMC3 Permitir las entradas inválidas (Arriba+Abajo e Izquierda+Derecha al mismo tiempo) @@ -358,6 +360,9 @@ Copia de seguridad online Aumentar la velocidad de la emulación de juegos de carga FDS + Keep rewind data for the last + minutes (Memory Usage ≈1MB/min) + Guardar online Mesen puede utilizar Google Drive para almacenar las partidas guardadas en su cuenta de Google Drive. Cuando se habilita la integración, todos los datos de copia de seguridad de sus juegos (archivos .sav y partidas guardadas rápidas) son fácilmente accesibles desde cualquier ordenador. La integración con Google Drive está activa. @@ -578,6 +583,9 @@ El archivo de trucos seleccionado ({0}) no contiene trucos que coincidan con el juego seleccionado. Avance rápido + Rewind + Rewind 10 seconds + Rewind 1 minute Aumentar velocidad Reducir velocidad Pausa diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 00abfb40..e69a68c0 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -286,7 +286,9 @@ % (0 = Vitesse maximale) Vitesse d'émulation : % (0 = Vitesse maximale) - Vitesse d'avance rapide: + Vitesse d'avance rapide : + % (0 = Vitesse maximale) + Vitesse du rembobinage : Avancé Utiliser la version alternative du comportement des IRQs du MMC3 @@ -350,6 +352,9 @@ Insérer le côté A du disque 1 lors du chargement d'un jeu de FDS Augmenter la vitesse d'émulation pendant le chargement des jeux FDS + Permettre de rembobiner jusqu'à + minutes (Utilise ≈1MB/min) + Raccourcis Action Raccourci #1 @@ -593,6 +598,9 @@ Le fichier sélectionné ({0}) ne contient aucun code correspondant au jeu sélectionné. Avance rapide + Rembobiner + Reculer de 10 secondes + Reculer d'une minute Augmenter la vitesse Réduire la vitesse Pause diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index 1008a67f..2a471e95 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -286,7 +286,9 @@ % (0 = 最高速度) エミュレーションの速度: % (0 = 最高速度) - 早送りの速度: + 早送りの速度: + % (0 = 最高速度) + 巻き戻しの速度: 詳細設定 MMC3AのIRQ仕様を使う @@ -349,6 +351,9 @@ ファミコンディスクシステムのゲームをロードする時に自動的にディスク1のA面を入れる ファミコンディスクシステムのゲームをディスクからロードする時に自動的に最高速度にする + 巻き戻し用データの + 分をキープする (メモリの使用量:1分に約1MB) + ショートカットキー 機能 ショートカットキー1 @@ -575,6 +580,9 @@ このファイル({0})に選択されたゲームに該当するチートコードを見つかりませんでした。 早送り + 巻き戻す + 10秒前に戻る + 1分前に戻る 速度を上げる 速度を下げる ポーズ diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index 72b1c2be..e21020b1 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -286,7 +286,9 @@ % (0 = Velocidade máxima) Velocidade de avanço rápido: Velocidade de emulação: - + % (0 = Velocidade máxima) + Rewind Speed: + Avançado Utilizar a versão alternativa de componentes de IRQs de MMC3 Permitir as entradas inválidas (Cima+Baixo e Esquerda+Direita ao mesmo tempo) @@ -357,6 +359,9 @@ Mostrar uma notificação na tela ao fazer a cópia de segurança automática Cópia de segurança online Aumentar a velocidade da emulação de jogos ao carregar no FDS + + Keep rewind data for the last + minutes (Memory Usage ≈1MB/min) Salvar online Mesen pode utilizar Google Drive para armazenar saves em sua conta do Google Drive. Quando se habilita a integração, todos os dados de cópia de segurança de seus jogos (arquivos .sav e save states) são facilmente acessíveis de qualquer computador. @@ -576,7 +581,11 @@ O arquivo selecionado ({0}) não é um arquivo de cheats válido. O arquivo selecionado ({0}) não é um arquivo XML válido. O arquivo de cheats selecionado ({0}) não contém cheats que pertencem ao jogo selecionado. + Fast Forward + Rewind + Rewind 10 seconds + Rewind 1 minute Aumentar velocidade Reduzir velocidade Pausar diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index 9a77be42..9993c910 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -287,6 +287,8 @@ Скорость эмуляции : % (0 = Максимальная скорость) Перемотка: + % (0 = Максимальная скорость) + Rewind Speed: Расширенные Использовать альтернативный IRQ MMC3 @@ -349,6 +351,9 @@ Автоматически вставлять диск 1 стороной А при загрузке FDS Использовать быструю загрузку FDS + Keep rewind data for the last + minutes (Memory Usage ≈1MB/min) + Горячие клавиши Действие Вариант #1 @@ -583,6 +588,9 @@ Выбранный Cheat файл ({0}) не содержит читов для данной игры. Перемотка + Rewind + Rewind 10 seconds + Rewind 1 minute Increase Speed Decrease Speed Пауза diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index 71b9909e..89e7b3cf 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -287,6 +287,8 @@ Швидкість емуляції : % (0 = Максимальна швидкiсть) Перемотка: + % (0 = Максимальна швидкiсть) + Rewind Speed: Розширені Використовувати альтернативний IRQ MMC3 @@ -349,6 +351,9 @@ Автоматично вставляти диск 1 стороною А при завантаженні FDS Використовувати швидке завантаження FDS + Keep rewind data for the last + minutes (Memory Usage ≈1MB/min) + Гарячі клавіші Дія Варiант #1 @@ -582,6 +587,9 @@ Обраний Cheat файл ({0}) не містить читiв для даної гри. Перемотка + Rewind + Rewind 10 seconds + Rewind 1 minute Збільшити Швидкість Зменшити Швидкість Пауза diff --git a/GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs b/GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs index fa1e6d54..99f772d7 100644 --- a/GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs +++ b/GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs @@ -84,6 +84,10 @@ namespace Mesen.GUI.Forms.Config this.chkShowLagCounter = new System.Windows.Forms.CheckBox(); this.btnResetLagCounter = new System.Windows.Forms.Button(); this.tmrUpdateClockRate = new System.Windows.Forms.Timer(this.components); + this.lblRewindSpeed = new System.Windows.Forms.Label(); + this.flowLayoutPanel10 = new System.Windows.Forms.FlowLayoutPanel(); + this.nudRewindSpeed = new System.Windows.Forms.NumericUpDown(); + this.lblRewindSpeedHint = new System.Windows.Forms.Label(); this.tabMain.SuspendLayout(); this.tpgGeneral.SuspendLayout(); this.tableLayoutPanel4.SuspendLayout(); @@ -108,6 +112,8 @@ namespace Mesen.GUI.Forms.Config ((System.ComponentModel.ISupportInitialize)(this.nudExtraScanlinesBeforeNmi)).BeginInit(); this.flowLayoutPanel2.SuspendLayout(); this.flowLayoutPanel7.SuspendLayout(); + this.flowLayoutPanel10.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudRewindSpeed)).BeginInit(); this.SuspendLayout(); // // baseConfigPanel @@ -133,7 +139,7 @@ namespace Mesen.GUI.Forms.Config this.tpgGeneral.Location = new System.Drawing.Point(4, 22); this.tpgGeneral.Name = "tpgGeneral"; this.tpgGeneral.Padding = new System.Windows.Forms.Padding(3); - this.tpgGeneral.Size = new System.Drawing.Size(525, 273); + this.tpgGeneral.Size = new System.Drawing.Size(525, 302); this.tpgGeneral.TabIndex = 0; this.tpgGeneral.Text = "General"; this.tpgGeneral.UseVisualStyleBackColor = true; @@ -147,14 +153,17 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel4.Controls.Add(this.lblTurboSpeed, 0, 1); this.tableLayoutPanel4.Controls.Add(this.flowLayoutPanel6, 1, 0); this.tableLayoutPanel4.Controls.Add(this.lblEmulationSpeed, 0, 0); + this.tableLayoutPanel4.Controls.Add(this.lblRewindSpeed, 0, 2); + this.tableLayoutPanel4.Controls.Add(this.flowLayoutPanel10, 1, 2); this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel4.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel4.Name = "tableLayoutPanel4"; - this.tableLayoutPanel4.RowCount = 3; + this.tableLayoutPanel4.RowCount = 4; + this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel4.Size = new System.Drawing.Size(519, 267); + this.tableLayoutPanel4.Size = new System.Drawing.Size(519, 296); this.tableLayoutPanel4.TabIndex = 0; // // flowLayoutPanel9 @@ -291,9 +300,10 @@ namespace Mesen.GUI.Forms.Config // chkEnableOamDecay // this.chkEnableOamDecay.AutoSize = true; - this.chkEnableOamDecay.Location = new System.Drawing.Point(3, 72); + this.chkEnableOamDecay.Checked = false; + this.chkEnableOamDecay.Location = new System.Drawing.Point(0, 69); this.chkEnableOamDecay.Name = "chkEnableOamDecay"; - this.chkEnableOamDecay.Size = new System.Drawing.Size(150, 17); + this.chkEnableOamDecay.Size = new System.Drawing.Size(243, 23); this.chkEnableOamDecay.TabIndex = 9; this.chkEnableOamDecay.Text = "Enable OAM RAM decay"; // @@ -414,7 +424,7 @@ namespace Mesen.GUI.Forms.Config this.tpgOverclocking.Location = new System.Drawing.Point(4, 22); this.tpgOverclocking.Name = "tpgOverclocking"; this.tpgOverclocking.Padding = new System.Windows.Forms.Padding(3); - this.tpgOverclocking.Size = new System.Drawing.Size(525, 273); + this.tpgOverclocking.Size = new System.Drawing.Size(525, 302); this.tpgOverclocking.TabIndex = 2; this.tpgOverclocking.Text = "Overclocking"; this.tpgOverclocking.UseVisualStyleBackColor = true; @@ -443,7 +453,7 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel3.Size = new System.Drawing.Size(519, 267); + this.tableLayoutPanel3.Size = new System.Drawing.Size(519, 296); this.tableLayoutPanel3.TabIndex = 0; // // flowLayoutPanel4 @@ -739,7 +749,7 @@ namespace Mesen.GUI.Forms.Config this.flowLayoutPanel7.Location = new System.Drawing.Point(0, 232); this.flowLayoutPanel7.Margin = new System.Windows.Forms.Padding(0); this.flowLayoutPanel7.Name = "flowLayoutPanel7"; - this.flowLayoutPanel7.Size = new System.Drawing.Size(519, 35); + this.flowLayoutPanel7.Size = new System.Drawing.Size(519, 64); this.flowLayoutPanel7.TabIndex = 12; // // chkShowLagCounter @@ -769,6 +779,50 @@ namespace Mesen.GUI.Forms.Config this.tmrUpdateClockRate.Enabled = true; this.tmrUpdateClockRate.Tick += new System.EventHandler(this.tmrUpdateClockRate_Tick); // + // lblRewindSpeed + // + this.lblRewindSpeed.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblRewindSpeed.AutoSize = true; + this.lblRewindSpeed.Location = new System.Drawing.Point(3, 58); + this.lblRewindSpeed.Name = "lblRewindSpeed"; + this.lblRewindSpeed.Size = new System.Drawing.Size(80, 13); + this.lblRewindSpeed.TabIndex = 15; + this.lblRewindSpeed.Text = "Rewind Speed:"; + // + // flowLayoutPanel10 + // + this.flowLayoutPanel10.AutoSize = true; + this.flowLayoutPanel10.Controls.Add(this.nudRewindSpeed); + this.flowLayoutPanel10.Controls.Add(this.lblRewindSpeedHint); + this.flowLayoutPanel10.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel10.Location = new System.Drawing.Point(111, 52); + this.flowLayoutPanel10.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel10.Name = "flowLayoutPanel10"; + this.flowLayoutPanel10.Size = new System.Drawing.Size(408, 26); + this.flowLayoutPanel10.TabIndex = 16; + // + // nudRewindSpeed + // + this.nudRewindSpeed.Location = new System.Drawing.Point(3, 3); + this.nudRewindSpeed.Maximum = new decimal(new int[] { + 500, + 0, + 0, + 0}); + this.nudRewindSpeed.Name = "nudRewindSpeed"; + this.nudRewindSpeed.Size = new System.Drawing.Size(48, 20); + this.nudRewindSpeed.TabIndex = 1; + // + // lblRewindSpeedHint + // + this.lblRewindSpeedHint.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblRewindSpeedHint.AutoSize = true; + this.lblRewindSpeedHint.Location = new System.Drawing.Point(57, 6); + this.lblRewindSpeedHint.Name = "lblRewindSpeedHint"; + this.lblRewindSpeedHint.Size = new System.Drawing.Size(121, 13); + this.lblRewindSpeedHint.TabIndex = 2; + this.lblRewindSpeedHint.Text = "% (0 = Maximum speed)"; + // // frmEmulationConfig // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -820,6 +874,9 @@ namespace Mesen.GUI.Forms.Config this.flowLayoutPanel2.PerformLayout(); this.flowLayoutPanel7.ResumeLayout(false); this.flowLayoutPanel7.PerformLayout(); + this.flowLayoutPanel10.ResumeLayout(false); + this.flowLayoutPanel10.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudRewindSpeed)).EndInit(); this.ResumeLayout(false); } @@ -879,5 +936,9 @@ namespace Mesen.GUI.Forms.Config private ctrlRiskyOption chkDisablePpuReset; private System.Windows.Forms.CheckBox chkUseNes101Hvc101Behavior; private Mesen.GUI.Controls.ctrlRiskyOption chkEnableOamDecay; + private System.Windows.Forms.Label lblRewindSpeed; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel10; + private System.Windows.Forms.NumericUpDown nudRewindSpeed; + private System.Windows.Forms.Label lblRewindSpeedHint; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmEmulationConfig.cs b/GUI.NET/Forms/Config/frmEmulationConfig.cs index 7ad072d8..f36bfb6b 100644 --- a/GUI.NET/Forms/Config/frmEmulationConfig.cs +++ b/GUI.NET/Forms/Config/frmEmulationConfig.cs @@ -23,6 +23,7 @@ namespace Mesen.GUI.Forms.Config AddBinding("EmulationSpeed", nudEmulationSpeed); AddBinding("TurboSpeed", nudTurboSpeed); + AddBinding("RewindSpeed", nudRewindSpeed); AddBinding("UseAlternativeMmc3Irq", chkUseAlternativeMmc3Irq); AddBinding("AllowInvalidInput", chkAllowInvalidInput); diff --git a/GUI.NET/Forms/Config/frmPreferences.Designer.cs b/GUI.NET/Forms/Config/frmPreferences.Designer.cs index 9aaff7f6..1f7147df 100644 --- a/GUI.NET/Forms/Config/frmPreferences.Designer.cs +++ b/GUI.NET/Forms/Config/frmPreferences.Designer.cs @@ -32,18 +32,21 @@ namespace Mesen.GUI.Forms.Config this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmPreferences)); this.tlpMain = new System.Windows.Forms.TableLayoutPanel(); - this.chkHidePauseOverlay = new System.Windows.Forms.CheckBox(); + this.lblPauseBackgroundSettings = new System.Windows.Forms.Label(); this.chkSingleInstance = new System.Windows.Forms.CheckBox(); this.chkAutomaticallyCheckForUpdates = new System.Windows.Forms.CheckBox(); - this.chkPauseOnMovieEnd = new System.Windows.Forms.CheckBox(); - this.chkAllowBackgroundInput = new System.Windows.Forms.CheckBox(); - this.chkPauseWhenInBackground = new System.Windows.Forms.CheckBox(); - this.chkAutoLoadIps = new System.Windows.Forms.CheckBox(); this.btnOpenMesenFolder = new System.Windows.Forms.Button(); this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); this.lblDisplayLanguage = new System.Windows.Forms.Label(); this.cboDisplayLanguage = new System.Windows.Forms.ComboBox(); + this.lblMiscSettings = new System.Windows.Forms.Label(); + this.chkAutoLoadIps = new System.Windows.Forms.CheckBox(); this.chkDisplayMovieIcons = new System.Windows.Forms.CheckBox(); + this.chkAutoHideMenu = new System.Windows.Forms.CheckBox(); + this.chkHidePauseOverlay = new System.Windows.Forms.CheckBox(); + this.chkAllowBackgroundInput = new System.Windows.Forms.CheckBox(); + this.chkPauseWhenInBackground = new System.Windows.Forms.CheckBox(); + this.chkPauseOnMovieEnd = new System.Windows.Forms.CheckBox(); this.tabMain = new System.Windows.Forms.TabControl(); this.tpgGeneral = new System.Windows.Forms.TabPage(); this.tpgShortcuts = new System.Windows.Forms.TabPage(); @@ -97,11 +100,12 @@ namespace Mesen.GUI.Forms.Config this.chkDisableGameDatabase = new Mesen.GUI.Controls.ctrlRiskyOption(); this.chkFdsAutoLoadDisk = new System.Windows.Forms.CheckBox(); this.chkFdsFastForwardOnLoad = new System.Windows.Forms.CheckBox(); - this.tmrSyncDateTime = new System.Windows.Forms.Timer(this.components); this.chkDisplayTitleBarInfo = new System.Windows.Forms.CheckBox(); - this.chkAutoHideMenu = new System.Windows.Forms.CheckBox(); - this.lblMiscSettings = new System.Windows.Forms.Label(); - this.lblPauseBackgroundSettings = new System.Windows.Forms.Label(); + this.tmrSyncDateTime = new System.Windows.Forms.Timer(this.components); + this.flowLayoutPanel6 = new System.Windows.Forms.FlowLayoutPanel(); + this.nudRewindBufferSize = new System.Windows.Forms.NumericUpDown(); + this.lblRewindMinutes = new System.Windows.Forms.Label(); + this.lblRewind = new System.Windows.Forms.Label(); this.tlpMain.SuspendLayout(); this.flowLayoutPanel2.SuspendLayout(); this.tabMain.SuspendLayout(); @@ -131,6 +135,8 @@ namespace Mesen.GUI.Forms.Config this.tlpFileFormat.SuspendLayout(); this.tpgAdvanced.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel6.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudRewindBufferSize)).BeginInit(); this.SuspendLayout(); // // baseConfigPanel @@ -176,16 +182,17 @@ namespace Mesen.GUI.Forms.Config this.tlpMain.Size = new System.Drawing.Size(473, 337); this.tlpMain.TabIndex = 1; // - // chkHidePauseOverlay + // lblPauseBackgroundSettings // - this.chkHidePauseOverlay.AutoSize = true; - this.chkHidePauseOverlay.Location = new System.Drawing.Point(13, 95); - this.chkHidePauseOverlay.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkHidePauseOverlay.Name = "chkHidePauseOverlay"; - this.chkHidePauseOverlay.Size = new System.Drawing.Size(133, 17); - this.chkHidePauseOverlay.TabIndex = 20; - this.chkHidePauseOverlay.Text = "Hide the pause screen"; - this.chkHidePauseOverlay.UseVisualStyleBackColor = true; + this.lblPauseBackgroundSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblPauseBackgroundSettings.AutoSize = true; + this.lblPauseBackgroundSettings.ForeColor = System.Drawing.SystemColors.GrayText; + this.lblPauseBackgroundSettings.Location = new System.Drawing.Point(0, 79); + this.lblPauseBackgroundSettings.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); + this.lblPauseBackgroundSettings.Name = "lblPauseBackgroundSettings"; + this.lblPauseBackgroundSettings.Size = new System.Drawing.Size(141, 13); + this.lblPauseBackgroundSettings.TabIndex = 23; + this.lblPauseBackgroundSettings.Text = "Pause/Background Settings"; // // chkSingleInstance // @@ -207,51 +214,6 @@ namespace Mesen.GUI.Forms.Config this.chkAutomaticallyCheckForUpdates.Text = "Automatically check for updates"; this.chkAutomaticallyCheckForUpdates.UseVisualStyleBackColor = true; // - // chkPauseOnMovieEnd - // - this.chkPauseOnMovieEnd.AutoSize = true; - this.chkPauseOnMovieEnd.Location = new System.Drawing.Point(13, 118); - this.chkPauseOnMovieEnd.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkPauseOnMovieEnd.Name = "chkPauseOnMovieEnd"; - this.chkPauseOnMovieEnd.Size = new System.Drawing.Size(199, 17); - this.chkPauseOnMovieEnd.TabIndex = 15; - this.chkPauseOnMovieEnd.Text = "Pause when a movie finishes playing"; - this.chkPauseOnMovieEnd.UseVisualStyleBackColor = true; - // - // chkAllowBackgroundInput - // - this.chkAllowBackgroundInput.AutoSize = true; - this.chkAllowBackgroundInput.Location = new System.Drawing.Point(13, 164); - this.chkAllowBackgroundInput.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkAllowBackgroundInput.Name = "chkAllowBackgroundInput"; - this.chkAllowBackgroundInput.Size = new System.Drawing.Size(177, 17); - this.chkAllowBackgroundInput.TabIndex = 14; - this.chkAllowBackgroundInput.Text = "Allow input when in background"; - this.chkAllowBackgroundInput.UseVisualStyleBackColor = true; - // - // chkPauseWhenInBackground - // - this.chkPauseWhenInBackground.AutoSize = true; - this.chkPauseWhenInBackground.Location = new System.Drawing.Point(13, 141); - this.chkPauseWhenInBackground.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkPauseWhenInBackground.Name = "chkPauseWhenInBackground"; - this.chkPauseWhenInBackground.Size = new System.Drawing.Size(204, 17); - this.chkPauseWhenInBackground.TabIndex = 13; - this.chkPauseWhenInBackground.Text = "Pause emulation when in background"; - this.chkPauseWhenInBackground.UseVisualStyleBackColor = true; - this.chkPauseWhenInBackground.CheckedChanged += new System.EventHandler(this.chkPauseWhenInBackground_CheckedChanged); - // - // chkAutoLoadIps - // - this.chkAutoLoadIps.AutoSize = true; - this.chkAutoLoadIps.Location = new System.Drawing.Point(13, 207); - this.chkAutoLoadIps.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkAutoLoadIps.Name = "chkAutoLoadIps"; - this.chkAutoLoadIps.Size = new System.Drawing.Size(198, 17); - this.chkAutoLoadIps.TabIndex = 9; - this.chkAutoLoadIps.Text = "Automatically load IPS/BPS patches"; - this.chkAutoLoadIps.UseVisualStyleBackColor = true; - // // btnOpenMesenFolder // this.btnOpenMesenFolder.AutoSize = true; @@ -292,6 +254,30 @@ namespace Mesen.GUI.Forms.Config this.cboDisplayLanguage.Size = new System.Drawing.Size(206, 21); this.cboDisplayLanguage.TabIndex = 1; // + // lblMiscSettings + // + this.lblMiscSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblMiscSettings.AutoSize = true; + this.lblMiscSettings.Enabled = false; + this.lblMiscSettings.ForeColor = System.Drawing.SystemColors.GrayText; + this.lblMiscSettings.Location = new System.Drawing.Point(0, 191); + this.lblMiscSettings.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); + this.lblMiscSettings.Name = "lblMiscSettings"; + this.lblMiscSettings.Size = new System.Drawing.Size(73, 13); + this.lblMiscSettings.TabIndex = 22; + this.lblMiscSettings.Text = "Misc. Settings"; + // + // chkAutoLoadIps + // + this.chkAutoLoadIps.AutoSize = true; + this.chkAutoLoadIps.Location = new System.Drawing.Point(13, 207); + this.chkAutoLoadIps.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkAutoLoadIps.Name = "chkAutoLoadIps"; + this.chkAutoLoadIps.Size = new System.Drawing.Size(198, 17); + this.chkAutoLoadIps.TabIndex = 9; + this.chkAutoLoadIps.Text = "Automatically load IPS/BPS patches"; + this.chkAutoLoadIps.UseVisualStyleBackColor = true; + // // chkDisplayMovieIcons // this.chkDisplayMovieIcons.AutoSize = true; @@ -303,6 +289,62 @@ namespace Mesen.GUI.Forms.Config this.chkDisplayMovieIcons.Text = "Display play/record icon when playing or recording a movie"; this.chkDisplayMovieIcons.UseVisualStyleBackColor = true; // + // chkAutoHideMenu + // + this.chkAutoHideMenu.AutoSize = true; + this.chkAutoHideMenu.Location = new System.Drawing.Point(13, 230); + this.chkAutoHideMenu.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkAutoHideMenu.Name = "chkAutoHideMenu"; + this.chkAutoHideMenu.Size = new System.Drawing.Size(158, 17); + this.chkAutoHideMenu.TabIndex = 21; + this.chkAutoHideMenu.Text = "Automatically hide menu bar"; + this.chkAutoHideMenu.UseVisualStyleBackColor = true; + // + // chkHidePauseOverlay + // + this.chkHidePauseOverlay.AutoSize = true; + this.chkHidePauseOverlay.Location = new System.Drawing.Point(13, 95); + this.chkHidePauseOverlay.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkHidePauseOverlay.Name = "chkHidePauseOverlay"; + this.chkHidePauseOverlay.Size = new System.Drawing.Size(133, 17); + this.chkHidePauseOverlay.TabIndex = 20; + this.chkHidePauseOverlay.Text = "Hide the pause screen"; + this.chkHidePauseOverlay.UseVisualStyleBackColor = true; + // + // chkAllowBackgroundInput + // + this.chkAllowBackgroundInput.AutoSize = true; + this.chkAllowBackgroundInput.Location = new System.Drawing.Point(13, 164); + this.chkAllowBackgroundInput.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkAllowBackgroundInput.Name = "chkAllowBackgroundInput"; + this.chkAllowBackgroundInput.Size = new System.Drawing.Size(177, 17); + this.chkAllowBackgroundInput.TabIndex = 14; + this.chkAllowBackgroundInput.Text = "Allow input when in background"; + this.chkAllowBackgroundInput.UseVisualStyleBackColor = true; + // + // chkPauseWhenInBackground + // + this.chkPauseWhenInBackground.AutoSize = true; + this.chkPauseWhenInBackground.Location = new System.Drawing.Point(13, 141); + this.chkPauseWhenInBackground.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkPauseWhenInBackground.Name = "chkPauseWhenInBackground"; + this.chkPauseWhenInBackground.Size = new System.Drawing.Size(204, 17); + this.chkPauseWhenInBackground.TabIndex = 13; + this.chkPauseWhenInBackground.Text = "Pause emulation when in background"; + this.chkPauseWhenInBackground.UseVisualStyleBackColor = true; + this.chkPauseWhenInBackground.CheckedChanged += new System.EventHandler(this.chkPauseWhenInBackground_CheckedChanged); + // + // chkPauseOnMovieEnd + // + this.chkPauseOnMovieEnd.AutoSize = true; + this.chkPauseOnMovieEnd.Location = new System.Drawing.Point(13, 118); + this.chkPauseOnMovieEnd.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); + this.chkPauseOnMovieEnd.Name = "chkPauseOnMovieEnd"; + this.chkPauseOnMovieEnd.Size = new System.Drawing.Size(199, 17); + this.chkPauseOnMovieEnd.TabIndex = 15; + this.chkPauseOnMovieEnd.Text = "Pause when a movie finishes playing"; + this.chkPauseOnMovieEnd.UseVisualStyleBackColor = true; + // // tabMain // this.tabMain.Controls.Add(this.tpgGeneral); @@ -916,16 +958,17 @@ namespace Mesen.GUI.Forms.Config this.tableLayoutPanel1.Controls.Add(this.chkFdsAutoLoadDisk, 0, 1); this.tableLayoutPanel1.Controls.Add(this.chkFdsFastForwardOnLoad, 0, 2); this.tableLayoutPanel1.Controls.Add(this.chkDisplayTitleBarInfo, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel6, 0, 4); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 5; + this.tableLayoutPanel1.RowCount = 6; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(473, 337); this.tableLayoutPanel1.TabIndex = 0; // @@ -959,11 +1002,6 @@ namespace Mesen.GUI.Forms.Config this.chkFdsFastForwardOnLoad.Text = "Automatically fast forward FDS games when disk or BIOS is loading"; this.chkFdsFastForwardOnLoad.UseVisualStyleBackColor = true; // - // tmrSyncDateTime - // - this.tmrSyncDateTime.Enabled = true; - this.tmrSyncDateTime.Tick += new System.EventHandler(this.tmrSyncDateTime_Tick); - // // chkDisplayTitleBarInfo // this.chkDisplayTitleBarInfo.AutoSize = true; @@ -974,41 +1012,59 @@ namespace Mesen.GUI.Forms.Config this.chkDisplayTitleBarInfo.Text = "Display additional information in title bar"; this.chkDisplayTitleBarInfo.UseVisualStyleBackColor = true; // - // chkAutoHideMenu + // tmrSyncDateTime // - this.chkAutoHideMenu.AutoSize = true; - this.chkAutoHideMenu.Location = new System.Drawing.Point(13, 230); - this.chkAutoHideMenu.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3); - this.chkAutoHideMenu.Name = "chkAutoHideMenu"; - this.chkAutoHideMenu.Size = new System.Drawing.Size(158, 17); - this.chkAutoHideMenu.TabIndex = 21; - this.chkAutoHideMenu.Text = "Automatically hide menu bar"; - this.chkAutoHideMenu.UseVisualStyleBackColor = true; + this.tmrSyncDateTime.Enabled = true; + this.tmrSyncDateTime.Tick += new System.EventHandler(this.tmrSyncDateTime_Tick); // - // lblMiscSettings + // flowLayoutPanel6 // - this.lblMiscSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.lblMiscSettings.AutoSize = true; - this.lblMiscSettings.Enabled = false; - this.lblMiscSettings.ForeColor = System.Drawing.SystemColors.GrayText; - this.lblMiscSettings.Location = new System.Drawing.Point(0, 191); - this.lblMiscSettings.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); - this.lblMiscSettings.Name = "lblMiscSettings"; - this.lblMiscSettings.Size = new System.Drawing.Size(73, 13); - this.lblMiscSettings.TabIndex = 22; - this.lblMiscSettings.Text = "Misc. Settings"; + this.flowLayoutPanel6.Controls.Add(this.lblRewind); + this.flowLayoutPanel6.Controls.Add(this.nudRewindBufferSize); + this.flowLayoutPanel6.Controls.Add(this.lblRewindMinutes); + this.flowLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel6.Location = new System.Drawing.Point(0, 92); + this.flowLayoutPanel6.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel6.Name = "flowLayoutPanel6"; + this.flowLayoutPanel6.Size = new System.Drawing.Size(473, 23); + this.flowLayoutPanel6.TabIndex = 9; // - // lblPauseBackgroundSettings + // nudRewindBufferSize // - this.lblPauseBackgroundSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.lblPauseBackgroundSettings.AutoSize = true; - this.lblPauseBackgroundSettings.ForeColor = System.Drawing.SystemColors.GrayText; - this.lblPauseBackgroundSettings.Location = new System.Drawing.Point(0, 79); - this.lblPauseBackgroundSettings.Margin = new System.Windows.Forms.Padding(0, 0, 3, 0); - this.lblPauseBackgroundSettings.Name = "lblPauseBackgroundSettings"; - this.lblPauseBackgroundSettings.Size = new System.Drawing.Size(141, 13); - this.lblPauseBackgroundSettings.TabIndex = 23; - this.lblPauseBackgroundSettings.Text = "Pause/Background Settings"; + this.nudRewindBufferSize.Location = new System.Drawing.Point(151, 3); + this.nudRewindBufferSize.Maximum = new decimal(new int[] { + 900, + 0, + 0, + 0}); + this.nudRewindBufferSize.Name = "nudRewindBufferSize"; + this.nudRewindBufferSize.Size = new System.Drawing.Size(42, 20); + this.nudRewindBufferSize.TabIndex = 1; + this.nudRewindBufferSize.Value = new decimal(new int[] { + 300, + 0, + 0, + 0}); + // + // lblRewindMinutes + // + this.lblRewindMinutes.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblRewindMinutes.AutoSize = true; + this.lblRewindMinutes.Location = new System.Drawing.Point(199, 6); + this.lblRewindMinutes.Name = "lblRewindMinutes"; + this.lblRewindMinutes.Size = new System.Drawing.Size(173, 13); + this.lblRewindMinutes.TabIndex = 2; + this.lblRewindMinutes.Text = "minutes (Memory Usage ≈1MB/min)"; + // + // lblRewind + // + this.lblRewind.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblRewind.AutoSize = true; + this.lblRewind.Location = new System.Drawing.Point(3, 6); + this.lblRewind.Name = "lblRewind"; + this.lblRewind.Size = new System.Drawing.Size(142, 13); + this.lblRewind.TabIndex = 3; + this.lblRewind.Text = "Keep rewind data for the last"; // // frmPreferences // @@ -1066,6 +1122,9 @@ namespace Mesen.GUI.Forms.Config this.tpgAdvanced.ResumeLayout(false); this.tableLayoutPanel1.ResumeLayout(false); this.tableLayoutPanel1.PerformLayout(); + this.flowLayoutPanel6.ResumeLayout(false); + this.flowLayoutPanel6.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudRewindBufferSize)).EndInit(); this.ResumeLayout(false); } @@ -1143,5 +1202,9 @@ namespace Mesen.GUI.Forms.Config private System.Windows.Forms.CheckBox chkDisplayTitleBarInfo; private System.Windows.Forms.Label lblPauseBackgroundSettings; private System.Windows.Forms.Label lblMiscSettings; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel6; + private System.Windows.Forms.Label lblRewind; + private System.Windows.Forms.NumericUpDown nudRewindBufferSize; + private System.Windows.Forms.Label lblRewindMinutes; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmPreferences.cs b/GUI.NET/Forms/Config/frmPreferences.cs index ce375d6e..fbeacd42 100644 --- a/GUI.NET/Forms/Config/frmPreferences.cs +++ b/GUI.NET/Forms/Config/frmPreferences.cs @@ -59,6 +59,8 @@ namespace Mesen.GUI.Forms.Config AddBinding("AutoHideMenu", chkAutoHideMenu); AddBinding("DisplayTitleBarInfo", chkDisplayTitleBarInfo); + AddBinding("RewindBufferSize", nudRewindBufferSize); + UpdateCloudDisplay(); } diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 94dd1de2..f515e95e 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -150,7 +150,8 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void IncreaseEmulationSpeed(); [DllImport(DLLPath)] public static extern void DecreaseEmulationSpeed(); [DllImport(DLLPath)] public static extern UInt32 GetEmulationSpeed(); - [DllImport(DLLPath)] public static extern void SetTurboSpeed(UInt32 turboSpeed); + [DllImport(DLLPath)] public static extern void SetTurboRewindSpeed(UInt32 turboSpeed, UInt32 rewindSpeed); + [DllImport(DLLPath)] public static extern void SetRewindBufferSize(UInt32 seconds); [DllImport(DLLPath)] public static extern void SetOverclockRate(UInt32 overclockRate, [MarshalAs(UnmanagedType.I1)]bool adjustApu); [DllImport(DLLPath)] public static extern void SetPpuNmiConfig(UInt32 extraScanlinesBeforeNmi, UInt32 extraScanlineAfterNmi); [DllImport(DLLPath)] public static extern void SetOverscanDimensions(UInt32 left, UInt32 right, UInt32 top, UInt32 bottom); @@ -1058,6 +1059,11 @@ namespace Mesen.GUI public struct EmulatorKeyMappings { public UInt32 FastForward; + + public UInt32 Rewind; + public UInt32 RewindTenSecs; + public UInt32 RewindOneMin; + public UInt32 Pause; public UInt32 Reset; public UInt32 Exit; diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index aa7cc8d4..5af1c531 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -9,6 +9,7 @@ #include "../Core/CheatManager.h" #include "../Core/EmulationSettings.h" #include "../Core/VideoDecoder.h" +#include "../Core/VideoRenderer.h" #include "../Core/AutomaticRomTest.h" #include "../Core/RecordedRomTest.h" #include "../Core/FDS.h" @@ -306,9 +307,9 @@ namespace InteropEmu { DllExport bool __stdcall MoviePlaying() { return MovieManager::Playing(); } DllExport bool __stdcall MovieRecording() { return MovieManager::Recording(); } - DllExport void __stdcall AviRecord(char* filename, VideoCodec codec, uint32_t compressionLevel) { VideoDecoder::GetInstance()->StartRecording(filename, codec, compressionLevel); } - DllExport void __stdcall AviStop() { VideoDecoder::GetInstance()->StopRecording(); } - DllExport bool __stdcall AviIsRecording() { return VideoDecoder::GetInstance()->IsRecording(); } + DllExport void __stdcall AviRecord(char* filename, VideoCodec codec, uint32_t compressionLevel) { VideoRenderer::GetInstance()->StartRecording(filename, codec, compressionLevel); } + DllExport void __stdcall AviStop() { VideoRenderer::GetInstance()->StopRecording(); } + DllExport bool __stdcall AviIsRecording() { return VideoRenderer::GetInstance()->IsRecording(); } DllExport void __stdcall WaveRecord(char* filename) { SoundMixer::StartRecording(filename); } DllExport void __stdcall WaveStop() { SoundMixer::StopRecording(); } @@ -384,7 +385,8 @@ namespace InteropEmu { DllExport void __stdcall IncreaseEmulationSpeed() { EmulationSettings::IncreaseEmulationSpeed(); } DllExport void __stdcall DecreaseEmulationSpeed() { EmulationSettings::DecreaseEmulationSpeed(); } DllExport uint32_t __stdcall GetEmulationSpeed() { return EmulationSettings::GetEmulationSpeed(true); } - DllExport void __stdcall SetTurboSpeed(uint32_t turboSpeed) { EmulationSettings::SetTurboSpeed(turboSpeed); } + DllExport void __stdcall SetTurboRewindSpeed(uint32_t turboSpeed, uint32_t rewindSpeed) { EmulationSettings::SetTurboRewindSpeed(turboSpeed, rewindSpeed); } + DllExport void __stdcall SetRewindBufferSize(uint32_t seconds) { EmulationSettings::SetRewindBufferSize(seconds); } DllExport void __stdcall SetOverclockRate(uint32_t overclockRate, bool adjustApu) { EmulationSettings::SetOverclockRate(overclockRate, adjustApu); } DllExport void __stdcall SetPpuNmiConfig(uint32_t extraScanlinesBeforeNmi, uint32_t extraScanlinesAfterNmi) { EmulationSettings::SetPpuNmiConfig(extraScanlinesBeforeNmi, extraScanlinesAfterNmi); } DllExport void __stdcall SetVideoScale(double scale) { EmulationSettings::SetVideoScale(scale); } diff --git a/Windows/Renderer.cpp b/Windows/Renderer.cpp index ede286ba..61b7b678 100644 --- a/Windows/Renderer.cpp +++ b/Windows/Renderer.cpp @@ -84,11 +84,12 @@ namespace NES } if(_textureBuffer[0]) { delete[] _textureBuffer[0]; + _textureBuffer[0] = nullptr; } if(_textureBuffer[1]) { delete[] _textureBuffer[1]; + _textureBuffer[1] = nullptr; } - if(_samplerState) { _samplerState->Release(); _samplerState = nullptr; @@ -358,7 +359,7 @@ namespace NES void Renderer::DisplayMessage(string title, string message) { - shared_ptr toast(new ToastInfo(title, message, 4000, "")); + shared_ptr toast(new ToastInfo(title, message, 4000)); _toasts.push_front(toast); }