Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controllers/LobbyGameRegistration.js @@ -90,6 +90,7 @@ let stanza = { "name": this.serverName, "hostUsername": Engine.LobbyGetNick(), + "hostJID": "", // Overwritten by C++, placeholder. "mapName": g_GameSettings.map.map, // TODO: if the map name was always up-to-date we wouldn't need the mapcache here. "niceMapName": this.mapCache.getTranslatableMapName(g_GameSettings.map.type, g_GameSettings.map.map), Index: ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js +++ ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js @@ -112,7 +112,7 @@ let joinServer = Engine.GetGUIObjectByName("joinServer").caption; let joinPort = Engine.GetGUIObjectByName("joinPort").caption; - if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort), false, "")) + if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort))) switchSetupPage("pageConnecting"); } else if (!Engine.GetGUIObjectByName("pageHost").hidden) @@ -365,7 +365,7 @@ try { - Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useSTUN, password); + Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, useSTUN, password); } catch (e) { @@ -388,13 +388,13 @@ } /** - * Connects via STUN if the hostJID is given. + * Connect via direct IP (used by the 'simple' MP screen) */ -function startJoin(playername, ip, port, useSTUN, hostJID) +function startJoin(playername, ip, port) { try { - Engine.StartNetworkJoin(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), ip, port, useSTUN, hostJID); + Engine.StartNetworkJoin(playername, ip, port); } catch (e) { @@ -409,18 +409,20 @@ startConnectionStatus("client"); + // Future-proofing: there could be an XMPP client even if we join a game directly. if (Engine.HasXmppClient()) Engine.LobbySetPlayerPresence("playing"); - else - { - // Only save the player name and host address if they're valid and we're not in the lobby - Engine.ConfigDB_CreateAndWriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg"); - Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerserver", ip, "config/user.cfg"); - Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerjoining.port", port, "config/user.cfg"); - } + + // Only save the player name and host address if they're valid. + Engine.ConfigDB_CreateAndWriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg"); + Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerserver", ip, "config/user.cfg"); + Engine.ConfigDB_CreateAndWriteValueToFile("user", "multiplayerjoining.port", port, "config/user.cfg"); return true; } +/** + * Connect via the lobby. + */ function startJoinFromLobby(playername, hostJID, password) { if (!Engine.HasXmppClient()) Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/JoinButton.js @@ -73,7 +73,7 @@ "name": g_Nickname, "rating": this.getRejoinRating(stanza), "hasPassword": !!stanza.hasPassword, - "hostJID": stanza.hostUsername + "@" + Engine.ConfigDB_GetValue("user", "lobby.server") + "/0ad" + "hostJID": stanza.hostJID }); } Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js @@ -282,6 +282,7 @@ "name", "hasPassword", "hostUsername", + "hostJID", "state", "nbp", "maxnbp", Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js +++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js @@ -98,13 +98,13 @@ let newGames = {}; for (let stanza of gameListData) { - let game = this.games[stanza.hostUsername] || undefined; + let game = this.games[stanza.hostJID] || undefined; let exists = !!game; if (!exists) game = new Game(this.mapCache); game.update(stanza, selectedColumn); - newGames[stanza.hostUsername] = game; + newGames[stanza.hostJID] = game; } this.games = newGames; Engine.ProfileStop(); @@ -160,7 +160,7 @@ this.list_maxnbp[i] = displayData.playerCount; this.list_gameRating[i] = game.gameRating; this.list[i] = ""; - if (selectedGame && game.stanza.hostUsername == selectedGame.stanza.hostUsername && game.stanza.name == selectedGame.stanza.name) + if (selectedGame && game.stanza.hostJID == selectedGame.stanza.hostJID && game.stanza.name == selectedGame.stanza.name) selectedGameIndex = i; }); Engine.ProfileStop(); Index: ps/trunk/source/lobby/IXmppClient.h =================================================================== --- ps/trunk/source/lobby/IXmppClient.h +++ ps/trunk/source/lobby/IXmppClient.h @@ -44,7 +44,8 @@ virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0; virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0; virtual void SetNick(const std::string& nick) = 0; - virtual std::string GetNick() = 0; + virtual std::string GetNick() const = 0; + virtual std::string GetJID() const = 0; virtual void kick(const std::string& nick, const std::string& reason) = 0; virtual void ban(const std::string& nick, const std::string& reason) = 0; virtual void SetPresence(const std::string& presence) = 0; Index: ps/trunk/source/lobby/XmppClient.h =================================================================== --- ps/trunk/source/lobby/XmppClient.h +++ ps/trunk/source/lobby/XmppClient.h @@ -91,7 +91,8 @@ void SendIqChangeStateGame(const std::string& nbp, const std::string& players); void SendIqLobbyAuth(const std::string& to, const std::string& token); void SetNick(const std::string& nick); - std::string GetNick(); + std::string GetNick() const; + std::string GetJID() const; void kick(const std::string& nick, const std::string& reason); void ban(const std::string& nick, const std::string& reason); void SetPresence(const std::string& presence); Index: ps/trunk/source/lobby/XmppClient.cpp =================================================================== --- ps/trunk/source/lobby/XmppClient.cpp +++ ps/trunk/source/lobby/XmppClient.cpp @@ -32,6 +32,7 @@ #include "network/StunClient.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" +#include "ps/GUID.h" #include "ps/Pyrogenesis.h" #include "scriptinterface/ScriptExtraHeaders.h" // StructuredClone #include "scriptinterface/ScriptInterface.h" @@ -109,7 +110,8 @@ m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC"; m_echelonId = sEchelon + "@" + m_server + "/CC"; - glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad"); + // Generate a unique, unpredictable resource to allow multiple 0 A.D. instances to connect to the lobby. + glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad-" + ps_generate_guid()); glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick); // If we are connecting, use the full jid and a password @@ -425,7 +427,7 @@ glooxwrapper::JID xpartamuppJid(m_xpartamuppId); // Setup some base stanza attributes - GameListQuery* g = new GameListQuery(); + std::unique_ptr g = std::make_unique(); g->m_Command = "register"; glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game"); @@ -434,17 +436,27 @@ scriptInterface.EnumeratePropertyNames(data, true, properties); for (const std::string& p : properties) { - std::wstring value; - scriptInterface.GetProperty(data, p.c_str(), value); - game->addAttribute(p, utf8_from_wstring(value)); + std::string value; + if (!scriptInterface.GetProperty(data, p.c_str(), value)) + { + LOGERROR("Could not parse attribute '%s' as string.", p); + return; + } + game->addAttribute(p, value); } + // Overwrite some attributes to make it slightly less trivial to do bad things, + // and explicit some invariants. + + // The JID must point to ourself. + game->addAttribute("hostJID", GetJID()); + // Push the stanza onto the IQ g->m_GameList.emplace_back(game); // Send IQ glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID()); - iq.addExtension(g); + iq.addExtension(g.release()); DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]"); m_client->send(iq); } @@ -504,7 +516,7 @@ LobbyAuth* auth = new LobbyAuth(); auth->m_Token = token; - glooxwrapper::JID clientJid(to + "@" + m_server + "/0ad"); + glooxwrapper::JID clientJid(to); glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID()); iq.addExtension(auth); DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]"); @@ -595,7 +607,7 @@ ScriptInterface::CreateArray(rq, &ret); int j = 0; - const char* stats[] = { "name", "hostUsername", "state", "hasPassword", + const char* stats[] = { "name", "hostUsername", "hostJID", "state", "hasPassword", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryConditions", "startTime", "mods" }; @@ -850,7 +862,7 @@ } if (!m_connectionDataIqId.empty() && m_connectionDataIqId.compare(iq.id().to_string()) != 0) { - LOGWARNING("XmppClient: Received connection data with invalid id"); + LOGMESSAGE("XmppClient: Received connection data with invalid id"); return true; } @@ -943,7 +955,7 @@ if (g_NetServer) g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string()); else - LOGERROR("Received lobby authentication request, but not hosting currently!"); + LOGMESSAGE("Received lobby authentication request, but not hosting currently!"); } } else if (iq.subtype() == gloox::IQ::Get) @@ -1164,11 +1176,16 @@ /** * Get current nickname. */ -std::string XmppClient::GetNick() +std::string XmppClient::GetNick() const { return m_mucRoom->nick().to_string(); } +std::string XmppClient::GetJID() const +{ + return m_client->getJID().to_string(); +} + /** * Kick a player from the current room. * Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h +++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h @@ -414,6 +414,7 @@ bool connect(bool block = true); gloox::ConnectionError recv(int timeout = -1); const string getID() const; + const string getJID() const; void send(const IQ& iq); void setTls(gloox::TLSPolicy tls); Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp =================================================================== --- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp +++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -354,6 +354,11 @@ return m_Wrapped->getID(); } +const glooxwrapper::string glooxwrapper::Client::getJID() const +{ + return m_Wrapped->jid().full(); +} + void glooxwrapper::Client::send(const IQ& iq) { m_Wrapped->send(iq.getWrapped()); Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp =================================================================== --- ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp +++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -218,6 +218,7 @@ REGISTER_XMPP(SetPresence, "LobbySetPlayerPresence"); REGISTER_XMPP(SetNick, "LobbySetNick"); REGISTER_XMPP(GetNick, "LobbyGetNick"); + REGISTER_XMPP(GetJID, "LobbyGetJID"); REGISTER_XMPP(kick, "LobbyKick"); REGISTER_XMPP(ban, "LobbyBan"); REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence"); Index: ps/trunk/source/network/NetClient.h =================================================================== --- ps/trunk/source/network/NetClient.h +++ ps/trunk/source/network/NetClient.h @@ -92,10 +92,10 @@ void SetUserName(const CStrW& username); /** - * Set the name of the hosting player. + * Store the JID of the host. * This is needed for the secure lobby authentication. */ - void SetHostingPlayerName(const CStr& hostingPlayerName); + void SetHostJID(const CStr& jid); void SetControllerSecret(const std::string& secret); @@ -295,7 +295,8 @@ CGame *m_Game; CStrW m_UserName; - CStr m_HostingPlayerName; + + CStr m_HostJID; CStr m_ServerAddress; u16 m_ServerPort; bool m_UseSTUN; Index: ps/trunk/source/network/NetClient.cpp =================================================================== --- ps/trunk/source/network/NetClient.cpp +++ ps/trunk/source/network/NetClient.cpp @@ -176,9 +176,9 @@ m_UserName = username; } -void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName) +void CNetClient::SetHostJID(const CStr& jid) { - m_HostingPlayerName = hostingPlayerName; + m_HostJID = jid; } void CNetClient::SetGamePassword(const CStr& hashedPassword) @@ -632,8 +632,8 @@ if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH) { - if (g_XmppClient && !client->m_HostingPlayerName.empty()) - g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID); + if (g_XmppClient && !client->m_HostJID.empty()) + g_XmppClient->SendIqLobbyAuth(client->m_HostJID, client->m_GUID); else { client->PushGuiMessage( 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 @@ -87,7 +87,8 @@ return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase(); } -void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password) + +void StartNetworkHost(const ScriptRequest& rq, const CStrW& playerName, const u16 serverPort, bool useSTUN, const CStr& password) { ENSURE(!g_NetClient); ENSURE(!g_NetServer); @@ -138,7 +139,8 @@ g_Game = new CGame(true); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); - g_NetClient->SetHostingPlayerName(hostLobbyName); + if (hasLobby) + g_NetClient->SetHostJID(g_XmppClient->GetJID()); g_NetClient->SetGamePassword(hashedPass); g_NetClient->SetupServerData("127.0.0.1", serverPort, false); g_NetClient->SetControllerSecret(secret); @@ -151,7 +153,7 @@ } } -void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID) +void StartNetworkJoin(const ScriptRequest& rq, const CStrW& playerName, const CStr& serverAddress, u16 serverPort) { ENSURE(!g_NetClient); ENSURE(!g_NetServer); @@ -160,8 +162,7 @@ g_Game = new CGame(true); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); - g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); - g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); + g_NetClient->SetupServerData(serverAddress, serverPort, false); if (!g_NetClient->SetupConnection(nullptr)) { @@ -187,7 +188,7 @@ g_Game = new CGame(true); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); - g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); + g_NetClient->SetHostJID(hostJID); g_NetClient->SetGamePassword(hashedPass); g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str()); }