New Member
|
11-23-2011
, 13:48
Re: SMAC Development Thread
|
#318
|
GoD-Tony,
Have you ever thought about detecting actual no spread, instead of just checking for improper angles?
For example
PHP Code:
/* Attempt to detect if the client is running no spread. This is done by comparing the fixed angles of the current and last frame. If the delta is small enough, then the fixed angles resulted in no difference, meaning that we found the client's original angles. If they exist then the client must be accounting for spread. The delta is variable and depends on the size of the spread. */ bool NoSpreadDetector::IsNoSpread( edict_t* pEdict, CBaseEntity* pEntity, CUserCmd* cmds, int num) { CBaseEntity* pWeapon = gBrainiacUtils.GetActiveWeapon(pEntity); int weaponID = GetWeaponID(playerinfomanager->GetPlayerInfo(pEdict)->GetWeaponName()); float spread = GetCSSSpread(pEntity, pWeapon, weaponID);
int numMouseMovements = 0;
NoSpreadClientData* pData = (NoSpreadClientData*)GetClientData(IndexOfEdict(pEdict));
pData->timeAlive += (cmds[0].tick_count - cmds[num-1].tick_count); //Loop through the given cmds for(int i = num - 2; i >= 0; i--) { CUserCmd* prev = &cmds[i+1]; CUserCmd* cur = &cmds[i];
Vector vOriginalOld, vOriginalNew, vDelta, vRealOld, vRealNew, vDeltaReal;
//Assuming the client is running no spread, //Get the real client view angles for the current and previous frame GetSpreadVector(prev->random_seed, spread, prev->viewangles, vOriginalOld); GetSpreadVector(cur->random_seed, spread, cur->viewangles, vOriginalNew);
//Get the real, non corrected angles as well AngleVectors(prev->viewangles, &vRealOld); AngleVectors(cur->viewangles, &vRealNew);
//Normalize to remove the increase in length due to adding the spread vector vOriginalOld.NormalizeInPlace(); vOriginalNew.NormalizeInPlace();
//Get the difference in the spread fixed forward vectors //If they are running no spread, then the delta should be very small vDelta = vOriginalNew - vOriginalOld;
//Check the actual angles to filter out legimate users vDeltaReal = vRealNew - vRealOld;
//Only apply checks if there have been angle changes //Otherwise, we may have increased false positives if(vDeltaReal.Length() > spread / 500.0f) { //We have detected no spread if(vDelta.Length() < spread / 500.0f) { pData->numStaticDetections++; return true; } //Angles are changing, but we aren't detecting no spread //Perhaps there have been mouse movements else { numMouseMovements++; } } }
//The mouse was moving for at least 3 frames //Try checking for a no spread pattern via linear regression if(numMouseMovements >= 3) { //First check for no spread if(IsNoSpreadWithMouseMovement(pEdict, pEntity, true, cmds, num)) { //Now filter out false positives if(IsNoSpreadWithMouseMovement(pEdict, pEntity, false, cmds, num)) { pData->numDynamicDetections++; return true; } } }
return false; }
PHP Code:
/* Check for no spread if there were changes in the angles that couldn't be accounted for by no spread alone. We must now check for a pattern in the changes. Also, allowing checking of error without no spread correction for removal of false positives. */ bool NoSpreadDetector::IsNoSpreadWithMouseMovement( edict_t* pEdict, CBaseEntity* pEntity, bool correctSpread, CUserCmd* cmds, int num ) { CBaseEntity* pWeapon = gBrainiacUtils.GetActiveWeapon(pEntity); int weaponID = GetWeaponID(playerinfomanager->GetPlayerInfo(pEdict)->GetWeaponName()); float spread = GetCSSSpread(pEntity, pWeapon, weaponID);
char url[512];
double x[32]; double y[32]; double z[32];
double out_x[32]; double out_y[32];
float flMovementDelta = 0; Vector vStart, vEnd;
if(correctSpread) { GetSpreadVector(cmds[0].random_seed, spread, cmds[0].viewangles, vEnd); GetSpreadVector(cmds[num - 1].random_seed, spread, cmds[num - 1].viewangles, vStart); } else { AngleVectors(cmds[0].viewangles, &vEnd); AngleVectors(cmds[num - 1].viewangles, &vStart); }
vEnd.NormalizeInPlace(); vStart.NormalizeInPlace();
flMovementDelta = (vEnd - vStart).Length();
//Loop through cmds saving each forward vector //Add each one to the list of points to be checked //Pass the data to our analyzer function
for(int i = num - 1; i >= 0; i--) { CUserCmd* cur = &cmds[i]; Vector vRealFwd;
if(correctSpread) GetSpreadVector(cur->random_seed, spread, cur->viewangles, vRealFwd); else AngleVectors(cur->viewangles, &vRealFwd);
vRealFwd.NormalizeInPlace();
x[i] = vRealFwd.x; y[i] = vRealFwd.y; z[i] = vRealFwd.z; }
//We have enough data to attempt linear regression if(num > 3) { float error = gBrainiacUtils.GetStandardErrorForPointsInSphere(vStart, vEnd, x, y, z, num, out_x, out_y);
//If the error is less than the error bound we have detected no spread. //Also, if we aren't correcting for spread and the error is bigger than a //larger error bound, we are probably running no spread if((correctSpread && (error < spread / 1000.0)) || (!correctSpread && (error > spread / 100.0f))) { //Ok, all signs point to the user running no spread //Perform the final check to excuse them if(!correctSpread && IsLegitimateErrorPattern(out_x, out_y, num)) return false;
return true; } }
return false; }
PHP Code:
/* Figure out what kind of error pattern the data shows. If the first and last approximations are on the same side of the least squares regression line, then perhaps they are a legitimate user who would have been cleared if polynomial fitting was used instead. Give them the benefit of the doubt since a client running real no spread will have no problem getting detected, even if they accidentally pass this test once or twice. */ bool NoSpreadDetector::IsLegitimateErrorPattern( double* x, double* y, int num ) { LinearRegression reg(x, y, num);
float firstError = reg.estimateY(x[0]) - y[0]; float lastError = reg.estimateY(x[num-1]) - y[num-1];
//We can't be sure they are running no spread //Give them the benefit of the doubt if( (firstError > 0 && lastError > 0) || (firstError < 0 && lastError < 0)) return true;
//If the error points lie on different sides of the residual line, //We have completed the process and detected a hacker, congratulations! return false; }
PHP Code:
/* We are given a set of points in a sphere, essentially the forward vectors of all of the cmd angles. We create a plane that is tangent to the sphere at the point on the sphere corresponding to the midpoint of the first and last vector. From there, we project all of the points on the sphere onto this plane. This results in a 2d mapping of the changes over the given cmds. Finally, we run a regression analysis on the points to see if there is a spread pattern, or rather lack of one, due to the corrected cmd angles. We do it this way so that we can stick to 2D regression and improve performance.
TODO: figure out what gets canceled and simplify */ float BrainiacUtils::GetStandardErrorForPointsInSphere(Vector vStart, Vector vEnd, double* x, double* y, double* z, int size, double* out_x, double* out_y) { Vector vTangentPoint, vMidPoint, vPlane, vOffset, vInPlane, vXAxis, vSignReference; LinearRegression reg;
vInPlane = vEnd - vStart;
vTangentPoint = vStart + vInPlane / 2; vTangentPoint.NormalizeInPlace();
//Equation of sphere: x^2 + y^2 + z^2 = 1 //Gradient vector: 2x, 2y, 2z vPlane = vTangentPoint * 2; float planeD = vPlane.x * -vTangentPoint.x + vPlane.y * -vTangentPoint.y + vPlane.z * -vTangentPoint.z;
//Project points onto the plane for(int i = 0; i < size; i++) { double distance, numerator, denominator, sign, length;
numerator = abs(vPlane.x * x[i] + vPlane.y * y[i] + vPlane.z * z[i] + planeD); denominator = vPlane.Length();
distance = numerator / denominator;
//Map point onto the 3D plain! yay Vector vNewPoint( x[i] + vTangentPoint.x * distance, y[i] + vTangentPoint.y * distance, z[i] + vTangentPoint.z * distance);
//Get the vector that we want to re-create in 2D //We will do so by going 3D cartesian -> polar -> 2D cartesian Vector vVecInPlane = vNewPoint - vTangentPoint;
length = vVecInPlane.Length();
vVecInPlane.NormalizeInPlace();
//Construct arbitrary axis for conversion to 2d //vStart and vEnd are equidistant from the plane //Add the distance to vInPlane to get a vector in the plane //Use it to detirmine the sign of the y value // //TODO: ghetto as fuck, find a better solution if(i == 0) { vXAxis = vTangentPoint + vInPlane; vXAxis.NormalizeInPlace();
vSignReference = vXAxis.Cross(vVecInPlane); }
//Sum with reference cross vector to see if it got longer, //or if it canceled out. The lengths should be 0 or 2 Vector vCrossSum = vSignReference + vXAxis.Cross(vVecInPlane);
if(vCrossSum.Length() > 1) sign = 1.0f; else sign = -1.0f;
//Get the angle between the two vectors float angle = acos(vXAxis.Dot(vVecInPlane) / (vXAxis.Length() * vVecInPlane.Length()));
float cx = length * cos(angle); float cy = length * sin(angle) * sign;
reg.addXY(cx, cy);
if(out_x) out_x[i] = cx;
if(out_y) out_y[i] = cy; }
return reg.getStdErrorEst(); }
Usage example:
PHP Code:
//Get outta here if(IsNoSpread(pEdict, pEntity, cmds, num)) { sprintf(kick, "say %s\n", playerinfomanager->GetPlayerInfo(pEdict)->GetName()); engine->ServerCommand(kick); }
It works extremely well in detecting when clients run no spread, including when they run it while moving their mouse. It provides virtually no false positives, and almost no false negatives.
|
|