Index: ps/trunk/source/lobby/IXmppClient.h =================================================================== --- ps/trunk/source/lobby/IXmppClient.h (revision 22890) +++ ps/trunk/source/lobby/IXmppClient.h (revision 22891) @@ -1,69 +1,69 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef IXMPPCLIENT_H #define IXMPPCLIENT_H #include "scriptinterface/ScriptTypes.h" class ScriptInterface; namespace StunClient { struct StunEndpoint; } class IXmppClient { public: static IXmppClient* create(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, bool regOpt = false); virtual ~IXmppClient() {} virtual void connect() = 0; virtual void disconnect() = 0; virtual bool isConnected() = 0; virtual void recv() = 0; virtual void SendIqGetBoardList() = 0; 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 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; virtual void SetNick(const std::string& nick) = 0; virtual void GetNick(std::string& nick) = 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; - virtual void GetPresence(const std::string& nickname, std::string& presence) = 0; - virtual void GetRole(const std::string& nickname, std::string& role) = 0; - virtual void GetSubject(std::string& subject) = 0; + virtual const char* GetPresence(const std::string& nickname) = 0; + virtual const char* GetRole(const std::string& nickname) = 0; + virtual const std::wstring& GetSubject() = 0; virtual void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0; virtual void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0; virtual void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0; virtual void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0; virtual JS::Value GuiPollNewMessage(const ScriptInterface& scriptInterface) = 0; virtual JS::Value GuiPollHistoricMessages(const ScriptInterface& scriptInterface) = 0; virtual bool GuiPollHasPlayerListUpdate() = 0; virtual void SendMUCMessage(const std::string& message) = 0; virtual void SendStunEndpointToHost(const StunClient::StunEndpoint& stunEndpoint, const std::string& hostJID) = 0; }; extern IXmppClient *g_XmppClient; extern bool g_rankedGame; #endif // XMPPCLIENT_H Index: ps/trunk/source/lobby/XmppClient.cpp =================================================================== --- ps/trunk/source/lobby/XmppClient.cpp (revision 22890) +++ ps/trunk/source/lobby/XmppClient.cpp (revision 22891) @@ -1,1287 +1,1334 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "XmppClient.h" #include "StanzaExtensions.h" #ifdef WIN32 # include #endif #include "i18n/L10n.h" +#include "lobby/scripting/GlooxScriptConversions.cpp" #include "lib/external_libraries/enet.h" #include "lib/utf8.h" #include "network/NetServer.h" #include "network/StunClient.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Pyrogenesis.h" #include "scriptinterface/ScriptInterface.h" #include //debug #if 1 #define DbgXMPP(x) #else #define DbgXMPP(x) std::cout << x << std::endl; static std::string tag_xml(const glooxwrapper::IQ& iq) { std::string ret; glooxwrapper::Tag* tag = iq.tag(); ret = tag->xml().to_string(); glooxwrapper::Tag::free(tag); return ret; } #endif static std::string tag_name(const glooxwrapper::IQ& iq) { std::string ret; glooxwrapper::Tag* tag = iq.tag(); ret = tag->name().to_string(); glooxwrapper::Tag::free(tag); return ret; } IXmppClient* IXmppClient::create(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt) { return new XmppClient(scriptInterface, sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt); } /** * Construct the XMPP client. * * @param scriptInterface - ScriptInterface to be used for storing GUI messages. * Can be left blank for non-visual applications. * @param sUsername Username to login with of register. * @param sPassword Password to login with or register. * @param sRoom MUC room to join. * @param sNick Nick to join with. * @param historyRequestSize Number of stanzas of room history to request. * @param regOpt If we are just registering or not. */ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt) : m_ScriptInterface(scriptInterface), m_client(nullptr), m_mucRoom(nullptr), m_registration(nullptr), m_username(sUsername), m_password(sPassword), m_room(sRoom), m_nick(sNick), m_initialLoadComplete(false), m_isConnected(false), m_sessionManager(nullptr), m_certStatus(gloox::CertStatus::CertOk), m_PlayerMapUpdate(false) { if (m_ScriptInterface) JS_AddExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), XmppClient::Trace, this); // Read lobby configuration from default.cfg std::string sXpartamupp; std::string sEchelon; CFG_GET_VAL("lobby.server", m_server); CFG_GET_VAL("lobby.xpartamupp", sXpartamupp); CFG_GET_VAL("lobby.echelon", sEchelon); m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC"; m_echelonId = sEchelon + "@" + m_server + "/CC"; glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad"); glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick); // If we are connecting, use the full jid and a password // If we are registering, only use the server name if (!regOpt) m_client = new glooxwrapper::Client(clientJid, sPassword); else m_client = new glooxwrapper::Client(m_server); // Optionally join without a TLS certificate, so a local server can be tested quickly. // Security risks from malicious JS mods can be mitigated if this option and also the hostname and login are shielded from JS access. bool tls = true; CFG_GET_VAL("lobby.tls", tls); m_client->setTls(tls ? gloox::TLSRequired : gloox::TLSDisabled); // Disable use of the SASL PLAIN mechanism, to prevent leaking credentials // if the server doesn't list any supported SASL mechanism or the response // has been modified to exclude those. const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain; m_client->setSASLMechanisms(mechs); m_client->registerConnectionListener(this); m_client->setPresence(gloox::Presence::Available, -1); m_client->disco()->setVersion("Pyrogenesis", engine_version); m_client->disco()->setIdentity("client", "bot"); m_client->setCompression(false); m_client->registerStanzaExtension(new GameListQuery()); m_client->registerIqHandler(this, EXTGAMELISTQUERY); m_client->registerStanzaExtension(new BoardListQuery()); m_client->registerIqHandler(this, EXTBOARDLISTQUERY); m_client->registerStanzaExtension(new ProfileQuery()); m_client->registerIqHandler(this, EXTPROFILEQUERY); m_client->registerStanzaExtension(new LobbyAuth()); m_client->registerIqHandler(this, EXTLOBBYAUTH); m_client->registerMessageHandler(this); // Uncomment to see the raw stanzas //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this ); if (!regOpt) { // Create a Multi User Chat Room m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0); // Get room history. m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas); } else { // Registration m_registration = new glooxwrapper::Registration(m_client); m_registration->registerRegistrationHandler(this); } m_sessionManager = new glooxwrapper::SessionManager(m_client, this); // Register plugins to allow gloox parse them in incoming sessions m_sessionManager->registerPlugins(); } /** * Destroy the xmpp client */ XmppClient::~XmppClient() { DbgXMPP("XmppClient destroyed"); delete m_registration; delete m_mucRoom; delete m_sessionManager; // Workaround for memory leak in gloox 1.0/1.0.1 m_client->removePresenceExtension(gloox::ExtCaps); delete m_client; for (const glooxwrapper::Tag* const& t : m_GameList) glooxwrapper::Tag::free(t); for (const glooxwrapper::Tag* const& t : m_BoardList) glooxwrapper::Tag::free(t); for (const glooxwrapper::Tag* const& t : m_Profile) glooxwrapper::Tag::free(t); if (m_ScriptInterface) JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), XmppClient::Trace, this); } void XmppClient::TraceMember(JSTracer* trc) { for (JS::Heap& guiMessage : m_GuiMessageQueue) JS_CallValueTracer(trc, &guiMessage, "m_GuiMessageQueue"); for (JS::Heap& guiMessage : m_HistoricGuiMessages) JS_CallValueTracer(trc, &guiMessage, "m_HistoricGuiMessages"); } /// Network void XmppClient::connect() { m_initialLoadComplete = false; m_client->connect(false); } void XmppClient::disconnect() { m_client->disconnect(); } bool XmppClient::isConnected() { return m_isConnected; } void XmppClient::recv() { m_client->recv(1); } /** * Log (debug) Handler */ void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) { std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl; } /***************************************************** * Connection handlers * *****************************************************/ /** * Handle connection */ void XmppClient::onConnect() { if (m_mucRoom) { m_isConnected = true; CreateGUIMessage("system", "connected", std::time(nullptr)); m_mucRoom->join(); } if (m_registration) m_registration->fetchRegistrationFields(); } /** * Handle disconnection */ void XmppClient::onDisconnect(gloox::ConnectionError error) { // Make sure we properly leave the room so that // everything works if we decide to come back later if (m_mucRoom) m_mucRoom->leave(); // Clear game, board and player lists. for (const glooxwrapper::Tag* const& t : m_GameList) glooxwrapper::Tag::free(t); for (const glooxwrapper::Tag* const& t : m_BoardList) glooxwrapper::Tag::free(t); for (const glooxwrapper::Tag* const& t : m_Profile) glooxwrapper::Tag::free(t); m_BoardList.clear(); m_GameList.clear(); m_PlayerMap.clear(); m_PlayerMapUpdate = true; m_Profile.clear(); m_HistoricGuiMessages.clear(); m_isConnected = false; CreateGUIMessage( "system", "disconnected", std::time(nullptr), - "reason", ConnectionErrorToString(error), - "certificate_status", CertificateErrorToString(m_certStatus)); + "reason", error, + "certificate_status", m_certStatus); } /** * Handle TLS connection. */ bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info) { DbgXMPP("onTLSConnect"); DbgXMPP( "status: " << info.status << "\nissuer: " << info.issuer << "\npeer: " << info.server << "\nprotocol: " << info.protocol << "\nmac: " << info.mac << "\ncipher: " << info.cipher << "\ncompression: " << info.compression ); m_certStatus = static_cast(info.status); // Optionally accept invalid certificates, see require_tls option. bool verify_certificate = true; CFG_GET_VAL("lobby.verify_certificate", verify_certificate); return info.status == gloox::CertOk || !verify_certificate; } /** * Handle MUC room errors */ void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err) { - CreateGUIMessage("system", "error", std::time(nullptr), "text", StanzaErrorToString(err)); + CreateGUIMessage("system", "error", std::time(nullptr), "text", err); } /***************************************************** * Requests to server * *****************************************************/ /** * Request the leaderboard data from the server. */ void XmppClient::SendIqGetBoardList() { glooxwrapper::JID echelonJid(m_echelonId); // Send IQ BoardListQuery* b = new BoardListQuery(); b->m_Command = "getleaderboard"; glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID()); iq.addExtension(b); DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]"); m_client->send(iq); } /** * Request the profile data from the server. */ void XmppClient::SendIqGetProfile(const std::string& player) { glooxwrapper::JID echelonJid(m_echelonId); // Send IQ ProfileQuery* b = new ProfileQuery(); b->m_Command = player; glooxwrapper::IQ iq(gloox::IQ::Get, echelonJid, m_client->getID()); iq.addExtension(b); DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]"); m_client->send(iq); } /** * Send game report containing numerous game properties to the server. * * @param data A JS array of game statistics */ void XmppClient::SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data) { glooxwrapper::JID echelonJid(m_echelonId); // Setup some base stanza attributes GameReport* game = new GameReport(); glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game"); // Iterate through all the properties reported and add them to the stanza. std::vector properties; scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties); for (const std::string& p : properties) { std::wstring value; scriptInterface.GetProperty(data, p.c_str(), value); report->addAttribute(p, utf8_from_wstring(value)); } // Add stanza to IQ game->m_GameReport.emplace_back(report); // Send IQ glooxwrapper::IQ iq(gloox::IQ::Set, echelonJid, m_client->getID()); iq.addExtension(game); DbgXMPP("SendGameReport [" << tag_xml(iq) << "]"); m_client->send(iq); }; /** * Send a request to register a game to the server. * * @param data A JS array of game attributes */ void XmppClient::SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) { glooxwrapper::JID xpartamuppJid(m_xpartamuppId); // Setup some base stanza attributes GameListQuery* g = new GameListQuery(); g->m_Command = "register"; glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game"); // Add a fake ip which will be overwritten by the ip stamp XMPP module on the server. game->addAttribute("ip", "fake"); // Iterate through all the properties reported and add them to the stanza. std::vector properties; scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties); for (const std::string& p : properties) { std::wstring value; scriptInterface.GetProperty(data, p.c_str(), value); game->addAttribute(p, utf8_from_wstring(value)); } // 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); DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]"); m_client->send(iq); } /** * Send a request to unregister a game to the server. */ void XmppClient::SendIqUnregisterGame() { glooxwrapper::JID xpartamuppJid(m_xpartamuppId); // Send IQ GameListQuery* g = new GameListQuery(); g->m_Command = "unregister"; g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game")); glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID()); iq.addExtension(g); DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]"); m_client->send(iq); } /** * Send a request to change the state of a registered game on the server. * * A game can either be in the 'running' or 'waiting' state - the server * decides which - but we need to update the current players that are * in-game so the server can make the calculation. */ void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players) { glooxwrapper::JID xpartamuppJid(m_xpartamuppId); // Send IQ GameListQuery* g = new GameListQuery(); g->m_Command = "changestate"; glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game"); game->addAttribute("nbp", nbp); game->addAttribute("players", players); g->m_GameList.emplace_back(game); glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID()); iq.addExtension(g); DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]"); m_client->send(iq); } /***************************************************** * iq to clients * *****************************************************/ /** * Send lobby authentication token. */ void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token) { LobbyAuth* auth = new LobbyAuth(); auth->m_Token = token; glooxwrapper::JID clientJid(to + "@" + m_server + "/0ad"); glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID()); iq.addExtension(auth); DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]"); m_client->send(iq); } /***************************************************** * Account registration * *****************************************************/ void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string) { glooxwrapper::RegistrationFields vals; vals.username = m_username; vals.password = m_password; m_registration->createAccount(fields, vals); } void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result) { if (result == gloox::RegistrationSuccess) CreateGUIMessage("system", "registered", std::time(nullptr)); else - CreateGUIMessage("system", "error", std::time(nullptr), "text", RegistrationResultToString(result)); + CreateGUIMessage("system", "error", std::time(nullptr), "text", result); disconnect(); } void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&) { DbgXMPP("the account already exists"); } void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&) { DbgXMPP("dataForm received"); } void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&) { DbgXMPP("OOB registration requested"); } /***************************************************** * Requests from GUI * *****************************************************/ /** * Handle requests from the GUI for the list of players. * * @return A JS array containing all known players and their presences */ void XmppClient::GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.CreateArray(ret); int j = 0; - for (const std::pair >& p : m_PlayerMap) + for (const std::pair& p : m_PlayerMap) { JS::RootedValue player(cx); scriptInterface.CreateObject( &player, - "name", wstring_from_utf8(p.first), - "presence", wstring_from_utf8(p.second[0]), - "rating", wstring_from_utf8(p.second[1]), - "role", wstring_from_utf8(p.second[2])); + "name", p.first, + "presence", p.second.m_Presence, + "rating", p.second.m_Rating, + "role", p.second.m_Role); scriptInterface.SetPropertyInt(ret, j++, player); } } /** * Handle requests from the GUI for the list of all active games. * * @return A JS array containing all known games */ void XmppClient::GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.CreateArray(ret); int j = 0; const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition", "startTime", "mods" }; for(const glooxwrapper::Tag* const& t : m_GameList) { JS::RootedValue game(cx); scriptInterface.CreateObject(&game); for (size_t i = 0; i < ARRAY_SIZE(stats); ++i) - scriptInterface.SetProperty(game, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string())); + scriptInterface.SetProperty(game, stats[i], t->findAttribute(stats[i])); scriptInterface.SetPropertyInt(ret, j++, game); } } /** * Handle requests from the GUI for leaderboard data. * * @return A JS array containing all known leaderboard data */ void XmppClient::GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.CreateArray(ret); int j = 0; const char* attributes[] = { "name", "rank", "rating" }; for(const glooxwrapper::Tag* const& t : m_BoardList) { JS::RootedValue board(cx); scriptInterface.CreateObject(&board); for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i) - scriptInterface.SetProperty(board, attributes[i], wstring_from_utf8(t->findAttribute(attributes[i]).to_string())); + scriptInterface.SetProperty(board, attributes[i], t->findAttribute(attributes[i])); scriptInterface.SetPropertyInt(ret, j++, board); } } /** * Handle requests from the GUI for profile data. * * @return A JS array containing the specific user's profile data */ void XmppClient::GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.CreateArray(ret); int j = 0; const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" }; for (const glooxwrapper::Tag* const& t : m_Profile) { JS::RootedValue profile(cx); scriptInterface.CreateObject(&profile); for (size_t i = 0; i < ARRAY_SIZE(stats); ++i) - scriptInterface.SetProperty(profile, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string())); + scriptInterface.SetProperty(profile, stats[i], t->findAttribute(stats[i])); scriptInterface.SetPropertyInt(ret, j++, profile); } } /***************************************************** * Message interfaces * *****************************************************/ void SetGUIMessageProperty(JSContext* UNUSED(cx), JS::HandleObject UNUSED(messageObj)) { } template void SetGUIMessageProperty(JSContext* cx, JS::HandleObject messageObj, const std::string& propertyName, const T& propertyValue, Args const&... args) { // JSAutoRequest is the responsibility of the caller JS::RootedValue scriptPropertyValue(cx); ScriptInterface::AssignOrToJSVal(cx, &scriptPropertyValue, propertyValue); JS_DefineProperty(cx, messageObj, propertyName.c_str(), scriptPropertyValue, JSPROP_ENUMERATE); SetGUIMessageProperty(cx, messageObj, args...); } template void XmppClient::CreateGUIMessage( const std::string& type, const std::string& level, const std::time_t time, Args const&... args) { if (!m_ScriptInterface) return; JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue message(cx); m_ScriptInterface->CreateObject( &message, "type", type, "level", level, "historic", false, "time", static_cast(time)); JS::RootedObject messageObj(cx, message.toObjectOrNull()); SetGUIMessageProperty(cx, messageObj, args...); m_ScriptInterface->FreezeObject(message, true); m_GuiMessageQueue.push_back(JS::Heap(message)); } bool XmppClient::GuiPollHasPlayerListUpdate() { bool hasUpdate = m_PlayerMapUpdate; m_PlayerMapUpdate = false; return hasUpdate; } JS::Value XmppClient::GuiPollNewMessage(const ScriptInterface& scriptInterface) { if (m_GuiMessageQueue.empty()) return JS::UndefinedValue(); JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue message(cx, m_GuiMessageQueue.front()); m_GuiMessageQueue.pop_front(); JS::RootedValue messageCopy(cx); if (JS_StructuredClone(cx, message, &messageCopy, nullptr, nullptr)) { scriptInterface.SetProperty(messageCopy, "historic", true); scriptInterface.FreezeObject(messageCopy, true); m_HistoricGuiMessages.push_back(JS::Heap(messageCopy)); } else LOGERROR("Could not clone historic lobby GUI message!"); return message; } JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& scriptInterface) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue ret(cx); scriptInterface.CreateArray(&ret); int j = 0; for (const JS::Heap& message : m_HistoricGuiMessages) scriptInterface.SetPropertyInt(ret, j++, message); return ret; } /** * Send a standard MUC textual message. */ void XmppClient::SendMUCMessage(const std::string& message) { m_mucRoom->send(message); } /** * Handle a room message. */ void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Message& msg, bool priv) { DbgXMPP(msg.from().resource() << " said " << msg.body()); CreateGUIMessage( "chat", priv ? "private-message" : "room-message", ComputeTimestamp(msg), - "from", msg.from().resource().to_string(), - "text", msg.body().to_string()); + "from", msg.from().resource(), + "text", msg.body()); } /** * Handle a private message. */ void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession*) { DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject() << ", message " << msg.body() << ", thread id " << msg.thread()); CreateGUIMessage( "chat", "private-message", ComputeTimestamp(msg), - "from", msg.from().resource().to_string(), - "text", msg.body().to_string()); + "from", msg.from().resource(), + "text", msg.body()); } /** * Handle portions of messages containing custom stanza extensions. */ bool XmppClient::handleIq(const glooxwrapper::IQ& iq) { DbgXMPP("handleIq [" << tag_xml(iq) << "]"); if (iq.subtype() == gloox::IQ::Result) { const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY); const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY); const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY); if (gq) { for (const glooxwrapper::Tag* const& t : m_GameList) glooxwrapper::Tag::free(t); m_GameList.clear(); for (const glooxwrapper::Tag* const& t : gq->m_GameList) m_GameList.emplace_back(t->clone()); CreateGUIMessage("game", "gamelist", std::time(nullptr)); } if (bq) { if (bq->m_Command == "boardlist") { for (const glooxwrapper::Tag* const& t : m_BoardList) glooxwrapper::Tag::free(t); m_BoardList.clear(); for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList) m_BoardList.emplace_back(t->clone()); CreateGUIMessage("game", "leaderboard", std::time(nullptr)); } else if (bq->m_Command == "ratinglist") { for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList) { - std::string name = t->findAttribute("name").to_string(); - if (m_PlayerMap.find(name) != m_PlayerMap.end()) + const PlayerMap::iterator it = m_PlayerMap.find(t->findAttribute("name")); + if (it != m_PlayerMap.end()) { - m_PlayerMap[name][1] = t->findAttribute("rating").to_string(); + it->second.m_Rating = t->findAttribute("rating"); m_PlayerMapUpdate = true; } } CreateGUIMessage("game", "ratinglist", std::time(nullptr)); } } if (pq) { for (const glooxwrapper::Tag* const& t : m_Profile) glooxwrapper::Tag::free(t); m_Profile.clear(); for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile) m_Profile.emplace_back(t->clone()); CreateGUIMessage("game", "profile", std::time(nullptr)); } } else if (iq.subtype() == gloox::IQ::Set) { const LobbyAuth* lobbyAuth = iq.findExtension(EXTLOBBYAUTH); if (lobbyAuth) { LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username()); glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id()); m_client->send(response); if (g_NetServer) g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string()); else LOGERROR("Received lobby authentication request, but not hosting currently!"); } } else if (iq.subtype() == gloox::IQ::Error) - CreateGUIMessage("system", "error", std::time(nullptr), "text", StanzaErrorToString(iq.error_error())); + CreateGUIMessage("system", "error", std::time(nullptr), "text", iq.error_error()); else { - CreateGUIMessage("system", "error", std::time(nullptr), "text", g_L10n.Translate("unknown subtype (see logs)")); + CreateGUIMessage("system", "error", std::time(nullptr), "text", wstring_from_utf8(g_L10n.Translate("unknown subtype (see logs)"))); LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str()); } + return true; } /** * Update local data when a user changes presence. */ void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence) { - std::string nick = participant.nick->resource().to_string(); - gloox::Presence::PresenceType presenceType = presence.presence(); - std::string presenceString, roleString; - GetPresenceString(presenceType, presenceString); - GetRoleString(participant.role, roleString); + const glooxwrapper::string& nick = participant.nick->resource(); - if (presenceType == gloox::Presence::Unavailable) + if (presence.presence() == gloox::Presence::Unavailable) { if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf))) { // we have a nick change - std::string newNick = participant.newNick.to_string(); - m_PlayerMap[newNick].resize(3); - m_PlayerMap[newNick][0] = presenceString; - m_PlayerMap[newNick][2] = roleString; + if (m_PlayerMap.find(participant.newNick) == m_PlayerMap.end()) + m_PlayerMap.emplace( + std::piecewise_construct, + std::forward_as_tuple(participant.newNick), + std::forward_as_tuple(presence.presence(), participant.role, std::move(m_PlayerMap.at(nick).m_Rating))); + else + LOGERROR("Nickname changed to an existing nick!"); - DbgXMPP(nick << " is now known as " << participant.newNick.to_string()); - CreateGUIMessage("chat", "nick", std::time(nullptr), "oldnick", nick, "newnick", participant.newNick.to_string()); + DbgXMPP(nick << " is now known as " << participant.newNick); + CreateGUIMessage( + "chat", + "nick", + std::time(nullptr), + "oldnick", nick, + "newnick", participant.newNick); } else if (participant.flags & gloox::UserKicked) { - DbgXMPP(nick << " was kicked. Reason: " << participant.reason.to_string()); - CreateGUIMessage("chat", "kicked", std::time(nullptr), "nick", nick, "reason", participant.reason.to_string()); + DbgXMPP(nick << " was kicked. Reason: " << participant.reason); + CreateGUIMessage( + "chat", + "kicked", + std::time(nullptr), + "nick", nick, + "reason", participant.reason); } else if (participant.flags & gloox::UserBanned) { - DbgXMPP(nick << " was banned. Reason: " << participant.reason.to_string()); - CreateGUIMessage("chat", "banned", std::time(nullptr), "nick", nick, "reason", participant.reason.to_string()); + DbgXMPP(nick << " was banned. Reason: " << participant.reason); + CreateGUIMessage( + "chat", + "banned", + std::time(nullptr), + "nick", nick, + "reason", participant.reason); } else { DbgXMPP(nick << " left the room (flags " << participant.flags << ")"); - CreateGUIMessage("chat", "leave", std::time(nullptr), "nick", nick); + CreateGUIMessage( + "chat", + "leave", + std::time(nullptr), + "nick", nick); } m_PlayerMap.erase(nick); } else { + const PlayerMap::iterator it = m_PlayerMap.find(nick); + /* During the initialization process, we receive join messages for everyone * currently in the room. We don't want to display these, so we filter them * out. We will always be the last to join during initialization. */ if (!m_initialLoadComplete) { - if (m_mucRoom->nick().to_string() == nick) + if (m_mucRoom->nick() == nick) m_initialLoadComplete = true; } - else if (m_PlayerMap.find(nick) == m_PlayerMap.end()) - CreateGUIMessage("chat", "join", std::time(nullptr), "nick", nick); - else if (m_PlayerMap[nick][2] != roleString) - CreateGUIMessage("chat", "role", std::time(nullptr), "nick", nick, "oldrole", m_PlayerMap[nick][2], "newrole", roleString); + else if (it == m_PlayerMap.end()) + { + CreateGUIMessage( + "chat", + "join", + std::time(nullptr), + "nick", nick); + } + else if (it->second.m_Role != participant.role) + { + CreateGUIMessage( + "chat", + "role", + std::time(nullptr), + "nick", nick, + "oldrole", it->second.m_Role, + "newrole", participant.role); + } else { // Don't create a GUI message for regular presence changes, because // several hundreds of them accumulate during a match, impacting performance terribly and // the only way they are used is to determine whether to update the playerlist. } - DbgXMPP(nick << " is in the room, presence : " << (int)presenceType); - m_PlayerMap[nick].resize(3); - m_PlayerMap[nick][0] = presenceString; - m_PlayerMap[nick][2] = roleString; + DbgXMPP( + nick << " is in the room, " + "presence: " << GetPresenceString(presence.presence()) << ", " + "role: "<< GetRoleString(participant.role)); + + if (it == m_PlayerMap.end()) + { + m_PlayerMap.emplace( + std::piecewise_construct, + std::forward_as_tuple(nick), + std::forward_as_tuple(presence.presence(), participant.role, std::string())); + } + else + { + it->second.m_Presence = presence.presence(); + it->second.m_Role = participant.role; + } } m_PlayerMapUpdate = true; } /** * Update local cache when subject changes. */ void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& nick, const glooxwrapper::string& subject) { - m_Subject = subject.c_str(); - CreateGUIMessage("chat", "subject", std::time(nullptr), "nick", nick.c_str(), "subject", m_Subject); + m_Subject = wstring_from_utf8(subject.to_string()); + + CreateGUIMessage( + "chat", + "subject", + std::time(nullptr), + "nick", nick, + "subject", m_Subject); } /** * Get current subject. - * - * @param topic Variable to store subject in. */ -void XmppClient::GetSubject(std::string& subject) +const std::wstring& XmppClient::GetSubject() { - subject = m_Subject; + return m_Subject; } /** * Request nick change, real change via mucRoomHandler. * * @param nick Desired nickname */ void XmppClient::SetNick(const std::string& nick) { m_mucRoom->setNick(nick); } /** * Get current nickname. * * @param nick Variable to store the nickname in. */ void XmppClient::GetNick(std::string& nick) { nick = m_mucRoom->nick().to_string(); } /** * Kick a player from the current room. * * @param nick Nickname to be kicked * @param reason Reason the player was kicked */ void XmppClient::kick(const std::string& nick, const std::string& reason) { m_mucRoom->kick(nick, reason); } /** * Ban a player from the current room. * * @param nick Nickname to be banned * @param reason Reason the player was banned */ void XmppClient::ban(const std::string& nick, const std::string& reason) { m_mucRoom->ban(nick, reason); } /** * Change the xmpp presence of the client. * * @param presence A string containing the desired presence */ void XmppClient::SetPresence(const std::string& presence) { #define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y) IF("available", Available); else IF("chat", Chat); else IF("away", Away); else IF("playing", DND); else IF("offline", Unavailable); // The others are not to be set #undef IF else LOGERROR("Unknown presence '%s'", presence.c_str()); } /** * Get the current xmpp presence of the given nick. - * - * @param nick Nickname to look up presence for - * @param presence Variable to store the presence in */ -void XmppClient::GetPresence(const std::string& nick, std::string& presence) +const char* XmppClient::GetPresence(const std::string& nick) { - if (m_PlayerMap.find(nick) != m_PlayerMap.end()) - presence = m_PlayerMap[nick][0]; - else - presence = "offline"; + const PlayerMap::iterator it = m_PlayerMap.find(nick); + + if (it == m_PlayerMap.end()) + return "offline"; + + return GetPresenceString(it->second.m_Presence); } /** * Get the current xmpp role of the given nick. - * - * @param nick Nickname to look up presence for - * @param role Variable to store the role in */ -void XmppClient::GetRole(const std::string& nick, std::string& role) +const char* XmppClient::GetRole(const std::string& nick) { - if (m_PlayerMap.find(nick) != m_PlayerMap.end()) - role = m_PlayerMap[nick][2]; - else - role = ""; + const PlayerMap::iterator it = m_PlayerMap.find(nick); + + if (it == m_PlayerMap.end()) + return ""; + + return GetRoleString(it->second.m_Role); } /***************************************************** * Utilities * *****************************************************/ /** * Parse and return the timestamp of a historic chat message and return the current time for new chat messages. * Historic chat messages are implement as DelayedDelivers as specified in XEP-0203. * Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082. * * @returns Seconds since the epoch. */ -std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) const +std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) { // Only historic messages contain a timestamp! if (!msg.when()) return std::time(nullptr); // The locale is irrelevant, because the XMPP date format doesn't contain written month names for (const std::string& format : std::vector{ "Y-M-d'T'H:m:sZ", "Y-M-d'T'H:m:s.SZ" }) { UDate dateTime = g_L10n.ParseDateTime(msg.when()->stamp().to_string(), format, icu::Locale::getUS()); if (dateTime) return dateTime / 1000.0; } return std::time(nullptr); } /** - * Convert a gloox presence type to string. - * - * @param p Presence to be converted - * @param presence Variable to store the converted presence string in + * Convert a gloox presence type to an untranslated string literal to be used as an identifier by the scripts. */ -void XmppClient::GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const +const char* XmppClient::GetPresenceString(const gloox::Presence::PresenceType presenceType) { - switch(p) + switch (presenceType) { -#define CASE(x,y) case gloox::Presence::x: presence = y; break +#define CASE(X,Y) case gloox::Presence::X: return Y CASE(Available, "available"); CASE(Chat, "chat"); CASE(Away, "away"); CASE(DND, "playing"); CASE(XA, "away"); CASE(Unavailable, "offline"); CASE(Probe, "probe"); CASE(Error, "error"); CASE(Invalid, "invalid"); default: - LOGERROR("Unknown presence type '%d'", (int)p); - break; + LOGERROR("Unknown presence type '%d'", static_cast(presenceType)); + return ""; #undef CASE } } /** - * Convert a gloox role type to string. - * - * @param p Role to be converted - * @param presence Variable to store the converted role string in + * Convert a gloox role type to an untranslated string literal to be used as an identifier by the scripts. */ -void XmppClient::GetRoleString(const gloox::MUCRoomRole r, std::string& role) const +const char* XmppClient::GetRoleString(const gloox::MUCRoomRole role) { - switch(r) + switch (role) { -#define CASE(X, Y) case gloox::X: role = Y; break +#define CASE(X, Y) case gloox::X: return Y CASE(RoleNone, "none"); CASE(RoleVisitor, "visitor"); CASE(RoleParticipant, "participant"); CASE(RoleModerator, "moderator"); CASE(RoleInvalid, "invalid"); default: - LOGERROR("Unknown role type '%d'", (int)r); - break; + LOGERROR("Unknown role type '%d'", static_cast(role)); + return ""; #undef CASE } } /** * Translates a gloox certificate error codes, i.e. gloox certificate statuses except CertOk. * Keep in sync with specifications. */ -std::string XmppClient::CertificateErrorToString(gloox::CertStatus status) const +std::string XmppClient::CertificateErrorToString(gloox::CertStatus status) { std::map certificateErrorStrings = { { gloox::CertInvalid, g_L10n.Translate("The certificate is not trusted.") }, { gloox::CertSignerUnknown, g_L10n.Translate("The certificate hasn't got a known issuer.") }, { gloox::CertRevoked, g_L10n.Translate("The certificate has been revoked.") }, { gloox::CertExpired, g_L10n.Translate("The certificate has expired.") }, { gloox::CertNotActive, g_L10n.Translate("The certifiacte is not yet active.") }, { gloox::CertWrongPeer, g_L10n.Translate("The certificate has not been issued for the peer connected to.") }, { gloox::CertSignerNotCa, g_L10n.Translate("The certificate signer is not a certificate authority.") } }; std::string result; for (std::map::iterator it = certificateErrorStrings.begin(); it != certificateErrorStrings.end(); ++it) if (status & it->first) result += "\n" + it->second; return result; } /** * Convert a gloox stanza error type to string. * Keep in sync with Gloox documentation * * @param err Error to be converted * @return Converted error string */ -std::string XmppClient::StanzaErrorToString(gloox::StanzaError err) const +std::string XmppClient::StanzaErrorToString(gloox::StanzaError err) { #define CASE(X, Y) case gloox::X: return Y #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")" switch (err) { CASE(StanzaErrorUndefined, g_L10n.Translate("No error")); DEBUG_CASE(StanzaErrorBadRequest, "Server received malformed XML"); CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in")); DEBUG_CASE(StanzaErrorFeatureNotImplemented, "Server does not implement requested feature"); CASE(StanzaErrorForbidden, g_L10n.Translate("Forbidden")); DEBUG_CASE(StanzaErrorGone, "Unable to find message receipiant"); CASE(StanzaErrorInternalServerError, g_L10n.Translate("Internal server error")); DEBUG_CASE(StanzaErrorItemNotFound, "Message receipiant does not exist"); DEBUG_CASE(StanzaErrorJidMalformed, "JID (XMPP address) malformed"); DEBUG_CASE(StanzaErrorNotAcceptable, "Receipiant refused message. Possible policy issue"); CASE(StanzaErrorNotAllowed, g_L10n.Translate("Not allowed")); CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized")); DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request"); DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment"); CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable")); DEBUG_CASE(StanzaErrorRedirect, "Request redirected"); CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required")); DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found"); DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out"); DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints"); CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable")); DEBUG_CASE(StanzaErrorSubscribtionRequired, "Service requires subscription"); DEBUG_CASE(StanzaErrorUnexpectedRequest, "Attempt to send from invalid stanza address"); DEBUG_CASE(StanzaErrorUnknownSender, "Invalid 'from' address"); default: return g_L10n.Translate("Unknown error"); } #undef DEBUG_CASE #undef CASE } /** * Convert a gloox connection error enum to string * Keep in sync with Gloox documentation * * @param err Error to be converted * @return Converted error string */ -std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err) const +std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err) { #define CASE(X, Y) case gloox::X: return Y #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")" switch (err) { CASE(ConnNoError, g_L10n.Translate("No error")); CASE(ConnStreamError, g_L10n.Translate("Stream error")); CASE(ConnStreamVersionError, g_L10n.Translate("The incoming stream version is unsupported")); CASE(ConnStreamClosed, g_L10n.Translate("The stream has been closed by the server")); DEBUG_CASE(ConnProxyAuthRequired, "The HTTP/SOCKS5 proxy requires authentication"); DEBUG_CASE(ConnProxyAuthFailed, "HTTP/SOCKS5 proxy authentication failed"); DEBUG_CASE(ConnProxyNoSupportedAuth, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism"); CASE(ConnIoError, g_L10n.Translate("An I/O error occurred")); DEBUG_CASE(ConnParseError, "An XML parse error occurred"); CASE(ConnConnectionRefused, g_L10n.Translate("The connection was refused by the server")); CASE(ConnDnsError, g_L10n.Translate("Resolving the server's hostname failed")); CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory")); DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available"); CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully")); CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption")); DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed"); CASE(ConnAuthenticationFailed, g_L10n.Translate("Authentication failed. Incorrect password or account does not exist")); CASE(ConnUserDisconnected, g_L10n.Translate("The user or system requested a disconnect")); CASE(ConnNotConnected, g_L10n.Translate("There is no active connection")); default: return g_L10n.Translate("Unknown error"); } #undef DEBUG_CASE #undef CASE } /** * Convert a gloox registration result enum to string * Keep in sync with Gloox documentation * * @param err Enum to be converted * @return Converted string */ -std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res) const +std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res) { #define CASE(X, Y) case gloox::X: return Y #define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")" switch (res) { CASE(RegistrationSuccess, g_L10n.Translate("Your account has been successfully registered")); CASE(RegistrationNotAcceptable, g_L10n.Translate("Not all necessary information provided")); CASE(RegistrationConflict, g_L10n.Translate("Username already exists")); DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change"); DEBUG_CASE(RegistrationBadRequest, "Server received an incomplete request"); DEBUG_CASE(RegistrationForbidden, "Registration forbidden"); DEBUG_CASE(RegistrationRequired, "Account cannot be removed as it does not exist"); DEBUG_CASE(RegistrationUnexpectedRequest, "This client is unregistered with the server"); DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes"); default: return ""; } #undef DEBUG_CASE #undef CASE } void XmppClient::SendStunEndpointToHost(const StunClient::StunEndpoint& stunEndpoint, const std::string& hostJIDStr) { + DbgXMPP("SendStunEndpointToHost " << hostJIDStr); + char ipStr[256] = "(error)"; ENetAddress addr; addr.host = ntohl(stunEndpoint.ip); enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr)); glooxwrapper::JID hostJID(hostJIDStr); glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID); session.sessionInitiate(ipStr, stunEndpoint.port); } void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle) { if (action == gloox::Jingle::SessionInitiate) handleSessionInitiation(session, jingle); } void XmppClient::handleSessionInitiation(glooxwrapper::Jingle::Session& UNUSED(session), const glooxwrapper::Jingle::Session::Jingle& jingle) { glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle.getCandidate(); if (candidate.ip.empty()) { LOGERROR("Failed to retrieve Jingle candidate"); return; } if (!g_NetServer) { LOGERROR("Received STUN connection request, but not hosting currently!"); return; } g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port); } Index: ps/trunk/source/lobby/XmppClient.h =================================================================== --- ps/trunk/source/lobby/XmppClient.h (revision 22890) +++ ps/trunk/source/lobby/XmppClient.h (revision 22891) @@ -1,185 +1,198 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef XXXMPPCLIENT_H #define XXXMPPCLIENT_H #include "IXmppClient.h" #include #include "glooxwrapper/glooxwrapper.h" #include "scriptinterface/ScriptVal.h" class ScriptInterface; namespace glooxwrapper { class Client; struct CertInfo; } class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler { NONCOPYABLE(XmppClient); private: // Components glooxwrapper::Client* m_client; glooxwrapper::MUCRoom* m_mucRoom; glooxwrapper::Registration* m_registration; glooxwrapper::SessionManager* m_sessionManager; // Account infos std::string m_username; std::string m_password; std::string m_server; std::string m_room; std::string m_nick; std::string m_xpartamuppId; std::string m_echelonId; // State gloox::CertStatus m_certStatus; bool m_initialLoadComplete; bool m_isConnected; public: // Basic XmppClient(const ScriptInterface* scriptInterface, const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, const bool regOpt = false); virtual ~XmppClient(); // JS::Heap is better for GC performance than JS::PersistentRooted static void Trace(JSTracer *trc, void *data) { static_cast(data)->TraceMember(trc); } void TraceMember(JSTracer *trc); // Network void connect(); void disconnect(); bool isConnected(); void recv(); void SendIqGetBoardList(); void SendIqGetProfile(const std::string& player); void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data); void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data); void SendIqUnregisterGame(); 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); void GetNick(std::string& nick); 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); - void GetPresence(const std::string& nickname, std::string& presence); - void GetRole(const std::string& nickname, std::string& role); - void GetSubject(std::string& subject); + const char* GetPresence(const std::string& nickname); + const char* GetRole(const std::string& nickname); + const std::wstring& GetSubject(); void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret); void SendStunEndpointToHost(const StunClient::StunEndpoint& stunEndpoint, const std::string& hostJID); + /** + * Convert gloox values to string or time. + */ + static const char* GetPresenceString(const gloox::Presence::PresenceType presenceType); + static const char* GetRoleString(const gloox::MUCRoomRole role); + static std::string StanzaErrorToString(gloox::StanzaError err); + static std::string RegistrationResultToString(gloox::RegistrationResult res); + static std::string ConnectionErrorToString(gloox::ConnectionError err); + static std::string CertificateErrorToString(gloox::CertStatus status); + static std::time_t ComputeTimestamp(const glooxwrapper::Message& msg); + protected: /* Xmpp handlers */ /* MUC handlers */ virtual void handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant, const glooxwrapper::Presence&); virtual void handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError); virtual void handleMUCMessage(glooxwrapper::MUCRoom* room, const glooxwrapper::Message& msg, bool priv); virtual void handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& nick, const glooxwrapper::string& subject); /* MUC handlers not supported by glooxwrapper */ // virtual bool handleMUCRoomCreation(glooxwrapper::MUCRoom*) {return false;} // virtual void handleMUCInviteDecline(glooxwrapper::MUCRoom*, const glooxwrapper::JID&, const std::string&) {} // virtual void handleMUCInfo(glooxwrapper::MUCRoom*, int, const std::string&, const glooxwrapper::DataForm*) {} // virtual void handleMUCItems(glooxwrapper::MUCRoom*, const std::list >&) {} /* Log handler */ virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message); /* ConnectionListener handlers*/ virtual void onConnect(); virtual void onDisconnect(gloox::ConnectionError e); virtual bool onTLSConnect(const glooxwrapper::CertInfo& info); /* Iq Handlers */ virtual bool handleIq(const glooxwrapper::IQ& iq); virtual void handleIqID(const glooxwrapper::IQ&, int) {} /* Registration Handlers */ virtual void handleRegistrationFields(const glooxwrapper::JID& /*from*/, int fields, glooxwrapper::string instructions ); virtual void handleRegistrationResult(const glooxwrapper::JID& /*from*/, gloox::RegistrationResult result); virtual void handleAlreadyRegistered(const glooxwrapper::JID& /*from*/); virtual void handleDataForm(const glooxwrapper::JID& /*from*/, const glooxwrapper::DataForm& /*form*/); virtual void handleOOB(const glooxwrapper::JID& /*from*/, const glooxwrapper::OOB& oob); /* Message Handler */ virtual void handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession* session); /* Session Handler */ virtual void handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle); virtual void handleSessionInitiation(glooxwrapper::Jingle::Session& session, const glooxwrapper::Jingle::Session::Jingle& jingle); - // Helpers - void GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const; - void GetRoleString(const gloox::MUCRoomRole r, std::string& role) const; - std::string StanzaErrorToString(gloox::StanzaError err) const; - std::string ConnectionErrorToString(gloox::ConnectionError err) const; - std::string CertificateErrorToString(gloox::CertStatus status) const; - std::string RegistrationResultToString(gloox::RegistrationResult res) const; - std::time_t ComputeTimestamp(const glooxwrapper::Message& msg) const; - public: JS::Value GuiPollNewMessage(const ScriptInterface& scriptInterface); JS::Value GuiPollHistoricMessages(const ScriptInterface& scriptInterface); bool GuiPollHasPlayerListUpdate(); void SendMUCMessage(const std::string& message); protected: template void CreateGUIMessage( const std::string& type, const std::string& level, const std::time_t time, Args const&... args); private: + struct SPlayer { + SPlayer(const gloox::Presence::PresenceType presence, const gloox::MUCRoomRole role, const glooxwrapper::string& rating) + : m_Presence(presence), m_Role(role), m_Rating(rating) + { + } + gloox::Presence::PresenceType m_Presence; + gloox::MUCRoomRole m_Role; + glooxwrapper::string m_Rating; + }; + using PlayerMap = std::map; + /// Map of players - std::map > m_PlayerMap; + PlayerMap m_PlayerMap; /// Whether or not the playermap has changed since the last time the GUI checked. bool m_PlayerMapUpdate; /// List of games std::vector m_GameList; /// List of rankings std::vector m_BoardList; /// Profile data std::vector m_Profile; /// ScriptInterface to root the values const ScriptInterface* m_ScriptInterface; /// Queue of messages for the GUI std::deque > m_GuiMessageQueue; /// Cache of all GUI messages received since the login std::vector > m_HistoricGuiMessages; /// Current room subject/topic. - std::string m_Subject; + std::wstring m_Subject; }; #endif // XMPPCLIENT_H Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h =================================================================== --- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 22890) +++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 22891) @@ -1,678 +1,691 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_GLOOXWRAPPER_H #define INCLUDED_GLOOXWRAPPER_H /* The gloox API uses various STL types (std::string, std::list, etc), and it has functions that acquire/release ownership of objects and expect the library's user's 'new'/'delete' functions to be compatible with the library's. These assumptions are invalid when the game and library are built with different compiler versions (or the same version with different build flags): the STL types have different layouts, and new/delete can use different heaps. We want to let people build the game on Windows with any compiler version (VC2008, 2010, 2012, 2013, and debug vs release), without requiring them to rebuild the gloox library themselves. And we don't want to provide ~8 different prebuilt versions of the library. glooxwrapper replaces the gloox API with a version that is safe to use across compiler versions. glooxwrapper and gloox must be compiled together with the same version, but the resulting library can be used with any other compiler. This is the small subset of the API that the game currently uses, with no attempt to be comprehensive. General design and rules: * There is a strict boundary between gloox+glooxwrapper.cpp, and the game code that includes glooxwrapper.h. Objects allocated with new/delete on one side of the boundary must be freed/allocated on the same side. Objects allocated with glooxwrapper_alloc()/glooxwrapper_delete() can be freely shared across the boundary. * glooxwrapper.h and users of glooxwrapper must not use any types from the gloox namespace, except for enums. * std::string is replaced with glooxwrapper::string, std::list with glooxwrapper::list * Most glooxwrapper classes are simple wrappers around gloox classes. Some always take ownership of their wrapped gloox object (i.e. their destructor will delete the wrapped object too); some never do; and some can be used either way (indicated by an m_Owned field). */ #if OS_WIN # include "lib/sysdep/os/win/win.h" // Prevent gloox pulling in windows.h # define _WINDOWS_ #endif #include #include #include #include #include #include #include #include #include #if OS_WIN #define GLOOXWRAPPER_API __declspec(dllexport) #else #define GLOOXWRAPPER_API #endif namespace glooxwrapper { class Client; class DataForm; class DelayedDelivery; class Disco; class IQ; class JID; class MUCRoom; class MUCRoomConfigHandler; class Message; class MessageSession; class OOB; class Presence; class StanzaError; class StanzaExtension; class Tag; class ClientImpl; class MUCRoomHandlerWrapper; class SessionHandlerWrapper; GLOOXWRAPPER_API void* glooxwrapper_alloc(size_t size); GLOOXWRAPPER_API void glooxwrapper_free(void* p); class string { private: size_t m_Size; char* m_Data; public: string() { m_Size = 0; m_Data = (char*)glooxwrapper_alloc(1); m_Data[0] = '\0'; } string(const string& str) { m_Size = str.m_Size; m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.m_Data, m_Size + 1); } string(const std::string& str) : m_Data(NULL) { m_Size = str.size(); m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.c_str(), m_Size + 1); } string(const char* str) { m_Size = strlen(str); m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str, m_Size + 1); } string& operator=(const string& str) { if (this != &str) { glooxwrapper_free(m_Data); m_Size = str.m_Size; m_Data = (char*)glooxwrapper_alloc(m_Size + 1); memcpy(m_Data, str.m_Data, m_Size + 1); } return *this; } ~string() { glooxwrapper_free(m_Data); } + /** + * Gloox strings are UTF encoded, so don't forget to decode it before passing it to the GUI! + */ std::string to_string() const { return std::string(m_Data, m_Size); } const char* c_str() const { return m_Data; } bool empty() const { return m_Size == 0; } bool operator==(const char* str) const { return strcmp(m_Data, str) == 0; } bool operator!=(const char* str) const { return strcmp(m_Data, str) != 0; } + + bool operator==(const string& str) const + { + return strcmp(m_Data, str.m_Data) == 0; + } + + bool operator<(const string& str) const + { + return strcmp(m_Data, str.m_Data) < 0; + } }; static inline std::ostream& operator<<(std::ostream& stream, const string& string) { return stream << string.c_str(); } template class list { private: struct node { node(const T& item) : m_Item(item), m_Next(NULL) {} T m_Item; node* m_Next; }; node* m_Head; node* m_Tail; public: struct const_iterator { const node* m_Node; const_iterator(const node* n) : m_Node(n) {} bool operator!=(const const_iterator& it) { return m_Node != it.m_Node; } const_iterator& operator++() { m_Node = m_Node->m_Next; return *this; } const T& operator*() { return m_Node->m_Item; } }; const_iterator begin() const { return const_iterator(m_Head); } const_iterator end() const { return const_iterator(NULL); } list() : m_Head(NULL), m_Tail(NULL) {} list(const list& src) : m_Head(NULL), m_Tail(NULL) { *this = src; } list& operator=(const list& src) { if (this != &src) { clear(); for (node* n = src.m_Head; n; n = n->m_Next) push_back(n->m_Item); } return *this; } ~list() { clear(); } void push_back(const T& item) { node* n = new (glooxwrapper_alloc(sizeof(node))) node(item); if (m_Tail) m_Tail->m_Next = n; m_Tail = n; if (!m_Head) m_Head = n; } void clear() { node* n = m_Head; while (n) { node* next = n->m_Next; glooxwrapper_free(n); n = next; } m_Head = m_Tail = NULL; } const T& front() const { return *begin(); } }; typedef glooxwrapper::list TagList; typedef glooxwrapper::list ConstTagList; struct CertInfo { int status; bool chain; string issuer; string server; int date_from; int date_to; string protocol; string cipher; string mac; string compression; }; struct RegistrationFields { string username; string nick; string password; string name; string first; string last; string email; string address; string city; string state; string zip; string phone; string url; string date; string misc; string text; }; struct MUCRoomParticipant { JID* nick; gloox::MUCRoomAffiliation affiliation; gloox::MUCRoomRole role; JID* jid; int flags; string reason; JID* actor; string newNick; string status; JID* alternate; }; class GLOOXWRAPPER_API ConnectionListener { public: virtual ~ConnectionListener() {} virtual void onConnect() = 0; virtual void onDisconnect(gloox::ConnectionError e) = 0; virtual bool onTLSConnect(const CertInfo& info) = 0; }; class GLOOXWRAPPER_API IqHandler { public: virtual ~IqHandler() {} virtual bool handleIq(const IQ& iq) = 0; virtual void handleIqID(const IQ& iq, int context) = 0; }; class GLOOXWRAPPER_API MessageHandler { public: virtual ~MessageHandler() {} virtual void handleMessage(const Message& msg, MessageSession* session = 0) = 0; // MessageSession not supported }; class GLOOXWRAPPER_API MUCRoomHandler { public: virtual ~MUCRoomHandler() {} virtual void handleMUCParticipantPresence(MUCRoom* room, const MUCRoomParticipant participant, const Presence& presence) = 0; // MUCRoom not supported virtual void handleMUCMessage(MUCRoom* room, const Message& msg, bool priv) = 0; // MUCRoom not supported virtual void handleMUCError(MUCRoom* room, gloox::StanzaError error) = 0; // MUCRoom not supported virtual void handleMUCSubject(MUCRoom* room, const string& nick, const string& subject) = 0; // MUCRoom not supported }; class GLOOXWRAPPER_API RegistrationHandler { public: virtual ~RegistrationHandler() {} virtual void handleRegistrationFields(const JID& from, int fields, string instructions) = 0; virtual void handleAlreadyRegistered(const JID& from) = 0; virtual void handleRegistrationResult(const JID& from, gloox::RegistrationResult regResult) = 0; virtual void handleDataForm(const JID& from, const DataForm& form) = 0; // DataForm not supported virtual void handleOOB(const JID& from, const OOB& oob) = 0; // OOB not supported }; class GLOOXWRAPPER_API StanzaExtension { public: StanzaExtension(int type) : m_extensionType(type) {} virtual ~StanzaExtension() {} virtual const string& filterString() const = 0; virtual StanzaExtension* newInstance(const Tag* tag) const = 0; virtual glooxwrapper::Tag* tag() const = 0; virtual StanzaExtension* clone() const = 0; int extensionType() const { return m_extensionType; } private: int m_extensionType; }; class GLOOXWRAPPER_API Client { NONCOPYABLE(Client); private: gloox::Client* m_Wrapped; ClientImpl* m_Impl; Disco* m_DiscoWrapper; public: gloox::Client* getWrapped() { return m_Wrapped; } bool connect(bool block = true); gloox::ConnectionError recv(int timeout = -1); const string getID() const; void send(const IQ& iq); void setTls(gloox::TLSPolicy tls); void setCompression(bool compression); void setSASLMechanisms(int mechanisms); void registerStanzaExtension(StanzaExtension* ext); void registerConnectionListener(ConnectionListener* cl); void registerIqHandler(IqHandler* ih, int exttype); void registerMessageHandler(MessageHandler* mh); bool removePresenceExtension(int type); Disco* disco() const { return m_DiscoWrapper; } Client(const string& server); Client(const JID& jid, const string& password, int port = -1); ~Client(); void setPresence(gloox::Presence::PresenceType pres, int priority, const string& status = ""); void disconnect(); }; class GLOOXWRAPPER_API DelayedDelivery { NONCOPYABLE(DelayedDelivery); private: const gloox::DelayedDelivery* m_Wrapped; public: DelayedDelivery(const gloox::DelayedDelivery* wrapped); const string stamp() const; }; class GLOOXWRAPPER_API Disco { NONCOPYABLE(Disco); private: gloox::Disco* m_Wrapped; public: Disco(gloox::Disco* wrapped); void setVersion(const string& name, const string& version, const string& os = ""); void setIdentity(const string& category, const string& type, const string& name = ""); }; class GLOOXWRAPPER_API IQ { NONCOPYABLE(IQ); private: gloox::IQ* m_Wrapped; bool m_Owned; public: const gloox::IQ& getWrapped() const { return *m_Wrapped; } IQ(const gloox::IQ& iq) : m_Wrapped(const_cast(&iq)), m_Owned(false) { } IQ(gloox::IQ::IqType type, const JID& to, const string& id); ~IQ(); void addExtension(const StanzaExtension* se); const StanzaExtension* findExtension(int type) const; template inline const T* findExtension(int type) const { return static_cast(findExtension(type)); } gloox::IQ::IqType subtype() const; const string id() const; const gloox::JID& from() const; gloox::StanzaError error_error() const; // wrapper for ->error()->error() Tag* tag() const; }; class GLOOXWRAPPER_API JID { NONCOPYABLE(JID); private: gloox::JID* m_Wrapped; bool m_Owned; void init(const char* data, size_t len); public: const gloox::JID& getWrapped() const { return *m_Wrapped; } JID(const gloox::JID& jid) : m_Wrapped(const_cast(&jid)), m_Owned(false) { } JID(); JID(const string& jid); JID(const std::string& jid) { init(jid.c_str(), jid.size()); } ~JID(); string username() const; string resource() const; }; class GLOOXWRAPPER_API Message { NONCOPYABLE(Message); private: gloox::Message* m_Wrapped; bool m_Owned; glooxwrapper::JID* m_From; public: Message(gloox::Message* wrapped, bool owned); ~Message(); gloox::Message::MessageType subtype() const; const JID& from() const; string body() const; string subject(const string& lang = "default") const; string thread() const; const glooxwrapper::DelayedDelivery* when() const; }; class GLOOXWRAPPER_API MUCRoom { NONCOPYABLE(MUCRoom); private: gloox::MUCRoom* m_Wrapped; MUCRoomHandlerWrapper* m_HandlerWrapper; public: MUCRoom(Client* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* mrch = 0); ~MUCRoom(); const string nick() const; void join(gloox::Presence::PresenceType type = gloox::Presence::Available, const string& status = "", int priority = 0); void leave(const string& msg = ""); void send(const string& message); void setNick(const string& nick); void setPresence(gloox::Presence::PresenceType presence, const string& msg = ""); void setRequestHistory(int value, gloox::MUCRoom::HistoryRequestType type); void kick(const string& nick, const string& reason); void ban(const string& nick, const string& reason); }; class GLOOXWRAPPER_API Presence { gloox::Presence::PresenceType m_Presence; public: Presence(gloox::Presence::PresenceType presence) : m_Presence(presence) {} gloox::Presence::PresenceType presence() const { return m_Presence; } }; class GLOOXWRAPPER_API Registration { NONCOPYABLE(Registration); private: gloox::Registration* m_Wrapped; std::list > m_RegistrationHandlers; public: Registration(Client* parent); ~Registration(); void fetchRegistrationFields(); bool createAccount(int fields, const RegistrationFields& values); void registerRegistrationHandler(RegistrationHandler* rh); }; class GLOOXWRAPPER_API Tag { NONCOPYABLE(Tag); private: gloox::Tag* m_Wrapped; bool m_Owned; Tag(const string& name); Tag(const string& name, const string& cdata); Tag(gloox::Tag* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} ~Tag(); public: // Internal use: gloox::Tag* getWrapped() { return m_Wrapped; } gloox::Tag* stealWrapped() { m_Owned = false; return m_Wrapped; } static Tag* allocate(gloox::Tag* wrapped, bool owned); // Instead of using new/delete, Tags must be allocated/freed with these functions static Tag* allocate(const string& name); static Tag* allocate(const string& name, const string& cdata); static void free(const Tag* tag); bool addAttribute(const string& name, const string& value); string findAttribute(const string& name) const; Tag* clone() const; string xmlns() const; bool setXmlns(const string& xmlns); string xml() const; void addChild(Tag* child); string name() const; string cdata() const; const Tag* findTag_clone(const string& expression) const; // like findTag but must be Tag::free()d ConstTagList findTagList_clone(const string& expression) const; // like findTagList but each tag must be Tag::free()d }; /** * See XEP-0166: Jingle and https://camaya.net/api/gloox/namespacegloox_1_1Jingle.html. */ namespace Jingle { class GLOOXWRAPPER_API Plugin { protected: const gloox::Jingle::Plugin* m_Wrapped; bool m_Owned; public: Plugin(const gloox::Jingle::Plugin* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} virtual ~Plugin(); const Plugin findPlugin(int type) const; const gloox::Jingle::Plugin* getWrapped() const { return m_Wrapped; } }; typedef list PluginList; /** * See XEP-0176: Jingle ICE-UDP Transport Method */ class GLOOXWRAPPER_API ICEUDP { public: struct Candidate { string ip; int port; }; private: // Class not implemented as it is not used. ICEUDP() = delete; }; class GLOOXWRAPPER_API Session { protected: gloox::Jingle::Session* m_Wrapped; bool m_Owned; public: class GLOOXWRAPPER_API Jingle { private: const gloox::Jingle::Session::Jingle* m_Wrapped; bool m_Owned; public: Jingle(const gloox::Jingle::Session::Jingle* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {} ~Jingle() { if (m_Owned) delete m_Wrapped; } ICEUDP::Candidate getCandidate() const; }; Session(gloox::Jingle::Session* wrapped, bool owned); ~Session(); bool sessionInitiate(char* ipStr, uint16_t port); }; class GLOOXWRAPPER_API SessionHandler { public: virtual ~SessionHandler() {} virtual void handleSessionAction(gloox::Jingle::Action action, Session& session, const Session::Jingle& jingle) = 0; }; } class GLOOXWRAPPER_API SessionManager { private: gloox::Jingle::SessionManager* m_Wrapped; SessionHandlerWrapper* m_HandlerWrapper; public: SessionManager(Client* parent, Jingle::SessionHandler* sh); ~SessionManager(); void registerPlugins(); Jingle::Session createSession(const JID& callee); }; } #endif // INCLUDED_GLOOXWRAPPER_H Index: ps/trunk/source/lobby/scripting/GlooxScriptConversions.cpp =================================================================== --- ps/trunk/source/lobby/scripting/GlooxScriptConversions.cpp (nonexistent) +++ ps/trunk/source/lobby/scripting/GlooxScriptConversions.cpp (revision 22891) @@ -0,0 +1,61 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "lib/config2.h" +#if CONFIG2_LOBBY + +#include "lobby/XmppClient.h" +#include "scriptinterface/ScriptInterface.h" + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const glooxwrapper::string& val) +{ + ToJSVal(cx, ret, wstring_from_utf8(val.to_string())); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::Presence::PresenceType& val) +{ + ToJSVal(cx, ret, XmppClient::GetPresenceString(val)); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::MUCRoomRole& val) +{ + ToJSVal(cx, ret, XmppClient::GetRoleString(val)); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::StanzaError& val) +{ + ToJSVal(cx, ret, wstring_from_utf8(XmppClient::StanzaErrorToString(val))); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::ConnectionError& val) +{ + ToJSVal(cx, ret, wstring_from_utf8(XmppClient::ConnectionErrorToString(val))); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::RegistrationResult& val) +{ + ToJSVal(cx, ret, wstring_from_utf8(XmppClient::RegistrationResultToString(val))); +} + +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const gloox::CertStatus& val) +{ + ToJSVal(cx, ret, wstring_from_utf8(XmppClient::CertificateErrorToString(val))); +} + +#endif // CONFIG2_LOBBY Property changes on: ps/trunk/source/lobby/scripting/GlooxScriptConversions.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp =================================================================== --- ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 22890) +++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 22891) @@ -1,389 +1,383 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "JSInterface_Lobby.h" #include "gui/GUIManager.h" #include "lib/utf8.h" #include "lobby/IXmppClient.h" #include "network/NetServer.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Util.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptVal.h" #include "third_party/encryption/pkcs5_pbkdf2.h" #include void JSI_Lobby::RegisterScriptFunctions(const ScriptInterface& scriptInterface) { // Lobby functions scriptInterface.RegisterFunction("HasXmppClient"); scriptInterface.RegisterFunction("IsRankedGame"); scriptInterface.RegisterFunction("SetRankedGame"); #if CONFIG2_LOBBY // Allow the lobby to be disabled scriptInterface.RegisterFunction("StartXmppClient"); scriptInterface.RegisterFunction("StartRegisterXmppClient"); scriptInterface.RegisterFunction("StopXmppClient"); scriptInterface.RegisterFunction("ConnectXmppClient"); scriptInterface.RegisterFunction("DisconnectXmppClient"); scriptInterface.RegisterFunction("IsXmppClientConnected"); scriptInterface.RegisterFunction("SendGetBoardList"); scriptInterface.RegisterFunction("SendGetProfile"); scriptInterface.RegisterFunction("SendRegisterGame"); scriptInterface.RegisterFunction("SendGameReport"); scriptInterface.RegisterFunction("SendUnregisterGame"); scriptInterface.RegisterFunction("SendChangeStateGame"); scriptInterface.RegisterFunction("GetPlayerList"); scriptInterface.RegisterFunction("GetGameList"); scriptInterface.RegisterFunction("GetBoardList"); scriptInterface.RegisterFunction("GetProfile"); scriptInterface.RegisterFunction("LobbyGuiPollNewMessage"); scriptInterface.RegisterFunction("LobbyGuiPollHistoricMessages"); scriptInterface.RegisterFunction("LobbyGuiPollHasPlayerListUpdate"); scriptInterface.RegisterFunction("LobbySendMessage"); scriptInterface.RegisterFunction("LobbySetPlayerPresence"); scriptInterface.RegisterFunction("LobbySetNick"); scriptInterface.RegisterFunction("LobbyGetNick"); scriptInterface.RegisterFunction("LobbyKick"); scriptInterface.RegisterFunction("LobbyBan"); - scriptInterface.RegisterFunction("LobbyGetPlayerPresence"); - scriptInterface.RegisterFunction("LobbyGetPlayerRole"); + scriptInterface.RegisterFunction("LobbyGetPlayerPresence"); + scriptInterface.RegisterFunction("LobbyGetPlayerRole"); scriptInterface.RegisterFunction("EncryptPassword"); scriptInterface.RegisterFunction("LobbyGetRoomSubject"); #endif // CONFIG2_LOBBY } bool JSI_Lobby::HasXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { return g_XmppClient; } bool JSI_Lobby::IsRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { return g_rankedGame; } void JSI_Lobby::SetRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool isRanked) { g_rankedGame = isRanked; } #if CONFIG2_LOBBY void JSI_Lobby::StartXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize) { ENSURE(!g_XmppClient); g_XmppClient = IXmppClient::create(g_GUI->GetScriptInterface().get(), utf8_from_wstring(username), utf8_from_wstring(password), utf8_from_wstring(room), utf8_from_wstring(nick), historyRequestSize); g_rankedGame = true; } void JSI_Lobby::StartRegisterXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password) { ENSURE(!g_XmppClient); g_XmppClient = IXmppClient::create(g_GUI->GetScriptInterface().get(), utf8_from_wstring(username), utf8_from_wstring(password), "", "", 0, true); } void JSI_Lobby::StopXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { ENSURE(g_XmppClient); SAFE_DELETE(g_XmppClient); g_rankedGame = false; } void JSI_Lobby::ConnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { ENSURE(g_XmppClient); g_XmppClient->connect(); } void JSI_Lobby::DisconnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { ENSURE(g_XmppClient); g_XmppClient->disconnect(); } bool JSI_Lobby::IsXmppClientConnected(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { ENSURE(g_XmppClient); return g_XmppClient->isConnected(); } void JSI_Lobby::SendGetBoardList(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { if (!g_XmppClient) return; g_XmppClient->SendIqGetBoardList(); } void JSI_Lobby::SendGetProfile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& player) { if (!g_XmppClient) return; g_XmppClient->SendIqGetProfile(utf8_from_wstring(player)); } void JSI_Lobby::SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data) { if (!g_XmppClient) return; g_XmppClient->SendIqGameReport(*(pCxPrivate->pScriptInterface), data); } void JSI_Lobby::SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data) { if (!g_XmppClient) return; // Prevent JS mods to register matches in the lobby that were started with lobby authentication disabled if (!g_NetServer || !g_NetServer->UseLobbyAuth()) { LOGERROR("Registering games in the lobby requires lobby authentication to be enabled!"); return; } g_XmppClient->SendIqRegisterGame(*(pCxPrivate->pScriptInterface), data); } void JSI_Lobby::SendUnregisterGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { if (!g_XmppClient) return; g_XmppClient->SendIqUnregisterGame(); } void JSI_Lobby::SendChangeStateGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nbp, const std::wstring& players) { if (!g_XmppClient) return; g_XmppClient->SendIqChangeStateGame(utf8_from_wstring(nbp), utf8_from_wstring(players)); } JS::Value JSI_Lobby::GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue playerList(cx); g_XmppClient->GUIGetPlayerList(*(pCxPrivate->pScriptInterface), &playerList); return playerList; } JS::Value JSI_Lobby::GetGameList(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue gameList(cx); g_XmppClient->GUIGetGameList(*(pCxPrivate->pScriptInterface), &gameList); return gameList; } JS::Value JSI_Lobby::GetBoardList(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue boardList(cx); g_XmppClient->GUIGetBoardList(*(pCxPrivate->pScriptInterface), &boardList); return boardList; } JS::Value JSI_Lobby::GetProfile(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue profileFetch(cx); g_XmppClient->GUIGetProfile(*(pCxPrivate->pScriptInterface), &profileFetch); return profileFetch; } bool JSI_Lobby::LobbyGuiPollHasPlayerListUpdate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { return g_XmppClient && g_XmppClient->GuiPollHasPlayerListUpdate(); } JS::Value JSI_Lobby::LobbyGuiPollNewMessage(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); return g_XmppClient->GuiPollNewMessage(*(pCxPrivate->pScriptInterface)); } JS::Value JSI_Lobby::LobbyGuiPollHistoricMessages(ScriptInterface::CxPrivate* pCxPrivate) { if (!g_XmppClient) return JS::UndefinedValue(); return g_XmppClient->GuiPollHistoricMessages(*(pCxPrivate->pScriptInterface)); } void JSI_Lobby::LobbySendMessage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& message) { if (!g_XmppClient) return; g_XmppClient->SendMUCMessage(utf8_from_wstring(message)); } void JSI_Lobby::LobbySetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& presence) { if (!g_XmppClient) return; g_XmppClient->SetPresence(utf8_from_wstring(presence)); } void JSI_Lobby::LobbySetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick) { if (!g_XmppClient) return; g_XmppClient->SetNick(utf8_from_wstring(nick)); } std::wstring JSI_Lobby::LobbyGetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { if (!g_XmppClient) return L""; std::string nick; g_XmppClient->GetNick(nick); return wstring_from_utf8(nick); } void JSI_Lobby::LobbyKick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason) { if (!g_XmppClient) return; g_XmppClient->kick(utf8_from_wstring(nick), utf8_from_wstring(reason)); } void JSI_Lobby::LobbyBan(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason) { if (!g_XmppClient) return; g_XmppClient->ban(utf8_from_wstring(nick), utf8_from_wstring(reason)); } -std::wstring JSI_Lobby::LobbyGetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname) +const char* JSI_Lobby::LobbyGetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname) { if (!g_XmppClient) - return L""; + return ""; - std::string presence; - g_XmppClient->GetPresence(utf8_from_wstring(nickname), presence); - return wstring_from_utf8(presence); + return g_XmppClient->GetPresence(utf8_from_wstring(nickname)); } -std::wstring JSI_Lobby::LobbyGetPlayerRole(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname) +const char* JSI_Lobby::LobbyGetPlayerRole(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname) { if (!g_XmppClient) - return L""; + return ""; - std::string role; - g_XmppClient->GetRole(utf8_from_wstring(nickname), role); - return wstring_from_utf8(role); + return g_XmppClient->GetRole(utf8_from_wstring(nickname)); } // Non-public secure PBKDF2 hash function with salting and 1,337 iterations // // TODO: We should use libsodium's crypto_pwhash instead of this. The first reason is that // libsodium doesn't propose a bare PBKDF2 hash in its API and it's too bad to rely on custom // code when we have a fully-fledged library available; the second reason is that Argon2 (the // default algorithm for crypto_pwhash) is better than what we use (and it's the default one // in the lib for a reason). // However changing the hashing method should be planned carefully, by trying to login with a // password hashed the old way, and, if successful, updating the password in the database using // the new hashing method. Dropping the old hashing code can only be done either by giving users // a way to reset their password, or by keeping track of successful password updates and dropping // old unused accounts after some time. std::string JSI_Lobby::EncryptPassword(const std::string& password, const std::string& username) { ENSURE(sodium_init() >= 0); const int DIGESTSIZE = crypto_hash_sha256_BYTES; const int ITERATIONS = 1337; cassert(DIGESTSIZE == 32); static const unsigned char salt_base[DIGESTSIZE] = { 244, 243, 249, 244, 32, 33, 34, 35, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 133, 123, 234, 123 }; // initialize the salt buffer unsigned char salt_buffer[DIGESTSIZE] = {0}; crypto_hash_sha256_state state; crypto_hash_sha256_init(&state); crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base)); crypto_hash_sha256_update(&state, (unsigned char*)username.c_str(), username.length()); crypto_hash_sha256_final(&state, salt_buffer); // PBKDF2 to create the buffer unsigned char encrypted[DIGESTSIZE]; pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS); return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase(); } std::wstring JSI_Lobby::EncryptPassword(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& pass, const std::wstring& user) { return wstring_from_utf8(JSI_Lobby::EncryptPassword(utf8_from_wstring(pass), utf8_from_wstring(user))); } std::wstring JSI_Lobby::LobbyGetRoomSubject(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) { if (!g_XmppClient) return L""; - std::string subject; - g_XmppClient->GetSubject(subject); - return wstring_from_utf8(subject); + return g_XmppClient->GetSubject(); } #endif Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.h =================================================================== --- ps/trunk/source/lobby/scripting/JSInterface_Lobby.h (revision 22890) +++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.h (revision 22891) @@ -1,72 +1,72 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_JSI_LOBBY #define INCLUDED_JSI_LOBBY #include "lib/config2.h" #include "scriptinterface/ScriptInterface.h" #include namespace JSI_Lobby { void RegisterScriptFunctions(const ScriptInterface& scriptInterface); bool HasXmppClient(ScriptInterface::CxPrivate* pCxPrivate); bool IsRankedGame(ScriptInterface::CxPrivate* pCxPrivate); void SetRankedGame(ScriptInterface::CxPrivate* pCxPrivate, bool isRanked); #if CONFIG2_LOBBY void StartXmppClient(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize); void StartRegisterXmppClient(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& username, const std::wstring& password); void StopXmppClient(ScriptInterface::CxPrivate* pCxPrivate); void ConnectXmppClient(ScriptInterface::CxPrivate* pCxPrivate); void DisconnectXmppClient(ScriptInterface::CxPrivate* pCxPrivate); bool IsXmppClientConnected(ScriptInterface::CxPrivate* pCxPrivate); void SendGetBoardList(ScriptInterface::CxPrivate* pCxPrivate); void SendGetProfile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& player); void SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data); void SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data); void SendUnregisterGame(ScriptInterface::CxPrivate* pCxPrivate); void SendChangeStateGame(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nbp, const std::wstring& players); JS::Value GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate); JS::Value GetGameList(ScriptInterface::CxPrivate* pCxPrivate); JS::Value GetBoardList(ScriptInterface::CxPrivate* pCxPrivate); JS::Value GetProfile(ScriptInterface::CxPrivate* pCxPrivate); JS::Value LobbyGuiPollNewMessage(ScriptInterface::CxPrivate* pCxPrivate); JS::Value LobbyGuiPollHistoricMessages(ScriptInterface::CxPrivate* pCxPrivate); bool LobbyGuiPollHasPlayerListUpdate(ScriptInterface::CxPrivate* pCxPrivate); void LobbySendMessage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& message); void LobbySetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& presence); void LobbySetNick(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick); std::wstring LobbyGetNick(ScriptInterface::CxPrivate* pCxPrivate); void LobbyKick(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick, const std::wstring& reason); void LobbyBan(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick, const std::wstring& reason); - std::wstring LobbyGetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname); - std::wstring LobbyGetPlayerRole(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname); + const char* LobbyGetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname); + const char* LobbyGetPlayerRole(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname); std::wstring LobbyGetRoomSubject(ScriptInterface::CxPrivate* pCxPrivate); // Non-public secure PBKDF2 hash function with salting and 1,337 iterations std::string EncryptPassword(const std::string& password, const std::string& username); // Public hash interface. std::wstring EncryptPassword(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& pass, const std::wstring& user); #endif // CONFIG2_LOBBY } #endif // INCLUDED_JSI_LOBBY