Netplay (not finished, but works), save state fixes

This commit is contained in:
Souryo 2014-07-06 19:54:47 -04:00
parent 021efccbed
commit f4802421d0
30 changed files with 979 additions and 79 deletions

View file

@ -78,9 +78,8 @@ bool APU::Exec(uint32_t executedCycles)
if(availableSampleCount >= APU::SamplesPerFrame) {
size_t sampleCount = _buf.read_samples(_outputBuffer, APU::SamplesPerFrame);
APU::AudioDevice->PlayBuffer(_outputBuffer, sampleCount * BitsPerSample / 8);
return true;
}
return true;
}
return false;
}
@ -91,6 +90,8 @@ void APU::StreamState(bool saving)
if(saving) {
_apu.save_snapshot(&snapshot);
}
Stream<uint32_t>(_currentClock);
StreamArray<uint8_t>(snapshot.w40xx, 0x14);
Stream<uint8_t>(snapshot.w4015);

View file

@ -4,11 +4,11 @@
#include "../Utilities/Timer.h"
Console* Console::Instance = nullptr;
IMessageManager* Console::MessageManager = nullptr;
uint32_t Console::Flags = 0;
uint32_t Console::CurrentFPS = 0;
atomic_flag Console::PauseFlag;
atomic_flag Console::RunningFlag;
SimpleLock Console::PauseLock;
SimpleLock Console::RunningLock;
Console::Console(wstring filename)
{
@ -29,16 +29,16 @@ Console::Console(wstring filename)
ResetComponents(false);
Console::PauseFlag.clear();
Console::RunningFlag.clear();
Console::PauseLock.Release();
Console::RunningLock.Release();
Console::Instance = this;
}
Console::~Console()
{
Movie::Stop();
Console::PauseFlag.clear();
Console::RunningFlag.clear();
Console::PauseLock.Release();
Console::RunningLock.Release();
if(Console::Instance == this) {
Console::Instance = nullptr;
}
@ -66,16 +66,16 @@ void Console::Stop()
void Console::Pause()
{
while(Console::PauseFlag.test_and_set());
Console::PauseLock.Acquire();
//Spin wait until emu pauses
while(Console::RunningFlag.test_and_set());
Console::RunningLock.Acquire();
}
void Console::Resume()
{
Console::RunningFlag.clear();
Console::PauseFlag.clear();
Console::RunningLock.Release();
Console::PauseLock.Release();
}
void Console::SetFlags(int flags)
@ -93,6 +93,18 @@ bool Console::CheckFlag(int flag)
return (Console::Flags & flag) == flag;
}
void Console::RegisterMessageManager(IMessageManager* messageManager)
{
Console::MessageManager = messageManager;
}
void Console::DisplayMessage(wstring message)
{
if(Console::MessageManager) {
Console::MessageManager->DisplayMessage(message);
}
}
uint32_t Console::GetFPS()
{
return Console::CurrentFPS;
@ -105,35 +117,18 @@ void Console::Run()
uint32_t lastFrameCount = 0;
double elapsedTime = 0;
double targetTime = 16.6666666666666666;
while(Console::RunningFlag.test_and_set());
Console::RunningLock.Acquire();
while(true) {
uint32_t executedCycles = _cpu->Exec();
_ppu->Exec();
bool frameDone = _apu->Exec(executedCycles);
if(frameDone) {
_cpu->EndFrame();
_ppu->EndFrame();
if(Console::PauseFlag.test_and_set()) {
//Need to temporarely pause the emu (to save/load a state, etc.)
Console::RunningFlag.clear();
//Spin wait until we are allowed to start again
while(Console::PauseFlag.test_and_set());
Console::PauseFlag.clear();
while(Console::RunningFlag.test_and_set());
} else {
Console::PauseFlag.clear();
}
if(_stop) {
_stop = false;
break;
}
if(CheckFlag(EmulationFlags::LimitFPS) && frameDone) {
elapsedTime = clockTimer.GetElapsedMS();
while(targetTime > elapsedTime) {
@ -142,18 +137,34 @@ void Console::Run()
}
elapsedTime = clockTimer.GetElapsedMS();
}
clockTimer.Reset();
}
if(!Console::PauseLock.IsFree()) {
//Need to temporarely pause the emu (to save/load a state, etc.)
Console::RunningLock.Release();
//Spin wait until we are allowed to start again
Console::PauseLock.WaitForRelease();
Console::RunningLock.Acquire();
}
if(fpsTimer.GetElapsedMS() > 1000) {
uint32_t frameCount = _ppu->GetFrameCount();
Console::CurrentFPS = (int)(std::round((double)(frameCount - lastFrameCount) / (fpsTimer.GetElapsedMS() / 1000)));
lastFrameCount = frameCount;
fpsTimer.Reset();
clockTimer.Reset();
if(_stop) {
_stop = false;
break;
}
}
if(fpsTimer.GetElapsedMS() > 1000) {
uint32_t frameCount = _ppu->GetFrameCount();
Console::CurrentFPS = (int)(std::round((double)(frameCount - lastFrameCount) / (fpsTimer.GetElapsedMS() / 1000)));
lastFrameCount = frameCount;
fpsTimer.Reset();
}
}
Console::RunningFlag.clear();
Console::RunningLock.Release();
}
void Console::SaveState(wstring filename)
@ -165,6 +176,8 @@ void Console::SaveState(wstring filename)
Console::SaveState(file);
Console::Resume();
file.close();
Console::DisplayMessage(L"State saved.");
}
}
@ -177,27 +190,45 @@ bool Console::LoadState(wstring filename)
Console::LoadState(file);
Console::Resume();
file.close();
Console::DisplayMessage(L"State loaded.");
return true;
}
Console::DisplayMessage(L"Slot is empty.");
return false;
}
void Console::SaveState(ostream &saveStream)
{
Instance->_cpu->SaveSnapshot(&saveStream);
Instance->_ppu->SaveSnapshot(&saveStream);
Instance->_memoryManager->SaveSnapshot(&saveStream);
Instance->_mapper->SaveSnapshot(&saveStream);
Instance->_apu->SaveSnapshot(&saveStream);
if(Instance) {
Instance->_cpu->SaveSnapshot(&saveStream);
Instance->_ppu->SaveSnapshot(&saveStream);
Instance->_memoryManager->SaveSnapshot(&saveStream);
Instance->_mapper->SaveSnapshot(&saveStream);
Instance->_apu->SaveSnapshot(&saveStream);
Instance->_controlManager->SaveSnapshot(&saveStream);
}
}
void Console::LoadState(istream &loadStream)
{
Instance->_cpu->LoadSnapshot(&loadStream);
Instance->_ppu->LoadSnapshot(&loadStream);
Instance->_memoryManager->LoadSnapshot(&loadStream);
Instance->_mapper->LoadSnapshot(&loadStream);
Instance->_apu->LoadSnapshot(&loadStream);
if(Instance) {
Instance->_cpu->LoadSnapshot(&loadStream);
Instance->_ppu->LoadSnapshot(&loadStream);
Instance->_memoryManager->LoadSnapshot(&loadStream);
Instance->_mapper->LoadSnapshot(&loadStream);
Instance->_apu->LoadSnapshot(&loadStream);
Instance->_controlManager->LoadSnapshot(&loadStream);
}
}
void Console::LoadState(uint8_t *buffer, uint32_t bufferSize)
{
stringstream stream;
stream.write((char*)buffer, bufferSize);
stream.seekg(0, ios::beg);
LoadState(stream);
}
bool Console::RunTest(uint8_t *expectedResult)

