2025-06-03 23:02:00 -07:00
|
|
|
/* Copyright (C) 2026 Wildfire Games.
|
2023-12-02 16:30:12 -08:00
|
|
|
* This file is part of 0 A.D.
|
2009-04-18 10:00:33 -07:00
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
2009-04-18 10:00:33 -07:00
|
|
|
* 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.
|
|
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
2009-04-18 10:00:33 -07:00
|
|
|
* 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
|
2023-12-02 16:30:12 -08:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
2009-04-18 10:00:33 -07:00
|
|
|
*/
|
|
|
|
|
|
2009-04-11 10:00:39 -07:00
|
|
|
#include "precompiled.h"
|
2010-06-07 15:19:05 -07:00
|
|
|
|
|
|
|
|
#include "NetServer.h"
|
|
|
|
|
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "lib/code_generation.h"
|
|
|
|
|
#include "lib/debug.h"
|
2016-05-08 09:25:00 -07:00
|
|
|
#include "lib/external_libraries/enet.h"
|
2026-05-20 12:12:39 -07:00
|
|
|
#include "lib/hash.h"
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "lib/secure_crt.h"
|
|
|
|
|
#include "lib/status.h"
|
2018-08-25 07:34:30 -07:00
|
|
|
#include "lib/types.h"
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "lib/utf8.h"
|
|
|
|
|
#include "network/FSM.h"
|
|
|
|
|
#include "network/NetEnet.h"
|
|
|
|
|
#include "network/NetFileTransfer.h"
|
|
|
|
|
#include "network/NetHost.h"
|
|
|
|
|
#include "network/NetMessage.h"
|
|
|
|
|
#include "network/NetProtocol.h"
|
|
|
|
|
#include "network/NetServerTurnManager.h"
|
2026-03-07 06:52:39 -08:00
|
|
|
#include "network/NetServerSession.h"
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "network/NetStats.h"
|
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-05-31 23:33:52 -07:00
|
|
|
#include "network/StunClient.h"
|
2010-06-07 15:19:05 -07:00
|
|
|
#include "ps/CLogger.h"
|
2014-11-17 15:29:49 -08:00
|
|
|
#include "ps/ConfigDB.h"
|
2017-10-24 15:05:24 -07:00
|
|
|
#include "ps/GUID.h"
|
2026-05-20 12:12:39 -07:00
|
|
|
#include "ps/GameSetup/CmdLineArgs.h"
|
|
|
|
|
#include "ps/GameSetup/Paths.h"
|
2021-05-18 07:47:36 -07:00
|
|
|
#include "ps/Hashing.h"
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "ps/ProfileViewer.h"
|
|
|
|
|
#include "ps/Profiler2.h"
|
2021-01-10 00:39:54 -08:00
|
|
|
#include "ps/Threading.h"
|
2025-07-06 11:15:27 -07:00
|
|
|
#include "ps/algorithm.h"
|
|
|
|
|
#include "scriptinterface/JSON.h"
|
|
|
|
|
#include "scriptinterface/Object.h"
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
#include "scriptinterface/Context.h"
|
|
|
|
|
#include "scriptinterface/Interface.h"
|
|
|
|
|
#include "scriptinterface/Request.h"
|
Use turn_id_t (int32_t) for turn-count across simulation, network and replay
CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).
Add (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).
Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.
Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.
Fixes #8718
2026-06-26 21:04:27 -07:00
|
|
|
#include "simulation2/helpers/SimulationCommand.h"
|
2017-01-23 18:04:50 -08:00
|
|
|
#include "simulation2/system/TurnManager.h"
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2025-07-06 11:15:27 -07:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cstring>
|
2026-08-03 08:44:20 -07:00
|
|
|
#include <fmt/format.h>
|
2025-07-06 11:15:27 -07:00
|
|
|
#include <functional>
|
2026-05-20 12:12:39 -07:00
|
|
|
#include <gnutls/crypto.h>
|
|
|
|
|
#include <gnutls/gnutls.h>
|
|
|
|
|
#include <gnutls/x509.h>
|
2025-07-06 11:15:27 -07:00
|
|
|
#include <iterator>
|
|
|
|
|
#include <memory>
|
2026-05-20 12:12:39 -07:00
|
|
|
#include <netdb.h>
|
|
|
|
|
#include <ngtcp2/ngtcp2.h>
|
|
|
|
|
#include <ngtcp2/ngtcp2_crypto.h>
|
|
|
|
|
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
|
|
|
|
|
#include <numeric>
|
|
|
|
|
#include <poll.h>
|
|
|
|
|
#include <random>
|
|
|
|
|
#include <ranges>
|
2025-08-10 10:59:02 -07:00
|
|
|
#include <set>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
2013-12-18 08:08:56 -08:00
|
|
|
#if CONFIG2_MINIUPNPC
|
2025-07-06 11:15:27 -07:00
|
|
|
#include <miniupnpc/igd_desc_parse.h>
|
2013-12-12 18:23:02 -08:00
|
|
|
#include <miniupnpc/miniupnpc.h>
|
|
|
|
|
#include <miniupnpc/upnpcommands.h>
|
2025-07-06 11:15:27 -07:00
|
|
|
#include <miniupnpc/upnpdev.h>
|
2013-12-12 18:23:02 -08:00
|
|
|
#include <miniupnpc/upnperrors.h>
|
2013-12-18 08:08:56 -08:00
|
|
|
#endif
|
2018-08-25 07:34:30 -07:00
|
|
|
|
2026-08-03 08:44:20 -07:00
|
|
|
#if FMT_VERSION >= 80000
|
|
|
|
|
#include <fmt/xchar.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-03-13 09:52:00 -07:00
|
|
|
/**
|
|
|
|
|
* Number of peers to allocate for the enet host.
|
|
|
|
|
* Limited by ENET_PROTOCOL_MAXIMUM_PEER_ID (4096).
|
|
|
|
|
*
|
2016-09-11 15:46:00 -07:00
|
|
|
* At most 8 players, 32 observers and 1 temporary connection to send the "server full" disconnect-reason.
|
2016-03-13 09:52:00 -07:00
|
|
|
*/
|
2016-09-11 15:46:00 -07:00
|
|
|
#define MAX_CLIENTS 41
|
2016-03-13 09:52:00 -07:00
|
|
|
|
2021-01-06 07:26:11 -08:00
|
|
|
constexpr int CHANNEL_COUNT = 1;
|
2021-01-26 12:20:48 -08:00
|
|
|
constexpr int FAILED_PASSWORD_TRIES_BEFORE_BAN = 3;
|
2011-05-29 13:57:28 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
/**
|
|
|
|
|
* enet_host_service timeout (msecs).
|
|
|
|
|
* Smaller numbers may hurt performance; larger numbers will
|
|
|
|
|
* hurt latency responding to messages from game thread.
|
|
|
|
|
*/
|
|
|
|
|
static const int HOST_SERVICE_TIMEOUT = 50;
|
|
|
|
|
|
Increase MP Command delay to 4 turns, decrease MP turns to 200ms.
To hide network latency, MP turns send commands not for the next turn
but N turns after that (introduced in c684c211a2).
Further, MP turn length was increased to 500ms compared to 200ms SP
turns (introduced in 6a15b78c98).
Unfortunately, increasing MP turn length has negative consequences:
- makes pathfinding/unit motion much worse & unit behaviour worse in
general.
- makes the game more 'lag-spikey', since computations are done less
often, but thus then can take more time.
This diff essentially reverts 6a15b78c98, instead increasing
COMMAND_DELAY from 2 to 4 in MP. This:
- Reduces the 'inherent command lag' in MP from 1000ms to 800ms
- Increases the lag range at which MP will run smoothtly from 500ms to
600ms
- makes SP and MP turns behave identically again, removing the
hindrances described above.
As a side effect, single-player was not actually using COMMAND_DELAY,
this is now done (can be used to simulate MP command lag).
Refs #3752
Differential Revision: https://code.wildfiregames.com/D3275
This was SVN commit r25001.
2021-03-03 13:02:57 -08:00
|
|
|
/**
|
|
|
|
|
* Once ping goes above turn length * command delay,
|
|
|
|
|
* the game will start 'freezing' for other clients while we catch up.
|
|
|
|
|
* Since commands are sent client -> server -> client, divide by 2.
|
|
|
|
|
* (duplicated in NetServer.cpp to avoid having to fetch the constants in a header file)
|
|
|
|
|
*/
|
|
|
|
|
constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2;
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CNetServer* g_NetServer = NULL;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
namespace
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
// static CStr DebugName(CNetServerSession* session)
|
|
|
|
|
// {
|
|
|
|
|
// if (session == NULL)
|
|
|
|
|
// return "[unknown host]";
|
|
|
|
|
// if (session->GetGUID().empty())
|
|
|
|
|
// return "[unauthed host]";
|
|
|
|
|
// return "[" + session->GetGUID().substr(0, 8) + "...]";
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
struct CidHash
|
|
|
|
|
{
|
|
|
|
|
std::size_t operator()(const ngtcp2_cid& cid) const
|
|
|
|
|
{
|
|
|
|
|
return std::accumulate(cid.data, cid.data + cid.datalen, static_cast<std::size_t>(0),
|
|
|
|
|
[](std::size_t carry, const std::uint8_t byte)
|
|
|
|
|
{
|
|
|
|
|
hash_combine(carry, byte);
|
|
|
|
|
return carry;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
struct CidEqual
|
|
|
|
|
{
|
|
|
|
|
bool operator()(const ngtcp2_cid& a, const ngtcp2_cid& b) const
|
|
|
|
|
{
|
|
|
|
|
return ngtcp2_cid_eq(&a, &b);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::size_t ReceivePackage(const int fd, std::span<std::uint8_t> data, AddressStorage& remoteAddress)
|
|
|
|
|
{
|
|
|
|
|
iovec iov{
|
|
|
|
|
.iov_base{data.data()},
|
|
|
|
|
.iov_len{data.size()}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
msghdr msg{};
|
|
|
|
|
msg.msg_name = &remoteAddress.address.sa;
|
|
|
|
|
msg.msg_namelen = remoteAddress.length;
|
|
|
|
|
msg.msg_iov = &iov;
|
|
|
|
|
msg.msg_iovlen = 1;
|
|
|
|
|
|
|
|
|
|
ssize_t ret;
|
|
|
|
|
do
|
|
|
|
|
ret = recvmsg(fd, &msg, MSG_DONTWAIT);
|
|
|
|
|
while (ret < 0 && errno == EINTR);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
throw std::system_error{errno, std::generic_category(), "recvmsg"};
|
|
|
|
|
|
|
|
|
|
remoteAddress.length = msg.msg_namelen;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GetRandomCid(ngtcp2_cid* cid)
|
|
|
|
|
{
|
|
|
|
|
std::array<std::uint8_t, NGTCP2_MAX_CIDLEN> buf;
|
|
|
|
|
const int ret{gnutls_rnd(GNUTLS_RND_RANDOM, buf.data(), buf.size())};
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
throw std::runtime_error{fmt::format("gnutls_rnd: {}", gnutls_strerror(ret))};
|
|
|
|
|
ngtcp2_cid_init(cid, buf.data(), buf.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ResolveAndBind(const char *port, AddressStorage& localAddress)
|
|
|
|
|
{
|
|
|
|
|
addrinfo hints{};
|
|
|
|
|
// IPv4 clients can connect to a IPv6 socket.
|
|
|
|
|
hints.ai_family = AF_INET6;
|
|
|
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
|
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
|
|
|
|
|
|
addrinfo* res;
|
|
|
|
|
if (getaddrinfo(nullptr, port, &hints, &res))
|
|
|
|
|
return -1;
|
|
|
|
|
std::unique_ptr<addrinfo, decltype(&freeaddrinfo)> infoList{res, &freeaddrinfo};
|
|
|
|
|
|
|
|
|
|
int fd;
|
|
|
|
|
addrinfo* rp;
|
|
|
|
|
for (rp = res; rp; rp = rp->ai_next)
|
|
|
|
|
{
|
|
|
|
|
fd = socket(AF_INET6, rp->ai_socktype, rp->ai_protocol);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Allow IPv4 using the same socket.
|
|
|
|
|
const int onlyIPv6{0};
|
|
|
|
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &onlyIPv6, sizeof(onlyIPv6)))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen))
|
|
|
|
|
{
|
|
|
|
|
close(fd);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!rp)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
localAddress.address.in6 = reinterpret_cast<ngtcp2_sockaddr_in6&>(*rp->ai_addr);
|
|
|
|
|
localAddress.length = rp->ai_addrlen;
|
|
|
|
|
return fd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenerateKeyfiles(const std::filesystem::path& privatekey, const std::filesystem::path& certificate)
|
|
|
|
|
{
|
|
|
|
|
constexpr auto binaryOverrideMode = std::ios::binary | std::ios::trunc;
|
|
|
|
|
gnutls_x509_privkey_t newKey;
|
|
|
|
|
if (const int ret{gnutls_x509_privkey_init(&newKey)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error initializing privatkey: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
std::unique_ptr<gnutls_x509_privkey_int, decltype(&gnutls_x509_privkey_deinit)> keyDeleter(
|
|
|
|
|
newKey, gnutls_x509_privkey_deinit);
|
|
|
|
|
|
|
|
|
|
if (const int ret{gnutls_x509_privkey_generate(newKey, GNUTLS_PK_EDDSA_ED25519, 256, 0)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error generating privatkey: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gnutls_datum_t exportedKey;
|
|
|
|
|
if (const int ret{gnutls_x509_privkey_export2_pkcs8(newKey, GNUTLS_X509_FMT_DER, nullptr, 0,
|
|
|
|
|
&exportedKey)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error exporting privatkey: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
const std::unique_ptr<unsigned char, decltype(gnutls_free)> exportedKeyDeleter{exportedKey.data,
|
|
|
|
|
gnutls_free};
|
|
|
|
|
|
|
|
|
|
std::ofstream{privatekey, binaryOverrideMode}.write(
|
|
|
|
|
reinterpret_cast<const char*>(exportedKey.data), exportedKey.size);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gnutls_x509_crt_t newCertificate;
|
|
|
|
|
if (const int ret{gnutls_x509_crt_init(&newCertificate)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error initializing certificate: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
std::unique_ptr<gnutls_x509_crt_int, decltype(&gnutls_x509_crt_deinit)> certificateDeleter(
|
|
|
|
|
newCertificate, gnutls_x509_crt_deinit);
|
|
|
|
|
|
|
|
|
|
std::array<unsigned char, 20> serialNumber;
|
|
|
|
|
auto generator = [&, randomDevice = std::random_device{},
|
|
|
|
|
distribution = std::uniform_int_distribution<unsigned char>{}]() mutable
|
|
|
|
|
{
|
|
|
|
|
return distribution(randomDevice);
|
|
|
|
|
};
|
|
|
|
|
std::ranges::generate(serialNumber, std::ref(generator));
|
|
|
|
|
std::get<0>(serialNumber) &= ~(1 << 8);
|
|
|
|
|
if (const int ret{gnutls_x509_crt_set_serial(newCertificate, serialNumber.data(),
|
|
|
|
|
serialNumber.size())})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error setting serial number: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const int ret{gnutls_x509_crt_set_activation_time(newCertificate, 0)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error setting activation time: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
if (const int ret{gnutls_x509_crt_set_expiration_time(newCertificate, -1)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error setting expiration time: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const int ret{gnutls_x509_crt_set_key(newCertificate, newKey)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error setting certificate key: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const int ret{gnutls_x509_crt_sign(newCertificate, newCertificate, newKey)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error signing certificate: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gnutls_datum_t exportedCertificate;
|
|
|
|
|
if (const int ret{gnutls_x509_crt_export2(newCertificate, GNUTLS_X509_FMT_DER,
|
|
|
|
|
&exportedCertificate)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("Error exporting certificate: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
const std::unique_ptr<unsigned char, decltype(gnutls_free)> exportedCertificateDeleter{
|
|
|
|
|
exportedCertificate.data, gnutls_free};
|
|
|
|
|
|
|
|
|
|
std::ofstream{certificate, binaryOverrideMode}.write(
|
|
|
|
|
reinterpret_cast<const char*>(exportedCertificate.data), exportedCertificate.size);
|
2010-06-30 14:41:04 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
std::unique_ptr<gnutls_certificate_credentials_st, CredentialsDeleter> CreateTlsServerCredentials()
|
|
|
|
|
{
|
|
|
|
|
gnutls_certificate_credentials_t temp;
|
|
|
|
|
if (const int ret{gnutls_certificate_allocate_credentials(&temp)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("gnutls_certificate_allocate_credentials: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
std::unique_ptr<gnutls_certificate_credentials_st, CredentialsDeleter> cred{temp};
|
|
|
|
|
|
|
|
|
|
const auto folder = (Paths(g_CmdLineArgs).UserData() / "encryption").fileSystemPath();
|
|
|
|
|
std::filesystem::create_directory(folder);
|
|
|
|
|
|
|
|
|
|
const auto privatekeyPath{folder / "privatekey.der"};
|
|
|
|
|
const auto certificatePath{folder / "certificate.der"};
|
|
|
|
|
|
|
|
|
|
if (!std::filesystem::is_regular_file(privatekeyPath) ||
|
|
|
|
|
!std::filesystem::is_regular_file(certificatePath))
|
|
|
|
|
{
|
|
|
|
|
GenerateKeyfiles(privatekeyPath, certificatePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const int ret{gnutls_certificate_set_x509_key_file(cred.get(), certificatePath.c_str(),
|
|
|
|
|
privatekeyPath.c_str(), GNUTLS_X509_FMT_DER)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("gnutls_certificate_set_x509_key_file: {}",
|
|
|
|
|
gnutls_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cred;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngtcp2_settings InitSettings()
|
|
|
|
|
{
|
|
|
|
|
ngtcp2_settings settings;
|
|
|
|
|
ngtcp2_settings_default(&settings);
|
|
|
|
|
settings.initial_ts = timestamp();
|
|
|
|
|
return settings;
|
|
|
|
|
}
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
|
|
class CNetServerWorker::Quic
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit Quic(const std::uint16_t port):
|
|
|
|
|
m_SocketFd{ResolveAndBind(std::to_string(port).c_str(), m_LocalAddress)}
|
|
|
|
|
{}
|
|
|
|
|
~Quic()
|
|
|
|
|
{
|
|
|
|
|
if (m_SocketFd >= 0)
|
|
|
|
|
close(m_SocketFd);
|
|
|
|
|
}
|
|
|
|
|
AddressStorage m_LocalAddress;
|
|
|
|
|
int m_SocketFd;
|
|
|
|
|
std::unique_ptr<gnutls_certificate_credentials_st, CredentialsDeleter> m_Credentials{
|
|
|
|
|
CreateTlsServerCredentials()};
|
|
|
|
|
ngtcp2_settings m_Settings{InitSettings()};
|
|
|
|
|
|
|
|
|
|
void HandleIncoming(CNetServerWorker& server)
|
|
|
|
|
{
|
|
|
|
|
std::array<std::uint8_t, MAX_UDP_PAYLOAD_SIZE> buf;
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
AddressStorage remoteAddress;
|
|
|
|
|
|
|
|
|
|
std::size_t n_read;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
n_read = ReceivePackage(m_SocketFd, buf, remoteAddress);
|
|
|
|
|
}
|
|
|
|
|
catch (std::system_error& e)
|
|
|
|
|
{
|
|
|
|
|
if (e.code().value() == EAGAIN || e.code().value() == EWOULDBLOCK)
|
|
|
|
|
return;
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngtcp2_version_cid version;
|
|
|
|
|
if (const int ret{ngtcp2_pkt_decode_version_cid(&version, buf.data(), n_read,
|
|
|
|
|
NGTCP2_MAX_CIDLEN)})
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error{fmt::format("ngtcp2_pkt_decode_version_cid: {}",
|
|
|
|
|
ngtcp2_strerror(ret))};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ngtcp2_addr remote{
|
|
|
|
|
.addr{&remoteAddress.address.sa},
|
|
|
|
|
.addrlen{remoteAddress.length}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Find any existing connection by DCID */
|
|
|
|
|
ngtcp2_cid tempCid;
|
|
|
|
|
ngtcp2_cid_init(&tempCid, version.dcid, version.dcidlen);
|
|
|
|
|
|
|
|
|
|
const auto connectionIter = std::ranges::find_if(server.m_Sessions,
|
|
|
|
|
[&tempCid](const std::vector<ngtcp2_cid> association)
|
|
|
|
|
{
|
|
|
|
|
return std::ranges::any_of(association, [&tempCid](const ngtcp2_cid& elem)
|
|
|
|
|
{
|
|
|
|
|
return ngtcp2_cid_eq(&elem, &tempCid);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[](auto& connection)
|
|
|
|
|
{
|
|
|
|
|
const std::size_t amount{ngtcp2_conn_get_scid(
|
|
|
|
|
connection->m_Connection.m_QuicConnection.get(), nullptr)};
|
|
|
|
|
std::vector<ngtcp2_cid> cids(amount);
|
|
|
|
|
ngtcp2_conn_get_scid(connection->m_Connection.m_QuicConnection.get(),
|
|
|
|
|
cids.data());
|
|
|
|
|
return cids;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const bool existing{connectionIter != server.m_Sessions.end()};
|
|
|
|
|
if (!existing)
|
|
|
|
|
{
|
|
|
|
|
ngtcp2_pkt_hd header;
|
|
|
|
|
if (ngtcp2_accept(&header, buf.data(), n_read))
|
|
|
|
|
throw std::invalid_argument{"Failed parsing package header"};
|
|
|
|
|
|
|
|
|
|
ngtcp2_cid newScid;
|
|
|
|
|
GetRandomCid(&newScid);
|
|
|
|
|
|
|
|
|
|
const ngtcp2_path path{
|
|
|
|
|
.local{
|
|
|
|
|
.addr{&m_LocalAddress.address.sa},
|
|
|
|
|
.addrlen{m_LocalAddress.length}
|
|
|
|
|
},
|
|
|
|
|
.remote{remote}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
server.m_Sessions.push_back(std::make_unique<CNetServerSession>(server, m_SocketFd,
|
|
|
|
|
m_Settings, m_Credentials.get(), header, newScid, path));
|
|
|
|
|
server.SetupSession(server.m_Sessions.back().get());
|
|
|
|
|
}
|
|
|
|
|
auto& session{existing ? *connectionIter : server.m_Sessions.back()};
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
session->m_Connection.Read(remote, {buf.data(), n_read});
|
|
|
|
|
}
|
|
|
|
|
catch (const std::system_error&)
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::runtime_error&)
|
|
|
|
|
{
|
|
|
|
|
const auto session = existing ? std::move(*connectionIter) :
|
|
|
|
|
std::move(server.m_Sessions.back());
|
|
|
|
|
if (existing)
|
|
|
|
|
server.m_Sessions.erase(connectionIter);
|
|
|
|
|
else
|
|
|
|
|
server.m_Sessions.pop_back();
|
|
|
|
|
|
|
|
|
|
session->Update(static_cast<uint>(NMT_CONNECTION_LOST), nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
/*
|
|
|
|
|
* XXX: We use some non-threadsafe functions from the worker thread.
|
2024-12-10 02:29:34 -08:00
|
|
|
* See https://gitea.wildfiregames.com/0ad/0ad/issues/654
|
2010-10-31 15:00:28 -07:00
|
|
|
*/
|
|
|
|
|
|
2026-03-03 10:46:40 -08:00
|
|
|
CNetServerWorker::CNetServerWorker(const bool continueSavedGame, std::uint16_t port,
|
|
|
|
|
const bool useLobbyAuth, std::string password, std::string controllerSecret,
|
|
|
|
|
std::string initAttributes) :
|
2024-08-31 01:54:56 -07:00
|
|
|
m_ContinuesSavedGame{continueSavedGame},
|
2026-03-02 10:03:19 -08:00
|
|
|
m_LobbyAuth{useLobbyAuth},
|
2026-03-02 10:22:44 -08:00
|
|
|
m_ControllerSecret{std::move(controllerSecret)},
|
2026-03-02 10:03:19 -08:00
|
|
|
m_Password{std::move(password)}
|
2010-06-30 14:41:04 -07:00
|
|
|
{
|
2026-03-03 10:46:40 -08:00
|
|
|
// Bind to default host
|
2026-05-20 12:12:39 -07:00
|
|
|
// ENetAddress addr;
|
|
|
|
|
// addr.host = ENET_HOST_ANY;
|
|
|
|
|
// addr.port = port;
|
2026-03-03 10:46:40 -08:00
|
|
|
|
|
|
|
|
// Create ENet server
|
2026-05-20 12:12:39 -07:00
|
|
|
// m_Host.reset(PS::Enet::CreateHost(&addr, MAX_CLIENTS, CHANNEL_COUNT));
|
|
|
|
|
// if (!m_Host)
|
|
|
|
|
// {
|
|
|
|
|
// LOGERROR("Net server: enet_host_create failed");
|
|
|
|
|
// throw std::runtime_error{"Failed to start server"};
|
|
|
|
|
// }
|
2026-03-03 10:46:40 -08:00
|
|
|
|
2026-04-16 10:18:12 -07:00
|
|
|
m_Stats = std::make_unique<CNetStatsTable>();
|
2026-03-03 10:46:40 -08:00
|
|
|
if (CProfileViewer::IsInitialised())
|
2026-04-16 10:18:12 -07:00
|
|
|
g_ProfileViewer.AddRootTable(m_Stats.get());
|
2026-03-03 10:46:40 -08:00
|
|
|
|
|
|
|
|
m_State = SERVER_STATE_PREGAME;
|
|
|
|
|
|
|
|
|
|
// Launch the worker thread
|
|
|
|
|
m_WorkerThread = std::thread(Threading::HandleExceptions<RunThread>::Wrapper, this,
|
2026-05-20 12:12:39 -07:00
|
|
|
std::move(initAttributes), port);
|
2026-03-03 10:46:40 -08:00
|
|
|
|
|
|
|
|
#if CONFIG2_MINIUPNPC
|
|
|
|
|
// Launch the UPnP thread
|
|
|
|
|
m_UPnPThread = std::thread(Threading::HandleExceptions<SetupUPnP>::Wrapper, port);
|
|
|
|
|
#endif
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker::~CNetServerWorker()
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2026-03-03 10:46:40 -08:00
|
|
|
// Tell the thread to shut down
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2026-03-03 10:46:40 -08:00
|
|
|
std::lock_guard<std::mutex> lock(m_WorkerMutex);
|
|
|
|
|
m_Shutdown = true;
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
|
|
|
|
|
2026-03-03 10:46:40 -08:00
|
|
|
// Wait for it to shut down cleanly
|
|
|
|
|
m_WorkerThread.join();
|
|
|
|
|
|
2019-08-16 10:38:58 -07:00
|
|
|
#if CONFIG2_MINIUPNPC
|
|
|
|
|
if (m_UPnPThread.joinable())
|
|
|
|
|
m_UPnPThread.detach();
|
|
|
|
|
#endif
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-27 09:44:59 -08:00
|
|
|
|
2021-05-18 07:47:36 -07:00
|
|
|
bool CNetServerWorker::CheckPassword(const std::string& password, const std::string& salt) const
|
|
|
|
|
{
|
|
|
|
|
return HashCryptographically(m_Password, salt) == password;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-18 08:08:56 -08:00
|
|
|
#if CONFIG2_MINIUPNPC
|
2025-02-20 12:48:00 -08:00
|
|
|
void CNetServerWorker::SetupUPnP(const u16 port)
|
2013-12-17 06:21:49 -08:00
|
|
|
{
|
2020-04-06 13:20:27 -07:00
|
|
|
debug_SetThreadName("UPnP");
|
|
|
|
|
|
2013-12-12 18:23:02 -08:00
|
|
|
// Values we want to set.
|
2013-12-14 17:02:26 -08:00
|
|
|
char psPort[6];
|
2025-02-20 12:48:00 -08:00
|
|
|
sprintf_s(psPort, ARRAY_SIZE(psPort), "%d", port);
|
2013-12-14 17:02:26 -08:00
|
|
|
const char* leaseDuration = "0"; // Indefinite/permanent lease duration.
|
|
|
|
|
const char* description = "0AD Multiplayer";
|
|
|
|
|
const char* protocall = "UDP";
|
2013-12-12 18:23:02 -08:00
|
|
|
char internalIPAddress[64];
|
|
|
|
|
char externalIPAddress[40];
|
2019-08-16 11:07:23 -07:00
|
|
|
|
2013-12-12 18:23:02 -08:00
|
|
|
// Variables to hold the values that actually get set.
|
|
|
|
|
char intClient[40];
|
|
|
|
|
char intPort[6];
|
|
|
|
|
char duration[16];
|
2019-08-16 11:07:23 -07:00
|
|
|
|
2013-12-12 18:23:02 -08:00
|
|
|
// Intermediate variables.
|
2019-08-16 11:07:23 -07:00
|
|
|
bool allocatedUrls = false;
|
2013-12-12 18:23:02 -08:00
|
|
|
struct UPNPUrls urls;
|
|
|
|
|
struct IGDdatas data;
|
2014-08-06 07:11:04 -07:00
|
|
|
struct UPNPDev* devlist = NULL;
|
2013-12-17 09:03:49 -08:00
|
|
|
|
2019-08-16 11:07:23 -07:00
|
|
|
// Make sure everything is properly freed.
|
|
|
|
|
std::function<void()> freeUPnP = [&allocatedUrls, &urls, &devlist]()
|
|
|
|
|
{
|
|
|
|
|
if (allocatedUrls)
|
|
|
|
|
FreeUPNPUrls(&urls);
|
|
|
|
|
freeUPNPDevlist(devlist);
|
|
|
|
|
// IGDdatas does not need to be freed according to UPNP_GetIGDFromUrl
|
|
|
|
|
};
|
|
|
|
|
|
2013-12-17 09:03:49 -08:00
|
|
|
// Cached root descriptor URL.
|
2025-01-29 07:38:52 -08:00
|
|
|
const std::string rootDescURL{g_ConfigDB.Get("network.upnprootdescurl", std::string{})};
|
2013-12-29 15:56:18 -08:00
|
|
|
if (!rootDescURL.empty())
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: attempting to use cached root descriptor URL: %s", rootDescURL.c_str());
|
2013-12-17 09:03:49 -08:00
|
|
|
|
2014-08-06 07:11:04 -07:00
|
|
|
int ret = 0;
|
2013-12-17 06:21:49 -08:00
|
|
|
|
2014-08-06 07:11:04 -07:00
|
|
|
// Try a cached URL first
|
Fix adding port forwardings using UPnP
The UPnP implementation included a combination of two subtle bugs, which
resulted in failure to create port forwardings every time after the
first one.
When using UPnP, the internet gateway to create the port forwardings at
needs to discovered. As that takes a while, the its root descriptor URL
was supposed to be cached after successful discovery in the user config
in "network.upnprootdescurl". However, instead of caching the root
descriptor URL, the control URL got cached. That caused following
requests to the root descriptor URL to fail, as they ended up at the
control URL instead. As such requests might also fail when the network
topology changed, the code was supposed to fall back to discovering the
internet gateway again when the cached one didn't work. However, due to
the inner workings of miniupnpc the request using the cached root
descriptor URL didn't result in an error, so the new discovery was never
triggered. As the wrong value was persisted in the user config there was
also no way to get out of this situation again.
This commit fixes both of these bugs.
As far as I can tell these bugs existed since the introduction of the
caching of the root descriptor URL in 0ba25e9968, which means creating
port forwardings using UPnP has been broken since Alpha 15.
2025-02-23 23:23:07 -08:00
|
|
|
if (!rootDescURL.empty() && UPNP_GetIGDFromUrl(rootDescURL.c_str(), &urls, &data, internalIPAddress, sizeof(internalIPAddress)) && strlen(data.first.controlurl) != 0)
|
2014-08-06 07:11:04 -07:00
|
|
|
{
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: using cached IGD = %s", urls.controlURL);
|
2014-08-06 07:11:04 -07:00
|
|
|
ret = 1;
|
|
|
|
|
}
|
2025-02-21 01:08:50 -08:00
|
|
|
// No cached URL, or it did not respond. Try discovering the UPnP IGD for 2 seconds.
|
2015-09-30 14:32:54 -07:00
|
|
|
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14
|
2025-02-21 01:08:50 -08:00
|
|
|
else if ((devlist = upnpDiscover(2000, 0, 0, 0, 0, 2, 0)) != NULL)
|
2015-09-30 14:32:54 -07:00
|
|
|
#else
|
2025-02-21 01:08:50 -08:00
|
|
|
else if ((devlist = upnpDiscover(2000, 0, 0, 0, 0, 0)) != NULL)
|
2015-09-30 14:32:54 -07:00
|
|
|
#endif
|
2014-08-06 07:11:04 -07:00
|
|
|
{
|
2024-07-30 11:44:38 -07:00
|
|
|
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 18
|
|
|
|
|
ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress), nullptr, 0);
|
|
|
|
|
#else
|
2014-08-06 07:11:04 -07:00
|
|
|
ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress));
|
2024-07-30 11:44:38 -07:00
|
|
|
#endif
|
2014-08-06 07:11:04 -07:00
|
|
|
allocatedUrls = ret != 0; // urls is allocated on non-zero return values
|
|
|
|
|
}
|
|
|
|
|
else
|
2013-12-17 06:21:49 -08:00
|
|
|
{
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGMESSAGE("Net server: upnpDiscover failed and no working cached URL.");
|
2019-08-16 11:07:23 -07:00
|
|
|
freeUPnP();
|
2019-08-15 02:07:16 -07:00
|
|
|
return;
|
2013-12-17 06:21:49 -08:00
|
|
|
}
|
2013-12-17 09:03:49 -08:00
|
|
|
|
2013-12-17 06:21:49 -08:00
|
|
|
switch (ret)
|
|
|
|
|
{
|
2014-08-06 07:11:04 -07:00
|
|
|
case 0:
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGMESSAGE("Net server: No IGD found");
|
2014-08-06 07:11:04 -07:00
|
|
|
break;
|
2013-12-17 06:21:49 -08:00
|
|
|
case 1:
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: found valid IGD = %s", urls.controlURL);
|
2013-12-17 06:21:49 -08:00
|
|
|
break;
|
2024-07-30 11:44:38 -07:00
|
|
|
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 18
|
2013-12-17 06:21:49 -08:00
|
|
|
case 2:
|
2024-07-30 11:44:38 -07:00
|
|
|
LOGMESSAGE("Net server: found IGD with reserved IP = %s, will try to continue anyway", urls.controlURL);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
#else
|
|
|
|
|
case 2:
|
|
|
|
|
#endif
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: found a valid, not connected IGD = %s, will try to continue anyway", urls.controlURL);
|
2013-12-17 06:21:49 -08:00
|
|
|
break;
|
2024-07-30 11:44:38 -07:00
|
|
|
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 18
|
|
|
|
|
case 4:
|
|
|
|
|
#else
|
2013-12-17 06:21:49 -08:00
|
|
|
case 3:
|
2024-07-30 11:44:38 -07:00
|
|
|
#endif
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: found a UPnP device unrecognized as IGD = %s, will try to continue anyway", urls.controlURL);
|
2013-12-17 06:21:49 -08:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
debug_warn(L"Unrecognized return value from UPNP_GetValidIGD");
|
|
|
|
|
}
|
2013-12-12 18:23:02 -08:00
|
|
|
|
2024-07-30 11:44:38 -07:00
|
|
|
// Try getting our external/internet facing IP. TODO: Display this on the game-setup page for convenience.
|
2013-12-17 06:21:49 -08:00
|
|
|
ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
|
|
|
|
|
if (ret != UPNPCOMMAND_SUCCESS)
|
2013-12-14 17:02:26 -08:00
|
|
|
{
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: GetExternalIPAddress failed with code %d (%s)", ret, strupnperror(ret));
|
2019-08-16 11:07:23 -07:00
|
|
|
freeUPnP();
|
2019-08-15 02:07:16 -07:00
|
|
|
return;
|
2013-12-17 06:21:49 -08:00
|
|
|
}
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: ExternalIPAddress = %s", externalIPAddress);
|
2013-12-12 18:23:02 -08:00
|
|
|
|
2013-12-17 06:21:49 -08:00
|
|
|
// Try to setup port forwarding.
|
|
|
|
|
ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, psPort, psPort,
|
|
|
|
|
internalIPAddress, description, protocall, 0, leaseDuration);
|
|
|
|
|
if (ret != UPNPCOMMAND_SUCCESS)
|
|
|
|
|
{
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s)",
|
2013-12-17 06:21:49 -08:00
|
|
|
psPort, psPort, internalIPAddress, ret, strupnperror(ret));
|
2019-08-16 11:07:23 -07:00
|
|
|
freeUPnP();
|
2019-08-15 02:07:16 -07:00
|
|
|
return;
|
2013-12-17 06:21:49 -08:00
|
|
|
}
|
2013-12-12 18:23:02 -08:00
|
|
|
|
2013-12-17 06:21:49 -08:00
|
|
|
// Check that the port was actually forwarded.
|
|
|
|
|
ret = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
|
|
|
|
|
data.first.servicetype,
|
|
|
|
|
psPort, protocall,
|
2015-01-05 12:05:53 -08:00
|
|
|
#if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10
|
2014-05-19 14:01:06 -07:00
|
|
|
NULL/*remoteHost*/,
|
|
|
|
|
#endif
|
2013-12-17 06:21:49 -08:00
|
|
|
intClient, intPort, NULL/*desc*/,
|
|
|
|
|
NULL/*enabled*/, duration);
|
2013-12-17 09:03:49 -08:00
|
|
|
|
2013-12-17 06:21:49 -08:00
|
|
|
if (ret != UPNPCOMMAND_SUCCESS)
|
|
|
|
|
{
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: GetSpecificPortMappingEntry() failed with code %d (%s)", ret, strupnperror(ret));
|
2019-08-16 11:07:23 -07:00
|
|
|
freeUPnP();
|
2019-08-15 02:07:16 -07:00
|
|
|
return;
|
2013-12-14 17:02:26 -08:00
|
|
|
}
|
2013-12-17 09:03:49 -08:00
|
|
|
|
2015-01-22 12:36:24 -08:00
|
|
|
LOGMESSAGE("Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s)",
|
2013-12-17 06:21:49 -08:00
|
|
|
externalIPAddress, psPort, protocall, intClient, intPort, duration);
|
2013-12-14 17:02:26 -08:00
|
|
|
|
2013-12-17 09:03:49 -08:00
|
|
|
// Cache root descriptor URL to try to avoid discovery next time.
|
Fix adding port forwardings using UPnP
The UPnP implementation included a combination of two subtle bugs, which
resulted in failure to create port forwardings every time after the
first one.
When using UPnP, the internet gateway to create the port forwardings at
needs to discovered. As that takes a while, the its root descriptor URL
was supposed to be cached after successful discovery in the user config
in "network.upnprootdescurl". However, instead of caching the root
descriptor URL, the control URL got cached. That caused following
requests to the root descriptor URL to fail, as they ended up at the
control URL instead. As such requests might also fail when the network
topology changed, the code was supposed to fall back to discovering the
internet gateway again when the cached one didn't work. However, due to
the inner workings of miniupnpc the request using the cached root
descriptor URL didn't result in an error, so the new discovery was never
triggered. As the wrong value was persisted in the user config there was
also no way to get out of this situation again.
This commit fixes both of these bugs.
As far as I can tell these bugs existed since the introduction of the
caching of the root descriptor URL in 0ba25e9968, which means creating
port forwardings using UPnP has been broken since Alpha 15.
2025-02-23 23:23:07 -08:00
|
|
|
g_ConfigDB.SetValueString(CFG_USER, "network.upnprootdescurl", urls.rootdescURL);
|
|
|
|
|
g_ConfigDB.WriteValueToFile(CFG_USER, "network.upnprootdescurl", urls.rootdescURL);
|
|
|
|
|
LOGMESSAGE("Net server: cached UPnP root descriptor URL as %s", urls.rootdescURL);
|
2013-12-12 18:23:02 -08:00
|
|
|
|
2019-08-16 11:07:23 -07:00
|
|
|
freeUPnP();
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
2013-12-18 08:08:56 -08:00
|
|
|
#endif // CONFIG2_MINIUPNPC
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
bool CNetServerWorker::SendMessage(const CNetMessage* message)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
m_Sessions.front()->SendMessage(message);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2024-11-30 02:18:24 -08:00
|
|
|
bool CNetServerWorker::Multicast(const CNetMessage* message,
|
|
|
|
|
const std::vector<NetServerSessionState>& targetStates,
|
|
|
|
|
const std::optional<std::vector<std::string>>& receivers /* = std::nullopt */)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2024-11-30 02:18:24 -08:00
|
|
|
const auto isReceiver = [&](const CNetServerSession& session)
|
|
|
|
|
{
|
|
|
|
|
if (!PS::contains(targetStates,
|
|
|
|
|
static_cast<NetServerSessionState>(session.GetCurrState())))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!receivers)
|
|
|
|
|
return true;
|
|
|
|
|
return PS::contains(*receivers, session.GetGUID());
|
|
|
|
|
};
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
bool ok = true;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2016-04-24 13:48:53 -07:00
|
|
|
// TODO: this does lots of repeated message serialisation if we have lots
|
|
|
|
|
// of remote peers; could do it more efficiently if that's a real problem
|
2017-01-25 11:04:17 -08:00
|
|
|
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : m_Sessions)
|
2024-11-30 02:18:24 -08:00
|
|
|
if (isReceiver(*session) && !session->SendMessage(message))
|
2017-01-25 11:04:17 -08:00
|
|
|
ok = false;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
return ok;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
void CNetServerWorker::RunThread(CNetServerWorker* data, const std::string& initAttributes, u16 port)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2010-10-31 15:00:28 -07:00
|
|
|
debug_SetThreadName("NetServer");
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
data->Run(initAttributes, port);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
void CNetServerWorker::Run(const std::string& initAttributes, u16 port)
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2020-11-14 02:57:50 -08:00
|
|
|
// The script context uses the profiler and therefore the thread must be registered before the context is created
|
2014-03-28 13:26:32 -07:00
|
|
|
g_Profiler2.RegisterCurrentThread("Net server");
|
2015-12-21 05:58:32 -08:00
|
|
|
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
// We create a new Script::Context for this network thread, with a single Script::Interface.
|
|
|
|
|
Script::Context netServerContext;
|
|
|
|
|
m_ScriptInterface = new Script::Interface("Engine", "Net server", netServerContext);
|
2021-03-22 03:13:27 -07:00
|
|
|
m_InitAttributes.init(m_ScriptInterface->GetGeneralJSContext(), JS::UndefinedValue());
|
2015-01-24 06:46:52 -08:00
|
|
|
|
2026-03-03 10:09:17 -08:00
|
|
|
if (!initAttributes.empty())
|
|
|
|
|
{
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
Script::Request rq(m_ScriptInterface);
|
2026-03-03 10:09:17 -08:00
|
|
|
JS::RootedValue gameAttributesVal(rq.cx);
|
|
|
|
|
Script::ParseJSON(rq, std::move(initAttributes), &gameAttributesVal);
|
|
|
|
|
m_InitAttributes = gameAttributesVal;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
Quic quic{port};
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
while (true)
|
2010-06-30 14:41:04 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
if (!RunStep(quic))
|
2010-10-31 15:00:28 -07:00
|
|
|
break;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
// Update profiler stats
|
2026-05-20 12:12:39 -07:00
|
|
|
m_Stats->LatchHostState(m_Sessions);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
2015-12-21 05:58:32 -08:00
|
|
|
|
2011-10-27 09:46:48 -07:00
|
|
|
// Clear roots before deleting their context
|
|
|
|
|
m_SavedCommands.clear();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
SAFE_DELETE(m_ScriptInterface);
|
2026-05-20 12:12:39 -07:00
|
|
|
|
|
|
|
|
for (const auto& session : m_Sessions)
|
|
|
|
|
session->Disconnect(NDR_SERVER_SHUTDOWN);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
bool CNetServerWorker::RunStep(Quic& quic)
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
|
|
|
|
// Check for messages from the game thread.
|
|
|
|
|
// (Do as little work as possible while the mutex is held open,
|
|
|
|
|
// to avoid performance problems and deadlocks.)
|
2015-12-21 05:58:32 -08:00
|
|
|
|
2024-11-23 09:36:23 -08:00
|
|
|
m_ScriptInterface->GetContext().MaybeIncrementalGC();
|
2015-12-21 05:58:32 -08:00
|
|
|
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
Script::Request rq(m_ScriptInterface);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
std::vector<bool> newStartGame;
|
2018-03-11 17:23:40 -07:00
|
|
|
std::vector<std::pair<CStr, CStr>> newLobbyAuths;
|
2010-10-31 15:00:28 -07:00
|
|
|
std::vector<u32> newTurnLength;
|
2010-06-30 14:41:04 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2019-06-06 12:30:48 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_WorkerMutex);
|
2010-07-06 12:54:17 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
if (m_Shutdown)
|
|
|
|
|
return false;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
newStartGame.swap(m_StartGameQueue);
|
2018-03-11 17:23:40 -07:00
|
|
|
newLobbyAuths.swap(m_LobbyAuthQueue);
|
2010-10-31 15:00:28 -07:00
|
|
|
newTurnLength.swap(m_TurnLengthQueue);
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
if (!newTurnLength.empty())
|
|
|
|
|
SetTurnLength(newTurnLength.back());
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2018-03-11 17:23:40 -07:00
|
|
|
while (!newLobbyAuths.empty())
|
|
|
|
|
{
|
|
|
|
|
const std::pair<CStr, CStr>& auth = newLobbyAuths.back();
|
|
|
|
|
ProcessLobbyAuth(auth.first, auth.second);
|
|
|
|
|
newLobbyAuths.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-27 09:46:48 -07:00
|
|
|
// Perform file transfers
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : m_Sessions)
|
2016-04-24 13:48:53 -07:00
|
|
|
session->GetFileTransferer().Poll();
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2016-02-04 09:14:46 -08:00
|
|
|
CheckClientConnections();
|
|
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
pollfd pollFd{
|
|
|
|
|
.fd{quic.m_SocketFd},
|
|
|
|
|
.events{POLLIN | POLLOUT}
|
|
|
|
|
};
|
|
|
|
|
const int ready{poll(&pollFd, 1, 25)};
|
2010-10-31 15:00:28 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
if (ready < 0)
|
|
|
|
|
throw std::runtime_error{fmt::format("poll: {}", std::strerror(errno))};
|
2010-10-31 15:00:28 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
if (ready == 0)
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
for (auto& session : m_Sessions)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
ngtcp2_conn *conn = session->m_Connection.m_QuicConnection.get();
|
|
|
|
|
const int ret{ngtcp2_conn_handle_expiry(conn, timestamp())};
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("ngtcp2_conn_handle_expiry: %s", ngtcp2_strerror(ret));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2010-10-31 15:00:28 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
session->m_Connection.Write(quic.m_SocketFd);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-20 12:12:39 -07:00
|
|
|
else
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
if (pollFd.revents & POLLIN)
|
|
|
|
|
quic.HandleIncoming(*this);
|
2010-10-31 15:00:28 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
if (pollFd.revents & POLLOUT)
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2026-05-20 12:12:39 -07:00
|
|
|
for (auto& session : m_Sessions)
|
|
|
|
|
session->m_Connection.Write(quic.m_SocketFd);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
return true;
|
2010-06-30 14:41:04 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2016-02-04 09:14:46 -08:00
|
|
|
void CNetServerWorker::CheckClientConnections()
|
|
|
|
|
{
|
|
|
|
|
// Send messages at most once per second
|
|
|
|
|
std::time_t now = std::time(nullptr);
|
|
|
|
|
if (now <= m_LastConnectionCheck)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_LastConnectionCheck = now;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < m_Sessions.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
u32 lastReceived = m_Sessions[i]->GetLastReceivedTime();
|
|
|
|
|
u32 meanRTT = m_Sessions[i]->GetMeanRTT();
|
|
|
|
|
|
|
|
|
|
CNetMessage* message = nullptr;
|
|
|
|
|
|
|
|
|
|
// Report if we didn't hear from the client since few seconds
|
|
|
|
|
if (lastReceived > NETWORK_WARNING_TIMEOUT)
|
|
|
|
|
{
|
|
|
|
|
CClientTimeoutMessage* msg = new CClientTimeoutMessage();
|
|
|
|
|
msg->m_GUID = m_Sessions[i]->GetGUID();
|
|
|
|
|
msg->m_LastReceivedTime = lastReceived;
|
|
|
|
|
message = msg;
|
|
|
|
|
}
|
|
|
|
|
// Report if the client has bad ping
|
Increase MP Command delay to 4 turns, decrease MP turns to 200ms.
To hide network latency, MP turns send commands not for the next turn
but N turns after that (introduced in c684c211a2).
Further, MP turn length was increased to 500ms compared to 200ms SP
turns (introduced in 6a15b78c98).
Unfortunately, increasing MP turn length has negative consequences:
- makes pathfinding/unit motion much worse & unit behaviour worse in
general.
- makes the game more 'lag-spikey', since computations are done less
often, but thus then can take more time.
This diff essentially reverts 6a15b78c98, instead increasing
COMMAND_DELAY from 2 to 4 in MP. This:
- Reduces the 'inherent command lag' in MP from 1000ms to 800ms
- Increases the lag range at which MP will run smoothtly from 500ms to
600ms
- makes SP and MP turns behave identically again, removing the
hindrances described above.
As a side effect, single-player was not actually using COMMAND_DELAY,
this is now done (can be used to simulate MP command lag).
Refs #3752
Differential Revision: https://code.wildfiregames.com/D3275
This was SVN commit r25001.
2021-03-03 13:02:57 -08:00
|
|
|
else if (meanRTT > NETWORK_BAD_PING)
|
2016-02-04 09:14:46 -08:00
|
|
|
{
|
|
|
|
|
CClientPerformanceMessage* msg = new CClientPerformanceMessage();
|
|
|
|
|
CClientPerformanceMessage::S_m_Clients client;
|
|
|
|
|
client.m_GUID = m_Sessions[i]->GetGUID();
|
|
|
|
|
client.m_MeanRTT = meanRTT;
|
|
|
|
|
msg->m_Clients.push_back(client);
|
|
|
|
|
message = msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send to all clients except the affected one
|
2018-06-01 10:35:00 -07:00
|
|
|
// (since that will show the locally triggered warning instead).
|
|
|
|
|
// Also send it to clients that finished the loading screen while
|
|
|
|
|
// the game is still waiting for other clients to finish the loading screen.
|
2016-02-04 09:14:46 -08:00
|
|
|
if (message)
|
|
|
|
|
for (size_t j = 0; j < m_Sessions.size(); ++j)
|
2016-05-01 03:33:51 -07:00
|
|
|
{
|
|
|
|
|
if (i != j && (
|
2018-06-01 10:35:00 -07:00
|
|
|
(m_Sessions[j]->GetCurrState() == NSS_PREGAME && m_State == SERVER_STATE_PREGAME) ||
|
2016-05-01 03:33:51 -07:00
|
|
|
m_Sessions[j]->GetCurrState() == NSS_INGAME))
|
|
|
|
|
{
|
2016-02-04 09:14:46 -08:00
|
|
|
m_Sessions[j]->SendMessage(message);
|
2016-05-01 03:33:51 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-02-04 09:14:46 -08:00
|
|
|
|
|
|
|
|
SAFE_DELETE(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
void CNetServerWorker::HandleMessageReceive(CNetMessage* message, CNetServerSession* session)
|
2010-06-30 14:41:04 -07:00
|
|
|
{
|
2011-10-27 09:46:48 -07:00
|
|
|
// Handle non-FSM messages first
|
2019-09-17 07:18:46 -07:00
|
|
|
Status status = session->GetFileTransferer().HandleMessageReceive(*message);
|
2011-10-27 09:46:48 -07:00
|
|
|
if (status != INFO::SKIPPED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
|
|
|
|
|
{
|
|
|
|
|
CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
|
|
|
|
|
|
2024-08-31 01:54:56 -07:00
|
|
|
// A client requested the gamestate. Clients only request the gamestate when we sent them a
|
|
|
|
|
// JoinSyncStart or a GameSavedStart message. We only send those messages after we received the
|
|
|
|
|
// gamestate.
|
|
|
|
|
// For joins and loads the gamestate is in a different format. Send the respective one.
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2024-08-31 01:54:56 -07:00
|
|
|
session->GetFileTransferer().StartResponse(reqMessage->m_RequestID,
|
|
|
|
|
static_cast<CNetFileTransferer::RequestType>(reqMessage->m_RequestType) ==
|
|
|
|
|
CNetFileTransferer::RequestType::LOADGAME ? m_SavedState : m_JoinSyncFile);
|
2011-10-27 09:46:48 -07:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
// Update FSM
|
2025-06-26 03:35:01 -07:00
|
|
|
if (!session->Update(message->GetType(), message))
|
2015-01-22 12:31:30 -08:00
|
|
|
LOGERROR("Net server: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)session->GetCurrState());
|
2010-06-30 14:41:04 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::SetupSession(CNetServerSession* session)
|
2010-06-30 14:41:04 -07:00
|
|
|
{
|
|
|
|
|
// Set up transitions for session
|
2010-07-06 12:54:17 -07:00
|
|
|
|
|
|
|
|
session->AddTransition(NSS_UNCONNECTED, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
|
|
|
|
|
|
|
|
|
|
session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
|
2024-05-04 09:13:02 -07:00
|
|
|
session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, &OnClientHandshake, session);
|
2010-07-02 14:28:48 -07:00
|
|
|
|
2018-03-11 17:23:40 -07:00
|
|
|
session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
|
2024-05-04 09:13:02 -07:00
|
|
|
session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, &OnAuthenticate, session);
|
2018-03-11 17:23:40 -07:00
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
|
2024-05-04 09:13:02 -07:00
|
|
|
session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, &OnAuthenticate, session);
|
|
|
|
|
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, &OnDisconnect, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, &OnChat, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_READY, NSS_PREGAME, &OnReady, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_CLEAR_ALL_READY, NSS_PREGAME, &OnClearAllReady, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_SETUP, NSS_PREGAME, &OnGameSetup, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_ASSIGN_PLAYER, NSS_PREGAME, &OnAssignPlayer, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_KICKED, NSS_PREGAME, &OnKickPlayer, session);
|
|
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_START, NSS_PREGAME, &OnGameStart, session);
|
2024-08-31 01:54:56 -07:00
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_SAVED_GAME_START, NSS_PREGAME, &OnSavedGameStart, session);
|
2024-05-04 09:13:02 -07:00
|
|
|
session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, &OnLoadedGame, session);
|
|
|
|
|
|
|
|
|
|
session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_KICKED, NSS_JOIN_SYNCING, &OnKickPlayer, session);
|
|
|
|
|
session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, &OnDisconnect, session);
|
|
|
|
|
session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, &OnJoinSyncingLoadedGame, session);
|
|
|
|
|
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_REJOINED, NSS_INGAME, &OnRejoined, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_KICKED, NSS_INGAME, &OnKickPlayer, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_CLIENT_PAUSED, NSS_INGAME, &OnClientPaused, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, &OnDisconnect, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, &OnChat, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, &OnSimulationCommand, session);
|
2024-12-03 12:24:55 -08:00
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_FLARE, NSS_INGAME, &OnFlare, session);
|
2024-05-04 09:13:02 -07:00
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, &OnSyncCheck, session);
|
|
|
|
|
session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, &OnEndCommandBatch, session);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
// Set first state
|
|
|
|
|
session->SetFirstState(NSS_HANDSHAKE);
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
bool CNetServerWorker::HandleConnect(CNetServerSession* session)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2015-11-10 18:05:23 -08:00
|
|
|
if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), session->GetIPAddress()) != m_BannedIPs.end())
|
|
|
|
|
{
|
|
|
|
|
session->Disconnect(NDR_BANNED);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 09:30:38 -08:00
|
|
|
const CSrvHandshakeMessage handshake(CreateHandshake<CSrvHandshakeMessage>());
|
|
|
|
|
return session->SendMessage(&handshake);
|
2010-06-30 14:41:04 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::OnUserJoin(CNetServerSession* session)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2010-06-30 14:41:04 -07:00
|
|
|
AddPlayer(session->GetGUID(), session->GetUserName());
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CPlayerAssignmentMessage assignMessage;
|
|
|
|
|
ConstructPlayerAssignmentMessage(assignMessage);
|
|
|
|
|
session->SendMessage(&assignMessage);
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::OnUserLeave(CNetServerSession* session)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
2016-05-19 15:10:38 -07:00
|
|
|
std::vector<CStr>::iterator pausing = std::find(m_PausingPlayers.begin(), m_PausingPlayers.end(), session->GetGUID());
|
|
|
|
|
if (pausing != m_PausingPlayers.end())
|
|
|
|
|
m_PausingPlayers.erase(pausing);
|
|
|
|
|
|
2010-07-02 14:28:48 -07:00
|
|
|
RemovePlayer(session->GetGUID());
|
2010-11-02 17:21:52 -07:00
|
|
|
|
2014-06-19 20:22:40 -07:00
|
|
|
if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
|
2021-03-29 00:53:06 -07:00
|
|
|
m_ServerTurnManager->UninitialiseClient(session->GetHostID());
|
2010-11-02 17:21:52 -07:00
|
|
|
|
|
|
|
|
// TODO: ought to switch the player controlled by that client
|
|
|
|
|
// back to AI control, or something?
|
2010-07-02 14:28:48 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2014-03-29 08:09:42 -07:00
|
|
|
// Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
|
2010-06-30 14:41:04 -07:00
|
|
|
std::set<i32> usedIDs;
|
2020-12-31 06:27:02 -08:00
|
|
|
for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
|
2016-04-24 13:48:53 -07:00
|
|
|
if (p.second.m_Enabled && p.second.m_PlayerID != -1)
|
|
|
|
|
usedIDs.insert(p.second.m_PlayerID);
|
2011-10-27 09:46:48 -07:00
|
|
|
|
|
|
|
|
// If the player is rejoining after disconnecting, try to give them
|
2021-06-06 01:02:28 -07:00
|
|
|
// back their old player ID. Don't do this in pregame however,
|
|
|
|
|
// as that ID might be invalid for various reasons.
|
2011-10-27 09:46:48 -07:00
|
|
|
|
|
|
|
|
i32 playerID = -1;
|
|
|
|
|
|
2026-03-03 10:46:40 -08:00
|
|
|
if (m_State != SERVER_STATE_PREGAME)
|
2011-10-27 09:46:48 -07:00
|
|
|
{
|
2021-06-06 01:02:28 -07:00
|
|
|
// Try to match GUID first
|
|
|
|
|
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
|
2011-10-27 09:46:48 -07:00
|
|
|
{
|
2021-06-06 01:02:28 -07:00
|
|
|
if (!it->second.m_Enabled && it->first == guid && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
|
|
|
|
|
{
|
|
|
|
|
playerID = it->second.m_PlayerID;
|
|
|
|
|
m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
|
|
|
|
|
goto found;
|
|
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 01:02:28 -07:00
|
|
|
// Try to match username next
|
|
|
|
|
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
|
2011-10-27 09:46:48 -07:00
|
|
|
{
|
2021-06-06 01:02:28 -07:00
|
|
|
if (!it->second.m_Enabled && it->second.m_Name == name && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
|
|
|
|
|
{
|
|
|
|
|
playerID = it->second.m_PlayerID;
|
|
|
|
|
m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
|
|
|
|
|
goto found;
|
|
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2014-05-19 09:02:42 -07:00
|
|
|
found:
|
2010-06-30 14:41:04 -07:00
|
|
|
PlayerAssignment assignment;
|
2011-10-27 09:46:48 -07:00
|
|
|
assignment.m_Enabled = true;
|
2010-06-30 14:41:04 -07:00
|
|
|
assignment.m_Name = name;
|
|
|
|
|
assignment.m_PlayerID = playerID;
|
2014-04-26 11:34:34 -07:00
|
|
|
assignment.m_Status = 0;
|
2010-06-30 14:41:04 -07:00
|
|
|
m_PlayerAssignments[guid] = assignment;
|
|
|
|
|
|
|
|
|
|
// Send the new assignments to all currently active players
|
|
|
|
|
// (which does not include the one that's just joining)
|
|
|
|
|
SendPlayerAssignments();
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::RemovePlayer(const CStr& guid)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2011-10-27 09:46:48 -07:00
|
|
|
m_PlayerAssignments[guid].m_Enabled = false;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-07-02 14:28:48 -07:00
|
|
|
SendPlayerAssignments();
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2014-04-26 11:34:34 -07:00
|
|
|
void CNetServerWorker::ClearAllPlayerReady()
|
|
|
|
|
{
|
2017-02-10 08:09:10 -08:00
|
|
|
for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
|
|
|
|
|
if (p.second.m_Status != 2)
|
|
|
|
|
p.second.m_Status = 0;
|
2014-04-26 11:34:34 -07:00
|
|
|
|
|
|
|
|
SendPlayerAssignments();
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-04 05:08:30 -07:00
|
|
|
void CNetServerWorker::KickPlayer(const CStrW& playerName, const bool ban)
|
2015-11-10 18:05:23 -08:00
|
|
|
{
|
|
|
|
|
// Find the user with that name
|
2026-04-16 09:59:07 -07:00
|
|
|
const auto it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
|
|
|
|
|
[&](const auto& session) { return session->GetUserName() == playerName; });
|
2015-11-10 18:05:23 -08:00
|
|
|
|
|
|
|
|
// and return if no one or the host has that name
|
2021-02-27 09:44:59 -08:00
|
|
|
if (it == m_Sessions.end() || (*it)->GetGUID() == m_ControllerGUID)
|
2016-06-04 05:08:30 -07:00
|
|
|
return;
|
2015-11-10 18:05:23 -08:00
|
|
|
|
|
|
|
|
if (ban)
|
|
|
|
|
{
|
|
|
|
|
// Remember name
|
|
|
|
|
if (std::find(m_BannedPlayers.begin(), m_BannedPlayers.end(), playerName) == m_BannedPlayers.end())
|
2018-10-25 04:58:26 -07:00
|
|
|
m_BannedPlayers.push_back(m_LobbyAuth ? CStrW(playerName.substr(0, playerName.find(L" ("))) : playerName);
|
2015-11-10 18:05:23 -08:00
|
|
|
|
|
|
|
|
// Remember IP address
|
2016-05-08 09:25:00 -07:00
|
|
|
u32 ipAddress = (*it)->GetIPAddress();
|
2016-05-08 04:46:19 -07:00
|
|
|
if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), ipAddress) == m_BannedIPs.end())
|
2015-11-10 18:05:23 -08:00
|
|
|
m_BannedIPs.push_back(ipAddress);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disconnect that user
|
|
|
|
|
(*it)->Disconnect(ban ? NDR_BANNED : NDR_KICKED);
|
|
|
|
|
|
|
|
|
|
// Send message notifying other clients
|
|
|
|
|
CKickedMessage kickedMessage;
|
|
|
|
|
kickedMessage.m_Name = playerName;
|
|
|
|
|
kickedMessage.m_Ban = ban;
|
2024-11-30 02:18:24 -08:00
|
|
|
Multicast(&kickedMessage, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
|
2015-11-10 18:05:23 -08:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2010-06-30 14:41:04 -07:00
|
|
|
// Remove anyone who's already assigned to this player
|
2016-04-24 13:48:53 -07:00
|
|
|
for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2016-04-24 13:48:53 -07:00
|
|
|
if (p.second.m_PlayerID == playerID)
|
|
|
|
|
p.second.m_PlayerID = -1;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
// Update this host's assignment if it exists
|
|
|
|
|
if (m_PlayerAssignments.find(guid) != m_PlayerAssignments.end())
|
|
|
|
|
m_PlayerAssignments[guid].m_PlayerID = playerID;
|
|
|
|
|
|
|
|
|
|
SendPlayerAssignments();
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2021-01-05 03:49:24 -08:00
|
|
|
for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2016-04-24 13:48:53 -07:00
|
|
|
if (!p.second.m_Enabled)
|
2011-10-27 09:46:48 -07:00
|
|
|
continue;
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CPlayerAssignmentMessage::S_m_Hosts h;
|
2016-04-24 13:48:53 -07:00
|
|
|
h.m_GUID = p.first;
|
|
|
|
|
h.m_Name = p.second.m_Name;
|
|
|
|
|
h.m_PlayerID = p.second.m_PlayerID;
|
|
|
|
|
h.m_Status = p.second.m_Status;
|
2010-06-30 14:41:04 -07:00
|
|
|
message.m_Hosts.push_back(h);
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::SendPlayerAssignments()
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2010-06-30 14:41:04 -07:00
|
|
|
CPlayerAssignmentMessage message;
|
|
|
|
|
ConstructPlayerAssignmentMessage(message);
|
2024-11-30 02:18:24 -08:00
|
|
|
Multicast(&message, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
const Script::Interface& CNetServerWorker::GetScriptInterface()
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2010-06-30 14:41:04 -07:00
|
|
|
return *m_ScriptInterface;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServerWorker::SetTurnLength(u32 msecs)
|
2010-08-13 09:42:53 -07:00
|
|
|
{
|
|
|
|
|
if (m_ServerTurnManager)
|
|
|
|
|
m_ServerTurnManager->SetTurnLength(msecs);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-11 17:23:40 -07:00
|
|
|
void CNetServerWorker::ProcessLobbyAuth(const CStr& name, const CStr& token)
|
|
|
|
|
{
|
|
|
|
|
LOGMESSAGE("Net Server: Received lobby auth message from %s with %s", name, token);
|
|
|
|
|
// Find the user with that guid
|
2026-04-16 09:59:07 -07:00
|
|
|
const auto it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
|
|
|
|
|
[&](const auto& session) { return session->GetGUID() == token; });
|
2018-03-11 17:23:40 -07:00
|
|
|
|
|
|
|
|
if (it == m_Sessions.end())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
(*it)->SetUserName(name.FromUTF8());
|
|
|
|
|
// Send an empty message to request the authentication message from the client
|
|
|
|
|
// after its identity has been confirmed via the lobby
|
|
|
|
|
CAuthenticateMessage emptyMessage;
|
|
|
|
|
(*it)->SendMessage(&emptyMessage);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnClientHandshake(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
|
|
|
|
|
if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
|
2010-06-30 14:41:04 -07:00
|
|
|
{
|
2010-07-06 12:54:17 -07:00
|
|
|
session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
|
|
|
|
|
return false;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2025-01-05 09:30:38 -08:00
|
|
|
if (CheckHandshake(CreateHandshake<CSrvHandshakeMessage>(), *message))
|
|
|
|
|
{
|
|
|
|
|
session->Disconnect(NDR_INCORRECT_SOFTWARE_VERSION);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-24 15:05:24 -07:00
|
|
|
CStr guid = ps_generate_guid();
|
|
|
|
|
int count = 0;
|
|
|
|
|
// Ensure unique GUID
|
|
|
|
|
while(std::find_if(
|
|
|
|
|
server.m_Sessions.begin(), server.m_Sessions.end(),
|
2026-04-16 09:59:07 -07:00
|
|
|
[&guid] (const auto& session)
|
2017-10-24 15:05:24 -07:00
|
|
|
{ return session->GetGUID() == guid; }) != server.m_Sessions.end())
|
|
|
|
|
{
|
|
|
|
|
if (++count > 100)
|
|
|
|
|
{
|
2019-09-26 04:36:03 -07:00
|
|
|
session->Disconnect(NDR_GUID_FAILED);
|
2017-10-24 15:05:24 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
guid = ps_generate_guid();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
session->SetGUID(guid);
|
|
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
CSrvHandshakeResponseMessage handshakeResponse;
|
|
|
|
|
handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
|
2017-10-24 15:05:24 -07:00
|
|
|
handshakeResponse.m_GUID = guid;
|
2010-07-06 12:54:17 -07:00
|
|
|
handshakeResponse.m_Flags = 0;
|
2018-03-11 17:23:40 -07:00
|
|
|
|
|
|
|
|
if (server.m_LobbyAuth)
|
|
|
|
|
{
|
|
|
|
|
handshakeResponse.m_Flags |= PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH;
|
|
|
|
|
session->SetNextState(NSS_LOBBY_AUTHENTICATE);
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
session->SendMessage(&handshakeResponse);
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnAuthenticate(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2015-08-28 13:20:10 -07:00
|
|
|
// Prohibit joins while the game is loading
|
|
|
|
|
if (server.m_State == SERVER_STATE_LOADING)
|
|
|
|
|
{
|
|
|
|
|
LOGMESSAGE("Refused connection while the game is loading");
|
|
|
|
|
session->Disconnect(NDR_SERVER_LOADING);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2015-08-28 13:20:10 -07:00
|
|
|
CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
|
2016-03-09 07:02:38 -08:00
|
|
|
CStrW username = SanitisePlayerName(message->m_Name);
|
2018-03-11 17:23:40 -07:00
|
|
|
CStrW usernameWithoutRating(username.substr(0, username.find(L" (")));
|
|
|
|
|
|
|
|
|
|
// Compare the lowercase names as specified by https://xmpp.org/extensions/xep-0029.html#sect-idm139493404168176
|
|
|
|
|
// "[...] comparisons will be made in case-normalized canonical form."
|
|
|
|
|
if (server.m_LobbyAuth && usernameWithoutRating.LowerCase() != session->GetUserName().LowerCase())
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("Net server: lobby auth: %s tried joining as %s",
|
2018-03-13 07:55:55 -07:00
|
|
|
session->GetUserName().ToUTF8(),
|
|
|
|
|
usernameWithoutRating.ToUTF8());
|
2018-03-11 17:23:40 -07:00
|
|
|
session->Disconnect(NDR_LOBBY_AUTH_FAILED);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-03-09 07:02:38 -08:00
|
|
|
|
2021-01-23 10:04:36 -08:00
|
|
|
// Check the password before anything else.
|
2021-05-18 07:47:36 -07:00
|
|
|
// NB: m_Name must match the client's salt, @see CNetClient::SetGamePassword
|
|
|
|
|
if (!server.CheckPassword(message->m_Password, message->m_Name.ToUTF8()))
|
2021-01-23 10:04:36 -08:00
|
|
|
{
|
|
|
|
|
// Noisy logerror because players are not supposed to be able to get the IP,
|
|
|
|
|
// so this might be someone targeting the host for some reason
|
|
|
|
|
// (or TODO a dedicated server and we do want to log anyways)
|
|
|
|
|
LOGERROR("Net server: user %s tried joining with the wrong password",
|
|
|
|
|
session->GetUserName().ToUTF8());
|
|
|
|
|
session->Disconnect(NDR_SERVER_REFUSED);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 04:09:42 -08:00
|
|
|
// If lobby authentication is enabled, the clients playername has already been registered.
|
|
|
|
|
// There also can't be any duplicated names.
|
2025-01-29 07:38:52 -08:00
|
|
|
// Either deduplicate or prohibit join if name is in use
|
|
|
|
|
if (!server.m_LobbyAuth && g_ConfigDB.Get("network.duplicateplayernames", false))
|
2016-03-09 07:02:38 -08:00
|
|
|
username = server.DeduplicatePlayerName(username);
|
2018-03-11 17:23:40 -07:00
|
|
|
else
|
|
|
|
|
{
|
2026-04-16 09:59:07 -07:00
|
|
|
const auto it = std::find_if(
|
2016-03-09 07:02:38 -08:00
|
|
|
server.m_Sessions.begin(), server.m_Sessions.end(),
|
2026-04-16 09:59:07 -07:00
|
|
|
[&username](const auto& session) { return session->GetUserName() == username; });
|
2018-03-11 17:23:40 -07:00
|
|
|
|
2026-04-16 09:59:07 -07:00
|
|
|
if (it != server.m_Sessions.end() && it->get() != session)
|
2018-03-11 17:23:40 -07:00
|
|
|
{
|
|
|
|
|
session->Disconnect(NDR_PLAYERNAME_IN_USE);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-03-09 07:02:38 -08:00
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2015-11-10 18:05:23 -08:00
|
|
|
// Disconnect banned usernames
|
2018-10-25 04:58:26 -07:00
|
|
|
if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), server.m_LobbyAuth ? usernameWithoutRating : username) != server.m_BannedPlayers.end())
|
2015-11-10 18:05:23 -08:00
|
|
|
{
|
|
|
|
|
session->Disconnect(NDR_BANNED);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-28 13:20:10 -07:00
|
|
|
bool isRejoining = false;
|
2016-03-13 09:52:00 -07:00
|
|
|
bool serverFull = false;
|
|
|
|
|
if (server.m_State == SERVER_STATE_PREGAME)
|
2010-07-06 12:54:17 -07:00
|
|
|
{
|
2016-03-13 09:52:00 -07:00
|
|
|
// Don't check for maxObservers in the gamesetup, as we don't know yet who will be assigned
|
|
|
|
|
serverFull = server.m_Sessions.size() >= MAX_CLIENTS;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
bool isObserver = true;
|
|
|
|
|
int disconnectedPlayers = 0;
|
|
|
|
|
int connectedPlayers = 0;
|
2011-10-27 09:46:48 -07:00
|
|
|
// (TODO: if GUIDs were stable, we should use them instead)
|
2021-01-05 03:49:24 -08:00
|
|
|
for (const std::pair<const CStr, PlayerAssignment>& p : server.m_PlayerAssignments)
|
2016-03-13 09:52:00 -07:00
|
|
|
{
|
2016-04-24 13:48:53 -07:00
|
|
|
const PlayerAssignment& assignment = p.second;
|
|
|
|
|
|
|
|
|
|
if (!assignment.m_Enabled && assignment.m_Name == username)
|
2016-03-13 09:52:00 -07:00
|
|
|
{
|
2016-04-24 13:48:53 -07:00
|
|
|
isObserver = assignment.m_PlayerID == -1;
|
2016-03-13 09:52:00 -07:00
|
|
|
isRejoining = true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-24 13:48:53 -07:00
|
|
|
if (assignment.m_PlayerID == -1)
|
2016-03-13 09:52:00 -07:00
|
|
|
continue;
|
|
|
|
|
|
2016-04-24 13:48:53 -07:00
|
|
|
if (assignment.m_Enabled)
|
2016-03-13 09:52:00 -07:00
|
|
|
++connectedPlayers;
|
|
|
|
|
else
|
|
|
|
|
++disconnectedPlayers;
|
|
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2017-05-28 11:05:08 -07:00
|
|
|
// Optionally allow everyone or only buddies to join after the game has started
|
|
|
|
|
if (!isRejoining)
|
|
|
|
|
{
|
2025-01-29 07:38:52 -08:00
|
|
|
const std::string observerLateJoin{
|
|
|
|
|
g_ConfigDB.Get("network.lateobservers", std::string{})};
|
2017-05-28 11:05:08 -07:00
|
|
|
|
|
|
|
|
if (observerLateJoin == "everyone")
|
|
|
|
|
{
|
|
|
|
|
isRejoining = true;
|
|
|
|
|
}
|
|
|
|
|
else if (observerLateJoin == "buddies")
|
|
|
|
|
{
|
2025-01-29 07:38:52 -08:00
|
|
|
std::wstringstream buddiesStream(wstring_from_utf8(
|
|
|
|
|
g_ConfigDB.Get("lobby.buddies", std::string{})));
|
2017-05-28 11:05:08 -07:00
|
|
|
CStrW buddy;
|
|
|
|
|
while (std::getline(buddiesStream, buddy, L','))
|
|
|
|
|
{
|
|
|
|
|
if (buddy == usernameWithoutRating)
|
|
|
|
|
{
|
|
|
|
|
isRejoining = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-27 09:46:48 -07:00
|
|
|
if (!isRejoining)
|
|
|
|
|
{
|
2015-01-22 12:37:38 -08:00
|
|
|
LOGMESSAGE("Refused connection after game start from not-previously-known user \"%s\"", utf8_from_wstring(username));
|
2011-10-27 09:46:48 -07:00
|
|
|
session->Disconnect(NDR_SERVER_ALREADY_IN_GAME);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-03-13 09:52:00 -07:00
|
|
|
|
|
|
|
|
// Ensure all players will be able to rejoin
|
|
|
|
|
serverFull = isObserver && (
|
2025-01-29 07:38:52 -08:00
|
|
|
(int) server.m_Sessions.size() - connectedPlayers >
|
|
|
|
|
g_ConfigDB.Get("network.observerlimit", 0) ||
|
2016-03-13 09:52:00 -07:00
|
|
|
(int) server.m_Sessions.size() + disconnectedPlayers >= MAX_CLIENTS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (serverFull)
|
|
|
|
|
{
|
|
|
|
|
session->Disconnect(NDR_SERVER_FULL);
|
|
|
|
|
return true;
|
2011-10-27 09:46:48 -07:00
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
u32 newHostID = server.m_NextHostID++;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-07-02 14:28:48 -07:00
|
|
|
session->SetUserName(username);
|
2010-06-30 14:41:04 -07:00
|
|
|
session->SetHostID(newHostID);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CAuthenticateResultMessage authenticateResult;
|
2024-08-31 01:54:56 -07:00
|
|
|
authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING :
|
|
|
|
|
server.m_ContinuesSavedGame ? ARC_OK_SAVED_GAME : ARC_OK;
|
2010-06-30 14:41:04 -07:00
|
|
|
authenticateResult.m_HostID = newHostID;
|
|
|
|
|
authenticateResult.m_Message = L"Logged in";
|
2021-02-27 09:44:59 -08:00
|
|
|
authenticateResult.m_IsController = 0;
|
|
|
|
|
|
|
|
|
|
if (message->m_ControllerSecret == server.m_ControllerSecret)
|
|
|
|
|
{
|
|
|
|
|
if (server.m_ControllerGUID.empty())
|
|
|
|
|
{
|
|
|
|
|
server.m_ControllerGUID = session->GetGUID();
|
|
|
|
|
authenticateResult.m_IsController = 1;
|
|
|
|
|
}
|
|
|
|
|
// TODO: we could probably handle having several controllers, or swapping?
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
session->SendMessage(&authenticateResult);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
server.OnUserJoin(session);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2024-03-09 06:31:43 -08:00
|
|
|
if (!isRejoining)
|
|
|
|
|
return true;
|
2021-03-22 03:13:27 -07:00
|
|
|
|
2026-03-03 10:46:40 -08:00
|
|
|
ENSURE(server.m_State != SERVER_STATE_PREGAME);
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2024-03-09 06:31:43 -08:00
|
|
|
// Request a copy of the current game state from an existing player, so we can send it on to the new
|
|
|
|
|
// player.
|
Prevent players from disconnecting during the loading screen by increasing the timeout tolerance to 60 seconds for that period, fixes #5163.
The NetClient runs in the main thread, so any part of the loading screen
consuming several seconds makes that client timeout.
This is a workaround because threading the NetClient would have prevent
these timeouts, refs #3700.
Coutnerintuitively, since enet timeout tolerance is proportional to the
latency, the better the connection of the player, the more likely it was
to drop on gamestart.
This problem became very frequent in Alpha 23, at least due to the Aura
bugfix 583b6ec625, AIInterface being particularly slow and that not
having been disabled yet in the loading screen resulting in additional
10 second freezes during the loading screen, even on empty maps, refs
#5200, 8e168f85e6.
Differential Revision: https://code.wildfiregames.com/D1513
Based on patch by: causative
This was SVN commit r21842.
2018-06-06 15:09:38 -07:00
|
|
|
|
2024-03-09 06:31:43 -08:00
|
|
|
// Assume session 0 is most likely the local player, so they're the most efficient client to request a
|
|
|
|
|
// copy from.
|
2026-04-16 09:59:07 -07:00
|
|
|
server.m_Sessions.at(0)->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::REJOIN,
|
2024-09-03 11:43:26 -07:00
|
|
|
[&server, newHostID](std::string buffer)
|
2024-03-09 06:31:43 -08:00
|
|
|
{
|
|
|
|
|
// We've received the game state from an existing player - now we need to send it onwards
|
|
|
|
|
// to the newly rejoining player.
|
|
|
|
|
|
|
|
|
|
const auto sessionIt = std::find_if(server.m_Sessions.begin(), server.m_Sessions.end(),
|
2026-04-16 09:59:07 -07:00
|
|
|
[newHostID](const auto& serverSession)
|
2024-03-09 06:31:43 -08:00
|
|
|
{
|
|
|
|
|
return serverSession->GetHostID() == newHostID;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (sessionIt == server.m_Sessions.end())
|
|
|
|
|
{
|
|
|
|
|
LOGMESSAGE("Net server: rejoining client disconnected before we sent to it");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2024-03-09 06:31:43 -08:00
|
|
|
// Store the received state file, and tell the client to stant downloading it from us.
|
|
|
|
|
// TODO: The server will get kind of confused if there's multiple clients downloading in
|
|
|
|
|
// parallel; they'll race and get whichever happens to be the latest received by the
|
|
|
|
|
// server, which should still work but isn't great.
|
|
|
|
|
server.m_JoinSyncFile = std::move(buffer);
|
|
|
|
|
|
|
|
|
|
// Send the init attributes alongside - these should be correct since the game should be
|
|
|
|
|
// started.
|
|
|
|
|
CJoinSyncStartMessage message;
|
|
|
|
|
message.m_InitAttributes = Script::StringifyJSON(
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
Script::Request{server.GetScriptInterface()}, &server.m_InitAttributes);
|
2024-03-09 06:31:43 -08:00
|
|
|
(*sessionIt)->SendMessage(&message);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
session->SetNextState(NSS_JOIN_SYNCING);
|
2010-06-30 14:41:04 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
2024-05-04 09:13:02 -07:00
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnSimulationCommand(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2019-01-02 16:15:31 -08:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_SIMULATION_COMMAND);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
|
|
|
|
CSimulationMessage* message = (CSimulationMessage*)event->GetParamRef();
|
|
|
|
|
|
|
|
|
|
// Ignore messages sent by one player on behalf of another player
|
|
|
|
|
// unless cheating is enabled
|
|
|
|
|
bool cheatsEnabled = false;
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
const Script::Interface& scriptInterface = server.GetScriptInterface();
|
|
|
|
|
Script::Request rq(scriptInterface);
|
2020-11-13 05:18:22 -08:00
|
|
|
JS::RootedValue settings(rq.cx);
|
2021-05-13 10:23:52 -07:00
|
|
|
Script::GetProperty(rq, server.m_InitAttributes, "settings", &settings);
|
|
|
|
|
if (Script::HasProperty(rq, settings, "CheatsEnabled"))
|
|
|
|
|
Script::GetProperty(rq, settings, "CheatsEnabled", cheatsEnabled);
|
2019-01-02 16:15:31 -08:00
|
|
|
|
|
|
|
|
PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID());
|
|
|
|
|
// When cheating is disabled, fail if the player the message claims to
|
|
|
|
|
// represent does not exist or does not match the sender's player name
|
|
|
|
|
if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Send it back to all clients that have finished
|
|
|
|
|
// the loading screen (and the synchronization when rejoining)
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(message, { NSS_INGAME });
|
2019-01-02 16:15:31 -08:00
|
|
|
|
|
|
|
|
// Save all the received commands
|
Use turn_id_t (int32_t) for turn-count across simulation, network and replay
CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).
Add (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).
Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.
Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.
Fixes #8718
2026-06-26 21:04:27 -07:00
|
|
|
if (server.m_SavedCommands.size() < static_cast<size_t>(message->m_Turn) + 1)
|
|
|
|
|
server.m_SavedCommands.resize(static_cast<size_t>(message->m_Turn) + 1);
|
|
|
|
|
server.m_SavedCommands[static_cast<size_t>(message->m_Turn)].push_back(*message);
|
2019-01-02 16:15:31 -08:00
|
|
|
|
|
|
|
|
// TODO: we shouldn't send the message back to the client that first sent it
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnFlare(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2024-12-03 12:24:55 -08:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_FLARE);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
CFlareMessage* message = (CFlareMessage*)event->GetParamRef();
|
|
|
|
|
message->m_GUID = session->GetGUID();
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(message, { NSS_INGAME });
|
2024-12-03 12:24:55 -08:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnSyncCheck(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2019-01-02 16:15:31 -08:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_SYNC_CHECK);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2019-01-02 16:15:31 -08:00
|
|
|
CSyncCheckMessage* message = (CSyncCheckMessage*)event->GetParamRef();
|
2015-11-01 19:20:44 -08:00
|
|
|
|
2019-01-02 16:15:31 -08:00
|
|
|
server.m_ServerTurnManager->NotifyFinishedClientUpdate(*session, message->m_Turn, message->m_Hash);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnEndCommandBatch(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2019-01-02 16:15:31 -08:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
|
2011-10-27 09:46:48 -07:00
|
|
|
|
2019-01-02 16:15:31 -08:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
|
|
|
|
CEndCommandBatchMessage* message = (CEndCommandBatchMessage*)event->GetParamRef();
|
2010-06-30 14:41:04 -07:00
|
|
|
|
2019-01-02 16:15:31 -08:00
|
|
|
// The turn-length field is ignored
|
|
|
|
|
server.m_ServerTurnManager->NotifyFinishedClientCommands(*session, message->m_Turn);
|
2010-06-30 14:41:04 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnChat(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_CHAT);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CChatMessage* message = (CChatMessage*)event->GetParamRef();
|
2010-07-02 14:28:48 -07:00
|
|
|
|
2024-11-30 02:18:24 -08:00
|
|
|
message->m_SenderGUID = session->GetGUID();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2024-11-30 02:18:24 -08:00
|
|
|
const std::vector<NetServerSessionState> receivingStates{NSS_PREGAME, NSS_INGAME};
|
|
|
|
|
const std::vector messageReceivers{std::exchange(message->m_Receivers, {})};
|
|
|
|
|
|
|
|
|
|
if (messageReceivers.empty())
|
|
|
|
|
server.Multicast(message, receivingStates);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto receivers = std::make_optional<std::vector<std::string>>();
|
|
|
|
|
std::transform(messageReceivers.begin(), messageReceivers.end(), std::back_inserter(*receivers),
|
|
|
|
|
std::mem_fn(&CChatMessage::S_m_Receivers::m_ReceiverGUID));
|
|
|
|
|
server.Multicast(message, receivingStates, std::move(receivers));
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnReady(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2014-04-26 11:34:34 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_READY);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2017-04-10 09:52:52 -07:00
|
|
|
// Occurs if a client presses not-ready
|
|
|
|
|
// in the very last moment before the hosts starts the game
|
|
|
|
|
if (server.m_State == SERVER_STATE_LOADING)
|
|
|
|
|
return true;
|
|
|
|
|
|
2014-04-26 11:34:34 -07:00
|
|
|
CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
|
|
|
|
|
message->m_GUID = session->GetGUID();
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(message, { NSS_PREGAME });
|
2017-04-09 15:59:04 -07:00
|
|
|
|
|
|
|
|
server.m_PlayerAssignments[message->m_GUID].m_Status = message->m_Status;
|
2016-06-04 05:08:30 -07:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnClearAllReady(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_CLEAR_ALL_READY);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2021-02-27 09:44:59 -08:00
|
|
|
if (session->GetGUID() == server.m_ControllerGUID)
|
2016-06-04 05:08:30 -07:00
|
|
|
server.ClearAllPlayerReady();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnGameSetup(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2018-06-05 05:24:30 -07:00
|
|
|
// Changing the settings after gamestart is not implemented and would cause an Out-of-sync error.
|
|
|
|
|
// This happened when doubleclicking on the startgame button.
|
|
|
|
|
if (server.m_State != SERVER_STATE_PREGAME)
|
|
|
|
|
return true;
|
|
|
|
|
|
2021-03-22 03:13:27 -07:00
|
|
|
// Only the controller is allowed to send game setup updates.
|
|
|
|
|
// TODO: it would be good to allow other players to request changes to some settings,
|
|
|
|
|
// e.g. their civilisation.
|
|
|
|
|
// Possibly this should use another message, to enforce a single source of truth.
|
2021-02-27 09:44:59 -08:00
|
|
|
if (session->GetGUID() == server.m_ControllerGUID)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(message, { NSS_PREGAME });
|
2016-06-04 05:08:30 -07:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnAssignPlayer(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_ASSIGN_PLAYER);
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2021-02-27 09:44:59 -08:00
|
|
|
if (session->GetGUID() == server.m_ControllerGUID)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
CAssignPlayerMessage* message = (CAssignPlayerMessage*)event->GetParamRef();
|
|
|
|
|
server.AssignPlayer(message->m_PlayerID, message->m_GUID);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnGameStart(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_GAME_START);
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2021-03-22 03:13:27 -07:00
|
|
|
if (session->GetGUID() != server.m_ControllerGUID)
|
|
|
|
|
return true;
|
2014-04-26 11:34:34 -07:00
|
|
|
|
2021-03-22 03:13:27 -07:00
|
|
|
CGameStartMessage* message = (CGameStartMessage*)event->GetParamRef();
|
|
|
|
|
server.StartGame(message->m_InitAttributes);
|
2014-04-26 11:34:34 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnSavedGameStart(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2024-08-31 01:54:56 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == static_cast<uint>(NMT_SAVED_GAME_START));
|
|
|
|
|
CNetServerWorker& server{session->GetServer()};
|
|
|
|
|
|
|
|
|
|
if (session->GetGUID() != server.m_ControllerGUID)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
CGameSavedStartMessage* message = static_cast<CGameSavedStartMessage*>(event->GetParamRef());
|
|
|
|
|
session->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::LOADGAME,
|
|
|
|
|
[&server, initAttributes = std::move(message->m_InitAttributes)](std::string buffer)
|
|
|
|
|
{
|
|
|
|
|
server.m_SavedState = std::move(buffer);
|
|
|
|
|
|
|
|
|
|
server.StartSavedGame(initAttributes);
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnLoadedGame(CNetServerSession* loadedSession, CFsmEvent<CNetMessage*>* event)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2017-03-21 11:50:29 -07:00
|
|
|
CNetServerWorker& server = loadedSession->GetServer();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2017-03-21 11:50:29 -07:00
|
|
|
// We're in the loading state, so wait until every client has loaded
|
|
|
|
|
// before starting the game
|
2011-10-27 09:46:48 -07:00
|
|
|
ENSURE(server.m_State == SERVER_STATE_LOADING);
|
2017-03-21 11:50:29 -07:00
|
|
|
if (server.CheckGameLoadStatus(loadedSession))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
CClientsLoadingMessage message;
|
|
|
|
|
// We always send all GUIDs of clients in the loading state
|
|
|
|
|
// so that we don't have to bother about switching GUI pages
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : server.m_Sessions)
|
2017-03-21 11:50:29 -07:00
|
|
|
if (session->GetCurrState() != NSS_INGAME && loadedSession->GetGUID() != session->GetGUID())
|
|
|
|
|
{
|
|
|
|
|
CClientsLoadingMessage::S_m_Clients client;
|
|
|
|
|
client.m_GUID = session->GetGUID();
|
|
|
|
|
message.m_Clients.push_back(client);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-31 01:54:56 -07:00
|
|
|
// If no other player is loading the server can clear the savestate
|
|
|
|
|
if (message.m_Clients.empty())
|
|
|
|
|
server.m_SavedState.clear();
|
|
|
|
|
|
2017-03-21 11:50:29 -07:00
|
|
|
// Send to the client who has loaded the game but did not reach the NSS_INGAME state yet
|
|
|
|
|
loadedSession->SendMessage(&message);
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(&message, { NSS_INGAME });
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnJoinSyncingLoadedGame(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2011-10-27 09:46:48 -07:00
|
|
|
{
|
|
|
|
|
// A client rejoining an in-progress game has now finished loading the
|
|
|
|
|
// map and deserialized the initial state.
|
|
|
|
|
// The simulation may have progressed since then, so send any subsequent
|
|
|
|
|
// commands to them and set them as an active player so they can participate
|
|
|
|
|
// in all future turns.
|
2015-12-21 05:58:32 -08:00
|
|
|
//
|
2011-10-27 09:46:48 -07:00
|
|
|
// (TODO: if it takes a long time for them to receive and execute all these
|
|
|
|
|
// commands, the other players will get frozen for that time and may be unhappy;
|
|
|
|
|
// we could try repeating this process a few times until the client converges
|
|
|
|
|
// on the up-to-date state, before setting them as active.)
|
|
|
|
|
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
|
|
|
|
CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();
|
|
|
|
|
|
Use turn_id_t (int32_t) for turn-count across simulation, network and replay
CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).
Add (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).
Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.
Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.
Fixes #8718
2026-06-26 21:04:27 -07:00
|
|
|
turn_id_t turn = message->m_CurrentTurn;
|
|
|
|
|
turn_id_t readyTurn = server.m_ServerTurnManager->GetReadyTurn();
|
2011-10-27 09:46:48 -07:00
|
|
|
|
|
|
|
|
// Send them all commands received since their saved state,
|
|
|
|
|
// and turn-ended messages for any turns that have already been processed
|
Use turn_id_t (int32_t) for turn-count across simulation, network and replay
CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).
Add (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).
Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.
Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.
Fixes #8718
2026-06-26 21:04:27 -07:00
|
|
|
for (turn_id_t i = turn + 1; i < std::max(readyTurn + 1, static_cast<turn_id_t>(server.m_SavedCommands.size())); ++i)
|
2011-10-27 09:46:48 -07:00
|
|
|
{
|
Use turn_id_t (int32_t) for turn-count across simulation, network and replay
CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).
Add (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).
Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.
Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.
Fixes #8718
2026-06-26 21:04:27 -07:00
|
|
|
if (static_cast<size_t>(i) < server.m_SavedCommands.size())
|
2011-10-27 09:46:48 -07:00
|
|
|
for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
|
|
|
|
|
session->SendMessage(&server.m_SavedCommands[i][j]);
|
|
|
|
|
|
|
|
|
|
if (i <= readyTurn)
|
|
|
|
|
{
|
|
|
|
|
CEndCommandBatchMessage endMessage;
|
|
|
|
|
endMessage.m_Turn = i;
|
|
|
|
|
endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
|
|
|
|
|
session->SendMessage(&endMessage);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tell the turn manager to expect commands from this new client
|
2021-03-29 00:53:06 -07:00
|
|
|
// Special case: the controller shouldn't be treated as an observer in any case.
|
|
|
|
|
bool isObserver = server.m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && server.m_ControllerGUID != session->GetGUID();
|
|
|
|
|
server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn, isObserver);
|
2011-10-27 09:46:48 -07:00
|
|
|
|
|
|
|
|
// Tell the client that everything has finished loading and it should start now
|
|
|
|
|
CLoadedGameMessage loaded;
|
|
|
|
|
loaded.m_CurrentTurn = readyTurn;
|
|
|
|
|
session->SendMessage(&loaded);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnRejoined(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2015-05-02 19:06:17 -07:00
|
|
|
{
|
|
|
|
|
// A client has finished rejoining and the loading screen disappeared.
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_REJOINED);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2017-01-25 11:04:17 -08:00
|
|
|
// Inform everyone of the client having rejoined
|
2015-05-02 19:06:17 -07:00
|
|
|
CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
|
|
|
|
|
message->m_GUID = session->GetGUID();
|
2024-11-30 02:18:24 -08:00
|
|
|
server.Multicast(message, { NSS_INGAME });
|
2015-05-02 19:06:17 -07:00
|
|
|
|
2016-05-19 15:10:38 -07:00
|
|
|
// Send all pausing players to the rejoined client.
|
|
|
|
|
for (const CStr& guid : server.m_PausingPlayers)
|
|
|
|
|
{
|
|
|
|
|
CClientPausedMessage pausedMessage;
|
|
|
|
|
pausedMessage.m_GUID = guid;
|
|
|
|
|
pausedMessage.m_Pause = true;
|
|
|
|
|
session->SendMessage(&pausedMessage);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-02 19:06:17 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnKickPlayer(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_KICKED);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
2021-02-27 09:44:59 -08:00
|
|
|
if (session->GetGUID() == server.m_ControllerGUID)
|
2016-06-04 05:08:30 -07:00
|
|
|
{
|
|
|
|
|
CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
|
|
|
|
|
server.KickPlayer(message->m_Name, message->m_Ban);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnDisconnect(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
2011-04-30 06:01:45 -07:00
|
|
|
ENSURE(event->GetType() == (uint)NMT_CONNECTION_LOST);
|
2010-07-02 14:28:48 -07:00
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CNetServerWorker& server = session->GetServer();
|
2010-07-02 14:28:48 -07:00
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
server.OnUserLeave(session);
|
2010-07-02 14:28:48 -07:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 03:35:01 -07:00
|
|
|
bool CNetServerWorker::OnClientPaused(CNetServerSession* session, CFsmEvent<CNetMessage*>* event)
|
2016-05-19 15:10:38 -07:00
|
|
|
{
|
|
|
|
|
ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
|
|
|
|
|
|
|
|
|
|
CNetServerWorker& server = session->GetServer();
|
|
|
|
|
|
|
|
|
|
CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
|
|
|
|
|
|
|
|
|
|
message->m_GUID = session->GetGUID();
|
|
|
|
|
|
|
|
|
|
// Update the list of pausing players.
|
|
|
|
|
std::vector<CStr>::iterator player = std::find(server.m_PausingPlayers.begin(), server.m_PausingPlayers.end(), session->GetGUID());
|
|
|
|
|
|
|
|
|
|
if (message->m_Pause)
|
|
|
|
|
{
|
|
|
|
|
if (player != server.m_PausingPlayers.end())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
server.m_PausingPlayers.push_back(session->GetGUID());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (player == server.m_PausingPlayers.end())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
server.m_PausingPlayers.erase(player);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send messages to clients that are in game, and are not the client who paused.
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& netSession : server.m_Sessions)
|
2020-11-26 14:28:50 -08:00
|
|
|
if (netSession->GetCurrState() == NSS_INGAME && message->m_GUID != netSession->GetGUID())
|
|
|
|
|
netSession->SendMessage(message);
|
2016-05-19 15:10:38 -07:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-21 11:50:29 -07:00
|
|
|
bool CNetServerWorker::CheckGameLoadStatus(CNetServerSession* changedSession)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : m_Sessions)
|
|
|
|
|
if (session.get() != changedSession && session->GetCurrState() != NSS_INGAME)
|
2017-03-21 11:50:29 -07:00
|
|
|
return false;
|
2010-06-30 14:41:04 -07:00
|
|
|
|
2017-01-25 11:04:17 -08:00
|
|
|
// Inform clients that everyone has loaded the map and that the game can start
|
2010-06-30 14:41:04 -07:00
|
|
|
CLoadedGameMessage loaded;
|
2011-10-27 09:46:48 -07:00
|
|
|
loaded.m_CurrentTurn = 0;
|
2017-01-28 12:47:26 -08:00
|
|
|
|
|
|
|
|
// Notice the changedSession is still in the NSS_PREGAME state
|
2024-11-30 02:18:24 -08:00
|
|
|
Multicast(&loaded, { NSS_PREGAME, NSS_INGAME });
|
2010-06-30 14:41:04 -07:00
|
|
|
|
|
|
|
|
m_State = SERVER_STATE_INGAME;
|
2017-03-21 11:50:29 -07:00
|
|
|
return true;
|
2009-04-11 10:00:39 -07:00
|
|
|
}
|
|
|
|
|
|
2024-08-31 01:54:56 -07:00
|
|
|
void CNetServerWorker::PreStartGame(const CStr& initAttribs)
|
2009-04-11 10:00:39 -07:00
|
|
|
{
|
2018-07-21 04:58:35 -07:00
|
|
|
for (std::pair<const CStr, PlayerAssignment>& player : m_PlayerAssignments)
|
|
|
|
|
if (player.second.m_Enabled && player.second.m_PlayerID != -1 && player.second.m_Status == 0)
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("Tried to start the game without player \"%s\" being ready!", utf8_from_wstring(player.second.m_Name).c_str());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 09:24:03 -07:00
|
|
|
m_ServerTurnManager = std::make_unique<CNetServerTurnManager>(*this);
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : m_Sessions)
|
2021-03-29 00:53:06 -07:00
|
|
|
{
|
2025-08-31 10:09:00 -07:00
|
|
|
// InitialiseClient makes the NetServerTurnManager wait for this client.
|
|
|
|
|
// But we only want to wait for clients who have passed authentication, i.e. who have the correct password.
|
|
|
|
|
// Therefore we may not call InitialiseClient for unauthenticated clients.
|
|
|
|
|
if (session->GetCurrState() != NSS_PREGAME)
|
|
|
|
|
{
|
|
|
|
|
// We only support clients joining before or after, not during the loading screen.
|
|
|
|
|
// Therefore we have to disconnect clients who did not complete the authentication yet.
|
|
|
|
|
LOGMESSAGE("Dropping client (%s / %s / %d) not in NSS_PREGAME when starting the game",
|
|
|
|
|
session->GetUserName().ToUTF8().c_str(),
|
|
|
|
|
session->GetGUID().c_str(),
|
|
|
|
|
session->GetHostID());
|
|
|
|
|
session->Disconnect(NDR_SERVER_LOADING);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 00:53:06 -07:00
|
|
|
// Special case: the controller shouldn't be treated as an observer in any case.
|
|
|
|
|
bool isObserver = m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && m_ControllerGUID != session->GetGUID();
|
|
|
|
|
m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0, isObserver);
|
|
|
|
|
}
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
m_State = SERVER_STATE_LOADING;
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2015-06-18 11:20:54 -07:00
|
|
|
// Remove players and observers that are not present when the game starts
|
|
|
|
|
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end();)
|
|
|
|
|
if (it->second.m_Enabled)
|
|
|
|
|
++it;
|
|
|
|
|
else
|
|
|
|
|
it = m_PlayerAssignments.erase(it);
|
|
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
SendPlayerAssignments();
|
2009-04-11 10:00:39 -07:00
|
|
|
|
2021-03-22 03:13:27 -07:00
|
|
|
// Update init attributes. They should no longer change.
|
Move Script* classes to Script namespace
- Rename ScriptEngine, ScriptContext, ScriptInterface, ScriptRequest to
Script::Engine, Script::Context, Script::Interface, Script::Request
- Remove 'Script' prefix from filenames:
ScriptContext.* → Context.*
ScriptInterface.* → Interface.*
ScriptRequest.* → Request.*
ScriptEngine.* → Engine.*
ScriptConversions.* → Conversions.*
ScriptExceptions.* → Exceptions.*
ScriptForward.* → ForwardDeclarations.*
ScriptStats.* → Stats.*
- Update all includes, forward declarations, and friend classes
- Use namespace Script { ... } in .cpp definitions to avoid repetitive
Script:: prefix (keeping global callbacks outside)
- Rename internal implementation structs:
ScriptInterface_impl → Interface_impl
ScriptFunction → Function
- Update copyright year to 2026 in all touched files
- Suppress pre-existing cppcheck warnings (uninitvar, nullPointer, unknown
macro) by adding them to suppressions-list.txt (these are not caused
by this refactor)
Fixes #7516
2026-06-02 14:06:50 -07:00
|
|
|
Script::ParseJSON(Script::Request(m_ScriptInterface), initAttribs, &m_InitAttributes);
|
2024-08-31 01:54:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CNetServerWorker::StartGame(const CStr& initAttribs)
|
|
|
|
|
{
|
|
|
|
|
PreStartGame(initAttribs);
|
2021-03-22 03:13:27 -07:00
|
|
|
|
2010-06-30 14:41:04 -07:00
|
|
|
CGameStartMessage gameStart;
|
2021-03-22 03:13:27 -07:00
|
|
|
gameStart.m_InitAttributes = initAttribs;
|
2024-11-30 02:18:24 -08:00
|
|
|
Multicast(&gameStart, { NSS_PREGAME });
|
2010-06-30 14:41:04 -07:00
|
|
|
}
|
|
|
|
|
|
2024-08-31 01:54:56 -07:00
|
|
|
void CNetServerWorker::StartSavedGame(const CStr& initAttribs)
|
|
|
|
|
{
|
|
|
|
|
PreStartGame(initAttribs);
|
|
|
|
|
|
|
|
|
|
CGameSavedStartMessage gameSavedStart;
|
|
|
|
|
gameSavedStart.m_InitAttributes = initAttribs;
|
2024-11-30 02:18:24 -08:00
|
|
|
Multicast(&gameSavedStart, { NSS_PREGAME });
|
2024-08-31 01:54:56 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CStrW CNetServerWorker::SanitisePlayerName(const CStrW& original)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
|
|
|
|
const size_t MAX_LENGTH = 32;
|
|
|
|
|
|
|
|
|
|
CStrW name = original;
|
|
|
|
|
name.Replace(L"[", L"{"); // remove GUI tags
|
|
|
|
|
name.Replace(L"]", L"}"); // remove for symmetry
|
|
|
|
|
|
|
|
|
|
// Restrict the length
|
|
|
|
|
if (name.length() > MAX_LENGTH)
|
|
|
|
|
name = name.Left(MAX_LENGTH);
|
|
|
|
|
|
|
|
|
|
// Don't allow surrounding whitespace
|
|
|
|
|
name.Trim(PS_TRIM_BOTH);
|
|
|
|
|
|
|
|
|
|
// Don't allow empty name
|
|
|
|
|
if (name.empty())
|
|
|
|
|
name = L"Anonymous";
|
|
|
|
|
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
|
|
|
|
CStrW name = original;
|
|
|
|
|
|
2010-07-06 12:54:17 -07:00
|
|
|
// Try names "Foo", "Foo (2)", "Foo (3)", etc
|
2010-07-02 14:28:48 -07:00
|
|
|
size_t id = 2;
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
bool unique = true;
|
2026-04-16 09:59:07 -07:00
|
|
|
for (const auto& session : m_Sessions)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
2016-04-24 13:48:53 -07:00
|
|
|
if (session->GetUserName() == name)
|
2010-07-02 14:28:48 -07:00
|
|
|
{
|
|
|
|
|
unique = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unique)
|
|
|
|
|
return name;
|
|
|
|
|
|
2026-08-03 08:44:20 -07:00
|
|
|
name = fmt::format(L"{}({})", original, id++);
|
2010-07-02 14:28:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
2010-10-31 15:00:28 -07:00
|
|
|
|
2026-05-20 12:12:39 -07:00
|
|
|
void CNetServerWorker::SendHolePunchingMessage(const CStr& /*ipStr*/, u16 /*port*/)
|
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-05-31 23:33:52 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-03 10:46:40 -08:00
|
|
|
CNetServer::CNetServer(const bool continueSavedGame, std::uint16_t port, const bool useLobbyAuth,
|
|
|
|
|
std::string password, std::string controllerSecret, std::string initAttributes) :
|
2026-04-16 09:16:41 -07:00
|
|
|
m_Worker{continueSavedGame, port, useLobbyAuth, password, std::move(controllerSecret),
|
|
|
|
|
std::move(initAttributes)},
|
2026-03-02 10:03:19 -08:00
|
|
|
m_LobbyAuth{useLobbyAuth},
|
|
|
|
|
m_Password{std::move(password)}
|
2010-10-31 15:00:28 -07:00
|
|
|
{
|
2026-03-03 12:20:33 -08:00
|
|
|
if (!useLobbyAuth)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// In lobby, we send our public ip and port on request to the players who want to connect.
|
|
|
|
|
// Thus we need to know our public IP and use STUN to get it.
|
2026-05-20 12:12:39 -07:00
|
|
|
// std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
|
|
|
|
// if (!m_Worker.m_Host || !StunClient::FindPublicIP(*m_Worker.m_Host, m_PublicIp, m_PublicPort))
|
|
|
|
|
// throw std::runtime_error{"Failed to resolve public IP-address."};
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
|
|
|
|
|
2018-08-25 07:34:30 -07:00
|
|
|
bool CNetServer::UseLobbyAuth() const
|
|
|
|
|
{
|
|
|
|
|
return m_LobbyAuth;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-16 08:34:38 -07:00
|
|
|
CStr CNetServer::GetPublicIp() const
|
|
|
|
|
{
|
|
|
|
|
return m_PublicIp;
|
|
|
|
|
}
|
|
|
|
|
|
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 10:31:39 -08:00
|
|
|
u16 CNetServer::GetPublicPort() const
|
|
|
|
|
{
|
|
|
|
|
return m_PublicPort;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-16 08:34:38 -07:00
|
|
|
u16 CNetServer::GetLocalPort() const
|
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 10:31:39 -08:00
|
|
|
{
|
2026-04-16 09:16:41 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
2026-05-20 12:12:39 -07:00
|
|
|
return 0; // m_Worker.m_Host->address.port;
|
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 10:31:39 -08:00
|
|
|
}
|
|
|
|
|
|
2021-05-18 07:47:36 -07:00
|
|
|
bool CNetServer::CheckPasswordAndIncrement(const std::string& username, const std::string& password, const std::string& salt)
|
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 10:31:39 -08:00
|
|
|
{
|
2021-01-26 12:20:48 -08:00
|
|
|
std::unordered_map<std::string, int>::iterator it = m_FailedAttempts.find(username);
|
2026-04-16 09:16:41 -07:00
|
|
|
if (m_Worker.CheckPassword(password, salt))
|
2021-01-26 12:20:48 -08:00
|
|
|
{
|
|
|
|
|
if (it != m_FailedAttempts.end())
|
|
|
|
|
it->second = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (it == m_FailedAttempts.end())
|
|
|
|
|
m_FailedAttempts.emplace(username, 1);
|
|
|
|
|
else
|
|
|
|
|
it->second++;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CNetServer::IsBanned(const std::string& username) const
|
|
|
|
|
{
|
|
|
|
|
std::unordered_map<std::string, int>::const_iterator it = m_FailedAttempts.find(username);
|
|
|
|
|
return it != m_FailedAttempts.end() && it->second >= FAILED_PASSWORD_TRIES_BEFORE_BAN;
|
Hide ip and port from users until they want to join, add optional password
Current issue with the lobby, is that we make ips of hosts public for
anyone to read. This patch consists of 3 parts.
1.) Removing ips and ports from lobby javascript
2.) Removing need of script on the server to attach public ips to game
stanza by asking the host using xmppclient as proxy.
3.) Implementing password protected matches, to deny this information to
not trusted players.
Further description:
Do not send ports and stunip to the bots.
Removed from stanza.
Do not send ip to the lobby.
Removed from mapping gamelist from backend to gui (still on the backend
side, because it is done by script on 0ad server).
Get ip and ports on request when trying to connect.
On the host side, ask stun server what is host's public ip and remember
it.
On the client side, send iq through xmppclient to the hosting player and
ask for ip, port and if Stun is used, then if answer is success,
continue
with connecting, else fail.
Add optional password for matches.
Add password required identifier to the stanza.
Allow host to setup password for the match. Hash it on the host side and
store inside Netserver. If no password is given, matches will behave
as it is not required.
On the client side, if password for the match is required, show
additional window before trying to connect and ask for password, then
hash it
and send with iq request for ip, port and stun.
Server will answer with ip, port and stun only if passwords matches,
else will asnwer with error string.
Some security:
Passwords are hashed before sending, so it is not easy to guess what
users typed. (per wraitii)
Hashes are using different salt as lobby hashing and not using usernames
as salt (as that is not doable), so they are different even typing the
same password as for the lobby account.
Client remembers which user was asked for connection data and iq's id of
request. If answer doesn't match these things, it is ignored. (thnx
user1)
Every request for connection data is logged with hostname of the
requester to the mainlog file (no ips).
If user gets iq to send connection data and is not hosting the match,
will respond with error string "not_server".
If server gets iq::result with connection data, request is ignored.
Differential revision: D3184
Reviewed by: @wraitii
Comments by: @Stan, @bb, @Imarok, @vladislavbelov
Tested in lobby
This was SVN commit r24728.
2021-01-20 10:31:39 -08:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServer::StartGame()
|
|
|
|
|
{
|
2026-04-16 09:16:41 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
|
|
|
|
m_Worker.m_StartGameQueue.push_back(true);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
|
|
|
|
|
2018-03-11 17:23:40 -07:00
|
|
|
void CNetServer::OnLobbyAuth(const CStr& name, const CStr& token)
|
|
|
|
|
{
|
2026-04-16 09:16:41 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
|
|
|
|
m_Worker.m_LobbyAuthQueue.push_back(std::make_pair(name, token));
|
2018-03-11 17:23:40 -07:00
|
|
|
}
|
|
|
|
|
|
2010-10-31 15:00:28 -07:00
|
|
|
void CNetServer::SetTurnLength(u32 msecs)
|
|
|
|
|
{
|
2026-04-16 09:16:41 -07:00
|
|
|
std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
|
|
|
|
m_Worker.m_TurnLengthQueue.push_back(msecs);
|
2010-10-31 15:00:28 -07:00
|
|
|
}
|
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-05-31 23:33:52 -07:00
|
|
|
|
|
|
|
|
void CNetServer::SendHolePunchingMessage(const CStr& ip, u16 port)
|
|
|
|
|
{
|
2026-04-16 09:16:41 -07:00
|
|
|
m_Worker.SendHolePunchingMessage(ip, port);
|
STUN + XMPP ICE implementation.
Allows lobby players to host games without having to configure their
router.
Differential Revision: https://code.wildfiregames.com/D364
Fixes #2305
Patch By: fcxSanya.
StunClient based on code by SuperTuxKart, relicensed with approval of
the according authors hilnius, hiker, Auria, deveee, Flakebi, leper,
konstin and KroArtem.
Added rfc5245 (ejabberd) support, a GUI option, refactoring and segfault
fixes by myself.
Tested By: user1, Sandarac, Sestroretsk1714, Vladislav, Grugnas,
javiergodas
Partially Reviewed By: leper, Philip, echotangoecho
This was SVN commit r19703.
2017-05-31 23:33:52 -07:00
|
|
|
}
|
2026-05-20 12:12:39 -07:00
|
|
|
|