[ServerList]: Check for duplicates (#820)

This commit is contained in:
Edo 2023-03-09 18:39:10 +00:00 committed by GitHub
parent a1112ed292
commit c71eddc5a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 160 additions and 138 deletions

View File

@ -471,7 +471,7 @@ namespace Components
} }
else else
{ {
const auto level = static_cast<uint32_t>(atoi(params->get(1))); const auto level = std::strtoul(params->get(1), nullptr, 10);
Auth::IncreaseSecurityLevel(level); Auth::IncreaseSecurityLevel(level);
} }
}); });

View File

@ -167,8 +167,10 @@ namespace Components
Scheduler::Once([] Scheduler::Once([]
{ {
auto* ent = Game::SV_AddTestClient(); auto* ent = Game::SV_AddTestClient();
if (ent == nullptr) if (!ent)
{
return; return;
}
Scheduler::Once([ent] Scheduler::Once([ent]
{ {
@ -232,7 +234,7 @@ namespace Components
const auto* weapon = Game::Scr_GetString(0); const auto* weapon = Game::Scr_GetString(0);
if (weapon == nullptr || weapon[0] == '\0') if (!weapon || !*weapon)
{ {
g_botai[entref.entnum].weapon = 1; g_botai[entref.entnum].weapon = 1;
return; return;
@ -255,7 +257,7 @@ namespace Components
const auto* action = Game::Scr_GetString(0); const auto* action = Game::Scr_GetString(0);
if (action == nullptr) if (!action)
{ {
Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!"); Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!");
return; return;
@ -305,8 +307,10 @@ namespace Components
void Bots::BotAiAction(Game::client_t* cl) void Bots::BotAiAction(Game::client_t* cl)
{ {
if (cl->gentity == nullptr) if (!cl->gentity)
{
return; return;
}
const auto entnum = cl->gentity->s.number; const auto entnum = cl->gentity->s.number;

View File

@ -205,7 +205,7 @@ namespace Components::GSC
{ {
assert(ent); assert(ent);
if (ent->client == nullptr) if (!ent->client)
{ {
Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number));
return nullptr; return nullptr;

View File

@ -28,7 +28,7 @@ namespace Components::GSC
assert(entref.entnum < Game::MAX_GENTITIES); assert(entref.entnum < Game::MAX_GENTITIES);
auto* ent = &Game::g_entities[entref.entnum]; auto* ent = &Game::g_entities[entref.entnum];
if (ent->client == nullptr) if (!ent->client)
{ {
Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum)); Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum));
return nullptr; return nullptr;

View File

@ -34,7 +34,7 @@ namespace Components::GSC
const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable; const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable;
if (table == nullptr) if (!table)
{ {
Game::Scr_ParamError(0, Utils::String::VA("%s does not exist", fileName)); Game::Scr_ParamError(0, Utils::String::VA("%s does not exist", fileName));
return; return;

View File

@ -14,7 +14,7 @@ namespace Components::GSC
const auto* key = Game::Scr_GetString(0); const auto* key = Game::Scr_GetString(0);
const auto* value = Game::Scr_GetString(1); const auto* value = Game::Scr_GetString(1);
if (key == nullptr || value == nullptr) if (!key || !value)
{ {
Game::Scr_Error("^1StorageSet: Illegal parameters!"); Game::Scr_Error("^1StorageSet: Illegal parameters!");
return; return;
@ -27,7 +27,7 @@ namespace Components::GSC
{ {
const auto* key = Game::Scr_GetString(0); const auto* key = Game::Scr_GetString(0);
if (key == nullptr) if (!key)
{ {
Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!"); Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!");
return; return;
@ -46,7 +46,7 @@ namespace Components::GSC
{ {
const auto* key = Game::Scr_GetString(0); const auto* key = Game::Scr_GetString(0);
if (key == nullptr) if (!key)
{ {
Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!"); Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!");
return; return;
@ -65,7 +65,7 @@ namespace Components::GSC
{ {
const auto* key = Game::Scr_GetString(0); const auto* key = Game::Scr_GetString(0);
if (key == nullptr) if (!key)
{ {
Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!"); Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!");
return; return;

View File

@ -52,7 +52,7 @@ namespace Components::GSC
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Game::GetPlayerEntity(entref);
const auto* name = Game::Scr_GetString(0); const auto* name = Game::Scr_GetString(0);
if (name == nullptr) if (!name)
{ {
Game::Scr_ParamError(0, "^1SetName: Illegal parameter!"); Game::Scr_ParamError(0, "^1SetName: Illegal parameter!");
return; return;
@ -77,7 +77,7 @@ namespace Components::GSC
const auto* ent = Game::GetPlayerEntity(entref); const auto* ent = Game::GetPlayerEntity(entref);
const auto* clanName = Game::Scr_GetString(0); const auto* clanName = Game::Scr_GetString(0);
if (clanName == nullptr) if (!clanName)
{ {
Game::Scr_ParamError(0, "^1SetClanTag: Illegal parameter!"); Game::Scr_ParamError(0, "^1SetClanTag: Illegal parameter!");
return; return;

View File

@ -96,7 +96,7 @@ namespace Components
++(*Game::com_errorPrintsCount); ++(*Game::com_errorPrintsCount);
MessagePrint(channel, msg); MessagePrint(channel, msg);
if (Game::cls->uiStarted != 0 && (*Game::com_fixedConsolePosition == 0)) if (Game::cls->uiStarted && (*Game::com_fixedConsolePosition == 0))
{ {
Game::CL_ConsoleFixPosition(); Game::CL_ConsoleFixPosition();
} }

View File

@ -55,7 +55,7 @@ namespace Components
} }
const auto* dvar = Game::Dvar_FindVar(name); const auto* dvar = Game::Dvar_FindVar(name);
if (dvar == nullptr) if (!dvar)
{ {
// If it's not a dvar let it continue // If it's not a dvar let it continue
Game::CL_SelectStringTableEntryInDvar_f(); Game::CL_SelectStringTableEntryInDvar_f();

View File

@ -194,7 +194,7 @@ namespace Components
auto* list = GetList(); auto* list = GetList();
if (!list) return; if (!list) return;
std::vector tempList(*list); const std::vector tempList(*list);
if (tempList.empty()) if (tempList.empty())
{ {
@ -209,7 +209,7 @@ namespace Components
RefreshContainer.sendCount = 0; RefreshContainer.sendCount = 0;
RefreshContainer.sentCount = 0; RefreshContainer.sentCount = 0;
for (auto& server : tempList) for (const auto& server : tempList)
{ {
InsertRequest(server.addr); InsertRequest(server.addr);
} }
@ -274,13 +274,7 @@ namespace Components
void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
Dvar::Var("ui_serverSelected").set(false); Dvar::Var("ui_serverSelected").set(false);
//Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0");
#if 0
OnlineList.clear();
OfflineList.clear();
FavouriteList.clear();
#endif
auto* list = GetList(); auto* list = GetList();
if (list) list->clear(); if (list) list->clear();
@ -320,7 +314,7 @@ namespace Components
RefreshContainer.awaitTime = Game::Sys_Milliseconds(); RefreshContainer.awaitTime = Game::Sys_Milliseconds();
RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort));
Logger::Print("Sending serverlist request to master\n"); Logger::Print("Sending server list request to master\n");
Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL)); Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL));
} }
else if (IsFavouriteList()) else if (IsFavouriteList())
@ -417,8 +411,6 @@ namespace Components
if (list) list->clear(); if (list) list->clear();
RefreshVisibleListInternal(UIScript::Token(), nullptr); RefreshVisibleListInternal(UIScript::Token(), nullptr);
Game::ShowMessageBox("Server removed from favourites.", "Success");
} }
void ServerList::LoadFavourties() void ServerList::LoadFavourties()
@ -471,7 +463,7 @@ namespace Components
container.sent = false; container.sent = false;
container.target = address; container.target = address;
bool alreadyInserted = false; auto alreadyInserted = false;
for (auto &server : RefreshContainer.servers) for (auto &server : RefreshContainer.servers)
{ {
if (server.target == container.target) if (server.target == container.target)
@ -510,99 +502,104 @@ namespace Components
for (auto i = RefreshContainer.servers.begin(); i != RefreshContainer.servers.end();) for (auto i = RefreshContainer.servers.begin(); i != RefreshContainer.servers.end();)
{ {
// Our desired server // Our desired server
if ((i->target == address) && i->sent) if ((i->target != address) || !i->sent)
{ {
// Challenge did not match ++i;
if (i->challenge != info.get("challenge")) continue;
}
// Challenge did not match
if (i->challenge != info.get("challenge"))
{
// Shall we remove the server from the queue?
// Better not, it might send a second response with the correct challenge.
// This might happen when users refresh twice (or more often) in a short period of time
break;
}
ServerInfo server;
server.hostname = info.get("hostname");
server.mapname = info.get("mapname");
server.gametype = info.get("gametype");
server.shortversion = info.get("shortversion");
server.mod = info.get("fs_game");
server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10);
server.clients = std::strtol(info.get("clients").data(), nullptr, 10);
server.bots = std::strtol(info.get("bots").data(), nullptr, 10);
server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10);
server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10);
server.password = info.get("isPrivate") == "1"s;
server.aimassist = info.get("aimAssist") == "1";
server.voice = info.get("voiceChat") == "1"s;
server.hardcore = info.get("hc") == "1"s;
server.svRunning = info.get("sv_running") == "1"s;
server.ping = (Game::Sys_Milliseconds() - i->sendTime);
server.addr = address;
std::hash<ServerInfo> hashFn;
server.hash = hashFn(server);
server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname);
server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname);
server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype);
server.mod = TextRenderer::StripMaterialTextIcons(server.mod);
// Remove server from queue
i = RefreshContainer.servers.erase(i);
// Servers with more than 18 players or less than 0 players are faking for sure
// So lets ignore those
if (static_cast<std::size_t>(server.clients) > Game::MAX_CLIENTS || static_cast<std::size_t>(server.maxClients) > Game::MAX_CLIENTS)
{
return;
}
// Check if already inserted and remove
auto* list = GetList();
if (!list) return;
std::size_t k = 0;
for (auto j = list->begin(); j != list->end(); ++k)
{
if (j->addr == address)
{ {
// Shall we remove the server from the queue? j = list->erase(j);
// Better not, it might send a second response with the correct challenge.
// This might happen when users refresh twice (or more often) in a short period of time
break;
} }
else
ServerInfo server;
server.hostname = info.get("hostname");
server.mapname = info.get("mapname");
server.gametype = info.get("gametype");
server.shortversion = info.get("shortversion");
server.mod = info.get("fs_game");
server.matchType = std::strtol(info.get("matchtype").data(), nullptr, 10);
server.clients = std::strtol(info.get("clients").data(), nullptr, 10);
server.bots = std::strtol(info.get("bots").data(), nullptr, 10);
server.securityLevel = std::strtol(info.get("securityLevel").data(), nullptr, 10);
server.maxClients = std::strtol(info.get("sv_maxclients").data(), nullptr, 10);
server.password = info.get("isPrivate") == "1"s;
server.aimassist = info.get("aimAssist") == "1";
server.voice = info.get("voiceChat") == "1"s;
server.hardcore = info.get("hc") == "1"s;
server.svRunning = info.get("sv_running") == "1"s;
server.ping = (Game::Sys_Milliseconds() - i->sendTime);
server.addr = address;
server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname);
server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname);
server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype);
server.mod = TextRenderer::StripMaterialTextIcons(server.mod);
// Remove server from queue
i = RefreshContainer.servers.erase(i);
// Servers with more than 18 players or less than 0 players are faking for sure
// So lets ignore those
if (server.clients > 18 || server.maxClients > 18 || server.clients < 0 || server.maxClients < 0)
{ {
return; ++j;
} }
}
// Check if already inserted and remove // Also remove from visible list
auto* list = GetList(); for (auto j = VisibleList.begin(); j != VisibleList.end();)
if (!list) return; {
if (*j == k)
std::size_t k = 0;
for (auto j = list->begin(); j != list->end(); ++k)
{ {
if (j->addr == address) j = VisibleList.erase(j);
{
j = list->erase(j);
}
else
{
++j;
}
} }
else
// Also remove from visible list
for (auto j = VisibleList.begin(); j != VisibleList.end();)
{ {
if (*j == k) ++j;
{
j = VisibleList.erase(j);
}
else
{
++j;
}
} }
}
if (info.get("gamename") == "IW4"s && server.matchType if (info.get("gamename") == "IW4"s && server.matchType
#if !defined(DEBUG) && defined(VERSION_FILTER) #if !defined(DEBUG) && defined(VERSION_FILTER)
&& CompareVersion(server.shortversion, SHORTVERSION) && CompareVersion(server.shortversion, SHORTVERSION)
#endif #endif
) )
{
auto* lList = GetList();
if (lList)
{ {
auto* lList = GetList(); if (!IsServerDuplicate(lList, server))
if (lList)
{ {
lList->push_back(server); lList->push_back(server);
RefreshVisibleListInternal(UIScript::Token(), nullptr); RefreshVisibleListInternal(UIScript::Token(), nullptr);
} }
} }
} }
else
{
++i;
}
} }
} }
@ -626,7 +623,7 @@ namespace Components
} }
catch (const std::exception& ex) catch (const std::exception& ex)
{ {
Logger::Warning(Game::CON_CHANNEL_CONSOLEONLY, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]); Logger::PrintError(Game::CON_CHANNEL_ERROR, "{} while performing numeric comparison between {} and {}\n", ex.what(), subVersions1[i], subVersions2[i]);
return false; return false;
} }
} }
@ -634,6 +631,19 @@ namespace Components
return true; return true;
} }
bool ServerList::IsServerDuplicate(const std::vector<ServerInfo>* list, const ServerInfo& server)
{
for (auto l = list->begin(); l != list->end(); ++l)
{
if (l->hash == server.hash)
{
return true;
}
}
return false;
}
ServerList::ServerInfo* ServerList::GetCurrentServer() ServerList::ServerInfo* ServerList::GetCurrentServer()
{ {
return GetServer(CurrentServer); return GetServer(CurrentServer);
@ -730,6 +740,7 @@ namespace Components
} }
} }
const auto challenge = Utils::Cryptography::Rand::GenerateChallenge();
auto requestLimit = NETServerQueryLimit.get<int>(); auto requestLimit = NETServerQueryLimit.get<int>();
for (std::size_t i = 0; i < RefreshContainer.servers.size() && requestLimit > 0; ++i) for (std::size_t i = 0; i < RefreshContainer.servers.size() && requestLimit > 0; ++i)
{ {
@ -741,14 +752,11 @@ namespace Components
requestLimit--; requestLimit--;
server->sendTime = Game::Sys_Milliseconds(); server->sendTime = Game::Sys_Milliseconds();
server->challenge = Utils::Cryptography::Rand::GenerateChallenge(); server->challenge = challenge;
++RefreshContainer.sentCount; ++RefreshContainer.sentCount;
Network::SendCommand(server->target, "getinfo", server->challenge); Network::SendCommand(server->target, "getinfo", server->challenge);
// Display in the menu, like in CoD4 - Disabled to avoid spamming?
//Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Sent requests: %d/%d", ServerList::RefreshContainer.sentCount, ServerList::RefreshContainer.sendCount));
} }
UpdateVisibleInfo(); UpdateVisibleInfo();
@ -784,17 +792,17 @@ namespace Components
void ServerList::UpdateVisibleInfo() void ServerList::UpdateVisibleInfo()
{ {
static int servers = 0; static auto servers = 0;
static int players = 0; static auto players = 0;
static int bots = 0; static auto bots = 0;
auto list = GetList(); auto* list = GetList();
if (list) if (list)
{ {
int newSevers = list->size(); auto newSevers = static_cast<int>(list->size());
int newPlayers = 0; auto newPlayers = 0;
int newBots = 0; auto newBots = 0;
for (std::size_t i = 0; i < list->size(); ++i) for (std::size_t i = 0; i < list->size(); ++i)
{ {
@ -808,7 +816,7 @@ namespace Components
players = newPlayers; players = newPlayers;
bots = newBots; bots = newBots;
Localization::Set("MPUI_SERVERQUERIED", Utils::String::VA("Servers: %i\nPlayers: %i (%i)", servers, players, bots)); Localization::Set("MPUI_SERVERQUERIED", std::format("Servers: {}\nPlayers: {} ({})", servers, players, bots));
} }
} }
} }
@ -960,18 +968,6 @@ namespace Components
} }
}); });
#ifdef _DEBUG
Command::Add("playerCount", [](Command::Params*)
{
auto count = 0;
for (const auto& server : OnlineList)
{
count += server.clients;
}
Logger::Debug("There are {} players playing", count);
});
#endif
// Add required ownerDraws // Add required ownerDraws
UIScript::AddOwnerDraw(220, UpdateSource); UIScript::AddOwnerDraw(220, UpdateSource);
UIScript::AddOwnerDraw(253, UpdateGameType); UIScript::AddOwnerDraw(253, UpdateGameType);
@ -980,7 +976,7 @@ namespace Components
Scheduler::Loop(Frame, Scheduler::Pipeline::CLIENT); Scheduler::Loop(Frame, Scheduler::Pipeline::CLIENT);
} }
ServerList::~ServerList() void ServerList::preDestroy()
{ {
std::lock_guard _(RefreshContainer.mutex); std::lock_guard _(RefreshContainer.mutex);
RefreshContainer.awatingList = false; RefreshContainer.awatingList = false;

View File

@ -10,15 +10,15 @@ namespace Components
public: public:
typedef int(SortCallback)(const void*, const void*); typedef int(SortCallback)(const void*, const void*);
class ServerInfo struct ServerInfo
{ {
public:
Network::Address addr; Network::Address addr;
std::string hostname; std::string hostname;
std::string mapname; std::string mapname;
std::string gametype; std::string gametype;
std::string mod; std::string mod;
std::string shortversion; std::string shortversion;
std::size_t hash;
int clients; int clients;
int bots; int bots;
int maxClients; int maxClients;
@ -33,7 +33,8 @@ namespace Components
}; };
ServerList(); ServerList();
~ServerList();
void preDestroy() override;
static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info); static void RefreshVisibleList([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
@ -138,6 +139,7 @@ namespace Components
static ServerInfo* GetServer(unsigned int index); static ServerInfo* GetServer(unsigned int index);
static bool CompareVersion(const std::string& version1, const std::string& version2); static bool CompareVersion(const std::string& version1, const std::string& version2);
static bool IsServerDuplicate(const std::vector<ServerInfo>* list, const ServerInfo& server);
static int SortKey; static int SortKey;
static bool SortAsc; static bool SortAsc;
@ -159,3 +161,20 @@ namespace Components
static bool IsServerListOpen(); static bool IsServerListOpen();
}; };
} }
template <>
struct std::hash<Components::ServerList::ServerInfo>
{
std::size_t operator()(const Components::ServerList::ServerInfo& x) const noexcept
{
std::size_t hash = 0;
hash ^= std::hash<std::string>()(x.hostname);
hash ^= std::hash<std::string>()(x.mapname);
hash ^= std::hash<std::string>()(x.mod);
hash ^= std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&x.addr.getIP().bytes[0]));;
hash ^= x.clients;
return hash;
}
};

