diff --git a/sql/anticheat.sql b/sql/anticheat.sql new file mode 100644 index 00000000000..a7326738446 --- /dev/null +++ b/sql/anticheat.sql @@ -0,0 +1,30 @@ +DROP TABLE IF EXISTS `players_reports_status`; + +CREATE TABLE `players_reports_status` ( + `guid` int(10) unsigned NOT NULL DEFAULT '0', + `creation_time` int(10) unsigned NOT NULL DEFAULT '0', + `average` float NOT NULL DEFAULT '0', + `total_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `speed_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `fly_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `jump_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `waterwalk_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `teleportplane_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `climb_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''; + +DROP TABLE IF EXISTS `daily_players_reports`; +CREATE TABLE `daily_players_reports` ( + `guid` int(10) unsigned NOT NULL DEFAULT '0', + `creation_time` int(10) unsigned NOT NULL DEFAULT '0', + `average` float NOT NULL DEFAULT '0', + `total_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `speed_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `fly_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `jump_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `waterwalk_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `teleportplane_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + `climb_reports` bigint(20) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''; diff --git a/src/server/game/Anticheat/AnticheatData.cpp b/src/server/game/Anticheat/AnticheatData.cpp new file mode 100644 index 00000000000..0a614739666 --- /dev/null +++ b/src/server/game/Anticheat/AnticheatData.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AnticheatData.h" + +AnticheatData::AnticheatData() +{ + lastOpcode = 0; + totalReports = 0; + for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) + { + typeReports[i] = 0; + tempReports[i] = 0; + tempReportsTimer[i] = 0; + } + average = 0; + creationTime = 0; + hasDailyReport = false; +} + +AnticheatData::~AnticheatData() +{ +} + +void AnticheatData::SetDailyReportState(bool b) +{ + hasDailyReport = b; +} + +bool AnticheatData::GetDailyReportState() +{ + return hasDailyReport; +} + +void AnticheatData::SetLastOpcode(uint32 opcode) +{ + lastOpcode = opcode; +} + +void AnticheatData::SetPosition(float x, float y, float z, float o) +{ + lastMovementInfo.pos = { x, y, z, o }; +} + +uint32 AnticheatData::GetLastOpcode() const +{ + return lastOpcode; +} + +const MovementInfo& AnticheatData::GetLastMovementInfo() const +{ + return lastMovementInfo; +} + +void AnticheatData::SetLastMovementInfo(MovementInfo& moveInfo) +{ + lastMovementInfo = moveInfo; +} + +uint32 AnticheatData::GetTotalReports() const +{ + return totalReports; +} + +void AnticheatData::SetTotalReports(uint32 _totalReports) +{ + totalReports = _totalReports; +} + +void AnticheatData::SetTypeReports(uint32 type, uint32 amount) +{ + typeReports[type] = amount; +} + +uint32 AnticheatData::GetTypeReports(uint32 type) const +{ + return typeReports[type]; +} + +float AnticheatData::GetAverage() const +{ + return average; +} + +void AnticheatData::SetAverage(float _average) +{ + average = _average; +} + +uint32 AnticheatData::GetCreationTime() const +{ + return creationTime; +} + +void AnticheatData::SetCreationTime(uint32 _creationTime) +{ + creationTime = _creationTime; +} + +void AnticheatData::SetTempReports(uint32 amount, uint8 type) +{ + tempReports[type] = amount; +} + +uint32 AnticheatData::GetTempReports(uint8 type) +{ + return tempReports[type]; +} + +void AnticheatData::SetTempReportsTimer(uint32 time, uint8 type) +{ + tempReportsTimer[type] = time; +} + +uint32 AnticheatData::GetTempReportsTimer(uint8 type) +{ + return tempReportsTimer[type]; +} diff --git a/src/server/game/Anticheat/AnticheatData.h b/src/server/game/Anticheat/AnticheatData.h new file mode 100644 index 00000000000..876c0543bea --- /dev/null +++ b/src/server/game/Anticheat/AnticheatData.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef SC_ACDATA_H +#define SC_ACDATA_H + +#include "AnticheatMgr.h" +#include "Player.h" + +#define MAX_REPORT_TYPES 6 + +class AnticheatData +{ +public: + AnticheatData(); + ~AnticheatData(); + + void SetLastOpcode(uint32 opcode); + uint32 GetLastOpcode() const; + + const MovementInfo& GetLastMovementInfo() const; + void SetLastMovementInfo(MovementInfo& moveInfo); + + void SetPosition(float x, float y, float z, float o); + + uint32 GetTotalReports() const; + void SetTotalReports(uint32 _totalReports); + + uint32 GetTypeReports(uint32 type) const; + void SetTypeReports(uint32 type, uint32 amount); + + float GetAverage() const; + void SetAverage(float _average); + + uint32 GetCreationTime() const; + void SetCreationTime(uint32 creationTime); + + void SetTempReports(uint32 amount, uint8 type); + uint32 GetTempReports(uint8 type); + + void SetTempReportsTimer(uint32 time, uint8 type); + uint32 GetTempReportsTimer(uint8 type); + + void SetDailyReportState(bool b); + bool GetDailyReportState(); +private: + uint32 lastOpcode; + MovementInfo lastMovementInfo; + //bool disableACCheck; + //uint32 disableACCheckTimer; + uint32 totalReports; + uint32 typeReports[MAX_REPORT_TYPES]; + float average; + uint32 creationTime; + uint32 tempReports[MAX_REPORT_TYPES]; + uint32 tempReportsTimer[MAX_REPORT_TYPES]; + bool hasDailyReport; +}; + +#endif diff --git a/src/server/game/Anticheat/AnticheatMgr.cpp b/src/server/game/Anticheat/AnticheatMgr.cpp new file mode 100644 index 00000000000..50b4763ad8b --- /dev/null +++ b/src/server/game/Anticheat/AnticheatMgr.cpp @@ -0,0 +1,438 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AnticheatMgr.h" +#include "AnticheatScripts.h" +#include "DatabaseEnv.h" +#include "Log.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "World.h" + +#define CLIMB_ANGLE 1.9f + +AnticheatMgr::AnticheatMgr() +{ +} + +AnticheatMgr::~AnticheatMgr() +{ + m_Players.clear(); +} + +void AnticheatMgr::JumpHackDetection(Player* player, MovementInfo /* movementInfo */,uint32 opcode) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & JUMP_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + + if (m_Players[key].GetLastOpcode() == MSG_MOVE_JUMP && opcode == MSG_MOVE_JUMP) + { + BuildReport(player,JUMP_HACK_REPORT); + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Jump-Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + } +} + +void AnticheatMgr::WalkOnWaterHackDetection(Player* player, MovementInfo /* movementInfo */) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & WALK_WATER_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + if (!m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_WATERWALKING)) + return; + + // if we are a ghost we can walk on water + if (!player->IsAlive()) + return; + + if (player->HasAuraType(SPELL_AURA_FEATHER_FALL) || + player->HasAuraType(SPELL_AURA_SAFE_FALL) || + player->HasAuraType(SPELL_AURA_WATER_WALK)) + return; + + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Walk on Water - Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + BuildReport(player,WALK_WATER_HACK_REPORT); + +} + +void AnticheatMgr::FlyHackDetection(Player* player, MovementInfo /* movementInfo */) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & FLY_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + if (!m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_FLYING)) + return; + + if (player->HasAuraType(SPELL_AURA_FLY) || + player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || + player->HasAuraType(SPELL_AURA_MOD_INCREASE_FLIGHT_SPEED)) + return; + + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Fly-Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + BuildReport(player,FLY_HACK_REPORT); +} + +void AnticheatMgr::TeleportPlaneHackDetection(Player* player, MovementInfo movementInfo) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & TELEPORT_PLANE_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + + if (m_Players[key].GetLastMovementInfo().pos.GetPositionZ() != 0 || + movementInfo.pos.GetPositionZ() != 0) + return; + + if (movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING)) + return; + + float x, y, z; + player->GetPosition(x, y, z); + float ground_Z = player->GetMap()->GetHeight(x, y, z); + float z_diff = fabs(ground_Z - z); + + // we are not really walking there + if (z_diff > 1.0f) + { + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Teleport To Plane - Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + BuildReport(player,TELEPORT_PLANE_HACK_REPORT); + } +} + +void AnticheatMgr::StartHackDetection(Player* player, MovementInfo movementInfo, uint32 opcode) +{ + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + return; + + if (player->IsGameMaster()) + return; + + uint32 key = player->GetGUID().GetCounter(); + + if (player->IsInFlight() || player->GetTransport() || player->GetVehicle()) + { + m_Players[key].SetLastMovementInfo(movementInfo); + m_Players[key].SetLastOpcode(opcode); + return; + } + + SpeedHackDetection(player,movementInfo); + FlyHackDetection(player,movementInfo); + WalkOnWaterHackDetection(player,movementInfo); + JumpHackDetection(player,movementInfo,opcode); + TeleportPlaneHackDetection(player, movementInfo); + ClimbHackDetection(player,movementInfo,opcode); + + m_Players[key].SetLastMovementInfo(movementInfo); + m_Players[key].SetLastOpcode(opcode); +} + +// basic detection +void AnticheatMgr::ClimbHackDetection(Player *player, MovementInfo movementInfo, uint32 opcode) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & CLIMB_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + + if (opcode != MSG_MOVE_HEARTBEAT || + m_Players[key].GetLastOpcode() != MSG_MOVE_HEARTBEAT) + return; + + // in this case we don't care if they are "legal" flags, they are handled in another parts of the Anticheat Manager. + if (player->IsInWater() || + player->IsFlying() || + player->IsFalling()) + return; + + Position playerPos; + + float deltaZ = fabs(playerPos.GetPositionZ() - movementInfo.pos.GetPositionZ()); + float deltaXY = movementInfo.pos.GetExactDist2d(&playerPos); + + float angle = Position::NormalizeOrientation(tan(deltaZ/deltaXY)); + + if (angle > CLIMB_ANGLE) + { + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Climb-Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + BuildReport(player,CLIMB_HACK_REPORT); + } +} + +void AnticheatMgr::SpeedHackDetection(Player* player,MovementInfo movementInfo) +{ + if ((sWorld->getIntConfig(CONFIG_ANTICHEAT_DETECTIONS_ENABLED) & SPEED_HACK_DETECTION) == 0) + return; + + uint32 key = player->GetGUID().GetCounter(); + + // We also must check the map because the movementFlag can be modified by the client. + // If we just check the flag, they could always add that flag and always skip the speed hacking detection. + // 369 == DEEPRUN TRAM + if (m_Players[key].GetLastMovementInfo().HasMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && player->GetMapId() == 369) + return; + + uint32 distance2D = (uint32)movementInfo.pos.GetExactDist2d(&m_Players[key].GetLastMovementInfo().pos); + uint8 moveType = 0; + + // we need to know HOW is the player moving + // TO-DO: Should we check the incoming movement flags? + if (player->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING)) + moveType = MOVE_SWIM; + else if (player->IsFlying()) + moveType = MOVE_FLIGHT; + else if (player->HasUnitMovementFlag(MOVEMENTFLAG_WALKING)) + moveType = MOVE_WALK; + else + moveType = MOVE_RUN; + + // how many yards the player can do in one sec. + uint32 speedRate = (uint32)(player->GetSpeed(UnitMoveType(moveType)) + movementInfo.jump.xyspeed); + + // how long the player took to move to here. + uint32 timeDiff = getMSTimeDiff(m_Players[key].GetLastMovementInfo().time,movementInfo.time); + + if (!timeDiff) + timeDiff = 1; + + // this is the distance doable by the player in 1 sec, using the time done to move to this point. + uint32 clientSpeedRate = distance2D * 1000 / timeDiff; + + // we did the (uint32) cast to accept a margin of tolerance + if (clientSpeedRate > speedRate) + { + BuildReport(player,SPEED_HACK_REPORT); + TC_LOG_DEBUG("entities.player.character", "AnticheatMgr:: Speed-Hack detected player GUID %s",player->GetGUID().ToString().c_str()); + } +} + +void AnticheatMgr::StartScripts() +{ + new AnticheatScripts(); +} + +void AnticheatMgr::HandlePlayerLogin(Player* player) +{ + // we must delete this to prevent errors in case of crash + CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u",player->GetGUID().GetCounter()); + // we initialize the pos of lastMovementPosition var. + m_Players[player->GetGUID().GetCounter()].SetPosition(player->GetPositionX(),player->GetPositionY(),player->GetPositionZ(),player->GetOrientation()); + QueryResult resultDB = CharacterDatabase.PQuery("SELECT * FROM daily_players_reports WHERE guid=%u;",player->GetGUID().GetCounter()); + + if (resultDB) + m_Players[player->GetGUID().GetCounter()].SetDailyReportState(true); +} + +void AnticheatMgr::HandlePlayerLogout(Player* player) +{ + // TO-DO Make a table that stores the cheaters of the day, with more detailed information. + + // We must also delete it at logout to prevent have data of offline players in the db when we query the database (IE: The GM Command) + CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u",player->GetGUID().GetCounter()); + // Delete not needed data from the memory. + m_Players.erase(player->GetGUID().GetCounter()); +} + +void AnticheatMgr::SavePlayerData(Player* player) +{ + CharacterDatabase.PExecute("REPLACE INTO players_reports_status (guid,average,total_reports,speed_reports,fly_reports,jump_reports,waterwalk_reports,teleportplane_reports,climb_reports,creation_time) VALUES (%u,%f,%u,%u,%u,%u,%u,%u,%u,%u);",player->GetGUID().GetCounter(),m_Players[player->GetGUID().GetCounter()].GetAverage(),m_Players[player->GetGUID().GetCounter()].GetTotalReports(), m_Players[player->GetGUID().GetCounter()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetCreationTime()); +} + +uint32 AnticheatMgr::GetTotalReports(uint32 lowGUID) +{ + return m_Players[lowGUID].GetTotalReports(); +} + +float AnticheatMgr::GetAverage(uint32 lowGUID) +{ + return m_Players[lowGUID].GetAverage(); +} + +uint32 AnticheatMgr::GetTypeReports(uint32 lowGUID, uint8 type) +{ + return m_Players[lowGUID].GetTypeReports(type); +} + +bool AnticheatMgr::MustCheckTempReports(uint8 type) +{ + if (type == JUMP_HACK_REPORT) + return false; + + return true; +} + +void AnticheatMgr::BuildReport(Player* player,uint8 reportType) +{ + uint32 key = player->GetGUID().GetCounter(); + + if (MustCheckTempReports(reportType)) + { + uint32 actualTime = getMSTime(); + + if (!m_Players[key].GetTempReportsTimer(reportType)) + m_Players[key].SetTempReportsTimer(actualTime,reportType); + + if (getMSTimeDiff(m_Players[key].GetTempReportsTimer(reportType),actualTime) < 3000) + { + m_Players[key].SetTempReports(m_Players[key].GetTempReports(reportType)+1,reportType); + + if (m_Players[key].GetTempReports(reportType) < 3) + return; + } else + { + m_Players[key].SetTempReportsTimer(actualTime,reportType); + m_Players[key].SetTempReports(1,reportType); + return; + } + } + + // generating creationTime for average calculation + if (!m_Players[key].GetTotalReports()) + m_Players[key].SetCreationTime(getMSTime()); + + // increasing total_reports + m_Players[key].SetTotalReports(m_Players[key].GetTotalReports()+1); + // increasing specific cheat report + m_Players[key].SetTypeReports(reportType,m_Players[key].GetTypeReports(reportType)+1); + + // diff time for average calculation + uint32 diffTime = getMSTimeDiff(m_Players[key].GetCreationTime(),getMSTime()) / IN_MILLISECONDS; + + if (diffTime > 0) + { + // Average == Reports per second + float average = float(m_Players[key].GetTotalReports()) / float(diffTime); + m_Players[key].SetAverage(average); + } + + if (sWorld->getIntConfig(CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT) < m_Players[key].GetTotalReports()) + { + if (!m_Players[key].GetDailyReportState()) + { + CharacterDatabase.PExecute("REPLACE INTO daily_players_reports (guid,average,total_reports,speed_reports,fly_reports,jump_reports,waterwalk_reports,teleportplane_reports,climb_reports,creation_time) VALUES (%u,%f,%u,%u,%u,%u,%u,%u,%u,%u);",player->GetGUID().GetCounter(),m_Players[player->GetGUID().GetCounter()].GetAverage(),m_Players[player->GetGUID().GetCounter()].GetTotalReports(), m_Players[player->GetGUID().GetCounter()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUID().GetCounter()].GetCreationTime()); + m_Players[key].SetDailyReportState(true); + } + } + + if (m_Players[key].GetTotalReports() > sWorld->getIntConfig(CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION)) + { + // display warning at the center of the screen, hacky way? + std::string str = ""; + str = "|cFFFFFC00[AC]|cFF00FFFF[|cFF60FF00" + std::string(player->GetName().c_str()) + "|cFF00FFFF] Possible cheater!"; + WorldPacket data(SMSG_NOTIFICATION, (str.size()+1)); + data << str; + sWorld->SendGlobalGMMessage(&data); + WorldPacket data2(SMSG_CHAT_SERVER_MESSAGE, (str.size()+1)); + data2 << str; + sWorld->SendGlobalGMMessage(&data2); + } +} + +void AnticheatMgr::AnticheatGlobalCommand(ChatHandler* handler) +{ + // MySQL will sort all for us, anyway this is not the best way we must only save the anticheat data not whole player's data!. + ObjectAccessor::SaveAllPlayers(); + + QueryResult resultDB = CharacterDatabase.Query("SELECT guid,average,total_reports FROM players_reports_status WHERE total_reports != 0 ORDER BY average ASC LIMIT 3;"); + if (!resultDB) + { + handler->PSendSysMessage("No players found."); + return; + } else + { + handler->SendSysMessage("============================="); + handler->PSendSysMessage("Players with the lowest averages:"); + do + { + Field *fieldsDB = resultDB->Fetch(); + + uint32 guid = fieldsDB[0].GetUInt32(); + float average = fieldsDB[1].GetFloat(); + uint32 total_reports = fieldsDB[2].GetUInt32(); + + if (Player* player = ObjectAccessor::FindPlayerByLowGUID(guid)) + handler->PSendSysMessage("Player: %s Average: %f Total Reports: %u",player->GetName().c_str(),average,total_reports); + + } while (resultDB->NextRow()); + } + + resultDB = CharacterDatabase.Query("SELECT guid,average,total_reports FROM players_reports_status WHERE total_reports != 0 ORDER BY total_reports DESC LIMIT 3;"); + + // this should never happen + if (!resultDB) + { + handler->PSendSysMessage("No players found."); + return; + } else + { + handler->SendSysMessage("============================="); + handler->PSendSysMessage("Players with the more reports:"); + do + { + Field *fieldsDB = resultDB->Fetch(); + + uint32 guid = fieldsDB[0].GetUInt32(); + float average = fieldsDB[1].GetFloat(); + uint32 total_reports = fieldsDB[2].GetUInt32(); + + if (Player* player = ObjectAccessor::FindPlayerByLowGUID(guid)) + handler->PSendSysMessage("Player: %s Total Reports: %u Average: %f",player->GetName().c_str(),total_reports,average); + + } while (resultDB->NextRow()); + } +} + +void AnticheatMgr::AnticheatDeleteCommand(uint32 guid) +{ + if (!guid) + { + for (AnticheatPlayersDataMap::iterator it = m_Players.begin(); it != m_Players.end(); ++it) + { + (*it).second.SetTotalReports(0); + (*it).second.SetAverage(0); + (*it).second.SetCreationTime(0); + for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) + { + (*it).second.SetTempReports(0,i); + (*it).second.SetTempReportsTimer(0,i); + (*it).second.SetTypeReports(i,0); + } + } + CharacterDatabase.PExecute("DELETE FROM players_reports_status;"); + } + else + { + m_Players[guid].SetTotalReports(0); + m_Players[guid].SetAverage(0); + m_Players[guid].SetCreationTime(0); + for (uint8 i = 0; i < MAX_REPORT_TYPES; i++) + { + m_Players[guid].SetTempReports(0,i); + m_Players[guid].SetTempReportsTimer(0,i); + m_Players[guid].SetTypeReports(i,0); + } + CharacterDatabase.PExecute("DELETE FROM players_reports_status WHERE guid=%u;",guid); + } +} + +void AnticheatMgr::ResetDailyReportStates() +{ + for (AnticheatPlayersDataMap::iterator it = m_Players.begin(); it != m_Players.end(); ++it) + m_Players[(*it).first].SetDailyReportState(false); +} diff --git a/src/server/game/Anticheat/AnticheatMgr.h b/src/server/game/Anticheat/AnticheatMgr.h new file mode 100644 index 00000000000..5f5a2064e5c --- /dev/null +++ b/src/server/game/Anticheat/AnticheatMgr.h @@ -0,0 +1,102 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef SC_ACMGR_H +#define SC_ACMGR_H + +#include "Common.h" +#include "SharedDefines.h" +#include "ScriptMgr.h" +#include "AnticheatData.h" +#include "Chat.h" +#include "Player.h" + +class Player; +class AnticheatData; + +enum ReportTypes +{ + SPEED_HACK_REPORT = 0, + FLY_HACK_REPORT, + WALK_WATER_HACK_REPORT, + JUMP_HACK_REPORT, + TELEPORT_PLANE_HACK_REPORT, + CLIMB_HACK_REPORT, + + // MAX_REPORT_TYPES +}; + +enum DetectionTypes +{ + SPEED_HACK_DETECTION = 1, + FLY_HACK_DETECTION = 2, + WALK_WATER_HACK_DETECTION = 4, + JUMP_HACK_DETECTION = 8, + TELEPORT_PLANE_HACK_DETECTION = 16, + CLIMB_HACK_DETECTION = 32 +}; + +// GUIDLow is the key. +typedef std::map AnticheatPlayersDataMap; + +class TC_GAME_API AnticheatMgr +{ + AnticheatMgr(); + ~AnticheatMgr(); + + public: + static AnticheatMgr* instance() + { + static AnticheatMgr* instance = new AnticheatMgr(); + return instance; + } + + void StartHackDetection(Player* player, MovementInfo movementInfo, uint32 opcode); + void DeletePlayerReport(Player* player, bool login); + void DeletePlayerData(Player* player); + void CreatePlayerData(Player* player); + void SavePlayerData(Player* player); + + void StartScripts(); + + void HandlePlayerLogin(Player* player); + void HandlePlayerLogout(Player* player); + + uint32 GetTotalReports(uint32 lowGUID); + float GetAverage(uint32 lowGUID); + uint32 GetTypeReports(uint32 lowGUID, uint8 type); + + void AnticheatGlobalCommand(ChatHandler* handler); + void AnticheatDeleteCommand(uint32 guid); + + void ResetDailyReportStates(); + private: + void SpeedHackDetection(Player* player, MovementInfo movementInfo); + void FlyHackDetection(Player* player, MovementInfo movementInfo); + void WalkOnWaterHackDetection(Player* player, MovementInfo movementInfo); + void JumpHackDetection(Player* player, MovementInfo movementInfo,uint32 opcode); + void TeleportPlaneHackDetection(Player* player, MovementInfo); + void ClimbHackDetection(Player* player,MovementInfo movementInfo,uint32 opcode); + + void BuildReport(Player* player,uint8 reportType); + + bool MustCheckTempReports(uint8 type); + + AnticheatPlayersDataMap m_Players; ///< Player data +}; + +#define sAnticheatMgr AnticheatMgr::instance() + +#endif diff --git a/src/server/game/Anticheat/AnticheatScripts.cpp b/src/server/game/Anticheat/AnticheatScripts.cpp new file mode 100644 index 00000000000..538db2c8eb7 --- /dev/null +++ b/src/server/game/Anticheat/AnticheatScripts.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "AnticheatScripts.h" +#include "AnticheatMgr.h" + +AnticheatScripts::AnticheatScripts(): PlayerScript("AnticheatScripts") {} + +void AnticheatScripts::OnLogout(Player* player) +{ + sAnticheatMgr->HandlePlayerLogout(player); +} + +void AnticheatScripts::OnLogin(Player* player,bool) +{ + sAnticheatMgr->HandlePlayerLogin(player); +} diff --git a/src/server/game/Anticheat/AnticheatScripts.h b/src/server/game/Anticheat/AnticheatScripts.h new file mode 100644 index 00000000000..1e6aa764db1 --- /dev/null +++ b/src/server/game/Anticheat/AnticheatScripts.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008-2018 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef SC_ACSCRIPTS_H +#define SC_ACSCRIPTS_H + +#include "ScriptMgr.h" + +class AnticheatScripts: public PlayerScript +{ + public: + AnticheatScripts(); + + void OnLogout(Player* player); + void OnLogin(Player* player,bool); +}; + +#endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index bffd5163ae8..bcae4b5e513 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -29,6 +29,7 @@ #include "Player.h" #include "AccountMgr.h" #include "AchievementMgr.h" +#include "AnticheatMgr.h" #include "ArenaTeam.h" #include "ArenaTeamMgr.h" #include "Bag.h" @@ -19599,6 +19600,12 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create /* = false if (m_session->isLogingOut() || !sWorld->getBoolConfig(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT)) _SaveStats(trans); + // we save the data here to prevent spamming + sAnticheatMgr->SavePlayerData(this); + + // in this way we prevent to spam the db by each report made! + // sAnticheatMgr->SavePlayerData(this); + // save pet (hunter pet level and experience and all type pets health/mana). if (Pet* pet = GetPet()) pet->SavePetToDB(PET_SAVE_AS_CURRENT); diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index bad6e220c1a..42e22258d7b 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "AnticheatMgr.h" #include "Battleground.h" #include "Common.h" #include "Corpse.h" @@ -372,6 +373,8 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData) if (opcode == MSG_MOVE_FALL_LAND || opcode == MSG_MOVE_START_SWIM) mover->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_LANDING); // Parachutes + if (plrMover) + sAnticheatMgr->StartHackDetection(plrMover, movementInfo, opcode); /* process position-change */ WorldPacket data(opcode, recvData.size()); int64 movementTime = (int64) movementInfo.time + _timeSyncClockDelta; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 2d8882dcaec..e5af0140d14 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -28,6 +28,7 @@ #include "AuctionHouseMgr.h" #include "BattlefieldMgr.h" #include "BattlegroundMgr.h" +#include "AnticheatMgr.h" #include "CalendarMgr.h" #include "ChannelMgr.h" #include "CharacterCache.h" @@ -1483,6 +1484,11 @@ void World::LoadConfigSettings(bool reload) m_bool_configs[CONFIG_PDUMP_NO_PATHS] = sConfigMgr->GetBoolDefault("PlayerDump.DisallowPaths", true); m_bool_configs[CONFIG_PDUMP_NO_OVERWRITE] = sConfigMgr->GetBoolDefault("PlayerDump.DisallowOverwrite", true); + m_bool_configs[CONFIG_ANTICHEAT_ENABLE] = sConfigMgr->GetBoolDefault("Anticheat.Enable", true); + m_int_configs[CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION] = sConfigMgr->GetIntDefault("Anticheat.ReportsForIngameWarnings", 70); + m_int_configs[CONFIG_ANTICHEAT_DETECTIONS_ENABLED] = sConfigMgr->GetIntDefault("Anticheat.DetectionsEnabled",31); + m_int_configs[CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT] = sConfigMgr->GetIntDefault("Anticheat.MaxReportsForDailyReport",70); + // Wintergrasp battlefield m_bool_configs[CONFIG_WINTERGRASP_ENABLE] = sConfigMgr->GetBoolDefault("Wintergrasp.Enable", false); m_int_configs[CONFIG_WINTERGRASP_PLR_MAX] = sConfigMgr->GetIntDefault("Wintergrasp.PlayerMax", 100); @@ -3251,6 +3257,8 @@ void World::ResetDailyQuests() // reselect pools sQuestPoolMgr->ChangeDailyQuests(); + sAnticheatMgr->ResetDailyReportStates(); + // store next reset time time_t now = GameTime::GetGameTime(); time_t next = GetNextDailyResetTime(now); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index eeda8e69709..b7c9ff5027a 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -147,6 +147,7 @@ enum WorldBoolConfigs CONFIG_DELETE_CHARACTER_TICKET_TRACE, CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, CONFIG_PRESERVE_CUSTOM_CHANNELS, + CONFIG_ANTICHEAT_ENABLE, CONFIG_PDUMP_NO_PATHS, CONFIG_PDUMP_NO_OVERWRITE, CONFIG_QUEST_IGNORE_AUTO_ACCEPT, @@ -349,7 +350,10 @@ enum WorldIntConfigs CONFIG_PRESERVE_CUSTOM_CHANNEL_INTERVAL, CONFIG_PERSISTENT_CHARACTER_CLEAN_FLAGS, CONFIG_LFG_OPTIONSMASK, + CONFIG_ANTICHEAT_REPORTS_INGAME_NOTIFICATION, + CONFIG_ANTICHEAT_MAX_REPORTS_FOR_DAILY_REPORT, CONFIG_MAX_INSTANCES_PER_HOUR, + CONFIG_ANTICHEAT_DETECTIONS_ENABLED, CONFIG_XP_BOOST_DAYMASK, CONFIG_WARDEN_CLIENT_RESPONSE_DELAY, CONFIG_WARDEN_CLIENT_CHECK_HOLDOFF, diff --git a/src/server/scripts/Commands/cs_anticheat.cpp b/src/server/scripts/Commands/cs_anticheat.cpp new file mode 100644 index 00000000000..264062d599c --- /dev/null +++ b/src/server/scripts/Commands/cs_anticheat.cpp @@ -0,0 +1,176 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Language.h" +#include "ScriptMgr.h" +#include "ObjectMgr.h" +#include "ObjectAccessor.h" +#include "Chat.h" +#include "AnticheatMgr.h" +#include "Player.h" +#include "World.h" +#include "WorldSession.h" + +using namespace Trinity::ChatCommands; + +class anticheat_commandscript : public CommandScript +{ +public: + anticheat_commandscript() : CommandScript("anticheat_commandscript") { } + + ChatCommandTable GetCommands() const override + { + static ChatCommandTable anticheatCommandTable = + { + { "global", HandleAntiCheatGlobalCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, + { "player", HandleAntiCheatPlayerCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, + { "delete", HandleAntiCheatDeleteCommand, rbac::RBAC_ROLE_ADMINISTRATOR, Console::Yes }, + { "handle", HandleAntiCheatHandleCommand, rbac::RBAC_ROLE_ADMINISTRATOR, Console::Yes }, + { "jail", HandleAnticheatJailCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, + { "warn", HandleAnticheatWarnCommand, rbac::RBAC_ROLE_GAMEMASTER, Console::Yes }, + }; + + static ChatCommandTable commandTable = + { + { "anticheat", anticheatCommandTable }, + }; + + return commandTable; + } + + static bool HandleAnticheatWarnCommand(ChatHandler* handler, Optional player) + { + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + return false; + + if (!player) + player = PlayerIdentifier::FromTarget(handler); + if (!player || !player->IsConnected()) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler->SetSentErrorMessage(true); + return false; + } + + ChatHandler(player->GetConnectedPlayer()->GetSession()).SendSysMessage("The anticheat system has reported several times that you may be cheating. You will be monitored to confirm if this is accurate."); + return true; + } + + static bool HandleAnticheatJailCommand(ChatHandler* handler, Optional player) + { + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + return false; + + if (!player) + player = PlayerIdentifier::FromTarget(handler); + if (!player || !player->IsConnected()) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler->SetSentErrorMessage(true); + return false; + } + + Player* pTarget = player->GetConnectedPlayer(); + + // teleport both to jail. + WorldLocation jail {1, 16226.5f, 16403.6f, -64.5f, 3.2f}; + pTarget->TeleportTo(jail); + handler->GetPlayer()->TeleportTo(jail); + pTarget->SetHomebind(jail, 876); + + return true; + } + + static bool HandleAntiCheatDeleteCommand(ChatHandler* /*handler*/, Variant command) + { + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + return false; + + if (command.holds_alternative()) + sAnticheatMgr->AnticheatDeleteCommand(0); + else + sAnticheatMgr->AnticheatDeleteCommand(command.get().GetGUID().GetCounter()); + + return true; + } + + static bool HandleAntiCheatPlayerCommand(ChatHandler* handler, Optional player) + { + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + return false; + + if (!player) + player = PlayerIdentifier::FromTarget(handler); + if (!player) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler->SetSentErrorMessage(true); + return false; + } + + uint32 guid = player->GetGUID().GetCounter(); + + float average = sAnticheatMgr->GetAverage(guid); + uint32 total_reports = sAnticheatMgr->GetTotalReports(guid); + uint32 speed_reports = sAnticheatMgr->GetTypeReports(guid,0); + uint32 fly_reports = sAnticheatMgr->GetTypeReports(guid,1); + uint32 jump_reports = sAnticheatMgr->GetTypeReports(guid,3); + uint32 waterwalk_reports = sAnticheatMgr->GetTypeReports(guid,2); + uint32 teleportplane_reports = sAnticheatMgr->GetTypeReports(guid,4); + uint32 climb_reports = sAnticheatMgr->GetTypeReports(guid,5); + + handler->PSendSysMessage("Information about player %s",player->GetName().c_str()); + handler->PSendSysMessage("Average: %f || Total Reports: %u ",average,total_reports); + handler->PSendSysMessage("Speed Reports: %u || Fly Reports: %u || Jump Reports: %u ",speed_reports,fly_reports,jump_reports); + handler->PSendSysMessage("Walk On Water Reports: %u || Teleport To Plane Reports: %u",waterwalk_reports,teleportplane_reports); + handler->PSendSysMessage("Climb Reports: %u", climb_reports); + + return true; + } + + static bool HandleAntiCheatHandleCommand(ChatHandler* handler, bool enable) + { + if (enable) + { + sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,true); + handler->SendSysMessage("The Anticheat System is now: Enabled!"); + } + else + { + sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,false); + handler->SendSysMessage("The Anticheat System is now: Disabled!"); + } + + return true; + } + + static bool HandleAntiCheatGlobalCommand(ChatHandler* handler) + { + if (!sWorld->getBoolConfig(CONFIG_ANTICHEAT_ENABLE)) + { + handler->PSendSysMessage("The Anticheat System is disabled."); + return true; + } + + sAnticheatMgr->AnticheatGlobalCommand(handler); + + return true; + } +}; + +void AddSC_anticheat_commandscript() +{ + new anticheat_commandscript(); +} diff --git a/src/server/scripts/Commands/cs_script_loader.cpp b/src/server/scripts/Commands/cs_script_loader.cpp index dd8fb48f22f..34e4e2bb846 100644 --- a/src/server/scripts/Commands/cs_script_loader.cpp +++ b/src/server/scripts/Commands/cs_script_loader.cpp @@ -15,10 +15,14 @@ * with this program. If not, see . */ +#include "World.h" +#include "AnticheatMgr.h" + // This is where scripts' loading functions should be declared: void AddSC_account_commandscript(); void AddSC_achievement_commandscript(); void AddSC_ahbot_commandscript(); +void AddSC_anticheat_commandscript(); void AddSC_arena_commandscript(); void AddSC_ban_commandscript(); void AddSC_bf_commandscript(); @@ -64,6 +68,7 @@ void AddCommandsScripts() AddSC_account_commandscript(); AddSC_achievement_commandscript(); AddSC_ahbot_commandscript(); + AddSC_anticheat_commandscript(); AddSC_arena_commandscript(); AddSC_ban_commandscript(); AddSC_bf_commandscript(); @@ -101,4 +106,5 @@ void AddCommandsScripts() AddSC_ticket_commandscript(); AddSC_titles_commandscript(); AddSC_wp_commandscript(); + sAnticheatMgr->StartScripts(); } diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index f475d0248e8..805c82f71c1 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -3293,6 +3293,40 @@ LevelReq.Auction = 1 LevelReq.Mail = 1 +# +# Anticheat.Enable +# Description: Enables or disables the Anticheat System functionality +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Anticheat.Enable = 1 + +# Anticheat.ReportsForIngameWarnings +# Description: How many reports the player must have to notify to GameMasters ingame when he generates a new report. +# Default: 70 + +Anticheat.ReportsForIngameWarnings = 70 + +# Anticheat.DetectionsEnabled +# Description: It represents which detections are enabled. +# +# SPEED_HACK_DETECTION = 1 +# FLY_HACK_DETECTION = 2 +# WALK_WATER_HACK_DETECTION = 4 +# JUMP_HACK_DETECTION = 8 +# TELEPORT_PLANE_HACK_DETECTION = 16 +# CLIMB_HACK_DETECTION = 32 +# +# Default: 31 + +Anticheat.DetectionsEnabled = 31 + +# Anticheat.MaxReportsForDailyReport +# Description: How many reports must the player have to make a report that it is in DB for a day (not only during the player's session). +# Default: 70 + +Anticheat.MaxReportsForDailyReport = 70 + # # PlayerDump.DisallowPaths # Description: Disallow using paths in PlayerDump output files