From 3e798af86594efc08d0e31ce00f3f7b90b8d4453 Mon Sep 17 00:00:00 2001 From: Souryo Date: Sat, 6 Feb 2016 15:33:45 -0500 Subject: [PATCH] NetPlay: Allow controller selection for host & clients + Spectator mode --- Core/ClientConnectionData.h | 6 +- Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/GameClient.cpp | 31 +++++- Core/GameClient.h | 4 + Core/GameClientConnection.cpp | 45 +++++++- Core/GameClientConnection.h | 9 +- Core/GameConnection.cpp | 4 + Core/GameConnection.h | 14 ++- Core/GameServer.cpp | 93 +++++++++++++++- Core/GameServer.h | 16 ++- Core/GameServerConnection.cpp | 105 +++++++++++++++--- Core/GameServerConnection.h | 12 +- Core/HandShakeMessage.h | 10 +- Core/MessageType.h | 2 + Core/NetMessage.h | 7 ++ Core/PlayerListMessage.h | 63 +++++++++++ Core/SelectControllerMessage.h | 28 +++++ GUI.NET/Config/ClientConnection.cs | 1 + GUI.NET/Config/InputInfo.cs | 5 +- .../Forms/NetPlay/frmClientConfig.Designer.cs | 10 +- GUI.NET/Forms/NetPlay/frmClientConfig.cs | 7 +- GUI.NET/Forms/NetPlay/frmClientConfig.resx | 3 + GUI.NET/Forms/frmMain.Designer.cs | 68 ++++++++++++ GUI.NET/Forms/frmMain.cs | 50 ++++++++- GUI.NET/InteropEmu.cs | 11 +- InteropDLL/ConsoleWrapper.cpp | 37 +++++- 27 files changed, 594 insertions(+), 55 deletions(-) create mode 100644 Core/PlayerListMessage.h create mode 100644 Core/SelectControllerMessage.h diff --git a/Core/ClientConnectionData.h b/Core/ClientConnectionData.h index d34ee838..6cd9caec 100644 --- a/Core/ClientConnectionData.h +++ b/Core/ClientConnectionData.h @@ -12,8 +12,10 @@ public: uint8_t* AvatarData; uint32_t AvatarSize; - ClientConnectionData(string host, uint16_t port, string playerName, uint8_t* avatarData, uint32_t avatarSize) : - Host(host), Port(port), PlayerName(playerName), AvatarSize(avatarSize) + bool Spectator; + + ClientConnectionData(string host, uint16_t port, string playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator) : + Host(host), Port(port), PlayerName(playerName), AvatarSize(avatarSize), Spectator(spectator) { if(avatarSize > 0) { AvatarData = new uint8_t[avatarSize]; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 50ad943f..20dd7542 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -407,9 +407,11 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 6ebbbd91..f0a30f19 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -539,6 +539,12 @@ Nes\Controllers + + NetPlay\Messages + + + NetPlay\Messages + diff --git a/Core/GameClient.cpp b/Core/GameClient.cpp index e487491c..1ad12521 100644 --- a/Core/GameClient.cpp +++ b/Core/GameClient.cpp @@ -85,5 +85,34 @@ void GameClient::ProcessNotification(ConsoleNotificationType type, void* paramet uint8_t GameClient::GetControllerState(uint8_t port) { - return Instance->_connection->GetControllerState(port); + if(Instance && Instance->_connection) { + return Instance->_connection->GetControllerState(port); + } + + return 0; +} + +void GameClient::SelectController(uint8_t port) +{ + if(Instance && Instance->_connection) { + Instance->_connection->SelectController(port); + } +} + +uint8_t GameClient::GetAvailableControllers() +{ + if(Instance && Instance->_connection) { + return Instance->_connection->GetAvailableControllers(); + } + + return 0; +} + +uint8_t GameClient::GetControllerPort() +{ + if(Instance && Instance->_connection) { + return Instance->_connection->GetControllerPort(); + } + + return GameConnection::SpectatorPort; } \ No newline at end of file diff --git a/Core/GameClient.h b/Core/GameClient.h index d1b3ccc2..fb2ab698 100644 --- a/Core/GameClient.h +++ b/Core/GameClient.h @@ -29,6 +29,10 @@ public: static void Connect(shared_ptr connectionData); static void Disconnect(); + static void SelectController(uint8_t port); + static uint8_t GetControllerPort(); + static uint8_t GetAvailableControllers(); + static uint8_t GetControllerState(uint8_t port); void ProcessNotification(ConsoleNotificationType type, void* parameter); diff --git a/Core/GameClientConnection.cpp b/Core/GameClientConnection.cpp index 3db40125..66819e76 100644 --- a/Core/GameClientConnection.cpp +++ b/Core/GameClientConnection.cpp @@ -10,6 +10,8 @@ #include "ControlManager.h" #include "ClientConnectionData.h" #include "StandardController.h" +#include "SelectControllerMessage.h" +#include "PlayerListMessage.h" GameClientConnection::GameClientConnection(shared_ptr socket, shared_ptr connectionData) : GameConnection(socket, connectionData) { @@ -28,7 +30,13 @@ GameClientConnection::~GameClientConnection() void GameClientConnection::SendHandshake() { - HandShakeMessage message(_connectionData->PlayerName, _connectionData->AvatarData, _connectionData->AvatarSize); + HandShakeMessage message(_connectionData->PlayerName, _connectionData->AvatarData, _connectionData->AvatarSize, _connectionData->Spectator); + SendNetMessage(message); +} + +void GameClientConnection::SendControllerSelection(uint8_t port) +{ + SelectControllerMessage message(port); SendNetMessage(message); } @@ -57,21 +65,31 @@ void GameClientConnection::ProcessMessage(NetMessage* message) case ControllerType::Zapper: _controlDevice = ControlManager::GetControlDevice(_controllerPort); break; } Console::Resume(); - } break; + case MessageType::MovieData: if(_gameLoaded) { PushControllerState(((MovieDataMessage*)message)->GetPortNumber(), ((MovieDataMessage*)message)->GetInputState()); } 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(); - MessageManager::DisplayMessage("Net Play", string("Connected as player ") + std::to_string(_controllerPort + 1)); + + if(_controllerPort == GameConnection::SpectatorPort) { + MessageManager::DisplayMessage("Net Play", "Connected as spectator"); + } else { + MessageManager::DisplayMessage("Net Play", string("Connected as player ") + std::to_string(_controllerPort + 1)); + } } ClearInputData(); @@ -160,4 +178,25 @@ void GameClientConnection::SendInput() _lastInputSent = inputState; } } +} + +void GameClientConnection::SelectController(uint8_t port) +{ + SendControllerSelection(port); +} + +uint8_t GameClientConnection::GetAvailableControllers() +{ + uint8_t availablePorts = 0x0F; + for(PlayerInfo &playerInfo : _playerList) { + if(playerInfo.ControllerPort < 4) { + availablePorts &= ~(1 << playerInfo.ControllerPort); + } + } + return availablePorts; +} + +uint8_t GameClientConnection::GetControllerPort() +{ + return _controllerPort; } \ No newline at end of file diff --git a/Core/GameClientConnection.h b/Core/GameClientConnection.h index 2cbf474a..cfbf8c6e 100644 --- a/Core/GameClientConnection.h +++ b/Core/GameClientConnection.h @@ -19,13 +19,16 @@ private: atomic _enableControllers = false; atomic _minimumQueueSize = 3; + vector _playerList; + shared_ptr _controlDevice; uint32_t _lastInputSent = 0x00; bool _gameLoaded = false; - uint8_t _controllerPort = 255; + uint8_t _controllerPort = GameConnection::SpectatorPort; private: void SendHandshake(); + void SendControllerSelection(uint8_t port); void ClearInputData(); void PushControllerState(uint8_t port, uint8_t state); void DisableControllers(); @@ -39,4 +42,8 @@ public: uint8_t GetControllerState(uint8_t port); void SendInput(); + + void SelectController(uint8_t port); + uint8_t GetAvailableControllers(); + uint8_t GetControllerPort(); }; \ No newline at end of file diff --git a/Core/GameConnection.cpp b/Core/GameConnection.cpp index 1c4686c0..1cab2c29 100644 --- a/Core/GameConnection.cpp +++ b/Core/GameConnection.cpp @@ -5,6 +5,8 @@ #include "MovieDataMessage.h" #include "GameInformationMessage.h" #include "SaveStateMessage.h" +#include "PlayerListMessage.h" +#include "SelectControllerMessage.h" #include "ClientConnectionData.h" GameConnection::GameConnection(shared_ptr socket, shared_ptr connectionData) @@ -54,6 +56,8 @@ NetMessage* GameConnection::ReadMessage() 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); } } return nullptr; diff --git a/Core/GameConnection.h b/Core/GameConnection.h index 6bf77045..ba233014 100644 --- a/Core/GameConnection.h +++ b/Core/GameConnection.h @@ -6,6 +6,13 @@ class Socket; class NetMessage; class ClientConnectionData; +struct PlayerInfo +{ + string Name; + uint8_t ControllerPort; + bool IsHost; +}; + class GameConnection { protected: @@ -24,14 +31,11 @@ private: virtual void ProcessMessage(NetMessage* message) = 0; -protected: - void SendNetMessage(NetMessage &message); - public: + static const uint8_t SpectatorPort = 0xFF; GameConnection(shared_ptr socket, shared_ptr connectionData); bool ConnectionError(); - void ProcessMessages(); - + void SendNetMessage(NetMessage &message); }; \ No newline at end of file diff --git a/Core/GameServer.cpp b/Core/GameServer.cpp index 34c902e7..d9f28c07 100644 --- a/Core/GameServer.cpp +++ b/Core/GameServer.cpp @@ -6,11 +6,15 @@ using std::thread; #include "GameServer.h" #include "Console.h" #include "../Utilities/Socket.h" +#include "PlayerListMessage.h" unique_ptr GameServer::Instance; -GameServer::GameServer() +GameServer::GameServer(uint16_t listenPort, string hostPlayerName) { + _port = listenPort; + _hostPlayerName = hostPlayerName; + _hostControllerPort = 0; ControlManager::RegisterBroadcaster(this); } @@ -29,7 +33,7 @@ void GameServer::AcceptConnections() while(true) { shared_ptr socket = _listener->Accept(); if(!socket->ConnectionError()) { - _openConnections.push_back(shared_ptr(new GameServerConnection(socket, 1))); + _openConnections.push_back(shared_ptr(new GameServerConnection(socket))); } else { break; } @@ -53,6 +57,15 @@ void GameServer::UpdateConnections() } } +list> GameServer::GetConnectionList() +{ + if(GameServer::Started()) { + return Instance->_openConnections; + } else { + return list>(); + } +} + void GameServer::Exec() { _listener.reset(new Socket()); @@ -77,10 +90,9 @@ void GameServer::Stop() MessageManager::DisplayMessage("Net Play", "Server stopped"); } -void GameServer::StartServer(uint16_t port) +void GameServer::StartServer(uint16_t port, string hostPlayerName) { - Instance.reset(new GameServer()); - Instance->_port = port; + Instance.reset(new GameServer(port, hostPlayerName)); Instance->_serverThread.reset(new thread(&GameServer::Exec, Instance.get())); } @@ -108,4 +120,75 @@ void GameServer::BroadcastInput(uint8_t inputData, uint8_t port) connection->SendMovieData(inputData, port); } } +} + +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()) { + Console::Pause(); + if(port == GameConnection::SpectatorPort || GetAvailableControllers() & (1 << port)) { + //Port is available + Instance->_hostControllerPort = port; + SendPlayerList(); + } + Console::Resume(); + } +} + +uint8_t GameServer::GetAvailableControllers() +{ + uint8_t availablePorts = 0x0F; + for(PlayerInfo &playerInfo : GetPlayerList()) { + if(playerInfo.ControllerPort < 4) { + availablePorts &= ~(1 << playerInfo.ControllerPort); + } + } + return availablePorts; +} + +vector GameServer::GetPlayerList() +{ + vector playerList; + + PlayerInfo playerInfo; + playerInfo.Name = GetHostPlayerName(); + playerInfo.ControllerPort = GetHostControllerPort(); + playerInfo.IsHost = true; + playerList.push_back(playerInfo); + + for(shared_ptr &connection : GetConnectionList()) { + playerInfo.Name = connection->GetPlayerName(); + playerInfo.ControllerPort = connection->GetControllerPort(); + playerInfo.IsHost = false; + playerList.push_back(playerInfo); + } + + return playerList; +} + +void GameServer::SendPlayerList() +{ + vector playerList = GetPlayerList(); + + for(shared_ptr &connection : GetConnectionList()) { + //Send player list update to all connections + PlayerListMessage message(playerList); + connection->SendNetMessage(message); + } } \ No newline at end of file diff --git a/Core/GameServer.h b/Core/GameServer.h index f046760c..b664127d 100644 --- a/Core/GameServer.h +++ b/Core/GameServer.h @@ -18,6 +18,9 @@ private: list> _openConnections; bool _initialized = false; + string _hostPlayerName; + uint8_t _hostControllerPort; + void AcceptConnections(); void UpdateConnections(); @@ -25,12 +28,21 @@ private: void Stop(); public: - GameServer(); + GameServer(uint16_t port, string hostPlayerName); ~GameServer(); - static void StartServer(uint16_t port); + static void StartServer(uint16_t port, 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 GetPlayerList(); + static void SendPlayerList(); + + static list> GetConnectionList(); + virtual void BroadcastInput(uint8_t inputData, uint8_t port); }; \ No newline at end of file diff --git a/Core/GameServerConnection.cpp b/Core/GameServerConnection.cpp index d7a7dde1..847a9a37 100644 --- a/Core/GameServerConnection.cpp +++ b/Core/GameServerConnection.cpp @@ -11,13 +11,16 @@ #include "ClientConnectionData.h" #include "EmulationSettings.h" #include "StandardController.h" +#include "SelectControllerMessage.h" +#include "PlayerListMessage.h" +#include "GameServer.h" -GameServerConnection* GameServerConnection::_netPlayDevices[4] = { nullptr,nullptr,nullptr, nullptr }; +GameServerConnection* GameServerConnection::_netPlayDevices[4] = { nullptr,nullptr,nullptr,nullptr }; -GameServerConnection::GameServerConnection(shared_ptr socket, int controllerPort) : GameConnection(socket, nullptr) +GameServerConnection::GameServerConnection(shared_ptr socket) : GameConnection(socket, nullptr) { //Server-side connection - _controllerPort = controllerPort; + _controllerPort = GameConnection::SpectatorPort; MessageManager::RegisterNotificationListener(this); } @@ -72,34 +75,75 @@ uint32_t GameServerConnection::GetState() return stateData; } +void GameServerConnection::ProcessHandshakeResponse(HandShakeMessage* message) +{ + //Send the game's current state to the client and register the controller + if(message->IsValid()) { + Console::Pause(); + + _controllerPort = message->IsSpectator() ? GameConnection::SpectatorPort : GetFirstFreeControllerPort(); + _connectionData.reset(new ClientConnectionData("", 0, message->GetPlayerName(), message->GetAvatarData(), message->GetAvatarSize(), false)); + + string playerPortMessage = _controllerPort == GameConnection::SpectatorPort ? "Spectator" : "Player " + std::to_string(_controllerPort + 1); + + MessageManager::DisplayToast("Net Play", _connectionData->PlayerName + " (" + playerPortMessage + ") connected.", _connectionData->AvatarData, _connectionData->AvatarSize); + + if(Console::GetROMPath().size() > 0) { + SendGameInformation(); + } + + _handshakeCompleted = true; + RegisterNetPlayDevice(this, _controllerPort); + GameServer::SendPlayerList(); + Console::Resume(); + } +} + void GameServerConnection::ProcessMessage(NetMessage* message) { switch(message->GetType()) { case MessageType::HandShake: - //Send the game's current state to the client and register the controller - if(((HandShakeMessage*)message)->IsValid()) { - Console::Pause(); - _connectionData.reset(new ClientConnectionData("", 0, ((HandShakeMessage*)message)->GetPlayerName(), ((HandShakeMessage*)message)->GetAvatarData(), ((HandShakeMessage*)message)->GetAvatarSize())); - - MessageManager::DisplayToast("Net Play", _connectionData->PlayerName + " (Player " + std::to_string(_controllerPort + 1) + ") connected.", _connectionData->AvatarData, _connectionData->AvatarSize); - - if(Console::GetROMPath().size() > 0) { - SendGameInformation(); - } - - _handshakeCompleted = true; - RegisterNetPlayDevice(this, _controllerPort); - Console::Resume(); - } + ProcessHandshakeResponse((HandShakeMessage*)message); break; + case MessageType::InputData: PushState(((InputDataMessage*)message)->GetInputState()); break; + + case MessageType::SelectController: + 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) { @@ -138,4 +182,29 @@ void GameServerConnection::UnregisterNetPlayDevice(GameServerConnection* device) GameServerConnection* GameServerConnection::GetNetPlayDevice(uint8_t port) { return GameServerConnection::_netPlayDevices[port]; +} + +uint8_t GameServerConnection::GetFirstFreeControllerPort() +{ + uint8_t hostPost = GameServer::GetHostControllerPort(); + for(int i = 0; i < 4; i++) { + if(hostPost != i && GameServerConnection::_netPlayDevices[i] == nullptr) { + return i; + } + } + return GameConnection::SpectatorPort; +} + +string GameServerConnection::GetPlayerName() +{ + if(_connectionData) { + return _connectionData->PlayerName; + } else { + return ""; + } +} + +uint8_t GameServerConnection::GetControllerPort() +{ + return _controllerPort; } \ No newline at end of file diff --git a/Core/GameServerConnection.h b/Core/GameServerConnection.h index 2e6e602a..9e0521d9 100644 --- a/Core/GameServerConnection.h +++ b/Core/GameServerConnection.h @@ -6,29 +6,39 @@ #include "IGameBroadcaster.h" #include "INotificationListener.h" +class HandShakeMessage; + class GameServerConnection : public GameConnection, public INotificationListener { private: static GameServerConnection* _netPlayDevices[4]; + list _inputData; int _controllerPort; bool _handshakeCompleted = false; void PushState(uint32_t state); void SendGameInformation(); + void SelectControllerPort(uint8_t port); + + 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); public: - GameServerConnection(shared_ptr socket, int controllerPort); + GameServerConnection(shared_ptr socket); ~GameServerConnection(); uint32_t GetState(); void SendMovieData(uint8_t state, uint8_t port); + string GetPlayerName(); + uint8_t GetControllerPort(); + virtual void ProcessNotification(ConsoleNotificationType type, void* parameter); static GameServerConnection* GetNetPlayDevice(uint8_t port); diff --git a/Core/HandShakeMessage.h b/Core/HandShakeMessage.h index 77629424..f55e4665 100644 --- a/Core/HandShakeMessage.h +++ b/Core/HandShakeMessage.h @@ -11,6 +11,7 @@ private: uint32_t _playerNameLength = 0; void* _avatarData = nullptr; uint32_t _avatarSize = 0; + bool _spectator = false; protected: virtual void ProtectedStreamState() @@ -18,16 +19,18 @@ protected: Stream(_protocolVersion); StreamArray((void**)&_playerName, _playerNameLength); StreamArray(&_avatarData, _avatarSize); + Stream(_spectator); } public: HandShakeMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { } - HandShakeMessage(string playerName, uint8_t* avatarData, uint32_t avatarSize) : NetMessage(MessageType::HandShake) + HandShakeMessage(string playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator) : NetMessage(MessageType::HandShake) { _protocolVersion = 1; CopyString(&_playerName, _playerNameLength, playerName); _avatarSize = avatarSize; _avatarData = avatarData; + _spectator = spectator; } string GetPlayerName() @@ -49,4 +52,9 @@ public: { return _protocolVersion == CurrentVersion; } + + bool IsSpectator() + { + return _spectator; + } }; diff --git a/Core/MessageType.h b/Core/MessageType.h index b37f64f4..99d03822 100644 --- a/Core/MessageType.h +++ b/Core/MessageType.h @@ -8,4 +8,6 @@ enum class MessageType : uint8_t InputData = 2, MovieData = 3, GameInformation = 4, + PlayerList = 5, + SelectController = 6, }; \ No newline at end of file diff --git a/Core/NetMessage.h b/Core/NetMessage.h index 9b1c1f14..50f4e3c1 100644 --- a/Core/NetMessage.h +++ b/Core/NetMessage.h @@ -28,6 +28,13 @@ protected: } } + void StreamArray(void* value, uint32_t length) + { + void* pointer = value; + uint32_t len = length; + StreamArray(&pointer, len); + } + void StreamArray(void** value, uint32_t &length) { if(_sending) { diff --git a/Core/PlayerListMessage.h b/Core/PlayerListMessage.h new file mode 100644 index 00000000..cc373c7e --- /dev/null +++ b/Core/PlayerListMessage.h @@ -0,0 +1,63 @@ +#pragma once +#include "stdafx.h" +#include "NetMessage.h" + +class PlayerListMessage : public NetMessage +{ +private: + static const uint32_t PlayerNameMaxLength = 50; + vector _playerList; + +protected: + virtual void ProtectedStreamState() + { + uint32_t nameLength = PlayerNameMaxLength + 1; + char playerName[PlayerNameMaxLength + 1]; + uint8_t playerPort; + bool isHost; + + if(_sending) { + uint32_t playerCount = (uint32_t)_playerList.size(); + Stream(playerCount); + for(uint32_t i = 0; i < playerCount; i++) { + memset(playerName, 0, nameLength); + memcpy(playerName, _playerList[i].Name.c_str(), std::min((uint32_t)_playerList[i].Name.size(), PlayerNameMaxLength)); + playerPort = _playerList[i].ControllerPort; + + StreamArray(playerName, nameLength); + Stream(playerPort); + Stream(isHost); + } + } else { + uint32_t playerCount; + Stream(playerCount); + + for(uint32_t i = 0; i < playerCount; i++) { + memset(playerName, 0, nameLength); + StreamArray(playerName, nameLength); + Stream(playerPort); + Stream(isHost); + + PlayerInfo playerInfo; + playerInfo.Name = playerName; + playerInfo.ControllerPort = playerPort; + playerInfo.IsHost = isHost; + + _playerList.push_back(playerInfo); + } + } + } + +public: + PlayerListMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { } + + PlayerListMessage(vector playerList) : NetMessage(MessageType::PlayerList) + { + _playerList = playerList; + } + + vector GetPlayerList() + { + return _playerList; + } +}; \ No newline at end of file diff --git a/Core/SelectControllerMessage.h b/Core/SelectControllerMessage.h new file mode 100644 index 00000000..ff22695b --- /dev/null +++ b/Core/SelectControllerMessage.h @@ -0,0 +1,28 @@ +#pragma once +#include "stdafx.h" +#include "NetMessage.h" + +class SelectControllerMessage : public NetMessage +{ +private: + uint8_t _portNumber; + +protected: + virtual void ProtectedStreamState() + { + Stream(_portNumber); + } + +public: + SelectControllerMessage(void* buffer, uint32_t length) : NetMessage(buffer, length) { } + + SelectControllerMessage(uint8_t port) : NetMessage(MessageType::SelectController) + { + _portNumber = port; + } + + uint8_t GetPortNumber() + { + return _portNumber; + } +}; \ No newline at end of file diff --git a/GUI.NET/Config/ClientConnection.cs b/GUI.NET/Config/ClientConnection.cs index 64360a75..bd7f9887 100644 --- a/GUI.NET/Config/ClientConnection.cs +++ b/GUI.NET/Config/ClientConnection.cs @@ -10,5 +10,6 @@ namespace Mesen.GUI.Config { public string Host = "localhost"; public UInt16 Port = 8888; + public bool Spectator = false; } } diff --git a/GUI.NET/Config/InputInfo.cs b/GUI.NET/Config/InputInfo.cs index 0b756971..a1a3213e 100644 --- a/GUI.NET/Config/InputInfo.cs +++ b/GUI.NET/Config/InputInfo.cs @@ -135,9 +135,10 @@ namespace Mesen.GUI.Config InteropEmu.SetConsoleType(inputInfo.ConsoleType); InteropEmu.SetExpansionDevice(inputInfo.ExpansionPortDevice); - InteropEmu.SetFlag(EmulationFlags.HasFourScore, (inputInfo.ConsoleType == ConsoleType.Nes && inputInfo.UseFourScore) || (inputInfo.ConsoleType == ConsoleType.Famicom && expansionDevice == InteropEmu.ExpansionPortDevice.FourPlayerAdapter)); + bool hasFourScore = (inputInfo.ConsoleType == ConsoleType.Nes && inputInfo.UseFourScore) || (inputInfo.ConsoleType == ConsoleType.Famicom && expansionDevice == InteropEmu.ExpansionPortDevice.FourPlayerAdapter); + InteropEmu.SetFlag(EmulationFlags.HasFourScore, hasFourScore); for(int i = 0; i < 4; i++) { - InteropEmu.SetControllerType(i, inputInfo.Controllers[i].ControllerType); + InteropEmu.SetControllerType(i, i < 2 || hasFourScore ? inputInfo.Controllers[i].ControllerType : InteropEmu.ControllerType.None); InteropEmu.SetControllerKeys(i, inputInfo.Controllers[i].GetKeyMappingSet()); } } diff --git a/GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs b/GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs index e8256506..63bdbb33 100644 --- a/GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs +++ b/GUI.NET/Forms/NetPlay/frmClientConfig.Designer.cs @@ -36,6 +36,11 @@ this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // + // baseConfigPanel + // + this.baseConfigPanel.Location = new System.Drawing.Point(0, 111); + this.baseConfigPanel.Size = new System.Drawing.Size(290, 29); + // // tableLayoutPanel1 // this.tableLayoutPanel1.ColumnCount = 2; @@ -54,7 +59,7 @@ this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel1.Size = new System.Drawing.Size(290, 110); + this.tableLayoutPanel1.Size = new System.Drawing.Size(290, 111); this.tableLayoutPanel1.TabIndex = 0; // // txtPort @@ -99,7 +104,6 @@ // this.chkSpectator.AutoSize = true; this.tableLayoutPanel1.SetColumnSpan(this.chkSpectator, 2); - this.chkSpectator.Enabled = false; this.chkSpectator.Location = new System.Drawing.Point(3, 55); this.chkSpectator.Name = "chkSpectator"; this.chkSpectator.Size = new System.Drawing.Size(106, 17); @@ -119,9 +123,9 @@ this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(306, 178); this.Name = "frmClientConfig"; - this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Connect..."; + this.Controls.SetChildIndex(this.baseConfigPanel, 0); this.Controls.SetChildIndex(this.tableLayoutPanel1, 0); this.tableLayoutPanel1.ResumeLayout(false); this.tableLayoutPanel1.PerformLayout(); diff --git a/GUI.NET/Forms/NetPlay/frmClientConfig.cs b/GUI.NET/Forms/NetPlay/frmClientConfig.cs index 63be871e..1c626536 100644 --- a/GUI.NET/Forms/NetPlay/frmClientConfig.cs +++ b/GUI.NET/Forms/NetPlay/frmClientConfig.cs @@ -18,13 +18,16 @@ namespace Mesen.GUI.Forms.NetPlay { InitializeComponent(); - this.txtHost.Text = ConfigManager.Config.ClientConnectionInfo.Host; + Entity = ConfigManager.Config.ClientConnectionInfo; + + AddBinding("Host", this.txtHost); + AddBinding("Spectator", chkSpectator); this.txtPort.Text = ConfigManager.Config.ClientConnectionInfo.Port.ToString(); } protected override void UpdateConfig() { - ConfigManager.Config.ClientConnectionInfo = new ClientConnectionInfo() { Host = this.txtHost.Text, Port = Convert.ToUInt16(this.txtPort.Text) }; + ((ClientConnectionInfo)Entity).Port = Convert.ToUInt16(this.txtPort.Text); } private void Field_TextChanged(object sender, EventArgs e) diff --git a/GUI.NET/Forms/NetPlay/frmClientConfig.resx b/GUI.NET/Forms/NetPlay/frmClientConfig.resx index 1af7de15..8766f298 100644 --- a/GUI.NET/Forms/NetPlay/frmClientConfig.resx +++ b/GUI.NET/Forms/NetPlay/frmClientConfig.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index bca41c73..b7af43a5 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -91,6 +91,13 @@ this.mnuNetPlay = new System.Windows.Forms.ToolStripMenuItem(); this.mnuStartServer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuConnect = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNetPlaySelectController = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNetPlayPlayer1 = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNetPlayPlayer2 = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNetPlayPlayer3 = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuNetPlayPlayer4 = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuNetPlaySpectator = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); this.mnuFindServer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuProfile = new System.Windows.Forms.ToolStripMenuItem(); @@ -556,6 +563,7 @@ this.mnuNetPlay.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mnuStartServer, this.mnuConnect, + this.mnuNetPlaySelectController, this.toolStripMenuItem2, this.mnuFindServer, this.mnuProfile}); @@ -578,6 +586,59 @@ this.mnuConnect.Text = "Connect to Server"; this.mnuConnect.Click += new System.EventHandler(this.mnuConnect_Click); // + // mnuNetPlaySelectController + // + this.mnuNetPlaySelectController.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuNetPlayPlayer1, + this.mnuNetPlayPlayer2, + this.mnuNetPlayPlayer3, + this.mnuNetPlayPlayer4, + this.toolStripMenuItem3, + this.mnuNetPlaySpectator}); + this.mnuNetPlaySelectController.Name = "mnuNetPlaySelectController"; + this.mnuNetPlaySelectController.Size = new System.Drawing.Size(177, 22); + this.mnuNetPlaySelectController.Text = "Select Controller"; + // + // mnuNetPlayPlayer1 + // + this.mnuNetPlayPlayer1.Name = "mnuNetPlayPlayer1"; + this.mnuNetPlayPlayer1.Size = new System.Drawing.Size(152, 22); + this.mnuNetPlayPlayer1.Text = "Player 1"; + this.mnuNetPlayPlayer1.Click += new System.EventHandler(this.mnuNetPlayPlayer1_Click); + // + // mnuNetPlayPlayer2 + // + this.mnuNetPlayPlayer2.Name = "mnuNetPlayPlayer2"; + this.mnuNetPlayPlayer2.Size = new System.Drawing.Size(152, 22); + this.mnuNetPlayPlayer2.Text = "Player 2"; + this.mnuNetPlayPlayer2.Click += new System.EventHandler(this.mnuNetPlayPlayer2_Click); + // + // mnuNetPlayPlayer3 + // + this.mnuNetPlayPlayer3.Name = "mnuNetPlayPlayer3"; + this.mnuNetPlayPlayer3.Size = new System.Drawing.Size(152, 22); + this.mnuNetPlayPlayer3.Text = "Player 3"; + this.mnuNetPlayPlayer3.Click += new System.EventHandler(this.mnuNetPlayPlayer3_Click); + // + // mnuNetPlayPlayer4 + // + this.mnuNetPlayPlayer4.Name = "mnuNetPlayPlayer4"; + this.mnuNetPlayPlayer4.Size = new System.Drawing.Size(152, 22); + this.mnuNetPlayPlayer4.Text = "Player 4"; + this.mnuNetPlayPlayer4.Click += new System.EventHandler(this.mnuNetPlayPlayer4_Click); + // + // toolStripMenuItem3 + // + this.toolStripMenuItem3.Name = "toolStripMenuItem3"; + this.toolStripMenuItem3.Size = new System.Drawing.Size(149, 6); + // + // mnuNetPlaySpectator + // + this.mnuNetPlaySpectator.Name = "mnuNetPlaySpectator"; + this.mnuNetPlaySpectator.Size = new System.Drawing.Size(152, 22); + this.mnuNetPlaySpectator.Text = "Spectator"; + this.mnuNetPlaySpectator.Click += new System.EventHandler(this.mnuNetPlaySpectator_Click); + // // toolStripMenuItem2 // this.toolStripMenuItem2.Name = "toolStripMenuItem2"; @@ -904,6 +965,13 @@ private System.Windows.Forms.ToolStripMenuItem mnuRegionDendy; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlaySelectController; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer1; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer2; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer3; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer4; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3; + private System.Windows.Forms.ToolStripMenuItem mnuNetPlaySpectator; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 01f74c39..d5afd121 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -334,6 +334,27 @@ namespace Mesen.GUI.Forms mnuStartServer.Enabled = !isNetPlayClient; mnuConnect.Enabled = !InteropEmu.IsServerRunning(); + mnuNetPlaySelectController.Enabled = isNetPlayClient || InteropEmu.IsServerRunning(); + if(mnuNetPlaySelectController.Enabled) { + int availableControllers = InteropEmu.NetPlayGetAvailableControllers(); + int currentControllerPort = InteropEmu.NetPlayGetControllerPort(); + mnuNetPlayPlayer1.Enabled = (availableControllers & 0x01) == 0x01; + mnuNetPlayPlayer2.Enabled = (availableControllers & 0x02) == 0x02; + mnuNetPlayPlayer3.Enabled = (availableControllers & 0x04) == 0x04; + mnuNetPlayPlayer4.Enabled = (availableControllers & 0x08) == 0x08; + mnuNetPlayPlayer1.Text = "Player 1 (" + InteropEmu.NetPlayGetControllerType(0).ToString() + ")"; + mnuNetPlayPlayer2.Text = "Player 2 (" + InteropEmu.NetPlayGetControllerType(1).ToString() + ")"; + mnuNetPlayPlayer3.Text = "Player 3 (" + InteropEmu.NetPlayGetControllerType(2).ToString() + ")"; + mnuNetPlayPlayer4.Text = "Player 4 (" + InteropEmu.NetPlayGetControllerType(3).ToString() + ")"; + + mnuNetPlayPlayer1.Checked = (currentControllerPort == 0); + mnuNetPlayPlayer2.Checked = (currentControllerPort == 1); + mnuNetPlayPlayer3.Checked = (currentControllerPort == 2); + mnuNetPlayPlayer4.Checked = (currentControllerPort == 3); + mnuNetPlaySpectator.Checked = (currentControllerPort == 0xFF); + + mnuNetPlaySpectator.Enabled = true; + } mnuStartServer.Text = InteropEmu.IsServerRunning() ? "Stop Server" : "Start Server"; mnuConnect.Text = isNetPlayClient ? "Disconnect" : "Connect to Server"; @@ -510,7 +531,7 @@ namespace Mesen.GUI.Forms } else { frmServerConfig frm = new frmServerConfig(); if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) { - InteropEmu.StartServer(ConfigManager.Config.ServerInfo.Port); + InteropEmu.StartServer(ConfigManager.Config.ServerInfo.Port, ConfigManager.Config.Profile.PlayerName); } } } @@ -522,7 +543,7 @@ namespace Mesen.GUI.Forms } else { frmClientConfig frm = new frmClientConfig(); if(frm.ShowDialog(sender) == System.Windows.Forms.DialogResult.OK) { - InteropEmu.Connect(ConfigManager.Config.ClientConnectionInfo.Host, ConfigManager.Config.ClientConnectionInfo.Port, ConfigManager.Config.Profile.PlayerName, ConfigManager.Config.Profile.PlayerAvatar, (UInt16)ConfigManager.Config.Profile.PlayerAvatar.Length); + InteropEmu.Connect(ConfigManager.Config.ClientConnectionInfo.Host, ConfigManager.Config.ClientConnectionInfo.Port, ConfigManager.Config.Profile.PlayerName, ConfigManager.Config.Profile.PlayerAvatar, (UInt16)ConfigManager.Config.Profile.PlayerAvatar.Length, ConfigManager.Config.ClientConnectionInfo.Spectator); } } } @@ -876,5 +897,30 @@ namespace Mesen.GUI.Forms e.Effect = DragDropEffects.Copy; } } + + private void mnuNetPlayPlayer1_Click(object sender, EventArgs e) + { + InteropEmu.NetPlaySelectController(0); + } + + private void mnuNetPlayPlayer2_Click(object sender, EventArgs e) + { + InteropEmu.NetPlaySelectController(1); + } + + private void mnuNetPlayPlayer3_Click(object sender, EventArgs e) + { + InteropEmu.NetPlaySelectController(2); + } + + private void mnuNetPlayPlayer4_Click(object sender, EventArgs e) + { + InteropEmu.NetPlaySelectController(3); + } + + private void mnuNetPlaySpectator_Click(object sender, EventArgs e) + { + InteropEmu.NetPlaySelectController(0xFF); + } } } diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 28970883..f8b2a1bc 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -43,13 +43,18 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void Stop(); [DllImport(DLLPath, EntryPoint="GetROMPath")] private static extern IntPtr GetROMPathWrapper(); [DllImport(DLLPath)] public static extern void Reset(); - [DllImport(DLLPath)] public static extern void StartServer(UInt16 port); + [DllImport(DLLPath)] public static extern void StartServer(UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string hostPlayerName); [DllImport(DLLPath)] public static extern void StopServer(); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsServerRunning(); - [DllImport(DLLPath)] public static extern void Connect([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string host, UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string playerName, byte[] avatarData, UInt32 avatarSize); + [DllImport(DLLPath)] public static extern void Connect([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string host, UInt16 port, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string playerName, byte[] avatarData, UInt32 avatarSize, [MarshalAs(UnmanagedType.I1)]bool spectator); [DllImport(DLLPath)] public static extern void Disconnect(); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsConnected(); - + + [DllImport(DLLPath)] public static extern Int32 NetPlayGetAvailableControllers(); + [DllImport(DLLPath)] public static extern void NetPlaySelectController(Int32 controllerPort); + [DllImport(DLLPath)] public static extern ControllerType NetPlayGetControllerType(Int32 controllerPort); + [DllImport(DLLPath)] public static extern Int32 NetPlayGetControllerPort(); + [DllImport(DLLPath)] public static extern void TakeScreenshot(); [DllImport(DLLPath)] public static extern IntPtr RegisterNotificationCallback(NotificationListener.NotificationCallback callback); diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index c9649eb9..1d9ab377 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -111,25 +111,54 @@ namespace InteropEmu { DllExport void __stdcall Reset() { Console::Reset(); } - DllExport void __stdcall StartServer(uint16_t port) { GameServer::StartServer(port); } + DllExport void __stdcall StartServer(uint16_t port, char* hostPlayerName) { GameServer::StartServer(port, hostPlayerName); } DllExport void __stdcall StopServer() { GameServer::StopServer(); } DllExport bool __stdcall IsServerRunning() { return GameServer::Started(); } - DllExport void __stdcall Connect(char* host, uint16_t port, char* playerName, uint8_t* avatarData, uint32_t avatarSize) + DllExport void __stdcall Connect(char* host, uint16_t port, char* playerName, uint8_t* avatarData, uint32_t avatarSize, bool spectator) { shared_ptr connectionData(new ClientConnectionData( host, port, playerName, avatarData, - avatarSize - )); + avatarSize, + spectator + )); GameClient::Connect(connectionData); } DllExport void __stdcall Disconnect() { GameClient::Disconnect(); } DllExport bool __stdcall IsConnected() { return GameClient::Connected(); } + DllExport ControllerType __stdcall NetPlayGetControllerType(int32_t port) { return EmulationSettings::GetControllerType(port); } + + DllExport int32_t __stdcall NetPlayGetAvailableControllers() + { + if(GameServer::Started()) { + return GameServer::GetAvailableControllers(); + } else { + return GameClient::GetAvailableControllers(); + } + } + + DllExport void __stdcall NetPlaySelectController(int32_t port) + { + if(GameServer::Started()) { + return GameServer::SetHostControllerPort(port); + } else { + return GameClient::SelectController(port); + } + } + + DllExport int32_t __stdcall NetPlayGetControllerPort() + { + if(GameServer::Started()) { + return GameServer::GetHostControllerPort(); + } else { + return GameClient::GetControllerPort(); + } + } DllExport void __stdcall Pause() {