View file

@ -7,6 +7,8 @@
#include "BaseMapper.h"
#include "MemoryManager.h"
#include "ControlManager.h"
#include "../Utilities/SimpleLock.h"
#include "IMessageManager.h"
enum EmulationFlags
{
@ -19,8 +21,9 @@ class Console
static Console* Instance;
static uint32_t Flags;
static uint32_t CurrentFPS;
static atomic_flag PauseFlag;
static atomic_flag RunningFlag;
static SimpleLock PauseLock;
static SimpleLock RunningLock;
static IMessageManager* MessageManager;
unique_ptr<CPU> _cpu;
unique_ptr<PPU> _ppu;
@ -56,9 +59,13 @@ class Console
static void SaveState(ostream &saveStream);
static bool LoadState(wstring filename);
static void LoadState(istream &loadStream);
static void LoadState(uint8_t *buffer, uint32_t bufferSize);
static bool CheckFlag(int flag);
static void SetFlags(int flags);
static void ClearFlags(int flags);
static uint32_t GetFPS();
static void RegisterMessageManager(IMessageManager* messageManager);
static void DisplayMessage(wstring message);
};

View file

@ -2,17 +2,60 @@
#include "ControlManager.h"
IControlDevice* ControlManager::ControlDevices[] = { nullptr, nullptr, nullptr, nullptr };
IControlDevice* ControlManager::OriginalControlDevices[] = { nullptr, nullptr, nullptr, nullptr };
IGameBroadcaster* ControlManager::GameBroadcaster = nullptr;
ControlManager::ControlManager()
{
}
void ControlManager::RegisterBroadcaster(IGameBroadcaster* gameBroadcaster)
{
ControlManager::GameBroadcaster = gameBroadcaster;
}
void ControlManager::UnregisterBroadcaster(IGameBroadcaster* gameBroadcaster)
{
if(ControlManager::GameBroadcaster == gameBroadcaster) {
ControlManager::GameBroadcaster = nullptr;
}
}
void ControlManager::BackupControlDevices()
{
for(int i = 0; i < 4; i++) {
OriginalControlDevices[i] = ControlDevices[i];
}
}
void ControlManager::RestoreControlDevices()
{
for(int i = 0; i < 4; i++) {
ControlDevices[i] = OriginalControlDevices[i];
}
}
IControlDevice* ControlManager::GetControlDevice(uint8_t port)
{
return ControlManager::ControlDevices[port];
}
void ControlManager::RegisterControlDevice(IControlDevice* controlDevice, uint8_t port)
{
ControlManager::ControlDevices[port] = controlDevice;
}
void ControlManager::UnregisterControlDevice(IControlDevice* controlDevice)
{
for(int i = 0; i < 4; i++) {
if(ControlManager::ControlDevices[i] == controlDevice) {
ControlManager::ControlDevices[i] = nullptr;
break;
}
}
}
void ControlManager::RefreshAllPorts()
{
RefreshStateBuffer(0);
@ -34,11 +77,7 @@ void ControlManager::RefreshStateBuffer(uint8_t port)
state = Movie::Instance->GetState(port);
} else {
if(controlDevice) {
ButtonState buttonState = controlDevice->GetButtonState();
//"Button status for each controller is returned as an 8-bit report in the following order: A, B, Select, Start, Up, Down, Left, Right."
state = (uint8_t)buttonState.A | ((uint8_t)buttonState.B << 1) | ((uint8_t)buttonState.Select << 2) | ((uint8_t)buttonState.Start << 3) |
((uint8_t)buttonState.Up << 4) | ((uint8_t)buttonState.Down << 5) | ((uint8_t)buttonState.Left << 6) | ((uint8_t)buttonState.Right << 7);
state = controlDevice->GetButtonState().ToByte();
} else {
state = 0x00;
}
@ -46,6 +85,9 @@ void ControlManager::RefreshStateBuffer(uint8_t port)
//Used when recording movies
Movie::Instance->RecordState(port, state);
if(ControlManager::GameBroadcaster) {
ControlManager::GameBroadcaster->BroadcastInput(state, port);
}
_stateBuffer[port] = state;
}
@ -96,4 +138,10 @@ void ControlManager::WriteRAM(uint16_t addr, uint8_t value)
break;
}
}
void ControlManager::StreamState(bool saving)
{
StreamArray<uint8_t>(_stateBuffer, 4);
Stream<bool>(_refreshState);
}

