[Cheat Detection] Anti-Cheat reimplementation (#1434)

* [Cheat Detection] Anti-Cheat reimplementation

* minor patch fixes

* ceiling to server side runspeed

Warp(LT) was picking up a bunch of expected 6.2 but it was reported back as 6.5, this should help reduce the amount of false positives we get

* use ceil instead of std::ceilf for linux

* boat false positive fix

* stopping the double detection

* fixes and cleanup

* auto merge tricked me...

* dummy divide by 0 checks

this should prevent anyone from setting Zone:MQWarpDetectionDistanceFactor to 0 and causing a crash.

* Formatting

* encapsulation to its own class and clean up

* more detections

* typo

* OP_UnderWorld implmentation

* Update client_packet.h

* Syntax changes, formatting, cleanup

* preventing crashes due to invalid packet size

* typos and clearer logic

* seperated the catagory for cheats

* Updated MQGhost for more detail

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
Dencelle 2021-08-31 01:08:31 -05:00 committed by GitHub
parent 26299354b6
commit 7b069dcf20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 664 additions and 26 deletions

View File

@ -567,4 +567,5 @@ N(OP_ZoneServerReady),
N(OP_ZoneSpawns),
N(OP_ZoneUnavail),
N(OP_ResetAA),
N(OP_UnderWorld),
// mail and chat opcodes located in ../mail_oplist.h

View File

@ -65,6 +65,7 @@
#define AT_FindBits 46 // set FindBits, whatever those are!
#define AT_TextureType 48 // TextureType
#define AT_FacePick 49 // Turns off face pick window? maybe ...
#define AT_AntiCheat 51 // sent by the client randomly telling the server how long since last action has occured
#define AT_GuildShow 52 // this is what MQ2 call sit, not sure
#define AT_Offline 53 // Offline mode

View File

@ -5530,6 +5530,23 @@ struct SayLinkBodyFrame_Struct {
/*056*/
};
struct UpdateMovementEntry {
/* 00 */ float Y;
/* 04 */ float X;
/* 08 */ float Z;
/* 12 */ uint8 type;
/* 13 */ unsigned int timestamp;
/* 17 */
};
struct UnderWorld {
/* 00 */ int spawn_id;
/* 04 */ float y;
/* 08 */ float x;
/* 12 */ float z;
/* 16 */
};
// Restore structure packing to default
#pragma pack()

View File

@ -129,6 +129,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
log_settings[Logs::HotReload].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::Loot].log_to_gmsay = static_cast<uint8>(Logs::General);
log_settings[Logs::Scheduler].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::Cheat].log_to_console = static_cast<uint8>(Logs::General);
/**
* RFC 5424

View File

@ -122,6 +122,7 @@ namespace Logs {
Expeditions,
DynamicZones,
Scheduler,
Cheat,
MaxCategoryID /* Don't Remove this */
};
@ -202,6 +203,7 @@ namespace Logs {
"Expeditions",
"DynamicZones",
"Scheduler",
"Cheat"
};
}

View File

