diff options
Diffstat (limited to 'NetConsole/netconsole.cpp')
-rw-r--r-- | NetConsole/netconsole.cpp | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/NetConsole/netconsole.cpp b/NetConsole/netconsole.cpp new file mode 100644 index 00000000..cfdd31e1 --- /dev/null +++ b/NetConsole/netconsole.cpp @@ -0,0 +1,405 @@ +//=====================================================================================// +// +// Purpose: Lightweight netconsole client. +// +//=====================================================================================// + +#include "pch.h" +#include "NetAdr2.h" +#include "socketcreator.h" +#include "sv_rcon.pb.h" +#include "cl_rcon.pb.h" +#include "net.h" +#include "netconsole.h" + +//----------------------------------------------------------------------------- +// Purpose: WSA and NETCON systems init +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +bool CNetCon::Init(void) +{ + WSAData wsaData{}; + int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + + if (nError != 0) + { + spdlog::error("Failed to start Winsock via WSAStartup: ({})", NET_ErrorString(WSAGetLastError())); + return false; + } + + this->TermSetup(); + + std::thread tFrame(&CNetCon::RunFrame, this); + tFrame.detach(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: WSA and NETCON systems shutdown +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +bool CNetCon::Shutdown(void) +{ + m_pSocket->CloseAllAcceptedSockets(); + m_abConnEstablished = false; + + int nError = ::WSACleanup(); + if (nError != 0) + { + spdlog::error("Failed to stop winsock via WSACleanup: ({})", NET_ErrorString(WSAGetLastError())); + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: terminal setup +//----------------------------------------------------------------------------- +void CNetCon::TermSetup(void) +{ + DWORD dwMode = NULL; + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFOEX sbInfoEx{}; + COLORREF storedBG = sbInfoEx.ColorTable[0]; + sbInfoEx.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + + GetConsoleScreenBufferInfoEx(hOutput, &sbInfoEx); + sbInfoEx.ColorTable[0] = 0x0000; + SetConsoleScreenBufferInfoEx(hOutput, &sbInfoEx); +} + +//----------------------------------------------------------------------------- +// Purpose: gets input IP and port for initialization +//----------------------------------------------------------------------------- +void CNetCon::UserInput(void) +{ + std::string svInput; + + if (std::getline(std::cin, svInput)) + { + if (strcmp(svInput.c_str(), "nquit") == 0) + { + m_bQuitApplication = true; + return; + } + if (m_abConnEstablished) + { + if (strcmp(svInput.c_str(), "disconnect") == 0) + { + this->Disconnect(); + return; + } + size_t nPos = svInput.find(" "); + if (!svInput.empty() && nPos > 0 && nPos < svInput.size() && nPos != svInput.size()) + { + std::string svReqVal = svInput.substr(nPos + 1); + std::string svReqBuf = svInput.erase(svInput.find(" ")); + + if (strcmp(svReqBuf.c_str(), "PASS") == 0) // Auth with RCON server. + { + std::string svSerialized = this->Serialize(svReqBuf, svReqVal, cl_rcon::request_t::SERVERDATA_REQUEST_AUTH); + this->Send(svSerialized); + } + else // This is a ConVar. + { + std::string svSerialized = this->Serialize(svReqBuf, svReqVal, cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE); + this->Send(svSerialized); + } + } + else // This is a ConCommand. + { + std::string svSerialized = this->Serialize(svInput, "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND); + this->Send(svSerialized); + } + } + else // Setup connection from input. + { + size_t nPos = svInput.find(" "); + if (!svInput.empty() && nPos > 0 && nPos < svInput.size() && nPos != svInput.size()) + { + std::string svInPort = svInput.substr(nPos + 1); + std::string svInAdr = svInput.erase(svInput.find(" ")); + + if (!this->Connect(svInAdr, svInPort)) + { + m_abPromptConnect = true; + return; + } + } + else // Initialize as [127.0.0.1]:37015. + { + if (!this->Connect("", "")) + { + m_abPromptConnect = true; + return; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: client's main processing loop +//----------------------------------------------------------------------------- +void CNetCon::RunFrame(void) +{ + for (;;) + { + 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; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: checks if application should be terminated +// Output : true for termination, false otherwise +//----------------------------------------------------------------------------- +bool CNetCon::ShouldQuit(void) const { return this->m_bQuitApplication; } + +//----------------------------------------------------------------------------- +// Purpose: connect to specified address and port +// Input : *svInAdr - +// *svInPort - +// Output : true if connection succeeds, false otherwise +//----------------------------------------------------------------------------- +bool CNetCon::Connect(const std::string& svInAdr, const std::string& svInPort) +{ + if (svInAdr.size() > 0 && svInPort.size() > 0) + { + // Default is [127.0.0.1]:37015 + m_pNetAdr2->SetIPAndPort(svInAdr, svInPort); + } + + if (m_pSocket->ConnectSocket(*m_pNetAdr2, true) == SOCKET_ERROR) + { + spdlog::warn("Failed to connect. Error: (SOCKET_ERROR). Verify IP and PORT."); + return false; + } + spdlog::info("Connected to: {}", m_pNetAdr2->GetIPAndPort()); + + m_abConnEstablished = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: disconnect from current session +//----------------------------------------------------------------------------- +void CNetCon::Disconnect(void) +{ + ::closesocket(m_pSocket->GetAcceptedSocketHandle(0)); + m_abPromptConnect = true; + m_abConnEstablished = false; +} + +//----------------------------------------------------------------------------- +// Purpose: send message +// Input : *svMessage - +//----------------------------------------------------------------------------- +void CNetCon::Send(const std::string& svMessage) const +{ + int nSendResult = ::send(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, svMessage.c_str(), svMessage.size(), MSG_NOSIGNAL); + if (nSendResult == SOCKET_ERROR) + { + spdlog::warn("Failed to send message: (SOCKET_ERROR)"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: receive message +//----------------------------------------------------------------------------- +void CNetCon::Recv(void) +{ + static char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{}; + + { ////////////////////////////////////////////// + int nPendingLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, sizeof(szRecvBuf), MSG_PEEK); + if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking()) + { + return; + } + if (nPendingLen <= 0 && m_abConnEstablished) // EOF or error. + { + this->Disconnect(); + spdlog::info("Server closed connection"); + return; + } + } ////////////////////////////////////////////// + + u_long nReadLen; // Find out how much we have to read. + ::ioctlsocket(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, FIONREAD, &nReadLen); + + while (nReadLen > 0) + { + memset(szRecvBuf, '\0', sizeof(szRecvBuf)); + int nRecvLen = ::recv(m_pSocket->GetAcceptedSocketData(0)->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), MSG_NOSIGNAL); + + if (nRecvLen == 0 && m_abConnEstablished) // Socket was closed. + { + this->Disconnect(); + spdlog::info("Server closed connection"); + break; + } + if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking()) + { + break; + } + + nReadLen -= nRecvLen; // Process what we've got. + this->ProcessBuffer(szRecvBuf, nRecvLen); + } +} + +//----------------------------------------------------------------------------- +// Purpose: handles input response buffer +// Input : *pszIn - +// nRecvLen - +//----------------------------------------------------------------------------- +void CNetCon::ProcessBuffer(const char* pszIn, int nRecvLen) const +{ + int nCharsInRespondBuffer = 0; + char szInputRespondBuffer[MAX_NETCONSOLE_INPUT_LEN]{}; + + while (nRecvLen) + { + switch (*pszIn) + { + case '\r': + { + if (nCharsInRespondBuffer) + { + sv_rcon::response sv_response = this->Deserialize(szInputRespondBuffer); + this->ProcessMessage(sv_response); + } + nCharsInRespondBuffer = 0; + break; + } + + default: + { + if (nCharsInRespondBuffer < MAX_NETCONSOLE_INPUT_LEN - 1) + { + szInputRespondBuffer[nCharsInRespondBuffer++] = *pszIn; + } + break; + } + } + pszIn++; + nRecvLen--; + } +} + +//----------------------------------------------------------------------------- +// Purpose: processes received message +// Input : *sv_response - +//----------------------------------------------------------------------------- +void CNetCon::ProcessMessage(const sv_rcon::response& sv_response) const +{ + switch (sv_response.responsetype()) + { + case sv_rcon::response_t::SERVERDATA_RESPONSE_AUTH: + case sv_rcon::response_t::SERVERDATA_RESPONSE_CONSOLE_LOG: + { + std::string svOut = sv_response.responsebuf(); + svOut.erase(std::remove(svOut.begin(), svOut.end(), '\n'), svOut.end()); + spdlog::info(svOut.c_str()); + break; + } + default: + { + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: serializes input +// Input : *svReqBuf - +// *svReqVal - +// request_t - +// Output : serialized results as string +//----------------------------------------------------------------------------- +std::string CNetCon::Serialize(const std::string& svReqBuf, const std::string& svReqVal, cl_rcon::request_t request_t) const +{ + cl_rcon::request cl_request; + + cl_request.set_requestid(-1); + cl_request.set_requesttype(request_t); + + switch (request_t) + { + case cl_rcon::request_t::SERVERDATA_REQUEST_SETVALUE: + case cl_rcon::request_t::SERVERDATA_REQUEST_AUTH: + { + cl_request.set_requestbuf(svReqBuf); + cl_request.set_requestval(svReqVal); + break; + } + case cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND: + { + cl_request.set_requestbuf(svReqBuf); + break; + } + } + return cl_request.SerializeAsString().append("\r"); +} + +//----------------------------------------------------------------------------- +// Purpose: de-serializes input +// Input : *svBuf - +// Output : de-serialized object +//----------------------------------------------------------------------------- +sv_rcon::response CNetCon::Deserialize(const std::string& svBuf) const +{ + sv_rcon::response sv_response; + sv_response.ParseFromArray(svBuf.c_str(), static_cast<int>(svBuf.size())); + + return sv_response; +} + +//----------------------------------------------------------------------------- +// Purpose: entrypoint +// Input : argc - +// *argv - +//----------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + CNetCon* pNetCon = new CNetCon(); + std::cout << "Northstar TCP net console [Version " << NETCON_VERSION << "]" << std::endl; + spdlog::default_logger()->set_pattern("[%H:%M:%S] [%l] %v"); + + if (!pNetCon->Init()) + { + return EXIT_FAILURE; + } + + if (argc >= 3) // Get IP and Port from command line. + { + if (!pNetCon->Connect(argv[1], argv[2])) + { + return EXIT_FAILURE; + } + } + + while (!pNetCon->ShouldQuit()) + { + pNetCon->UserInput(); + } + + if (!pNetCon->Shutdown()) + { + return EXIT_FAILURE; + } + + return ERROR_SUCCESS; +}
\ No newline at end of file |