diff --git a/Client/core/CClientVariables.cpp b/Client/core/CClientVariables.cpp index ce823f07631..b0a55b11a32 100644 --- a/Client/core/CClientVariables.cpp +++ b/Client/core/CClientVariables.cpp @@ -354,6 +354,7 @@ void CClientVariables::LoadDefaults() DEFAULT("dynamic_ped_shadows", 0); // Disable dynamic ped shadows DEFAULT("fast_clothes_loading", 1); // 0-off 1-auto 2-on DEFAULT("allow_screen_upload", 1); // 0-off 1-on + DEFAULT("allow_cpu_info", 1); // 0-off 1-on DEFAULT("allow_external_sounds", 1); // 0-off 1-on DEFAULT("max_clientscript_log_kb", 5000); // Max size in KB (0-No limit) DEFAULT("display_fullscreen_style", 0); // 0-standard 1-borderless 2-borderless keep res 3-borderless stretch diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index d956181d5ca..c9802213833 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -221,6 +221,7 @@ void CSettings::ResetGuiPointers() m_pCheckBoxDeviceSelectionDialog = NULL; m_pCheckBoxShowUnsafeResolutions = NULL; m_pCheckBoxAllowScreenUpload = NULL; + m_pCheckBoxAllowCPUInfo = NULL; m_pCheckBoxAllowExternalSounds = NULL; m_pCheckBoxCustomizedSAFiles = NULL; m_pCheckBoxAllowDiscordRPC = NULL; @@ -502,6 +503,7 @@ void CSettings::CreateGUI() m_dwFrameCount = 0; m_bShownVolumetricShadowsWarning = false; m_bShownAllowScreenUploadMessage = false; + m_bShownAllowCPUInfoMessage = false; CVector2D vecTemp; CVector2D vecSize; @@ -902,6 +904,11 @@ void CSettings::CreateGUI() m_pCheckBoxAllowScreenUpload->GetPosition(vecTemp, false); m_pCheckBoxAllowScreenUpload->AutoSize(NULL, 20.0f); + m_pCheckBoxAllowCPUInfo = reinterpret_cast(pManager->CreateCheckBox(pTabMultiplayer, _("Allow CPU info"), true)); + m_pCheckBoxAllowCPUInfo->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 20.0f)); + m_pCheckBoxAllowCPUInfo->GetPosition(vecTemp, false); + m_pCheckBoxAllowCPUInfo->AutoSize(NULL, 20.0f); + m_pCheckBoxAllowExternalSounds = reinterpret_cast(pManager->CreateCheckBox(pTabMultiplayer, _("Allow external sounds"), true)); m_pCheckBoxAllowExternalSounds->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 20.0f)); m_pCheckBoxAllowExternalSounds->GetPosition(vecTemp, false); @@ -2001,6 +2008,7 @@ void CSettings::CreateGUI() m_pComboFxQuality->SetSelectionHandler(GUI_CALLBACK(&CSettings::OnFxQualityChanged, this)); m_pCheckBoxVolumetricShadows->SetClickHandler(GUI_CALLBACK(&CSettings::OnVolumetricShadowsClick, this)); m_pCheckBoxAllowScreenUpload->SetClickHandler(GUI_CALLBACK(&CSettings::OnAllowScreenUploadClick, this)); + m_pCheckBoxAllowCPUInfo->SetClickHandler(GUI_CALLBACK(&CSettings::OnAllowCPUInfoClick, this)); m_pCheckBoxAllowExternalSounds->SetClickHandler(GUI_CALLBACK(&CSettings::OnAllowExternalSoundsClick, this)); m_pCheckBoxAllowDiscordRPC->SetClickHandler(GUI_CALLBACK(&CSettings::OnAllowDiscordRPC, this)); m_pCheckBoxCustomizedSAFiles->SetClickHandler(GUI_CALLBACK(&CSettings::OnCustomizedSAFilesClick, this)); @@ -3981,6 +3989,10 @@ void CSettings::LoadData() CVARS_GET("allow_screen_upload", bAllowScreenUploadEnabled); m_pCheckBoxAllowScreenUpload->SetSelected(bAllowScreenUploadEnabled); + bool bAllowCPUInfoEnabled; + CVARS_GET("allow_cpu_info", bAllowCPUInfoEnabled); + m_pCheckBoxAllowCPUInfo->SetSelected(bAllowCPUInfoEnabled); + // Allow external sounds bool bAllowExternalSoundsEnabled; CVARS_GET("allow_external_sounds", bAllowExternalSoundsEnabled); @@ -4472,6 +4484,9 @@ void CSettings::SaveData() bool bAllowScreenUploadEnabled = m_pCheckBoxAllowScreenUpload->GetSelected(); CVARS_SET("allow_screen_upload", bAllowScreenUploadEnabled); + bool bAllowCPUInfoEnabled = m_pCheckBoxAllowCPUInfo->GetSelected(); + CVARS_SET("allow_cpu_info", bAllowCPUInfoEnabled); + // Allow external sounds bool bAllowExternalSoundsEnabled = m_pCheckBoxAllowExternalSounds->GetSelected(); CVARS_SET("allow_external_sounds", bAllowExternalSoundsEnabled); @@ -5736,6 +5751,23 @@ bool CSettings::OnAllowScreenUploadClick(CGUIElement* pElement) return true; } +// +// AllowCPUInfo +// +bool CSettings::OnAllowCPUInfoClick(CGUIElement* pElement) +{ + if (!m_pCheckBoxAllowCPUInfo->GetSelected() && !m_bShownAllowCPUInfoMessage) + { + m_bShownAllowCPUInfoMessage = true; + SString strMessage; + strMessage += + _("Some servers use CPU information to adjust quality or identify clients." + "\n\nDisabling this setting may prevent you from joining those servers.\n"); + CCore::GetSingleton().ShowMessageBox(_("CPU INFO"), strMessage, MB_BUTTON_OK | MB_ICON_INFO); + } + return true; +} + // // AllowExternalSounds // diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index 864feadb727..e87ffb9de43 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -163,6 +163,7 @@ class CSettings CGUICheckBox* m_pCheckBoxDeviceSelectionDialog; CGUICheckBox* m_pCheckBoxShowUnsafeResolutions; CGUICheckBox* m_pCheckBoxAllowScreenUpload; + CGUICheckBox* m_pCheckBoxAllowCPUInfo; CGUICheckBox* m_pCheckBoxAllowExternalSounds; CGUICheckBox* m_pCheckBoxCustomizedSAFiles; CGUICheckBox* m_pCheckBoxAllowDiscordRPC; @@ -444,6 +445,7 @@ class CSettings bool OnFxQualityChanged(CGUIElement* pElement); bool OnVolumetricShadowsClick(CGUIElement* pElement); bool OnAllowScreenUploadClick(CGUIElement* pElement); + bool OnAllowCPUInfoClick(CGUIElement* pElement); bool OnAllowExternalSoundsClick(CGUIElement* pElement); bool OnAllowDiscordRPC(CGUIElement* pElement); bool OnCustomizedSAFilesClick(CGUIElement* pElement); @@ -518,6 +520,7 @@ class CSettings DWORD m_dwFrameCount; bool m_bShownVolumetricShadowsWarning; bool m_bShownAllowScreenUploadMessage; + bool m_bShownAllowCPUInfoMessage; bool m_bShownAllowExternalSoundsMessage; int m_iMaxAnisotropic; diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 6ed7e017179..368e04e538c 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include +#include #include #include #include @@ -728,6 +729,35 @@ bool CClientGame::StartGame(const char* szNick, const char* szPassword, eServerT g_pNet->SendPacket(PACKET_ID_PLAYER_JOINDATA, pBitStream, PACKET_PRIORITY_HIGH, PACKET_RELIABILITY_RELIABLE_ORDERED); g_pNet->DeallocateNetBitStream(pBitStream); + // CPU info is a separate packet so old servers can ignore the unknown id. + // AllowCPUInfo=false is still sent so the server can tell opt-out from "no packet". + { + bool bAllowCPUInfo = true; + g_pCore->GetCVars()->Get("allow_cpu_info", bAllowCPUInfo); + + NetBitStreamInterface* pCPUBitStream = g_pNet->AllocateNetBitStream(); + if (pCPUBitStream) + { + pCPUBitStream->WriteBit(bAllowCPUInfo); + if (bAllowCPUInfo) + { + SharedUtil::SCPUInfo cpuInfo; + SharedUtil::GetCPUInfo(cpuInfo); + if (cpuInfo.strName.length() > 128) + cpuInfo.strName = cpuInfo.strName.Left(128); + pCPUBitStream->WriteString(cpuInfo.strName); + pCPUBitStream->Write(cpuInfo.uiMaxClockSpeedMHz); + pCPUBitStream->Write(cpuInfo.uiCores); + pCPUBitStream->Write(cpuInfo.uiThreads); + pCPUBitStream->Write(cpuInfo.uiL1CacheKB); + pCPUBitStream->Write(cpuInfo.uiL2CacheKB); + pCPUBitStream->Write(cpuInfo.uiL3CacheKB); + } + g_pNet->SendPacket(PACKET_ID_PLAYER_CPUINFO, pCPUBitStream, PACKET_PRIORITY_HIGH, PACKET_RELIABILITY_RELIABLE_ORDERED); + g_pNet->DeallocateNetBitStream(pCPUBitStream); + } + } + return true; } } diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.cpp index 2d9e72c340a..afdced6c43e 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.cpp @@ -12,6 +12,7 @@ #include "StdInc.h" #include "CLuaClientDefs.h" #include "lua/CLuaFunctionParser.h" +#include void CLuaClientDefs::LoadFunctions() { @@ -23,7 +24,8 @@ void CLuaClientDefs::LoadFunctions() {"isChatInputBlocked", ArgumentParser}, {"clearDebugBox", ArgumentParser}, {"isMTAWindowFocused", ArgumentParser}, - {"isCapsLockEnabled", ArgumentParser}}; + {"isCapsLockEnabled", ArgumentParser}, + {"getCPUInfo", ArgumentParser}}; for (const auto& [name, func] : functions) CLuaCFunctions::AddFunction(name, func); @@ -80,3 +82,36 @@ bool CLuaClientDefs::IsCapsLockEnabled() { return ((::GetKeyState(VK_CAPITAL) & 0x0001) != 0); } + +CLuaArguments CLuaClientDefs::GetCPUInfo() +{ + CLuaArguments table; + bool bAllowCPUInfo = true; + g_pCore->GetCVars()->Get("allow_cpu_info", bAllowCPUInfo); + + table.PushString("AllowCPUInfo"); + table.PushBoolean(bAllowCPUInfo); + + // Opt-out must omit hardware fields so server scripts cannot recover them from a client event. + if (!bAllowCPUInfo) + return table; + + SharedUtil::SCPUInfo info; + SharedUtil::GetCPUInfo(info); + + table.PushString("Name"); + table.PushString(info.strName); + table.PushString("MaxClockSpeedMHz"); + table.PushNumber(info.uiMaxClockSpeedMHz); + table.PushString("Cores"); + table.PushNumber(info.uiCores); + table.PushString("Threads"); + table.PushNumber(info.uiThreads); + table.PushString("L1CacheKB"); + table.PushNumber(info.uiL1CacheKB); + table.PushString("L2CacheKB"); + table.PushNumber(info.uiL2CacheKB); + table.PushString("L3CacheKB"); + table.PushNumber(info.uiL3CacheKB); + return table; +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.h index 7b3b276b83e..2101c3aad24 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaClientDefs.h @@ -12,6 +12,7 @@ #pragma once #include "CLuaDefs.h" +#include "lua/CLuaArguments.h" class CLuaClientDefs : public CLuaDefs { @@ -19,13 +20,14 @@ class CLuaClientDefs : public CLuaDefs static void LoadFunctions(); private: - static bool SetTransferBoxVisible(bool visible); - static bool IsTransferBoxVisible(); - static bool IsTransferBoxAlwaysVisible(); - static bool ShowChat(bool bVisible, std::optional optInputBlocked); - static bool IsChatVisible(); - static bool IsChatInputBlocked(); - static bool ClearDebug(); - static bool IsMTAWindowFocused(); - static bool IsCapsLockEnabled(); + static bool SetTransferBoxVisible(bool visible); + static bool IsTransferBoxVisible(); + static bool IsTransferBoxAlwaysVisible(); + static bool ShowChat(bool bVisible, std::optional optInputBlocked); + static bool IsChatVisible(); + static bool IsChatInputBlocked(); + static bool ClearDebug(); + static bool IsMTAWindowFocused(); + static bool IsCapsLockEnabled(); + static CLuaArguments GetCPUInfo(); }; diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index c6d4001455d..4c3ae1f2a6d 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -1341,6 +1341,12 @@ bool CGame::ProcessPacket(CPacket& Packet) return true; } + case PACKET_ID_PLAYER_CPUINFO: + { + Packet_PlayerCPUInfo(static_cast(Packet)); + return true; + } + case PACKET_ID_PLAYER_SCREENSHOT: { Packet_PlayerScreenShot(static_cast(Packet)); @@ -1704,6 +1710,7 @@ void CGame::AddBuiltInEvents() m_Events.AddEvent("onPlayerCommand", "command", NULL, false); m_Events.AddEvent("onPlayerModInfo", "filename, itemlist", NULL, false); m_Events.AddEvent("onPlayerACInfo", "aclist, size, md5, sha256", NULL, false); + m_Events.AddEvent("onPlayerCPUInfo", "allowed, name, maxClockSpeedMHz, cores, threads, l1CacheKB, l2CacheKB, l3CacheKB", NULL, false); m_Events.AddEvent("onPlayerNetworkStatus", "type, ticks", NULL, false); m_Events.AddEvent("onPlayerScreenShot", "resource, status, file_data, timestamp, tag", NULL, false); m_Events.AddEvent("onPlayerResourceStart", "resource", NULL, false); @@ -4670,6 +4677,34 @@ void CGame::Packet_PlayerACInfo(CPlayerACInfoPacket& Packet) } } +void CGame::Packet_PlayerCPUInfo(CPlayerCPUInfoPacket& Packet) +{ + CPlayer* pPlayer = Packet.GetSourcePlayer(); + if (!pPlayer) + return; + + pPlayer->m_bCPUInfoReceived = true; + pPlayer->m_bAllowCPUInfo = Packet.m_bAllowCPUInfo; + pPlayer->m_strCPUName = Packet.m_strName; + pPlayer->m_uiCPUMaxClockSpeedMHz = Packet.m_uiMaxClockSpeedMHz; + pPlayer->m_uiCPUCores = Packet.m_uiCores; + pPlayer->m_uiCPUThreads = Packet.m_uiThreads; + pPlayer->m_uiCPUL1CacheKB = Packet.m_uiL1CacheKB; + pPlayer->m_uiCPUL2CacheKB = Packet.m_uiL2CacheKB; + pPlayer->m_uiCPUL3CacheKB = Packet.m_uiL3CacheKB; + + CLuaArguments Arguments; + Arguments.PushBoolean(Packet.m_bAllowCPUInfo); + Arguments.PushString(Packet.m_strName); + Arguments.PushNumber(Packet.m_uiMaxClockSpeedMHz); + Arguments.PushNumber(Packet.m_uiCores); + Arguments.PushNumber(Packet.m_uiThreads); + Arguments.PushNumber(Packet.m_uiL1CacheKB); + Arguments.PushNumber(Packet.m_uiL2CacheKB); + Arguments.PushNumber(Packet.m_uiL3CacheKB); + pPlayer->CallEvent("onPlayerCPUInfo", Arguments); +} + void CGame::PlayerCompleteConnect(CPlayer* pPlayer) { SString strIPAndSerial("IP: %s Serial: %s Version: %s", pPlayer->GetSourceIP(), pPlayer->GetSerial().c_str(), pPlayer->GetPlayerVersion().c_str()); diff --git a/Server/mods/deathmatch/logic/CGame.h b/Server/mods/deathmatch/logic/CGame.h index dda626da3fd..d7ebdfe1fda 100644 --- a/Server/mods/deathmatch/logic/CGame.h +++ b/Server/mods/deathmatch/logic/CGame.h @@ -50,6 +50,7 @@ class CGame; #include "packets/CPlayerDiagnosticPacket.h" #include "packets/CPlayerModInfoPacket.h" #include "packets/CPlayerACInfoPacket.h" +#include "packets/CPlayerCPUInfoPacket.h" #include "packets/CPlayerScreenShotPacket.h" #include "CRPCFunctions.h" @@ -520,6 +521,7 @@ class CGame void Packet_PlayerDiagnostic(class CPlayerDiagnosticPacket& Packet); void Packet_PlayerModInfo(class CPlayerModInfoPacket& Packet); void Packet_PlayerACInfo(class CPlayerACInfoPacket& Packet); + void Packet_PlayerCPUInfo(class CPlayerCPUInfoPacket& Packet); void Packet_PlayerScreenShot(class CPlayerScreenShotPacket& Packet); void Packet_PlayerNoSocket(class CPlayerNoSocketPacket& Packet); void Packet_PlayerNetworkStatus(class CPlayerNetworkStatusPacket& Packet); diff --git a/Server/mods/deathmatch/logic/CPacketTranslator.cpp b/Server/mods/deathmatch/logic/CPacketTranslator.cpp index 20ced1ab808..7f1b71ac4a0 100644 --- a/Server/mods/deathmatch/logic/CPacketTranslator.cpp +++ b/Server/mods/deathmatch/logic/CPacketTranslator.cpp @@ -43,6 +43,7 @@ #include "packets/CPlayerDiagnosticPacket.h" #include "packets/CPlayerModInfoPacket.h" #include "packets/CPlayerACInfoPacket.h" +#include "packets/CPlayerCPUInfoPacket.h" #include "packets/CPlayerScreenShotPacket.h" #include "packets/CUnoccupiedVehiclePushPacket.h" #include "packets/CPlayerNoSocketPacket.h" @@ -193,6 +194,10 @@ CPacket* CPacketTranslator::Translate(const NetServerPlayerID& Socket, ePacketID pTemp = new CPlayerACInfoPacket; break; + case PACKET_ID_PLAYER_CPUINFO: + pTemp = new CPlayerCPUInfoPacket; + break; + case PACKET_ID_PLAYER_SCREENSHOT: pTemp = new CPlayerScreenShotPacket; break; diff --git a/Server/mods/deathmatch/logic/CPlayer.h b/Server/mods/deathmatch/logic/CPlayer.h index 02f96647004..d898733bcf2 100644 --- a/Server/mods/deathmatch/logic/CPlayer.h +++ b/Server/mods/deathmatch/logic/CPlayer.h @@ -354,6 +354,15 @@ class CPlayer final : public CPed, public CClient uint m_uiD3d9Size; SString m_strD3d9Md5; SString m_strD3d9Sha256; + bool m_bCPUInfoReceived = false; + bool m_bAllowCPUInfo = false; + SString m_strCPUName; + unsigned int m_uiCPUMaxClockSpeedMHz = 0; + unsigned int m_uiCPUCores = 0; + unsigned int m_uiCPUThreads = 0; + unsigned int m_uiCPUL1CacheKB = 0; + unsigned int m_uiCPUL2CacheKB = 0; + unsigned int m_uiCPUL3CacheKB = 0; // Per-player token bucket throttling for onPlayerResourceStart acks. Genuine duplicates // consume a token; race-condition acks (resource stopped/restarted before the ack arrived) diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp index d89fb3fd9ad..beb16793de4 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.cpp @@ -15,6 +15,7 @@ #include "CStaticFunctionDefinitions.h" #include "CScriptArgReader.h" #include "CKeyBinds.h" +#include "lua/CLuaFunctionParser.h" #include "packets/CLuaPacket.h" #include #include @@ -49,6 +50,7 @@ void CLuaPlayerDefs::LoadFunctions() {"getPlayerAccount", GetPlayerAccount}, {"getPlayerVersion", GetPlayerVersion}, {"getPlayerACInfo", GetPlayerACInfo}, + {"getPlayerCPUInfo", ArgumentParser}, {"resendPlayerModInfo", ResendPlayerModInfo}, {"resendPlayerACInfo", ResendPlayerACInfo}, {"getPlayerScriptDebugLevel", ArgumentParser}, @@ -179,6 +181,7 @@ void CLuaPlayerDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "getMoney", "getPlayerMoney"); lua_classfunction(luaVM, "getAnnounceValue", "getPlayerAnnounceValue"); lua_classfunction(luaVM, "getACInfo", "getPlayerACInfo"); + lua_classfunction(luaVM, "getCPUInfo", "getPlayerCPUInfo"); lua_classfunction(luaVM, "getCameraInterior", "getCameraInterior"); lua_classfunction(luaVM, "getCameraMatrix", "getCameraMatrix"); lua_classfunction(luaVM, "getCameraTarget", "getCameraTarget"); @@ -189,6 +192,7 @@ void CLuaPlayerDefs::AddClass(lua_State* luaVM) lua_classvariable(luaVM, "cameraMatrix", "setCameraMatrix", "getCameraMatrix"); lua_classvariable(luaVM, "cameraTarget", "setCameraTarget", "getCameraTarget"); lua_classvariable(luaVM, "ACInfo", NULL, "getPlayerACInfo"); + lua_classvariable(luaVM, "CPUInfo", NULL, "getPlayerCPUInfo"); lua_classvariable(luaVM, "voiceBroadcastTo", "setPlayerVoiceBroadcastTo", NULL); lua_classvariable(luaVM, "voiceIgnoreFrom", "setPlayerVoiceIgnoreFrom", NULL); lua_classvariable(luaVM, "money", "setPlayerMoney", "getPlayerMoney"); @@ -500,6 +504,36 @@ int CLuaPlayerDefs::GetPlayerACInfo(lua_State* luaVM) return 1; } +std::variant CLuaPlayerDefs::GetPlayerCPUInfo(CPlayer* const player) +{ + if (!player->m_bCPUInfoReceived) + return false; + + CLuaArguments table; + table.PushString("AllowCPUInfo"); + table.PushBoolean(player->m_bAllowCPUInfo); + + if (player->m_bAllowCPUInfo) + { + table.PushString("Name"); + table.PushString(player->m_strCPUName); + table.PushString("MaxClockSpeedMHz"); + table.PushNumber(player->m_uiCPUMaxClockSpeedMHz); + table.PushString("Cores"); + table.PushNumber(player->m_uiCPUCores); + table.PushString("Threads"); + table.PushNumber(player->m_uiCPUThreads); + table.PushString("L1CacheKB"); + table.PushNumber(player->m_uiCPUL1CacheKB); + table.PushString("L2CacheKB"); + table.PushNumber(player->m_uiCPUL2CacheKB); + table.PushString("L3CacheKB"); + table.PushNumber(player->m_uiCPUL3CacheKB); + } + + return table; +} + int CLuaPlayerDefs::GetPlayerWantedLevel(lua_State* luaVM) { CPlayer* pPlayer; diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h index b0f43658f63..6c32fd9069a 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h +++ b/Server/mods/deathmatch/logic/luadefs/CLuaPlayerDefs.h @@ -11,6 +11,8 @@ #pragma once #include "CLuaDefs.h" +#include "lua/CLuaArguments.h" +#include class CLuaPlayerDefs : public CLuaDefs { @@ -46,7 +48,8 @@ class CLuaPlayerDefs : public CLuaDefs LUA_DECLARE(GetPlayerAccount); LUA_DECLARE(GetPlayerVersion); LUA_DECLARE(GetPlayerACInfo); - static unsigned int GetPlayerScriptDebugLevel(CPlayer* const player); + static std::variant GetPlayerCPUInfo(CPlayer* const player); + static unsigned int GetPlayerScriptDebugLevel(CPlayer* const player); // Player set functions LUA_DECLARE(SetPlayerMoney); diff --git a/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.cpp b/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.cpp new file mode 100644 index 00000000000..78477f4cbf5 --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.cpp @@ -0,0 +1,43 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.cpp + * PURPOSE: Player CPU info packet class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CPlayerCPUInfoPacket.h" + +namespace +{ + constexpr unsigned int MAX_CPU_NAME_LENGTH = 128; + constexpr unsigned int MAX_CPU_CLOCK_MHZ = 10000; + constexpr unsigned int MAX_CPU_CORES = 512; + constexpr unsigned int MAX_CPU_CACHE_KB = 1024 * 1024; +} + +bool CPlayerCPUInfoPacket::Read(NetBitStreamInterface& BitStream) +{ + if (!BitStream.ReadBit(m_bAllowCPUInfo)) + return false; + + if (!m_bAllowCPUInfo) + return true; + + if (!BitStream.ReadString(m_strName) || m_strName.length() > MAX_CPU_NAME_LENGTH) + return false; + + if (!BitStream.Read(m_uiMaxClockSpeedMHz) || !BitStream.Read(m_uiCores) || !BitStream.Read(m_uiThreads) || !BitStream.Read(m_uiL1CacheKB) || + !BitStream.Read(m_uiL2CacheKB) || !BitStream.Read(m_uiL3CacheKB)) + return false; + + if (m_uiMaxClockSpeedMHz > MAX_CPU_CLOCK_MHZ || m_uiCores == 0 || m_uiCores > MAX_CPU_CORES || m_uiThreads == 0 || m_uiThreads > MAX_CPU_CORES || + m_uiL1CacheKB > MAX_CPU_CACHE_KB || m_uiL2CacheKB > MAX_CPU_CACHE_KB || m_uiL3CacheKB > MAX_CPU_CACHE_KB) + return false; + + return true; +} diff --git a/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.h b/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.h new file mode 100644 index 00000000000..d472ef8cf7a --- /dev/null +++ b/Server/mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.h @@ -0,0 +1,32 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/packets/CPlayerCPUInfoPacket.h + * PURPOSE: Player CPU info packet class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CPacket.h" + +class CPlayerCPUInfoPacket final : public CPacket +{ +public: + ePacketID GetPacketID() const { return PACKET_ID_PLAYER_CPUINFO; }; + unsigned long GetFlags() const { return 0; }; // Not used + + bool Read(NetBitStreamInterface& BitStream); + + bool m_bAllowCPUInfo = false; + SString m_strName; + unsigned int m_uiMaxClockSpeedMHz = 0; + unsigned int m_uiCores = 0; + unsigned int m_uiThreads = 0; + unsigned int m_uiL1CacheKB = 0; + unsigned int m_uiL2CacheKB = 0; + unsigned int m_uiL3CacheKB = 0; +}; diff --git a/Shared/mods/deathmatch/logic/Enums.cpp b/Shared/mods/deathmatch/logic/Enums.cpp index 8bb99db974f..168b2a979fe 100644 --- a/Shared/mods/deathmatch/logic/Enums.cpp +++ b/Shared/mods/deathmatch/logic/Enums.cpp @@ -228,6 +228,7 @@ ADD_ENUM1(PACKET_ID_PED_TASK) ADD_ENUM1(PACKET_ID_PLAYER_NO_SOCKET) ADD_ENUM1(PACKET_ID_PLAYER_NETWORK_STATUS) ADD_ENUM1(PACKET_ID_PLAYER_ACINFO) +ADD_ENUM1(PACKET_ID_PLAYER_CPUINFO) ADD_ENUM1(PACKET_ID_CHAT_CLEAR) ADD_ENUM1(PACKET_ID_SERVER_INFO_SYNC) ADD_ENUM1(PACKET_ID_DISCORD_JOIN) diff --git a/Shared/sdk/SharedUtil.SysInfo.h b/Shared/sdk/SharedUtil.SysInfo.h index d6580b555bb..e3a12b97db3 100644 --- a/Shared/sdk/SharedUtil.SysInfo.h +++ b/Shared/sdk/SharedUtil.SysInfo.h @@ -36,6 +36,17 @@ namespace SharedUtil SString strProductName; }; + struct SCPUInfo + { + SString strName; + unsigned int uiMaxClockSpeedMHz = 0; + unsigned int uiCores = 0; + unsigned int uiThreads = 0; + unsigned int uiL1CacheKB = 0; + unsigned int uiL2CacheKB = 0; + unsigned int uiL3CacheKB = 0; + }; + bool QueryWMI(SQueryWMIResult& outResult, const SString& strQuery, const SString& strKeys, const SString& strNamespace = "CIMV2"); SString GetWMIOSVersion(); unsigned int GetWMIVideoAdapterMemorySize(const unsigned long ulVen, const unsigned long ulDev); @@ -45,6 +56,7 @@ namespace SharedUtil bool IsHotFixInstalled(const SString& strHotFixId); bool GetLibVersionInfo(const SString& strLibName, SLibVersionInfo* pOutLibVersionInfo); bool Is64BitOS(); + void GetCPUInfo(SCPUInfo& outInfo); } // namespace SharedUtil #endif diff --git a/Shared/sdk/SharedUtil.SysInfo.hpp b/Shared/sdk/SharedUtil.SysInfo.hpp index b32f40920f6..870d97aec5e 100644 --- a/Shared/sdk/SharedUtil.SysInfo.hpp +++ b/Shared/sdk/SharedUtil.SysInfo.hpp @@ -12,7 +12,9 @@ #ifdef MTA_CLIENT #define _WIN32_DCOM + #include #include + #include #include #pragma comment(lib, "wbemuuid.lib") @@ -538,4 +540,96 @@ bool SharedUtil::Is64BitOS() return bIs64BitOS; } +///////////////////////////////////////////////////////////////////// +// +// GetCPUInfo +// +// CPUID brand string, registry ~MHz, and GetLogicalProcessorInformation +// for cores/threads/cache. Cached because the hardware does not change. +// +///////////////////////////////////////////////////////////////////// +void SharedUtil::GetCPUInfo(SCPUInfo& outInfo) +{ + static SCPUInfo cached{}; + static bool bReady = false; + + if (bReady) + { + outInfo = cached; + return; + } + + int cpuInfo[4] = {}; + __cpuid(cpuInfo, 0x80000000); + if (static_cast(cpuInfo[0]) >= 0x80000004) + { + char szBrand[49] = {}; + __cpuid(reinterpret_cast(szBrand), 0x80000002); + __cpuid(reinterpret_cast(szBrand + 16), 0x80000003); + __cpuid(reinterpret_cast(szBrand + 32), 0x80000004); + cached.strName = SStringX(szBrand).Trim(); + } + + HKEY hKey = nullptr; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + DWORD dwMHz = 0; + DWORD dwSize = sizeof(dwMHz); + DWORD dwType = 0; + if (RegQueryValueExA(hKey, "~MHz", nullptr, &dwType, reinterpret_cast(&dwMHz), &dwSize) == ERROR_SUCCESS && dwType == REG_DWORD) + cached.uiMaxClockSpeedMHz = dwMHz; + + if (cached.strName.empty()) + { + char szName[64] = {}; + DWORD dwNameSize = sizeof(szName); + dwType = 0; + if (RegQueryValueExA(hKey, "ProcessorNameString", nullptr, &dwType, reinterpret_cast(szName), &dwNameSize) == ERROR_SUCCESS && + dwType == REG_SZ) + cached.strName = SStringX(szName).Trim(); + } + + RegCloseKey(hKey); + } + + SYSTEM_INFO sysInfo{}; + GetNativeSystemInfo(&sysInfo); + cached.uiThreads = sysInfo.dwNumberOfProcessors; + + DWORD dwBufferSize = 0; + GetLogicalProcessorInformation(nullptr, &dwBufferSize); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && dwBufferSize >= sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) + { + std::vector buffer(dwBufferSize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); + if (GetLogicalProcessorInformation(buffer.data(), &dwBufferSize)) + { + unsigned int uiCores = 0; + for (const auto& info : buffer) + { + if (info.Relationship == RelationProcessorCore) + ++uiCores; + else if (info.Relationship == RelationCache) + { + const unsigned int uiSizeKB = info.Cache.Size / 1024; + if (info.Cache.Level == 1) + cached.uiL1CacheKB = std::max(cached.uiL1CacheKB, uiSizeKB); + else if (info.Cache.Level == 2) + cached.uiL2CacheKB = std::max(cached.uiL2CacheKB, uiSizeKB); + else if (info.Cache.Level == 3) + cached.uiL3CacheKB = std::max(cached.uiL3CacheKB, uiSizeKB); + } + } + cached.uiCores = uiCores != 0 ? uiCores : cached.uiThreads; + } + } + + if (cached.uiThreads == 0) + cached.uiThreads = 1; + if (cached.uiCores == 0) + cached.uiCores = cached.uiThreads; + + bReady = true; + outInfo = cached; +} + #endif // MTA_CLIENT diff --git a/Shared/sdk/net/Packets.h b/Shared/sdk/net/Packets.h index 5331cc54667..96eb88e5d40 100644 --- a/Shared/sdk/net/Packets.h +++ b/Shared/sdk/net/Packets.h @@ -178,6 +178,7 @@ enum ePacketID PACKET_ID_DISCORD_JOIN, PACKET_ID_PLAYER_RESOURCE_START, PACKET_ID_PLAYER_WORLD_SPECIAL_PROPERTY, + PACKET_ID_PLAYER_CPUINFO, // Keep all packet ids above this line so NUM_PACKETS always reflects the // complete range used by packet iteration code.