1 /*
2    sv_wallhack.c -- functions to prevent wallhack cheats
3 
4    Copyright (C) 2013 Laszlo Menczel
5 
6    This is free software distributed under the terms of the GNU
7    General Public License version 2. NO WARRANTY, see 'LICENSE.TXT'.
8  */
9 
10 #ifdef ANTIWALLHACK		// added whole file
11 
12 #include <time.h>               // for random seed generation
13 #include "server.h"
14 
15 //======================================================================
16 
17 static vec3_t pred_ppos, pred_opos;
18 
19 static trajectory_t traject;
20 
21 static int rand_seed;
22 
23 static float delta_sign[8][3] =
24 {
25 	{  1,  1,  1  },
26 	{  1,  1,  1  },
27 	{  1,  1, -1  },
28 	{  1,  1, -1  },
29 	{ -1,  1,  1  },
30 	{  1, -1,  1  },
31 	{ -1,  1, -1  },
32 	{  1, -1, -1  }
33 };
34 
35 static vec3_t delta[8];
36 
37 //======================================================================
38 // local functions
39 //======================================================================
40 
41 #define POS_LIM     1.0
42 #define NEG_LIM    -1.0
43 
zero_vector(vec3_t v)44 static int zero_vector(vec3_t v)
45 {
46 	if (v[0] > POS_LIM || v[0] < NEG_LIM)
47 		return 0;
48 
49 	if (v[1] > POS_LIM || v[1] < NEG_LIM)
50 		return 0;
51 
52 	if (v[2] > POS_LIM || v[2] < NEG_LIM)
53 		return 0;
54 
55 	return 1;
56 }
57 
58 //======================================================================
59 /*
60    The following functions for predicting player positions
61    have been adopted from 'g_unlagged.c' which is part of the
62    'unlagged' system created by Neil "haste" Toronto.
63    WEB site: http://www.ra.is/unlagged
64  */
65 
66 #define OVERCLIP                1.001f
67 
predict_clip_velocity(vec3_t in,vec3_t normal,vec3_t out)68 static void predict_clip_velocity(vec3_t in, vec3_t normal, vec3_t out)
69 {
70 	float backoff;
71 
72 	// find the magnitude of the vector "in" along "normal"
73 	backoff = DotProduct(in, normal);
74 
75 	// tilt the plane a bit to avoid floating-point error issues
76 	if (backoff < 0)
77 		backoff *= OVERCLIP;
78 	else
79 		backoff /= OVERCLIP;
80 
81 	// slide along
82 	VectorMA(in, -backoff, normal, out);
83 }
84 
85 //======================================================================
86 
87 #define MAX_CLIP_PLANES   5
88 #define Z_ADJUST          1
89 
predict_slide_move(sharedEntity_t * ent,float frametime,trajectory_t * tr,vec3_t result)90 static int predict_slide_move(sharedEntity_t * ent, float frametime, trajectory_t * tr, vec3_t result)
91 {
92 	int count, numbumps, numplanes, i, j, k;
93 
94 	float d, time_left, into;
95 
96 	vec3_t planes[MAX_CLIP_PLANES],
97 	       velocity, origin, clipVelocity, endVelocity, endClipVelocity, dir, end;
98 
99 	trace_t trace;
100 
101 	numbumps = 4;
102 
103 	VectorCopy(tr->trBase, origin);
104 	origin[2] += Z_ADJUST;  // move it off the floor
105 
106 	VectorCopy(tr->trDelta, velocity);
107 	VectorCopy(tr->trDelta, endVelocity);
108 
109 	time_left = frametime;
110 
111 	numplanes = 0;
112 
113 	for (count = 0; count < numbumps; count++)
114 	{
115 		// calculate position we are trying to move to
116 		VectorMA(origin, time_left, velocity, end);
117 
118 		// see if we can make it there
119 		SV_Trace(&trace, origin, ent->r.mins, ent->r.maxs, end, ent->s.number,
120 		         CONTENTS_SOLID, qfalse);
121 
122 		if (trace.allsolid)
123 		{
124 			// entity is completely trapped in another solid
125 			VectorCopy(origin, result);
126 			return 0;
127 		}
128 
129 		if (trace.fraction > 0.99) // moved the entire distance
130 		{
131 			VectorCopy(trace.endpos, result);
132 			return 1;
133 		}
134 
135 		if (trace.fraction > 0) // covered some distance
136 			VectorCopy(trace.endpos, origin);
137 
138 		time_left -= time_left * trace.fraction;
139 
140 		if (numplanes >= MAX_CLIP_PLANES)
141 		{
142 			// this shouldn't really happen
143 			VectorCopy(origin, result);
144 			return 0;
145 		}
146 
147 		// if this is the same plane we hit before, nudge velocity
148 		// out along it, which fixes some epsilon issues with
149 		// non-axial planes
150 		for (i = 0; i < numplanes; i++)
151 			if (DotProduct(trace.plane.normal, planes[i]) > 0.99)
152 			{
153 				VectorAdd(trace.plane.normal, velocity, velocity);
154 				break;
155 			}
156 
157 		if (i < numplanes)
158 			continue;
159 
160 		VectorCopy(trace.plane.normal, planes[numplanes]);
161 		numplanes++;
162 
163 		// modify velocity so it parallels all of the clip planes
164 		// find a plane that it enters
165 		for (i = 0; i < numplanes; i++)
166 		{
167 			into = DotProduct(velocity, planes[i]);
168 			if (into >= 0.1) // move doesn't interact with the plane
169 				continue;
170 
171 			// slide along the plane
172 			predict_clip_velocity(velocity, planes[i], clipVelocity);
173 
174 			// slide along the plane
175 			predict_clip_velocity(endVelocity, planes[i], endClipVelocity);
176 
177 			// see if there is a second plane that the new move enters
178 			for (j = 0; j < numplanes; j++)
179 			{
180 				if (j == i)
181 					continue;
182 
183 				if (DotProduct(clipVelocity, planes[j]) >= 0.1) // move doesn't interact with the plane
184 					continue;
185 
186 				// try clipping the move to the plane
187 				predict_clip_velocity(clipVelocity, planes[j], clipVelocity);
188 				predict_clip_velocity(endClipVelocity, planes[j], endClipVelocity);
189 
190 				// see if it goes back into the first clip plane
191 				if (DotProduct(clipVelocity, planes[i]) >= 0)
192 					continue;
193 
194 				// slide the original velocity along the crease
195 				CrossProduct(planes[i], planes[j], dir);
196 				VectorNormalize(dir);
197 				d = DotProduct(dir, velocity);
198 				VectorScale(dir, d, clipVelocity);
199 
200 				CrossProduct(planes[i], planes[j], dir);
201 				VectorNormalize(dir);
202 				d = DotProduct(dir, endVelocity);
203 				VectorScale(dir, d, endClipVelocity);
204 
205 				// see if there is a third plane the new move enters
206 				for (k = 0; k < numplanes; k++)
207 				{
208 					if (k == i || k == j)
209 						continue;
210 
211 					if (DotProduct(clipVelocity, planes[k]) >= 0.1) // move doesn't interact with the plane
212 						continue;
213 
214 					// stop dead at a tripple plane interaction
215 					VectorCopy(origin, result);
216 					return 1;
217 				}
218 			}
219 
220 			// if we have fixed all interactions, try another move
221 			VectorCopy(clipVelocity, velocity);
222 			VectorCopy(endClipVelocity, endVelocity);
223 			break;
224 		}
225 	}
226 
227 	VectorCopy(origin, result);
228 
229 	if (count == 0)
230 		return 1;
231 
232 	return 0;
233 }
234 
235 //======================================================================
236 
237 #define STEPSIZE 18
238 
239 // 'frametime' is interpreted as seconds
240 
predict_move(sharedEntity_t * ent,float frametime,trajectory_t * tr,vec3_t result)241 static void predict_move(sharedEntity_t * ent, float frametime, trajectory_t * tr, vec3_t result)
242 {
243 	float stepSize;
244 
245 	vec3_t start_o, start_v, down, up;
246 
247 	trace_t trace;
248 
249 	VectorCopy(tr->trBase, result); // assume the move fails
250 
251 	if (zero_vector(tr->trDelta)) // not moving
252 		return;
253 
254 	if (predict_slide_move(ent, frametime, tr, result)) // move completed
255 		return;
256 
257 	VectorCopy(tr->trBase, start_o);
258 	VectorCopy(tr->trDelta, start_v);
259 
260 	VectorCopy(start_o, up);
261 	up[2] += STEPSIZE;
262 
263 	// test the player position if they were a stepheight higher
264 	SV_Trace(&trace, start_o, ent->r.mins, ent->r.maxs, up, ent->s.number,
265 	         CONTENTS_SOLID, qfalse);
266 
267 	if (trace.allsolid)     // can't step up
268 		return;
269 
270 	stepSize = trace.endpos[2] - start_o[2];
271 
272 	// try slidemove from this position
273 	VectorCopy(trace.endpos, tr->trBase);
274 	VectorCopy(start_v, tr->trDelta);
275 
276 	predict_slide_move(ent, frametime, tr, result);
277 
278 	// push down the final amount
279 	VectorCopy(tr->trBase, down);
280 	down[2] -= stepSize;
281 	SV_Trace(&trace, tr->trBase, ent->r.mins, ent->r.maxs, down, ent->s.number,
282 	         CONTENTS_SOLID, qfalse);
283 
284 	if (!trace.allsolid)
285 		VectorCopy(trace.endpos, result);
286 }
287 
288 //======================================================================
289 /*
290    Calculates the view point of a player model at position 'org' using
291    information in the player state 'ps' of its client, and stores the
292    viewpoint coordinates in 'vp'.
293  */
294 
calc_viewpoint(playerState_t * ps,vec3_t org,vec3_t vp)295 static void calc_viewpoint(playerState_t * ps, vec3_t org, vec3_t vp)
296 {
297 	VectorCopy(org, vp);
298 
299 	if ( ps->leanf != 0 )
300 	{
301 		vec3_t right, v3ViewAngles;
302 		VectorCopy( ps->viewangles, v3ViewAngles );
303 		v3ViewAngles[2] += ps->leanf / 2.0f;
304 		AngleVectors( v3ViewAngles, NULL, right, NULL );
305 		VectorMA( org, ps->leanf, right, org );
306 	}
307 
308 	if (ps->pm_flags & PMF_DUCKED)
309 		vp[2] += CROUCH_VIEWHEIGHT;
310 	else
311 		vp[2] += DEFAULT_VIEWHEIGHT;
312 }
313 
314 //======================================================================
315 
316 #define MAX_PITCH    20
317 
player_in_fov(vec3_t viewangle,vec3_t ppos,vec3_t opos)318 static int player_in_fov(vec3_t viewangle, vec3_t ppos, vec3_t opos)
319 {
320 	float yaw, pitch, cos_angle;
321 	vec3_t dir, los;
322 
323 	/*
324 	   FIXME:
325 	   For some reason my FOV calculation does not work correctly for large
326 	   pitch values. It does not matter, the test's purpose is to eliminate
327 	   info that would reveal the position of opponents behind the player
328 	   on the same floor.
329 	 */
330 	if (viewangle[PITCH] > MAX_PITCH || viewangle[PITCH] < -1 * MAX_PITCH)
331 		return 1;
332 
333 	// calculate unit vector of the direction the player looks at
334 	yaw = viewangle[YAW] * (M_PI * 2 / 360);
335 	pitch = viewangle[PITCH] * (M_PI * 2 / 360);
336 	dir[0] = cos(yaw) * cos(pitch);
337 	dir[1] = sin(yaw);
338 	dir[2] = cos(yaw) * sin(pitch);
339 
340 	// calculate unit vector corresponding to line of sight to opponent
341 	VectorSubtract(opos, ppos, los);
342 	VectorNormalize(los);
343 
344 	// calculate and test the angle between the two vectors
345 	cos_angle = DotProduct(dir, los);
346 	if (cos_angle > 0)      // +/- 90 degrees (fov = 180)
347 		return 1;
348 
349 	return 0;
350 }
351 
352 //======================================================================
353 
copy_trajectory(trajectory_t * src,trajectory_t * dst)354 static void copy_trajectory(trajectory_t * src, trajectory_t * dst)
355 {
356 	dst->trType = src->trType;
357 	dst->trTime = src->trTime;
358 	dst->trDuration = src->trDuration;
359 	VectorCopy(src->trBase, dst->trBase);
360 	VectorCopy(src->trDelta, dst->trDelta);
361 }
362 
363 //======================================================================
364 
is_visible(vec3_t start,vec3_t end)365 int is_visible(vec3_t start, vec3_t end)
366 {
367 	trace_t trace;
368 
369 	CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_SOLID, 0);
370 
371 	if (trace.contents & CONTENTS_SOLID)
372 		return 0;
373 
374 	return 1;
375 }
376 
377 //======================================================================
378 
379 #define MIN_DIST           200.0
380 #define MAX_DIST           1000.0
381 #define MIN_OFS_FACT       0.1
382 #define MAX_OFS_FACT       0.4
383 #define OFS_FACT_DIFF      (MAX_OFS_FACT - MIN_OFS_FACT)
384 
randomize_position(sharedEntity_t * anchor,sharedEntity_t * object)385 static void randomize_position(sharedEntity_t *anchor, sharedEntity_t *object)
386 {
387 	vec3_t los;
388 	float dist, ofs, rand_fact, dist_fact;
389 
390 	VectorSubtract(anchor->s.pos.trBase, object->s.pos.trBase, los);
391 	dist = VectorLength(los);
392 
393 	if (dist > MAX_DIST)
394 		dist_fact = MIN_OFS_FACT;
395 	else if (dist < MIN_DIST)
396 		dist_fact = MAX_OFS_FACT;
397 	else
398 		dist_fact = MAX_OFS_FACT - OFS_FACT_DIFF * (dist - MIN_DIST) / (MAX_DIST - MIN_DIST);
399 
400 	rand_fact = (float) (rand() % 100) / 100.0;
401 	ofs = dist * dist_fact;
402 	ofs += ofs * rand_fact;
403 
404 	if (rand() & 1)
405 		object->s.pos.trBase[0] += ofs;
406 	else
407 		object->s.pos.trBase[0] -= ofs;
408 
409 	rand_fact = (float) (rand() % 100) / 100.0;
410 	ofs = dist * dist_fact;
411 	ofs += ofs * rand_fact;
412 
413 	if (rand() & 1)
414 		object->s.pos.trBase[1] += ofs;
415 	else
416 		object->s.pos.trBase[1] -= ofs;
417 
418 	rand_fact = (float) (rand() % 100) / 100.0;
419 	ofs = dist * dist_fact;
420 	ofs += ofs * rand_fact;
421 
422 	if (rand() & 1)
423 		object->s.pos.trBase[2] += ofs;
424 	else
425 		object->s.pos.trBase[2] -= ofs;
426 }
427 
428 //======================================================================
429 /*
430    'can_see' checks if 'player' can see 'other' or not. First
431    a check is made if 'other' is in the maximum allowed fov of
432    'player'. If not, then zero is returned w/o any further checks.
433    Next traces are carried out from the present viewpoint of 'player'
434    to the corners of the bounding box of 'other'. If any of these
435    traces are successful (i.e. nothing solid is between the start
436    and end positions) then non-zero is returned.
437 
438    Otherwise the expected positions of the two players are calculated,
439    by extrapolating their movements for PREDICT_TIME seconds and the above
440    tests are carried out again. The result is reported by returning non-zero
441    (expected to become visible) or zero (not expected to become visible
442    in the next frame).
443  */
444 
445 #define PREDICT_TIME      0.1
446 #define VOFS              6
447 
can_see(sharedEntity_t * pent,sharedEntity_t * oent,playerState_t * ps)448 static int can_see(sharedEntity_t *pent, sharedEntity_t*oent, playerState_t *ps)
449 {
450 	vec3_t viewpoint, tmp;
451 	int i;
452 
453 	/* check if 'other' is in the maximum fov allowed */
454 	if (!player_in_fov(pent->s.apos.trBase, pent->s.pos.trBase, oent->s.pos.trBase))
455 		return 0;
456 
457 	/* check if visible in this frame */
458 	calc_viewpoint(ps, pent->s.pos.trBase, viewpoint);
459 
460 	for (i = 0; i < 8; i++)
461 	{
462 		VectorCopy(oent->s.pos.trBase, tmp);
463 		tmp[0] += delta[i][0];
464 		tmp[1] += delta[i][1];
465 		tmp[2] += delta[i][2] + VOFS;
466 
467 		if (is_visible(viewpoint, tmp))
468 			return 1;
469 	}
470 
471 	/* predict player positions */
472 	copy_trajectory(&pent->s.pos, &traject);
473 	predict_move(pent, PREDICT_TIME, &traject, pred_ppos);
474 
475 	copy_trajectory(&oent->s.pos, &traject);
476 	predict_move(oent, PREDICT_TIME, &traject, pred_opos);
477 
478 	/*
479 	   Check again if 'other' is in the maximum fov allowed.
480 	   FIXME: We use the original viewangle that may have
481 	   changed during the move. This could introduce some
482 	   errors.
483 	 */
484 	if (!player_in_fov(pent->s.apos.trBase, pred_ppos, pred_opos))
485 		return 0;
486 
487 	/* check if expected to be visible in the next frame */
488 	calc_viewpoint(ps, pred_ppos, viewpoint);
489 
490 	for (i = 0; i < 8; i++)
491 	{
492 		VectorCopy(pred_opos, tmp);
493 		tmp[0] += delta[i][0];
494 		tmp[1] += delta[i][1];
495 		tmp[2] += delta[i][2] + VOFS;
496 
497 		if (is_visible(viewpoint, tmp))
498 			return 1;
499 	}
500 
501 	return 0;
502 }
503 
504 //======================================================================
505 // public functions
506 //======================================================================
507 
AWH_Init(void)508 void AWH_Init(void)
509 {
510 	int i;
511 
512 	for (i = 0; i < 8; i++)
513 	{
514 		delta[i][0] = ((float) awh_bbox_horz->integer * delta_sign[i][0]) / 2.0;
515 		delta[i][1] = ((float) awh_bbox_horz->integer * delta_sign[i][1]) / 2.0;
516 		delta[i][2] = ((float) awh_bbox_vert->integer * delta_sign[i][2]) / 2.0;
517 	}
518 
519 	rand_seed = (int) time(NULL);
520 }
521 
522 //======================================================================
523 
AWH_CanSee(int player,int other)524 int AWH_CanSee(int player, int other)
525 {
526 	sharedEntity_t *pent, *oent;
527 	playerState_t *ps;
528 
529 	ps = SV_GameClientNum(player);
530 	pent = SV_GentityNum(player);
531 	oent = SV_GentityNum(other);
532 
533 	return can_see(pent, oent, ps);
534 }
535 
536 //======================================================================
537 
538 // The value below is equal to the default value of the Cvar
539 // 's_alMaxDistance' (= 1024).
540 #define SOUND_HEARING_LIMIT    1024
541 
AWH_CanHear(int player,int other)542 int AWH_CanHear(int player, int other)
543 {
544 	sharedEntity_t *pent, *oent;
545 	vec3_t dist;
546 
547 	pent = SV_GentityNum(player);
548 	oent = SV_GentityNum(other);
549 
550 	VectorSubtract(pent->s.pos.trBase, oent->s.pos.trBase, dist);
551 	if (VectorLength(dist) > SOUND_HEARING_LIMIT)
552 		return 0;
553 
554 	return 1;
555 }
556 
557 //======================================================================
558 /*
559    Randomizes the position of 'other'. Amount of displacement depends
560    on the distance of 'other' from 'player'. Checks if the new position
561    is still invisible from the position of 'player', returns if yes,
562    otherwise tries again. After three attempts the function gives up and
563    restores the original position of 'other'.
564  */
565 
AWH_RandomizePos(int player,int other)566 void AWH_RandomizePos(int player, int other)
567 {
568 	int i;
569 	sharedEntity_t *pent, *oent;
570 	playerState_t *ps;
571 	vec3_t pos;
572 
573 	ps = SV_GameClientNum(player);
574 	pent = SV_GentityNum(player);
575 	oent = SV_GentityNum(other);
576 	VectorCopy(oent->s.pos.trBase, pos);
577 
578 	for (i = 0; i < 3; i++)
579 	{
580 		randomize_position(pent, oent);
581 
582 		if (!can_see(pent, oent, ps))
583 			return;
584 		else
585 			VectorCopy(pos, oent->s.pos.trBase);
586 	}
587 }
588 
589 #endif
590