Forum

SV_Invisible_To_Client (Anti-Wallhack)

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

SV_Invisible_To_Client (Anti-Wallhack)

Postby Baker » Sun Dec 07, 2008 2:53 pm

Quake had always had a weakness in that it was vulnerable to "stick models", textures being removed in maps, external cheater opengl32.dll libraries that allow walls to be toggled don't draw walls (etc.)

DarkPlaces add a sv_cullentities_trace feature to screen out entities that can't be see so Nexuiz would run faster and this had the side effect of being a strong cheat-protection measure.

Last year, this functionality was added to FTEQW and later in the year I ported the FTEQW version to ProQuake server.

Later Rook, would test and perfect a streamlined version that condensed it down to a single function.

Summary: Anti-Wallhack

Using SV_Invisible_To_Client, you can screen out entities that a client shouldn't be able to see and have the server simply not send them.

Then you don't have to worry much about cheaters with wallhacks or altered player models: there is no model to draw because the client is never sent data for what they cannot see.

Other Benefits

This reduces the amount of data sent to the client by the server.

In single player, this visibility screening can speed up rendering and increase frames per second.

The downside is that doing this check for 300-600 entities for every frame is a little CPU intensive. To deal with this, there are 2 options:

1. sv_cullentities 1 - only checks the player entities. So it is only checking at most 16 entities per frame.

2. sv_cullentities 2 - check everything

Culling Weakness

If I recall correctly, this function has a very minor weakness in that if ONLY the middle of a model is visible and not extremities, the function falsely returns that the entity can't be seen.

Not typically a problem except for lifts, platforms and other entities that are large. As a reult, it the function skips those entities.


Adding the Code:

The code was written by Rook.

It is very easily to implement. It is a single function with 1 supporting cvar we need to register and 1 instance where our new function gets called.

And it's all in sv_main.c!

1. sv_main.c

a. Let's create the sv_cullentities cvar; we will be default it to 1 (players only screen).

At the top of sv_main.c, add the yellow text:

char localmodels[MAX_MODELS][5]; // inline model names for precache

cvar_t sv_cullentities = {"sv_cullentities","1", false, true};
//============================================================================


b. We need to register the cvar. Go 5 or 6 lines downad to SV_Init.c and add the yellow text.

Cvar_RegisterVariable (&sv_cull_entities);
Cvar_RegisterVariable (&sv_maxvelocity)
Cvar_RegisterVariable (&sv_gravity);
Cvar_RegisterVariable (&sv_friction);
Cvar_RegisterVariable (&sv_edgefriction);
Cvar_RegisterVariable (&sv_stopspeed);


c. Now we need to add our new function, find this code:

Code: Select all
/*
=============
SV_WriteEntitiesToClient

=============
*/


And above it paste our new function:

Code: Select all
extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);

qboolean SV_InvisibleToClient(edict_t *viewer, edict_t *seen)
{
    int      i;
    trace_t   tr;
    vec3_t   start;
    vec3_t   end;

    if (seen->v.movetype == MOVETYPE_PUSH )//dont cull doors and plats :(
    {
        return false;
    }

    if (sv_cullentities.value == 1)    //1 only check player models, 2 = check all ents
    if (strcmp(pr_strings + seen->v.classname, "player"))   
        return false;

    memset (&tr, 0, sizeof(tr));               
    tr.fraction = 1;

    start[0] = viewer->v.origin[0];
    start[1] = viewer->v.origin[1];
    start[2] = viewer->v.origin[2] + viewer->v.view_ofs[2];

    //aim straight at the center of "seen" from our eyes
    end[0] = 0.5 * (seen->v.mins[0] + seen->v.maxs[0]);
    end[1] = 0.5 * (seen->v.mins[1] + seen->v.maxs[1]);
    end[2] = 0.5 * (seen->v.mins[2] + seen->v.maxs[2]);           

    tr = SV_ClipMoveToEntity (sv.edicts, start, vec3_origin, vec3_origin, end);
    if (tr.fraction == 1)// line hit the ent
            return false;

    //last attempt to eliminate any flaws...
    if ((!strcmp(pr_strings + seen->v.classname, "player")) || (sv_cullentities.value > 1))
    {
        for (i = 0; i < 64; i++)
        {
            end[0] = seen->v.origin[0] + offsetrandom(seen->v.mins[0], seen->v.maxs[0]);
            end[1] = seen->v.origin[1] + offsetrandom(seen->v.mins[1], seen->v.maxs[1]);
            end[2] = seen->v.origin[2] + offsetrandom(seen->v.mins[2], seen->v.maxs[2]);

            tr = SV_ClipMoveToEntity (sv.edicts, start, vec3_origin, vec3_origin, end);
            if (tr.fraction == 1)// line hit the ent
         {
                Con_DPrintf (va("found ent in %i hits\n", i));
                    return false;
         }
        }
    }

    return true;
}


4. Finally we need to make sure this screening process gets called:

Go to SV_WriteEntitiesToClient and find this code and add the yellow text:

if (!ent->v.modelindex || !pr_strings[ent->v.model])
continue;

for (i=0 ; i < ent->num_leafs ; i++)
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) ))
break;

if (i == ent->num_leafs)
continue; // not visible

if (sv_cullentities.value && SV_InvisibleToClient(clent,ent))
continue; // Baker: bail out and skip to next entity because we are not going to send data on this one
Last edited by Baker on Mon Dec 08, 2008 12:09 am, edited 3 times in total.
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby Stealth Kill » Sun Dec 07, 2008 6:28 pm

