diff --git a/detours.sln b/detours.sln index 4aed7e75..556de107 100644 --- a/detours.sln +++ b/detours.sln @@ -3,12 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31808.319 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "r5devdll", "r5dev\r5dev.vcxproj", "{28CC6B4F-7A95-4933-ADA9-65E38D48516D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "r5dev", "r5dev\r5dev.vcxproj", "{28CC6B4F-7A95-4933-ADA9-65E38D48516D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdklauncher", "r5dev\sdklauncher.vcxproj", "{18F8C75E-3844-4AA6-AB93-980A08253519}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dedicated", "r5dev\dedicated.vcxproj", "{ED2C50B3-7C2C-4E44-988E-DAA059F72B9C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netconsole", "r5dev\netconsole.vcxproj", "{9579B31F-CE24-4852-A941-CD1AD71E2248}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -41,6 +43,14 @@ Global {ED2C50B3-7C2C-4E44-988E-DAA059F72B9C}.Release|x64.Build.0 = Release|x64 {ED2C50B3-7C2C-4E44-988E-DAA059F72B9C}.Release|x86.ActiveCfg = Release|Win32 {ED2C50B3-7C2C-4E44-988E-DAA059F72B9C}.Release|x86.Build.0 = Release|Win32 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Debug|x64.ActiveCfg = Debug|x64 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Debug|x64.Build.0 = Debug|x64 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Debug|x86.ActiveCfg = Debug|Win32 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Debug|x86.Build.0 = Debug|Win32 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Release|x64.ActiveCfg = Release|x64 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Release|x64.Build.0 = Release|x64 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Release|x86.ActiveCfg = Release|Win32 + {9579B31F-CE24-4852-A941-CD1AD71E2248}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/license/mathlib/CRC32.txt b/license/mathlib/CRC32.txt new file mode 100644 index 00000000..db2dd059 --- /dev/null +++ b/license/mathlib/CRC32.txt @@ -0,0 +1,2 @@ +* COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or +* code or tables extracted from it, as desired without restriction. diff --git a/license/mathlib/SHA256.txt b/license/mathlib/SHA256.txt new file mode 100644 index 00000000..6bd905f6 --- /dev/null +++ b/license/mathlib/SHA256.txt @@ -0,0 +1,36 @@ +/* + * Updated to C++, zedwood.com 2012 + * Based on Olivier Gay's version + * See Modified BSD License below: + * + * FIPS 180-2 SHA-224/256/384/512 implementation + * Issue date: 04/30/2005 + * http://www.ouah.org/ogay/sha2/ + * + * Copyright (C) 2005, 2007 Olivier Gay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/r5dev/common/igameserverdata.h b/r5dev/common/igameserverdata.h new file mode 100644 index 00000000..cdf8c4c2 --- /dev/null +++ b/r5dev/common/igameserverdata.h @@ -0,0 +1,48 @@ +//===========================================================================// +// +// Purpose: Enumerations for writing out the requests. +// +//===========================================================================// +#pragma once + +enum class ServerDataRequestType_t : int +{ + SERVERDATA_REQUESTVALUE = 0, + SERVERDATA_SETVALUE, + SERVERDATA_EXECCOMMAND, + SERVERDATA_AUTH, + SERVERDATA_VPROF, + SERVERDATA_REMOVE_VPROF, + SERVERDATA_TAKE_SCREENSHOT, + SERVERDATA_SEND_CONSOLE_LOG, + SERVERDATA_SEND_REMOTEBUG, +}; + +enum class ServerDataResponseType_t : int +{ + SERVERDATA_RESPONSE_VALUE = 0, + SERVERDATA_UPDATE, + SERVERDATA_AUTH_RESPONSE, + SERVERDATA_VPROF_DATA, + SERVERDATA_VPROF_GROUPS, + SERVERDATA_SCREENSHOT_RESPONSE, + SERVERDATA_CONSOLE_LOG_RESPONSE, + SERVERDATA_RESPONSE_STRING, + SERVERDATA_RESPONSE_REMOTEBUG, +}; + +/* PACKET FORMAT ********************************** + +REQUEST: + int requestID; + int ServerDataRequestType_t; + NullTerminatedString (variable or command) + NullTerminatedString (value) + +RESPONSE: + int requestID; + int ServerDataResponseType_t; + NullTerminatedString (variable) + NullTerminatedString (value) + +***************************************************/ diff --git a/r5dev/core/stdafx.cpp b/r5dev/core/stdafx.cpp index 1f4c90b9..371042be 100644 --- a/r5dev/core/stdafx.cpp +++ b/r5dev/core/stdafx.cpp @@ -1 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// *.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + #include "core/stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/r5dev/core/stdafx.h b/r5dev/core/stdafx.h index 9e3cfef7..894b1a1f 100644 --- a/r5dev/core/stdafx.h +++ b/r5dev/core/stdafx.h @@ -21,26 +21,26 @@ #include #include -#if !defined(DEDICATED) +#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) #include -#endif // !DEDICATED +#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE #include "thirdparty/detours/include/detours.h" #include "thirdparty/detours/include/idetour.h" -#if !defined(DEDICATED) +#if !defined(DEDICATED) && !defined(SDKLAUNCHER) && !defined (NETCONSOLE) #include "thirdparty/imgui/include/imgui.h" #include "thirdparty/imgui/include/imgui_stdlib.h" #include "thirdparty/imgui/include/imgui_utility.h" #include "thirdparty/imgui/include/imgui_internal.h" #include "thirdparty/imgui/include/imgui_impl_dx11.h" #include "thirdparty/imgui/include/imgui_impl_win32.h" -#endif // !DEDICATED +#endif // !DEDICATED && !SDKLAUNCHER && !NETCONSOLE -#if !defined(SDKLAUNCHER) +#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) #include "thirdparty/lzham/include/lzham_types.h" #include "thirdparty/lzham/include/lzham.h" -#endif // !SDKLAUNCHER +#endif // !SDKLAUNCHER && !NETCONSOLE #include "thirdparty/spdlog/include/spdlog.h" #include "thirdparty/spdlog/include/async.h" @@ -61,24 +61,23 @@ #include "common/pseudodefs.h" #include "tier0/basetypes.h" -#if !defined (SDKLAUNCHER) +#if !defined(SDKLAUNCHER) && !defined (NETCONSOLE) namespace { #if !defined (DEDICATED) MODULE g_mGameDll = MODULE("r5apex.exe"); -#else +#else // No DX imports. MODULE g_mGameDll = MODULE("r5apex_ds.exe"); #endif // !DEDICATED MODULE g_mRadVideoToolsDll = MODULE("bink2w64.dll"); MODULE g_mRadAudioDecoderDll = MODULE("binkawin64.dll"); MODULE g_mRadAudioSystemDll = MODULE("mileswin64.dll"); } -#endif // !SDKLAUNCHER -#define MEMBER_AT_OFFSET(varType, varName, offset) \ - varType& varName() \ - { \ - static int _##varName = offset; \ +#define MEMBER_AT_OFFSET(varType, varName, offset) \ + varType& varName() \ + { \ + static int _##varName = offset; \ return *(varType*)((std::uintptr_t)this + _##varName); \ } @@ -86,4 +85,5 @@ template ReturnType CallVFunc(int index, void* thisPtr, Args... args) { return (*reinterpret_cast(thisPtr))[index](thisPtr, args...); -} \ No newline at end of file +} +#endif // !SDKLAUNCHER && !NETCONSOLE \ No newline at end of file diff --git a/r5dev/dedicated.vcxproj b/r5dev/dedicated.vcxproj index 65b655e1..b28f8f68 100644 --- a/r5dev/dedicated.vcxproj +++ b/r5dev/dedicated.vcxproj @@ -172,6 +172,7 @@ + @@ -185,8 +186,10 @@ + + @@ -198,6 +201,8 @@ + + @@ -350,6 +355,8 @@ + + @@ -376,8 +383,10 @@ + + @@ -387,6 +396,7 @@ + @@ -500,6 +510,8 @@ + + diff --git a/r5dev/dedicated.vcxproj.filters b/r5dev/dedicated.vcxproj.filters index d29af520..c8249ec6 100644 --- a/r5dev/dedicated.vcxproj.filters +++ b/r5dev/dedicated.vcxproj.filters @@ -109,6 +109,12 @@ {463e0739-1e5f-47a0-94d1-6cf5b6bf3ea6} + + {da2c5c3d-eff4-404f-af3f-e30ec17dcc1a} + + + {efae8c5b-e29e-497f-8bbb-af3b213f6c79} + @@ -678,6 +684,27 @@ sdk\engine + + sdk\engine + + + sdk\tier2 + + + sdk\mathlib + + + sdk\tier1 + + + sdk\mathlib + + + sdk\common + + + sdk\engine + @@ -899,6 +926,21 @@ sdk\engine + + sdk\engine + + + sdk\tier2 + + + sdk\tier1 + + + sdk\mathlib + + + sdk\engine + diff --git a/r5dev/engine/cl_rcon.cpp b/r5dev/engine/cl_rcon.cpp new file mode 100644 index 00000000..83077ef7 --- /dev/null +++ b/r5dev/engine/cl_rcon.cpp @@ -0,0 +1,13 @@ +//===========================================================================// +// +// Purpose: Implementation of the rcon client +// +//===========================================================================// + +#include "core/stdafx.h" +#include "tier0/IConVar.h" +#include "tier0/ConCommand.h" +#include "engine/cl_rcon.h" +#include "common/igameserverdata.h" + +// TODO.. \ No newline at end of file diff --git a/r5dev/engine/cl_rcon.h b/r5dev/engine/cl_rcon.h new file mode 100644 index 00000000..ff77590b --- /dev/null +++ b/r5dev/engine/cl_rcon.h @@ -0,0 +1,10 @@ +#pragma once + +class CRConClient +{ + CRConClient(void){}; + ~CRConClient(void){}; + + void Authenticate(void){}; + void ProcessMessage(void){}; +}; \ No newline at end of file diff --git a/r5dev/engine/host_state.cpp b/r5dev/engine/host_state.cpp index c9ae6ee9..5a4faee8 100644 --- a/r5dev/engine/host_state.cpp +++ b/r5dev/engine/host_state.cpp @@ -1,6 +1,14 @@ +//=============================================================================// +// +// Purpose: Runs the state machine for the host & server +// +//=============================================================================// + #include "core/stdafx.h" #include "tier0/cvar.h" #include "tier0/commandline.h" +#include "tier1/NetAdr2.h" +#include "tier2/socketcreator.h" #include "engine/sys_utils.h" #include "engine/host_state.h" #include "engine/net_chan.h" @@ -9,6 +17,7 @@ #include "squirrel/sqinit.h" #include "public/include/bansystem.h" #include "engine/sys_engine.h" +#include "engine/sv_rcon.h" //----------------------------------------------------------------------------- // Purpose: Send keep alive request to Pylon Master Server. @@ -29,10 +38,6 @@ void KeepAliveToPylon() g_pCVar->FindVar("hostport")->GetString(), g_pCVar->FindVar("mp_gamemode")->GetString(), false, - - // BUG BUG: Checksum is null on dedi - // ADDITIONAL NOTES: seems to be related to scripts, this also happens when the listen server is started but the client from the same process never connects. - // Checksum only gets set on the server if the client from its own process connects to it. std::to_string(*g_nServerRemoteChecksum), std::string(), g_szNetKey.c_str() @@ -41,7 +46,6 @@ void KeepAliveToPylon() } } - //----------------------------------------------------------------------------- // Purpose: Check refuse list and kill netchan connection. //----------------------------------------------------------------------------- @@ -130,25 +134,32 @@ void HCHostState_FrameUpdate(void* rcx, void* rdx, float time) static bool bInitialized = false; if (!bInitialized) { - g_pConVar->ClearHostNames(); + g_pRConServer = new CRConServer(); if (!g_pCmdLine->CheckParm("-devsdk")) { IVEngineClient_CommandExecute(NULL, "exec autoexec_server.cfg"); + IVEngineClient_CommandExecute(NULL, "exec rcon_server.cfg"); #ifndef DEDICATED IVEngineClient_CommandExecute(NULL, "exec autoexec_client.cfg"); + IVEngineClient_CommandExecute(NULL, "exec rcon_client.cfg"); #endif // !DEDICATED IVEngineClient_CommandExecute(NULL, "exec autoexec.cfg"); } else // Development configs. { IVEngineClient_CommandExecute(NULL, "exec autoexec_server_dev.cfg"); + IVEngineClient_CommandExecute(NULL, "exec rcon_server_dev.cfg"); #ifndef DEDICATED IVEngineClient_CommandExecute(NULL, "exec autoexec_client_dev.cfg"); + IVEngineClient_CommandExecute(NULL, "exec rcon_client_dev.cfg"); #endif // !DEDICATED IVEngineClient_CommandExecute(NULL, "exec autoexec_dev.cfg"); } + g_pConVar->ClearHostNames(); + g_pRConServer->Init(); + *(bool*)m_bRestrictServerCommands = true; // Restrict commands. ConCommandBase* disconnect = (ConCommandBase*)g_pCVar->FindCommand("disconnect"); disconnect->AddFlags(FCVAR_SERVER_CAN_EXECUTE); // Make sure server is not restricted to this. @@ -181,6 +192,8 @@ void HCHostState_FrameUpdate(void* rcx, void* rdx, float time) bInitialized = true; } + g_pRConServer->RunFrame(); + HostStates_t oldState{}; void* placeHolder = nullptr; if (setjmpFn(*host_abortserver, placeHolder)) @@ -348,7 +361,7 @@ void HCHostState_FrameUpdate(void* rcx, void* rdx, float time) } } - } while ((oldState != HostStates_t::HS_RUN || g_pHostState->m_iNextState == HostStates_t::HS_LOAD_GAME && g_pCVar->FindVar("g_single_frame_shutdown_for_reload_cvar")->GetBool()) + } while ((oldState != HostStates_t::HS_RUN || g_pHostState->m_iNextState == HostStates_t::HS_LOAD_GAME && g_pCVar->FindVar("single_frame_shutdown_for_reload")->GetBool()) && oldState != HostStates_t::HS_SHUTDOWN && oldState != HostStates_t::HS_RESTART); diff --git a/r5dev/engine/net.cpp b/r5dev/engine/net.cpp new file mode 100644 index 00000000..3f66e06e --- /dev/null +++ b/r5dev/engine/net.cpp @@ -0,0 +1,63 @@ +//=============================================================================// +// +// Purpose: Net system utilities +// +//=============================================================================// + +#include "core/stdafx.h" +#include "engine/net.h" + +//----------------------------------------------------------------------------- +// Purpose: returns the WSA error code +//----------------------------------------------------------------------------- +const char* NET_ErrorString(int iCode) +{ + switch (iCode) + { + case WSAEINTR : return "WSAEINTR"; + case WSAEBADF : return "WSAEBADF"; + case WSAEACCES : return "WSAEACCES"; + case WSAEDISCON : return "WSAEDISCON"; + case WSAEFAULT : return "WSAEFAULT"; + case WSAEINVAL : return "WSAEINVAL"; + case WSAEMFILE : return "WSAEMFILE"; + case WSAEWOULDBLOCK : return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS : return "WSAEINPROGRESS"; + case WSAEALREADY : return "WSAEALREADY"; + case WSAENOTSOCK : return "WSAENOTSOCK"; + case WSAEDESTADDRREQ : return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE : return "WSAEMSGSIZE"; + case WSAEPROTOTYPE : return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT : return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP : return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT : return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT : return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE : return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL : return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN : return "WSAENETDOWN"; + case WSAENETUNREACH : return "WSAENETUNREACH"; + case WSAENETRESET : return "WSAENETRESET"; + case WSAECONNABORTED : return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET : return "WSAECONNRESET"; + case WSAENOBUFS : return "WSAENOBUFS"; + case WSAEISCONN : return "WSAEISCONN"; + case WSAENOTCONN : return "WSAENOTCONN"; + case WSAESHUTDOWN : return "WSAESHUTDOWN"; + case WSAETOOMANYREFS : return "WSAETOOMANYREFS"; + case WSAETIMEDOUT : return "WSAETIMEDOUT"; + case WSAECONNREFUSED : return "WSAECONNREFUSED"; + case WSAELOOP : return "WSAELOOP"; + case WSAENAMETOOLONG : return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN : return "WSAEHOSTDOWN"; + case WSASYSNOTREADY : return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED : return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND : return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN : return "WSATRY_AGAIN"; + case WSANO_RECOVERY : return "WSANO_RECOVERY"; + case WSANO_DATA : return "WSANO_DATA"; + default : return "UNKNOWN ERROR"; + } +} diff --git a/r5dev/engine/net.h b/r5dev/engine/net.h new file mode 100644 index 00000000..20627996 --- /dev/null +++ b/r5dev/engine/net.h @@ -0,0 +1,3 @@ +#pragma once + +const char* NET_ErrorString(int iCode); diff --git a/r5dev/engine/net_chan.cpp b/r5dev/engine/net_chan.cpp index f0c1dc95..40423fd6 100644 --- a/r5dev/engine/net_chan.cpp +++ b/r5dev/engine/net_chan.cpp @@ -124,7 +124,7 @@ void HNET_PrintFunc(const char* fmt, ...) buf[sizeof(buf) -1] = 0; va_end(args); - DevMsg(eDLL_T::CLIENT, "%s\n", buf); + DevMsg(eDLL_T::CLIENT, "%s", buf); } //----------------------------------------------------------------------------- diff --git a/r5dev/engine/net_chan.h b/r5dev/engine/net_chan.h index 8bbd526c..b668d620 100644 --- a/r5dev/engine/net_chan.h +++ b/r5dev/engine/net_chan.h @@ -1,32 +1,7 @@ #pragma once -#include "core/stdafx.h" #include "common/protocol.h" #include "client/client.h" - -typedef struct netpacket_s netpacket_t; -typedef struct __declspec(align(8)) netpacket_s -{ - DWORD family_maybe; - sockaddr_in sin; - WORD sin_port; - char gap16; - char byte17; - DWORD source; - double received; - std::uint8_t* data; - std::uint64_t label; - BYTE byte38; - std::uint64_t qword40; - std::uint64_t qword48; - BYTE gap50[8]; - std::uint64_t qword58; - std::uint64_t qword60; - std::uint64_t qword68; - int less_than_12; - DWORD wiresize; - BYTE gap78[8]; - struct netpacket_s *pNext; -} netpacket_t; +#include "tier1/NetAdr2.h" namespace { diff --git a/r5dev/engine/sv_rcon.cpp b/r5dev/engine/sv_rcon.cpp new file mode 100644 index 00000000..9acc2f13 --- /dev/null +++ b/r5dev/engine/sv_rcon.cpp @@ -0,0 +1,230 @@ +//===========================================================================// +// +// Purpose: Implementation of the rcon server +// +//===========================================================================// + +#include "core/stdafx.h" +#include "tier0/cvar.h" +#include "tier0/IConVar.h" +#include "tier0/ConCommand.h" +#include "tier1/NetAdr2.h" +#include "tier2/socketcreator.h" +#include "engine/sys_utils.h" +#include "engine/sv_rcon.h" +#include "mathlib/sha256.h" +#include "client/IVEngineClient.h" +#include "common/igameserverdata.h" + +//----------------------------------------------------------------------------- +// Purpose: Create's listen socket for RCON +//----------------------------------------------------------------------------- +void CRConServer::Init(void) +{ + if (std::strlen(rcon_password->GetString()) < 8) + { + DevMsg(eDLL_T::SERVER, "RCON disabled\n"); + + m_bInitialized = false; + return; + } + + static ConVar* hostport = g_pCVar->FindVar("hostport"); + m_pAdr2 = new CNetAdr2(rcon_address->GetString(), hostport->GetString()); + m_pSocket->CreateListenSocket(*m_pAdr2, false); + + m_bInitialized = true; +} + +//----------------------------------------------------------------------------- +// Purpose: run tasks for RCON +//----------------------------------------------------------------------------- +void CRConServer::Think(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: server RCON main loop (run this every frame) +//----------------------------------------------------------------------------- +void CRConServer::RunFrame(void) +{ + if (m_bInitialized) + { + m_pSocket->RunFrame(); + ProcessMessage(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: process incoming packet +//----------------------------------------------------------------------------- +void CRConServer::ProcessMessage(void) +{ + int nCount = m_pSocket->GetAcceptedSocketCount(); + + for (int i = nCount - 1; i >= 0; i--) + { + CConnectedNetConsoleData* pData = m_pSocket->GetAcceptedSocketData(i); + + {////////////////////////////////////////////// + if (CheckForBan(i, pData)) + { + send(pData->m_hSocket, s_pszBannedMessage, strlen(s_pszBannedMessage), MSG_NOSIGNAL); + CloseConnection(i); + continue; + } + + char szRecvBuf{}; + int nPendingLen = recv(pData->m_hSocket, &szRecvBuf, sizeof(szRecvBuf), MSG_PEEK); + + if (nPendingLen == SOCKET_ERROR && m_pSocket->IsSocketBlocking()) + { + continue; + } + + if (nPendingLen <= 0) // EOF or error. + { + CloseConnection(i); + continue; + } + }////////////////////////////////////////////// + + u_long nReadLen; // Find out how much we have to read. + ioctlsocket(pData->m_hSocket, FIONREAD, &nReadLen); + + while (nReadLen > 0 && nReadLen < MAX_NETCONSOLE_INPUT_LEN -1) + { + char szRecvBuf[MAX_NETCONSOLE_INPUT_LEN]{}; + int nRecvLen = ::recv(pData->m_hSocket, szRecvBuf, MIN(sizeof(szRecvBuf), nReadLen), 0); + + if (nRecvLen == 0) // Socket was closed. + { + CloseConnection(i); + break; + } + + if (nRecvLen < 0 && !m_pSocket->IsSocketBlocking()) + { + break; + } + + nReadLen -= nRecvLen; + + // Write what we've got into the command buffer. + HandleInputChars(szRecvBuf, nRecvLen, pData); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: authenticate new connections +// TODO : implement logic for key exchange instead so we never network our +// password in plain text over the wire. create a cvar for this so user could +// also opt out and use legacy authentication instead for legacy RCON clients +//----------------------------------------------------------------------------- +void CRConServer::Authenticate(CConnectedNetConsoleData* pData) +{ + if (pData->m_bAuthorized) + { + return; + } + else if (std::memcmp(pData->m_pszInputCommandBuffer, "PASS ", 5) == 0) + { + if (std::strcmp(pData->m_pszInputCommandBuffer + 5, rcon_password->GetString()) == 0) + { // TODO: Hash password instead! + pData->m_bAuthorized = true; + } + else // Bad password. + { + DevMsg(eDLL_T::SERVER, "Bad password attempt from net console\n"); + ::send(pData->m_hSocket, s_pszWrongPwMessage, strlen(s_pszWrongPwMessage), MSG_NOSIGNAL); + + pData->m_bAuthorized = false; + pData->m_nFailedAttempts++; + } + } + else + { + ::send(pData->m_hSocket, s_pszNoAuthMessage, strlen(s_pszNoAuthMessage), MSG_NOSIGNAL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: handles input command buffer +//----------------------------------------------------------------------------- +void CRConServer::HandleInputChars(const char* pszIn, int nRecvLen, CConnectedNetConsoleData* pData) +{ + while (nRecvLen) + { + switch (*pszIn) + { + case '\r': + case '\n': + { + if (pData->m_nCharsInCommandBuffer) + { + pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer] = 0; + Authenticate(pData); + + if (pData->m_bAuthorized) + { + Execute(pData); + } + } + pData->m_nCharsInCommandBuffer = 0; + break; + } + default: + { + if (pData->m_nCharsInCommandBuffer < MAX_NETCONSOLE_INPUT_LEN - 1) + { + pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pszIn; + } + break; + } + } + pszIn++; + nRecvLen--; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRConServer::Execute(CConnectedNetConsoleData* pData) +{ + IVEngineClient_CommandExecute(NULL, pData->m_pszInputCommandBuffer); +} + +//----------------------------------------------------------------------------- +// Purpose: checks for amount of failed attempts and bans netconsole accordingly +//----------------------------------------------------------------------------- +bool CRConServer::CheckForBan(int nIdx, CConnectedNetConsoleData* pData) +{ + CNetAdr2 netAdr2 = m_pSocket->GetAcceptedSocketAddress(nIdx); + + // Check if IP is in the ban vector. + if (std::find(m_vBannedAddress.begin(), m_vBannedAddress.end(), + netAdr2.GetIP(true)) != m_vBannedAddress.end()) + { + return true; + } + + // Check if netconsole has reached maximum number of attempts and add to ban vector. + if (pData->m_nFailedAttempts >= sv_rcon_maxfailures->GetInt()) + { + m_vBannedAddress.push_back(netAdr2.GetIP(true)); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRConServer::CloseConnection(int nIdx) // NETMGR +{ + m_pSocket->CloseAcceptedSocket(nIdx); +} + +CRConServer* g_pRConServer = nullptr; diff --git a/r5dev/engine/sv_rcon.h b/r5dev/engine/sv_rcon.h new file mode 100644 index 00000000..144f2ab0 --- /dev/null +++ b/r5dev/engine/sv_rcon.h @@ -0,0 +1,32 @@ +#pragma once +#include "tier1/NetAdr2.h" + +constexpr char s_pszNoAuthMessage[] = "This server is password protected for console access. Must send 'PASS ' command.\n\r"; +constexpr char s_pszWrongPwMessage[] = "Password incorrect.\n\r"; +constexpr char s_pszBannedMessage[] = "Go away.\n\r"; + +class CRConServer +{ +public: + void Init(void); + void Think(void); + + void RunFrame(void); + + void ProcessMessage(void); + void Authenticate(CConnectedNetConsoleData* pData); + + void HandleInputChars(const char* pIn, int recvLen, CConnectedNetConsoleData* pData); + void Execute(CConnectedNetConsoleData* pData); + + bool CheckForBan(int nIdx, CConnectedNetConsoleData* pData); + void CloseConnection(int nIdx); + +private: + + bool m_bInitialized = false; + CNetAdr2* m_pAdr2 = new CNetAdr2(); + CSocketCreator* m_pSocket = new CSocketCreator(); + std::vector m_vBannedAddress; +}; +extern CRConServer* g_pRConServer; \ No newline at end of file diff --git a/r5dev/mathlib/sha256.cpp b/r5dev/mathlib/sha256.cpp new file mode 100644 index 00000000..9b4cd768 --- /dev/null +++ b/r5dev/mathlib/sha256.cpp @@ -0,0 +1,132 @@ +#include "core/stdafx.h" +#include "mathlib/sha256.h" + +const unsigned int SHA256::sha256_k[64] = //UL = uint32 + {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +void SHA256::transform(const unsigned char *message, unsigned int block_nb) +{ + uint32 w[64]{}; + uint32 wv[8]{}; + uint32 t1, t2; + const unsigned char *sub_block; + int i; + int j; + for (i = 0; i < (int) block_nb; i++) { + sub_block = message + (i << 6); + for (j = 0; j < 16; j++) { + SHA2_PACK32(&sub_block[j << 2], &w[j]); + } + for (j = 16; j < 64; j++) { + w[j] = SHA256_F4(w[j - 2]) + w[j - 7] + SHA256_F3(w[j - 15]) + w[j - 16]; + } + for (j = 0; j < 8; j++) { + wv[j] = m_h[j]; + } + for (j = 0; j < 64; j++) { + t1 = wv[7] + SHA256_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) + + sha256_k[j] + w[j]; + t2 = SHA256_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + for (j = 0; j < 8; j++) { + m_h[j] += wv[j]; + } + } +} + +void SHA256::init() +{ + m_h[0] = 0x6a09e667; + m_h[1] = 0xbb67ae85; + m_h[2] = 0x3c6ef372; + m_h[3] = 0xa54ff53a; + m_h[4] = 0x510e527f; + m_h[5] = 0x9b05688c; + m_h[6] = 0x1f83d9ab; + m_h[7] = 0x5be0cd19; + m_len = 0; + m_tot_len = 0; +} + +void SHA256::update(const unsigned char *message, unsigned int len) +{ + unsigned int block_nb; + unsigned int new_len, rem_len, tmp_len; + const unsigned char *shifted_message; + tmp_len = SHA224_256_BLOCK_SIZE - m_len; + rem_len = len < tmp_len ? len : tmp_len; + memcpy(&m_block[m_len], message, rem_len); + if (m_len + len < SHA224_256_BLOCK_SIZE) { + m_len += len; + return; + } + new_len = len - rem_len; + block_nb = new_len / SHA224_256_BLOCK_SIZE; + shifted_message = message + rem_len; + transform(m_block, 1); + transform(shifted_message, block_nb); + rem_len = new_len % SHA224_256_BLOCK_SIZE; + memcpy(m_block, &shifted_message[block_nb << 6], rem_len); + m_len = rem_len; + m_tot_len += (block_nb + 1) << 6; +} + +void SHA256::final(unsigned char *digest) +{ + unsigned int block_nb; + unsigned int pm_len; + unsigned int len_b; + int i; + block_nb = (1 + ((SHA224_256_BLOCK_SIZE - 9) + < (m_len % SHA224_256_BLOCK_SIZE))); + len_b = (m_tot_len + m_len) << 3; + pm_len = block_nb << 6; + memset(m_block + m_len, 0, pm_len - m_len); + m_block[m_len] = 0x80; + SHA2_UNPACK32(len_b, m_block + pm_len - 4); + transform(m_block, block_nb); + for (i = 0 ; i < 8; i++) { + SHA2_UNPACK32(m_h[i], &digest[i << 2]); + } +} + +std::string sha256(std::string input) +{ + unsigned char digest[SHA256::DIGEST_SIZE]; + memset(digest,0,SHA256::DIGEST_SIZE); + + SHA256 ctx = SHA256(); + ctx.init(); + ctx.update( (unsigned char*)input.c_str(), input.length()); + ctx.final(digest); + + char buf[2*SHA256::DIGEST_SIZE+1]{}; + buf[2*SHA256::DIGEST_SIZE] = 0; + for (int i = 0; i < SHA256::DIGEST_SIZE; i++) { + sprintf_s(buf + i * 2, sizeof(buf), "%02x", digest[i]); + } + return std::string(buf); +} diff --git a/r5dev/mathlib/sha256.h b/r5dev/mathlib/sha256.h new file mode 100644 index 00000000..b6ef8bc1 --- /dev/null +++ b/r5dev/mathlib/sha256.h @@ -0,0 +1,52 @@ +#ifndef SHA256_H +#define SHA256_H + +class SHA256 +{ +protected: + typedef unsigned char uint8; + typedef unsigned int uint32; + typedef unsigned long long uint64; + + const static uint32 sha256_k[]; + static const unsigned int SHA224_256_BLOCK_SIZE = (512/8); +public: + void init(); + void update(const unsigned char *message, unsigned int len); + void final(unsigned char *digest); + static const unsigned int DIGEST_SIZE = ( 256 / 8); + +protected: + void transform(const unsigned char *message, unsigned int block_nb); + unsigned int m_tot_len; + unsigned int m_len; + unsigned char m_block[2*SHA224_256_BLOCK_SIZE]; + uint32 m_h[8]; +}; + +std::string sha256(std::string input); + +#define SHA2_SHFR(x, n) (x >> n) +#define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) +#define SHA2_CH(x, y, z) ((x & y) ^ (~x & z)) +#define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) +#define SHA256_F1(x) (SHA2_ROTR(x, 2) ^ SHA2_ROTR(x, 13) ^ SHA2_ROTR(x, 22)) +#define SHA256_F2(x) (SHA2_ROTR(x, 6) ^ SHA2_ROTR(x, 11) ^ SHA2_ROTR(x, 25)) +#define SHA256_F3(x) (SHA2_ROTR(x, 7) ^ SHA2_ROTR(x, 18) ^ SHA2_SHFR(x, 3)) +#define SHA256_F4(x) (SHA2_ROTR(x, 17) ^ SHA2_ROTR(x, 19) ^ SHA2_SHFR(x, 10)) +#define SHA2_UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8) ((x) ); \ + *((str) + 2) = (uint8) ((x) >> 8); \ + *((str) + 1) = (uint8) ((x) >> 16); \ + *((str) + 0) = (uint8) ((x) >> 24); \ +} +#define SHA2_PACK32(str, x) \ +{ \ + *(x) = ((uint32) *((str) + 3) ) \ + | ((uint32) *((str) + 2) << 8) \ + | ((uint32) *((str) + 1) << 16) \ + | ((uint32) *((str) + 0) << 24); \ +} +#endif diff --git a/r5dev/mathlib/swap.h b/r5dev/mathlib/swap.h new file mode 100644 index 00000000..cf4c52d9 --- /dev/null +++ b/r5dev/mathlib/swap.h @@ -0,0 +1,53 @@ +//===========================================================================// +// +// Purpose: basic endian swap utils. +// +//===========================================================================// +#pragma once + +template +inline T WordSwapC(T w) +{ + std::uint16_t swap; + + static_assert(sizeof(T) == sizeof(std::uint16_t)); + + swap = ((*((std::uint16_t*)&w) & 0xff00) >> 8); + swap |= ((*((std::uint16_t*)&w) & 0x00ff) << 8); + + return *((T*)&swap); +} + +template +inline T DWordSwapC(T dw) +{ + std::uint32_t swap; + + static_assert(sizeof(T) == sizeof(std::uint32_t)); + + swap = *((std::uint32_t*)&dw) >> 24; + swap |= ((*((std::uint32_t*)&dw) & 0x00FF0000) >> 8); + swap |= ((*((std::uint32_t*)&dw) & 0x0000FF00) << 8); + swap |= ((*((std::uint32_t*)&dw) & 0x000000FF) << 24); + + return *((T*)&swap); +} + +template +inline T QWordSwapC(T dw) +{ + static_assert(sizeof(dw) == sizeof(std::uint64_t)); + + std::uint64_t swap; + + swap = *((std::uint64_t*)&dw) >> 56; + swap |= ((*((std::uint64_t*)&dw) & 0x00FF000000000000ull) >> 40); + swap |= ((*((std::uint64_t*)&dw) & 0x0000FF0000000000ull) >> 24); + swap |= ((*((std::uint64_t*)&dw) & 0x000000FF00000000ull) >> 8); + swap |= ((*((std::uint64_t*)&dw) & 0x00000000FF000000ull) << 8); + swap |= ((*((std::uint64_t*)&dw) & 0x0000000000FF0000ull) << 24); + swap |= ((*((std::uint64_t*)&dw) & 0x000000000000FF00ull) << 40); + swap |= ((*((std::uint64_t*)&dw) & 0x00000000000000FFull) << 56); + + return *((T*)&swap); +} diff --git a/r5dev/netconsole.vcxproj b/r5dev/netconsole.vcxproj new file mode 100644 index 00000000..526a7ba6 --- /dev/null +++ b/r5dev/netconsole.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {9579b31f-ce24-4852-a941-cd1ad71e2248} + netconsole + 10.0 + + + + Application + true + v143 + MultiByte + Static + + + Application + false + v143 + true + MultiByte + Static + + + Application + true + v143 + MultiByte + Static + + + Application + false + v143 + true + MultiByte + Static + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + netcon86 + $(SolutionDir)r5dev\;$(IncludePath); + + + false + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + netcon86 + $(SolutionDir)r5dev\;$(IncludePath); + + + true + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + netcon64 + $(SolutionDir)r5dev\;$(IncludePath); + + + false + $(SolutionDir)bin\$(Configuration)\ + $(SolutionDir)build\$(ProjectName)\$(Configuration)\ + netcon64 + $(SolutionDir)r5dev\;$(IncludePath); + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + /D NETCONSOLE %(AdditionalOptions) + Use + core\stdafx.h + + + Console + true + User32.lib;Bcrypt.lib;Ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + /D NETCONSOLE %(AdditionalOptions) + Use + core\stdafx.h + + + Console + true + true + true + User32.lib;Bcrypt.lib;Ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + /D NETCONSOLE %(AdditionalOptions) + Use + core\stdafx.h + + + Console + true + User32.lib;Bcrypt.lib;Ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + /D NETCONSOLE %(AdditionalOptions) + Use + core\stdafx.h + + + Console + true + true + true + User32.lib;Bcrypt.lib;Ws2_32.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/r5dev/netconsole.vcxproj.filters b/r5dev/netconsole.vcxproj.filters new file mode 100644 index 00000000..ab5864ec --- /dev/null +++ b/r5dev/netconsole.vcxproj.filters @@ -0,0 +1,61 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {7ec4619a-05f9-4949-937b-4b945edc3fe8} + + + {82bd526b-7838-4923-8c87-b035f2d5d2c7} + + + {033185e7-f6ca-4225-8ddb-089bf5bd5891} + + + {a6970588-331b-4da5-bfcf-b6f6cf1d87ee} + + + {99b7ba90-bc5d-4f07-a299-b68322c5ca63} + + + + + sdk\tier1 + + + sdk\public + + + core + + + sdk\tier2 + + + sdk\engine + + + core + + + + + sdk\tier1 + + + core + + + sdk\tier2 + + + sdk\engine + + + core + + + \ No newline at end of file diff --git a/r5dev/netconsole/netconsole.cpp b/r5dev/netconsole/netconsole.cpp new file mode 100644 index 00000000..4b470054 --- /dev/null +++ b/r5dev/netconsole/netconsole.cpp @@ -0,0 +1,116 @@ +//=====================================================================================// +// +// Purpose: Lightweight netconsole. +// +//=====================================================================================// + +#include "core/stdafx.h" +#include "tier1/NetAdr2.h" +#include "tier2/socketcreator.h" +#include "netconsole/netconsole.h" + +//----------------------------------------------------------------------------- +// purpose: send datagram +//----------------------------------------------------------------------------- +void CNetCon::Send(void) +{ + char buf[MAX_NETCONSOLE_INPUT_LEN]{}; + std::string svUserInput; + + do + { + printf(">"); + std::getline(std::cin, svUserInput); + svUserInput.append("\n\r"); + + int nSendResult = ::send(pSocket->GetAcceptedSocketData(0)->m_hSocket, svUserInput.c_str(), svUserInput.size(), MSG_NOSIGNAL); + + if (nSendResult != SOCKET_ERROR) + { + memcpy(buf, "", MAX_NETCONSOLE_INPUT_LEN); + } + } while (svUserInput.size() > 0); +} + +//----------------------------------------------------------------------------- +// purpose: receive datagram +//----------------------------------------------------------------------------- +void CNetCon::Recv(void) +{ + static char buf[MAX_NETCONSOLE_INPUT_LEN]{}; + + for (;;) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + int nRecvLen = ::recv(pSocket->GetAcceptedSocketData(0)->m_hSocket, buf, sizeof(buf), MSG_NOSIGNAL); + if (nRecvLen > 0 && nRecvLen < MAX_NETCONSOLE_INPUT_LEN - 1) + { + buf[nRecvLen + 1] = '\0'; + printf("%s\n", buf); + } + } +} + +//----------------------------------------------------------------------------- +// purpose: WSA and NETCON systems init +//----------------------------------------------------------------------------- +bool CNetCon::Init(void) +{ + WSAData wsaData{}; + int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + if (nError != 0) + { + assert(nError != 0 && "Failed to start Winsock via WSAStartup."); + return false; + } + + if (pSocket->ConnectSocket(*pNetAdr2, true) == SOCKET_ERROR) + { + assert(nError != 0 && "'pSocket->ConnectSocket()' returned 'SOCKET_ERROR'"); + return false; + } + + std::thread tRecv(&CNetCon::Recv, this, this->pSocket); + tRecv.detach(); + + return true; +} + +//----------------------------------------------------------------------------- +// purpose: WSA and NETCON systems shutdown +//----------------------------------------------------------------------------- +bool CNetCon::Shutdown(void) +{ + pSocket->CloseAllAcceptedSockets(); + + int nError = ::WSACleanup(); + if (nError != 0) + { + assert(nError != 0 && "Failed to stop winsock via WSACleanup.\n"); + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// purpose: entrypoint +//----------------------------------------------------------------------------- +int main(void) +{ + CNetCon* pNetCon = new CNetCon(); + + if (!pNetCon->Init()) + { + return EXIT_FAILURE; + } + + pNetCon->Send(); + + if (!pNetCon->Shutdown()) + { + return EXIT_FAILURE; + } + + return ERROR_SUCCESS; +} \ No newline at end of file diff --git a/r5dev/netconsole/netconsole.h b/r5dev/netconsole/netconsole.h new file mode 100644 index 00000000..abcc7f24 --- /dev/null +++ b/r5dev/netconsole/netconsole.h @@ -0,0 +1,19 @@ +//===========================================================================// +// +// Purpose: +// +//===========================================================================// +#pragma once + +class CNetCon +{ +public: + bool Init(void); + bool Shutdown(void); + + void Send(void); + void Recv(void); + + CNetAdr2* pNetAdr2 = new CNetAdr2("localhost", "37015"); + CSocketCreator* pSocket = new CSocketCreator(); +}; \ No newline at end of file diff --git a/r5dev/r5dev.vcxproj b/r5dev/r5dev.vcxproj index 1c44ff14..0b973bf1 100644 --- a/r5dev/r5dev.vcxproj +++ b/r5dev/r5dev.vcxproj @@ -35,10 +35,13 @@ + + + @@ -51,6 +54,7 @@ + @@ -200,6 +204,8 @@ + + @@ -219,6 +225,7 @@ + @@ -231,11 +238,14 @@ + + + @@ -252,6 +262,8 @@ + + @@ -417,6 +429,8 @@ + + diff --git a/r5dev/r5dev.vcxproj.filters b/r5dev/r5dev.vcxproj.filters index 6753858e..973cb22d 100644 --- a/r5dev/r5dev.vcxproj.filters +++ b/r5dev/r5dev.vcxproj.filters @@ -97,12 +97,6 @@ {942b8ea5-ce53-4e1e-ad7a-845991aaead6} - - {b0696621-8658-4918-b0f2-ba20acc26829} - - - {cbe60970-f348-4a8b-8cee-d4cfebbe0d99} - {14a61eec-93ec-4e7c-b0bf-2ce23c3b782c} @@ -139,6 +133,18 @@ {5beb12b5-0422-4337-9be6-2e6c0a05a69b} + + {c5adc45b-d14c-4d52-9835-29948cab931a} + + + {b0696621-8658-4918-b0f2-ba20acc26829} + + + {cbe60970-f348-4a8b-8cee-d4cfebbe0d99} + + + {9da19829-c065-4584-9cf2-af751fb0d060} + @@ -417,6 +423,24 @@ sdk\engine + + sdk\engine + + + sdk\engine + + + sdk\tier2 + + + sdk\tier1 + + + sdk\mathlib + + + sdk\engine + @@ -1067,6 +1091,30 @@ sdk\engine + + sdk\engine + + + sdk\engine + + + sdk\mathlib + + + sdk\tier2 + + + sdk\tier1 + + + sdk\mathlib + + + sdk\common + + + sdk\engine + diff --git a/r5dev/squirrel/sqinit.cpp b/r5dev/squirrel/sqinit.cpp index 03ade399..f630c2b8 100644 --- a/r5dev/squirrel/sqinit.cpp +++ b/r5dev/squirrel/sqinit.cpp @@ -38,7 +38,7 @@ namespace VSquirrel } namespace UI { - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: get server's current name from serverlist index //----------------------------------------------------------------------------- SQRESULT GetServerName(void* sqvm) @@ -51,7 +51,7 @@ namespace VSquirrel return SQ_OK; } - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: get server's current playlist via serverlist index //----------------------------------------------------------------------------- SQRESULT GetServerPlaylist(void* sqvm) @@ -64,7 +64,7 @@ namespace VSquirrel return SQ_OK; } - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: get server's current map via serverlist index //----------------------------------------------------------------------------- SQRESULT GetServerMap(void* sqvm) @@ -77,7 +77,7 @@ namespace VSquirrel return SQ_OK; } - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: get current server count from pylon //----------------------------------------------------------------------------- SQRESULT GetServerCount(void* sqvm) @@ -89,7 +89,7 @@ namespace VSquirrel return SQ_OK; } - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: expose SDK version to the UI VM //----------------------------------------------------------------------------- SQRESULT GetSDKVersion(void* sqvm) @@ -99,7 +99,7 @@ namespace VSquirrel return SQ_OK; } - //---------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Purpose: get promo data for serverbrowser panels //----------------------------------------------------------------------------- SQRESULT GetPromoData(void* sqvm) diff --git a/r5dev/tier0/basetypes.h b/r5dev/tier0/basetypes.h index bc9effff..6f5facdc 100644 --- a/r5dev/tier0/basetypes.h +++ b/r5dev/tier0/basetypes.h @@ -17,3 +17,17 @@ #define MAX_PLAYERS 128 // Max R5 players. #define SDK_VERSION "beta 1.6" + +// #define COMPILETIME_MAX and COMPILETIME_MIN for max/min in constant expressions +#define COMPILETIME_MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) +#define COMPILETIME_MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) +#ifndef MIN +#define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) +#endif + +#ifndef MAX +#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) +#endif + +constexpr int MAX_NETCONSOLE_INPUT_LEN = 4096; +constexpr int MSG_NOSIGNAL = 0; diff --git a/r5dev/tier1/NetAdr.cpp b/r5dev/tier1/NetAdr.cpp new file mode 100644 index 00000000..9010b74b --- /dev/null +++ b/r5dev/tier1/NetAdr.cpp @@ -0,0 +1,440 @@ +//===========================================================================// +// +// Purpose: implementation of the CNetAdr class. +// -------------------------------------------------------------------------- +// +// NOTE: This implementation is considered deprecated. I rebuilded this +// not knowing that the engine supports IPv6 as well. I have fully rewritten +// this class in 'tier1/NetAdr2.cpp' in modern C++. Please use this instead. +// This class is for reference material only may some bits in the engine line +// up with this original 'CNetAdr' implementation. +// +//===========================================================================// + +#include "core/stdafx.h" +#include "tier1/netadr.h" +#include "mathlib/swap.h" + +////////////////////////////////////////////////////////////////////// +// Constructors +////////////////////////////////////////////////////////////////////// +netadr_s::netadr_s(void) +{ + SetIP(0); + SetPort(0); + SetType(netadrtype_t::NA_IP); +} +netadr_s::netadr_s(std::uint32_t unIP, std::uint16_t usPort) +{ + SetIP(unIP); + SetPort(usPort); + SetType(netadrtype_t::NA_IP); +} +netadr_s::netadr_s(const char* pch) +{ + SetFromString(pch); +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +void netadr_t::SetFromSocket(int hSocket) +{ + Clear(); + type = netadrtype_t::NA_IP; + + struct sockaddr address{}; + socklen_t namelen = sizeof(address); + if (getsockname(hSocket, (struct sockaddr*)&address, &namelen) == 0) + { + SetFromSockadr(&address); + } +} + +////////////////////////////////////////////////////////////////////// +// Compares IP for equality +////////////////////////////////////////////////////////////////////// +bool netadr_t::CompareAdr(const netadr_t& a, bool onlyBase) const +{ + if (a.type != type) + { + return false; + } + + if (type == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (type == netadrtype_t::NA_BROADCAST) + { + return true; + } + + if (type == netadrtype_t::NA_IP) + { + if (!onlyBase && (port != a.port)) + { + return false; + } + + if (a.ip[0] == ip[0] && a.ip[1] == ip[1] && a.ip[2] == ip[2] && a.ip[3] == ip[3]) + { + return true; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +// Compares Class-B IP for equality +////////////////////////////////////////////////////////////////////// +bool netadr_t::CompareClassBAdr(const netadr_t& a) const +{ + if (a.type != type) + { + return false; + } + + if (type == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (type == netadrtype_t::NA_IP) + { + if (a.ip[0] == ip[0] && a.ip[1] == ip[1]) + { + return true; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +// Compares Class-C IP for equality +////////////////////////////////////////////////////////////////////// +bool netadr_t::CompareClassCAdr(const netadr_t& a) const +{ + if (a.type != type) + { + return false; + } + + if (type == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (type == netadrtype_t::NA_IP) + { + if (a.ip[0] == ip[0] && a.ip[1] == ip[1] && a.ip[2] == ip[2]) + { + return true; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////// +// Convert address to string +////////////////////////////////////////////////////////////////////// +const char* netadr_t::ToString(bool onlyBase) const +{ + // Select a static buffer. + static char s[4][64]{}; + static int slot = 0; + int useSlot = (slot++) % 4; + + // Render into it. + ToString(s[useSlot], sizeof(s[0]), onlyBase); + + // Pray the caller uses it before it gets clobbered. + return s[useSlot]; +} + +////////////////////////////////////////////////////////////////////// +// Convert address to string +////////////////////////////////////////////////////////////////////// +void netadr_t::ToString(char* pchBuffer, std::uint32_t unBufferSize, bool bOnlyBase) const +{ + if (type == netadrtype_t::NA_LOOPBACK) + { + memmove(pchBuffer, "loopback", unBufferSize); + } + else if (type == netadrtype_t::NA_BROADCAST) + { + memmove(pchBuffer, "broadcast", unBufferSize); + } + else if (type == netadrtype_t::NA_IP) + { + if (bOnlyBase) + { + snprintf(pchBuffer, unBufferSize, "%i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3]); + } + else + { + snprintf(pchBuffer, unBufferSize, "%i.%i.%i.%i:%i", ip[0], ip[1], ip[2], ip[3], ntohs(port)); + } + } + else + { + memmove(pchBuffer, "unknown", unBufferSize); + } +} + +////////////////////////////////////////////////////////////////////// +// Clears IP +////////////////////////////////////////////////////////////////////// +void netadr_t::Clear(void) +{ + ip[0] = ip[1] = ip[2] = ip[3] = 0; + port = 0; + type = netadrtype_t::NA_NULL; +} + +////////////////////////////////////////////////////////////////////// +// Sets IP +////////////////////////////////////////////////////////////////////// +void netadr_t::SetIP(std::uint8_t b1, std::uint8_t b2, std::uint8_t b3, std::uint8_t b4) +{ + ip[0] = b1; + ip[1] = b2; + ip[2] = b3; + ip[3] = b4; +} + +////////////////////////////////////////////////////////////////////// +// Sets IP +////////////////////////////////////////////////////////////////////// +void netadr_t::SetIP(std::uint32_t unIP) +{ + *((std::uint32_t*)ip) = DWordSwapC(unIP); +} + +////////////////////////////////////////////////////////////////////// +// Sets type +////////////////////////////////////////////////////////////////////// +void netadr_t::SetType(netadrtype_t newtype) +{ + type = newtype; +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +netadrtype_t netadr_t::GetType(void) const +{ + return type; +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +std::uint16_t netadr_t::GetPort(void) const +{ + return WordSwapC(port); +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +std::uint32_t netadr_t::GetIPNetworkByteOrder(void) const +{ + return *(std::uint32_t*)&ip; +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +std::uint32_t netadr_t::GetIPHostByteOrder(void) const +{ + return DWordSwapC(GetIPNetworkByteOrder()); +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +void netadr_t::ToSockadr(struct sockaddr* s) const +{ + memset(s, 0, sizeof(struct sockaddr)); + + if (type == netadrtype_t::NA_BROADCAST) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_port = port; + ((struct sockaddr_in*)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if (type == netadrtype_t::NA_IP) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_addr.s_addr = *(int*)&ip; + ((struct sockaddr_in*)s)->sin_port = port; + } + else if (type == netadrtype_t::NA_LOOPBACK) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_port = port; + ((struct sockaddr_in*)s)->sin_addr.s_addr = INADDR_LOOPBACK; + } +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +bool netadr_t::SetFromSockadr(const struct sockaddr* s) +{ + if (s->sa_family == AF_INET) + { + type = netadrtype_t::NA_IP; + *(int*)&ip = ((struct sockaddr_in*)s)->sin_addr.s_addr; + port = ((struct sockaddr_in*)s)->sin_port; + return true; + } + else + { + Clear(); + return false; + } +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +bool netadr_t::IsValid(void) const +{ + return ((port != 0) && (type != netadrtype_t::NA_NULL) && + (ip[0] != 0 || ip[1] != 0 || ip[2] != 0 || ip[3] != 0)); +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +bool netadr_t::IsBaseAdrValid(void) const +{ + return ((type != netadrtype_t::NA_NULL) && + (ip[0] != 0 || ip[1] != 0 || ip[2] != 0 || ip[3] != 0)); +} + +////////////////////////////////////////////////////////////////////// +// Returns true if we are localhost +////////////////////////////////////////////////////////////////////// +bool netadr_t::IsLocalhost(void) const +{ + return (ip[0] == 127) && (ip[1] == 0) && (ip[2] == 0) && (ip[3] == 1); +} + +////////////////////////////////////////////////////////////////////// +// Returns true if we use the loopback buffers +////////////////////////////////////////////////////////////////////// +bool netadr_t::IsLoopback(void) const +{ + return type == netadrtype_t::NA_LOOPBACK; +} + +////////////////////////////////////////////////////////////////////// +// Check if address is reserved and not routable. +////////////////////////////////////////////////////////////////////// +bool netadr_t::IsReservedAdr(void) const +{ + if (type == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (type == netadrtype_t::NA_IP) + { + if ((ip[0] == 10) || // 10.x.x.x is reserved + (ip[0] == 127) || // 127.x.x.x + (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) || // 172.16.x.x - 172.31.x.x + (ip[0] == 192 && ip[1] >= 168)) // 192.168.x.x + { + return true; + } + } + return false; +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +void netadr_t::SetPort(std::uint16_t newport) +{ + port = WordSwapC(newport); +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +bool netadr_t::SetFromString(const char* szIpAdr, bool bUseDNS) +{ + Clear(); + + if (!szIpAdr) + { + Assert(szIpAdr, "Invalid call: 'szIpAdr' was nullptr."); + return false; + } + + type = netadrtype_t::NA_IP; + + char szAddress[128]{}; + strcpy_s(szAddress, szIpAdr); + + if (!_strnicmp(szAddress, "loopback", 8)) + { + char szNewAddress[128]{}; + type = netadrtype_t::NA_LOOPBACK; + + strcpy_s(szNewAddress, "127.0.0.1"); + strcat_s(szNewAddress, szAddress + 8); // copy anything after "loopback" + + strcpy_s(szAddress, szNewAddress); + } + + if (!_strnicmp(szAddress, "localhost", 9)) + { + memcpy(szAddress, "127.0.0.1", 9); // Note use of memcpy allows us to keep the colon and rest of string since localhost and 127.0.0.1 are both 9 characters. + } + + // IPv4 starts with a number and has a dot. + if (szAddress[0] >= '0' && szAddress[0] <= '9' && strchr(szAddress, '.')) + { + int i0 = -1, i1 = -1, i2 = -1, i3 = -1, n0 = 0; // Initialize port as zero + int nRes = sscanf_s(szAddress, "%d.%d.%d.%d:%d", &i0, &i1, &i2, &i3, &n0); + if ( + nRes < 4 + || i0 < 0 || i0 > 255 + || i1 < 0 || i1 > 255 + || i2 < 0 || i2 > 255 + || i3 < 0 || i3 > 255 + || n0 < 0 || n0 > 65535 + ) + { + return false; + } + + SetIP(i0, i1, i2, i3); + SetPort((std::uint16_t)n0); + return true; + } +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +bool netadr_t::operator<(const netadr_t& netadr) const +{ + if (*((std::uint32_t*)netadr.ip) < *((std::uint32_t*)ip)) + { + return true; + } + else if (*((std::uint32_t*)netadr.ip) > *((std::uint32_t*)ip)) + { + return false; + } + return (netadr.port < port); +} diff --git a/r5dev/tier1/NetAdr.h b/r5dev/tier1/NetAdr.h new file mode 100644 index 00000000..fc7383e8 --- /dev/null +++ b/r5dev/tier1/NetAdr.h @@ -0,0 +1,69 @@ +#pragma once + +enum class netadrtype_t +{ + NA_NULL = 0, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, +}; + +typedef struct netadr_s netadr_t; +typedef struct netadr_s +{ +public: + netadr_s(void); + netadr_s(std::uint32_t unIP, std::uint16_t usPort); + netadr_s(const char* pch); + void Clear(void); + + void SetType(netadrtype_t type); + void SetPort(std::uint16_t port); + bool SetFromSockadr(const struct sockaddr* s); + void SetIP(std::uint8_t b1, std::uint8_t b2, std::uint8_t b3, std::uint8_t b4); + void SetIP(std::uint32_t unIP); + void SetIPAndPort(std::uint32_t unIP, std::uint16_t usPort) { SetIP(unIP); SetPort(usPort); } + bool SetFromString(const char* pch, bool bUseDNS = false); + void SetFromSocket(int hSocket); + + bool CompareAdr(const netadr_s& a, bool onlyBase = false) const; + bool CompareClassBAdr(const netadr_s& a) const; + bool CompareClassCAdr(const netadr_s& a) const; + + netadrtype_t GetType(void) const; + std::uint16_t GetPort(void) const; + + const char* ToString(bool onlyBase = false) const; + + void ToString(char* pchBuffer, std::uint32_t unBufferSize, bool onlyBase = false) const; + template< size_t maxLenInChars > + void ToString_safe(char(&pDest)[maxLenInChars], bool onlyBase = false) const + { + ToString(&pDest[0], maxLenInChars, onlyBase); + } + + //[xxxx::xxxx:xxxx:xxxx:xxxx]:00000 + + void ToSockadr(struct sockaddr* s) const; + + // Returns 0xAABBCCDD for AA.BB.CC.DD on all platforms, which is the same format used by SetIP(). + std::uint32_t GetIPHostByteOrder(void) const; + + // Returns a number that depends on the platform. In most cases, this probably should not be used. + std::uint32_t GetIPNetworkByteOrder(void) const; + + bool IsValid(void) const; // ip & port != 0 + bool IsBaseAdrValid(void) const; // ip != 0 + bool IsLocalhost(void) const; // true, if this is the localhost IP + bool IsLoopback(void) const; // true if engine loopback buffers are used + bool IsReservedAdr(void) const; // true, if this is a private LAN IP + + bool operator==(const netadr_s& netadr) const { return (CompareAdr(netadr)); } + bool operator!=(const netadr_s& netadr) const { return !(CompareAdr(netadr)); } + bool operator<(const netadr_s& netadr) const; + +public: // members are public to avoid to much changes + netadrtype_t type; + std::uint8_t ip[4]; + std::uint16_t port; +} netadr_t; diff --git a/r5dev/tier1/NetAdr2.cpp b/r5dev/tier1/NetAdr2.cpp new file mode 100644 index 00000000..b385efbd --- /dev/null +++ b/r5dev/tier1/NetAdr2.cpp @@ -0,0 +1,604 @@ +//===========================================================================// +// +// Purpose: Protocol-agnostic implementation of the CNetAdr class. +// +//===========================================================================// + +#include +#include +#ifndef NETCONSOLE +#include // !! IMPLEMENT 'Warning(..)' !! +#endif // !NETCONSOLE + +//----------------------------------------------------------------------------- +// Purpose: constructor (use this when string contains <[IP]:PORT> or 'loopback'/'localhost'). +// input : svInAdr - +//----------------------------------------------------------------------------- +CNetAdr2::CNetAdr2(std::string svInAdr) +{ + SetType(netadrtype_t::NA_IP); + if (std::strcmp(svInAdr.c_str(), "loopback") == 0 || std::strcmp(svInAdr.c_str(), "::1") == 0) + { + SetType(netadrtype_t::NA_LOOPBACK); + svInAdr = "[127.0.0.1" + GetPort(svInAdr); + } + else if (std::strcmp(svInAdr.c_str(), "localhost")) + { + svInAdr = "[127.0.0.1" + GetPort(svInAdr); + } + + // [IP]:PORT + m_svip = GetBase(svInAdr); + SetVersion(); + + if (GetVersion() == netadrversion_t::NA_V4) + { + reinterpret_cast(&m_sadr)->sin_port = htons(stoi(GetPort())); + } + else if (GetVersion() == netadrversion_t::NA_V6) + { + reinterpret_cast(&m_sadr)->sin6_port = htons(stoi(GetPort())); + } +} + +//----------------------------------------------------------------------------- +// Purpose: constructor (expects string format ). +// input : svInAdr - +// svInPort - +//----------------------------------------------------------------------------- +CNetAdr2::CNetAdr2(std::string svInAdr, std::string svInPort) +{ + SetType(netadrtype_t::NA_IP); + + if (std::strcmp(svInAdr.c_str(), "loopback") == 0 || std::strcmp(svInAdr.c_str(), "::1") == 0) + { + SetType(netadrtype_t::NA_LOOPBACK); + } + else if (std::strcmp(svInAdr.c_str(), "localhost") == 0) + { + svInAdr = "127.0.0.1"; + } + + if (std::strstr(svInAdr.c_str(), "[")) + { + svInAdr = GetBase(svInAdr); + } + + SetIPAndPort(svInAdr, svInPort); + + if (m_version == netadrversion_t::NA_V4) + { + reinterpret_cast(&m_sadr)->sin_port = htons(stoi(GetPort())); + } + else if (m_version == netadrversion_t::NA_V6) + { + reinterpret_cast(&m_sadr)->sin6_port = htons(stoi(GetPort())); + } +} + +//----------------------------------------------------------------------------- +// Purpose: destructor. +//----------------------------------------------------------------------------- +CNetAdr2::~CNetAdr2(void) +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the IP address. +// input : svInAdr - +//----------------------------------------------------------------------------- +void CNetAdr2::SetIP(std::string svInAdr) +{ + m_svip = "[" + svInAdr + "]"; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the port. +// input : svInPort - +//----------------------------------------------------------------------------- +void CNetAdr2::SetPort(std::string svInPort) +{ + m_svip += ":" + svInPort; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the IP address and port. +// input : svInAdr - +// svInPort - +//----------------------------------------------------------------------------- +void CNetAdr2::SetIPAndPort(std::string svInAdr, std::string svInPort) +{ + m_svip = "[" + svInAdr + "]:" + svInPort; + SetVersion(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the type. +// Input : type - +//----------------------------------------------------------------------------- +void CNetAdr2::SetType(netadrtype_t type) +{ + m_type = type; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the IP version (IPv4/IPv6/INVALID) based on input. +//----------------------------------------------------------------------------- +void CNetAdr2::SetVersion(void) +{ + if (inet_pton(reinterpret_cast(&m_sadr)->sin_family, + GetBase().c_str(), &reinterpret_cast(m_sadr)->sin_addr) && + !std::strstr(GetBase().c_str(), "::")) + { + m_version = netadrversion_t::NA_V4; + return; + } + else if (inet_pton(reinterpret_cast(&m_sadr)->sin6_family, + GetBase().c_str(), &reinterpret_cast(m_sadr)->sin6_addr)) + { + m_version = netadrversion_t::NA_V6; + return; + } + m_version = netadrversion_t::NA_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: sets IP address and port from sockaddr struct. +// Input : hSocket - +//----------------------------------------------------------------------------- +void CNetAdr2:: SetFromSocket(int hSocket) +{ + Clear(); + m_type = netadrtype_t::NA_IP; + + sockaddr_storage address{}; + socklen_t namelen = sizeof(address); + if (getsockname(hSocket, (sockaddr*)&address, &namelen) == 0) + { + SetFromSockadr(&address); + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets fields based on 'sockaddr' input. +// Input : *s - +//----------------------------------------------------------------------------- +bool CNetAdr2::SetFromSockadr(sockaddr_storage* s) +{ + if (reinterpret_cast(s)->sin_family == AF_INET) + { + char szAdrv4[INET_ADDRSTRLEN]{}; + sockaddr_in* pAdrv4 = reinterpret_cast(s); + + inet_ntop(pAdrv4->sin_family, &pAdrv4->sin_addr, szAdrv4, sizeof(sockaddr_in)); + SetIPAndPort(szAdrv4, std::to_string(ntohs(pAdrv4->sin_port))); + return true; + } + else if (reinterpret_cast(s)->sin6_family == AF_INET6) + { + char szAdrv6[INET6_ADDRSTRLEN]{}; + sockaddr_in6* pAdrv6 = reinterpret_cast(s); + + inet_ntop(pAdrv6->sin6_family, &pAdrv6->sin6_addr, szAdrv6, sizeof(sockaddr_in6)); + SetIPAndPort(szAdrv6, std::to_string(ntohs(pAdrv6->sin6_port))); + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: removes brackets and port from IP address. +//----------------------------------------------------------------------------- +std::string CNetAdr2::GetBase(void) const +{ + std::string svIpAdr = m_svip; + static std::regex rx("\\].*"); + svIpAdr.erase(0, 1); + svIpAdr = std::regex_replace(svIpAdr, rx, ""); + + return svIpAdr; +} + +//----------------------------------------------------------------------------- +// Purpose: removes brackets and port from IP address. +// Input : svInAdr - +//----------------------------------------------------------------------------- +std::string CNetAdr2::GetBase(std::string svInAdr) const +{ + static std::regex rx("\\].*"); + svInAdr.erase(0, 1); + svInAdr = std::regex_replace(svInAdr, rx, ""); + + return svInAdr; +} + +//----------------------------------------------------------------------------- +// Purpose: gets the IP address. +// Input : bBaseOnly - +//----------------------------------------------------------------------------- +std::string CNetAdr2::GetIP(bool bBaseOnly) const +{ + if (GetType() == netadrtype_t::NA_LOOPBACK) + { + return "loopback"; + } + else if (GetType() == netadrtype_t::NA_BROADCAST) + { + return "broadcast"; + } + else if (GetType() == netadrtype_t::NA_IP) + { + if (bBaseOnly) + { + return GetBase(); + } + else + { + return m_svip; + } + } + else + { + return "unknown"; + } +} + +//----------------------------------------------------------------------------- +// Purpose: removes brackets and port from IP address. +//----------------------------------------------------------------------------- +std::string CNetAdr2::GetPort(void) const +{ + std::string svport = m_svip; + static std::regex rx(".*\\]:"); + svport = std::regex_replace(svport, rx, ""); + + return svport; +} +std::string CNetAdr2::GetPort(std::string svInPort) const +{ + static std::regex rx(".*\\]:"); + svInPort = std::regex_replace(svInPort, rx, ""); + + return svInPort; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the IP address and port. +//----------------------------------------------------------------------------- +std::string CNetAdr2::GetIPAndPort(void) const +{ + return m_svip; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the address type. +//----------------------------------------------------------------------------- +netadrtype_t CNetAdr2::GetType(void) const +{ + return m_type; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the IP version. +//----------------------------------------------------------------------------- +netadrversion_t CNetAdr2::GetVersion(void) const +{ + return m_version; +} + +//----------------------------------------------------------------------------- +// Purpose: splits IP address into parts by their delimiters. +// Output : string vector containing IP parts. +//----------------------------------------------------------------------------- +std::vector CNetAdr2::GetParts(void) const +{ + std::vector results; + + // Make sure we have a valid address. + if (m_version == netadrversion_t::NA_INVALID || m_type != netadrtype_t::NA_IP) + { + assert(m_version == netadrversion_t::NA_INVALID && "Invalid IP address for 'GetParts()'."); + return results; + } + + std::string svIpAdr = m_svip, svDelim; + std::string::size_type prev_pos = 0, curr_pos = 0; + + // 000.000.000.000 -> vparts. + if (m_version == netadrversion_t::NA_V4) + { + svDelim = "."; + } + // 0000:0000:0000:0000:0000:0000:0000:0000 -> vparts. + else if (m_version == netadrversion_t::NA_V6) + { + svDelim = ":"; + StringReplace(svIpAdr, "::", ":"); + } + + while ((curr_pos = svIpAdr.find(svDelim, curr_pos)) != std::string::npos) + { + std::string substr(svIpAdr.substr(prev_pos, curr_pos - prev_pos)); + + results.push_back(substr); + prev_pos = ++curr_pos; + } + results.push_back(m_svip.substr(prev_pos, curr_pos - prev_pos)); + + return results; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the size of the network family struct. +//----------------------------------------------------------------------------- +int CNetAdr2::GetSize(void) const +{ + if (GetVersion() == netadrversion_t::NA_V4) + { + return sizeof(sockaddr_in); + } + else if (GetVersion() == netadrversion_t::NA_V6) + { + return sizeof(sockaddr_in6); + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: sets fields from 'sockaddr'. +// Input : *pSadr - +//----------------------------------------------------------------------------- +void CNetAdr2::ToSockadr(sockaddr_storage* pSadr) const +{ + if (GetVersion() == netadrversion_t::NA_V4) + { + if (GetType() == netadrtype_t::NA_BROADCAST) + { + reinterpret_cast(pSadr)->sin_family = AF_INET; + reinterpret_cast(pSadr)->sin_port = htons(stoi(GetPort())); + reinterpret_cast(pSadr)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if (GetType() == netadrtype_t::NA_IP) + { + reinterpret_cast(pSadr)->sin_family = AF_INET; + reinterpret_cast(pSadr)->sin_port = htons(stoi(GetPort()));; + inet_pton(AF_INET, GetBase().c_str(), &reinterpret_cast(pSadr)->sin_addr.s_addr); + } + else if (GetType() == netadrtype_t::NA_LOOPBACK) + { + reinterpret_cast(pSadr)->sin_family = AF_INET; + reinterpret_cast(pSadr)->sin_port = htons(stoi(GetPort()));; + reinterpret_cast(pSadr)->sin_addr.s_addr = INADDR_LOOPBACK; + } + } + else if (GetVersion() == netadrversion_t::NA_V6) + { + if (GetType() == netadrtype_t::NA_IP) + { + reinterpret_cast(pSadr)->sin6_family = AF_INET6; + reinterpret_cast(pSadr)->sin6_port = htons(stoi(GetPort()));; + inet_pton(AF_INET6, GetBase().c_str(), &reinterpret_cast(pSadr)->sin6_addr); + } + else if (GetType() == netadrtype_t::NA_LOOPBACK) + { + reinterpret_cast(pSadr)->sin6_family = AF_INET6; + reinterpret_cast(pSadr)->sin6_port = htons(stoi(GetPort()));; + reinterpret_cast(pSadr)->sin6_addr = in6addr_loopback; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets fields from 'addrinfo'. +// Input : *pHint - +//----------------------------------------------------------------------------- +void CNetAdr2::ToAdrinfo(addrinfo* pHint) const +{ + int results{ }; + addrinfo hint{ }; // <-- TODO: Pass these instead. + if (GetVersion() == netadrversion_t::NA_V4) + { + hint.ai_family = AF_INET; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_flags = AI_PASSIVE; + results = getaddrinfo(GetBase().c_str(), GetPort().c_str(), &hint, &pHint); + if (results != 0) + { + // TODO: Implement 'Warning(..)' instead! +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Address info translation failed (%s)\n", gai_strerror(results)); +#else + printf("Address info translation failed (%s)\n", gai_strerror(results)); +#endif // !NETCONSOLE + } + } + else if (GetVersion() == netadrversion_t::NA_V6) + { + hint.ai_family = AF_INET6; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_flags = AI_PASSIVE; + results = getaddrinfo(GetBase().c_str(), GetPort().c_str(), &hint, &pHint); + if (results != 0) + { + // TODO: Implement 'Warning(..)' instead! +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Address info translation failed (%s)\n", gai_strerror(results)); +#else + printf("Address info translation failed (%s)\n", gai_strerror(results)); +#endif // !NETCONSOLE + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we are localhost. +//----------------------------------------------------------------------------- +bool CNetAdr2::IsLocalhost(void) const +{ + return (std::strcmp(GetBase().c_str(), "127.0.0.1") == 0); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we use the loopback buffers. +//----------------------------------------------------------------------------- +bool CNetAdr2::IsLoopback(void) const +{ + return GetType() == netadrtype_t::NA_LOOPBACK; +} + +//----------------------------------------------------------------------------- +// Purpose: check if address is reserved and not routable. +//----------------------------------------------------------------------------- +bool CNetAdr2::IsReservedAdr(void) const +{ + if (GetType() == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (GetType() == netadrtype_t::NA_IP) + { + std::vector ip_parts = GetParts(); + + int n0 = stoi(ip_parts[0]); + int n1 = stoi(ip_parts[1]); + + if ((n0 == 10) || // 10.x.x.x is reserved + (n0 == 127) || // 127.x.x.x + (n0 == 172 && n1 >= 16 && n1 <= 31) || // 172.16.x.x - 172.31.x.x + (n0 == 192 && n1 >= 168)) // 192.168.x.x + { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: compares IP for equality (IPv4/IPv6). +// Input : *netAdr2 - +// bBaseOnly - +// Output : true if equal, false otherwise. +//----------------------------------------------------------------------------- +bool CNetAdr2::CompareAdr(const CNetAdr2& netAdr2, bool bBaseOnly) const +{ + if (netAdr2.GetType() != GetType()) + { + return false; + } + + if (GetType() == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (GetType() == netadrtype_t::NA_BROADCAST) + { + return true; + } + + if (GetType() == netadrtype_t::NA_IP) + { + if (!bBaseOnly && + (std::strcmp(netAdr2.GetPort().c_str(), GetPort().c_str()) != 0)) + { + return false; + } + + if (std::strcmp(netAdr2.GetBase().c_str(), GetBase().c_str()) == 0) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: compares Class-B IP for equality. +// Input : *netAdr2 - +// Output : true if equal, false otherwise. +//----------------------------------------------------------------------------- +bool CNetAdr2::CompareClassBAdr(const CNetAdr2& netAdr2) const +{ + if (netAdr2.m_version != netadrversion_t::NA_V4 && m_version != netadrversion_t::NA_V4) + { + return false; + } + + if (netAdr2.GetType() != GetType()) + { + return false; + } + + if (GetType() == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (GetType() == netadrtype_t::NA_IP) + { + std::vector v0 = netAdr2.GetParts(); + std::vector v1 = GetParts(); + + if (std::strcmp(v0[0].c_str(), v1[0].c_str()) == 0 && + std::strcmp(v0[1].c_str(), v1[1].c_str()) == 0) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: compares Class-C IP for equality. +// Input : *netAdr2 - +// Output : true if equal, false otherwise. +//----------------------------------------------------------------------------- +bool CNetAdr2::CompareClassCAdr(const CNetAdr2& netAdr2) const +{ + if (netAdr2.GetVersion() != netadrversion_t::NA_V4 && GetVersion() != netadrversion_t::NA_V4) + { + return false; + } + + if (netAdr2.GetType() != GetType()) + { + return false; + } + + if (GetType() == netadrtype_t::NA_LOOPBACK) + { + return true; + } + + if (GetType() == netadrtype_t::NA_IP) + { + std::vector v0 = netAdr2.GetParts(); + std::vector v1 = GetParts(); + + if (std::strcmp(v0[0].c_str(), v1[0].c_str()) == 0 && + std::strcmp(v0[1].c_str(), v1[1].c_str()) == 0 && + std::strcmp(v0[2].c_str(), v1[2].c_str()) == 0) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears IP address. +//----------------------------------------------------------------------------- +void CNetAdr2::Clear(void) +{ + m_svip.clear(); + m_type = netadrtype_t::NA_NULL; + m_version = netadrversion_t::NA_INVALID; + m_sadr = {}; +} diff --git a/r5dev/tier1/NetAdr2.h b/r5dev/tier1/NetAdr2.h new file mode 100644 index 00000000..3086def3 --- /dev/null +++ b/r5dev/tier1/NetAdr2.h @@ -0,0 +1,88 @@ +#pragma once + +typedef struct netpacket_s netpacket_t; +typedef struct __declspec(align(8)) netpacket_s +{ + DWORD family_maybe; + sockaddr_in sin; + WORD sin_port; + char gap16; + char byte17; + DWORD source; + double received; + std::uint8_t* data; + std::uint64_t label; + BYTE byte38; + std::uint64_t qword40; + std::uint64_t qword48; + BYTE gap50[8]; + std::uint64_t qword58; + std::uint64_t qword60; + std::uint64_t qword68; + int less_than_12; + DWORD wiresize; + BYTE gap78[8]; + struct netpacket_s* pNext; +} netpacket_t; + +enum class netadrtype_t +{ + NA_NULL = 0, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, +}; + +enum class netadrversion_t +{ + NA_INVALID = -1, + NA_V4 = 4, + NA_V6 = 6, +}; + +class CNetAdr2 +{ +public: + CNetAdr2(void) {}; + CNetAdr2(std::string svInAdr); + CNetAdr2(std::string svInAdr, std::string svInPort); + ~CNetAdr2(void); + + void SetIP(std::string svInAdr); + void SetPort(std::string svInPort); + void SetIPAndPort(std::string svInAdr, std::string svInPort); + void SetType(netadrtype_t version); + void SetVersion(void); + void SetFromSocket(int hSocket); + bool SetFromSockadr(sockaddr_storage* s); + + std::string GetIP(bool bBaseOnly) const; + std::string GetPort(void) const; + std::string GetPort(std::string svInPort) const; + std::string GetIPAndPort(void) const; + netadrtype_t GetType(void) const; + netadrversion_t GetVersion(void) const; + std::string GetBase(void) const; + std::string GetBase(std::string svInAdr) const; + std::vector GetParts(void) const; + int GetSize(void) const; + + void ToSockadr(sockaddr_storage* pSadr) const; + void ToAdrinfo(addrinfo* pHint) const; + + bool IsLocalhost(void) const; + bool IsLoopback(void) const; + bool IsReservedAdr(void) const; + + bool CompareAdr(const CNetAdr2& adr2, bool bBaseOnly) const; + bool CompareClassBAdr(const CNetAdr2& adr2) const; + bool CompareClassCAdr(const CNetAdr2& adr2) const; + + void Clear(void); + +private: + std::string m_svip; + netadrtype_t m_type{}; + netadrversion_t m_version{}; + sockaddr_storage* m_sadr{}; +}; diff --git a/r5dev/tier2/socketcreator.cpp b/r5dev/tier2/socketcreator.cpp new file mode 100644 index 00000000..2c0e564e --- /dev/null +++ b/r5dev/tier2/socketcreator.cpp @@ -0,0 +1,367 @@ +//===========================================================================// +// +// Purpose: Server/Client dual-stack socket utility class +// +//===========================================================================// + +#include +#include +#include +#ifndef NETCONSOLE +#include +#endif // !NETCONSOLE +#include +#include + +// TODO [AMOS] IMPLEMENT 'Warning(...)' for every DevMsg spew here.. + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CSocketCreator::CSocketCreator(void) +{ + m_hListenSocket = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CSocketCreator::~CSocketCreator(void) +{ + DisconnectSocket(); +} + +//----------------------------------------------------------------------------- +// Purpose: accept new connections and walk open sockets and handle any incoming data +//----------------------------------------------------------------------------- +void CSocketCreator::RunFrame(void) +{ + if (IsListening()) + { + ProcessAccept(); // handle any new connection requests + } +} + +//----------------------------------------------------------------------------- +// Purpose: handle a new connection +//----------------------------------------------------------------------------- +void CSocketCreator::ProcessAccept(void) +{ + sockaddr_storage inClient{}; + int nLengthAddr = sizeof(inClient); + int newSocket = accept(m_hListenSocket, reinterpret_cast(&inClient), &nLengthAddr); + if (newSocket == -1) + { + if (!IsSocketBlocking()) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket ProcessAccept Error: %s\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Socket ProcessAccept Error: %s\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + } + return; + } + + if (!ConfigureListenSocket(newSocket)) + { + closesocket(newSocket); + return; + } + + CNetAdr2 netAdr2; + netAdr2.SetFromSockadr(&inClient); + + OnSocketAccepted(newSocket, netAdr2); +} + +//----------------------------------------------------------------------------- +// Purpose: Configures a listen socket for use +//----------------------------------------------------------------------------- +bool CSocketCreator::ConfigureListenSocket(int iSocket) +{ + // Disable NAGLE as RCON cmds are small in size. + int nodelay = 1; + int v6only = 0; + u_long opt = 1; + + setsockopt(iSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay)); + setsockopt(iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&nodelay, sizeof(nodelay)); + setsockopt(iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&v6only, sizeof(v6only)); + + int results = ioctlsocket(iSocket, FIONBIO, (u_long*)&opt); // Non-blocking. + if (results == -1) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket accept 'ioctl(FIONBIO)' failed (%i)\n", WSAGetLastError()); +#else + printf("Socket accept 'ioctl(FIONBIO)' failed (%i)\n", WSAGetLastError()); +#endif // !NETCONSOLE + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Configures a accepted socket for use +//----------------------------------------------------------------------------- +bool CSocketCreator::ConfigureConnectSocket(SocketHandle_t hSocket) +{ + int opt = 1; + int ret = 0; + + ret = ioctlsocket(hSocket, FIONBIO, reinterpret_cast(&opt)); // Non-blocking + if (ret == -1) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket ioctl(FIONBIO) failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Socket ioctl(FIONBIO) failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + closesocket(hSocket); + return false; + } + + // Disable NAGLE as RCON cmds are small in size. + int nodelay = 1; + setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay)); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: bind to a TCP port and accept incoming connections +// Input : *netAdr2 - +// bListenOnAllInterfaces - +// Output : true on success, failed otherwise +//----------------------------------------------------------------------------- +bool CSocketCreator::CreateListenSocket(const CNetAdr2& netAdr2, bool bListenOnAllInterfaces = false) +{ + CloseListenSocket(); + m_ListenAddress = netAdr2; + m_hListenSocket = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + + if (m_hListenSocket != INVALID_SOCKET) + { + if (!ConfigureListenSocket(m_hListenSocket)) + { + CloseListenSocket(); + return false; + } + + sockaddr_storage sadr{}; + m_ListenAddress.ToSockadr(&sadr); + + int results = bind(m_hListenSocket, reinterpret_cast(&sadr), m_ListenAddress.GetSize()); + if (results == -1) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket bind failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Socket bind failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + CloseListenSocket(); + return false; + } + + results = listen(m_hListenSocket, SOCKET_TCP_MAX_ACCEPTS); + if (results == -1) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket listen failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Socket listen failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + CloseListenSocket(); + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: close an open rcon connection +//----------------------------------------------------------------------------- +void CSocketCreator::CloseListenSocket(void) +{ + if (m_hListenSocket != -1) + { + closesocket(m_hListenSocket); + m_hListenSocket = -1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: connect to the remote server +// Input : *netAdr2 - +// bSingleSocker - +// Output : accepted socket index, SOCKET_ERROR (-1) if failed +//----------------------------------------------------------------------------- +int CSocketCreator::ConnectSocket(const CNetAdr2& netAdr2, bool bSingleSocket) +{ + if (bSingleSocket) + { // NOTE: Closing an accepted socket will re-index all the sockets with higher indices + CloseAllAcceptedSockets(); + } + + SocketHandle_t hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (hSocket == SOCKET_ERROR) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Unable to create socket (%s)\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Unable to create socket (%s)\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + return SOCKET_ERROR; + } + + if (!ConfigureConnectSocket(hSocket)) + { + return SOCKET_ERROR; + } + + struct sockaddr_storage s{}; + netAdr2.ToSockadr(&s); + + int results = connect(hSocket, reinterpret_cast(&s), sizeof(s)); + if (results == SOCKET_ERROR) + { + if (!IsSocketBlocking()) + { +#ifndef NETCONSOLE + DevMsg(eDLL_T::ENGINE, "Socket connection failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#else + printf("Socket connection failed (%s)\n", NET_ErrorString(WSAGetLastError())); +#endif // !NETCONSOLE + closesocket(hSocket); + return SOCKET_ERROR; + } + + fd_set writefds{}; + timeval tv{}; + + tv.tv_usec = 0; + tv.tv_sec = 1; + + FD_ZERO(&writefds); + FD_SET(static_cast(hSocket), &writefds); + + if (select(hSocket + 1, NULL, &writefds, NULL, &tv) < 1) // block for at most 1 second + { + closesocket(hSocket); // took too long to connect to, give up + return SOCKET_ERROR; + } + } + + // TODO: CRConClient check if connected. + + int nIndex = OnSocketAccepted(hSocket, netAdr2); + return nIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: closes all open sockets (listen + accepted) +//----------------------------------------------------------------------------- +void CSocketCreator::DisconnectSocket(void) +{ + CloseListenSocket(); + CloseAllAcceptedSockets(); +} + +//----------------------------------------------------------------------------- +// Purpose: handles new TCP requests and puts them in accepted queue +// Input : hSocket - +// *netAdr2 - +// Output : accepted socket index, -1 if failed +//----------------------------------------------------------------------------- +int CSocketCreator::OnSocketAccepted(SocketHandle_t hSocket, CNetAdr2 netAdr2) +{ + AcceptedSocket_t pNewEntry; + + pNewEntry.m_hSocket = hSocket; + pNewEntry.m_Address = netAdr2; + pNewEntry.m_pData = new CConnectedNetConsoleData(hSocket); + + m_hAcceptedSockets.push_back(pNewEntry); + + int nIndex = (int)m_hAcceptedSockets.size() - 1; + return nIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: close an accepted socket +// Input : nIndex - +//----------------------------------------------------------------------------- +void CSocketCreator::CloseAcceptedSocket(int nIndex) +{ + if (nIndex >= m_hAcceptedSockets.size()) + { + return; + } + + AcceptedSocket_t& connected = m_hAcceptedSockets[nIndex]; + closesocket(connected.m_hSocket); + m_hAcceptedSockets.erase(m_hAcceptedSockets.begin() + nIndex); +} + +//----------------------------------------------------------------------------- +// Purpose: close all accepted sockets +//----------------------------------------------------------------------------- +void CSocketCreator::CloseAllAcceptedSockets(void) +{ + int nCount = m_hAcceptedSockets.size(); + for (int i = 0; i < nCount; ++i) + { + AcceptedSocket_t& connected = m_hAcceptedSockets[i]; + closesocket(connected.m_hSocket); + } + m_hAcceptedSockets.clear(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the listening socket is created and listening +//----------------------------------------------------------------------------- +bool CSocketCreator::IsListening(void) const +{ + return m_hListenSocket != -1; +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the socket would block because of the last socket command +//----------------------------------------------------------------------------- +bool CSocketCreator::IsSocketBlocking(void) const +{ + return (WSAGetLastError() == WSAEWOULDBLOCK); +} + +//----------------------------------------------------------------------------- +// Purpose: returns accepted socket count +//----------------------------------------------------------------------------- +int CSocketCreator::GetAcceptedSocketCount(void) const +{ + return m_hAcceptedSockets.size(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns accepted socket handle +//----------------------------------------------------------------------------- +SocketHandle_t CSocketCreator::GetAcceptedSocketHandle(int nIndex) const +{ + return m_hAcceptedSockets[nIndex].m_hSocket; +} + +//----------------------------------------------------------------------------- +// Purpose: returns accepted socket address +//----------------------------------------------------------------------------- +const CNetAdr2& CSocketCreator::GetAcceptedSocketAddress(int nIndex) const +{ + return m_hAcceptedSockets[nIndex].m_Address; +} + +//----------------------------------------------------------------------------- +// Purpose: returns accepted socket data +//----------------------------------------------------------------------------- +CConnectedNetConsoleData* CSocketCreator::GetAcceptedSocketData(int nIndex) const +{ + return m_hAcceptedSockets[nIndex].m_pData; +} diff --git a/r5dev/tier2/socketcreator.h b/r5dev/tier2/socketcreator.h new file mode 100644 index 00000000..58c5b41d --- /dev/null +++ b/r5dev/tier2/socketcreator.h @@ -0,0 +1,78 @@ +#pragma once +#include "tier1/netadr2.h" + +typedef int SocketHandle_t; + +class CConnectedNetConsoleData +{ +public: + SocketHandle_t m_hSocket {}; + int m_nCharsInCommandBuffer {}; + char m_pszInputCommandBuffer[MAX_NETCONSOLE_INPUT_LEN] {}; + bool m_bAuthorized {}; // If set, this netconsole is authenticated. + bool m_bInputOnly {}; // If set, don't send spew to this netconsole. + int m_nFailedAttempts {}; // Num failed authentication attempts. + + CConnectedNetConsoleData(SocketHandle_t hSocket = -1) + { + m_nCharsInCommandBuffer = 0; + m_bAuthorized = false; + m_hSocket = hSocket; + m_bInputOnly = false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: container class to handle network streams +//----------------------------------------------------------------------------- +class CSocketCreator +{ +public: + CSocketCreator(void); + ~CSocketCreator(void); + + void RunFrame(void); + void ProcessAccept(void); + + bool ConfigureListenSocket(int iSocket); + bool ConfigureConnectSocket(SocketHandle_t hSocket); + + bool CreateListenSocket(const CNetAdr2& netAdr2, bool bListenOnAllInterfaces); + void CloseListenSocket(void); + + int ConnectSocket(const CNetAdr2& netAdr2, bool bSingleSocket); + void DisconnectSocket(void); + + int OnSocketAccepted(SocketHandle_t hSocket, CNetAdr2 netAdr2); + + void CloseAcceptedSocket(int nIndex); + void CloseAllAcceptedSockets(void); + + bool IsListening(void) const; + bool IsSocketBlocking(void) const; + + int GetAcceptedSocketCount(void) const; + SocketHandle_t GetAcceptedSocketHandle(int nIndex) const; + const CNetAdr2& GetAcceptedSocketAddress(int nIndex) const; + CConnectedNetConsoleData* GetAcceptedSocketData(int nIndex) const; + +public: + struct AcceptedSocket_t + { + SocketHandle_t m_hSocket{}; + CNetAdr2 m_Address{}; + CConnectedNetConsoleData* m_pData = nullptr; + + bool operator==(const AcceptedSocket_t& rhs) const { return (m_Address.CompareAdr(rhs.m_Address, false) == 0); } + }; + + std::vector m_hAcceptedSockets{}; + SocketHandle_t m_hListenSocket {}; // Used to accept connections. + CNetAdr2 m_ListenAddress {}; // Address used to listen on. + +private: + enum + { + SOCKET_TCP_MAX_ACCEPTS = 2 + }; +};