mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
* Plumbing * Batch processing in world * Cleanup * Cleanup * Update player_event_logs.cpp * Add player zoning event * Use generics * Comments * Add events * Add more events * AA_GAIN, AA_PURCHASE, FORAGE_SUCCESS, FORAGE_FAILURE * FISH_SUCCESS, FISH_FAILURE, ITEM_DESTROY * Add charges to ITEM_DESTROY * WENT_ONLINE, WENT_OFFLINE * LEVEL_GAIN, LEVEL_LOSS * LOOT_ITEM * MERCHANT_PURCHASE * MERCHANT_SELL * SKILL_UP * Add events * Add more events * TASK_ACCEPT, TASK_COMPLETE, and TASK_UPDATE * GROUNDSPAWN_PICKUP * SAY * REZ_ACCEPTED * COMBINE_FAILURE and COMBINE_SUCCESS * DROPPED_ITEM * DEATH * SPLIT_MONEY * TRADER_PURCHASE and TRADER_SELL * DISCOVER_ITEM * Convert GM_COMMAND to use new macro * Convert ZONING event to use macro * Revert some code changes * Revert "Revert some code changes" This reverts commit d53682f997e89a053a660761085913245db91e9d. * Add cereal generation support to repositories * TRADE * Formatting * Cleanup * Relocate discord_manager to discord folder * Discord sending plumbing * Rename UCS's Database class to UCSDatabase to be more specific and not collide with base Database class for repository usage * More discord sending plumbing * More discord message formatting work * More discord formatting work * Discord formatting of events * Format WENT_ONLINE, WENT_OFFLINE * Add merchant purchase event * Handle Discord MERCHANT_SELL formatter * Update player_event_discord_formatter.cpp * Tweaks * Implement retention truncation * Put mutex locking on batch queue, put processor on its own thread * Process on initial bootup * Implement optional QS processing, implement keepalive from world to QS * Reload player event settings when logs are reloaded in game * Set settings defaults * Update player_event_logs.cpp * Update player_event_logs.cpp * Set retention days on boot * Update player_event_logs.cpp * Player Handin Event Testing. Testing player handin stuff. * Cleanup. * Finish NPC Handin. * set a reference to the client inside of the trade object as well for plugins to process * Fix for windows _inline * Bump to cpp20 default, ignore excessive warnings on windows * Bump FMT to 6.1.2 for cpp20 compat and swap fmt::join for Strings::Join * Windows compile fixes * Update CMakeLists.txt * Update CMakeLists.txt * Update CMakeLists.txt * Create 2022_12_19_player_events_tables.sql * [Formatters] Work on Discord Formatters * Handin money. * Format header * [Formatters] Work on Discord Formatters * Format * Format * [Formatters] More Formatter work, need to test further. * [Formatters] More Work on Formatters. * Add missing #endif * [Formatters] Work on Formatters, fix Bot formatting in ^create help * NPC Handin Discord Formatter * Update player_event_logs.cpp * Discover Item Discord Formatter * Dropped Item Discord Formatter * Split Money Discord Formatter * Trader Discord Formatters * Cleanup. * Trade Event Discord Formatter Groundwork * SAY don't record GM commands * GM_Command don't record #help * Update player_event_logs.cpp * Fill in more event data * Post rebase fixes * Post rebase fix * Discord formatting adjustments * Add event deprecation or unimplemented tag support * Trade events * Add return money and sanity checks. * Update schema * Update ucs.cpp * Update client.cpp * Update 2022_12_19_player_events_tables.sql * Implement archive single line * Replace hackers table and functions with PossibleHack player event * Replace very old eventlog table since the same events are covered by player event logs * Update bot_command.cpp * Record NPC kill events ALL / Named / Raid * Add BatchEventProcessIntervalSeconds rule * Naming * Update CMakeLists.txt * Update database_schema.h * Remove logging function and methods * DB version * Cleanup SendPlayerHandinEvent --------- Co-authored-by: Kinglykrab <kinglykrab@gmail.com> Co-authored-by: Aeadoin <109764533+Aeadoin@users.noreply.github.com>
364 lines
12 KiB
C++
364 lines
12 KiB
C++
#include "cheat_manager.h"
|
|
#include "client.h"
|
|
#include "quest_parser_collection.h"
|
|
#include "../common/events/player_event_logs.h"
|
|
#include "worldserver.h"
|
|
|
|
extern WorldServer worldserver;
|
|
|
|
void CheatManager::SetClient(Client *cli)
|
|
{
|
|
m_target = cli;
|
|
}
|
|
|
|
void CheatManager::SetExemptStatus(ExemptionType type, bool v)
|
|
{
|
|
if (v) {
|
|
MovementCheck();
|
|
}
|
|
m_exemption[type] = v;
|
|
}
|
|
|
|
bool CheatManager::GetExemptStatus(ExemptionType type)
|
|
{
|
|
return m_exemption[type];
|
|
}
|
|
|
|
void CheatManager::CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3 position2)
|
|
{
|
|
switch (type) {
|
|
case MQWarp:
|
|
if (m_time_since_last_warp_detection.GetRemainingTime() == 0 && RuleB(Cheat, EnableMQWarpDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQWarp (large warp detection) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] to x [{:.2f}] y [{:.2f}] z [{:.2f}] Distance [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z,
|
|
position2.x,
|
|
position2.y,
|
|
position2.z,
|
|
Distance(position1, position2)
|
|
);
|
|
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
|
|
LogCheat(fmt::runtime(message));
|
|
std::string export_string = fmt::format(
|
|
"{} {} {}",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
parse->EventPlayer(EVENT_WARP, m_target, export_string, 0);
|
|
}
|
|
break;
|
|
case MQWarpAbsolute:
|
|
if (RuleB(Cheat, EnableMQWarpDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQWarp (Absolute) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] to x [{:.2f}] y [{:.2f}] z [{:.2f}] Distance [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z,
|
|
position2.x,
|
|
position2.y,
|
|
position2.z,
|
|
Distance(position1, position2)
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
std::string export_string = fmt::format(
|
|
"{} {} {}",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
parse->EventPlayer(EVENT_WARP, m_target, export_string, 0);
|
|
m_time_since_last_warp_detection.Start(2500);
|
|
}
|
|
break;
|
|
case MQWarpShadowStep:
|
|
if (RuleB(Cheat, EnableMQWarpDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQWarp (ShadowStep) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] the target was shadow step exempt but we still found this suspicious.",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
case MQWarpKnockBack:
|
|
if (RuleB(Cheat, EnableMQWarpDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQWarp (Knockback) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] the target was Knock Back exempt but we still found this suspicious.",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
|
|
case MQWarpLight:
|
|
if (RuleB(Cheat, EnableMQWarpDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQWarpExemptStatus) || (RuleI(Cheat, MQWarpExemptStatus)) == -1))) {
|
|
if (RuleB(Cheat, MarkMQWarpLT)) {
|
|
std::string message = fmt::format(
|
|
"/MQWarp(Knockback) with location from x [{:.2f}] y [{:.2f}] z [{:.2f}] running fast but not fast enough to get killed, possibly: small warp, speed hack, excessive lag, marked as suspicious.",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MQZone:
|
|
if (RuleB(Cheat, EnableMQZoneDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQZoneExemptStatus) || (RuleI(Cheat, MQZoneExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQZone used at x [{:.2f}] y [{:.2f}] z [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
case MQZoneUnknownDest:
|
|
if (RuleB(Cheat, EnableMQZoneDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQZoneExemptStatus) || (RuleI(Cheat, MQZoneExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQZone used at x [{:.2f}] y [{:.2f}] z [{:.2f}] with Unknown Destination",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
case MQGate:
|
|
if (RuleB(Cheat, EnableMQGateDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQGateExemptStatus) || (RuleI(Cheat, MQGateExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQGate used at x [{:.2f}] y [{:.2f}] z [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
case MQGhost:
|
|
// this isn't just for ghost, its also for if a person isn't sending their MovementHistory packet also.
|
|
if (RuleB(Cheat, EnableMQGhostDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQGhostExemptStatus) ||
|
|
(RuleI(Cheat, MQGhostExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"[MQGhost] [{}] [{}] was caught not sending the proper packets as regularly as they were suppose to.",
|
|
m_target->AccountName(),
|
|
m_target->GetName()
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat("{}", message);
|
|
}
|
|
break;
|
|
case MQFastMem:
|
|
if (RuleB(Cheat, EnableMQFastMemDetector) &&
|
|
((m_target->Admin() < RuleI(Cheat, MQFastMemExemptStatus) ||
|
|
(RuleI(Cheat, MQFastMemExemptStatus)) == -1))) {
|
|
std::string message = fmt::format(
|
|
"/MQFastMem used at x [{:.2f}] y [{:.2f}] z [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
}
|
|
break;
|
|
default:
|
|
std::string message = fmt::format(
|
|
"Unhandled HackerDetection flag with location from x [{:.2f}] y [{:.2f}] z [{:.2f}]",
|
|
position1.x,
|
|
position1.y,
|
|
position1.z
|
|
);
|
|
RecordPlayerEventLogWithClient(m_target, PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{.message = message});
|
|
LogCheat(fmt::runtime(message));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CheatManager::MovementCheck(glm::vec3 updated_position)
|
|
{
|
|
if (m_time_since_last_movement_history.GetRemainingTime() == 0) {
|
|
CheatDetected(MQGhost, updated_position);
|
|
}
|
|
|
|
float dist = DistanceNoZ(m_target->GetPosition(), updated_position);
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if (dist == 0) {
|
|
if (m_distance_since_last_position_check > 0.0f) {
|
|
MovementCheck(0);
|
|
}
|
|
else {
|
|
m_time_since_last_position_check = cur_time;
|
|
m_cheat_detect_moved = false;
|
|
}
|
|
}
|
|
else {
|
|
m_distance_since_last_position_check += dist;
|
|
m_cheat_detect_moved = true;
|
|
if (m_time_since_last_position_check == 0) {
|
|
m_time_since_last_position_check = cur_time;
|
|
}
|
|
else {
|
|
MovementCheck(2500);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheatManager::MovementCheck(uint32 time_between_checks)
|
|
{
|
|
uint32 cur_time = Timer::GetCurrentTime();
|
|
if ((cur_time - m_time_since_last_position_check) > time_between_checks) {
|
|
float estimated_speed =
|
|
(m_distance_since_last_position_check * 100) / (float) (cur_time - m_time_since_last_position_check);
|
|
|
|
// MQWarpDetection shouldn't go below 1.0f so we can't end up dividing by 0.
|
|
float run_speed = m_target->GetRunspeed() /
|
|
std::min(
|
|
RuleR(Cheat, MQWarpDetectionDistanceFactor),
|
|
1.0f
|
|
);
|
|
if (estimated_speed > run_speed) {
|
|
bool using_gm_speed = m_target->GetGMSpeed();
|
|
bool is_immobile = m_target->GetRunspeed() == 0; // this covers stuns, roots, mez, and pseudorooted.
|
|
if (!using_gm_speed && !is_immobile) {
|
|
if (GetExemptStatus(ShadowStep)) {
|
|
if (m_distance_since_last_position_check > 800) {
|
|
CheatDetected(
|
|
MQWarpShadowStep,
|
|
glm::vec3(
|
|
m_target->GetX(),
|
|
m_target->GetY(),
|
|
m_target->GetZ()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
else if (GetExemptStatus(KnockBack)) {
|
|
if (estimated_speed > 30.0f) {
|
|
CheatDetected(MQWarpKnockBack, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ()));
|
|
}
|
|
}
|
|
else if (!GetExemptStatus(Port)) {
|
|
if (estimated_speed > (run_speed * 1.5)) {
|
|
CheatDetected(MQWarp, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ()));
|
|
m_time_since_last_position_check = cur_time;
|
|
m_distance_since_last_position_check = 0.0f;
|
|
}
|
|
else {
|
|
CheatDetected(MQWarpLight, glm::vec3(m_target->GetX(), m_target->GetY(), m_target->GetZ()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (time_between_checks != 1000) {
|
|
SetExemptStatus(ShadowStep, false);
|
|
SetExemptStatus(KnockBack, false);
|
|
SetExemptStatus(Port, false);
|
|
}
|
|
m_time_since_last_position_check = cur_time;
|
|
m_distance_since_last_position_check = 0.0f;
|
|
}
|
|
}
|
|
|
|
void CheatManager::CheckMemTimer()
|
|
{
|
|
if (m_target == nullptr) {
|
|
return;
|
|
}
|
|
if (m_time_since_last_memorization - Timer::GetCurrentTime() <= 1) {
|
|
glm::vec3 pos = m_target->GetPosition();
|
|
CheatDetected(MQFastMem, pos);
|
|
}
|
|
m_time_since_last_memorization = Timer::GetCurrentTime();
|
|
}
|
|
|
|
void CheatManager::ProcessMovementHistory(const EQApplicationPacket *app)
|
|
{
|
|
// if they haven't sent sent the packet within this time... they are probably spoofing...
|
|
// linux users reported that they don't send this packet at all but i can't prove they don't so i'm not sure if thats a fake or not.
|
|
m_time_since_last_movement_history.Start(70000);
|
|
if (GetExemptStatus(Port)) {
|
|
return;
|
|
}
|
|
auto *m_MovementHistory = (UpdateMovementEntry *) app->pBuffer;
|
|
if (app->size < sizeof(UpdateMovementEntry)) {
|
|
LogDebug(
|
|
"Size mismatch in OP_MovementHistoryList, expected {}, got [{}]",
|
|
sizeof(UpdateMovementEntry),
|
|
app->size
|
|
);
|
|
DumpPacket(app);
|
|
return;
|
|
}
|
|
|
|
for (int index = 0; index < (app->size) / sizeof(UpdateMovementEntry); index++) {
|
|
glm::vec3 to = glm::vec3(m_MovementHistory[index].X, m_MovementHistory[index].Y, m_MovementHistory[index].Z);
|
|
switch (m_MovementHistory[index].type) {
|
|
case UpdateMovementType::ZoneLine:
|
|
SetExemptStatus(Port, true);
|
|
break;
|
|
case UpdateMovementType::TeleportA:
|
|
if (index != 0) {
|
|
glm::vec3 from = glm::vec3(
|
|
m_MovementHistory[index - 1].X,
|
|
m_MovementHistory[index - 1].Y,
|
|
m_MovementHistory[index - 1].Z
|
|
);
|
|
CheatDetected(MQWarpAbsolute, from, to);
|
|
}
|
|
SetExemptStatus(Port, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheatManager::ProcessSpawnApperance(uint16 spawn_id, uint16 type, uint32 parameter)
|
|
{
|
|
if (type == AT_Anim && parameter == ANIM_SIT) {
|
|
m_time_since_last_memorization = Timer::GetCurrentTime();
|
|
}
|
|
else if (spawn_id == 0 && type == AT_AntiCheat) {
|
|
m_time_since_last_action = parameter;
|
|
}
|
|
}
|
|
|
|
void CheatManager::ProcessItemVerifyRequest(int32 slot_id, uint32 target_id)
|
|
{
|
|
if (slot_id == -1 && m_warp_counter != target_id) {
|
|
m_warp_counter = target_id;
|
|
}
|
|
}
|
|
|
|
void CheatManager::ClientProcess()
|
|
{
|
|
if (!m_cheat_detect_moved) {
|
|
m_time_since_last_position_check = Timer::GetCurrentTime();
|
|
}
|
|
}
|