1
0
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:
Aaron van Geffen
2025-08-31 15:26:10 +02:00
parent d6e9f3d205
commit 6ea5959b2b
28 changed files with 7396 additions and 7311 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
{ {