[FilterScript] Cheat detector (health-hack + no-spread)
#1

__________________________________________________ ________________________________



Health-hack (bulletproof) detector - ******* video

Код:
#define FILTERSCRIPT

#include <a_samp>

    new Float:previousHealthValue[MAX_PLAYERS],
	hits[MAX_PLAYERS],
	noDamageCount[MAX_PLAYERS], //count how many times the player didn't lose health after being hit
	bool:delayer[MAX_PLAYERS], // delayer is neccessary because damage is not updated quickly enough and gives false detections
	bool:afterSpawnCover[MAX_PLAYERS], //to avoid false detections straight after spawn which happened to me while testing it (it's set to 3 seconds right now)
	bool:isDead[MAX_PLAYERS]; // hp check to see if the player is dead didn't work(the value of hp stayed the same as before last shot)
	
	
	////////////////////////////////
	//SETTINGS
	#define DELAY_TIME 500 // 500ms delay works well on my syrver with 2 players online, I'm not sure what's it's going to be like on a server with over 100 players online
	#define AFTER_SPAWN_COVER_TIME 3000
	#define NO_DAMAGE_COUNT_LIMIT 0 // number of times the script detected someone taking no damage after shot needed to: send info to admins/kick/ban
	//SETTINGS
	////////////////////////////////


    public OnFilterScriptInit()
    {
            print("\n--------------------------------------");
            print("Health-hack detector by monday");
            print("--------------------------------------\n");
            return 1;
    }

    public OnPlayerConnect(playerid)
    {
		previousHealthValue[playerid] = 0.0;
        hits[playerid] = 0;
        noDamageCount[playerid] = 0;
        delayer[playerid] = false;
        return 1;
    }
	

	public OnPlayerDeath(playerid, killerid, reason)
	{
	    isDead[playerid] = true;
	    return 1;
	}
	
	public OnPlayerSpawn(playerid)
	{
	    previousHealthValue[playerid] = 0.0;
        hits[playerid] = 0;
        delayer[playerid] = false;
        afterSpawnCover[playerid] = true;
        SetTimerEx("RemoveSpawnCover", AFTER_SPAWN_COVER_TIME, false, "i", playerid);
        isDead[playerid] = false;
	    return 1;
	}

    forward SwitchDelay(playerid);
	public SwitchDelay(playerid)
	{
		delayer[playerid] = true;
		return 1;
	}
	
	forward RemoveSpawnCover(playerid);
	public RemoveSpawnCover(playerid)
	{
		afterSpawnCover[playerid] = false;
		return 1;
	}
    
	public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ)
	{
	    if(hittype ==  BULLET_HIT_TYPE_PLAYER)
	    {
	        new Float:h, Float:a, Float:newHealthValue, message[128], cheaterName[MAX_PLAYER_NAME];
	        
			if(hits[hitid] == 0)
			{
				GetPlayerHealth(hitid, h);
				GetPlayerArmour(hitid, a);
				previousHealthValue[hitid] = h + a;
				if(isDead[hitid] == true || afterSpawnCover[hitid] == true) // + here must be a check for AFK to avoid false detections
	   			{
	      			hits[hitid] = 0;
				}
				else
				{
				    SetTimerEx("SwitchDelay", DELAY_TIME, false, "i", hitid);
				}
			}
            else if(delayer[hitid] == true)
            {
                delayer[hitid] = false;
                GetPlayerHealth(hitid, h);
            	GetPlayerArmour(hitid, a);
            	newHealthValue = h + a;
	   			if(newHealthValue == previousHealthValue[hitid] && isDead[hitid] != true && afterSpawnCover[hitid] != true)
		        {
		            noDamageCount[hitid]++;
		            if(noDamageCount[hitid] > NO_DAMAGE_COUNT_LIMIT)
		            {
		            //send message to admins, kick, ban
	             	GetPlayerName(hitid, cheaterName, sizeof(cheaterName));
		            format(message,sizeof(message),"%s didn't lose health %d times after being shot since his last login.",cheaterName, noDamageCount[hitid]);
    				SendClientMessageToAll(0xED6755AA,message);
		            }
				}
				else
				{
				    previousHealthValue[hitid] = newHealthValue; //update health value if it decreased like it should
				}
				SetTimerEx("SwitchDelay", DELAY_TIME, false, "i", hitid); // new delay before next check
			}
			hits[hitid]++;

	    }
	    return 1;
	}
