mirror of
https://github.com/libretro/Mesen.git
synced 2024-11-23 09:09:45 +00:00
Remove Rewind code and Movie code
This commit is contained in:
parent
8c77855c6b
commit
8836d84954
@ -1,225 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "ControlManager.h"
|
||||
#include "SystemActionManager.h"
|
||||
#include "FdsSystemActionManager.h"
|
||||
#include "VsSystemActionManager.h"
|
||||
#include "BizhawkMovie.h"
|
||||
#include "VsControlManager.h"
|
||||
#include "Console.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
BizhawkMovie::BizhawkMovie(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_originalPowerOnState = _console->GetSettings()->GetRamPowerOnState();
|
||||
}
|
||||
|
||||
BizhawkMovie::~BizhawkMovie()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void BizhawkMovie::Stop()
|
||||
{
|
||||
if(_isPlaying) {
|
||||
MessageManager::DisplayMessage("Movies", "MovieEnded");
|
||||
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::MovieEnded);
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::PauseOnMovieEnd)) {
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
}
|
||||
|
||||
_console->GetSettings()->SetRamPowerOnState(_originalPowerOnState);
|
||||
_isPlaying = false;
|
||||
}
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
}
|
||||
|
||||
bool BizhawkMovie::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
SystemActionManager* actionManager = dynamic_cast<SystemActionManager*>(device);
|
||||
int32_t pollCounter = _console->GetControlManager()->GetPollCounter();
|
||||
if(actionManager) {
|
||||
if(pollCounter < (int32_t)_systemActionByFrame.size()) {
|
||||
uint32_t systemAction = _systemActionByFrame[pollCounter];
|
||||
if(systemAction & 0x01) {
|
||||
actionManager->SetBit(SystemActionManager::Buttons::PowerButton);
|
||||
}
|
||||
if(systemAction & 0x02) {
|
||||
actionManager->SetBit(SystemActionManager::Buttons::ResetButton);
|
||||
}
|
||||
|
||||
VsSystemActionManager* vsActionManager = dynamic_cast<VsSystemActionManager*>(device);
|
||||
if(vsActionManager) {
|
||||
if(systemAction & 0x04) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin1);
|
||||
}
|
||||
if(systemAction & 0x08) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::InsertCoin2);
|
||||
}
|
||||
if(systemAction & 0x10) {
|
||||
actionManager->SetBit(VsSystemActionManager::VsButtons::ServiceButton);
|
||||
}
|
||||
}
|
||||
|
||||
FdsSystemActionManager* fdsActionManager = dynamic_cast<FdsSystemActionManager*>(device);
|
||||
if(fdsActionManager) {
|
||||
//FDS timings between NesHawk & Mesen are currently significantly different
|
||||
//So FDS games will always go out of sync
|
||||
if(systemAction & 0x04) {
|
||||
fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::EjectDiskButton);
|
||||
}
|
||||
|
||||
if(systemAction >= 8) {
|
||||
systemAction >>= 3;
|
||||
uint32_t diskNumber = 0;
|
||||
while(!(systemAction & 0x01)) {
|
||||
systemAction >>= 1;
|
||||
diskNumber++;
|
||||
}
|
||||
|
||||
fdsActionManager->SetBit(FdsSystemActionManager::FdsButtons::InsertDisk1 + diskNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int port = device->GetPort();
|
||||
StandardController* controller = dynamic_cast<StandardController*>(device);
|
||||
if(controller) {
|
||||
if(pollCounter < (int32_t)_dataByFrame[port].size()) {
|
||||
controller->SetTextState(_dataByFrame[port][pollCounter]);
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::InitializeGameData(ZipReader &reader)
|
||||
{
|
||||
stringstream fileData;
|
||||
if(!reader.GetStream("Header.txt", fileData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_console->GetControlManager()->SetPollCounter(0);
|
||||
|
||||
while(!fileData.eof()) {
|
||||
string line;
|
||||
std::getline(fileData, line);
|
||||
if(line.compare(0, 4, "SHA1", 4) == 0) {
|
||||
if(line.size() >= 45) {
|
||||
HashInfo hashInfo;
|
||||
hashInfo.Sha1 = line.substr(5, 40);
|
||||
if(_console->LoadMatchingRom("", hashInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if(line.compare(0, 3, "MD5", 3) == 0) {
|
||||
if(line.size() >= 36) {
|
||||
HashInfo hashInfo;
|
||||
hashInfo.PrgChrMd5 = line.substr(4, 32);
|
||||
std::transform(hashInfo.PrgChrMd5.begin(), hashInfo.PrgChrMd5.end(), hashInfo.PrgChrMd5.begin(), ::toupper);
|
||||
if(_console->LoadMatchingRom("", hashInfo)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::InitializeInputData(ZipReader &reader)
|
||||
{
|
||||
stringstream inputData;
|
||||
if(!reader.GetStream("Input Log.txt", inputData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int systemActionCount = 2;
|
||||
shared_ptr<FdsSystemActionManager> fdsActionManager = _console->GetSystemActionManager<FdsSystemActionManager>();
|
||||
if(fdsActionManager) {
|
||||
//Eject disk + Insert Disk #XX
|
||||
systemActionCount += fdsActionManager->GetSideCount() + 1;
|
||||
} else {
|
||||
shared_ptr<VsSystemActionManager> vsActionManager = _console->GetSystemActionManager<VsSystemActionManager>();
|
||||
if(vsActionManager) {
|
||||
//Insert coin 1, 2 + service button
|
||||
systemActionCount += 3;
|
||||
}
|
||||
}
|
||||
|
||||
while(!inputData.eof()) {
|
||||
string line;
|
||||
std::getline(inputData, line);
|
||||
|
||||
if(line.size() > 0 && line[0] == '|') {
|
||||
line.erase(std::remove(line.begin(), line.end(), '|'), line.end());
|
||||
line = line.substr(0, line.size() - 1);
|
||||
|
||||
//Read power/reset/FDS/VS/etc. commands
|
||||
uint32_t systemAction = 0;
|
||||
for(int i = 0; i < systemActionCount; i++) {
|
||||
if(line[i] != '.') {
|
||||
systemAction |= (1 << i);
|
||||
}
|
||||
}
|
||||
_systemActionByFrame.push_back(systemAction);
|
||||
|
||||
line = line.substr(systemActionCount);
|
||||
int port = 0;
|
||||
while(line.size() >= 8) {
|
||||
_dataByFrame[port].push_back(line.substr(0, 8));
|
||||
line = line.substr(8);
|
||||
port++;
|
||||
}
|
||||
while(port < 4) {
|
||||
_dataByFrame[port].push_back("........");
|
||||
port++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _dataByFrame[0].size() > 0;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::Play(VirtualFile &file)
|
||||
{
|
||||
_console->Pause();
|
||||
ZipReader reader;
|
||||
|
||||
std::stringstream ss;
|
||||
file.ReadFile(ss);
|
||||
|
||||
reader.LoadArchive(ss);
|
||||
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
_console->GetSettings()->SetRamPowerOnState(RamPowerOnState::AllOnes);
|
||||
_console->GetBatteryManager()->SetBatteryProvider(shared_from_this());
|
||||
if(InitializeInputData(reader) && InitializeGameData(reader)) {
|
||||
//NesHawk initializes memory to 1s
|
||||
_isPlaying = true;
|
||||
}
|
||||
_console->Resume();
|
||||
return _isPlaying;
|
||||
}
|
||||
|
||||
bool BizhawkMovie::IsPlaying()
|
||||
{
|
||||
return _isPlaying;
|
||||
}
|
||||
|
||||
void BizhawkMovie::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::GameLoaded) {
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint8_t> BizhawkMovie::LoadBattery(string extension)
|
||||
{
|
||||
return vector<uint8_t>();
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MovieManager.h"
|
||||
#include "../Utilities/ZipReader.h"
|
||||
#include "INotificationListener.h"
|
||||
#include "BatteryManager.h"
|
||||
|
||||
class VirtualFile;
|
||||
class Console;
|
||||
|
||||
class BizhawkMovie : public IMovie, public INotificationListener, public IBatteryProvider, public std::enable_shared_from_this<BizhawkMovie>
|
||||
{
|
||||
private:
|
||||
bool InitializeGameData(ZipReader &reader);
|
||||
bool InitializeInputData(ZipReader &reader);
|
||||
void Stop();
|
||||
|
||||
protected:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
vector<uint32_t> _systemActionByFrame;
|
||||
vector<string> _dataByFrame[4];
|
||||
bool _isPlaying = false;
|
||||
RamPowerOnState _originalPowerOnState;
|
||||
|
||||
public:
|
||||
BizhawkMovie(shared_ptr<Console>);
|
||||
virtual ~BizhawkMovie();
|
||||
|
||||
bool SetInput(BaseControlDevice *device) override;
|
||||
bool Play(VirtualFile &file) override;
|
||||
bool IsPlaying() override;
|
||||
|
||||
// Inherited via INotificationListener
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void * parameter) override;
|
||||
virtual vector<uint8_t> LoadBattery(string extension) override;
|
||||
};
|
@ -21,7 +21,6 @@
|
||||
#include "NsfPpu.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "NsfMapper.h"
|
||||
#include "MovieManager.h"
|
||||
#include "SaveStateManager.h"
|
||||
#include "HdPackBuilder.h"
|
||||
#include "HdAudioDevice.h"
|
||||
@ -39,7 +38,6 @@
|
||||
#include "VideoDecoder.h"
|
||||
#include "VideoRenderer.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "HistoryViewer.h"
|
||||
#include "ConsolePauseHelper.h"
|
||||
#include "EventManager.h"
|
||||
|
||||
@ -65,7 +63,6 @@ Console::Console(shared_ptr<Console> master, EmulationSettings* initialSettings)
|
||||
|
||||
Console::~Console()
|
||||
{
|
||||
MovieManager::Stop();
|
||||
}
|
||||
|
||||
void Console::Init()
|
||||
@ -266,7 +263,6 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile, bool forP
|
||||
_patchFilename = patchFile;
|
||||
|
||||
//Changed game, stop all recordings
|
||||
MovieManager::Stop();
|
||||
_soundMixer->StopRecording();
|
||||
StopRecordingHdPack();
|
||||
}
|
||||
@ -468,11 +464,6 @@ CheatManager* Console::GetCheatManager()
|
||||
return _cheatManager.get();
|
||||
}
|
||||
|
||||
HistoryViewer* Console::GetHistoryViewer()
|
||||
{
|
||||
return _historyViewer.get();
|
||||
}
|
||||
|
||||
VirtualFile Console::GetRomPath()
|
||||
{
|
||||
return static_cast<VirtualFile>(_romFilepath);
|
||||
@ -640,9 +631,6 @@ void Console::Run()
|
||||
RunSlaveCpu();
|
||||
}
|
||||
|
||||
if(_historyViewer) {
|
||||
_historyViewer->ProcessEndOfFrame();
|
||||
}
|
||||
_settings->DisableOverclocking(_disableOcNextFrame || IsNsf());
|
||||
_disableOcNextFrame = false;
|
||||
|
||||
@ -734,7 +722,6 @@ void Console::Run()
|
||||
|
||||
StopRecordingHdPack();
|
||||
|
||||
MovieManager::Stop();
|
||||
_soundMixer->StopRecording();
|
||||
|
||||
_settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "VirtualFile.h"
|
||||
|
||||
class BaseMapper;
|
||||
class HistoryViewer;
|
||||
class APU;
|
||||
class CPU;
|
||||
class PPU;
|
||||
@ -47,8 +46,6 @@ private:
|
||||
SimpleLock _stopLock;
|
||||
atomic<uint32_t> _pauseCounter;
|
||||
|
||||
shared_ptr<HistoryViewer> _historyViewer;
|
||||
|
||||
shared_ptr<CPU> _cpu;
|
||||
shared_ptr<PPU> _ppu;
|
||||
shared_ptr<APU> _apu;
|
||||
@ -127,7 +124,6 @@ public:
|
||||
ControlManager* GetControlManager();
|
||||
MemoryManager* GetMemoryManager();
|
||||
CheatManager* GetCheatManager();
|
||||
HistoryViewer* GetHistoryViewer();
|
||||
|
||||
bool LoadMatchingRom(string romName, HashInfo hashInfo);
|
||||
string FindMatchingRom(string romName, HashInfo hashInfo);
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include "BaseMapper.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "Console.h"
|
||||
#include "GameServerConnection.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "IKeyManager.h"
|
||||
#include "IInputProvider.h"
|
||||
@ -290,12 +289,6 @@ void ControlManager::UpdateInputState()
|
||||
//log += "|" + device->GetTextState();
|
||||
}
|
||||
|
||||
if(!_console->GetSettings()->IsRunAheadFrame()) {
|
||||
for(IInputRecorder* recorder : _inputRecorders) {
|
||||
recorder->RecordInput(_controlDevices);
|
||||
}
|
||||
}
|
||||
|
||||
//Used by VS System games
|
||||
RemapControllerButtons();
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <algorithm>
|
||||
#include "stdafx.h"
|
||||
#include "MessageManager.h"
|
||||
#include "GameClient.h"
|
||||
#include "KeyManager.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "FdsAudio.h"
|
||||
#include "MemoryManager.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "MovieManager.h"
|
||||
|
||||
void FDS::InitMapper()
|
||||
{
|
||||
@ -553,5 +552,5 @@ bool FDS::IsDiskInserted()
|
||||
|
||||
bool FDS::IsAutoInsertDiskEnabled()
|
||||
{
|
||||
return !_disableAutoInsertDisk && _settings->CheckFlag(EmulationFlags::FdsAutoInsertDisk) && !MovieManager::Playing() && !MovieManager::Recording();
|
||||
}
|
||||
return !_disableAutoInsertDisk && _settings->CheckFlag(EmulationFlags::FdsAutoInsertDisk);
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include <algorithm>
|
||||
#include "../Utilities/StringUtilities.h"
|
||||
#include "../Utilities/HexUtilities.h"
|
||||
#include "../Utilities/Base64.h"
|
||||
#include "ControlManager.h"
|
||||
#include "FceuxMovie.h"
|
||||
#include "Console.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
bool FceuxMovie::InitializeData(stringstream &filestream)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
_dataByFrame[0].push_back("");
|
||||
_dataByFrame[1].push_back("");
|
||||
_dataByFrame[2].push_back("");
|
||||
_dataByFrame[3].push_back("");
|
||||
|
||||
_console->GetControlManager()->SetPollCounter(0);
|
||||
|
||||
while(!filestream.eof()) {
|
||||
string line;
|
||||
std::getline(filestream, line);
|
||||
if(line.compare(0, 19, "romChecksum base64:", 19) == 0) {
|
||||
vector<uint8_t> md5array = Base64::Decode(line.substr(19, line.size() - 20));
|
||||
HashInfo hashInfo;
|
||||
hashInfo.PrgChrMd5 = HexUtilities::ToHex(md5array);
|
||||
_console->GetSettings()->SetRamPowerOnState(RamPowerOnState::AllZeros);
|
||||
if(_console->LoadMatchingRom("", hashInfo)) {
|
||||
result = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if(line.size() > 0 && line[0] == '|') {
|
||||
vector<string> lineData = StringUtilities::Split(line.substr(1), '|');
|
||||
|
||||
if(lineData.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Read power/reset/FDS/VS/etc. commands
|
||||
uint32_t systemAction = 0;
|
||||
try {
|
||||
systemAction = (uint32_t)std::atol(lineData[0].c_str());
|
||||
} catch(std::exception&) {
|
||||
}
|
||||
_systemActionByFrame.push_back(systemAction);
|
||||
|
||||
//Only supports regular controllers (up to 4 of them)
|
||||
for(size_t i = 1; i < lineData.size() && i < 5; i++) {
|
||||
if(lineData[i].size() >= 8) {
|
||||
string data = lineData[i].substr(3, 1) + lineData[i].substr(2, 1) + lineData[i].substr(1, 1) + lineData[i].substr(0, 1);
|
||||
_dataByFrame[i - 1].push_back(data + lineData[i].substr(4, 4));
|
||||
} else {
|
||||
_dataByFrame[i - 1].push_back("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FceuxMovie::Play(VirtualFile &file)
|
||||
{
|
||||
_console->Pause();
|
||||
|
||||
std::stringstream ss;
|
||||
file.ReadFile(ss);
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
_console->GetBatteryManager()->SetBatteryProvider(shared_from_this());
|
||||
if(InitializeData(ss)) {
|
||||
_console->Reset(false);
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
_console->Resume();
|
||||
return _isPlaying;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/ZipReader.h"
|
||||
#include "MovieManager.h"
|
||||
#include "BizhawkMovie.h"
|
||||
|
||||
class FceuxMovie : public BizhawkMovie
|
||||
{
|
||||
private:
|
||||
bool InitializeData(stringstream &filestream);
|
||||
|
||||
public:
|
||||
using BizhawkMovie::BizhawkMovie;
|
||||
|
||||
bool Play(VirtualFile &file) override;
|
||||
};
|
@ -1,112 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
using std::thread;
|
||||
|
||||
#include "MessageManager.h"
|
||||
#include "GameClient.h"
|
||||
#include "Console.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "../Utilities/Socket.h"
|
||||
#include "ClientConnectionData.h"
|
||||
#include "GameClientConnection.h"
|
||||
|
||||
shared_ptr<GameClient> GameClient::_instance;
|
||||
|
||||
GameClient::GameClient(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_stop = false;
|
||||
}
|
||||
|
||||
GameClient::~GameClient()
|
||||
{
|
||||
_stop = true;
|
||||
if(_clientThread) {
|
||||
_clientThread->join();
|
||||
}
|
||||
}
|
||||
|
||||
bool GameClient::Connected()
|
||||
{
|
||||
shared_ptr<GameClient> instance = _instance;
|
||||
return instance ? instance->_connected : false;
|
||||
}
|
||||
|
||||
void GameClient::Connect(shared_ptr<Console> console, ClientConnectionData &connectionData)
|
||||
{
|
||||
_instance.reset(new GameClient(console));
|
||||
console->GetNotificationManager()->RegisterNotificationListener(_instance);
|
||||
|
||||
shared_ptr<GameClient> instance = _instance;
|
||||
if(instance) {
|
||||
instance->PrivateConnect(connectionData);
|
||||
instance->_clientThread.reset(new thread(&GameClient::Exec, instance.get()));
|
||||
}
|
||||
}
|
||||
|
||||
void GameClient::Disconnect()
|
||||
{
|
||||
_instance.reset();
|
||||
}
|
||||
|
||||
shared_ptr<GameClientConnection> GameClient::GetConnection()
|
||||
{
|
||||
shared_ptr<GameClient> instance = _instance;
|
||||
return instance ? instance->_connection : nullptr;
|
||||
}
|
||||
|
||||
void GameClient::PrivateConnect(ClientConnectionData &connectionData)
|
||||
{
|
||||
_stop = false;
|
||||
shared_ptr<Socket> socket(new Socket());
|
||||
if(socket->Connect(connectionData.Host.c_str(), connectionData.Port)) {
|
||||
_connection.reset(new GameClientConnection(_console, socket, connectionData));
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(_connection);
|
||||
_connected = true;
|
||||
} else {
|
||||
MessageManager::DisplayMessage("NetPlay", "CouldNotConnect");
|
||||
_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GameClient::Exec()
|
||||
{
|
||||
if(_connected) {
|
||||
while(!_stop) {
|
||||
if(!_connection->ConnectionError()) {
|
||||
_connection->ProcessMessages();
|
||||
_connection->SendInput();
|
||||
} else {
|
||||
_connected = false;
|
||||
_connection->Shutdown();
|
||||
_connection.reset();
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameClient::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
}
|
||||
|
||||
void GameClient::SelectController(uint8_t port)
|
||||
{
|
||||
shared_ptr<GameClientConnection> connection = GetConnection();
|
||||
if(connection) {
|
||||
connection->SelectController(port);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GameClient::GetAvailableControllers()
|
||||
{
|
||||
shared_ptr<GameClientConnection> connection = GetConnection();
|
||||
return connection ? connection->GetAvailableControllers() : 0;
|
||||
}
|
||||
|
||||
uint8_t GameClient::GetControllerPort()
|
||||
{
|
||||
shared_ptr<GameClientConnection> connection = GetConnection();
|
||||
return connection ? connection->GetControllerPort() : GameConnection::SpectatorPort;
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
#include "INotificationListener.h"
|
||||
|
||||
using std::thread;
|
||||
class Socket;
|
||||
class GameClientConnection;
|
||||
class ClientConnectionData;
|
||||
class Console;
|
||||
|
||||
class GameClient : public INotificationListener
|
||||
{
|
||||
private:
|
||||
static shared_ptr<GameClient> _instance;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
unique_ptr<thread> _clientThread;
|
||||
atomic<bool> _stop;
|
||||
|
||||
shared_ptr<GameClientConnection> _connection;
|
||||
bool _connected = false;
|
||||
|
||||
static shared_ptr<GameClientConnection> GetConnection();
|
||||
|
||||
void PrivateConnect(ClientConnectionData &connectionData);
|
||||
void Exec();
|
||||
|
||||
public:
|
||||
GameClient(shared_ptr<Console> console);
|
||||
virtual ~GameClient();
|
||||
|
||||
static bool Connected();
|
||||
static void Connect(shared_ptr<Console> console, ClientConnectionData &connectionData);
|
||||
static void Disconnect();
|
||||
|
||||
static void SelectController(uint8_t port);
|
||||
static uint8_t GetControllerPort();
|
||||
static uint8_t GetAvailableControllers();
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
};
|
@ -1,270 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "GameClientConnection.h"
|
||||
#include "HandShakeMessage.h"
|
||||
#include "InputDataMessage.h"
|
||||
#include "MovieDataMessage.h"
|
||||
#include "GameInformationMessage.h"
|
||||
#include "SaveStateMessage.h"
|
||||
#include "Console.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "ControlManager.h"
|
||||
#include "ClientConnectionData.h"
|
||||
#include "StandardController.h"
|
||||
#include "Zapper.h"
|
||||
#include "ArkanoidController.h"
|
||||
#include "BandaiHyperShot.h"
|
||||
#include "SelectControllerMessage.h"
|
||||
#include "PlayerListMessage.h"
|
||||
#include "ForceDisconnectMessage.h"
|
||||
#include "ServerInformationMessage.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
GameClientConnection::GameClientConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, ClientConnectionData &connectionData) : GameConnection(console, socket)
|
||||
{
|
||||
_connectionData = connectionData;
|
||||
_shutdown = false;
|
||||
_enableControllers = false;
|
||||
_minimumQueueSize = 3;
|
||||
|
||||
MessageManager::DisplayMessage("NetPlay", "ConnectedToServer");
|
||||
}
|
||||
|
||||
GameClientConnection::~GameClientConnection()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void GameClientConnection::Shutdown()
|
||||
{
|
||||
if(!_shutdown) {
|
||||
_shutdown = true;
|
||||
DisableControllers();
|
||||
|
||||
ControlManager* controlManager = _console->GetControlManager();
|
||||
if(controlManager) {
|
||||
controlManager->UnregisterInputProvider(this);
|
||||
}
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::DisconnectedFromServer);
|
||||
MessageManager::DisplayMessage("NetPlay", "ConnectionLost");
|
||||
_console->GetSettings()->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::SendHandshake()
|
||||
{
|
||||
HandShakeMessage message(_connectionData.PlayerName, HandShakeMessage::GetPasswordHash(_connectionData.Password, _serverSalt), _connectionData.Spectator);
|
||||
SendNetMessage(message);
|
||||
}
|
||||
|
||||
void GameClientConnection::SendControllerSelection(uint8_t port)
|
||||
{
|
||||
SelectControllerMessage message(port);
|
||||
SendNetMessage(message);
|
||||
}
|
||||
|
||||
void GameClientConnection::ClearInputData()
|
||||
{
|
||||
LockHandler lock = _writeLock.AcquireSafe();
|
||||
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
|
||||
_inputSize[i] = 0;
|
||||
_inputData[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::ProcessMessage(NetMessage* message)
|
||||
{
|
||||
GameInformationMessage* gameInfo;
|
||||
|
||||
switch(message->GetType()) {
|
||||
case MessageType::ServerInformation:
|
||||
_serverSalt = ((ServerInformationMessage*)message)->GetHashSalt();
|
||||
SendHandshake();
|
||||
break;
|
||||
|
||||
case MessageType::SaveState:
|
||||
if(_gameLoaded) {
|
||||
DisableControllers();
|
||||
_console->Pause();
|
||||
ClearInputData();
|
||||
((SaveStateMessage*)message)->LoadState(_console);
|
||||
_enableControllers = true;
|
||||
InitControlDevice();
|
||||
_console->Resume();
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageType::MovieData:
|
||||
if(_gameLoaded) {
|
||||
PushControllerState(((MovieDataMessage*)message)->GetPortNumber(), ((MovieDataMessage*)message)->GetInputState());
|
||||
}
|
||||
break;
|
||||
|
||||
case MessageType::ForceDisconnect:
|
||||
MessageManager::DisplayMessage("NetPlay", ((ForceDisconnectMessage*)message)->GetMessage());
|
||||
break;
|
||||
|
||||
case MessageType::PlayerList:
|
||||
_playerList = ((PlayerListMessage*)message)->GetPlayerList();
|
||||
break;
|
||||
|
||||
case MessageType::GameInformation:
|
||||
DisableControllers();
|
||||
_console->Pause();
|
||||
gameInfo = (GameInformationMessage*)message;
|
||||
if(gameInfo->GetPort() != _controllerPort) {
|
||||
_controllerPort = gameInfo->GetPort();
|
||||
|
||||
if(_controllerPort == GameConnection::SpectatorPort) {
|
||||
MessageManager::DisplayMessage("NetPlay", "ConnectedAsSpectator");
|
||||
} else {
|
||||
MessageManager::DisplayMessage("NetPlay", "ConnectedAsPlayer", std::to_string(_controllerPort + 1));
|
||||
}
|
||||
}
|
||||
|
||||
ClearInputData();
|
||||
|
||||
_gameLoaded = AttemptLoadGame(gameInfo->GetRomFilename(), gameInfo->GetCrc32Hash());
|
||||
if(gameInfo->IsPaused()) {
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
} else {
|
||||
_console->GetSettings()->ClearFlags(EmulationFlags::Paused);
|
||||
}
|
||||
_console->Resume();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameClientConnection::AttemptLoadGame(string filename, uint32_t crc32Hash)
|
||||
{
|
||||
if(filename.size() > 0) {
|
||||
HashInfo hashInfo;
|
||||
hashInfo.Crc32 = crc32Hash;
|
||||
if(_console->LoadMatchingRom(filename, hashInfo)) {
|
||||
return true;
|
||||
} else {
|
||||
MessageManager::DisplayMessage("NetPlay", "CouldNotFindRom");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameClientConnection::PushControllerState(uint8_t port, ControlDeviceState state)
|
||||
{
|
||||
LockHandler lock = _writeLock.AcquireSafe();
|
||||
_inputData[port].push_back(state);
|
||||
_inputSize[port]++;
|
||||
|
||||
if(_inputData[port].size() >= _minimumQueueSize) {
|
||||
_waitForInput[port].Signal();
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::DisableControllers()
|
||||
{
|
||||
//Used to prevent deadlocks when client is trying to fill its buffer while the host changes the current game/settings/etc. (i.e situations where we need to call Console::Pause())
|
||||
ClearInputData();
|
||||
_enableControllers = false;
|
||||
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
|
||||
_waitForInput[i].Signal();
|
||||
}
|
||||
}
|
||||
|
||||
bool GameClientConnection::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
if(_enableControllers) {
|
||||
uint8_t port = device->GetPort();
|
||||
while(_inputSize[port] == 0) {
|
||||
_waitForInput[port].Wait();
|
||||
|
||||
if(port == 0 && _minimumQueueSize < 10) {
|
||||
//Increase buffer size - reduces freezes at the cost of additional lag
|
||||
_minimumQueueSize++;
|
||||
}
|
||||
|
||||
if(_shutdown || !_enableControllers) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LockHandler lock = _writeLock.AcquireSafe();
|
||||
ControlDeviceState state = _inputData[port].front();
|
||||
_inputData[port].pop_front();
|
||||
_inputSize[port]--;
|
||||
|
||||
if(_inputData[port].size() > _minimumQueueSize) {
|
||||
//Too much data, catch up
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::ForceMaxSpeed);
|
||||
} else {
|
||||
_console->GetSettings()->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
_console->GetSettings()->SetEmulationSpeed(100);
|
||||
}
|
||||
|
||||
device->SetRawState(state);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameClientConnection::InitControlDevice()
|
||||
{
|
||||
if(_controllerPort == BaseControlDevice::ExpDevicePort) {
|
||||
_newControlDevice = ControlManager::CreateExpansionDevice(_console->GetSettings()->GetExpansionDevice(), _console);
|
||||
} else {
|
||||
//Pretend we are using port 0 (to use player 1's keybindings during netplay)
|
||||
_newControlDevice = ControlManager::CreateControllerDevice(_console->GetSettings()->GetControllerType(_controllerPort), 0, _console);
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::ConfigChanged) {
|
||||
InitControlDevice();
|
||||
} else if(type == ConsoleNotificationType::GameLoaded) {
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::SendInput()
|
||||
{
|
||||
if(_gameLoaded) {
|
||||
if(_newControlDevice) {
|
||||
_controlDevice = _newControlDevice;
|
||||
_newControlDevice.reset();
|
||||
}
|
||||
|
||||
ControlDeviceState inputState;
|
||||
if(_controlDevice) {
|
||||
_controlDevice->SetStateFromInput();
|
||||
inputState = _controlDevice->GetRawState();
|
||||
}
|
||||
|
||||
if(_lastInputSent != inputState) {
|
||||
InputDataMessage message(inputState);
|
||||
SendNetMessage(message);
|
||||
_lastInputSent = inputState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameClientConnection::SelectController(uint8_t port)
|
||||
{
|
||||
SendControllerSelection(port);
|
||||
}
|
||||
|
||||
uint8_t GameClientConnection::GetAvailableControllers()
|
||||
{
|
||||
uint8_t availablePorts = (1 << BaseControlDevice::PortCount) - 1;
|
||||
for(PlayerInfo &playerInfo : _playerList) {
|
||||
if(playerInfo.ControllerPort < BaseControlDevice::PortCount) {
|
||||
availablePorts &= ~(1 << playerInfo.ControllerPort);
|
||||
}
|
||||
}
|
||||
return availablePorts;
|
||||
}
|
||||
|
||||
uint8_t GameClientConnection::GetControllerPort()
|
||||
{
|
||||
return _controllerPort;
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "GameConnection.h"
|
||||
#include "../Utilities/AutoResetEvent.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "IInputProvider.h"
|
||||
#include "ControlDeviceState.h"
|
||||
#include "ClientConnectionData.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class GameClientConnection : public GameConnection, public INotificationListener, public IInputProvider
|
||||
{
|
||||
private:
|
||||
std::deque<ControlDeviceState> _inputData[BaseControlDevice::PortCount];
|
||||
atomic<uint32_t> _inputSize[BaseControlDevice::PortCount];
|
||||
AutoResetEvent _waitForInput[BaseControlDevice::PortCount];
|
||||
SimpleLock _writeLock;
|
||||
atomic<bool> _shutdown;
|
||||
atomic<bool> _enableControllers;
|
||||
atomic<uint32_t> _minimumQueueSize;
|
||||
|
||||
vector<PlayerInfo> _playerList;
|
||||
|
||||
shared_ptr<BaseControlDevice> _controlDevice;
|
||||
shared_ptr<BaseControlDevice> _newControlDevice;
|
||||
ControlDeviceState _lastInputSent;
|
||||
bool _gameLoaded = false;
|
||||
uint8_t _controllerPort = GameConnection::SpectatorPort;
|
||||
ClientConnectionData _connectionData;
|
||||
string _serverSalt;
|
||||
|
||||
private:
|
||||
void SendHandshake();
|
||||
void SendControllerSelection(uint8_t port);
|
||||
void ClearInputData();
|
||||
void PushControllerState(uint8_t port, ControlDeviceState state);
|
||||
void DisableControllers();
|
||||
bool AttemptLoadGame(string filename, uint32_t crc32Hash);
|
||||
|
||||
protected:
|
||||
void ProcessMessage(NetMessage* message) override;
|
||||
|
||||
public:
|
||||
GameClientConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, ClientConnectionData &connectionData);
|
||||
virtual ~GameClientConnection();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
|
||||
bool SetInput(BaseControlDevice *device) override;
|
||||
void InitControlDevice();
|
||||
void SendInput();
|
||||
|
||||
void SelectController(uint8_t port);
|
||||
uint8_t GetAvailableControllers();
|
||||
uint8_t GetControllerPort();
|
||||
};
|
@ -1,99 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "GameConnection.h"
|
||||
#include "HandShakeMessage.h"
|
||||
#include "InputDataMessage.h"
|
||||
#include "MovieDataMessage.h"
|
||||
#include "GameInformationMessage.h"
|
||||
#include "SaveStateMessage.h"
|
||||
#include "PlayerListMessage.h"
|
||||
#include "SelectControllerMessage.h"
|
||||
#include "ClientConnectionData.h"
|
||||
#include "ForceDisconnectMessage.h"
|
||||
#include "ServerInformationMessage.h"
|
||||
|
||||
GameConnection::GameConnection(shared_ptr<Console> console, shared_ptr<Socket> socket)
|
||||
{
|
||||
_console = console;
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
void GameConnection::ReadSocket()
|
||||
{
|
||||
auto lock = _socketLock.AcquireSafe();
|
||||
int bytesReceived = _socket->Recv((char*)_readBuffer + _readPosition, 0x40000 - _readPosition, 0);
|
||||
if(bytesReceived > 0) {
|
||||
_readPosition += bytesReceived;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameConnection::ExtractMessage(void *buffer, uint32_t &messageLength)
|
||||
{
|
||||
messageLength = _readBuffer[0] | (_readBuffer[1] << 8) | (_readBuffer[2] << 16) | (_readBuffer[3] << 24);
|
||||
|
||||
if(messageLength > 1000000) {
|
||||
MessageManager::Log("[Netplay] Invalid data received, closing connection.");
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
int packetLength = messageLength + sizeof(messageLength);
|
||||
|
||||
if(_readPosition >= packetLength) {
|
||||
memcpy(buffer, _readBuffer+sizeof(messageLength), messageLength);
|
||||
memmove(_readBuffer, _readBuffer + packetLength, _readPosition - packetLength);
|
||||
_readPosition -= packetLength;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NetMessage* GameConnection::ReadMessage()
|
||||
{
|
||||
ReadSocket();
|
||||
|
||||
if(_readPosition > 4) {
|
||||
uint32_t messageLength;
|
||||
if(ExtractMessage(_messageBuffer, messageLength)) {
|
||||
switch((MessageType)_messageBuffer[0]) {
|
||||
case MessageType::HandShake: return new HandShakeMessage(_messageBuffer, messageLength);
|
||||
case MessageType::SaveState: return new SaveStateMessage(_messageBuffer, messageLength);
|
||||
case MessageType::InputData: return new InputDataMessage(_messageBuffer, messageLength);
|
||||
case MessageType::MovieData: return new MovieDataMessage(_messageBuffer, messageLength);
|
||||
case MessageType::GameInformation: return new GameInformationMessage(_messageBuffer, messageLength);
|
||||
case MessageType::PlayerList: return new PlayerListMessage(_messageBuffer, messageLength);
|
||||
case MessageType::SelectController: return new SelectControllerMessage(_messageBuffer, messageLength);
|
||||
case MessageType::ForceDisconnect: return new ForceDisconnectMessage(_messageBuffer, messageLength);
|
||||
case MessageType::ServerInformation: return new ServerInformationMessage(_messageBuffer, messageLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GameConnection::SendNetMessage(NetMessage &message)
|
||||
{
|
||||
auto lock = _socketLock.AcquireSafe();
|
||||
message.Send(*_socket.get());
|
||||
}
|
||||
|
||||
void GameConnection::Disconnect()
|
||||
{
|
||||
auto lock = _socketLock.AcquireSafe();
|
||||
_socket->Close();
|
||||
}
|
||||
|
||||
bool GameConnection::ConnectionError()
|
||||
{
|
||||
return _socket->ConnectionError();
|
||||
}
|
||||
|
||||
void GameConnection::ProcessMessages()
|
||||
{
|
||||
NetMessage* message;
|
||||
while((message = ReadMessage()) != nullptr) {
|
||||
//Loop until all messages have been processed
|
||||
message->Initialize();
|
||||
ProcessMessage(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/SimpleLock.h"
|
||||
|
||||
class Socket;
|
||||
class NetMessage;
|
||||
class Console;
|
||||
|
||||
struct PlayerInfo
|
||||
{
|
||||
string Name;
|
||||
uint8_t ControllerPort;
|
||||
bool IsHost;
|
||||
};
|
||||
|
||||
class GameConnection
|
||||
{
|
||||
protected:
|
||||
shared_ptr<Socket> _socket;
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
uint8_t _readBuffer[0x40000] = {};
|
||||
uint8_t _messageBuffer[0x40000] = {};
|
||||
int _readPosition = 0;
|
||||
SimpleLock _socketLock;
|
||||
|
||||
private:
|
||||
void ReadSocket();
|
||||
|
||||
bool ExtractMessage(void *buffer, uint32_t &messageLength);
|
||||
NetMessage* ReadMessage();
|
||||
|
||||
virtual void ProcessMessage(NetMessage* message) = 0;
|
||||
|
||||
protected:
|
||||
void Disconnect();
|
||||
|
||||
public:
|
||||
static constexpr uint8_t SpectatorPort = 0xFF;
|
||||
GameConnection(shared_ptr<Console> console, shared_ptr<Socket> socket);
|
||||
|
||||
bool ConnectionError();
|
||||
void ProcessMessages();
|
||||
void SendNetMessage(NetMessage &message);
|
||||
};
|
@ -1,242 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
using std::thread;
|
||||
|
||||
#include "MessageManager.h"
|
||||
#include "GameServer.h"
|
||||
#include "Console.h"
|
||||
#include "ControlManager.h"
|
||||
#include "../Utilities/Socket.h"
|
||||
#include "PlayerListMessage.h"
|
||||
#include "NotificationManager.h"
|
||||
|
||||
shared_ptr<GameServer> GameServer::Instance;
|
||||
|
||||
GameServer::GameServer(shared_ptr<Console> console, uint16_t listenPort, string password, string hostPlayerName)
|
||||
{
|
||||
_console = console;
|
||||
_stop = false;
|
||||
_port = listenPort;
|
||||
_password = password;
|
||||
_hostPlayerName = hostPlayerName;
|
||||
_hostControllerPort = 0;
|
||||
|
||||
//If a game is already running, register ourselves as an input recorder/provider right away
|
||||
RegisterServerInput();
|
||||
}
|
||||
|
||||
GameServer::~GameServer()
|
||||
{
|
||||
_stop = true;
|
||||
_serverThread->join();
|
||||
|
||||
Stop();
|
||||
|
||||
ControlManager* controlManager = _console->GetControlManager();
|
||||
if(controlManager) {
|
||||
controlManager->UnregisterInputRecorder(this);
|
||||
controlManager->UnregisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::RegisterServerInput()
|
||||
{
|
||||
ControlManager* controlManager = _console->GetControlManager();
|
||||
if(controlManager) {
|
||||
controlManager->RegisterInputRecorder(this);
|
||||
controlManager->RegisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::AcceptConnections()
|
||||
{
|
||||
while(true) {
|
||||
shared_ptr<Socket> socket = _listener->Accept();
|
||||
if(!socket->ConnectionError()) {
|
||||
auto connection = shared_ptr<GameServerConnection>(new GameServerConnection(_console, socket, _password));
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(connection);
|
||||
_openConnections.push_back(connection);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_listener->Listen(10);
|
||||
}
|
||||
|
||||
void GameServer::UpdateConnections()
|
||||
{
|
||||
vector<shared_ptr<GameServerConnection>> connectionsToRemove;
|
||||
for(shared_ptr<GameServerConnection> connection : _openConnections) {
|
||||
if(connection->ConnectionError()) {
|
||||
connectionsToRemove.push_back(connection);
|
||||
} else {
|
||||
connection->ProcessMessages();
|
||||
}
|
||||
}
|
||||
|
||||
for(shared_ptr<GameServerConnection> gameConnection : connectionsToRemove) {
|
||||
_openConnections.remove(gameConnection);
|
||||
}
|
||||
}
|
||||
|
||||
list<shared_ptr<GameServerConnection>> GameServer::GetConnectionList()
|
||||
{
|
||||
if(GameServer::Started()) {
|
||||
return Instance->_openConnections;
|
||||
} else {
|
||||
return list<shared_ptr<GameServerConnection>>();
|
||||
}
|
||||
}
|
||||
|
||||
bool GameServer::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
uint8_t port = device->GetPort();
|
||||
|
||||
GameServerConnection* connection = GameServerConnection::GetNetPlayDevice(port);
|
||||
if(connection) {
|
||||
//Device is controlled by a client
|
||||
device->SetRawState(connection->GetState());
|
||||
return true;
|
||||
}
|
||||
|
||||
//Host is controlling this device
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameServer::RecordInput(vector<shared_ptr<BaseControlDevice>> devices)
|
||||
{
|
||||
for(shared_ptr<BaseControlDevice> &device : devices) {
|
||||
for(shared_ptr<GameServerConnection> connection : _openConnections) {
|
||||
if(!connection->ConnectionError()) {
|
||||
//Send movie stream
|
||||
connection->SendMovieData(device->GetPort(), device->GetRawState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::ProcessNotification(ConsoleNotificationType type, void * parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::GameLoaded) {
|
||||
//Register the server as an input provider/recorder
|
||||
RegisterServerInput();
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::Exec()
|
||||
{
|
||||
_listener.reset(new Socket());
|
||||
_listener->Bind(_port);
|
||||
_listener->Listen(10);
|
||||
_stop = false;
|
||||
_initialized = true;
|
||||
MessageManager::DisplayMessage("NetPlay" , "ServerStarted", std::to_string(_port));
|
||||
|
||||
while(!_stop) {
|
||||
AcceptConnections();
|
||||
UpdateConnections();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::Stop()
|
||||
{
|
||||
_initialized = false;
|
||||
_listener.reset();
|
||||
MessageManager::DisplayMessage("NetPlay", "ServerStopped");
|
||||
}
|
||||
|
||||
void GameServer::StartServer(shared_ptr<Console> console, uint16_t port, string password, string hostPlayerName)
|
||||
{
|
||||
Instance.reset(new GameServer(console, port, password, hostPlayerName));
|
||||
console->GetNotificationManager()->RegisterNotificationListener(Instance);
|
||||
Instance->_serverThread.reset(new thread(&GameServer::Exec, Instance.get()));
|
||||
}
|
||||
|
||||
void GameServer::StopServer()
|
||||
{
|
||||
if(Instance) {
|
||||
Instance.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool GameServer::Started()
|
||||
{
|
||||
if(Instance) {
|
||||
return Instance->_initialized;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string GameServer::GetHostPlayerName()
|
||||
{
|
||||
if(GameServer::Started()) {
|
||||
return Instance->_hostPlayerName;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
uint8_t GameServer::GetHostControllerPort()
|
||||
{
|
||||
if(GameServer::Started()) {
|
||||
return Instance->_hostControllerPort;
|
||||
}
|
||||
return GameConnection::SpectatorPort;
|
||||
}
|
||||
|
||||
void GameServer::SetHostControllerPort(uint8_t port)
|
||||
{
|
||||
if(GameServer::Started()) {
|
||||
Instance->_console->Pause();
|
||||
if(port == GameConnection::SpectatorPort || GetAvailableControllers() & (1 << port)) {
|
||||
//Port is available
|
||||
Instance->_hostControllerPort = port;
|
||||
SendPlayerList();
|
||||
}
|
||||
Instance->_console->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GameServer::GetAvailableControllers()
|
||||
{
|
||||
uint8_t availablePorts = (1 << BaseControlDevice::PortCount) - 1;
|
||||
for(PlayerInfo &playerInfo : GetPlayerList()) {
|
||||
if(playerInfo.ControllerPort < BaseControlDevice::PortCount) {
|
||||
availablePorts &= ~(1 << playerInfo.ControllerPort);
|
||||
}
|
||||
}
|
||||
return availablePorts;
|
||||
}
|
||||
|
||||
vector<PlayerInfo> GameServer::GetPlayerList()
|
||||
{
|
||||
vector<PlayerInfo> playerList;
|
||||
|
||||
PlayerInfo playerInfo;
|
||||
playerInfo.Name = GetHostPlayerName();
|
||||
playerInfo.ControllerPort = GetHostControllerPort();
|
||||
playerInfo.IsHost = true;
|
||||
playerList.push_back(playerInfo);
|
||||
|
||||
for(shared_ptr<GameServerConnection> &connection : GetConnectionList()) {
|
||||
playerInfo.Name = connection->GetPlayerName();
|
||||
playerInfo.ControllerPort = connection->GetControllerPort();
|
||||
playerInfo.IsHost = false;
|
||||
playerList.push_back(playerInfo);
|
||||
}
|
||||
|
||||
return playerList;
|
||||
}
|
||||
|
||||
void GameServer::SendPlayerList()
|
||||
{
|
||||
vector<PlayerInfo> playerList = GetPlayerList();
|
||||
|
||||
for(shared_ptr<GameServerConnection> &connection : GetConnectionList()) {
|
||||
//Send player list update to all connections
|
||||
PlayerListMessage message(playerList);
|
||||
connection->SendNetMessage(message);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <thread>
|
||||
#include "GameServerConnection.h"
|
||||
#include "INotificationListener.h"
|
||||
#include "IInputProvider.h"
|
||||
#include "IInputRecorder.h"
|
||||
|
||||
using std::thread;
|
||||
class Console;
|
||||
|
||||
class GameServer : public IInputRecorder, public IInputProvider, public INotificationListener
|
||||
{
|
||||
private:
|
||||
static shared_ptr<GameServer> Instance;
|
||||
shared_ptr<Console> _console;
|
||||
unique_ptr<thread> _serverThread;
|
||||
atomic<bool> _stop;
|
||||
unique_ptr<Socket> _listener;
|
||||
uint16_t _port;
|
||||
string _password;
|
||||
list<shared_ptr<GameServerConnection>> _openConnections;
|
||||
bool _initialized = false;
|
||||
|
||||
string _hostPlayerName;
|
||||
uint8_t _hostControllerPort;
|
||||
|
||||
void AcceptConnections();
|
||||
void UpdateConnections();
|
||||
|
||||
void Exec();
|
||||
void Stop();
|
||||
|
||||
public:
|
||||
GameServer(shared_ptr<Console> console, uint16_t port, string password, string hostPlayerName);
|
||||
virtual ~GameServer();
|
||||
|
||||
void RegisterServerInput();
|
||||
|
||||
static void StartServer(shared_ptr<Console> console, uint16_t port, string password, string hostPlayerName);
|
||||
static void StopServer();
|
||||
static bool Started();
|
||||
|
||||
static string GetHostPlayerName();
|
||||
static uint8_t GetHostControllerPort();
|
||||
static void SetHostControllerPort(uint8_t port);
|
||||
static uint8_t GetAvailableControllers();
|
||||
static vector<PlayerInfo> GetPlayerList();
|
||||
static void SendPlayerList();
|
||||
|
||||
static list<shared_ptr<GameServerConnection>> GetConnectionList();
|
||||
|
||||
bool SetInput(BaseControlDevice *device) override;
|
||||
void RecordInput(vector<shared_ptr<BaseControlDevice>> devices) override;
|
||||
|
||||
// Inherited via INotificationListener
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void * parameter) override;
|
||||
};
|
@ -1,248 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include <random>
|
||||
#include "MessageManager.h"
|
||||
#include "GameServerConnection.h"
|
||||
#include "HandShakeMessage.h"
|
||||
#include "InputDataMessage.h"
|
||||
#include "MovieDataMessage.h"
|
||||
#include "GameInformationMessage.h"
|
||||
#include "SaveStateMessage.h"
|
||||
#include "Console.h"
|
||||
#include "ControlManager.h"
|
||||
#include "ClientConnectionData.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "StandardController.h"
|
||||
#include "SelectControllerMessage.h"
|
||||
#include "PlayerListMessage.h"
|
||||
#include "GameServer.h"
|
||||
#include "ForceDisconnectMessage.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "ServerInformationMessage.h"
|
||||
|
||||
GameServerConnection* GameServerConnection::_netPlayDevices[BaseControlDevice::PortCount] = { };
|
||||
|
||||
GameServerConnection::GameServerConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, string serverPassword) : GameConnection(console, socket)
|
||||
{
|
||||
//Server-side connection
|
||||
_serverPassword = serverPassword;
|
||||
_controllerPort = GameConnection::SpectatorPort;
|
||||
SendServerInformation();
|
||||
}
|
||||
|
||||
GameServerConnection::~GameServerConnection()
|
||||
{
|
||||
if(!_playerName.empty()) {
|
||||
MessageManager::DisplayMessage("NetPlay", _playerName + " (Player " + std::to_string(_controllerPort + 1) + ") disconnected.");
|
||||
}
|
||||
|
||||
UnregisterNetPlayDevice(this);
|
||||
}
|
||||
|
||||
void GameServerConnection::SendServerInformation()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 engine(rd());
|
||||
std::uniform_int_distribution<> dist((int)' ', (int)'~');
|
||||
string hash(50, ' ');
|
||||
for(int i = 0; i < 50; i++) {
|
||||
int random = dist(engine);
|
||||
hash[i] = (char)random;
|
||||
}
|
||||
|
||||
_connectionHash = hash;
|
||||
|
||||
ServerInformationMessage message(hash);
|
||||
SendNetMessage(message);
|
||||
}
|
||||
|
||||
void GameServerConnection::SendGameInformation()
|
||||
{
|
||||
_console->Pause();
|
||||
RomInfo romInfo = _console->GetRomInfo();
|
||||
GameInformationMessage gameInfo(romInfo.RomName, romInfo.Hash.Crc32, _controllerPort, _console->GetSettings()->CheckFlag(EmulationFlags::Paused));
|
||||
SendNetMessage(gameInfo);
|
||||
SaveStateMessage saveState(_console);
|
||||
SendNetMessage(saveState);
|
||||
_console->Resume();
|
||||
}
|
||||
|
||||
void GameServerConnection::SendMovieData(uint8_t port, ControlDeviceState state)
|
||||
{
|
||||
if(_handshakeCompleted) {
|
||||
MovieDataMessage message(state, port);
|
||||
SendNetMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void GameServerConnection::SendForceDisconnectMessage(string disconnectMessage)
|
||||
{
|
||||
ForceDisconnectMessage message(disconnectMessage);
|
||||
SendNetMessage(message);
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
void GameServerConnection::PushState(ControlDeviceState state)
|
||||
{
|
||||
if(_inputData.size() == 0 || state != _inputData.back()) {
|
||||
_inputData.clear();
|
||||
_inputData.push_back(state);
|
||||
}
|
||||
}
|
||||
|
||||
ControlDeviceState GameServerConnection::GetState()
|
||||
{
|
||||
size_t inputBufferSize = _inputData.size();
|
||||
ControlDeviceState stateData;
|
||||
if(inputBufferSize > 0) {
|
||||
stateData = _inputData.front();
|
||||
if(inputBufferSize > 1) {
|
||||
//Always keep the last one the client sent, it will be used until a new one is received
|
||||
_inputData.pop_front();
|
||||
}
|
||||
}
|
||||
return stateData;
|
||||
}
|
||||
|
||||
void GameServerConnection::ProcessHandshakeResponse(HandShakeMessage* message)
|
||||
{
|
||||
//Send the game's current state to the client and register the controller
|
||||
if(message->IsValid()) {
|
||||
if(message->CheckPassword(_serverPassword, _connectionHash)) {
|
||||
_console->Pause();
|
||||
|
||||
_controllerPort = message->IsSpectator() ? GameConnection::SpectatorPort : GetFirstFreeControllerPort();
|
||||
_playerName = message->GetPlayerName();
|
||||
|
||||
string playerPortMessage = _controllerPort == GameConnection::SpectatorPort ? "Spectator" : "Player " + std::to_string(_controllerPort + 1);
|
||||
|
||||
MessageManager::DisplayMessage("NetPlay", _playerName + " (" + playerPortMessage + ") connected.");
|
||||
|
||||
if(_console->GetRomInfo().RomName.size() > 0) {
|
||||
SendGameInformation();
|
||||
}
|
||||
|
||||
_handshakeCompleted = true;
|
||||
RegisterNetPlayDevice(this, _controllerPort);
|
||||
GameServer::SendPlayerList();
|
||||
_console->Resume();
|
||||
} else {
|
||||
SendForceDisconnectMessage("The password you provided did not match - you have been disconnected.");
|
||||
}
|
||||
} else {
|
||||
SendForceDisconnectMessage("Server is using a different version of Mesen (" + EmulationSettings::GetMesenVersionString() + ") - you have been disconnected.");
|
||||
MessageManager::DisplayMessage("NetPlay", + "NetplayVersionMismatch", message->GetPlayerName());
|
||||
}
|
||||
}
|
||||
|
||||
void GameServerConnection::ProcessMessage(NetMessage* message)
|
||||
{
|
||||
switch(message->GetType()) {
|
||||
case MessageType::HandShake:
|
||||
ProcessHandshakeResponse((HandShakeMessage*)message);
|
||||
break;
|
||||
|
||||
case MessageType::InputData:
|
||||
if(!_handshakeCompleted) {
|
||||
SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
|
||||
return;
|
||||
}
|
||||
PushState(((InputDataMessage*)message)->GetInputState());
|
||||
break;
|
||||
|
||||
case MessageType::SelectController:
|
||||
if(!_handshakeCompleted) {
|
||||
SendForceDisconnectMessage("Handshake has not been completed - invalid packet");
|
||||
return;
|
||||
}
|
||||
SelectControllerPort(((SelectControllerMessage*)message)->GetPortNumber());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GameServerConnection::SelectControllerPort(uint8_t port)
|
||||
{
|
||||
_console->Pause();
|
||||
if(port == GameConnection::SpectatorPort) {
|
||||
//Client wants to be a spectator, make sure we are not using any controller
|
||||
UnregisterNetPlayDevice(this);
|
||||
_controllerPort = port;
|
||||
} else {
|
||||
GameServerConnection* netPlayDevice = GetNetPlayDevice(port);
|
||||
if(netPlayDevice == this) {
|
||||
//Nothing to do, we're already this player
|
||||
} else if(netPlayDevice == nullptr) {
|
||||
//This port is free, we can switch
|
||||
UnregisterNetPlayDevice(this);
|
||||
RegisterNetPlayDevice(this, port);
|
||||
_controllerPort = port;
|
||||
} else {
|
||||
//Another player is using this port, we can't use it
|
||||
}
|
||||
}
|
||||
SendGameInformation();
|
||||
GameServer::SendPlayerList();
|
||||
_console->Resume();
|
||||
}
|
||||
|
||||
void GameServerConnection::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
switch(type) {
|
||||
case ConsoleNotificationType::GamePaused:
|
||||
case ConsoleNotificationType::GameResumed:
|
||||
case ConsoleNotificationType::GameReset:
|
||||
case ConsoleNotificationType::StateLoaded:
|
||||
case ConsoleNotificationType::CheatAdded:
|
||||
case ConsoleNotificationType::ConfigChanged:
|
||||
case ConsoleNotificationType::GameInitCompleted:
|
||||
SendGameInformation();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GameServerConnection::RegisterNetPlayDevice(GameServerConnection* device, uint8_t port)
|
||||
{
|
||||
GameServerConnection::_netPlayDevices[port] = device;
|
||||
}
|
||||
|
||||
void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device)
|
||||
{
|
||||
if(device != nullptr) {
|
||||
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
|
||||
if(GameServerConnection::_netPlayDevices[i] == device) {
|
||||
GameServerConnection::_netPlayDevices[i] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port)
|
||||
{
|
||||
return GameServerConnection::_netPlayDevices[port];
|
||||
}
|
||||
|
||||
uint8_t GameServerConnection::GetFirstFreeControllerPort()
|
||||
{
|
||||
uint8_t hostPost = GameServer::GetHostControllerPort();
|
||||
for(int i = 0; i < BaseControlDevice::PortCount; i++) {
|
||||
if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return GameConnection::SpectatorPort;
|
||||
}
|
||||
|
||||
string GameServerConnection::GetPlayerName()
|
||||
{
|
||||
return _playerName;
|
||||
}
|
||||
|
||||
uint8_t GameServerConnection::GetControllerPort()
|
||||
{
|
||||
return _controllerPort;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "GameConnection.h"
|
||||
#include "INotificationListener.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "ControlDeviceState.h"
|
||||
|
||||
class HandShakeMessage;
|
||||
|
||||
class GameServerConnection : public GameConnection, public INotificationListener
|
||||
{
|
||||
private:
|
||||
static GameServerConnection* _netPlayDevices[BaseControlDevice::PortCount];
|
||||
|
||||
list<ControlDeviceState> _inputData;
|
||||
string _playerName;
|
||||
int _controllerPort;
|
||||
string _connectionHash;
|
||||
string _serverPassword;
|
||||
bool _handshakeCompleted = false;
|
||||
|
||||
void PushState(ControlDeviceState state);
|
||||
void SendServerInformation();
|
||||
void SendGameInformation();
|
||||
void SelectControllerPort(uint8_t port);
|
||||
|
||||
void SendForceDisconnectMessage(string disconnectMessage);
|
||||
|
||||
void ProcessHandshakeResponse(HandShakeMessage* message);
|
||||
|
||||
static void RegisterNetPlayDevice(GameServerConnection* connection, uint8_t port);
|
||||
static void UnregisterNetPlayDevice(GameServerConnection* device);
|
||||
static uint8_t GetFirstFreeControllerPort();
|
||||
|
||||
protected:
|
||||
void ProcessMessage(NetMessage* message) override;
|
||||
|
||||
public:
|
||||
GameServerConnection(shared_ptr<Console> console, shared_ptr<Socket> socket, string serverPassword);
|
||||
virtual ~GameServerConnection();
|
||||
|
||||
ControlDeviceState GetState();
|
||||
void SendMovieData(uint8_t port, ControlDeviceState state);
|
||||
|
||||
string GetPlayerName();
|
||||
uint8_t GetControllerPort();
|
||||
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
|
||||
static GameServerConnection* GetNetPlayDevice(uint8_t port);
|
||||
};
|
@ -1,164 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "HistoryViewer.h"
|
||||
#include "RewindData.h"
|
||||
#include "Console.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "RomData.h"
|
||||
#include "MovieRecorder.h"
|
||||
#include "SaveStateManager.h"
|
||||
|
||||
HistoryViewer::HistoryViewer(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_position = 0;
|
||||
_pollCounter = 0;
|
||||
}
|
||||
|
||||
HistoryViewer::~HistoryViewer()
|
||||
{
|
||||
}
|
||||
|
||||
void HistoryViewer::SetHistoryData(std::deque<RewindData> &history)
|
||||
{
|
||||
_history = history;
|
||||
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
|
||||
SeekTo(0);
|
||||
}
|
||||
|
||||
uint32_t HistoryViewer::GetHistoryLength()
|
||||
{
|
||||
//Returns history length in number of frames
|
||||
return (uint32_t)(_history.size() * HistoryViewer::BufferSize);
|
||||
}
|
||||
|
||||
void HistoryViewer::GetHistorySegments(uint32_t *segmentBuffer, uint32_t &bufferSize)
|
||||
{
|
||||
uint32_t segmentIndex = 0;
|
||||
for(size_t i = 0; i < _history.size(); i++) {
|
||||
if(_history[i].EndOfSegment) {
|
||||
segmentBuffer[segmentIndex] = (uint32_t)i;
|
||||
segmentIndex++;
|
||||
|
||||
if(segmentIndex == bufferSize) {
|
||||
//Reached buffer size, can't return any more values
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bufferSize = segmentIndex;
|
||||
}
|
||||
|
||||
uint32_t HistoryViewer::GetPosition()
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
|
||||
void HistoryViewer::SeekTo(uint32_t seekPosition)
|
||||
{
|
||||
//Seek to the specified position
|
||||
if(seekPosition < _history.size()) {
|
||||
_console->Pause();
|
||||
|
||||
bool wasPaused = _console->GetSettings()->CheckFlag(EmulationFlags::Paused);
|
||||
_console->GetSettings()->ClearFlags(EmulationFlags::Paused);
|
||||
_position = seekPosition;
|
||||
RewindData rewindData = _history[_position];
|
||||
rewindData.LoadState(_console);
|
||||
|
||||
_pollCounter = 0;
|
||||
|
||||
if(wasPaused) {
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
}
|
||||
|
||||
_console->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryViewer::CreateSaveState(string outputFile, uint32_t position)
|
||||
{
|
||||
if(position < _history.size()) {
|
||||
std::stringstream stateData;
|
||||
_console->GetSaveStateManager()->GetSaveStateHeader(stateData);
|
||||
_history[position].GetStateData(stateData);
|
||||
|
||||
ofstream output(outputFile, ios::binary);
|
||||
if(output) {
|
||||
output << stateData.rdbuf();
|
||||
output.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HistoryViewer::SaveMovie(string movieFile, uint32_t startPosition, uint32_t endPosition)
|
||||
{
|
||||
//Take a savestate to be able to restore it after generating the movie file
|
||||
//(the movie generation uses the console's inputs, which could affect the emulation otherwise)
|
||||
stringstream state;
|
||||
_console->Pause();
|
||||
_console->SaveState(state);
|
||||
|
||||
//Convert the rewind data to a .mmo file
|
||||
unique_ptr<MovieRecorder> recorder(new MovieRecorder(_console));
|
||||
bool result = recorder->CreateMovie(movieFile, _history, startPosition, endPosition);
|
||||
|
||||
//Resume the state and resume
|
||||
_console->LoadState(state);
|
||||
_console->Resume();
|
||||
return result;
|
||||
}
|
||||
|
||||
void HistoryViewer::ResumeGameplay(shared_ptr<Console> console, uint32_t resumePosition)
|
||||
{
|
||||
console->Pause();
|
||||
if(_console->GetRomInfo().Hash.Sha1 != console->GetRomInfo().Hash.Sha1) {
|
||||
//Load game on the main window if they aren't the same
|
||||
console->Initialize(_console->GetRomPath(), _console->GetPatchFile());
|
||||
}
|
||||
if(resumePosition < _history.size()) {
|
||||
_history[resumePosition].LoadState(console);
|
||||
} else {
|
||||
_history[_history.size() - 1].LoadState(console);
|
||||
}
|
||||
console->Resume();
|
||||
}
|
||||
|
||||
bool HistoryViewer::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
uint8_t port = device->GetPort();
|
||||
if(_position < _history.size()) {
|
||||
std::deque<ControlDeviceState> &stateData = _history[_position].InputLogs[port];
|
||||
if(_pollCounter < stateData.size()) {
|
||||
ControlDeviceState state = stateData[_pollCounter];
|
||||
device->SetRawState(state);
|
||||
}
|
||||
}
|
||||
if(port == 0 && _pollCounter < 30) {
|
||||
_pollCounter++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryViewer::ProcessEndOfFrame()
|
||||
{
|
||||
if(_pollCounter == HistoryViewer::BufferSize) {
|
||||
_pollCounter = 0;
|
||||
_position++;
|
||||
|
||||
if(_position >= _history.size()) {
|
||||
//Reached the end of history data
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
return;
|
||||
}
|
||||
|
||||
RewindData rewindData = _history[_position];
|
||||
rewindData.LoadState(_console);
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "IInputProvider.h"
|
||||
#include "RewindData.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class HistoryViewer : public IInputProvider
|
||||
{
|
||||
private:
|
||||
static constexpr int32_t BufferSize = 30; //Number of frames between each save state
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
std::deque<RewindData> _history;
|
||||
uint32_t _position;
|
||||
uint32_t _pollCounter;
|
||||
|
||||
public:
|
||||
HistoryViewer(shared_ptr<Console> console);
|
||||
virtual ~HistoryViewer();
|
||||
|
||||
void SetHistoryData(std::deque<RewindData> &history);
|
||||
|
||||
uint32_t GetHistoryLength();
|
||||
void GetHistorySegments(uint32_t * segmentBuffer, uint32_t &bufferSize);
|
||||
uint32_t GetPosition();
|
||||
void SeekTo(uint32_t seekPosition);
|
||||
|
||||
bool CreateSaveState(string outputFile, uint32_t position);
|
||||
bool SaveMovie(string movieFile, uint32_t startPosition, uint32_t endPosition);
|
||||
|
||||
void ResumeGameplay(shared_ptr<Console> console, uint32_t resumePosition);
|
||||
|
||||
void ProcessEndOfFrame();
|
||||
|
||||
// Inherited via IInputProvider
|
||||
bool SetInput(BaseControlDevice * device) override;
|
||||
};
|
@ -2,9 +2,3 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
class BaseControlDevice;
|
||||
|
||||
class IInputRecorder
|
||||
{
|
||||
public:
|
||||
virtual void RecordInput(vector<shared_ptr<BaseControlDevice>> devices) = 0;
|
||||
};
|
@ -1,340 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/ZipReader.h"
|
||||
#include "../Utilities/StringUtilities.h"
|
||||
#include "../Utilities/HexUtilities.h"
|
||||
#include "MesenMovie.h"
|
||||
#include "MessageManager.h"
|
||||
#include "ControlManager.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "Console.h"
|
||||
#include "SaveStateManager.h"
|
||||
#include "CheatManager.h"
|
||||
#include "MovieRecorder.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "VirtualFile.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "RomData.h"
|
||||
|
||||
MesenMovie::MesenMovie(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
MesenMovie::~MesenMovie()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void MesenMovie::Stop()
|
||||
{
|
||||
if(_playing) {
|
||||
MessageManager::DisplayMessage("Movies", "MovieEnded");
|
||||
|
||||
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::MovieEnded);
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::PauseOnMovieEnd)) {
|
||||
_console->GetSettings()->SetFlags(EmulationFlags::Paused);
|
||||
}
|
||||
|
||||
_playing = false;
|
||||
}
|
||||
_console->GetSettings()->SetInputPollScanline(241);
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
}
|
||||
|
||||
bool MesenMovie::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
uint32_t inputRowIndex = _console->GetControlManager()->GetPollCounter();
|
||||
|
||||
if(_inputData.size() > inputRowIndex && _inputData[inputRowIndex].size() > _deviceIndex) {
|
||||
device->SetTextState(_inputData[inputRowIndex][_deviceIndex]);
|
||||
|
||||
_deviceIndex++;
|
||||
if(_deviceIndex >= _inputData[inputRowIndex].size()) {
|
||||
//Move to the next frame's data
|
||||
_deviceIndex = 0;
|
||||
}
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MesenMovie::IsPlaying()
|
||||
{
|
||||
return _playing;
|
||||
}
|
||||
|
||||
vector<uint8_t> MesenMovie::LoadBattery(string extension)
|
||||
{
|
||||
vector<uint8_t> batteryData;
|
||||
_reader->ExtractFile("Battery" + extension, batteryData);
|
||||
return batteryData;
|
||||
}
|
||||
|
||||
void MesenMovie::ProcessNotification(ConsoleNotificationType type, void * parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::GameLoaded) {
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool MesenMovie::Play(VirtualFile &file)
|
||||
{
|
||||
_movieFile = file;
|
||||
|
||||
std::stringstream ss;
|
||||
file.ReadFile(ss);
|
||||
|
||||
_reader.reset(new ZipReader());
|
||||
_reader->LoadArchive(ss);
|
||||
|
||||
stringstream settingsData, inputData;
|
||||
if(!_reader->GetStream("GameSettings.txt", settingsData)) {
|
||||
MessageManager::Log("[Movie] File not found: GameSettings.txt");
|
||||
return false;
|
||||
}
|
||||
if(!_reader->GetStream("Input.txt", inputData)) {
|
||||
MessageManager::Log("[Movie] File not found: Input.txt");
|
||||
return false;
|
||||
}
|
||||
|
||||
while(inputData) {
|
||||
string line;
|
||||
std::getline(inputData, line);
|
||||
if(line.substr(0, 1) == "|") {
|
||||
_inputData.push_back(StringUtilities::Split(line.substr(1), '|'));
|
||||
}
|
||||
}
|
||||
|
||||
_deviceIndex = 0;
|
||||
|
||||
ParseSettings(settingsData);
|
||||
|
||||
_console->Pause();
|
||||
|
||||
_console->GetBatteryManager()->SetBatteryProvider(shared_from_this());
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
ApplySettings();
|
||||
|
||||
//Disable auto-configure input option (otherwise the movie file's input types are ignored)
|
||||
bool autoConfigureInput = _console->GetSettings()->CheckFlag(EmulationFlags::AutoConfigureInput);
|
||||
_console->GetSettings()->ClearFlags(EmulationFlags::AutoConfigureInput);
|
||||
|
||||
ControlManager *controlManager = _console->GetControlManager();
|
||||
if(controlManager) {
|
||||
//ControlManager can be empty if no game is loaded
|
||||
controlManager->SetPollCounter(0);
|
||||
}
|
||||
|
||||
bool gameLoaded = LoadGame();
|
||||
_console->GetSettings()->SetFlagState(EmulationFlags::AutoConfigureInput, autoConfigureInput);
|
||||
|
||||
if(!gameLoaded) {
|
||||
_console->Resume();
|
||||
return false;
|
||||
}
|
||||
|
||||
stringstream saveStateData;
|
||||
if(_reader->GetStream("SaveState.mst", saveStateData)) {
|
||||
if(!_console->GetSaveStateManager()->LoadState(saveStateData, true)) {
|
||||
_console->Resume();
|
||||
return false;
|
||||
} else {
|
||||
_console->GetControlManager()->SetPollCounter(0);
|
||||
}
|
||||
}
|
||||
|
||||
_playing = true;
|
||||
|
||||
_console->Resume();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T FromString(string name, const vector<string> &enumNames, T defaultValue)
|
||||
{
|
||||
for(size_t i = 0; i < enumNames.size(); i++) {
|
||||
if(name == enumNames[i]) {
|
||||
return (T)i;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void MesenMovie::ParseSettings(stringstream &data)
|
||||
{
|
||||
while(!data.eof()) {
|
||||
string line;
|
||||
std::getline(data, line);
|
||||
|
||||
if(!line.empty()) {
|
||||
size_t index = line.find_first_of(' ');
|
||||
if(index != string::npos) {
|
||||
string name = line.substr(0, index);
|
||||
string value = line.substr(index + 1);
|
||||
|
||||
if(name == "Cheat") {
|
||||
_cheats.push_back(value);
|
||||
} else {
|
||||
_settings[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MesenMovie::LoadGame()
|
||||
{
|
||||
string mesenVersion = LoadString(_settings, MovieKeys::MesenVersion);
|
||||
string gameFile = LoadString(_settings, MovieKeys::GameFile);
|
||||
string sha1Hash = LoadString(_settings, MovieKeys::Sha1);
|
||||
//string patchFile = LoadString(_settings, MovieKeys::PatchFile);
|
||||
//string patchFileSha1 = LoadString(_settings, MovieKeys::PatchFileSha1);
|
||||
//string patchedRomSha1 = LoadString(_settings, MovieKeys::PatchedRomSha1);
|
||||
|
||||
if(_console->GetSettings()->CheckFlag(EmulationFlags::AllowMismatchingSaveState) && _console->GetRomInfo().RomName == gameFile) {
|
||||
//Loaded game has the right name, and we don't want to validate the hash values
|
||||
_console->PowerCycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
HashInfo hashInfo;
|
||||
hashInfo.Sha1 = sha1Hash;
|
||||
|
||||
VirtualFile romFile = _console->FindMatchingRom(gameFile, hashInfo);
|
||||
bool gameLoaded = false;
|
||||
if(romFile.IsValid()) {
|
||||
VirtualFile patchFile(_movieFile.GetFilePath(), "PatchData.dat");
|
||||
if(patchFile.IsValid()) {
|
||||
gameLoaded = _console->Initialize(romFile, patchFile);
|
||||
} else {
|
||||
gameLoaded = _console->Initialize(romFile);
|
||||
}
|
||||
}
|
||||
|
||||
return gameLoaded;
|
||||
}
|
||||
|
||||
void MesenMovie::ApplySettings()
|
||||
{
|
||||
EmulationSettings* settings = _console->GetSettings();
|
||||
|
||||
NesModel region = FromString(LoadString(_settings, MovieKeys::Region), NesModelNames, NesModel::NTSC);
|
||||
ConsoleType consoleType = FromString(LoadString(_settings, MovieKeys::ConsoleType), ConsoleTypeNames, ConsoleType::Nes);
|
||||
ControllerType controller1 = FromString(LoadString(_settings, MovieKeys::Controller1), ControllerTypeNames, ControllerType::None);
|
||||
ControllerType controller2 = FromString(LoadString(_settings, MovieKeys::Controller2), ControllerTypeNames, ControllerType::None);
|
||||
ControllerType controller3 = FromString(LoadString(_settings, MovieKeys::Controller3), ControllerTypeNames, ControllerType::None);
|
||||
ControllerType controller4 = FromString(LoadString(_settings, MovieKeys::Controller4), ControllerTypeNames, ControllerType::None);
|
||||
ExpansionPortDevice expansionDevice = FromString<ExpansionPortDevice>(LoadString(_settings, MovieKeys::ExpansionDevice), ExpansionPortDeviceNames, ExpansionPortDevice::None);
|
||||
|
||||
settings->SetNesModel(region);
|
||||
settings->SetConsoleType(consoleType);
|
||||
settings->SetControllerType(0, controller1);
|
||||
settings->SetControllerType(1, controller2);
|
||||
settings->SetControllerType(2, controller3);
|
||||
settings->SetControllerType(3, controller4);
|
||||
settings->SetExpansionDevice(expansionDevice);
|
||||
|
||||
uint32_t ramPowerOnState = LoadInt(_settings, MovieKeys::RamPowerOnState);
|
||||
if(ramPowerOnState == 0xFF) {
|
||||
settings->SetRamPowerOnState(RamPowerOnState::AllOnes);
|
||||
} else {
|
||||
settings->SetRamPowerOnState(RamPowerOnState::AllZeros);
|
||||
}
|
||||
|
||||
settings->SetInputPollScanline(LoadInt(_settings, MovieKeys::InputPollScanline, 240));
|
||||
|
||||
settings->SetZapperDetectionRadius(LoadInt(_settings, MovieKeys::ZapperDetectionRadius));
|
||||
|
||||
settings->SetPpuNmiConfig(
|
||||
LoadInt(_settings, MovieKeys::ExtraScanlinesBeforeNmi),
|
||||
LoadInt(_settings, MovieKeys::ExtraScanlinesAfterNmi)
|
||||
);
|
||||
|
||||
settings->SetFlagState(EmulationFlags::DisablePpu2004Reads, LoadBool(_settings, MovieKeys::DisablePpu2004Reads));
|
||||
settings->SetFlagState(EmulationFlags::DisablePaletteRead, LoadBool(_settings, MovieKeys::DisablePaletteRead));
|
||||
settings->SetFlagState(EmulationFlags::DisableOamAddrBug, LoadBool(_settings, MovieKeys::DisableOamAddrBug));
|
||||
settings->SetFlagState(EmulationFlags::UseNes101Hvc101Behavior, LoadBool(_settings, MovieKeys::UseNes101Hvc101Behavior));
|
||||
settings->SetFlagState(EmulationFlags::EnableOamDecay, LoadBool(_settings, MovieKeys::EnableOamDecay));
|
||||
settings->SetFlagState(EmulationFlags::DisablePpuReset, LoadBool(_settings, MovieKeys::DisablePpuReset));
|
||||
|
||||
settings->SetDipSwitches(HexUtilities::FromHex(LoadString(_settings, MovieKeys::DipSwitches)));
|
||||
|
||||
LoadCheats();
|
||||
}
|
||||
|
||||
uint32_t MesenMovie::LoadInt(std::unordered_map<string, string> &settings, string name, uint32_t defaultValue)
|
||||
{
|
||||
auto result = settings.find(name);
|
||||
if(result != settings.end()) {
|
||||
try {
|
||||
return (uint32_t)std::stoul(result->second);
|
||||
} catch(std::exception&) {
|
||||
MessageManager::Log("[Movies] Invalid value for tag: " + name);
|
||||
return defaultValue;
|
||||
}
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
bool MesenMovie::LoadBool(std::unordered_map<string, string> &settings, string name)
|
||||
{
|
||||
auto result = settings.find(name);
|
||||
if(result != settings.end()) {
|
||||
if(result->second == "true") {
|
||||
return true;
|
||||
} else if(result->second == "false") {
|
||||
return false;
|
||||
} else {
|
||||
MessageManager::Log("[Movies] Invalid value for tag: " + name);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string MesenMovie::LoadString(std::unordered_map<string, string> &settings, string name)
|
||||
{
|
||||
auto result = settings.find(name);
|
||||
if(result != settings.end()) {
|
||||
return result->second;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void MesenMovie::LoadCheats()
|
||||
{
|
||||
vector<CodeInfo> cheats;
|
||||
for(string cheatData : _cheats) {
|
||||
CodeInfo code;
|
||||
if(LoadCheat(cheatData, code)) {
|
||||
cheats.push_back(code);
|
||||
}
|
||||
}
|
||||
_console->GetCheatManager()->SetCheats(cheats);
|
||||
}
|
||||
|
||||
bool MesenMovie::LoadCheat(string cheatData, CodeInfo &code)
|
||||
{
|
||||
vector<string> data = StringUtilities::Split(cheatData, ' ');
|
||||
|
||||
if(data.size() >= 3) {
|
||||
uint32_t address = HexUtilities::FromHex(data[0]);
|
||||
uint8_t value = HexUtilities::FromHex(data[1]);
|
||||
bool relativeAddress = data[2] == "true";
|
||||
int32_t compareValue = data.size() > 3 ? HexUtilities::FromHex(data[3]) : -1;
|
||||
|
||||
code.Address = address;
|
||||
code.Value = value;
|
||||
code.IsRelativeAddress = relativeAddress;
|
||||
code.CompareValue = compareValue;
|
||||
return true;
|
||||
} else {
|
||||
MessageManager::Log("[Movie] Invalid cheat definition: " + cheatData);
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "MovieManager.h"
|
||||
#include "VirtualFile.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "INotificationListener.h"
|
||||
|
||||
class ZipReader;
|
||||
class Console;
|
||||
struct CodeInfo;
|
||||
|
||||
class MesenMovie : public IMovie, public INotificationListener, public IBatteryProvider, public std::enable_shared_from_this<MesenMovie>
|
||||
{
|
||||
private:
|
||||
shared_ptr<Console> _console;
|
||||
|
||||
VirtualFile _movieFile;
|
||||
shared_ptr<ZipReader> _reader;
|
||||
bool _playing = false;
|
||||
size_t _deviceIndex = 0;
|
||||
vector<vector<string>> _inputData;
|
||||
vector<string> _cheats;
|
||||
std::unordered_map<string, string> _settings;
|
||||
string _filename;
|
||||
|
||||
private:
|
||||
void ParseSettings(stringstream &data);
|
||||
void ApplySettings();
|
||||
bool LoadGame();
|
||||
void Stop();
|
||||
|
||||
uint32_t LoadInt(std::unordered_map<string, string> &settings, string name, uint32_t defaultValue = 0);
|
||||
bool LoadBool(std::unordered_map<string, string> &settings, string name);
|
||||
string LoadString(std::unordered_map<string, string> &settings, string name);
|
||||
void LoadCheats();
|
||||
bool LoadCheat(string cheatData, CodeInfo &code);
|
||||
|
||||
public:
|
||||
MesenMovie(shared_ptr<Console> console);
|
||||
virtual ~MesenMovie();
|
||||
|
||||
bool Play(VirtualFile &file) override;
|
||||
bool SetInput(BaseControlDevice* device) override;
|
||||
bool IsPlaying() override;
|
||||
|
||||
// Inherited via IBatteryProvider
|
||||
virtual vector<uint8_t> LoadBattery(string extension) override;
|
||||
|
||||
// Inherited via INotificationListener
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void * parameter) override;
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "NetMessage.h"
|
||||
#include "ControlDeviceState.h"
|
||||
|
||||
class MovieDataMessage : public NetMessage
|
||||
{
|
||||
private:
|
||||
uint8_t _portNumber;
|
||||
ControlDeviceState _inputState;
|
||||
|
||||
protected:
|
||||
virtual void ProtectedStreamState()
|
||||
{
|
||||
Stream<uint8_t>(_portNumber);
|
||||
StreamArray(_inputState.State);
|
||||
}
|
||||
|
||||
public:
|
||||
MovieDataMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { }
|
||||
|
||||
MovieDataMessage(ControlDeviceState state, uint8_t port) : NetMessage(MessageType::MovieData)
|
||||
{
|
||||
_portNumber = port;
|
||||
_inputState = state;
|
||||
}
|
||||
|
||||
uint8_t GetPortNumber()
|
||||
{
|
||||
return _portNumber;
|
||||
}
|
||||
|
||||
ControlDeviceState GetInputState()
|
||||
{
|
||||
return _inputState;
|
||||
}
|
||||
};
|
@ -1,70 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "MovieManager.h"
|
||||
#include "MesenMovie.h"
|
||||
#include "BizhawkMovie.h"
|
||||
#include "FceuxMovie.h"
|
||||
#include "MovieRecorder.h"
|
||||
#include "VirtualFile.h"
|
||||
|
||||
shared_ptr<IMovie> MovieManager::_player;
|
||||
shared_ptr<MovieRecorder> MovieManager::_recorder;
|
||||
|
||||
void MovieManager::Record(RecordMovieOptions options, shared_ptr<Console> console)
|
||||
{
|
||||
shared_ptr<MovieRecorder> recorder(new MovieRecorder(console));
|
||||
if(recorder->Record(options)) {
|
||||
_recorder = recorder;
|
||||
}
|
||||
}
|
||||
|
||||
void MovieManager::Play(VirtualFile file, shared_ptr<Console> console)
|
||||
{
|
||||
vector<uint8_t> fileData;
|
||||
if(file.IsValid() && file.ReadFile(fileData)) {
|
||||
shared_ptr<IMovie> player;
|
||||
if(memcmp(fileData.data(), "MMO", 3) == 0) {
|
||||
//Old movie format, no longer supported
|
||||
MessageManager::DisplayMessage("Movies", "MovieIncompatibleVersion");
|
||||
} else if(memcmp(fileData.data(), "PK", 2) == 0) {
|
||||
//Mesen or Bizhawk movie
|
||||
ZipReader reader;
|
||||
reader.LoadArchive(fileData);
|
||||
|
||||
vector<string> files = reader.GetFileList();
|
||||
if(std::find(files.begin(), files.end(), "GameSettings.txt") != files.end()) {
|
||||
player.reset(new MesenMovie(console));
|
||||
} else {
|
||||
player.reset(new BizhawkMovie(console));
|
||||
}
|
||||
} else if(memcmp(fileData.data(), "ver", 3) == 0) {
|
||||
player.reset(new FceuxMovie(console));
|
||||
}
|
||||
|
||||
if(player && player->Play(file)) {
|
||||
_player = player;
|
||||
|
||||
MessageManager::DisplayMessage("Movies", "MoviePlaying", file.GetFileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovieManager::Stop()
|
||||
{
|
||||
_player.reset();
|
||||
|
||||
if(_recorder) {
|
||||
_recorder.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool MovieManager::Playing()
|
||||
{
|
||||
shared_ptr<IMovie> player = _player;
|
||||
return player && player->IsPlaying();
|
||||
}
|
||||
|
||||
bool MovieManager::Recording()
|
||||
{
|
||||
return _recorder != nullptr;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "MessageManager.h"
|
||||
#include "EmulationSettings.h"
|
||||
#include "IInputProvider.h"
|
||||
#include "Types.h"
|
||||
|
||||
class MovieRecorder;
|
||||
class VirtualFile;
|
||||
class Console;
|
||||
|
||||
class IMovie : public IInputProvider
|
||||
{
|
||||
public:
|
||||
virtual bool Play(VirtualFile &file) = 0;
|
||||
virtual bool IsPlaying() = 0;
|
||||
};
|
||||
|
||||
class MovieManager
|
||||
{
|
||||
private:
|
||||
static shared_ptr<IMovie> _player;
|
||||
static shared_ptr<MovieRecorder> _recorder;
|
||||
|
||||
public:
|
||||
static void Record(RecordMovieOptions options, shared_ptr<Console> console);
|
||||
static void Play(VirtualFile file, shared_ptr<Console> console);
|
||||
static void Stop();
|
||||
static bool Playing();
|
||||
static bool Recording();
|
||||
};
|
@ -1,265 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "../Utilities/HexUtilities.h"
|
||||
#include "../Utilities/FolderUtilities.h"
|
||||
#include "../Utilities/ZipWriter.h"
|
||||
#include "MovieRecorder.h"
|
||||
#include "ControlManager.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "Console.h"
|
||||
#include "CheatManager.h"
|
||||
#include "VirtualFile.h"
|
||||
#include "SaveStateManager.h"
|
||||
#include "NotificationManager.h"
|
||||
#include "RomData.h"
|
||||
#include "RewindData.h"
|
||||
|
||||
MovieRecorder::MovieRecorder(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
MovieRecorder::~MovieRecorder()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool MovieRecorder::Record(RecordMovieOptions options)
|
||||
{
|
||||
_filename = options.Filename;
|
||||
_author = options.Author;
|
||||
_description = options.Description;
|
||||
_writer.reset(new ZipWriter());
|
||||
_inputData = stringstream();
|
||||
_saveStateData = stringstream();
|
||||
_hasSaveState = false;
|
||||
|
||||
if(!_writer->Initialize(_filename)) {
|
||||
_writer.reset();
|
||||
return false;
|
||||
} else {
|
||||
_console->Pause();
|
||||
|
||||
if(options.RecordFrom == RecordMovieFrom::StartWithoutSaveData) {
|
||||
_console->GetBatteryManager()->SetBatteryProvider(shared_from_this());
|
||||
}
|
||||
|
||||
//Save existing battery files
|
||||
if(options.RecordFrom == RecordMovieFrom::StartWithSaveData) {
|
||||
_console->GetBatteryManager()->SetBatteryRecorder(shared_from_this());
|
||||
}
|
||||
|
||||
_console->GetNotificationManager()->RegisterNotificationListener(shared_from_this());
|
||||
if(options.RecordFrom == RecordMovieFrom::CurrentState) {
|
||||
_console->GetControlManager()->RegisterInputRecorder(this);
|
||||
_console->GetSaveStateManager()->SaveState(_saveStateData);
|
||||
_hasSaveState = true;
|
||||
} else {
|
||||
_console->PowerCycle();
|
||||
}
|
||||
_console->GetBatteryManager()->SetBatteryRecorder(nullptr);
|
||||
_console->Resume();
|
||||
|
||||
MessageManager::DisplayMessage("Movies", "MovieRecordingTo", FolderUtilities::GetFilename(_filename, true));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MovieRecorder::GetGameSettings(stringstream &out)
|
||||
{
|
||||
EmulationSettings* settings = _console->GetSettings();
|
||||
NesModel model = _console->GetModel();
|
||||
|
||||
WriteString(out, MovieKeys::MesenVersion, settings->GetMesenVersionString());
|
||||
WriteInt(out, MovieKeys::MovieFormatVersion, MovieRecorder::MovieFormatVersion);
|
||||
|
||||
VirtualFile romFile = _console->GetRomPath();
|
||||
WriteString(out, MovieKeys::GameFile, romFile.GetFileName());
|
||||
WriteString(out, MovieKeys::Sha1, romFile.GetSha1Hash());
|
||||
|
||||
VirtualFile patchFile = _console->GetPatchFile();
|
||||
if(patchFile.IsValid()) {
|
||||
WriteString(out, MovieKeys::PatchFile, patchFile.GetFileName());
|
||||
WriteString(out, MovieKeys::PatchFileSha1, patchFile.GetSha1Hash());
|
||||
WriteString(out, MovieKeys::PatchedRomSha1, _console->GetRomInfo().Hash.Sha1);
|
||||
}
|
||||
|
||||
switch(model) {
|
||||
case NesModel::Auto: break; //Console::GetModel() will never return Auto.
|
||||
case NesModel::NTSC: WriteString(out, MovieKeys::Region, "NTSC"); break;
|
||||
case NesModel::PAL: WriteString(out, MovieKeys::Region, "PAL"); break;
|
||||
case NesModel::Dendy: WriteString(out, MovieKeys::Region, "Dendy"); break;
|
||||
}
|
||||
|
||||
switch(settings->GetConsoleType()) {
|
||||
case ConsoleType::Nes: WriteString(out, MovieKeys::ConsoleType, "NES"); break;
|
||||
case ConsoleType::Famicom: WriteString(out, MovieKeys::ConsoleType, "Famicom"); break;
|
||||
}
|
||||
|
||||
WriteString(out, MovieKeys::Controller1, ControllerTypeNames[(int)settings->GetControllerType(0)]);
|
||||
WriteString(out, MovieKeys::Controller2, ControllerTypeNames[(int)settings->GetControllerType(1)]);
|
||||
if(settings->CheckFlag(EmulationFlags::HasFourScore)) {
|
||||
WriteString(out, MovieKeys::Controller3, ControllerTypeNames[(int)settings->GetControllerType(2)]);
|
||||
WriteString(out, MovieKeys::Controller4, ControllerTypeNames[(int)settings->GetControllerType(3)]);
|
||||
}
|
||||
|
||||
if(settings->GetConsoleType() == ConsoleType::Famicom) {
|
||||
WriteString(out, MovieKeys::ExpansionDevice, ExpansionPortDeviceNames[(int)settings->GetExpansionDevice()]);
|
||||
}
|
||||
|
||||
WriteInt(out, MovieKeys::ExtraScanlinesBeforeNmi, settings->GetPpuExtraScanlinesBeforeNmi());
|
||||
WriteInt(out, MovieKeys::ExtraScanlinesAfterNmi, settings->GetPpuExtraScanlinesAfterNmi());
|
||||
WriteInt(out, MovieKeys::InputPollScanline, settings->GetInputPollScanline());
|
||||
|
||||
WriteBool(out, MovieKeys::DisablePpu2004Reads, settings->CheckFlag(EmulationFlags::DisablePpu2004Reads));
|
||||
WriteBool(out, MovieKeys::DisablePaletteRead, settings->CheckFlag(EmulationFlags::DisablePaletteRead));
|
||||
WriteBool(out, MovieKeys::DisableOamAddrBug, settings->CheckFlag(EmulationFlags::DisableOamAddrBug));
|
||||
WriteBool(out, MovieKeys::UseNes101Hvc101Behavior, settings->CheckFlag(EmulationFlags::UseNes101Hvc101Behavior));
|
||||
WriteBool(out, MovieKeys::EnableOamDecay, settings->CheckFlag(EmulationFlags::EnableOamDecay));
|
||||
WriteBool(out, MovieKeys::DisablePpuReset, settings->CheckFlag(EmulationFlags::DisablePpuReset));
|
||||
|
||||
WriteInt(out, MovieKeys::ZapperDetectionRadius, settings->GetZapperDetectionRadius());
|
||||
|
||||
switch(settings->GetRamPowerOnState()) {
|
||||
case RamPowerOnState::AllZeros: WriteInt(out, MovieKeys::RamPowerOnState, 0x00); break;
|
||||
case RamPowerOnState::AllOnes: WriteInt(out, MovieKeys::RamPowerOnState, 0xFF); break;
|
||||
case RamPowerOnState::Random: WriteInt(out, MovieKeys::RamPowerOnState, -1); break; //TODO: Shouldn't be used for movies
|
||||
}
|
||||
|
||||
if(_console->GetDipSwitchCount() > 0) {
|
||||
WriteString(out, MovieKeys::DipSwitches, HexUtilities::ToHex(settings->GetDipSwitches()));
|
||||
}
|
||||
|
||||
for(CodeInfo &code : _console->GetCheatManager()->GetCheats()) {
|
||||
WriteCheat(out, code);
|
||||
}
|
||||
}
|
||||
|
||||
void MovieRecorder::WriteCheat(stringstream &out, CodeInfo &code)
|
||||
{
|
||||
out << "Cheat " <<
|
||||
HexUtilities::ToHex(code.Address) << " " <<
|
||||
HexUtilities::ToHex(code.Value) << " " <<
|
||||
(code.IsRelativeAddress ? "true" : "false") << " " <<
|
||||
(code.CompareValue < 0 ? HexUtilities::ToHex((uint8_t)code.CompareValue) : "") << "\n";
|
||||
}
|
||||
|
||||
void MovieRecorder::WriteString(stringstream &out, string name, string value)
|
||||
{
|
||||
out << name << " " << value << "\n";
|
||||
}
|
||||
|
||||
void MovieRecorder::WriteInt(stringstream &out, string name, uint32_t value)
|
||||
{
|
||||
out << name << " " << std::to_string(value) << "\n";
|
||||
}
|
||||
|
||||
void MovieRecorder::WriteBool(stringstream &out, string name, bool enabled)
|
||||
{
|
||||
out << name << " " << (enabled ? "true" : "false") << "\n";
|
||||
}
|
||||
|
||||
bool MovieRecorder::Stop()
|
||||
{
|
||||
if(_writer) {
|
||||
_console->GetControlManager()->UnregisterInputRecorder(this);
|
||||
|
||||
_writer->AddFile(_inputData, "Input.txt");
|
||||
|
||||
stringstream out;
|
||||
GetGameSettings(out);
|
||||
_writer->AddFile(out, "GameSettings.txt");
|
||||
|
||||
if(!_author.empty() || !_description.empty()) {
|
||||
stringstream movieInfo;
|
||||
WriteString(movieInfo, "Author", _author);
|
||||
movieInfo << "Description\n" << _description;
|
||||
_writer->AddFile(movieInfo, "MovieInfo.txt");
|
||||
}
|
||||
|
||||
VirtualFile patchFile = _console->GetPatchFile();
|
||||
vector<uint8_t> patchData;
|
||||
if(patchFile.IsValid() && patchFile.ReadFile(patchData)) {
|
||||
_writer->AddFile(patchData, "PatchData.dat");
|
||||
}
|
||||
|
||||
if(_hasSaveState) {
|
||||
_writer->AddFile(_saveStateData, "SaveState.mst");
|
||||
}
|
||||
|
||||
for(auto kvp : _batteryData) {
|
||||
_writer->AddFile(kvp.second, "Battery" + kvp.first);
|
||||
}
|
||||
|
||||
bool result = _writer->Save();
|
||||
if(result) {
|
||||
MessageManager::DisplayMessage("Movies", "MovieSaved", FolderUtilities::GetFilename(_filename, true));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MovieRecorder::RecordInput(vector<shared_ptr<BaseControlDevice>> devices)
|
||||
{
|
||||
for(shared_ptr<BaseControlDevice> &device : devices) {
|
||||
_inputData << ("|" + device->GetTextState());
|
||||
}
|
||||
_inputData << "\n";
|
||||
}
|
||||
|
||||
void MovieRecorder::OnLoadBattery(string extension, vector<uint8_t> batteryData)
|
||||
{
|
||||
_batteryData[extension] = batteryData;
|
||||
}
|
||||
|
||||
vector<uint8_t> MovieRecorder::LoadBattery(string extension)
|
||||
{
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
|
||||
void MovieRecorder::ProcessNotification(ConsoleNotificationType type, void *parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::GameLoaded) {
|
||||
_console->GetControlManager()->RegisterInputRecorder(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool MovieRecorder::CreateMovie(string movieFile, std::deque<RewindData> &data, uint32_t startPosition, uint32_t endPosition)
|
||||
{
|
||||
_filename = movieFile;
|
||||
_writer.reset(new ZipWriter());
|
||||
if(startPosition < data.size() && endPosition <= data.size() && _writer->Initialize(_filename)) {
|
||||
vector<shared_ptr<BaseControlDevice>> devices = _console->GetControlManager()->GetControlDevices();
|
||||
|
||||
if(startPosition > 0 || _console->GetRomInfo().HasBattery || _console->GetSettings()->GetRamPowerOnState() == RamPowerOnState::Random) {
|
||||
//Create a movie from a savestate if we don't start from the beginning (or if the game has save ram, or if the power on ram state is random)
|
||||
_hasSaveState = true;
|
||||
_saveStateData = stringstream();
|
||||
_console->GetSaveStateManager()->GetSaveStateHeader(_saveStateData);
|
||||
data[startPosition].GetStateData(_saveStateData);
|
||||
}
|
||||
|
||||
_inputData = stringstream();
|
||||
|
||||
for(uint32_t i = startPosition; i < endPosition; i++) {
|
||||
RewindData rewindData = data[i];
|
||||
for(uint32_t i = 0; i < 30; i++) {
|
||||
for(shared_ptr<BaseControlDevice> &device : devices) {
|
||||
uint8_t port = device->GetPort();
|
||||
if(i < rewindData.InputLogs[port].size()) {
|
||||
device->SetRawState(rewindData.InputLogs[port][i]);
|
||||
_inputData << ("|" + device->GetTextState());
|
||||
}
|
||||
}
|
||||
_inputData << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
//Write the movie file
|
||||
return Stop();
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include "IInputRecorder.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "Types.h"
|
||||
#include "INotificationListener.h"
|
||||
|
||||
class ZipWriter;
|
||||
class Console;
|
||||
class RewindData;
|
||||
struct CodeInfo;
|
||||
|
||||
class MovieRecorder : public INotificationListener, public IInputRecorder, public IBatteryRecorder, public IBatteryProvider, public std::enable_shared_from_this<MovieRecorder>
|
||||
{
|
||||
private:
|
||||
static const uint32_t MovieFormatVersion = 1;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
string _filename;
|
||||
string _author;
|
||||
string _description;
|
||||
unique_ptr<ZipWriter> _writer;
|
||||
std::unordered_map<string, vector<uint8_t>> _batteryData;
|
||||
stringstream _inputData;
|
||||
bool _hasSaveState = false;
|
||||
stringstream _saveStateData;
|
||||
|
||||
void GetGameSettings(stringstream &out);
|
||||
void WriteCheat(stringstream &out, CodeInfo &code);
|
||||
void WriteString(stringstream &out, string name, string value);
|
||||
void WriteInt(stringstream &out, string name, uint32_t value);
|
||||
void WriteBool(stringstream &out, string name, bool enabled);
|
||||
|
||||
public:
|
||||
MovieRecorder(shared_ptr<Console> console);
|
||||
virtual ~MovieRecorder();
|
||||
|
||||
bool Record(RecordMovieOptions options);
|
||||
bool Stop();
|
||||
|
||||
bool CreateMovie(string movieFile, std::deque<RewindData> &data, uint32_t startPosition, uint32_t endPosition);
|
||||
|
||||
// Inherited via IInputRecorder
|
||||
void RecordInput(vector<shared_ptr<BaseControlDevice>> devices) override;
|
||||
|
||||
// Inherited via IBatteryRecorder
|
||||
virtual void OnLoadBattery(string extension, vector<uint8_t> batteryData) override;
|
||||
|
||||
// Inherited via IBatteryProvider
|
||||
virtual vector<uint8_t> LoadBattery(string extension) override;
|
||||
|
||||
// Inherited via INotificationListener
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void *parameter) override;
|
||||
};
|
||||
|
||||
namespace MovieKeys
|
||||
{
|
||||
constexpr const char* MesenVersion = "MesenVersion";
|
||||
constexpr const char* MovieFormatVersion = "MovieFormatVersion";
|
||||
constexpr const char* GameFile = "GameFile";
|
||||
constexpr const char* Sha1 = "SHA1";
|
||||
constexpr const char* PatchFile = "PatchFile";
|
||||
constexpr const char* PatchFileSha1 = "PatchFileSHA1";
|
||||
constexpr const char* PatchedRomSha1 = "PatchedRomSHA1";
|
||||
constexpr const char* Region = "Region";
|
||||
constexpr const char* ConsoleType = "ConsoleType";
|
||||
constexpr const char* Controller1 = "Controller1";
|
||||
constexpr const char* Controller2 = "Controller2";
|
||||
constexpr const char* Controller3 = "Controller3";
|
||||
constexpr const char* Controller4 = "Controller4";
|
||||
constexpr const char* ExpansionDevice = "ExpansionDevice";
|
||||
constexpr const char* ExtraScanlinesBeforeNmi = "ExtraScanlinesBeforeNmi";
|
||||
constexpr const char* ExtraScanlinesAfterNmi = "ExtraScanlinesAfterNmi";
|
||||
constexpr const char* DisablePpu2004Reads = "DisablePpu2004Reads";
|
||||
constexpr const char* DisablePaletteRead = "DisablePaletteRead";
|
||||
constexpr const char* DisableOamAddrBug = "DisableOamAddrBug";
|
||||
constexpr const char* UseNes101Hvc101Behavior = "UseNes101Hvc101Behavior";
|
||||
constexpr const char* EnableOamDecay = "EnableOamDecay";
|
||||
constexpr const char* DisablePpuReset = "DisablePpuReset";
|
||||
constexpr const char* ZapperDetectionRadius = "ZapperDetectionRadius";
|
||||
constexpr const char* RamPowerOnState = "RamPowerOnState";
|
||||
constexpr const char* DipSwitches = "DipSwitches";
|
||||
constexpr const char* InputPollScanline = "InputPollScanline";
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "RewindData.h"
|
||||
#include "Console.h"
|
||||
#include "../Utilities/miniz.h"
|
||||
|
||||
void RewindData::GetStateData(stringstream &stateData)
|
||||
{
|
||||
unsigned long length = OriginalSaveStateSize;
|
||||
uint8_t* buffer = new uint8_t[length];
|
||||
uncompress(buffer, &length, SaveStateData.data(), (unsigned long)SaveStateData.size());
|
||||
stateData.write((char*)buffer, length);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
void RewindData::LoadState(shared_ptr<Console> &console)
|
||||
{
|
||||
if(SaveStateData.size() > 0 && OriginalSaveStateSize > 0) {
|
||||
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(shared_ptr<Console> &console)
|
||||
{
|
||||
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;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "BaseControlDevice.h"
|
||||
|
||||
class Console;
|
||||
|
||||
class RewindData
|
||||
{
|
||||
private:
|
||||
vector<uint8_t> SaveStateData;
|
||||
uint32_t OriginalSaveStateSize = 0;
|
||||
|
||||
void CompressState(string stateData, vector<uint8_t> &compressedState);
|
||||
|
||||
public:
|
||||
std::deque<ControlDeviceState> InputLogs[BaseControlDevice::PortCount];
|
||||
int32_t FrameCount = 0;
|
||||
bool EndOfSegment = false;
|
||||
|
||||
void GetStateData(stringstream &stateData);
|
||||
|
||||
void LoadState(shared_ptr<Console> &console);
|
||||
void SaveState(shared_ptr<Console> &console);
|
||||
};
|
@ -1,382 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "RewindManager.h"
|
||||
#include "MessageManager.h"
|
||||
#include "Console.h"
|
||||
#include "VideoRenderer.h"
|
||||
#include "SoundMixer.h"
|
||||
#include "BaseControlDevice.h"
|
||||
#include "HistoryViewer.h"
|
||||
|
||||
RewindManager::RewindManager(shared_ptr<Console> console)
|
||||
{
|
||||
_console = console;
|
||||
_settings = console->GetSettings();
|
||||
_rewindState = RewindState::Stopped;
|
||||
_framesToFastForward = 0;
|
||||
_hasHistory = false;
|
||||
AddHistoryBlock();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
RewindManager::~RewindManager()
|
||||
{
|
||||
_console->GetControlManager()->UnregisterInputProvider(this);
|
||||
_console->GetControlManager()->UnregisterInputRecorder(this);
|
||||
}
|
||||
|
||||
void RewindManager::Initialize()
|
||||
{
|
||||
_console->GetControlManager()->RegisterInputProvider(this);
|
||||
_console->GetControlManager()->RegisterInputRecorder(this);
|
||||
}
|
||||
|
||||
void RewindManager::ClearBuffer()
|
||||
{
|
||||
_hasHistory = false;
|
||||
_history.clear();
|
||||
_historyBackup.clear();
|
||||
_currentHistory = RewindData();
|
||||
_framesToFastForward = 0;
|
||||
_videoHistory.clear();
|
||||
_videoHistoryBuilder.clear();
|
||||
_audioHistory.clear();
|
||||
_audioHistoryBuilder.clear();
|
||||
_rewindState = RewindState::Stopped;
|
||||
_currentHistory = RewindData();
|
||||
}
|
||||
|
||||
void RewindManager::ProcessNotification(ConsoleNotificationType type, void * parameter)
|
||||
{
|
||||
if(_settings->IsRunAheadFrame()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(type == ConsoleNotificationType::PpuFrameDone) {
|
||||
_hasHistory = _history.size() >= 2;
|
||||
if(_settings->GetRewindBufferSize() > 0) {
|
||||
switch(_rewindState) {
|
||||
case RewindState::Starting:
|
||||
case RewindState::Started:
|
||||
case RewindState::Debugging:
|
||||
_currentHistory.FrameCount--;
|
||||
break;
|
||||
|
||||
case 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;
|
||||
_settings->ClearFlags(EmulationFlags::Rewind);
|
||||
_settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
}
|
||||
break;
|
||||
|
||||
case RewindState::Stopped:
|
||||
_currentHistory.FrameCount++;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ClearBuffer();
|
||||
}
|
||||
} else if(type == ConsoleNotificationType::StateLoaded) {
|
||||
if(_rewindState == RewindState::Stopped) {
|
||||
//A save state was loaded by the user, mark as the end of the current "segment" (for history viewer)
|
||||
_currentHistory.EndOfSegment = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::AddHistoryBlock()
|
||||
{
|
||||
uint32_t maxHistorySize = _settings->GetRewindBufferSize() * 120;
|
||||
if(maxHistorySize > 0) {
|
||||
while(_history.size() > maxHistorySize) {
|
||||
_history.pop_front();
|
||||
}
|
||||
|
||||
if(_currentHistory.FrameCount > 0) {
|
||||
_history.push_back(_currentHistory);
|
||||
}
|
||||
_currentHistory = RewindData();
|
||||
_currentHistory.SaveState(_console);
|
||||
}
|
||||
}
|
||||
|
||||
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(_console);
|
||||
if(!_audioHistoryBuilder.empty()) {
|
||||
_audioHistory.insert(_audioHistory.begin(), _audioHistoryBuilder.begin(), _audioHistoryBuilder.end());
|
||||
_audioHistoryBuilder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::Start(bool forDebugger)
|
||||
{
|
||||
if(_rewindState == RewindState::Stopped && _settings->GetRewindBufferSize() > 0) {
|
||||
if(_history.empty() && !forDebugger) {
|
||||
//No history to rewind
|
||||
return;
|
||||
}
|
||||
|
||||
_console->Pause();
|
||||
|
||||
_rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting;
|
||||
_videoHistoryBuilder.clear();
|
||||
_videoHistory.clear();
|
||||
_audioHistoryBuilder.clear();
|
||||
_audioHistory.clear();
|
||||
_historyBackup.clear();
|
||||
|
||||
if(_history.empty()) {
|
||||
_currentHistory.LoadState(_console);
|
||||
} else {
|
||||
PopHistory();
|
||||
}
|
||||
|
||||
_settings->SetFlags(EmulationFlags::ForceMaxSpeed);
|
||||
_settings->SetFlags(EmulationFlags::Rewind);
|
||||
|
||||
_console->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::ForceStop()
|
||||
{
|
||||
if(_rewindState != RewindState::Stopped) {
|
||||
while(_historyBackup.size() > 1) {
|
||||
_history.push_back(_historyBackup.front());
|
||||
_historyBackup.pop_front();
|
||||
}
|
||||
if(!_historyBackup.empty()) {
|
||||
_currentHistory = _historyBackup.front();
|
||||
}
|
||||
_historyBackup.clear();
|
||||
_rewindState = RewindState::Stopped;
|
||||
_settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
_settings->ClearFlags(EmulationFlags::Rewind);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if(_historyBackup.size() > 1) {
|
||||
_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 && _historyBackup.size() > 1);
|
||||
}
|
||||
} 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(_console);
|
||||
if(_framesToFastForward > 0) {
|
||||
_rewindState = RewindState::Stopping;
|
||||
_currentHistory.FrameCount = 0;
|
||||
_settings->SetFlags(EmulationFlags::ForceMaxSpeed);
|
||||
} else {
|
||||
_rewindState = RewindState::Stopped;
|
||||
_historyBackup.clear();
|
||||
_settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
_settings->ClearFlags(EmulationFlags::Rewind);
|
||||
}
|
||||
|
||||
_videoHistoryBuilder.clear();
|
||||
_videoHistory.clear();
|
||||
_audioHistoryBuilder.clear();
|
||||
_audioHistory.clear();
|
||||
|
||||
_console->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::ProcessEndOfFrame()
|
||||
{
|
||||
if(_rewindState >= RewindState::Starting) {
|
||||
if(_currentHistory.FrameCount <= 0 && _rewindState != RewindState::Debugging) {
|
||||
//If we're debugging, we want to keep running the emulation to the end of the next frame (even if it's incomplete)
|
||||
//Otherwise the emulation might diverge due to missing inputs.
|
||||
PopHistory();
|
||||
}
|
||||
} else if(_currentHistory.FrameCount >= RewindManager::BufferSize) {
|
||||
AddHistoryBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::ProcessFrame(void * frameBuffer, uint32_t width, uint32_t height, bool forRewind)
|
||||
{
|
||||
if(_rewindState == RewindState::Starting || _rewindState == RewindState::Started) {
|
||||
if(!forRewind) {
|
||||
//Ignore any frames that occur between start of rewind process & first rewinded frame completed
|
||||
//These are caused by the fact that VideoDecoder is asynchronous - a previous (extra) frame can end up
|
||||
//in the rewind queue, which causes display glitches
|
||||
return;
|
||||
}
|
||||
|
||||
_videoHistoryBuilder.push_back(vector<uint32_t>((uint32_t*)frameBuffer, (uint32_t*)frameBuffer + width*height));
|
||||
|
||||
if(_videoHistoryBuilder.size() == (size_t)_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;
|
||||
_settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
|
||||
if(!_videoHistory.empty()) {
|
||||
_console->GetVideoRenderer()->UpdateFrame(_videoHistory.back().data(), width, height);
|
||||
_videoHistory.pop_back();
|
||||
}
|
||||
}
|
||||
} else if(_rewindState == RewindState::Stopping || _rewindState == RewindState::Debugging) {
|
||||
//Display nothing while resyncing
|
||||
} else {
|
||||
_console->GetVideoRenderer()->UpdateFrame(frameBuffer, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
bool RewindManager::ProcessAudio(int16_t * soundBuffer, uint32_t sampleCount, uint32_t sampleRate)
|
||||
{
|
||||
if(_rewindState == RewindState::Starting || _rewindState == RewindState::Started) {
|
||||
_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 || _rewindState == RewindState::Debugging) {
|
||||
//Mute while we resync
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::RecordInput(vector<shared_ptr<BaseControlDevice>> devices)
|
||||
{
|
||||
if(_settings->GetRewindBufferSize() > 0 && _rewindState == RewindState::Stopped) {
|
||||
for(shared_ptr<BaseControlDevice> &device : devices) {
|
||||
_currentHistory.InputLogs[device->GetPort()].push_back(device->GetRawState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RewindManager::SetInput(BaseControlDevice *device)
|
||||
{
|
||||
uint8_t port = device->GetPort();
|
||||
if(!_currentHistory.InputLogs[port].empty() && IsRewinding()) {
|
||||
ControlDeviceState state = _currentHistory.InputLogs[port].front();
|
||||
_currentHistory.InputLogs[port].pop_front();
|
||||
device->SetRawState(state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RewindManager::StartRewinding(bool forDebugger)
|
||||
{
|
||||
Start(forDebugger);
|
||||
}
|
||||
|
||||
void RewindManager::StopRewinding(bool forDebugger)
|
||||
{
|
||||
if(forDebugger) {
|
||||
ForceStop();
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool RewindManager::IsRewinding()
|
||||
{
|
||||
return _rewindState != RewindState::Stopped;
|
||||
}
|
||||
|
||||
bool RewindManager::IsStepBack()
|
||||
{
|
||||
return _rewindState == RewindState::Debugging;
|
||||
}
|
||||
|
||||
void RewindManager::RewindSeconds(uint32_t seconds)
|
||||
{
|
||||
if(_rewindState == RewindState::Stopped) {
|
||||
uint32_t removeCount = (seconds * 60 / RewindManager::BufferSize) + 1;
|
||||
_console->Pause();
|
||||
for(uint32_t i = 0; i < removeCount; i++) {
|
||||
if(!_history.empty()) {
|
||||
_currentHistory = _history.back();
|
||||
_history.pop_back();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_currentHistory.LoadState(_console);
|
||||
_console->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
bool RewindManager::HasHistory()
|
||||
{
|
||||
return _hasHistory;
|
||||
}
|
||||
|
||||
void RewindManager::CopyHistory(shared_ptr<HistoryViewer> destHistoryViewer)
|
||||
{
|
||||
destHistoryViewer->SetHistoryData(_history);
|
||||
}
|
||||
|
||||
void RewindManager::SendFrame(void * frameBuffer, uint32_t width, uint32_t height, bool forRewind)
|
||||
{
|
||||
ProcessFrame(frameBuffer, width, height, forRewind);
|
||||
}
|
||||
|
||||
bool RewindManager::SendAudio(int16_t * soundBuffer, uint32_t sampleCount, uint32_t sampleRate)
|
||||
{
|
||||
return ProcessAudio(soundBuffer, sampleCount, sampleRate);
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include <deque>
|
||||
#include "INotificationListener.h"
|
||||
#include "RewindData.h"
|
||||
#include "IInputProvider.h"
|
||||
#include "IInputRecorder.h"
|
||||
|
||||
class Console;
|
||||
class HistoryViewer;
|
||||
|
||||
enum class RewindState
|
||||
{
|
||||
Stopped = 0,
|
||||
Stopping = 1,
|
||||
Starting = 2,
|
||||
Started = 3,
|
||||
Debugging = 4
|
||||
};
|
||||
|
||||
class RewindManager : public INotificationListener, public IInputProvider, public IInputRecorder
|
||||
{
|
||||
private:
|
||||
static constexpr int32_t BufferSize = 30; //Number of frames between each save state
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
EmulationSettings* _settings;
|
||||
|
||||
bool _hasHistory;
|
||||
|
||||
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(bool forDebugger);
|
||||
void Stop();
|
||||
void ForceStop();
|
||||
|
||||
void ProcessFrame(void *frameBuffer, uint32_t width, uint32_t height, bool forRewind);
|
||||
bool ProcessAudio(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate);
|
||||
|
||||
void ClearBuffer();
|
||||
|
||||
public:
|
||||
RewindManager(shared_ptr<Console> console);
|
||||
virtual ~RewindManager();
|
||||
|
||||
void Initialize();
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
void ProcessEndOfFrame();
|
||||
|
||||
void RecordInput(vector<shared_ptr<BaseControlDevice>> devices) override;
|
||||
bool SetInput(BaseControlDevice *device) override;
|
||||
|
||||
void StartRewinding(bool forDebugger = false);
|
||||
void StopRewinding(bool forDebugger = false);
|
||||
bool IsRewinding();
|
||||
bool IsStepBack();
|
||||
void RewindSeconds(uint32_t seconds);
|
||||
|
||||
bool HasHistory();
|
||||
void CopyHistory(shared_ptr<HistoryViewer> destHistoryViewer);
|
||||
|
||||
void SendFrame(void *frameBuffer, uint32_t width, uint32_t height, bool forRewind);
|
||||
bool SendAudio(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate);
|
||||
};
|
@ -5,7 +5,6 @@
|
||||
#include "EmulationSettings.h"
|
||||
#include "VideoDecoder.h"
|
||||
#include "Debugger.h"
|
||||
#include "MovieManager.h"
|
||||
#include "RomData.h"
|
||||
#include "DefaultVideoFilter.h"
|
||||
#include "PPU.h"
|
||||
@ -97,10 +96,6 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
|
||||
}
|
||||
}
|
||||
|
||||
//Stop any movie that might have been playing/recording if a state is loaded
|
||||
//(Note: Loading a state is disabled in the UI while a movie is playing/recording)
|
||||
MovieManager::Stop();
|
||||
|
||||
_console->LoadState(stream, fileFormatVersion);
|
||||
|
||||
return true;
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "VsZapper.h"
|
||||
#include <assert.h>
|
||||
#include "StandardController.h"
|
||||
#include "MovieManager.h"
|
||||
#include "IInputProvider.h"
|
||||
|
||||
class BaseControlDevice;
|
||||
@ -68,4 +67,4 @@ public:
|
||||
|
||||
// Inherited via IInputProvider
|
||||
virtual bool SetInput(BaseControlDevice* device) override;
|
||||
};
|
||||
};
|
||||
|
@ -28,7 +28,6 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
|
||||
$(CORE_DIR)/BaseVideoFilter.cpp \
|
||||
$(CORE_DIR)/BatteryManager.cpp \
|
||||
$(CORE_DIR)/BisqwitNtscFilter.cpp \
|
||||
$(CORE_DIR)/BizhawkMovie.cpp \
|
||||
$(CORE_DIR)/Breakpoint.cpp \
|
||||
$(CORE_DIR)/CheatManager.cpp \
|
||||
$(CORE_DIR)/CodeDataLogger.cpp \
|
||||
@ -46,22 +45,15 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
|
||||
$(CORE_DIR)/EmulationSettings.cpp \
|
||||
$(CORE_DIR)/EventManager.cpp \
|
||||
$(CORE_DIR)/ExpressionEvaluator.cpp \
|
||||
$(CORE_DIR)/FceuxMovie.cpp \
|
||||
$(CORE_DIR)/FDS.cpp \
|
||||
$(CORE_DIR)/FdsLoader.cpp \
|
||||
$(CORE_DIR)/GameClient.cpp \
|
||||
$(CORE_DIR)/GameClientConnection.cpp \
|
||||
$(CORE_DIR)/GameConnection.cpp \
|
||||
$(CORE_DIR)/GameDatabase.cpp \
|
||||
$(CORE_DIR)/GameServer.cpp \
|
||||
$(CORE_DIR)/GameServerConnection.cpp \
|
||||
$(CORE_DIR)/HdAudioDevice.cpp \
|
||||
$(CORE_DIR)/HdNesPack.cpp \
|
||||
$(CORE_DIR)/HdPackBuilder.cpp \
|
||||
$(CORE_DIR)/HdPackLoader.cpp \
|
||||
$(CORE_DIR)/HdPpu.cpp \
|
||||
$(CORE_DIR)/HdVideoFilter.cpp \
|
||||
$(CORE_DIR)/HistoryViewer.cpp \
|
||||
$(CORE_DIR)/iNesLoader.cpp \
|
||||
$(CORE_DIR)/KeyManager.cpp \
|
||||
$(CORE_DIR)/LabelManager.cpp \
|
||||
@ -69,10 +61,7 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
|
||||
$(CORE_DIR)/MemoryAccessCounter.cpp \
|
||||
$(CORE_DIR)/MemoryDumper.cpp \
|
||||
$(CORE_DIR)/MemoryManager.cpp \
|
||||
$(CORE_DIR)/MesenMovie.cpp \
|
||||
$(CORE_DIR)/MessageManager.cpp \
|
||||
$(CORE_DIR)/MovieManager.cpp \
|
||||
$(CORE_DIR)/MovieRecorder.cpp \
|
||||
$(CORE_DIR)/NESHeader.cpp \
|
||||
$(CORE_DIR)/NotificationManager.cpp \
|
||||
$(CORE_DIR)/NsfLoader.cpp \
|
||||
@ -83,8 +72,6 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \
|
||||
$(CORE_DIR)/OggReader.cpp \
|
||||
$(CORE_DIR)/PPU.cpp \
|
||||
$(CORE_DIR)/ReverbFilter.cpp \
|
||||
$(CORE_DIR)/RewindData.cpp \
|
||||
$(CORE_DIR)/RewindManager.cpp \
|
||||
$(CORE_DIR)/RomLoader.cpp \
|
||||
$(CORE_DIR)/RotateFilter.cpp \
|
||||
$(CORE_DIR)/SaveStateManager.cpp \
|
||||
|
Loading…
Reference in New Issue
Block a user