Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -20,7 +20,7 @@ /** * Is this user in control of game settings (i.e. is a network server, or offline player). */ -const g_IsController = !g_IsNetworked || Engine.HasNetServer(); +const g_IsController = !g_IsNetworked || Engine.IsNetController(); /** * Central data storing all settings relevant to the map generation and simulation. Index: ps/trunk/binaries/data/mods/public/gui/session/message_box/QuitConfirmationDefeat.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/message_box/QuitConfirmationDefeat.js +++ ps/trunk/binaries/data/mods/public/gui/session/message_box/QuitConfirmationDefeat.js @@ -33,7 +33,7 @@ unregisterSimulationUpdateHandler(this.confirmHandler); - // Don't ask for exit if other humans are still playing. + // Don't invite the host to exit if other humans are still playing. let askExit = !Engine.HasNetServer() || g_Players.every((player, i) => i == 0 || player.state != "active" || Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js +++ ps/trunk/binaries/data/mods/public/gui/session/session.js @@ -48,7 +48,7 @@ /** * Is this user in control of game settings (i.e. is a network server, or offline player). */ -var g_IsController = !g_IsNetworked || Engine.HasNetServer(); +var g_IsController = !g_IsNetworked || Engine.IsNetController(); /** * Whether we have finished the synchronization and Index: ps/trunk/source/network/NetClient.h =================================================================== --- ps/trunk/source/network/NetClient.h +++ ps/trunk/source/network/NetClient.h @@ -70,7 +70,7 @@ * Construct a client associated with the given game object. * The game must exist for the lifetime of this object. */ - CNetClient(CGame* game, bool isLocalClient); + CNetClient(CGame* game); virtual ~CNetClient(); @@ -99,6 +99,10 @@ */ void SetHostingPlayerName(const CStr& hostingPlayerName); + void SetControllerSecret(const std::string& secret); + + bool IsController() const { return m_IsController; } + /** * Set the game password. */ @@ -303,6 +307,12 @@ */ CStr m_Password; + /// The 'secret' used to identify the controller of the game. + std::string m_ControllerSecret; + + /// Note that this is just a "gui hint" with no actual impact on being controller. + bool m_IsController = false; + /// Current network session (or NULL if not connected) CNetClientSession* m_Session; @@ -317,9 +327,6 @@ /// True if the player is currently rejoining or has already rejoined the game. bool m_Rejoin; - /// Whether to prevent the client of the host from timing out - bool m_IsLocalClient; - /// Latest copy of game setup attributes heard from the server JS::PersistentRootedValue m_GameAttributes; Index: ps/trunk/source/network/NetClient.cpp =================================================================== --- ps/trunk/source/network/NetClient.cpp +++ ps/trunk/source/network/NetClient.cpp @@ -71,12 +71,11 @@ CNetClient& m_Client; }; -CNetClient::CNetClient(CGame* game, bool isLocalClient) : +CNetClient::CNetClient(CGame* game) : m_Session(NULL), m_UserName(L"anonymous"), m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game), m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetGeneralJSContext()), - m_IsLocalClient(isLocalClient), m_LastConnectionCheck(0), m_ServerAddress(), m_ServerPort(0), @@ -180,10 +179,16 @@ m_Password = hashedPassword; } +void CNetClient::SetControllerSecret(const std::string& secret) +{ + m_ControllerSecret = secret; +} + + bool CNetClient::SetupConnection(ENetHost* enetClient) { CNetClientSession* session = new CNetClientSession(*this); - bool ok = session->Connect(m_ServerAddress, m_ServerPort, m_IsLocalClient, enetClient); + bool ok = session->Connect(m_ServerAddress, m_ServerPort, enetClient); SetAndOwnSession(session); m_PollingThread = std::thread(Threading::HandleExceptions::Wrapper, m_Session); return ok; @@ -576,7 +581,7 @@ CAuthenticateMessage authenticate; authenticate.m_Name = m_UserName; authenticate.m_Password = m_Password; - authenticate.m_IsLocalClient = m_IsLocalClient; + authenticate.m_ControllerSecret = m_ControllerSecret; SendMessage(&authenticate); } @@ -657,6 +662,7 @@ client->m_HostID = message->m_HostID; client->m_Rejoin = message->m_Code == ARC_OK_REJOINING; + client->m_IsController = message->m_IsController; client->PushGuiMessage( "type", "netstatus", Index: ps/trunk/source/network/NetMessages.h =================================================================== --- ps/trunk/source/network/NetMessages.h +++ ps/trunk/source/network/NetMessages.h @@ -28,7 +28,7 @@ #define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' #define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' -#define PS_PROTOCOL_VERSION 0x01010016 // Arbitrary protocol +#define PS_PROTOCOL_VERSION 0x01010017 // Arbitrary protocol #define PS_DEFAULT_PORT 0x5073 // 'P', 's' // Set when lobby authentication is required. Used in the SrvHandshakeResponseMessage. @@ -122,12 +122,13 @@ START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE) NMT_FIELD(CStrW, m_Name) NMT_FIELD_SECRET(CStr, m_Password) - NMT_FIELD_INT(m_IsLocalClient, u8, 1) + NMT_FIELD_SECRET(CStr, m_ControllerSecret) END_NMT_CLASS() START_NMT_CLASS_(AuthenticateResult, NMT_AUTHENTICATE_RESULT) NMT_FIELD_INT(m_Code, u32, 4) NMT_FIELD_INT(m_HostID, u32, 2) + NMT_FIELD_INT(m_IsController, u8, 1) NMT_FIELD(CStrW, m_Message) END_NMT_CLASS() Index: ps/trunk/source/network/NetServer.h =================================================================== --- ps/trunk/source/network/NetServer.h +++ ps/trunk/source/network/NetServer.h @@ -109,7 +109,7 @@ public: /** * Construct a new network server. - * @param autostartPlayers if positive then StartGame will be called automatically + * @param autostartPlayers - if positive then StartGame will be called automatically * once this many players are connected (intended for the command-line testing mode). */ CNetServer(bool useLobbyAuth = false, int autostartPlayers = -1); @@ -173,6 +173,8 @@ void SetPassword(const CStr& password); + void SetControllerSecret(const std::string& secret); + private: CNetServerWorker* m_Worker; const bool m_LobbyAuth; @@ -226,6 +228,8 @@ void SetPassword(const CStr& hashedPassword); + void SetControllerSecret(const std::string& secret); + /** * Begin listening for network connections. * @return true on success, false on error (e.g. port already in use) @@ -369,7 +373,15 @@ CNetServerTurnManager* m_ServerTurnManager; - CStr m_HostGUID; + /** + * The GUID of the client in control of the game (the 'host' from the players' perspective). + */ + CStr m_ControllerGUID; + + /** + * The 'secret' used to identify the controller of the game. + */ + std::string m_ControllerSecret; /** * A copy of all simulation commands received so far, indexed by Index: ps/trunk/source/network/NetServer.cpp =================================================================== --- ps/trunk/source/network/NetServer.cpp +++ ps/trunk/source/network/NetServer.cpp @@ -137,7 +137,7 @@ m_LobbyAuth(useLobbyAuth), m_Shutdown(false), m_ScriptInterface(NULL), - m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL), + m_NextHostID(1), m_Host(NULL), m_ControllerGUID(), m_Stats(NULL), m_LastConnectionCheck(0) { m_State = SERVER_STATE_UNCONNECTED; @@ -187,6 +187,13 @@ m_Password = hashedPassword; } + +void CNetServerWorker::SetControllerSecret(const std::string& secret) +{ + m_ControllerSecret = secret; +} + + bool CNetServerWorker::SetupConnection(const u16 port) { ENSURE(m_State == SERVER_STATE_UNCONNECTED); @@ -714,9 +721,6 @@ { AddPlayer(session->GetGUID(), session->GetUserName()); - if (m_HostGUID.empty() && session->IsLocalClient()) - m_HostGUID = session->GetGUID(); - CGameSetupMessage gameSetupMessage(GetScriptInterface()); gameSetupMessage.m_Data = m_GameAttributes; session->SendMessage(&gameSetupMessage); @@ -814,7 +818,7 @@ [&](CNetServerSession* session) { return session->GetUserName() == playerName; }); // and return if no one or the host has that name - if (it == m_Sessions.end() || (*it)->GetGUID() == m_HostGUID) + if (it == m_Sessions.end() || (*it)->GetGUID() == m_ControllerGUID) return; if (ban) @@ -1110,12 +1114,23 @@ session->SetUserName(username); session->SetHostID(newHostID); - session->SetLocalClient(message->m_IsLocalClient); CAuthenticateResultMessage authenticateResult; authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK; authenticateResult.m_HostID = newHostID; authenticateResult.m_Message = L"Logged in"; + authenticateResult.m_IsController = 0; + + if (message->m_ControllerSecret == server.m_ControllerSecret) + { + if (server.m_ControllerGUID.empty()) + { + server.m_ControllerGUID = session->GetGUID(); + authenticateResult.m_IsController = 1; + } + // TODO: we could probably handle having several controllers, or swapping? + } + session->SendMessage(&authenticateResult); server.OnUserJoin(session); @@ -1247,7 +1262,7 @@ CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); - if (session->GetGUID() == server.m_HostGUID) + if (session->GetGUID() == server.m_ControllerGUID) server.ClearAllPlayerReady(); return true; @@ -1265,7 +1280,7 @@ if (server.m_State != SERVER_STATE_PREGAME) return true; - if (session->GetGUID() == server.m_HostGUID) + if (session->GetGUID() == server.m_ControllerGUID) { CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef(); server.UpdateGameAttributes(&(message->m_Data)); @@ -1279,7 +1294,7 @@ CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); - if (session->GetGUID() == server.m_HostGUID) + if (session->GetGUID() == server.m_ControllerGUID) { CAssignPlayerMessage* message = (CAssignPlayerMessage*)event->GetParamRef(); server.AssignPlayer(message->m_PlayerID, message->m_GUID); @@ -1293,7 +1308,7 @@ CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); - if (session->GetGUID() == server.m_HostGUID) + if (session->GetGUID() == server.m_ControllerGUID) server.StartGame(); return true; @@ -1413,7 +1428,7 @@ CNetServerSession* session = (CNetServerSession*)context; CNetServerWorker& server = session->GetServer(); - if (session->GetGUID() == server.m_HostGUID) + if (session->GetGUID() == server.m_ControllerGUID) { CKickedMessage* message = (CKickedMessage*)event->GetParamRef(); server.KickPlayer(message->m_Name, message->m_Ban); @@ -1659,6 +1674,12 @@ m_Worker->SetPassword(password); } +void CNetServer::SetControllerSecret(const std::string& secret) +{ + std::lock_guard lock(m_Worker->m_WorkerMutex); + m_Worker->SetControllerSecret(secret); +} + void CNetServer::StartGame() { std::lock_guard lock(m_Worker->m_WorkerMutex); Index: ps/trunk/source/network/NetSession.h =================================================================== --- ps/trunk/source/network/NetSession.h +++ ps/trunk/source/network/NetSession.h @@ -72,7 +72,7 @@ CNetClientSession(CNetClient& client); ~CNetClientSession(); - bool Connect(const CStr& server, const u16 port, const bool isLocalClient, ENetHost* enetClient); + bool Connect(const CStr& server, const u16 port, ENetHost* enetClient); /** * The client NetSession is threaded to avoid getting timeouts if the main thread hangs. @@ -140,8 +140,6 @@ ENetHost* m_Host; ENetPeer* m_Server; CNetStatsTable* m_Stats; - - bool m_IsLocalClient; }; @@ -174,11 +172,6 @@ u32 GetIPAddress() const; /** - * Whether this client is running in the same process as the server. - */ - bool IsLocalClient() const; - - /** * Number of milliseconds since the latest packet of that client was received. */ u32 GetLastReceivedTime() const; @@ -204,11 +197,6 @@ void DisconnectNow(NetDisconnectReason reason); /** - * Prevent timeouts for the client running in the same process as the server. - */ - void SetLocalClient(bool isLocalClient); - - /** * Send a message to the client. */ virtual bool SendMessage(const CNetMessage* message); @@ -226,8 +214,6 @@ CStrW m_UserName; u32 m_HostID; CStr m_Password; - - bool m_IsLocalClient; }; #endif // NETSESSION_H Index: ps/trunk/source/network/NetSession.cpp =================================================================== --- ps/trunk/source/network/NetSession.cpp +++ ps/trunk/source/network/NetSession.cpp @@ -33,7 +33,7 @@ CNetClientSession::CNetClientSession(CNetClient& client) : m_Client(client), m_FileTransferer(this), m_Host(nullptr), m_Server(nullptr), - m_Stats(nullptr), m_IsLocalClient(false), m_IncomingMessages(16), m_OutgoingMessages(16), + m_Stats(nullptr), m_IncomingMessages(16), m_OutgoingMessages(16), m_LoopRunning(false), m_ShouldShutdown(false), m_MeanRTT(0), m_LastReceivedTime(0) { } @@ -55,7 +55,7 @@ } } -bool CNetClientSession::Connect(const CStr& server, const u16 port, const bool isLocalClient, ENetHost* enetClient) +bool CNetClientSession::Connect(const CStr& server, const u16 port, ENetHost* enetClient) { ENSURE(!m_LoopRunning); ENSURE(!m_Host); @@ -84,7 +84,6 @@ m_Host = host; m_Server = peer; - m_IsLocalClient = isLocalClient; m_Stats = new CNetStatsTable(m_Server); if (CProfileViewer::IsInitialised()) @@ -236,7 +235,7 @@ } CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) : - m_Server(server), m_FileTransferer(this), m_Peer(peer), m_IsLocalClient(false), m_HostID(0), m_GUID(), m_UserName() + m_Server(server), m_FileTransferer(this), m_Peer(peer), m_HostID(0), m_GUID(), m_UserName() { } @@ -283,16 +282,3 @@ { return m_Server.SendMessage(m_Peer, message); } - -bool CNetServerSession::IsLocalClient() const -{ - return m_IsLocalClient; -} - -void CNetServerSession::SetLocalClient(bool isLocalClient) -{ - m_IsLocalClient = isLocalClient; - - if (!isLocalClient) - return; -} Index: ps/trunk/source/network/scripting/JSInterface_Network.h =================================================================== --- ps/trunk/source/network/scripting/JSInterface_Network.h +++ ps/trunk/source/network/scripting/JSInterface_Network.h @@ -25,6 +25,7 @@ namespace JSI_Network { u16 GetDefaultPort(ScriptInterface::CmptPrivate* pCmptPrivate); + bool IsNetController(ScriptInterface::CmptPrivate* pCmptPrivate); bool HasNetServer(ScriptInterface::CmptPrivate* pCmptPrivate); bool HasNetClient(ScriptInterface::CmptPrivate* pCmptPrivate); void StartNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate); Index: ps/trunk/source/network/scripting/JSInterface_Network.cpp =================================================================== --- ps/trunk/source/network/scripting/JSInterface_Network.cpp +++ ps/trunk/source/network/scripting/JSInterface_Network.cpp @@ -29,6 +29,7 @@ #include "network/StunClient.h" #include "ps/CLogger.h" #include "ps/Game.h" +#include "ps/GUID.h" #include "ps/Util.h" #include "scriptinterface/ScriptInterface.h" @@ -39,6 +40,11 @@ return PS_DEFAULT_PORT; } +bool JSI_Network::IsNetController(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) +{ + return !!g_NetClient && g_NetClient->IsController(); +} + bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { return !!g_NetServer; @@ -120,16 +126,21 @@ return; } + // Generate a secret to identify the host client. + std::string secret = ps_generate_guid(); + // We will get hashed password from clients, so hash it once for server CStr hashedPass = HashPassword(password); g_NetServer->SetPassword(hashedPass); + g_NetServer->SetControllerSecret(secret); g_Game = new CGame(true); - g_NetClient = new CNetClient(g_Game, true); + g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); g_NetClient->SetHostingPlayerName(hostLobbyName); g_NetClient->SetGamePassword(hashedPass); g_NetClient->SetupServerData("127.0.0.1", serverPort, false); + g_NetClient->SetControllerSecret(secret); if (!g_NetClient->SetupConnection(nullptr)) { @@ -147,7 +158,7 @@ ENSURE(!g_Game); g_Game = new CGame(true); - g_NetClient = new CNetClient(g_Game, false); + g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); @@ -170,7 +181,7 @@ CStr hashedPass = HashPassword(password); g_Game = new CGame(true); - g_NetClient = new CNetClient(g_Game, false); + g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); g_NetClient->SetGamePassword(hashedPass); @@ -269,6 +280,7 @@ void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface) { scriptInterface.RegisterFunction("GetDefaultPort"); + scriptInterface.RegisterFunction("IsNetController"); scriptInterface.RegisterFunction("HasNetServer"); scriptInterface.RegisterFunction("HasNetClient"); scriptInterface.RegisterFunction("StartNetworkHost"); Index: ps/trunk/source/network/tests/test_Net.h =================================================================== --- ps/trunk/source/network/tests/test_Net.h +++ ps/trunk/source/network/tests/test_Net.h @@ -150,7 +150,7 @@ CGame client2Game(false); CGame client3Game(false); - CNetServer server; + CNetServer server("no_secret"); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( @@ -163,9 +163,9 @@ server.UpdateGameAttributes(&attrs, scriptInterface); - CNetClient client1(&client1Game, false); - CNetClient client2(&client2Game, false); - CNetClient client3(&client3Game, false); + CNetClient client1(&client1Game); + CNetClient client2(&client2Game); + CNetClient client3(&client3Game); clients.push_back(&client1); clients.push_back(&client2); @@ -229,7 +229,7 @@ CGame client2Game(false); CGame client3Game(false); - CNetServer server; + CNetServer server("no_secret"); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( @@ -242,9 +242,9 @@ server.UpdateGameAttributes(&attrs, scriptInterface); - CNetClient client1(&client1Game, false); - CNetClient client2(&client2Game, false); - CNetClient client3(&client3Game, false); + CNetClient client1(&client1Game); + CNetClient client2(&client2Game); + CNetClient client3(&client3Game); client1.SetUserName(L"alice"); client2.SetUserName(L"bob"); @@ -303,7 +303,7 @@ debug_printf("==== Connecting client 2B\n"); CGame client2BGame(false); - CNetClient client2B(&client2BGame, false); + CNetClient client2B(&client2BGame); client2B.SetUserName(L"bob"); clients.push_back(&client2B); Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp @@ -57,6 +57,7 @@ #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/HWDetect.h" #include "ps/Globals.h" +#include "ps/GUID.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" @@ -1556,23 +1557,27 @@ if (args.Has("autostart-host-players")) maxPlayers = args.Get("autostart-host-players").ToUInt(); - g_NetServer = new CNetServer(false, maxPlayers); + // Generate a secret to identify the host client. + std::string secret = ps_generate_guid(); + g_NetServer = new CNetServer(false, maxPlayers); + g_NetServer->SetControllerSecret(secret); g_NetServer->UpdateGameAttributes(&attrs, scriptInterface); bool ok = g_NetServer->SetupConnection(PS_DEFAULT_PORT); ENSURE(ok); - g_NetClient = new CNetClient(g_Game, true); + g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); g_NetClient->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false); + g_NetClient->SetControllerSecret(secret); g_NetClient->SetupConnection(nullptr); } else if (args.Has("autostart-client")) { InitPsAutostart(true, attrs); - g_NetClient = new CNetClient(g_Game, false); + g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); CStr ip = args.Get("autostart-client");