From 5b4cba1f9d3fd019194d5234a241f1a1acef9ab1 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 7 Aug 2023 19:47:30 -0400 Subject: [PATCH] Don't trust the local machine's system time. Previously, when asking for the server's time, the calculation was done by basically offsetting the local system time by some previously calculated delta. This was basically useless because the user can easily change the system time out from under us and cause huge deltas. The attempt to fix things in #173 by using `WM_TIMECHANGE` was a good idea but ultimately incorrect because `hsTimer` is a monotonic timer. At this point, it uses `std::chrono::steady_clock` as its backend, so attempting to use `hsTimer` to recalculate the server/client time offset is useless. Therefore, this changes us to simply save the last time value that we received from the game server and the monotonic time that we received that update. This is definitely not perfect because we don't consider things like round trip time for the message and time spent in the update loop. However, it *is* a clear improvement in that now the KI will always display what the server thinks Mountain time is, and it should be impossible to perform system clock based exploits (such as instantly baking pellets)... without any platform specific code! --- .../Plasma/Apps/plClient/win32/winmain.cpp | 8 --- .../plNetClient/plNetCliAgeJoiner.cpp | 2 +- .../PubUtilLib/plNetClient/plNetClientMgr.cpp | 58 +++++++------------ .../PubUtilLib/plNetClient/plNetClientMgr.h | 7 +-- 4 files changed, 26 insertions(+), 49 deletions(-) diff --git a/Sources/Plasma/Apps/plClient/win32/winmain.cpp b/Sources/Plasma/Apps/plClient/win32/winmain.cpp index 12079ece04..e9c8df245c 100644 --- a/Sources/Plasma/Apps/plClient/win32/winmain.cpp +++ b/Sources/Plasma/Apps/plClient/win32/winmain.cpp @@ -207,14 +207,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) // Handle messages switch (message) { - case WM_TIMECHANGE: - // To prevent cheating and keep things better synchronized, - // we will completely re-eval the offsets on the next NetMsg we - // get from the server - if (plNetClientMgr* nc = plNetClientMgr::GetInstance()) - nc->ResetServerTimeOffset(true); - break; - case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_LBUTTONDBLCLK: diff --git a/Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp b/Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp index e5791f7ace..24434103c0 100644 --- a/Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp +++ b/Sources/Plasma/PubUtilLib/plNetClient/plNetCliAgeJoiner.cpp @@ -212,7 +212,7 @@ void plNCAgeJoiner::Start () { plNetClientMgr * nc = plNetClientMgr::GetInstance(); nc->SetFlagsBit(plNetClientMgr::kPlayingGame, false); - nc->fServerTimeOffset = 0; // reset since we're connecting to a new server + nc->ResetServerTimeOffset(); nc->fRequiredNumInitialSDLStates = 0; nc->fNumInitialSDLStates = 0; nc->SetFlagsBit(plNetClientApp::kNeedInitialAgeStateCount); diff --git a/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp b/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp index 3c416dc305..04ee04361c 100644 --- a/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp +++ b/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp @@ -102,10 +102,10 @@ plNetClientMgr::PendingLoad::~PendingLoad() // plNetClientMgr::plNetClientMgr() : fLocalPlayerKey(), fMsgHandler(this), fJoinOrder(), fTaskProgBar(), - fMsgRecorder(), fServerTimeOffset(), fTimeSamples(), fLastTimeUpdate(), - fListenListMode(kListenList_Distance), fAgeSDLObjectKey(), fExperimentalLevel(), - fOverrideAgeTimeOfDayPercent(-1.f), fNumInitialSDLStates(), fRequiredNumInitialSDLStates(), - fDisableMsg(), fIsOwner(true), fIniPlayerID(), fPingServerType() + fMsgRecorder(), fLastLocalTime(), fListenListMode(kListenList_Distance), + fAgeSDLObjectKey(), fExperimentalLevel(), fOverrideAgeTimeOfDayPercent(-1.f), + fNumInitialSDLStates(), fRequiredNumInitialSDLStates(), fDisableMsg(), fIsOwner(true), + fIniPlayerID(), fPingServerType() { #ifndef HS_DEBUGGING // release code will timeout inactive players on servers by default @@ -403,50 +403,36 @@ void plNetClientMgr::IUnloadNPCs() // void plNetClientMgr::UpdateServerTimeOffset(plNetMessage* msg) { - if ((hsTimer::GetSysSeconds() - fLastTimeUpdate) > 5) - { - fLastTimeUpdate = hsTimer::GetSysSeconds(); - - const plUnifiedTime& msgSentUT = msg->GetTimeSent(); - if (!msgSentUT.AtEpoch()) - { - double diff = plUnifiedTime::GetTimeDifference(msgSentUT, plUnifiedTime::GetCurrent()); + if (!msg->GetHasTimeSent()) + return; + if (msg->GetTimeSent().AtEpoch()) + return; - if (fServerTimeOffset == 0) - { - fServerTimeOffset = diff; - } - else - { - fServerTimeOffset = fServerTimeOffset + ((diff - fServerTimeOffset) / ++fTimeSamples); - } + double localTime = hsTimer::GetSeconds(); + if (localTime - fLastLocalTime < 1.0) + return; - DebugMsg("Setting server time offset to {f}", fServerTimeOffset); - } - } + fLastServerTime = msg->GetTimeSent(); + fLastLocalTime = localTime; } -void plNetClientMgr::ResetServerTimeOffset(bool delayed) +void plNetClientMgr::ResetServerTimeOffset() { - if (!delayed) - fServerTimeOffset = 0; - fTimeSamples = 0; - fLastTimeUpdate = 0; + fLastServerTime.ToEpoch(); + fLastLocalTime = 0.0; } // // return the gameservers time // plUnifiedTime plNetClientMgr::GetServerTime() const -{ - if ( fServerTimeOffset==0 ) // offline mode or before connecting/calibrating to a server +{ + if (fLastServerTime.AtEpoch()) { + WarningMsg("WARNING: Someone asked for the server time, but we don't know it yet!"); return plUnifiedTime::GetCurrent(); - - plUnifiedTime serverUT; - if (fServerTimeOffset<0) - return plUnifiedTime::GetCurrent() - plUnifiedTime(fabs(fServerTimeOffset)); - else - return plUnifiedTime::GetCurrent() + plUnifiedTime(fServerTimeOffset); + } + + return fLastServerTime + plUnifiedTime(hsTimer::GetSeconds() - fLastLocalTime); } // diff --git a/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h b/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h index 63ec58f560..66e1a193a4 100644 --- a/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h +++ b/Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h @@ -161,9 +161,8 @@ class plNetClientMgr : public plNetClientApp std::string fSPDesiredPlayerName; // SP: the player we want to load from vault. // server info - double fServerTimeOffset; // diff between our unified time and server's unified time - uint32_t fTimeSamples; - double fLastTimeUpdate; + plUnifiedTime fLastServerTime; // Last received time update from the server + double fLastLocalTime; // Last monotonic time (in seconds) when the above update was received uint8_t fJoinOrder; // returned by the server @@ -379,7 +378,7 @@ class plNetClientMgr : public plNetClientApp void StoreSDLState(const plStateDataRecord* sdRec, const plUoid& uoid, uint32_t sendFlags, uint32_t writeOptions); void UpdateServerTimeOffset(plNetMessage* msg); - void ResetServerTimeOffset(bool delayed=false); + void ResetServerTimeOffset(); private: plNetClientComm fNetClientComm;