OpenRCT2/src/openrct2/network/NetworkBase.cpp

4266 lines
126 KiB
C++
Raw Normal View History

2015-02-12 03:01:02 +01:00
/*****************************************************************************
* Copyright (c) 2014-2023 OpenRCT2 developers
2015-02-12 03:01:02 +01:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2015-02-12 03:01:02 +01:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2015-02-12 03:01:02 +01:00
*****************************************************************************/
2020-07-26 16:13:46 +02:00
#include "NetworkBase.h"
2018-06-22 23:02:47 +02:00
#include "../Context.h"
#include "../Game.h"
#include "../GameStateSnapshots.h"
2017-02-08 13:53:00 +01:00
#include "../OpenRCT2.h"
2017-06-06 18:55:13 +02:00
#include "../PlatformEnvironment.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "../actions/LoadOrQuitAction.h"
#include "../actions/NetworkModifyGroupAction.h"
#include "../actions/PeepPickupAction.h"
#include "../core/File.h"
2018-06-22 23:02:47 +02:00
#include "../core/Guard.hpp"
#include "../core/Json.hpp"
2021-11-24 15:48:33 +01:00
#include "../entity/EntityList.h"
2021-11-24 15:58:01 +01:00
#include "../entity/EntityRegistry.h"
2021-11-24 15:48:33 +01:00
#include "../entity/EntityTweener.h"
2021-12-12 00:06:06 +01:00
#include "../localisation/Formatter.h"
#include "../localisation/Formatting.h"
#include "../park/ParkFile.h"
#include "../platform/Platform.h"
#include "../scenario/Scenario.h"
2019-07-25 23:59:26 +02:00
#include "../scripting/ScriptEngine.h"
#include "../ui/UiContext.h"
#include "../ui/WindowManager.h"
2017-12-13 13:02:24 +01:00
#include "../util/SawyerCoding.h"
2018-03-19 23:34:37 +01:00
#include "../world/Location.hpp"
2020-07-26 16:13:46 +02:00
#include "network.h"
#include <algorithm>
2018-11-21 23:16:04 +01:00
#include <iterator>
2018-06-22 23:02:47 +02:00
#include <stdexcept>
// This string specifies which version of network stream current build uses.
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
#define NETWORK_STREAM_VERSION "15"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
2019-02-28 20:28:58 +01:00
static Peep* _pickup_peep = nullptr;
static int32_t _pickup_peep_old_x = LOCATION_NULL;
#ifndef DISABLE_NETWORK
// General chunk size is 63 KiB, this can not be any larger because the packet size is encoded
// with uint16_t and needs some spare room for other data in the packet.
static constexpr uint32_t CHUNK_SIZE = 1024 * 63;
// If data is sent fast enough it would halt the entire server, process only a maximum amount.
2021-07-29 19:08:57 +02:00
// This limit is per connection, the current value was determined by tests with fuzzing.
static constexpr uint32_t MaxPacketsPerUpdate = 100;
2018-07-21 16:17:06 +02:00
# include "../Cheats.h"
# include "../ParkImporter.h"
# include "../Version.h"
2018-07-21 16:17:06 +02:00
# include "../actions/GameAction.h"
# include "../config/Config.h"
# include "../core/Console.hpp"
# include "../core/FileStream.h"
2018-07-21 16:17:06 +02:00
# include "../core/MemoryStream.h"
# include "../core/Path.hpp"
# include "../core/String.hpp"
# include "../interface/Chat.h"
# include "../interface/Window.h"
# include "../localisation/Date.h"
# include "../localisation/Localisation.h"
# include "../object/ObjectManager.h"
# include "../object/ObjectRepository.h"
# include "../scenario/Scenario.h"
# include "../util/Util.h"
# include "../world/Park.h"
# include "NetworkAction.h"
# include "NetworkConnection.h"
# include "NetworkGroup.h"
# include "NetworkKey.h"
# include "NetworkPacket.h"
# include "NetworkPlayer.h"
# include "NetworkServerAdvertiser.h"
# include "NetworkUser.h"
2019-05-05 18:54:16 +02:00
# include "Socket.h"
2018-07-21 16:17:06 +02:00
# include <algorithm>
# include <array>
2018-07-21 16:17:06 +02:00
# include <cerrno>
# include <cmath>
# include <fstream>
# include <functional>
# include <list>
# include <map>
# include <memory>
2018-07-21 16:17:06 +02:00
# include <set>
# include <string>
# include <vector>
2018-07-21 16:17:06 +02:00
2017-06-11 13:53:37 +02:00
using namespace OpenRCT2;
static void NetworkChatShowConnectedMessage();
static void NetworkChatShowServerGreeting();
static u8string NetworkGetKeysDirectory();
static u8string NetworkGetPrivateKeyPath(u8string_view playerName);
static u8string NetworkGetPublicKeyPath(u8string_view playerName, u8string_view hash);
2021-08-17 05:20:07 +02:00
NetworkBase::NetworkBase(OpenRCT2::IContext& context)
: OpenRCT2::System(context)
2015-02-12 03:01:02 +01:00
{
mode = NETWORK_MODE_NONE;
status = NETWORK_STATUS_NONE;
last_ping_sent_time = 0;
_actionId = 0;
client_command_handlers[NetworkCommand::Auth] = &NetworkBase::Client_Handle_AUTH;
client_command_handlers[NetworkCommand::Map] = &NetworkBase::Client_Handle_MAP;
client_command_handlers[NetworkCommand::Chat] = &NetworkBase::Client_Handle_CHAT;
client_command_handlers[NetworkCommand::GameAction] = &NetworkBase::Client_Handle_GAME_ACTION;
client_command_handlers[NetworkCommand::Tick] = &NetworkBase::Client_Handle_TICK;
client_command_handlers[NetworkCommand::PlayerList] = &NetworkBase::Client_Handle_PLAYERLIST;
client_command_handlers[NetworkCommand::PlayerInfo] = &NetworkBase::Client_Handle_PLAYERINFO;
client_command_handlers[NetworkCommand::Ping] = &NetworkBase::Client_Handle_PING;
client_command_handlers[NetworkCommand::PingList] = &NetworkBase::Client_Handle_PINGLIST;
client_command_handlers[NetworkCommand::DisconnectMessage] = &NetworkBase::Client_Handle_SETDISCONNECTMSG;
client_command_handlers[NetworkCommand::ShowError] = &NetworkBase::Client_Handle_SHOWERROR;
client_command_handlers[NetworkCommand::GroupList] = &NetworkBase::Client_Handle_GROUPLIST;
client_command_handlers[NetworkCommand::Event] = &NetworkBase::Client_Handle_EVENT;
client_command_handlers[NetworkCommand::GameInfo] = &NetworkBase::Client_Handle_GAMEINFO;
client_command_handlers[NetworkCommand::Token] = &NetworkBase::Client_Handle_TOKEN;
client_command_handlers[NetworkCommand::ObjectsList] = &NetworkBase::Client_Handle_OBJECTS_LIST;
client_command_handlers[NetworkCommand::ScriptsHeader] = &NetworkBase::Client_Handle_SCRIPTS_HEADER;
client_command_handlers[NetworkCommand::ScriptsData] = &NetworkBase::Client_Handle_SCRIPTS_DATA;
client_command_handlers[NetworkCommand::GameState] = &NetworkBase::Client_Handle_GAMESTATE;
server_command_handlers[NetworkCommand::Auth] = &NetworkBase::ServerHandleAuth;
server_command_handlers[NetworkCommand::Chat] = &NetworkBase::ServerHandleChat;
server_command_handlers[NetworkCommand::GameAction] = &NetworkBase::ServerHandleGameAction;
server_command_handlers[NetworkCommand::Ping] = &NetworkBase::ServerHandlePing;
server_command_handlers[NetworkCommand::GameInfo] = &NetworkBase::ServerHandleGameInfo;
server_command_handlers[NetworkCommand::Token] = &NetworkBase::ServerHandleToken;
server_command_handlers[NetworkCommand::MapRequest] = &NetworkBase::ServerHandleMapRequest;
server_command_handlers[NetworkCommand::RequestGameState] = &NetworkBase::ServerHandleRequestGamestate;
server_command_handlers[NetworkCommand::Heartbeat] = &NetworkBase::ServerHandleHeartbeat;
_chat_log_fs << std::unitbuf;
_server_log_fs << std::unitbuf;
2015-07-10 21:53:41 +02:00
}
bool NetworkBase::Init()
2015-07-10 21:53:41 +02:00
{
status = NETWORK_STATUS_READY;
2016-05-30 23:25:43 +02:00
ServerName.clear();
ServerDescription.clear();
ServerGreeting.clear();
ServerProviderName.clear();
ServerProviderEmail.clear();
ServerProviderWebsite.clear();
return true;
2015-02-12 03:01:02 +01:00
}
void NetworkBase::Reconnect()
{
if (status != NETWORK_STATUS_NONE)
{
Close();
}
if (_requireClose)
{
_requireReconnect = true;
return;
}
BeginClient(_host, _port);
}
void NetworkBase::Close()
2015-02-12 03:01:02 +01:00
{
if (status != NETWORK_STATUS_NONE)
{
// HACK Because Close() is closed all over the place, it sometimes gets called inside an Update
// call. This then causes disposed data to be accessed. Therefore, save closing until the
// end of the update loop.
if (_closeLock)
{
_requireClose = true;
return;
}
CloseChatLog();
CloseServerLog();
CloseConnection();
client_connection_list.clear();
GameActions::ClearQueue();
GameActions::ResumeQueue();
player_list.clear();
group_list.clear();
_serverTickData.clear();
_pendingPlayerLists.clear();
_pendingPlayerInfo.clear();
# ifdef ENABLE_SCRIPTING
auto& scriptEngine = GetContext().GetScriptEngine();
scriptEngine.RemoveNetworkPlugins();
# endif
GfxInvalidateScreen();
_requireClose = false;
}
}
void NetworkBase::DecayCooldown(NetworkPlayer* player)
{
if (player == nullptr)
return; // No valid connection yet.
for (auto it = std::begin(player->CooldownTime); it != std::end(player->CooldownTime);)
{
it->second -= _currentDeltaTime;
if (it->second <= 0)
it = player->CooldownTime.erase(it);
else
it++;
}
}
void NetworkBase::CloseConnection()
{
if (mode == NETWORK_MODE_CLIENT)
{
2018-12-17 12:58:12 +01:00
_serverConnection.reset();
}
else if (mode == NETWORK_MODE_SERVER)
{
2018-12-17 12:58:12 +01:00
_listenSocket.reset();
_advertiser.reset();
}
mode = NETWORK_MODE_NONE;
status = NETWORK_STATUS_NONE;
_lastConnectStatus = SocketStatus::Closed;
2015-02-12 03:01:02 +01:00
}
bool NetworkBase::BeginClient(const std::string& host, uint16_t port)
2015-02-12 12:30:57 +01:00
{
2018-06-22 23:02:47 +02:00
if (GetMode() != NETWORK_MODE_NONE)
{
return false;
}
Close();
if (!Init())
return false;
mode = NETWORK_MODE_CLIENT;
LOG_INFO("Connecting to %s:%u", host.c_str(), port);
_host = host;
_port = port;
2018-12-17 12:58:12 +01:00
_serverConnection = std::make_unique<NetworkConnection>();
_serverConnection->Socket = CreateTcpSocket();
_serverConnection->Socket->ConnectAsync(host, port);
_serverState.gamestateSnapshotsEnabled = false;
status = NETWORK_STATUS_CONNECTING;
_lastConnectStatus = SocketStatus::Closed;
_clientMapLoaded = false;
_serverTickData.clear();
BeginChatLog();
BeginServerLog();
// We need to wait for the map load before we execute any actions.
// If the client has the title screen running then there's a potential
// risk of tick collision with the server map and title screen map.
GameActions::SuspendQueue();
auto keyPath = NetworkGetPrivateKeyPath(gConfigNetwork.PlayerName);
if (!File::Exists(keyPath))
2018-06-22 23:02:47 +02:00
{
Console::WriteLine("Generating key... This may take a while");
Console::WriteLine("Need to collect enough entropy from the system");
_key.Generate();
Console::WriteLine("Key generated, saving private bits as %s", keyPath.c_str());
const auto keysDirectory = NetworkGetKeysDirectory();
if (!Platform::EnsureDirectoryExists(keysDirectory.c_str()))
2018-06-22 23:02:47 +02:00
{
LOG_ERROR("Unable to create directory %s.", keysDirectory.c_str());
return false;
}
try
{
auto fs = FileStream(keyPath, FILE_MODE_WRITE);
_key.SavePrivate(&fs);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
LOG_ERROR("Unable to save private key at %s.", keyPath.c_str());
return false;
}
const std::string hash = _key.PublicKeyHash();
2018-06-22 23:02:47 +02:00
const utf8* publicKeyHash = hash.c_str();
keyPath = NetworkGetPublicKeyPath(gConfigNetwork.PlayerName, publicKeyHash);
Console::WriteLine("Key generated, saving public bits as %s", keyPath.c_str());
try
{
auto fs = FileStream(keyPath, FILE_MODE_WRITE);
_key.SavePublic(&fs);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
LOG_ERROR("Unable to save public key at %s.", keyPath.c_str());
return false;
}
2018-06-22 23:02:47 +02:00
}
else
{
// LoadPrivate returns validity of loaded key
bool ok = false;
try
{
LOG_VERBOSE("Loading key from %s", keyPath.c_str());
auto fs = FileStream(keyPath, FILE_MODE_OPEN);
ok = _key.LoadPrivate(&fs);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
LOG_ERROR("Unable to read private key from %s.", keyPath.c_str());
return false;
}
// Don't store private key in memory when it's not in use.
_key.Unload();
return ok;
}
return true;
2015-02-12 12:30:57 +01:00
}
bool NetworkBase::BeginServer(uint16_t port, const std::string& address)
2015-02-12 12:30:57 +01:00
{
Close();
if (!Init())
return false;
mode = NETWORK_MODE_SERVER;
_userManager.Load();
LOG_VERBOSE("Begin listening for clients");
2018-12-17 12:58:12 +01:00
_listenSocket = CreateTcpSocket();
try
{
2018-12-17 12:58:12 +01:00
_listenSocket->Listen(address, port);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception& ex)
{
2018-01-02 20:23:22 +01:00
Console::Error::WriteLine(ex.what());
Close();
return false;
}
ServerName = gConfigNetwork.ServerName;
ServerDescription = gConfigNetwork.ServerDescription;
ServerGreeting = gConfigNetwork.ServerGreeting;
ServerProviderName = gConfigNetwork.ProviderName;
ServerProviderEmail = gConfigNetwork.ProviderEmail;
ServerProviderWebsite = gConfigNetwork.ProviderWebsite;
IsServerPlayerInvisible = gOpenRCT2Headless;
CheatsReset();
LoadGroups();
BeginChatLog();
BeginServerLog();
NetworkPlayer* player = AddPlayer(gConfigNetwork.PlayerName, "");
player->Flags |= NETWORK_PLAYER_FLAG_ISSERVER;
player->Group = 0;
player_id = player->Id;
if (NetworkGetMode() == NETWORK_MODE_SERVER)
2018-06-22 23:02:47 +02:00
{
// Add SERVER to users.json and save.
2018-06-22 23:02:47 +02:00
NetworkUser* networkUser = _userManager.GetOrAddUser(player->KeyHash);
networkUser->GroupId = player->Group;
networkUser->Name = player->Name;
_userManager.Save();
}
2021-02-11 22:17:15 +01:00
auto* szAddress = address.empty() ? "*" : address.c_str();
2021-02-13 01:15:27 +01:00
Console::WriteLine("Listening for clients on %s:%hu", szAddress, port);
NetworkChatShowConnectedMessage();
NetworkChatShowServerGreeting();
status = NETWORK_STATUS_CONNECTED;
listening_port = port;
_serverState.gamestateSnapshotsEnabled = gConfigNetwork.DesyncDebugging;
2019-05-05 03:02:20 +02:00
_advertiser = CreateServerAdvertiser(listening_port);
GameLoadScripts();
GameNotifyMapChanged();
return true;
2015-02-12 03:01:02 +01:00
}
2022-02-09 21:57:25 +01:00
int32_t NetworkBase::GetMode() const noexcept
2015-02-12 03:01:02 +01:00
{
return mode;
2015-02-12 03:01:02 +01:00
}
2022-02-09 21:57:25 +01:00
int32_t NetworkBase::GetStatus() const noexcept
{
return status;
}
NetworkAuth NetworkBase::GetAuthStatus()
2015-07-16 19:32:43 +02:00
{
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_CLIENT)
{
2018-12-17 12:58:12 +01:00
return _serverConnection->AuthStatus;
2018-06-22 23:02:47 +02:00
}
2021-09-15 22:22:15 +02:00
if (GetMode() == NETWORK_MODE_SERVER)
2018-06-22 23:02:47 +02:00
{
return NetworkAuth::Ok;
}
return NetworkAuth::None;
2015-07-16 19:32:43 +02:00
}
2022-02-09 21:57:25 +01:00
uint32_t NetworkBase::GetServerTick() const noexcept
2015-02-12 12:30:57 +01:00
{
return _serverState.tick;
2015-07-08 03:21:05 +02:00
}
2015-02-12 12:30:57 +01:00
2022-02-09 21:57:25 +01:00
uint8_t NetworkBase::GetPlayerID() const noexcept
{
return player_id;
}
2022-02-09 21:57:25 +01:00
NetworkConnection* NetworkBase::GetPlayerConnection(uint8_t id) const
{
auto player = GetPlayerByID(id);
if (player != nullptr)
{
auto clientIt = std::find_if(
2020-04-30 18:12:02 +02:00
client_connection_list.begin(), client_connection_list.end(),
[player](const auto& conn) -> bool { return conn->Player == player; });
return clientIt != client_connection_list.end() ? clientIt->get() : nullptr;
}
return nullptr;
}
void NetworkBase::Update()
2015-07-08 03:21:05 +02:00
{
_closeLock = true;
// Update is not necessarily called per game tick, maintain our own delta time
uint32_t ticks = Platform::GetTicks();
_currentDeltaTime = std::max<uint32_t>(ticks - _lastUpdateTime, 1);
_lastUpdateTime = ticks;
2018-06-22 23:02:47 +02:00
switch (GetMode())
{
case NETWORK_MODE_SERVER:
UpdateServer();
break;
case NETWORK_MODE_CLIENT:
UpdateClient();
break;
}
// If the Close() was called during the update, close it for real
_closeLock = false;
2018-06-22 23:02:47 +02:00
if (_requireClose)
{
Close();
if (_requireReconnect)
{
Reconnect();
}
}
2015-08-16 00:19:15 +02:00
}
void NetworkBase::Flush()
{
if (GetMode() == NETWORK_MODE_CLIENT)
{
2018-12-17 12:58:12 +01:00
_serverConnection->SendQueuedPackets();
}
else
{
for (auto& it : client_connection_list)
{
it->SendQueuedPackets();
}
}
}
void NetworkBase::UpdateServer()
2015-08-16 00:19:15 +02:00
{
for (auto& connection : client_connection_list)
2018-06-22 23:02:47 +02:00
{
// This can be called multiple times before the connection is removed.
if (!connection->IsValid())
continue;
if (!ProcessConnection(*connection))
2018-06-22 23:02:47 +02:00
{
connection->Disconnect();
2018-06-22 23:02:47 +02:00
}
else
{
DecayCooldown(connection->Player);
}
}
uint32_t ticks = Platform::GetTicks();
2018-06-22 23:02:47 +02:00
if (ticks > last_ping_sent_time + 3000)
{
ServerSendPing();
ServerSendPingList();
}
2018-06-22 23:02:47 +02:00
if (_advertiser != nullptr)
{
_advertiser->Update();
}
2018-12-17 12:58:12 +01:00
std::unique_ptr<ITcpSocket> tcpSocket = _listenSocket->Accept();
2018-06-22 23:02:47 +02:00
if (tcpSocket != nullptr)
{
2018-12-17 12:58:12 +01:00
AddClient(std::move(tcpSocket));
}
2015-08-16 00:19:15 +02:00
}
2015-08-14 17:59:27 +02:00
void NetworkBase::UpdateClient()
2015-08-16 00:19:15 +02:00
{
2018-12-17 12:58:12 +01:00
assert(_serverConnection != nullptr);
2018-06-22 23:02:47 +02:00
switch (status)
{
2018-06-22 23:02:47 +02:00
case NETWORK_STATUS_CONNECTING:
{
2018-12-17 12:58:12 +01:00
switch (_serverConnection->Socket->GetStatus())
{
case SocketStatus::Resolving:
2018-06-22 23:02:47 +02:00
{
if (_lastConnectStatus != SocketStatus::Resolving)
2018-06-22 23:02:47 +02:00
{
_lastConnectStatus = SocketStatus::Resolving;
2018-06-22 23:02:47 +02:00
char str_resolving[256];
FormatStringLegacy(str_resolving, 256, STR_MULTIPLAYER_RESOLVING, nullptr);
2018-06-22 23:02:47 +02:00
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_resolving });
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2018-06-22 23:02:47 +02:00
}
break;
}
case SocketStatus::Connecting:
2018-06-22 23:02:47 +02:00
{
if (_lastConnectStatus != SocketStatus::Connecting)
2018-06-22 23:02:47 +02:00
{
_lastConnectStatus = SocketStatus::Connecting;
2018-06-22 23:02:47 +02:00
char str_connecting[256];
FormatStringLegacy(str_connecting, 256, STR_MULTIPLAYER_CONNECTING, nullptr);
2018-06-22 23:02:47 +02:00
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_connecting });
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2018-06-22 23:02:47 +02:00
server_connect_time = Platform::GetTicks();
2018-06-22 23:02:47 +02:00
}
break;
}
case SocketStatus::Connected:
2018-06-22 23:02:47 +02:00
{
status = NETWORK_STATUS_CONNECTED;
2018-12-17 12:58:12 +01:00
_serverConnection->ResetLastPacketTime();
2018-06-22 23:02:47 +02:00
Client_Send_TOKEN();
char str_authenticating[256];
FormatStringLegacy(str_authenticating, 256, STR_MULTIPLAYER_AUTHENTICATING, nullptr);
2018-06-22 23:02:47 +02:00
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_authenticating });
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2018-06-22 23:02:47 +02:00
break;
}
default:
{
2018-12-17 12:58:12 +01:00
const char* error = _serverConnection->Socket->GetError();
2018-06-22 23:02:47 +02:00
if (error != nullptr)
{
Console::Error::WriteLine(error);
}
Close();
2022-11-06 21:49:07 +01:00
ContextForceCloseWindowByClass(WindowClass::NetworkStatus);
ContextShowError(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_NONE, {});
2018-06-22 23:02:47 +02:00
break;
}
}
break;
}
2018-06-22 23:02:47 +02:00
case NETWORK_STATUS_CONNECTED:
{
2018-12-17 12:58:12 +01:00
if (!ProcessConnection(*_serverConnection))
{
2018-06-22 23:02:47 +02:00
// Do not show disconnect message window when password window closed/canceled
if (_serverConnection->AuthStatus == NetworkAuth::RequirePassword)
2018-06-22 23:02:47 +02:00
{
2022-11-06 21:49:07 +01:00
ContextForceCloseWindowByClass(WindowClass::NetworkStatus);
2018-06-22 23:02:47 +02:00
}
else
{
char str_disconnected[256];
2018-12-17 12:58:12 +01:00
if (_serverConnection->GetLastDisconnectReason())
2018-06-22 23:02:47 +02:00
{
2018-12-17 12:58:12 +01:00
const char* disconnect_reason = _serverConnection->GetLastDisconnectReason();
FormatStringLegacy(str_disconnected, 256, STR_MULTIPLAYER_DISCONNECTED_WITH_REASON, &disconnect_reason);
2018-06-22 23:02:47 +02:00
}
else
{
FormatStringLegacy(str_disconnected, 256, STR_MULTIPLAYER_DISCONNECTED_NO_REASON, nullptr);
2018-06-22 23:02:47 +02:00
}
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_disconnected });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
}
WindowCloseByClass(WindowClass::Multiplayer);
2018-06-22 23:02:47 +02:00
Close();
}
else
{
uint32_t ticks = Platform::GetTicks();
if (ticks - _lastSentHeartbeat >= 3000)
{
Client_Send_HEARTBEAT(*_serverConnection);
_lastSentHeartbeat = ticks;
}
}
2018-06-22 23:02:47 +02:00
break;
}
}
2015-07-08 03:21:05 +02:00
}
2015-02-12 12:30:57 +01:00
2022-02-09 21:57:25 +01:00
auto NetworkBase::GetPlayerIteratorByID(uint8_t id) const
2016-01-20 23:45:09 +01:00
{
2022-02-09 21:57:25 +01:00
return std::find_if(player_list.begin(), player_list.end(), [id](std::unique_ptr<NetworkPlayer> const& player) {
2018-06-22 23:02:47 +02:00
return player->Id == id;
});
2016-01-20 23:45:09 +01:00
}
2022-02-09 21:57:25 +01:00
NetworkPlayer* NetworkBase::GetPlayerByID(uint8_t id) const
2016-01-20 23:45:09 +01:00
{
auto it = GetPlayerIteratorByID(id);
2018-06-22 23:02:47 +02:00
if (it != player_list.end())
{
return it->get();
}
return nullptr;
2016-01-20 23:45:09 +01:00
}
2022-02-09 21:57:25 +01:00
auto NetworkBase::GetGroupIteratorByID(uint8_t id) const
2016-01-20 23:45:09 +01:00
{
2022-02-09 21:57:25 +01:00
return std::find_if(
group_list.begin(), group_list.end(), [id](std::unique_ptr<NetworkGroup> const& group) { return group->Id == id; });
2016-01-20 23:45:09 +01:00
}
2022-02-09 21:57:25 +01:00
NetworkGroup* NetworkBase::GetGroupByID(uint8_t id) const
2016-01-20 23:45:09 +01:00
{
auto it = GetGroupIteratorByID(id);
2018-06-22 23:02:47 +02:00
if (it != group_list.end())
{
return it->get();
}
return nullptr;
2015-07-17 02:24:39 +02:00
}
int32_t NetworkBase::GetTotalNumPlayers() const noexcept
{
return static_cast<int32_t>(player_list.size());
}
int32_t NetworkBase::GetNumVisiblePlayers() const noexcept
{
if (IsServerPlayerInvisible)
return static_cast<int32_t>(player_list.size() - 1);
return static_cast<int32_t>(player_list.size());
}
const char* NetworkBase::FormatChat(NetworkPlayer* fromplayer, const char* text)
2015-07-19 01:57:42 +02:00
{
2020-10-16 01:13:52 +02:00
static std::string formatted;
formatted.clear();
formatted += "{OUTLINE}";
2021-09-24 20:05:50 +02:00
if (fromplayer != nullptr)
2018-06-22 23:02:47 +02:00
{
2020-10-16 01:13:52 +02:00
formatted += "{BABYBLUE}";
formatted += fromplayer->Name;
formatted += ": ";
}
2020-10-16 01:13:52 +02:00
formatted += "{WHITE}";
formatted += text;
return formatted.c_str();
2015-07-19 01:57:42 +02:00
}
2022-02-09 21:57:25 +01:00
void NetworkBase::SendPacketToClients(const NetworkPacket& packet, bool front, bool gameCmd) const
2015-07-19 01:57:42 +02:00
{
2018-06-22 23:02:47 +02:00
for (auto& client_connection : client_connection_list)
{
if (gameCmd)
{
// If marked as game command we can not send the packet to connections that are not fully connected.
// Sending the packet would cause the client to store a command that is behind the tick where he starts,
2018-06-22 23:02:47 +02:00
// which would be essentially never executed. The clients do not require commands before the server has not sent the
// map data.
if (client_connection->Player == nullptr)
{
continue;
}
}
2022-02-09 21:57:25 +01:00
client_connection->QueuePacket(packet, front);
}
2015-07-19 01:57:42 +02:00
}
bool NetworkBase::CheckSRAND(uint32_t tick, uint32_t srand0)
2015-07-30 16:34:17 +02:00
{
// We have to wait for the map to be loaded first, ticks may match current loaded map.
2019-05-10 22:00:38 +02:00
if (!_clientMapLoaded)
return true;
auto itTickData = _serverTickData.find(tick);
if (itTickData == std::end(_serverTickData))
return true;
const ServerTickData storedTick = itTickData->second;
_serverTickData.erase(itTickData);
if (storedTick.srand0 != srand0)
{
LOG_INFO("Srand0 mismatch, client = %08X, server = %08X", srand0, storedTick.srand0);
return false;
}
2019-05-10 22:00:38 +02:00
if (!storedTick.spriteHash.empty())
2018-06-22 23:02:47 +02:00
{
2021-11-24 14:37:47 +01:00
EntitiesChecksum checksum = GetAllEntitiesChecksum();
std::string clientSpriteHash = checksum.ToString();
if (clientSpriteHash != storedTick.spriteHash)
2018-06-22 23:02:47 +02:00
{
LOG_INFO("Sprite hash mismatch, client = %s, server = %s", clientSpriteHash.c_str(), storedTick.spriteHash.c_str());
return false;
}
}
return true;
2015-07-30 16:34:17 +02:00
}
2022-02-09 21:57:25 +01:00
bool NetworkBase::IsDesynchronised() const noexcept
{
return _serverState.state == NetworkServerStatus::Desynced;
}
bool NetworkBase::CheckDesynchronizaton()
{
// Check synchronisation
if (GetMode() == NETWORK_MODE_CLIENT && _serverState.state != NetworkServerStatus::Desynced
&& !CheckSRAND(gCurrentTicks, ScenarioRandState().s0))
2018-06-22 23:02:47 +02:00
{
_serverState.state = NetworkServerStatus::Desynced;
_serverState.desyncTick = gCurrentTicks;
char str_desync[256];
FormatStringLegacy(str_desync, 256, STR_MULTIPLAYER_DESYNC, nullptr);
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_desync });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
if (!gConfigNetwork.StayConnected)
2018-06-22 23:02:47 +02:00
{
Close();
}
return true;
}
return false;
}
void NetworkBase::RequestStateSnapshot()
{
LOG_INFO("Requesting game state for tick %u", _serverState.desyncTick);
Client_Send_RequestGameState(_serverState.desyncTick);
}
NetworkServerState NetworkBase::GetServerState() const noexcept
{
return _serverState;
}
void NetworkBase::KickPlayer(int32_t playerId)
2015-11-02 01:28:53 +01:00
{
2018-06-22 23:02:47 +02:00
for (auto& client_connection : client_connection_list)
{
if (client_connection->Player->Id == playerId)
{
// Disconnect the client gracefully
2017-10-03 00:00:32 +02:00
client_connection->SetLastDisconnectReason(STR_MULTIPLAYER_KICKED);
char str_disconnect_msg[256];
FormatStringLegacy(str_disconnect_msg, 256, STR_MULTIPLAYER_KICKED_REASON, nullptr);
ServerSendSetDisconnectMsg(*client_connection, str_disconnect_msg);
client_connection->Disconnect();
break;
}
}
2015-11-02 01:28:53 +01:00
}
2022-01-27 14:21:46 +01:00
void NetworkBase::SetPassword(u8string_view password)
2015-11-02 01:28:53 +01:00
{
2022-01-27 14:21:46 +01:00
_password = password;
2015-11-04 00:31:09 +01:00
}
void NetworkBase::ServerClientDisconnected()
2015-11-04 00:31:09 +01:00
{
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_CLIENT)
{
_serverConnection->Disconnect();
}
2015-11-04 00:31:09 +01:00
}
std::string NetworkBase::GenerateAdvertiseKey()
2015-11-04 00:31:09 +01:00
{
// Generate a string of 16 random hex characters (64-integer key as a hex formatted string)
static char hexChars[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
};
char key[17];
2018-06-22 23:02:47 +02:00
for (int32_t i = 0; i < 16; i++)
{
int32_t hexCharIndex = UtilRand() % std::size(hexChars);
key[i] = hexChars[hexCharIndex];
}
2018-11-21 23:16:04 +01:00
key[std::size(key) - 1] = 0;
2015-11-08 03:12:12 +01:00
return key;
2015-11-08 03:12:12 +01:00
}
std::string NetworkBase::GetMasterServerUrl()
2015-11-08 03:12:12 +01:00
{
if (gConfigNetwork.MasterServerUrl.empty())
2018-06-22 23:02:47 +02:00
{
return OPENRCT2_MASTER_SERVER_URL;
2018-06-22 23:02:47 +02:00
}
2021-09-15 22:22:15 +02:00
return gConfigNetwork.MasterServerUrl;
2015-11-08 03:12:12 +01:00
}
NetworkGroup* NetworkBase::AddGroup()
{
NetworkGroup* addedgroup = nullptr;
int32_t newid = -1;
// Find first unused group id
2018-06-22 23:02:47 +02:00
for (int32_t id = 0; id < 255; id++)
{
if (std::find_if(
group_list.begin(), group_list.end(),
2018-06-22 23:02:47 +02:00
[&id](std::unique_ptr<NetworkGroup> const& group) { return group->Id == id; })
== group_list.end())
{
newid = id;
break;
}
}
2018-06-22 23:02:47 +02:00
if (newid != -1)
{
auto group = std::make_unique<NetworkGroup>();
group->Id = newid;
group->SetName("Group #" + std::to_string(newid));
addedgroup = group.get();
group_list.push_back(std::move(group));
}
return addedgroup;
}
void NetworkBase::RemoveGroup(uint8_t id)
2016-01-22 07:32:40 +01:00
{
auto group = GetGroupIteratorByID(id);
2018-06-22 23:02:47 +02:00
if (group != group_list.end())
{
group_list.erase(group);
}
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_SERVER)
{
_userManager.UnsetUsersOfGroup(id);
_userManager.Save();
}
2016-01-22 07:32:40 +01:00
}
uint8_t NetworkBase::GetGroupIDByHash(const std::string& keyhash)
2016-05-19 10:23:42 +02:00
{
2018-06-22 23:02:47 +02:00
const NetworkUser* networkUser = _userManager.GetUserByHash(keyhash);
uint8_t groupId = GetDefaultGroup();
if (networkUser != nullptr && networkUser->GroupId.has_value())
2018-06-22 23:02:47 +02:00
{
const uint8_t assignedGroup = *networkUser->GroupId;
2018-06-22 23:02:47 +02:00
if (GetGroupByID(assignedGroup) != nullptr)
{
groupId = assignedGroup;
2018-06-22 23:02:47 +02:00
}
else
{
LOG_WARNING(
"User %s is assigned to non-existent group %u. Assigning to default group (%u)", keyhash.c_str(), assignedGroup,
2018-06-22 23:02:47 +02:00
groupId);
}
}
return groupId;
2016-05-19 10:23:42 +02:00
}
2022-02-09 21:57:25 +01:00
uint8_t NetworkBase::GetDefaultGroup() const noexcept
2016-01-20 23:45:09 +01:00
{
return default_group;
2016-01-20 23:45:09 +01:00
}
void NetworkBase::SetDefaultGroup(uint8_t id)
2016-01-20 23:45:09 +01:00
{
2021-09-24 20:05:50 +02:00
if (GetGroupByID(id) != nullptr)
2018-06-22 23:02:47 +02:00
{
default_group = id;
}
2016-01-20 23:45:09 +01:00
}
void NetworkBase::SaveGroups()
2016-01-24 02:05:53 +01:00
{
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_SERVER)
{
auto env = GetContext().GetPlatformEnvironment();
auto path = Path::Combine(env->GetDirectoryPath(DIRBASE::USER), u8"groups.json");
2016-01-24 02:05:53 +01:00
json_t jsonGroups = json_t::array();
2018-06-22 23:02:47 +02:00
for (auto& group : group_list)
{
jsonGroups.push_back(group->ToJson());
}
json_t jsonGroupsCfg = {
{ "default_group", default_group },
{ "groups", jsonGroups },
};
try
{
Json::WriteToFile(path, jsonGroupsCfg);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception& ex)
{
LOG_ERROR("Unable to save %s: %s", path.c_str(), ex.what());
}
}
2016-01-24 02:05:53 +01:00
}
void NetworkBase::SetupDefaultGroups()
{
// Admin group
auto admin = std::make_unique<NetworkGroup>();
admin->SetName("Admin");
admin->ActionsAllowed.fill(0xFF);
admin->Id = 0;
group_list.push_back(std::move(admin));
// Spectator group
auto spectator = std::make_unique<NetworkGroup>();
spectator->SetName("Spectator");
spectator->ToggleActionPermission(NetworkPermission::Chat);
spectator->Id = 1;
group_list.push_back(std::move(spectator));
// User group
auto user = std::make_unique<NetworkGroup>();
user->SetName("User");
user->ActionsAllowed.fill(0xFF);
user->ToggleActionPermission(NetworkPermission::KickPlayer);
user->ToggleActionPermission(NetworkPermission::ModifyGroups);
user->ToggleActionPermission(NetworkPermission::SetPlayerGroup);
user->ToggleActionPermission(NetworkPermission::Cheat);
user->ToggleActionPermission(NetworkPermission::PasswordlessLogin);
user->ToggleActionPermission(NetworkPermission::ModifyTile);
user->ToggleActionPermission(NetworkPermission::EditScenarioOptions);
user->Id = 2;
group_list.push_back(std::move(user));
SetDefaultGroup(1);
}
void NetworkBase::LoadGroups()
2016-01-24 02:05:53 +01:00
{
group_list.clear();
auto env = GetContext().GetPlatformEnvironment();
auto path = Path::Combine(env->GetDirectoryPath(DIRBASE::USER), u8"groups.json");
json_t jsonGroupConfig;
if (File::Exists(path))
2018-06-22 23:02:47 +02:00
{
try
{
jsonGroupConfig = Json::ReadFromFile(path);
2018-06-22 23:02:47 +02:00
}
catch (const std::exception& e)
{
LOG_ERROR("Failed to read %s as JSON. Setting default groups. %s", path.c_str(), e.what());
}
}
if (!jsonGroupConfig.is_object())
2018-06-22 23:02:47 +02:00
{
SetupDefaultGroups();
2018-06-22 23:02:47 +02:00
}
else
{
json_t jsonGroups = jsonGroupConfig["groups"];
if (jsonGroups.is_array())
2018-06-22 23:02:47 +02:00
{
for (auto& jsonGroup : jsonGroups)
{
group_list.emplace_back(std::make_unique<NetworkGroup>(NetworkGroup::FromJson(jsonGroup)));
}
}
default_group = Json::GetNumber<uint8_t>(jsonGroupConfig["default_group"]);
2018-06-22 23:02:47 +02:00
if (GetGroupByID(default_group) == nullptr)
{
default_group = 0;
}
}
// Host group should always contain all permissions.
group_list.at(0)->ActionsAllowed.fill(0xFF);
2016-05-19 10:23:42 +02:00
}
std::string NetworkBase::BeginLog(const std::string& directory, const std::string& midName, const std::string& filenameFormat)
2016-05-30 15:24:06 +02:00
{
utf8 filename[256];
time_t timer;
time(&timer);
auto tmInfo = localtime(&timer);
2018-06-22 23:02:47 +02:00
if (strftime(filename, sizeof(filename), filenameFormat.c_str(), tmInfo) == 0)
{
throw std::runtime_error("strftime failed");
}
2016-05-30 15:24:06 +02:00
Platform::EnsureDirectoryExists(Path::Combine(directory, midName).c_str());
return Path::Combine(directory, midName, filename);
2016-05-30 15:24:06 +02:00
}
void NetworkBase::AppendLog(std::ostream& fs, std::string_view s)
2016-05-30 15:24:06 +02:00
{
if (fs.fail())
{
LOG_ERROR("bad ostream failed to append log");
return;
}
try
{
utf8 buffer[1024];
time_t timer;
time(&timer);
auto tmInfo = localtime(&timer);
if (strftime(buffer, sizeof(buffer), "[%Y/%m/%d %H:%M:%S] ", tmInfo) != 0)
{
String::Append(buffer, sizeof(buffer), std::string(s).c_str());
String::Append(buffer, sizeof(buffer), PLATFORM_NEWLINE);
fs.write(buffer, strlen(buffer));
}
}
2018-06-22 23:02:47 +02:00
catch (const std::exception& ex)
{
LOG_ERROR("%s", ex.what());
}
2016-05-30 15:24:06 +02:00
}
void NetworkBase::BeginChatLog()
{
auto env = GetContext().GetPlatformEnvironment();
auto directory = env->GetDirectoryPath(DIRBASE::USER, DIRID::LOG_CHAT);
_chatLogPath = BeginLog(directory, "", _chatLogFilenameFormat);
2022-03-18 23:21:23 +01:00
_chat_log_fs.open(fs::u8path(_chatLogPath), std::ios::out | std::ios::app);
}
void NetworkBase::AppendChatLog(std::string_view s)
{
if (gConfigNetwork.LogChat && _chat_log_fs.is_open())
{
AppendLog(_chat_log_fs, s);
}
}
void NetworkBase::CloseChatLog()
2016-05-30 15:24:06 +02:00
{
_chat_log_fs.close();
2016-05-30 15:24:06 +02:00
}
void NetworkBase::BeginServerLog()
{
auto env = GetContext().GetPlatformEnvironment();
auto directory = env->GetDirectoryPath(DIRBASE::USER, DIRID::LOG_SERVER);
_serverLogPath = BeginLog(directory, ServerName, _serverLogFilenameFormat);
2022-03-18 23:21:23 +01:00
_server_log_fs.open(fs::u8path(_serverLogPath), std::ios::out | std::ios::app | std::ios::binary);
// Log server start event
utf8 logMessage[256];
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_CLIENT)
{
FormatStringLegacy(logMessage, sizeof(logMessage), STR_LOG_CLIENT_STARTED, nullptr);
2018-06-22 23:02:47 +02:00
}
else if (GetMode() == NETWORK_MODE_SERVER)
{
FormatStringLegacy(logMessage, sizeof(logMessage), STR_LOG_SERVER_STARTED, nullptr);
}
2019-10-21 05:32:13 +02:00
else
{
logMessage[0] = '\0';
Guard::Assert(false, "Unknown network mode!");
2019-10-21 05:32:13 +02:00
}
AppendServerLog(logMessage);
}
void NetworkBase::AppendServerLog(const std::string& s)
{
if (gConfigNetwork.LogServerActions && _server_log_fs.is_open())
{
AppendLog(_server_log_fs, s);
}
}
void NetworkBase::CloseServerLog()
{
// Log server stopped event
char logMessage[256];
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_CLIENT)
{
FormatStringLegacy(logMessage, sizeof(logMessage), STR_LOG_CLIENT_STOPPED, nullptr);
2018-06-22 23:02:47 +02:00
}
else if (GetMode() == NETWORK_MODE_SERVER)
{
FormatStringLegacy(logMessage, sizeof(logMessage), STR_LOG_SERVER_STOPPED, nullptr);
}
2019-10-21 05:32:13 +02:00
else
{
logMessage[0] = '\0';
Guard::Assert(false, "Unknown network mode!");
2019-10-21 05:32:13 +02:00
}
AppendServerLog(logMessage);
_server_log_fs.close();
}
void NetworkBase::Client_Send_RequestGameState(uint32_t tick)
{
if (_serverState.gamestateSnapshotsEnabled == false)
{
LOG_VERBOSE("Server does not store a gamestate history");
return;
}
LOG_VERBOSE("Requesting gamestate from server for tick %u", tick);
NetworkPacket packet(NetworkCommand::RequestGameState);
packet << tick;
_serverConnection->QueuePacket(std::move(packet));
}
void NetworkBase::Client_Send_TOKEN()
2016-05-19 10:23:42 +02:00
{
LOG_VERBOSE("requesting token");
NetworkPacket packet(NetworkCommand::Token);
_serverConnection->AuthStatus = NetworkAuth::Requested;
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2016-05-19 10:23:42 +02:00
}
void NetworkBase::Client_Send_AUTH(
const std::string& name, const std::string& password, const std::string& pubkey, const std::vector<uint8_t>& signature)
2015-07-10 21:53:41 +02:00
{
NetworkPacket packet(NetworkCommand::Auth);
packet.WriteString(NetworkGetVersion());
2022-02-09 21:57:25 +01:00
packet.WriteString(name);
packet.WriteString(password);
packet.WriteString(pubkey);
assert(signature.size() <= static_cast<size_t>(UINT32_MAX));
packet << static_cast<uint32_t>(signature.size());
packet.Write(signature.data(), signature.size());
_serverConnection->AuthStatus = NetworkAuth::Requested;
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2015-07-15 08:23:04 +02:00
}
void NetworkBase::Client_Send_MAPREQUEST(const std::vector<ObjectEntryDescriptor>& objects)
{
LOG_VERBOSE("client requests %u objects", uint32_t(objects.size()));
NetworkPacket packet(NetworkCommand::MapRequest);
packet << static_cast<uint32_t>(objects.size());
2018-06-22 23:02:47 +02:00
for (const auto& object : objects)
{
std::string name(object.GetName());
LOG_VERBOSE("client requests object %s", name.c_str());
if (object.Generation == ObjectGeneration::DAT)
{
packet << static_cast<uint8_t>(0);
packet.Write(&object.Entry, sizeof(RCTObjectEntry));
}
else
{
packet << static_cast<uint8_t>(1);
packet.WriteString(name);
}
}
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
}
void NetworkBase::ServerSendToken(NetworkConnection& connection)
2016-05-19 10:23:42 +02:00
{
NetworkPacket packet(NetworkCommand::Token);
packet << static_cast<uint32_t>(connection.Challenge.size());
packet.Write(connection.Challenge.data(), connection.Challenge.size());
connection.QueuePacket(std::move(packet));
2016-05-19 10:23:42 +02:00
}
void NetworkBase::ServerSendObjectsList(
2020-07-24 23:10:11 +02:00
NetworkConnection& connection, const std::vector<const ObjectRepositoryItem*>& objects) const
{
LOG_VERBOSE("Server sends objects list with %u items", objects.size());
if (objects.empty())
2018-06-22 23:02:47 +02:00
{
NetworkPacket packet(NetworkCommand::ObjectsList);
packet << static_cast<uint32_t>(0) << static_cast<uint32_t>(objects.size());
connection.QueuePacket(std::move(packet));
}
else
{
for (size_t i = 0; i < objects.size(); ++i)
{
const auto* object = objects[i];
NetworkPacket packet(NetworkCommand::ObjectsList);
packet << static_cast<uint32_t>(i) << static_cast<uint32_t>(objects.size());
if (object->Identifier.empty())
{
// DAT
LOG_VERBOSE("Object %.8s (checksum %x)", object->ObjectEntry.name, object->ObjectEntry.checksum);
packet << static_cast<uint8_t>(0);
packet.Write(&object->ObjectEntry, sizeof(RCTObjectEntry));
}
else
{
// JSON
LOG_VERBOSE("Object %s", object->Identifier.c_str());
packet << static_cast<uint8_t>(1);
packet.WriteString(object->Identifier);
}
connection.QueuePacket(std::move(packet));
}
}
}
void NetworkBase::ServerSendScripts(NetworkConnection& connection)
{
# ifdef ENABLE_SCRIPTING
using namespace OpenRCT2::Scripting;
auto& scriptEngine = GetContext().GetScriptEngine();
// Get remote plugin list.
const auto remotePlugins = scriptEngine.GetRemotePlugins();
LOG_VERBOSE("Server sends %zu scripts", remotePlugins.size());
// Build the data contents for each plugin.
MemoryStream pluginData;
for (auto& plugin : remotePlugins)
{
const auto& code = plugin->GetCode();
const auto codeSize = static_cast<uint32_t>(code.size());
pluginData.WriteValue(codeSize);
pluginData.WriteArray(code.c_str(), code.size());
}
// Send the header packet.
NetworkPacket packetScriptHeader(NetworkCommand::ScriptsHeader);
packetScriptHeader << static_cast<uint32_t>(remotePlugins.size());
packetScriptHeader << static_cast<uint32_t>(pluginData.GetLength());
connection.QueuePacket(std::move(packetScriptHeader));
// Segment the plugin data into chunks and send them.
const uint8_t* pluginDataBuffer = static_cast<const uint8_t*>(pluginData.GetData());
uint32_t dataOffset = 0;
while (dataOffset < pluginData.GetLength())
{
const uint32_t chunkSize = std::min<uint32_t>(pluginData.GetLength() - dataOffset, CHUNK_SIZE);
NetworkPacket packet(NetworkCommand::ScriptsData);
packet << chunkSize;
packet.Write(pluginDataBuffer + dataOffset, chunkSize);
connection.QueuePacket(std::move(packet));
dataOffset += chunkSize;
}
Guard::Assert(dataOffset == pluginData.GetLength());
2020-02-23 13:55:48 +01:00
# else
NetworkPacket packetScriptHeader(NetworkCommand::ScriptsHeader);
packetScriptHeader << static_cast<uint32_t>(0U);
packetScriptHeader << static_cast<uint32_t>(0U);
2020-02-23 13:55:48 +01:00
# endif
}
void NetworkBase::Client_Send_HEARTBEAT(NetworkConnection& connection) const
{
LOG_VERBOSE("Sending heartbeat");
NetworkPacket packet(NetworkCommand::Heartbeat);
connection.QueuePacket(std::move(packet));
}
NetworkStats NetworkBase::GetStats() const
{
NetworkStats stats = {};
if (mode == NETWORK_MODE_CLIENT)
{
stats = _serverConnection->Stats;
}
else
{
for (auto& connection : client_connection_list)
{
for (size_t n = 0; n < EnumValue(NetworkStatisticsGroup::Max); n++)
{
stats.bytesReceived[n] += connection->Stats.bytesReceived[n];
stats.bytesSent[n] += connection->Stats.bytesSent[n];
}
}
}
return stats;
}
void NetworkBase::ServerSendAuth(NetworkConnection& connection)
2015-11-02 04:12:14 +01:00
{
uint8_t new_playerid = 0;
2021-09-24 20:05:50 +02:00
if (connection.Player != nullptr)
2018-06-22 23:02:47 +02:00
{
new_playerid = connection.Player->Id;
}
NetworkPacket packet(NetworkCommand::Auth);
packet << static_cast<uint32_t>(connection.AuthStatus) << new_playerid;
if (connection.AuthStatus == NetworkAuth::BadVersion)
2018-06-22 23:02:47 +02:00
{
packet.WriteString(NetworkGetVersion());
}
connection.QueuePacket(std::move(packet));
if (connection.AuthStatus != NetworkAuth::Ok && connection.AuthStatus != NetworkAuth::RequirePassword)
2018-06-22 23:02:47 +02:00
{
connection.Disconnect();
}
2015-11-02 04:12:14 +01:00
}
void NetworkBase::ServerSendMap(NetworkConnection* connection)
2015-07-15 08:23:04 +02:00
{
2018-06-22 23:02:47 +02:00
std::vector<const ObjectRepositoryItem*> objects;
2021-09-24 20:05:50 +02:00
if (connection != nullptr)
2018-06-22 23:02:47 +02:00
{
objects = connection->RequestedObjects;
2018-06-22 23:02:47 +02:00
}
else
{
// This will send all custom objects to connected clients
// TODO: fix it so custom objects negotiation is performed even in this case.
auto& context = GetContext();
auto& objManager = context.GetObjectManager();
objects = objManager.GetPackableObjects();
}
auto header = SaveForNetwork(objects);
if (header.empty())
2018-06-22 23:02:47 +02:00
{
2021-09-24 20:05:50 +02:00
if (connection != nullptr)
2018-06-22 23:02:47 +02:00
{
connection->SetLastDisconnectReason(STR_MULTIPLAYER_CONNECTION_CLOSED);
connection->Disconnect();
}
return;
}
size_t chunksize = CHUNK_SIZE;
for (size_t i = 0; i < header.size(); i += chunksize)
2018-06-22 23:02:47 +02:00
{
size_t datasize = std::min(chunksize, header.size() - i);
NetworkPacket packet(NetworkCommand::Map);
packet << static_cast<uint32_t>(header.size()) << static_cast<uint32_t>(i);
packet.Write(&header[i], datasize);
2021-09-24 20:05:50 +02:00
if (connection != nullptr)
2018-06-22 23:02:47 +02:00
{
connection->QueuePacket(std::move(packet));
2018-06-22 23:02:47 +02:00
}
else
{
SendPacketToClients(packet);
}
}
2016-10-13 14:57:40 +02:00
}
std::vector<uint8_t> NetworkBase::SaveForNetwork(const std::vector<const ObjectRepositoryItem*>& objects) const
2016-10-13 14:57:40 +02:00
{
std::vector<uint8_t> result;
auto ms = OpenRCT2::MemoryStream();
if (SaveMap(&ms, objects))
2018-06-22 23:02:47 +02:00
{
result.resize(ms.GetLength());
std::memcpy(result.data(), ms.GetData(), result.size());
2018-06-22 23:02:47 +02:00
}
else
{
LOG_WARNING("Failed to export map.");
}
return result;
2015-07-10 21:53:41 +02:00
}
void NetworkBase::Client_Send_CHAT(const char* text)
2015-07-10 21:53:41 +02:00
{
NetworkPacket packet(NetworkCommand::Chat);
packet.WriteString(text);
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2015-07-15 08:23:04 +02:00
}
void NetworkBase::ServerSendChat(const char* text, const std::vector<uint8_t>& playerIds)
2015-07-15 08:23:04 +02:00
{
NetworkPacket packet(NetworkCommand::Chat);
packet.WriteString(text);
if (playerIds.empty())
{
2020-04-30 03:18:32 +02:00
// Empty players / default value means send to all players
SendPacketToClients(packet);
}
else
{
for (auto playerId : playerIds)
{
auto conn = GetPlayerConnection(playerId);
if (conn != nullptr)
{
conn->QueuePacket(packet);
}
}
}
2015-07-10 21:53:41 +02:00
}
void NetworkBase::Client_Send_GAME_ACTION(const GameAction* action)
2017-03-21 20:05:53 +01:00
{
NetworkPacket packet(NetworkCommand::GameAction);
uint32_t networkId = 0;
networkId = ++_actionId;
// I know its ugly, want basic functionality for now.
2017-07-21 19:54:05 +02:00
const_cast<GameAction*>(action)->SetNetworkId(networkId);
2018-06-22 23:02:47 +02:00
if (action->GetCallback())
{
_gameActionCallbacks.insert(std::make_pair(networkId, action->GetCallback()));
}
DataSerialiser stream(true);
action->Serialise(stream);
packet << gCurrentTicks << action->GetType() << stream;
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2017-03-21 20:05:53 +01:00
}
void NetworkBase::ServerSendGameAction(const GameAction* action)
2017-03-21 20:05:53 +01:00
{
NetworkPacket packet(NetworkCommand::GameAction);
DataSerialiser stream(true);
action->Serialise(stream);
packet << gCurrentTicks << action->GetType() << stream;
SendPacketToClients(packet);
2017-03-21 20:05:53 +01:00
}
void NetworkBase::ServerSendTick()
2015-07-15 06:40:22 +02:00
{
NetworkPacket packet(NetworkCommand::Tick);
packet << gCurrentTicks << ScenarioRandState().s0;
uint32_t flags = 0;
// Simple counter which limits how often a sprite checksum gets sent.
// This can get somewhat expensive, so we don't want to push it every tick in release,
// but debug version can check more often.
static int32_t checksum_counter = 0;
checksum_counter++;
2018-06-22 23:02:47 +02:00
if (checksum_counter >= 100)
{
checksum_counter = 0;
flags |= NETWORK_TICK_FLAG_CHECKSUMS;
}
// Send flags always, so we can understand packet structure on the other end,
// and allow for some expansion.
packet << flags;
2018-06-22 23:02:47 +02:00
if (flags & NETWORK_TICK_FLAG_CHECKSUMS)
{
2021-11-24 14:37:47 +01:00
EntitiesChecksum checksum = GetAllEntitiesChecksum();
2022-02-09 21:57:25 +01:00
packet.WriteString(checksum.ToString());
}
SendPacketToClients(packet);
2015-07-16 19:32:43 +02:00
}
void NetworkBase::ServerSendPlayerInfo(int32_t playerId)
{
NetworkPacket packet(NetworkCommand::PlayerInfo);
packet << gCurrentTicks;
auto* player = GetPlayerByID(playerId);
if (player == nullptr)
return;
player->Write(packet);
SendPacketToClients(packet);
}
void NetworkBase::ServerSendPlayerList()
2015-07-16 19:32:43 +02:00
{
NetworkPacket packet(NetworkCommand::PlayerList);
packet << gCurrentTicks << static_cast<uint8_t>(player_list.size());
2018-06-22 23:02:47 +02:00
for (auto& player : player_list)
{
player->Write(packet);
}
SendPacketToClients(packet);
2015-07-15 06:40:22 +02:00
}
void NetworkBase::Client_Send_PING()
2015-07-17 02:24:39 +02:00
{
NetworkPacket packet(NetworkCommand::Ping);
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2015-07-17 02:24:39 +02:00
}
void NetworkBase::ServerSendPing()
2015-07-17 02:24:39 +02:00
{
last_ping_sent_time = Platform::GetTicks();
NetworkPacket packet(NetworkCommand::Ping);
2018-06-22 23:02:47 +02:00
for (auto& client_connection : client_connection_list)
{
client_connection->PingTime = Platform::GetTicks();
}
SendPacketToClients(packet, true);
2015-07-17 02:24:39 +02:00
}
void NetworkBase::ServerSendPingList()
2015-07-17 02:24:39 +02:00
{
NetworkPacket packet(NetworkCommand::PingList);
packet << static_cast<uint8_t>(player_list.size());
2018-06-22 23:02:47 +02:00
for (auto& player : player_list)
{
packet << player->Id << player->Ping;
}
SendPacketToClients(packet);
2015-07-17 02:24:39 +02:00
}
void NetworkBase::ServerSendSetDisconnectMsg(NetworkConnection& connection, const char* msg)
{
NetworkPacket packet(NetworkCommand::DisconnectMessage);
packet.WriteString(msg);
connection.QueuePacket(std::move(packet));
}
json_t NetworkBase::GetServerInfoAsJson() const
2015-11-04 00:31:09 +01:00
{
json_t jsonObj = {
{ "name", gConfigNetwork.ServerName },
{ "requiresPassword", _password.size() > 0 },
{ "version", NetworkGetVersion() },
{ "players", GetNumVisiblePlayers() },
{ "maxPlayers", gConfigNetwork.Maxplayers },
{ "description", gConfigNetwork.ServerDescription },
{ "greeting", gConfigNetwork.ServerGreeting },
{ "dedicated", gOpenRCT2Headless },
};
return jsonObj;
}
void NetworkBase::ServerSendGameInfo(NetworkConnection& connection)
{
NetworkPacket packet(NetworkCommand::GameInfo);
# ifndef DISABLE_HTTP
json_t jsonObj = GetServerInfoAsJson();
// Provider details
json_t jsonProvider = {
{ "name", gConfigNetwork.ProviderName },
{ "email", gConfigNetwork.ProviderEmail },
{ "website", gConfigNetwork.ProviderWebsite },
};
jsonObj["provider"] = jsonProvider;
2022-02-09 21:57:25 +01:00
packet.WriteString(jsonObj.dump());
packet << _serverState.gamestateSnapshotsEnabled;
packet << IsServerPlayerInvisible;
2018-07-21 16:17:06 +02:00
# endif
connection.QueuePacket(std::move(packet));
2015-11-04 00:31:09 +01:00
}
void NetworkBase::ServerSendShowError(NetworkConnection& connection, StringId title, StringId message)
2016-01-20 23:45:09 +01:00
{
NetworkPacket packet(NetworkCommand::ShowError);
packet << title << message;
connection.QueuePacket(std::move(packet));
2016-01-20 23:45:09 +01:00
}
void NetworkBase::ServerSendGroupList(NetworkConnection& connection)
{
NetworkPacket packet(NetworkCommand::GroupList);
packet << static_cast<uint8_t>(group_list.size()) << default_group;
2018-06-22 23:02:47 +02:00
for (auto& group : group_list)
{
group->Write(packet);
}
connection.QueuePacket(std::move(packet));
}
void NetworkBase::ServerSendEventPlayerJoined(const char* playerName)
{
NetworkPacket packet(NetworkCommand::Event);
packet << static_cast<uint16_t>(SERVER_EVENT_PLAYER_JOINED);
packet.WriteString(playerName);
SendPacketToClients(packet);
}
void NetworkBase::ServerSendEventPlayerDisconnected(const char* playerName, const char* reason)
{
NetworkPacket packet(NetworkCommand::Event);
packet << static_cast<uint16_t>(SERVER_EVENT_PLAYER_DISCONNECTED);
packet.WriteString(playerName);
packet.WriteString(reason);
SendPacketToClients(packet);
}
bool NetworkBase::ProcessConnection(NetworkConnection& connection)
2015-07-08 03:21:05 +02:00
{
NetworkReadPacket packetStatus;
uint32_t countProcessed = 0;
2018-06-22 23:02:47 +02:00
do
{
countProcessed++;
packetStatus = connection.ReadPacket();
2018-06-22 23:02:47 +02:00
switch (packetStatus)
{
case NetworkReadPacket::Disconnected:
2018-06-22 23:02:47 +02:00
// closed connection or network error
if (!connection.GetLastDisconnectReason())
{
connection.SetLastDisconnectReason(STR_MULTIPLAYER_CONNECTION_CLOSED);
}
return false;
case NetworkReadPacket::Success:
2018-06-22 23:02:47 +02:00
// done reading in packet
ProcessPacket(connection, connection.InboundPacket);
if (!connection.IsValid())
2018-06-22 23:02:47 +02:00
{
return false;
}
break;
case NetworkReadPacket::MoreData:
2018-06-22 23:02:47 +02:00
// more data required to be read
break;
case NetworkReadPacket::NoData:
2018-06-22 23:02:47 +02:00
// could not read anything from socket
break;
}
} while (packetStatus == NetworkReadPacket::Success && countProcessed < MaxPacketsPerUpdate);
2018-06-22 23:02:47 +02:00
if (!connection.ReceivedPacketRecently())
{
if (!connection.GetLastDisconnectReason())
{
connection.SetLastDisconnectReason(STR_MULTIPLAYER_NO_DATA);
}
return false;
}
return true;
2015-07-09 04:19:12 +02:00
}
void NetworkBase::ProcessPacket(NetworkConnection& connection, NetworkPacket& packet)
2015-02-12 12:30:57 +01:00
{
const auto& handlerList = GetMode() == NETWORK_MODE_SERVER ? server_command_handlers : client_command_handlers;
2021-07-29 19:08:57 +02:00
auto it = handlerList.find(packet.GetCommand());
if (it != handlerList.end())
2018-06-22 23:02:47 +02:00
{
2021-07-29 19:08:57 +02:00
auto commandHandler = it->second;
if (connection.AuthStatus == NetworkAuth::Ok || !packet.CommandRequiresAuth())
2018-06-22 23:02:47 +02:00
{
2021-07-29 19:08:57 +02:00
try
{
(this->*commandHandler)(connection, packet);
}
2021-07-29 19:08:57 +02:00
catch (const std::exception& ex)
{
LOG_VERBOSE("Exception during packet processing: %s", ex.what());
2021-07-29 19:08:57 +02:00
}
}
}
packet.Clear();
2015-07-05 17:19:01 +02:00
}
// This is called at the end of each game tick, this where things should be processed that affects the game state.
void NetworkBase::ProcessPending()
{
if (GetMode() == NETWORK_MODE_SERVER)
{
ProcessDisconnectedClients();
}
else if (GetMode() == NETWORK_MODE_CLIENT)
{
ProcessPlayerInfo();
}
ProcessPlayerList();
}
2020-04-28 20:33:30 +02:00
static bool ProcessPlayerAuthenticatePluginHooks(
const NetworkConnection& connection, std::string_view name, std::string_view publicKeyHash)
2020-04-28 20:33:30 +02:00
{
# ifdef ENABLE_SCRIPTING
using namespace OpenRCT2::Scripting;
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_AUTHENTICATE))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("name", name);
eObj.Set("publicKeyHash", publicKeyHash);
eObj.Set("ipAddress", connection.Socket->GetIpAddress());
eObj.Set("cancel", false);
auto e = eObj.Take();
// Call the subscriptions
hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_AUTHENTICATE, e, false);
// Check if any hook has cancelled the join
if (AsOrDefault(e["cancel"], false))
{
return false;
}
}
# endif
return true;
}
static void ProcessPlayerJoinedPluginHooks(uint8_t playerId)
{
# ifdef ENABLE_SCRIPTING
using namespace OpenRCT2::Scripting;
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_JOIN))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("player", playerId);
auto e = eObj.Take();
// Call the subscriptions
hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_JOIN, e, false);
}
# endif
}
static void ProcessPlayerLeftPluginHooks(uint8_t playerId)
{
# ifdef ENABLE_SCRIPTING
using namespace OpenRCT2::Scripting;
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_LEAVE))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("player", playerId);
auto e = eObj.Take();
// Call the subscriptions
hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_LEAVE, e, false);
}
# endif
}
void NetworkBase::ProcessPlayerList()
{
if (GetMode() == NETWORK_MODE_SERVER)
{
// Avoid sending multiple times the player list, we mark the list invalidated on modifications
// and then send at the end of the tick the final player list.
if (_playerListInvalidated)
{
_playerListInvalidated = false;
ServerSendPlayerList();
}
}
else
{
// As client we have to keep things in order so the update is tick bound.
// Commands/Actions reference players and so this list needs to be in sync with those.
auto itPending = _pendingPlayerLists.begin();
while (itPending != _pendingPlayerLists.end())
{
if (itPending->first > gCurrentTicks)
break;
// List of active players found in the list.
std::vector<uint8_t> activePlayerIds;
2020-04-28 20:33:30 +02:00
std::vector<uint8_t> newPlayers;
std::vector<uint8_t> removedPlayers;
for (const auto& pendingPlayer : itPending->second.players)
{
activePlayerIds.push_back(pendingPlayer.Id);
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Add new player.
player = AddPlayer("", "");
2021-09-24 20:05:50 +02:00
if (player != nullptr)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
else
{
// Update.
*player = pendingPlayer;
}
}
// Remove any players that are not in newly received list
2020-04-28 20:33:30 +02:00
for (const auto& player : player_list)
{
2020-04-28 20:33:30 +02:00
if (std::find(activePlayerIds.begin(), activePlayerIds.end(), player->Id) == activePlayerIds.end())
{
2020-04-28 20:33:30 +02:00
removedPlayers.push_back(player->Id);
}
}
2020-04-28 20:33:30 +02:00
// Run player removed hooks (must be before players removed from list)
for (auto playerId : removedPlayers)
{
ProcessPlayerLeftPluginHooks(playerId);
}
// Run player joined hooks (must be after players added to list)
for (auto playerId : newPlayers)
{
ProcessPlayerJoinedPluginHooks(playerId);
}
// Now actually remove removed players from player list
player_list.erase(
std::remove_if(
player_list.begin(), player_list.end(),
[&removedPlayers](const std::unique_ptr<NetworkPlayer>& player) {
return std::find(removedPlayers.begin(), removedPlayers.end(), player->Id) != removedPlayers.end();
}),
player_list.end());
_pendingPlayerLists.erase(itPending);
itPending = _pendingPlayerLists.begin();
}
}
}
void NetworkBase::ProcessPlayerInfo()
{
auto range = _pendingPlayerInfo.equal_range(gCurrentTicks);
for (auto it = range.first; it != range.second; it++)
{
auto* player = GetPlayerByID(it->second.Id);
if (player != nullptr)
{
const NetworkPlayer& networkedInfo = it->second;
player->Flags = networkedInfo.Flags;
player->Group = networkedInfo.Group;
player->LastAction = networkedInfo.LastAction;
player->LastActionCoord = networkedInfo.LastActionCoord;
player->MoneySpent = networkedInfo.MoneySpent;
player->CommandsRan = networkedInfo.CommandsRan;
}
}
_pendingPlayerInfo.erase(gCurrentTicks);
}
void NetworkBase::ProcessDisconnectedClients()
{
for (auto it = client_connection_list.begin(); it != client_connection_list.end();)
{
auto& connection = *it;
if (!connection->ShouldDisconnect)
{
it++;
continue;
}
// Make sure to send all remaining packets out before disconnecting.
connection->SendQueuedPackets();
connection->Socket->Disconnect();
ServerClientDisconnected(connection);
RemovePlayer(connection);
it = client_connection_list.erase(it);
}
}
void NetworkBase::AddClient(std::unique_ptr<ITcpSocket>&& socket)
2015-07-07 22:52:41 +02:00
{
2018-12-17 12:58:12 +01:00
// Log connection info.
char addr[128];
snprintf(addr, sizeof(addr), "Client joined from %s", socket->GetHostName());
AppendServerLog(addr);
2018-12-17 12:58:12 +01:00
// Store connection
auto connection = std::make_unique<NetworkConnection>();
connection->Socket = std::move(socket);
client_connection_list.push_back(std::move(connection));
2015-07-07 22:52:41 +02:00
}
void NetworkBase::ServerClientDisconnected(std::unique_ptr<NetworkConnection>& connection)
2015-07-07 16:09:21 +02:00
{
NetworkPlayer* connection_player = connection->Player;
if (connection_player == nullptr)
return;
2018-06-22 23:02:47 +02:00
char text[256];
const char* has_disconnected_args[2] = {
connection_player->Name.c_str(),
connection->GetLastDisconnectReason(),
};
2021-09-24 20:05:50 +02:00
if (has_disconnected_args[1] != nullptr)
{
FormatStringLegacy(text, 256, STR_MULTIPLAYER_PLAYER_HAS_DISCONNECTED_WITH_REASON, has_disconnected_args);
}
else
{
FormatStringLegacy(text, 256, STR_MULTIPLAYER_PLAYER_HAS_DISCONNECTED_NO_REASON, &(has_disconnected_args[0]));
}
2023-01-16 21:14:50 +01:00
ChatAddHistory(text);
Peep* pickup_peep = NetworkGetPickupPeep(connection_player->Id);
2021-09-24 20:05:50 +02:00
if (pickup_peep != nullptr)
{
PeepPickupAction pickupAction{ PeepPickupType::Cancel,
2023-01-27 18:18:44 +01:00
pickup_peep->Id,
{ NetworkGetPickupPeepOldX(connection_player->Id), 0, 0 },
NetworkGetCurrentPlayerId() };
auto res = GameActions::Execute(&pickupAction);
}
ServerSendEventPlayerDisconnected(
const_cast<char*>(connection_player->Name.c_str()), connection->GetLastDisconnectReason());
// Log player disconnected event
AppendServerLog(text);
2020-04-28 20:33:30 +02:00
ProcessPlayerLeftPluginHooks(connection_player->Id);
}
void NetworkBase::RemovePlayer(std::unique_ptr<NetworkConnection>& connection)
{
NetworkPlayer* connection_player = connection->Player;
if (connection_player == nullptr)
return;
2018-06-22 23:02:47 +02:00
player_list.erase(
std::remove_if(
player_list.begin(), player_list.end(),
2018-06-22 23:02:47 +02:00
[connection_player](std::unique_ptr<NetworkPlayer>& player) { return player.get() == connection_player; }),
player_list.end());
// Send new player list.
_playerListInvalidated = true;
2015-02-12 03:01:02 +01:00
}
NetworkPlayer* NetworkBase::AddPlayer(const std::string& name, const std::string& keyhash)
2015-07-17 02:24:39 +02:00
{
NetworkPlayer* addedplayer = nullptr;
int32_t newid = -1;
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_SERVER)
{
// Find first unused player id
2018-06-22 23:02:47 +02:00
for (int32_t id = 0; id < 255; id++)
{
if (std::find_if(
player_list.begin(), player_list.end(),
2018-06-22 23:02:47 +02:00
[&id](std::unique_ptr<NetworkPlayer> const& player) { return player->Id == id; })
== player_list.end())
{
newid = id;
break;
}
}
2018-06-22 23:02:47 +02:00
}
else
{
newid = 0;
}
2018-06-22 23:02:47 +02:00
if (newid != -1)
{
std::unique_ptr<NetworkPlayer> player;
2018-06-22 23:02:47 +02:00
if (GetMode() == NETWORK_MODE_SERVER)
{
// Load keys host may have added manually
_userManager.Load();
// Check if the key is registered
2018-06-22 23:02:47 +02:00
const NetworkUser* networkUser = _userManager.GetUserByHash(keyhash);
2017-10-03 00:00:32 +02:00
player = std::make_unique<NetworkPlayer>();
player->Id = newid;
player->KeyHash = keyhash;
2018-06-22 23:02:47 +02:00
if (networkUser == nullptr)
{
player->Group = GetDefaultGroup();
2019-05-10 22:00:38 +02:00
if (!name.empty())
2018-06-22 23:02:47 +02:00
{
2019-02-23 15:54:17 +01:00
player->SetName(MakePlayerNameUnique(String::Trim(name)));
}
2018-06-22 23:02:47 +02:00
}
else
{
player->Group = networkUser->GroupId.has_value() ? *networkUser->GroupId : GetDefaultGroup();
player->SetName(networkUser->Name);
}
// Send new player list.
_playerListInvalidated = true;
2018-06-22 23:02:47 +02:00
}
else
{
2017-10-03 00:00:32 +02:00
player = std::make_unique<NetworkPlayer>();
player->Id = newid;
player->Group = GetDefaultGroup();
player->SetName(String::Trim(std::string(name)));
}
addedplayer = player.get();
player_list.push_back(std::move(player));
}
return addedplayer;
2015-07-17 02:24:39 +02:00
}
std::string NetworkBase::MakePlayerNameUnique(const std::string& name)
{
// Note: Player names are case-insensitive
std::string new_name = name.substr(0, 31);
int32_t counter = 1;
bool unique;
2018-06-22 23:02:47 +02:00
do
{
unique = true;
// Check if there is already a player with this name in the server
2018-06-22 23:02:47 +02:00
for (const auto& player : player_list)
{
if (String::Equals(player->Name.c_str(), new_name.c_str(), true))
{
unique = false;
break;
}
}
2018-06-22 23:02:47 +02:00
if (unique)
{
// Check if there is already a registered player with this name
2018-06-22 23:02:47 +02:00
if (_userManager.GetUserByName(new_name) != nullptr)
{
unique = false;
}
}
2018-06-22 23:02:47 +02:00
if (!unique)
{
// Increment name counter
counter++;
new_name = name.substr(0, 31) + " #" + std::to_string(counter);
}
} while (!unique);
return new_name;
}
void NetworkBase::Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet)
2016-05-19 10:23:42 +02:00
{
auto keyPath = NetworkGetPrivateKeyPath(gConfigNetwork.PlayerName);
if (!File::Exists(keyPath))
2018-06-22 23:02:47 +02:00
{
LOG_ERROR("Key file (%s) was not found. Restart client to re-generate it.", keyPath.c_str());
return;
}
try
{
auto fs = FileStream(keyPath, FILE_MODE_OPEN);
if (!_key.LoadPrivate(&fs))
{
2018-01-02 20:23:22 +01:00
throw std::runtime_error("Failed to load private key.");
}
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
LOG_ERROR("Failed to load key %s", keyPath.c_str());
connection.SetLastDisconnectReason(STR_MULTIPLAYER_VERIFICATION_FAILURE);
connection.Disconnect();
return;
}
uint32_t challenge_size;
packet >> challenge_size;
const char* challenge = reinterpret_cast<const char*>(packet.Read(challenge_size));
std::vector<uint8_t> signature;
const std::string pubkey = _key.PublicKeyString();
_challenge.resize(challenge_size);
std::memcpy(_challenge.data(), challenge, challenge_size);
bool ok = _key.Sign(_challenge.data(), _challenge.size(), signature);
2018-06-22 23:02:47 +02:00
if (!ok)
{
LOG_ERROR("Failed to sign server's challenge.");
connection.SetLastDisconnectReason(STR_MULTIPLAYER_VERIFICATION_FAILURE);
connection.Disconnect();
return;
}
// Don't keep private key in memory. There's no need and it may get leaked
// when process dump gets collected at some point in future.
_key.Unload();
Client_Send_AUTH(gConfigNetwork.PlayerName, gCustomPassword, pubkey, signature);
2016-05-19 10:23:42 +02:00
}
void NetworkBase::ServerHandleRequestGamestate(NetworkConnection& connection, NetworkPacket& packet)
{
uint32_t tick;
packet >> tick;
if (_serverState.gamestateSnapshotsEnabled == false)
{
// Ignore this if this is off.
return;
}
IGameStateSnapshots* snapshots = GetContext().GetGameStateSnapshots();
const GameStateSnapshot_t* snapshot = snapshots->GetLinkedSnapshot(tick);
2021-09-24 20:05:50 +02:00
if (snapshot != nullptr)
{
MemoryStream snapshotMemory;
DataSerialiser ds(true, snapshotMemory);
snapshots->SerialiseSnapshot(const_cast<GameStateSnapshot_t&>(*snapshot), ds);
uint32_t bytesSent = 0;
uint32_t length = static_cast<uint32_t>(snapshotMemory.GetLength());
while (bytesSent < length)
{
uint32_t dataSize = CHUNK_SIZE;
if (bytesSent + dataSize > snapshotMemory.GetLength())
{
dataSize = snapshotMemory.GetLength() - bytesSent;
}
NetworkPacket packetGameStateChunk(NetworkCommand::GameState);
packetGameStateChunk << tick << length << bytesSent << dataSize;
packetGameStateChunk.Write(static_cast<const uint8_t*>(snapshotMemory.GetData()) + bytesSent, dataSize);
connection.QueuePacket(std::move(packetGameStateChunk));
bytesSent += dataSize;
}
}
}
void NetworkBase::ServerHandleHeartbeat(NetworkConnection& connection, NetworkPacket& packet)
{
LOG_VERBOSE("Client %s heartbeat", connection.Socket->GetHostName());
connection.ResetLastPacketTime();
}
void NetworkBase::Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 06:40:22 +02:00
{
uint32_t auth_status;
packet >> auth_status >> const_cast<uint8_t&>(player_id);
connection.AuthStatus = static_cast<NetworkAuth>(auth_status);
2018-06-22 23:02:47 +02:00
switch (connection.AuthStatus)
{
case NetworkAuth::Ok:
2018-06-22 23:02:47 +02:00
Client_Send_GAMEINFO();
break;
case NetworkAuth::BadName:
2018-06-22 23:02:47 +02:00
connection.SetLastDisconnectReason(STR_MULTIPLAYER_BAD_PLAYER_NAME);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
case NetworkAuth::BadVersion:
2018-06-22 23:02:47 +02:00
{
auto version = std::string(packet.ReadString());
auto versionp = version.c_str();
connection.SetLastDisconnectReason(STR_MULTIPLAYER_INCORRECT_SOFTWARE_VERSION, &versionp);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
}
case NetworkAuth::BadPassword:
2018-06-22 23:02:47 +02:00
connection.SetLastDisconnectReason(STR_MULTIPLAYER_BAD_PASSWORD);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
case NetworkAuth::VerificationFailure:
2018-06-22 23:02:47 +02:00
connection.SetLastDisconnectReason(STR_MULTIPLAYER_VERIFICATION_FAILURE);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
case NetworkAuth::Full:
2018-06-22 23:02:47 +02:00
connection.SetLastDisconnectReason(STR_MULTIPLAYER_SERVER_FULL);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
case NetworkAuth::RequirePassword:
2022-11-06 21:49:07 +01:00
ContextOpenWindowView(WV_NETWORK_PASSWORD);
2018-06-22 23:02:47 +02:00
break;
case NetworkAuth::UnknownKeyDisallowed:
2018-06-22 23:02:47 +02:00
connection.SetLastDisconnectReason(STR_MULTIPLAYER_UNKNOWN_KEY_DISALLOWED);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
default:
connection.SetLastDisconnectReason(STR_MULTIPLAYER_RECEIVED_INVALID_DATA);
connection.Disconnect();
2018-06-22 23:02:47 +02:00
break;
}
2015-07-15 06:40:22 +02:00
}
void NetworkBase::ServerClientJoined(std::string_view name, const std::string& keyhash, NetworkConnection& connection)
2016-05-19 10:23:42 +02:00
{
auto player = AddPlayer(std::string(name), keyhash);
connection.Player = player;
2020-04-28 20:33:30 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
char text[256];
const char* player_name = static_cast<const char*>(player->Name.c_str());
FormatStringLegacy(text, 256, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name);
2023-01-16 21:14:50 +01:00
ChatAddHistory(text);
2017-02-05 16:45:23 +01:00
auto& context = GetContext();
auto& objManager = context.GetObjectManager();
auto objects = objManager.GetPackableObjects();
ServerSendObjectsList(connection, objects);
ServerSendScripts(connection);
// Log player joining event
std::string playerNameHash = player->Name + " (" + keyhash + ")";
player_name = static_cast<const char*>(playerNameHash.c_str());
FormatStringLegacy(text, 256, STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, &player_name);
AppendServerLog(text);
2020-04-28 20:33:30 +02:00
ProcessPlayerJoinedPluginHooks(player->Id);
}
2016-05-19 10:23:42 +02:00
}
void NetworkBase::ServerHandleToken(NetworkConnection& connection, [[maybe_unused]] NetworkPacket& packet)
2016-05-19 10:23:42 +02:00
{
uint8_t token_size = 10 + (rand() & 0x7f);
connection.Challenge.resize(token_size);
2018-06-22 23:02:47 +02:00
for (int32_t i = 0; i < token_size; i++)
{
connection.Challenge[i] = static_cast<uint8_t>(rand() & 0xff);
}
ServerSendToken(connection);
2016-05-19 10:23:42 +02:00
}
void NetworkBase::Client_Handle_OBJECTS_LIST(NetworkConnection& connection, NetworkPacket& packet)
{
auto& repo = GetContext().GetObjectRepository();
uint32_t index = 0;
uint32_t totalObjects = 0;
packet >> index >> totalObjects;
2020-07-25 15:27:19 +02:00
static constexpr uint32_t OBJECT_START_INDEX = 0;
if (index == OBJECT_START_INDEX)
{
_missingObjects.clear();
}
if (totalObjects > 0)
{
char objectListMsg[256];
const uint32_t args[] = {
index + 1,
totalObjects,
};
FormatStringLegacy(objectListMsg, 256, STR_MULTIPLAYER_RECEIVING_OBJECTS_LIST, &args);
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ objectListMsg });
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
2020-07-25 10:45:04 +02:00
uint8_t objectType{};
packet >> objectType;
if (objectType == 0)
{
// DAT
auto entry = reinterpret_cast<const RCTObjectEntry*>(packet.Read(sizeof(RCTObjectEntry)));
if (entry != nullptr)
{
const auto* object = repo.FindObject(entry);
if (object == nullptr)
{
auto objectName = std::string(entry->GetName());
LOG_VERBOSE("Requesting object %s with checksum %x from server", objectName.c_str(), entry->checksum);
_missingObjects.push_back(ObjectEntryDescriptor(*entry));
}
else if (object->ObjectEntry.checksum != entry->checksum || object->ObjectEntry.flags != entry->flags)
{
auto objectName = std::string(entry->GetName());
LOG_WARNING(
"Object %s has different checksum/flags (%x/%x) than server (%x/%x).", objectName.c_str(),
object->ObjectEntry.checksum, object->ObjectEntry.flags, entry->checksum, entry->flags);
}
}
}
else
{
// JSON
auto identifier = packet.ReadString();
if (!identifier.empty())
{
const auto* object = repo.FindObject(identifier);
if (object == nullptr)
{
auto objectName = std::string(identifier);
LOG_VERBOSE("Requesting object %s from server", objectName.c_str());
_missingObjects.push_back(ObjectEntryDescriptor(objectName));
}
}
}
}
2020-07-25 10:45:04 +02:00
if (index + 1 >= totalObjects)
{
LOG_VERBOSE("client received object list, it has %u entries", totalObjects);
2020-07-25 15:27:19 +02:00
Client_Send_MAPREQUEST(_missingObjects);
_missingObjects.clear();
}
}
void NetworkBase::Client_Handle_SCRIPTS_HEADER(NetworkConnection& connection, NetworkPacket& packet)
{
uint32_t numScripts{};
uint32_t dataSize{};
packet >> numScripts >> dataSize;
2020-02-23 13:55:48 +01:00
# ifdef ENABLE_SCRIPTING
_serverScriptsData.data.Clear();
_serverScriptsData.pluginCount = numScripts;
_serverScriptsData.dataSize = dataSize;
2020-02-23 13:55:48 +01:00
# else
if (numScripts > 0)
{
connection.SetLastDisconnectReason("The client requires plugin support.");
2020-02-23 13:55:48 +01:00
Close();
}
# endif
}
void NetworkBase::Client_Handle_SCRIPTS_DATA(NetworkConnection& connection, NetworkPacket& packet)
{
# ifdef ENABLE_SCRIPTING
uint32_t dataSize{};
packet >> dataSize;
Guard::Assert(dataSize > 0);
const auto* data = packet.Read(dataSize);
Guard::Assert(data != nullptr);
auto& scriptsData = _serverScriptsData.data;
scriptsData.Write(data, dataSize);
if (scriptsData.GetLength() == _serverScriptsData.dataSize)
{
auto& scriptEngine = GetContext().GetScriptEngine();
scriptsData.SetPosition(0);
for (uint32_t i = 0; i < _serverScriptsData.pluginCount; ++i)
{
const auto codeSize = scriptsData.ReadValue<uint32_t>();
const auto scriptData = scriptsData.ReadArray<char>(codeSize);
auto code = std::string_view(reinterpret_cast<const char*>(scriptData.get()), codeSize);
scriptEngine.AddNetworkPlugin(code);
}
Guard::Assert(scriptsData.GetPosition() == scriptsData.GetLength());
// Empty the current buffer.
_serverScriptsData = {};
}
# else
connection.SetLastDisconnectReason("The client requires plugin support.");
Close();
# endif
}
void NetworkBase::Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet)
{
uint32_t tick;
uint32_t totalSize;
uint32_t offset;
uint32_t dataSize;
packet >> tick >> totalSize >> offset >> dataSize;
if (offset == 0)
{
// Reset
_serverGameState = MemoryStream();
}
_serverGameState.SetPosition(offset);
const uint8_t* data = packet.Read(dataSize);
_serverGameState.Write(data, dataSize);
LOG_VERBOSE(
"Received Game State %.02f%%",
(static_cast<float>(_serverGameState.GetLength()) / static_cast<float>(totalSize)) * 100.0f);
if (_serverGameState.GetLength() == totalSize)
{
_serverGameState.SetPosition(0);
DataSerialiser ds(false, _serverGameState);
IGameStateSnapshots* snapshots = GetContext().GetGameStateSnapshots();
GameStateSnapshot_t& serverSnapshot = snapshots->CreateSnapshot();
snapshots->SerialiseSnapshot(serverSnapshot, ds);
const GameStateSnapshot_t* desyncSnapshot = snapshots->GetLinkedSnapshot(tick);
2021-09-24 20:05:50 +02:00
if (desyncSnapshot != nullptr)
{
GameStateCompareData cmpData = snapshots->Compare(serverSnapshot, *desyncSnapshot);
std::string outputPath = GetContext().GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::USER, DIRID::LOG_DESYNCS);
Platform::EnsureDirectoryExists(outputPath.c_str());
char uniqueFileName[128] = {};
snprintf(
uniqueFileName, sizeof(uniqueFileName), "desync_%llu_%u.txt",
static_cast<long long unsigned>(Platform::GetDatetimeNowUTC()), tick);
std::string outputFile = Path::Combine(outputPath, uniqueFileName);
if (snapshots->LogCompareDataToFile(outputFile, cmpData))
{
LOG_INFO("Wrote desync report to '%s'", outputFile.c_str());
auto ft = Formatter();
ft.Add<char*>(uniqueFileName);
char str_desync[1024];
FormatStringLegacy(str_desync, sizeof(str_desync), STR_DESYNC_REPORT, ft.Data());
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_desync });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
}
}
}
}
void NetworkBase::ServerHandleMapRequest(NetworkConnection& connection, NetworkPacket& packet)
{
uint32_t size;
packet >> size;
LOG_VERBOSE("Client requested %u objects", size);
auto& repo = GetContext().GetObjectRepository();
for (uint32_t i = 0; i < size; i++)
{
uint8_t generation{};
packet >> generation;
std::string objectName;
const ObjectRepositoryItem* item{};
if (generation == static_cast<uint8_t>(ObjectGeneration::DAT))
{
const auto* entry = reinterpret_cast<const RCTObjectEntry*>(packet.Read(sizeof(RCTObjectEntry)));
objectName = std::string(entry->GetName());
LOG_VERBOSE("Client requested object %s", objectName.c_str());
item = repo.FindObject(entry);
}
else
{
objectName = std::string(packet.ReadString());
LOG_VERBOSE("Client requested object %s", objectName.c_str());
item = repo.FindObject(objectName);
}
2018-06-22 23:02:47 +02:00
if (item == nullptr)
{
LOG_WARNING("Client tried getting non-existent object %s from us.", objectName.c_str());
2018-06-22 23:02:47 +02:00
}
else
{
connection.RequestedObjects.push_back(item);
}
}
auto player_name = connection.Player->Name.c_str();
ServerSendMap(&connection);
ServerSendEventPlayerJoined(player_name);
ServerSendGroupList(connection);
}
void NetworkBase::ServerHandleAuth(NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 08:23:04 +02:00
{
if (connection.AuthStatus != NetworkAuth::Ok)
2018-06-22 23:02:47 +02:00
{
auto* hostName = connection.Socket->GetHostName();
auto gameversion = packet.ReadString();
auto name = packet.ReadString();
auto password = packet.ReadString();
auto pubkey = packet.ReadString();
uint32_t sigsize;
packet >> sigsize;
if (pubkey.empty())
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::VerificationFailure;
2018-06-22 23:02:47 +02:00
}
else
{
try
{
// RSA technically supports keys up to 65536 bits, so this is the
// maximum signature size for now.
constexpr auto MaxRSASignatureSizeInBytes = 8192;
if (sigsize == 0 || sigsize > MaxRSASignatureSizeInBytes)
{
throw std::runtime_error("Invalid signature size");
}
std::vector<uint8_t> signature;
signature.resize(sigsize);
const uint8_t* signatureData = packet.Read(sigsize);
if (signatureData == nullptr)
{
2018-01-02 20:23:22 +01:00
throw std::runtime_error("Failed to read packet.");
}
std::memcpy(signature.data(), signatureData, sigsize);
auto ms = MemoryStream(pubkey.data(), pubkey.size());
if (!connection.Key.LoadPublic(&ms))
{
2018-01-02 20:23:22 +01:00
throw std::runtime_error("Failed to load public key.");
}
bool verified = connection.Key.Verify(connection.Challenge.data(), connection.Challenge.size(), signature);
const std::string hash = connection.Key.PublicKeyHash();
if (verified)
{
LOG_VERBOSE("Connection %s: Signature verification ok. Hash %s", hostName, hash.c_str());
if (gConfigNetwork.KnownKeysOnly && _userManager.GetUserByHash(hash) == nullptr)
{
LOG_VERBOSE("Connection %s: Hash %s, not known", hostName, hash.c_str());
connection.AuthStatus = NetworkAuth::UnknownKeyDisallowed;
}
else
{
connection.AuthStatus = NetworkAuth::Verified;
}
}
else
{
connection.AuthStatus = NetworkAuth::VerificationFailure;
LOG_VERBOSE("Connection %s: Signature verification failed!", hostName);
}
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
connection.AuthStatus = NetworkAuth::VerificationFailure;
LOG_VERBOSE("Connection %s: Signature verification failed, invalid data!", hostName);
}
}
bool passwordless = false;
if (connection.AuthStatus == NetworkAuth::Verified)
2018-06-22 23:02:47 +02:00
{
const NetworkGroup* group = GetGroupByID(GetGroupIDByHash(connection.Key.PublicKeyHash()));
2021-11-11 15:43:39 +01:00
passwordless = group->CanPerformAction(NetworkPermission::PasswordlessLogin);
}
if (gameversion != NetworkGetVersion())
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::BadVersion;
LOG_INFO("Connection %s: Bad version.", hostName);
2018-06-22 23:02:47 +02:00
}
else if (name.empty())
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::BadName;
LOG_INFO("Connection %s: Bad name.", connection.Socket->GetHostName());
2018-06-22 23:02:47 +02:00
}
else if (!passwordless)
{
if (password.empty() && !_password.empty())
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::RequirePassword;
LOG_INFO("Connection %s: Requires password.", hostName);
2018-06-22 23:02:47 +02:00
}
else if (!password.empty() && _password != password)
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::BadPassword;
LOG_INFO("Connection %s: Bad password.", hostName);
}
}
if (GetNumVisiblePlayers() >= gConfigNetwork.Maxplayers)
2018-06-22 23:02:47 +02:00
{
connection.AuthStatus = NetworkAuth::Full;
LOG_INFO("Connection %s: Server is full.", hostName);
2018-06-22 23:02:47 +02:00
}
else if (connection.AuthStatus == NetworkAuth::Verified)
2018-06-22 23:02:47 +02:00
{
const std::string hash = connection.Key.PublicKeyHash();
2020-04-28 20:33:30 +02:00
if (ProcessPlayerAuthenticatePluginHooks(connection, name, hash))
{
connection.AuthStatus = NetworkAuth::Ok;
ServerClientJoined(name, hash, connection);
2020-04-28 20:33:30 +02:00
}
else
{
connection.AuthStatus = NetworkAuth::VerificationFailure;
LOG_INFO("Connection %s: Denied by plugin.", hostName);
2020-04-28 20:33:30 +02:00
}
2018-06-22 23:02:47 +02:00
}
ServerSendAuth(connection);
}
2015-07-15 08:23:04 +02:00
}
void NetworkBase::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 06:40:22 +02:00
{
uint32_t size, offset;
packet >> size >> offset;
2020-08-02 23:34:02 +02:00
int32_t chunksize = static_cast<int32_t>(packet.Header.Size - packet.BytesRead);
2018-06-22 23:02:47 +02:00
if (chunksize <= 0)
{
return;
}
if (offset == 0)
{
// Start of a new map load, clear the queue now as we have to buffer them
// until the map is fully loaded.
GameActions::ClearQueue();
GameActions::SuspendQueue();
_serverTickData.clear();
_clientMapLoaded = false;
}
2018-06-22 23:02:47 +02:00
if (size > chunk_buffer.size())
{
chunk_buffer.resize(size);
}
char str_downloading_map[256];
uint32_t downloading_map_args[2] = {
(offset + chunksize) / 1024,
size / 1024,
};
FormatStringLegacy(str_downloading_map, 256, STR_MULTIPLAYER_DOWNLOADING_MAP, downloading_map_args);
auto intent = Intent(WindowClass::NetworkStatus);
intent.PutExtra(INTENT_EXTRA_MESSAGE, std::string{ str_downloading_map });
intent.PutExtra(INTENT_EXTRA_CALLBACK, []() -> void { ::GetContext()->GetNetwork().Close(); });
2022-11-06 21:49:07 +01:00
ContextOpenIntent(&intent);
std::memcpy(&chunk_buffer[offset], const_cast<void*>(static_cast<const void*>(packet.Read(chunksize))), chunksize);
2018-06-22 23:02:47 +02:00
if (offset + chunksize == size)
{
// Allow queue processing of game actions again.
GameActions::ResumeQueue();
2022-11-06 21:49:07 +01:00
ContextForceCloseWindowByClass(WindowClass::NetworkStatus);
GameUnloadScripts();
GameNotifyMapChange();
bool has_to_free = false;
2018-06-22 23:02:47 +02:00
uint8_t* data = &chunk_buffer[0];
size_t data_size = size;
auto ms = MemoryStream(data, data_size);
if (LoadMap(&ms))
{
GameLoadInit();
GameLoadScripts();
GameNotifyMapChanged();
_serverState.tick = gCurrentTicks;
// WindowNetworkStatusOpen("Loaded new map from network");
_serverState.state = NetworkServerStatus::Ok;
_clientMapLoaded = true;
gFirstTimeSaving = true;
// Notify user he is now online and which shortcut key enables chat
NetworkChatShowConnectedMessage();
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
FixInvalidVehicleSpriteSizes();
// NOTE: Game actions are normally processed before processing the player list.
// Given that during map load game actions are buffered we have to process the
// player list first to have valid players for the queued game actions.
ProcessPlayerList();
}
else
{
2018-06-22 23:02:47 +02:00
// Something went wrong, game is not loaded. Return to main screen.
auto loadOrQuitAction = LoadOrQuitAction(LoadOrQuitModes::OpenSavePrompt, PromptMode::SaveBeforeQuit);
2019-02-21 10:34:30 +01:00
GameActions::Execute(&loadOrQuitAction);
}
if (has_to_free)
{
free(data);
}
}
2015-07-15 06:40:22 +02:00
}
bool NetworkBase::LoadMap(IStream* stream)
2017-02-08 19:16:33 +01:00
{
bool result = false;
try
{
auto& context = GetContext();
auto& objManager = context.GetObjectManager();
auto importer = ParkImporter::CreateParkFile(context.GetObjectRepository());
auto loadResult = importer->LoadFromStream(stream, false);
objManager.LoadObjects(loadResult.RequiredObjects);
importer->Import();
EntityTweener::Get().Reset();
MapAnimationAutoCreate();
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
result = true;
}
catch (const std::exception& e)
{
Console::Error::WriteLine("Unable to read map from server: %s", e.what());
}
return result;
2017-02-08 19:16:33 +01:00
}
bool NetworkBase::SaveMap(IStream* stream, const std::vector<const ObjectRepositoryItem*>& objects) const
2017-02-08 19:16:33 +01:00
{
bool result = false;
2022-03-07 21:40:48 +01:00
PrepareMapForSave();
try
{
auto exporter = std::make_unique<ParkFileExporter>();
exporter->ExportObjectsList = objects;
exporter->Export(*stream);
result = true;
}
catch (const std::exception& e)
{
Console::Error::WriteLine("Unable to serialise map: %s", e.what());
}
return result;
2017-02-08 19:16:33 +01:00
}
void NetworkBase::Client_Handle_CHAT([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 06:40:22 +02:00
{
auto text = packet.ReadString();
if (!text.empty())
2018-06-22 23:02:47 +02:00
{
2023-01-16 21:14:50 +01:00
ChatAddHistory(std::string(text));
}
2015-07-15 06:40:22 +02:00
}
static bool ProcessChatMessagePluginHooks(uint8_t playerId, std::string& text)
2020-04-22 23:53:32 +02:00
{
# ifdef ENABLE_SCRIPTING
2020-04-22 23:53:32 +02:00
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_CHAT))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
auto objIdx = duk_push_object(ctx);
duk_push_number(ctx, playerId);
2020-04-22 23:53:32 +02:00
duk_put_prop_string(ctx, objIdx, "player");
duk_push_string(ctx, text.c_str());
duk_put_prop_string(ctx, objIdx, "message");
auto e = DukValue::take_from_stack(ctx);
// Call the subscriptions
hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::NETWORK_CHAT, e, false);
// Update text from object if subscriptions changed it
if (e["message"].type() != DukValue::Type::STRING)
{
// Subscription set text to non-string, do not relay message
return false;
}
text = e["message"].as_string();
if (text.empty())
{
// Subscription set text to empty string, do not relay message
return false;
}
}
# endif
return true;
}
void NetworkBase::ServerHandleChat(NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 08:23:04 +02:00
{
2020-02-11 00:33:00 +01:00
auto szText = packet.ReadString();
if (szText.empty())
2020-02-11 00:33:00 +01:00
return;
2021-09-24 20:05:50 +02:00
if (connection.Player != nullptr)
2018-06-22 23:02:47 +02:00
{
NetworkGroup* group = GetGroupByID(connection.Player->Group);
2021-11-11 15:43:39 +01:00
if (group == nullptr || !group->CanPerformAction(NetworkPermission::Chat))
2018-06-22 23:02:47 +02:00
{
return;
}
}
2020-02-11 00:33:00 +01:00
std::string text(szText);
2020-02-11 00:33:00 +01:00
if (connection.Player != nullptr)
2018-06-22 23:02:47 +02:00
{
if (!ProcessChatMessagePluginHooks(connection.Player->Id, text))
2020-02-11 00:33:00 +01:00
{
2020-04-22 23:53:32 +02:00
// Message not to be relayed
return;
2020-02-11 00:33:00 +01:00
}
}
2020-02-11 00:33:00 +01:00
const char* formatted = FormatChat(connection.Player, text.c_str());
2023-01-16 21:14:50 +01:00
ChatAddHistory(formatted);
ServerSendChat(formatted);
2015-07-15 08:23:04 +02:00
}
void NetworkBase::Client_Handle_GAME_ACTION([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2017-03-23 20:49:19 +01:00
{
uint32_t tick;
GameCommand actionType;
packet >> tick >> actionType;
2017-07-21 19:21:18 +02:00
MemoryStream stream;
2020-08-02 23:34:02 +02:00
const size_t size = packet.Header.Size - packet.BytesRead;
2017-07-21 19:21:18 +02:00
stream.WriteArray(packet.Read(size), size);
stream.SetPosition(0);
DataSerialiser ds(false, stream);
GameAction::Ptr action = GameActions::Create(actionType);
if (action == nullptr)
{
LOG_ERROR("Received unregistered game action type: 0x%08X", actionType);
return;
}
action->Serialise(ds);
2018-11-20 06:04:42 +01:00
if (player_id == action->GetPlayer().id)
{
2018-03-24 05:50:59 +01:00
// Only execute callbacks that belong to us,
// clients can have identical network ids assigned.
auto itr = _gameActionCallbacks.find(action->GetNetworkId());
if (itr != _gameActionCallbacks.end())
{
action->SetCallback(itr->second);
_gameActionCallbacks.erase(itr);
}
}
GameActions::Enqueue(std::move(action), tick);
2017-03-23 20:49:19 +01:00
}
2015-07-15 06:40:22 +02:00
void NetworkBase::ServerHandleGameAction(NetworkConnection& connection, NetworkPacket& packet)
2017-03-23 20:49:19 +01:00
{
uint32_t tick;
GameCommand actionType;
2017-07-21 19:21:18 +02:00
NetworkPlayer* player = connection.Player;
if (player == nullptr)
2018-06-22 23:02:47 +02:00
{
2017-07-21 19:21:18 +02:00
return;
}
packet >> tick >> actionType;
2017-07-21 19:21:18 +02:00
// Don't let clients send pause or quit
if (actionType == GameCommand::TogglePause || actionType == GameCommand::LoadOrQuit)
{
return;
}
if (actionType != GameCommand::Custom)
2018-06-22 23:02:47 +02:00
{
2020-03-01 22:56:49 +01:00
// Check if player's group permission allows command to run
NetworkGroup* group = GetGroupByID(connection.Player->Group);
if (group == nullptr || group->CanPerformCommand(actionType) == false)
{
ServerSendShowError(connection, STR_CANT_DO_THIS, STR_PERMISSION_DENIED);
2020-03-01 22:56:49 +01:00
return;
}
2017-07-21 19:21:18 +02:00
}
// Create and enqueue the action.
GameAction::Ptr ga = GameActions::Create(actionType);
if (ga == nullptr)
2018-06-22 23:02:47 +02:00
{
LOG_ERROR(
"Received unregistered game action type: 0x%08X from player: (%d) %s", actionType, connection.Player->Id,
connection.Player->Name.c_str());
return;
2017-07-21 19:21:18 +02:00
}
2019-03-01 11:01:00 +01:00
// Player who is hosting is not affected by cooldowns.
if ((player->Flags & NETWORK_PLAYER_FLAG_ISSERVER) == 0)
2018-06-22 23:02:47 +02:00
{
2019-03-01 11:01:00 +01:00
auto cooldownIt = player->CooldownTime.find(actionType);
if (cooldownIt != std::end(player->CooldownTime))
2018-06-22 23:02:47 +02:00
{
2019-03-01 11:01:00 +01:00
if (cooldownIt->second > 0)
{
ServerSendShowError(connection, STR_CANT_DO_THIS, STR_NETWORK_ACTION_RATE_LIMIT_MESSAGE);
2019-03-01 11:01:00 +01:00
return;
}
2017-07-21 19:21:18 +02:00
}
2019-03-01 11:01:00 +01:00
uint32_t cooldownTime = ga->GetCooldownTime();
if (cooldownTime > 0)
{
player->CooldownTime[actionType] = cooldownTime;
}
}
DataSerialiser stream(false);
2020-08-02 23:34:02 +02:00
const size_t size = packet.Header.Size - packet.BytesRead;
stream.GetStream().WriteArray(packet.Read(size), size);
stream.GetStream().SetPosition(0);
ga->Serialise(stream);
// Set player to sender, should be 0 if sent from client.
2018-11-20 06:04:42 +01:00
ga->SetPlayer(NetworkPlayerId_t{ connection.Player->Id });
GameActions::Enqueue(std::move(ga), tick);
2017-03-23 20:49:19 +01:00
}
void NetworkBase::Client_Handle_TICK([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2015-07-15 06:40:22 +02:00
{
uint32_t srand0;
uint32_t flags;
uint32_t serverTick;
packet >> serverTick >> srand0 >> flags;
2019-03-12 14:22:31 +01:00
ServerTickData tickData;
tickData.srand0 = srand0;
tickData.tick = serverTick;
2019-03-12 14:22:31 +01:00
if (flags & NETWORK_TICK_FLAG_CHECKSUMS)
2018-06-22 23:02:47 +02:00
{
auto text = packet.ReadString();
if (!text.empty())
{
tickData.spriteHash = text;
}
}
// Don't let the history grow too much.
while (_serverTickData.size() >= 100)
{
_serverTickData.erase(_serverTickData.begin());
}
_serverState.tick = serverTick;
_serverTickData.emplace(serverTick, tickData);
2015-07-15 06:40:22 +02:00
}
void NetworkBase::Client_Handle_PLAYERINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
{
uint32_t tick;
packet >> tick;
NetworkPlayer playerInfo;
playerInfo.Read(packet);
_pendingPlayerInfo.emplace(tick, playerInfo);
}
void NetworkBase::Client_Handle_PLAYERLIST([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2015-07-16 19:32:43 +02:00
{
uint32_t tick;
uint8_t size;
packet >> tick >> size;
auto& pending = _pendingPlayerLists[tick];
pending.players.clear();
2018-06-22 23:02:47 +02:00
for (uint32_t i = 0; i < size; i++)
{
NetworkPlayer tempplayer;
tempplayer.Read(packet);
pending.players.push_back(std::move(tempplayer));
}
2015-07-17 02:24:39 +02:00
}
void NetworkBase::Client_Handle_PING([[maybe_unused]] NetworkConnection& connection, [[maybe_unused]] NetworkPacket& packet)
2015-07-17 02:24:39 +02:00
{
Client_Send_PING();
2015-07-17 02:24:39 +02:00
}
void NetworkBase::ServerHandlePing(NetworkConnection& connection, [[maybe_unused]] NetworkPacket& packet)
2015-07-17 02:24:39 +02:00
{
int32_t ping = Platform::GetTicks() - connection.PingTime;
2018-06-22 23:02:47 +02:00
if (ping < 0)
{
ping = 0;
}
2021-09-24 20:05:50 +02:00
if (connection.Player != nullptr)
2018-06-22 23:02:47 +02:00
{
connection.Player->Ping = ping;
WindowInvalidateByNumber(WindowClass::Player, connection.Player->Id);
}
2015-07-17 02:24:39 +02:00
}
void NetworkBase::Client_Handle_PINGLIST([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2015-07-17 02:24:39 +02:00
{
uint8_t size;
packet >> size;
2018-06-22 23:02:47 +02:00
for (uint32_t i = 0; i < size; i++)
{
uint8_t id;
uint16_t ping;
packet >> id >> ping;
NetworkPlayer* player = GetPlayerByID(id);
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
player->Ping = ping;
}
}
WindowInvalidateByClass(WindowClass::Player);
2015-07-16 19:32:43 +02:00
}
void NetworkBase::Client_Handle_SETDISCONNECTMSG(NetworkConnection& connection, NetworkPacket& packet)
{
auto disconnectmsg = packet.ReadString();
if (!disconnectmsg.empty())
2018-06-22 23:02:47 +02:00
{
connection.SetLastDisconnectReason(disconnectmsg);
}
}
void NetworkBase::ServerHandleGameInfo(NetworkConnection& connection, [[maybe_unused]] NetworkPacket& packet)
2015-11-04 00:31:09 +01:00
{
ServerSendGameInfo(connection);
2015-11-04 00:31:09 +01:00
}
void NetworkBase::Client_Handle_SHOWERROR([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2016-01-20 23:45:09 +01:00
{
2022-07-31 14:22:58 +02:00
StringId title, message;
packet >> title >> message;
2022-11-06 21:49:07 +01:00
ContextShowError(title, message, {});
2016-01-22 07:32:40 +01:00
}
void NetworkBase::Client_Handle_GROUPLIST([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2016-01-22 07:32:40 +01:00
{
group_list.clear();
uint8_t size;
packet >> size >> default_group;
2018-06-22 23:02:47 +02:00
for (uint32_t i = 0; i < size; i++)
{
NetworkGroup group;
group.Read(packet);
auto newgroup = std::make_unique<NetworkGroup>(group);
group_list.push_back(std::move(newgroup));
}
2016-01-20 23:45:09 +01:00
}
void NetworkBase::Client_Handle_EVENT([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
{
uint16_t eventType;
packet >> eventType;
2018-06-22 23:02:47 +02:00
switch (eventType)
{
2018-06-22 23:02:47 +02:00
case SERVER_EVENT_PLAYER_JOINED:
{
auto playerName = packet.ReadString();
auto message = FormatStringID(STR_MULTIPLAYER_PLAYER_HAS_JOINED_THE_GAME, playerName);
2023-01-16 21:14:50 +01:00
ChatAddHistory(message);
2018-06-22 23:02:47 +02:00
break;
}
case SERVER_EVENT_PLAYER_DISCONNECTED:
{
auto playerName = packet.ReadString();
auto reason = packet.ReadString();
std::string message;
if (reason.empty())
2018-06-22 23:02:47 +02:00
{
message = FormatStringID(STR_MULTIPLAYER_PLAYER_HAS_DISCONNECTED_NO_REASON, playerName);
2018-06-22 23:02:47 +02:00
}
else
{
message = FormatStringID(STR_MULTIPLAYER_PLAYER_HAS_DISCONNECTED_WITH_REASON, playerName, reason);
2018-06-22 23:02:47 +02:00
}
2023-01-16 21:14:50 +01:00
ChatAddHistory(message);
2018-06-22 23:02:47 +02:00
break;
}
}
}
void NetworkBase::Client_Send_GAMEINFO()
2016-05-30 23:25:43 +02:00
{
LOG_VERBOSE("requesting gameinfo");
NetworkPacket packet(NetworkCommand::GameInfo);
2018-12-17 12:58:12 +01:00
_serverConnection->QueuePacket(std::move(packet));
2016-05-30 23:25:43 +02:00
}
void NetworkBase::Client_Handle_GAMEINFO([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
2016-05-30 23:25:43 +02:00
{
auto jsonString = packet.ReadString();
packet >> _serverState.gamestateSnapshotsEnabled;
packet >> IsServerPlayerInvisible;
2016-05-30 23:25:43 +02:00
json_t jsonData = Json::FromString(jsonString);
2016-05-30 23:25:43 +02:00
if (jsonData.is_object())
2018-06-22 23:02:47 +02:00
{
ServerName = Json::GetString(jsonData["name"]);
ServerDescription = Json::GetString(jsonData["description"]);
ServerGreeting = Json::GetString(jsonData["greeting"]);
json_t jsonProvider = jsonData["provider"];
if (jsonProvider.is_object())
{
ServerProviderName = Json::GetString(jsonProvider["name"]);
ServerProviderEmail = Json::GetString(jsonProvider["email"]);
ServerProviderWebsite = Json::GetString(jsonProvider["website"]);
}
}
NetworkChatShowServerGreeting();
2016-05-30 23:25:43 +02:00
}
void NetworkReconnect()
{
2021-08-17 05:20:07 +02:00
OpenRCT2::GetContext()->GetNetwork().Reconnect();
}
void NetworkShutdownClient()
2015-11-04 00:31:09 +01:00
{
2021-08-17 05:20:07 +02:00
OpenRCT2::GetContext()->GetNetwork().ServerClientDisconnected();
2015-11-04 00:31:09 +01:00
}
int32_t NetworkBeginClient(const std::string& host, int32_t port)
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().BeginClient(host, port);
2015-07-10 21:53:41 +02:00
}
int32_t NetworkBeginServer(int32_t port, const std::string& address)
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().BeginServer(port, address);
2015-07-10 21:53:41 +02:00
}
void NetworkUpdate()
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
OpenRCT2::GetContext()->GetNetwork().Update();
2015-07-10 21:53:41 +02:00
}
void NetworkProcessPending()
{
2021-08-17 05:20:07 +02:00
OpenRCT2::GetContext()->GetNetwork().ProcessPending();
}
void NetworkFlush()
{
2021-08-17 05:20:07 +02:00
OpenRCT2::GetContext()->GetNetwork().Flush();
}
int32_t NetworkGetMode()
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().GetMode();
2015-07-10 21:53:41 +02:00
}
int32_t NetworkGetStatus()
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().GetStatus();
}
bool NetworkIsDesynchronised()
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().IsDesynchronised();
}
bool NetworkCheckDesynchronisation()
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().CheckDesynchronizaton();
}
void NetworkRequestGamestateSnapshot()
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().RequestStateSnapshot();
}
void NetworkSendTick()
{
OpenRCT2::GetContext()->GetNetwork().ServerSendTick();
}
NetworkAuth NetworkGetAuthstatus()
2015-07-16 19:32:43 +02:00
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().GetAuthStatus();
2015-07-16 19:32:43 +02:00
}
uint32_t NetworkGetServerTick()
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().GetServerTick();
2015-07-10 21:53:41 +02:00
}
uint8_t NetworkGetCurrentPlayerId()
{
2021-08-17 05:20:07 +02:00
return OpenRCT2::GetContext()->GetNetwork().GetPlayerID();
}
int32_t NetworkGetNumPlayers()
2015-07-16 19:32:43 +02:00
{
return OpenRCT2::GetContext()->GetNetwork().GetTotalNumPlayers();
}
int32_t NetworkGetNumVisiblePlayers()
{
return OpenRCT2::GetContext()->GetNetwork().GetNumVisiblePlayers();
2015-07-16 19:32:43 +02:00
}
const char* NetworkGetPlayerName(uint32_t index)
2015-07-16 19:32:43 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return static_cast<const char*>(network.player_list[index]->Name.c_str());
2015-07-16 19:32:43 +02:00
}
uint32_t NetworkGetPlayerFlags(uint32_t index)
2015-07-17 02:24:39 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->Flags;
2015-07-17 02:24:39 +02:00
}
int32_t NetworkGetPlayerPing(uint32_t index)
2015-07-17 02:24:39 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->Ping;
2015-07-17 02:24:39 +02:00
}
int32_t NetworkGetPlayerID(uint32_t index)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->Id;
}
money64 NetworkGetPlayerMoneySpent(uint32_t index)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->MoneySpent;
2016-01-20 23:45:09 +01:00
}
std::string NetworkGetPlayerIPAddress(uint32_t id)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
auto conn = network.GetPlayerConnection(id);
2020-04-30 03:18:32 +02:00
if (conn != nullptr && conn->Socket != nullptr)
{
return conn->Socket->GetIpAddress();
}
return {};
}
std::string NetworkGetPlayerPublicKeyHash(uint32_t id)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
auto player = network.GetPlayerByID(id);
if (player != nullptr)
{
return player->KeyHash;
}
return {};
}
void NetworkAddPlayerMoneySpent(uint32_t index, money64 cost)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
network.player_list[index]->AddMoneySpent(cost);
2016-01-20 23:45:09 +01:00
}
int32_t NetworkGetPlayerLastAction(uint32_t index, int32_t time)
2016-01-23 20:32:02 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
if (time && Platform::GetTicks() > network.player_list[index]->LastActionTime + time)
2018-06-22 23:02:47 +02:00
{
return -999;
}
2021-08-17 05:20:07 +02:00
return network.player_list[index]->LastAction;
2016-01-23 20:32:02 +01:00
}
void NetworkSetPlayerLastAction(uint32_t index, GameCommand command)
2016-01-23 20:32:02 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
network.player_list[index]->LastAction = static_cast<int32_t>(NetworkActions::FindCommand(command));
network.player_list[index]->LastActionTime = Platform::GetTicks();
2016-01-23 20:32:02 +01:00
}
CoordsXYZ NetworkGetPlayerLastActionCoord(uint32_t index)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, OpenRCT2::GetContext()->GetNetwork().player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->LastActionCoord;
}
void NetworkSetPlayerLastActionCoord(uint32_t index, const CoordsXYZ& coord)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
if (index < network.player_list.size())
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
network.player_list[index]->LastActionCoord = coord;
}
}
uint32_t NetworkGetPlayerCommandsRan(uint32_t index)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, OpenRCT2::GetContext()->GetNetwork().player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->CommandsRan;
2016-01-20 23:45:09 +01:00
}
int32_t NetworkGetPlayerIndex(uint32_t id)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
auto it = network.GetPlayerIteratorByID(id);
if (it == network.player_list.end())
2018-06-22 23:02:47 +02:00
{
return -1;
}
2021-08-17 05:20:07 +02:00
return static_cast<int32_t>(network.GetPlayerIteratorByID(id) - network.player_list.begin());
2016-01-20 23:45:09 +01:00
}
uint8_t NetworkGetPlayerGroup(uint32_t index)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.player_list[index]->Group;
2016-01-20 23:45:09 +01:00
}
void NetworkSetPlayerGroup(uint32_t index, uint32_t groupindex)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.player_list);
Guard::IndexInRange(groupindex, network.group_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
network.player_list[index]->Group = network.group_list[groupindex]->Id;
2016-01-20 23:45:09 +01:00
}
int32_t NetworkGetGroupIndex(uint8_t id)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
auto it = network.GetGroupIteratorByID(id);
if (it == network.group_list.end())
2018-06-22 23:02:47 +02:00
{
return -1;
}
2021-08-17 05:20:07 +02:00
return static_cast<int32_t>(network.GetGroupIteratorByID(id) - network.group_list.begin());
2016-01-20 23:45:09 +01:00
}
uint8_t NetworkGetGroupID(uint32_t index)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(index, network.group_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.group_list[index]->Id;
2016-01-20 23:45:09 +01:00
}
int32_t NetworkGetNumGroups()
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return static_cast<int32_t>(network.group_list.size());
2016-01-20 23:45:09 +01:00
}
const char* NetworkGetGroupName(uint32_t index)
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.group_list[index]->GetName().c_str();
2016-01-20 23:45:09 +01:00
}
void NetworkChatShowConnectedMessage()
{
auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
std::string s = windowManager->GetKeyboardShortcutString("interface.misc.multiplayer_chat");
2018-06-22 23:02:47 +02:00
const char* sptr = s.c_str();
utf8 buffer[256];
FormatStringLegacy(buffer, sizeof(buffer), STR_MULTIPLAYER_CONNECTED_CHAT_HINT, &sptr);
NetworkPlayer server;
server.Name = "Server";
const char* formatted = NetworkBase::FormatChat(&server, buffer);
2023-01-16 21:14:50 +01:00
ChatAddHistory(formatted);
}
// Display server greeting if one exists
void NetworkChatShowServerGreeting()
{
const auto& greeting = NetworkGetServerGreeting();
if (!greeting.empty())
2018-06-22 23:02:47 +02:00
{
2020-10-14 00:34:18 +02:00
thread_local std::string greeting_formatted;
greeting_formatted.assign("{OUTLINE}{GREEN}");
greeting_formatted += greeting;
2023-01-16 21:14:50 +01:00
ChatAddHistory(greeting_formatted);
}
}
GameActions::Result NetworkSetPlayerGroup(
NetworkPlayerId_t actionPlayerId, NetworkPlayerId_t playerId, uint8_t groupId, bool isExecuting)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
NetworkPlayer* player = network.GetPlayerByID(playerId);
2021-08-17 05:20:07 +02:00
NetworkGroup* fromgroup = network.GetGroupByID(actionPlayerId);
if (player == nullptr)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::InvalidParameters, STR_CANT_DO_THIS, STR_NONE);
}
2021-09-24 20:05:50 +02:00
if (network.GetGroupByID(groupId) == nullptr)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::InvalidParameters, STR_CANT_DO_THIS, STR_NONE);
}
2018-06-22 23:02:47 +02:00
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
return GameActions::Result(
GameActions::Status::InvalidParameters, STR_CANT_CHANGE_GROUP_THAT_THE_HOST_BELONGS_TO, STR_NONE);
}
2021-09-24 20:05:50 +02:00
if (groupId == 0 && fromgroup != nullptr && fromgroup->Id != 0)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::InvalidParameters, STR_CANT_SET_TO_THIS_GROUP, STR_NONE);
}
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
player->Group = groupId;
if (NetworkGetMode() == NETWORK_MODE_SERVER)
2018-06-22 23:02:47 +02:00
{
// Add or update saved user
2021-08-17 05:20:07 +02:00
NetworkUserManager& userManager = network._userManager;
NetworkUser* networkUser = userManager.GetOrAddUser(player->KeyHash);
networkUser->GroupId = groupId;
networkUser->Name = player->Name;
2021-08-17 05:20:07 +02:00
userManager.Save();
}
WindowInvalidateByNumber(WindowClass::Player, playerId);
// Log set player group event
2021-08-17 05:20:07 +02:00
NetworkPlayer* game_command_player = network.GetPlayerByID(actionPlayerId);
NetworkGroup* new_player_group = network.GetGroupByID(groupId);
char log_msg[256];
const char* args[3] = {
player->Name.c_str(),
new_player_group->GetName().c_str(),
game_command_player->Name.c_str(),
};
FormatStringLegacy(log_msg, 256, STR_LOG_SET_PLAYER_GROUP, args);
NetworkAppendServerLog(log_msg);
}
return GameActions::Result();
2016-01-20 23:45:09 +01:00
}
GameActions::Result NetworkModifyGroups(
NetworkPlayerId_t actionPlayerId, ModifyGroupType type, uint8_t groupId, const std::string& name, uint32_t permissionIndex,
PermissionState permissionState, bool isExecuting)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
switch (type)
{
case ModifyGroupType::AddGroup:
{
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
NetworkGroup* newgroup = network.AddGroup();
2019-05-13 20:49:06 +02:00
if (newgroup == nullptr)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::Unknown, STR_CANT_DO_THIS, STR_NONE);
2018-06-22 23:02:47 +02:00
}
2017-07-22 11:56:46 +02:00
}
}
2018-06-22 23:02:47 +02:00
break;
case ModifyGroupType::RemoveGroup:
{
if (groupId == 0)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::Disallowed, STR_THIS_GROUP_CANNOT_BE_MODIFIED, STR_NONE);
}
2021-08-17 05:20:07 +02:00
for (const auto& it : network.player_list)
2018-06-22 23:02:47 +02:00
{
2019-05-13 20:49:06 +02:00
if ((it.get())->Group == groupId)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(
GameActions::Status::Disallowed, STR_CANT_REMOVE_GROUP_THAT_PLAYERS_BELONG_TO, STR_NONE);
2018-06-22 23:02:47 +02:00
}
2017-07-22 11:56:46 +02:00
}
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
network.RemoveGroup(groupId);
2018-06-22 23:02:47 +02:00
}
}
2018-06-22 23:02:47 +02:00
break;
case ModifyGroupType::SetPermissions:
{
if (groupId == 0)
{ // can't change admin group permissions
return GameActions::Result(GameActions::Status::Disallowed, STR_THIS_GROUP_CANNOT_BE_MODIFIED, STR_NONE);
}
2018-06-22 23:02:47 +02:00
NetworkGroup* mygroup = nullptr;
2021-08-17 05:20:07 +02:00
NetworkPlayer* player = network.GetPlayerByID(actionPlayerId);
auto networkPermission = static_cast<NetworkPermission>(permissionIndex);
2019-05-13 20:49:06 +02:00
if (player != nullptr && permissionState == PermissionState::Toggle)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
mygroup = network.GetGroupByID(player->Group);
if (mygroup == nullptr || !mygroup->CanPerformAction(networkPermission))
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(
GameActions::Status::Disallowed, STR_CANT_MODIFY_PERMISSION_THAT_YOU_DO_NOT_HAVE_YOURSELF, STR_NONE);
2018-06-22 23:02:47 +02:00
}
}
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
NetworkGroup* group = network.GetGroupByID(groupId);
2019-05-13 20:49:06 +02:00
if (group != nullptr)
2018-06-22 23:02:47 +02:00
{
if (permissionState != PermissionState::Toggle)
2018-06-22 23:02:47 +02:00
{
2019-05-13 20:49:06 +02:00
if (mygroup != nullptr)
2018-06-22 23:02:47 +02:00
{
if (permissionState == PermissionState::SetAll)
2018-06-22 23:02:47 +02:00
{
group->ActionsAllowed = mygroup->ActionsAllowed;
}
else
{
group->ActionsAllowed.fill(0x00);
}
}
}
2018-06-22 23:02:47 +02:00
else
{
group->ToggleActionPermission(networkPermission);
2018-06-22 23:02:47 +02:00
}
}
2018-06-22 23:02:47 +02:00
}
}
2018-06-22 23:02:47 +02:00
break;
case ModifyGroupType::SetName:
{
2021-08-17 05:20:07 +02:00
NetworkGroup* group = network.GetGroupByID(groupId);
2018-06-22 23:02:47 +02:00
const char* oldName = group->GetName().c_str();
if (strcmp(oldName, name.c_str()) == 0)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result();
2018-06-22 23:02:47 +02:00
}
if (name.empty())
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(
GameActions::Status::InvalidParameters, STR_CANT_RENAME_GROUP, STR_INVALID_GROUP_NAME);
2018-06-22 23:02:47 +02:00
}
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2019-05-13 20:49:06 +02:00
if (group != nullptr)
2018-06-22 23:02:47 +02:00
{
group->SetName(name);
2018-06-22 23:02:47 +02:00
}
}
}
2018-06-22 23:02:47 +02:00
break;
case ModifyGroupType::SetDefault:
{
if (groupId == 0)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::Disallowed, STR_CANT_SET_TO_THIS_GROUP, STR_NONE);
2018-06-22 23:02:47 +02:00
}
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
network.SetDefaultGroup(groupId);
}
}
2018-06-22 23:02:47 +02:00
break;
2019-05-13 20:53:33 +02:00
default:
LOG_ERROR("Invalid Modify Group Type: %u", static_cast<uint8_t>(type));
return GameActions::Result(GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
}
2021-08-17 05:20:07 +02:00
network.SaveGroups();
return GameActions::Result();
}
GameActions::Result NetworkKickPlayer(NetworkPlayerId_t playerId, bool isExecuting)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
NetworkPlayer* player = network.GetPlayerByID(playerId);
2018-06-22 23:02:47 +02:00
if (player == nullptr)
{
2020-02-11 00:33:00 +01:00
// Player might be already removed by the PLAYERLIST command, need to refactor non-game commands executing too
// early.
return GameActions::Result(GameActions::Status::Unknown, STR_NONE, STR_NONE);
}
2021-09-24 20:05:50 +02:00
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result(GameActions::Status::Disallowed, STR_CANT_KICK_THE_HOST, STR_NONE);
}
2017-07-22 11:56:46 +02:00
if (isExecuting)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
if (network.GetMode() == NETWORK_MODE_SERVER)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
network.KickPlayer(playerId);
2021-08-17 05:20:07 +02:00
NetworkUserManager& networkUserManager = network._userManager;
networkUserManager.Load();
networkUserManager.RemoveUser(player->KeyHash);
networkUserManager.Save();
}
}
return GameActions::Result();
2016-01-20 23:45:09 +01:00
}
uint8_t NetworkGetDefaultGroup()
2016-01-20 23:45:09 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.GetDefaultGroup();
2016-01-20 23:45:09 +01:00
}
int32_t NetworkGetNumActions()
{
return static_cast<int32_t>(NetworkActions::Actions.size());
}
StringId NetworkGetActionNameStringID(uint32_t index)
{
if (index < NetworkActions::Actions.size())
{
return NetworkActions::Actions[index].Name;
2018-06-22 23:02:47 +02:00
}
2021-09-15 22:22:15 +02:00
return STR_NONE;
}
int32_t NetworkCanPerformAction(uint32_t groupindex, NetworkPermission index)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(groupindex, network.group_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.group_list[groupindex]->CanPerformAction(index);
}
int32_t NetworkCanPerformCommand(uint32_t groupindex, int32_t index)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
Guard::IndexInRange(groupindex, network.group_list);
2020-08-10 15:35:54 +02:00
2021-08-17 05:20:07 +02:00
return network.group_list[groupindex]->CanPerformCommand(static_cast<GameCommand>(index)); // TODO
}
void NetworkSetPickupPeep(uint8_t playerid, Peep* peep)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
if (network.GetMode() == NETWORK_MODE_NONE)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
_pickup_peep = peep;
2018-06-22 23:02:47 +02:00
}
else
{
2021-08-17 05:20:07 +02:00
NetworkPlayer* player = network.GetPlayerByID(playerid);
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
player->PickupPeep = peep;
}
}
}
Peep* NetworkGetPickupPeep(uint8_t playerid)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
if (network.GetMode() == NETWORK_MODE_NONE)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
return _pickup_peep;
2018-06-22 23:02:47 +02:00
}
2021-09-15 22:22:15 +02:00
NetworkPlayer* player = network.GetPlayerByID(playerid);
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
2021-09-15 22:22:15 +02:00
return player->PickupPeep;
2017-07-22 11:56:46 +02:00
}
2021-09-15 22:22:15 +02:00
return nullptr;
}
void NetworkSetPickupPeepOldX(uint8_t playerid, int32_t x)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
if (network.GetMode() == NETWORK_MODE_NONE)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
_pickup_peep_old_x = x;
2018-06-22 23:02:47 +02:00
}
else
{
2021-08-17 05:20:07 +02:00
NetworkPlayer* player = network.GetPlayerByID(playerid);
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
player->PickupPeepOldX = x;
}
}
}
int32_t NetworkGetPickupPeepOldX(uint8_t playerid)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
if (network.GetMode() == NETWORK_MODE_NONE)
2018-06-22 23:02:47 +02:00
{
2017-07-22 11:56:46 +02:00
return _pickup_peep_old_x;
2018-06-22 23:02:47 +02:00
}
2021-09-15 22:22:15 +02:00
NetworkPlayer* player = network.GetPlayerByID(playerid);
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
2021-09-15 22:22:15 +02:00
return player->PickupPeepOldX;
2017-07-22 11:56:46 +02:00
}
2021-09-15 22:22:15 +02:00
return -1;
}
bool NetworkIsServerPlayerInvisible()
{
return OpenRCT2::GetContext()->GetNetwork().IsServerPlayerInvisible;
}
int32_t NetworkGetCurrentPlayerGroupIndex()
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
NetworkPlayer* player = network.GetPlayerByID(network.GetPlayerID());
2021-09-24 20:05:50 +02:00
if (player != nullptr)
2018-06-22 23:02:47 +02:00
{
return NetworkGetGroupIndex(player->Group);
2017-07-22 11:56:46 +02:00
}
return -1;
}
void NetworkSendChat(const char* text, const std::vector<uint8_t>& playerIds)
2015-07-10 21:53:41 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
if (network.GetMode() == NETWORK_MODE_CLIENT)
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
network.Client_Send_CHAT(text);
2018-06-22 23:02:47 +02:00
}
2021-08-17 05:20:07 +02:00
else if (network.GetMode() == NETWORK_MODE_SERVER)
2018-06-22 23:02:47 +02:00
{
std::string message = text;
2021-08-17 05:20:07 +02:00
if (ProcessChatMessagePluginHooks(network.GetPlayerID(), message))
{
2021-08-17 05:20:07 +02:00
auto player = network.GetPlayerByID(network.GetPlayerID());
if (player != nullptr)
{
2021-08-17 05:20:07 +02:00
auto formatted = network.FormatChat(player, message.c_str());
if (playerIds.empty()
2021-08-17 05:20:07 +02:00
|| std::find(playerIds.begin(), playerIds.end(), network.GetPlayerID()) != playerIds.end())
{
2020-04-30 03:18:32 +02:00
// Server is one of the recipients
2023-01-16 21:14:50 +01:00
ChatAddHistory(formatted);
}
network.ServerSendChat(formatted, playerIds);
}
}
}
2015-07-10 21:53:41 +02:00
}
void NetworkSendGameAction(const GameAction* action)
2017-03-21 20:05:53 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
switch (network.GetMode())
2018-06-22 23:02:47 +02:00
{
case NETWORK_MODE_SERVER:
network.ServerSendGameAction(action);
2018-06-22 23:02:47 +02:00
break;
case NETWORK_MODE_CLIENT:
2021-08-17 05:20:07 +02:00
network.Client_Send_GAME_ACTION(action);
2018-06-22 23:02:47 +02:00
break;
2017-07-21 19:21:18 +02:00
}
2017-03-21 20:05:53 +01:00
}
void NetworkSendPassword(const std::string& password)
2015-11-02 04:12:14 +01:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
const auto keyPath = NetworkGetPrivateKeyPath(gConfigNetwork.PlayerName);
if (!File::Exists(keyPath))
2018-06-22 23:02:47 +02:00
{
LOG_ERROR("Private key %s missing! Restart the game to generate it.", keyPath.c_str());
return;
}
try
{
auto fs = FileStream(keyPath, FILE_MODE_OPEN);
2021-08-17 05:20:07 +02:00
network._key.LoadPrivate(&fs);
}
2018-06-22 23:02:47 +02:00
catch (const std::exception&)
{
LOG_ERROR("Error reading private key from %s.", keyPath.c_str());
return;
}
2021-08-17 05:20:07 +02:00
const std::string pubkey = network._key.PublicKeyString();
std::vector<uint8_t> signature;
2021-08-17 05:20:07 +02:00
network._key.Sign(network._challenge.data(), network._challenge.size(), signature);
// Don't keep private key in memory. There's no need and it may get leaked
// when process dump gets collected at some point in future.
2021-08-17 05:20:07 +02:00
network._key.Unload();
network.Client_Send_AUTH(gConfigNetwork.PlayerName, password, pubkey, signature);
2015-11-02 04:12:14 +01:00
}
void NetworkSetPassword(const char* password)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
network.SetPassword(password);
}
void NetworkAppendChatLog(std::string_view text)
2016-05-30 15:24:06 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
network.AppendChatLog(text);
2016-05-30 15:24:06 +02:00
}
void NetworkAppendServerLog(const utf8* text)
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
network.AppendServerLog(text);
}
static u8string NetworkGetKeysDirectory()
2016-05-25 21:46:59 +02:00
{
auto env = GetContext()->GetPlatformEnvironment();
return Path::Combine(env->GetDirectoryPath(DIRBASE::USER), u8"keys");
2016-05-25 21:46:59 +02:00
}
static u8string NetworkGetPrivateKeyPath(u8string_view playerName)
2016-05-25 21:46:59 +02:00
{
return Path::Combine(NetworkGetKeysDirectory(), u8string(playerName) + u8".privkey");
2016-05-25 21:46:59 +02:00
}
static u8string NetworkGetPublicKeyPath(u8string_view playerName, u8string_view hash)
2016-05-25 21:46:59 +02:00
{
const auto filename = u8string(playerName) + u8"-" + u8string(hash) + u8".pubkey";
return Path::Combine(NetworkGetKeysDirectory(), filename);
2016-05-25 21:46:59 +02:00
}
u8string NetworkGetServerName()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerName;
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerDescription()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerDescription;
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerGreeting()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerGreeting;
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderName()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerProviderName;
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderEmail()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerProviderEmail;
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderWebsite()
2018-06-22 23:02:47 +02:00
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.ServerProviderWebsite;
2018-06-22 23:02:47 +02:00
}
2016-05-30 23:25:43 +02:00
std::string NetworkGetVersion()
{
return NETWORK_STREAM_ID;
}
NetworkStats NetworkGetStats()
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.GetStats();
}
NetworkServerState NetworkGetServerState()
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.GetServerState();
}
bool NetworkGamestateSnapshotsEnabled()
{
return NetworkGetServerState().gamestateSnapshotsEnabled;
}
json_t NetworkGetServerInfoAsJson()
{
2021-08-17 05:20:07 +02:00
auto& network = OpenRCT2::GetContext()->GetNetwork();
return network.GetServerInfoAsJson();
}
#else
int32_t NetworkGetMode()
2018-06-22 23:02:47 +02:00
{
return NETWORK_MODE_NONE;
}
int32_t NetworkGetStatus()
2018-06-22 23:02:47 +02:00
{
return NETWORK_STATUS_NONE;
}
NetworkAuth NetworkGetAuthstatus()
2018-06-22 23:02:47 +02:00
{
return NetworkAuth::None;
2018-06-22 23:02:47 +02:00
}
uint32_t NetworkGetServerTick()
2018-06-22 23:02:47 +02:00
{
return gCurrentTicks;
}
void NetworkFlush()
2018-06-22 23:02:47 +02:00
{
}
void NetworkSendTick()
2018-06-22 23:02:47 +02:00
{
}
bool NetworkIsDesynchronised()
2019-02-08 01:32:05 +01:00
{
return false;
}
bool NetworkGamestateSnapshotsEnabled()
{
return false;
}
bool NetworkCheckDesynchronisation()
{
return false;
}
void NetworkRequestGamestateSnapshot()
2018-06-22 23:02:47 +02:00
{
}
void NetworkSendGameAction(const GameAction* action)
2018-06-22 23:02:47 +02:00
{
}
void NetworkUpdate()
2018-06-22 23:02:47 +02:00
{
}
void NetworkProcessPending()
2018-06-22 23:02:47 +02:00
{
}
int32_t NetworkBeginClient(const std::string& host, int32_t port)
2018-06-22 23:02:47 +02:00
{
return 1;
}
int32_t NetworkBeginServer(int32_t port, const std::string& address)
2018-06-22 23:02:47 +02:00
{
return 1;
}
int32_t NetworkGetNumPlayers()
2018-06-22 23:02:47 +02:00
{
return 1;
}
int32_t NetworkGetNumVisiblePlayers()
{
return 1;
}
const char* NetworkGetPlayerName(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return "local (OpenRCT2 compiled without MP)";
}
uint32_t NetworkGetPlayerFlags(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetPlayerPing(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetPlayerID(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
money64 NetworkGetPlayerMoneySpent(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0.00_GBP;
2018-06-22 23:02:47 +02:00
}
std::string NetworkGetPlayerIPAddress(uint32_t id)
{
return {};
}
std::string NetworkGetPlayerPublicKeyHash(uint32_t id)
{
return {};
}
void NetworkAddPlayerMoneySpent(uint32_t index, money64 cost)
2018-06-22 23:02:47 +02:00
{
}
int32_t NetworkGetPlayerLastAction(uint32_t index, int32_t time)
2018-06-22 23:02:47 +02:00
{
return -999;
}
void NetworkSetPlayerLastAction(uint32_t index, GameCommand command)
2018-06-22 23:02:47 +02:00
{
}
CoordsXYZ NetworkGetPlayerLastActionCoord(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return { 0, 0, 0 };
}
void NetworkSetPlayerLastActionCoord(uint32_t index, const CoordsXYZ& coord)
2018-06-22 23:02:47 +02:00
{
}
uint32_t NetworkGetPlayerCommandsRan(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetPlayerIndex(uint32_t id)
2018-06-22 23:02:47 +02:00
{
return -1;
}
uint8_t NetworkGetPlayerGroup(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
void NetworkSetPlayerGroup(uint32_t index, uint32_t groupindex)
2018-06-22 23:02:47 +02:00
{
}
int32_t NetworkGetGroupIndex(uint8_t id)
2018-06-22 23:02:47 +02:00
{
return -1;
}
uint8_t NetworkGetGroupID(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetNumGroups()
2018-06-22 23:02:47 +02:00
{
return 0;
}
const char* NetworkGetGroupName(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return "";
};
GameActions::Result NetworkSetPlayerGroup(
NetworkPlayerId_t actionPlayerId, NetworkPlayerId_t playerId, uint8_t groupId, bool isExecuting)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result();
2018-06-22 23:02:47 +02:00
}
GameActions::Result NetworkModifyGroups(
NetworkPlayerId_t actionPlayerId, ModifyGroupType type, uint8_t groupId, const std::string& name, uint32_t permissionIndex,
PermissionState permissionState, bool isExecuting)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result();
2018-06-22 23:02:47 +02:00
}
GameActions::Result NetworkKickPlayer(NetworkPlayerId_t playerId, bool isExecuting)
2018-06-22 23:02:47 +02:00
{
return GameActions::Result();
2018-06-22 23:02:47 +02:00
}
uint8_t NetworkGetDefaultGroup()
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetNumActions()
2018-06-22 23:02:47 +02:00
{
return 0;
}
StringId NetworkGetActionNameStringID(uint32_t index)
2018-06-22 23:02:47 +02:00
{
return -1;
}
int32_t NetworkCanPerformAction(uint32_t groupindex, NetworkPermission index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkCanPerformCommand(uint32_t groupindex, int32_t index)
2018-06-22 23:02:47 +02:00
{
return 0;
}
void NetworkSetPickupPeep(uint8_t playerid, Peep* peep)
2018-06-22 23:02:47 +02:00
{
_pickup_peep = peep;
}
Peep* NetworkGetPickupPeep(uint8_t playerid)
2018-06-22 23:02:47 +02:00
{
return _pickup_peep;
}
void NetworkSetPickupPeepOldX(uint8_t playerid, int32_t x)
2018-06-22 23:02:47 +02:00
{
_pickup_peep_old_x = x;
}
int32_t NetworkGetPickupPeepOldX(uint8_t playerid)
2018-06-22 23:02:47 +02:00
{
return _pickup_peep_old_x;
}
void NetworkSendChat(const char* text, const std::vector<uint8_t>& playerIds)
2018-06-22 23:02:47 +02:00
{
}
void NetworkSendPassword(const std::string& password)
2018-06-22 23:02:47 +02:00
{
}
void NetworkReconnect()
2019-02-08 01:32:05 +01:00
{
}
void NetworkShutdownClient()
2018-06-22 23:02:47 +02:00
{
}
void NetworkSetPassword(const char* password)
2018-06-22 23:02:47 +02:00
{
}
uint8_t NetworkGetCurrentPlayerId()
2018-06-22 23:02:47 +02:00
{
return 0;
}
int32_t NetworkGetCurrentPlayerGroupIndex()
2018-06-22 23:02:47 +02:00
{
return 0;
}
bool NetworkIsServerPlayerInvisible()
{
return false;
}
void NetworkAppendChatLog(std::string_view)
2018-06-22 23:02:47 +02:00
{
}
void NetworkAppendServerLog(const utf8* text)
2018-06-22 23:02:47 +02:00
{
}
u8string NetworkGetServerName()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerDescription()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerGreeting()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderName()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderEmail()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
u8string NetworkGetServerProviderWebsite()
2018-06-22 23:02:47 +02:00
{
return u8string();
2018-06-22 23:02:47 +02:00
}
std::string NetworkGetVersion()
2018-06-22 23:02:47 +02:00
{
return "Multiplayer disabled";
}
NetworkStats NetworkGetStats()
{
return NetworkStats{};
}
NetworkServerState NetworkGetServerState()
{
return NetworkServerState{};
}
json_t NetworkGetServerInfoAsJson()
{
return {};
}
#endif /* DISABLE_NETWORK */