From 8f1c99ae14531ef72f5007f4bd05bd6f27c30081 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:29:08 +0300 Subject: [PATCH 1/5] Implement bulletsync validation checks across various components - Added validation for bullet sync geometry and damage in CNetAPI, CGame, and CPlayer classes to prevent invalid bullet sync packets. - Introduced a method in CPlayer to limit the rate of accepted bulletsync packets, enhancing anti-cheat measures. - Updated packet reading methods in CBulletsyncPacket and related classes to utilize new validation functions for improved integrity checks. - Refactored existing checks to leverage the new SyncBulletsyncValidation class for consistency and maintainability. --- .vscode/settings.json | 1 + Client/mods/deathmatch/logic/CClientPed.cpp | 8 +- Client/mods/deathmatch/logic/CNetAPI.cpp | 10 + .../deathmatch/README.weapon-sync-guard.md | 47 +++++ Server/mods/deathmatch/logic/CGame.cpp | 19 +- Server/mods/deathmatch/logic/CPlayer.cpp | 20 ++ Server/mods/deathmatch/logic/CPlayer.h | 6 + .../logic/net/CSimBulletsyncPacket.cpp | 19 +- .../logic/net/CSimPlayerManager.cpp | 38 +++- .../logic/net/CSimPlayerPuresyncPacket.cpp | 5 + .../logic/net/CSimVehiclePuresyncPacket.cpp | 6 + .../logic/packets/CBulletsyncPacket.cpp | 86 ++------- .../packets/CCustomWeaponBulletSyncPacket.cpp | 3 +- .../logic/packets/CKeysyncPacket.cpp | 7 + .../logic/packets/CPlayerPuresyncPacket.cpp | 6 + .../logic/packets/CVehiclePuresyncPacket.cpp | 6 + .../logic/SyncBulletsyncValidation.h | 172 ++++++++++++++++++ 17 files changed, 366 insertions(+), 93 deletions(-) create mode 100644 Server/mods/deathmatch/README.weapon-sync-guard.md create mode 100644 Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h diff --git a/.vscode/settings.json b/.vscode/settings.json index f27f5f2511a..f0759d01ad0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,5 @@ "powershell.codeFormatting.openBraceOnSameLine": true, "powershell.codeFormatting.newLineAfterCloseBrace": false, + "C_Cpp.default.compilerPath": "c:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\MSVC\\14.51.36231\\bin\\Hostx86\\x64\\cl.exe", } diff --git a/Client/mods/deathmatch/logic/CClientPed.cpp b/Client/mods/deathmatch/logic/CClientPed.cpp index bfb2cb271cf..d2d3432383c 100644 --- a/Client/mods/deathmatch/logic/CClientPed.cpp +++ b/Client/mods/deathmatch/logic/CClientPed.cpp @@ -1014,6 +1014,9 @@ void CClientPed::SetTargetTarget(unsigned long ulDelay, const CVector& vecSource { if (!m_bIsLocalPlayer) { + if (!vecSource.IsValid() || !vecTarget.IsValid()) + return; + m_ulBeginTarget = CClientTime::GetTime(); m_ulEndTarget = m_ulBeginTarget + ulDelay; m_vecBeginSource = m_shotSyncData->m_vecShotOrigin; @@ -1021,8 +1024,9 @@ void CClientPed::SetTargetTarget(unsigned long ulDelay, const CVector& vecSource m_vecTargetSource = vecSource; m_vecTargetTarget = vecTarget; - // Grab the radius of the target circle - float fRadius = DistanceBetweenPoints3D(m_vecTargetSource, m_vecTargetTarget); + const float fRadius = DistanceBetweenPoints3D(m_vecTargetSource, m_vecTargetTarget); + if (fRadius < 0.01f) + return; // Grab the angle of the source vector and the angle of the target vector relative to the source vector that applies m_vecBeginTargetAngle.fX = acos(Clamp(-1.0f, (m_vecBeginTarget.fX - m_vecBeginSource.fX) / fRadius, 1.0f)); diff --git a/Client/mods/deathmatch/logic/CNetAPI.cpp b/Client/mods/deathmatch/logic/CNetAPI.cpp index 403240260c3..00a87ef5c93 100644 --- a/Client/mods/deathmatch/logic/CNetAPI.cpp +++ b/Client/mods/deathmatch/logic/CNetAPI.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include +#include #include #include #include @@ -2295,6 +2296,15 @@ void CNetAPI::ReadBulletsync(CClientPlayer* player, NetBitStreamInterface& strea !end.IsValid()) return; + if (!SyncBulletsyncValidation::IsSyncedBulletSegmentNonDegenerate(start, end)) + return; + + CWeaponStat* pWeaponStat = g_pGame->GetWeaponStatManager()->GetOriginalWeaponStats(type); + const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, start, end, + fWeaponRange)) + return; + std::uint8_t order = 0; if (!stream.Read(order)) return; diff --git a/Server/mods/deathmatch/README.weapon-sync-guard.md b/Server/mods/deathmatch/README.weapon-sync-guard.md new file mode 100644 index 00000000000..ff3cc69e7ce --- /dev/null +++ b/Server/mods/deathmatch/README.weapon-sync-guard.md @@ -0,0 +1,47 @@ +# Weapon Sync Guard (Bullet Sync + Aim Sync) + +## Why this exists + +Cheats hook client `SendBulletSync` and can: + +- send many bullet-sync packets per physical shot (`DamageDividier` loops) +- forge start/end vectors (including non-finite or extreme trajectories) +- bypass `onPlayerWeaponFire` on the server while still crashing remote clients + +Root cause: the **sim thread** relays `PACKET_ID_PLAYER_BULLETSYNC` before the main thread runs full validation. Invalid packets were broadcast to nearby players even when `CBulletsyncPacket::Read()` later rejected them. + +Weapon aim data in puresync/keysync can also crash remote clients when origin ≈ target (division by zero in `SetTargetTarget`). + +## What is fixed + +### 1) Player-relative geometry (`SyncBulletsyncValidation.h`) + +Validation is derived from the shooter sync position and weapon stats: + +- **Muzzle origin** within a physical envelope around the ped/vehicle sync point (5 m on foot, 25 m in vehicle) +- **Bullet segment** length ≤ weapon range (+ lag tolerance) +- **Impact point** reachable from shooter: `origin_offset + weapon_range` +- Non-finite vectors and degenerate segments rejected + +### 2) Sim-thread parity (`CSimPlayerManager::HandleBulletSync`) + +Sim relay runs the same checks as `CBulletsyncPacket::Read()` **before** broadcasting: + +- spawned/alive player, weapon ownership + ammo +- player-relative geometry + damage validation +- per-player rate limit (25 packets / second) + +### 3) Weapon aim validation (puresync + keysync) + +Full aim sync rejected when origin/target would crash remote simulation. Applied on main and sim relay paths. + +### 4) Client defense in depth + +- `CNetAPI::ReadBulletsync` drops invalid geometry +- `CClientPed::SetTargetTarget` ignores invalid/degenerate aim data + +## Residual limits + +- Rate limit is per-second, not per-weapon fire rate from stats +- Compromised clients can still send sub-threshold values; server relay rejection is the meaningful fix +- Custom weapon bullet sync validates on main thread only (no sim fast path) diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index 5a30b0ed611..c79eada2ed6 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "CGame.h" +#include "SyncBulletsyncValidation.h" #ifdef WIN32 #include @@ -2559,19 +2560,13 @@ void CGame::Packet_Bulletsync(CBulletsyncPacket& packet) if (player->GetWeaponTotalAmmo(slot) <= 0) return; - // Note: Don't check ammo in clip here - it can be out of sync due to network timing - // The total ammo check above is sufficient - const auto stat = CWeaponStatManager::GetSkillStatIndex(packet.m_weapon); const auto level = player->GetPlayerStat(stat); auto* stats = g_pGame->GetWeaponStatManager()->GetWeaponStatsFromSkillLevel(packet.m_weapon, level); + const float fWeaponRange = stats->GetWeaponRange(); - const float distanceSq = (packet.m_start - packet.m_end).LengthSquared(); - const float range = stats->GetWeaponRange(); - const float rangeSq = range * range; - - const float maxRangeSq = rangeSq * 1.1f; // 10% tolerance for floating point - if (distanceSq > maxRangeSq) + if (!SyncBulletsyncValidation::IsSyncedBulletsyncPacketAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, packet.m_start, + packet.m_end, packet.m_damage, packet.m_zone, packet.m_damaged, fWeaponRange)) return; CLuaArguments args; @@ -2612,6 +2607,12 @@ void CGame::Packet_WeaponBulletsync(CCustomWeaponBulletSyncPacket& packet) if (weapon->GetClipAmmo() <= 0) return; + CWeaponStat* pWeaponStat = g_pGame->GetWeaponStatManager()->GetOriginalWeaponStats(weapon->GetWeaponType()); + const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, packet.m_start, + packet.m_end, fWeaponRange)) + return; + CLuaArguments args; args.PushElement(player); diff --git a/Server/mods/deathmatch/logic/CPlayer.cpp b/Server/mods/deathmatch/logic/CPlayer.cpp index 35641fe7c9b..69cf61dce2a 100644 --- a/Server/mods/deathmatch/logic/CPlayer.cpp +++ b/Server/mods/deathmatch/logic/CPlayer.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "SyncBulletsyncValidation.h" #include "CPlayer.h" #include "CElementRefManager.h" #include "CGame.h" @@ -1112,6 +1113,25 @@ void CPlayer::SetPosition(const CVector& vecPosition) CElement::SetPosition(vecPosition); } +bool CPlayer::TryAcceptBulletsync() noexcept +{ + using namespace SyncBulletsyncValidation; + + const unsigned long long ullNow = GetTickCount64_(); + + if (m_ullBulletsyncWindowStartMs == 0 || ullNow - m_ullBulletsyncWindowStartMs >= RATE_WINDOW_MS) + { + m_ullBulletsyncWindowStartMs = ullNow; + m_uiBulletsyncCountInWindow = 0; + } + + if (m_uiBulletsyncCountInWindow >= MAX_PACKETS_PER_SECOND) + return false; + + ++m_uiBulletsyncCountInWindow; + return true; +} + void CPlayer::SetPlayerStat(unsigned short usStat, float fValue) { m_pPlayerStatsPacket->Add(usStat, fValue); diff --git a/Server/mods/deathmatch/logic/CPlayer.h b/Server/mods/deathmatch/logic/CPlayer.h index b332f40eb12..7510637155e 100644 --- a/Server/mods/deathmatch/logic/CPlayer.h +++ b/Server/mods/deathmatch/logic/CPlayer.h @@ -263,6 +263,9 @@ class CPlayer final : public CPed, public CClient bool GetTeleported() const noexcept { return m_teleported; } void SetTeleported(bool state) noexcept { m_teleported = state; } + // Anti-cheat: cap forged bullet-sync floods (e.g. cheat loops SendBulletSync many times per shot). + bool TryAcceptBulletsync() noexcept; + protected: bool ReadSpecialData(const int iLine) override { return true; } @@ -463,4 +466,7 @@ class CPlayer final : public CPed, public CClient SString m_strQuitReasonForLog; bool m_teleported = false; + + unsigned long long m_ullBulletsyncWindowStartMs = 0; + unsigned int m_uiBulletsyncCountInWindow = 0; }; diff --git a/Server/mods/deathmatch/logic/net/CSimBulletsyncPacket.cpp b/Server/mods/deathmatch/logic/net/CSimBulletsyncPacket.cpp index 1d5c4735e95..5d37b1e6adc 100644 --- a/Server/mods/deathmatch/logic/net/CSimBulletsyncPacket.cpp +++ b/Server/mods/deathmatch/logic/net/CSimBulletsyncPacket.cpp @@ -11,6 +11,8 @@ #include "SimHeaders.h" #include "CPickupManager.h" #include "CWeaponStatManager.h" +#include "CElementIDs.h" +#include "SyncBulletsyncValidation.h" CSimBulletsyncPacket::CSimBulletsyncPacket(ElementID id) : m_id(id) { @@ -27,17 +29,26 @@ bool CSimBulletsyncPacket::Read(NetBitStreamInterface& stream) if (!stream.Read(reinterpret_cast(&m_cache.start), sizeof(CVector)) || !stream.Read(reinterpret_cast(&m_cache.end), sizeof(CVector))) return false; - if (!m_cache.start.IsValid() || !m_cache.end.IsValid()) + if (!SyncBulletsyncValidation::IsSyncedBulletSegmentNonDegenerate(m_cache.start, m_cache.end)) return false; if (!stream.Read(m_cache.order)) return false; + m_cache.damage = 0.0f; + m_cache.zone = 0; + m_cache.damaged = INVALID_ELEMENT_ID; + if (stream.ReadBit()) { - stream.Read(m_cache.damage); - stream.Read(m_cache.zone); - stream.Read(m_cache.damaged); + if (!stream.Read(m_cache.damage) || !stream.Read(m_cache.zone) || !stream.Read(m_cache.damaged)) + return false; + + if (!SyncBulletsyncValidation::IsSyncedBulletDamageAcceptable(m_cache.damage, m_cache.zone, m_cache.damaged)) + return false; + + if (m_cache.damaged != INVALID_ELEMENT_ID && !CElementIDs::GetElement(m_cache.damaged)) + return false; } return true; diff --git a/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp b/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp index 6ad6c25120a..1f21c9801b0 100644 --- a/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp +++ b/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp @@ -9,6 +9,8 @@ #include "StdInc.h" #include "SimHeaders.h" +#include "SyncBulletsyncValidation.h" +#include "CWeaponNames.h" // // CSimPlayer object is created on CPlayer construction @@ -343,7 +345,15 @@ bool CSimPlayerManager::HandleBulletSync(const NetServerPlayerID& socket, NetBit LockSimSystem(); auto* player = Get(socket); - if (!player || !player->IsJoined()) + if (!player || !player->IsJoined() || !player->m_pRealPlayer) + { + UnlockSimSystem(); + return true; + } + + CPlayer* pRealPlayer = player->m_pRealPlayer; + + if (!pRealPlayer->IsSpawned() || pRealPlayer->IsDead()) { UnlockSimSystem(); return true; @@ -356,7 +366,31 @@ bool CSimPlayerManager::HandleBulletSync(const NetServerPlayerID& socket, NetBit return true; } - if (!player->m_pRealPlayer->HasWeaponType(packet->m_cache.weapon)) + const auto weaponType = static_cast(packet->m_cache.weapon); + if (!pRealPlayer->HasWeaponType(weaponType)) + { + UnlockSimSystem(); + return true; + } + + const auto slot = CWeaponNames::GetSlotFromWeapon(weaponType); + if (pRealPlayer->GetWeaponTotalAmmo(slot) <= 0) + { + UnlockSimSystem(); + return true; + } + + const bool bInVehicle = pRealPlayer->GetOccupiedVehicle() != nullptr; + const float fWeaponRange = pRealPlayer->GetWeaponRangeFromSlot(slot); + + if (!SyncBulletsyncValidation::IsSyncedBulletsyncPacketAcceptable(pRealPlayer->GetPosition(), bInVehicle, packet->m_cache.start, packet->m_cache.end, + packet->m_cache.damage, packet->m_cache.zone, packet->m_cache.damaged, fWeaponRange)) + { + UnlockSimSystem(); + return true; + } + + if (!pRealPlayer->TryAcceptBulletsync()) { UnlockSimSystem(); return true; diff --git a/Server/mods/deathmatch/logic/net/CSimPlayerPuresyncPacket.cpp b/Server/mods/deathmatch/logic/net/CSimPlayerPuresyncPacket.cpp index 69f2f5e3e69..41637837535 100644 --- a/Server/mods/deathmatch/logic/net/CSimPlayerPuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/net/CSimPlayerPuresyncPacket.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "SimHeaders.h" #include "Utils.h" +#include "SyncBulletsyncValidation.h" #include "CWeaponNames.h" CSimPlayerPuresyncPacket::CSimPlayerPuresyncPacket(ElementID PlayerID, ushort PlayerLatency, uchar PlayerSyncTimeContext, uchar PlayerGotWeaponType, @@ -157,6 +158,10 @@ bool CSimPlayerPuresyncPacket::Read(NetBitStreamInterface& BitStream) // Read the aim data only if he's shooting or aiming if (sync.isFull()) { + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(m_Cache.Position, sync.data.vecOrigin, sync.data.vecTarget, m_WeaponRange, + false)) + return false; + m_Cache.vecSniperSource = sync.data.vecOrigin; m_Cache.vecTargetting = sync.data.vecTarget; m_Cache.bIsAimFull = true; diff --git a/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp b/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp index 2beb4dd92a8..f8faffe9820 100644 --- a/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp @@ -8,6 +8,7 @@ #include "StdInc.h" #include "SimHeaders.h" #include "Utils.h" +#include "SyncBulletsyncValidation.h" #include "CVehicleManager.h" #include "CWeaponNames.h" @@ -216,6 +217,11 @@ bool CSimVehiclePuresyncPacket::Read(NetBitStreamInterface& BitStream) SWeaponAimSync aim(m_fPlayerGotWeaponRange, true); if (!BitStream.Read(&aim)) return false; + + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(m_Cache.PlrPosition, aim.data.vecOrigin, aim.data.vecTarget, + m_fPlayerGotWeaponRange, true)) + return false; + m_Cache.fAimDirection = aim.data.fArm; m_Cache.vecSniperSource = aim.data.vecOrigin; m_Cache.vecTargetting = aim.data.vecTarget; diff --git a/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp index a185a2d892b..9b6d7b208a0 100644 --- a/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp @@ -16,6 +16,7 @@ #include "CElementIDs.h" #include "CElement.h" #include "CWeaponNames.h" +#include "SyncBulletsyncValidation.h" CBulletsyncPacket::CBulletsyncPacket(CPlayer* player) : m_weapon(WEAPONTYPE_UNARMED), m_start(), m_end(), m_order(0), m_damage(0.0f), m_zone(0), m_damaged(INVALID_ELEMENT_ID) @@ -25,19 +26,7 @@ CBulletsyncPacket::CBulletsyncPacket(CPlayer* player) bool CBulletsyncPacket::IsValidVector(const CVector& vec) noexcept { - if (!vec.IsValid()) - return false; - - if (IsNaN(vec.fX)) - return false; - - if (IsNaN(vec.fY)) - return false; - - if (IsNaN(vec.fZ)) - return false; - - return true; + return SyncBulletsyncValidation::IsSyncedBulletVectorAcceptable(vec); } bool CBulletsyncPacket::IsValidWeaponId(unsigned char weaponId) noexcept @@ -47,22 +36,7 @@ bool CBulletsyncPacket::IsValidWeaponId(unsigned char weaponId) noexcept bool CBulletsyncPacket::ValidateTrajectory() const noexcept { - const float dx = m_end.fX - m_start.fX; - const float dy = m_end.fY - m_start.fY; - const float dz = m_end.fZ - m_start.fZ; - - const float movementSq = (dx * dx) + (dy * dy) + (dz * dz); - - if (IsNaN(movementSq)) - return false; - - if (movementSq < MIN_DISTANCE_SQ) - return false; - - if (movementSq > MAX_DISTANCE_SQ) - return false; - - return true; + return SyncBulletsyncValidation::IsSyncedBulletSegmentNonDegenerate(m_start, m_end); } void CBulletsyncPacket::ResetDamageData() noexcept @@ -113,32 +87,12 @@ bool CBulletsyncPacket::ReadOptionalDamage(NetBitStreamInterface& stream) stream.Read(m_zone); stream.Read(m_damaged); - if (IsNaN(m_damage)) - { - ResetDamageData(); - return false; - } - - if (m_damage < 0.0f || m_damage > MAX_DAMAGE) + if (!SyncBulletsyncValidation::IsSyncedBulletDamageAcceptable(m_damage, m_zone, m_damaged)) { ResetDamageData(); return false; } - if (m_zone > MAX_BODY_ZONE) - { - ResetDamageData(); - return false; - } - - if (m_damaged == 0) - { - ResetDamageData(); - return false; - } - - // Check that target element exists (if specified) - // Note: m_damaged can be INVALID_ELEMENT_ID when shooting at ground/world if (m_damaged != INVALID_ELEMENT_ID) { CElement* pElement = CElementIDs::GetElement(m_damaged); @@ -147,7 +101,6 @@ bool CBulletsyncPacket::ReadOptionalDamage(NetBitStreamInterface& stream) ResetDamageData(); return false; } - // Element exists } return true; @@ -161,44 +114,27 @@ bool CBulletsyncPacket::Read(NetBitStreamInterface& stream) CPlayer* pPlayer = static_cast(m_pSourceElement); if (pPlayer) { - // Check if player is spawned and alive if (!pPlayer->IsSpawned() || pPlayer->IsDead()) return false; - - // Check player position is reasonable relative to bullet start - const CVector& playerPos = pPlayer->GetPosition(); - const float maxShootDistance = 50.0f; // Max distance from player to bullet start - - // This check will be done after we read positions } if (!ReadWeaponAndPositions(stream)) return false; - // Now validate player position relative to shot origin if (pPlayer) { - const CVector& playerPos = pPlayer->GetPosition(); - float dx = m_start.fX - playerPos.fX; - float dy = m_start.fY - playerPos.fY; - float dz = m_start.fZ - playerPos.fZ; - float distSq = dx * dx + dy * dy + dz * dz; - - // Allow larger distance if player is in vehicle (vehicle guns like Hunter have offsets of ~5m, - // plus vehicle size, plus network lag compensation) - const float maxShootDistanceSq = pPlayer->GetOccupiedVehicle() ? (100.0f * 100.0f) : (50.0f * 50.0f); - if (distSq > maxShootDistanceSq) - return false; + const auto type = static_cast(m_weapon); + const auto slot = CWeaponNames::GetSlotFromWeapon(type); - // Check if player has this weapon - if (!pPlayer->HasWeaponType(static_cast(m_weapon))) + if (!pPlayer->HasWeaponType(type)) return false; - // Check if weapon has ammo - const auto type = static_cast(m_weapon); - const auto slot = CWeaponNames::GetSlotFromWeapon(type); if (pPlayer->GetWeaponTotalAmmo(slot) <= 0) return false; + + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(pPlayer->GetPosition(), pPlayer->GetOccupiedVehicle() != nullptr, m_start, + m_end, pPlayer->GetWeaponRangeFromSlot(slot))) + return false; } if (!stream.Read(m_order)) diff --git a/Server/mods/deathmatch/logic/packets/CCustomWeaponBulletSyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CCustomWeaponBulletSyncPacket.cpp index 83b37ef062f..850820718a2 100644 --- a/Server/mods/deathmatch/logic/packets/CCustomWeaponBulletSyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CCustomWeaponBulletSyncPacket.cpp @@ -12,6 +12,7 @@ #include "net/SyncStructures.h" #include "CPlayer.h" #include "lua/CLuaFunctionParseHelpers.h" +#include "SyncBulletsyncValidation.h" CCustomWeaponBulletSyncPacket::CCustomWeaponBulletSyncPacket(CPlayer* player) { @@ -32,7 +33,7 @@ bool CCustomWeaponBulletSyncPacket::Read(NetBitStreamInterface& stream) if (!stream.Read(reinterpret_cast(&m_start), sizeof(CVector)) || !stream.Read(reinterpret_cast(&m_end), sizeof(CVector))) return false; - if (!m_start.IsValid() || !m_end.IsValid()) + if (!SyncBulletsyncValidation::IsSyncedBulletSegmentNonDegenerate(m_start, m_end)) return false; if (!stream.Read(m_order)) diff --git a/Server/mods/deathmatch/logic/packets/CKeysyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CKeysyncPacket.cpp index 0987ab189fc..bbc8fdbbd42 100644 --- a/Server/mods/deathmatch/logic/packets/CKeysyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CKeysyncPacket.cpp @@ -16,6 +16,7 @@ #include "CVehicleManager.h" #include "net/SyncStructures.h" #include "Utils.h" +#include "SyncBulletsyncValidation.h" CKeysyncPacket::CKeysyncPacket(CPlayer* pPlayer) { @@ -106,6 +107,12 @@ bool CKeysyncPacket::Read(NetBitStreamInterface& BitStream) SWeaponAimSync aim(fWeaponRange); if (!BitStream.Read(&aim)) return false; + + if (bWeaponCorrect && + !SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(pSourcePlayer->GetPosition(), aim.data.vecOrigin, aim.data.vecTarget, + fWeaponRange, pSourcePlayer->GetOccupiedVehicle() != nullptr)) + return false; + pSourcePlayer->SetSniperSourceVector(aim.data.vecOrigin); pSourcePlayer->SetTargettingVector(aim.data.vecTarget); diff --git a/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp index cb05c2138f7..4ca3b9b855a 100644 --- a/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp @@ -14,6 +14,7 @@ #include "CElementIDs.h" #include "CWeaponNames.h" #include "Utils.h" +#include "SyncBulletsyncValidation.h" #include "CTickRateSettings.h" #include @@ -279,6 +280,11 @@ bool CPlayerPuresyncPacket::Read(NetBitStreamInterface& BitStream) // Read the aim data only if he's shooting or aiming if (sync.isFull()) { + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable( + position.data.vecPosition, sync.data.vecOrigin, sync.data.vecTarget, fWeaponRange, + pSourcePlayer->GetOccupiedVehicle() != nullptr)) + return false; + pSourcePlayer->SetSniperSourceVector(sync.data.vecOrigin); pSourcePlayer->SetTargettingVector(sync.data.vecTarget); } diff --git a/Server/mods/deathmatch/logic/packets/CVehiclePuresyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CVehiclePuresyncPacket.cpp index 6f1cbdca3df..ceb9d2a8c4e 100644 --- a/Server/mods/deathmatch/logic/packets/CVehiclePuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CVehiclePuresyncPacket.cpp @@ -16,6 +16,7 @@ #include "CTrainTrackManager.h" #include "CWeaponNames.h" #include "Utils.h" +#include "SyncBulletsyncValidation.h" #include "lua/CLuaFunctionParseHelpers.h" #include "net/SyncStructures.h" @@ -378,6 +379,11 @@ bool CVehiclePuresyncPacket::Read(NetBitStreamInterface& BitStream) SWeaponAimSync aim(fWeaponRange, true); if (!BitStream.Read(&aim)) return false; + + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(pSourcePlayer->GetPosition(), aim.data.vecOrigin, aim.data.vecTarget, + fWeaponRange, true)) + return false; + pSourcePlayer->SetAimDirection(aim.data.fArm); pSourcePlayer->SetSniperSourceVector(aim.data.vecOrigin); pSourcePlayer->SetTargettingVector(aim.data.vecTarget); diff --git a/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h new file mode 100644 index 00000000000..355c8bdf97d --- /dev/null +++ b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h @@ -0,0 +1,172 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: mods/deathmatch/logic/SyncBulletsyncValidation.h + * PURPOSE: Player-relative bullet sync and weapon-aim validation + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CCommon.h" +#include "CVector.h" +#include + +namespace SyncBulletsyncValidation +{ + // Degenerate segment guard (SetTargetTarget divides by origin-target distance on clients). + inline constexpr float MIN_TRAJECTORY_DISTANCE_SQ = 0.0001f; + + // Physical muzzle/mount offsets from the ped or seated sync point — not gameplay caps. + inline constexpr float PED_WEAPON_ORIGIN_MAX_OFFSET = 5.0f; + inline constexpr float VEHICLE_WEAPON_ORIGIN_MAX_OFFSET = 25.0f; + + inline constexpr float MAX_DAMAGE = 200.0f; + inline constexpr unsigned char MAX_BODY_ZONE = 9; + inline constexpr float WEAPON_RANGE_TOLERANCE = 1.15f; + inline constexpr unsigned int MAX_PACKETS_PER_SECOND = 25; + inline constexpr unsigned long long RATE_WINDOW_MS = 1000; + + inline float DistanceSquared3D(const CVector& vecA, const CVector& vecB) noexcept + { + const float fDx = vecA.fX - vecB.fX; + const float fDy = vecA.fY - vecB.fY; + const float fDz = vecA.fZ - vecB.fZ; + return (fDx * fDx) + (fDy * fDy) + (fDz * fDz); + } + + inline bool IsNaN(float fValue) noexcept + { + return fValue != fValue; + } + + inline bool IsSyncedBulletVectorAcceptable(const CVector& vec) noexcept + { + return vec.IsValid(); + } + + inline float GetMaxBulletOriginOffsetFromPlayer(bool bInVehicle) noexcept + { + return bInVehicle ? VEHICLE_WEAPON_ORIGIN_MAX_OFFSET : PED_WEAPON_ORIGIN_MAX_OFFSET; + } + + inline float GetMaxBulletReachFromPlayer(bool bInVehicle, float fWeaponRange) noexcept + { + const float fRange = fWeaponRange > 0.0f ? fWeaponRange * WEAPON_RANGE_TOLERANCE : 0.0f; + return GetMaxBulletOriginOffsetFromPlayer(bInVehicle) + fRange; + } + + inline bool IsSyncedBulletSegmentNonDegenerate(const CVector& vecStart, const CVector& vecEnd) noexcept + { + if (!IsSyncedBulletVectorAcceptable(vecStart) || !IsSyncedBulletVectorAcceptable(vecEnd)) + return false; + + const float fMovementSq = DistanceSquared3D(vecStart, vecEnd); + + if (IsNaN(fMovementSq)) + return false; + + return fMovementSq >= MIN_TRAJECTORY_DISTANCE_SQ; + } + + inline bool IsSyncedBulletOriginPlausible(const CVector& vecOrigin, const CVector& vecPlayerPosition, bool bInVehicle) noexcept + { + if (!IsSyncedBulletVectorAcceptable(vecOrigin) || !IsSyncedBulletVectorAcceptable(vecPlayerPosition)) + return false; + + const float fMaxOffset = GetMaxBulletOriginOffsetFromPlayer(bInVehicle); + return DistanceSquared3D(vecOrigin, vecPlayerPosition) <= (fMaxOffset * fMaxOffset); + } + + inline bool IsSyncedBulletImpactPlausible(const CVector& vecImpact, const CVector& vecPlayerPosition, bool bInVehicle, float fWeaponRange) noexcept + { + if (!IsSyncedBulletVectorAcceptable(vecImpact) || !IsSyncedBulletVectorAcceptable(vecPlayerPosition)) + return false; + + const float fMaxReach = GetMaxBulletReachFromPlayer(bInVehicle, fWeaponRange); + return DistanceSquared3D(vecImpact, vecPlayerPosition) <= (fMaxReach * fMaxReach); + } + + inline bool IsSyncedBulletSegmentWithinWeaponRange(const CVector& vecStart, const CVector& vecEnd, float fWeaponRange) noexcept + { + if (!IsSyncedBulletSegmentNonDegenerate(vecStart, vecEnd)) + return false; + + if (fWeaponRange <= 0.0f) + return true; + + const float fMaxRange = fWeaponRange * WEAPON_RANGE_TOLERANCE; + return DistanceSquared3D(vecStart, vecEnd) <= (fMaxRange * fMaxRange); + } + + inline bool IsSyncedBulletsyncGeometryAcceptable(const CVector& vecPlayerPosition, bool bInVehicle, const CVector& vecStart, const CVector& vecEnd, + float fWeaponRange) noexcept + { + if (!IsSyncedBulletOriginPlausible(vecStart, vecPlayerPosition, bInVehicle)) + return false; + + if (!IsSyncedBulletSegmentWithinWeaponRange(vecStart, vecEnd, fWeaponRange)) + return false; + + if (!IsSyncedBulletImpactPlausible(vecEnd, vecPlayerPosition, bInVehicle, fWeaponRange)) + return false; + + return true; + } + + inline bool IsSyncedBulletDamageAcceptable(float fDamage, unsigned char ucZone, ElementID damagedElementId) noexcept + { + if (IsNaN(fDamage)) + return false; + + if (fDamage <= 0.0f) + return true; + + if (fDamage > MAX_DAMAGE) + return false; + + if (ucZone > MAX_BODY_ZONE) + return false; + + if (damagedElementId == 0) + return false; + + return true; + } + + inline bool IsSyncedWeaponAimAcceptable(const CVector& vecPlayerPosition, const CVector& vecOrigin, const CVector& vecTarget, float fWeaponRange, + bool bInVehicle) noexcept + { + return IsSyncedBulletsyncGeometryAcceptable(vecPlayerPosition, bInVehicle, vecOrigin, vecTarget, fWeaponRange); + } + + inline bool IsSyncedBulletsyncPacketAcceptable(const CVector& vecPlayerPosition, bool bInVehicle, const CVector& vecStart, const CVector& vecEnd, + float fDamage, unsigned char ucZone, ElementID damagedElementId, float fWeaponRange) noexcept + { + if (!IsSyncedBulletsyncGeometryAcceptable(vecPlayerPosition, bInVehicle, vecStart, vecEnd, fWeaponRange)) + return false; + + if (!IsSyncedBulletDamageAcceptable(fDamage, ucZone, damagedElementId)) + return false; + + return true; + } + + inline bool IsSyncedBulletTrajectoryAcceptable(const CVector& vecStart, const CVector& vecEnd) noexcept + { + return IsSyncedBulletSegmentNonDegenerate(vecStart, vecEnd); + } + + inline bool IsSyncedBulletOriginNearPlayer(const CVector& vecStart, const CVector& vecPlayerPosition, bool bInVehicle) noexcept + { + return IsSyncedBulletOriginPlausible(vecStart, vecPlayerPosition, bInVehicle); + } + + inline bool IsSyncedBulletWithinWeaponRange(const CVector& vecStart, const CVector& vecEnd, float fWeaponRange) noexcept + { + return IsSyncedBulletSegmentWithinWeaponRange(vecStart, vecEnd, fWeaponRange); + } +} From 2c115a5cbe9147762af9f444ef46ba8ee94ceb56 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:17:48 +0300 Subject: [PATCH 2/5] Refactor bulletsync validation checks for improved readability This commit enhances the formatting of bulletsync validation checks across multiple files, ensuring consistent indentation and spacing. The changes primarily focus on improving code clarity without altering functionality. Key files modified include CNetAPI.cpp, CGame.cpp, CSimPlayerManager.cpp, and various packet handling files. --- Client/mods/deathmatch/logic/CNetAPI.cpp | 2 +- Server/mods/deathmatch/logic/CGame.cpp | 10 +++++----- Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp | 4 ++-- .../deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp | 4 ++-- .../deathmatch/logic/packets/CBulletsyncPacket.cpp | 4 ++-- .../deathmatch/logic/packets/CPlayerPuresyncPacket.cpp | 5 ++--- .../mods/deathmatch/logic/SyncBulletsyncValidation.h | 8 ++++---- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Client/mods/deathmatch/logic/CNetAPI.cpp b/Client/mods/deathmatch/logic/CNetAPI.cpp index dc3162ebe27..6daa9a61688 100644 --- a/Client/mods/deathmatch/logic/CNetAPI.cpp +++ b/Client/mods/deathmatch/logic/CNetAPI.cpp @@ -2318,7 +2318,7 @@ void CNetAPI::ReadBulletsync(CClientPlayer* player, NetBitStreamInterface& strea CWeaponStat* pWeaponStat = g_pGame->GetWeaponStatManager()->GetOriginalWeaponStats(type); const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, start, end, - fWeaponRange)) + fWeaponRange)) return; std::uint8_t order = 0; diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index b92c500897d..dae2e59667e 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -2572,13 +2572,13 @@ void CGame::Packet_Bulletsync(CBulletsyncPacket& packet) if (player->GetWeaponTotalAmmo(slot) <= 0) return; - const auto stat = CWeaponStatManager::GetSkillStatIndex(packet.m_weapon); - const auto level = player->GetPlayerStat(stat); - auto* stats = g_pGame->GetWeaponStatManager()->GetWeaponStatsFromSkillLevel(packet.m_weapon, level); + const auto stat = CWeaponStatManager::GetSkillStatIndex(packet.m_weapon); + const auto level = player->GetPlayerStat(stat); + auto* stats = g_pGame->GetWeaponStatManager()->GetWeaponStatsFromSkillLevel(packet.m_weapon, level); const float fWeaponRange = stats->GetWeaponRange(); if (!SyncBulletsyncValidation::IsSyncedBulletsyncPacketAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, packet.m_start, - packet.m_end, packet.m_damage, packet.m_zone, packet.m_damaged, fWeaponRange)) + packet.m_end, packet.m_damage, packet.m_zone, packet.m_damaged, fWeaponRange)) return; CLuaArguments args; @@ -2622,7 +2622,7 @@ void CGame::Packet_WeaponBulletsync(CCustomWeaponBulletSyncPacket& packet) CWeaponStat* pWeaponStat = g_pGame->GetWeaponStatManager()->GetOriginalWeaponStats(weapon->GetWeaponType()); const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, packet.m_start, - packet.m_end, fWeaponRange)) + packet.m_end, fWeaponRange)) return; CLuaArguments args; diff --git a/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp b/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp index 1f21c9801b0..b00a37116c5 100644 --- a/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp +++ b/Server/mods/deathmatch/logic/net/CSimPlayerManager.cpp @@ -380,11 +380,11 @@ bool CSimPlayerManager::HandleBulletSync(const NetServerPlayerID& socket, NetBit return true; } - const bool bInVehicle = pRealPlayer->GetOccupiedVehicle() != nullptr; + const bool bInVehicle = pRealPlayer->GetOccupiedVehicle() != nullptr; const float fWeaponRange = pRealPlayer->GetWeaponRangeFromSlot(slot); if (!SyncBulletsyncValidation::IsSyncedBulletsyncPacketAcceptable(pRealPlayer->GetPosition(), bInVehicle, packet->m_cache.start, packet->m_cache.end, - packet->m_cache.damage, packet->m_cache.zone, packet->m_cache.damaged, fWeaponRange)) + packet->m_cache.damage, packet->m_cache.zone, packet->m_cache.damaged, fWeaponRange)) { UnlockSimSystem(); return true; diff --git a/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp b/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp index f8faffe9820..423e427f592 100644 --- a/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/net/CSimVehiclePuresyncPacket.cpp @@ -218,8 +218,8 @@ bool CSimVehiclePuresyncPacket::Read(NetBitStreamInterface& BitStream) if (!BitStream.Read(&aim)) return false; - if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(m_Cache.PlrPosition, aim.data.vecOrigin, aim.data.vecTarget, - m_fPlayerGotWeaponRange, true)) + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(m_Cache.PlrPosition, aim.data.vecOrigin, aim.data.vecTarget, m_fPlayerGotWeaponRange, + true)) return false; m_Cache.fAimDirection = aim.data.fArm; diff --git a/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp index 9b6d7b208a0..af3fbe20190 100644 --- a/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CBulletsyncPacket.cpp @@ -132,8 +132,8 @@ bool CBulletsyncPacket::Read(NetBitStreamInterface& stream) if (pPlayer->GetWeaponTotalAmmo(slot) <= 0) return false; - if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(pPlayer->GetPosition(), pPlayer->GetOccupiedVehicle() != nullptr, m_start, - m_end, pPlayer->GetWeaponRangeFromSlot(slot))) + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(pPlayer->GetPosition(), pPlayer->GetOccupiedVehicle() != nullptr, m_start, m_end, + pPlayer->GetWeaponRangeFromSlot(slot))) return false; } diff --git a/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp b/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp index 4ca3b9b855a..7f9adeffbca 100644 --- a/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp @@ -280,9 +280,8 @@ bool CPlayerPuresyncPacket::Read(NetBitStreamInterface& BitStream) // Read the aim data only if he's shooting or aiming if (sync.isFull()) { - if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable( - position.data.vecPosition, sync.data.vecOrigin, sync.data.vecTarget, fWeaponRange, - pSourcePlayer->GetOccupiedVehicle() != nullptr)) + if (!SyncBulletsyncValidation::IsSyncedWeaponAimAcceptable(position.data.vecPosition, sync.data.vecOrigin, sync.data.vecTarget, + fWeaponRange, pSourcePlayer->GetOccupiedVehicle() != nullptr)) return false; pSourcePlayer->SetSniperSourceVector(sync.data.vecOrigin); diff --git a/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h index 355c8bdf97d..9ff13311135 100644 --- a/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h +++ b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h @@ -24,10 +24,10 @@ namespace SyncBulletsyncValidation inline constexpr float PED_WEAPON_ORIGIN_MAX_OFFSET = 5.0f; inline constexpr float VEHICLE_WEAPON_ORIGIN_MAX_OFFSET = 25.0f; - inline constexpr float MAX_DAMAGE = 200.0f; - inline constexpr unsigned char MAX_BODY_ZONE = 9; - inline constexpr float WEAPON_RANGE_TOLERANCE = 1.15f; - inline constexpr unsigned int MAX_PACKETS_PER_SECOND = 25; + inline constexpr float MAX_DAMAGE = 200.0f; + inline constexpr unsigned char MAX_BODY_ZONE = 9; + inline constexpr float WEAPON_RANGE_TOLERANCE = 1.15f; + inline constexpr unsigned int MAX_PACKETS_PER_SECOND = 25; inline constexpr unsigned long long RATE_WINDOW_MS = 1000; inline float DistanceSquared3D(const CVector& vecA, const CVector& vecB) noexcept From 8d1695fe542b955def324aabe5894f5341c9b2d4 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:34:51 +0300 Subject: [PATCH 3/5] Fix C1083: use Shared Common.h in SyncBulletsyncValidation --- Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h index 9ff13311135..4a7b007bf47 100644 --- a/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h +++ b/Shared/mods/deathmatch/logic/SyncBulletsyncValidation.h @@ -11,8 +11,8 @@ #pragma once -#include "CCommon.h" -#include "CVector.h" +#include +#include #include namespace SyncBulletsyncValidation From a0826b454706765918f11580ed288f8b69ec1daf Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:14:27 +0300 Subject: [PATCH 4/5] Fix CNetAPI GetPosition call for bulletsync validation --- .vscode/settings.json | 1 - Client/mods/deathmatch/logic/CNetAPI.cpp | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f0759d01ad0..f27f5f2511a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,4 @@ "powershell.codeFormatting.openBraceOnSameLine": true, "powershell.codeFormatting.newLineAfterCloseBrace": false, - "C_Cpp.default.compilerPath": "c:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\MSVC\\14.51.36231\\bin\\Hostx86\\x64\\cl.exe", } diff --git a/Client/mods/deathmatch/logic/CNetAPI.cpp b/Client/mods/deathmatch/logic/CNetAPI.cpp index 6daa9a61688..080d895aedc 100644 --- a/Client/mods/deathmatch/logic/CNetAPI.cpp +++ b/Client/mods/deathmatch/logic/CNetAPI.cpp @@ -2317,7 +2317,9 @@ void CNetAPI::ReadBulletsync(CClientPlayer* player, NetBitStreamInterface& strea CWeaponStat* pWeaponStat = g_pGame->GetWeaponStatManager()->GetOriginalWeaponStats(type); const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; - if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(player->GetPosition(), player->GetOccupiedVehicle() != nullptr, start, end, + CVector playerPosition; + player->GetPosition(playerPosition); + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(playerPosition, player->GetOccupiedVehicle() != nullptr, start, end, fWeaponRange)) return; From 83ad65d65b5c1c2166909880b28ecf79833c4d19 Mon Sep 17 00:00:00 2001 From: Batuhan Tonga <76632145+QueryOfficial@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:09:06 +0300 Subject: [PATCH 5/5] syntax fixed --- Client/mods/deathmatch/logic/CNetAPI.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/mods/deathmatch/logic/CNetAPI.cpp b/Client/mods/deathmatch/logic/CNetAPI.cpp index 080d895aedc..f93209d3c47 100644 --- a/Client/mods/deathmatch/logic/CNetAPI.cpp +++ b/Client/mods/deathmatch/logic/CNetAPI.cpp @@ -2319,8 +2319,7 @@ void CNetAPI::ReadBulletsync(CClientPlayer* player, NetBitStreamInterface& strea const float fWeaponRange = pWeaponStat ? pWeaponStat->GetWeaponRange() : 0.0f; CVector playerPosition; player->GetPosition(playerPosition); - if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(playerPosition, player->GetOccupiedVehicle() != nullptr, start, end, - fWeaponRange)) + if (!SyncBulletsyncValidation::IsSyncedBulletsyncGeometryAcceptable(playerPosition, player->GetOccupiedVehicle() != nullptr, start, end, fWeaponRange)) return; std::uint8_t order = 0;