Merge pull request #982 from XLabsProject/develop

Release r4226
This commit is contained in:
Edo 2023-04-26 23:30:19 +01:00 committed by GitHub
commit 11a10ed35d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 149 additions and 71 deletions

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
[*.{cpp,hpp}]
end_of_line = crlf
insert_final_newline = true
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
brace_style = next_line
namespace_indentation = all
cpp_indent_namespace_contents = true

View File

@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/).
## r4226 - 2023-04-26
- Chat system will go back to using `SV_CMD_CAN_IGNORE` commands (#972)
### Fixed
- Fix bug with how `sv_mapRotationCurrent` is parsed (#977)
### Known issues
- Sound issue fix is experimental as the bug is not fully understood.
## r4208 - 2023-04-22
### Changed

View File

@ -16,7 +16,7 @@ namespace Components
const Game::dvar_t* Bots::sv_randomBotNames;
const Game::dvar_t* Bots::sv_replaceBots;
std::size_t Bots::botDataIndex;
std::size_t Bots::BotDataIndex;
struct BotMovementInfo
{
@ -121,8 +121,8 @@ namespace Components
if (!botNames.empty())
{
botDataIndex %= botNames.size();
const auto index = botDataIndex++;
BotDataIndex %= botNames.size();
const auto index = BotDataIndex++;
botName = botNames[index].first;
clanName = botNames[index].second;
}

View File

@ -15,7 +15,7 @@ namespace Components
static const Game::dvar_t* sv_randomBotNames;
static const Game::dvar_t* sv_replaceBots;
static std::size_t botDataIndex;
static std::size_t BotDataIndex;
static std::vector<botData> LoadBotNames();
static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);

View File

@ -101,7 +101,7 @@ namespace Components
RegisterBrandingDvars();
// UI version string
Utils::Hook::Set<const char*>(0x43F73B, "IW4x - " REVISION_STR);
Utils::Hook::Set<const char*>(0x43F73B, "IW4x " REVISION_STR);
// Short version dvar
Utils::Hook::Set<const char*>(0x60BD91, REVISION_STR);

View File

@ -56,13 +56,13 @@ namespace Components
if (IsMuted(player))
{
SendChat = false;
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You are muted\"", 0x65));
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65));
}
if (sv_disableChat.get<bool>())
{
SendChat = false;
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
}
// Message might be empty after the special characters
@ -297,7 +297,7 @@ namespace Components
});
Logger::Print("{} was muted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were muted\"", 0x65));
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65));
}
void Chat::UnmuteClient(const Game::client_t* client)
@ -305,7 +305,7 @@ namespace Components
UnmuteInternal(client->steamID);
Logger::Print("{} was unmuted\n", client->name);
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
}
void Chat::UnmuteInternal(const std::uint64_t id, bool everyone)
@ -461,12 +461,12 @@ namespace Components
if (!name.empty())
{
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message));
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "{}: {}\n", name, message);
}
else
{
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message);
}
});
@ -489,12 +489,12 @@ namespace Components
if (!name.empty())
{
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message));
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message));
Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, clientNum, message);
}
else
{
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", clientNum, message);
}
});
@ -510,7 +510,7 @@ namespace Components
if (params->size() < 2) return;
const auto message = params->join(1);
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message);
});
@ -528,7 +528,7 @@ namespace Components
const auto clientNum = static_cast<int>(std::min<std::size_t>(parsedInput, Game::MAX_CLIENTS));
const auto message = params->join(2);
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", clientNum, message);
});
@ -619,9 +619,6 @@ namespace Components
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D0110, PostSayStub, HOOK_CALL).install()->quick();
// Patch G_SayTo to use SV_CMD_RELIABLE
Utils::Hook::Set<std::uint8_t>(0x5DF7E3, Game::SV_CMD_RELIABLE);
// Change logic that does word splitting with new lines for chat messages to support fonticons
Utils::Hook(0x592E10, CG_AddToTeamChat_Stub, HOOK_JUMP).install()->quick();

View File

@ -97,9 +97,9 @@ namespace Components
else if (IsWindow(GetWindow()) != FALSE)
{
#ifdef EXPERIMENTAL_BUILD
SetWindowTextA(GetWindow(), Utils::String::Format("IW4x " REVISION_STR "-develop : %s", hostname));
SetWindowTextA(GetWindow(), Utils::String::Format("IW4x " REVISION_STR "-develop : {}", hostname));
#else
SetWindowTextA(GetWindow(), Utils::String::Format("IW4x " REVISION_STR " : %s", hostname));
SetWindowTextA(GetWindow(), Utils::String::Format("IW4x " REVISION_STR " : {}", hostname));
#endif
}
}

View File

@ -22,7 +22,7 @@ namespace Components
void Logger::Print_Stub(const int channel, const char* message, ...)
{
char buf[4096] = {0};
char buf[4096]{};
va_list va;
va_start(va, message);

View File

@ -126,7 +126,7 @@ namespace Components
return mapRotationJson;
}
void MapRotation::LoadRotation(const std::string& data)
void MapRotation::ParseRotation(const std::string& data)
{
try
{
@ -134,12 +134,25 @@ namespace Components
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotation)->name);
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}. {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotation)->name);
}
Logger::Debug("DedicatedRotation size after parsing is '{}'", DedicatedRotation.getEntriesSize());
}
void MapRotation::RandomizeMapRotation()
{
if (SVRandomMapRotation.get<bool>())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "Randomizing the map rotation\n");
DedicatedRotation.randomize();
}
else
{
Logger::Debug("Map rotation was not randomized");
}
}
void MapRotation::LoadMapRotation()
{
static auto loaded = false;
@ -155,14 +168,15 @@ namespace Components
// People may have sv_mapRotation empty because they only use 'addMap' or 'addGametype'
if (!mapRotation.empty())
{
Logger::Debug("sv_mapRotation is not empty. Parsing...");
LoadRotation(mapRotation);
Logger::Debug("{} is not empty. Parsing...", (*Game::sv_mapRotation)->name);
ParseRotation(mapRotation);
RandomizeMapRotation();
}
}
void MapRotation::AddMapRotationCommands()
{
Command::Add("addMap", [](Command::Params* params)
Command::AddSV("addMap", [](Command::Params* params)
{
if (params->size() < 2)
{
@ -173,7 +187,7 @@ namespace Components
DedicatedRotation.addEntry("map", params->get(1));
});
Command::Add("addGametype", [](Command::Params* params)
Command::AddSV("addGametype", [](Command::Params* params)
{
if (params->size() < 2)
{
@ -286,9 +300,13 @@ namespace Components
assert(!data.empty());
// Ook, ook, eek
Logger::Warning(Game::CON_CHANNEL_SERVER, "You are using deprecated {}", (*Game::sv_mapRotationCurrent)->name);
Logger::Warning(Game::CON_CHANNEL_SERVER, "You are using deprecated {}\n", (*Game::sv_mapRotationCurrent)->name);
RotationData rotationCurrent;
rotationCurrent.setHandler("map", ApplyMap);
rotationCurrent.setHandler("gametype", ApplyGametype);
rotationCurrent.setHandler("exec", ApplyExec);
try
{
Logger::Debug("Parsing {}", (*Game::sv_mapRotationCurrent)->name);
@ -296,7 +314,7 @@ namespace Components
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotationCurrent)->name);
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}. {} contains invalid data!\n", ex.what(), (*Game::sv_mapRotationCurrent)->name);
}
Game::Dvar_SetString(*Game::sv_mapRotationCurrent, "");
@ -337,19 +355,6 @@ namespace Components
SVNextMap.set("");
}
void MapRotation::RandomizeMapRotation()
{
if (SVRandomMapRotation.get<bool>())
{
Logger::Print(Game::CON_CHANNEL_SERVER, "Randomizing the map rotation\n");
DedicatedRotation.randomize();
}
else
{
Logger::Debug("Map rotation was not randomized");
}
}
void MapRotation::SV_MapRotate_f()
{
if (!ShouldRotate())
@ -378,8 +383,6 @@ namespace Components
return;
}
RandomizeMapRotation();
ApplyRotation(DedicatedRotation);
SetNextMap(DedicatedRotation);
}
@ -393,7 +396,7 @@ namespace Components
MapRotation::MapRotation()
{
AddMapRotationCommands();
Events::OnSVInit(AddMapRotationCommands);
Utils::Hook::Set<void(*)()>(0x4152E8, SV_MapRotate_f);
DedicatedRotation.setHandler("map", ApplyMap);
@ -407,30 +410,35 @@ namespace Components
{
Logger::Debug("Testing map rotation parsing...");
const auto* normal = "map mp_highrise map mp_terminal map mp_firingrange map mp_trailerpark gametype dm map mp_shipment_long";
const auto* normal = "exec war.cfg map mp_highrise map mp_terminal map mp_firingrange map mp_trailerpark gametype dm map mp_shipment_long";
RotationData rotation;
rotation.setHandler("map", ApplyMap);
rotation.setHandler("gametype", ApplyGametype);
rotation.setHandler("exec", ApplyExec);
try
{
DedicatedRotation.parse(normal);
rotation.parse(normal);
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}: parsing of 'normal' failed\n", ex.what());
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}. parsing of 'normal' failed\n", ex.what());
return false;
}
DedicatedRotation.clear();
rotation.clear();
const auto* mistake = "spdevmap mp_dome";
auto success = false;
try
{
DedicatedRotation.parse(mistake);
rotation.parse(mistake);
}
catch (const std::exception& ex)
{
Logger::Debug("{}: parsing of 'normal' failed as expected", ex.what());
Logger::Debug("{}. parsing of 'normal' failed as expected", ex.what());
success = true;
}

View File

@ -84,7 +84,8 @@ namespace Components
// Holds the parsed data from sv_mapRotation
static RotationData DedicatedRotation;
static void LoadRotation(const std::string& data);
static void RandomizeMapRotation();
static void ParseRotation(const std::string& data);
static void LoadMapRotation();
// Use these commands before SV_MapRotate_f is called
@ -103,7 +104,6 @@ namespace Components
static void SetNextMap(RotationData& rotation); // Only call this after ApplyRotation
static void SetNextMap(const char* value);
static void ClearNextMap();
static void RandomizeMapRotation();
static void SV_MapRotate_f();
};

View File

@ -5,6 +5,7 @@ namespace Components
Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
// Packet interception
std::unordered_map<std::string, Network::networkCallback> Network::CL_Callbacks;
std::unordered_map<std::string, Network::networkRawCallback> Network::CL_RawCallbacks;
Network::Address::Address()
{
@ -274,17 +275,34 @@ namespace Components
CL_Callbacks[Utils::String::ToLower(command)] = callback;
}
bool Network::CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
void Network::OnClientPacketRaw(const std::string& command, const networkRawCallback& callback)
{
CL_RawCallbacks[Utils::String::ToLower(command)] = callback;
}
bool Network::CL_HandleCommand(Game::netadr_t* address, const char* command, Game::msg_t* message)
{
const auto command_ = Utils::String::ToLower(command);
const auto handler = CL_Callbacks.find(command_);
const auto offset = command_.size() + 5;
if (static_cast<std::size_t>(message->cursize) < offset || handler == CL_Callbacks.end())
if (static_cast<std::size_t>(message->cursize) < offset)
{
return false;
}
if (const auto rawHandler = CL_RawCallbacks.find(command_); rawHandler != CL_RawCallbacks.end())
{
rawHandler->second(address, message);
return true;
}
const auto handler = CL_Callbacks.find(command_);
if (handler == CL_Callbacks.end())
{
// Normal handler was not found, return
return false;
}
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
auto target = Address{ address };
@ -360,9 +378,13 @@ namespace Components
Utils::Hook::Set<std::uint8_t>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
Utils::Hook::Set<std::uint8_t>(0x5AA69F, 0xEB); // echo
Utils::Hook::Set<std::uint8_t>(0x5AAA82, 0xEB); // SP
Utils::Hook::Set<std::uint8_t>(0x5A9F77, 0xEB); // CL_WeNowCantHearSomeone
Utils::Hook::Set<std::uint8_t>(0x5A9F18, 0xEB); // CL_VoiceConnectionTestPacket
Utils::Hook::Set<std::uint8_t>(0x5A9FF3, 0xEB); // CL_HandleRelayPacket
// For security reasons check the sender of the 'print' OOB
Utils::Hook::Set<std::uint8_t>(0x5AA729, 0xEB);
// Com_GetProtocol
Utils::Hook::Set<std::uint32_t>(0x4FB501, PROTOCOL);
@ -381,9 +403,24 @@ namespace Components
Utils::Hook::Set<std::uint8_t>(0x682170, 0xC3); // Telling LSP that we're playing a private match
Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket
OnClientPacket("resolveAddress", [](const Address& address, [[maybe_unused]] const std::string& data)
OnClientPacket("resolveAddress", []([[maybe_unused]] const Address& address, [[maybe_unused]] const std::string& data)
{
SendRaw(address, address.getString());
});
OnClientPacket("print", []([[maybe_unused]] const Address& address, [[maybe_unused]] const std::string& data)
{
auto* clc = Game::CL_GetLocalClientConnection(0);
if (!Game::NET_CompareBaseAdr(clc->serverAddress, *address.get()))
{
return;
}
char buffer[2048]{};
Game::I_strncpyz(clc->serverMessage, data.data(), sizeof(clc->serverMessage));
Game::Com_sprintf(buffer, sizeof(buffer), "%s", data.data());
Game::Com_PrintMessage(Game::CON_CHANNEL_CLIENT, buffer, 0);
});
}
}

View File

@ -49,6 +49,7 @@ namespace Components
typedef void(CallbackRaw)();
using networkCallback = std::function<void(Address&, const std::string&)>;
using networkRawCallback = std::function<void(Game::netadr_t*, Game::msg_t* msg)>;
Network();
@ -73,17 +74,19 @@ namespace Components
static void BroadcastAll(const std::string& data);
static void OnClientPacket(const std::string& command, const networkCallback& callback);
static void OnClientPacketRaw(const std::string& command, const networkRawCallback& callback);
private:
static Utils::Signal<CallbackRaw> StartupSignal;
static std::unordered_map<std::string, networkCallback> CL_Callbacks;
static std::unordered_map<std::string, networkRawCallback> CL_RawCallbacks;
static void NetworkStart();
static void NetworkStartStub();
static void PacketErrorCheck();
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, Game::msg_t* message);
static void CL_HandleCommandStub();
};