@ -646,6 +646,16 @@
OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogCheat(message, ...) do {\
if (LogSys.log_settings[Logs::Cheat].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogCheatDetail(message, ...) do {\
if (LogSys.log_settings[Logs::Cheat].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::Cheat, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -762,6 +762,21 @@ RULE_BOOL(DynamicZone, EnableInDynamicZoneStatus, false, "Enables the 'In Dynami
RULE_INT(DynamicZone, WorldProcessRate, 6000, "Timer interval (milliseconds) that systems check their dynamic zone states")
RULE_CATEGORY_END()
RULE_CATEGORY(Cheat)
RULE_REAL(Cheat, MQWarpDetectionDistanceFactor, 9.0, "clients move at 4.4 about if in a straight line but with movement and to acct for lag we raise it a bit")
RULE_INT(Cheat, MQWarpExemptStatus, -1, "Required status level to exempt the MQWarpDetector. Set to -1 to disable this feature.")
RULE_INT(Cheat, MQZoneExemptStatus, -1, "Required status level to exempt the MQZoneDetector. Set to -1 to disable this feature.")
RULE_INT(Cheat, MQGateExemptStatus, -1, "Required status level to exempt the MQGateDetector. Set to -1 to disable this feature.")
RULE_INT(Cheat, MQGhostExemptStatus, -1, "Required status level to exempt the MQGhostDetector. Set to -1 to disable this feature.")
RULE_INT(Cheat, MQFastMemExemptStatus, -1, "Required status level to exempt the MQFastMemDetector. Set to -1 to disable this feature.")
RULE_BOOL(Cheat, EnableMQWarpDetector, true, "Enable the MQWarp Detector. Set to False to disable this feature.")
RULE_BOOL(Cheat, EnableMQZoneDetector, true, "Enable the MQZone Detector. Set to False to disable this feature.")
RULE_BOOL(Cheat, EnableMQGateDetector, true, "Enable the MQGate Detector. Set to False to disable this feature.")
RULE_BOOL(Cheat, EnableMQGhostDetector, true, "Enable the MQGhost Detector. Set to False to disable this feature.")
RULE_BOOL(Cheat, EnableMQFastMemDetector, true, "Enable the MQFastMem Detector. Set to False to disable this feature.")
RULE_BOOL(Cheat, MarkMQWarpLT, false, "Mark clients makeing smaller warps")
RULE_CATEGORY_END()
#undef RULE_CATEGORY
#undef RULE_INT
#undef RULE_REAL

View File

@ -14,6 +14,7 @@ SET(zone_sources
bot_command.cpp
bot_database.cpp
botspellsai.cpp
cheat_manager.cpp
client.cpp
client_mods.cpp
client_packet.cpp
@ -157,7 +158,8 @@ SET(zone_sources
zone_event_scheduler.cpp
zone_reload.cpp
zone_store.cpp
zoning.cpp)
zoning.cpp
)
SET(zone_headers
aa.h
@ -171,6 +173,7 @@ SET(zone_headers
bot_command.h
bot_database.h
bot_structs.h
cheat_manager.h
client.h
client_packet.h
command.h
@ -274,7 +277,8 @@ SET(zone_headers
zonedb.h
zonedump.h
zone_reload.h
zone_store.h)
zone_store.h
)
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})

386
zone/cheat_manager.cpp Normal file
View File

@ -0,0 +1,386 @@
#include "cheat_manager.h"
#include "client.h"
#include "quest_parser_collection.h"
void CheatManager::SetClient(Client *cli)
{
m_target = cli;
}
void CheatManager::SetExemptStatus(ExemptionType type, bool v)
{
if (v == true) {
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)
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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)
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName());
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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))) {
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
"Packet blocking detected.",
zone->GetShortName());
LogCheat("{} was caught not sending the proper packets as regularly as they were suppose to.");
}
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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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
);
database.SetMQDetectionFlag(
m_target->AccountName(),
m_target->GetName(),
message.c_str(),
zone->GetShortName()
);
LogCheat(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);
float run_speed = m_target->GetRunspeed() /
std::min(RuleR(Cheat, MQWarpDetectionDistanceFactor), 1.0f); // MQWarpDetection shouldn't go below 1.0f so we can't end up dividing by 0.
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();
}
}

88
zone/cheat_manager.h Normal file
View File

