Improve threading (work in progress)

* Only run '_DownloadPlaylists_f()' in the main thread, schedule for next frame if we aren't in the main thread. (this should fix crash cases related to disconnecting from the game).

* Locked read/write to CBrowser members (thread for obtaining the server list is detached, but once the 'slow' post operation in this thread is complete, mutex lock is acquired (locking the render thread if the browser is active) to set the string members of CBrowser, this operation is very fast as we only set the string and the color after the http post operation (this never caused a crash, but the behavior without any lock mechanism is technically undefined regardless).

* Obtain the host name dynamically from the ConVar 'pylon_matchmaking_hostname' (atomic operation). Initial approach was deleting the whole master server pointer just to construct a new httpclient object..
This commit is contained in:
Kawe Mazidjatari 2022-08-27 18:57:56 +02:00
parent cf5ab260da
commit 656b0be3ec
23 changed files with 309 additions and 187 deletions

View File

@ -8,6 +8,7 @@
#include "engine/net.h"
#ifndef NETCONSOLE
#include "core/logdef.h"
#include "tier0/frametask.h"
#include "tier1/cvar.h"
#include "vstdlib/callback.h"
#include "mathlib/color.h"
@ -140,7 +141,14 @@ void NET_PrintFunc(const char* fmt, ...)
//-----------------------------------------------------------------------------
void NET_Shutdown(void* thisptr, const char* szReason, uint8_t bBadRep, bool bRemoveNow)
{
_DownloadPlaylists_f(); // Re-load playlist from disk after getting disconnected from the server.
if (!ThreadInMainThread())
{
g_TaskScheduler->Dispatch([]()
{
// Re-load playlist from disk the next frame.
_DownloadPlaylists_f();
}, 0);
}
v_NET_Shutdown(thisptr, szReason, bBadRep, bRemoveNow);
}

View File

@ -14,6 +14,7 @@ History:
#include "core/stdafx.h"
#include "core/init.h"
#include "core/resource.h"
#include "tier0/fasttimer.h"
#include "tier0/frametask.h"
#include "tier0/commandline.h"
#include "tier1/IConVar.h"
@ -45,22 +46,12 @@ History:
CBrowser::CBrowser(void)
{
memset(m_szServerAddressBuffer, '\0', sizeof(m_szServerAddressBuffer));
static std::thread request([this]()
{
RefreshServerList();
#ifndef CLIENT_DLL
while (true)
{
UpdateHostingStatus();
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
}
#endif // !CLIENT_DLL
});
request.detach();
std::thread refresh(&CBrowser::RefreshServerList, this);
refresh.detach();
std::thread think(&CBrowser::Think, this);
think.detach();
think.detach(); // !FIXME: Run from SDK MainFrame when finished.
m_pszBrowserTitle = "Server Browser";
m_rLockedIconBlob = GetModuleResource(IDB_PNG2);
@ -132,6 +123,28 @@ void CBrowser::RunFrame(void)
ImGui::PopStyleVar(nVars);
}
//-----------------------------------------------------------------------------
// Purpose: runs tasks for the browser while not being drawn
// (!!! RunTask and RunFrame must be called from the same thread !!!)
//-----------------------------------------------------------------------------
void CBrowser::RunTask()
{
static bool bInitialized = false;
static CFastTimer timer;
if (!bInitialized)
{
timer.Start();
bInitialized = true;
}
if (timer.GetDurationInProgress().GetSeconds() > pylon_host_update_interval->GetDouble())
{
UpdateHostingStatus();
timer.Start();
}
}
//-----------------------------------------------------------------------------
// Purpose: think
//-----------------------------------------------------------------------------
@ -159,6 +172,8 @@ void CBrowser::Think(void)
//-----------------------------------------------------------------------------
void CBrowser::DrawSurface(void)
{
std::lock_guard<std::mutex> l(m_Mutex);
ImGui::BeginTabBar("CompMenu");
if (ImGui::BeginTabItem("Browsing"))
{
@ -190,7 +205,10 @@ void CBrowser::BrowserPanel(void)
ImGui::SameLine();
if (ImGui::Button("Refresh List"))
{
RefreshServerList();
m_svServerListMessage.clear();
std::thread refresh(&CBrowser::RefreshServerList, this);
refresh.detach();
}
ImGui::EndGroup();
ImGui::TextColored(ImVec4(1.00f, 0.00f, 0.00f, 1.00f), m_svServerListMessage.c_str());
@ -219,10 +237,11 @@ void CBrowser::BrowserPanel(void)
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 5);
ImGui::TableHeadersRow();
for (NetGameServer_t& server : g_pServerListManager->m_vServerList)
g_pServerListManager->m_Mutex.lock();
for (const NetGameServer_t& server : g_pServerListManager->m_vServerList)
{
const char* pszHostName = server.m_svHostName.c_str();
const char* pszHostMap = server.m_svMapName.c_str();
const char* pszHostMap = server.m_svHostMap.c_str();
const char* pszPlaylist = server.m_svPlaylist.c_str();
const char* pszHostPort = server.m_svGamePort.c_str();
@ -247,15 +266,16 @@ void CBrowser::BrowserPanel(void)
ImGui::TableNextColumn();
string svConnectBtn = "Connect##";
svConnectBtn.append(server.m_svHostName + server.m_svIpAddress + server.m_svMapName);
svConnectBtn.append(server.m_svHostName + server.m_svIpAddress + server.m_svHostMap);
if (ImGui::Button(svConnectBtn.c_str()))
{
g_pServerListManager->ConnectToServer(server.m_svIpAddress, pszHostPort, server.m_svEncryptionKey);
}
}
}
g_pServerListManager->m_Mutex.unlock();
ImGui::EndTable();
ImGui::PopStyleVar(nVars);
}
@ -291,21 +311,13 @@ void CBrowser::BrowserPanel(void)
//-----------------------------------------------------------------------------
void CBrowser::RefreshServerList(void)
{
static bool bThreadLocked = false;
m_svServerListMessage.clear();
DevMsg(eDLL_T::CLIENT, "Refreshing server list with matchmaking host '%s'\n", pylon_matchmaking_hostname->GetString());
std::string svServerListMessage;
g_pServerListManager->RefreshServerList(svServerListMessage);
if (!bThreadLocked)
{
std::thread t([this]()
{
bThreadLocked = true;
DevMsg(eDLL_T::CLIENT, "Refreshing server list with matchmaking host '%s'\n", pylon_matchmaking_hostname->GetString());
g_pServerListManager->GetServerList(m_svServerListMessage);
bThreadLocked = false;
});
t.detach();
}
std::lock_guard<std::mutex> l(m_Mutex);
m_svServerListMessage = svServerListMessage;
}
//-----------------------------------------------------------------------------
@ -380,7 +392,8 @@ void CBrowser::HiddenServersModal(void)
void CBrowser::HostPanel(void)
{
#ifndef CLIENT_DLL
static string svServerNameErr = "";
static string svServerNameErr = ""; // Member?
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
ImGui::InputTextWithHint("##ServerHost_ServerName", "Server Name (Required)", &g_pServerListManager->m_Server.m_svHostName);
ImGui::InputTextWithHint("##ServerHost_ServerDesc", "Server Description (Optional)", &g_pServerListManager->m_Server.m_svDescription);
@ -398,13 +411,13 @@ void CBrowser::HostPanel(void)
ImGui::EndCombo();
}
if (ImGui::BeginCombo("Map##ServerHost_MapListBox", g_pServerListManager->m_Server.m_svMapName.c_str()))
if (ImGui::BeginCombo("Map##ServerHost_MapListBox", g_pServerListManager->m_Server.m_svHostMap.c_str()))
{
for (auto& item : g_vAllMaps)
{
if (ImGui::Selectable(item.c_str(), item == g_pServerListManager->m_Server.m_svMapName))
if (ImGui::Selectable(item.c_str(), item == g_pServerListManager->m_Server.m_svHostMap))
{
g_pServerListManager->m_Server.m_svMapName = item;
g_pServerListManager->m_Server.m_svHostMap = item;
}
}
ImGui::EndCombo();
@ -437,10 +450,9 @@ void CBrowser::HostPanel(void)
if (ImGui::Button("Start Server", ImVec2((ImGui::GetWindowSize().x - 10), 32)))
{
svServerNameErr.clear();
if (!g_pServerListManager->m_Server.m_svHostName.empty() && !g_pServerListManager->m_Server.m_svPlaylist.empty() && !g_pServerListManager->m_Server.m_svMapName.empty())
if (!g_pServerListManager->m_Server.m_svHostName.empty() && !g_pServerListManager->m_Server.m_svPlaylist.empty() && !g_pServerListManager->m_Server.m_svHostMap.empty())
{
g_pServerListManager->LaunchServer(); // Launch server.
UpdateHostingStatus(); // Update hosting status.
}
else
{
@ -452,7 +464,7 @@ void CBrowser::HostPanel(void)
{
svServerNameErr = "No playlist assigned.";
}
else if (g_pServerListManager->m_Server.m_svMapName.empty())
else if (g_pServerListManager->m_Server.m_svHostMap.empty())
{
svServerNameErr = "No level name assigned.";
}
@ -463,10 +475,9 @@ void CBrowser::HostPanel(void)
if (ImGui::Button("Force Start", ImVec2((ImGui::GetWindowSize().x - 10), 32)))
{
svServerNameErr.clear();
if (!g_pServerListManager->m_Server.m_svPlaylist.empty() && !g_pServerListManager->m_Server.m_svMapName.empty())
if (!g_pServerListManager->m_Server.m_svPlaylist.empty() && !g_pServerListManager->m_Server.m_svHostMap.empty())
{
g_pServerListManager->LaunchServer(); // Launch server.
UpdateHostingStatus(); // Update hosting status.
}
else
{
@ -474,7 +485,7 @@ void CBrowser::HostPanel(void)
{
svServerNameErr = "No playlist assigned.";
}
else if (g_pServerListManager->m_Server.m_svMapName.empty())
else if (g_pServerListManager->m_Server.m_svHostMap.empty())
{
svServerNameErr = "No level name assigned.";
}
@ -499,10 +510,9 @@ void CBrowser::HostPanel(void)
if (ImGui::Button("Change Level", ImVec2((ImGui::GetWindowSize().x - 10), 32)))
{
if (!g_pServerListManager->m_Server.m_svMapName.empty())
if (!g_pServerListManager->m_Server.m_svHostMap.empty())
{
strncpy_s(g_pHostState->m_levelName, g_pServerListManager->m_Server.m_svMapName.c_str(), MAX_MAP_NAME); // Copy new map into hoststate levelname.
g_pHostState->m_iNextState = HostStates_t::HS_CHANGE_LEVEL_MP; // Force CHostState::FrameUpdate to change the level.
g_pServerListManager->LaunchServer();
}
else
{
@ -513,15 +523,22 @@ void CBrowser::HostPanel(void)
if (ImGui::Button("Stop Server", ImVec2((ImGui::GetWindowSize().x - 10), 32)))
{
ProcessCommand("LeaveMatch"); // TODO: use script callback instead.
g_pHostState->m_iNextState = HostStates_t::HS_GAME_SHUTDOWN; // Force CHostState::FrameUpdate to shutdown the server for dedicated.
g_TaskScheduler->Dispatch([]()
{
// Force CHostState::FrameUpdate to shutdown the server for dedicated.
g_pHostState->m_iNextState = HostStates_t::HS_GAME_SHUTDOWN;
}, 0);
}
}
else
{
if (ImGui::Button("Reload Playlist", ImVec2((ImGui::GetWindowSize().x - 10), 32)))
{
_DownloadPlaylists_f();
KeyValues::InitPlaylists(); // Re-Init playlist.
g_TaskScheduler->Dispatch([]()
{
_DownloadPlaylists_f();
KeyValues::InitPlaylists(); // Re-Init playlist.
}, 0);
}
}
#endif // !CLIENT_DLL
@ -538,7 +555,9 @@ void CBrowser::UpdateHostingStatus(void)
return;
}
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
g_pServerListManager->m_HostingStatus = g_pHostState->m_bActiveGame ? EHostStatus_t::HOSTING : EHostStatus_t::NOT_HOSTING; // Are we hosting a server?
switch (g_pServerListManager->m_HostingStatus)
{
case EHostStatus_t::NOT_HOSTING:
@ -573,23 +592,7 @@ void CBrowser::UpdateHostingStatus(void)
break;
}
SendHostingPostRequest();
break;
}
default:
break;
}
#endif // !CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose: sends the hosting POST request to the comp server
//-----------------------------------------------------------------------------
void CBrowser::SendHostingPostRequest(void)
{
#ifndef CLIENT_DLL
bool result = g_pMasterServer->PostServerHost(m_svHostRequestMessage, m_svHostToken,
NetGameServer_t
NetGameServer_t gameServer // !FIXME: create from main thread.
{
g_pServerListManager->m_Server.m_svHostName,
g_pServerListManager->m_Server.m_svDescription,
@ -606,8 +609,34 @@ void CBrowser::SendHostingPostRequest(void)
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count()
}
);
};
std::thread post(&CBrowser::SendHostingPostRequest, this, gameServer);
post.detach();
break;
}
default:
break;
}
#endif // !CLIENT_DLL
}
//-----------------------------------------------------------------------------
// Purpose: sends the hosting POST request to the comp server
// Input : &gameServer -
//-----------------------------------------------------------------------------
void CBrowser::SendHostingPostRequest(const NetGameServer_t& gameServer)
{
#ifndef CLIENT_DLL
string svHostRequestMessage;
string svHostToken;
bool result = g_pMasterServer->PostServerHost(svHostRequestMessage, svHostToken, gameServer);
std::lock_guard<std::mutex> l(m_Mutex);
m_svHostRequestMessage = svHostRequestMessage;
m_svHostToken = svHostToken;
if (result)
{
@ -634,7 +663,7 @@ void CBrowser::SendHostingPostRequest(void)
void CBrowser::ProcessCommand(const char* pszCommand) const
{
Cbuf_AddText(Cbuf_GetCurrentPlayer(), pszCommand, cmd_source_t::kCommandSrcCode);
//g_DelayedCallTask->AddFunc(Cbuf_Execute, 0); // Run in main thread.
//g_TaskScheduler->Dispatch(Cbuf_Execute, 0); // Run in main thread.
}
//-----------------------------------------------------------------------------
@ -645,20 +674,26 @@ void CBrowser::SettingsPanel(void)
ImGui::InputTextWithHint("Hostname", "Matchmaking Server String", &m_szMatchmakingHostName);
if (ImGui::Button("Update Hostname"))
{
pylon_matchmaking_hostname->SetValue(m_szMatchmakingHostName.c_str());
if (g_pMasterServer)
{
delete g_pMasterServer;
g_pMasterServer = new CPylon(pylon_matchmaking_hostname->GetString());
}
ProcessCommand(fmt::format("{:s} \"{:s}\"", "pylon_matchmaking_hostname", m_szMatchmakingHostName.c_str()).c_str());
}
ImGui::InputText("Netkey", const_cast<char*>(g_svNetKey.c_str()), ImGuiInputTextFlags_ReadOnly);
if (ImGui::Button("Regenerate Encryption Key"))
{
NET_GenerateKey();
g_TaskScheduler->Dispatch(NET_GenerateKey, 0);
}
}
//-----------------------------------------------------------------------------
// Purpose: hooked to 'MP_HostName_Changed_f' to sync hostname field with
// the 'pylon_matchmaking_hostname' ConVar (!!! DO NOT USE !!!).
// Input : *pszHostName -
//-----------------------------------------------------------------------------
void CBrowser::SetHostName(const char* pszHostName)
{
std::lock_guard<std::mutex> l(m_Mutex);
m_szMatchmakingHostName = pszHostName;
}
//-----------------------------------------------------------------------------
// Purpose: sets the browser front-end style
//-----------------------------------------------------------------------------

View File

@ -17,7 +17,7 @@ public:
virtual void Think(void);
virtual void RunFrame(void);
virtual void RunTask(void){};
virtual void RunTask(void);
virtual void DrawSurface(void);
@ -28,16 +28,18 @@ public:
void HostPanel(void);
void UpdateHostingStatus(void);
void SendHostingPostRequest(void);
void SendHostingPostRequest(const NetGameServer_t& gameServer);
void ProcessCommand(const char* pszCommand) const;
void SettingsPanel(void);
void SetHostName(const char* pszHostName);
virtual void SetStyleVar(void);
const char* m_pszBrowserTitle = nullptr;
bool m_bActivate = false;
private:
bool m_bInitialized = false;
char m_szServerAddressBuffer[256] = { '\0' };
@ -46,7 +48,8 @@ private:
ImGuiStyle_t m_Style = ImGuiStyle_t::NONE;
ID3D11ShaderResourceView* m_idLockedIcon = nullptr;
MODULERESOURCE m_rLockedIconBlob;
MODULERESOURCE m_rLockedIconBlob;
mutable std::mutex m_Mutex;
////////////////////
// Server List //

View File

@ -43,7 +43,7 @@ CConsole::CConsole(void)
snprintf(m_szSummary, sizeof(m_szSummary), "%zu history items", m_vHistory.size());
std::thread think(&CConsole::Think, this);
think.detach();
think.detach(); // !FIXME: Run from SDK MainFrame when finished.
}
//-----------------------------------------------------------------------------
@ -528,7 +528,7 @@ void CConsole::ProcessCommand(const char* pszCommand)
DevMsg(eDLL_T::COMMON, "] %s\n", pszCommand);
Cbuf_AddText(Cbuf_GetCurrentPlayer(), pszCommand, cmd_source_t::kCommandSrcCode);
//g_DelayedCallTask->AddFunc(Cbuf_Execute, 0); // Run in main thread.
//g_TaskScheduler->Dispatch(Cbuf_Execute, 0); // Run in main thread.
m_nHistoryPos = -1;
for (size_t i = m_vHistory.size(); i-- > 0; )