View File

@ -110,12 +110,6 @@ namespace Components
bool RCon::RateLimitCheck(const Network::Address& address, const int time)
{
const auto ip = address.getIP();
if (!RateLimit.contains(ip.full))
{
RateLimit[ip.full] = 0;
}
const auto lastTime = RateLimit[ip.full];
if (lastTime && (time - lastTime) < RconTimeout.get<int>())

View File

@ -241,7 +241,7 @@ namespace Components
void Voice::CL_WriteVoicePacket_Hk(const int localClientNum)
{
const auto connstate = Game::CL_GetLocalClientConnectionState(localClientNum);
const auto clc = Game::CL_GetLocalClientConnection(localClientNum);
const auto* clc = Game::CL_GetLocalClientConnection(localClientNum);
const auto* vc = Game::CL_GetLocalClientVoiceCommunication(localClientNum);
if (clc->demoplaying || (connstate < Game::CA_LOADING))
{
@ -315,8 +315,14 @@ namespace Components
}
}
void Voice::CL_VoicePacket_Hk(const int localClientNum, Game::msg_t* msg)
void Voice::CL_VoicePacket(Game::netadr_t* address, Game::msg_t* msg)
{
auto* clc = Game::CL_GetLocalClientConnection(0);
if (!Game::NET_CompareBaseAdr(clc->serverAddress, *address))
{
return;
}
const auto numPackets = Game::MSG_ReadByte(msg);
if (numPackets < 0 || numPackets > MAX_SERVER_QUEUED_VOICE_PACKETS)
{
@ -342,7 +348,7 @@ namespace Components
return;
}
if (!CL_IsPlayerMuted_Hk(nullptr, localClientNum, voicePacket.talker))
if (!CL_IsPlayerMuted_Hk(nullptr, 0, voicePacket.talker))
{
if ((*Game::cl_voice)->current.enabled)
{
@ -395,7 +401,10 @@ namespace Components
// Write voice packets to the server instead of other clients
Utils::Hook(0x487935, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x5AD945, CL_WriteVoicePacket_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x5A9E06, CL_VoicePacket_Hk, HOOK_CALL).install()->quick();
// Disable 'v' OOB handler and use our own
Utils::Hook::Set<std::uint8_t>(0x5A9E02, 0xEB);
Network::OnClientPacketRaw("v", CL_VoicePacket);
Utils::Hook(0x4AE740, CL_IsPlayerTalking_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(0x4B6250, CL_IsPlayerMuted_Hk, HOOK_JUMP).install()->quick();

View File

@ -46,7 +46,7 @@ namespace Components
static void CL_TogglePlayerMute(int localClientNum, int muteClientIndex);
static void CL_WriteVoicePacket_Hk(int localClientNum);
static void CL_VoicePacket_Hk(int localClientNum, Game::msg_t* msg);
static void CL_VoicePacket(Game::netadr_t* address, Game::msg_t* msg);
static void UI_Mute_player(int clientNum, int localClientNum);
static void UI_Mute_Player_Stub();

View File

@ -12,6 +12,7 @@ namespace Game
Com_PrintError_t Com_PrintError = Com_PrintError_t(0x4F8C70);
Com_PrintWarning_t Com_PrintWarning = Com_PrintWarning_t(0x4E0200);
Com_PrintMessage_t Com_PrintMessage = Com_PrintMessage_t(0x4AA830);
Com_sprintf_t Com_sprintf = Com_sprintf_t(0x413DE0);
Com_EndParseSession_t Com_EndParseSession = Com_EndParseSession_t(0x4B80B0);
Com_BeginParseSession_t Com_BeginParseSession = Com_BeginParseSession_t(0x4AAB80);
Com_ParseOnLine_t Com_ParseOnLine = Com_ParseOnLine_t(0x4C0350);

View File

@ -29,6 +29,9 @@ namespace Game
typedef void(*Com_PrintMessage_t)(int channel, const char* msg, int error);
extern Com_PrintMessage_t Com_PrintMessage;
typedef int(*Com_sprintf_t)(char* dest, int size, const char* fmt, ...);
extern Com_sprintf_t Com_sprintf;
typedef void(*Com_EndParseSession_t)();
extern Com_EndParseSession_t Com_EndParseSession;

View File

@ -109,6 +109,7 @@ namespace Game
NET_AdrToString_t NET_AdrToString = NET_AdrToString_t(0x469880);
NET_CompareAdr_t NET_CompareAdr = NET_CompareAdr_t(0x4D0AA0);
NET_CompareBaseAdr_t NET_CompareBaseAdr = NET_CompareBaseAdr_t(0x455510);
NET_DeferPacketToClient_t NET_DeferPacketToClient = NET_DeferPacketToClient_t(0x4C8AA0);
NET_ErrorString_t NET_ErrorString = NET_ErrorString_t(0x4E7720);
NET_Init_t NET_Init = NET_Init_t(0x491860);

View File

@ -269,12 +269,15 @@ namespace Game
typedef void(*NetadrToSockadr_t)(netadr_t *a, sockaddr *s);
extern NetadrToSockadr_t NetadrToSockadr;
typedef const char* (*NET_AdrToString_t)(netadr_t adr);
typedef const char*(*NET_AdrToString_t)(netadr_t adr);
extern NET_AdrToString_t NET_AdrToString;
typedef bool(*NET_CompareAdr_t)(netadr_t a, netadr_t b);
extern NET_CompareAdr_t NET_CompareAdr;
typedef int(*NET_CompareBaseAdr_t)(netadr_t a, netadr_t b);
extern NET_CompareBaseAdr_t NET_CompareBaseAdr;
typedef void(*NET_DeferPacketToClient_t)(netadr_t*, msg_t*);
extern NET_DeferPacketToClient_t NET_DeferPacketToClient;

View File

@ -8,7 +8,7 @@ namespace Utils::String
const char* VA(const char* fmt, ...)
{
static VAProvider<4, 256> globalProvider;
static thread_local VAProvider<8, 1024> provider;
static thread_local VAProvider<8, 256> provider;
va_list ap;
va_start(ap, fmt);