@ -0,0 +1,88 @@
#ifndef ANTICHEAT_H
#define ANTICHEAT_H
class CheatManager;
class Client;
#include "../common/timer.h"
#include "../common/rulesys.h"
#include <glm/ext/vector_float3.hpp>
#include "../common/eq_packet_structs.h"
#include "../common/eq_packet.h"
typedef enum {
Collision = 1,
TeleportB,
TeleportA,
ZoneLine,
Unknown0x5,
Unknown0x6,
SpellA, // Titanium - UF
Unknown0x8,
SpellB // Used in RoF+
} UpdateMovementType;
typedef enum {
ShadowStep,
KnockBack,
Port,
Assist,
Sense,
MAX_EXEMPTIONS
} ExemptionType;
typedef enum {
MQWarp,
MQWarpShadowStep,
MQWarpKnockBack,
MQWarpLight,
MQZone,
MQZoneUnknownDest,
MQGate,
MQGhost,
MQFastMem,
MQWarpAbsolute
} CheatTypes;
class CheatManager {
public:
CheatManager()
{
SetExemptStatus(ShadowStep, false);
SetExemptStatus(KnockBack, false);
SetExemptStatus(Port, false);
SetExemptStatus(Assist, false);
SetExemptStatus(Sense, false);
m_distance_since_last_position_check = 0.0f;
m_cheat_detect_moved = false;
m_target = nullptr;
m_time_since_last_memorization = 0;
m_time_since_last_position_check = 0;
m_time_since_last_warp_detection.Start();
m_time_since_last_movement_history.Start(70000);
m_warp_counter = 0;
}
void SetClient(Client *cli);
void SetExemptStatus(ExemptionType type, bool v);
bool GetExemptStatus(ExemptionType type);
void CheatDetected(CheatTypes type, glm::vec3 position1, glm::vec3 position2 = glm::vec3(0, 0, 0));
void MovementCheck(glm::vec3 updated_position);
void MovementCheck(uint32 time_between_checks = 1000);
void CheckMemTimer();
void ProcessMovementHistory(const EQApplicationPacket *app);
void ProcessSpawnApperance(uint16 spawn_id, uint16 type, uint32 parameter);
void ProcessItemVerifyRequest(int32 slot_id, uint32 target_id);
void ClientProcess();
private:
bool m_exemption[ExemptionType::MAX_EXEMPTIONS]{};
float m_distance_since_last_position_check;
bool m_cheat_detect_moved;
Client *m_target;
uint32 m_time_since_last_position_check;
uint32 m_time_since_last_memorization;
uint32 m_time_since_last_action{};
Timer m_time_since_last_warp_detection;
Timer m_time_since_last_movement_history;
uint32 m_warp_counter;
};
#endif ANTICHEAT_H

View File