View file

@ -4,11 +4,16 @@
#include "IControlDevice.h"
#include "IMemoryHandler.h"
#include "Movie.h"
#include "IGameBroadcaster.h"
#include "Snapshotable.h"
class ControlManager : public IMemoryHandler
class ControlManager : public Snapshotable, public IMemoryHandler
{
private:
static IControlDevice* ControlDevices[4];
static IControlDevice* OriginalControlDevices[4];
static IGameBroadcaster* GameBroadcaster;
bool _refreshState = false;
uint8_t _stateBuffer[4];
@ -16,10 +21,20 @@ class ControlManager : public IMemoryHandler
void RefreshStateBuffer(uint8_t port);
uint8_t GetPortValue(uint8_t port);
protected:
virtual void StreamState(bool saving);
public:
ControlManager();
static void RegisterBroadcaster(IGameBroadcaster* gameBroadcaster);
static void UnregisterBroadcaster(IGameBroadcaster* gameBroadcaster);
static void BackupControlDevices();
static void RestoreControlDevices();
static IControlDevice* GetControlDevice(uint8_t port);
static void RegisterControlDevice(IControlDevice* controlDevice, uint8_t port);
static void UnregisterControlDevice(IControlDevice* controlDevice);
void GetMemoryRanges(MemoryRanges &ranges)
{

View file

@ -42,6 +42,8 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
@ -93,12 +95,20 @@
<ClInclude Include="CNROM.h" />
<ClInclude Include="ColorDreams.h" />
<ClInclude Include="ControlManager.h" />
<ClInclude Include="GameClient.h" />
<ClInclude Include="GameClientConnection.h" />
<ClInclude Include="GameConnection.h" />
<ClInclude Include="GameServer.h" />
<ClInclude Include="GameServerConnection.h" />
<ClInclude Include="IAudioDevice.h" />
<ClInclude Include="IControlDevice.h" />
<ClInclude Include="IGameBroadcaster.h" />
<ClInclude Include="IMemoryHandler.h" />
<ClInclude Include="Console.h" />
<ClInclude Include="IMessageManager.h" />
<ClInclude Include="MMC2.h" />
<ClInclude Include="Movie.h" />
<ClInclude Include="NetMessage.h" />
<ClInclude Include="Snapshotable.h" />
<ClInclude Include="IVideoDevice.h" />
<ClInclude Include="MapperFactory.h" />
@ -123,6 +133,7 @@
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="UNROM.h" />
<ClInclude Include="VirtualController.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="APU.cpp" />

View file

@ -22,6 +22,9 @@
<Filter Include="Header Files\Mappers">
<UniqueIdentifier>{67b52e86-0ff2-4dbe-b9ed-7c92aace61d5}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\NetPlay">
<UniqueIdentifier>{43f375c6-d7c7-4bbf-8520-ad53e4e1b4a3}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
@ -132,6 +135,33 @@
<ClInclude Include="Movie.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GameConnection.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="GameClient.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="GameServer.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="VirtualController.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="NetMessage.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="IGameBroadcaster.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="GameClientConnection.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="GameServerConnection.h">
<Filter>Header Files\NetPlay</Filter>
</ClInclude>
<ClInclude Include="IMessageManager.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CPU.cpp">

62
Core/GameClient.h Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include "stdafx.h"
#include "GameClientConnection.h"
class GameClient
{
private:
shared_ptr<Socket> _socket;
unique_ptr<GameClientConnection> _connection;
bool _connected = false;
public:
GameClient()
{
}
~GameClient()
{
}
bool Connected()
{
return _connected;
}
void Connect(const char *host, u_short port)
{
_socket.reset(new Socket());
if(_socket->Connect(host, port)) {
_connection.reset(new GameClientConnection(_socket));
_connected = true;
} else {
Console::DisplayMessage(L"Could not connect to server.");
_connected = false;
_socket.reset();
}
}
void Disconnect()
{
if(_connected) {
_connected = false;
_socket.reset();
_connection.reset();
}
}
void Exec()
{
if(_connected) {
if(!_connection->ConnectionError()) {
_connection->ProcessMessages();
_connection->SendInput();
} else {
_connected = false;
_socket.reset();
_connection.reset();
}
}
}
};

91
Core/GameServer.h Normal file
View file

@ -0,0 +1,91 @@
#pragma once
#include "stdafx.h"
#include "GameServerConnection.h"
class GameServer : public IGameBroadcaster
{
private:
shared_ptr<Socket> _listener;
list<shared_ptr<GameServerConnection>> _openConnections;
bool _initialized = false;
void AcceptConnections()
{
while(true) {
shared_ptr<Socket> socket = _listener->Accept(NULL, NULL);
if(!socket->ConnectionError()) {
_openConnections.push_back(shared_ptr<GameServerConnection>(new GameServerConnection(socket, 1, this)));
std::cout << "Client connected." << std::endl;
} else {
break;
}
}
_listener->Listen(10);
}
void 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);
}
}
public:
GameServer()
{
ControlManager::RegisterBroadcaster(this);
}
~GameServer()
{
ControlManager::UnregisterBroadcaster(this);
}
void Start()
{
Console::DisplayMessage(L"Server started.");
_listener.reset(new Socket());
_listener->Bind(8888);
_listener->Listen(10);
_initialized = true;
}
void Exec()
{
if(_initialized) {
AcceptConnections();
UpdateConnections();
}
}
void Stop()
{
_initialized = false;
_listener.reset();
Console::DisplayMessage(L"Server stopped.");
}
bool Started()
{
return _initialized;
}
void BroadcastInput(uint8_t inputData, uint8_t port)
{
for(shared_ptr<GameServerConnection> connection : _openConnections) {
if(!connection->ConnectionError()) {
//Send movie stream
connection->SendMovieData(inputData, port);
}
}
}
};