Estimated reliability:


Calibration:
1. Increase delayer time in case if there are false detections
2. Evaluate if there are false detections by spectating each detected player
3. Repeat step 1 until there is no false detections

Individual script modification needed:
-afk check has to be added
-number of hits without losing health needed to take action(after calibration)
-final action (after calibration)








No-spread detector - ******* video
It's not effective if the player is waving the gun around.

Код:
#define FILTERSCRIPT

#include <a_samp>

    new hitsInaRow[MAX_PLAYERS];
   	new Float:lastMiddle_X[MAX_PLAYERS];
	new Float:lastMiddle_Y[MAX_PLAYERS];
	new Float:lastMiddle_Z[MAX_PLAYERS];
    new Float:lastHit_X[MAX_PLAYERS];
    new Float:lastHit_Y[MAX_PLAYERS];
    new Float:lastHit_Z[MAX_PLAYERS];
    new weaponUsed[MAX_PLAYERS];
    new Float:averageSpread[MAX_PLAYERS];


    public OnFilterScriptInit()
    {
            print("\n--------------------------------------");
            print("No-spread detector by monday");
            print("--------------------------------------\n");
            return 1;
    }

	stock Float:GetPositiveNumber(Float:number)
	{
		if(0.0 > number)
		{
			number *= -1.0;
		}
		return number;
	}

     stock Float:GetDistanceBetweenPoints(Float:x1, Float:y1, Float:z1, Float:x2, Float:y2, Float:z2)
	{
    	return VectorSize(x1-x2, y1-y2, z1-z2);
	}

    
    public OnPlayerConnect(playerid)
    {
        hitsInaRow[playerid]=0;
        lastMiddle_X[playerid]=0.0;
		lastMiddle_Y[playerid]=0.0;
		lastMiddle_Z[playerid]=0.0;
	    lastHit_X[playerid]=0.0;
	    lastHit_Y[playerid]=0.0;
		lastHit_Z[playerid]=0.0;
		averageSpread[playerid]=0.0;
		weaponUsed[playerid] = 0;
        return 1;
    }
    
    forward SpreadCheckReset(playerid);
    public SpreadCheckReset(playerid)
    {
        hitsInaRow[playerid]=0;
        lastMiddle_X[playerid]=0.0;
		lastMiddle_Y[playerid]=0.0;
		lastMiddle_Z[playerid]=0.0;
	    lastHit_X[playerid]=0.0;
	    lastHit_Y[playerid]=0.0;
		lastHit_Z[playerid]=0.0;
		averageSpread[playerid]=0.0;
		weaponUsed[playerid] = 0;
  		return 1;
    }

    
    
    
    forward OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ);
    public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ)
    {

            new
            Float:fOriginX, Float:fOriginY, Float:fOriginZ,
            Float:fVX, Float:fVY, Float:fVZ,
            Float:fHitPosX, Float:fHitPosY, Float:fHitPosZ,
            Float:middle_x, Float:middle_y, Float:middle_z,
            Float:compareMiddle_x, Float:compareMiddle_y, Float:compareMiddle_z,
            Float:compareHit_x, Float:compareHit_y, Float:compareHit_z,
            Float:compareBoth_x, Float:compareBoth_y, Float:compareBoth_z,
			Float:distance,
			Float:spreadValue,
			Float:spreadLimit,
			cheaterName[MAX_PLAYER_NAME],
			hitsNeeded,
			weaponTime,
			currentWeapon,
			finalMessage[128],
			gunName[32],
			spread[128];
			
			currentWeapon = GetPlayerWeapon(playerid);
			GetPlayerLastShotVectors(playerid, fOriginX, fOriginY, fOriginZ, fHitPosX, fHitPosY, fHitPosZ);
			distance = GetDistanceBetweenPoints(fOriginX, fOriginY, fOriginZ, fHitPosX, fHitPosY, fHitPosZ);
        	GetPlayerCameraFrontVector(playerid, fVX, fVY, fVZ);

        	middle_x = fOriginX + floatmul(fVX, distance);
        	middle_y = fOriginY + floatmul(fVY, distance);
        	middle_z = fOriginZ + floatmul(fVZ, distance);

            // middle_x, middle_y, middle_z = middle of the screen
        	// fHitPosX, fHitPosY, fHitPosZ = shooting point

        	//compare with the last shot
        	compareMiddle_x = GetPositiveNumber(middle_x - lastMiddle_X[playerid]);
        	compareMiddle_y = GetPositiveNumber(middle_y - lastMiddle_Y[playerid]);
        	compareMiddle_z = GetPositiveNumber(middle_z - lastMiddle_Z[playerid]);
        	compareHit_x = GetPositiveNumber(fHitPosX - lastHit_X[playerid]);
        	compareHit_y = GetPositiveNumber(fHitPosY - lastHit_Y[playerid]);
        	compareHit_z = GetPositiveNumber(fHitPosZ - lastHit_Z[playerid]);
			compareBoth_x = GetPositiveNumber(compareMiddle_x - compareHit_x);
        	compareBoth_y = GetPositiveNumber(compareMiddle_y - compareHit_y);
        	compareBoth_z = GetPositiveNumber(compareMiddle_z - compareHit_z);
			spreadValue = GetPositiveNumber(compareBoth_x + compareBoth_y + compareBoth_z);

        	//replace last shot data with new one
			lastMiddle_X[playerid] = middle_x;
			lastMiddle_Y[playerid] = middle_y;
			lastMiddle_Z[playerid] = middle_z;
			lastHit_X[playerid] = fHitPosX;
		    lastHit_Y[playerid] = fHitPosY;
		    lastHit_Z[playerid] = fHitPosZ;
		    
            if (weaponUsed[playerid] != currentWeapon)
			{
			    weaponUsed[playerid] = currentWeapon;
				SpreadCheckReset(playerid);
				return 1;
			}

			if (currentWeapon == 31 || currentWeapon == 30 ||currentWeapon == 28 || currentWeapon == 32 ||currentWeapon == 29) //m4, ak47, tec, uzi, mp5 - all of them are set to m4's limit which is the most accurate weapon. Mp5 has a bit faster shooting rate than other weapons included but its spread is also higher so it's not an issue, I tested it and it's not even close to get false detection
			{
			    hitsNeeded = 3; //
				weaponTime = 500; //time after which the stored hits will be reset(to make sure that there is no breaks between single shots)
				weaponUsed[playerid] = currentWeapon;
			}
			else
			{
			    if (currentWeapon == 24)//deagle
			    {
			    hitsNeeded = 2;
				weaponTime = 1600;
				weaponUsed[playerid] = currentWeapon;
				}
				else
				{
				return 1;
				}
			}
			
			if( GetPlayerSpecialAction(playerid) == SPECIAL_ACTION_DUCK )
			{
				spreadLimit = 0.00075 * distance; //decreased limit if the player is crouching
			}
			else
			{
			    spreadLimit = 0.00140 * distance;
			}
			
			format(spread, sizeof(spread), "Spread: %f Limit: %f",spreadValue, spreadLimit);
            //SendClientMessage(playerid, -1, spread);

			if(spreadValue < spreadLimit && 200.0 > distance > 3.0)
			{
            hitsInaRow[playerid]++;
            averageSpread[playerid] = averageSpread[playerid] + spreadValue;
            }
            
            if (hitsInaRow[playerid] == 1)
            {
            	SetTimerEx("SpreadCheckReset", weaponTime, 0, "i", playerid);
			}
			
			if (hitsInaRow[playerid] == hitsNeeded || hitsInaRow[playerid] > hitsNeeded)
			{
			//send message to admins, kick, ban
			GetWeaponName(currentWeapon, gunName, sizeof(gunName));
			GetPlayerName(playerid, cheaterName, MAX_PLAYER_NAME);
			averageSpread[playerid] /= float(hitsInaRow[playerid]);
			format(finalMessage, sizeof(finalMessage), "%s is shooting under spread limit using %s. Average spread=%f Limit=%f", cheaterName, gunName, averageSpread[playerid], spreadLimit);
            SendClientMessageToAll(0xED6755AA,finalMessage);
            SpreadCheckReset(playerid);
            }
            return 1;
    }