@ -61,6 +61,7 @@ extern volatile bool RunLoops;
#include "mob_movement_manager.h"
#include "../common/content/world_content_service.h"
#include "../common/expedition_lockout_timer.h"
#include "cheat_manager.h"
extern QueryServ* QServ;
extern EntityList entity_list;
@ -177,7 +178,7 @@ Client::Client(EQStreamInterface* ieqs)
for (int client_filter = 0; client_filter < _FilterCount; client_filter++)
ClientFilters[client_filter] = FilterShow;
cheat_manager.SetClient(this);
mMovementManager->AddClient(this);
character_id = 0;
conn_state = NoPacketsReceived;
@ -10258,7 +10259,7 @@ void Client::ApplyWeaponsStance()
- From spells, just remove the Primary buff that contains the WeaponStance effect in it.
- For items with worn effect, unequip the item.
- For AA abilities, a hotkey is used to Enable and Disable the effect. See. Client::TogglePassiveAlternativeAdvancement in aa.cpp for extensive details.
Rank
- Most important for AA, but if you have more than one of WeaponStance effect for a given type, the spell trigger buff will apply whatever has the highest
'rank' value from the spells table. AA's on live for this effect naturally do this. Be awere of this if making custom spells/worn effects/AA.
@ -10270,7 +10271,7 @@ void Client::ApplyWeaponsStance()
if (!IsWeaponStanceEnabled()) {
return;
}
bool enabled = false;
bool item_bonus_exists = false;
bool aa_bonus_exists = false;
@ -10326,7 +10327,7 @@ void Client::ApplyWeaponsStance()
if (itembonuses.WeaponStance[WEAPON_STANCE_TYPE_2H] || itembonuses.WeaponStance[WEAPON_STANCE_TYPE_SHIELD] ||
itembonuses.WeaponStance[WEAPON_STANCE_TYPE_DUAL_WIELD]) {
enabled = true;
item_bonus_exists = true;

View File

@ -66,6 +66,7 @@ namespace EQ
#include "zone_store.h"
#include "task_manager.h"
#include "task_client_state.h"
#include "cheat_manager.h"
#ifdef _WINDOWS
// since windows defines these within windef.h (which windows.h include)
@ -120,17 +121,6 @@ typedef enum {
EvacToSafeCoords
} ZoneMode;
typedef enum {
MQWarp,
MQWarpShadowStep,
MQWarpKnockBack,
MQWarpLight,
MQZone,
MQZoneUnknownDest,
MQGate,
MQGhost
} CheatTypes;
enum {
HideCorpseNone = 0,
HideCorpseAll = 1,
@ -604,7 +594,7 @@ public:
inline double GetEXPModifier(uint32 zone_id) const { return database.GetEXPModifier(CharacterID(), zone_id); };
inline void SetAAEXPModifier(uint32 zone_id, double aa_modifier) { database.SetAAEXPModifier(CharacterID(), zone_id, aa_modifier); };
inline void SetEXPModifier(uint32 zone_id, double exp_modifier) { database.SetEXPModifier(CharacterID(), zone_id, exp_modifier); };
bool UpdateLDoNPoints(uint32 theme_id, int points);
void SetPVPPoints(uint32 Points) { m_pp.PVPCurrentPoints = Points; }
uint32 GetPVPPoints() { return m_pp.PVPCurrentPoints; }
@ -1598,6 +1588,7 @@ public:
Raid *p_raid_instance;
void ShowDevToolsMenu();
CheatManager cheat_manager;
protected:
friend class Mob;

View File

@ -209,7 +209,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath;
ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest;
ConnectedOpcodes[OP_Fishing] = &Client::Handle_OP_Fishing;
ConnectedOpcodes[OP_FloatListThing] = &Client::Handle_OP_Ignore;
ConnectedOpcodes[OP_FloatListThing] = &Client::Handle_OP_MovementHistoryList;
ConnectedOpcodes[OP_Forage] = &Client::Handle_OP_Forage;
ConnectedOpcodes[OP_FriendsWho] = &Client::Handle_OP_FriendsWho;
ConnectedOpcodes[OP_GetGuildMOTD] = &Client::Handle_OP_GetGuildMOTD;
@ -413,6 +413,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_YellForHelp] = &Client::Handle_OP_YellForHelp;
ConnectedOpcodes[OP_ZoneChange] = &Client::Handle_OP_ZoneChange;
ConnectedOpcodes[OP_ResetAA] = &Client::Handle_OP_ResetAA;
ConnectedOpcodes[OP_UnderWorld] = &Client::Handle_OP_UnderWorld;
}
void ClearMappedOpcode(EmuOpcode op)
@ -2922,6 +2923,7 @@ void Client::Handle_OP_Assist(const EQApplicationPacket *app)
Mob *new_target = assistee->GetTarget();
if (new_target && (GetGM() ||
Distance(m_Position, assistee->GetPosition()) <= TARGETING_RANGE)) {
cheat_manager.SetExemptStatus(Assist, true);
eid->entity_id = new_target->GetID();
} else {
eid->entity_id = 0;
@ -4488,7 +4490,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
double cosine = std::cos(thetar);
double sine = std::sin(thetar);
double normalizedx, normalizedy;
double normalizedx, normalizedy;
normalizedx = cx * cosine - -cy * sine;
normalizedy = -cx * sine + cy * cosine;
@ -4500,6 +4502,8 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
}
}
cheat_manager.MovementCheck(glm::vec3(cx, cy, cz));
if (IsDraggingCorpse())
DragCorpses();
@ -8765,6 +8769,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
slot_id = request->slot;
target_id = request->target;
cheat_manager.ProcessItemVerifyRequest(request->slot, request->target);
EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ItemVerifyReply, sizeof(ItemVerifyReply_Struct));
@ -9614,6 +9619,7 @@ return;
void Client::Handle_OP_MemorizeSpell(const EQApplicationPacket *app)
{
cheat_manager.CheckMemTimer();
OPMemorizeSpell(app);
return;
}
@ -13465,6 +13471,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
}
SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer;
cheat_manager.ProcessSpawnApperance(sa->spawn_id, sa->type, sa->parameter);
if (sa->spawn_id != GetID())
return;
@ -13917,6 +13925,11 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
GetTarget()->IsTargeted(1);
return;
}
else if (cheat_manager.GetExemptStatus(Assist)) {
GetTarget()->IsTargeted(1);
cheat_manager.SetExemptStatus(Assist, false);
return;
}
else if (GetTarget()->IsClient())
{
//make sure this client is in our raid/group
@ -13932,6 +13945,15 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
SetTarget((Mob*)nullptr);
return;
}
else if (cheat_manager.GetExemptStatus(Port)) {
GetTarget()->IsTargeted(1);
return;
}
else if (cheat_manager.GetExemptStatus(Sense)) {
GetTarget()->IsTargeted(1);
cheat_manager.SetExemptStatus(Sense, false);
return;
}
else if (IsXTarget(GetTarget()))
{
GetTarget()->IsTargeted(1);
@ -15173,3 +15195,21 @@ void Client::Handle_OP_ResetAA(const EQApplicationPacket *app)
}
return;
}
void Client::Handle_OP_MovementHistoryList(const EQApplicationPacket* app) {
cheat_manager.ProcessMovementHistory(app);
}
void Client::Handle_OP_UnderWorld(const EQApplicationPacket* app) {
UnderWorld* m_UnderWorld = (UnderWorld*)app->pBuffer;
if (app->size != sizeof(UnderWorld))
{
LogDebug("Size mismatch in OP_UnderWorld, expected {}, got [{}]", sizeof(UnderWorld), app->size);
DumpPacket(app);
return;
}
auto dist = Distance(glm::vec3(m_UnderWorld->x, m_UnderWorld->y, zone->newzone_data.underworld), glm::vec3(m_UnderWorld->x, m_UnderWorld->y, m_UnderWorld->z));
cheat_manager.MovementCheck(glm::vec3(m_UnderWorld->x, m_UnderWorld->y, m_UnderWorld->z));
if (m_UnderWorld->spawn_id == GetID() && dist <= 5.0f && zone->newzone_data.underworld_teleport_index != 0)
cheat_manager.SetExemptStatus(Port, true);
}

