diff --git a/r5dev/common/global.cpp b/r5dev/common/global.cpp index 0dc4456a..672be1e9 100644 --- a/r5dev/common/global.cpp +++ b/r5dev/common/global.cpp @@ -241,6 +241,8 @@ ConVar* con_suggest_showflags = nullptr; ConVar* origin_disconnectWhenOffline = nullptr; ConVar* discord_updatePresence = nullptr; +ConVar* eula_version = nullptr; +ConVar* eula_version_accepted = nullptr; ConVar* settings_reflex = nullptr; ConVar* serverbrowser_hideEmptyServers = nullptr; @@ -560,6 +562,8 @@ void ConVar_InitShipped(void) #ifndef DEDICATED origin_disconnectWhenOffline = g_pCVar->FindVar("origin_disconnectWhenOffline"); discord_updatePresence = g_pCVar->FindVar("discord_updatePresence"); + eula_version = g_pCVar->FindVar("eula_version"); + eula_version_accepted = g_pCVar->FindVar("eula_version_accepted"); #endif // !DEDICATED mp_gamemode = g_pCVar->FindVar("mp_gamemode"); ip_cvar = g_pCVar->FindVar("ip"); diff --git a/r5dev/common/global.h b/r5dev/common/global.h index 586eef16..4ce1cc3e 100644 --- a/r5dev/common/global.h +++ b/r5dev/common/global.h @@ -233,6 +233,8 @@ extern ConVar* con_suggest_showflags; extern ConVar* origin_disconnectWhenOffline; extern ConVar* discord_updatePresence; +extern ConVar* eula_version; +extern ConVar* eula_version_accepted; #endif // !DEDICATED //------------------------------------------------------------------------- // FILESYSTEM | diff --git a/r5dev/core/init.cpp b/r5dev/core/init.cpp index 87aa86bb..74f3f88d 100644 --- a/r5dev/core/init.cpp +++ b/r5dev/core/init.cpp @@ -46,6 +46,7 @@ #include "vgui/vgui_baseui_interface.h" #include "vgui/vgui_debugpanel.h" #include "vgui/vgui_fpspanel.h" +#include "vgui/vgui_controls/RichText.h" #include "vguimatsurface/MatSystemSurface.h" #include "engine/client/vengineclient_impl.h" #include "engine/client/cdll_engine_int.h" @@ -515,6 +516,7 @@ void DetourRegister() // Register detour classes to be searched and hooked. // VGui REGISTER(VEngineVGui); // REGISTER CLIENT ONLY! REGISTER(VFPSPanel); // REGISTER CLIENT ONLY! + REGISTER(VVGUIRichText); // REGISTER CLIENT ONLY! REGISTER(VMatSystemSurface); // Client diff --git a/r5dev/game/client/vscript_client.cpp b/r5dev/game/client/vscript_client.cpp index e514e717..531c1ed9 100644 --- a/r5dev/game/client/vscript_client.cpp +++ b/r5dev/game/client/vscript_client.cpp @@ -1,4 +1,4 @@ -//=============================================================================// + //=============================================================================// // // Purpose: Expose native code to VScript API // @@ -290,6 +290,25 @@ namespace VScriptCode return SQ_OK; } + SQRESULT GetEULAContents(HSQUIRRELVM v) + { + MSEulaData_t eulaData; + if (g_pMasterServer->GetEULA(eulaData)) + { + // set EULA version cvar to the newly fetched EULA version + eula_version->SetValue(eulaData.version); + + sq_pushstring(v, eulaData.contents.c_str(), -1); + } + else + { + Warning(eDLL_T::UI, "Failed to load EULA Data\n"); + sq_pushstring(v, "Failed to load EULA Data", -1); + } + + return SQ_OK; + } + //----------------------------------------------------------------------------- // Purpose: connect to server from native server browser entries //----------------------------------------------------------------------------- @@ -400,6 +419,7 @@ void Script_RegisterUIFunctions(CSquirrelVM* s) // Misc main menu functions DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetPromoData, "Gets promo data for specified slot type", "string", "int"); + DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetEULAContents, "Gets EULA contents from masterserver", "string", ""); // Functions for connecting to servers DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, ConnectToServer, "Joins server by ip address and encryption key", "void", "string, string"); diff --git a/r5dev/networksystem/pylon.cpp b/r5dev/networksystem/pylon.cpp index 41ee01db..1df3196e 100644 --- a/r5dev/networksystem/pylon.cpp +++ b/r5dev/networksystem/pylon.cpp @@ -117,6 +117,7 @@ bool CPylon::GetServerByToken(NetGameServer_t& outGameServer, requestJson.SetObject(); rapidjson::Document::AllocatorType& allocator = requestJson.GetAllocator(); + requestJson.AddMember("version", rapidjson::Value(SDK_VERSION, requestJson.GetAllocator()), allocator); requestJson.AddMember("token", rapidjson::Value(token.c_str(), requestJson.GetAllocator()), allocator); rapidjson::Document responseJson; @@ -340,6 +341,60 @@ bool CPylon::AuthForConnection(const uint64_t nucleusId, const char* ipAddress, return false; } +static bool ValidateEULAData(const rapidjson::Document& doc) +{ + if (!doc.HasMember("data") || !doc["data"].IsObject()) + return false; + + const rapidjson::Value& data = doc["data"]; + + if (!data.HasMember("version") || !data["version"].IsInt()) + return false; + + if (!data.HasMember("lang") || !data["lang"].IsString()) + return false; + + if (!data.HasMember("contents") || !data["contents"].IsString()) + return false; + + return true; +} + +static bool IsEULAUpToDate() +{ + return (eula_version_accepted->GetInt() == eula_version->GetInt()); +} + +bool CPylon::GetEULA(MSEulaData_t& outData) const +{ + rapidjson::Document requestJson; + requestJson.SetObject(); + + rapidjson::Document responseJson; + + CURLINFO status; + + string outMessage; + + if (!SendRequest("/eula", requestJson, responseJson, outMessage, status, "eula fetch error", false)) + { + return false; + } + + if (!ValidateEULAData(responseJson)) + { + return false; + } + + const rapidjson::Value& data = responseJson["data"]; + + outData.version = data["version"].GetInt(); + outData.language = data["lang"].GetString(); + outData.contents = data["contents"].GetString(); + + return true; +} + //----------------------------------------------------------------------------- // Purpose: Sends request to Pylon Master Server. // Input : *endpoint - @@ -347,11 +402,19 @@ bool CPylon::AuthForConnection(const uint64_t nucleusId, const char* ipAddress, // &responseJson - // &outMessage - // &status - +// checkEula - // Output : True on success, false on failure. //----------------------------------------------------------------------------- bool CPylon::SendRequest(const char* endpoint, const rapidjson::Document& requestJson, - rapidjson::Document& responseJson, string& outMessage, CURLINFO& status, const char* errorText) const + rapidjson::Document& responseJson, string& outMessage, CURLINFO& status, + const char* errorText, const bool checkEula) const { + if (checkEula && !IsEULAUpToDate()) + { + outMessage = "EULA not accepted"; + return false; + } + rapidjson::StringBuffer stringBuffer; JSON_DocumentToBufferDeserialize(requestJson, stringBuffer); diff --git a/r5dev/networksystem/pylon.h b/r5dev/networksystem/pylon.h index c1d5f878..18f19cbe 100644 --- a/r5dev/networksystem/pylon.h +++ b/r5dev/networksystem/pylon.h @@ -4,6 +4,13 @@ #include "serverlisting.h" #include "localize/ilocalize.h" +struct MSEulaData_t +{ + int version; + string language; + string contents; +}; + class CPylon { public: @@ -18,11 +25,13 @@ public: bool AuthForConnection(const uint64_t nucleusId, const char* ipAddress, const char* authCode, string& outToken, string& outMessage) const; + bool GetEULA(MSEulaData_t& outData) const; + void ExtractError(const rapidjson::Document& resultBody, string& outMessage, CURLINFO status, const char* errorText = nullptr) const; void ExtractError(const string& response, string& outMessage, CURLINFO status, const char* messageText = nullptr) const; void LogBody(const rapidjson::Document& responseJson) const; - bool SendRequest(const char* endpoint, const rapidjson::Document& requestJson, rapidjson::Document& responseJson, string& outMessage, CURLINFO& status, const char* errorText = nullptr) const; + bool SendRequest(const char* endpoint, const rapidjson::Document& requestJson, rapidjson::Document& responseJson, string& outMessage, CURLINFO& status, const char* errorText = nullptr, const bool checkEula = true) const; bool QueryServer(const char* endpoint, const char* request, string& outResponse, string& outMessage, CURLINFO& outStatus) const; inline const string& GetCurrentToken() const { return m_Token; } diff --git a/r5dev/vgui/CMakeLists.txt b/r5dev/vgui/CMakeLists.txt index 00467da4..17f8d546 100644 --- a/r5dev/vgui/CMakeLists.txt +++ b/r5dev/vgui/CMakeLists.txt @@ -16,4 +16,9 @@ add_sources( SOURCE_GROUP "Debug" "vgui_fpspanel.h" ) +add_sources( SOURCE_GROUP "vgui_controls" + "vgui_controls/RichText.cpp" + "vgui_controls/RichText.h" +) + end_sources() diff --git a/r5dev/vgui/vgui_controls/RichText.cpp b/r5dev/vgui/vgui_controls/RichText.cpp new file mode 100644 index 00000000..9b49a98f --- /dev/null +++ b/r5dev/vgui/vgui_controls/RichText.cpp @@ -0,0 +1,56 @@ +//===========================================================================// +// +// Purpose: Implements all the functions exported by the GameUI dll. +// +// $NoKeywords: $ +//===========================================================================// + +#include +#include +#include +#include +#include + +void RichText_SetText(vgui::RichText* thisptr, const char* text) +{ + thisptr->SetText(text); +} + +void vgui::RichText::SetText(const char* text) +{ + // Originally 4096, increased to 8192 + WCHAR unicode[VGUI_RICHTEXT_MAX_LEN]; + + if (text && *text) + { + if (text[0] == '#') + { + this->__vftable->ResolveLocalizedTextAndVariables(this, text, unicode, sizeof(unicode)); + this->__vftable->SetText(this, unicode); + } + else + { + unicode[0] = 0; + MultiByteToWideChar(CP_UTF8, 0, text, -1, unicode, VGUI_RICHTEXT_MAX_LEN); + unicode[VGUI_RICHTEXT_MAX_LEN - 1] = 0; + this->__vftable->SetText(this, unicode); + } + } + else + { + this->__vftable->SetText(this, NULL); + } +} + +/////////////////////////////////////////////////////////////////////////////// +void VVGUIRichText::Attach() const +{ + DetourAttach((LPVOID*)&vgui_RichText_SetText, &RichText_SetText); +} + +void VVGUIRichText::Detach() const +{ + DetourDetach((LPVOID*)&vgui_RichText_SetText, &RichText_SetText); +} + +/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/r5dev/vgui/vgui_controls/RichText.h b/r5dev/vgui/vgui_controls/RichText.h new file mode 100644 index 00000000..1c9ddecf --- /dev/null +++ b/r5dev/vgui/vgui_controls/RichText.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +#define VGUI_RICHTEXT_MAX_LEN 8192 + +namespace vgui +{ + class RichText; + + struct vgui_RichText_vtbl + { + void* funcs_0[239]; + __int64(__fastcall* SetText)(vgui::RichText*, WCHAR*); + void* funcs_780[43]; + __int64(__fastcall* ResolveLocalizedTextAndVariables)(vgui::RichText*, const CHAR*, WCHAR*, __int64); + }; + + class RichText + { + public: + vgui_RichText_vtbl* __vftable; + + public: + void SetText(const char* text); + }; +}; + +/* ==== RICHTEXT ===================================================================================================================================================== */ +inline CMemory p_vgui_RichText_SetText; +inline void(*vgui_RichText_SetText)(vgui::RichText* thisptr, const char* text); + +/////////////////////////////////////////////////////////////////////////////// +class VVGUIRichText : public IDetour +{ + virtual void GetAdr(void) const + { + LogFunAdr("vgui::RichText::SetText", p_vgui_RichText_SetText.GetPtr()); + } + virtual void GetFun(void) const + { + p_vgui_RichText_SetText = g_GameDll.FindPatternSIMD("40 53 B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B D9"); + vgui_RichText_SetText = p_vgui_RichText_SetText.RCast(); + } + virtual void GetVar(void) const { } + virtual void GetCon(void) const { } + virtual void Attach(void) const; + virtual void Detach(void) const; +}; +///////////////////////////////////////////////////////////////////////////////