Reply
#2

Monday huh ?
Sound like someone i know,

Btw nice work dude!
Reply
#3

Nice Filterscript, gonna test it.
Reply
#4

nice FS, i love it
Reply
#5

THANKS, NICE FS
Reply
#6

I'm going to test it, but I don't think this shit is going to work
Reply
#7

I highly doubt this would work at the first place! Here's the reasons for the health hack to not work.

1) Lag / Desync is a big factor at SA-MP, you will face false positives.
2) There are cheats that can set back the health to its old value showing as they got hit but as soon as they are hit, it is restored (as the "shot" package is sent to the server)
3) OnPlayerWeaponShot callback can be spoofed and can be avoided to be called.
4) This will clearly give false positives for idle players. If I'm right, onplayerweaponshot is called when a player is AFK aswell, however their health will decrease when they are back from idling.
5) If not idle, the players who are not sending enough packets to the server (for example the same second they died) if the script checks a bit late, which is likely to happen because you have a delayer over there. The player is shot, the delayer fires off, the same second the player dies, then your delayer eventually checks what's the player's health after they died, usually when the player is dead the server can give false information about the player's health, that would send false positives aswell.
6) Most of these will be caused from desync, it will declare desynced players as health hacking. Don't forget the SA-MP server is not multi-threaded so is the client, it can not process all the data at once.
7) As above, it will really change the results you give on a full server, because there's the server's networking systems and packetloss, delays on the client's screen, delays from the server itself, server lag, code constantly running at the background, if the server hangs up even for 200ms the results would be noticeable on your detector and will cause for it to give false positives, not one or two, but many because the server players overall will feel this.
8 ) Sorry but some cute test you do on localhost will not prove anything, your ping on localhost is 1 and you never have packetloss, some bot you spawn in the game to shoot at will clearly be on the same network as you are so there would be absolutely no false positives ofcourse because you are already connected to the server pretty fine.
Reply
#8