Can you add a entity view distance?

For example you can see only entites max 1000 units away from
your player model
i need something like that for singleplayer.


and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);
Stealth Kill
 
Posts: 83
Joined: Fri Dec 29, 2006 12:34 pm

Postby Downsider » Sun Dec 07, 2008 7:21 pm

Stealth Kill wrote:Can you add a entity view distance?

For example you can see only entites max 1000 units away from
your player model
i need something like that for singleplayer.


and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);


Just modify this so if the distance between the player and the entity is >= 1000, it returns the same as if they were behind an object. Easy :o.
User avatar
Downsider
 
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Postby Baker » Sun Dec 07, 2008 7:48 pm

and i think step b) should be
Cvar_RegisterVariable (&sv_cullentities);


Didn't catch that, heh. Fixed. Thanks!
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: SV_Invisible_To_Client (Anti-Wallhack)

Postby Baker » Mon Dec 08, 2008 12:00 am

Hmmm. This looks like it needs improvement really:


if ((SV_InvisibleToClient(clent,ent)) && (sv_cullentities.value))
continue;


It should go like this:

if (sv_cullentities.value && SV_InvisibleToClient(clent, ent))
continue;


(Due to C's short-circuit evaluation, if sv_cullentities has a non-zero value the second part, SV_InvisibleToClient, doesn't even get called and it really shouldn't get called unless it has a non-zero value. And I don't see anything in SV_InvisibleToClient that returns early is sv_cull_entities is 0.)
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby Baker » Mon Dec 08, 2008 1:26 am

Stealth Kill wrote:Can you add a entity view distance?


I haven't played around with calculating distances yet, but it probably goes something like this:

If ( fabs ( Length(viewer->v.origin, seen->v.origin)) > 1000.0 )
continue;


And would go before this:

if (sv_cullentities.value && SV_InvisibleToClient(clent, ent))
continue;


I haven't mess around with that kind of stuff and am not looking at the source a the moment, so I don't know if that code works "as-is" or needs slight touching.
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby r00k » Mon Dec 08, 2008 8:56 pm

You meant to say?
Code: Select all
         VectorSubtract(clent->v.origin, ent->v.origin, edist);      

         if (VectorLength(edist) > 1024)
            continue;

         if (sv_cullentities.value)
         {
            if (SV_InvisibleToClient(clent,ent))
               continue;
         }


r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Postby Baker » Mon Dec 08, 2008 9:06 pm

r00k wrote:You meant to say?
Code: Select all
         VectorSubtract(clent->v.origin, ent->v.origin, edist);      

         if (VectorLength(edist) > 1024)
            continue;

         if (sv_cullentities.value)
         {
            if (SV_InvisibleToClient(clent,ent))
               continue;
         }




^^^ is better than me :D

I'm still learning.
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby r00k » Mon Dec 08, 2008 9:15 pm

WEll, I meant the distance check is in SV_WriteEntitiesToClient not in
SV_InvisibleToClient.

;)
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Postby Baker » Mon Dec 08, 2008 9:50 pm

r00k wrote:WEll, I meant the distance check is in SV_WriteEntitiesToClient not in
SV_InvisibleToClient.

;)


Hehe, I should have only quoted the relevant part. If I didn't know recognize the equivalence of the latter part, I'd be in a whole world of trouble :D

/The dangers of self-deprecating humor
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby LordHavoc » Sat Sep 26, 2009 12:20 am

You forgot to define offsetrandom.

Also using 64 traces is exceedingly slow - DarkPlaces uses only a few and simply renews a timer in the edict_t whenever one hits the entity, as long as the timer is in the future it shows the entity, the timer has to be set low (like 0.2) to prevent wallhacks from being useful, but it's much lower cpu usage.

I should also mention that I got the idea from Unreal1, a developer note mentioned the technique, and that the lag/flicker problems of it are lost in the noise of multiplayer gameplay, it looks like some kind of network lag rather than a major annoyance.

UnrealEngine3 however just uses the single trace to the center, and modders have complained about how much trouble this causes, it seems UE3 does one thing worse than UE1 :)
LordHavoc
 
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA

Postby Team Xlink » Sat Sep 26, 2009 12:31 am

Code: Select all
#define offsetrandom(MIN,MAX) (((double)(rand() + 0.5) / ((double)RAND_MAX + 1)) * ((MAX)-(MIN)) + (MIN))


The equivalent to DarkPlaces lhrandom courtesy of LordHavoc.

So about the 64 traces being slow.

I would set it to lets say 32?
Team Xlink
 
Posts: 368
Joined: Thu Jun 25, 2009 4:45 am
Location: Michigan

Postby Downsider » Sat Sep 26, 2009 12:38 am

It shouldn't make a dent on a decent dedicated server.A
User avatar
Downsider
 
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Postby LordHavoc » Sat Sep 26, 2009 12:40 am

Downsider wrote:It shouldn't make a dent on a decent dedicated server.A


You haven't run helm18.bsp (10000 knights) :)

Also you probably haven't played dpmod with the cvar spawnmonsters set to 2000 (and multiple players) :)

Note the cost goes up with player count exponentially (each player is producing more entities to cull, and each player runs culling code on all of them).
LordHavoc
 
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA

Postby Downsider » Sat Sep 26, 2009 4:22 am

lol well I was talking more in context to what XLink needs for his project, but no, I did not think of that xD
User avatar
Downsider
 
Posts: 621
Joined: Tue Sep 16, 2008 1:35 am

Next

Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest