[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_ZoneSpawns),
N(OP_ZoneUnavail), N(OP_ZoneUnavail),
N(OP_ResetAA), N(OP_ResetAA),
N(OP_UnderWorld),
// mail and chat opcodes located in ../mail_oplist.h // 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_FindBits 46 // set FindBits, whatever those are!
#define AT_TextureType 48 // TextureType #define AT_TextureType 48 // TextureType
#define AT_FacePick 49 // Turns off face pick window? maybe ... #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_GuildShow 52 // this is what MQ2 call sit, not sure
#define AT_Offline 53 // Offline mode #define AT_Offline 53 // Offline mode

View File

@ -5530,6 +5530,23 @@ struct SayLinkBodyFrame_Struct {
/*056*/ /*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 // Restore structure packing to default
#pragma pack() #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::HotReload].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::Loot].log_to_gmsay = 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::Scheduler].log_to_console = static_cast<uint8>(Logs::General);
log_settings[Logs::Cheat].log_to_console = static_cast<uint8>(Logs::General);
/** /**
* RFC 5424 * RFC 5424

View File

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

View File

@ -646,6 +646,16 @@
OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::Scheduler, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } 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 {\ #define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ 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_INT(DynamicZone, WorldProcessRate, 6000, "Timer interval (milliseconds) that systems check their dynamic zone states")
RULE_CATEGORY_END() 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_CATEGORY
#undef RULE_INT #undef RULE_INT
#undef RULE_REAL #undef RULE_REAL

View File

@ -14,6 +14,7 @@ SET(zone_sources
bot_command.cpp bot_command.cpp
bot_database.cpp bot_database.cpp
botspellsai.cpp botspellsai.cpp
cheat_manager.cpp
client.cpp client.cpp
client_mods.cpp client_mods.cpp
client_packet.cpp client_packet.cpp
@ -157,7 +158,8 @@ SET(zone_sources
zone_event_scheduler.cpp zone_event_scheduler.cpp
zone_reload.cpp zone_reload.cpp
zone_store.cpp zone_store.cpp
zoning.cpp) zoning.cpp
)
SET(zone_headers SET(zone_headers
aa.h aa.h
@ -171,6 +173,7 @@ SET(zone_headers
bot_command.h bot_command.h
bot_database.h bot_database.h
bot_structs.h bot_structs.h
cheat_manager.h
client.h client.h
client_packet.h client_packet.h
command.h command.h
@ -274,7 +277,8 @@ SET(zone_headers
zonedb.h zonedb.h
zonedump.h zonedump.h
zone_reload.h zone_reload.h
zone_store.h) zone_store.h
)
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers}) 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 "mob_movement_manager.h"
#include "../common/content/world_content_service.h" #include "../common/content/world_content_service.h"
#include "../common/expedition_lockout_timer.h" #include "../common/expedition_lockout_timer.h"
#include "cheat_manager.h"
extern QueryServ* QServ; extern QueryServ* QServ;
extern EntityList entity_list; extern EntityList entity_list;
@ -177,7 +178,7 @@ Client::Client(EQStreamInterface* ieqs)
for (int client_filter = 0; client_filter < _FilterCount; client_filter++) for (int client_filter = 0; client_filter < _FilterCount; client_filter++)
ClientFilters[client_filter] = FilterShow; ClientFilters[client_filter] = FilterShow;
cheat_manager.SetClient(this);
mMovementManager->AddClient(this); mMovementManager->AddClient(this);
character_id = 0; character_id = 0;
conn_state = NoPacketsReceived; conn_state = NoPacketsReceived;

View File

@ -66,6 +66,7 @@ namespace EQ
#include "zone_store.h" #include "zone_store.h"
#include "task_manager.h" #include "task_manager.h"
#include "task_client_state.h" #include "task_client_state.h"
#include "cheat_manager.h"
#ifdef _WINDOWS #ifdef _WINDOWS
// since windows defines these within windef.h (which windows.h include) // since windows defines these within windef.h (which windows.h include)
@ -120,17 +121,6 @@ typedef enum {
EvacToSafeCoords EvacToSafeCoords
} ZoneMode; } ZoneMode;
typedef enum {
MQWarp,
MQWarpShadowStep,
MQWarpKnockBack,
MQWarpLight,
MQZone,
MQZoneUnknownDest,
MQGate,
MQGhost
} CheatTypes;
enum { enum {
HideCorpseNone = 0, HideCorpseNone = 0,
HideCorpseAll = 1, HideCorpseAll = 1,
@ -1598,6 +1588,7 @@ public:
Raid *p_raid_instance; Raid *p_raid_instance;
void ShowDevToolsMenu(); void ShowDevToolsMenu();
CheatManager cheat_manager;
protected: protected:
friend class Mob; friend class Mob;

View File

@ -209,7 +209,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath; ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath;
ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest; ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest;
ConnectedOpcodes[OP_Fishing] = &Client::Handle_OP_Fishing; 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_Forage] = &Client::Handle_OP_Forage;
ConnectedOpcodes[OP_FriendsWho] = &Client::Handle_OP_FriendsWho; ConnectedOpcodes[OP_FriendsWho] = &Client::Handle_OP_FriendsWho;
ConnectedOpcodes[OP_GetGuildMOTD] = &Client::Handle_OP_GetGuildMOTD; ConnectedOpcodes[OP_GetGuildMOTD] = &Client::Handle_OP_GetGuildMOTD;
@ -413,6 +413,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_YellForHelp] = &Client::Handle_OP_YellForHelp; ConnectedOpcodes[OP_YellForHelp] = &Client::Handle_OP_YellForHelp;
ConnectedOpcodes[OP_ZoneChange] = &Client::Handle_OP_ZoneChange; ConnectedOpcodes[OP_ZoneChange] = &Client::Handle_OP_ZoneChange;
ConnectedOpcodes[OP_ResetAA] = &Client::Handle_OP_ResetAA; ConnectedOpcodes[OP_ResetAA] = &Client::Handle_OP_ResetAA;
ConnectedOpcodes[OP_UnderWorld] = &Client::Handle_OP_UnderWorld;
} }
void ClearMappedOpcode(EmuOpcode op) void ClearMappedOpcode(EmuOpcode op)
@ -2922,6 +2923,7 @@ void Client::Handle_OP_Assist(const EQApplicationPacket *app)
Mob *new_target = assistee->GetTarget(); Mob *new_target = assistee->GetTarget();
if (new_target && (GetGM() || if (new_target && (GetGM() ||
Distance(m_Position, assistee->GetPosition()) <= TARGETING_RANGE)) { Distance(m_Position, assistee->GetPosition()) <= TARGETING_RANGE)) {
cheat_manager.SetExemptStatus(Assist, true);
eid->entity_id = new_target->GetID(); eid->entity_id = new_target->GetID();
} else { } else {
eid->entity_id = 0; eid->entity_id = 0;
@ -4500,6 +4502,8 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
} }
} }
cheat_manager.MovementCheck(glm::vec3(cx, cy, cz));
if (IsDraggingCorpse()) if (IsDraggingCorpse())
DragCorpses(); DragCorpses();
@ -8765,6 +8769,7 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
slot_id = request->slot; slot_id = request->slot;
target_id = request->target; target_id = request->target;
cheat_manager.ProcessItemVerifyRequest(request->slot, request->target);
EQApplicationPacket *outapp = nullptr; EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ItemVerifyReply, sizeof(ItemVerifyReply_Struct)); outapp = new EQApplicationPacket(OP_ItemVerifyReply, sizeof(ItemVerifyReply_Struct));
@ -9614,6 +9619,7 @@ return;
void Client::Handle_OP_MemorizeSpell(const EQApplicationPacket *app) void Client::Handle_OP_MemorizeSpell(const EQApplicationPacket *app)
{ {
cheat_manager.CheckMemTimer();
OPMemorizeSpell(app); OPMemorizeSpell(app);
return; return;
} }
@ -13465,6 +13471,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
} }
SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer; SpawnAppearance_Struct* sa = (SpawnAppearance_Struct*)app->pBuffer;
cheat_manager.ProcessSpawnApperance(sa->spawn_id, sa->type, sa->parameter);
if (sa->spawn_id != GetID()) if (sa->spawn_id != GetID())
return; return;
@ -13917,6 +13925,11 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
GetTarget()->IsTargeted(1); GetTarget()->IsTargeted(1);
return; return;
} }
else if (cheat_manager.GetExemptStatus(Assist)) {
GetTarget()->IsTargeted(1);
cheat_manager.SetExemptStatus(Assist, false);
return;
}
else if (GetTarget()->IsClient()) else if (GetTarget()->IsClient())
{ {
//make sure this client is in our raid/group //make sure this client is in our raid/group
@ -13932,6 +13945,15 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app)
SetTarget((Mob*)nullptr); SetTarget((Mob*)nullptr);
return; 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())) else if (IsXTarget(GetTarget()))
{ {
GetTarget()->IsTargeted(1); GetTarget()->IsTargeted(1);
@ -15173,3 +15195,21 @@ void Client::Handle_OP_ResetAA(const EQApplicationPacket *app)
} }
return; 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_YellForHelp(const EQApplicationPacket *app);
void Handle_OP_ZoneChange(const EQApplicationPacket *app); void Handle_OP_ZoneChange(const EQApplicationPacket *app);
void Handle_OP_ResetAA(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()) if (IsStunned() && stunned_timer.Check())
Mob::UnStun(); Mob::UnStun();
cheat_manager.ClientProcess();
if (bardsong_timer.Check() && bardsong != 0) { if (bardsong_timer.Check() && bardsong != 0) {
//NOTE: this is kinda a heavy-handed check to make sure the mob still exists before //NOTE: this is kinda a heavy-handed check to make sure the mob still exists before
//doing the next pulse on them... //doing the next pulse on them...

View File

@ -120,6 +120,7 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_USE_SKILL", "EVENT_USE_SKILL",
"EVENT_COMBINE_VALIDATE", "EVENT_COMBINE_VALIDATE",
"EVENT_BOT_COMMAND", "EVENT_BOT_COMMAND",
"EVENT_WARP",
"EVENT_TEST_BUFF" "EVENT_TEST_BUFF"
}; };
@ -1636,6 +1637,13 @@ void PerlembParser::ExportEventVariables(
ExportVar(package_name.c_str(), "langid", extradata); ExportVar(package_name.c_str(), "langid", extradata);
break; 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: { default: {
break; break;

View File

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

View File

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

View File

@ -131,6 +131,7 @@ const char *LuaEvents[_LargestEventID] = {
"event_use_skill", "event_use_skill",
"event_combine_validate", "event_combine_validate",
"event_bot_command", "event_bot_command",
"event_warp",
"event_test_buff" "event_test_buff"
}; };
@ -217,6 +218,7 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_TEST_BUFF] = handle_test_buff; PlayerArgumentDispatch[EVENT_TEST_BUFF] = handle_test_buff;
PlayerArgumentDispatch[EVENT_COMBINE_VALIDATE] = handle_player_combine_validate; PlayerArgumentDispatch[EVENT_COMBINE_VALIDATE] = handle_player_combine_validate;
PlayerArgumentDispatch[EVENT_BOT_COMMAND] = handle_player_bot_command; 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] = handle_item_click;
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = 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"); 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 //Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, 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) { 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); std::vector<EQ::Any>* extra_pointers);
void handle_player_bot_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, void handle_player_bot_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
std::vector<EQ::Any> *extra_pointers); 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 //Item
void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, EQ::ItemInstance* item, Mob *mob, std::string data, uint32 extra_data, 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()) if(IsClient())
{ {
CastToClient()->cheat_manager.SetExemptStatus(KnockBack, true);
auto outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct)); auto outapp_push = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer; 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; 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)) if(!IsEffectInSpell(spell_id, SE_BindAffinity))
{ {
CastToClient()->QueuePacket(packet); 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(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.x += action->force * g_Math.FastSin(action->hit_heading);
spelltar->m_Delta.y += action->force * g_Math.FastCos(action->hit_heading); spelltar->m_Delta.y += action->force * g_Math.FastCos(action->hit_heading);
spelltar->m_Delta.z += action->hit_pitch; 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(!IsEffectInSpell(spell_id, SE_BindAffinity))
{ {
if(spelltar != this && spelltar->IsClient()) // send to target 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 // 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)) 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("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()); 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 //unable to find a zone point... is there anything else
//that can be a valid un-zolicited zone request? //that can be a valid un-zolicited zone request?
//Todo cheat detection
Message(Chat::Red, "Invalid unsolicited zone request."); Message(Chat::Red, "Invalid unsolicited zone request.");
LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), target_zone_id); 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); SendZoneCancel(zc);
return; return;
} }
@ -134,7 +139,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
//then we assume this is invalid. //then we assume this is invalid.
if(!zone_point || zone_point->target_zone_id != target_zone_id) { if(!zone_point || zone_point->target_zone_id != target_zone_id) {
LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]", GetName(), 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); SendZoneCancel(zc);
return; return;
} }
@ -282,7 +292,12 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
//for now, there are no other cases... //for now, there are no other cases...
//could not find a valid reason for them to be zoning, stop it. //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); LogError("Zoning [{}]: Invalid unsolicited zone request to zone id [{}]. Not near a zone point", GetName(), target_zone_name);
SendZoneCancel(zc); SendZoneCancel(zc);
return; return;
@ -379,6 +394,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
void Client::SendZoneCancel(ZoneChange_Struct *zc) { void Client::SendZoneCancel(ZoneChange_Struct *zc) {
//effectively zone them right back to where they were //effectively zone them right back to where they were
//unless we find a better way to stop the zoning process. //unless we find a better way to stop the zoning process.
cheat_manager.SetExemptStatus(Port, true);
EQApplicationPacket *outapp = nullptr; EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct)); outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct));
ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer; 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) void Client::SendZoneError(ZoneChange_Struct *zc, int8 err)
{ {
LogError("Zone [{}] is not available because target wasn't found or character insufficent level", zc->zoneID); LogError("Zone [{}] is not available because target wasn't found or character insufficent level", zc->zoneID);
cheat_manager.SetExemptStatus(Port, true);
EQApplicationPacket *outapp = nullptr; EQApplicationPacket *outapp = nullptr;
outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct)); outapp = new EQApplicationPacket(OP_ZoneChange, sizeof(ZoneChange_Struct));
ZoneChange_Struct *zc2 = (ZoneChange_Struct*)outapp->pBuffer; 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); pShortZoneName = ZoneName(zoneID);
content_db.GetZoneLongName(pShortZoneName, &pZoneName); content_db.GetZoneLongName(pShortZoneName, &pZoneName);
cheat_manager.SetExemptStatus(Port, true);
if(!pZoneName) { if(!pZoneName) {
Message(Chat::Red, "Invalid zone number specified"); Message(Chat::Red, "Invalid zone number specified");
safe_delete_array(pZoneName); safe_delete_array(pZoneName);