View File

@ -313,3 +313,5 @@
void Handle_OP_YellForHelp(const EQApplicationPacket *app);
void Handle_OP_ZoneChange(const EQApplicationPacket *app);
void Handle_OP_ResetAA(const EQApplicationPacket *app);
void Handle_OP_MovementHistoryList(const EQApplicationPacket* app);
void Handle_OP_UnderWorld(const EQApplicationPacket* app);

View File

@ -203,6 +203,8 @@ bool Client::Process() {
if (IsStunned() && stunned_timer.Check())
Mob::UnStun();
cheat_manager.ClientProcess();
if (bardsong_timer.Check() && bardsong != 0) {
//NOTE: this is kinda a heavy-handed check to make sure the mob still exists before
//doing the next pulse on them...

View File

@ -120,6 +120,7 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_USE_SKILL",
"EVENT_COMBINE_VALIDATE",
"EVENT_BOT_COMMAND",
"EVENT_WARP",
"EVENT_TEST_BUFF"
};
@ -1636,6 +1637,13 @@ void PerlembParser::ExportEventVariables(
ExportVar(package_name.c_str(), "langid", extradata);
break;
}
case EVENT_WARP: {
Seperator sep(data);
ExportVar(package_name.c_str(), "from_x", sep.arg[0]);
ExportVar(package_name.c_str(), "from_y", sep.arg[1]);
ExportVar(package_name.c_str(), "from_z", sep.arg[2]);
break;
}
default: {
break;

View File

@ -88,6 +88,7 @@ typedef enum {
EVENT_USE_SKILL,
EVENT_COMBINE_VALIDATE,
EVENT_BOT_COMMAND,
EVENT_WARP,
EVENT_TEST_BUFF,
_LargestEventID
} QuestEventID;

View File

@ -3225,6 +3225,7 @@ luabind::scope lua_register_events() {
luabind::value("spawn_zone", static_cast<int>(EVENT_SPAWN_ZONE)),
luabind::value("death_zone", static_cast<int>(EVENT_DEATH_ZONE)),
luabind::value("use_skill", static_cast<int>(EVENT_USE_SKILL)),
luabind::value("warp", static_cast<int>(EVENT_WARP)),
luabind::value("test_buff", static_cast<int>(EVENT_TEST_BUFF))
];
}

View File

@ -131,6 +131,7 @@ const char *LuaEvents[_LargestEventID] = {
"event_use_skill",
"event_combine_validate",
"event_bot_command",
"event_warp",
"event_test_buff"
};
@ -217,6 +218,7 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_TEST_BUFF] = handle_test_buff;
PlayerArgumentDispatch[EVENT_COMBINE_VALIDATE] = handle_player_combine_validate;
PlayerArgumentDispatch[EVENT_BOT_COMMAND] = handle_player_bot_command;
PlayerArgumentDispatch[EVENT_WARP] = handle_player_warp;
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;

