mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
Host bans, and a quieter direct-connection log
Host bans: the backend's new banned_hosts list (players the team has stopped from hosting lobbies) is read with the other identity lists. A listed player's own game refuses to host and stops a lobby already running. Since that is theirs to change, everyone else's game does the rest: the lobby browser leaves their lobbies out, and the game will not join one by code or stay in one. They can still play on servers and in other players' lobbies. The browser now also leaves out lobbies hosted by globally banned players. Server log: "Direct connection asked for by ..." is written once a minute per player, with a count of the repeats. A player waiting for a place on a full server asks again about every second, and each ask was a line.
This commit is contained in:
1 parent
20bd777b4c
commit
e98169a2b6
10 files changed
+54
-7
No files matched your search
@@ -936,6 +936,7 @@ std::string command(std::string_view action, std::string_view argument, std::str
|
||||
};
|
||||
// Joining is for the host or the server to refuse (a server may opt out of the bans).
|
||||
if (action == "host" && reskate_banned(s.transport.status().local_id)) return refuse(std::string(banned_notice));
|
||||
if (action == "host" && banned_host(s.transport.status().local_id)) return refuse(std::string(banned_host_notice));
|
||||
if (action == "host") {
|
||||
const auto space = argument.find(' ');
|
||||
if (space != std::string_view::npos) {
|
||||
@@ -975,6 +976,7 @@ std::string command(std::string_view action, std::string_view argument, std::str
|
||||
if (!invitation)
|
||||
return refuse("Invalid join code. Paste the complete SteamID-session code from the host.");
|
||||
if (blocked_server(invitation->steam_id)) return refuse(std::string(blocked_server_notice));
|
||||
if (banned_host(invitation->steam_id)) return refuse(std::string(banned_host_lobby_notice));
|
||||
}
|
||||
stop(s, "Starting multiplayer...");
|
||||
s.chat.clear(); // A new session starts with an empty chat.
|
||||
|
||||
@@ -215,7 +215,7 @@ struct Session {
|
||||
std::mutex request_mutex;
|
||||
std::deque<std::unique_ptr<PrivateRequest>> requests;
|
||||
SteamTransport transport;
|
||||
SteamLobbies lobbies{make_steam_lobby_api()};
|
||||
SteamLobbies lobbies{make_steam_lobby_api(), [](std::uint64_t host) { return banned_host(host) || reskate_banned(host); }};
|
||||
SteamServerBrowser servers;
|
||||
// Guest of a dedicated server: whether the roster lists us as an admin,
|
||||
// and the server's voice range from it.
|
||||
|
||||
@@ -454,6 +454,15 @@ void networking(Session &s, const NativeFrame &local, std::uint64_t now) {
|
||||
stop(s, "This host is banned from ReSkate multiplayer.");
|
||||
return;
|
||||
}
|
||||
// The same for a player who may not host (banned_host): theirs stops, and nobody stays in it.
|
||||
if (s.mode == Mode::host && banned_host(s.transport.status().local_id)) {
|
||||
stop(s, std::string(banned_host_notice));
|
||||
return;
|
||||
}
|
||||
if (s.mode == Mode::join && banned_host(s.host_id)) {
|
||||
stop(s, std::string(banned_host_lobby_notice));
|
||||
return;
|
||||
}
|
||||
for (const auto &link : links) {
|
||||
if (s.mode == Mode::host && s.banned.contains(link.id)) {
|
||||
s.transport.disconnect(link.id, "You were kicked from this session.");
|
||||
|
||||
@@ -254,7 +254,7 @@ void SteamLobbies::tick(std::uint64_t now) {
|
||||
for (std::uint32_t i = 0; i < std::min(result.count, 50U); ++i) {
|
||||
const auto id = api_->at(static_cast<int>(i));
|
||||
const auto row = read_lobby(id, 0, [&](const char *key) { return api_->data(id, key); });
|
||||
if (row && !text::contains_bad_words(row->name) &&
|
||||
if (row && !text::contains_bad_words(row->name) && !(hidden_host_ && hidden_host_(row->owner)) &&
|
||||
std::none_of(state_.rows.begin(), state_.rows.end(),
|
||||
[id](const auto &v) { return v.id == id; }))
|
||||
state_.rows.push_back(*row);
|
||||
|
||||
@@ -38,7 +38,9 @@ struct LobbyStatus {
|
||||
};
|
||||
class SteamLobbies {
|
||||
public:
|
||||
explicit SteamLobbies(std::unique_ptr<LobbyApi> api) : api_(std::move(api)) {}
|
||||
// `hidden_host`: whether a player's lobbies are left out of the browser (the team's bans).
|
||||
explicit SteamLobbies(std::unique_ptr<LobbyApi> api, std::function<bool(std::uint64_t)> hidden_host = {})
|
||||
: api_(std::move(api)), hidden_host_(std::move(hidden_host)) {}
|
||||
void host(const std::string &code, unsigned capacity = multiplayer_lobby_player_limit, bool password_required = false,
|
||||
std::string_view name = {});
|
||||
void update_host(bool ready, unsigned players, std::string_view map, std::uint64_t now);
|
||||
@@ -57,6 +59,7 @@ class SteamLobbies {
|
||||
bool cancelled{};
|
||||
};
|
||||
std::unique_ptr<LobbyApi> api_;
|
||||
std::function<bool(std::uint64_t)> hidden_host_;
|
||||
LobbyStatus state_;
|
||||
Pending creating_, searching_, joining_;
|
||||
bool host_wanted_{}, failed_{}, ready_{}, password_required_{};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
@@ -139,6 +140,9 @@ struct SteamTransport::Impl {
|
||||
note_direct("Direct connection to the server did not come up (" + why + "); using Steam's relays.");
|
||||
}
|
||||
std::string direct_note; // how the last direct attempt went
|
||||
// When each player's direct connection was last noted, and how often they asked since.
|
||||
struct DirectAsked { std::uint64_t since{}; unsigned again{}; };
|
||||
std::unordered_map<std::uint64_t, DirectAsked> direct_asked;
|
||||
std::vector<std::string> direct_notes; // take_direct_notes
|
||||
void note_direct(std::string text) {
|
||||
if (direct_notes.size() < 64) direct_notes.push_back(std::move(text));
|
||||
@@ -705,7 +709,19 @@ void SteamTransport::poll() {
|
||||
if (existing != p.links.end() && existing->second.handle == event.m_hConn)
|
||||
continue;
|
||||
const bool direct = p.direct_listener && event.m_info.m_hListenSocket == p.direct_listener;
|
||||
if (direct) p.note_direct("Direct connection asked for by " + std::to_string(id) + ".");
|
||||
// Once a minute a player: one waiting for a place on a full server asks again every
|
||||
// second or so, and each ask used to be a line.
|
||||
if (direct) {
|
||||
if (p.direct_asked.size() > 512) std::erase_if(p.direct_asked, [&](const auto &asked) { return now - asked.second.since >= 60000; });
|
||||
auto &asked = p.direct_asked[id];
|
||||
if (!asked.since || now - asked.since >= 60000) {
|
||||
p.note_direct("Direct connection asked for by " + std::to_string(id) +
|
||||
(asked.again ? " (and " + std::to_string(asked.again) + " more times in the last minute)." : "."));
|
||||
asked = {now, 0};
|
||||
} else {
|
||||
++asked.again;
|
||||
}
|
||||
}
|
||||
// (A connection by address names a Steam ID only when Steam has vouched for it.)
|
||||
if ((event.m_info.m_hListenSocket != p.listener && !direct) || !p.listener || !id || id == p.state.local_id ||
|
||||
(!p.state.hosting && !p.allowed.contains(id)) || p.links.size() >= p.capacity - 1 ||
|
||||
|
||||
@@ -62,6 +62,13 @@ void parsing() {
|
||||
blocks[static_cast<std::size_t>(L::official_server)].empty() && servers[static_cast<std::size_t>(L::blocked_server)].empty(),
|
||||
"The blocked server list was not read");
|
||||
check(rejected(R"({"categories":{},"blocked_servers":["76561198000000001"]})"), "A player was accepted as a blocked server");
|
||||
// Players who may not host: players' IDs, their own list, and none when the answer has none.
|
||||
const auto no_hosts = parse_identity_lists(R"({"categories":{},"banned":["76561198000000001"],"banned_hosts":["76561198000000007","76561198000000003"]})");
|
||||
check(no_hosts[static_cast<std::size_t>(L::banned_host)] == std::vector<std::uint64_t>{76561198000000003ULL, 76561198000000007ULL} &&
|
||||
no_hosts[static_cast<std::size_t>(L::banned)] == std::vector<std::uint64_t>{76561198000000001ULL} &&
|
||||
blocks[static_cast<std::size_t>(L::banned_host)].empty(),
|
||||
"The list of players banned from hosting was not read");
|
||||
check(rejected(R"({"categories":{},"banned_hosts":["85568392924040001"]})"), "A server was accepted as a banned host");
|
||||
check(parse_server_tokens_required(R"({"categories":{},"server_tokens_required":true})") &&
|
||||
!parse_server_tokens_required(R"({"categories":{},"server_tokens_required":false})") &&
|
||||
!parse_server_tokens_required(R"({"categories":{}})") && !parse_server_tokens_required(R"({"server_tokens_required":"yes"})"),
|
||||
|
||||
@@ -43,6 +43,7 @@ bool clear_lobby_guest_objects() { ++simulated_guest_wipes; return true; }
|
||||
}
|
||||
namespace dingosdk {
|
||||
bool teleport_local_skater(const std::array<float, 3>&) { return true; }
|
||||
ControllerBindingsModel local_profile_controller_bindings() { return {}; }
|
||||
void update_board_lock(std::uintptr_t, std::uintptr_t, bool) noexcept {}
|
||||
void update_developer_hoodie(std::uintptr_t, std::uintptr_t, std::uint64_t, std::uint64_t, DeveloperHoodieState &,
|
||||
const multiplayer::MarkStyles &) noexcept {}
|
||||
|
||||
@@ -18,6 +18,7 @@ constexpr std::array<std::pair<std::string_view, IdentityList>, 5> categories{{
|
||||
constexpr auto banned = static_cast<std::size_t>(IdentityList::banned);
|
||||
constexpr auto servers = static_cast<std::size_t>(IdentityList::official_server);
|
||||
constexpr auto blocked = static_cast<std::size_t>(IdentityList::blocked_server);
|
||||
constexpr auto hosts = static_cast<std::size_t>(IdentityList::banned_host);
|
||||
|
||||
std::atomic<std::shared_ptr<const IdentityLists>> current;
|
||||
|
||||
@@ -65,6 +66,7 @@ IdentityLists parse_identity_lists(std::string_view json) {
|
||||
if (answer.contains("banned")) read(answer.at("banned"), lists[banned]);
|
||||
if (answer.contains("official_servers")) read(answer.at("official_servers"), lists[servers], server_id);
|
||||
if (answer.contains("blocked_servers")) read(answer.at("blocked_servers"), lists[blocked], any_server_id);
|
||||
if (answer.contains("banned_hosts")) read(answer.at("banned_hosts"), lists[hosts]);
|
||||
return lists;
|
||||
}
|
||||
bool parse_server_tokens_required(std::string_view json) {
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace dingosdk::multiplayer {
|
||||
// The last list is not of players: the dedicated servers the ReSkate team runs, which the
|
||||
// server browser puts first. A server is on it by the Steam ID its login token gives it.
|
||||
// And one of servers the team has blocked: the browser does not show them and the game does
|
||||
// not join them.
|
||||
enum class IdentityList : std::uint8_t { developer, homie, content_creator, centrix, banned, official_server, staff, blocked_server, count };
|
||||
// not join them. And one of players who may not host a lobby (banned_host, below).
|
||||
enum class IdentityList : std::uint8_t { developer, homie, content_creator, centrix, banned, official_server, staff, blocked_server, banned_host, count };
|
||||
using IdentityLists = std::array<std::vector<std::uint64_t>, static_cast<std::size_t>(IdentityList::count)>;
|
||||
inline constexpr std::string_view identity_lists_url = "https://api.reskate.dev/api/v1/steam-ids";
|
||||
|
||||
@@ -57,6 +57,13 @@ inline bool reskate_banned(std::uint64_t id) noexcept { return identity_listed(i
|
||||
inline bool official_server(std::uint64_t id) noexcept { return identity_listed(id, IdentityList::official_server); }
|
||||
inline bool blocked_server(std::uint64_t id) noexcept { return identity_listed(id, IdentityList::blocked_server); }
|
||||
inline constexpr std::string_view blocked_server_notice = "This server has been blocked by the ReSkate team.";
|
||||
// A player the team has stopped from hosting lobbies ("banned_hosts"); they still play on
|
||||
// servers and in other players' lobbies. Their own game does not host, and since that is theirs
|
||||
// to change, everyone else's does the rest: the lobby browser leaves their lobbies out and the
|
||||
// game does not join one or stay in one.
|
||||
inline bool banned_host(std::uint64_t id) noexcept { return identity_listed(id, IdentityList::banned_host); }
|
||||
inline constexpr std::string_view banned_host_notice = "You are banned from hosting ReSkate lobbies. You can still join servers and other players' lobbies.";
|
||||
inline constexpr std::string_view banned_host_lobby_notice = "This player has been banned from hosting lobbies by the ReSkate team.";
|
||||
// Whether the server browser shows only dedicated servers signed in with a Steam login token
|
||||
// (protocol.h: persistent_server_steam_id), which the backend switches on and off beside the
|
||||
// lists ("server_tokens_required"). Such a server keeps its Steam ID, so a block holds on it.
|
||||
@@ -70,7 +77,7 @@ bool parse_server_tokens_required(std::string_view json);
|
||||
inline constexpr std::string_view banned_notice = "You are banned from ReSkate multiplayer.";
|
||||
|
||||
// The API's answer, {"categories":{"dev":["7656119..."],"homie":[],...},"banned":[],
|
||||
// "official_servers":["8556839..."],"blocked_servers":[]}, as sorted lists. A category this build does not know
|
||||
// "official_servers":["8556839..."],"blocked_servers":[],"banned_hosts":[]}, as sorted lists. A category this build does not know
|
||||
// is ignored and a list the answer leaves out is empty; an entry that is not a player's
|
||||
// SteamID64 (for the servers: a token server's) throws.
|
||||
IdentityLists parse_identity_lists(std::string_view json);
|
||||
|
||||
Reference in new issue
Block a user