mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-21 23:03:04 +01:00
Move network units to OpenRCT2::Network namespace
This commit is contained in:
@@ -24,130 +24,128 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <discord_rpc.h>
|
#include <discord_rpc.h>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
constexpr const char* kApplicationID = "378612438200877056";
|
constexpr const char* kApplicationID = "378612438200877056";
|
||||||
constexpr const char* kSteamAppID = nullptr;
|
constexpr const char* kSteamAppID = nullptr;
|
||||||
constexpr auto kRefreshInterval = 5.0s;
|
constexpr auto kRefreshInterval = 5.0s;
|
||||||
} // namespace
|
|
||||||
|
|
||||||
static void OnReady([[maybe_unused]] const DiscordUser* request)
|
static void OnReady([[maybe_unused]] const DiscordUser* request)
|
||||||
{
|
|
||||||
LOG_VERBOSE("DiscordService::OnReady()");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnDisconnected(int errorCode, const char* message)
|
|
||||||
{
|
|
||||||
Console::Error::WriteLine("DiscordService::OnDisconnected(%d, %s)", errorCode, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnErrored(int errorCode, const char* message)
|
|
||||||
{
|
|
||||||
Console::Error::WriteLine("DiscordService::OnErrored(%d, %s)", errorCode, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscordService::DiscordService()
|
|
||||||
{
|
|
||||||
DiscordEventHandlers handlers = {};
|
|
||||||
handlers.ready = OnReady;
|
|
||||||
handlers.disconnected = OnDisconnected;
|
|
||||||
handlers.errored = OnErrored;
|
|
||||||
Discord_Initialize(kApplicationID, &handlers, 1, kSteamAppID);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscordService::~DiscordService()
|
|
||||||
{
|
|
||||||
Discord_Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string GetParkName()
|
|
||||||
{
|
|
||||||
auto& gameState = getGameState();
|
|
||||||
return gameState.park.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscordService::Tick()
|
|
||||||
{
|
|
||||||
Discord_RunCallbacks();
|
|
||||||
|
|
||||||
if (_updateTimer.GetElapsedTime() < kRefreshInterval)
|
|
||||||
return;
|
|
||||||
|
|
||||||
RefreshPresence();
|
|
||||||
_updateTimer.Restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscordService::RefreshPresence() const
|
|
||||||
{
|
|
||||||
DiscordRichPresence discordPresence = {};
|
|
||||||
discordPresence.largeImageKey = "logo";
|
|
||||||
|
|
||||||
std::string state;
|
|
||||||
std::string details;
|
|
||||||
std::string partyId;
|
|
||||||
|
|
||||||
switch (gLegacyScene)
|
|
||||||
{
|
{
|
||||||
default:
|
LOG_VERBOSE("DiscordService::OnReady()");
|
||||||
details = GetParkName();
|
|
||||||
if (NetworkGetMode() == NETWORK_MODE_NONE)
|
|
||||||
{
|
|
||||||
state = "Playing Solo";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OpenRCT2::FmtString fmtServerName(NetworkGetServerName());
|
|
||||||
std::string serverName;
|
|
||||||
for (const auto& token : fmtServerName)
|
|
||||||
{
|
|
||||||
if (token.IsLiteral())
|
|
||||||
{
|
|
||||||
serverName += token.text;
|
|
||||||
}
|
|
||||||
else if (token.IsCodepoint())
|
|
||||||
{
|
|
||||||
auto codepoint = token.GetCodepoint();
|
|
||||||
char buffer[8]{};
|
|
||||||
UTF8WriteCodepoint(buffer, codepoint);
|
|
||||||
serverName += buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state = serverName;
|
|
||||||
|
|
||||||
partyId = NetworkGetServerName();
|
|
||||||
// NOTE: the party size is displayed next to state
|
|
||||||
discordPresence.partyId = partyId.c_str();
|
|
||||||
discordPresence.partySize = NetworkGetNumPlayers();
|
|
||||||
discordPresence.partyMax = 256;
|
|
||||||
|
|
||||||
// TODO generate secrets for the server
|
|
||||||
discordPresence.matchSecret = nullptr;
|
|
||||||
discordPresence.spectateSecret = nullptr;
|
|
||||||
discordPresence.instance = 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case LegacyScene::titleSequence:
|
|
||||||
details = "In Menus";
|
|
||||||
break;
|
|
||||||
case LegacyScene::scenarioEditor:
|
|
||||||
details = "In Scenario Editor";
|
|
||||||
break;
|
|
||||||
case LegacyScene::trackDesigner:
|
|
||||||
details = "In Track Designer";
|
|
||||||
break;
|
|
||||||
case LegacyScene::trackDesignsManager:
|
|
||||||
details = "In Track Designs Manager";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
discordPresence.state = state.c_str();
|
static void OnDisconnected(int errorCode, const char* message)
|
||||||
discordPresence.details = details.c_str();
|
{
|
||||||
|
Console::Error::WriteLine("DiscordService::OnDisconnected(%d, %s)", errorCode, message);
|
||||||
|
}
|
||||||
|
|
||||||
Discord_UpdatePresence(&discordPresence);
|
static void OnErrored(int errorCode, const char* message)
|
||||||
}
|
{
|
||||||
|
Console::Error::WriteLine("DiscordService::OnErrored(%d, %s)", errorCode, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordService::DiscordService()
|
||||||
|
{
|
||||||
|
DiscordEventHandlers handlers = {};
|
||||||
|
handlers.ready = OnReady;
|
||||||
|
handlers.disconnected = OnDisconnected;
|
||||||
|
handlers.errored = OnErrored;
|
||||||
|
Discord_Initialize(kApplicationID, &handlers, 1, kSteamAppID);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordService::~DiscordService()
|
||||||
|
{
|
||||||
|
Discord_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string GetParkName()
|
||||||
|
{
|
||||||
|
auto& gameState = getGameState();
|
||||||
|
return gameState.park.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordService::Tick()
|
||||||
|
{
|
||||||
|
Discord_RunCallbacks();
|
||||||
|
|
||||||
|
if (_updateTimer.GetElapsedTime() < kRefreshInterval)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RefreshPresence();
|
||||||
|
_updateTimer.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordService::RefreshPresence() const
|
||||||
|
{
|
||||||
|
DiscordRichPresence discordPresence = {};
|
||||||
|
discordPresence.largeImageKey = "logo";
|
||||||
|
|
||||||
|
std::string state;
|
||||||
|
std::string details;
|
||||||
|
std::string partyId;
|
||||||
|
|
||||||
|
switch (gLegacyScene)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
details = GetParkName();
|
||||||
|
if (NetworkGetMode() == NETWORK_MODE_NONE)
|
||||||
|
{
|
||||||
|
state = "Playing Solo";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OpenRCT2::FmtString fmtServerName(NetworkGetServerName());
|
||||||
|
std::string serverName;
|
||||||
|
for (const auto& token : fmtServerName)
|
||||||
|
{
|
||||||
|
if (token.IsLiteral())
|
||||||
|
{
|
||||||
|
serverName += token.text;
|
||||||
|
}
|
||||||
|
else if (token.IsCodepoint())
|
||||||
|
{
|
||||||
|
auto codepoint = token.GetCodepoint();
|
||||||
|
char buffer[8]{};
|
||||||
|
UTF8WriteCodepoint(buffer, codepoint);
|
||||||
|
serverName += buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = serverName;
|
||||||
|
|
||||||
|
partyId = NetworkGetServerName();
|
||||||
|
// NOTE: the party size is displayed next to state
|
||||||
|
discordPresence.partyId = partyId.c_str();
|
||||||
|
discordPresence.partySize = NetworkGetNumPlayers();
|
||||||
|
discordPresence.partyMax = 256;
|
||||||
|
|
||||||
|
// TODO generate secrets for the server
|
||||||
|
discordPresence.matchSecret = nullptr;
|
||||||
|
discordPresence.spectateSecret = nullptr;
|
||||||
|
discordPresence.instance = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LegacyScene::titleSequence:
|
||||||
|
details = "In Menus";
|
||||||
|
break;
|
||||||
|
case LegacyScene::scenarioEditor:
|
||||||
|
details = "In Scenario Editor";
|
||||||
|
break;
|
||||||
|
case LegacyScene::trackDesigner:
|
||||||
|
details = "In Track Designer";
|
||||||
|
break;
|
||||||
|
case LegacyScene::trackDesignsManager:
|
||||||
|
details = "In Track Designs Manager";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
discordPresence.state = state.c_str();
|
||||||
|
discordPresence.details = details.c_str();
|
||||||
|
|
||||||
|
Discord_UpdatePresence(&discordPresence);
|
||||||
|
}
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -15,19 +15,22 @@
|
|||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
class DiscordService final
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
private:
|
class DiscordService final
|
||||||
OpenRCT2::Timer _updateTimer;
|
{
|
||||||
|
private:
|
||||||
|
OpenRCT2::Timer _updateTimer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DiscordService();
|
DiscordService();
|
||||||
~DiscordService();
|
~DiscordService();
|
||||||
|
|
||||||
void Tick();
|
void Tick();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RefreshPresence() const;
|
void RefreshPresence() const;
|
||||||
};
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,12 +19,6 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
constexpr uint16_t kNetworkDefaultPort = 11753;
|
|
||||||
constexpr uint16_t kNetworkLanBroadcastPort = 11754;
|
|
||||||
constexpr const char* kNetworkLanBroadcastMsg = "openrct2.server.query";
|
|
||||||
constexpr const char* kMasterServerURL = "https://servers.openrct2.io";
|
|
||||||
constexpr uint16_t kMaxServerDescriptionLength = 256;
|
|
||||||
|
|
||||||
struct Peep;
|
struct Peep;
|
||||||
struct CoordsXYZ;
|
struct CoordsXYZ;
|
||||||
|
|
||||||
@@ -36,85 +30,94 @@ namespace OpenRCT2::GameActions
|
|||||||
class Result;
|
class Result;
|
||||||
} // namespace OpenRCT2::GameActions
|
} // namespace OpenRCT2::GameActions
|
||||||
|
|
||||||
enum class NetworkPermission : uint32_t;
|
namespace OpenRCT2::Network
|
||||||
|
{
|
||||||
|
constexpr uint16_t kNetworkDefaultPort = 11753;
|
||||||
|
constexpr uint16_t kNetworkLanBroadcastPort = 11754;
|
||||||
|
constexpr const char* kNetworkLanBroadcastMsg = "openrct2.server.query";
|
||||||
|
constexpr const char* kMasterServerURL = "https://servers.openrct2.io";
|
||||||
|
constexpr uint16_t kMaxServerDescriptionLength = 256;
|
||||||
|
|
||||||
void NetworkReconnect();
|
enum class NetworkPermission : uint32_t;
|
||||||
void NetworkShutdownClient();
|
|
||||||
int32_t NetworkBeginClient(const std::string& host, int32_t port);
|
|
||||||
int32_t NetworkBeginServer(int32_t port, const std::string& address);
|
|
||||||
|
|
||||||
[[nodiscard]] int32_t NetworkGetMode();
|
void NetworkReconnect();
|
||||||
[[nodiscard]] int32_t NetworkGetStatus();
|
void NetworkShutdownClient();
|
||||||
bool NetworkIsDesynchronised();
|
int32_t NetworkBeginClient(const std::string& host, int32_t port);
|
||||||
bool NetworkCheckDesynchronisation();
|
int32_t NetworkBeginServer(int32_t port, const std::string& address);
|
||||||
void NetworkRequestGamestateSnapshot();
|
|
||||||
void NetworkSendTick();
|
|
||||||
bool NetworkGamestateSnapshotsEnabled();
|
|
||||||
void NetworkUpdate();
|
|
||||||
void NetworkProcessPending();
|
|
||||||
void NetworkFlush();
|
|
||||||
|
|
||||||
[[nodiscard]] NetworkAuth NetworkGetAuthstatus();
|
[[nodiscard]] int32_t NetworkGetMode();
|
||||||
[[nodiscard]] uint32_t NetworkGetServerTick();
|
[[nodiscard]] int32_t NetworkGetStatus();
|
||||||
[[nodiscard]] uint8_t NetworkGetCurrentPlayerId();
|
bool NetworkIsDesynchronised();
|
||||||
[[nodiscard]] int32_t NetworkGetNumPlayers();
|
bool NetworkCheckDesynchronisation();
|
||||||
[[nodiscard]] int32_t NetworkGetNumVisiblePlayers();
|
void NetworkRequestGamestateSnapshot();
|
||||||
[[nodiscard]] const char* NetworkGetPlayerName(uint32_t index);
|
void NetworkSendTick();
|
||||||
[[nodiscard]] uint32_t NetworkGetPlayerFlags(uint32_t index);
|
bool NetworkGamestateSnapshotsEnabled();
|
||||||
[[nodiscard]] int32_t NetworkGetPlayerPing(uint32_t index);
|
void NetworkUpdate();
|
||||||
[[nodiscard]] int32_t NetworkGetPlayerID(uint32_t index);
|
void NetworkProcessPending();
|
||||||
[[nodiscard]] money64 NetworkGetPlayerMoneySpent(uint32_t index);
|
void NetworkFlush();
|
||||||
[[nodiscard]] std::string NetworkGetPlayerIPAddress(uint32_t id);
|
|
||||||
[[nodiscard]] std::string NetworkGetPlayerPublicKeyHash(uint32_t id);
|
|
||||||
void NetworkIncrementPlayerNumCommands(uint32_t playerIndex);
|
|
||||||
void NetworkAddPlayerMoneySpent(uint32_t index, money64 cost);
|
|
||||||
[[nodiscard]] int32_t NetworkGetPlayerLastAction(uint32_t index, int32_t time);
|
|
||||||
void NetworkSetPlayerLastAction(uint32_t index, GameCommand command);
|
|
||||||
[[nodiscard]] CoordsXYZ NetworkGetPlayerLastActionCoord(uint32_t index);
|
|
||||||
void NetworkSetPlayerLastActionCoord(uint32_t index, const CoordsXYZ& coord);
|
|
||||||
[[nodiscard]] uint32_t NetworkGetPlayerCommandsRan(uint32_t index);
|
|
||||||
[[nodiscard]] int32_t NetworkGetPlayerIndex(uint32_t id);
|
|
||||||
[[nodiscard]] uint8_t NetworkGetPlayerGroup(uint32_t index);
|
|
||||||
void NetworkSetPlayerGroup(uint32_t index, uint32_t groupindex);
|
|
||||||
[[nodiscard]] int32_t NetworkGetGroupIndex(uint8_t id);
|
|
||||||
[[nodiscard]] int32_t NetworkGetCurrentPlayerGroupIndex();
|
|
||||||
[[nodiscard]] uint8_t NetworkGetGroupID(uint32_t index);
|
|
||||||
[[nodiscard]] int32_t NetworkGetNumGroups();
|
|
||||||
[[nodiscard]] const char* NetworkGetGroupName(uint32_t index);
|
|
||||||
[[nodiscard]] OpenRCT2::GameActions::Result NetworkSetPlayerGroup(
|
|
||||||
NetworkPlayerId_t actionPlayerId, NetworkPlayerId_t playerId, uint8_t groupId, bool isExecuting);
|
|
||||||
[[nodiscard]] OpenRCT2::GameActions::Result NetworkModifyGroups(
|
|
||||||
NetworkPlayerId_t actionPlayerId, OpenRCT2::GameActions::ModifyGroupType type, uint8_t groupId, const std::string& name,
|
|
||||||
uint32_t permissionIndex, OpenRCT2::GameActions::PermissionState permissionState, bool isExecuting);
|
|
||||||
[[nodiscard]] OpenRCT2::GameActions::Result NetworkKickPlayer(NetworkPlayerId_t playerId, bool isExecuting);
|
|
||||||
[[nodiscard]] uint8_t NetworkGetDefaultGroup();
|
|
||||||
[[nodiscard]] int32_t NetworkGetNumActions();
|
|
||||||
[[nodiscard]] StringId NetworkGetActionNameStringID(uint32_t index);
|
|
||||||
[[nodiscard]] int32_t NetworkCanPerformAction(uint32_t groupindex, NetworkPermission index);
|
|
||||||
[[nodiscard]] int32_t NetworkCanPerformCommand(uint32_t groupindex, int32_t index);
|
|
||||||
void NetworkSetPickupPeep(uint8_t playerid, Peep* peep);
|
|
||||||
[[nodiscard]] Peep* NetworkGetPickupPeep(uint8_t playerid);
|
|
||||||
void NetworkSetPickupPeepOldX(uint8_t playerid, int32_t x);
|
|
||||||
[[nodiscard]] int32_t NetworkGetPickupPeepOldX(uint8_t playerid);
|
|
||||||
[[nodiscard]] bool NetworkIsServerPlayerInvisible();
|
|
||||||
|
|
||||||
void NetworkSendChat(const char* text, const std::vector<uint8_t>& playerIds = {});
|
[[nodiscard]] NetworkAuth NetworkGetAuthstatus();
|
||||||
void NetworkSendGameAction(const OpenRCT2::GameActions::GameAction* action);
|
[[nodiscard]] uint32_t NetworkGetServerTick();
|
||||||
void NetworkSendPassword(const std::string& password);
|
[[nodiscard]] uint8_t NetworkGetCurrentPlayerId();
|
||||||
|
[[nodiscard]] int32_t NetworkGetNumPlayers();
|
||||||
|
[[nodiscard]] int32_t NetworkGetNumVisiblePlayers();
|
||||||
|
[[nodiscard]] const char* NetworkGetPlayerName(uint32_t index);
|
||||||
|
[[nodiscard]] uint32_t NetworkGetPlayerFlags(uint32_t index);
|
||||||
|
[[nodiscard]] int32_t NetworkGetPlayerPing(uint32_t index);
|
||||||
|
[[nodiscard]] int32_t NetworkGetPlayerID(uint32_t index);
|
||||||
|
[[nodiscard]] money64 NetworkGetPlayerMoneySpent(uint32_t index);
|
||||||
|
[[nodiscard]] std::string NetworkGetPlayerIPAddress(uint32_t id);
|
||||||
|
[[nodiscard]] std::string NetworkGetPlayerPublicKeyHash(uint32_t id);
|
||||||
|
void NetworkIncrementPlayerNumCommands(uint32_t playerIndex);
|
||||||
|
void NetworkAddPlayerMoneySpent(uint32_t index, money64 cost);
|
||||||
|
[[nodiscard]] int32_t NetworkGetPlayerLastAction(uint32_t index, int32_t time);
|
||||||
|
void NetworkSetPlayerLastAction(uint32_t index, GameCommand command);
|
||||||
|
[[nodiscard]] CoordsXYZ NetworkGetPlayerLastActionCoord(uint32_t index);
|
||||||
|
void NetworkSetPlayerLastActionCoord(uint32_t index, const CoordsXYZ& coord);
|
||||||
|
[[nodiscard]] uint32_t NetworkGetPlayerCommandsRan(uint32_t index);
|
||||||
|
[[nodiscard]] int32_t NetworkGetPlayerIndex(uint32_t id);
|
||||||
|
[[nodiscard]] uint8_t NetworkGetPlayerGroup(uint32_t index);
|
||||||
|
void NetworkSetPlayerGroup(uint32_t index, uint32_t groupindex);
|
||||||
|
[[nodiscard]] int32_t NetworkGetGroupIndex(uint8_t id);
|
||||||
|
[[nodiscard]] int32_t NetworkGetCurrentPlayerGroupIndex();
|
||||||
|
[[nodiscard]] uint8_t NetworkGetGroupID(uint32_t index);
|
||||||
|
[[nodiscard]] int32_t NetworkGetNumGroups();
|
||||||
|
[[nodiscard]] const char* NetworkGetGroupName(uint32_t index);
|
||||||
|
[[nodiscard]] OpenRCT2::GameActions::Result NetworkSetPlayerGroup(
|
||||||
|
NetworkPlayerId_t actionPlayerId, NetworkPlayerId_t playerId, uint8_t groupId, bool isExecuting);
|
||||||
|
[[nodiscard]] OpenRCT2::GameActions::Result NetworkModifyGroups(
|
||||||
|
NetworkPlayerId_t actionPlayerId, OpenRCT2::GameActions::ModifyGroupType type, uint8_t groupId, const std::string& name,
|
||||||
|
uint32_t permissionIndex, OpenRCT2::GameActions::PermissionState permissionState, bool isExecuting);
|
||||||
|
[[nodiscard]] OpenRCT2::GameActions::Result NetworkKickPlayer(NetworkPlayerId_t playerId, bool isExecuting);
|
||||||
|
[[nodiscard]] uint8_t NetworkGetDefaultGroup();
|
||||||
|
[[nodiscard]] int32_t NetworkGetNumActions();
|
||||||
|
[[nodiscard]] StringId NetworkGetActionNameStringID(uint32_t index);
|
||||||
|
[[nodiscard]] int32_t NetworkCanPerformAction(uint32_t groupindex, NetworkPermission index);
|
||||||
|
[[nodiscard]] int32_t NetworkCanPerformCommand(uint32_t groupindex, int32_t index);
|
||||||
|
void NetworkSetPickupPeep(uint8_t playerid, Peep* peep);
|
||||||
|
[[nodiscard]] Peep* NetworkGetPickupPeep(uint8_t playerid);
|
||||||
|
void NetworkSetPickupPeepOldX(uint8_t playerid, int32_t x);
|
||||||
|
[[nodiscard]] int32_t NetworkGetPickupPeepOldX(uint8_t playerid);
|
||||||
|
[[nodiscard]] bool NetworkIsServerPlayerInvisible();
|
||||||
|
|
||||||
void NetworkSetPassword(const char* password);
|
void NetworkSendChat(const char* text, const std::vector<uint8_t>& playerIds = {});
|
||||||
|
void NetworkSendGameAction(const OpenRCT2::GameActions::GameAction* action);
|
||||||
|
void NetworkSendPassword(const std::string& password);
|
||||||
|
|
||||||
void NetworkAppendChatLog(std::string_view text);
|
void NetworkSetPassword(const char* password);
|
||||||
void NetworkAppendServerLog(const utf8* text);
|
|
||||||
[[nodiscard]] u8string NetworkGetServerName();
|
|
||||||
[[nodiscard]] u8string NetworkGetServerDescription();
|
|
||||||
[[nodiscard]] u8string NetworkGetServerGreeting();
|
|
||||||
[[nodiscard]] u8string NetworkGetServerProviderName();
|
|
||||||
[[nodiscard]] u8string NetworkGetServerProviderEmail();
|
|
||||||
[[nodiscard]] u8string NetworkGetServerProviderWebsite();
|
|
||||||
|
|
||||||
[[nodiscard]] std::string NetworkGetVersion();
|
void NetworkAppendChatLog(std::string_view text);
|
||||||
|
void NetworkAppendServerLog(const utf8* text);
|
||||||
|
[[nodiscard]] u8string NetworkGetServerName();
|
||||||
|
[[nodiscard]] u8string NetworkGetServerDescription();
|
||||||
|
[[nodiscard]] u8string NetworkGetServerGreeting();
|
||||||
|
[[nodiscard]] u8string NetworkGetServerProviderName();
|
||||||
|
[[nodiscard]] u8string NetworkGetServerProviderEmail();
|
||||||
|
[[nodiscard]] u8string NetworkGetServerProviderWebsite();
|
||||||
|
|
||||||
[[nodiscard]] NetworkStats NetworkGetStats();
|
[[nodiscard]] std::string NetworkGetVersion();
|
||||||
[[nodiscard]] NetworkServerState NetworkGetServerState();
|
|
||||||
[[nodiscard]] json_t NetworkGetServerInfoAsJson();
|
[[nodiscard]] NetworkStats NetworkGetStats();
|
||||||
|
[[nodiscard]] NetworkServerState NetworkGetServerState();
|
||||||
|
[[nodiscard]] json_t NetworkGetServerInfoAsJson();
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -16,254 +16,257 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
NetworkPermission NetworkActions::FindCommand(GameCommand command)
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
auto it = std::find_if(Actions.begin(), Actions.end(), [&command](NetworkAction const& action) {
|
NetworkPermission NetworkActions::FindCommand(GameCommand command)
|
||||||
for (GameCommand currentCommand : action.Commands)
|
{
|
||||||
{
|
auto it = std::find_if(Actions.begin(), Actions.end(), [&command](NetworkAction const& action) {
|
||||||
if (currentCommand == command)
|
for (GameCommand currentCommand : action.Commands)
|
||||||
{
|
{
|
||||||
return true;
|
if (currentCommand == command)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (it != Actions.end())
|
||||||
|
{
|
||||||
|
return static_cast<NetworkPermission>(it - Actions.begin());
|
||||||
}
|
}
|
||||||
return false;
|
return NetworkPermission::Count;
|
||||||
});
|
|
||||||
if (it != Actions.end())
|
|
||||||
{
|
|
||||||
return static_cast<NetworkPermission>(it - Actions.begin());
|
|
||||||
}
|
}
|
||||||
return NetworkPermission::Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPermission NetworkActions::FindCommandByPermissionName(const std::string& permission_name)
|
NetworkPermission NetworkActions::FindCommandByPermissionName(const std::string& permission_name)
|
||||||
{
|
|
||||||
auto it = std::find_if(Actions.begin(), Actions.end(), [&permission_name](NetworkAction const& action) {
|
|
||||||
return action.PermissionName == permission_name;
|
|
||||||
});
|
|
||||||
if (it != Actions.end())
|
|
||||||
{
|
{
|
||||||
return static_cast<NetworkPermission>(it - Actions.begin());
|
auto it = std::find_if(Actions.begin(), Actions.end(), [&permission_name](NetworkAction const& action) {
|
||||||
|
return action.PermissionName == permission_name;
|
||||||
|
});
|
||||||
|
if (it != Actions.end())
|
||||||
|
{
|
||||||
|
return static_cast<NetworkPermission>(it - Actions.begin());
|
||||||
|
}
|
||||||
|
return NetworkPermission::Count;
|
||||||
}
|
}
|
||||||
return NetworkPermission::Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::array<NetworkAction, static_cast<size_t>(NetworkPermission::Count)> NetworkActions::Actions = {
|
const std::array<NetworkAction, static_cast<size_t>(NetworkPermission::Count)> NetworkActions::Actions = {
|
||||||
NetworkAction{
|
NetworkAction{
|
||||||
STR_ACTION_CHAT,
|
STR_ACTION_CHAT,
|
||||||
"PERMISSION_CHAT",
|
"PERMISSION_CHAT",
|
||||||
{},
|
{},
|
||||||
},
|
|
||||||
NetworkAction{
|
|
||||||
STR_ACTION_TERRAFORM,
|
|
||||||
"PERMISSION_TERRAFORM",
|
|
||||||
{
|
|
||||||
GameCommand::SetLandHeight,
|
|
||||||
GameCommand::RaiseLand,
|
|
||||||
GameCommand::LowerLand,
|
|
||||||
GameCommand::EditLandSmooth,
|
|
||||||
GameCommand::ChangeSurfaceStyle,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_TERRAFORM,
|
||||||
STR_ACTION_SET_WATER_LEVEL,
|
"PERMISSION_TERRAFORM",
|
||||||
"PERMISSION_SET_WATER_LEVEL",
|
{
|
||||||
{
|
GameCommand::SetLandHeight,
|
||||||
GameCommand::SetWaterHeight,
|
GameCommand::RaiseLand,
|
||||||
GameCommand::RaiseWater,
|
GameCommand::LowerLand,
|
||||||
GameCommand::LowerWater,
|
GameCommand::EditLandSmooth,
|
||||||
|
GameCommand::ChangeSurfaceStyle,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_SET_WATER_LEVEL,
|
||||||
STR_ACTION_TOGGLE_PAUSE,
|
"PERMISSION_SET_WATER_LEVEL",
|
||||||
"PERMISSION_TOGGLE_PAUSE",
|
{
|
||||||
{
|
GameCommand::SetWaterHeight,
|
||||||
GameCommand::TogglePause,
|
GameCommand::RaiseWater,
|
||||||
|
GameCommand::LowerWater,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_TOGGLE_PAUSE,
|
||||||
STR_ACTION_CREATE_RIDE,
|
"PERMISSION_TOGGLE_PAUSE",
|
||||||
"PERMISSION_CREATE_RIDE",
|
{
|
||||||
{
|
GameCommand::TogglePause,
|
||||||
GameCommand::CreateRide,
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_CREATE_RIDE,
|
||||||
STR_ACTION_REMOVE_RIDE,
|
"PERMISSION_CREATE_RIDE",
|
||||||
"PERMISSION_REMOVE_RIDE",
|
{
|
||||||
{
|
GameCommand::CreateRide,
|
||||||
GameCommand::DemolishRide,
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_REMOVE_RIDE,
|
||||||
STR_ACTION_BUILD_RIDE,
|
"PERMISSION_REMOVE_RIDE",
|
||||||
"PERMISSION_BUILD_RIDE",
|
{
|
||||||
{
|
GameCommand::DemolishRide,
|
||||||
GameCommand::PlaceTrack,
|
},
|
||||||
GameCommand::RemoveTrack,
|
|
||||||
GameCommand::SetMazeTrack,
|
|
||||||
GameCommand::PlaceTrackDesign,
|
|
||||||
GameCommand::PlaceMazeDesign,
|
|
||||||
GameCommand::PlaceRideEntranceOrExit,
|
|
||||||
GameCommand::RemoveRideEntranceOrExit,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_BUILD_RIDE,
|
||||||
STR_ACTION_RIDE_PROPERTIES,
|
"PERMISSION_BUILD_RIDE",
|
||||||
"PERMISSION_RIDE_PROPERTIES",
|
{
|
||||||
{
|
GameCommand::PlaceTrack,
|
||||||
GameCommand::SetRideName,
|
GameCommand::RemoveTrack,
|
||||||
GameCommand::SetRideAppearance,
|
GameCommand::SetMazeTrack,
|
||||||
GameCommand::SetRideStatus,
|
GameCommand::PlaceTrackDesign,
|
||||||
GameCommand::SetRideVehicles,
|
GameCommand::PlaceMazeDesign,
|
||||||
GameCommand::SetRideSetting,
|
GameCommand::PlaceRideEntranceOrExit,
|
||||||
GameCommand::SetRidePrice,
|
GameCommand::RemoveRideEntranceOrExit,
|
||||||
GameCommand::SetBrakesSpeed,
|
},
|
||||||
GameCommand::SetColourScheme,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_RIDE_PROPERTIES,
|
||||||
STR_ACTION_SCENERY,
|
"PERMISSION_RIDE_PROPERTIES",
|
||||||
"PERMISSION_SCENERY",
|
{
|
||||||
{
|
GameCommand::SetRideName,
|
||||||
GameCommand::RemoveScenery,
|
GameCommand::SetRideAppearance,
|
||||||
GameCommand::PlaceScenery,
|
GameCommand::SetRideStatus,
|
||||||
GameCommand::SetBrakesSpeed,
|
GameCommand::SetRideVehicles,
|
||||||
GameCommand::RemoveWall,
|
GameCommand::SetRideSetting,
|
||||||
GameCommand::PlaceWall,
|
GameCommand::SetRidePrice,
|
||||||
GameCommand::RemoveLargeScenery,
|
GameCommand::SetBrakesSpeed,
|
||||||
GameCommand::PlaceLargeScenery,
|
GameCommand::SetColourScheme,
|
||||||
GameCommand::PlaceBanner,
|
},
|
||||||
GameCommand::RemoveBanner,
|
|
||||||
GameCommand::SetSceneryColour,
|
|
||||||
GameCommand::SetWallColour,
|
|
||||||
GameCommand::SetLargeSceneryColour,
|
|
||||||
GameCommand::SetBannerColour,
|
|
||||||
GameCommand::SetBannerName,
|
|
||||||
GameCommand::SetSignName,
|
|
||||||
GameCommand::SetBannerStyle,
|
|
||||||
GameCommand::SetSignStyle,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_SCENERY,
|
||||||
STR_ACTION_PATH,
|
"PERMISSION_SCENERY",
|
||||||
"PERMISSION_PATH",
|
{
|
||||||
{
|
GameCommand::RemoveScenery,
|
||||||
GameCommand::PlacePath,
|
GameCommand::PlaceScenery,
|
||||||
GameCommand::PlacePathLayout,
|
GameCommand::SetBrakesSpeed,
|
||||||
GameCommand::RemovePath,
|
GameCommand::RemoveWall,
|
||||||
GameCommand::PlaceFootpathAddition,
|
GameCommand::PlaceWall,
|
||||||
GameCommand::RemoveFootpathAddition,
|
GameCommand::RemoveLargeScenery,
|
||||||
|
GameCommand::PlaceLargeScenery,
|
||||||
|
GameCommand::PlaceBanner,
|
||||||
|
GameCommand::RemoveBanner,
|
||||||
|
GameCommand::SetSceneryColour,
|
||||||
|
GameCommand::SetWallColour,
|
||||||
|
GameCommand::SetLargeSceneryColour,
|
||||||
|
GameCommand::SetBannerColour,
|
||||||
|
GameCommand::SetBannerName,
|
||||||
|
GameCommand::SetSignName,
|
||||||
|
GameCommand::SetBannerStyle,
|
||||||
|
GameCommand::SetSignStyle,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_PATH,
|
||||||
STR_ACTION_CLEAR_LANDSCAPE,
|
"PERMISSION_PATH",
|
||||||
"PERMISSION_CLEAR_LANDSCAPE",
|
{
|
||||||
{
|
GameCommand::PlacePath,
|
||||||
GameCommand::ClearScenery,
|
GameCommand::PlacePathLayout,
|
||||||
|
GameCommand::RemovePath,
|
||||||
|
GameCommand::PlaceFootpathAddition,
|
||||||
|
GameCommand::RemoveFootpathAddition,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_CLEAR_LANDSCAPE,
|
||||||
STR_ACTION_GUEST,
|
"PERMISSION_CLEAR_LANDSCAPE",
|
||||||
"PERMISSION_GUEST",
|
{
|
||||||
{
|
GameCommand::ClearScenery,
|
||||||
GameCommand::SetGuestName,
|
},
|
||||||
GameCommand::PickupGuest,
|
|
||||||
GameCommand::BalloonPress,
|
|
||||||
GameCommand::GuestSetFlags,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_GUEST,
|
||||||
STR_ACTION_STAFF,
|
"PERMISSION_GUEST",
|
||||||
"PERMISSION_STAFF",
|
{
|
||||||
{
|
GameCommand::SetGuestName,
|
||||||
GameCommand::HireNewStaffMember,
|
GameCommand::PickupGuest,
|
||||||
GameCommand::SetStaffPatrol,
|
GameCommand::BalloonPress,
|
||||||
GameCommand::FireStaffMember,
|
GameCommand::GuestSetFlags,
|
||||||
GameCommand::SetStaffOrders,
|
},
|
||||||
GameCommand::SetStaffCostume,
|
|
||||||
GameCommand::SetStaffColour,
|
|
||||||
GameCommand::SetStaffName,
|
|
||||||
GameCommand::PickupStaff,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_STAFF,
|
||||||
STR_ACTION_PARK_PROPERTIES,
|
"PERMISSION_STAFF",
|
||||||
"PERMISSION_PARK_PROPERTIES",
|
{
|
||||||
{
|
GameCommand::HireNewStaffMember,
|
||||||
GameCommand::SetParkName,
|
GameCommand::SetStaffPatrol,
|
||||||
GameCommand::SetParkOpen,
|
GameCommand::FireStaffMember,
|
||||||
GameCommand::SetParkEntranceFee,
|
GameCommand::SetStaffOrders,
|
||||||
GameCommand::SetLandOwnership,
|
GameCommand::SetStaffCostume,
|
||||||
GameCommand::BuyLandRights,
|
GameCommand::SetStaffColour,
|
||||||
GameCommand::PlaceParkEntrance,
|
GameCommand::SetStaffName,
|
||||||
GameCommand::RemoveParkEntrance,
|
GameCommand::PickupStaff,
|
||||||
GameCommand::PlacePeepSpawn,
|
},
|
||||||
GameCommand::ChangeMapSize,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_PARK_PROPERTIES,
|
||||||
STR_ACTION_PARK_FUNDING,
|
"PERMISSION_PARK_PROPERTIES",
|
||||||
"PERMISSION_PARK_FUNDING",
|
{
|
||||||
{
|
GameCommand::SetParkName,
|
||||||
GameCommand::SetCurrentLoan,
|
GameCommand::SetParkOpen,
|
||||||
GameCommand::SetResearchFunding,
|
GameCommand::SetParkEntranceFee,
|
||||||
GameCommand::StartMarketingCampaign,
|
GameCommand::SetLandOwnership,
|
||||||
|
GameCommand::BuyLandRights,
|
||||||
|
GameCommand::PlaceParkEntrance,
|
||||||
|
GameCommand::RemoveParkEntrance,
|
||||||
|
GameCommand::PlacePeepSpawn,
|
||||||
|
GameCommand::ChangeMapSize,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_PARK_FUNDING,
|
||||||
STR_ACTION_KICK_PLAYER,
|
"PERMISSION_PARK_FUNDING",
|
||||||
"PERMISSION_KICK_PLAYER",
|
{
|
||||||
{
|
GameCommand::SetCurrentLoan,
|
||||||
GameCommand::KickPlayer,
|
GameCommand::SetResearchFunding,
|
||||||
|
GameCommand::StartMarketingCampaign,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_KICK_PLAYER,
|
||||||
STR_ACTION_MODIFY_GROUPS,
|
"PERMISSION_KICK_PLAYER",
|
||||||
"PERMISSION_MODIFY_GROUPS",
|
{
|
||||||
{
|
GameCommand::KickPlayer,
|
||||||
GameCommand::ModifyGroups,
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_MODIFY_GROUPS,
|
||||||
STR_ACTION_SET_PLAYER_GROUP,
|
"PERMISSION_MODIFY_GROUPS",
|
||||||
"PERMISSION_SET_PLAYER_GROUP",
|
{
|
||||||
{
|
GameCommand::ModifyGroups,
|
||||||
GameCommand::SetPlayerGroup,
|
},
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_SET_PLAYER_GROUP,
|
||||||
STR_ACTION_CHEAT,
|
"PERMISSION_SET_PLAYER_GROUP",
|
||||||
"PERMISSION_CHEAT",
|
{
|
||||||
{
|
GameCommand::SetPlayerGroup,
|
||||||
GameCommand::Cheat,
|
},
|
||||||
GameCommand::SetDate,
|
|
||||||
GameCommand::FreezeRideRating,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_CHEAT,
|
||||||
STR_ACTION_TOGGLE_SCENERY_CLUSTER,
|
"PERMISSION_CHEAT",
|
||||||
"PERMISSION_TOGGLE_SCENERY_CLUSTER",
|
{
|
||||||
{},
|
GameCommand::Cheat,
|
||||||
},
|
GameCommand::SetDate,
|
||||||
NetworkAction{
|
GameCommand::FreezeRideRating,
|
||||||
STR_ACTION_PASSWORDLESS_LOGIN,
|
},
|
||||||
"PERMISSION_PASSWORDLESS_LOGIN",
|
|
||||||
{},
|
|
||||||
},
|
|
||||||
NetworkAction{
|
|
||||||
STR_ACTION_MODIFY_TILE,
|
|
||||||
"PERMISSION_MODIFY_TILE",
|
|
||||||
{
|
|
||||||
GameCommand::ModifyTile,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
NetworkAction{
|
STR_ACTION_TOGGLE_SCENERY_CLUSTER,
|
||||||
STR_ACTION_EDIT_SCENARIO_OPTIONS,
|
"PERMISSION_TOGGLE_SCENERY_CLUSTER",
|
||||||
"PERMISSION_EDIT_SCENARIO_OPTIONS",
|
{},
|
||||||
{
|
|
||||||
GameCommand::EditScenarioOptions,
|
|
||||||
},
|
},
|
||||||
},
|
NetworkAction{
|
||||||
};
|
STR_ACTION_PASSWORDLESS_LOGIN,
|
||||||
|
"PERMISSION_PASSWORDLESS_LOGIN",
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
NetworkAction{
|
||||||
|
STR_ACTION_MODIFY_TILE,
|
||||||
|
"PERMISSION_MODIFY_TILE",
|
||||||
|
{
|
||||||
|
GameCommand::ModifyTile,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkAction{
|
||||||
|
STR_ACTION_EDIT_SCENARIO_OPTIONS,
|
||||||
|
"PERMISSION_EDIT_SCENARIO_OPTIONS",
|
||||||
|
{
|
||||||
|
GameCommand::EditScenarioOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,48 +16,51 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class NetworkPermission : uint32_t
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
Chat,
|
enum class NetworkPermission : uint32_t
|
||||||
Terraform,
|
{
|
||||||
SetWaterLevel,
|
Chat,
|
||||||
TogglePause,
|
Terraform,
|
||||||
CreateRide,
|
SetWaterLevel,
|
||||||
RemoveRide,
|
TogglePause,
|
||||||
BuildRide,
|
CreateRide,
|
||||||
RideProperties,
|
RemoveRide,
|
||||||
Scenery,
|
BuildRide,
|
||||||
Path,
|
RideProperties,
|
||||||
ClearLandscape,
|
Scenery,
|
||||||
Guest,
|
Path,
|
||||||
Staff,
|
ClearLandscape,
|
||||||
ParkProperties,
|
Guest,
|
||||||
ParkFunding,
|
Staff,
|
||||||
KickPlayer,
|
ParkProperties,
|
||||||
ModifyGroups,
|
ParkFunding,
|
||||||
SetPlayerGroup,
|
KickPlayer,
|
||||||
Cheat,
|
ModifyGroups,
|
||||||
ToggleSceneryCluster,
|
SetPlayerGroup,
|
||||||
PasswordlessLogin,
|
Cheat,
|
||||||
ModifyTile,
|
ToggleSceneryCluster,
|
||||||
EditScenarioOptions,
|
PasswordlessLogin,
|
||||||
|
ModifyTile,
|
||||||
|
EditScenarioOptions,
|
||||||
|
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
class NetworkAction final
|
class NetworkAction final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StringId Name;
|
StringId Name;
|
||||||
std::string PermissionName;
|
std::string PermissionName;
|
||||||
std::vector<GameCommand> Commands;
|
std::vector<GameCommand> Commands;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NetworkActions final
|
class NetworkActions final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const std::array<NetworkAction, static_cast<size_t>(NetworkPermission::Count)> Actions;
|
static const std::array<NetworkAction, static_cast<size_t>(NetworkPermission::Count)> Actions;
|
||||||
|
|
||||||
static NetworkPermission FindCommand(GameCommand command);
|
static NetworkPermission FindCommand(GameCommand command);
|
||||||
static NetworkPermission FindCommandByPermissionName(const std::string& permission_name);
|
static NetworkPermission FindCommandByPermissionName(const std::string& permission_name);
|
||||||
};
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,235 +22,239 @@ namespace OpenRCT2
|
|||||||
struct IContext;
|
struct IContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkBase : public OpenRCT2::System
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkBase : public OpenRCT2::System
|
||||||
NetworkBase(OpenRCT2::IContext& context);
|
|
||||||
|
|
||||||
public: // Uncategorized
|
|
||||||
bool BeginServer(uint16_t port, const std::string& address);
|
|
||||||
bool BeginClient(const std::string& host, uint16_t port);
|
|
||||||
|
|
||||||
public: // Common
|
|
||||||
bool Init();
|
|
||||||
void Close();
|
|
||||||
uint32_t GetServerTick() const noexcept;
|
|
||||||
// FIXME: This is currently the wrong function to override in System, will be refactored later.
|
|
||||||
void Update() override final;
|
|
||||||
void Flush();
|
|
||||||
void ProcessPending();
|
|
||||||
void ProcessPlayerList();
|
|
||||||
auto GetPlayerIteratorByID(uint8_t id) const;
|
|
||||||
auto GetGroupIteratorByID(uint8_t id) const;
|
|
||||||
NetworkPlayer* GetPlayerByID(uint8_t id) const;
|
|
||||||
NetworkGroup* GetGroupByID(uint8_t id) const;
|
|
||||||
int32_t GetTotalNumPlayers() const noexcept;
|
|
||||||
int32_t GetNumVisiblePlayers() const noexcept;
|
|
||||||
void SetPassword(u8string_view password);
|
|
||||||
uint8_t GetDefaultGroup() const noexcept;
|
|
||||||
std::string BeginLog(const std::string& directory, const std::string& midName, const std::string& filenameFormat);
|
|
||||||
void AppendLog(std::ostream& fs, std::string_view s);
|
|
||||||
void BeginChatLog();
|
|
||||||
void AppendChatLog(std::string_view s);
|
|
||||||
void CloseChatLog();
|
|
||||||
NetworkStats GetStats() const;
|
|
||||||
json_t GetServerInfoAsJson() const;
|
|
||||||
bool ProcessConnection(NetworkConnection& connection);
|
|
||||||
void CloseConnection();
|
|
||||||
NetworkPlayer* AddPlayer(const std::string& name, const std::string& keyhash);
|
|
||||||
void ProcessPacket(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
|
|
||||||
public: // Server
|
|
||||||
NetworkConnection* GetPlayerConnection(uint8_t id) const;
|
|
||||||
void KickPlayer(int32_t playerId);
|
|
||||||
NetworkGroup* AddGroup();
|
|
||||||
void LoadGroups();
|
|
||||||
void SetDefaultGroup(uint8_t id);
|
|
||||||
void SaveGroups();
|
|
||||||
void RemoveGroup(uint8_t id);
|
|
||||||
uint8_t GetGroupIDByHash(const std::string& keyhash);
|
|
||||||
void BeginServerLog();
|
|
||||||
void AppendServerLog(const std::string& s);
|
|
||||||
void CloseServerLog();
|
|
||||||
void DecayCooldown(NetworkPlayer* player);
|
|
||||||
void AddClient(std::unique_ptr<ITcpSocket>&& socket);
|
|
||||||
std::string GetMasterServerUrl();
|
|
||||||
std::string GenerateAdvertiseKey();
|
|
||||||
void SetupDefaultGroups();
|
|
||||||
void RemovePlayer(std::unique_ptr<NetworkConnection>& connection);
|
|
||||||
void UpdateServer();
|
|
||||||
void ServerClientDisconnected(std::unique_ptr<NetworkConnection>& connection);
|
|
||||||
bool SaveMap(OpenRCT2::IStream* stream, const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
|
||||||
std::vector<uint8_t> SaveForNetwork(const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
|
||||||
std::string MakePlayerNameUnique(const std::string& name);
|
|
||||||
|
|
||||||
// Packet dispatchers.
|
|
||||||
void ServerSendAuth(NetworkConnection& connection);
|
|
||||||
void ServerSendToken(NetworkConnection& connection);
|
|
||||||
void ServerSendMap(NetworkConnection* connection = nullptr);
|
|
||||||
void ServerSendChat(const char* text, const std::vector<uint8_t>& playerIds = {});
|
|
||||||
void ServerSendGameAction(const OpenRCT2::GameActions::GameAction* action);
|
|
||||||
void ServerSendTick();
|
|
||||||
void ServerSendPlayerInfo(int32_t playerId);
|
|
||||||
void ServerSendPlayerList();
|
|
||||||
void ServerSendPing();
|
|
||||||
void ServerSendPingList();
|
|
||||||
void ServerSendSetDisconnectMsg(NetworkConnection& connection, const char* msg);
|
|
||||||
void ServerSendGameInfo(NetworkConnection& connection);
|
|
||||||
void ServerSendShowError(NetworkConnection& connection, StringId title, StringId message);
|
|
||||||
void ServerSendGroupList(NetworkConnection& connection);
|
|
||||||
void ServerSendEventPlayerJoined(const char* playerName);
|
|
||||||
void ServerSendEventPlayerDisconnected(const char* playerName, const char* reason);
|
|
||||||
void ServerSendObjectsList(
|
|
||||||
NetworkConnection& connection, const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
|
||||||
void ServerSendScripts(NetworkConnection& connection);
|
|
||||||
|
|
||||||
// Handlers
|
|
||||||
void ServerHandleRequestGamestate(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleHeartbeat(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleAuth(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerClientJoined(std::string_view name, const std::string& keyhash, NetworkConnection& connection);
|
|
||||||
void ServerHandleChat(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleGameAction(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandlePing(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleGameInfo(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleToken(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void ServerHandleMapRequest(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
|
|
||||||
public: // Client
|
|
||||||
void Reconnect();
|
|
||||||
int32_t GetMode() const noexcept;
|
|
||||||
NetworkAuth GetAuthStatus();
|
|
||||||
int32_t GetStatus() const noexcept;
|
|
||||||
uint8_t GetPlayerID() const noexcept;
|
|
||||||
void ProcessPlayerInfo();
|
|
||||||
void ProcessDisconnectedClients();
|
|
||||||
static const char* FormatChat(NetworkPlayer* fromplayer, const char* text);
|
|
||||||
void SendPacketToClients(const NetworkPacket& packet, bool front = false, bool gameCmd = false) const;
|
|
||||||
bool CheckSRAND(uint32_t tick, uint32_t srand0);
|
|
||||||
bool CheckDesynchronizaton();
|
|
||||||
void RequestStateSnapshot();
|
|
||||||
bool IsDesynchronised() const noexcept;
|
|
||||||
NetworkServerState GetServerState() const noexcept;
|
|
||||||
void ServerClientDisconnected();
|
|
||||||
bool LoadMap(OpenRCT2::IStream* stream);
|
|
||||||
void UpdateClient();
|
|
||||||
|
|
||||||
// Packet dispatchers.
|
|
||||||
void Client_Send_RequestGameState(uint32_t tick);
|
|
||||||
void Client_Send_TOKEN();
|
|
||||||
void Client_Send_AUTH(
|
|
||||||
const std::string& name, const std::string& password, const std::string& pubkey, const std::vector<uint8_t>& signature);
|
|
||||||
void Client_Send_CHAT(const char* text);
|
|
||||||
void Client_Send_GAME_ACTION(const OpenRCT2::GameActions::GameAction* action);
|
|
||||||
void Client_Send_PING();
|
|
||||||
void Client_Send_GAMEINFO();
|
|
||||||
void Client_Send_MAPREQUEST(const std::vector<OpenRCT2::ObjectEntryDescriptor>& objects);
|
|
||||||
void Client_Send_HEARTBEAT(NetworkConnection& connection) const;
|
|
||||||
|
|
||||||
// Handlers.
|
|
||||||
void Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_MAP(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_CHAT(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_TICK(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_PLAYERINFO(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_PLAYERLIST(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_PING(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_PINGLIST(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_SETDISCONNECTMSG(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_GAMEINFO(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_SHOWERROR(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_GROUPLIST(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_EVENT(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_OBJECTS_LIST(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_SCRIPTS_HEADER(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_SCRIPTS_DATA(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
void Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
|
|
||||||
std::vector<uint8_t> _challenge;
|
|
||||||
std::map<uint32_t, OpenRCT2::GameActions::GameAction::Callback_t> _gameActionCallbacks;
|
|
||||||
NetworkKey _key;
|
|
||||||
NetworkUserManager _userManager;
|
|
||||||
|
|
||||||
public: // Public common
|
|
||||||
std::string ServerName;
|
|
||||||
std::string ServerDescription;
|
|
||||||
std::string ServerGreeting;
|
|
||||||
std::string ServerProviderName;
|
|
||||||
std::string ServerProviderEmail;
|
|
||||||
std::string ServerProviderWebsite;
|
|
||||||
std::vector<std::unique_ptr<NetworkPlayer>> player_list;
|
|
||||||
std::vector<std::unique_ptr<NetworkGroup>> group_list;
|
|
||||||
bool IsServerPlayerInvisible = false;
|
|
||||||
|
|
||||||
private: // Common Data
|
|
||||||
using CommandHandler = void (NetworkBase::*)(NetworkConnection& connection, NetworkPacket& packet);
|
|
||||||
|
|
||||||
std::vector<uint8_t> chunk_buffer;
|
|
||||||
std::ofstream _chat_log_fs;
|
|
||||||
uint32_t _lastUpdateTime = 0;
|
|
||||||
uint32_t _currentDeltaTime = 0;
|
|
||||||
int32_t mode = NETWORK_MODE_NONE;
|
|
||||||
uint8_t default_group = 0;
|
|
||||||
bool _closeLock = false;
|
|
||||||
bool _requireClose = false;
|
|
||||||
|
|
||||||
private: // Server Data
|
|
||||||
std::unordered_map<NetworkCommand, CommandHandler> server_command_handlers;
|
|
||||||
std::unique_ptr<ITcpSocket> _listenSocket;
|
|
||||||
std::unique_ptr<INetworkServerAdvertiser> _advertiser;
|
|
||||||
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
|
||||||
std::string _serverLogPath;
|
|
||||||
std::string _serverLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
|
||||||
std::ofstream _server_log_fs;
|
|
||||||
uint16_t listening_port = 0;
|
|
||||||
bool _playerListInvalidated = false;
|
|
||||||
|
|
||||||
private: // Client Data
|
|
||||||
struct PlayerListUpdate
|
|
||||||
{
|
{
|
||||||
std::vector<NetworkPlayer> players;
|
public:
|
||||||
};
|
NetworkBase(OpenRCT2::IContext& context);
|
||||||
|
|
||||||
struct ServerTickData
|
public: // Uncategorized
|
||||||
{
|
bool BeginServer(uint16_t port, const std::string& address);
|
||||||
uint32_t srand0;
|
bool BeginClient(const std::string& host, uint16_t port);
|
||||||
uint32_t tick;
|
|
||||||
std::string spriteHash;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ServerScriptsData
|
public: // Common
|
||||||
{
|
bool Init();
|
||||||
uint32_t pluginCount{};
|
void Close();
|
||||||
uint32_t dataSize{};
|
uint32_t GetServerTick() const noexcept;
|
||||||
OpenRCT2::MemoryStream data;
|
// FIXME: This is currently the wrong function to override in System, will be refactored later.
|
||||||
};
|
void Update() override final;
|
||||||
|
void Flush();
|
||||||
|
void ProcessPending();
|
||||||
|
void ProcessPlayerList();
|
||||||
|
auto GetPlayerIteratorByID(uint8_t id) const;
|
||||||
|
auto GetGroupIteratorByID(uint8_t id) const;
|
||||||
|
NetworkPlayer* GetPlayerByID(uint8_t id) const;
|
||||||
|
NetworkGroup* GetGroupByID(uint8_t id) const;
|
||||||
|
int32_t GetTotalNumPlayers() const noexcept;
|
||||||
|
int32_t GetNumVisiblePlayers() const noexcept;
|
||||||
|
void SetPassword(u8string_view password);
|
||||||
|
uint8_t GetDefaultGroup() const noexcept;
|
||||||
|
std::string BeginLog(const std::string& directory, const std::string& midName, const std::string& filenameFormat);
|
||||||
|
void AppendLog(std::ostream& fs, std::string_view s);
|
||||||
|
void BeginChatLog();
|
||||||
|
void AppendChatLog(std::string_view s);
|
||||||
|
void CloseChatLog();
|
||||||
|
NetworkStats GetStats() const;
|
||||||
|
json_t GetServerInfoAsJson() const;
|
||||||
|
bool ProcessConnection(NetworkConnection& connection);
|
||||||
|
void CloseConnection();
|
||||||
|
NetworkPlayer* AddPlayer(const std::string& name, const std::string& keyhash);
|
||||||
|
void ProcessPacket(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
|
||||||
std::unordered_map<NetworkCommand, CommandHandler> client_command_handlers;
|
public: // Server
|
||||||
std::unique_ptr<NetworkConnection> _serverConnection;
|
NetworkConnection* GetPlayerConnection(uint8_t id) const;
|
||||||
std::map<uint32_t, PlayerListUpdate> _pendingPlayerLists;
|
void KickPlayer(int32_t playerId);
|
||||||
std::multimap<uint32_t, NetworkPlayer> _pendingPlayerInfo;
|
NetworkGroup* AddGroup();
|
||||||
std::map<uint32_t, ServerTickData> _serverTickData;
|
void LoadGroups();
|
||||||
std::vector<OpenRCT2::ObjectEntryDescriptor> _missingObjects;
|
void SetDefaultGroup(uint8_t id);
|
||||||
std::string _host;
|
void SaveGroups();
|
||||||
std::string _chatLogPath;
|
void RemoveGroup(uint8_t id);
|
||||||
std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
uint8_t GetGroupIDByHash(const std::string& keyhash);
|
||||||
std::string _password;
|
void BeginServerLog();
|
||||||
OpenRCT2::MemoryStream _serverGameState;
|
void AppendServerLog(const std::string& s);
|
||||||
NetworkServerState _serverState;
|
void CloseServerLog();
|
||||||
uint32_t _lastSentHeartbeat = 0;
|
void DecayCooldown(NetworkPlayer* player);
|
||||||
uint32_t last_ping_sent_time = 0;
|
void AddClient(std::unique_ptr<ITcpSocket>&& socket);
|
||||||
uint32_t server_connect_time = 0;
|
std::string GetMasterServerUrl();
|
||||||
uint32_t _actionId;
|
std::string GenerateAdvertiseKey();
|
||||||
int32_t status = NETWORK_STATUS_NONE;
|
void SetupDefaultGroups();
|
||||||
uint8_t player_id = 0;
|
void RemovePlayer(std::unique_ptr<NetworkConnection>& connection);
|
||||||
uint16_t _port = 0;
|
void UpdateServer();
|
||||||
SocketStatus _lastConnectStatus = SocketStatus::Closed;
|
void ServerClientDisconnected(std::unique_ptr<NetworkConnection>& connection);
|
||||||
bool _requireReconnect = false;
|
bool SaveMap(OpenRCT2::IStream* stream, const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
||||||
bool _clientMapLoaded = false;
|
std::vector<uint8_t> SaveForNetwork(const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
||||||
ServerScriptsData _serverScriptsData{};
|
std::string MakePlayerNameUnique(const std::string& name);
|
||||||
};
|
|
||||||
|
// Packet dispatchers.
|
||||||
|
void ServerSendAuth(NetworkConnection& connection);
|
||||||
|
void ServerSendToken(NetworkConnection& connection);
|
||||||
|
void ServerSendMap(NetworkConnection* connection = nullptr);
|
||||||
|
void ServerSendChat(const char* text, const std::vector<uint8_t>& playerIds = {});
|
||||||
|
void ServerSendGameAction(const OpenRCT2::GameActions::GameAction* action);
|
||||||
|
void ServerSendTick();
|
||||||
|
void ServerSendPlayerInfo(int32_t playerId);
|
||||||
|
void ServerSendPlayerList();
|
||||||
|
void ServerSendPing();
|
||||||
|
void ServerSendPingList();
|
||||||
|
void ServerSendSetDisconnectMsg(NetworkConnection& connection, const char* msg);
|
||||||
|
void ServerSendGameInfo(NetworkConnection& connection);
|
||||||
|
void ServerSendShowError(NetworkConnection& connection, StringId title, StringId message);
|
||||||
|
void ServerSendGroupList(NetworkConnection& connection);
|
||||||
|
void ServerSendEventPlayerJoined(const char* playerName);
|
||||||
|
void ServerSendEventPlayerDisconnected(const char* playerName, const char* reason);
|
||||||
|
void ServerSendObjectsList(
|
||||||
|
NetworkConnection& connection, const std::vector<const OpenRCT2::ObjectRepositoryItem*>& objects) const;
|
||||||
|
void ServerSendScripts(NetworkConnection& connection);
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
void ServerHandleRequestGamestate(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleHeartbeat(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleAuth(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerClientJoined(std::string_view name, const std::string& keyhash, NetworkConnection& connection);
|
||||||
|
void ServerHandleChat(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleGameAction(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandlePing(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleGameInfo(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleToken(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void ServerHandleMapRequest(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
|
||||||
|
public: // Client
|
||||||
|
void Reconnect();
|
||||||
|
int32_t GetMode() const noexcept;
|
||||||
|
NetworkAuth GetAuthStatus();
|
||||||
|
int32_t GetStatus() const noexcept;
|
||||||
|
uint8_t GetPlayerID() const noexcept;
|
||||||
|
void ProcessPlayerInfo();
|
||||||
|
void ProcessDisconnectedClients();
|
||||||
|
static const char* FormatChat(NetworkPlayer* fromplayer, const char* text);
|
||||||
|
void SendPacketToClients(const NetworkPacket& packet, bool front = false, bool gameCmd = false) const;
|
||||||
|
bool CheckSRAND(uint32_t tick, uint32_t srand0);
|
||||||
|
bool CheckDesynchronizaton();
|
||||||
|
void RequestStateSnapshot();
|
||||||
|
bool IsDesynchronised() const noexcept;
|
||||||
|
NetworkServerState GetServerState() const noexcept;
|
||||||
|
void ServerClientDisconnected();
|
||||||
|
bool LoadMap(OpenRCT2::IStream* stream);
|
||||||
|
void UpdateClient();
|
||||||
|
|
||||||
|
// Packet dispatchers.
|
||||||
|
void Client_Send_RequestGameState(uint32_t tick);
|
||||||
|
void Client_Send_TOKEN();
|
||||||
|
void Client_Send_AUTH(
|
||||||
|
const std::string& name, const std::string& password, const std::string& pubkey,
|
||||||
|
const std::vector<uint8_t>& signature);
|
||||||
|
void Client_Send_CHAT(const char* text);
|
||||||
|
void Client_Send_GAME_ACTION(const OpenRCT2::GameActions::GameAction* action);
|
||||||
|
void Client_Send_PING();
|
||||||
|
void Client_Send_GAMEINFO();
|
||||||
|
void Client_Send_MAPREQUEST(const std::vector<OpenRCT2::ObjectEntryDescriptor>& objects);
|
||||||
|
void Client_Send_HEARTBEAT(NetworkConnection& connection) const;
|
||||||
|
|
||||||
|
// Handlers.
|
||||||
|
void Client_Handle_AUTH(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_MAP(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_CHAT(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_TICK(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_PLAYERINFO(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_PLAYERLIST(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_PING(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_PINGLIST(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_SETDISCONNECTMSG(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_GAMEINFO(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_SHOWERROR(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_GROUPLIST(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_EVENT(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_OBJECTS_LIST(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_SCRIPTS_HEADER(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_SCRIPTS_DATA(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
void Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
|
||||||
|
std::vector<uint8_t> _challenge;
|
||||||
|
std::map<uint32_t, OpenRCT2::GameActions::GameAction::Callback_t> _gameActionCallbacks;
|
||||||
|
NetworkKey _key;
|
||||||
|
NetworkUserManager _userManager;
|
||||||
|
|
||||||
|
public: // Public common
|
||||||
|
std::string ServerName;
|
||||||
|
std::string ServerDescription;
|
||||||
|
std::string ServerGreeting;
|
||||||
|
std::string ServerProviderName;
|
||||||
|
std::string ServerProviderEmail;
|
||||||
|
std::string ServerProviderWebsite;
|
||||||
|
std::vector<std::unique_ptr<NetworkPlayer>> player_list;
|
||||||
|
std::vector<std::unique_ptr<NetworkGroup>> group_list;
|
||||||
|
bool IsServerPlayerInvisible = false;
|
||||||
|
|
||||||
|
private: // Common Data
|
||||||
|
using CommandHandler = void (NetworkBase::*)(NetworkConnection& connection, NetworkPacket& packet);
|
||||||
|
|
||||||
|
std::vector<uint8_t> chunk_buffer;
|
||||||
|
std::ofstream _chat_log_fs;
|
||||||
|
uint32_t _lastUpdateTime = 0;
|
||||||
|
uint32_t _currentDeltaTime = 0;
|
||||||
|
int32_t mode = NETWORK_MODE_NONE;
|
||||||
|
uint8_t default_group = 0;
|
||||||
|
bool _closeLock = false;
|
||||||
|
bool _requireClose = false;
|
||||||
|
|
||||||
|
private: // Server Data
|
||||||
|
std::unordered_map<NetworkCommand, CommandHandler> server_command_handlers;
|
||||||
|
std::unique_ptr<ITcpSocket> _listenSocket;
|
||||||
|
std::unique_ptr<INetworkServerAdvertiser> _advertiser;
|
||||||
|
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
||||||
|
std::string _serverLogPath;
|
||||||
|
std::string _serverLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
||||||
|
std::ofstream _server_log_fs;
|
||||||
|
uint16_t listening_port = 0;
|
||||||
|
bool _playerListInvalidated = false;
|
||||||
|
|
||||||
|
private: // Client Data
|
||||||
|
struct PlayerListUpdate
|
||||||
|
{
|
||||||
|
std::vector<NetworkPlayer> players;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerTickData
|
||||||
|
{
|
||||||
|
uint32_t srand0;
|
||||||
|
uint32_t tick;
|
||||||
|
std::string spriteHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerScriptsData
|
||||||
|
{
|
||||||
|
uint32_t pluginCount{};
|
||||||
|
uint32_t dataSize{};
|
||||||
|
OpenRCT2::MemoryStream data;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<NetworkCommand, CommandHandler> client_command_handlers;
|
||||||
|
std::unique_ptr<NetworkConnection> _serverConnection;
|
||||||
|
std::map<uint32_t, PlayerListUpdate> _pendingPlayerLists;
|
||||||
|
std::multimap<uint32_t, NetworkPlayer> _pendingPlayerInfo;
|
||||||
|
std::map<uint32_t, ServerTickData> _serverTickData;
|
||||||
|
std::vector<OpenRCT2::ObjectEntryDescriptor> _missingObjects;
|
||||||
|
std::string _host;
|
||||||
|
std::string _chatLogPath;
|
||||||
|
std::string _chatLogFilenameFormat = "%Y%m%d-%H%M%S.txt";
|
||||||
|
std::string _password;
|
||||||
|
OpenRCT2::MemoryStream _serverGameState;
|
||||||
|
NetworkServerState _serverState;
|
||||||
|
uint32_t _lastSentHeartbeat = 0;
|
||||||
|
uint32_t last_ping_sent_time = 0;
|
||||||
|
uint32_t server_connect_time = 0;
|
||||||
|
uint32_t _actionId;
|
||||||
|
int32_t status = NETWORK_STATUS_NONE;
|
||||||
|
uint8_t player_id = 0;
|
||||||
|
uint16_t _port = 0;
|
||||||
|
SocketStatus _lastConnectStatus = SocketStatus::Closed;
|
||||||
|
bool _requireReconnect = false;
|
||||||
|
bool _clientMapLoaded = false;
|
||||||
|
ServerScriptsData _serverScriptsData{};
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
#ifndef DISABLE_NETWORK
|
#ifndef DISABLE_NETWORK
|
||||||
|
|
||||||
class NetworkClient final : public NetworkBase
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkClient final : public NetworkBase
|
||||||
};
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -19,217 +19,218 @@
|
|||||||
|
|
||||||
#include <sfl/small_vector.hpp>
|
#include <sfl/small_vector.hpp>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
{
|
||||||
static constexpr size_t kNetworkDisconnectReasonBufSize = 256;
|
static constexpr size_t kNetworkDisconnectReasonBufSize = 256;
|
||||||
static constexpr size_t kNetworkBufferSize = (1024 * 64) - 1; // 64 KiB, maximum packet size.
|
static constexpr size_t kNetworkBufferSize = (1024 * 64) - 1; // 64 KiB, maximum packet size.
|
||||||
#ifndef DEBUG
|
#ifndef DEBUG
|
||||||
static constexpr size_t kNetworkNoDataTimeout = 20; // Seconds.
|
static constexpr size_t kNetworkNoDataTimeout = 20; // Seconds.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static_assert(kNetworkBufferSize <= std::numeric_limits<uint16_t>::max(), "kNetworkBufferSize too big, uint16_t is max.");
|
static_assert(kNetworkBufferSize <= std::numeric_limits<uint16_t>::max(), "kNetworkBufferSize too big, uint16_t is max.");
|
||||||
|
|
||||||
NetworkConnection::NetworkConnection() noexcept
|
NetworkConnection::NetworkConnection() noexcept
|
||||||
{
|
|
||||||
ResetLastPacketTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkReadPacket NetworkConnection::ReadPacket()
|
|
||||||
{
|
|
||||||
size_t bytesRead = 0;
|
|
||||||
|
|
||||||
// Read packet header.
|
|
||||||
auto& header = InboundPacket.Header;
|
|
||||||
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
|
||||||
{
|
{
|
||||||
const size_t missingLength = sizeof(header) - InboundPacket.BytesTransferred;
|
ResetLastPacketTime();
|
||||||
|
|
||||||
uint8_t* buffer = reinterpret_cast<uint8_t*>(&InboundPacket.Header);
|
|
||||||
|
|
||||||
NetworkReadPacket status = Socket->ReceiveData(buffer, missingLength, &bytesRead);
|
|
||||||
if (status != NetworkReadPacket::Success)
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
InboundPacket.BytesTransferred += bytesRead;
|
|
||||||
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
|
||||||
{
|
|
||||||
// If still not enough data for header, keep waiting.
|
|
||||||
return NetworkReadPacket::MoreData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalise values.
|
|
||||||
header.Size = Convert::NetworkToHost(header.Size);
|
|
||||||
header.Id = ByteSwapBE(header.Id);
|
|
||||||
|
|
||||||
// NOTE: For compatibility reasons for the master server we need to remove sizeof(Header.Id) from the size.
|
|
||||||
// Previously the Id field was not part of the header rather part of the body.
|
|
||||||
header.Size -= std::min<uint16_t>(header.Size, sizeof(header.Id));
|
|
||||||
|
|
||||||
// Fall-through: Read rest of packet.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read packet body.
|
NetworkReadPacket NetworkConnection::ReadPacket()
|
||||||
{
|
{
|
||||||
// NOTE: BytesTransfered includes the header length, this will not underflow.
|
size_t bytesRead = 0;
|
||||||
const size_t missingLength = header.Size - (InboundPacket.BytesTransferred - sizeof(header));
|
|
||||||
|
|
||||||
uint8_t buffer[kNetworkBufferSize];
|
// Read packet header.
|
||||||
|
auto& header = InboundPacket.Header;
|
||||||
if (missingLength > 0)
|
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
||||||
{
|
{
|
||||||
NetworkReadPacket status = Socket->ReceiveData(buffer, std::min(missingLength, kNetworkBufferSize), &bytesRead);
|
const size_t missingLength = sizeof(header) - InboundPacket.BytesTransferred;
|
||||||
|
|
||||||
|
uint8_t* buffer = reinterpret_cast<uint8_t*>(&InboundPacket.Header);
|
||||||
|
|
||||||
|
NetworkReadPacket status = Socket->ReceiveData(buffer, missingLength, &bytesRead);
|
||||||
if (status != NetworkReadPacket::Success)
|
if (status != NetworkReadPacket::Success)
|
||||||
{
|
{
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
InboundPacket.BytesTransferred += bytesRead;
|
InboundPacket.BytesTransferred += bytesRead;
|
||||||
InboundPacket.Write(buffer, bytesRead);
|
if (InboundPacket.BytesTransferred < sizeof(InboundPacket.Header))
|
||||||
|
{
|
||||||
|
// If still not enough data for header, keep waiting.
|
||||||
|
return NetworkReadPacket::MoreData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalise values.
|
||||||
|
header.Size = Convert::NetworkToHost(header.Size);
|
||||||
|
header.Id = ByteSwapBE(header.Id);
|
||||||
|
|
||||||
|
// NOTE: For compatibility reasons for the master server we need to remove sizeof(Header.Id) from the size.
|
||||||
|
// Previously the Id field was not part of the header rather part of the body.
|
||||||
|
header.Size -= std::min<uint16_t>(header.Size, sizeof(header.Id));
|
||||||
|
|
||||||
|
// Fall-through: Read rest of packet.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (InboundPacket.Data.size() == header.Size)
|
// Read packet body.
|
||||||
{
|
{
|
||||||
// Received complete packet.
|
// NOTE: BytesTransfered includes the header length, this will not underflow.
|
||||||
_lastPacketTime = Platform::GetTicks();
|
const size_t missingLength = header.Size - (InboundPacket.BytesTransferred - sizeof(header));
|
||||||
|
|
||||||
RecordPacketStats(InboundPacket, false);
|
uint8_t buffer[kNetworkBufferSize];
|
||||||
|
|
||||||
return NetworkReadPacket::Success;
|
if (missingLength > 0)
|
||||||
|
{
|
||||||
|
NetworkReadPacket status = Socket->ReceiveData(buffer, std::min(missingLength, kNetworkBufferSize), &bytesRead);
|
||||||
|
if (status != NetworkReadPacket::Success)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
InboundPacket.BytesTransferred += bytesRead;
|
||||||
|
InboundPacket.Write(buffer, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InboundPacket.Data.size() == header.Size)
|
||||||
|
{
|
||||||
|
// Received complete packet.
|
||||||
|
_lastPacketTime = Platform::GetTicks();
|
||||||
|
|
||||||
|
RecordPacketStats(InboundPacket, false);
|
||||||
|
|
||||||
|
return NetworkReadPacket::Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetworkReadPacket::MoreData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sfl::small_vector<uint8_t, 512> serializePacket(const NetworkPacket& packet)
|
||||||
|
{
|
||||||
|
// NOTE: For compatibility reasons for the master server we need to add sizeof(Header.Id) to the size.
|
||||||
|
// Previously the Id field was not part of the header rather part of the body.
|
||||||
|
const auto bodyLength = packet.Data.size() + sizeof(packet.Header.Id);
|
||||||
|
|
||||||
|
Guard::Assert(bodyLength <= std::numeric_limits<uint16_t>::max(), "Packet size too large");
|
||||||
|
|
||||||
|
auto header = packet.Header;
|
||||||
|
header.Size = static_cast<uint16_t>(bodyLength);
|
||||||
|
header.Size = Convert::HostToNetwork(header.Size);
|
||||||
|
header.Id = ByteSwapBE(header.Id);
|
||||||
|
|
||||||
|
sfl::small_vector<uint8_t, 512> buffer;
|
||||||
|
buffer.reserve(sizeof(header) + packet.Data.size());
|
||||||
|
|
||||||
|
buffer.insert(buffer.end(), reinterpret_cast<uint8_t*>(&header), reinterpret_cast<uint8_t*>(&header) + sizeof(header));
|
||||||
|
buffer.insert(buffer.end(), packet.Data.begin(), packet.Data.end());
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::QueuePacket(const NetworkPacket& packet, bool front)
|
||||||
|
{
|
||||||
|
if (AuthStatus == NetworkAuth::Ok || !packet.CommandRequiresAuth())
|
||||||
|
{
|
||||||
|
const auto payload = serializePacket(packet);
|
||||||
|
if (front)
|
||||||
|
{
|
||||||
|
_outboundBuffer.insert(_outboundBuffer.begin(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_outboundBuffer.insert(_outboundBuffer.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordPacketStats(packet, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NetworkReadPacket::MoreData;
|
void NetworkConnection::Disconnect() noexcept
|
||||||
}
|
|
||||||
|
|
||||||
static sfl::small_vector<uint8_t, 512> serializePacket(const NetworkPacket& packet)
|
|
||||||
{
|
|
||||||
// NOTE: For compatibility reasons for the master server we need to add sizeof(Header.Id) to the size.
|
|
||||||
// Previously the Id field was not part of the header rather part of the body.
|
|
||||||
const auto bodyLength = packet.Data.size() + sizeof(packet.Header.Id);
|
|
||||||
|
|
||||||
Guard::Assert(bodyLength <= std::numeric_limits<uint16_t>::max(), "Packet size too large");
|
|
||||||
|
|
||||||
auto header = packet.Header;
|
|
||||||
header.Size = static_cast<uint16_t>(bodyLength);
|
|
||||||
header.Size = Convert::HostToNetwork(header.Size);
|
|
||||||
header.Id = ByteSwapBE(header.Id);
|
|
||||||
|
|
||||||
sfl::small_vector<uint8_t, 512> buffer;
|
|
||||||
buffer.reserve(sizeof(header) + packet.Data.size());
|
|
||||||
|
|
||||||
buffer.insert(buffer.end(), reinterpret_cast<uint8_t*>(&header), reinterpret_cast<uint8_t*>(&header) + sizeof(header));
|
|
||||||
buffer.insert(buffer.end(), packet.Data.begin(), packet.Data.end());
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::QueuePacket(const NetworkPacket& packet, bool front)
|
|
||||||
{
|
|
||||||
if (AuthStatus == NetworkAuth::Ok || !packet.CommandRequiresAuth())
|
|
||||||
{
|
{
|
||||||
const auto payload = serializePacket(packet);
|
ShouldDisconnect = true;
|
||||||
if (front)
|
}
|
||||||
|
|
||||||
|
bool NetworkConnection::IsValid() const
|
||||||
|
{
|
||||||
|
return !ShouldDisconnect && Socket->GetStatus() == SocketStatus::Connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::SendQueuedData()
|
||||||
|
{
|
||||||
|
if (_outboundBuffer.empty())
|
||||||
{
|
{
|
||||||
_outboundBuffer.insert(_outboundBuffer.begin(), payload.begin(), payload.end());
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bytesSent = Socket->SendData(_outboundBuffer.data(), _outboundBuffer.size());
|
||||||
|
|
||||||
|
if (bytesSent > 0)
|
||||||
|
{
|
||||||
|
_outboundBuffer.erase(_outboundBuffer.begin(), _outboundBuffer.begin() + bytesSent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::ResetLastPacketTime() noexcept
|
||||||
|
{
|
||||||
|
_lastPacketTime = Platform::GetTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkConnection::ReceivedPacketRecently() const noexcept
|
||||||
|
{
|
||||||
|
#ifndef DEBUG
|
||||||
|
constexpr auto kTimeoutMs = kNetworkNoDataTimeout * 1000;
|
||||||
|
if (Platform::GetTicks() > _lastPacketTime + kTimeoutMs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const utf8* NetworkConnection::GetLastDisconnectReason() const noexcept
|
||||||
|
{
|
||||||
|
return this->_lastDisconnectReason.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::SetLastDisconnectReason(std::string_view src)
|
||||||
|
{
|
||||||
|
_lastDisconnectReason = src;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::SetLastDisconnectReason(const StringId string_id, void* args)
|
||||||
|
{
|
||||||
|
char buffer[kNetworkDisconnectReasonBufSize];
|
||||||
|
OpenRCT2::FormatStringLegacy(buffer, kNetworkDisconnectReasonBufSize, string_id, args);
|
||||||
|
SetLastDisconnectReason(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkConnection::RecordPacketStats(const NetworkPacket& packet, bool sending)
|
||||||
|
{
|
||||||
|
uint32_t packetSize = static_cast<uint32_t>(packet.BytesTransferred);
|
||||||
|
NetworkStatisticsGroup trafficGroup;
|
||||||
|
|
||||||
|
switch (packet.GetCommand())
|
||||||
|
{
|
||||||
|
case NetworkCommand::GameAction:
|
||||||
|
trafficGroup = NetworkStatisticsGroup::Commands;
|
||||||
|
break;
|
||||||
|
case NetworkCommand::Map:
|
||||||
|
trafficGroup = NetworkStatisticsGroup::MapData;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
trafficGroup = NetworkStatisticsGroup::Base;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sending)
|
||||||
|
{
|
||||||
|
Stats.bytesSent[EnumValue(trafficGroup)] += packetSize;
|
||||||
|
Stats.bytesSent[EnumValue(NetworkStatisticsGroup::Total)] += packetSize;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_outboundBuffer.insert(_outboundBuffer.end(), payload.begin(), payload.end());
|
Stats.bytesReceived[EnumValue(trafficGroup)] += packetSize;
|
||||||
|
Stats.bytesReceived[EnumValue(NetworkStatisticsGroup::Total)] += packetSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordPacketStats(packet, true);
|
|
||||||
}
|
}
|
||||||
}
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
void NetworkConnection::Disconnect() noexcept
|
|
||||||
{
|
|
||||||
ShouldDisconnect = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkConnection::IsValid() const
|
|
||||||
{
|
|
||||||
return !ShouldDisconnect && Socket->GetStatus() == SocketStatus::Connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::SendQueuedData()
|
|
||||||
{
|
|
||||||
if (_outboundBuffer.empty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto bytesSent = Socket->SendData(_outboundBuffer.data(), _outboundBuffer.size());
|
|
||||||
|
|
||||||
if (bytesSent > 0)
|
|
||||||
{
|
|
||||||
_outboundBuffer.erase(_outboundBuffer.begin(), _outboundBuffer.begin() + bytesSent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::ResetLastPacketTime() noexcept
|
|
||||||
{
|
|
||||||
_lastPacketTime = Platform::GetTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkConnection::ReceivedPacketRecently() const noexcept
|
|
||||||
{
|
|
||||||
#ifndef DEBUG
|
|
||||||
constexpr auto kTimeoutMs = kNetworkNoDataTimeout * 1000;
|
|
||||||
if (Platform::GetTicks() > _lastPacketTime + kTimeoutMs)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const utf8* NetworkConnection::GetLastDisconnectReason() const noexcept
|
|
||||||
{
|
|
||||||
return this->_lastDisconnectReason.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::SetLastDisconnectReason(std::string_view src)
|
|
||||||
{
|
|
||||||
_lastDisconnectReason = src;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::SetLastDisconnectReason(const StringId string_id, void* args)
|
|
||||||
{
|
|
||||||
char buffer[kNetworkDisconnectReasonBufSize];
|
|
||||||
OpenRCT2::FormatStringLegacy(buffer, kNetworkDisconnectReasonBufSize, string_id, args);
|
|
||||||
SetLastDisconnectReason(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkConnection::RecordPacketStats(const NetworkPacket& packet, bool sending)
|
|
||||||
{
|
|
||||||
uint32_t packetSize = static_cast<uint32_t>(packet.BytesTransferred);
|
|
||||||
NetworkStatisticsGroup trafficGroup;
|
|
||||||
|
|
||||||
switch (packet.GetCommand())
|
|
||||||
{
|
|
||||||
case NetworkCommand::GameAction:
|
|
||||||
trafficGroup = NetworkStatisticsGroup::Commands;
|
|
||||||
break;
|
|
||||||
case NetworkCommand::Map:
|
|
||||||
trafficGroup = NetworkStatisticsGroup::MapData;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
trafficGroup = NetworkStatisticsGroup::Base;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sending)
|
|
||||||
{
|
|
||||||
Stats.bytesSent[EnumValue(trafficGroup)] += packetSize;
|
|
||||||
Stats.bytesSent[EnumValue(NetworkStatisticsGroup::Total)] += packetSize;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Stats.bytesReceived[EnumValue(trafficGroup)] += packetSize;
|
|
||||||
Stats.bytesReceived[EnumValue(NetworkStatisticsGroup::Total)] += packetSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,51 +20,54 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class NetworkPlayer;
|
|
||||||
|
|
||||||
namespace OpenRCT2
|
namespace OpenRCT2
|
||||||
{
|
{
|
||||||
struct ObjectRepositoryItem;
|
struct ObjectRepositoryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkConnection final
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkPlayer;
|
||||||
std::unique_ptr<ITcpSocket> Socket = nullptr;
|
|
||||||
NetworkPacket InboundPacket;
|
|
||||||
NetworkAuth AuthStatus = NetworkAuth::None;
|
|
||||||
NetworkStats Stats = {};
|
|
||||||
NetworkPlayer* Player = nullptr;
|
|
||||||
uint32_t PingTime = 0;
|
|
||||||
NetworkKey Key;
|
|
||||||
std::vector<uint8_t> Challenge;
|
|
||||||
std::vector<const OpenRCT2::ObjectRepositoryItem*> RequestedObjects;
|
|
||||||
bool ShouldDisconnect = false;
|
|
||||||
|
|
||||||
NetworkConnection() noexcept;
|
class NetworkConnection final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::unique_ptr<ITcpSocket> Socket = nullptr;
|
||||||
|
NetworkPacket InboundPacket;
|
||||||
|
NetworkAuth AuthStatus = NetworkAuth::None;
|
||||||
|
NetworkStats Stats = {};
|
||||||
|
NetworkPlayer* Player = nullptr;
|
||||||
|
uint32_t PingTime = 0;
|
||||||
|
NetworkKey Key;
|
||||||
|
std::vector<uint8_t> Challenge;
|
||||||
|
std::vector<const OpenRCT2::ObjectRepositoryItem*> RequestedObjects;
|
||||||
|
bool ShouldDisconnect = false;
|
||||||
|
|
||||||
NetworkReadPacket ReadPacket();
|
NetworkConnection() noexcept;
|
||||||
void QueuePacket(const NetworkPacket& packet, bool front = false);
|
|
||||||
|
|
||||||
// This will not immediately disconnect the client. The disconnect
|
NetworkReadPacket ReadPacket();
|
||||||
// will happen post-tick.
|
void QueuePacket(const NetworkPacket& packet, bool front = false);
|
||||||
void Disconnect() noexcept;
|
|
||||||
|
|
||||||
bool IsValid() const;
|
// This will not immediately disconnect the client. The disconnect
|
||||||
void SendQueuedData();
|
// will happen post-tick.
|
||||||
void ResetLastPacketTime() noexcept;
|
void Disconnect() noexcept;
|
||||||
bool ReceivedPacketRecently() const noexcept;
|
|
||||||
|
|
||||||
const utf8* GetLastDisconnectReason() const noexcept;
|
bool IsValid() const;
|
||||||
void SetLastDisconnectReason(std::string_view src);
|
void SendQueuedData();
|
||||||
void SetLastDisconnectReason(const StringId string_id, void* args = nullptr);
|
void ResetLastPacketTime() noexcept;
|
||||||
|
bool ReceivedPacketRecently() const noexcept;
|
||||||
|
|
||||||
private:
|
const utf8* GetLastDisconnectReason() const noexcept;
|
||||||
std::vector<uint8_t> _outboundBuffer;
|
void SetLastDisconnectReason(std::string_view src);
|
||||||
uint32_t _lastPacketTime = 0;
|
void SetLastDisconnectReason(const StringId string_id, void* args = nullptr);
|
||||||
std::string _lastDisconnectReason;
|
|
||||||
|
|
||||||
void RecordPacketStats(const NetworkPacket& packet, bool sending);
|
private:
|
||||||
};
|
std::vector<uint8_t> _outboundBuffer;
|
||||||
|
uint32_t _lastPacketTime = 0;
|
||||||
|
std::string _lastDisconnectReason;
|
||||||
|
|
||||||
|
void RecordPacketStats(const NetworkPacket& packet, bool sending);
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -15,119 +15,120 @@
|
|||||||
#include "NetworkAction.h"
|
#include "NetworkAction.h"
|
||||||
#include "NetworkTypes.h"
|
#include "NetworkTypes.h"
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
NetworkGroup NetworkGroup::FromJson(const json_t& jsonData)
|
|
||||||
{
|
{
|
||||||
Guard::Assert(jsonData.is_object(), "NetworkGroup::FromJson expects parameter jsonData to be object");
|
NetworkGroup NetworkGroup::FromJson(const json_t& jsonData)
|
||||||
|
|
||||||
NetworkGroup group;
|
|
||||||
json_t jsonId = jsonData["id"];
|
|
||||||
json_t jsonName = jsonData["name"];
|
|
||||||
json_t jsonPermissions = jsonData["permissions"];
|
|
||||||
|
|
||||||
if (jsonId.is_null() || jsonName.is_null() || jsonPermissions.is_null())
|
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Missing group data");
|
Guard::Assert(jsonData.is_object(), "NetworkGroup::FromJson expects parameter jsonData to be object");
|
||||||
|
|
||||||
|
NetworkGroup group;
|
||||||
|
json_t jsonId = jsonData["id"];
|
||||||
|
json_t jsonName = jsonData["name"];
|
||||||
|
json_t jsonPermissions = jsonData["permissions"];
|
||||||
|
|
||||||
|
if (jsonId.is_null() || jsonName.is_null() || jsonPermissions.is_null())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Missing group data");
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Id = Json::GetNumber<uint8_t>(jsonId);
|
||||||
|
group._name = Json::GetString(jsonName);
|
||||||
|
std::fill(group.ActionsAllowed.begin(), group.ActionsAllowed.end(), 0);
|
||||||
|
|
||||||
|
for (const auto& jsonValue : jsonPermissions)
|
||||||
|
{
|
||||||
|
const std::string permission = Json::GetString(jsonValue);
|
||||||
|
|
||||||
|
NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(permission);
|
||||||
|
if (action_id != NetworkPermission::Count)
|
||||||
|
{
|
||||||
|
group.ToggleActionPermission(action_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Id = Json::GetNumber<uint8_t>(jsonId);
|
json_t NetworkGroup::ToJson() const
|
||||||
group._name = Json::GetString(jsonName);
|
|
||||||
std::fill(group.ActionsAllowed.begin(), group.ActionsAllowed.end(), 0);
|
|
||||||
|
|
||||||
for (const auto& jsonValue : jsonPermissions)
|
|
||||||
{
|
{
|
||||||
const std::string permission = Json::GetString(jsonValue);
|
json_t jsonGroup = {
|
||||||
|
{ "id", Id },
|
||||||
NetworkPermission action_id = NetworkActions::FindCommandByPermissionName(permission);
|
{ "name", GetName() },
|
||||||
if (action_id != NetworkPermission::Count)
|
};
|
||||||
|
json_t actionsArray = json_t::array();
|
||||||
|
for (size_t i = 0; i < NetworkActions::Actions.size(); i++)
|
||||||
{
|
{
|
||||||
group.ToggleActionPermission(action_id);
|
if (CanPerformAction(static_cast<NetworkPermission>(i)))
|
||||||
|
{
|
||||||
|
actionsArray.emplace_back(NetworkActions::Actions[i].PermissionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonGroup["permissions"] = actionsArray;
|
||||||
|
return jsonGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& NetworkGroup::GetName() const noexcept
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGroup::SetName(std::string_view name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGroup::Read(NetworkPacket& packet)
|
||||||
|
{
|
||||||
|
packet >> Id;
|
||||||
|
SetName(packet.ReadString());
|
||||||
|
for (auto& action : ActionsAllowed)
|
||||||
|
{
|
||||||
|
packet >> action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t NetworkGroup::ToJson() const
|
void NetworkGroup::Write(NetworkPacket& packet) const
|
||||||
{
|
|
||||||
json_t jsonGroup = {
|
|
||||||
{ "id", Id },
|
|
||||||
{ "name", GetName() },
|
|
||||||
};
|
|
||||||
json_t actionsArray = json_t::array();
|
|
||||||
for (size_t i = 0; i < NetworkActions::Actions.size(); i++)
|
|
||||||
{
|
{
|
||||||
if (CanPerformAction(static_cast<NetworkPermission>(i)))
|
packet << Id;
|
||||||
|
packet.WriteString(GetName().c_str());
|
||||||
|
for (const auto& action : ActionsAllowed)
|
||||||
{
|
{
|
||||||
actionsArray.emplace_back(NetworkActions::Actions[i].PermissionName);
|
packet << action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonGroup["permissions"] = actionsArray;
|
|
||||||
return jsonGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& NetworkGroup::GetName() const noexcept
|
void NetworkGroup::ToggleActionPermission(NetworkPermission index)
|
||||||
{
|
|
||||||
return _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGroup::SetName(std::string_view name)
|
|
||||||
{
|
|
||||||
_name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGroup::Read(NetworkPacket& packet)
|
|
||||||
{
|
|
||||||
packet >> Id;
|
|
||||||
SetName(packet.ReadString());
|
|
||||||
for (auto& action : ActionsAllowed)
|
|
||||||
{
|
{
|
||||||
packet >> action;
|
size_t index_st = static_cast<size_t>(index);
|
||||||
|
size_t byte = index_st / 8;
|
||||||
|
size_t bit = index_st % 8;
|
||||||
|
if (byte >= ActionsAllowed.size())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ActionsAllowed[byte] ^= (1 << bit);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGroup::Write(NetworkPacket& packet) const
|
bool NetworkGroup::CanPerformAction(NetworkPermission index) const noexcept
|
||||||
{
|
|
||||||
packet << Id;
|
|
||||||
packet.WriteString(GetName().c_str());
|
|
||||||
for (const auto& action : ActionsAllowed)
|
|
||||||
{
|
{
|
||||||
packet << action;
|
size_t index_st = static_cast<size_t>(index);
|
||||||
|
size_t byte = index_st / 8;
|
||||||
|
size_t bit = index_st % 8;
|
||||||
|
if (byte >= ActionsAllowed.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (ActionsAllowed[byte] & (1 << bit)) != 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGroup::ToggleActionPermission(NetworkPermission index)
|
bool NetworkGroup::CanPerformCommand(GameCommand command) const
|
||||||
{
|
|
||||||
size_t index_st = static_cast<size_t>(index);
|
|
||||||
size_t byte = index_st / 8;
|
|
||||||
size_t bit = index_st % 8;
|
|
||||||
if (byte >= ActionsAllowed.size())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ActionsAllowed[byte] ^= (1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkGroup::CanPerformAction(NetworkPermission index) const noexcept
|
|
||||||
{
|
|
||||||
size_t index_st = static_cast<size_t>(index);
|
|
||||||
size_t byte = index_st / 8;
|
|
||||||
size_t bit = index_st % 8;
|
|
||||||
if (byte >= ActionsAllowed.size())
|
|
||||||
{
|
{
|
||||||
|
NetworkPermission action = NetworkActions::FindCommand(command);
|
||||||
|
if (action != NetworkPermission::Count)
|
||||||
|
{
|
||||||
|
return CanPerformAction(action);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (ActionsAllowed[byte] & (1 << bit)) != 0;
|
} // namespace OpenRCT2::Network
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkGroup::CanPerformCommand(GameCommand command) const
|
|
||||||
{
|
|
||||||
NetworkPermission action = NetworkActions::FindCommand(command);
|
|
||||||
if (action != NetworkPermission::Count)
|
|
||||||
{
|
|
||||||
return CanPerformAction(action);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,39 +16,42 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
enum class NetworkPermission : uint32_t;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
class NetworkGroup final
|
|
||||||
{
|
{
|
||||||
public:
|
enum class NetworkPermission : uint32_t;
|
||||||
std::array<uint8_t, 8> ActionsAllowed{};
|
|
||||||
uint8_t Id = 0;
|
|
||||||
|
|
||||||
/**
|
class NetworkGroup final
|
||||||
* Creates a NetworkGroup object from a JSON object
|
{
|
||||||
*
|
public:
|
||||||
* @param json JSON data source
|
std::array<uint8_t, 8> ActionsAllowed{};
|
||||||
* @return A NetworkGroup object
|
uint8_t Id = 0;
|
||||||
* @note json is deliberately left non-const: json_t behaviour changes when const
|
|
||||||
*/
|
|
||||||
static NetworkGroup FromJson(const json_t& json);
|
|
||||||
|
|
||||||
const std::string& GetName() const noexcept;
|
/**
|
||||||
void SetName(std::string_view name);
|
* Creates a NetworkGroup object from a JSON object
|
||||||
|
*
|
||||||
|
* @param json JSON data source
|
||||||
|
* @return A NetworkGroup object
|
||||||
|
* @note json is deliberately left non-const: json_t behaviour changes when const
|
||||||
|
*/
|
||||||
|
static NetworkGroup FromJson(const json_t& json);
|
||||||
|
|
||||||
void Read(NetworkPacket& packet);
|
const std::string& GetName() const noexcept;
|
||||||
void Write(NetworkPacket& packet) const;
|
void SetName(std::string_view name);
|
||||||
void ToggleActionPermission(NetworkPermission index);
|
|
||||||
bool CanPerformAction(NetworkPermission index) const noexcept;
|
|
||||||
bool CanPerformCommand(GameCommand command) const;
|
|
||||||
|
|
||||||
/**
|
void Read(NetworkPacket& packet);
|
||||||
* Serialise a NetworkGroup object into a JSON object
|
void Write(NetworkPacket& packet) const;
|
||||||
*
|
void ToggleActionPermission(NetworkPermission index);
|
||||||
* @return JSON representation of the NetworkGroup object
|
bool CanPerformAction(NetworkPermission index) const noexcept;
|
||||||
*/
|
bool CanPerformCommand(GameCommand command) const;
|
||||||
json_t ToJson() const;
|
|
||||||
|
|
||||||
private:
|
/**
|
||||||
std::string _name;
|
* Serialise a NetworkGroup object into a JSON object
|
||||||
};
|
*
|
||||||
|
* @return JSON representation of the NetworkGroup object
|
||||||
|
*/
|
||||||
|
json_t ToJson() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _name;
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -19,199 +19,200 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
NetworkKey::NetworkKey() = default;
|
|
||||||
NetworkKey::~NetworkKey() = default;
|
|
||||||
|
|
||||||
void NetworkKey::Unload()
|
|
||||||
{
|
{
|
||||||
_key = nullptr;
|
NetworkKey::NetworkKey() = default;
|
||||||
}
|
NetworkKey::~NetworkKey() = default;
|
||||||
|
|
||||||
bool NetworkKey::Generate()
|
void NetworkKey::Unload()
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_key = Crypt::CreateRSAKey();
|
_key = nullptr;
|
||||||
_key->Generate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::Generate failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkKey::LoadPrivate(OpenRCT2::IStream* stream)
|
|
||||||
{
|
|
||||||
Guard::ArgumentNotNull(stream);
|
|
||||||
|
|
||||||
size_t size = static_cast<size_t>(stream->GetLength());
|
|
||||||
if (size == static_cast<size_t>(-1))
|
|
||||||
{
|
|
||||||
LOG_ERROR("unknown size, refusing to load key");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (size > 4 * 1024 * 1024)
|
|
||||||
{
|
|
||||||
LOG_ERROR("Key file suspiciously large, refusing to load it");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string pem(size, '\0');
|
bool NetworkKey::Generate()
|
||||||
stream->Read(pem.data(), pem.size());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_key = Crypt::CreateRSAKey();
|
try
|
||||||
_key->SetPrivate(pem);
|
{
|
||||||
return true;
|
_key = Crypt::CreateRSAKey();
|
||||||
}
|
_key->Generate();
|
||||||
catch (const std::exception& e)
|
return true;
|
||||||
{
|
}
|
||||||
LOG_ERROR("NetworkKey::LoadPrivate failed: %s", e.what());
|
catch (const std::exception& e)
|
||||||
return false;
|
{
|
||||||
}
|
LOG_ERROR("NetworkKey::Generate failed: %s", e.what());
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
bool NetworkKey::LoadPublic(OpenRCT2::IStream* stream)
|
|
||||||
{
|
|
||||||
Guard::ArgumentNotNull(stream);
|
|
||||||
|
|
||||||
size_t size = static_cast<size_t>(stream->GetLength());
|
|
||||||
if (size == static_cast<size_t>(-1))
|
|
||||||
{
|
|
||||||
LOG_ERROR("unknown size, refusing to load key");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (size > 4 * 1024 * 1024)
|
|
||||||
{
|
|
||||||
LOG_ERROR("Key file suspiciously large, refusing to load it");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string pem(size, '\0');
|
bool NetworkKey::LoadPrivate(OpenRCT2::IStream* stream)
|
||||||
stream->Read(pem.data(), pem.size());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_key = Crypt::CreateRSAKey();
|
Guard::ArgumentNotNull(stream);
|
||||||
_key->SetPublic(pem);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::LoadPublic failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkKey::SavePrivate(OpenRCT2::IStream* stream)
|
size_t size = static_cast<size_t>(stream->GetLength());
|
||||||
{
|
if (size == static_cast<size_t>(-1))
|
||||||
try
|
{
|
||||||
|
LOG_ERROR("unknown size, refusing to load key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (size > 4 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Key file suspiciously large, refusing to load it");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string pem(size, '\0');
|
||||||
|
stream->Read(pem.data(), pem.size());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_key = Crypt::CreateRSAKey();
|
||||||
|
_key->SetPrivate(pem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::LoadPrivate failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkKey::LoadPublic(OpenRCT2::IStream* stream)
|
||||||
|
{
|
||||||
|
Guard::ArgumentNotNull(stream);
|
||||||
|
|
||||||
|
size_t size = static_cast<size_t>(stream->GetLength());
|
||||||
|
if (size == static_cast<size_t>(-1))
|
||||||
|
{
|
||||||
|
LOG_ERROR("unknown size, refusing to load key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (size > 4 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Key file suspiciously large, refusing to load it");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string pem(size, '\0');
|
||||||
|
stream->Read(pem.data(), pem.size());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_key = Crypt::CreateRSAKey();
|
||||||
|
_key->SetPublic(pem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::LoadPublic failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkKey::SavePrivate(OpenRCT2::IStream* stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_key == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No key loaded");
|
||||||
|
}
|
||||||
|
auto pem = _key->GetPrivate();
|
||||||
|
stream->Write(pem.data(), pem.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::SavePrivate failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkKey::SavePublic(OpenRCT2::IStream* stream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_key == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No key loaded");
|
||||||
|
}
|
||||||
|
auto pem = _key->GetPublic();
|
||||||
|
stream->Write(pem.data(), pem.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::SavePublic failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetworkKey::PublicKeyString()
|
||||||
{
|
{
|
||||||
if (_key == nullptr)
|
if (_key == nullptr)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("No key loaded");
|
throw std::runtime_error("No key loaded");
|
||||||
}
|
}
|
||||||
auto pem = _key->GetPrivate();
|
return _key->GetPublic();
|
||||||
stream->Write(pem.data(), pem.size());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::SavePrivate failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkKey::SavePublic(OpenRCT2::IStream* stream)
|
/**
|
||||||
{
|
* @brief NetworkKey::PublicKeyHash
|
||||||
try
|
* Computes a short, human-readable (e.g. asciif-ied hex) hash for a given
|
||||||
|
* public key. Serves a purpose of easy identification keys in multiplayer
|
||||||
|
* overview, multiplayer settings.
|
||||||
|
*
|
||||||
|
* In particular, any of digest functions applied to a standardised key
|
||||||
|
* representation, like PEM, will be sufficient.
|
||||||
|
*
|
||||||
|
* @return returns a string containing key hash.
|
||||||
|
*/
|
||||||
|
std::string NetworkKey::PublicKeyHash()
|
||||||
{
|
{
|
||||||
if (_key == nullptr)
|
try
|
||||||
{
|
{
|
||||||
throw std::runtime_error("No key loaded");
|
std::string key = PublicKeyString();
|
||||||
|
if (key.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No key found");
|
||||||
|
}
|
||||||
|
auto hash = Crypt::SHA1(key.c_str(), key.size());
|
||||||
|
return String::StringFromHex(hash);
|
||||||
}
|
}
|
||||||
auto pem = _key->GetPublic();
|
catch (const std::exception& e)
|
||||||
stream->Write(pem.data(), pem.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::SavePublic failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NetworkKey::PublicKeyString()
|
|
||||||
{
|
|
||||||
if (_key == nullptr)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("No key loaded");
|
|
||||||
}
|
|
||||||
return _key->GetPublic();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief NetworkKey::PublicKeyHash
|
|
||||||
* Computes a short, human-readable (e.g. asciif-ied hex) hash for a given
|
|
||||||
* public key. Serves a purpose of easy identification keys in multiplayer
|
|
||||||
* overview, multiplayer settings.
|
|
||||||
*
|
|
||||||
* In particular, any of digest functions applied to a standardised key
|
|
||||||
* representation, like PEM, will be sufficient.
|
|
||||||
*
|
|
||||||
* @return returns a string containing key hash.
|
|
||||||
*/
|
|
||||||
std::string NetworkKey::PublicKeyHash()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::string key = PublicKeyString();
|
|
||||||
if (key.empty())
|
|
||||||
{
|
{
|
||||||
throw std::runtime_error("No key found");
|
LOG_ERROR("Failed to create hash of public key: %s", e.what());
|
||||||
}
|
}
|
||||||
auto hash = Crypt::SHA1(key.c_str(), key.size());
|
return nullptr;
|
||||||
return String::StringFromHex(hash);
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("Failed to create hash of public key: %s", e.what());
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkKey::Sign(const uint8_t* md, const size_t len, std::vector<uint8_t>& signature) const
|
bool NetworkKey::Sign(const uint8_t* md, const size_t len, std::vector<uint8_t>& signature) const
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
auto rsa = Crypt::CreateRSA();
|
try
|
||||||
signature = rsa->SignData(*_key, md, len);
|
{
|
||||||
return true;
|
auto rsa = Crypt::CreateRSA();
|
||||||
|
signature = rsa->SignData(*_key, md, len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::Sign failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::Sign failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkKey::Verify(const uint8_t* md, const size_t len, const std::vector<uint8_t>& signature) const
|
bool NetworkKey::Verify(const uint8_t* md, const size_t len, const std::vector<uint8_t>& signature) const
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
auto rsa = Crypt::CreateRSA();
|
try
|
||||||
return rsa->VerifyData(*_key, md, len, signature.data(), signature.size());
|
{
|
||||||
|
auto rsa = Crypt::CreateRSA();
|
||||||
|
return rsa->VerifyData(*_key, md, len, signature.data(), signature.size());
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("NetworkKey::Verify failed: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
} // namespace OpenRCT2::Network
|
||||||
{
|
|
||||||
LOG_ERROR("NetworkKey::Verify failed: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -25,25 +25,28 @@ namespace OpenRCT2::Crypt
|
|||||||
class RsaKey;
|
class RsaKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkKey final
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkKey final
|
||||||
NetworkKey();
|
{
|
||||||
~NetworkKey();
|
public:
|
||||||
bool Generate();
|
NetworkKey();
|
||||||
bool LoadPrivate(OpenRCT2::IStream* stream);
|
~NetworkKey();
|
||||||
bool LoadPublic(OpenRCT2::IStream* stream);
|
bool Generate();
|
||||||
bool SavePrivate(OpenRCT2::IStream* stream);
|
bool LoadPrivate(OpenRCT2::IStream* stream);
|
||||||
bool SavePublic(OpenRCT2::IStream* stream);
|
bool LoadPublic(OpenRCT2::IStream* stream);
|
||||||
std::string PublicKeyString();
|
bool SavePrivate(OpenRCT2::IStream* stream);
|
||||||
std::string PublicKeyHash();
|
bool SavePublic(OpenRCT2::IStream* stream);
|
||||||
void Unload();
|
std::string PublicKeyString();
|
||||||
bool Sign(const uint8_t* md, const size_t len, std::vector<uint8_t>& signature) const;
|
std::string PublicKeyHash();
|
||||||
bool Verify(const uint8_t* md, const size_t len, const std::vector<uint8_t>& signature) const;
|
void Unload();
|
||||||
|
bool Sign(const uint8_t* md, const size_t len, std::vector<uint8_t>& signature) const;
|
||||||
|
bool Verify(const uint8_t* md, const size_t len, const std::vector<uint8_t>& signature) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NetworkKey(const NetworkKey&) = delete;
|
NetworkKey(const NetworkKey&) = delete;
|
||||||
std::unique_ptr<OpenRCT2::Crypt::RsaKey> _key;
|
std::unique_ptr<OpenRCT2::Crypt::RsaKey> _key;
|
||||||
};
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -15,97 +15,100 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
NetworkPacket::NetworkPacket(NetworkCommand id) noexcept
|
namespace OpenRCT2::Network
|
||||||
: Header{ 0, id }
|
|
||||||
{
|
{
|
||||||
}
|
NetworkPacket::NetworkPacket(NetworkCommand id) noexcept
|
||||||
|
: Header{ 0, id }
|
||||||
uint8_t* NetworkPacket::GetData() noexcept
|
|
||||||
{
|
|
||||||
return Data.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* NetworkPacket::GetData() const noexcept
|
|
||||||
{
|
|
||||||
return Data.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkCommand NetworkPacket::GetCommand() const noexcept
|
|
||||||
{
|
|
||||||
return Header.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkPacket::Clear() noexcept
|
|
||||||
{
|
|
||||||
BytesTransferred = 0;
|
|
||||||
BytesRead = 0;
|
|
||||||
Data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkPacket::CommandRequiresAuth() const noexcept
|
|
||||||
{
|
|
||||||
switch (GetCommand())
|
|
||||||
{
|
{
|
||||||
case NetworkCommand::Ping:
|
|
||||||
case NetworkCommand::Auth:
|
|
||||||
case NetworkCommand::Token:
|
|
||||||
case NetworkCommand::GameInfo:
|
|
||||||
case NetworkCommand::ObjectsList:
|
|
||||||
case NetworkCommand::ScriptsHeader:
|
|
||||||
case NetworkCommand::ScriptsData:
|
|
||||||
case NetworkCommand::MapRequest:
|
|
||||||
case NetworkCommand::Heartbeat:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkPacket::Write(const void* bytes, size_t size)
|
|
||||||
{
|
|
||||||
const uint8_t* src = reinterpret_cast<const uint8_t*>(bytes);
|
|
||||||
Data.insert(Data.end(), src, src + size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkPacket::WriteString(std::string_view s)
|
|
||||||
{
|
|
||||||
Write(s.data(), s.size());
|
|
||||||
Data.push_back(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* NetworkPacket::Read(size_t size)
|
|
||||||
{
|
|
||||||
if (BytesRead + size > Data.size())
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t* data = Data.data() + BytesRead;
|
uint8_t* NetworkPacket::GetData() noexcept
|
||||||
BytesRead += size;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view NetworkPacket::ReadString()
|
|
||||||
{
|
|
||||||
if (BytesRead >= Data.size())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
const char* str = reinterpret_cast<const char*>(Data.data() + BytesRead);
|
|
||||||
|
|
||||||
size_t stringLen = 0;
|
|
||||||
while (BytesRead < Data.size() && str[stringLen] != '\0')
|
|
||||||
{
|
{
|
||||||
|
return Data.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* NetworkPacket::GetData() const noexcept
|
||||||
|
{
|
||||||
|
return Data.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkCommand NetworkPacket::GetCommand() const noexcept
|
||||||
|
{
|
||||||
|
return Header.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkPacket::Clear() noexcept
|
||||||
|
{
|
||||||
|
BytesTransferred = 0;
|
||||||
|
BytesRead = 0;
|
||||||
|
Data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkPacket::CommandRequiresAuth() const noexcept
|
||||||
|
{
|
||||||
|
switch (GetCommand())
|
||||||
|
{
|
||||||
|
case NetworkCommand::Ping:
|
||||||
|
case NetworkCommand::Auth:
|
||||||
|
case NetworkCommand::Token:
|
||||||
|
case NetworkCommand::GameInfo:
|
||||||
|
case NetworkCommand::ObjectsList:
|
||||||
|
case NetworkCommand::ScriptsHeader:
|
||||||
|
case NetworkCommand::ScriptsData:
|
||||||
|
case NetworkCommand::MapRequest:
|
||||||
|
case NetworkCommand::Heartbeat:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkPacket::Write(const void* bytes, size_t size)
|
||||||
|
{
|
||||||
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(bytes);
|
||||||
|
Data.insert(Data.end(), src, src + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkPacket::WriteString(std::string_view s)
|
||||||
|
{
|
||||||
|
Write(s.data(), s.size());
|
||||||
|
Data.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* NetworkPacket::Read(size_t size)
|
||||||
|
{
|
||||||
|
if (BytesRead + size > Data.size())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* data = Data.data() + BytesRead;
|
||||||
|
BytesRead += size;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view NetworkPacket::ReadString()
|
||||||
|
{
|
||||||
|
if (BytesRead >= Data.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const char* str = reinterpret_cast<const char*>(Data.data() + BytesRead);
|
||||||
|
|
||||||
|
size_t stringLen = 0;
|
||||||
|
while (BytesRead < Data.size() && str[stringLen] != '\0')
|
||||||
|
{
|
||||||
|
BytesRead++;
|
||||||
|
stringLen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str[stringLen] != '\0')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Skip null terminator.
|
||||||
BytesRead++;
|
BytesRead++;
|
||||||
stringLen++;
|
|
||||||
|
return std::string_view(str, stringLen);
|
||||||
}
|
}
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
if (str[stringLen] != '\0')
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// Skip null terminator.
|
|
||||||
BytesRead++;
|
|
||||||
|
|
||||||
return std::string_view(str, stringLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,68 +16,71 @@
|
|||||||
#include <sfl/small_vector.hpp>
|
#include <sfl/small_vector.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
namespace OpenRCT2::Network
|
||||||
struct PacketHeader
|
|
||||||
{
|
{
|
||||||
uint16_t Size = 0;
|
#pragma pack(push, 1)
|
||||||
NetworkCommand Id = NetworkCommand::Invalid;
|
struct PacketHeader
|
||||||
};
|
{
|
||||||
static_assert(sizeof(PacketHeader) == 6);
|
uint16_t Size = 0;
|
||||||
|
NetworkCommand Id = NetworkCommand::Invalid;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(PacketHeader) == 6);
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
struct NetworkPacket final
|
struct NetworkPacket final
|
||||||
{
|
|
||||||
NetworkPacket() noexcept = default;
|
|
||||||
NetworkPacket(NetworkCommand id) noexcept;
|
|
||||||
|
|
||||||
uint8_t* GetData() noexcept;
|
|
||||||
const uint8_t* GetData() const noexcept;
|
|
||||||
|
|
||||||
NetworkCommand GetCommand() const noexcept;
|
|
||||||
|
|
||||||
void Clear() noexcept;
|
|
||||||
bool CommandRequiresAuth() const noexcept;
|
|
||||||
|
|
||||||
const uint8_t* Read(size_t size);
|
|
||||||
std::string_view ReadString();
|
|
||||||
|
|
||||||
void Write(const void* bytes, size_t size);
|
|
||||||
void WriteString(std::string_view s);
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
NetworkPacket& operator>>(T& value)
|
|
||||||
{
|
{
|
||||||
if (BytesRead + sizeof(value) > Header.Size)
|
NetworkPacket() noexcept = default;
|
||||||
|
NetworkPacket(NetworkCommand id) noexcept;
|
||||||
|
|
||||||
|
uint8_t* GetData() noexcept;
|
||||||
|
const uint8_t* GetData() const noexcept;
|
||||||
|
|
||||||
|
NetworkCommand GetCommand() const noexcept;
|
||||||
|
|
||||||
|
void Clear() noexcept;
|
||||||
|
bool CommandRequiresAuth() const noexcept;
|
||||||
|
|
||||||
|
const uint8_t* Read(size_t size);
|
||||||
|
std::string_view ReadString();
|
||||||
|
|
||||||
|
void Write(const void* bytes, size_t size);
|
||||||
|
void WriteString(std::string_view s);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
NetworkPacket& operator>>(T& value)
|
||||||
{
|
{
|
||||||
value = T{};
|
if (BytesRead + sizeof(value) > Header.Size)
|
||||||
|
{
|
||||||
|
value = T{};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
T local;
|
||||||
|
std::memcpy(&local, &GetData()[BytesRead], sizeof(local));
|
||||||
|
value = ByteSwapBE(local);
|
||||||
|
BytesRead += sizeof(value);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
template<typename T>
|
||||||
|
NetworkPacket& operator<<(T value)
|
||||||
{
|
{
|
||||||
T local;
|
T swapped = ByteSwapBE(value);
|
||||||
std::memcpy(&local, &GetData()[BytesRead], sizeof(local));
|
Write(&swapped, sizeof(T));
|
||||||
value = ByteSwapBE(local);
|
return *this;
|
||||||
BytesRead += sizeof(value);
|
|
||||||
}
|
}
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
NetworkPacket& operator<<(DataSerialiser& data)
|
||||||
NetworkPacket& operator<<(T value)
|
{
|
||||||
{
|
Write(static_cast<const uint8_t*>(data.GetStream().GetData()), data.GetStream().GetLength());
|
||||||
T swapped = ByteSwapBE(value);
|
return *this;
|
||||||
Write(&swapped, sizeof(T));
|
}
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPacket& operator<<(DataSerialiser& data)
|
public:
|
||||||
{
|
PacketHeader Header{};
|
||||||
Write(static_cast<const uint8_t*>(data.GetStream().GetData()), data.GetStream().GetLength());
|
sfl::small_vector<uint8_t, 512> Data;
|
||||||
return *this;
|
size_t BytesTransferred = 0;
|
||||||
}
|
size_t BytesRead = 0;
|
||||||
|
};
|
||||||
public:
|
} // namespace OpenRCT2::Network
|
||||||
PacketHeader Header{};
|
|
||||||
sfl::small_vector<uint8_t, 512> Data;
|
|
||||||
size_t BytesTransferred = 0;
|
|
||||||
size_t BytesRead = 0;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -15,39 +15,42 @@
|
|||||||
#include "../ui/WindowManager.h"
|
#include "../ui/WindowManager.h"
|
||||||
#include "NetworkPacket.h"
|
#include "NetworkPacket.h"
|
||||||
|
|
||||||
void NetworkPlayer::SetName(std::string_view name)
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
// 36 == 31 + strlen(" #255");
|
void NetworkPlayer::SetName(std::string_view name)
|
||||||
Name = name.substr(0, 36);
|
{
|
||||||
}
|
// 36 == 31 + strlen(" #255");
|
||||||
|
Name = name.substr(0, 36);
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkPlayer::Read(NetworkPacket& packet)
|
void NetworkPlayer::Read(NetworkPacket& packet)
|
||||||
{
|
{
|
||||||
auto name = packet.ReadString();
|
auto name = packet.ReadString();
|
||||||
SetName(name);
|
SetName(name);
|
||||||
packet >> Id >> Flags >> Group >> LastAction >> LastActionCoord.x >> LastActionCoord.y >> LastActionCoord.z >> MoneySpent
|
packet >> Id >> Flags >> Group >> LastAction >> LastActionCoord.x >> LastActionCoord.y >> LastActionCoord.z
|
||||||
>> CommandsRan;
|
>> MoneySpent >> CommandsRan;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkPlayer::Write(NetworkPacket& packet)
|
void NetworkPlayer::Write(NetworkPacket& packet)
|
||||||
{
|
{
|
||||||
packet.WriteString(Name);
|
packet.WriteString(Name);
|
||||||
packet << Id << Flags << Group << LastAction << LastActionCoord.x << LastActionCoord.y << LastActionCoord.z << MoneySpent
|
packet << Id << Flags << Group << LastAction << LastActionCoord.x << LastActionCoord.y << LastActionCoord.z
|
||||||
<< CommandsRan;
|
<< MoneySpent << CommandsRan;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkPlayer::IncrementNumCommands()
|
void NetworkPlayer::IncrementNumCommands()
|
||||||
{
|
{
|
||||||
CommandsRan++;
|
CommandsRan++;
|
||||||
auto* windowMgr = OpenRCT2::Ui::GetWindowManager();
|
auto* windowMgr = OpenRCT2::Ui::GetWindowManager();
|
||||||
windowMgr->InvalidateByNumber(WindowClass::Player, Id);
|
windowMgr->InvalidateByNumber(WindowClass::Player, Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkPlayer::AddMoneySpent(money64 cost)
|
void NetworkPlayer::AddMoneySpent(money64 cost)
|
||||||
{
|
{
|
||||||
MoneySpent += cost;
|
MoneySpent += cost;
|
||||||
auto* windowMgr = OpenRCT2::Ui::GetWindowManager();
|
auto* windowMgr = OpenRCT2::Ui::GetWindowManager();
|
||||||
windowMgr->InvalidateByNumber(WindowClass::Player, Id);
|
windowMgr->InvalidateByNumber(WindowClass::Player, Id);
|
||||||
}
|
}
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -17,34 +17,38 @@
|
|||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
struct NetworkPacket;
|
|
||||||
struct Peep;
|
struct Peep;
|
||||||
|
|
||||||
class NetworkPlayer final
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
struct NetworkPacket;
|
||||||
uint8_t Id = 0;
|
|
||||||
std::string Name;
|
|
||||||
uint16_t Ping = 0;
|
|
||||||
uint8_t Flags = 0;
|
|
||||||
uint8_t Group = 0;
|
|
||||||
money64 MoneySpent = 0.00_GBP;
|
|
||||||
uint32_t CommandsRan = 0;
|
|
||||||
int32_t LastAction = -999;
|
|
||||||
uint32_t LastActionTime = 0;
|
|
||||||
CoordsXYZ LastActionCoord = {};
|
|
||||||
Peep* PickupPeep = nullptr;
|
|
||||||
int32_t PickupPeepOldX = kLocationNull;
|
|
||||||
std::string KeyHash;
|
|
||||||
uint32_t LastDemolishRideTime = 0;
|
|
||||||
uint32_t LastPlaceSceneryTime = 0;
|
|
||||||
std::unordered_map<GameCommand, int32_t> CooldownTime;
|
|
||||||
NetworkPlayer() noexcept = default;
|
|
||||||
|
|
||||||
void SetName(std::string_view name);
|
class NetworkPlayer final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
uint8_t Id = 0;
|
||||||
|
std::string Name;
|
||||||
|
uint16_t Ping = 0;
|
||||||
|
uint8_t Flags = 0;
|
||||||
|
uint8_t Group = 0;
|
||||||
|
money64 MoneySpent = 0.00_GBP;
|
||||||
|
uint32_t CommandsRan = 0;
|
||||||
|
int32_t LastAction = -999;
|
||||||
|
uint32_t LastActionTime = 0;
|
||||||
|
CoordsXYZ LastActionCoord = {};
|
||||||
|
Peep* PickupPeep = nullptr;
|
||||||
|
int32_t PickupPeepOldX = kLocationNull;
|
||||||
|
std::string KeyHash;
|
||||||
|
uint32_t LastDemolishRideTime = 0;
|
||||||
|
uint32_t LastPlaceSceneryTime = 0;
|
||||||
|
std::unordered_map<GameCommand, int32_t> CooldownTime;
|
||||||
|
NetworkPlayer() noexcept = default;
|
||||||
|
|
||||||
void Read(NetworkPacket& packet);
|
void SetName(std::string_view name);
|
||||||
void Write(NetworkPacket& packet);
|
|
||||||
void IncrementNumCommands();
|
void Read(NetworkPacket& packet);
|
||||||
void AddMoneySpent(money64 cost);
|
void Write(NetworkPacket& packet);
|
||||||
};
|
void IncrementNumCommands();
|
||||||
|
void AddMoneySpent(money64 cost);
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
#ifndef DISABLE_NETWORK
|
#ifndef DISABLE_NETWORK
|
||||||
|
|
||||||
class NetworkServer final : public NetworkBase
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkServer final : public NetworkBase
|
||||||
};
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -34,330 +34,331 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
enum class MasterServerStatus
|
|
||||||
{
|
{
|
||||||
Ok = 200,
|
enum class MasterServerStatus
|
||||||
InvalidToken = 401,
|
{
|
||||||
ServerNotFound = 404,
|
Ok = 200,
|
||||||
InternalError = 500
|
InvalidToken = 401,
|
||||||
};
|
ServerNotFound = 404,
|
||||||
|
InternalError = 500
|
||||||
|
};
|
||||||
|
|
||||||
#ifndef DISABLE_HTTP
|
#ifndef DISABLE_HTTP
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(2min).count();
|
constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(2min).count();
|
||||||
constexpr int32_t kMasterServerHeartbeatTime = std::chrono::milliseconds(1min).count();
|
constexpr int32_t kMasterServerHeartbeatTime = std::chrono::milliseconds(1min).count();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class NetworkServerAdvertiser final : public INetworkServerAdvertiser
|
class NetworkServerAdvertiser final : public INetworkServerAdvertiser
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
uint16_t _port;
|
uint16_t _port;
|
||||||
|
|
||||||
std::unique_ptr<IUdpSocket> _lanListener;
|
std::unique_ptr<IUdpSocket> _lanListener;
|
||||||
uint32_t _lastListenTime{};
|
uint32_t _lastListenTime{};
|
||||||
|
|
||||||
AdvertiseStatus _status = AdvertiseStatus::unregistered;
|
AdvertiseStatus _status = AdvertiseStatus::unregistered;
|
||||||
|
|
||||||
#ifndef DISABLE_HTTP
|
#ifndef DISABLE_HTTP
|
||||||
uint32_t _lastAdvertiseTime = 0;
|
uint32_t _lastAdvertiseTime = 0;
|
||||||
uint32_t _lastHeartbeatTime = 0;
|
uint32_t _lastHeartbeatTime = 0;
|
||||||
|
|
||||||
// Our unique token for this server
|
// Our unique token for this server
|
||||||
std::string _token;
|
std::string _token;
|
||||||
|
|
||||||
// Key received from the master server
|
// Key received from the master server
|
||||||
std::string _key;
|
std::string _key;
|
||||||
|
|
||||||
// See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953
|
// See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953
|
||||||
bool _forceIPv4 = false;
|
bool _forceIPv4 = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NetworkServerAdvertiser(uint16_t port)
|
explicit NetworkServerAdvertiser(uint16_t port)
|
||||||
{
|
|
||||||
_port = port;
|
|
||||||
_lanListener = CreateUdpSocket();
|
|
||||||
#ifndef DISABLE_HTTP
|
|
||||||
_key = GenerateAdvertiseKey();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
AdvertiseStatus GetStatus() const override
|
|
||||||
{
|
|
||||||
return _status;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update() override
|
|
||||||
{
|
|
||||||
UpdateLAN();
|
|
||||||
#ifndef DISABLE_HTTP
|
|
||||||
if (Config::Get().network.Advertise)
|
|
||||||
{
|
{
|
||||||
UpdateWAN();
|
_port = port;
|
||||||
|
_lanListener = CreateUdpSocket();
|
||||||
|
#ifndef DISABLE_HTTP
|
||||||
|
_key = GenerateAdvertiseKey();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
AdvertiseStatus GetStatus() const override
|
||||||
void UpdateLAN()
|
|
||||||
{
|
|
||||||
auto ticks = Platform::GetTicks();
|
|
||||||
if (ticks > _lastListenTime + 500)
|
|
||||||
{
|
{
|
||||||
if (_lanListener->GetStatus() != SocketStatus::Listening)
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update() override
|
||||||
|
{
|
||||||
|
UpdateLAN();
|
||||||
|
#ifndef DISABLE_HTTP
|
||||||
|
if (Config::Get().network.Advertise)
|
||||||
{
|
{
|
||||||
_lanListener->Listen(kNetworkLanBroadcastPort);
|
UpdateWAN();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateLAN()
|
||||||
|
{
|
||||||
|
auto ticks = Platform::GetTicks();
|
||||||
|
if (ticks > _lastListenTime + 500)
|
||||||
|
{
|
||||||
|
if (_lanListener->GetStatus() != SocketStatus::Listening)
|
||||||
|
{
|
||||||
|
_lanListener->Listen(kNetworkLanBroadcastPort);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char buffer[256]{};
|
||||||
|
size_t recievedBytes{};
|
||||||
|
std::unique_ptr<INetworkEndpoint> endpoint;
|
||||||
|
auto p = _lanListener->ReceiveData(buffer, sizeof(buffer) - 1, &recievedBytes, &endpoint);
|
||||||
|
if (p == NetworkReadPacket::Success)
|
||||||
|
{
|
||||||
|
std::string sender = endpoint->GetHostname();
|
||||||
|
LOG_VERBOSE("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str());
|
||||||
|
if (String::equals(buffer, kNetworkLanBroadcastMsg))
|
||||||
|
{
|
||||||
|
auto body = GetBroadcastJson();
|
||||||
|
auto bodyDump = body.dump();
|
||||||
|
size_t sendLen = bodyDump.size() + 1;
|
||||||
|
LOG_VERBOSE("Sending %zu bytes back to %s", sendLen, sender.c_str());
|
||||||
|
_lanListener->SendData(*endpoint, bodyDump.c_str(), sendLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lastListenTime = ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t GetBroadcastJson()
|
||||||
|
{
|
||||||
|
json_t root = NetworkGetServerInfoAsJson();
|
||||||
|
root["port"] = _port;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_HTTP
|
||||||
|
void UpdateWAN()
|
||||||
|
{
|
||||||
|
switch (_status)
|
||||||
|
{
|
||||||
|
case AdvertiseStatus::unregistered:
|
||||||
|
if (_lastAdvertiseTime == 0 || Platform::GetTicks() > _lastAdvertiseTime + kMasterServerRegisterTime)
|
||||||
|
{
|
||||||
|
if (_lastAdvertiseTime == 0)
|
||||||
|
{
|
||||||
|
Console::WriteLine("Registering server on master server");
|
||||||
|
}
|
||||||
|
SendRegistration(_forceIPv4);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AdvertiseStatus::registered:
|
||||||
|
if (Platform::GetTicks() > _lastHeartbeatTime + kMasterServerHeartbeatTime)
|
||||||
|
{
|
||||||
|
SendHeartbeat();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// exhaust enum values to satisfy clang
|
||||||
|
case AdvertiseStatus::disabled:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendRegistration(bool forceIPv4)
|
||||||
|
{
|
||||||
|
_lastAdvertiseTime = Platform::GetTicks();
|
||||||
|
|
||||||
|
// Send the registration request
|
||||||
|
Http::Request request;
|
||||||
|
request.url = GetMasterServerUrl();
|
||||||
|
request.method = Http::Method::POST;
|
||||||
|
request.forceIPv4 = forceIPv4;
|
||||||
|
|
||||||
|
json_t body = {
|
||||||
|
{ "key", _key },
|
||||||
|
{ "port", _port },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!Config::Get().network.AdvertiseAddress.empty())
|
||||||
|
{
|
||||||
|
body["address"] = Config::Get().network.AdvertiseAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.body = body.dump();
|
||||||
|
request.header["Content-Type"] = "application/json";
|
||||||
|
|
||||||
|
Http::DoAsync(request, [&](Http::Response response) -> void {
|
||||||
|
if (response.status != Http::Status::Ok)
|
||||||
|
{
|
||||||
|
Console::Error::WriteLine("Unable to connect to master server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t root = Json::FromString(response.body);
|
||||||
|
root = Json::AsObject(root);
|
||||||
|
this->OnRegistrationResponse(root);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendHeartbeat()
|
||||||
|
{
|
||||||
|
Http::Request request;
|
||||||
|
request.url = GetMasterServerUrl();
|
||||||
|
request.method = Http::Method::PUT;
|
||||||
|
|
||||||
|
json_t body = GetHeartbeatJson();
|
||||||
|
request.body = body.dump();
|
||||||
|
request.header["Content-Type"] = "application/json";
|
||||||
|
|
||||||
|
_lastHeartbeatTime = Platform::GetTicks();
|
||||||
|
Http::DoAsync(request, [&](Http::Response response) -> void {
|
||||||
|
if (response.status != Http::Status::Ok)
|
||||||
|
{
|
||||||
|
Console::Error::WriteLine("Unable to connect to master server");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t root = Json::FromString(response.body);
|
||||||
|
root = Json::AsObject(root);
|
||||||
|
this->OnHeartbeatResponse(root);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param jsonRoot must be of JSON type object or null
|
||||||
|
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
|
||||||
|
*/
|
||||||
|
void OnRegistrationResponse(json_t& jsonRoot)
|
||||||
|
{
|
||||||
|
Guard::Assert(jsonRoot.is_object(), "OnRegistrationResponse expects parameter jsonRoot to be object");
|
||||||
|
|
||||||
|
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
|
||||||
|
|
||||||
|
if (status == MasterServerStatus::Ok)
|
||||||
|
{
|
||||||
|
Console::WriteLine("Server successfully registered on master server");
|
||||||
|
json_t jsonToken = jsonRoot["token"];
|
||||||
|
if (jsonToken.is_string())
|
||||||
|
{
|
||||||
|
_token = Json::GetString(jsonToken);
|
||||||
|
_status = AdvertiseStatus::registered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char buffer[256]{};
|
std::string message = Json::GetString(jsonRoot["message"]);
|
||||||
size_t recievedBytes{};
|
if (message.empty())
|
||||||
std::unique_ptr<INetworkEndpoint> endpoint;
|
|
||||||
auto p = _lanListener->ReceiveData(buffer, sizeof(buffer) - 1, &recievedBytes, &endpoint);
|
|
||||||
if (p == NetworkReadPacket::Success)
|
|
||||||
{
|
{
|
||||||
std::string sender = endpoint->GetHostname();
|
message = "Invalid response from server";
|
||||||
LOG_VERBOSE("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str());
|
}
|
||||||
if (String::equals(buffer, kNetworkLanBroadcastMsg))
|
Console::Error::WriteLine(
|
||||||
{
|
"Unable to advertise (%d): %s\n * Check that you have port forwarded %u\n * Try setting "
|
||||||
auto body = GetBroadcastJson();
|
"advertise_address in config.ini",
|
||||||
auto bodyDump = body.dump();
|
status, message.c_str(), _port);
|
||||||
size_t sendLen = bodyDump.size() + 1;
|
|
||||||
LOG_VERBOSE("Sending %zu bytes back to %s", sendLen, sender.c_str());
|
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
|
||||||
_lanListener->SendData(*endpoint, bodyDump.c_str(), sendLen);
|
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
|
||||||
}
|
// don't wait the full timeout.
|
||||||
|
if (!_forceIPv4 && status == MasterServerStatus::InternalError)
|
||||||
|
{
|
||||||
|
_forceIPv4 = true;
|
||||||
|
_lastAdvertiseTime = 0;
|
||||||
|
LOG_INFO("Forcing HTTP(S) over IPv4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lastListenTime = ticks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t GetBroadcastJson()
|
|
||||||
{
|
|
||||||
json_t root = NetworkGetServerInfoAsJson();
|
|
||||||
root["port"] = _port;
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef DISABLE_HTTP
|
|
||||||
void UpdateWAN()
|
|
||||||
{
|
|
||||||
switch (_status)
|
|
||||||
{
|
|
||||||
case AdvertiseStatus::unregistered:
|
|
||||||
if (_lastAdvertiseTime == 0 || Platform::GetTicks() > _lastAdvertiseTime + kMasterServerRegisterTime)
|
|
||||||
{
|
|
||||||
if (_lastAdvertiseTime == 0)
|
|
||||||
{
|
|
||||||
Console::WriteLine("Registering server on master server");
|
|
||||||
}
|
|
||||||
SendRegistration(_forceIPv4);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AdvertiseStatus::registered:
|
|
||||||
if (Platform::GetTicks() > _lastHeartbeatTime + kMasterServerHeartbeatTime)
|
|
||||||
{
|
|
||||||
SendHeartbeat();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// exhaust enum values to satisfy clang
|
|
||||||
case AdvertiseStatus::disabled:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendRegistration(bool forceIPv4)
|
|
||||||
{
|
|
||||||
_lastAdvertiseTime = Platform::GetTicks();
|
|
||||||
|
|
||||||
// Send the registration request
|
|
||||||
Http::Request request;
|
|
||||||
request.url = GetMasterServerUrl();
|
|
||||||
request.method = Http::Method::POST;
|
|
||||||
request.forceIPv4 = forceIPv4;
|
|
||||||
|
|
||||||
json_t body = {
|
|
||||||
{ "key", _key },
|
|
||||||
{ "port", _port },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!Config::Get().network.AdvertiseAddress.empty())
|
|
||||||
{
|
|
||||||
body["address"] = Config::Get().network.AdvertiseAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.body = body.dump();
|
/**
|
||||||
request.header["Content-Type"] = "application/json";
|
* @param jsonRoot must be of JSON type object or null
|
||||||
|
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
|
||||||
Http::DoAsync(request, [&](Http::Response response) -> void {
|
*/
|
||||||
if (response.status != Http::Status::Ok)
|
void OnHeartbeatResponse(json_t& jsonRoot)
|
||||||
{
|
|
||||||
Console::Error::WriteLine("Unable to connect to master server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t root = Json::FromString(response.body);
|
|
||||||
root = Json::AsObject(root);
|
|
||||||
this->OnRegistrationResponse(root);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendHeartbeat()
|
|
||||||
{
|
|
||||||
Http::Request request;
|
|
||||||
request.url = GetMasterServerUrl();
|
|
||||||
request.method = Http::Method::PUT;
|
|
||||||
|
|
||||||
json_t body = GetHeartbeatJson();
|
|
||||||
request.body = body.dump();
|
|
||||||
request.header["Content-Type"] = "application/json";
|
|
||||||
|
|
||||||
_lastHeartbeatTime = Platform::GetTicks();
|
|
||||||
Http::DoAsync(request, [&](Http::Response response) -> void {
|
|
||||||
if (response.status != Http::Status::Ok)
|
|
||||||
{
|
|
||||||
Console::Error::WriteLine("Unable to connect to master server");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t root = Json::FromString(response.body);
|
|
||||||
root = Json::AsObject(root);
|
|
||||||
this->OnHeartbeatResponse(root);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param jsonRoot must be of JSON type object or null
|
|
||||||
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
|
|
||||||
*/
|
|
||||||
void OnRegistrationResponse(json_t& jsonRoot)
|
|
||||||
{
|
|
||||||
Guard::Assert(jsonRoot.is_object(), "OnRegistrationResponse expects parameter jsonRoot to be object");
|
|
||||||
|
|
||||||
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
|
|
||||||
|
|
||||||
if (status == MasterServerStatus::Ok)
|
|
||||||
{
|
{
|
||||||
Console::WriteLine("Server successfully registered on master server");
|
Guard::Assert(jsonRoot.is_object(), "OnHeartbeatResponse expects parameter jsonRoot to be object");
|
||||||
json_t jsonToken = jsonRoot["token"];
|
|
||||||
if (jsonToken.is_string())
|
|
||||||
{
|
|
||||||
_token = Json::GetString(jsonToken);
|
|
||||||
_status = AdvertiseStatus::registered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string message = Json::GetString(jsonRoot["message"]);
|
|
||||||
if (message.empty())
|
|
||||||
{
|
|
||||||
message = "Invalid response from server";
|
|
||||||
}
|
|
||||||
Console::Error::WriteLine(
|
|
||||||
"Unable to advertise (%d): %s\n * Check that you have port forwarded %u\n * Try setting "
|
|
||||||
"advertise_address in config.ini",
|
|
||||||
status, message.c_str(), _port);
|
|
||||||
|
|
||||||
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
|
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
|
||||||
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
|
if (status == MasterServerStatus::Ok)
|
||||||
// don't wait the full timeout.
|
|
||||||
if (!_forceIPv4 && status == MasterServerStatus::InternalError)
|
|
||||||
{
|
{
|
||||||
_forceIPv4 = true;
|
// Master server has successfully updated our server status
|
||||||
|
}
|
||||||
|
else if (status == MasterServerStatus::InvalidToken)
|
||||||
|
{
|
||||||
|
_status = AdvertiseStatus::unregistered;
|
||||||
_lastAdvertiseTime = 0;
|
_lastAdvertiseTime = 0;
|
||||||
LOG_INFO("Forcing HTTP(S) over IPv4");
|
Console::Error::WriteLine("Master server heartbeat failed: Invalid Token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
json_t GetHeartbeatJson()
|
||||||
* @param jsonRoot must be of JSON type object or null
|
|
||||||
* @note jsonRoot is deliberately left non-const: json_t behaviour changes when const
|
|
||||||
*/
|
|
||||||
void OnHeartbeatResponse(json_t& jsonRoot)
|
|
||||||
{
|
|
||||||
Guard::Assert(jsonRoot.is_object(), "OnHeartbeatResponse expects parameter jsonRoot to be object");
|
|
||||||
|
|
||||||
auto status = Json::GetEnum<MasterServerStatus>(jsonRoot["status"], MasterServerStatus::InternalError);
|
|
||||||
if (status == MasterServerStatus::Ok)
|
|
||||||
{
|
{
|
||||||
// Master server has successfully updated our server status
|
uint32_t numPlayers = NetworkGetNumVisiblePlayers();
|
||||||
}
|
|
||||||
else if (status == MasterServerStatus::InvalidToken)
|
|
||||||
{
|
|
||||||
_status = AdvertiseStatus::unregistered;
|
|
||||||
_lastAdvertiseTime = 0;
|
|
||||||
Console::Error::WriteLine("Master server heartbeat failed: Invalid Token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t GetHeartbeatJson()
|
json_t root = {
|
||||||
{
|
{ "token", _token },
|
||||||
uint32_t numPlayers = NetworkGetNumVisiblePlayers();
|
{ "players", numPlayers },
|
||||||
|
};
|
||||||
|
|
||||||
json_t root = {
|
const auto& gameState = getGameState();
|
||||||
{ "token", _token },
|
const auto& date = GetDate();
|
||||||
{ "players", numPlayers },
|
json_t mapSize = { { "x", gameState.mapSize.x - 2 }, { "y", gameState.mapSize.y - 2 } };
|
||||||
};
|
json_t gameInfo = {
|
||||||
|
{ "mapSize", mapSize },
|
||||||
|
{ "day", date.GetMonthTicks() },
|
||||||
|
{ "month", date.GetMonthsElapsed() },
|
||||||
|
{ "guests", gameState.park.numGuestsInPark },
|
||||||
|
{ "parkValue", gameState.park.value },
|
||||||
|
};
|
||||||
|
|
||||||
const auto& gameState = getGameState();
|
if (!(gameState.park.flags & PARK_FLAGS_NO_MONEY))
|
||||||
const auto& date = GetDate();
|
{
|
||||||
json_t mapSize = { { "x", gameState.mapSize.x - 2 }, { "y", gameState.mapSize.y - 2 } };
|
gameInfo["cash"] = gameState.park.cash;
|
||||||
json_t gameInfo = {
|
}
|
||||||
{ "mapSize", mapSize },
|
|
||||||
{ "day", date.GetMonthTicks() },
|
|
||||||
{ "month", date.GetMonthsElapsed() },
|
|
||||||
{ "guests", gameState.park.numGuestsInPark },
|
|
||||||
{ "parkValue", gameState.park.value },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!(gameState.park.flags & PARK_FLAGS_NO_MONEY))
|
root["gameInfo"] = gameInfo;
|
||||||
{
|
|
||||||
gameInfo["cash"] = gameState.park.cash;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
root["gameInfo"] = gameInfo;
|
static std::string GenerateAdvertiseKey()
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string GenerateAdvertiseKey()
|
|
||||||
{
|
|
||||||
// Generate a string of 16 random hex characters (64-integer key as a hex formatted string)
|
|
||||||
static constexpr char hexChars[] = {
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
|
|
||||||
};
|
|
||||||
|
|
||||||
std::random_device rd;
|
|
||||||
std::uniform_int_distribution<int32_t> dist(0, static_cast<int32_t>(std::size(hexChars) - 1));
|
|
||||||
|
|
||||||
char key[17];
|
|
||||||
for (int32_t i = 0; i < 16; i++)
|
|
||||||
{
|
{
|
||||||
int32_t hexCharIndex = dist(rd);
|
// Generate a string of 16 random hex characters (64-integer key as a hex formatted string)
|
||||||
key[i] = hexChars[hexCharIndex];
|
static constexpr char hexChars[] = {
|
||||||
}
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
key[std::size(key) - 1] = 0;
|
};
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string GetMasterServerUrl()
|
std::random_device rd;
|
||||||
{
|
std::uniform_int_distribution<int32_t> dist(0, static_cast<int32_t>(std::size(hexChars) - 1));
|
||||||
std::string result = kMasterServerURL;
|
|
||||||
if (!Config::Get().network.MasterServerUrl.empty())
|
char key[17];
|
||||||
{
|
for (int32_t i = 0; i < 16; i++)
|
||||||
result = Config::Get().network.MasterServerUrl;
|
{
|
||||||
|
int32_t hexCharIndex = dist(rd);
|
||||||
|
key[i] = hexChars[hexCharIndex];
|
||||||
|
}
|
||||||
|
key[std::size(key) - 1] = 0;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string GetMasterServerUrl()
|
||||||
|
{
|
||||||
|
std::string result = kMasterServerURL;
|
||||||
|
if (!Config::Get().network.MasterServerUrl.empty())
|
||||||
|
{
|
||||||
|
result = Config::Get().network.MasterServerUrl;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<INetworkServerAdvertiser> CreateServerAdvertiser(uint16_t port)
|
std::unique_ptr<INetworkServerAdvertiser> CreateServerAdvertiser(uint16_t port)
|
||||||
{
|
{
|
||||||
return std::make_unique<NetworkServerAdvertiser>(port);
|
return std::make_unique<NetworkServerAdvertiser>(port);
|
||||||
}
|
}
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif // DISABLE_NETWORK
|
#endif // DISABLE_NETWORK
|
||||||
|
|||||||
@@ -11,21 +11,24 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
enum class AdvertiseStatus
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
disabled,
|
enum class AdvertiseStatus
|
||||||
unregistered,
|
|
||||||
registered,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct INetworkServerAdvertiser
|
|
||||||
{
|
|
||||||
virtual ~INetworkServerAdvertiser()
|
|
||||||
{
|
{
|
||||||
}
|
disabled,
|
||||||
|
unregistered,
|
||||||
|
registered,
|
||||||
|
};
|
||||||
|
|
||||||
virtual AdvertiseStatus GetStatus() const = 0;
|
struct INetworkServerAdvertiser
|
||||||
virtual void Update() = 0;
|
{
|
||||||
};
|
virtual ~INetworkServerAdvertiser()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::unique_ptr<INetworkServerAdvertiser> CreateServerAdvertiser(uint16_t port);
|
virtual AdvertiseStatus GetStatus() const = 0;
|
||||||
|
virtual void Update() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<INetworkServerAdvertiser> CreateServerAdvertiser(uint16_t port);
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -13,136 +13,139 @@
|
|||||||
#include "../core/EnumUtils.hpp"
|
#include "../core/EnumUtils.hpp"
|
||||||
#include "../ride/RideTypes.h"
|
#include "../ride/RideTypes.h"
|
||||||
|
|
||||||
enum
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
SERVER_EVENT_PLAYER_JOINED,
|
enum
|
||||||
SERVER_EVENT_PLAYER_DISCONNECTED,
|
{
|
||||||
};
|
SERVER_EVENT_PLAYER_JOINED,
|
||||||
|
SERVER_EVENT_PLAYER_DISCONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
NETWORK_TICK_FLAG_CHECKSUMS = 1 << 0,
|
NETWORK_TICK_FLAG_CHECKSUMS = 1 << 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
NETWORK_MODE_NONE,
|
NETWORK_MODE_NONE,
|
||||||
NETWORK_MODE_CLIENT,
|
NETWORK_MODE_CLIENT,
|
||||||
NETWORK_MODE_SERVER
|
NETWORK_MODE_SERVER
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
NETWORK_PLAYER_FLAG_ISSERVER = 1 << 0,
|
NETWORK_PLAYER_FLAG_ISSERVER = 1 << 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
NETWORK_STATUS_NONE,
|
NETWORK_STATUS_NONE,
|
||||||
NETWORK_STATUS_READY,
|
NETWORK_STATUS_READY,
|
||||||
NETWORK_STATUS_CONNECTING,
|
NETWORK_STATUS_CONNECTING,
|
||||||
NETWORK_STATUS_CONNECTED
|
NETWORK_STATUS_CONNECTED
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class NetworkAuth : int32_t
|
enum class NetworkAuth : int32_t
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Requested,
|
Requested,
|
||||||
Ok,
|
Ok,
|
||||||
BadVersion,
|
BadVersion,
|
||||||
BadName,
|
BadName,
|
||||||
BadPassword,
|
BadPassword,
|
||||||
VerificationFailure,
|
VerificationFailure,
|
||||||
Full,
|
Full,
|
||||||
RequirePassword,
|
RequirePassword,
|
||||||
Verified,
|
Verified,
|
||||||
UnknownKeyDisallowed
|
UnknownKeyDisallowed
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class NetworkCommand : uint32_t
|
enum class NetworkCommand : uint32_t
|
||||||
{
|
{
|
||||||
Auth,
|
Auth,
|
||||||
Map,
|
Map,
|
||||||
Chat,
|
Chat,
|
||||||
Tick = 4,
|
Tick = 4,
|
||||||
PlayerList,
|
PlayerList,
|
||||||
Ping,
|
Ping,
|
||||||
PingList,
|
PingList,
|
||||||
DisconnectMessage,
|
DisconnectMessage,
|
||||||
GameInfo,
|
GameInfo,
|
||||||
ShowError,
|
ShowError,
|
||||||
GroupList,
|
GroupList,
|
||||||
Event,
|
Event,
|
||||||
Token,
|
Token,
|
||||||
ObjectsList,
|
ObjectsList,
|
||||||
MapRequest,
|
MapRequest,
|
||||||
GameAction,
|
GameAction,
|
||||||
PlayerInfo,
|
PlayerInfo,
|
||||||
RequestGameState,
|
RequestGameState,
|
||||||
GameState,
|
GameState,
|
||||||
ScriptsHeader,
|
ScriptsHeader,
|
||||||
ScriptsData,
|
ScriptsData,
|
||||||
Heartbeat,
|
Heartbeat,
|
||||||
Max,
|
Max,
|
||||||
Invalid = static_cast<uint32_t>(-1),
|
Invalid = static_cast<uint32_t>(-1),
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(NetworkCommand::GameInfo == static_cast<NetworkCommand>(9), "Master server expects this to be 9");
|
static_assert(NetworkCommand::GameInfo == static_cast<NetworkCommand>(9), "Master server expects this to be 9");
|
||||||
|
|
||||||
enum class NetworkServerStatus
|
enum class NetworkServerStatus
|
||||||
{
|
{
|
||||||
Ok,
|
Ok,
|
||||||
Desynced
|
Desynced
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NetworkServerState
|
struct NetworkServerState
|
||||||
{
|
{
|
||||||
NetworkServerStatus state = NetworkServerStatus::Ok;
|
NetworkServerStatus state = NetworkServerStatus::Ok;
|
||||||
uint32_t desyncTick = 0;
|
uint32_t desyncTick = 0;
|
||||||
uint32_t tick = 0;
|
uint32_t tick = 0;
|
||||||
uint32_t srand0 = 0;
|
uint32_t srand0 = 0;
|
||||||
bool gamestateSnapshotsEnabled = false;
|
bool gamestateSnapshotsEnabled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Structure is used for networking specific fields with meaning,
|
// Structure is used for networking specific fields with meaning,
|
||||||
// this structure can be used in combination with DataSerialiser
|
// this structure can be used in combination with DataSerialiser
|
||||||
// to provide extra details with template specialization.
|
// to provide extra details with template specialization.
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
template<typename T, size_t _TypeID>
|
template<typename T, size_t _TypeID>
|
||||||
struct NetworkObjectId
|
struct NetworkObjectId
|
||||||
{
|
|
||||||
NetworkObjectId(T v)
|
|
||||||
: id(v)
|
|
||||||
{
|
{
|
||||||
}
|
NetworkObjectId(T v)
|
||||||
NetworkObjectId()
|
: id(v)
|
||||||
: id(T(-1))
|
{
|
||||||
{
|
}
|
||||||
}
|
NetworkObjectId()
|
||||||
operator T() const
|
: id(T(-1))
|
||||||
{
|
{
|
||||||
return id;
|
}
|
||||||
}
|
operator T() const
|
||||||
T id;
|
{
|
||||||
};
|
return id;
|
||||||
|
}
|
||||||
|
T id;
|
||||||
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
// NOTE: When adding new types make sure to have no duplicate _TypeID's otherwise
|
// NOTE: When adding new types make sure to have no duplicate _TypeID's otherwise
|
||||||
// there is no way to specialize templates if they have the exact symbol.
|
// there is no way to specialize templates if they have the exact symbol.
|
||||||
using NetworkPlayerId_t = NetworkObjectId<int32_t, 0>;
|
using NetworkPlayerId_t = NetworkObjectId<int32_t, 0>;
|
||||||
using NetworkCheatType_t = NetworkObjectId<int32_t, 2>;
|
using NetworkCheatType_t = NetworkObjectId<int32_t, 2>;
|
||||||
|
|
||||||
enum class NetworkStatisticsGroup : uint32_t
|
enum class NetworkStatisticsGroup : uint32_t
|
||||||
{
|
{
|
||||||
Total = 0, // Entire network traffic.
|
Total = 0, // Entire network traffic.
|
||||||
Base, // Messages such as Tick, Ping
|
Base, // Messages such as Tick, Ping
|
||||||
Commands, // Command / Game actions
|
Commands, // Command / Game actions
|
||||||
MapData,
|
MapData,
|
||||||
Max,
|
Max,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NetworkStats
|
struct NetworkStats
|
||||||
{
|
{
|
||||||
uint64_t bytesReceived[EnumValue(NetworkStatisticsGroup::Max)];
|
uint64_t bytesReceived[EnumValue(NetworkStatisticsGroup::Max)];
|
||||||
uint64_t bytesSent[EnumValue(NetworkStatisticsGroup::Max)];
|
uint64_t bytesSent[EnumValue(NetworkStatisticsGroup::Max)];
|
||||||
};
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -22,198 +22,199 @@
|
|||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
constexpr const utf8* kUserStoreFilename = "users.json";
|
|
||||||
|
|
||||||
std::unique_ptr<NetworkUser> NetworkUser::FromJson(const json_t& jsonData)
|
|
||||||
{
|
{
|
||||||
Guard::Assert(jsonData.is_object(), "NetworkUser::FromJson expects parameter jsonData to be object");
|
constexpr const utf8* kUserStoreFilename = "users.json";
|
||||||
|
|
||||||
const std::string hash = Json::GetString(jsonData["hash"]);
|
std::unique_ptr<NetworkUser> NetworkUser::FromJson(const json_t& jsonData)
|
||||||
const std::string name = Json::GetString(jsonData["name"]);
|
|
||||||
json_t jsonGroupId = jsonData["groupId"];
|
|
||||||
|
|
||||||
std::unique_ptr<NetworkUser> user = nullptr;
|
|
||||||
if (!hash.empty() && !name.empty())
|
|
||||||
{
|
{
|
||||||
user = std::make_unique<NetworkUser>();
|
Guard::Assert(jsonData.is_object(), "NetworkUser::FromJson expects parameter jsonData to be object");
|
||||||
user->Hash = hash;
|
|
||||||
user->Name = name;
|
const std::string hash = Json::GetString(jsonData["hash"]);
|
||||||
if (jsonGroupId.is_number_integer())
|
const std::string name = Json::GetString(jsonData["name"]);
|
||||||
|
json_t jsonGroupId = jsonData["groupId"];
|
||||||
|
|
||||||
|
std::unique_ptr<NetworkUser> user = nullptr;
|
||||||
|
if (!hash.empty() && !name.empty())
|
||||||
{
|
{
|
||||||
user->GroupId = Json::GetNumber<uint8_t>(jsonGroupId);
|
user = std::make_unique<NetworkUser>();
|
||||||
}
|
user->Hash = hash;
|
||||||
user->Remove = false;
|
user->Name = name;
|
||||||
}
|
if (jsonGroupId.is_number_integer())
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_t NetworkUser::ToJson() const
|
|
||||||
{
|
|
||||||
json_t jsonData;
|
|
||||||
jsonData["hash"] = Hash;
|
|
||||||
jsonData["name"] = Name;
|
|
||||||
|
|
||||||
json_t jsonGroupId;
|
|
||||||
if (GroupId.has_value())
|
|
||||||
{
|
|
||||||
jsonGroupId = *GroupId;
|
|
||||||
}
|
|
||||||
jsonData["groupId"] = jsonGroupId;
|
|
||||||
|
|
||||||
return jsonData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkUserManager::Load()
|
|
||||||
{
|
|
||||||
const auto path = GetStorePath();
|
|
||||||
|
|
||||||
if (File::Exists(path))
|
|
||||||
{
|
|
||||||
_usersByHash.clear();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json_t jsonUsers = Json::ReadFromFile(path);
|
|
||||||
for (const auto& jsonUser : jsonUsers)
|
|
||||||
{
|
{
|
||||||
if (jsonUser.is_object())
|
user->GroupId = Json::GetNumber<uint8_t>(jsonGroupId);
|
||||||
|
}
|
||||||
|
user->Remove = false;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t NetworkUser::ToJson() const
|
||||||
|
{
|
||||||
|
json_t jsonData;
|
||||||
|
jsonData["hash"] = Hash;
|
||||||
|
jsonData["name"] = Name;
|
||||||
|
|
||||||
|
json_t jsonGroupId;
|
||||||
|
if (GroupId.has_value())
|
||||||
|
{
|
||||||
|
jsonGroupId = *GroupId;
|
||||||
|
}
|
||||||
|
jsonData["groupId"] = jsonGroupId;
|
||||||
|
|
||||||
|
return jsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkUserManager::Load()
|
||||||
|
{
|
||||||
|
const auto path = GetStorePath();
|
||||||
|
|
||||||
|
if (File::Exists(path))
|
||||||
|
{
|
||||||
|
_usersByHash.clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json_t jsonUsers = Json::ReadFromFile(path);
|
||||||
|
for (const auto& jsonUser : jsonUsers)
|
||||||
{
|
{
|
||||||
auto networkUser = NetworkUser::FromJson(jsonUser);
|
if (jsonUser.is_object())
|
||||||
if (networkUser != nullptr)
|
|
||||||
{
|
{
|
||||||
_usersByHash[networkUser->Hash] = std::move(networkUser);
|
auto networkUser = NetworkUser::FromJson(jsonUser);
|
||||||
|
if (networkUser != nullptr)
|
||||||
|
{
|
||||||
|
_usersByHash[networkUser->Hash] = std::move(networkUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (const std::exception& ex)
|
||||||
catch (const std::exception& ex)
|
{
|
||||||
{
|
Console::Error::WriteLine("Failed to read %s as JSON. %s", path.c_str(), ex.what());
|
||||||
Console::Error::WriteLine("Failed to read %s as JSON. %s", path.c_str(), ex.what());
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void NetworkUserManager::Save()
|
||||||
void NetworkUserManager::Save()
|
{
|
||||||
{
|
const auto path = GetStorePath();
|
||||||
const auto path = GetStorePath();
|
|
||||||
|
json_t jsonUsers;
|
||||||
json_t jsonUsers;
|
try
|
||||||
try
|
{
|
||||||
{
|
if (File::Exists(path))
|
||||||
if (File::Exists(path))
|
{
|
||||||
{
|
jsonUsers = Json::ReadFromFile(path);
|
||||||
jsonUsers = Json::ReadFromFile(path);
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (const std::exception&)
|
||||||
catch (const std::exception&)
|
{
|
||||||
{
|
}
|
||||||
}
|
|
||||||
|
// Update existing users
|
||||||
// Update existing users
|
std::unordered_set<std::string> savedHashes;
|
||||||
std::unordered_set<std::string> savedHashes;
|
for (auto it = jsonUsers.begin(); it != jsonUsers.end();)
|
||||||
for (auto it = jsonUsers.begin(); it != jsonUsers.end();)
|
{
|
||||||
{
|
json_t jsonUser = *it;
|
||||||
json_t jsonUser = *it;
|
if (!jsonUser.is_object())
|
||||||
if (!jsonUser.is_object())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string hashString = Json::GetString(jsonUser["hash"]);
|
|
||||||
|
|
||||||
const auto networkUser = GetUserByHash(hashString);
|
|
||||||
if (networkUser != nullptr)
|
|
||||||
{
|
|
||||||
if (networkUser->Remove)
|
|
||||||
{
|
{
|
||||||
it = jsonUsers.erase(it);
|
|
||||||
// erase advances the iterator so make sure we don't do it again
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
std::string hashString = Json::GetString(jsonUser["hash"]);
|
||||||
|
|
||||||
// replace the existing element in jsonUsers
|
const auto networkUser = GetUserByHash(hashString);
|
||||||
*it = networkUser->ToJson();
|
if (networkUser != nullptr)
|
||||||
savedHashes.insert(hashString);
|
{
|
||||||
|
if (networkUser->Remove)
|
||||||
|
{
|
||||||
|
it = jsonUsers.erase(it);
|
||||||
|
// erase advances the iterator so make sure we don't do it again
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace the existing element in jsonUsers
|
||||||
|
*it = networkUser->ToJson();
|
||||||
|
savedHashes.insert(hashString);
|
||||||
|
}
|
||||||
|
|
||||||
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
it++;
|
// Add new users
|
||||||
}
|
for (const auto& kvp : _usersByHash)
|
||||||
|
|
||||||
// Add new users
|
|
||||||
for (const auto& kvp : _usersByHash)
|
|
||||||
{
|
|
||||||
const auto& networkUser = kvp.second;
|
|
||||||
if (!networkUser->Remove && savedHashes.find(networkUser->Hash) == savedHashes.end())
|
|
||||||
{
|
{
|
||||||
jsonUsers.push_back(networkUser->ToJson());
|
const auto& networkUser = kvp.second;
|
||||||
|
if (!networkUser->Remove && savedHashes.find(networkUser->Hash) == savedHashes.end())
|
||||||
|
{
|
||||||
|
jsonUsers.push_back(networkUser->ToJson());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::WriteToFile(path, jsonUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::WriteToFile(path, jsonUsers);
|
void NetworkUserManager::UnsetUsersOfGroup(uint8_t groupId)
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkUserManager::UnsetUsersOfGroup(uint8_t groupId)
|
|
||||||
{
|
|
||||||
for (const auto& kvp : _usersByHash)
|
|
||||||
{
|
{
|
||||||
auto& networkUser = kvp.second;
|
for (const auto& kvp : _usersByHash)
|
||||||
if (networkUser->GroupId.has_value() && *networkUser->GroupId == groupId)
|
|
||||||
{
|
{
|
||||||
networkUser->GroupId = std::nullopt;
|
auto& networkUser = kvp.second;
|
||||||
|
if (networkUser->GroupId.has_value() && *networkUser->GroupId == groupId)
|
||||||
|
{
|
||||||
|
networkUser->GroupId = std::nullopt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkUserManager::RemoveUser(const std::string& hash)
|
void NetworkUserManager::RemoveUser(const std::string& hash)
|
||||||
{
|
|
||||||
NetworkUser* networkUser = const_cast<NetworkUser*>(GetUserByHash(hash));
|
|
||||||
if (networkUser != nullptr)
|
|
||||||
{
|
{
|
||||||
networkUser->Remove = true;
|
NetworkUser* networkUser = const_cast<NetworkUser*>(GetUserByHash(hash));
|
||||||
}
|
if (networkUser != nullptr)
|
||||||
}
|
|
||||||
|
|
||||||
const NetworkUser* NetworkUserManager::GetUserByHash(const std::string& hash) const
|
|
||||||
{
|
|
||||||
auto it = _usersByHash.find(hash);
|
|
||||||
if (it != _usersByHash.end())
|
|
||||||
{
|
|
||||||
return it->second.get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NetworkUser* NetworkUserManager::GetUserByName(const std::string& name) const
|
|
||||||
{
|
|
||||||
for (const auto& kvp : _usersByHash)
|
|
||||||
{
|
|
||||||
const auto& networkUser = kvp.second;
|
|
||||||
if (String::iequals(name, networkUser->Name))
|
|
||||||
{
|
{
|
||||||
return networkUser.get();
|
networkUser->Remove = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkUser* NetworkUserManager::GetOrAddUser(const std::string& hash)
|
const NetworkUser* NetworkUserManager::GetUserByHash(const std::string& hash) const
|
||||||
{
|
|
||||||
NetworkUser* networkUser = const_cast<NetworkUser*>(GetUserByHash(hash));
|
|
||||||
if (networkUser == nullptr)
|
|
||||||
{
|
{
|
||||||
auto newNetworkUser = std::make_unique<NetworkUser>();
|
auto it = _usersByHash.find(hash);
|
||||||
newNetworkUser->Hash = hash;
|
if (it != _usersByHash.end())
|
||||||
networkUser = newNetworkUser.get();
|
{
|
||||||
_usersByHash[hash] = std::move(newNetworkUser);
|
return it->second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return networkUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8string NetworkUserManager::GetStorePath()
|
const NetworkUser* NetworkUserManager::GetUserByName(const std::string& name) const
|
||||||
{
|
{
|
||||||
auto& env = OpenRCT2::GetContext()->GetPlatformEnvironment();
|
for (const auto& kvp : _usersByHash)
|
||||||
return Path::Combine(env.GetDirectoryPath(OpenRCT2::DirBase::user), kUserStoreFilename);
|
{
|
||||||
}
|
const auto& networkUser = kvp.second;
|
||||||
|
if (String::iequals(name, networkUser->Name))
|
||||||
|
{
|
||||||
|
return networkUser.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkUser* NetworkUserManager::GetOrAddUser(const std::string& hash)
|
||||||
|
{
|
||||||
|
NetworkUser* networkUser = const_cast<NetworkUser*>(GetUserByHash(hash));
|
||||||
|
if (networkUser == nullptr)
|
||||||
|
{
|
||||||
|
auto newNetworkUser = std::make_unique<NetworkUser>();
|
||||||
|
newNetworkUser->Hash = hash;
|
||||||
|
networkUser = newNetworkUser.get();
|
||||||
|
_usersByHash[hash] = std::move(newNetworkUser);
|
||||||
|
}
|
||||||
|
return networkUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8string NetworkUserManager::GetStorePath()
|
||||||
|
{
|
||||||
|
auto& env = OpenRCT2::GetContext()->GetPlatformEnvironment();
|
||||||
|
return Path::Combine(env.GetDirectoryPath(OpenRCT2::DirBase::user), kUserStoreFilename);
|
||||||
|
}
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,52 +16,55 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
class NetworkUser final
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
public:
|
class NetworkUser final
|
||||||
std::string Hash;
|
{
|
||||||
std::string Name;
|
public:
|
||||||
std::optional<uint8_t> GroupId;
|
std::string Hash;
|
||||||
bool Remove;
|
std::string Name;
|
||||||
|
std::optional<uint8_t> GroupId;
|
||||||
|
bool Remove;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a NetworkUser object from a JSON object
|
* Creates a NetworkUser object from a JSON object
|
||||||
* @param jsonData Must be a JSON node of type object
|
* @param jsonData Must be a JSON node of type object
|
||||||
* @return Pointer to a new NetworkUser object
|
* @return Pointer to a new NetworkUser object
|
||||||
* @note jsonData is deliberately left non-const: json_t behaviour changes when const
|
* @note jsonData is deliberately left non-const: json_t behaviour changes when const
|
||||||
*/
|
*/
|
||||||
static std::unique_ptr<NetworkUser> FromJson(const json_t& jsonData);
|
static std::unique_ptr<NetworkUser> FromJson(const json_t& jsonData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialise a NetworkUser object into a JSON object
|
* Serialise a NetworkUser object into a JSON object
|
||||||
*
|
*
|
||||||
* @return JSON representation of the NetworkUser object
|
* @return JSON representation of the NetworkUser object
|
||||||
*/
|
*/
|
||||||
json_t ToJson() const;
|
json_t ToJson() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NetworkUserManager final
|
class NetworkUserManager final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Load();
|
void Load();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief NetworkUserManager::Save
|
* @brief NetworkUserManager::Save
|
||||||
* Reads mappings from JSON, updates them in-place and saves JSON.
|
* Reads mappings from JSON, updates them in-place and saves JSON.
|
||||||
*
|
*
|
||||||
* Useful for retaining custom entries in JSON file.
|
* Useful for retaining custom entries in JSON file.
|
||||||
*/
|
*/
|
||||||
void Save();
|
void Save();
|
||||||
|
|
||||||
void UnsetUsersOfGroup(uint8_t groupId);
|
void UnsetUsersOfGroup(uint8_t groupId);
|
||||||
void RemoveUser(const std::string& hash);
|
void RemoveUser(const std::string& hash);
|
||||||
|
|
||||||
const NetworkUser* GetUserByHash(const std::string& hash) const;
|
const NetworkUser* GetUserByHash(const std::string& hash) const;
|
||||||
const NetworkUser* GetUserByName(const std::string& name) const;
|
const NetworkUser* GetUserByName(const std::string& name) const;
|
||||||
NetworkUser* GetOrAddUser(const std::string& hash);
|
NetworkUser* GetOrAddUser(const std::string& hash);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, std::unique_ptr<NetworkUser>> _usersByHash;
|
std::unordered_map<std::string, std::unique_ptr<NetworkUser>> _usersByHash;
|
||||||
|
|
||||||
static u8string GetStorePath();
|
static u8string GetStorePath();
|
||||||
};
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
@@ -31,412 +31,415 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
using namespace OpenRCT2;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const
|
|
||||||
{
|
{
|
||||||
const auto& a = *this;
|
int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const
|
||||||
const auto& b = other;
|
|
||||||
|
|
||||||
if (a.Favourite != b.Favourite)
|
|
||||||
{
|
{
|
||||||
return a.Favourite ? -1 : 1;
|
const auto& a = *this;
|
||||||
}
|
const auto& b = other;
|
||||||
|
|
||||||
if (a.Local != b.Local)
|
if (a.Favourite != b.Favourite)
|
||||||
{
|
|
||||||
return a.Local ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool serverACompatible = a.Version == NetworkGetVersion();
|
|
||||||
bool serverBCompatible = b.Version == NetworkGetVersion();
|
|
||||||
if (serverACompatible != serverBCompatible)
|
|
||||||
{
|
|
||||||
return serverACompatible ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.RequiresPassword != b.RequiresPassword)
|
|
||||||
{
|
|
||||||
return a.RequiresPassword ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.Players != b.Players)
|
|
||||||
{
|
|
||||||
return a.Players > b.Players ? -1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String::compare(a.Name, b.Name, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerListEntry::IsVersionValid() const noexcept
|
|
||||||
{
|
|
||||||
return Version.empty() || Version == NetworkGetVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<ServerListEntry> ServerListEntry::FromJson(json_t& server)
|
|
||||||
{
|
|
||||||
Guard::Assert(server.is_object(), "ServerListEntry::FromJson expects parameter server to be object");
|
|
||||||
|
|
||||||
const auto port = Json::GetNumber<int32_t>(server["port"]);
|
|
||||||
const auto name = Json::GetString(server["name"]);
|
|
||||||
const auto description = Json::GetString(server["description"]);
|
|
||||||
const auto requiresPassword = Json::GetBoolean(server["requiresPassword"]);
|
|
||||||
const auto version = Json::GetString(server["version"]);
|
|
||||||
const auto players = Json::GetNumber<uint8_t>(server["players"]);
|
|
||||||
const auto maxPlayers = Json::GetNumber<uint8_t>(server["maxPlayers"]);
|
|
||||||
std::string ip;
|
|
||||||
// if server["ip"] or server["ip"]["v4"] are values, this will throw an exception, so check first
|
|
||||||
if (server["ip"].is_object() && server["ip"]["v4"].is_array())
|
|
||||||
{
|
|
||||||
ip = Json::GetString(server["ip"]["v4"][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.empty() || version.empty())
|
|
||||||
{
|
|
||||||
LOG_VERBOSE("Cowardly refusing to add server without name or version specified.");
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerListEntry entry;
|
|
||||||
|
|
||||||
entry.Address = ip + ":" + std::to_string(port);
|
|
||||||
entry.Name = name;
|
|
||||||
entry.Description = description;
|
|
||||||
entry.Version = version;
|
|
||||||
entry.RequiresPassword = requiresPassword;
|
|
||||||
entry.Players = players;
|
|
||||||
entry.MaxPlayers = maxPlayers;
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::Sort()
|
|
||||||
{
|
|
||||||
_serverEntries.erase(
|
|
||||||
std::unique(
|
|
||||||
_serverEntries.begin(), _serverEntries.end(),
|
|
||||||
[](const ServerListEntry& a, const ServerListEntry& b) {
|
|
||||||
if (a.Favourite == b.Favourite)
|
|
||||||
{
|
|
||||||
return String::iequals(a.Address, b.Address);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
_serverEntries.end());
|
|
||||||
std::sort(_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& a, const ServerListEntry& b) {
|
|
||||||
return a.CompareTo(b) < 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerListEntry& ServerList::GetServer(size_t index)
|
|
||||||
{
|
|
||||||
return _serverEntries[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ServerList::GetCount() const
|
|
||||||
{
|
|
||||||
return _serverEntries.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::Add(const ServerListEntry& entry)
|
|
||||||
{
|
|
||||||
_serverEntries.push_back(entry);
|
|
||||||
Sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::AddRange(const std::vector<ServerListEntry>& entries)
|
|
||||||
{
|
|
||||||
_serverEntries.insert(_serverEntries.end(), entries.begin(), entries.end());
|
|
||||||
Sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::AddOrUpdateRange(const std::vector<ServerListEntry>& entries)
|
|
||||||
{
|
|
||||||
for (auto& existsEntry : _serverEntries)
|
|
||||||
{
|
|
||||||
auto match = std::find_if(
|
|
||||||
entries.begin(), entries.end(), [&](const ServerListEntry& entry) { return existsEntry.Address == entry.Address; });
|
|
||||||
if (match != entries.end())
|
|
||||||
{
|
{
|
||||||
// Keep favourites
|
return a.Favourite ? -1 : 1;
|
||||||
auto fav = existsEntry.Favourite;
|
|
||||||
|
|
||||||
existsEntry = *match;
|
|
||||||
existsEntry.Favourite = fav;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a.Local != b.Local)
|
||||||
|
{
|
||||||
|
return a.Local ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool serverACompatible = a.Version == NetworkGetVersion();
|
||||||
|
bool serverBCompatible = b.Version == NetworkGetVersion();
|
||||||
|
if (serverACompatible != serverBCompatible)
|
||||||
|
{
|
||||||
|
return serverACompatible ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.RequiresPassword != b.RequiresPassword)
|
||||||
|
{
|
||||||
|
return a.RequiresPassword ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Players != b.Players)
|
||||||
|
{
|
||||||
|
return a.Players > b.Players ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String::compare(a.Name, b.Name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ServerListEntry> newServers;
|
bool ServerListEntry::IsVersionValid() const noexcept
|
||||||
std::copy_if(entries.begin(), entries.end(), std::back_inserter(newServers), [this](const ServerListEntry& entry) {
|
|
||||||
return std::find_if(
|
|
||||||
_serverEntries.begin(), _serverEntries.end(),
|
|
||||||
[&](const ServerListEntry& existsEntry) { return existsEntry.Address == entry.Address; })
|
|
||||||
== _serverEntries.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddRange(newServers);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::Clear() noexcept
|
|
||||||
{
|
|
||||||
_serverEntries.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ServerListEntry> ServerList::ReadFavourites() const
|
|
||||||
{
|
|
||||||
LOG_VERBOSE("server_list_read(...)");
|
|
||||||
std::vector<ServerListEntry> entries;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
auto& env = GetContext()->GetPlatformEnvironment();
|
return Version.empty() || Version == NetworkGetVersion();
|
||||||
auto path = env.GetFilePath(PathId::networkServers);
|
}
|
||||||
if (File::Exists(path))
|
|
||||||
|
std::optional<ServerListEntry> ServerListEntry::FromJson(json_t& server)
|
||||||
|
{
|
||||||
|
Guard::Assert(server.is_object(), "ServerListEntry::FromJson expects parameter server to be object");
|
||||||
|
|
||||||
|
const auto port = Json::GetNumber<int32_t>(server["port"]);
|
||||||
|
const auto name = Json::GetString(server["name"]);
|
||||||
|
const auto description = Json::GetString(server["description"]);
|
||||||
|
const auto requiresPassword = Json::GetBoolean(server["requiresPassword"]);
|
||||||
|
const auto version = Json::GetString(server["version"]);
|
||||||
|
const auto players = Json::GetNumber<uint8_t>(server["players"]);
|
||||||
|
const auto maxPlayers = Json::GetNumber<uint8_t>(server["maxPlayers"]);
|
||||||
|
std::string ip;
|
||||||
|
// if server["ip"] or server["ip"]["v4"] are values, this will throw an exception, so check first
|
||||||
|
if (server["ip"].is_object() && server["ip"]["v4"].is_array())
|
||||||
{
|
{
|
||||||
auto fs = FileStream(path, FileMode::open);
|
ip = Json::GetString(server["ip"]["v4"][0]);
|
||||||
auto numEntries = fs.ReadValue<uint32_t>();
|
}
|
||||||
for (size_t i = 0; i < numEntries; i++)
|
|
||||||
|
if (name.empty() || version.empty())
|
||||||
|
{
|
||||||
|
LOG_VERBOSE("Cowardly refusing to add server without name or version specified.");
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerListEntry entry;
|
||||||
|
|
||||||
|
entry.Address = ip + ":" + std::to_string(port);
|
||||||
|
entry.Name = name;
|
||||||
|
entry.Description = description;
|
||||||
|
entry.Version = version;
|
||||||
|
entry.RequiresPassword = requiresPassword;
|
||||||
|
entry.Players = players;
|
||||||
|
entry.MaxPlayers = maxPlayers;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerList::Sort()
|
||||||
|
{
|
||||||
|
_serverEntries.erase(
|
||||||
|
std::unique(
|
||||||
|
_serverEntries.begin(), _serverEntries.end(),
|
||||||
|
[](const ServerListEntry& a, const ServerListEntry& b) {
|
||||||
|
if (a.Favourite == b.Favourite)
|
||||||
|
{
|
||||||
|
return String::iequals(a.Address, b.Address);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
_serverEntries.end());
|
||||||
|
std::sort(_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& a, const ServerListEntry& b) {
|
||||||
|
return a.CompareTo(b) < 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerListEntry& ServerList::GetServer(size_t index)
|
||||||
|
{
|
||||||
|
return _serverEntries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ServerList::GetCount() const
|
||||||
|
{
|
||||||
|
return _serverEntries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerList::Add(const ServerListEntry& entry)
|
||||||
|
{
|
||||||
|
_serverEntries.push_back(entry);
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerList::AddRange(const std::vector<ServerListEntry>& entries)
|
||||||
|
{
|
||||||
|
_serverEntries.insert(_serverEntries.end(), entries.begin(), entries.end());
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerList::AddOrUpdateRange(const std::vector<ServerListEntry>& entries)
|
||||||
|
{
|
||||||
|
for (auto& existsEntry : _serverEntries)
|
||||||
|
{
|
||||||
|
auto match = std::find_if(entries.begin(), entries.end(), [&](const ServerListEntry& entry) {
|
||||||
|
return existsEntry.Address == entry.Address;
|
||||||
|
});
|
||||||
|
if (match != entries.end())
|
||||||
{
|
{
|
||||||
ServerListEntry serverInfo;
|
// Keep favourites
|
||||||
serverInfo.Address = fs.ReadString();
|
auto fav = existsEntry.Favourite;
|
||||||
serverInfo.Name = fs.ReadString();
|
|
||||||
serverInfo.RequiresPassword = false;
|
existsEntry = *match;
|
||||||
serverInfo.Description = fs.ReadString();
|
existsEntry.Favourite = fav;
|
||||||
serverInfo.Version.clear();
|
|
||||||
serverInfo.Favourite = true;
|
|
||||||
serverInfo.Players = 0;
|
|
||||||
serverInfo.MaxPlayers = 0;
|
|
||||||
entries.push_back(std::move(serverInfo));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ServerListEntry> newServers;
|
||||||
|
std::copy_if(entries.begin(), entries.end(), std::back_inserter(newServers), [this](const ServerListEntry& entry) {
|
||||||
|
return std::find_if(
|
||||||
|
_serverEntries.begin(), _serverEntries.end(),
|
||||||
|
[&](const ServerListEntry& existsEntry) { return existsEntry.Address == entry.Address; })
|
||||||
|
== _serverEntries.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddRange(newServers);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
|
||||||
|
void ServerList::Clear() noexcept
|
||||||
{
|
{
|
||||||
LOG_ERROR("Unable to read server list: %s", e.what());
|
_serverEntries.clear();
|
||||||
entries = std::vector<ServerListEntry>();
|
|
||||||
}
|
}
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::ReadAndAddFavourites()
|
std::vector<ServerListEntry> ServerList::ReadFavourites() const
|
||||||
{
|
|
||||||
_serverEntries.erase(
|
|
||||||
std::remove_if(
|
|
||||||
_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& entry) { return entry.Favourite; }),
|
|
||||||
_serverEntries.end());
|
|
||||||
auto entries = ReadFavourites();
|
|
||||||
AddRange(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerList::WriteFavourites() const
|
|
||||||
{
|
|
||||||
// Save just favourite servers
|
|
||||||
std::vector<ServerListEntry> favouriteServers;
|
|
||||||
std::copy_if(
|
|
||||||
_serverEntries.begin(), _serverEntries.end(), std::back_inserter(favouriteServers),
|
|
||||||
[](const ServerListEntry& entry) { return entry.Favourite; });
|
|
||||||
WriteFavourites(favouriteServers);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerList::WriteFavourites(const std::vector<ServerListEntry>& entries) const
|
|
||||||
{
|
|
||||||
LOG_VERBOSE("server_list_write(%d, 0x%p)", entries.size(), entries.data());
|
|
||||||
|
|
||||||
auto& env = GetContext()->GetPlatformEnvironment();
|
|
||||||
auto path = Path::Combine(env.GetDirectoryPath(DirBase::user), u8"servers.cfg");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
auto fs = FileStream(path, FileMode::write);
|
LOG_VERBOSE("server_list_read(...)");
|
||||||
fs.WriteValue<uint32_t>(static_cast<uint32_t>(entries.size()));
|
|
||||||
for (const auto& entry : entries)
|
|
||||||
{
|
|
||||||
fs.WriteString(entry.Address);
|
|
||||||
fs.WriteString(entry.Name);
|
|
||||||
fs.WriteString(entry.Description);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
LOG_ERROR("Unable to write server list: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<std::vector<ServerListEntry>> ServerList::FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) const
|
|
||||||
{
|
|
||||||
auto broadcastAddress = broadcastEndpoint.GetHostname();
|
|
||||||
return std::async(std::launch::async, [broadcastAddress] {
|
|
||||||
constexpr auto kReceiveDelayInMs = 10;
|
|
||||||
constexpr auto kReceiveWaitInMs = 2000;
|
|
||||||
|
|
||||||
std::string_view msg = kNetworkLanBroadcastMsg;
|
|
||||||
auto udpSocket = CreateUdpSocket();
|
|
||||||
|
|
||||||
LOG_VERBOSE("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress.c_str());
|
|
||||||
auto len = udpSocket->SendData(broadcastAddress, kNetworkLanBroadcastPort, msg.data(), msg.size());
|
|
||||||
if (len != msg.size())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Unable to broadcast server query.");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ServerListEntry> entries;
|
std::vector<ServerListEntry> entries;
|
||||||
for (int i = 0; i < (kReceiveWaitInMs / kReceiveDelayInMs); i++)
|
try
|
||||||
{
|
{
|
||||||
try
|
auto& env = GetContext()->GetPlatformEnvironment();
|
||||||
|
auto path = env.GetFilePath(PathId::networkServers);
|
||||||
|
if (File::Exists(path))
|
||||||
{
|
{
|
||||||
// Start with initialised buffer in case we receive a non-terminated string
|
auto fs = FileStream(path, FileMode::open);
|
||||||
char buffer[1024]{};
|
auto numEntries = fs.ReadValue<uint32_t>();
|
||||||
size_t recievedLen{};
|
for (size_t i = 0; i < numEntries; i++)
|
||||||
std::unique_ptr<INetworkEndpoint> endpoint;
|
|
||||||
auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint);
|
|
||||||
if (p == NetworkReadPacket::Success)
|
|
||||||
{
|
{
|
||||||
auto sender = endpoint->GetHostname();
|
ServerListEntry serverInfo;
|
||||||
LOG_VERBOSE("Received %zu bytes back from %s", recievedLen, sender.c_str());
|
serverInfo.Address = fs.ReadString();
|
||||||
auto jinfo = Json::FromString(std::string_view(buffer));
|
serverInfo.Name = fs.ReadString();
|
||||||
|
serverInfo.RequiresPassword = false;
|
||||||
|
serverInfo.Description = fs.ReadString();
|
||||||
|
serverInfo.Version.clear();
|
||||||
|
serverInfo.Favourite = true;
|
||||||
|
serverInfo.Players = 0;
|
||||||
|
serverInfo.MaxPlayers = 0;
|
||||||
|
entries.push_back(std::move(serverInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Unable to read server list: %s", e.what());
|
||||||
|
entries = std::vector<ServerListEntry>();
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
if (jinfo.is_object())
|
void ServerList::ReadAndAddFavourites()
|
||||||
|
{
|
||||||
|
_serverEntries.erase(
|
||||||
|
std::remove_if(
|
||||||
|
_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& entry) { return entry.Favourite; }),
|
||||||
|
_serverEntries.end());
|
||||||
|
auto entries = ReadFavourites();
|
||||||
|
AddRange(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerList::WriteFavourites() const
|
||||||
|
{
|
||||||
|
// Save just favourite servers
|
||||||
|
std::vector<ServerListEntry> favouriteServers;
|
||||||
|
std::copy_if(
|
||||||
|
_serverEntries.begin(), _serverEntries.end(), std::back_inserter(favouriteServers),
|
||||||
|
[](const ServerListEntry& entry) { return entry.Favourite; });
|
||||||
|
WriteFavourites(favouriteServers);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerList::WriteFavourites(const std::vector<ServerListEntry>& entries) const
|
||||||
|
{
|
||||||
|
LOG_VERBOSE("server_list_write(%d, 0x%p)", entries.size(), entries.data());
|
||||||
|
|
||||||
|
auto& env = GetContext()->GetPlatformEnvironment();
|
||||||
|
auto path = Path::Combine(env.GetDirectoryPath(DirBase::user), u8"servers.cfg");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto fs = FileStream(path, FileMode::write);
|
||||||
|
fs.WriteValue<uint32_t>(static_cast<uint32_t>(entries.size()));
|
||||||
|
for (const auto& entry : entries)
|
||||||
|
{
|
||||||
|
fs.WriteString(entry.Address);
|
||||||
|
fs.WriteString(entry.Name);
|
||||||
|
fs.WriteString(entry.Description);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("Unable to write server list: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<std::vector<ServerListEntry>> ServerList::FetchLocalServerListAsync(
|
||||||
|
const INetworkEndpoint& broadcastEndpoint) const
|
||||||
|
{
|
||||||
|
auto broadcastAddress = broadcastEndpoint.GetHostname();
|
||||||
|
return std::async(std::launch::async, [broadcastAddress] {
|
||||||
|
constexpr auto kReceiveDelayInMs = 10;
|
||||||
|
constexpr auto kReceiveWaitInMs = 2000;
|
||||||
|
|
||||||
|
std::string_view msg = kNetworkLanBroadcastMsg;
|
||||||
|
auto udpSocket = CreateUdpSocket();
|
||||||
|
|
||||||
|
LOG_VERBOSE("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress.c_str());
|
||||||
|
auto len = udpSocket->SendData(broadcastAddress, kNetworkLanBroadcastPort, msg.data(), msg.size());
|
||||||
|
if (len != msg.size())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unable to broadcast server query.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ServerListEntry> entries;
|
||||||
|
for (int i = 0; i < (kReceiveWaitInMs / kReceiveDelayInMs); i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start with initialised buffer in case we receive a non-terminated string
|
||||||
|
char buffer[1024]{};
|
||||||
|
size_t recievedLen{};
|
||||||
|
std::unique_ptr<INetworkEndpoint> endpoint;
|
||||||
|
auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint);
|
||||||
|
if (p == NetworkReadPacket::Success)
|
||||||
{
|
{
|
||||||
jinfo["ip"] = { { "v4", { sender } } };
|
auto sender = endpoint->GetHostname();
|
||||||
|
LOG_VERBOSE("Received %zu bytes back from %s", recievedLen, sender.c_str());
|
||||||
|
auto jinfo = Json::FromString(std::string_view(buffer));
|
||||||
|
|
||||||
auto entry = ServerListEntry::FromJson(jinfo);
|
if (jinfo.is_object())
|
||||||
if (entry.has_value())
|
|
||||||
{
|
{
|
||||||
(*entry).Local = true;
|
jinfo["ip"] = { { "v4", { sender } } };
|
||||||
entries.push_back(std::move(*entry));
|
|
||||||
|
auto entry = ServerListEntry::FromJson(jinfo);
|
||||||
|
if (entry.has_value())
|
||||||
|
{
|
||||||
|
(*entry).Local = true;
|
||||||
|
entries.push_back(std::move(*entry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
LOG_WARNING("Error receiving data: %s", e.what());
|
||||||
|
}
|
||||||
|
Platform::Sleep(kReceiveDelayInMs);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
return entries;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<std::vector<ServerListEntry>> ServerList::FetchLocalServerListAsync() const
|
||||||
|
{
|
||||||
|
return std::async(std::launch::async, [&] {
|
||||||
|
// Get all possible LAN broadcast addresses
|
||||||
|
auto broadcastEndpoints = GetBroadcastAddresses();
|
||||||
|
|
||||||
|
// Spin off a fetch for each broadcast address
|
||||||
|
std::vector<std::future<std::vector<ServerListEntry>>> futures;
|
||||||
|
for (const auto& broadcastEndpoint : broadcastEndpoints)
|
||||||
{
|
{
|
||||||
LOG_WARNING("Error receiving data: %s", e.what());
|
auto f = FetchLocalServerListAsync(*broadcastEndpoint);
|
||||||
|
futures.push_back(std::move(f));
|
||||||
}
|
}
|
||||||
Platform::Sleep(kReceiveDelayInMs);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<std::vector<ServerListEntry>> ServerList::FetchLocalServerListAsync() const
|
// Wait and merge all results
|
||||||
{
|
std::vector<ServerListEntry> mergedEntries;
|
||||||
return std::async(std::launch::async, [&] {
|
for (auto& f : futures)
|
||||||
// Get all possible LAN broadcast addresses
|
{
|
||||||
auto broadcastEndpoints = GetBroadcastAddresses();
|
try
|
||||||
|
{
|
||||||
|
auto entries = f.get();
|
||||||
|
mergedEntries.insert(mergedEntries.begin(), entries.begin(), entries.end());
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Ignore any exceptions from a particular broadcast fetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergedEntries;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Spin off a fetch for each broadcast address
|
std::future<std::vector<ServerListEntry>> ServerList::FetchOnlineServerListAsync() const
|
||||||
std::vector<std::future<std::vector<ServerListEntry>>> futures;
|
{
|
||||||
for (const auto& broadcastEndpoint : broadcastEndpoints)
|
#ifdef DISABLE_HTTP
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
|
||||||
|
auto p = std::make_shared<std::promise<std::vector<ServerListEntry>>>();
|
||||||
|
auto f = p->get_future();
|
||||||
|
|
||||||
|
std::string masterServerUrl = kMasterServerURL;
|
||||||
|
if (!Config::Get().network.MasterServerUrl.empty())
|
||||||
{
|
{
|
||||||
auto f = FetchLocalServerListAsync(*broadcastEndpoint);
|
masterServerUrl = Config::Get().network.MasterServerUrl;
|
||||||
futures.push_back(std::move(f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait and merge all results
|
Http::Request request;
|
||||||
std::vector<ServerListEntry> mergedEntries;
|
request.url = std::move(masterServerUrl);
|
||||||
for (auto& f : futures)
|
request.method = Http::Method::GET;
|
||||||
{
|
request.header["Accept"] = "application/json";
|
||||||
|
Http::DoAsync(request, [p](Http::Response& response) -> void {
|
||||||
|
json_t root;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto entries = f.get();
|
if (response.status != Http::Status::Ok)
|
||||||
mergedEntries.insert(mergedEntries.begin(), entries.begin(), entries.end());
|
{
|
||||||
|
throw MasterServerException(STR_SERVER_LIST_NO_CONNECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
root = Json::FromString(response.body);
|
||||||
|
if (root.is_object())
|
||||||
|
{
|
||||||
|
auto jsonStatus = root["status"];
|
||||||
|
if (!jsonStatus.is_number_integer())
|
||||||
|
{
|
||||||
|
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status = Json::GetNumber<int32_t>(jsonStatus);
|
||||||
|
if (status != 200)
|
||||||
|
{
|
||||||
|
throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto jServers = root["servers"];
|
||||||
|
if (!jServers.is_array())
|
||||||
|
{
|
||||||
|
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ServerListEntry> entries;
|
||||||
|
for (auto& jServer : jServers)
|
||||||
|
{
|
||||||
|
if (jServer.is_object())
|
||||||
|
{
|
||||||
|
auto entry = ServerListEntry::FromJson(jServer);
|
||||||
|
if (entry.has_value())
|
||||||
|
{
|
||||||
|
entries.push_back(std::move(*entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p->set_value(entries);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
// Ignore any exceptions from a particular broadcast fetch
|
p->set_exception(std::current_exception());
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return mergedEntries;
|
return f;
|
||||||
});
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
std::future<std::vector<ServerListEntry>> ServerList::FetchOnlineServerListAsync() const
|
|
||||||
{
|
|
||||||
#ifdef DISABLE_HTTP
|
|
||||||
return {};
|
|
||||||
#else
|
|
||||||
|
|
||||||
auto p = std::make_shared<std::promise<std::vector<ServerListEntry>>>();
|
|
||||||
auto f = p->get_future();
|
|
||||||
|
|
||||||
std::string masterServerUrl = kMasterServerURL;
|
|
||||||
if (!Config::Get().network.MasterServerUrl.empty())
|
|
||||||
{
|
|
||||||
masterServerUrl = Config::Get().network.MasterServerUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Http::Request request;
|
uint32_t ServerList::GetTotalPlayerCount() const
|
||||||
request.url = std::move(masterServerUrl);
|
{
|
||||||
request.method = Http::Method::GET;
|
return std::accumulate(_serverEntries.begin(), _serverEntries.end(), 0, [](uint32_t acc, const ServerListEntry& entry) {
|
||||||
request.header["Accept"] = "application/json";
|
return acc + entry.Players;
|
||||||
Http::DoAsync(request, [p](Http::Response& response) -> void {
|
});
|
||||||
json_t root;
|
}
|
||||||
try
|
|
||||||
{
|
|
||||||
if (response.status != Http::Status::Ok)
|
|
||||||
{
|
|
||||||
throw MasterServerException(STR_SERVER_LIST_NO_CONNECTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
root = Json::FromString(response.body);
|
const char* MasterServerException::what() const noexcept
|
||||||
if (root.is_object())
|
{
|
||||||
{
|
static std::string localisedStatusText = LanguageGetString(StatusText);
|
||||||
auto jsonStatus = root["status"];
|
return localisedStatusText.c_str();
|
||||||
if (!jsonStatus.is_number_integer())
|
}
|
||||||
{
|
} // namespace OpenRCT2::Network
|
||||||
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto status = Json::GetNumber<int32_t>(jsonStatus);
|
|
||||||
if (status != 200)
|
|
||||||
{
|
|
||||||
throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto jServers = root["servers"];
|
|
||||||
if (!jServers.is_array())
|
|
||||||
{
|
|
||||||
throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ServerListEntry> entries;
|
|
||||||
for (auto& jServer : jServers)
|
|
||||||
{
|
|
||||||
if (jServer.is_object())
|
|
||||||
{
|
|
||||||
auto entry = ServerListEntry::FromJson(jServer);
|
|
||||||
if (entry.has_value())
|
|
||||||
{
|
|
||||||
entries.push_back(std::move(*entry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p->set_value(entries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
p->set_exception(std::current_exception());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return f;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ServerList::GetTotalPlayerCount() const
|
|
||||||
{
|
|
||||||
return std::accumulate(_serverEntries.begin(), _serverEntries.end(), 0, [](uint32_t acc, const ServerListEntry& entry) {
|
|
||||||
return acc + entry.Players;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* MasterServerException::what() const noexcept
|
|
||||||
{
|
|
||||||
static std::string localisedStatusText = LanguageGetString(StatusText);
|
|
||||||
return localisedStatusText.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,68 +18,71 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct INetworkEndpoint;
|
namespace OpenRCT2::Network
|
||||||
|
|
||||||
struct ServerListEntry
|
|
||||||
{
|
{
|
||||||
std::string Address;
|
struct INetworkEndpoint;
|
||||||
std::string Name;
|
|
||||||
std::string Description;
|
|
||||||
std::string Version;
|
|
||||||
bool RequiresPassword{};
|
|
||||||
bool Favourite{};
|
|
||||||
uint8_t Players{};
|
|
||||||
uint8_t MaxPlayers{};
|
|
||||||
bool Local{};
|
|
||||||
|
|
||||||
int32_t CompareTo(const ServerListEntry& other) const;
|
struct ServerListEntry
|
||||||
bool IsVersionValid() const noexcept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a ServerListEntry object from a JSON object
|
|
||||||
*
|
|
||||||
* @param json JSON data source - must be object type
|
|
||||||
* @return A NetworkGroup object
|
|
||||||
* @note json is deliberately left non-const: json_t behaviour changes when const
|
|
||||||
*/
|
|
||||||
static std::optional<ServerListEntry> FromJson(json_t& server);
|
|
||||||
};
|
|
||||||
|
|
||||||
class ServerList
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
std::vector<ServerListEntry> _serverEntries;
|
|
||||||
|
|
||||||
void Sort();
|
|
||||||
std::vector<ServerListEntry> ReadFavourites() const;
|
|
||||||
bool WriteFavourites(const std::vector<ServerListEntry>& entries) const;
|
|
||||||
std::future<std::vector<ServerListEntry>> FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ServerListEntry& GetServer(size_t index);
|
|
||||||
size_t GetCount() const;
|
|
||||||
void Add(const ServerListEntry& entry);
|
|
||||||
void AddRange(const std::vector<ServerListEntry>& entries);
|
|
||||||
void AddOrUpdateRange(const std::vector<ServerListEntry>& entries);
|
|
||||||
void Clear() noexcept;
|
|
||||||
|
|
||||||
void ReadAndAddFavourites();
|
|
||||||
void WriteFavourites() const;
|
|
||||||
|
|
||||||
std::future<std::vector<ServerListEntry>> FetchLocalServerListAsync() const;
|
|
||||||
std::future<std::vector<ServerListEntry>> FetchOnlineServerListAsync() const;
|
|
||||||
uint32_t GetTotalPlayerCount() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MasterServerException : public std::exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
StringId StatusText;
|
|
||||||
|
|
||||||
MasterServerException(StringId statusText)
|
|
||||||
: StatusText(statusText)
|
|
||||||
{
|
{
|
||||||
}
|
std::string Address;
|
||||||
|
std::string Name;
|
||||||
|
std::string Description;
|
||||||
|
std::string Version;
|
||||||
|
bool RequiresPassword{};
|
||||||
|
bool Favourite{};
|
||||||
|
uint8_t Players{};
|
||||||
|
uint8_t MaxPlayers{};
|
||||||
|
bool Local{};
|
||||||
|
|
||||||
const char* what() const noexcept override;
|
int32_t CompareTo(const ServerListEntry& other) const;
|
||||||
};
|
bool IsVersionValid() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ServerListEntry object from a JSON object
|
||||||
|
*
|
||||||
|
* @param json JSON data source - must be object type
|
||||||
|
* @return A NetworkGroup object
|
||||||
|
* @note json is deliberately left non-const: json_t behaviour changes when const
|
||||||
|
*/
|
||||||
|
static std::optional<ServerListEntry> FromJson(json_t& server);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ServerList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<ServerListEntry> _serverEntries;
|
||||||
|
|
||||||
|
void Sort();
|
||||||
|
std::vector<ServerListEntry> ReadFavourites() const;
|
||||||
|
bool WriteFavourites(const std::vector<ServerListEntry>& entries) const;
|
||||||
|
std::future<std::vector<ServerListEntry>> FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ServerListEntry& GetServer(size_t index);
|
||||||
|
size_t GetCount() const;
|
||||||
|
void Add(const ServerListEntry& entry);
|
||||||
|
void AddRange(const std::vector<ServerListEntry>& entries);
|
||||||
|
void AddOrUpdateRange(const std::vector<ServerListEntry>& entries);
|
||||||
|
void Clear() noexcept;
|
||||||
|
|
||||||
|
void ReadAndAddFavourites();
|
||||||
|
void WriteFavourites() const;
|
||||||
|
|
||||||
|
std::future<std::vector<ServerListEntry>> FetchLocalServerListAsync() const;
|
||||||
|
std::future<std::vector<ServerListEntry>> FetchOnlineServerListAsync() const;
|
||||||
|
uint32_t GetTotalPlayerCount() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MasterServerException : public std::exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StringId StatusText;
|
||||||
|
|
||||||
|
MasterServerException(StringId statusText)
|
||||||
|
: StatusText(statusText)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* what() const noexcept override;
|
||||||
|
};
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,93 +13,96 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class SocketStatus
|
namespace OpenRCT2::Network
|
||||||
{
|
{
|
||||||
Closed,
|
enum class SocketStatus
|
||||||
Waiting,
|
|
||||||
Resolving,
|
|
||||||
Connecting,
|
|
||||||
Connected,
|
|
||||||
Listening,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class NetworkReadPacket : int32_t
|
|
||||||
{
|
|
||||||
Success,
|
|
||||||
NoData,
|
|
||||||
MoreData,
|
|
||||||
Disconnected
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an address and port.
|
|
||||||
*/
|
|
||||||
struct INetworkEndpoint
|
|
||||||
{
|
|
||||||
virtual ~INetworkEndpoint()
|
|
||||||
{
|
{
|
||||||
}
|
Closed,
|
||||||
|
Waiting,
|
||||||
|
Resolving,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Listening,
|
||||||
|
};
|
||||||
|
|
||||||
virtual std::string GetHostname() const = 0;
|
enum class NetworkReadPacket : int32_t
|
||||||
};
|
{
|
||||||
|
Success,
|
||||||
|
NoData,
|
||||||
|
MoreData,
|
||||||
|
Disconnected
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a TCP socket / connection or listener.
|
* Represents an address and port.
|
||||||
*/
|
*/
|
||||||
struct ITcpSocket
|
struct INetworkEndpoint
|
||||||
{
|
{
|
||||||
public:
|
virtual ~INetworkEndpoint()
|
||||||
virtual ~ITcpSocket() = default;
|
{
|
||||||
|
}
|
||||||
|
|
||||||
virtual SocketStatus GetStatus() const = 0;
|
virtual std::string GetHostname() const = 0;
|
||||||
virtual const char* GetError() const = 0;
|
};
|
||||||
virtual const char* GetHostName() const = 0;
|
|
||||||
virtual std::string GetIpAddress() const = 0;
|
|
||||||
|
|
||||||
virtual void Listen(uint16_t port) = 0;
|
/**
|
||||||
virtual void Listen(const std::string& address, uint16_t port) = 0;
|
* Represents a TCP socket / connection or listener.
|
||||||
[[nodiscard]] virtual std::unique_ptr<ITcpSocket> Accept() = 0;
|
*/
|
||||||
|
struct ITcpSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ITcpSocket() = default;
|
||||||
|
|
||||||
virtual void Connect(const std::string& address, uint16_t port) = 0;
|
virtual SocketStatus GetStatus() const = 0;
|
||||||
virtual void ConnectAsync(const std::string& address, uint16_t port) = 0;
|
virtual const char* GetError() const = 0;
|
||||||
|
virtual const char* GetHostName() const = 0;
|
||||||
|
virtual std::string GetIpAddress() const = 0;
|
||||||
|
|
||||||
virtual size_t SendData(const void* buffer, size_t size) = 0;
|
virtual void Listen(uint16_t port) = 0;
|
||||||
virtual NetworkReadPacket ReceiveData(void* buffer, size_t size, size_t* sizeReceived) = 0;
|
virtual void Listen(const std::string& address, uint16_t port) = 0;
|
||||||
|
[[nodiscard]] virtual std::unique_ptr<ITcpSocket> Accept() = 0;
|
||||||
|
|
||||||
virtual void SetNoDelay(bool noDelay) = 0;
|
virtual void Connect(const std::string& address, uint16_t port) = 0;
|
||||||
|
virtual void ConnectAsync(const std::string& address, uint16_t port) = 0;
|
||||||
|
|
||||||
virtual void Finish() = 0;
|
virtual size_t SendData(const void* buffer, size_t size) = 0;
|
||||||
virtual void Disconnect() = 0;
|
virtual NetworkReadPacket ReceiveData(void* buffer, size_t size, size_t* sizeReceived) = 0;
|
||||||
virtual void Close() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
virtual void SetNoDelay(bool noDelay) = 0;
|
||||||
* Represents a UDP socket / listener.
|
|
||||||
*/
|
|
||||||
struct IUdpSocket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~IUdpSocket() = default;
|
|
||||||
|
|
||||||
virtual SocketStatus GetStatus() const = 0;
|
virtual void Finish() = 0;
|
||||||
virtual const char* GetError() const = 0;
|
virtual void Disconnect() = 0;
|
||||||
virtual const char* GetHostName() const = 0;
|
virtual void Close() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
virtual void Listen(uint16_t port) = 0;
|
/**
|
||||||
virtual void Listen(const std::string& address, uint16_t port) = 0;
|
* Represents a UDP socket / listener.
|
||||||
|
*/
|
||||||
|
struct IUdpSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IUdpSocket() = default;
|
||||||
|
|
||||||
virtual size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) = 0;
|
virtual SocketStatus GetStatus() const = 0;
|
||||||
virtual size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) = 0;
|
virtual const char* GetError() const = 0;
|
||||||
virtual NetworkReadPacket ReceiveData(
|
virtual const char* GetHostName() const = 0;
|
||||||
void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr<INetworkEndpoint>* sender)
|
|
||||||
= 0;
|
|
||||||
|
|
||||||
virtual void Close() = 0;
|
virtual void Listen(uint16_t port) = 0;
|
||||||
};
|
virtual void Listen(const std::string& address, uint16_t port) = 0;
|
||||||
|
|
||||||
[[nodiscard]] std::unique_ptr<ITcpSocket> CreateTcpSocket();
|
virtual size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) = 0;
|
||||||
[[nodiscard]] std::unique_ptr<IUdpSocket> CreateUdpSocket();
|
virtual size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) = 0;
|
||||||
[[nodiscard]] std::vector<std::unique_ptr<INetworkEndpoint>> GetBroadcastAddresses();
|
virtual NetworkReadPacket ReceiveData(
|
||||||
|
void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr<INetworkEndpoint>* sender)
|
||||||
|
= 0;
|
||||||
|
|
||||||
|
virtual void Close() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<ITcpSocket> CreateTcpSocket();
|
||||||
|
[[nodiscard]] std::unique_ptr<IUdpSocket> CreateUdpSocket();
|
||||||
|
[[nodiscard]] std::vector<std::unique_ptr<INetworkEndpoint>> GetBroadcastAddresses();
|
||||||
|
} // namespace OpenRCT2::Network
|
||||||
|
|
||||||
namespace OpenRCT2::Convert
|
namespace OpenRCT2::Convert
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user