mirror of
https://github.com/libretro/Mesen.git
synced 2024-11-27 02:50:28 +00:00
Added rewind functionality
This commit is contained in:
parent
8a2f09b0fd
commit
3a6c8ca416
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
};
|
@ -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;
|
||||
}
|
24
Core/CPU.cpp
24
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) {
|
||||
|
25
Core/CPU.h
25
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();
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "NsfMapper.h"
|
||||
#include "ShortcutKeyHandler.h"
|
||||
#include "MovieManager.h"
|
||||
#include "RewindManager.h"
|
||||
|
||||
shared_ptr<Console> 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);
|
||||
|
@ -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> _rewindManager;
|
||||
shared_ptr<CPU> _cpu;
|
||||
shared_ptr<PPU> _ppu;
|
||||
unique_ptr<APU> _apu;
|
||||
|
@ -449,6 +449,8 @@
|
||||
<ClInclude Include="CrossFeedFilter.h" />
|
||||
<ClInclude Include="GoldenFive.h" />
|
||||
<ClInclude Include="MesenMovie.h" />
|
||||
<ClInclude Include="RewindData.h" />
|
||||
<ClInclude Include="RewindManager.h" />
|
||||
<ClInclude Include="SealieComputing.h" />
|
||||
<ClInclude Include="UnlD1038.h" />
|
||||
<ClInclude Include="DaouInfosys.h" />
|
||||
@ -807,6 +809,8 @@
|
||||
<ClCompile Include="OekaKidsTablet.cpp" />
|
||||
<ClCompile Include="Profiler.cpp" />
|
||||
<ClCompile Include="ReverbFilter.cpp" />
|
||||
<ClCompile Include="RewindData.cpp" />
|
||||
<ClCompile Include="RewindManager.cpp" />
|
||||
<ClCompile Include="RomLoader.cpp" />
|
||||
<ClCompile Include="ShortcutKeyHandler.cpp" />
|
||||
<ClCompile Include="Snapshotable.cpp" />
|
||||
|
@ -89,6 +89,9 @@
|
||||
<Filter Include="Movies">
|
||||
<UniqueIdentifier>{16c0726b-0c54-4fbc-a602-7930348d7f44}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Rewinder">
|
||||
<UniqueIdentifier>{52b03b24-dd62-4daf-bac8-bb60a555d3d2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="IAudioDevice.h">
|
||||
@ -1162,6 +1165,12 @@
|
||||
<ClInclude Include="FceuxMovie.h">
|
||||
<Filter>Movies</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RewindData.h">
|
||||
<Filter>Rewinder</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RewindManager.h">
|
||||
<Filter>Rewinder</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
@ -1377,5 +1386,11 @@
|
||||
<ClCompile Include="FceuxMovie.cpp">
|
||||
<Filter>Movies</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RewindData.cpp">
|
||||
<Filter>Rewinder</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RewindManager.cpp">
|
||||
<Filter>Rewinder</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
|
@ -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::milliseconds>(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();
|
||||
|
@ -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.
|
||||
|
35
Core/RewindData.cpp
Normal file
35
Core/RewindData.cpp
Normal file
@ -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<uint8_t>& 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<uint8_t>(compressedData, compressedData + compressedSize);
|
||||
delete[] compressedData;
|
||||
}
|
||||
|
||||
void RewindData::SaveState()
|
||||
{
|
||||
std::stringstream state;
|
||||
Console::SaveState(state);
|
||||
|
||||
string stateData = state.str();
|
||||
vector<uint8_t> compressedState;
|
||||
CompressState(stateData, compressedState);
|
||||
SaveStateData = compressedState;
|
||||
OriginalSaveStateSize = (uint32_t)stateData.size();
|
||||
FrameCount = 0;
|
||||
}
|
19
Core/RewindData.h
Normal file
19
Core/RewindData.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
|
||||
class RewindData
|
||||
{
|
||||
private:
|
||||
vector<uint8_t> SaveStateData;
|
||||
uint32_t OriginalSaveStateSize;
|
||||
|
||||
void CompressState(string stateData, vector<uint8_t> &compressedState);
|
||||
|
||||
public:
|
||||
std::deque<uint8_t> InputLogs[4];
|
||||
int32_t FrameCount = 0;
|
||||
|
||||
void LoadState();
|
||||
void SaveState();
|
||||
};
|
285
Core/RewindManager.cpp
Normal file
285
Core/RewindManager.cpp
Normal file
@ -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>((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;
|
||||
}
|
58
Core/RewindManager.h
Normal file
58
Core/RewindManager.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#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<RewindData> _history;
|
||||
std::deque<RewindData> _historyBackup;
|
||||
RewindData _currentHistory;
|
||||
|
||||
RewindState _rewindState;
|
||||
int32_t _framesToFastForward;
|
||||
|
||||
std::deque<vector<uint32_t>> _videoHistory;
|
||||
vector<vector<uint32_t>> _videoHistoryBuilder;
|
||||
std::deque<int16_t> _audioHistory;
|
||||
vector<int16_t> _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);
|
||||
};
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<WaveRecorder> 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) {
|
||||
|
@ -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> 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 = _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<AviRecorder> 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 = _aviRecorder;
|
||||
if(aviRecorder) {
|
||||
aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoDecoder::StopRecording()
|
||||
{
|
||||
_aviRecorder.reset();
|
||||
}
|
||||
|
||||
bool VideoDecoder::IsRecording()
|
||||
{
|
||||
return _aviRecorder != nullptr && _aviRecorder->IsRecording();
|
||||
}
|
@ -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<thread> _decodeThread;
|
||||
shared_ptr<AviRecorder> _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();
|
||||
};
|
@ -1,6 +1,8 @@
|
||||
#include "stdafx.h"
|
||||
#include "IRenderingDevice.h"
|
||||
#include "VideoRenderer.h"
|
||||
#include "AviRecorder.h"
|
||||
#include "VideoDecoder.h"
|
||||
|
||||
unique_ptr<VideoRenderer> VideoRenderer::Instance;
|
||||
|
||||
@ -60,6 +62,11 @@ void VideoRenderer::RenderThread()
|
||||
|
||||
void VideoRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height)
|
||||
{
|
||||
shared_ptr<AviRecorder> 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<AviRecorder> 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 = _aviRecorder;
|
||||
if(aviRecorder) {
|
||||
aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRenderer::StopRecording()
|
||||
{
|
||||
_aviRecorder.reset();
|
||||
}
|
||||
|
||||
bool VideoRenderer::IsRecording()
|
||||
{
|
||||
return _aviRecorder != nullptr && _aviRecorder->IsRecording();
|
||||
}
|
@ -2,8 +2,11 @@
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
#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<bool> _stopFlag;
|
||||
|
||||
shared_ptr<AviRecorder> _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();
|
||||
};
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,9 @@
|
||||
<Message ID="NoMatchingCheats">The selected cheat file ({0}) contains no cheats that match the selected game.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Fast Forward</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rewind</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Rewind 10 seconds</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Rewind 1 minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Increase Speed</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Decrease Speed</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Pause</Message>
|
||||
|
@ -286,7 +286,9 @@
|
||||
<Control ID="lblTurboSpeedHint">% (0 = Velocidad máxima)</Control>
|
||||
<Control ID="lblTurboSpeed">Velocidad de avance rápido:</Control>
|
||||
<Control ID="lblEmulationSpeed">Velocidad de emulación:</Control>
|
||||
|
||||
<Control ID="lblRewindSpeedHint">% (0 = Velocidad máxima)</Control>
|
||||
<Control ID="lblRewindSpeed">Rewind Speed:</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">Avanzado</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">Utilizar la versión alternativa de componentes de IRQs de MMC3</Control>
|
||||
<Control ID="chkAllowInvalidInput">Permitir las entradas inválidas (Arriba+Abajo e Izquierda+Derecha al mismo tiempo)</Control>
|
||||
@ -358,6 +360,9 @@
|
||||
<Control ID="grpCloudSaves">Copia de seguridad online</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">Aumentar la velocidad de la emulación de juegos de carga FDS</Control>
|
||||
|
||||
<Control ID="lblRewind">Keep rewind data for the last</Control>
|
||||
<Control ID="lblRewindMinutes">minutes (Memory Usage ≈1MB/min)</Control>
|
||||
|
||||
<Control ID="tpgCloudSave">Guardar online</Control>
|
||||
<Control ID="lblGoogleDriveIntegration">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.</Control>
|
||||
<Control ID="lblIntegrationOK">La integración con Google Drive está activa.</Control>
|
||||
@ -578,6 +583,9 @@
|
||||
<Message ID="NoMatchingCheats">El archivo de trucos seleccionado ({0}) no contiene trucos que coincidan con el juego seleccionado.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Avance rápido</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rewind</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Rewind 10 seconds</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Rewind 1 minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Aumentar velocidad</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Reducir velocidad</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Pausa</Message>
|
||||
|
@ -286,7 +286,9 @@
|
||||
<Control ID="lblEmuSpeedHint">% (0 = Vitesse maximale)</Control>
|
||||
<Control ID="lblEmulationSpeed">Vitesse d'émulation :</Control>
|
||||
<Control ID="lblTurboSpeedHint">% (0 = Vitesse maximale)</Control>
|
||||
<Control ID="lblTurboSpeed">Vitesse d'avance rapide:</Control>
|
||||
<Control ID="lblTurboSpeed">Vitesse d'avance rapide :</Control>
|
||||
<Control ID="lblRewindSpeedHint">% (0 = Vitesse maximale)</Control>
|
||||
<Control ID="lblRewindSpeed">Vitesse du rembobinage :</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">Avancé</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">Utiliser la version alternative du comportement des IRQs du MMC3</Control>
|
||||
@ -350,6 +352,9 @@
|
||||
<Control ID="chkFdsAutoLoadDisk">Insérer le côté A du disque 1 lors du chargement d'un jeu de FDS</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">Augmenter la vitesse d'émulation pendant le chargement des jeux FDS</Control>
|
||||
|
||||
<Control ID="lblRewind">Permettre de rembobiner jusqu'à</Control>
|
||||
<Control ID="lblRewindMinutes">minutes (Utilise ≈1MB/min)</Control>
|
||||
|
||||
<Control ID="tpgShortcuts">Raccourcis</Control>
|
||||
<Control ID="colAction">Action</Control>
|
||||
<Control ID="colBinding1">Raccourci #1</Control>
|
||||
@ -593,6 +598,9 @@
|
||||
<Message ID="NoMatchingCheats">Le fichier sélectionné ({0}) ne contient aucun code correspondant au jeu sélectionné.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Avance rapide</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rembobiner</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Reculer de 10 secondes</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Reculer d'une minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Augmenter la vitesse</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Réduire la vitesse</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Pause</Message>
|
||||
|
@ -286,7 +286,9 @@
|
||||
<Control ID="lblEmuSpeedHint">% (0 = 最高速度)</Control>
|
||||
<Control ID="lblEmulationSpeed">エミュレーションの速度:</Control>
|
||||
<Control ID="lblTurboSpeedHint">% (0 = 最高速度)</Control>
|
||||
<Control ID="lblTurboSpeed">早送りの速度:</Control>
|
||||
<Control ID="lblTurboSpeed">早送りの速度:</Control>
|
||||
<Control ID="lblRewindSpeedHint">% (0 = 最高速度)</Control>
|
||||
<Control ID="lblRewindSpeed">巻き戻しの速度:</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">詳細設定</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">MMC3AのIRQ仕様を使う</Control>
|
||||
@ -349,6 +351,9 @@
|
||||
<Control ID="chkFdsAutoLoadDisk">ファミコンディスクシステムのゲームをロードする時に自動的にディスク1のA面を入れる</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">ファミコンディスクシステムのゲームをディスクからロードする時に自動的に最高速度にする</Control>
|
||||
|
||||
<Control ID="lblRewind">巻き戻し用データの</Control>
|
||||
<Control ID="lblRewindMinutes">分をキープする (メモリの使用量:1分に約1MB)</Control>
|
||||
|
||||
<Control ID="tpgShortcuts"> ショートカットキー </Control>
|
||||
<Control ID="colAction">機能</Control>
|
||||
<Control ID="colBinding1">ショートカットキー1</Control>
|
||||
@ -575,6 +580,9 @@
|
||||
<Message ID="NoMatchingCheats">このファイル({0})に選択されたゲームに該当するチートコードを見つかりませんでした。</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">早送り</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">巻き戻す</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">10秒前に戻る</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">1分前に戻る</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">速度を上げる</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">速度を下げる</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">ポーズ</Message>
|
||||
|
@ -286,7 +286,9 @@
|
||||
<Control ID="lblTurboSpeedHint">% (0 = Velocidade máxima)</Control>
|
||||
<Control ID="lblTurboSpeed">Velocidade de avanço rápido:</Control>
|
||||
<Control ID="lblEmulationSpeed">Velocidade de emulação:</Control>
|
||||
|
||||
<Control ID="lblRewindSpeedHint">% (0 = Velocidade máxima)</Control>
|
||||
<Control ID="lblRewindSpeed">Rewind Speed:</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">Avançado</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">Utilizar a versão alternativa de componentes de IRQs de MMC3</Control>
|
||||
<Control ID="chkAllowInvalidInput">Permitir as entradas inválidas (Cima+Baixo e Esquerda+Direita ao mesmo tempo)</Control>
|
||||
@ -357,6 +359,9 @@
|
||||
<Control ID="chkAutoSaveNotify">Mostrar uma notificação na tela ao fazer a cópia de segurança automática</Control>
|
||||
<Control ID="grpCloudSaves">Cópia de segurança online</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">Aumentar a velocidade da emulação de jogos ao carregar no FDS</Control>
|
||||
|
||||
<Control ID="lblRewind">Keep rewind data for the last</Control>
|
||||
<Control ID="lblRewindMinutes">minutes (Memory Usage ≈1MB/min)</Control>
|
||||
|
||||
<Control ID="tpgCloudSave">Salvar online</Control>
|
||||
<Control ID="lblGoogleDriveIntegration">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.</Control>
|
||||
@ -576,7 +581,11 @@
|
||||
<Message ID="InvalidCheatFile">O arquivo selecionado ({0}) não é um arquivo de cheats válido.</Message>
|
||||
<Message ID="InvalidXmlFile">O arquivo selecionado ({0}) não é um arquivo XML válido.</Message>
|
||||
<Message ID="NoMatchingCheats">O arquivo de cheats selecionado ({0}) não contém cheats que pertencem ao jogo selecionado.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Fast Forward</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rewind</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Rewind 10 seconds</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Rewind 1 minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Aumentar velocidade</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Reduzir velocidade</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Pausar</Message>
|
||||
|
@ -287,6 +287,8 @@
|
||||
<Control ID="lblEmulationSpeed">Скорость эмуляции :</Control>
|
||||
<Control ID="lblTurboSpeedHint">% (0 = Максимальная скорость)</Control>
|
||||
<Control ID="lblTurboSpeed">Перемотка:</Control>
|
||||
<Control ID="lblRewindSpeedHint">% (0 = Максимальная скорость)</Control>
|
||||
<Control ID="lblRewindSpeed">Rewind Speed:</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">Расширенные</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">Использовать альтернативный IRQ MMC3</Control>
|
||||
@ -349,6 +351,9 @@
|
||||
<Control ID="chkFdsAutoLoadDisk">Автоматически вставлять диск 1 стороной А при загрузке FDS</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">Использовать быструю загрузку FDS</Control>
|
||||
|
||||
<Control ID="lblRewind">Keep rewind data for the last</Control>
|
||||
<Control ID="lblRewindMinutes">minutes (Memory Usage ≈1MB/min)</Control>
|
||||
|
||||
<Control ID="tpgShortcuts">Горячие клавиши</Control>
|
||||
<Control ID="colAction">Действие</Control>
|
||||
<Control ID="colBinding1">Вариант #1</Control>
|
||||
@ -583,6 +588,9 @@
|
||||
<Message ID="NoMatchingCheats">Выбранный Cheat файл ({0}) не содержит читов для данной игры.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Перемотка</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rewind</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Rewind 10 seconds</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Rewind 1 minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Increase Speed</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Decrease Speed</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Пауза</Message>
|
||||
|
@ -287,6 +287,8 @@
|
||||
<Control ID="lblEmulationSpeed">Швидкість емуляції :</Control>
|
||||
<Control ID="lblTurboSpeedHint">% (0 = Максимальна швидкiсть)</Control>
|
||||
<Control ID="lblTurboSpeed">Перемотка:</Control>
|
||||
<Control ID="lblRewindSpeedHint">% (0 = Максимальна швидкiсть)</Control>
|
||||
<Control ID="lblRewindSpeed">Rewind Speed:</Control>
|
||||
|
||||
<Control ID="tpgAdvanced">Розширені</Control>
|
||||
<Control ID="chkUseAlternativeMmc3Irq">Використовувати альтернативний IRQ MMC3</Control>
|
||||
@ -349,6 +351,9 @@
|
||||
<Control ID="chkFdsAutoLoadDisk">Автоматично вставляти диск 1 стороною А при завантаженні FDS</Control>
|
||||
<Control ID="chkFdsFastForwardOnLoad">Використовувати швидке завантаження FDS</Control>
|
||||
|
||||
<Control ID="lblRewind">Keep rewind data for the last</Control>
|
||||
<Control ID="lblRewindMinutes">minutes (Memory Usage ≈1MB/min)</Control>
|
||||
|
||||
<Control ID="tpgShortcuts">Гарячі клавіші</Control>
|
||||
<Control ID="colAction">Дія</Control>
|
||||
<Control ID="colBinding1">Варiант #1</Control>
|
||||
@ -582,6 +587,9 @@
|
||||
<Message ID="NoMatchingCheats">Обраний Cheat файл ({0}) не містить читiв для даної гри.</Message>
|
||||
|
||||
<Message ID="EmulatorShortcutMappings_FastForward">Перемотка</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Rewind">Rewind</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindTenSecs">Rewind 10 seconds</Message>
|
||||
<Message ID="EmulatorShortcutMappings_RewindOneMin">Rewind 1 minute</Message>
|
||||
<Message ID="EmulatorShortcutMappings_IncreaseSpeed">Збільшити Швидкість</Message>
|
||||
<Message ID="EmulatorShortcutMappings_DecreaseSpeed">Зменшити Швидкість</Message>
|
||||
<Message ID="EmulatorShortcutMappings_Pause">Пауза</Message>
|
||||
|
77
GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs
generated
77
GUI.NET/Forms/Config/frmEmulationConfig.Designer.cs
generated
@ -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;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ namespace Mesen.GUI.Forms.Config
|
||||
|
||||
AddBinding("EmulationSpeed", nudEmulationSpeed);
|
||||
AddBinding("TurboSpeed", nudTurboSpeed);
|
||||
AddBinding("RewindSpeed", nudRewindSpeed);
|
||||
|
||||
AddBinding("UseAlternativeMmc3Irq", chkUseAlternativeMmc3Irq);
|
||||
AddBinding("AllowInvalidInput", chkAllowInvalidInput);
|
||||
|
263
GUI.NET/Forms/Config/frmPreferences.Designer.cs
generated
263
GUI.NET/Forms/Config/frmPreferences.Designer.cs
generated
@ -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;
|
||||
}
|
||||
}
|
@ -59,6 +59,8 @@ namespace Mesen.GUI.Forms.Config
|
||||
AddBinding("AutoHideMenu", chkAutoHideMenu);
|
||||
AddBinding("DisplayTitleBarInfo", chkDisplayTitleBarInfo);
|
||||
|
||||
AddBinding("RewindBufferSize", nudRewindBufferSize);
|
||||
|
||||
UpdateCloudDisplay();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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<ToastInfo> toast(new ToastInfo(title, message, 4000, ""));
|
||||
shared_ptr<ToastInfo> toast(new ToastInfo(title, message, 4000));
|
||||
_toasts.push_front(toast);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user