View File

@ -93,7 +93,7 @@ bool CModAppSystemGroup::Create(CModAppSystemGroup* pModAppSystemGroup)
g_pHLClient = nullptr;
}
g_FrameTasks.push_back(std::move(g_DelayedCallTask));
g_FrameTasks.push_back(std::move(g_TaskScheduler));
g_bAppSystemInit = true;
return CModAppSystemGroup_Create(pModAppSystemGroup);

View File

@ -54,8 +54,14 @@ bool CNetCon::Init(void)
this->TermSetup();
std::thread tFrame(&CNetCon::RunFrame, this);
tFrame.detach();
static std::thread frame([this]()
{
for (;;)
{
this->RunFrame();
}
});
frame.detach();
return true;
}
@ -130,6 +136,8 @@ void CNetCon::UserInput(void)
m_bQuitApplication = true;
return;
}
std::lock_guard<std::mutex> l(m_Mutex);
if (m_abConnEstablished)
{
if (svInput.compare("disconnect") == 0)
@ -202,18 +210,17 @@ void CNetCon::UserInput(void)
//-----------------------------------------------------------------------------
void CNetCon::RunFrame(void)
{
for (;;)
if (m_abConnEstablished)
{
if (m_abConnEstablished)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
this->Recv();
}
else if (m_abPromptConnect)
{
std::cout << "Enter <IP> <PORT>: ";
m_abPromptConnect = false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::lock_guard<std::mutex> l(m_Mutex);
this->Recv();
}
else if (m_abPromptConnect)
{
std::cout << "Enter <IP> <PORT>: ";
m_abPromptConnect = false;
}
}

View File

@ -45,4 +45,6 @@ private:
bool m_bQuitApplication;
std::atomic<bool> m_abPromptConnect;
std::atomic<bool> m_abConnEstablished;
mutable std::mutex m_Mutex;
};

