Files
ReSkate/Extension/Multiplayer/developer_identity.cpp
T
AyeZeeBB 101e1af6e4 Official, staff and friends in the server browser; server tokens; Linux self-update
Server browser
- Official servers (the backend's "official_servers") sort first and are
  drawn in ReSkate blue
- Servers Steam friends are skating in sort next, drawn in green with the
  friends' names; searching a friend's name finds their server
- A player in a session they joined can join another server straight from
  the list; hosts still end their session first, and a Steam friend's join
  offer never replaces a session
- Joining a dedicated server from the list records it as the player's
  public session, so friends see it and the row shows HERE
- Blocked servers (the backend's "blocked_servers") are hidden and cannot
  be joined, by code either
- When the backend sets "server_tokens_required", dedicated servers that
  signed in without a login token are hidden, except on the player's own
  network
- A search that gets no internet list from Steam keeps the last list for
  two minutes, and waits six seconds before treating it as empty
Roles
- New Staff tag from the backend's "staff" list: green name, badge and
  item animation, ranked after Dev
- Staff can pick a rainbow for their items on the Special page; it is sent
  as a solid colour older builds can show, so the protocol is unchanged
Dedicated server
- steam_token: sign in with a Steam game server login token and keep the
  same Steam ID every start; empty signs in anonymously as before
- A listed server without a token says so at startup, and when the token
  rule starts or stops hiding it
- Linux servers update themselves from the release's "server_linux"
  tarball, with the machine's curl and tar, pinned by SHA-256, and carry
  on in the same process
Launcher
- Only create the MiniDumpWriteDump API-set shim under Wine/Proton
2026-10-07 00:19:56 -05:00

88 lines
4.6 KiB
C++

#include "developer_identity.h"
#include "Engine/Core/Json/json.h"
#include "Extension/Multiplayer/Net/protocol.h"
#include <algorithm>
#include <atomic>
#include <charconv>
#include <memory>
#include <stdexcept>
// The lists themselves, shared by the game and the dedicated server. Each reads
// them from the backend its own way: developer_identity_fetch.cpp, Server/global_bans.cpp.
namespace dingosdk::multiplayer {
namespace {
// The API's name for each category of players. The ban list and the servers come beside them.
constexpr std::array<std::pair<std::string_view, IdentityList>, 5> categories{{
{"dev", IdentityList::developer}, {"staff", IdentityList::staff}, {"homie", IdentityList::homie},
{"content_creator", IdentityList::content_creator}, {"centrix", IdentityList::centrix}}};
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);
std::atomic<std::shared_ptr<const IdentityLists>> current;
// A player's SteamID64 is 76561197960265728 plus a 32-bit account number, and
// nobody has account 0. The API sends them as strings.
std::optional<std::uint64_t> number(const Json &entry) {
std::uint64_t id{};
if (!entry.is_string()) return std::nullopt;
const auto &text = entry.string();
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), id);
return parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size() ? std::optional(id) : std::nullopt;
}
std::uint64_t steam_id(const Json &entry) {
constexpr std::uint64_t base = 76561197960265728ULL, last = base + 0xffffffffULL;
if (const auto id = number(entry); id && *id > base && *id <= last) return *id;
throw std::runtime_error("an entry is not a player's SteamID64");
}
// Only a server signed in with a login token keeps its ID; an anonymous one's is gone at its
// next start, so listing it would mean nothing.
std::uint64_t server_id(const Json &entry) {
if (const auto id = number(entry); id && persistent_server_steam_id(*id)) return *id;
throw std::runtime_error("an entry is not the SteamID64 of a server with a login token");
}
// A block can name an anonymous server too: its ID is all there is to hold on to.
std::uint64_t any_server_id(const Json &entry) {
if (const auto id = number(entry); id && game_server_steam_id(*id)) return *id;
throw std::runtime_error("an entry is not the SteamID64 of a server");
}
} // namespace
IdentityLists parse_identity_lists(std::string_view json) {
const auto answer = Json::parse(json);
if (!answer.is_object() || !answer.contains("categories") || !answer.at("categories").is_object())
throw std::runtime_error("the answer has no categories");
const auto &listed = answer.at("categories");
IdentityLists lists;
const auto read = [](const Json &entries, std::vector<std::uint64_t> &ids, std::uint64_t (*id)(const Json &) = steam_id) {
if (!entries.is_array()) throw std::runtime_error("a list of players is not a list");
for (const auto &entry : entries) ids.push_back(id(entry));
std::sort(ids.begin(), ids.end());
ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
};
for (const auto &[name, list] : categories)
if (listed.contains(name)) read(listed.at(name), lists[static_cast<std::size_t>(list)]);
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);
return lists;
}
bool parse_server_tokens_required(std::string_view json) {
const auto answer = Json::parse(json);
return answer.is_object() && answer.contains("server_tokens_required") && answer.at("server_tokens_required").is_boolean() &&
answer.at("server_tokens_required").get<bool>();
}
bool publish_identity_lists(IdentityLists lists) {
const auto previous = current.load();
if (previous && *previous == lists) return false;
current.store(std::make_shared<const IdentityLists>(std::move(lists)));
return true;
}
bool identity_listed(std::uint64_t id, IdentityList list) noexcept {
const auto lists = current.load();
if (!id || !lists || list >= IdentityList::count) return false;
const auto &ids = (*lists)[static_cast<std::size_t>(list)];
return std::binary_search(ids.begin(), ids.end(), id);
}
} // namespace dingosdk::multiplayer