View File

@ -56,7 +56,7 @@ BOOL APIENTRY DllMain(HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReser
#ifndef DEBUG_BINARY_CHECK #ifndef DEBUG_BINARY_CHECK
const auto* binary = reinterpret_cast<const char*>(0x6F9358); const auto* binary = reinterpret_cast<const char*>(0x6F9358);
if (binary == nullptr || std::memcmp(binary, BASEGAME_NAME, 14) != 0) if (!binary || std::memcmp(binary, BASEGAME_NAME, 14) != 0)
#endif #endif
{ {
MessageBoxA(nullptr, MessageBoxA(nullptr,

View File

@ -29,6 +29,7 @@
#include <cctype> #include <cctype>
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <format> #include <format>
#include <fstream> #include <fstream>

View File

@ -22,11 +22,11 @@ namespace Steam
if (!idBits) if (!idBits)
{ {
if (Components::Dedicated::IsEnabled() || Components::ZoneBuilder::IsEnabled()) // Dedi guid if (Components::ZoneBuilder::IsEnabled())
{ {
idBits = *reinterpret_cast<unsigned __int64*>(const_cast<char*>("DEDICATE")); idBits = *reinterpret_cast<unsigned __int64*>(const_cast<char*>("DEDICATE"));
} }
else if (Components::Singleton::IsFirstInstance()) // ECDSA guid else if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid
{ {
idBits = Components::Auth::GetKeyHash(); idBits = Components::Auth::GetKeyHash();
} }

View File

@ -18,12 +18,14 @@ namespace Utils
std::string Rand::GenerateChallenge() std::string Rand::GenerateChallenge()
{ {
std::string challenge; char buffer[512]{};
challenge.append(String::VA("%X", GenerateInt())); int buffer_pos = 0;
challenge.append(String::VA("%X", ~timeGetTime() ^ GenerateInt()));
challenge.append(String::VA("%X", GenerateInt()));
return challenge; buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt());
buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", ~timeGetTime() ^ GenerateInt());
buffer_pos += sprintf_s(&buffer[buffer_pos], sizeof(buffer) - buffer_pos, "%X", GenerateInt());
return std::string(buffer, static_cast<std::size_t>(buffer_pos));
} }
std::uint32_t Rand::GenerateInt() std::uint32_t Rand::GenerateInt()