Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ 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: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -67,7 +67,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(); @@ -96,6 +96,10 @@ */ void SetHostingPlayerName(const CStr& hostingPlayerName); + void SetControllerSecret(const std::string& secret); + + bool IsController() const { return m_IsController; } + /** * Returns the GUID of the local client. * Used for distinguishing observers. @@ -271,6 +275,12 @@ CStrW m_UserName; CStr m_HostingPlayerName; + /// 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; @@ -283,9 +293,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: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -67,12 +67,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_Rejoin(false) { @@ -165,10 +164,15 @@ m_HostingPlayerName = hostingPlayerName; } +void CNetClient::SetControllerSecret(const std::string &secret) +{ + m_ControllerSecret = secret; +} + bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient) { CNetClientSession* session = new CNetClientSession(*this); - bool ok = session->Connect(server, port, m_IsLocalClient, enetClient); + bool ok = session->Connect(server, port, enetClient); SetAndOwnSession(session); return ok; } @@ -473,7 +477,7 @@ CAuthenticateMessage authenticate; authenticate.m_Name = m_UserName; authenticate.m_Password = L""; // TODO - authenticate.m_IsLocalClient = m_IsLocalClient; + authenticate.m_ControllerSecret = m_ControllerSecret; SendMessage(&authenticate); } @@ -554,6 +558,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: source/network/NetMessages.h =================================================================== --- source/network/NetMessages.h +++ 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 0x01010015 // Arbitrary protocol +#define PS_PROTOCOL_VERSION 0x01010016 // Arbitrary protocol #define PS_DEFAULT_PORT 0x5073 // 'P', 's' // Set when lobby authentication is required. Used in the SrvHandshakeResponseMessage. @@ -123,12 +123,13 @@ NMT_FIELD(CStrW, m_Name) // TODO: The password should not be printed to logfiles NMT_FIELD(CStrW, m_Password) - NMT_FIELD_INT(m_IsLocalClient, u8, 1) + NMT_FIELD(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: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -107,10 +107,11 @@ public: /** * Construct a new network server. - * @param autostartPlayers if positive then StartGame will be called automatically + * @param secret - arbitrary but ideally hard-to-guess secret to identity the controller client. + * @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); + CNetServer(const std::string& secret, bool useLobbyAuth = false, int autostartPlayers = -1); ~CNetServer(); @@ -189,7 +190,7 @@ friend class CNetServer; friend class CNetFileReceiveTask_ServerRejoin; - CNetServerWorker(bool useLobbyAuth, int autostartPlayers); + CNetServerWorker(const std::string& secret, bool useLobbyAuth, int autostartPlayers); ~CNetServerWorker(); /** @@ -333,7 +334,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: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -130,12 +130,12 @@ * See http://trac.wildfiregames.com/ticket/654 */ -CNetServerWorker::CNetServerWorker(bool useLobbyAuth, int autostartPlayers) : +CNetServerWorker::CNetServerWorker(const std::string& secret, bool useLobbyAuth, int autostartPlayers) : m_AutostartPlayers(autostartPlayers), 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_ControllerSecret(secret), m_Stats(NULL), m_LastConnectionCheck(0) { m_State = SERVER_STATE_UNCONNECTED; @@ -707,9 +707,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); @@ -807,7 +804,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) @@ -1093,12 +1090,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); @@ -1232,7 +1240,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; @@ -1250,7 +1258,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)); @@ -1264,7 +1272,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); @@ -1278,7 +1286,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; @@ -1402,7 +1410,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); @@ -1579,8 +1587,8 @@ -CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) : - m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)), +CNetServer::CNetServer(const std::string& secret, bool useLobbyAuth, int autostartPlayers) : + m_Worker(new CNetServerWorker(secret, useLobbyAuth, autostartPlayers)), m_LobbyAuth(useLobbyAuth) { } Index: source/network/NetSession.h =================================================================== --- source/network/NetSession.h +++ source/network/NetSession.h @@ -71,7 +71,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); /** * Process queued incoming messages. @@ -120,8 +120,6 @@ ENetHost* m_Host; ENetPeer* m_Server; CNetStatsTable* m_Stats; - - bool m_IsLocalClient; }; @@ -153,11 +151,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. */ @@ -183,11 +176,6 @@ */ void DisconnectNow(NetDisconnectReason reason); - /** - * Prevent timeouts for the client running in the same process as the server. - */ - void SetLocalClient(bool isLocalClient); - /** * Allows increasing the timeout to prevent drops during an expensive operation, * and decreasing it back to normal afterwards. @@ -211,8 +199,6 @@ CStr m_GUID; CStrW m_UserName; u32 m_HostID; - - bool m_IsLocalClient; }; #endif // NETSESSION_H Index: source/network/NetSession.cpp =================================================================== --- source/network/NetSession.cpp +++ source/network/NetSession.cpp @@ -35,10 +35,10 @@ // Only disable long timeouts after a packet from the remote enet peer has been processed. // Otherwise a long timeout can still be in progress when disabling it here. -void SetEnetLongTimeout(ENetPeer* peer, bool isLocalClient, bool enabled) +void SetEnetLongTimeout(ENetPeer* peer, bool enabled) { #if (ENET_VERSION >= ENET_VERSION_CREATE(1, 3, 4)) - if (!peer || isLocalClient) + if (!peer) return; if (enabled) @@ -53,7 +53,7 @@ } CNetClientSession::CNetClientSession(CNetClient& client) : - m_Client(client), m_FileTransferer(this), m_Host(nullptr), m_Server(nullptr), m_Stats(nullptr), m_IsLocalClient(false) + m_Client(client), m_FileTransferer(this), m_Host(nullptr), m_Server(nullptr), m_Stats(nullptr) { } @@ -72,7 +72,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_Host); ENSURE(!m_Server); @@ -100,13 +100,6 @@ m_Host = host; m_Server = peer; - m_IsLocalClient = isLocalClient; - - // Prevent the local client of the host from timing out too quickly. -#if (ENET_VERSION >= ENET_VERSION_CREATE(1, 3, 4)) - if (isLocalClient) - enet_peer_timeout(peer, 1, MAXIMUM_HOST_TIMEOUT, MAXIMUM_HOST_TIMEOUT); -#endif m_Stats = new CNetStatsTable(m_Server); if (CProfileViewer::IsInitialised()) @@ -226,11 +219,11 @@ void CNetClientSession::SetLongTimeout(bool enabled) { - SetEnetLongTimeout(m_Server, m_IsLocalClient, enabled); + SetEnetLongTimeout(m_Server, enabled); } 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() { } @@ -278,25 +271,7 @@ 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; - - // Prevent the local client of the host from timing out too quickly -#if (ENET_VERSION >= ENET_VERSION_CREATE(1, 3, 4)) - enet_peer_timeout(m_Peer, 0, MAXIMUM_HOST_TIMEOUT, MAXIMUM_HOST_TIMEOUT); -#endif -} - void CNetServerSession::SetLongTimeout(bool enabled) { - SetEnetLongTimeout(m_Peer, m_IsLocalClient, enabled); + SetEnetLongTimeout(m_Peer, enabled); } Index: source/network/scripting/JSInterface_Network.h =================================================================== --- source/network/scripting/JSInterface_Network.h +++ source/network/scripting/JSInterface_Network.h @@ -25,7 +25,7 @@ namespace JSI_Network { u16 GetDefaultPort(ScriptInterface::CmptPrivate* pCmptPrivate); - bool HasNetServer(ScriptInterface::CmptPrivate* pCmptPrivate); + bool IsNetController(ScriptInterface::CmptPrivate* pCmptPrivate); bool HasNetClient(ScriptInterface::CmptPrivate* pCmptPrivate); void StartNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate); void SetNetworkGameAttributes(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue attribs1); Index: source/network/scripting/JSInterface_Network.cpp =================================================================== --- source/network/scripting/JSInterface_Network.cpp +++ 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 "scriptinterface/ScriptInterface.h" u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) @@ -36,9 +37,9 @@ return PS_DEFAULT_PORT; } -bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) +bool JSI_Network::IsNetController(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { - return !!g_NetServer; + return !!g_NetClient && g_NetClient->IsController(); } bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) @@ -57,8 +58,10 @@ ENSURE(!g_NetServer); ENSURE(!g_Game); + std::string secret = ps_generate_guid(); + // Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames. - g_NetServer = new CNetServer(!!g_XmppClient); + g_NetServer = new CNetServer(secret, !!g_XmppClient); if (!g_NetServer->SetupConnection(serverPort)) { ScriptRequest rq(pCmptPrivate->pScriptInterface); @@ -68,9 +71,10 @@ } 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->SetControllerSecret(secret); if (!g_NetClient->SetupConnection("127.0.0.1", serverPort, nullptr)) { @@ -121,7 +125,7 @@ } 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("@"))); @@ -229,7 +233,7 @@ void JSI_Network::RegisterScriptFunctions(const ScriptInterface& scriptInterface) { scriptInterface.RegisterFunction("GetDefaultPort"); - scriptInterface.RegisterFunction("HasNetServer"); + scriptInterface.RegisterFunction("IsNetController"); scriptInterface.RegisterFunction("HasNetClient"); scriptInterface.RegisterFunction("FindStunEndpoint"); scriptInterface.RegisterFunction("StartNetworkHost"); Index: source/network/tests/test_Net.h =================================================================== --- source/network/tests/test_Net.h +++ source/network/tests/test_Net.h @@ -147,7 +147,7 @@ CGame client2Game(false); CGame client3Game(false); - CNetServer server; + CNetServer server("no_secret"); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( @@ -160,9 +160,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); @@ -226,7 +226,7 @@ CGame client2Game(false); CGame client3Game(false); - CNetServer server; + CNetServer server("no_secret"); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( @@ -239,9 +239,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"); @@ -300,7 +300,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: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -1549,24 +1549,33 @@ if (args.Has("autostart-host-players")) maxPlayers = args.Get("autostart-host-players").ToUInt(); - g_NetServer = new CNetServer(false, maxPlayers); + std::string secret; + if (args.Has("autostart-host-secret")) + secret = args.Get("autostart-host-secret"); + + g_NetServer = new CNetServer(secret, false, maxPlayers); 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); + if (args.Has("autostart-host-secret")) + g_NetClient->SetControllerSecret(secret); g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, 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); + if (args.Has("autostart-client-secret")) + g_NetClient->SetControllerSecret(args.Get("autostart-client-secret")); + CStr ip = args.Get("autostart-client"); if (ip.empty()) ip = "127.0.0.1";