diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 5c60b50b..c3b8b10a 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -55,6 +55,7 @@ namespace Components Loader::Register(new Discovery()); Loader::Register(new Exception()); Loader::Register(new FastFiles()); + Loader::Register(new FrameTime()); Loader::Register(new Gametypes()); Loader::Register(new Materials()); Loader::Register(new Threading()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 4e5199e0..677244c0 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -67,6 +67,7 @@ namespace Components #include "Modules\Discovery.hpp" #include "Modules\Exception.hpp" #include "Modules\FastFiles.hpp" +#include "Modules\FrameTime.hpp" #include "Modules\Gametypes.hpp" #include "Modules\Materials.hpp" #include "Modules\Singleton.hpp" diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 1980ec79..5ab2f06d 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -9,7 +9,14 @@ namespace Components bool Dedicated::IsEnabled() { - return Flags::HasFlag("dedicated"); + static Utils::Value flag; + + if (!flag.isValid()) + { + flag.set(Flags::HasFlag("dedicated")); + } + + return flag.get(); } void Dedicated::InitDedicatedServer() diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index bc3874ac..dd3a21da 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -20,6 +20,7 @@ namespace Components Var() : dvar(0) {}; Var(const Var &obj) { this->dvar = obj.dvar; }; Var(Game::dvar_t* _dvar) : dvar(_dvar) {}; + Var(DWORD ppdvar) : Var(*reinterpret_cast(ppdvar)) {}; Var(std::string dvarName); template T get(); diff --git a/src/Components/Modules/FrameTime.cpp b/src/Components/Modules/FrameTime.cpp new file mode 100644 index 00000000..cce875c2 --- /dev/null +++ b/src/Components/Modules/FrameTime.cpp @@ -0,0 +1,131 @@ +#include "STDInclude.hpp" + +namespace Components +{ + void FrameTime::NetSleep(int msec) + { + if (msec < 0) msec = 0; + + fd_set fdr; + FD_ZERO(&fdr); + + SOCKET highestfd = INVALID_SOCKET; + if (*Game::ip_socket != INVALID_SOCKET) + { + FD_SET(*Game::ip_socket, &fdr); + highestfd = *Game::ip_socket; + } + + if (highestfd == INVALID_SOCKET) + { + // windows ain't happy when select is called without valid FDs + std::this_thread::sleep_for(std::chrono::milliseconds(msec)); + return; + } + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + int retval = select(highestfd + 1, &fdr, NULL, NULL, &timeout); + + if (retval == SOCKET_ERROR) + { + Logger::Print("Warning: select() syscall failed: %s\n", Game::NET_ErrorString()); + } + else if (retval > 0) + { + // process packets + if (Dvar::Var(0x1AD7934).get()) // com_sv_running + { + Utils::Hook::Call(0x458160)(); + } + else + { + Utils::Hook::Call(0x49F0B0)(); + } + } + } + + void FrameTime::SVFrameWaitFunc() + { + int sv_residualTime = *reinterpret_cast(0x2089E14); + + if (sv_residualTime < 50) + { + FrameTime::NetSleep(50 - sv_residualTime); + } + } + + void __declspec(naked) FrameTime::SVFrameWaitStub() + { + __asm + { + pushad + call FrameTime::SVFrameWaitFunc + popad + + push 4CD420h + retn + } + } + + int FrameTime::ComTimeVal(int minMsec) + { + int timeVal = Game::Sys_Milliseconds() - *reinterpret_cast(0x1AD8F3C); // com_frameTime + return (timeVal >= minMsec ? 0 : minMsec - timeVal); + } + + uint32_t FrameTime::ComFrameWait(int minMsec) + { + int timeVal; + + do + { + timeVal = FrameTime::ComTimeVal(minMsec); + FrameTime::NetSleep(timeVal < 1 ? 0 : timeVal - 1); + } while (FrameTime::ComTimeVal(minMsec)); + + uint32_t lastTime = *Game::com_frameTime; + Utils::Hook::Call(0x43D140)(); // Com_EventLoop + *Game::com_frameTime = Game::Sys_Milliseconds(); + + return *Game::com_frameTime - lastTime; + } + + void __declspec(naked) FrameTime::ComFrameWaitStub() + { + __asm + { + push ecx + pushad + + push edi + call FrameTime::ComFrameWait + add esp, 4 + + mov[esp + 20h], eax + popad + pop eax + mov ecx, eax + + mov edx, 1AD7934h // com_sv_running + cmp byte ptr[edx + 10h], 0 + + push 47DDC1h + retn + } + } + + FrameTime::FrameTime() + { + if (Dedicated::IsEnabled()) + { + Utils::Hook(0x4BAAAD, FrameTime::SVFrameWaitStub, HOOK_CALL).install()->quick(); + } + else + { + Utils::Hook(0x47DD80, FrameTime::ComFrameWaitStub, HOOK_JUMP).install()->quick(); + } + } +} diff --git a/src/Components/Modules/FrameTime.hpp b/src/Components/Modules/FrameTime.hpp new file mode 100644 index 00000000..b622c9b0 --- /dev/null +++ b/src/Components/Modules/FrameTime.hpp @@ -0,0 +1,22 @@ +namespace Components +{ + class FrameTime : public Component + { + public: + FrameTime(); + +#if defined(DEBUG) || defined(FORCE_UNIT_TESTS) + const char* getName() { return "FrameTime"; }; +#endif + + private: + static void SVFrameWaitStub(); + static void SVFrameWaitFunc(); + + static void NetSleep(int msec); + + static int ComTimeVal(int minMsec); + static uint32_t ComFrameWait(int minMsec); + static void ComFrameWaitStub(); + }; +} diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 983c63a8..6fc708b4 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -630,7 +630,14 @@ namespace Components bool ZoneBuilder::IsEnabled() { - return (Flags::HasFlag("zonebuilder") && !Dedicated::IsEnabled()); + static Utils::Value flag; + + if (!flag.isValid()) + { + flag.set(Flags::HasFlag("zonebuilder")); + } + + return (flag.get() && !Dedicated::IsEnabled()); } void ZoneBuilder::BeginAssetTrace(std::string zone) diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index b59c0a3b..5e253ff8 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -154,6 +154,7 @@ namespace Game NET_AdrToString_t NET_AdrToString = (NET_AdrToString_t)0x469880; NET_CompareAdr_t NET_CompareAdr = (NET_CompareAdr_t)0x4D0AA0; + NET_ErrorString_t NET_ErrorString = (NET_ErrorString_t)0x4E7720; NET_Init_t NET_Init = (NET_Init_t)0x491860; NET_IsLocalAddress_t NET_IsLocalAddress = (NET_IsLocalAddress_t)0x402BD0; NET_StringToAdr_t NET_StringToAdr = (NET_StringToAdr_t)0x409010; @@ -304,6 +305,8 @@ namespace Game SOCKET* ip_socket = (SOCKET*)0x64A3008; + uint32_t* com_frameTime = (uint32_t*)0x1AD8F3C; + SafeArea* safeArea = (SafeArea*)0xA15F3C; SpawnVar* spawnVars = (SpawnVar*)0x1A83DE8; diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 43e63a1b..fbdfc485 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -380,6 +380,9 @@ namespace Game typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b); extern NET_CompareAdr_t NET_CompareAdr; + typedef const char* (__cdecl * NET_ErrorString_t)(); + extern NET_ErrorString_t NET_ErrorString; + typedef void(__cdecl * NET_Init_t)(); extern NET_Init_t NET_Init; @@ -646,6 +649,8 @@ namespace Game extern netadr_t* connectedHost; extern SOCKET* ip_socket; + extern uint32_t* com_frameTime; + extern SafeArea* safeArea; extern SpawnVar* spawnVars; diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index f8ba53af..c2dde954 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -52,4 +52,32 @@ namespace Utils private: std::vector> slots; }; + + template + class Value + { + public: + Value() : hasValue(false) {} + Value(T _value) { this->set(_value); } + + void set(T _value) + { + this->value = _value; + this->hasValue = true; + } + + bool isValid() + { + return this->hasValue; + } + + T get() + { + return this->value; + } + + private: + bool hasValue; + T value; + }; }