Quote:

1) Lag / Desync is a big factor at SA-MP, you will face false positives.

The delayer is there to prevent false detections caused by lag, it has to be adjusted properly depending on the server as I stated in the comment within the script.


Quote:

2) There are cheats that can set back the health to its old value showing as they got hit but as soon as they are hit, it is restored (as the "shot" package is sent to the server)

The "delayer" is not there to check health at a certain time after hit. It's there to prevent counting shots if they are too close to the first one.


Quote:

4) This will clearly give false positives for idle players. If I'm right, onplayerweaponshot is called when a player is AFK aswell, however their health will decrease when they are back from idling.

There's a comment in the code notifying you that there needs to be an AFK check to avoid false detections.
"if(isDead[hitid] == true || afterSpawnCover[hitid] == true) // + here must be a check for AFK to avoid false detections"


Quote:

5) If not idle, the players who are not sending enough packets to the server (for example the same second they died) if the script checks a bit late, which is likely to happen because you have a delayer over there. The player is shot, the delayer fires off, the same second the player dies, then your delayer eventually checks what's the player's health after they died, usually when the player is dead the server can give false information about the player's health, that would send false positives aswell.

As I stated regarding 2nd point of your message, the "delayer" is not there to check health after certain time, it's there to prevent potential checks. After the "delayer" time comes to the end there is no check until the player is hit again. + That's why there is a death check.


