/** * [CSRD] Name Filter * * An in-house plugin that tidies up names. * Made in response to a few script kiddies that are (ab)using newlines and other non-printing * characters in their name. * * If their existing name has no printable characters, their name will be changed to * "unprintable" plus their userid and their name will be locked. */ #pragma semicolon 1 #include #include #pragma newdecls required #define PLUGIN_VERSION "1.0.0" public Plugin myinfo = { name = "[CSRD] Name Filter", author = "nosoop", description = "Filters out rather unscrupulous characters from names and locks the name if " ... "they are using a completely non-printable name.", version = PLUGIN_VERSION, url = "https://pika.nom-nom-nom.us/git/" } #define UNPRINTABLE_NAME_USER_FORMAT "unprintable (%d)" bool g_bNameLockSupported; int g_FilteredWhitespace[] = { 0xe2a080, // braille pattern blank U+2800 0x0a, // linefeed 0x0d, // carriage return 0 }; public void OnPluginStart() { for (int i = 1; i <= MaxClients; i++) { if (IsClientConnected(i)) { OnNameUpdated(i); } } g_bNameLockSupported = CommandExists("namelockid"); } public void OnClientConnected(int client) { OnNameUpdated(client); } public void OnClientSettingsChanged(int client) { OnNameUpdated(client); } void OnNameUpdated(int client) { char currentName[MAX_NAME_LENGTH], newNameBuffer[MAX_NAME_LENGTH]; GetClientName(client, currentName, sizeof(currentName)); bool bNameChanged; /** * Copies valid characters into another buffer (while also dealing with Unicode). * The buffer is used if any characters are filtered. */ for (int i = 0; i < strlen(currentName); /* ... */) { int fullChar, nCharBytes; fullChar = GetMultiByteCharacterAt(currentName, i, nCharBytes); bool bCharFiltered; // Filter out whitespace int c; do { bCharFiltered |= (fullChar == g_FilteredWhitespace[c]); } while (g_FilteredWhitespace[++c] != 0 && !bCharFiltered); if (!bCharFiltered) { // it's a character that we don't really care about StrCatMultiByteChar(newNameBuffer, sizeof(newNameBuffer), fullChar, nCharBytes); } else { StrCat(newNameBuffer, sizeof(newNameBuffer), " "); } bNameChanged |= bCharFiltered; i += nCharBytes; } if (bNameChanged) { // Remove surrounding whitespace TrimString(newNameBuffer); // If there's no printable characters in the buffer, the player is a total dongbus. if (strlen(newNameBuffer) == 0) { Format(newNameBuffer, sizeof(newNameBuffer), UNPRINTABLE_NAME_USER_FORMAT, GetClientUserId(client)); SetClientNameLock(client); } SetClientName(client, newNameBuffer); } // TODO else not name changed, but check if we can do other non-filter stuff? (trim) } /** * Attempts to lock the specified client's name. */ stock void SetClientNameLock(int client, bool locked = true) { if (g_bNameLockSupported) { ServerCommand("namelockid %d %d", GetClientUserId(client), locked ? 0 : 1); } } /** * Gets a possibly multi-byte character at the specified position in a string as an integer. * * If the character is an ASCII character, the integer representation of the character will be * returned, and the number of characters read will be 1. * * This stock is made specifically for the purpose of handling strings with multi-byte * characters. */ stock int GetMultiByteCharacterAt(const char[] text, int pos, int &read = 0) { int nCharBytes, charOutput; nCharBytes = GetCharBytes(text[pos]); for (int b = 0; b < nCharBytes; b++) { charOutput = charOutput << 8; // read in the current byte charOutput |= text[pos + b] & 0xFF; read++; } return charOutput; } /** * Appends a multi-byte character to a string. */ stock void StrCatMultiByteChar(char[] buffer, int maxlen, int character, int size) { char[] appended = new char[size]; MultiByteCharacterToCharArray(appended, size, character); StrCat(buffer, maxlen, appended); } /** * Converts a multi-byte character to a char array. */ stock void MultiByteCharacterToCharArray(char[] buffer, int charsize, int character) { for (int i = 0; i < charsize; i++) { buffer[i] = (character >> ((charsize - i - 1) * 8 )) & 0xFF; } }