feat: Add cheat detection knowledge (#2034)

* feat: Add cheat detection knowledge

tested that getting kicked correctly displays the reasons why and their corresponding values
tested that a user with developer permissions is not kicked for said funness
tested that logs are correct for those funness values which i could actually trigger

* fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
David Markowitz
2026-09-18 02:30:59 -07:00
committed by GitHub
parent 8f2004c9a3
commit 057de71773
4 changed files with 89 additions and 9 deletions

View File

@@ -0,0 +1,26 @@
#ifndef EFUNNESSTYPES_H
#define EFUNNESSTYPES_H
#include <cstdint>
// Most of these are guesses from the client, some are unused (maybe server side only?) and
// some are really hard to pin a definition down for because they are buried in havok stuff
enum class eFunnessTypes : int32_t {
DebuggerActive = 1,
CharacterPosLength = 2,
CharacterVelLength = 5,
CharacterRunMultiplier = 6,
CharacterGravityScale = 7,
Unknown_9 = 9,
Unknown_10 = 10,
RacingBoostTimeTooLong = 12,
SomeRacingManipCheat = 13,
FdbFailedChecksum = 14,
};
struct CaughtFunness {
eFunnessTypes cheatType{};
float cheatInfo{};
};
#endif //!EFUNNESSTYPES_H

View File

@@ -9,6 +9,7 @@
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "MessageType/Chat.h" #include "MessageType/Chat.h"
#include "StringifiedEnum.h"
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
@@ -127,11 +128,16 @@ void User::SetMuteExpire(time_t value) {
m_MuteExpire = value; m_MuteExpire = value;
} }
void User::UserOutOfSync() { void User::UserOutOfSync(const CaughtFunness& funness) {
m_CaughtFunness.push_back(funness);
m_AmountOfTimesOutOfSync++; m_AmountOfTimesOutOfSync++;
if (m_AmountOfTimesOutOfSync > m_MaxDesyncAllowed) { if (m_AmountOfTimesOutOfSync > m_MaxDesyncAllowed) {
//YEET //YEET
LOG("User %s was out of sync %i times out of %i, disconnecting for suspected speedhacking.", m_Username.c_str(), m_AmountOfTimesOutOfSync, m_MaxDesyncAllowed); LOG("User %s was out of sync %i times out of %i, disconnecting for the following reasons: {", m_Username.c_str(), m_AmountOfTimesOutOfSync, m_MaxDesyncAllowed);
for (const auto [cheatType, cheatInfo] : m_CaughtFunness) {
LOG(" Reason %s value %f", StringifiedEnum::ToString(cheatType).data(), cheatInfo);
}
LOG("}");
Game::server->Disconnect(this->m_SystemAddress, eServerDisconnectIdentifiers::PLAY_SCHEDULE_TIME_DONE); Game::server->Disconnect(this->m_SystemAddress, eServerDisconnectIdentifiers::PLAY_SCHEDULE_TIME_DONE);
} }
} }

View File

@@ -6,6 +6,7 @@
#include <chrono> #include <chrono>
#include "RakNetTypes.h" #include "RakNetTypes.h"
#include "dCommonVars.h" #include "dCommonVars.h"
#include "eFunnessTypes.h"
#include <unordered_map> #include <unordered_map>
@@ -55,7 +56,7 @@ public:
// Added for GameMessageHandler // Added for GameMessageHandler
std::unordered_map<uint32_t, BehaviorParams> uiBehaviorHandles; std::unordered_map<uint32_t, BehaviorParams> uiBehaviorHandles;
void UserOutOfSync(); void UserOutOfSync(const CaughtFunness& funness);
private: private:
uint32_t m_AccountID; uint32_t m_AccountID;
@@ -75,6 +76,7 @@ private:
const int m_MaxDesyncAllowed = 12; const int m_MaxDesyncAllowed = 12;
uint64_t m_MuteExpire; uint64_t m_MuteExpire;
std::chrono::steady_clock::time_point m_LastMuteCheck{}; std::chrono::steady_clock::time_point m_LastMuteCheck{};
std::vector<CaughtFunness> m_CaughtFunness{};
}; };
#endif // USER_H #endif // USER_H

View File

