Index: binaries/data/mods/public/gui/common/network.js =================================================================== --- binaries/data/mods/public/gui/common/network.js +++ binaries/data/mods/public/gui/common/network.js @@ -76,6 +76,9 @@ case 10: return translate("Error: Server failed to allocate a unique client identifier."); case 11: return translate("Error: Client commands were ready for an unexpected game turn."); case 12: return translate("Error: Client simulated an unexpected game turn."); + case 13: return translate("You have been blocked by server from joining."); + case 14: return translate("Could not find an unused port for the enet STUN client."); + case 15: return translate("Could not find the STUN endpoint."); default: warn("Unknown disconnect-reason ID received: " + id); return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id }); Index: binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -13,21 +13,11 @@ */ var g_ServerName = ""; -/** - * Cached to pass it to the gamesetup of the controller to report the game to the lobby. - */ -var g_ServerPort; - var g_IsRejoining = false; var g_GameAttributes; // used when rejoining var g_PlayerAssignments; // used when rejoining var g_UserRating; -/** - * Object containing the IP address and port of the STUN server. - */ -var g_StunEndpoint; - function init(attribs) { g_UserRating = attribs.rating; @@ -38,7 +28,7 @@ { if (Engine.HasXmppClient()) { - if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port), attribs.useSTUN, attribs.hostJID)) + if (startJoinFromLobby(attribs.name, attribs.hostJID)) switchSetupPage("pageConnecting"); } else @@ -155,11 +145,12 @@ break; log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) })); - + warn(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) })); // If we're rejoining an active game, we don't want to actually display // the game setup screen, so perform similar processing to gamesetup.js // in this screen if (g_IsRejoining) + { switch (message.type) { case "netstatus": @@ -211,9 +202,10 @@ default: error("Unrecognised net message type: " + message.type); } + } else // Not rejoining - just trying to connect to server - + { switch (message.type) { case "netstatus": @@ -231,9 +223,7 @@ return; // we'll process the game setup messages in the next tick } Engine.SwitchGuiPage("page_gamesetup.xml", { - "serverName": g_ServerName, - "serverPort": g_ServerPort, - "stunEndpoint": g_StunEndpoint + "serverName": g_ServerName }); return; // don't process any more messages - leave them for the game GUI loop @@ -255,6 +245,7 @@ error("Unrecognised net message type: " + message.type); break; } + } } } @@ -301,20 +292,25 @@ return false; } - if (Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked) + let useSTUN = Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked; + let stunPort; + let stunIp; + if (useSTUN) { - g_StunEndpoint = Engine.FindStunEndpoint(port); - if (!g_StunEndpoint) + let stunEndpoint = Engine.FindStunEndpoint(port); + if (!stunEndpoint) { cancelSetup(); hostFeedback.caption = translate("Failed to host via STUN."); return false; } + stunIp = stunEndpoint.ip; + stunPort = stunEndpoint.port; } try { - Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername); + Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useSTUN, stunIp, stunPort); } catch (e) { @@ -328,7 +324,6 @@ } g_ServerName = servername; - g_ServerPort = port; if (Engine.HasXmppClient()) Engine.LobbySetPlayerPresence("playing"); @@ -370,6 +365,41 @@ return true; } +function startJoinFromLobby(playername, hostJID) +{ + if (!Engine.HasXmppClient()) + { + cancelSetup(); + messageBox( + 400, 200, + sprintf("You are not in lobby. Wrong function was called."), + translate("Error") + ); + return false; + } + + try + { + Engine.StartNetworkJoinLobby(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), hostJID); + } + catch (e) + { + cancelSetup(); + messageBox( + 400, 200, + sprintf(translate("Cannot join game: %(message)s."), { "message": e.message }), + translate("Error") + ); + return false; + } + + startConnectionStatus("client"); + + Engine.LobbySetPlayerPresence("playing"); + + return true; +} + function getDefaultGameName() { return sprintf(translate("%(playername)s's game"), { Index: binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js @@ -67,38 +67,12 @@ if (!game) return; - let ip; - let port; let stanza = game.stanza; - if (stanza.stunIP) - { - ip = stanza.stunIP; - port = stanza.stunPort; - } - else - { - ip = stanza.ip; - port = stanza.port; - } - - if (ip.split('.').length != 4) - { - messageBox( - 400, 250, - sprintf( - translate("This game's address '%(ip)s' does not appear to be valid."), - { "ip": escapeText(stanza.ip) }), - translate("Error")); - return; - } Engine.PushGuiPage("page_gamesetup_mp.xml", { "multiplayerGameType": "join", - "ip": ip, - "port": port, "name": g_Nickname, "rating": this.getRejoinRating(stanza), - "useSTUN": !!stanza.stunIP, "hostJID": stanza.hostUsername + "@" + Engine.ConfigDB_GetValue("user", "lobby.server") + "/0ad" }); } Index: binaries/data/mods/public/gui/lobby/LobbyPage/Game.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/Game.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/Game.js @@ -270,10 +270,6 @@ */ Game.prototype.StanzaKeys = [ "name", - "ip", - "port", - "stunIP", - "stunPort", "hostUsername", "state", "nbp", Index: binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js =================================================================== --- binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js +++ binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js @@ -97,13 +97,13 @@ let newGames = {}; for (let stanza of gameListData) { - let game = this.games[stanza.ip] || undefined; + let game = this.games[stanza.hostUsername] || undefined; let exists = !!game; if (!exists) game = new Game(this.mapCache); game.update(stanza, selectedColumn); - newGames[stanza.ip] = game; + newGames[stanza.hostUsername] = game; } this.games = newGames; Engine.ProfileStop(); @@ -157,8 +157,7 @@ this.list_maxnbp[i] = displayData.playerCount; this.list_gameRating[i] = game.gameRating; this.list[i] = ""; - - if (selectedGame && game.stanza.ip == selectedGame.stanza.ip && game.stanza.port == selectedGame.stanza.port) + if (selectedGame && game.hostUsername == selectedGame.hostUsername && game.stanza.gameName == selectedGame.stanza.gameName) selectedGameIndex = i; }); Engine.ProfileStop(); Index: source/lobby/IXmppClient.h =================================================================== --- source/lobby/IXmppClient.h +++ source/lobby/IXmppClient.h @@ -39,6 +39,7 @@ virtual void SendIqGetProfile(const std::string& player) = 0; virtual void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0; virtual void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0; + virtual void SendIqGetConnectionData(const std::string& jid) = 0; virtual void SendIqUnregisterGame() = 0; virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0; virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0; Index: source/lobby/StanzaExtensions.h =================================================================== --- source/lobby/StanzaExtensions.h +++ source/lobby/StanzaExtensions.h @@ -41,6 +41,28 @@ #define EXTLOBBYAUTH 1407 #define XMLNS_LOBBYAUTH "jabber:iq:lobbyauth" +#define EXTCONNECTIONDATA 1408 +#define XMLNS_CONNECTIONDATA "jabber:iq:connectiondata" + +class ConnectionData : public glooxwrapper::StanzaExtension +{ +public: + ConnectionData(const glooxwrapper::Tag* tag = 0); + + // Following four methods are all required by gloox + virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const + { + return new ConnectionData(tag); + } + virtual const glooxwrapper::string& filterString() const; + virtual glooxwrapper::Tag* tag() const; + virtual glooxwrapper::StanzaExtension* clone() const; + + glooxwrapper::string m_Ip; + glooxwrapper::string m_port; + glooxwrapper::string m_useSTUN; +}; + class GameReport : public glooxwrapper::StanzaExtension { public: Index: source/lobby/StanzaExtensions.cpp =================================================================== --- source/lobby/StanzaExtensions.cpp +++ source/lobby/StanzaExtensions.cpp @@ -283,3 +283,59 @@ { return new LobbyAuth(); } + +/****************************************************** + * ConnectionData, a custom IQ Stanza, used to send and + * receive a ip and port of the server. + */ +ConnectionData::ConnectionData(const glooxwrapper::Tag* tag) + : StanzaExtension(EXTCONNECTIONDATA) +{ + if (!tag || tag->name() != "connectiondata" || tag->xmlns() != XMLNS_CONNECTIONDATA) + return; + + const glooxwrapper::Tag* c = tag->findTag_clone("connectiondata/ip"); + if (c) + m_Ip = c->cdata(); + const glooxwrapper::Tag* p= tag->findTag_clone("connectiondata/port"); + if (p) + m_port = p->cdata(); + const glooxwrapper::Tag* s = tag->findTag_clone("connectiondata/useSTUN"); + if (s) + m_useSTUN = s->cdata(); + + glooxwrapper::Tag::free(c); + glooxwrapper::Tag::free(p); + glooxwrapper::Tag::free(s); +} + +/** + * Required by gloox, used to find the LobbyAuth element in a received IQ. + */ +const glooxwrapper::string& ConnectionData::filterString() const +{ + static const glooxwrapper::string filter = "/iq/connectiondata[@xmlns='" XMLNS_CONNECTIONDATA "']"; + return filter; +} + +/** + * Required by gloox, used to serialize the auth object into XML for sending. + */ +glooxwrapper::Tag* ConnectionData::tag() const +{ + glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("connectiondata"); + t->setXmlns(XMLNS_CONNECTIONDATA); + + if (!m_Ip.empty()) + t->addChild(glooxwrapper::Tag::allocate("ip", m_Ip)); + if (!m_port.empty()) + t->addChild(glooxwrapper::Tag::allocate("port", m_port)); + if (!m_useSTUN.empty()) + t->addChild(glooxwrapper::Tag::allocate("useSTUN", m_useSTUN)); + return t; +} + +glooxwrapper::StanzaExtension* ConnectionData::clone() const +{ + return new ConnectionData(); +} Index: source/lobby/XmppClient.h =================================================================== --- source/lobby/XmppClient.h +++ source/lobby/XmppClient.h @@ -82,6 +82,7 @@ void SendIqGetProfile(const std::string& player); void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data); void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data); + void SendIqGetConnectionData(const std::string& jid); void SendIqUnregisterGame(); void SendIqChangeStateGame(const std::string& nbp, const std::string& players); void SendIqLobbyAuth(const std::string& to, const std::string& token); Index: source/lobby/XmppClient.cpp =================================================================== --- source/lobby/XmppClient.cpp +++ source/lobby/XmppClient.cpp @@ -28,6 +28,7 @@ #include "lib/external_libraries/enet.h" #include "lib/utf8.h" #include "network/NetServer.h" +#include "network/NetClient.h" #include "network/StunClient.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" @@ -148,6 +149,9 @@ m_client->registerStanzaExtension(new LobbyAuth()); m_client->registerIqHandler(this, EXTLOBBYAUTH); + m_client->registerStanzaExtension(new ConnectionData()); + m_client->registerIqHandler(this, EXTCONNECTIONDATA); + m_client->registerMessageHandler(this); // Uncomment to see the raw stanzas @@ -361,6 +365,17 @@ m_client->send(iq); } +void XmppClient::SendIqGetConnectionData(const std::string& jid) +{ + glooxwrapper::JID targetJID(jid); + + ConnectionData* b = new ConnectionData(); + glooxwrapper::IQ iq(gloox::IQ::Get, targetJID, m_client->getID()); + iq.addExtension(b); + DbgXMPP("SendIqGetConnectionData []"); + m_client->send(iq); +} + /** * Send game report containing numerous game properties to the server. * @@ -573,7 +588,7 @@ ScriptInterface::CreateArray(rq, ret); int j = 0; - const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state", + const char* stats[] = { "name", "hostUsername", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryConditions", "startTime", "mods" }; @@ -811,6 +826,20 @@ const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY); const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY); const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY); + const ConnectionData* cd = iq.findExtension(EXTCONNECTIONDATA); + if (cd) + { + if (cd->m_Ip.empty()) + { + g_NetClient->SetupServerData("", 0, false); + g_NetClient->TryToConnect(iq.from().full()); + return true; + } + + g_NetClient->SetupServerData(cd->m_Ip.to_string(), stoi(cd->m_port.to_string()), !cd->m_useSTUN.empty()); + g_NetClient->TryToConnect(iq.from().full()); + + } if (gq) { for (const glooxwrapper::Tag* const& t : m_GameList) @@ -877,6 +906,31 @@ LOGERROR("Received lobby authentication request, but not hosting currently!"); } } + else if (iq.subtype() == gloox::IQ::Get) + { + const ConnectionData* cd = iq.findExtension(EXTCONNECTIONDATA); + if (cd) + { + LOGMESSAGE("XmppClient: Recieved request for connection data from %s", iq.from().username()); + if (!g_NetServer) + return true; + + bool useSTUN = g_NetServer->GetUseSTUN(); + u16 port = g_NetServer->GetSTUNport(); + std::string ip = g_NetServer->GetSTUNip(); + + glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); + ConnectionData* connectionData = new ConnectionData(); + connectionData->m_Ip = ip; + connectionData->m_port = std::to_string(port); + connectionData->m_useSTUN = useSTUN ? "true" : ""; + + response.addExtension(connectionData); + + m_client->send(response); + } + + } else if (iq.subtype() == gloox::IQ::Error) CreateGUIMessage("system", "error", std::time(nullptr), "text", iq.error_error()); else Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -108,7 +108,11 @@ * @param server IP address or host name to connect to * @return true on success, false on connection failure */ - bool SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient); + bool SetupConnection(ENetHost* enetClient); + + void SetupServerData(CStr address, u16 port, bool stun); + + bool TryToConnect(const CStr& hostJID); /** * Destroy the connection to the server. @@ -271,6 +275,9 @@ CGame *m_Game; CStrW m_UserName; CStr m_HostingPlayerName; + CStr m_ServerAddress; + u16 m_ServerPort; + bool m_UseSTUN; /// Current network session (or NULL if not connected) CNetClientSession* m_Session; Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -35,6 +35,10 @@ #include "ps/Loader.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" +#include "network/StunClient.h" + +#include "../../libraries/win32/sdl2/include/SDL/SDL_timer.h" + CNetClient *g_NetClient = NULL; @@ -74,6 +78,8 @@ m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetGeneralJSContext()), m_IsLocalClient(isLocalClient), m_LastConnectionCheck(0), + m_ServerAddress(""), + m_ServerPort(0), m_Rejoin(false) { m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it @@ -165,14 +171,89 @@ m_HostingPlayerName = hostingPlayerName; } -bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient) +bool CNetClient::SetupConnection(ENetHost* enetClient) { CNetClientSession* session = new CNetClientSession(*this); - bool ok = session->Connect(server, port, m_IsLocalClient, enetClient); + bool ok = session->Connect(m_ServerAddress, m_ServerPort, m_IsLocalClient, enetClient); SetAndOwnSession(session); return ok; } +void CNetClient::SetupServerData(CStr address, u16 port, bool stun) +{ + if (m_Session) + return; + + m_ServerAddress = address; + m_ServerPort = port; + m_UseSTUN = stun; +} + +bool CNetClient::TryToConnect(const CStr& hostJID) +{ + if (m_Session) + return false; + + if (m_ServerAddress.empty()) + { + PushGuiMessage( + "type", "netstatus", + "status", "disconnected", + "reason", static_cast(NDR_SERVER_REFUSED)); + return false; + } + + ENetHost* enetClient = nullptr; + if (g_XmppClient && m_UseSTUN) + { + // Find an unused port + for (int i = 0; i < 5 && !enetClient; ++i) + { + // Ports below 1024 are privileged on unix + u16 port = 1024 + rand() % (UINT16_MAX - 1024); + ENetAddress hostAddr{ ENET_HOST_ANY, port }; + enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0); + ++hostAddr.port; + } + + if (!enetClient) + { + PushGuiMessage( + "type", "netstatus", + "status", "disconnected", + "reason", static_cast(NDR_STUN_PORT_FAILED)); + return false; + } + + StunClient::StunEndpoint stunEndpoint; + if (!StunClient::FindStunEndpointJoin(*enetClient, stunEndpoint)) + { + PushGuiMessage( + "type", "netstatus", + "status", "disconnected", + "reason", static_cast(NDR_STUN_ENDPOINT_FAILED)); + return false; + } + + g_XmppClient->SendStunEndpointToHost(stunEndpoint, hostJID); + + SDL_Delay(1000); + + StunClient::SendHolePunchingMessages(*enetClient, m_ServerAddress, m_ServerPort); + } + + if (!g_NetClient->SetupConnection(enetClient)) + { + PushGuiMessage( + "type", "netstatus", + "status", "disconnected", + "reason", static_cast(NDR_UNKNOWN)); + return false; + } + + return true; +} + void CNetClient::SetAndOwnSession(CNetClientSession* session) { delete m_Session; Index: source/network/NetHost.h =================================================================== --- source/network/NetHost.h +++ source/network/NetHost.h @@ -73,7 +73,10 @@ NDR_LOBBY_AUTH_FAILED, NDR_GUID_FAILED, NDR_INCORRECT_READY_TURN_COMMANDS, - NDR_INCORRECT_READY_TURN_SIMULATED + NDR_INCORRECT_READY_TURN_SIMULATED, + NDR_SERVER_REFUSED, + NDR_STUN_PORT_FAILED, + NDR_STUN_ENDPOINT_FAILED }; class CNetHost Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -147,9 +147,23 @@ void SendHolePunchingMessage(const CStr& ip, u16 port); + void SetSTUN(const CStr& ip, u16 port, bool useSTUN); + + bool GetUseSTUN() const; + + CStr GetSTUNip() const; + + u16 GetSTUNport() const; + + u16 GetPort() const; + private: CNetServerWorker* m_Worker; const bool m_LobbyAuth; + bool m_UseSTUN; + u16 m_Port; + u16 m_PortSTUN; + CStr m_IpSTUN; }; /** Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -1581,7 +1581,7 @@ CNetServer::CNetServer(bool useLobbyAuth, int autostartPlayers) : m_Worker(new CNetServerWorker(useLobbyAuth, autostartPlayers)), - m_LobbyAuth(useLobbyAuth) + m_LobbyAuth(useLobbyAuth), m_UseSTUN(false), m_Port(256) { } @@ -1590,6 +1590,11 @@ delete m_Worker; } +bool CNetServer::GetUseSTUN() const +{ + return m_UseSTUN; +} + bool CNetServer::UseLobbyAuth() const { return m_LobbyAuth; @@ -1597,9 +1602,32 @@ bool CNetServer::SetupConnection(const u16 port) { + m_Port = port; return m_Worker->SetupConnection(port); } +u16 CNetServer::GetPort() const +{ + return m_Port; +} + +u16 CNetServer::GetSTUNport() const +{ + return m_PortSTUN; +} + +CStr CNetServer::GetSTUNip() const +{ + return m_IpSTUN; +} + +void CNetServer::SetSTUN(const CStr& ip, const u16 port, bool useSTUN) +{ + m_IpSTUN = ip; + m_PortSTUN = port; + m_UseSTUN = useSTUN; +} + void CNetServer::StartGame() { std::lock_guard lock(m_Worker->m_WorkerMutex); Index: source/network/StunClient.h =================================================================== --- source/network/StunClient.h +++ source/network/StunClient.h @@ -41,6 +41,7 @@ void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort); +bool GetPublicIp(CStr& ip, u16 port); } #endif // STUNCLIENT_H Index: source/network/StunClient.cpp =================================================================== --- source/network/StunClient.cpp +++ source/network/StunClient.cpp @@ -361,14 +361,42 @@ bool STUNRequestAndResponse(ENetHost& transactionHost) { - if (!CreateStunRequest(transactionHost)) + if (!CreateStunRequest(transactionHost)) { + LOGWARNING("failed to create stun request"); return false; + } std::vector buffer; return ReceiveStunResponse(transactionHost, buffer) && ParseStunResponse(buffer); } +bool StunClient::GetPublicIp(CStr& ip, u16 port) +{ + ENetAddress hostAddr{ ENET_HOST_ANY, static_cast(port) }; + ENetHost* transactionHost = enet_host_create(&hostAddr, 1, 1, 0, 0); + if (!transactionHost) + { + LOGERROR("FindStunEndpointHost: Failed to create enet host"); + return false; + } + + bool success = STUNRequestAndResponse(*transactionHost); + enet_host_destroy(transactionHost); + if (!success) + { + LOGERROR("FinfStunEndpointHost: Failed to get Respond"); + return false; + } + // Convert m_IP to string + char ipStr[256] = "(error)"; + ENetAddress addr; + addr.host = ntohl(m_IP); + enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr)); + ip = ipStr; + return strcmp(ipStr, "(error)") != 0; +} + JS::Value StunClient::FindStunEndpointHost(const ScriptInterface& scriptInterface, int port) { ENetAddress hostAddr{ENET_HOST_ANY, static_cast(port)}; @@ -382,7 +410,10 @@ bool success = STUNRequestAndResponse(*transactionHost); enet_host_destroy(transactionHost); if (!success) + { + LOGERROR("FinfStunEndpointHost: Failed to get Respond"); return JS::UndefinedValue(); + } // Convert m_IP to string char ipStr[256] = "(error)"; Index: source/network/scripting/JSInterface_Network.h =================================================================== --- source/network/scripting/JSInterface_Network.h +++ source/network/scripting/JSInterface_Network.h @@ -29,8 +29,9 @@ bool HasNetClient(ScriptInterface::CmptPrivate* pCmptPrivate); void StartNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate); void SetNetworkGameAttributes(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue attribs1); - void StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName); + void StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& stunIp, const u16 stunPort); void StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID); + void StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID); JS::Value FindStunEndpoint(ScriptInterface::CmptPrivate* pCmptPrivate, int port); void DisconnectNetworkGame(ScriptInterface::CmptPrivate* pCmptPrivate); JS::Value PollNetworkClient(ScriptInterface::CmptPrivate* pCmptPrivate); Index: source/network/scripting/JSInterface_Network.cpp =================================================================== --- source/network/scripting/JSInterface_Network.cpp +++ source/network/scripting/JSInterface_Network.cpp @@ -51,14 +51,33 @@ return StunClient::FindStunEndpointHost(*(pCmptPrivate->pScriptInterface), port); } -void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName) +void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN,const CStr& stunIp, const u16 stunPort) { ENSURE(!g_NetClient); ENSURE(!g_NetServer); ENSURE(!g_Game); // 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); + bool hasLobby = !!g_XmppClient; + g_NetServer = new CNetServer(hasLobby); + // We need to get public ip + if (hasLobby && !useSTUN) + { + CStr ip; + if (!StunClient::GetPublicIp(ip, serverPort)) + { + ScriptRequest rq(pCmptPrivate->pScriptInterface); + ScriptException::Raise(rq, "Failed to get public ip."); + SAFE_DELETE(g_NetServer); + return; + } + g_NetServer->SetSTUN(ip, serverPort, false); + } + else + { + g_NetServer->SetSTUN(stunIp, stunPort, useSTUN); + } + if (!g_NetServer->SetupConnection(serverPort)) { ScriptRequest rq(pCmptPrivate->pScriptInterface); @@ -71,8 +90,9 @@ g_NetClient = new CNetClient(g_Game, true); g_NetClient->SetUserName(playerName); g_NetClient->SetHostingPlayerName(hostLobbyName); + g_NetClient->SetupServerData("127.0.0.1", serverPort, false); - if (!g_NetClient->SetupConnection("127.0.0.1", serverPort, nullptr)) + if (!g_NetClient->SetupConnection(nullptr)) { ScriptRequest rq(pCmptPrivate->pScriptInterface); ScriptException::Raise(rq, "Failed to connect to server"); @@ -124,11 +144,12 @@ g_NetClient = new CNetClient(g_Game, false); g_NetClient->SetUserName(playerName); g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); + g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); if (g_XmppClient && useSTUN) StunClient::SendHolePunchingMessages(*enetClient, serverAddress, serverPort); - if (!g_NetClient->SetupConnection(serverAddress, serverPort, enetClient)) + if (!g_NetClient->SetupConnection(enetClient)) { ScriptRequest rq(pCmptPrivate->pScriptInterface); ScriptException::Raise(rq, "Failed to connect to server"); @@ -137,6 +158,20 @@ } } +/*** +* Require XmppClient to communicate with game server. +*/ +void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& hostJID) +{ + ENSURE(!!g_XmppClient); + + g_Game = new CGame(true); + g_NetClient = new CNetClient(g_Game, false); + g_NetClient->SetUserName(playerName); + g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); + g_XmppClient->SendIqGetConnectionData(hostJID); +} + void JSI_Network::DisconnectNetworkGame(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { // TODO: we ought to do async reliable disconnections @@ -232,8 +267,9 @@ scriptInterface.RegisterFunction("HasNetServer"); scriptInterface.RegisterFunction("HasNetClient"); scriptInterface.RegisterFunction("FindStunEndpoint"); - scriptInterface.RegisterFunction("StartNetworkHost"); + scriptInterface.RegisterFunction("StartNetworkHost"); scriptInterface.RegisterFunction("StartNetworkJoin"); + scriptInterface.RegisterFunction("StartNetworkJoinLobby"); scriptInterface.RegisterFunction("DisconnectNetworkGame"); scriptInterface.RegisterFunction("GetPlayerGUID"); scriptInterface.RegisterFunction("PollNetworkClient"); Index: source/network/tests/test_Net.h =================================================================== --- source/network/tests/test_Net.h +++ source/network/tests/test_Net.h @@ -75,7 +75,10 @@ { TS_ASSERT(server.SetupConnection(PS_DEFAULT_PORT)); for (size_t j = 0; j < clients.size(); ++j) - TS_ASSERT(clients[j]->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr)); + { + clients[j]->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false); + TS_ASSERT(clients[j]->SetupConnection(nullptr)); + } for (size_t i = 0; ; ++i) { @@ -304,7 +307,8 @@ client2B.SetUserName(L"bob"); clients.push_back(&client2B); - TS_ASSERT(client2B.SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr)); + client2B.SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false); + TS_ASSERT(client2B.SetupConnection(nullptr)); for (size_t i = 0; ; ++i) { Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -1558,7 +1558,8 @@ g_NetClient = new CNetClient(g_Game, true); g_NetClient->SetUserName(userName); - g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT, nullptr); + g_NetClient->SetupServerData("127.0.0.1", PS_DEFAULT_PORT, false); + g_NetClient->SetupConnection(nullptr); } else if (args.Has("autostart-client")) { @@ -1571,7 +1572,8 @@ if (ip.empty()) ip = "127.0.0.1"; - bool ok = g_NetClient->SetupConnection(ip, PS_DEFAULT_PORT, nullptr); + g_NetClient->SetupServerData(ip, PS_DEFAULT_PORT, false); + bool ok = g_NetClient->SetupConnection(nullptr); ENSURE(ok); } else