Skip to content

Commit

Permalink
Don't trust the local machine's system time.
Browse files Browse the repository at this point in the history
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!
  • Loading branch information
Hoikas committed Aug 9, 2023
1 parent 8cfd633 commit 5b4cba1
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 49 deletions.
8 changes: 0 additions & 8 deletions Sources/Plasma/Apps/plClient/win32/winmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
58 changes: 22 additions & 36 deletions Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

//
Expand Down
7 changes: 3 additions & 4 deletions Sources/Plasma/PubUtilLib/plNetClient/plNetClientMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 5b4cba1

Please sign in to comment.