View File

@ -561,6 +561,18 @@ void handle_player_bot_command(QuestInterface* parse, lua_State* L, Client* clie
lua_setfield(L, -2, "args");
}
void handle_player_warp(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector<EQ::Any>* extra_pointers) {
Seperator sep(data.c_str());
lua_pushnumber(L, std::stof(sep.arg[0]));
lua_setfield(L, -2, "from_x");
lua_pushnumber(L, std::stof(sep.arg[1]));
lua_setfield(L, -2, "from_y");
lua_pushnumber(L, std::stof(sep.arg[2]));
lua_setfield(L, -2, "from_z");
}
//Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data,
std::vector<EQ::Any> *extra_pointers) {

View File

@ -103,6 +103,8 @@ void handle_player_combine_validate(QuestInterface* parse, lua_State* L, Client*
std::vector<EQ::Any>* extra_pointers);
void handle_player_bot_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
std::vector<EQ::Any> *extra_pointers);
void handle_player_warp(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
std::vector<EQ::Any>* extra_pointers);
//Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data,

View File

@ -4657,6 +4657,7 @@ void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup)
{
if(IsClient())
{
CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true);
auto outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;

View File

@ -2743,6 +2743,22 @@ void Mob::BardPulse(uint16 spell_id, Mob *caster) {
action->effect_flag = 4;
if (spells[spell_id].pushback != 0.0f || spells[spell_id].pushup != 0.0f)
{
if (IsClient())
{
if (!IsBuffSpell(spell_id))
{
CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true);
}
}
}
if (IsClient() && IsEffectInSpell(spell_id, SE_ShadowStep))
{
CastToClient()->cheat_manager.SetExemptStatus(ShadowStep, true);
}
if(!IsEffectInSpell(spell_id, SE_BindAffinity))
{
CastToClient()->QueuePacket(packet);
@ -4028,7 +4044,14 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
if(spells[spell_id].pushback != 0.0f || spells[spell_id].pushup != 0.0f)
{
if (RuleB(Spells, NPCSpellPush) && !spelltar->IsRooted() && spelltar->ForcedMovement == 0) {
if (spelltar->IsClient())
{
if (!IsBuffSpell(spell_id))
{
spelltar->CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true);
}
}
else if (RuleB(Spells, NPCSpellPush) && !spelltar->IsRooted() && spelltar->ForcedMovement == 0) {
spelltar->m_Delta.x += action->force * g_Math.FastSin(action->hit_heading);
spelltar->m_Delta.y += action->force * g_Math.FastCos(action->hit_heading);
spelltar->m_Delta.z += action->hit_pitch;
@ -4036,6 +4059,11 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
}
}
if (spelltar->IsClient() && IsEffectInSpell(spell_id, SE_ShadowStep))
{
spelltar->CastToClient()->cheat_manager.SetExemptStatus(ShadowStep, true);
}
if(!IsEffectInSpell(spell_id, SE_BindAffinity))
{
if(spelltar != this && spelltar->IsClient()) // send to target

View File

@ -1911,7 +1911,11 @@ ZonePoint* Zone::GetClosestZonePoint(const glm::vec3& location, uint32 to, Clien
// this shouldn't open up any exploits since those situations are detected later on
if ((zone->HasWaterMap() && !zone->watermap->InZoneLine(glm::vec3(client->GetPosition()))) || (!zone->HasWaterMap() && closest_dist > 400.0f && closest_dist < max_distance2))
{
//TODO cheat detection
if (client) {
if (!client->cheat_manager.GetExemptStatus(Port)) {
client->cheat_manager.CheatDetected(MQZoneUnknownDest, location);
}
}
LogInfo("WARNING: Closest zone point for zone id [{}] is [{}], you might need to update your zone_points table if you dont arrive at the right spot", to, closest_dist);
LogInfo("<Real Zone Points>. [{}]", to_string(location).c_str());
}

View File

@ -99,9 +99,14 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
//unable to find a zone point... is there anything else
//that can be a valid un-zolicited zone request?
//Todo cheat detection
Message(Chat::Red, "Invalid unsolicited zone request.");
LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), target_zone_id);
if (GetBindZoneID() == target_zone_id) {
cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z));
}
else {
cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z));
}
SendZoneCancel(zc);
return;
}
@ -134,7 +139,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
//then we assume this is invalid.
if(!zone_point || zone_point->target_zone_id != target_zone_id) {
LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), target_zone_id);
//todo cheat detection
if (GetBindZoneID() == target_zone_id) {
cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z));
}
else {
cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z));
}
SendZoneCancel(zc);
return;
}
@ -282,7 +292,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
//for now, there are no other cases...
//could not find a valid reason for them to be zoning, stop it.
//todo cheat detection
if (GetBindZoneID() == target_zone_id) {
cheat_manager.CheatDetected(MQGate, glm::vec3(zc->x, zc->y, zc->z));
}
else {
cheat_manager.CheatDetected(MQZone, glm::vec3(zc->x, zc->y, zc->z));
}
LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]. Not near a zone point", GetName(), target_zone_name);
SendZoneCancel(zc);
return;
@ -379,6 +394,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
void Client::SendZoneCancel(ZoneChange_Struct *zc) {
//effectively zone them right back to where they were
//unless we find a better way to stop the zoning process.
cheat_manager.SetExemptStatus(Port, true);
EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct));
ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer;
@ -397,7 +413,7 @@ void Client::SendZoneCancel(ZoneChange_Struct *zc) {
void Client::SendZoneError(ZoneChange_Struct *zc, int8 err)
{
LogError("Zone [{}] is not available because target wasn't found or character insufficent level", zc->zoneID);
cheat_manager.SetExemptStatus(Port, true);
EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct));
ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer;
@ -667,6 +683,8 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z
pShortZoneName = ZoneName(zoneID);
content_db.GetZoneLongName(pShortZoneName, &pZoneName);
cheat_manager.SetExemptStatus(Port, true);
if(!pZoneName) {
Message(Chat::Red, "Invalid zone number specified");
safe_delete_array(pZoneName);