diff --git a/Core/APU.cpp b/Core/APU.cpp index bac0c678..a2d8bb98 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -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(_currentClock); StreamArray(snapshot.w40xx, 0x14); Stream(snapshot.w4015); diff --git a/Core/Console.cpp b/Core/Console.cpp index a39a07e8..bcef00f9 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -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) diff --git a/Core/Console.h b/Core/Console.h index 9bcc3923..18d078a3 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -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; unique_ptr _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); }; diff --git a/Core/ControlManager.cpp b/Core/ControlManager.cpp index 1b1f766f..0acfc76e 100644 --- a/Core/ControlManager.cpp +++ b/Core/ControlManager.cpp @@ -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(_stateBuffer, 4); + Stream(_refreshState); } \ No newline at end of file diff --git a/Core/ControlManager.h b/Core/ControlManager.h index 0bf3e2c6..0888f454 100644 --- a/Core/ControlManager.h +++ b/Core/ControlManager.h @@ -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) { diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index a67145d0..895fb7e1 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -42,6 +42,8 @@ true + $(IncludePath) + $(LibraryPath) false @@ -93,12 +95,20 @@ + + + + + + + + @@ -123,6 +133,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 99897453..84b997e2 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -22,6 +22,9 @@ {67b52e86-0ff2-4dbe-b9ed-7c92aace61d5} + + {43f375c6-d7c7-4bbf-8520-ad53e4e1b4a3} + @@ -132,6 +135,33 @@ Header Files + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\NetPlay + + + Header Files\Interfaces + diff --git a/Core/GameClient.h b/Core/GameClient.h new file mode 100644 index 00000000..e74360e8 --- /dev/null +++ b/Core/GameClient.h @@ -0,0 +1,62 @@ +#pragma once +#include "stdafx.h" +#include "GameClientConnection.h" + +class GameClient +{ +private: + shared_ptr _socket; + unique_ptr _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(); + } + } + } +}; + diff --git a/Core/GameServer.h b/Core/GameServer.h new file mode 100644 index 00000000..dfc90424 --- /dev/null +++ b/Core/GameServer.h @@ -0,0 +1,91 @@ +#pragma once +#include "stdafx.h" +#include "GameServerConnection.h" + +class GameServer : public IGameBroadcaster +{ +private: + shared_ptr _listener; + list> _openConnections; + bool _initialized = false; + + void AcceptConnections() + { + while(true) { + shared_ptr socket = _listener->Accept(NULL, NULL); + if(!socket->ConnectionError()) { + _openConnections.push_back(shared_ptr(new GameServerConnection(socket, 1, this))); + std::cout << "Client connected." << std::endl; + } else { + break; + } + } + _listener->Listen(10); + } + + void UpdateConnections() + { + vector> connectionsToRemove; + for(shared_ptr connection : _openConnections) { + if(connection->ConnectionError()) { + connectionsToRemove.push_back(connection); + } else { + connection->ProcessMessages(); + } + } + + for(shared_ptr 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 connection : _openConnections) { + if(!connection->ConnectionError()) { + //Send movie stream + connection->SendMovieData(inputData, port); + } + } + } +}; \ No newline at end of file diff --git a/Core/GameServerConnection.h b/Core/GameServerConnection.h new file mode 100644 index 00000000..de07496b --- /dev/null +++ b/Core/GameServerConnection.h @@ -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 _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, 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; + } +}; diff --git a/Core/IControlDevice.h b/Core/IControlDevice.h index 0c484154..7970e495 100644 --- a/Core/IControlDevice.h +++ b/Core/IControlDevice.h @@ -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 diff --git a/Core/IGameBroadcaster.h b/Core/IGameBroadcaster.h new file mode 100644 index 00000000..c32ea908 --- /dev/null +++ b/Core/IGameBroadcaster.h @@ -0,0 +1,9 @@ +#pragma once + +#include "stdafx.h" + +class IGameBroadcaster +{ +public: + virtual void BroadcastInput(uint8_t inputData, uint8_t port) = 0; +}; \ No newline at end of file diff --git a/Core/IMessageManager.h b/Core/IMessageManager.h new file mode 100644 index 00000000..2c0f0015 --- /dev/null +++ b/Core/IMessageManager.h @@ -0,0 +1,9 @@ +#pragma once + +#include "stdafx.h" + +class IMessageManager +{ +public: + virtual void DisplayMessage(wstring message) = 0; +}; \ No newline at end of file diff --git a/Core/Movie.cpp b/Core/Movie.cpp index bd5d9b52..ae48764d 100644 --- a/Core/Movie.cpp +++ b/Core/Movie.cpp @@ -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)); } } diff --git a/Core/NetMessage.h b/Core/NetMessage.h new file mode 100644 index 00000000..f23e2d5b --- /dev/null +++ b/Core/NetMessage.h @@ -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(); + } + +}; diff --git a/Core/stdafx.h b/Core/stdafx.h index 96538b4f..97301316 100644 --- a/Core/stdafx.h +++ b/Core/stdafx.h @@ -19,6 +19,7 @@ #include #include #include +#include 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; \ No newline at end of file diff --git a/GUI/GUI.rc b/GUI/GUI.rc index 03c64f1a..6b8a3e27 100644 Binary files a/GUI/GUI.rc and b/GUI/GUI.rc differ diff --git a/GUI/MainWindow.cpp b/GUI/MainWindow.cpp index c0e1cd4a..ba5572d0 100644 --- a/GUI/MainWindow.cpp +++ b/GUI/MainWindow.cpp @@ -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(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(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; diff --git a/GUI/MainWindow.h b/GUI/MainWindow.h index 9cd294c1..1a4a1f79 100644 --- a/GUI/MainWindow.h +++ b/GUI/MainWindow.h @@ -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); diff --git a/GUI/Renderer.cpp b/GUI/Renderer.cpp index 4f336534..874834bc 100644 --- a/GUI/Renderer.cpp +++ b/GUI/Renderer.cpp @@ -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() { diff --git a/GUI/Renderer.h b/GUI/Renderer.h index a35d6d9b..35b2bc2b 100644 --- a/GUI/Renderer.h +++ b/GUI/Renderer.h @@ -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; - 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) { diff --git a/GUI/resource.h b/GUI/resource.h index 7556c9c6..35981c4d 100644 Binary files a/GUI/resource.h and b/GUI/resource.h differ diff --git a/GUI/stdafx.h b/GUI/stdafx.h index 958486f4..fd6c683c 100644 --- a/GUI/stdafx.h +++ b/GUI/stdafx.h @@ -47,6 +47,11 @@ #include #include +#include +#include + using std::thread; +using std::list; +using std::vector; // TODO: reference additional headers your program requires here diff --git a/Utilities/ConfigManager.cpp b/Utilities/ConfigManager.cpp index 0a6c9662..82340db4 100644 --- a/Utilities/ConfigManager.cpp +++ b/Utilities/ConfigManager.cpp @@ -11,6 +11,7 @@ string ConfigManager::ConfigTagNames[] = { "MRU3", "MRU4", "LastGameFolder", + "LastNetPlayHost", "Player1_ButtonA", "Player1_ButtonB", "Player1_Select", diff --git a/Utilities/ConfigManager.h b/Utilities/ConfigManager.h index d7916497..f4569fad 100644 --- a/Utilities/ConfigManager.h +++ b/Utilities/ConfigManager.h @@ -14,6 +14,7 @@ enum class Config MRU3, MRU4, LastGameFolder, + LastNetPlayHost, Player1_ButtonA, Player1_ButtonB, Player1_Select, diff --git a/Utilities/SimpleLock.h b/Utilities/SimpleLock.h new file mode 100644 index 00000000..2d30e370 --- /dev/null +++ b/Utilities/SimpleLock.h @@ -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(); + } +}; \ No newline at end of file diff --git a/Utilities/Socket.h b/Utilities/Socket.h new file mode 100644 index 00000000..21fe1f75 --- /dev/null +++ b/Utilities/Socket.h @@ -0,0 +1,206 @@ +#pragma once +#pragma comment(lib,"ws2_32.lib") //Winsock Library +#include "stdafx.h" +#include + +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 Accept(sockaddr* addr, int *addrlen) + { + SOCKET socket = accept(_socket, addr, addrlen); + return shared_ptr(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; + } +}; diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index 9b5c8436..38d7f9e1 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -73,6 +73,9 @@ + + + diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index 44aa0dc8..90b6cacf 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -42,6 +42,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + diff --git a/Utilities/stdafx.h b/Utilities/stdafx.h index 93abb979..46c24b6b 100644 --- a/Utilities/stdafx.h +++ b/Utilities/stdafx.h @@ -10,7 +10,9 @@ #include #include #include +#include +using std::atomic_flag; using std::shared_ptr; using std::ifstream; using std::string;