fix: a player's own messages have to be sent for that player

The die request, the smash request, the loaded notice, chat commands
and the racing pickup notice were handled for whichever object they
named. They are now only handled for the player that sent them, or for
the vehicle that player drives where the client sends them for it. A
racing pickup also has to be an object of a race track that is not a
player, a vehicle or the zone's own object.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-11-10 13:06:27 -06:00
parent f170b46a5e
commit 081e26ded8
6 changed files with 57 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
#include "CombatMessages.h"
#include "CheatDetection.h"
#include "BitStreamUtils.h"
#include "ControllablePhysicsComponent.h"
#include "CppScripts.h"
@@ -69,6 +70,13 @@ namespace GameMessages {
void RequestDie::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
Entity* entity = &entityRef;
// A player asks for their own death or that of the vehicle they drive
if (!CheatDetection::IsSendersPlayer(entity->GetObjectID(), sysAddr)) {
LOG("Object %llu (LOT %i) is not the player that asked for its death or their vehicle", entity->GetObjectID(), entity->GetLOT());
return;
}
auto* zoneController = Game::zoneManager->GetZoneControlObject();
auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>();
@@ -103,6 +111,11 @@ namespace GameMessages {
}
void RequestSmashPlayer::Handle(Entity& entity, const SystemAddress& sysAddr) {
// A player asks for their own smash
if (!entity.IsPlayer() || entity.GetSystemAddress() != sysAddr) {
LOG("Object %llu (LOT %i) is not the player that asked for its smash", entity.GetObjectID(), entity.GetLOT());
return;
}
entity.Smash(entity.GetObjectID());
}

View File

@@ -1,5 +1,6 @@
#include "PlayerMessages.h"
#include "Logger.h"
#include "BitStreamUtils.h"
#include "Character.h"
#include "CharacterComponent.h"
@@ -190,6 +191,11 @@ namespace GameMessages {
}
void ParseChatMessage::Handle(Entity& entity, const SystemAddress& sysAddr) {
// A chat command runs for the player that typed it
if (!entity.IsPlayer() || entity.GetSystemAddress() != sysAddr) {
LOG("Object %llu (LOT %i) is not the player that sent a chat command for it", entity.GetObjectID(), entity.GetLOT());
return;
}
if (!wsString.empty() && wsString[0] == L'/') {
SlashCommandHandler::HandleChatCommand(wsString, &entity, sysAddr);
}

View File

@@ -1,5 +1,7 @@
#include "RacingMessages.h"
#include "CheatDetection.h"
#include "PossessorComponent.h"
#include "BitStreamUtils.h"
#include "CharacterComponent.h"
#include "Entity.h"
@@ -215,6 +217,21 @@ namespace GameMessages {
return;
}
// A player reports the pickups their own vehicle drove through
if (!CheatDetection::IsSendersPlayer(vehicle.GetObjectID(), sysAddr)) {
LOG("Object %llu (LOT %i) is not the vehicle of the player that reported a pickup for it", vehicle.GetObjectID(), vehicle.GetLOT());
return;
}
// Pickups are objects of a race track, and never a player, a vehicle or the zone's own object
const auto* const zoneControl = Game::zoneManager->GetZoneControlObject();
if (!zoneControl || !zoneControl->GetComponent<RacingControlComponent>()
|| pickup == zoneControl || pickup->IsPlayer()
|| pickup->GetComponent<PossessableComponent>() || pickup->GetComponent<PossessorComponent>()) {
LOG("Object %llu (LOT %i) is not a pickup of a race track", pickupObjID, pickup->GetLOT());
return;
}
// The sender is the vehicle; credit its driver if it has one.
Entity* entity = &vehicle;
auto* possessableComponent = entity->GetComponent<PossessableComponent>();

View File

@@ -1,4 +1,5 @@
#include "ZoneMessages.h"
#include "Logger.h"
#include "MovementMessages.h"
#include "MissionMessages.h"
@@ -63,6 +64,11 @@ namespace GameMessages {
void PlayerLoaded::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
auto* entity = &entityRef;
// A player reports that they themselves have loaded
if (!entity->IsPlayer() || entity->GetSystemAddress() != sysAddr) {
LOG("Object %llu (LOT %i) is not the player that reported it as loaded", entity->GetObjectID(), entity->GetLOT());
return;
}
const auto* const zoneControlEntity = Game::entityManager->GetZoneControlEntity();
SendPlayerReady(entity->GetObjectID(), zoneControlEntity ? zoneControlEntity->GetObjectID() : LWOOBJID_EMPTY, sysAddr);
entity->SetPlayerReadyForUpdates();

View File

@@ -105,6 +105,12 @@ void CheatDetection::ReportCheat(User* user, const SystemAddress& sysAddr, const
va_end(args);
}
bool CheatDetection::IsSendersPlayer(const LWOOBJID& id, const SystemAddress& sysAddr) {
// The player themselves, or the player that possesses the object
const auto* const entity = GetPossessedEntity(id);
return entity && entity->IsPlayer() && entity->GetSystemAddress() == sysAddr;
}
bool CheatDetection::VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddress& sysAddr, const CheckType checkType, const char* messageIfNotSender, ...) {
// Get variables we'll need for the whole function
bool invalidPacket = false;

View File

@@ -24,6 +24,15 @@ namespace CheatDetection {
*/
bool VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddress& sysAddr, const CheckType checkType, const char* messageIfNotSender, ...);
void ReportCheat(User* user, const SystemAddress& sysAddr, const char* messageIfNotSender, ...);
/**
* @brief Whether an object is the player at a system address or the object that player possesses, such as their vehicle.
* Unlike VerifyLwoobjidIsSender this is false for every other object, so it is for messages only a player sends about themselves.
*
* @param id The object ID a message was sent for
* @param sysAddr The system address which sent the message
*/
bool IsSendersPlayer(const LWOOBJID& id, const SystemAddress& sysAddr);
};
#endif //!__CHEATDETECTION__H__