commit 05231e0c2c7efa69f03ce85ded02a46eb4c228c7 Author: 3ndos Date: Mon Dec 5 11:39:52 2016 -0500 Initial upload of AnticheatModule diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..69ae986 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ + +AC_ADD_SCRIPT("${CMAKE_CURRENT_LIST_DIR}/src/PassiveAnticheat.cpp") +AC_ADD_SCRIPT("${CMAKE_CURRENT_LIST_DIR}/src/AnticheatMgr.cpp") +AC_ADD_SCRIPT("${CMAKE_CURRENT_LIST_DIR}/src/AnticheatScripts.cpp") +AC_ADD_SCRIPT("${CMAKE_CURRENT_LIST_DIR}/src/AnticheatData.cpp") +AC_ADD_SCRIPT("${CMAKE_CURRENT_LIST_DIR}/src/cs_anticheat.cpp") + +AC_ADD_SCRIPT_LOADER("PassiveAnticheat" "${CMAKE_CURRENT_LIST_DIR}/src/loader.h") + +CU_ADD_HOOK(AFTER_WORLDSERVER_CMAKE "${CMAKE_CURRENT_LIST_DIR}/src/cmake/after_ws_install.cmake") diff --git a/README.md b/README.md new file mode 100644 index 0000000..690f909 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +Currently this only works with the moreScriptHooks Branch: +https://github.com/3ndos/azerothcore-wotlk/tree/moreScriptHooks +Make sure to support the main project: +https://github.com/azerothcore/azerothcore-wotlk/ +# AnticheatModule +* !!BEFORE RUNNING!!: Execute the included "conf/SQL/charactersdb_anticheat.sql" file on your characters database. This creates a necessary table for this module. +* This is a port of the PassiveAnticheat Script from lordpsyan's repo to azerothcore. diff --git a/conf/Anticheat.conf.dist b/conf/Anticheat.conf.dist new file mode 100644 index 0000000..8eca4d3 --- /dev/null +++ b/conf/Anticheat.conf.dist @@ -0,0 +1,40 @@ +[worldserver] +# +################################################################################################### + +################################################################################################### +# Anticheat.Enable +# Description: Enables or disables the Anticheat System functionality +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Anticheat.Enabled = 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.LoginMessage +# Description: Enable login message "This server is running an Anticheat module." +# Default: 1 + +Anticheat.LoginMessage = 1 + +# Anticheat.Detect +# Description: It represents which detections are enabled. +# Default 1 (climbhack = 0 by default) +Anticheat.DetectFlyHack = 1 +Anticheat.DetectWaterWalk = 1 +Anticheat.DetectJumpHack = 1 +Anticheat.DetectTelePlaneHack = 1 +Anticheat.DetectSpeedHack = 1 +Anticheat.DetectClimbHack = 0 + + +# 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 diff --git a/conf/SQL/charactersdb_anticheat.sql b/conf/SQL/charactersdb_anticheat.sql new file mode 100644 index 0000000..3504594 --- /dev/null +++ b/conf/SQL/charactersdb_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=''; \ No newline at end of file diff --git a/src/AnticheatData.cpp b/src/AnticheatData.cpp new file mode 100644 index 0000000..b0584e5 --- /dev/null +++ b/src/AnticheatData.cpp @@ -0,0 +1,118 @@ +#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/AnticheatData.h b/src/AnticheatData.h new file mode 100644 index 0000000..700ad2d --- /dev/null +++ b/src/AnticheatData.h @@ -0,0 +1,63 @@ +#ifndef SC_ACDATA_H +#define SC_ACDATA_H + +#include "AnticheatMgr.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); + + /* + bool GetDisableACCheck() const; + void SetDisableACCheck(bool check); + + uint32 GetDisableACTimer() const; + void SetDisableACTimer(uint32 timer);*/ + + 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 \ No newline at end of file diff --git a/src/AnticheatMgr.cpp b/src/AnticheatMgr.cpp new file mode 100644 index 0000000..b5808a9 --- /dev/null +++ b/src/AnticheatMgr.cpp @@ -0,0 +1,430 @@ +/* + * 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 "MapManager.h" +#include "Player.h" +#include "Configuration\Config.h" +#define CLIMB_ANGLE 1.9f + +AnticheatMgr::AnticheatMgr() +{ +} + +AnticheatMgr::~AnticheatMgr() +{ + m_Players.clear(); +} + +void AnticheatMgr::JumpHackDetection(Player* player, MovementInfo /* movementInfo */,uint32 opcode) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.DetectJumpHack", true)) + return; + + uint32 key = player->GetGUIDLow(); + + if (m_Players[key].GetLastOpcode() == MSG_MOVE_JUMP && opcode == MSG_MOVE_JUMP) + { + BuildReport(player,JUMP_HACK_REPORT); + sLog->outString( "AnticheatMgr:: Jump-Hack detected player GUID (low) %u",player->GetGUIDLow()); + } +} + +void AnticheatMgr::WalkOnWaterHackDetection(Player* player, MovementInfo /* movementInfo */) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.DetectWaterWalk", true)) + return; + + uint32 key = player->GetGUIDLow(); + 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; + + sLog->outString( "AnticheatMgr:: Walk on Water - Hack detected player GUID (low) %u",player->GetGUIDLow()); + BuildReport(player,WALK_WATER_HACK_REPORT); + +} + +void AnticheatMgr::FlyHackDetection(Player* player, MovementInfo /* movementInfo */) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.DetectFlyHack", true)) + return; + + uint32 key = player->GetGUIDLow(); + 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; + + sLog->outString( "AnticheatMgr:: Fly-Hack detected player GUID (low) %u",player->GetGUIDLow()); + BuildReport(player,FLY_HACK_REPORT); +} + +void AnticheatMgr::TeleportPlaneHackDetection(Player* player, MovementInfo movementInfo) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.DetectTelePlaneHack", true)) + return; + + uint32 key = player->GetGUIDLow(); + + if (m_Players[key].GetLastMovementInfo().pos.GetPositionZ() != 0 || + movementInfo.pos.GetPositionZ() != 0) + return; + + if (movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING)) + return; + + // DEAD_FALLING was deprecated + //if (player->getDeathState() == DEAD_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) + { + sLog->outString( "AnticheatMgr:: Teleport To Plane - Hack detected player GUID (low) %u",player->GetGUIDLow()); + BuildReport(player,TELEPORT_PLANE_HACK_REPORT); + } +} + +void AnticheatMgr::StartHackDetection(Player* player, MovementInfo movementInfo, uint32 opcode) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + return; + + if (player->IsGameMaster()) + return; + + uint32 key = player->GetGUIDLow(); + + 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 (!sConfigMgr->GetBoolDefault("Anticheat.DetectClimbHack", false)) + return; + + uint32 key = player->GetGUIDLow(); + + 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; + // Position pos = player->GetPosition(); + + 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) + { + sLog->outString( "AnticheatMgr:: Climb-Hack detected player GUID (low) %u", player->GetGUIDLow()); + BuildReport(player,CLIMB_HACK_REPORT); + } +} + +void AnticheatMgr::SpeedHackDetection(Player* player,MovementInfo movementInfo) +{ + if (!sConfigMgr->GetBoolDefault("Anticheat.DetectSpeedHack", true)) + return; + + uint32 key = player->GetGUIDLow(); + + // 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); + sLog->outString( "AnticheatMgr:: Speed-Hack detected player GUID (low) %u",player->GetGUIDLow()); + } +} + + +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->GetGUIDLow()); + // we initialize the pos of lastMovementPosition var. + m_Players[player->GetGUIDLow()].SetPosition(player->GetPositionX(),player->GetPositionY(),player->GetPositionZ(),player->GetOrientation()); + QueryResult resultDB = CharacterDatabase.PQuery("SELECT * FROM daily_players_reports WHERE guid=%u;",player->GetGUIDLow()); + + if (resultDB) + m_Players[player->GetGUIDLow()].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->GetGUIDLow()); + // Delete not needed data from the memory. + m_Players.erase(player->GetGUIDLow()); +} + +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->GetGUIDLow(),m_Players[player->GetGUIDLow()].GetAverage(),m_Players[player->GetGUIDLow()].GetTotalReports(), m_Players[player->GetGUIDLow()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUIDLow()].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->GetGUIDLow(); + + 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 ((uint32)sConfigMgr->GetIntDefault("Anticheat.MaxReportsForDailyReport", 70) < 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->GetGUIDLow(),m_Players[player->GetGUIDLow()].GetAverage(),m_Players[player->GetGUIDLow()].GetTotalReports(), m_Players[player->GetGUIDLow()].GetTypeReports(SPEED_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(FLY_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(JUMP_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(WALK_WATER_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(TELEPORT_PLANE_HACK_REPORT),m_Players[player->GetGUIDLow()].GetTypeReports(CLIMB_HACK_REPORT),m_Players[player->GetGUIDLow()].GetCreationTime()); + m_Players[key].SetDailyReportState(true); + } + } + + if (m_Players[key].GetTotalReports() > (uint32)sConfigMgr->GetIntDefault("Anticheat.ReportsForIngameWarnings", 70)) + { + // 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); + } +} + +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 = sObjectMgr->GetPlayerByLowGUID(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->PSendSysMessage("============================="); + 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 = sObjectMgr->GetPlayerByLowGUID(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/AnticheatMgr.h b/src/AnticheatMgr.h new file mode 100644 index 0000000..e8ebb0c --- /dev/null +++ b/src/AnticheatMgr.h @@ -0,0 +1,101 @@ +/* + * 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 +#include "Common.h" +#include "SharedDefines.h" +#include "ScriptMgr.h" +#include "AnticheatData.h" +#include "Chat.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 AnticheatMgr +{ +// friend class ACE_Singleton; + 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 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/AnticheatScripts.cpp b/src/AnticheatScripts.cpp new file mode 100644 index 0000000..b037f76 --- /dev/null +++ b/src/AnticheatScripts.cpp @@ -0,0 +1,93 @@ +#include "Configuration/Config.h" +#include "AnticheatMgr.h" +#include "Object.h" + +int64 resetTime = 0; +int64 lastIterationPlayer = sWorld->GetUptime() + 30;//TODO: change 30 secs static to a configurable option +class AnticheatPlayerScript : public PlayerScript +{ +public: + AnticheatPlayerScript() + : PlayerScript("AnticheatPlayerScript") + { + } + + void OnLogout(Player* player) override + { + sAnticheatMgr->HandlePlayerLogout(player); + } + + void OnLogin(Player* player) override + { + sAnticheatMgr->HandlePlayerLogin(player); + if(sConfigMgr->GetBoolDefault("Anticheat.LoginMessage", true)) + ChatHandler(player->GetSession()).PSendSysMessage("This server is running an Anticheat Module."); + } +}; +class AnticheatWorldScript : public WorldScript +{ +public: + AnticheatWorldScript() + : WorldScript("AnticheatWorldScript") + { + } + void OnUpdate(uint32 diff) override + { + if (sWorld->GetGameTime() > resetTime) + { + sLog->outString( "Anticheat: Resetting daily report states."); + sAnticheatMgr->ResetDailyReportStates(); + UpdateReportResetTime(); + sLog->outString( "Anticheat: Next daily report reset: %u", resetTime); + } + if (sWorld->GetUptime() > lastIterationPlayer) + { + lastIterationPlayer = sWorld->GetUptime() + 30;//TODO: change 30 secs static to a configurable option + sLog->outString( "Saving reports for %u players.", sWorld->GetPlayerCount()); + for (SessionMap::const_iterator itr = sWorld->GetAllSessions().begin(); itr != sWorld->GetAllSessions().end(); ++itr) + if (Player* plr = itr->second->GetPlayer()) + sAnticheatMgr->SavePlayerData(plr); + } + } + void OnBeforeConfigLoad(bool reload) override + { + /* from skeleton module */ + if (!reload) { + std::string conf_path = _CONF_DIR; + std::string cfg_file = conf_path + "/Anticheat.conf"; + if (WIN32) + cfg_file = "Anticheat.conf"; + std::string cfg_def_file = cfg_file + ".dist"; + sConfigMgr->LoadMore(cfg_def_file.c_str()); + + sConfigMgr->LoadMore(cfg_file.c_str()); + } + /* end from skeleton module */ + } + void OnAfterConfigLoad(bool reload) override + { + sLog->outString("AnticheatModule Loaded."); + } + void UpdateReportResetTime() + { + resetTime = sWorld->GetNextTimeWithDayAndHour(-1, 6); + } +}; +class AnticheatMovementHandlerScript : public MovementHandlerScript +{ + public: + AnticheatMovementHandlerScript() + : MovementHandlerScript("AnticheatMovementHandlerScript") + { + } + void AnticheatMovementHandlerScript::OnPlayerMove(Player* player, MovementInfo mi, uint32 opcode) override + { + sAnticheatMgr->StartHackDetection(player, mi, opcode); + } +}; +void startAnticheatScripts() +{ + new AnticheatWorldScript(); + new AnticheatPlayerScript(); + new AnticheatMovementHandlerScript(); +} \ No newline at end of file diff --git a/src/AnticheatScripts.h b/src/AnticheatScripts.h new file mode 100644 index 0000000..088ce9a --- /dev/null +++ b/src/AnticheatScripts.h @@ -0,0 +1 @@ +void startAnticheatScripts(); \ No newline at end of file diff --git a/src/PassiveAnticheat.cpp b/src/PassiveAnticheat.cpp new file mode 100644 index 0000000..f467ae6 --- /dev/null +++ b/src/PassiveAnticheat.cpp @@ -0,0 +1,8 @@ +#include "cs_anticheat.h" +#include "AnticheatScripts.h" + +void AddPassiveAnticheatScripts() +{ + startAnticheatScripts(); + AddSC_anticheat_commandscript(); +} \ No newline at end of file diff --git a/src/cmake/after_ws_install.cmake b/src/cmake/after_ws_install.cmake new file mode 100644 index 0000000..627a56e --- /dev/null +++ b/src/cmake/after_ws_install.cmake @@ -0,0 +1,15 @@ +if( WIN32 ) + if ( MSVC ) + add_custom_command(TARGET worldserver + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/modules/AnticheatModule/conf/Anticheat.conf.dist" ${CMAKE_BINARY_DIR}/bin/$(ConfigurationName)/ + ) + elseif ( MINGW ) + add_custom_command(TARGET worldserver + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/modules/AnticheatModule/conf/Anticheat.conf.dist" ${CMAKE_BINARY_DIR}/bin/ + ) + endif() +endif() + +install(FILES "${CMAKE_SOURCE_DIR}/modules/AnticheatModule/conf/Anticheat.conf.dist" DESTINATION ${CONF_DIR}) diff --git a/src/cs_anticheat.cpp b/src/cs_anticheat.cpp new file mode 100644 index 0000000..b1745f8 --- /dev/null +++ b/src/cs_anticheat.cpp @@ -0,0 +1,263 @@ +/* + * 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 "Chat.h" +#include "AnticheatMgr.h" +#include "Configuration\Config.h" + +class anticheat_commandscript : public CommandScript +{ +public: + anticheat_commandscript() : CommandScript("anticheat_commandscript") { } + + std::vector GetCommands() const override + { + static std::vector anticheatCommandTable = + { + { "global", SEC_GAMEMASTER, true, &HandleAntiCheatGlobalCommand, "" }, + { "player", SEC_GAMEMASTER, true, &HandleAntiCheatPlayerCommand, "" }, + { "delete", SEC_ADMINISTRATOR, true, &HandleAntiCheatDeleteCommand, "" }, + { "handle", SEC_ADMINISTRATOR, true, &HandleAntiCheatHandleCommand, "" }, + { "jail", SEC_GAMEMASTER, true, &HandleAnticheatJailCommand, "" }, + { "warn", SEC_GAMEMASTER, true, &HandleAnticheatWarnCommand, "" }, +// { NULL, 0, false, NULL, "", NULL } + }; + + static std::vector commandTable = + { + { "anticheat", SEC_GAMEMASTER, true, NULL, "", anticheatCommandTable}, +// { NULL, 0, false, NULL, "", NULL } + }; + + return commandTable; + } + + static bool HandleAnticheatWarnCommand(ChatHandler* handler, const char* args) + { + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + return false; + + Player* pTarget = NULL; + + std::string strCommand; + + char* command = strtok((char*)args, " "); + + if (command) + { + strCommand = command; + normalizePlayerName(strCommand); + + pTarget = ObjectAccessor::FindPlayerByName(strCommand.c_str()); // get player by name + }else + pTarget = handler->getSelectedPlayer(); + + if (!pTarget) + return false; + + WorldPacket data; + + // need copy to prevent corruption by strtok call in LineFromMessage original string + char* buf = strdup("The anticheat system has reported several times that you may be cheating. You will be monitored to confirm if this is accurate."); + char* pos = buf; + + while (char* line = handler->LineFromMessage(pos)) + { + handler->BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, NULL, NULL, line); + pTarget->GetSession()->SendPacket(&data); + } + + free(buf); + return true; + } + + static bool HandleAnticheatJailCommand(ChatHandler* handler, const char* args) + { + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + return false; + + Player* pTarget = NULL; + + std::string strCommand; + + char* command = strtok((char*)args, " "); + + if (command) + { + strCommand = command; + normalizePlayerName(strCommand); + + pTarget = ObjectAccessor::FindPlayerByName(strCommand.c_str()); // get player by name + }else + pTarget = handler->getSelectedPlayer(); + + if (!pTarget) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler->SetSentErrorMessage(true); + return false; + } + + if (pTarget == handler->GetSession()->GetPlayer()) + return false; + + // teleport both to jail. + pTarget->TeleportTo(1,16226.5f,16403.6f,-64.5f,3.2f); + handler->GetSession()->GetPlayer()->TeleportTo(1,16226.5f,16403.6f,-64.5f,3.2f); + + + + // the player should be already there, but no :( + // pTarget->GetPosition(&loc); + + WorldLocation loc; + loc = WorldLocation(1, 16226.5f, 16403.6f, -64.5f, 3.2f); + pTarget->SetHomebind(loc, 876); + + + + pTarget->SetHomebind(loc,876); + return true; + } + + static bool HandleAntiCheatDeleteCommand(ChatHandler* handler, const char* args) + { + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + return false; + + std::string strCommand; + + char* command = strtok((char*)args, " "); // get entered name + + if (!command) + return true; + + strCommand = command; + + if (strCommand.compare("deleteall") == 0) + sAnticheatMgr->AnticheatDeleteCommand(0); + else + { + normalizePlayerName(strCommand); + Player* player = ObjectAccessor::FindPlayerByName(strCommand.c_str()); // get player by name + if (!player) + handler->PSendSysMessage("Player doesn't exist"); + else + sAnticheatMgr->AnticheatDeleteCommand(player->GetGUIDLow()); + } + + return true; + } + + static bool HandleAntiCheatPlayerCommand(ChatHandler* handler, const char* args) + { + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + return false; + + std::string strCommand; + + char* command = strtok((char*)args, " "); + + uint32 guid = 0; + Player* player = NULL; + + if (command) + { + strCommand = command; + + normalizePlayerName(strCommand); + player = ObjectAccessor::FindPlayerByName(strCommand.c_str()); // get player by name + + if (player) + guid = player->GetGUIDLow(); + }else + { + player = handler->getSelectedPlayer(); + if (player) + guid = player->GetGUIDLow(); + } + + if (!guid) + { + handler->PSendSysMessage("There is no player."); + return true; + } + + 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, const char* args) + { + /* std::string strCommand; + + char* command = strtok((char*)args, " "); + + if (!command) + return true; + + if (!handler->GetSession()->GetPlayer()) + return true; + + strCommand = command; + + if (strCommand.compare("on") == 0) + { + sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,true); + handler->SendSysMessage("The Anticheat System is now: Enabled!"); + } + else if (strCommand.compare("off") == 0) + { + sWorld->setBoolConfig(CONFIG_ANTICHEAT_ENABLE,false); + handler->SendSysMessage("The Anticheat System is now: Disabled!"); + }*/ + handler->PSendSysMessage("Please change value in config file and reload to disable/enable anticheat system."); + return true; + } + + static bool HandleAntiCheatGlobalCommand(ChatHandler* handler, const char* /* args */) + { + if (!sConfigMgr->GetBoolDefault("Anticheat.Enabled", 0)) + { + 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/cs_anticheat.h b/src/cs_anticheat.h new file mode 100644 index 0000000..33d3cce --- /dev/null +++ b/src/cs_anticheat.h @@ -0,0 +1 @@ +void AddSC_anticheat_commandscript(); \ No newline at end of file diff --git a/src/loader.h b/src/loader.h new file mode 100644 index 0000000..634c701 --- /dev/null +++ b/src/loader.h @@ -0,0 +1 @@ +void AddPassiveAnticheatScripts();