View file

@ -0,0 +1,95 @@
#pragma once
#include "stdafx.h"
#include "GameConnection.h"
#include "IControlDevice.h"
#include "IGameBroadcaster.h"
class GameServerConnection : public GameConnection, public IControlDevice
{
private:
int _controllerPort;
list<uint8_t> _inputData;
IGameBroadcaster* _gameBroadcaster;
bool _handshakeCompleted = false;
private:
void SendGameState()
{
Console::Pause();
stringstream state;
Console::SaveState(state);
_handshakeCompleted = true;
ControlManager::RegisterControlDevice(this, _controllerPort);
Console::Resume();
uint32_t size = (uint32_t)state.tellp();
char* buffer = new char[size];
state.read(buffer, size);
SendNetMessage(SaveStateMessage(buffer, size));
delete[] buffer;
}
protected:
void ProcessMessage(NetMessage* message)
{
switch(message->Type) {
case MessageType::HandShake:
//Send the game's current state to the client and register the controller
if(((HandShakeMessage*)message)->IsValid()) {
//SendPlayerNumber();
SendGameState();
}
break;
case MessageType::InputData:
uint8_t state = ((InputDataMessage*)message)->InputState;
if(_inputData.size() == 0 || state != _inputData.back()) {
_inputData.push_back(state);
}
break;
}
}
public:
GameServerConnection(shared_ptr<Socket> socket, int controllerPort, IGameBroadcaster* gameBroadcaster) : GameConnection(socket)
{
//Server-side connection
_gameBroadcaster = gameBroadcaster;
_controllerPort = controllerPort;
Console::DisplayMessage(L"Player " + std::to_wstring(_controllerPort) + L" connected.");
ControlManager::BackupControlDevices();
}
~GameServerConnection()
{
Console::DisplayMessage(L"Player " + std::to_wstring(_controllerPort) + L" disconnected.");
ControlManager::RestoreControlDevices();
}
void SendMovieData(uint8_t state, uint8_t port)
{
if(_handshakeCompleted) {
SendNetMessage(MovieDataMessage(state, port));
}
}
ButtonState GetButtonState()
{
ButtonState state;
uint32_t inputBufferSize = _inputData.size();
uint8_t stateData = 0;
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();
}
}
state.FromByte(stateData);
return state;
}
};

View file

@ -14,6 +14,25 @@ struct ButtonState
bool Select = false;
bool Start = false;
uint8_t ToByte()
{
//"Button status for each controller is returned as an 8-bit report in the following order: A, B, Select, Start, Up, Down, Left, Right."
return (uint8_t)A | ((uint8_t)B << 1) | ((uint8_t)Select << 2) | ((uint8_t)Start << 3) |
((uint8_t)Up << 4) | ((uint8_t)Down << 5) | ((uint8_t)Left << 6) | ((uint8_t)Right << 7);
}
void FromByte(uint8_t stateData)
{
A = (stateData & 0x01) == 0x01;
B = (stateData & 0x02) == 0x02;
Select = (stateData & 0x04) == 0x04;
Start = (stateData & 0x08) == 0x08;
Up = (stateData & 0x10) == 0x10;
Down = (stateData & 0x20) == 0x20;
Left = (stateData & 0x40) == 0x40;
Right = (stateData & 0x80) == 0x80;
}
};
class IControlDevice

9
Core/IGameBroadcaster.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include "stdafx.h"
class IGameBroadcaster
{
public:
virtual void BroadcastInput(uint8_t inputData, uint8_t port) = 0;
};