Quote:

3) OnPlayerWeaponShot callback can be spoofed and can be avoided to be called.

6) Most of these will be caused from desync, it will declare desynced players as health hacking. Don't forget the SA-MP server is not multi-threaded so is the client, it can not process all the data at once.

7) As above, it will really change the results you give on a full server, because there's the server's networking systems and packetloss, delays on the client's screen, delays from the server itself, server lag, code constantly running at the background, if the server hangs up even for 200ms the results would be noticeable on your detector and will cause for it to give false positives, not one or two, but many because the server players overall will feel this.

8 ) Sorry but some cute test you do on localhost will not prove anything, your ping on localhost is 1 and you never have packetloss, some bot you spawn in the game to shoot at will clearly be on the same network as you are so there would be absolutely no false positives ofcourse because you are already connected to the server pretty fine.

Shit happens, that's why it's called "cheat detector" not "anti-cheat". I wish I had an option to check how it works with large number of players online but I don't have. No one forces you to set auto-ban for detected players, check it, assess it, adjust it, use it as you wish.
Reply
#9

Made similar no-spread detector for ma server, but if the player moves his crosshair around, it may show different value.. But, I feel like this one is more accurate but will use much more resource than mine... Was gonna post it here and found this :P..
Good job..
Reply
#10

u can use
pawn Код:
floatabs
instead of
pawn Код:
GetPositiveNumber
. ppl shouldn't ban a player for that health hack script cuz of the sa-mp sync. all I would do is kick him. U did a pretty nice work here.
Reply
#11

@Mic_H
This one is not effective when the player's moving his mouse too. Btw the more simple = the better ;d It's still a good way to prevent players from using no-spread, even by posting a presentation about it on the server's forum. People are going to think twice before they use no-spread when they see that something like this.

@Skream
Thanks for the floatabs
Reply
#12

it good but you should add kick
Reply
#13

Hmm, it's not a complete module you add to the server so I didn't add anything like kick. Several things has to be added/adjusted individually before it becomes useful, things such as:
-afk check in health-hack detector
-delayer time in health-hack detector
-number of hits without losing health in health-hack detector
-final action has to be set for both scripts (for example send message to admins, currently it sends message to everyone)

No-spread detector seems to be a reliable in terms of false detections. There shouldn't be any at all so setting auto-kick shouldn't be unfair in any case, but again, check it by yourself.

About the health-hack detector, if I had my own server I'd add a message to admins, then I'd:
1. Adjust delayer by increasing its time in case if there is a huge number of false detections
2. Evaluate if there are false detections by spectating each detected player
3. If 10/10 of the detections were legitimate I'd start to think about making any auto-kick or auto-ban, not the other way around.
Reply
#14

ok but still nice script . .
Reply
#15

Did anyone test it on large number of players? I wish I had a way to do it by myself but I don't
Reply
#16

For the boolean array use the char arrays.
Look this https://sampforum.blast.hk/showthread.php?tid=216730
Reply
#17

Thanks, so basically instead of this:
Код:
new bool:isDead[MAX_PLAYERS];
isDead[playerid] = false;
I'm going to use this:
Код:
new bool:isDead[MAX_PLAYERS char ];
isDead{playerid} = false;
Is it right or I missed something?
Reply
#18

i need help / i cread .pwn and paste it here and i want compile it and this script not copile plz send me link pwn and amx file thxalls@gmail.com
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)