@@ -82,6 +82,7 @@
#include "SlashCommandHandler.h" #include "SlashCommandHandler.h"
#include "InventoryComponent.h" #include "InventoryComponent.h"
#include "Item.h" #include "Item.h"
#include "eFunnessTypes.h"
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
@@ -1427,16 +1428,61 @@ void HandlePacket(Packet* packet) {
//Could be insane lag, but I'mma just YEET them as it's usually speedhacking. //Could be insane lag, but I'mma just YEET them as it's usually speedhacking.
//This is updated to now count the amount of times we've been caught "speedhacking" to kick with a delay //This is updated to now count the amount of times we've been caught "speedhacking" to kick with a delay
//This is hopefully going to fix the random disconnects people face sometimes. //This is hopefully going to fix the random disconnects people face sometimes.
if (Game::config->GetValue("disable_anti_speedhack") == "1") {
CINSTREAM_SKIP_HEADER;
CaughtFunness funness;
inStream.Read(funness.cheatInfo);
inStream.Read(funness.cheatType);
const auto [cheatType, cheatInfo] = funness;
LOG_DEBUG("Received cheat type %s with info %f", StringifiedEnum::ToString(cheatType).data(), cheatInfo);
const auto disabledCheats = Game::config->GetValue("disable_anti_speedhack") == "1";
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) {
Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::KICK);
return; return;
} }
User* user = UserManager::Instance()->GetUser(packet->systemAddress); const auto* const character = user->GetLastUsedChar();
if (user) { if (character) {
user->UserOutOfSync(); const auto dcUser = [](const User& user, const SystemAddress& sysAddr) {
} else { if (user.GetMaxGMLevel() < eGameMasterLevel::DEVELOPER) {
Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::KICK); Game::server->Disconnect(sysAddr, eServerDisconnectIdentifiers::KICK);
}
};
if (cheatType == eFunnessTypes::DebuggerActive) {
LOG("Player %s was detected to be using a debugger (funness thread was %f seconds longer than expected deviation).", character->GetName().c_str(), cheatInfo);
// Immediately kick unless they are a gm
if (!disabledCheats) dcUser(*user, packet->systemAddress);
} else if (cheatType == eFunnessTypes::FdbFailedChecksum) {
LOG("Player %s has failed the fdb checksum after passing it to sign into this server, highly likely cheating.", character->GetName().c_str());
// Immedately kick unless they are a gm
if (!disabledCheats) dcUser(*user, packet->systemAddress);
} else if (cheatType == eFunnessTypes::RacingBoostTimeTooLong) {
if (cheatInfo == 0.0f) LOG("Player %s has enabled a speedboost for far too long (> 3.501 seconds).", character->GetName().c_str());
else if (cheatInfo == 1.0f) LOG("Unknown racing boost/speed variable was tampered with.");
else if (cheatInfo == 2.0f) LOG("Player vehicle top speed was tampered with.");
if (!disabledCheats) dcUser(*user, packet->systemAddress);
} else if (cheatType == eFunnessTypes::SomeRacingManipCheat) {
if (cheatInfo >= 0.0f && cheatInfo <= 6.0f) LOG("Cheat RNG value A does not match what it should be %f.", cheatInfo);
else if (cheatInfo >= 7.0f && cheatInfo <= 10.0f) LOG("Cheat RNG value B does not match what it should be %f.", cheatInfo);
} else if (cheatType == eFunnessTypes::Unknown_9) {
LOG("Racing cheat 9 detected with value %f.", cheatInfo);
} else if (cheatType == eFunnessTypes::Unknown_10) {
LOG("Racing cheat 10 detected with value %f.", cheatInfo);
} else if (cheatType == eFunnessTypes::CharacterPosLength) {
LOG("Detected pos length of %f which is greater than the expected value 1.0f, not normal!", cheatInfo);
} else if (cheatType == eFunnessTypes::CharacterVelLength) {
LOG("Detected vel length of %f which is greater than the expected value 1.0f, not normal!", cheatInfo);
} else if (cheatType == eFunnessTypes::CharacterGravityScale) {
LOG("Detected gravity scale difference of %f which is greater than the expected value of 0.0f, not normal!", cheatInfo);
} else if (cheatType == eFunnessTypes::CharacterRunMultiplier) {
LOG("Detected run multiplier difference of %f which is greater than the expected value 0.0f, not normal!", cheatInfo);
}
if (!disabledCheats && user->GetMaxGMLevel() < eGameMasterLevel::DEVELOPER) user->UserOutOfSync(funness);
} }
break; break;
} }