9
Core/IMessageManager.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include "stdafx.h"
class IMessageManager
{
public:
virtual void DisplayMessage(wstring message) = 0;
};

View file

@ -43,6 +43,7 @@ uint8_t Movie::GetState(uint8_t port)
if(_readPosition[port] >= _data.DataSize[port]) {
//End of movie file
Console::DisplayMessage(L"Movie ended.");
_playing = false;
}
@ -82,6 +83,8 @@ void Movie::StartRecording(wstring filename, bool reset)
_recording = true;
Console::Resume();
Console::DisplayMessage(L"Recording...");
}
}
@ -113,6 +116,7 @@ void Movie::PlayMovie(wstring filename)
}
_playing = true;
Console::Resume();
Console::DisplayMessage(L"Playing movie: " + FolderUtilities::GetFilename(filename, true));
}
}

150
Core/NetMessage.h Normal file
View file

@ -0,0 +1,150 @@
#pragma once
#include "stdafx.h"
#include "Console.h"
enum class MessageType : uint8_t
{
HandShake = 0,
SaveState = 1,
InputData = 2,
MovieData = 3,
};
class NetMessage
{
public:
MessageType Type;
NetMessage(MessageType type)
{
Type = type;
}
virtual void Send(Socket &socket) = 0;
};
class HandShakeMessage : public NetMessage
{
private:
const int CurrentVersion = 1;
public:
uint32_t ProtocolVersion;
HandShakeMessage(char *readBuffer) : NetMessage(MessageType::HandShake)
{
ProtocolVersion = *(uint32_t*)readBuffer;
}
HandShakeMessage() : NetMessage(MessageType::HandShake)
{
ProtocolVersion = 1;
}
bool IsValid()
{
return ProtocolVersion == CurrentVersion;
}
virtual void Send(Socket &socket)
{
uint32_t messageLength = sizeof(Type) + sizeof(ProtocolVersion);
socket.BufferedSend((char*)&messageLength, sizeof(messageLength));
socket.BufferedSend((char*)&Type, sizeof(Type));
socket.BufferedSend((char*)&ProtocolVersion, sizeof(ProtocolVersion));
socket.SendBuffer();
}
};
class InputDataMessage : public NetMessage
{
public:
uint8_t InputState;
InputDataMessage(char *readBuffer) : NetMessage(MessageType::InputData)
{
InputState = readBuffer[0];
}
InputDataMessage(uint8_t inputState) : NetMessage(MessageType::InputData)
{
InputState = inputState;
}
virtual void Send(Socket &socket)
{
uint32_t messageLength = sizeof(Type) + sizeof(InputState);
socket.BufferedSend((char*)&messageLength, sizeof(messageLength));
socket.BufferedSend((char*)&Type, sizeof(Type));
socket.BufferedSend((char*)&InputState, sizeof(InputState));
socket.SendBuffer();
}
};
class SaveStateMessage : public NetMessage
{
private:
char* _stateData;
uint32_t _dataSize;
public:
SaveStateMessage(char *readBuffer, uint32_t bufferLength) : NetMessage(MessageType::SaveState)
{
_stateData = new char[bufferLength];
_dataSize = bufferLength;
memcpy(_stateData, readBuffer, bufferLength);
}
~SaveStateMessage()
{
delete[] _stateData;
}
virtual void Send(Socket &socket)
{
uint32_t messageLength = _dataSize + sizeof(Type);
socket.BufferedSend((char*)&messageLength, sizeof(messageLength));
socket.BufferedSend((char*)&Type, sizeof(Type));
socket.BufferedSend(_stateData, _dataSize);
socket.SendBuffer();
}
void LoadState()
{
Console::LoadState((uint8_t*)_stateData, _dataSize);
}
};
class MovieDataMessage : public NetMessage
{
public:
uint8_t PortNumber;
uint8_t InputState;
MovieDataMessage(char *readBuffer) : NetMessage(MessageType::MovieData)
{
PortNumber = readBuffer[0];
InputState = readBuffer[1];
}
MovieDataMessage(uint8_t state, uint8_t port) : NetMessage(MessageType::MovieData)
{
PortNumber = port;
InputState = state;
}
virtual void Send(Socket &socket)
{
uint32_t messageLength = sizeof(Type) + sizeof(PortNumber) + sizeof(InputState);
uint8_t type = (uint8_t)Type;
uint8_t portNumber = PortNumber;
uint8_t inputState = InputState;
socket.BufferedSend((char*)&messageLength, sizeof(messageLength));
socket.BufferedSend((char*)&type, sizeof(type));
socket.BufferedSend((char*)&portNumber, sizeof(portNumber));
socket.BufferedSend((char*)&inputState, sizeof(inputState));
socket.SendBuffer();
}
};

View file

@ -19,6 +19,7 @@
#include <array>
#include <sstream>
#include <atomic>
#include <list>
using std::vector;
using std::shared_ptr;
@ -32,4 +33,6 @@ using std::ofstream;
using std::wstring;
using std::exception;
using std::atomic_flag;
using std::atomic;
using std::list;
using std::max;

Binary file not shown.

View file

