mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Before, we had to do a hack of capturing the command line using GetCommandLineA, and then checking if a certain argument is present. This was required due to how early the GameSDK dll was loaded (the g_CmdLine object was far from initialized in the engine). Due to the loader refactor, the commandline can be used directly after creation in the game's entry point (which is the time the SDK is getting loaded). Therefore, no copies of the command line are required anymore. This commit contains the following changes: - Correctly ordered the initialization, and deinitialization of systems (first init = last shutdown). - Factored out command line string copy in favor of game's implementation. - Factored the R5Reloaded emblem print into its own function. - Removed 'SpdLog_PostInit()', we can now directly call DevMsg() once SpdLog_Init() has been called, the logger callback sink deals with the formatting of the output. - Fixed a bug where the logger did not print the correct color for 'SYSTEM_WARNING' and 'SYSTEM_ERROR' in the external console. - Fixed a bug where the command line did not work when the game wasn't launched with the '-launcher' parameter. - Logs now equally appear on the external, and in-game console windows.
405 lines
11 KiB
C++
405 lines
11 KiB
C++
//=====================================================================================//
|
|
//
|
|
// Purpose: Lightweight netconsole client.
|
|
//
|
|
//=====================================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "core/logdef.h"
|
|
#include "core/logger.h"
|
|
#include "tier0/utility.h"
|
|
#include "tier1/NetAdr.h"
|
|
#include "tier2/socketcreator.h"
|
|
#include "windows/console.h"
|
|
#include "protoc/sv_rcon.pb.h"
|
|
#include "protoc/cl_rcon.pb.h"
|
|
#include "engine/net.h"
|
|
#include "engine/shared/shared_rcon.h"
|
|
#include "netconsole/netconsole.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CNetCon::CNetCon(void)
|
|
: m_bInitialized(false)
|
|
, m_bQuitApplication(false)
|
|
, m_bPromptConnect(true)
|
|
, m_flTickInterval(0.05f)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CNetCon::~CNetCon(void)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: WSA and NETCON systems init
|
|
// Output : true on success, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::Init(void)
|
|
{
|
|
g_CoreMsgVCallback = &EngineLoggerSink;
|
|
|
|
WSAData wsaData;
|
|
const int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
|
|
if (nError != 0)
|
|
{
|
|
Error(eDLL_T::CLIENT, NO_ERROR, "%s - Failed to start Winsock: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError()));
|
|
return false;
|
|
}
|
|
|
|
m_bInitialized = true;
|
|
|
|
TermSetup();
|
|
DevMsg(eDLL_T::NONE, "R5 TCP net console [Version %s]\n", NETCON_VERSION);
|
|
|
|
static std::thread frame([this]()
|
|
{
|
|
for (;;)
|
|
{
|
|
RunFrame();
|
|
}
|
|
});
|
|
frame.detach();
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: WSA and NETCON systems shutdown
|
|
// Output : true on success, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::Shutdown(void)
|
|
{
|
|
bool bResult = false;
|
|
|
|
m_Socket.CloseAllAcceptedSockets();
|
|
|
|
const int nError = ::WSACleanup();
|
|
if (nError == 0)
|
|
{
|
|
bResult = true;
|
|
}
|
|
else // WSACleanup() failed.
|
|
{
|
|
Error(eDLL_T::CLIENT, NO_ERROR, "%s - Failed to stop Winsock: (%s)\n",
|
|
__FUNCTION__, NET_ErrorString(WSAGetLastError()));
|
|
}
|
|
|
|
SpdLog_Shutdown();
|
|
Console_Shutdown();
|
|
|
|
return bResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: terminal setup
|
|
//-----------------------------------------------------------------------------
|
|
void CNetCon::TermSetup(void)
|
|
{
|
|
const char* pszCommandLine = GetCommandLineA();
|
|
const bool bEnableColor = strstr("-ansicolor", pszCommandLine) != nullptr;
|
|
|
|
SpdLog_Init(bEnableColor);
|
|
Console_Init(bEnableColor);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: gets input IP and port for initialization
|
|
//-----------------------------------------------------------------------------
|
|
void CNetCon::UserInput(void)
|
|
{
|
|
if (std::getline(std::cin, m_Input))
|
|
{
|
|
if (m_Input.compare("nquit") == 0)
|
|
{
|
|
m_bQuitApplication = true;
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> l(m_Mutex);
|
|
if (IsConnected())
|
|
{
|
|
if (m_Input.compare("disconnect") == 0)
|
|
{
|
|
Disconnect("user closed connection");
|
|
return;
|
|
}
|
|
|
|
const vector<string> vSubStrings = StringSplit(m_Input, ' ', 2);
|
|
vector<char> vecMsg;
|
|
|
|
const SocketHandle_t hSocket = GetSocket();
|
|
bool bSend = false;
|
|
|
|
if (vSubStrings.size() > 1)
|
|
{
|
|
if (vSubStrings[0].compare("PASS") == 0) // Auth with RCON server.
|
|
{
|
|
bSend = Serialize(vecMsg, vSubStrings[1].c_str(), "",
|
|
cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
|
|
}
|
|
else if (vSubStrings[0].compare("SET") == 0) // Set value query.
|
|
{
|
|
if (vSubStrings.size() > 2)
|
|
{
|
|
bSend = Serialize(vecMsg, vSubStrings[1].c_str(), vSubStrings[2].c_str(),
|
|
cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE);
|
|
}
|
|
}
|
|
else // Execute command query.
|
|
{
|
|
bSend = Serialize(vecMsg, m_Input.c_str(), "",
|
|
cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
|
|
}
|
|
}
|
|
else if (!m_Input.empty()) // Single arg command query.
|
|
{
|
|
bSend = Serialize(vecMsg, m_Input.c_str(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
|
|
}
|
|
|
|
if (bSend) // Only send if serialization process was successful.
|
|
{
|
|
if (!Send(hSocket, vecMsg.data(), int(vecMsg.size())))
|
|
{
|
|
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
|
|
}
|
|
}
|
|
}
|
|
else // Setup connection from input.
|
|
{
|
|
const vector<string> vSubStrings = StringSplit(m_Input, ' ', 2);
|
|
if (vSubStrings.size() > 1)
|
|
{
|
|
const string::size_type nPos = m_Input.find(' ');
|
|
if (nPos > 0
|
|
&& nPos < m_Input.size()
|
|
&& nPos != m_Input.size())
|
|
{
|
|
string svInPort = m_Input.substr(nPos + 1);
|
|
string svInAdr = m_Input.erase(m_Input.find(' '));
|
|
|
|
if (svInPort.empty() || svInAdr.empty())
|
|
{
|
|
Warning(eDLL_T::CLIENT, "No IP address or port provided\n");
|
|
m_bPromptConnect = true;
|
|
return;
|
|
}
|
|
|
|
if (!Connect(svInAdr.c_str(), atoi(svInPort.c_str())))
|
|
{
|
|
m_bPromptConnect = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else // Initialize as [ip]:port.
|
|
{
|
|
if (m_Input.empty() || !Connect(m_Input.c_str()))
|
|
{
|
|
m_bPromptConnect = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: clears the input buffer
|
|
//-----------------------------------------------------------------------------
|
|
void CNetCon::ClearInput(void)
|
|
{
|
|
m_Input.clear();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: client's main processing loop
|
|
//-----------------------------------------------------------------------------
|
|
void CNetCon::RunFrame(void)
|
|
{
|
|
if (IsInitialized() && IsConnected())
|
|
{
|
|
std::lock_guard<std::mutex> l(m_Mutex);
|
|
|
|
CConnectedNetConsoleData* pData = m_Socket.GetAcceptedSocketData(0);
|
|
Recv(pData);
|
|
}
|
|
else if (m_bPromptConnect)
|
|
{
|
|
DevMsg(eDLL_T::NONE, "Enter [<IP>]:<PORT> or <IP> <PORT>: ");
|
|
m_bPromptConnect = false;
|
|
}
|
|
|
|
std::this_thread::sleep_for(IntervalToDuration(m_flTickInterval));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: checks if application should be terminated
|
|
// Output : true for termination, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::ShouldQuit(void) const
|
|
{
|
|
return m_bQuitApplication;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: disconnect from current session
|
|
// Input : *szReason -
|
|
//-----------------------------------------------------------------------------
|
|
void CNetCon::Disconnect(const char* szReason)
|
|
{
|
|
if (IsConnected())
|
|
{
|
|
if (!szReason)
|
|
{
|
|
szReason = "unknown reason";
|
|
}
|
|
|
|
DevMsg(eDLL_T::CLIENT, "Disconnect: (%s)\n", szReason);
|
|
m_Socket.CloseAcceptedSocket(0);
|
|
}
|
|
|
|
m_bPromptConnect = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: processes received message
|
|
// Input : *pMsgBuf -
|
|
// nMsgLen -
|
|
// Output : true on success, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::ProcessMessage(const char* pMsgBuf, const int nMsgLen)
|
|
{
|
|
sv_rcon::response response;
|
|
bool bSuccess = Decode(&response, pMsgBuf, nMsgLen);
|
|
|
|
if (!bSuccess)
|
|
{
|
|
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to decode RCON buffer\n");
|
|
return false;
|
|
}
|
|
|
|
switch (response.responsetype())
|
|
{
|
|
case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH:
|
|
{
|
|
if (!response.responseval().empty())
|
|
{
|
|
const long i = strtol(response.responseval().c_str(), NULL, NULL);
|
|
if (!i) // sv_rcon_sendlogs is not set.
|
|
{
|
|
vector<char> vecMsg;
|
|
bool ret = Serialize(vecMsg, "", "1", cl_rcon::request_t::SERVERDATA_REQUEST_SEND_CONSOLE_LOG);
|
|
|
|
if (ret && !Send(GetSocket(), vecMsg.data(), int(vecMsg.size())))
|
|
{
|
|
Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
|
|
}
|
|
}
|
|
}
|
|
|
|
DevMsg(eDLL_T::NETCON, "%s", response.responsemsg().c_str());
|
|
break;
|
|
}
|
|
case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG:
|
|
{
|
|
NetMsg(static_cast<LogType_t>(response.messagetype()),
|
|
static_cast<eDLL_T>(response.messageid()),
|
|
response.responseval().c_str(), "%s", response.responsemsg().c_str());
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: serializes message to vector
|
|
// Input : &vecBuf -
|
|
// *szReqBuf -
|
|
// *svReqVal -
|
|
// requestType -
|
|
// Output : true on success, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::Serialize(vector<char>& vecBuf, const char* szReqBuf,
|
|
const char* szReqVal, const cl_rcon::request_t requestType) const
|
|
{
|
|
return CL_NetConSerialize(this, vecBuf, szReqBuf, szReqVal, requestType);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: retrieves the remote socket
|
|
// Output : SOCKET_ERROR (-1) on failure
|
|
//-----------------------------------------------------------------------------
|
|
SocketHandle_t CNetCon::GetSocket(void)
|
|
{
|
|
return SH_GetNetConSocketHandle(this, 0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns whether the rcon client is initialized
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::IsInitialized(void) const
|
|
{
|
|
return m_bInitialized;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns whether the rcon client is connected
|
|
//-----------------------------------------------------------------------------
|
|
bool CNetCon::IsConnected(void)
|
|
{
|
|
return (GetSocket() != SOCKET_ERROR);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: entrypoint
|
|
// Input : argc -
|
|
// *argv -
|
|
//-----------------------------------------------------------------------------
|
|
int main(int argc, char* argv[])
|
|
{
|
|
if (!NetConsole()->Init())
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (argc >= 3) // Get IP and Port from command line.
|
|
{
|
|
if (!NetConsole()->Connect(argv[1], atoi(argv[2])))
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
while (!NetConsole()->ShouldQuit())
|
|
{
|
|
NetConsole()->UserInput();
|
|
NetConsole()->ClearInput();
|
|
}
|
|
|
|
if (!NetConsole()->Shutdown())
|
|
{
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// singleton
|
|
//-----------------------------------------------------------------------------
|
|
CNetCon g_NetCon;
|
|
inline CNetCon* NetConsole()
|
|
{
|
|
return &g_NetCon;
|
|
} |