View File

@ -7,6 +7,7 @@
//=============================================================================//
#include "core/stdafx.h"
#include "tier0/threadtools.h"
#include "tier0/frametask.h"
#include "tier1/cmd.h"
#include "tier1/cvar.h"
@ -26,13 +27,28 @@ CServerListManager::CServerListManager(void)
}
//-----------------------------------------------------------------------------
// Purpose: get server list from pylon.
// Purpose: get server list from pylon
// Input : &svMessage -
// Output : amount of servers found
//-----------------------------------------------------------------------------
void CServerListManager::GetServerList(string& svMessage)
size_t CServerListManager::RefreshServerList(string& svMessage)
{
ClearServerList();
vector<NetGameServer_t> vServerList = g_pMasterServer->GetServerList(svMessage);
std::lock_guard<std::mutex> l(m_Mutex);
m_vServerList = vServerList;
return m_vServerList.size();
}
//-----------------------------------------------------------------------------
// Purpose: clears the server list
//-----------------------------------------------------------------------------
void CServerListManager::ClearServerList(void)
{
std::lock_guard<std::mutex> l(m_Mutex);
m_vServerList.clear();
m_vServerList = g_pMasterServer->GetServerList(svMessage);
}
//-----------------------------------------------------------------------------
@ -41,23 +57,32 @@ void CServerListManager::GetServerList(string& svMessage)
void CServerListManager::LaunchServer(void) const
{
#ifndef CLIENT_DLL
DevMsg(eDLL_T::ENGINE, "Starting server with name: \"%s\" map: \"%s\" playlist: \"%s\"\n", m_Server.m_svHostName.c_str(), m_Server.m_svMapName.c_str(), m_Server.m_svPlaylist.c_str());
if (!ThreadInMainThread())
{
g_TaskScheduler->Dispatch([this]()
{
this->LaunchServer();
}, 0);
return;
}
DevMsg(eDLL_T::ENGINE, "Starting server with name: \"%s\" map: \"%s\" playlist: \"%s\"\n", m_Server.m_svHostName.c_str(), m_Server.m_svHostMap.c_str(), m_Server.m_svPlaylist.c_str());
/*
* Playlist gets parsed in two instances, first in KeyValues::ParsePlaylists with all the neccessary values.
* Then when you would normally call launchplaylist which calls StartPlaylist it would cmd call mp_gamemode which parses the gamemode specific part of the playlist..
*/
KeyValues::ParsePlaylists(m_Server.m_svPlaylist.c_str());
mp_gamemode->SetValue(m_Server.m_svPlaylist.c_str());
if (g_pHostState->m_bActiveGame)
{
ProcessCommand(fmt::format("{:s} \"{:s}\"", "changelevel", m_Server.m_svMapName).c_str());
ProcessCommand(fmt::format("{:s} \"{:s}\"", "changelevel", m_Server.m_svHostMap).c_str());
}
else // Initial launch.
{
ProcessCommand(fmt::format("{:s} \"{:s}\"", "map", m_Server.m_svMapName).c_str());
ProcessCommand(fmt::format("{:s} \"{:s}\"", "map", m_Server.m_svHostMap).c_str());
}
#endif // !CLIENT_DLL
@ -99,7 +124,7 @@ void CServerListManager::ConnectToServer(const string& svServer, const string& s
void CServerListManager::ProcessCommand(const char* pszCommand) const
{
Cbuf_AddText(Cbuf_GetCurrentPlayer(), pszCommand, cmd_source_t::kCommandSrcCode);
//g_DelayedCallTask->AddFunc(Cbuf_Execute, 0); // Run in main thread.
//g_TaskScheduler->Dispatch(Cbuf_Execute, 0); // Run in main thread.
}
CServerListManager* g_pServerListManager = new CServerListManager();

View File

@ -4,35 +4,38 @@
enum EHostStatus_t
{
NOT_HOSTING,
HOSTING
NOT_HOSTING,
HOSTING
};
enum EServerVisibility_t
{
OFFLINE,
HIDDEN,
PUBLIC
OFFLINE,
HIDDEN,
PUBLIC
};
class CServerListManager
{
public:
CServerListManager();
CServerListManager();
void GetServerList(string& svMessage);
size_t RefreshServerList(string& svMessage);
void ClearServerList(void);
void LaunchServer(void) const;
void ConnectToServer(const string& svIp, const string& svPort, const string& svNetKey) const;
void ConnectToServer(const string& svServer, const string& svNetKey) const;
void LaunchServer(void) const;
void ConnectToServer(const string& svIp, const string& svPort, const string& svNetKey) const;
void ConnectToServer(const string& svServer, const string& svNetKey) const;
void ProcessCommand(const char* pszCommand) const;
void ProcessCommand(const char* pszCommand) const;
EHostStatus_t m_HostingStatus;
EHostStatus_t m_HostingStatus;
EServerVisibility_t m_ServerVisibility;
NetGameServer_t m_Server;
vector<NetGameServer_t> m_vServerList;
mutable std::mutex m_Mutex;
};
extern CServerListManager* g_pServerListManager;

View File

@ -71,15 +71,17 @@ vector<NetGameServer_t> CPylon::GetServerList(string& svOutMessage)
DevMsg(eDLL_T::ENGINE, "%s - Sending server list request to comp-server:\n%s\n", __FUNCTION__, svRequestBody.c_str());
}
httplib::Result htResults = m_HttpClient.Post("/servers", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
if (htResults && pylon_showdebug->GetBool())
httplib::Client htClient(pylon_matchmaking_hostname->GetString()); htClient.set_connection_timeout(10);
httplib::Result htResult = htClient.Post("/servers", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
if (htResult && pylon_showdebug->GetBool())
{
DevMsg(eDLL_T::ENGINE, "%s - replied with '%d'.\n", __FUNCTION__, htResults->status);
DevMsg(eDLL_T::ENGINE, "%s - replied with '%d'.\n", __FUNCTION__, htResult->status);
}
if (htResults && htResults->status == 200) // STATUS_OK
if (htResult && htResult->status == 200) // STATUS_OK
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["success"].is_boolean() && jsResultBody["success"].get<bool>())
{
for (auto& obj : jsResultBody["servers"])
@ -120,11 +122,11 @@ vector<NetGameServer_t> CPylon::GetServerList(string& svOutMessage)
}
else
{
if (htResults)
if (htResult)
{
if (!htResults->body.empty())
if (!htResult->body.empty())
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["err"].is_string())
{
@ -132,13 +134,13 @@ vector<NetGameServer_t> CPylon::GetServerList(string& svOutMessage)
}
else
{
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResult->status);
}
return vslList;
}
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResult->status);
return vslList;
}
@ -162,7 +164,7 @@ bool CPylon::PostServerHost(string& svOutMessage, string& svOutToken, const NetG
jsRequestBody["name"] = slServerListing.m_svHostName;
jsRequestBody["description"] = slServerListing.m_svDescription;
jsRequestBody["hidden"] = slServerListing.m_bHidden;
jsRequestBody["map"] = slServerListing.m_svMapName;
jsRequestBody["map"] = slServerListing.m_svHostMap;
jsRequestBody["playlist"] = slServerListing.m_svPlaylist;
jsRequestBody["ip"] = slServerListing.m_svIpAddress;
jsRequestBody["port"] = slServerListing.m_svGamePort;
@ -181,15 +183,17 @@ bool CPylon::PostServerHost(string& svOutMessage, string& svOutToken, const NetG
DevMsg(eDLL_T::ENGINE, "%s - Sending post host request to comp-server:\n%s\n", __FUNCTION__, svRequestBody.c_str());
}
httplib::Result htResults = m_HttpClient.Post("/servers/add", svRequestBody.c_str(), svRequestBody.length(), "application/json");
if (htResults && pylon_showdebug->GetBool())
httplib::Client htClient(pylon_matchmaking_hostname->GetString()); htClient.set_connection_timeout(10);
httplib::Result htResult = htClient.Post("/servers/add", svRequestBody.c_str(), svRequestBody.length(), "application/json");
if (htResult && pylon_showdebug->GetBool())
{
DevMsg(eDLL_T::ENGINE, "%s - Comp-server replied with '%d'\n", __FUNCTION__, htResults->status);
DevMsg(eDLL_T::ENGINE, "%s - Comp-server replied with '%d'\n", __FUNCTION__, htResult->status);
}
if (htResults && htResults->status == 200) // STATUS_OK
if (htResult && htResult->status == 200) // STATUS_OK
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["success"].is_boolean() && jsResultBody["success"].get<bool>())
{
if (jsResultBody["token"].is_string())
@ -218,11 +222,11 @@ bool CPylon::PostServerHost(string& svOutMessage, string& svOutToken, const NetG
}
else
{
if (htResults)
if (htResult)
{
if (!htResults->body.empty())
if (!htResult->body.empty())
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["err"].is_string())
{
@ -230,7 +234,7 @@ bool CPylon::PostServerHost(string& svOutMessage, string& svOutToken, const NetG
}
else
{
svOutMessage = string("Failed to reach comp-server ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server ") + std::to_string(htResult->status);
}
svOutToken = string();
@ -238,7 +242,7 @@ bool CPylon::PostServerHost(string& svOutMessage, string& svOutToken, const NetG
}
svOutToken = string();
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResult->status);
return false;
}
@ -269,20 +273,21 @@ bool CPylon::GetServerByToken(NetGameServer_t& slOutServer, string& svOutMessage
DevMsg(eDLL_T::ENGINE, "%s - Sending token connect request to comp-server:\n%s\n", __FUNCTION__, svRequestBody.c_str());
}
httplib::Result htResults = m_HttpClient.Post("/server/byToken", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
httplib::Client htClient(pylon_matchmaking_hostname->GetString()); htClient.set_connection_timeout(10);
httplib::Result htResult = htClient.Post("/server/byToken", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
if (pylon_showdebug->GetBool())
{
DevMsg(eDLL_T::ENGINE, "%s - Comp-server replied with '%d'\n", __FUNCTION__, htResults->status);
DevMsg(eDLL_T::ENGINE, "%s - Comp-server replied with '%d'\n", __FUNCTION__, htResult->status);
}
if (htResults && htResults->status == 200) // STATUS_OK
if (htResult && htResult->status == 200) // STATUS_OK
{
if (!htResults->body.empty())
if (!htResult->body.empty())
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (htResults && jsResultBody["success"].is_boolean() && jsResultBody["success"])
if (htResult && jsResultBody["success"].is_boolean() && jsResultBody["success"])
{
slOutServer = NetGameServer_t
{
@ -322,11 +327,11 @@ bool CPylon::GetServerByToken(NetGameServer_t& slOutServer, string& svOutMessage
}
else
{
if (htResults)
if (htResult)
{
if (!htResults->body.empty())
if (!htResult->body.empty())
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["err"].is_string())
{
@ -334,13 +339,13 @@ bool CPylon::GetServerByToken(NetGameServer_t& slOutServer, string& svOutMessage
}
else
{
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResult->status);
}
return false;
}
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResults->status);
svOutMessage = string("Failed to reach comp-server: ") + std::to_string(htResult->status);
return false;
}
@ -365,10 +370,12 @@ bool CPylon::GetClientIsBanned(const string& svIpAddress, uint64_t nOriginID, st
jsRequestBody["oid"] = nOriginID;
jsRequestBody["ip"] = svIpAddress;
httplib::Result htResults = m_HttpClient.Post("/banlist/isBanned", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
if (htResults && htResults->status == 200)
httplib::Client htClient(pylon_matchmaking_hostname->GetString()); htClient.set_connection_timeout(10);
httplib::Result htResult = htClient.Post("/banlist/isBanned", jsRequestBody.dump(4).c_str(), jsRequestBody.dump(4).length(), "application/json");
if (htResult && htResult->status == 200)
{
nlohmann::json jsResultBody = nlohmann::json::parse(htResults->body);
nlohmann::json jsResultBody = nlohmann::json::parse(htResult->body);
if (jsResultBody["success"].is_boolean() && jsResultBody["success"].get<bool>())
{
if (jsResultBody["banned"].is_boolean() && jsResultBody["banned"].get<bool>())
@ -381,4 +388,4 @@ bool CPylon::GetClientIsBanned(const string& svIpAddress, uint64_t nOriginID, st
return false;
}
///////////////////////////////////////////////////////////////////////////////
CPylon* g_pMasterServer(new CPylon("r5a-comp-sv.herokuapp.com"));
CPylon* g_pMasterServer(new CPylon());

View File

@ -6,20 +6,9 @@ void KeepAliveToPylon();
class CPylon
{
public:
CPylon(string serverString) : m_HttpClient(serverString.c_str())
{
m_HttpClient.set_connection_timeout(10);
}
vector<NetGameServer_t> GetServerList(string& svOutMessage);
bool PostServerHost(string& svOutMessage, string& svOutToken, const NetGameServer_t& slServerListing);
bool GetServerByToken(NetGameServer_t& slOutServer, string& svOutMessage, const string& svToken);
bool GetClientIsBanned(const string& svIpAddress, uint64_t nOriginID, string& svOutErrCl);
CPylon* pR5net = nullptr;
CPylon* GetR5Net() { return pR5net; }
private:
httplib::Client m_HttpClient;
};
extern CPylon* g_pMasterServer;

View File

@ -17,7 +17,7 @@ struct NetGameServer_t
string m_svDescription;
bool m_bHidden;
string m_svMapName = "mp_lobby";
string m_svHostMap = "mp_lobby";
string m_svPlaylist = "dev_default";
string m_svIpAddress;

View File

@ -123,11 +123,26 @@ namespace VSquirrel
}
namespace UI
{
//-----------------------------------------------------------------------------
// Purpose: refreshes the server list
//-----------------------------------------------------------------------------
SQRESULT RefreshServerCount(HSQUIRRELVM v)
{
string svMessage; // Refresh svListing list.
size_t iCount = g_pServerListManager->RefreshServerList(svMessage);
sq_pushinteger(v, static_cast<SQInteger>(iCount));
return SQ_OK;
}
//-----------------------------------------------------------------------------
// Purpose: get server's current name from serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerName(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -148,6 +163,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerDescription(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -168,6 +185,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerMap(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -177,7 +196,7 @@ namespace VSquirrel
return SQ_ERROR;
}
string svServerMapName = g_pServerListManager->m_vServerList[iServer].m_svMapName;
string svServerMapName = g_pServerListManager->m_vServerList[iServer].m_svHostMap;
sq_pushstring(v, svServerMapName.c_str(), -1);
return SQ_OK;
@ -188,6 +207,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerPlaylist(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -208,6 +229,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerCurrentPlayers(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -227,6 +250,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerMaxPlayers(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -246,10 +271,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT GetServerCount(HSQUIRRELVM v)
{
string svMessage;
g_pServerListManager->GetServerList(svMessage); // Refresh svListing list.
sq_pushinteger(v, static_cast<SQInteger>(g_pServerListManager->m_vServerList.size()));
size_t iCount = g_pServerListManager->m_vServerList.size();
sq_pushinteger(v, static_cast<SQInteger>(iCount));
return SQ_OK;
}
@ -320,6 +343,8 @@ namespace VSquirrel
//-----------------------------------------------------------------------------
SQRESULT SetEncKeyAndConnect(HSQUIRRELVM v)
{
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
SQInteger iServer = sq_getinteger(v, 1);
SQInteger iCount = static_cast<SQInteger>(g_pServerListManager->m_vServerList.size());
@ -351,9 +376,11 @@ namespace VSquirrel
return SQ_OK;
// Adjust browser settings.
std::lock_guard<std::mutex> l(g_pServerListManager->m_Mutex);
g_pServerListManager->m_Server.m_svHostName = svServerName;
g_pServerListManager->m_Server.m_svDescription = svServerDescription;
g_pServerListManager->m_Server.m_svMapName = svServerMapName;
g_pServerListManager->m_Server.m_svHostMap = svServerMapName;
g_pServerListManager->m_Server.m_svPlaylist = svServerPlaylist;
g_pServerListManager->m_ServerVisibility = eServerVisibility;

View File

@ -37,6 +37,7 @@ namespace VSquirrel
}
namespace UI
{
SQRESULT RefreshServerCount(HSQUIRRELVM v);
SQRESULT GetServerName(HSQUIRRELVM v);
SQRESULT GetServerDescription(HSQUIRRELVM v);
SQRESULT GetServerMap(HSQUIRRELVM v);

View File

@ -81,6 +81,8 @@ void Script_RegisterUIFunctions(CSquirrelVM* pSquirrelVM)
{
Script_RegisterFunction(pSquirrelVM, "SDKNativeTest", "Script_SDKNativeTest", "Native UI test function", "void", "", &VSquirrel::SHARED::SDKNativeTest);
Script_RegisterFunction(pSquirrelVM, "RefreshServerList", "Script_RefreshServerList", "Refreshes the public server list and returns the count", "int", "", &VSquirrel::UI::RefreshServerCount);
// Functions for retrieving server browser data
Script_RegisterFunction(pSquirrelVM, "GetServerName", "Script_GetServerName", "Gets the name of the server at the specified index of the server list", "string", "int", &VSquirrel::UI::GetServerName);
Script_RegisterFunction(pSquirrelVM, "GetServerDescription", "Script_GetServerDescription", "Gets the description of the server at the specified index of the server list", "string", "int", &VSquirrel::UI::GetServerDescription);
@ -249,7 +251,7 @@ void Script_Execute(const SQChar* code, SQCONTEXT context)
{
if (!ThreadInMainThread())
{
g_DelayedCallTask->AddFunc([code, context]()
g_TaskScheduler->Dispatch([code, context]()
{
string scode(code);
Script_Execute(scode.c_str(), context);

View File

@ -44,7 +44,7 @@ bool CFrameTask::IsFinished() const
// Input : functor -
// frames -
//-----------------------------------------------------------------------------
void CFrameTask::AddFunc(std::function<void()> functor, int frames)
void CFrameTask::Dispatch(std::function<void()> functor, int frames)
{
std::lock_guard<std::mutex> l(m_Mutex);
m_DelayedCalls.emplace_back(frames, functor);
@ -52,4 +52,4 @@ void CFrameTask::AddFunc(std::function<void()> functor, int frames)
//-----------------------------------------------------------------------------
std::list<IFrameTask*> g_FrameTasks;
CFrameTask* g_DelayedCallTask = new CFrameTask();
CFrameTask* g_TaskScheduler = new CFrameTask();

View File

@ -3,6 +3,13 @@
#include "public/iframetask.h"
//=============================================================================//
// This class is set up to run before each frame (main thread).
// Commited tasks are scheduled to execute after 'i' frames.
// ----------------------------------------------------------------------------
// A usecase for scheduling tasks in the main thread would be (for example)
// calling 'KeyValues::ParsePlaylists(...)' from the render thread.
//=============================================================================//
class CFrameTask : public IFrameTask
{
public:
@ -10,14 +17,14 @@ public:
virtual void RunFrame();
virtual bool IsFinished() const;
void AddFunc(std::function<void()> functor, int frames);
void Dispatch(std::function<void()> functor, int frames);
private:
std::mutex m_Mutex;
mutable std::mutex m_Mutex;
std::list<DelayedCall_s> m_DelayedCalls;
};
extern std::list<IFrameTask*> g_FrameTasks;
extern CFrameTask* g_DelayedCallTask;
extern CFrameTask* g_TaskScheduler;
#endif // TIER0_FRAMETASK_H

View File

@ -199,7 +199,8 @@ void ConVar::Init(void) const
net_useRandomKey = ConVar::Create("net_useRandomKey" , "1" , FCVAR_RELEASE , "Use random base64 netkey for game packets.", false, 0.f, false, 0.f, nullptr, nullptr);
//-------------------------------------------------------------------------
// NETWORKSYSTEM |
pylon_matchmaking_hostname = ConVar::Create("pylon_matchmaking_hostname", "r5a-comp-sv.herokuapp.com", FCVAR_RELEASE , "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, nullptr, nullptr);
pylon_matchmaking_hostname = ConVar::Create("pylon_matchmaking_hostname", "r5a-comp-sv.herokuapp.com", FCVAR_RELEASE , "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, &MP_HostName_Changed_f, nullptr);
pylon_host_update_interval = ConVar::Create("pylon_host_update_interval", "5" , FCVAR_RELEASE , "Length of time in seconds between each status update interval to master server.", true, 5.f, false, 0.f, nullptr, nullptr);
pylon_showdebug = ConVar::Create("pylon_showdebug" , "0" , FCVAR_DEVELOPMENTONLY, "Shows debug output for pylon.", false, 0.f, false, 0.f, nullptr, nullptr);
//-------------------------------------------------------------------------
// RTECH API |

View File

@ -166,6 +166,7 @@ ConVar* net_encryptionEnable = nullptr;
ConVar* net_useRandomKey = nullptr;
ConVar* net_usesocketsforloopback = nullptr;
ConVar* pylon_matchmaking_hostname = nullptr;
ConVar* pylon_host_update_interval = nullptr;
ConVar* pylon_showdebug = nullptr;
//-----------------------------------------------------------------------------
// RTECH API |

View File

@ -161,6 +161,7 @@ extern ConVar* net_encryptionEnable;
extern ConVar* net_useRandomKey;
extern ConVar* net_usesocketsforloopback;
extern ConVar* pylon_matchmaking_hostname;
extern ConVar* pylon_host_update_interval;
extern ConVar* pylon_showdebug;
//-------------------------------------------------------------------------
// RTECH API |

View File

@ -61,6 +61,18 @@ void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOld
SetupGamemode(mp_gamemode->GetString());
}
/*
=====================
MP_HostName_Changed_f
=====================
*/
void MP_HostName_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue)
{
#ifndef DEDICATED
g_pBrowser->SetHostName(pylon_matchmaking_hostname->GetString());
#endif // !DEDICATED
}
#ifndef DEDICATED
/*
=====================

View File

@ -5,14 +5,12 @@ inline CMemory p_SetupGamemode;
inline auto SetupGamemode = p_SetupGamemode.RCast<bool(*)(const char* pszPlayList)>();
/* ==== CONCOMMANDCALLBACK ============================================================================================================================================== */
inline CMemory p_Host_Map_f;
inline auto _Host_Map_f = p_Host_Map_f.RCast<void (*)(CCommand* pCommand, char a2)>();
inline CMemory p_DownloadPlaylists_f;
inline auto _DownloadPlaylists_f = p_DownloadPlaylists_f.RCast<void(*)(void)>();
///////////////////////////////////////////////////////////////////////////////
void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue);
void MP_HostName_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue);
#ifndef DEDICATED
void GameConsole_Invoke_f(const CCommand& args);
void ServerBrowser_Invoke_f(const CCommand& args);
@ -67,22 +65,15 @@ class VCallback : public IDetour
virtual void GetAdr(void) const
{
spdlog::debug("| FUN: SetupGamemode : {:#18x} |\n", p_SetupGamemode.GetPtr());
spdlog::debug("| FUN: Host_Map_f : {:#18x} |\n", p_Host_Map_f.GetPtr());
spdlog::debug("| FUN: DownloadPlaylist_f : {:#18x} |\n", p_DownloadPlaylists_f.GetPtr());
spdlog::debug("+----------------------------------------------------------------+\n");
}
virtual void GetFun(void) const
{
p_SetupGamemode = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x40\x53\x48\x83\xEC\x20\x48\x8B\xD9\x48\xC7\xC0\x00\x00\x00\x00"), "xxxxxxxxxxxx????");
#if defined (GAMEDLL_S0) || defined (GAMEDLL_S1)
p_Host_Map_f = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x48\x89\x5C\x24\x18\x55\x41\x56\x41\x00\x00\x00\x00\x40\x02"), "xxxxxxxxx????xx");
#elif defined (GAMEDLL_S2) || defined (GAMEDLL_S3)
p_Host_Map_f = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x40\x55\x41\x56\x41\x57\x48\x81\xEC\x00\x00\x00\x00\x83\x3D"), "xxxxxxxxx????xx");
#endif
p_DownloadPlaylists_f = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x33\xC9\xC6\x05\x00\x00\x00\x00\x00\xE9\x00\x00\x00\x00"), "xxxx?????x????");
SetupGamemode = p_SetupGamemode.RCast<bool(*)(const char*)>(); /*40 53 48 83 EC 20 48 8B D9 48 C7 C0 ?? ?? ?? ??*/
_Host_Map_f = p_Host_Map_f.RCast<void (*)(CCommand*, char)>(); /*40 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 83 3D*/
_DownloadPlaylists_f = p_DownloadPlaylists_f.RCast<void(*)(void)>(); /*33 C9 C6 05 ?? ?? ?? ?? ?? E9 ?? ?? ?? ??*/
}
virtual void GetVar(void) const { }

View File

@ -136,7 +136,7 @@ DWORD __stdcall ProcessConsoleWorker(LPVOID)
// Execute the command.
Cbuf_AddText(Cbuf_GetCurrentPlayer(), sCommand.c_str(), cmd_source_t::kCommandSrcCode);
//g_DelayedCallTask->AddFunc(Cbuf_Execute, 0);
//g_TaskScheduler->Dispatch(Cbuf_Execute, 0);
sCommand.clear();