mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/anticheat' into 'master'
Improved Anticheat See merge request veloren/veloren!3993
This commit is contained in:
commit
2a4187e769
@ -9,7 +9,7 @@ use common::{
|
|||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
link::Is,
|
link::Is,
|
||||||
mounting::{Rider, VolumeRider},
|
mounting::{Rider, VolumeRider},
|
||||||
resources::{PlayerPhysicsSetting, PlayerPhysicsSettings},
|
resources::{DeltaTime, PlayerPhysicsSetting, PlayerPhysicsSettings},
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::TerrainGrid,
|
terrain::TerrainGrid,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
@ -53,13 +53,11 @@ impl Sys {
|
|||||||
can_build: &ReadStorage<'_, CanBuild>,
|
can_build: &ReadStorage<'_, CanBuild>,
|
||||||
is_rider: &ReadStorage<'_, Is<Rider>>,
|
is_rider: &ReadStorage<'_, Is<Rider>>,
|
||||||
is_volume_rider: &ReadStorage<'_, Is<VolumeRider>>,
|
is_volume_rider: &ReadStorage<'_, Is<VolumeRider>>,
|
||||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
force_update: Option<&&mut ForceUpdate>,
|
||||||
skill_set: &mut Option<Cow<'_, SkillSet>>,
|
skill_set: &mut Option<Cow<'_, SkillSet>>,
|
||||||
healths: &ReadStorage<'_, Health>,
|
healths: &ReadStorage<'_, Health>,
|
||||||
rare_writes: &parking_lot::Mutex<RareWrites<'_, '_>>,
|
rare_writes: &parking_lot::Mutex<RareWrites<'_, '_>>,
|
||||||
position: Option<&mut Pos>,
|
position: Option<&mut Pos>,
|
||||||
velocity: Option<&mut Vel>,
|
|
||||||
orientation: Option<&mut Ori>,
|
|
||||||
controller: Option<&mut Controller>,
|
controller: Option<&mut Controller>,
|
||||||
settings: &Read<'_, Settings>,
|
settings: &Read<'_, Settings>,
|
||||||
build_areas: &Read<'_, AreasContainer<BuildArea>>,
|
build_areas: &Read<'_, AreasContainer<BuildArea>>,
|
||||||
@ -67,6 +65,7 @@ impl Sys {
|
|||||||
maybe_admin: &Option<&Admin>,
|
maybe_admin: &Option<&Admin>,
|
||||||
time_for_vd_changes: Instant,
|
time_for_vd_changes: Instant,
|
||||||
msg: ClientGeneral,
|
msg: ClientGeneral,
|
||||||
|
player_physics: &mut Option<(Pos, Vel, Ori)>,
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
let presence = match maybe_presence.as_deref_mut() {
|
let presence = match maybe_presence.as_deref_mut() {
|
||||||
Some(g) => g,
|
Some(g) => g,
|
||||||
@ -121,7 +120,7 @@ impl Sys {
|
|||||||
},
|
},
|
||||||
ClientGeneral::PlayerPhysics { pos, vel, ori, force_counter } => {
|
ClientGeneral::PlayerPhysics { pos, vel, ori, force_counter } => {
|
||||||
if presence.kind.controlling_char()
|
if presence.kind.controlling_char()
|
||||||
&& force_updates.get(entity).map_or(true, |force_update| force_update.counter() == force_counter)
|
&& force_update.map_or(true, |force_update| force_update.counter() == force_counter)
|
||||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||||
&& is_rider.get(entity).is_none()
|
&& is_rider.get(entity).is_none()
|
||||||
&& is_volume_rider.get(entity).is_none()
|
&& is_volume_rider.get(entity).is_none()
|
||||||
@ -129,75 +128,7 @@ impl Sys {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |s| s.client_authoritative())
|
.map_or(true, |s| s.client_authoritative())
|
||||||
{
|
{
|
||||||
enum Rejection {
|
*player_physics = Some((pos, vel, ori));
|
||||||
TooFar { old: Vec3<f32>, new: Vec3<f32> },
|
|
||||||
TooFast { vel: Vec3<f32> },
|
|
||||||
}
|
|
||||||
|
|
||||||
let rejection = if maybe_admin.is_some() {
|
|
||||||
None
|
|
||||||
} else if let Some(mut setting) = player_physics_setting {
|
|
||||||
// If we detect any thresholds being exceeded, force server-authoritative
|
|
||||||
// physics for that player. This doesn't detect subtle hacks, but it
|
|
||||||
// prevents blatant ones and forces people to not debug physics hacks on the
|
|
||||||
// live server (and also mitigates some floating-point overflow crashes)
|
|
||||||
let rejection = None
|
|
||||||
// Check position
|
|
||||||
.or_else(|| {
|
|
||||||
if let Some(prev_pos) = &position {
|
|
||||||
if prev_pos.0.distance_squared(pos.0) > (500.0f32).powf(2.0) {
|
|
||||||
Some(Rejection::TooFar { old: prev_pos.0, new: pos.0 })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Check velocity
|
|
||||||
.or_else(|| {
|
|
||||||
if vel.0.magnitude_squared() > (500.0f32).powf(2.0) {
|
|
||||||
Some(Rejection::TooFast { vel: vel.0 })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force a client-side physics update if rejectable physics data is
|
|
||||||
// received.
|
|
||||||
if rejection.is_some() {
|
|
||||||
// We skip this for `TooFar` because false positives can occur when
|
|
||||||
// using server-side teleportation commands
|
|
||||||
// that the client doesn't know about (leading to the client sending
|
|
||||||
// physics state that disagree with the server). In the future,
|
|
||||||
// client-authoritative physics will be gone
|
|
||||||
// and this will no longer be necessary.
|
|
||||||
setting.server_force =
|
|
||||||
!matches!(rejection, Some(Rejection::TooFar { .. })); // true;
|
|
||||||
}
|
|
||||||
|
|
||||||
rejection
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
match rejection {
|
|
||||||
Some(Rejection::TooFar { old, new }) => warn!(
|
|
||||||
"Rejected player physics update (new position {:?} is too far from \
|
|
||||||
old position {:?})",
|
|
||||||
new, old
|
|
||||||
),
|
|
||||||
Some(Rejection::TooFast { vel }) => warn!(
|
|
||||||
"Rejected player physics update (new velocity {:?} is too fast)",
|
|
||||||
vel
|
|
||||||
),
|
|
||||||
None => {
|
|
||||||
// Don't insert unless the component already exists
|
|
||||||
position.map(|p| *p = pos);
|
|
||||||
velocity.map(|v| *v = vel);
|
|
||||||
orientation.map(|o| *o = ori);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientGeneral::BreakBlock(pos) => {
|
ClientGeneral::BreakBlock(pos) => {
|
||||||
@ -319,7 +250,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
ReadExpect<'a, SlowJobPool>,
|
ReadExpect<'a, SlowJobPool>,
|
||||||
ReadStorage<'a, CanBuild>,
|
ReadStorage<'a, CanBuild>,
|
||||||
ReadStorage<'a, ForceUpdate>,
|
WriteStorage<'a, ForceUpdate>,
|
||||||
ReadStorage<'a, Is<Rider>>,
|
ReadStorage<'a, Is<Rider>>,
|
||||||
ReadStorage<'a, Is<VolumeRider>>,
|
ReadStorage<'a, Is<VolumeRider>>,
|
||||||
WriteStorage<'a, SkillSet>,
|
WriteStorage<'a, SkillSet>,
|
||||||
@ -331,6 +262,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, Presence>,
|
WriteStorage<'a, Presence>,
|
||||||
WriteStorage<'a, Client>,
|
WriteStorage<'a, Client>,
|
||||||
WriteStorage<'a, Controller>,
|
WriteStorage<'a, Controller>,
|
||||||
|
Read<'a, DeltaTime>,
|
||||||
Read<'a, Settings>,
|
Read<'a, Settings>,
|
||||||
Read<'a, AreasContainer<BuildArea>>,
|
Read<'a, AreasContainer<BuildArea>>,
|
||||||
Write<'a, PlayerPhysicsSettings>,
|
Write<'a, PlayerPhysicsSettings>,
|
||||||
@ -351,7 +283,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
terrain,
|
terrain,
|
||||||
slow_jobs,
|
slow_jobs,
|
||||||
can_build,
|
can_build,
|
||||||
force_updates,
|
mut force_updates,
|
||||||
is_rider,
|
is_rider,
|
||||||
is_volume_rider,
|
is_volume_rider,
|
||||||
mut skill_sets,
|
mut skill_sets,
|
||||||
@ -363,6 +295,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut presences,
|
mut presences,
|
||||||
mut clients,
|
mut clients,
|
||||||
mut controllers,
|
mut controllers,
|
||||||
|
dt,
|
||||||
settings,
|
settings,
|
||||||
build_areas,
|
build_areas,
|
||||||
mut player_physics_settings_,
|
mut player_physics_settings_,
|
||||||
@ -392,6 +325,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
(&mut velocities).maybe(),
|
(&mut velocities).maybe(),
|
||||||
(&mut orientations).maybe(),
|
(&mut orientations).maybe(),
|
||||||
(&mut controllers).maybe(),
|
(&mut controllers).maybe(),
|
||||||
|
(&mut force_updates).maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
// NOTE: Required because Specs has very poor work splitting for sparse joins.
|
// NOTE: Required because Specs has very poor work splitting for sparse joins.
|
||||||
@ -409,6 +343,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ref mut vel,
|
ref mut vel,
|
||||||
ref mut ori,
|
ref mut ori,
|
||||||
ref mut controller,
|
ref mut controller,
|
||||||
|
ref mut force_update,
|
||||||
)| {
|
)| {
|
||||||
let old_player_physics_setting = maybe_player.map(|p| {
|
let old_player_physics_setting = maybe_player.map(|p| {
|
||||||
player_physics_settings
|
player_physics_settings
|
||||||
@ -422,6 +357,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// ingame messages to be ignored.
|
// ingame messages to be ignored.
|
||||||
let mut clearable_maybe_presence = maybe_presence.as_deref_mut();
|
let mut clearable_maybe_presence = maybe_presence.as_deref_mut();
|
||||||
let mut skill_set = skill_set.map(Cow::Borrowed);
|
let mut skill_set = skill_set.map(Cow::Borrowed);
|
||||||
|
let mut player_physics = None;
|
||||||
let _ = super::try_recv_all(client, 2, |client, msg| {
|
let _ = super::try_recv_all(client, 2, |client, msg| {
|
||||||
Self::handle_client_in_game_msg(
|
Self::handle_client_in_game_msg(
|
||||||
server_emitter,
|
server_emitter,
|
||||||
@ -432,13 +368,11 @@ impl<'a> System<'a> for Sys {
|
|||||||
&can_build,
|
&can_build,
|
||||||
&is_rider,
|
&is_rider,
|
||||||
&is_volume_rider,
|
&is_volume_rider,
|
||||||
&force_updates,
|
force_update.as_ref(),
|
||||||
&mut skill_set,
|
&mut skill_set,
|
||||||
&healths,
|
&healths,
|
||||||
&rare_writes,
|
&rare_writes,
|
||||||
pos.as_deref_mut(),
|
pos.as_deref_mut(),
|
||||||
vel.as_deref_mut(),
|
|
||||||
ori.as_deref_mut(),
|
|
||||||
controller.as_deref_mut(),
|
controller.as_deref_mut(),
|
||||||
&settings,
|
&settings,
|
||||||
&build_areas,
|
&build_areas,
|
||||||
@ -446,9 +380,86 @@ impl<'a> System<'a> for Sys {
|
|||||||
&maybe_admin,
|
&maybe_admin,
|
||||||
time_for_vd_changes,
|
time_for_vd_changes,
|
||||||
msg,
|
msg,
|
||||||
|
&mut player_physics,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some((new_pos, new_vel, new_ori)) = player_physics
|
||||||
|
&& let Some(old_pos) = pos.as_deref_mut()
|
||||||
|
&& let Some(old_vel) = vel.as_deref_mut()
|
||||||
|
&& let Some(old_ori) = ori.as_deref_mut()
|
||||||
|
{
|
||||||
|
enum Rejection {
|
||||||
|
TooFar { old: Vec3<f32>, new: Vec3<f32> },
|
||||||
|
TooFast { vel: Vec3<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
let rejection = if maybe_admin.is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// Reminder: review these frequently to ensure they're reasonable
|
||||||
|
const MAX_H_VELOCITY: f32 = 75.0;
|
||||||
|
const MAX_V_VELOCITY: std::ops::Range<f32> = -100.0..80.0;
|
||||||
|
|
||||||
|
'rejection: {
|
||||||
|
let is_velocity_ok = new_vel.0.xy().magnitude_squared() < MAX_H_VELOCITY.powi(2)
|
||||||
|
&& MAX_V_VELOCITY.contains(&new_vel.0.z);
|
||||||
|
|
||||||
|
if !is_velocity_ok {
|
||||||
|
break 'rejection Some(Rejection::TooFast { vel: new_vel.0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The position can either be sensible with respect to either the old or the new
|
||||||
|
// velocity such that we don't punish for edge cases after a sudden change
|
||||||
|
let is_position_ok = [old_vel.0, new_vel.0]
|
||||||
|
.into_iter()
|
||||||
|
.any(|ref_vel| {
|
||||||
|
let rpos = new_pos.0 - old_pos.0;
|
||||||
|
// Determine whether the change in position is broadly consistent with both
|
||||||
|
// the magnitude and direction of the velocity, with appropriate thresholds.
|
||||||
|
LineSegment3 {
|
||||||
|
start: Vec3::zero(),
|
||||||
|
end: ref_vel * dt.0,
|
||||||
|
}
|
||||||
|
.projected_point(rpos)
|
||||||
|
// + 1.5 accounts for minor changes in position without corresponding
|
||||||
|
// velocity like block hopping/snapping
|
||||||
|
.distance_squared(rpos) < (rpos.magnitude() * 0.5 + 1.5).powi(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
if !is_position_ok {
|
||||||
|
break 'rejection Some(Rejection::TooFar { old: old_pos.0, new: new_pos.0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rejection) = rejection {
|
||||||
|
let alias = maybe_player.map(|p| &p.alias);
|
||||||
|
|
||||||
|
match rejection {
|
||||||
|
Rejection::TooFar { old, new } => warn!("Rejected physics for player {alias:?} (new position {new:?} is too far from old position {old:?})"),
|
||||||
|
Rejection::TooFast { vel } => warn!("Rejected physics for player {alias:?} (new velocity {vel:?} is too fast)"),
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Perhaps this is overzealous?
|
||||||
|
if let Some(mut setting) = new_player_physics_setting.as_mut() {
|
||||||
|
setting.server_force = true;
|
||||||
|
warn!("Switching player {alias:?} to server-side physics");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Reject the change and force the server's view of the physics state
|
||||||
|
force_update.as_mut().map(|fu| fu.update());
|
||||||
|
} else {
|
||||||
|
*old_pos = new_pos;
|
||||||
|
*old_vel = new_vel;
|
||||||
|
*old_ori = new_ori;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure deferred view distance changes are applied (if the
|
// Ensure deferred view distance changes are applied (if the
|
||||||
// requsite time has elapsed).
|
// requsite time has elapsed).
|
||||||
if let Some(presence) = maybe_presence {
|
if let Some(presence) = maybe_presence {
|
||||||
|
Loading…
Reference in New Issue
Block a user