@ -20,6 +20,8 @@ namespace NES {
_renderer.reset(new Renderer(_hWnd));
_soundManager.reset(new SoundManager(_hWnd));
Console::RegisterMessageManager(_renderer.get());
return true;
}
@ -58,9 +60,9 @@ namespace NES {
int MainWindow::Run()
{
#if _DEBUG
//#if _DEBUG
CreateConsole();
#endif
//#endif
Initialize();
@ -83,14 +85,21 @@ namespace NES {
}
} else {
_renderer->Render();
_gameServer.Exec();
_gameClient.Exec();
}
SetMenuEnabled(ID_NETPLAY_STARTSERVER, !_gameServer.Started() && !_gameClient.Connected());
SetMenuEnabled(ID_NETPLAY_STOPSERVER, _gameServer.Started() && !_gameClient.Connected());
SetMenuEnabled(ID_NETPLAY_CONNECT, !_gameServer.Started() && !_gameClient.Connected());
SetMenuEnabled(ID_NETPLAY_DISCONNECT, !_gameServer.Started() && _gameClient.Connected());
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(1));
if(_playingMovie) {
if(!Movie::Playing()) {
_playingMovie = false;
_renderer->DisplayMessage(L"Movie ended.", 3000);
//Pause game
Stop(false);
}
@ -154,6 +163,35 @@ namespace NES {
}
return (INT_PTR)FALSE;
}
INT_PTR CALLBACK MainWindow::ConnectWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
wchar_t hostName[1000];
wstring lastHost;
switch(message) {
case WM_INITDIALOG:
lastHost = ConfigManager::GetValue<wstring>(Config::LastNetPlayHost);
SetDlgItemText(hDlg, IDC_HOSTNAME, lastHost.size() > 0 ? lastHost.c_str() : L"localhost");
return (INT_PTR)TRUE;
case WM_COMMAND:
if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
if(LOWORD(wParam) == IDOK) {
GetDlgItemText(hDlg, IDC_HOSTNAME, (LPWSTR)hostName, 1000);
ConfigManager::SetValue(Config::LastNetPlayHost, wstring(hostName));
MainWindow::GetInstance()->_gameClient.Connect(utf8util::UTF8FromUTF16(hostName).c_str(), 8888);
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
INT_PTR CALLBACK MainWindow::ControllerSetup(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
@ -460,7 +498,7 @@ namespace NES {
void MainWindow::SelectSaveSlot(int slot)
{
_currentSaveSlot = slot % 5;
_renderer->DisplayMessage(L"Savestate slot: " + std::to_wstring(_currentSaveSlot + 1), 3000);
_renderer->DisplayMessage(L"Savestate slot: " + std::to_wstring(_currentSaveSlot + 1));
SetMenuCheck(ID_SAVESTATESLOT_1, _currentSaveSlot == 0);
SetMenuCheck(ID_SAVESTATESLOT_2, _currentSaveSlot == 1);
@ -520,15 +558,10 @@ namespace NES {
break;
case ID_FILE_QUICKLOAD:
if(mainWindow->_console->LoadState(FolderUtilities::GetSaveStateFolder() + mainWindow->_currentROMName + L".ss" + std::to_wstring(mainWindow->_currentSaveSlot + 1))) {
mainWindow->_renderer->DisplayMessage(L"State loaded.", 3000);
} else {
mainWindow->_renderer->DisplayMessage(L"Slot is empty.", 3000);
}
mainWindow->_console->LoadState(FolderUtilities::GetSaveStateFolder() + mainWindow->_currentROMName + L".ss" + std::to_wstring(mainWindow->_currentSaveSlot + 1));
break;
case ID_FILE_QUICKSAVE:
mainWindow->_console->SaveState(FolderUtilities::GetSaveStateFolder() + mainWindow->_currentROMName + L".ss" + std::to_wstring(mainWindow->_currentSaveSlot + 1));
mainWindow->_renderer->DisplayMessage(L"State saved.", 3000);
break;
case ID_CHANGESLOT:
mainWindow->SelectSaveSlot(mainWindow->_currentSaveSlot + 1);
@ -563,7 +596,6 @@ namespace NES {
case ID_MOVIES_PLAY:
filename = FolderUtilities::OpenFile(L"Movie Files (*.nmo)\0*.nmo\0All (*.*)\0*.*", FolderUtilities::GetMovieFolder(), false);
if(!filename.empty()) {
mainWindow->_renderer->DisplayMessage(L"Playing movie: " + FolderUtilities::GetFilename(filename, true), 3000);
mainWindow->_playingMovie = true;
Movie::Play(filename);
}
@ -573,7 +605,6 @@ namespace NES {
case ID_RECORDFROM_NOW:
filename = FolderUtilities::OpenFile(L"Movie Files (*.nmo)\0*.nmo\0All (*.*)\0*.*", FolderUtilities::GetMovieFolder(), true, L"nmo");
if(!filename.empty()) {
mainWindow->_renderer->DisplayMessage(L"Recording...", 3000);
Movie::Record(filename, wmId == ID_RECORDFROM_START);
}
mainWindow->SetMenuEnabled(ID_MOVIES_STOP, true);
@ -585,6 +616,20 @@ namespace NES {
mainWindow->SetMenuEnabled(ID_MOVIES_STOP, false);
break;
case ID_NETPLAY_STARTSERVER:
mainWindow->_gameServer.Start();
break;
case ID_NETPLAY_STOPSERVER:
mainWindow->_gameServer.Stop();
break;
case ID_NETPLAY_CONNECT:
DialogBox(nullptr, MAKEINTRESOURCE(IDD_CONNECT), hWnd, ConnectWndProc);
break;
case ID_NETPLAY_DISCONNECT:
mainWindow->_gameClient.Disconnect();
break;
case ID_TESTS_RUNTESTS:
mainWindow->RunTests();
break;
@ -641,6 +686,7 @@ namespace NES {
break;
case WM_DESTROY:
mainWindow->_gameClient.Disconnect();
mainWindow->Stop(true);
PostQuitMessage(0);
break;

View file

@ -3,6 +3,8 @@
#include "SoundManager.h"
#include "../Core/Console.h"
#include "../Utilities/ConfigManager.h"
#include "../Core/GameServer.h"
#include "../Core/GameClient.h"
namespace NES {
class MainWindow
@ -22,6 +24,9 @@ namespace NES {
wstring _currentROM;
wstring _currentROMName;
GameClient _gameClient;
GameServer _gameServer;
int _currentSaveSlot = 0;
bool _playingMovie = false;
@ -31,6 +36,7 @@ namespace NES {
HRESULT InitWindow();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK ConnectWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK InputConfig(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
static INT_PTR CALLBACK ControllerSetup(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

View file

@ -276,10 +276,10 @@ namespace NES
return shaderResourceView;
}
void Renderer::DisplayMessage(wstring text, uint32_t duration)
void Renderer::DisplayMessage(wstring text)
{
_displayMessage = text;
_displayTimestamp = timeGetTime() + duration;
_displayTimestamp = timeGetTime() + 3000; //3 secs
}
void Renderer::DrawNESScreen()
@ -335,7 +335,7 @@ namespace NES
_font->DrawString(_spriteBatch.get(), L"PAUSED", XMFLOAT2((float)_hdScreenWidth / 2 - 142, (float)_hdScreenHeight / 2 - 77), Colors::Black, 0.0f, XMFLOAT2(0, 0), 2.0f);
_font->DrawString(_spriteBatch.get(), L"PAUSED", XMFLOAT2((float)_hdScreenWidth / 2 - 145, (float)_hdScreenHeight / 2 - 80), Colors::AntiqueWhite, 0.0f, XMFLOAT2(0, 0), 2.0f);
}
/*
HRESULT Renderer::CompileShader(wstring filename, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut)
{
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
@ -353,7 +353,7 @@ namespace NES
}
return hr;
}
}*/
void Renderer::Render()
{

View file

@ -2,6 +2,7 @@
#include "DirectXTK\SpriteBatch.h"
#include "DirectXTK\SpriteFont.h"
#include "../Core/IVideoDevice.h"
#include "../Core/IMessageManager.h"
using namespace DirectX;
@ -12,7 +13,7 @@ namespace NES {
ShowPauseScreen = 2,
};
class Renderer : IVideoDevice
class Renderer : public IVideoDevice, public IMessageManager
{
private:
HWND _hWnd = nullptr;
@ -39,7 +40,7 @@ namespace NES {
byte* _overlayBuffer = nullptr;
std::unique_ptr<SpriteBatch> _spriteBatch;
ID3D11PixelShader* _pixelShader = nullptr;
//ID3D11PixelShader* _pixelShader = nullptr;
uint32_t _screenWidth;
uint32_t _screenHeight;
@ -63,7 +64,7 @@ namespace NES {
void DrawNESScreen();
void DrawPauseScreen();
HRESULT CompileShader(wstring filename, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut);
//HRESULT CompileShader(wstring filename, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut);
public:
Renderer(HWND hWnd);
@ -71,7 +72,7 @@ namespace NES {
void Render();
void Renderer::DisplayMessage(wstring text, uint32_t duration);
void DisplayMessage(wstring text);
void SetFlags(uint32_t flags)
{

Binary file not shown.

View file

@ -47,6 +47,11 @@
#include <Fcntl.h>
#include <thread>
#include <list>
#include <vector>
using std::thread;
using std::list;
using std::vector;
// TODO: reference additional headers your program requires here

View file

@ -11,6 +11,7 @@ string ConfigManager::ConfigTagNames[] = {
"MRU3",
"MRU4",
"LastGameFolder",
"LastNetPlayHost",
"Player1_ButtonA",
"Player1_ButtonB",
"Player1_Select",

View file

@ -14,6 +14,7 @@ enum class Config
MRU3,
MRU4,
LastGameFolder,
LastNetPlayHost,
Player1_ButtonA,
Player1_ButtonB,
Player1_Select,

35
Utilities/SimpleLock.h Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "stdafx.h"
class SimpleLock
{
private:
atomic_flag _lock;
public:
void Acquire()
{
while(_lock.test_and_set());
}
bool IsFree()
{
if(!_lock.test_and_set()) {
_lock.clear();
return true;
}
return false;
}
void WaitForRelease()
{
//Wait until we are able to grab a lock, and then release it again
Acquire();
Release();
}
void Release()
{
_lock.clear();
}
};

206
Utilities/Socket.h Normal file
View file

@ -0,0 +1,206 @@
#pragma once
#pragma comment(lib,"ws2_32.lib") //Winsock Library
#include "stdafx.h"
#include <winsock2.h>
class Socket
{
private:
SOCKET _socket = INVALID_SOCKET;
bool _connectionError = false;
bool _cleanupWSA = false;
char* _sendBuffer;
int _bufferPosition;
public:
Socket()
{
WSADATA wsaDat;
if(WSAStartup(MAKEWORD(2, 2), &wsaDat) != 0) {
std::cout << "WSAStartup failed." << std::endl;
SetConnectionErrorFlag();
} else {
_cleanupWSA = true;
_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(_socket == INVALID_SOCKET) {
std::cout << "Socket creation failed." << std::endl;
SetConnectionErrorFlag();
} else {
SetSocketOptions();
}
}
_sendBuffer = new char[200000];
_bufferPosition = 0;
}
Socket(SOCKET socket)
{
_socket = socket;
if(socket == INVALID_SOCKET) {
SetConnectionErrorFlag();
} else {
SetSocketOptions();
}
_sendBuffer = new char[200000];
_bufferPosition = 0;
}
~Socket()
{
if(_socket != INVALID_SOCKET) {
Close();
}
if(_cleanupWSA) {
WSACleanup();
}
delete[] _sendBuffer;
}
void SetSocketOptions()
{
//Non-blocking mode
u_long iMode = 1;
ioctlsocket(_socket, FIONBIO, &iMode);
//Set send/recv buffers to 256k
int bufferSize = 0x40000;
setsockopt(_socket, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(int));
setsockopt(_socket, SOL_SOCKET, SO_SNDBUF, (char*)&bufferSize, sizeof(int));
//Disable nagle's algorithm to improve latency
u_long value = 1;
setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(value));
}
void SetConnectionErrorFlag()
{
_connectionError = true;
}
void Close()
{
std::cout << "Client disconnected!\r\n";
shutdown(_socket, SD_SEND);
closesocket(_socket);
SetConnectionErrorFlag();
}
bool ConnectionError()
{
return _connectionError;
}
void Bind(u_short port)
{
SOCKADDR_IN serverInf;
serverInf.sin_family = AF_INET;
serverInf.sin_addr.s_addr = INADDR_ANY;
serverInf.sin_port = htons(port);
if(bind(_socket, (SOCKADDR*)(&serverInf), sizeof(serverInf)) == SOCKET_ERROR) {
std::cout << "Unable to bind socket." << std::endl;
SetConnectionErrorFlag();
}
}
bool Connect(const char* hostname, u_short port)
{
// Resolve IP address for hostname
struct hostent *host;
if((host = gethostbyname(hostname)) == NULL) {
std::cout << "Failed to resolve hostname." << std::endl;
SetConnectionErrorFlag();
} else {
// Setup our socket address structure
SOCKADDR_IN SockAddr;
SockAddr.sin_port = htons(port);
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr);
u_long iMode = 0;
ioctlsocket(_socket, FIONBIO, &iMode);
// Attempt to connect to server
if(connect(_socket, (SOCKADDR*)(&SockAddr), sizeof(SockAddr)) == SOCKET_ERROR) {
std::cout << "Failed to establish connection with server." << std::endl;
SetConnectionErrorFlag();
} else {
iMode = 1;
ioctlsocket(_socket, FIONBIO, &iMode);
return true;
}
}
return false;
}
void Listen(int backlog)
{
if(listen(_socket, backlog) == SOCKET_ERROR) {
std::cout << "listen failed." << std::endl;
SetConnectionErrorFlag();
}
}
shared_ptr<Socket> Accept(sockaddr* addr, int *addrlen)
{
SOCKET socket = accept(_socket, addr, addrlen);
return shared_ptr<Socket>(new Socket(socket));
}
int Send(char *buf, int len, int flags)
{
int nError;
int returnVal;
do {
//Loop until everything has been sent (shouldn't loop at all in the vast majority of cases)
returnVal = send(_socket, buf, len, flags);
nError = WSAGetLastError();
if(nError != 0) {
if(nError != WSAEWOULDBLOCK) {
SetConnectionErrorFlag();
} else {
if(returnVal > 0) {
//Sent partial data, adjust pointer & length
buf += returnVal;
len -= returnVal;
}
}
}
} while(nError == WSAEWOULDBLOCK);
return returnVal;
}
void BufferedSend(char *buf, int len)
{
memcpy(_sendBuffer+_bufferPosition, buf, len);
_bufferPosition += len;
}
void SendBuffer()
{
Send(_sendBuffer, _bufferPosition, 0);
_bufferPosition = 0;
}
int Recv(char *buf, int len, int flags)
{
static ofstream received("received.log", ios::out | ios::binary);
int returnVal = recv(_socket, buf, len, flags);
int nError = WSAGetLastError();
if(nError != WSAEWOULDBLOCK && nError != 0) {
SetConnectionErrorFlag();
}
return returnVal;
}
};

View file

@ -73,6 +73,9 @@
<ItemGroup>
<ClInclude Include="ConfigManager.h" />
<ClInclude Include="FolderUtilities.h" />
<ClInclude Include="NATPortMapper.h" />
<ClInclude Include="SimpleLock.h" />
<ClInclude Include="Socket.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="Timer.h" />

View file

@ -42,6 +42,15 @@
<ClInclude Include="FolderUtilities.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Socket.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SimpleLock.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NATPortMapper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">

View file

@ -10,7 +10,9 @@
#include <fstream>
#include <memory>
#include <vector>
#include <atomic>
using std::atomic_flag;
using std::shared_ptr;
using std::ifstream;
using std::string;