AlliedModders

AlliedModders (https://forums.alliedmods.net/index.php)
-   SourceBans / SourceBans++ (https://forums.alliedmods.net/forumdisplay.php?f=152)
-   -   Does Sourcebans not store the admin name in the database? (https://forums.alliedmods.net/showthread.php?t=331215)

jugule 03-10-2021 19:38

Does Sourcebans not store the admin name in the database?
 
I've been installing sourcebans on the server for some time, but I need to store the admin's name in the database, but it only stores the admin's IP, not the name.
Bans are applied from the game.
Source:

Code:

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//  SourceBans 1.4.11
//  Copyright (C) 2007-2015 SourceBans Team - Part of GameConnect
//  Licensed under GNU GPL version 3, or later.
//  Page: <http://www.sourcebans.net/> - <https://github.com/GameConnect/sourcebansv1>
//
// *************************************************************************

#pragma semicolon 1
#include <sourcemod>
#include <sourcebanspp>

#undef REQUIRE_PLUGIN
#include <adminmenu>
#tryinclude <updater>

#define SB_VERSION "1.6.3"
#define SBR_VERSION "1.6.3"

#if defined _updater_included
#define UPDATE_URL "https://sbpp.github.io/updater/updatefile.txt"
#endif

//GLOBAL DEFINES
#define YELLOW                                0x01
#define NAMECOLOR                        0x02
#define TEAMCOLOR                        0x03
#define GREEN                                0x04

#define DISABLE_ADDBAN                1
#define DISABLE_UNBAN                2

#define FLAG_LETTERS_SIZE 26

//#define DEBUG

enum State/* ConfigState */
{
        ConfigStateNone = 0,
        ConfigStateConfig,
        ConfigStateReasons,
        ConfigStateHacking,
        ConfigStateTime
}

State ConfigState;

#define Prefix "[SourceBans++] "

/* Admin Stuff*/
AdminCachePart loadPart;

AdminFlag g_FlagLetters[FLAG_LETTERS_SIZE];

/* Cvar handle*/
ConVar
        CvarHostIp
        , CvarPort;

/* Database handle */
Database DB;
Database SQLiteDB;

char
        ServerIp[24]
        , ServerPort[7]
        , DatabasePrefix[10] = "sb"
        , WebsiteAddress[128]
        , groupsLoc[128] /* Admin KeyValues */
        , adminsLoc[128]
        , overridesLoc[128]
        , logFile[256]; /* Log Stuff */

float RetryTime = 15.0;

bool
        loadAdmins /* Admin Stuff*/
        , loadGroups
        , loadOverrides
        , LateLoaded
        , AutoAdd
        , g_bConnecting = false
        , requireSiteLogin = false /* Require a lastvisited from SB site */
        , backupConfig = true
        , enableAdmins = true
        , PlayerStatus[MAXPLAYERS + 1]; /* Player ban check status */

int
        g_BanTarget[MAXPLAYERS + 1] =  { -1, ... }
        , g_BanTime[MAXPLAYERS + 1] =  { -1, ... }
        , curLoading
        , serverID = -1
        , ProcessQueueTime = 5
        , g_ownReasons[MAXPLAYERS + 1] =  { false, ... } /* Own Chat Reason */
        , CommandDisable; /* Disable of addban and unban */

Handle
        ConfigParser
        , hTopMenu = INVALID_HANDLE
        , TimeMenuHandle /* Menu file globals */
        , ReasonMenuHandle
        , HackingMenuHandle
        , g_hFwd_OnBanAdded
        , g_hFwd_OnReportAdded
        , PlayerRecheck[MAXPLAYERS + 1] =  { INVALID_HANDLE, ... } /* Datapack and Timer handles */
        , PlayerDataPack[MAXPLAYERS + 1] =  { INVALID_HANDLE, ... };

public Plugin myinfo =
{
        name = "SourceBans++: Main Plugin",
        author = "SourceBans Development Team, SourceBans++ Dev Team",
        description = "Advanced ban management for the Source engine",
        version = SBR_VERSION,
        url = "https://sbpp.github.io"
};

#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
#else
public bool AskPluginLoad(Handle myself, bool late, char[] error, int err_max)
#endif
{
        RegPluginLibrary("sourcebans++");

        CreateNative("SBBanPlayer", Native_SBBanPlayer);
        CreateNative("SBPP_BanPlayer", Native_SBBanPlayer);
        CreateNative("SBPP_ReportPlayer", Native_SBReportPlayer);

        g_hFwd_OnBanAdded = CreateGlobalForward("SBPP_OnBanPlayer", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String);
        g_hFwd_OnReportAdded = CreateGlobalForward("SBPP_OnReportPlayer", ET_Ignore, Param_Cell, Param_Cell, Param_String);

        LateLoaded = late;

        #if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
        return APLRes_Success;
        #else
        return true;
        #endif
}

public OnPluginStart()
{
        LoadTranslations("common.phrases");
        LoadTranslations("plugin.basecommands");
        LoadTranslations("sourcebans.phrases");
        LoadTranslations("basebans.phrases");
        loadAdmins = loadGroups = loadOverrides = false;

        CvarHostIp = FindConVar("hostip");
        CvarPort = FindConVar("hostport");
        CreateConVar("sb_version", SB_VERSION, _, FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY);
        CreateConVar("sbr_version", SBR_VERSION, _, FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY);
        RegServerCmd("sm_rehash", sm_rehash, "Reload SQL admins");
        RegAdminCmd("sm_ban", CommandBan, ADMFLAG_BAN, "sm_ban <#userid|name> <minutes|0> [reason]", "sourcebans");
        RegAdminCmd("sm_banip", CommandBanIp, ADMFLAG_BAN, "sm_banip <ip|#userid|name> <time> [reason]", "sourcebans");
        RegAdminCmd("sm_addban", CommandAddBan, ADMFLAG_RCON, "sm_addban <time> <steamid> [reason]", "sourcebans");
        RegAdminCmd("sm_unban", CommandUnban, ADMFLAG_UNBAN, "sm_unban <steamid|ip> [reason]", "sourcebans");
        RegAdminCmd("sb_reload",
                _CmdReload,
                ADMFLAG_RCON,
                "Reload sourcebans config and ban reason menu options",
                "sourcebans");

        RegConsoleCmd("say", ChatHook);
        RegConsoleCmd("say_team", ChatHook);

        if ((TimeMenuHandle = CreateMenu(MenuHandler_BanTimeList, MenuAction_Select|MenuAction_Cancel|MenuAction_DrawItem)) != INVALID_HANDLE)
        {
                SetMenuPagination(TimeMenuHandle, 8);
                SetMenuExitBackButton(TimeMenuHandle, true);
        }

        if ((ReasonMenuHandle = CreateMenu(ReasonSelected)) != INVALID_HANDLE)
        {
                SetMenuPagination(ReasonMenuHandle, 8);
                SetMenuExitBackButton(ReasonMenuHandle, true);
        }

        if ((HackingMenuHandle = CreateMenu(HackingSelected)) != INVALID_HANDLE)
        {
                SetMenuPagination(HackingMenuHandle, 8);
                SetMenuExitBackButton(HackingMenuHandle, true);
        }

        g_FlagLetters = CreateFlagLetters();

        BuildPath(Path_SM, logFile, sizeof(logFile), "logs/sourcebans.log");
        g_bConnecting = true;

        // Catch config error and show link to FAQ
        if (!SQL_CheckConfig("sourcebans"))
        {
                if (ReasonMenuHandle != INVALID_HANDLE)
                        CloseHandle(ReasonMenuHandle);
                if (HackingMenuHandle != INVALID_HANDLE)
                        CloseHandle(HackingMenuHandle);
                LogToFile(logFile, "Database failure: Could not find Database conf \"sourcebans\". See Docs: https://sbpp.github.io/docs/");
                SetFailState("Database failure: Could not find Database conf \"sourcebans\"");
                return;
        }

        Database.Connect(GotDatabase, "sourcebans");

        BuildPath(Path_SM, groupsLoc, sizeof(groupsLoc), "configs/sourcebans/sb_admin_groups.cfg");

        BuildPath(Path_SM, adminsLoc, sizeof(adminsLoc), "configs/sourcebans/sb_admins.cfg");

        BuildPath(Path_SM, overridesLoc, sizeof(overridesLoc), "configs/sourcebans/overrides_backup.cfg");

        InitializeBackupDB();

        // This timer is what processes the SQLite queue when the database is unavailable
        CreateTimer(float(ProcessQueueTime * 60), ProcessQueue);

        if (LateLoaded)
        {
                AccountForLateLoading();
        }

        #if defined _updater_included
        if (LibraryExists("updater"))
        {
                Updater_AddPlugin(UPDATE_URL);
        }
        #endif
}

#if defined _updater_included
public void OnLibraryAdded(const char[] name)
{
        if (StrEqual(name, "updater"))
        {
                Updater_AddPlugin(UPDATE_URL);
        }
}
#endif

public void OnAllPluginsLoaded()
{
        Handle topmenu;
        #if defined DEBUG
        LogToFile(logFile, "OnAllPluginsLoaded()");
        #endif

        if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE))
        {
                OnAdminMenuReady(topmenu);
        }
}

public void OnConfigsExecuted()
{
        char filename[200];
        BuildPath(Path_SM, filename, sizeof(filename), "plugins/basebans.smx");
        if (FileExists(filename))
        {
                char newfilename[200];
                BuildPath(Path_SM, newfilename, sizeof(newfilename), "plugins/disabled/basebans.smx");
                ServerCommand("sm plugins unload basebans");
                if (FileExists(newfilename))
                        DeleteFile(newfilename);
                RenameFile(newfilename, filename);
                LogToFile(logFile, "plugins/basebans.smx was unloaded and moved to plugins/disabled/basebans.smx");
        }
}

public void OnMapStart()
{
        ResetSettings();
}

public OnMapEnd()
{
        for (int i = 0; i <= MaxClients; i++)
        {
                if (PlayerDataPack[i] != INVALID_HANDLE)
                {
                        /* Need to close reason pack */
                        CloseHandle(PlayerDataPack[i]);
                        PlayerDataPack[i] = INVALID_HANDLE;
                }
        }
}

// CLIENT CONNECTION FUNCTIONS //

public Action OnClientPreAdminCheck(int client)
{
        if (!DB || GetUserAdmin(client) != INVALID_ADMIN_ID)
                return Plugin_Continue;

        return curLoading > 0 ? Plugin_Handled : Plugin_Continue;
}

public OnClientDisconnect(int client)
{
        if (PlayerRecheck[client] != INVALID_HANDLE)
        {
                KillTimer(PlayerRecheck[client]);
                PlayerRecheck[client] = INVALID_HANDLE;
        }
        g_ownReasons[client] = false;
}

public bool OnClientConnect(int client, char[] rejectmsg, int maxlen)
{
        PlayerStatus[client] = false;
        return true;
}

public void OnClientAuthorized(int client, const char[] auth)
{
        /* Do not check bots nor check player with lan steamid. */
        if (auth[0] == 'B' || auth[9] == 'L' || DB == INVALID_HANDLE)
        {
                PlayerStatus[client] = true;
                return;
        }

        char Query[256], ip[30];

        GetClientIP(client, ip, sizeof(ip));

        FormatEx(Query, sizeof(Query), "SELECT bid, ip FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, auth[8], ip);

        #if defined DEBUG
        LogToFile(logFile, "Checking ban for: %s", auth);
        #endif

        DB.Query(VerifyBan, Query, GetClientUserId(client), DBPrio_High);
}

public void OnRebuildAdminCache(AdminCachePart part)
{
        loadPart = part;
        switch (loadPart)
        {
                case AdminCache_Overrides:
                loadOverrides = true;
                case AdminCache_Groups:
                loadGroups = true;
                case AdminCache_Admins:
                loadAdmins = true;
        }
        if (DB == INVALID_HANDLE) {
                if (!g_bConnecting) {
                        g_bConnecting = true;
                        Database.Connect(GotDatabase, "sourcebans");
                }
        }
        else {
                GotDatabase(DB, "", 0);
        }
}

// COMMAND CODE //

public Action ChatHook(int client, int args)
{
        // is this player preparing to ban someone
        if (g_ownReasons[client])
        {
                // get the reason
                char reason[512];
                GetCmdArgString(reason, sizeof(reason));
                StripQuotes(reason);

                g_ownReasons[client] = false;

                if (StrEqual(reason[0], "!noreason"))
                {
                        PrintToChat(client, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason Aborted");
                        return Plugin_Handled;
                }

                // ban him!
                PrepareBan(client, g_BanTarget[client], g_BanTime[client], reason, sizeof(reason));

                // block the reason to be sent in chat
                return Plugin_Handled;
        }
        return Plugin_Continue;
}

public Action _CmdReload(int client, int args)
{
        ResetSettings();
        return Plugin_Handled;
}

public Action CommandBan(int client, int args)
{
        if (args < 2)
        {
                ReplyToCommand(client, "%sUsage: sm_ban <#userid|name> <time|0> [reason]", Prefix);
                return Plugin_Handled;
        }

        // This is mainly for me sanity since client used to be called admin and target used to be called client
        int admin = client;

        // Get the target, find target returns a message on failure so we do not
        char buffer[100];

        GetCmdArg(1, buffer, sizeof(buffer));

        int  target = FindTarget(client, buffer, true);

        if (target == -1)
        {
                return Plugin_Handled;
        }

        // Get the ban time
        GetCmdArg(2, buffer, sizeof(buffer));

        int time = StringToInt(buffer);

        if (!time && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN | ADMFLAG_ROOT)))
        {
                ReplyToCommand(client, "You do not have Perm Ban Permission");
                return Plugin_Handled;
        }

        // Get the reason
        char reason[128];

        if (args >= 3)
        {
                GetCmdArg(3, reason, sizeof(reason));
                for (new i = 4; i <= args; i++)
                {
                        GetCmdArg(i, buffer, sizeof(buffer));
                        Format(reason, sizeof(reason), "%s %s", reason, buffer);
                }
        }
        else
        {
                reason[0] = '\0';
        }

        g_BanTarget[client] = target;
        g_BanTime[client] = time;

        if (!PlayerStatus[target])
        {
                // The target has not been banned verify. It must be completed before you can ban anyone.
                ReplyToCommand(admin, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Ban Not Verified");
                return Plugin_Handled;
        }


        CreateBan(client, target, time, reason);
        return Plugin_Handled;
}

public Action CommandBanIp(int client, int args)
{
        if (args < 2)
        {
                ReplyToCommand(client, "%sUsage: sm_banip <ip|#userid|name> <time> [reason]", Prefix);
                return Plugin_Handled;
        }

        int len, next_len;
        char Arguments[256], arg[50], time[20];

        GetCmdArgString(Arguments, sizeof(Arguments));
        len = BreakString(Arguments, arg, sizeof(arg));

        if ((next_len = BreakString(Arguments[len], time, sizeof(time))) != -1)
        {
                len += next_len;
        }
        else
        {
                len = 0;
                Arguments[0] = '\0';
        }

        char target_name[MAX_TARGET_LENGTH];
        int target_list[1];
        bool tn_is_ml;

        int target = -1;

        if (ProcessTargetString(
                        arg,
                        client,
                        target_list,
                        1,
                        COMMAND_FILTER_CONNECTED | COMMAND_FILTER_NO_MULTI,
                        target_name,
                        sizeof(target_name),
                        tn_is_ml) > 0)
        {
                target = target_list[0];

                if (!IsFakeClient(target) && CanUserTarget(client, target))
                        GetClientIP(target, arg, sizeof(arg));
        }

        char adminIp[24], adminAuth[64];

        int minutes = StringToInt(time);

        if (!minutes && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN | ADMFLAG_ROOT)))
        {
                ReplyToCommand(client, "You do not have Perm Ban Permission");
                return Plugin_Handled;
        }
        if (!client)
        {
                // setup dummy adminAuth and adminIp for server
                strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
                strcopy(adminIp, sizeof(adminIp), ServerIp);
        } else {
                GetClientIP(client, adminIp, sizeof(adminIp));
                GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
        }

        // Pack everything into a data pack so we can retain it
        DataPack dataPack = new DataPack();
        dataPack.WriteCell(client);
        dataPack.WriteCell(minutes);
        dataPack.WriteString(Arguments[len]);
        dataPack.WriteString(arg);
        dataPack.WriteString(adminAuth);
        dataPack.WriteString(adminIp);

        char sQuery[256];

        FormatEx(sQuery, sizeof(sQuery), "SELECT bid FROM %s_bans WHERE type = 1 AND ip    = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL",
                DatabasePrefix, arg);

        SQL_TQuery(DB, SelectBanIpCallback, sQuery, dataPack, DBPrio_High);

        return Plugin_Handled;
}

public Action CommandUnban(int client, int args)
{
        if (args < 1)
        {
                ReplyToCommand(client, "%sUsage: sm_unban <steamid|ip> [reason]", Prefix);
                return Plugin_Handled;
        }

        if (CommandDisable & DISABLE_UNBAN)
        {
                // They must go to the website to unban people
                ReplyToCommand(client, "%s%t", Prefix, "Can Not Unban", WebsiteAddress);
                return Plugin_Handled;
        }

        int len;
        char Arguments[256], arg[50], adminAuth[64];

        GetCmdArgString(Arguments, sizeof(Arguments));

        if ((len = BreakString(Arguments, arg, sizeof(arg))) == -1)
        {
                len = 0;
                Arguments[0] = '\0';
        }
        if (!client)
        {
                // setup dummy adminAuth and adminIp for server
                strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
        } else {
                GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
        }

        // Pack everything into a data pack so we can retain it
        DataPack dataPack = new DataPack();
        dataPack.WriteCell(client);
        dataPack.WriteString(Arguments[len]);
        dataPack.WriteString(arg);
        dataPack.WriteString(adminAuth);

        char query[200];

        if (strncmp(arg, "STEAM_", 6) == 0)
        {
                Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 0 AND authid = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
        } else {
                Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 1 AND ip    = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
        }

        SQL_TQuery(DB, SelectUnbanCallback, query, dataPack);

        return Plugin_Handled;
}

public Action CommandAddBan(int client, int args)
{
        if (args < 2)
        {
                ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
                return Plugin_Handled;
        }

        if (CommandDisable & DISABLE_ADDBAN)
        {
                // They must go to the website to add bans
                ReplyToCommand(client, "%s%t", Prefix, "Can Not Add Ban", WebsiteAddress);
                return Plugin_Handled;
        }

        char arg_string[256], time[50], authid[50];

        GetCmdArgString(arg_string, sizeof(arg_string));

        int len, total_len;

        /* Get time */
        if ((len = BreakString(arg_string, time, sizeof(time))) == -1)
        {
                ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
                return Plugin_Handled;
        }
        total_len += len;

        /* Get steamid */
        if ((len = BreakString(arg_string[total_len], authid, sizeof(authid))) != -1)
        {
                total_len += len;
        }
        else
        {
                total_len = 0;
                arg_string[0] = '\0';
        }

        char adminIp[24], adminAuth[64];

        int  minutes = StringToInt(time);

        if (!minutes && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN | ADMFLAG_ROOT)))
        {
                ReplyToCommand(client, "You do not have Perm Ban Permission");
                return Plugin_Handled;
        }
        if (!client)
        {
                // setup dummy adminAuth and adminIp for server
                strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
                strcopy(adminIp, sizeof(adminIp), ServerIp);
        } else {
                GetClientIP(client, adminIp, sizeof(adminIp));
                GetClientAuthId(client, AuthId_Steam2, adminAuth, sizeof(adminAuth));
        }

        // Pack everything into a data pack so we can retain it
        DataPack dataPack = new DataPack();
        dataPack.WriteCell(client);
        dataPack.WriteCell(minutes);
        dataPack.WriteString(arg_string[total_len]);
        dataPack.WriteString(authid);
        dataPack.WriteString(adminAuth);
        dataPack.WriteString(adminIp);

        char sQuery[256];

        FormatEx(sQuery, sizeof sQuery, "SELECT bid FROM %s_bans WHERE type = 0 AND authid = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL",
                DatabasePrefix, authid);

        SQL_TQuery(DB, SelectAddbanCallback, sQuery, dataPack, DBPrio_High);

        return Plugin_Handled;
}

public Action sm_rehash(int args)
{
        if (enableAdmins)
                DumpAdminCache(AdminCache_Groups, true);
        DumpAdminCache(AdminCache_Overrides, true);
        return Plugin_Handled;
}



// MENU CODE //

public void OnAdminMenuReady(Handle topmenu)
{
        #if defined DEBUG
        LogToFile(logFile, "OnAdminMenuReady()");
        #endif

        /* Block us from being called twice */
        if (topmenu == hTopMenu)
        {
                return;
        }

        /* Save the Handle */
        hTopMenu = topmenu;

        /* Find the "Player Commands" category */
        TopMenuObject player_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_PLAYERCOMMANDS);

        if (player_commands != INVALID_TOPMENUOBJECT)
        {
                // just to avoid "unused variable 'res'" warning
                #if defined DEBUG
                TopMenuObject res = AddToTopMenu(hTopMenu,
                        "sm_ban",  // Name
                        TopMenuObject_Item,  // We are a submenu
                        AdminMenu_Ban,  // Handler function
                        player_commands,  // We are a submenu of Player Commands
                        "sm_ban",  // The command to be finally called (Override checks)
                        ADMFLAG_BAN); // What flag do we need to see the menu option
                char temp[125];
                Format(temp, 125, "Result of AddToTopMenu: %d", res);
                LogToFile(logFile, temp);
                LogToFile(logFile, "Added Ban option to admin menu");
                #else
                AddToTopMenu(hTopMenu,
                        "sm_ban",  // Name
                        TopMenuObject_Item,  // We are a submenu
                        AdminMenu_Ban,  // Handler function
                        player_commands,  // We are a submenu of Player Commands
                        "sm_ban",  // The command to be finally called (Override checks)
                        ADMFLAG_BAN); // What flag do we need to see the menu option
                #endif
        }
}

public void AdminMenu_Ban(Handle topmenu,
        TopMenuAction action,  // Action being performed
        TopMenuObject object_id,  // The object ID (if used)
        int param,  // client idx of admin who chose the option (if used)
        char[] buffer,  // Output buffer (if used)
        int maxlength) // Output buffer (if used)
{
        /* Clear the Ownreason bool, so he is able to chat again;) */
        g_ownReasons[param] = false;

        #if defined DEBUG
        LogToFile(logFile, "AdminMenu_Ban()");
        #endif

        switch (action)
        {
                // We are only being displayed, We only need to show the option name
                case TopMenuAction_DisplayOption:
                {
                        FormatEx(buffer, maxlength, "%T", "Ban player", param);

                        #if defined DEBUG
                        LogToFile(logFile, "AdminMenu_Ban() -> Formatted the Ban option text");
                        #endif
                }

                case TopMenuAction_SelectOption:
                {
                        DisplayBanTargetMenu(param); // Someone chose to ban someone, show the list of users menu

                        #if defined DEBUG
                        LogToFile(logFile, "AdminMenu_Ban() -> DisplayBanTargetMenu()");
                        #endif
                }
        }
}

public int ReasonSelected(Handle menu, MenuAction action, int param1, int param2)
{
        switch (action)
        {
                case MenuAction_Select:
                {
                        char info[128], key[128];

                        GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));

                        if (StrEqual("Hacking", key))
                        {
                                DisplayMenu(HackingMenuHandle, param1, MENU_TIME_FOREVER);
                                return;
                        }

                        else if (StrEqual("Own Reason", key)) // admin wants to use his own reason
                        {
                                g_ownReasons[param1] = true;
                                PrintToChat(param1, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason");
                                return;
                        }

                        else if (g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
                                PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));
                }

                case MenuAction_Cancel:
                {
                        if (param2 == MenuCancel_Disconnected)
                        {
                                if (PlayerDataPack[param1] != INVALID_HANDLE)
                                {
                                        CloseHandle(PlayerDataPack[param1]);
                                        PlayerDataPack[param1] = INVALID_HANDLE;
                                }
                        }

                        else
                        {
                                DisplayBanTimeMenu(param1);
                        }
                }
        }
}

public int HackingSelected(Handle menu, MenuAction action, int param1, int param2)
{
        switch (action)
        {
                case MenuAction_Select:
                {
                        char info[128], key[128];

                        GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));

                        if (g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
                                PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));
                }

                case MenuAction_Cancel:
                {
                        if (param2 == MenuCancel_Disconnected)
                        {
                                DataPack Pack = view_as<DataPack>(PlayerDataPack[param1]);

                                if (Pack != INVALID_HANDLE)
                                {
                                        Pack.ReadCell(); // admin index
                                        Pack.ReadCell(); // target index
                                        Pack.ReadCell(); // admin userid
                                        Pack.ReadCell(); // target userid
                                        Pack.ReadCell(); // time

                                        DataPack ReasonPack = Pack.ReadCell();

                                        if (ReasonPack != INVALID_HANDLE)
                                        {
                                                CloseHandle(ReasonPack);
                                        }

                                        CloseHandle(Pack);
                                        PlayerDataPack[param1] = INVALID_HANDLE;
                                }
                        }

                        else
                        {
                                DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
                        }
                }
        }
}

public int MenuHandler_BanPlayerList(Handle menu, MenuAction action, int param1, int param2)
{
        #if defined DEBUG
        LogToFile(logFile, "MenuHandler_BanPlayerList()");
        #endif

        switch (action)
        {
                case MenuAction_End:
                {
                        CloseHandle(menu);
                }

                case MenuAction_Cancel:
                {
                        if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                        {
                                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
                        }
                }

                case MenuAction_Select:
                {
                        char info[32], name[32];
                        int userid, target;

                        GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name));
                        userid = StringToInt(info);

                        if ((target = GetClientOfUserId(userid)) == 0)
                        {
                                PrintToChat(param1, "%s%t", Prefix, "Player no longer available");
                        }
                        else if (!CanUserTarget(param1, target))
                        {
                                PrintToChat(param1, "%s%t", Prefix, "Unable to target");
                        }
                        else
                        {
                                g_BanTarget[param1] = target;
                                DisplayBanTimeMenu(param1);
                        }
                }
        }
}

public int MenuHandler_BanTimeList(Handle menu, MenuAction action, int param1, int param2)
{
        #if defined DEBUG
        LogToFile(logFile, "MenuHandler_BanTimeList()");
        #endif

        switch (action)
        {
                case MenuAction_Cancel:
                {
                        if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
                        {
                                DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
                        }
                }

                case MenuAction_Select:
                {
                        char info[32];

                        GetMenuItem(menu, param2, info, sizeof(info));
                        g_BanTime[param1] = StringToInt(info);

                        //DisplayBanReasonMenu(param1);
                        DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
                }

                case MenuAction_DrawItem:
                {
                        char time[16];

                        GetMenuItem(menu, param2, time, sizeof(time));

                        return (StringToInt(time) > 0 || CheckCommandAccess(param1, "sm_unban", ADMFLAG_UNBAN | ADMFLAG_ROOT)) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED;
                }
        }

        return 0;
}

stock void DisplayBanTargetMenu(int client)
{
        #if defined DEBUG
        LogToFile(logFile, "DisplayBanTargetMenu()");
        #endif

        Handle menu = CreateMenu(MenuHandler_BanPlayerList); // Create a new menu, pass it the handler.

        char title[100];

        FormatEx(title, sizeof(title), "%T:", "Ban player", client);

        SetMenuTitle(menu, title); // Set the title
        SetMenuExitBackButton(menu, true); // Yes we want back/exit

        AddTargetsToMenu(menu,  // Add clients to our menu
                client,  // The client that called the display
                false,  // We want to see people connecting
                false); // And dead people

        DisplayMenu(menu, client, MENU_TIME_FOREVER); // Show the menu to the client FOREVER!
}

stock void DisplayBanTimeMenu(int client)
{
        #if defined DEBUG
        LogToFile(logFile, "DisplayBanTimeMenu()");
        #endif

        char title[100];
        FormatEx(title, sizeof(title), "%T:", "Ban player", client);
        SetMenuTitle(TimeMenuHandle, title);

        DisplayMenu(TimeMenuHandle, client, MENU_TIME_FOREVER);
}

stock void ResetMenu()
{
        if (TimeMenuHandle != INVALID_HANDLE)
        {
                RemoveAllMenuItems(TimeMenuHandle);
        }

        if (ReasonMenuHandle != INVALID_HANDLE)
        {
                RemoveAllMenuItems(ReasonMenuHandle);
        }

        if (HackingMenuHandle != INVALID_HANDLE)
        {
                RemoveAllMenuItems(HackingMenuHandle);
        }
}

// QUERY CALL BACKS //

public void GotDatabase(Database db, const char[] error, any data)
{
        if (db == INVALID_HANDLE)
        {
                LogToFile(logFile, "Database failure: %s. See Docs: https://sbpp.github.io/docs/", error);
                g_bConnecting = false;

                // Parse the overrides backup!
                ParseBackupConfig_Overrides();
                return;
        }

        DB = db;

        char query[1024];

        SQL_SetCharset(DB, "utf8");

        InsertServerInfo();

        //CreateTimer(900.0, PruneBans);

        if (loadOverrides)
        {
                Format(query, 1024, "SELECT type, name, flags FROM %s_overrides", DatabasePrefix);
                SQL_TQuery(DB, OverridesDone, query);
                loadOverrides = false;
        }

        if (loadGroups && enableAdmins)
        {
                FormatEx(query, 1024, "SELECT name, flags, immunity, groups_immune  \
                                        FROM %s_srvgroups ORDER BY id", DatabasePrefix);
                curLoading++;
                SQL_TQuery(DB, GroupsDone, query);

                #if defined DEBUG
                LogToFile(logFile, "Fetching Group List");
                #endif
                loadGroups = false;
        }

        if (loadAdmins && enableAdmins)
        {
                char queryLastLogin[50] = "";

                if (requireSiteLogin)
                        queryLastLogin = "lastvisit IS NOT NULL AND lastvisit != '' AND";

                if (serverID == -1)
                {
                        FormatEx(query, 1024, "SELECT authid, srv_password, (SELECT name FROM %s_srvgroups WHERE name = srv_group AND flags != '') AS srv_group, srv_flags, user, immunity  \
                                                FROM %s_admins_servers_groups AS asg \
                                                LEFT JOIN %s_admins AS a ON a.aid = asg.admin_id \
                                                WHERE %s (server_id = (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1)  \
                                                OR srv_group_id = ANY (SELECT group_id FROM %s_servers_groups WHERE server_id = (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1))) \
                                                GROUP BY aid, authid, srv_password, srv_group, srv_flags, user",
                                DatabasePrefix, DatabasePrefix, DatabasePrefix, queryLastLogin, DatabasePrefix, ServerIp, ServerPort, DatabasePrefix, DatabasePrefix, ServerIp, ServerPort);
                } else {
                        FormatEx(query, 1024, "SELECT authid, srv_password, (SELECT name FROM %s_srvgroups WHERE name = srv_group AND flags != '') AS srv_group, srv_flags, user, immunity  \
                                                FROM %s_admins_servers_groups AS asg \
                                                LEFT JOIN %s_admins AS a ON a.aid = asg.admin_id \
                                                WHERE %s server_id = %d  \
                                                OR srv_group_id = ANY (SELECT group_id FROM %s_servers_groups WHERE server_id = %d) \
                                                GROUP BY aid, authid, srv_password, srv_group, srv_flags, user",
                                DatabasePrefix, DatabasePrefix, DatabasePrefix, queryLastLogin, serverID, DatabasePrefix, serverID);
                }
                curLoading++;
                SQL_TQuery(DB, AdminsDone, query);

                #if defined DEBUG
                LogToFile(logFile, "Fetching Admin List");
                LogToFile(logFile, query);
                #endif
                loadAdmins = false;
        }
        g_bConnecting = false;
}

public VerifyInsert(Handle:owner, Handle:hndl, const String:error[], DataPack dataPack)
{
        if (dataPack == INVALID_HANDLE)
        {
                LogToFile(logFile, "Ban Failed: %s", error);
                return;
        }

        if (hndl == INVALID_HANDLE || error[0])
        {
                LogToFile(logFile, "Verify Insert Query Failed: %s", error);

                int admin = dataPack.ReadCell();
                dataPack.ReadCell(); // target
                dataPack.ReadCell(); // admin userid
                dataPack.ReadCell(); // target userid
                int time = dataPack.ReadCell();

                DataPack reasonPack = dataPack.ReadCell();

                char reason[128], name[50], auth[30], ip[20], adminAuth[30], adminIp[20];

                reasonPack.ReadString(reason, sizeof reason);

                dataPack.ReadString(name, sizeof name);
                dataPack.ReadString(auth, sizeof auth);
                dataPack.ReadString(ip, sizeof ip);
                dataPack.ReadString(adminAuth, sizeof adminAuth);
                dataPack.ReadString(adminIp, sizeof adminIp);

                dataPack.Reset();
                reasonPack.Reset();

                PlayerDataPack[admin] = INVALID_HANDLE;
                UTIL_InsertTempBan(time, name, auth, ip, reason, adminAuth, adminIp, dataPack);
                return;
        }

        int admin = dataPack.ReadCell();
        int client = dataPack.ReadCell();

        if (!IsClientConnected(client) || IsFakeClient(client))
                return;

        dataPack.ReadCell(); // admin userid

        int UserId = dataPack.ReadCell();
        int time = dataPack.ReadCell();

        DataPack ReasonPack = dataPack.ReadCell();

        char Name[64], Reason[128];

        dataPack.ReadString(Name, sizeof(Name));
        ReasonPack.ReadString(Reason, sizeof(Reason));

        if (!time)
        {
                if (Reason[0] == '\0')
                {
                        ShowActivityEx(admin, Prefix, "%t", "Permabanned player", Name);
                } else {
                        ShowActivityEx(admin, Prefix, "%t", "Permabanned player reason", Name, Reason);
                }
        } else {
                if (Reason[0] == '\0')
                {
                        ShowActivityEx(admin, Prefix, "%t", "Banned player", Name, time);
                } else {
                        ShowActivityEx(admin, Prefix, "%t", "Banned player reason", Name, time, Reason);
                }
        }

        LogAction(admin, client, "\"%L\" banned \"%L\" (minutes \"%d\") (reason \"%s\")", admin, client, time, Reason);

        if (PlayerDataPack[admin] != INVALID_HANDLE)
        {
                CloseHandle(PlayerDataPack[admin]);
                CloseHandle(ReasonPack);
                PlayerDataPack[admin] = INVALID_HANDLE;
        }

        // Kick player
        if (GetClientUserId(client) == UserId)
                KickClient(client, "%t", "Banned Check Site", WebsiteAddress);
}

public SelectBanIpCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        decl admin, minutes, String:adminAuth[30], String:adminIp[30], String:banReason[256], String:ip[16], String:Query[512];
        new String:reason[128];
        ResetPack(data);
        admin = ReadPackCell(data);
        minutes = ReadPackCell(data);
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, ip, sizeof(ip));
        ReadPackString(data, adminAuth, sizeof(adminAuth));
        ReadPackString(data, adminIp, sizeof(adminIp));
        SQL_EscapeString(DB, reason, banReason, sizeof(banReason));

        if (error[0])
        {
                LogToFile(logFile, "Ban IP Select Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                        PrintToChat(admin, "%sFailed to ban %s.", Prefix, ip);
                else
                        PrintToServer("%sFailed to ban %s.", Prefix, ip);
                return;
        }
        if (SQL_GetRowCount(hndl))
        {
                if (admin && IsClientInGame(admin))
                        PrintToChat(admin, "%s%s is already banned.", Prefix, ip);
                else
                        PrintToServer("%s%s is already banned.", Prefix, ip);
                return;
        }
        if (serverID == -1)
        {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (type, ip, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                (1, '%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')",
                        DatabasePrefix, ip, (minutes * 60), (minutes * 60), banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, ServerIp, ServerPort);
        } else {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (type, ip, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                (1, '%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                %d, ' ')",
                        DatabasePrefix, ip, (minutes * 60), (minutes * 60), banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, serverID);
        }

        SQL_TQuery(DB, InsertBanIpCallback, Query, data, DBPrio_High);
}

public InsertBanIpCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        // if the pack is good unpack it and close the handle
        new admin, minutes;
        new String:reason[128];
        decl String:arg[30];
        if (data != INVALID_HANDLE)
        {
                ResetPack(data);
                admin = ReadPackCell(data);
                minutes = ReadPackCell(data);
                ReadPackString(data, reason, sizeof(reason));
                ReadPackString(data, arg, sizeof(arg));
                CloseHandle(data);
        } else {
                // Technically this should not be possible
                ThrowError("Invalid Handle in InsertBanIpCallback");
        }

        // If error is not an empty string the query failed
        if (error[0] != '\0')
        {
                LogToFile(logFile, "Ban IP Insert Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                        PrintToChat(admin, "%ssm_banip failed", Prefix);
                return;
        }

        LogAction(admin,
                -1,
                "\"%L\" added ban (minutes \"%d\") (ip \"%s\") (reason \"%s\")",
                admin,
                minutes,
                arg,
                reason);
        if (admin && IsClientInGame(admin))
                PrintToChat(admin, "%s%s successfully banned", Prefix, arg);
        else
                PrintToServer("%s%s successfully banned", Prefix, arg);
}

public SelectUnbanCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        decl admin, String:arg[30], String:adminAuth[30], String:unbanReason[256];
        new String:reason[128];
        ResetPack(data);
        admin = ReadPackCell(data);
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, arg, sizeof(arg));
        ReadPackString(data, adminAuth, sizeof(adminAuth));
        SQL_EscapeString(DB, reason, unbanReason, sizeof(unbanReason));

        // If error is not an empty string the query failed
        if (error[0] != '\0')
        {
                LogToFile(logFile, "Unban Select Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                {
                        PrintToChat(admin, "%ssm_unban failed", Prefix);
                }
                return;
        }

        // If there was no results then a ban does not exist for that id
        if (hndl == INVALID_HANDLE || !SQL_GetRowCount(hndl))
        {
                if (admin && IsClientInGame(admin))
                {
                        PrintToChat(admin, "%sNo active bans found for that filter", Prefix);
                } else {
                        PrintToServer("%sNo active bans found for that filter", Prefix);
                }
                return;
        }

        // There is ban
        if (hndl != INVALID_HANDLE && SQL_FetchRow(hndl))
        {
                // Get the values from the existing ban record
                new bid = SQL_FetchInt(hndl, 0);

                decl String:query[1000];
                Format(query, sizeof(query), "UPDATE %s_bans SET RemovedBy = (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), RemoveType = 'U', RemovedOn = UNIX_TIMESTAMP(), ureason = '%s' WHERE bid = %d",
                        DatabasePrefix, DatabasePrefix, adminAuth, adminAuth[8], unbanReason, bid);

                SQL_TQuery(DB, InsertUnbanCallback, query, data);
        }
        return;
}

public InsertUnbanCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        // if the pack is good unpack it and close the handle
        decl admin, String:arg[30];
        new String:reason[128];
        if (data != INVALID_HANDLE)
        {
                ResetPack(data);
                admin = ReadPackCell(data);
                ReadPackString(data, reason, sizeof(reason));
                ReadPackString(data, arg, sizeof(arg));
                CloseHandle(data);
        } else {
                // Technically this should not be possible
                ThrowError("Invalid Handle in InsertUnbanCallback");
        }

        // If error is not an empty string the query failed
        if (error[0] != '\0')
        {
                LogToFile(logFile, "Unban Insert Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                {
                        PrintToChat(admin, "%ssm_unban failed", Prefix);
                }
                return;
        }

        LogAction(admin, -1, "\"%L\" removed ban (filter \"%s\") (reason \"%s\")", admin, arg, reason);
        if (admin && IsClientInGame(admin))
        {
                PrintToChat(admin, "%s%s successfully unbanned", Prefix, arg);
        } else {
                PrintToServer("%s%s successfully unbanned", Prefix, arg);
        }
}

public SelectAddbanCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        decl admin, minutes, String:adminAuth[30], String:adminIp[30], String:authid[20], String:banReason[256], String:Query[512];
        new String:reason[128];
        ResetPack(data);
        admin = ReadPackCell(data);
        minutes = ReadPackCell(data);
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, authid, sizeof(authid));
        ReadPackString(data, adminAuth, sizeof(adminAuth));
        ReadPackString(data, adminIp, sizeof(adminIp));
        SQL_EscapeString(DB, reason, banReason, sizeof(banReason));

        if (error[0])
        {
                LogToFile(logFile, "Add Ban Select Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                        PrintToChat(admin, "%sFailed to ban %s.", Prefix, authid);
                else
                        PrintToServer("%sFailed to ban %s.", Prefix, authid);
                return;
        }
        if (SQL_GetRowCount(hndl))
        {
                if (admin && IsClientInGame(admin))
                        PrintToChat(admin, "%s%s is already banned.", Prefix, authid);
                else
                        PrintToServer("%s%s is already banned.", Prefix, authid);
                return;
        }
        if (serverID == -1)
        {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')",
                        DatabasePrefix, authid, (minutes * 60), (minutes * 60), banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, ServerIp, ServerPort);
        } else {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                ('%s', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                %d, ' ')",
                        DatabasePrefix, authid, (minutes * 60), (minutes * 60), banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, serverID);
        }

        SQL_TQuery(DB, InsertAddbanCallback, Query, data, DBPrio_High);
}

public InsertAddbanCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        decl admin, minutes, String:authid[20];
        new String:reason[128];
        ResetPack(data);
        admin = ReadPackCell(data);
        minutes = ReadPackCell(data);
        ReadPackString(data, reason, sizeof(reason));
        ReadPackString(data, authid, sizeof(authid));

        // If error is not an empty string the query failed
        if (error[0] != '\0')
        {
                LogToFile(logFile, "Add Ban Insert Query Failed: %s", error);
                if (admin && IsClientInGame(admin))
                {
                        PrintToChat(admin, "%ssm_addban failed", Prefix);
                }
                return;
        }

        LogAction(admin,
                -1,
                "\"%L\" added ban (minutes \"%i\") (id \"%s\") (reason \"%s\")",
                admin,
                minutes,
                authid,
                reason);
        if (admin && IsClientInGame(admin))
        {
                PrintToChat(admin, "%s%s successfully banned", Prefix, authid);
        } else {
                PrintToServer("%s%s successfully banned", Prefix, authid);
        }
}

// ProcessQueueCallback is called as the result of selecting all the rows from the queue table
public ProcessQueueCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (hndl == INVALID_HANDLE || strlen(error) > 0)
        {
                LogToFile(logFile, "Failed to retrieve queued bans from sqlite database, %s", error);
                return;
        }

        decl String:auth[30];
        decl time;
        decl startTime;
        new String:reason[128];
        decl String:name[64];
        decl String:ip[20];
        decl String:adminAuth[30];
        decl String:adminIp[20];
        decl String:query[1024];
        decl String:banName[128];
        decl String:banReason[256];
        while (SQL_MoreRows(hndl))
        {
                // Oh noes! What happened?!
                if (!SQL_FetchRow(hndl))
                        continue;

                // if we get to here then there are rows in the queue pending processing
                SQL_FetchString(hndl, 0, auth, sizeof(auth));
                time = SQL_FetchInt(hndl, 1);
                startTime = SQL_FetchInt(hndl, 2);
                SQL_FetchString(hndl, 3, reason, sizeof(reason));
                SQL_FetchString(hndl, 4, name, sizeof(name));
                SQL_FetchString(hndl, 5, ip, sizeof(ip));
                SQL_FetchString(hndl, 6, adminAuth, sizeof(adminAuth));
                SQL_FetchString(hndl, 7, adminIp, sizeof(adminIp));
                SQL_EscapeString(SQLiteDB, name, banName, sizeof(banName));
                SQL_EscapeString(SQLiteDB, reason, banReason, sizeof(banReason));
                if (startTime + time * 60 > GetTime() || time == 0)
                {
                        // This ban is still valid and should be entered into the db
                        if (serverID == -1)
                        {
                                FormatEx(query, sizeof(query),
                                        "INSERT INTO %s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, sid) VALUES  \
                                                ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1))",
                                        DatabasePrefix, ip, auth, banName, startTime, startTime + time * 60, time * 60, banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, DatabasePrefix, ServerIp, ServerPort);
                        }
                        else
                        {
                                FormatEx(query, sizeof(query),
                                        "INSERT INTO %s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, sid) VALUES  \
                                                ('%s', '%s', '%s', %d, %d, %d, '%s', (SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'), '%s', \
                                                %d)",
                                        DatabasePrefix, ip, auth, banName, startTime, startTime + time * 60, time * 60, banReason, DatabasePrefix, adminAuth, adminAuth[8], adminIp, serverID);
                        }
                        new Handle:authPack = CreateDataPack();
                        WritePackString(authPack, auth);
                        ResetPack(authPack);
                        SQL_TQuery(DB, AddedFromSQLiteCallback, query, authPack);
                } else {
                        // The ban is no longer valid and should be deleted from the queue
                        FormatEx(query, sizeof(query), "DELETE FROM queue WHERE steam_id = '%s'", auth);
                        SQL_TQuery(SQLiteDB, ErrorCheckCallback, query);
                }
        }
        // We have finished processing the queue but should process again in ProcessQueueTime minutes
        CreateTimer(float(ProcessQueueTime * 60), ProcessQueue);
}

public AddedFromSQLiteCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        decl String:buffer[512];
        decl String:auth[40];
        ReadPackString(data, auth, sizeof(auth));
        if (error[0] == '\0')
        {
                // The insert was successful so delete the record from the queue
                FormatEx(buffer, sizeof(buffer), "DELETE FROM queue WHERE steam_id = '%s'", auth);
                SQL_TQuery(SQLiteDB, ErrorCheckCallback, buffer);

                // They are added to main banlist, so remove the temp ban
                RemoveBan(auth, BANFLAG_AUTHID);

        } else {
                // the insert failed so we leave the record in the queue and increase our temporary ban
                FormatEx(buffer, sizeof(buffer), "banid %d %s", ProcessQueueTime, auth);
                ServerCommand(buffer);
        }
        CloseHandle(data);
}

public ServerInfoCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (error[0])
        {
                LogToFile(logFile, "Server Select Query Failed: %s", error);
                return;
        }

        if (hndl == INVALID_HANDLE || SQL_GetRowCount(hndl) == 0)
        {
                // get the game folder name used to determine the mod
                decl String:desc[64], String:query[200];
                GetGameFolderName(desc, sizeof(desc));
                FormatEx(query, sizeof(query), "INSERT INTO %s_servers (ip, port, rcon, modid) VALUES ('%s', '%s', '', (SELECT mid FROM %s_mods WHERE modfolder = '%s'))", DatabasePrefix, ServerIp, ServerPort, DatabasePrefix, desc);
                SQL_TQuery(DB, ErrorCheckCallback, query);
        }
}

public ErrorCheckCallback(Handle:owner, Handle:hndle, const String:error[], any:data)
{
        if (error[0])
        {
                LogToFile(logFile, "Query Failed: %s", error);
        }
}

public void VerifyBan(Database db, DBResultSet results, const char[] error, int userid)
{
        char clientName[64], clientAuth[64], clientIp[64];

        int client = GetClientOfUserId(userid);

        if (!client)
                return;

        /* Failure happen. Do retry with delay */
        if (results == null)
        {
                LogToFile(logFile, "Verify Ban Query Failed: %s", error);
                PlayerRecheck[client] = CreateTimer(RetryTime, ClientRecheck, client);
                return;
        }

        GetClientIP(client, clientIp, sizeof(clientIp));
        GetClientAuthId(client, AuthId_Steam2, clientAuth, sizeof(clientAuth));
        GetClientName(client, clientName, sizeof(clientName));

        if (results.RowCount > 0)
        {
                char buffer[40], Name[128], Query[512];

                // Amending to ban record's IP field
                if (results.FetchRow())
                {
                        char sIP[32];

                        int iBid = results.FetchInt(0);
                        results.FetchString(1, sIP, sizeof sIP);

                        if (StrEqual(sIP, ""))
                        {
                                char sQuery[256];

                                FormatEx(sQuery, sizeof sQuery, "UPDATE %s_bans SET `ip` = '%s' WHERE `bid` = '%d'", DatabasePrefix, clientIp, iBid);

                                DB.Query(SQL_OnIPMend, sQuery, client);
                        }
                }

                DB.Escape(clientName, Name, sizeof Name);

                if (serverID == -1)
                {
                        FormatEx(Query, sizeof(Query), "INSERT INTO %s_banlog (sid ,time ,name ,bid) VALUES  \
                                ((SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), UNIX_TIMESTAMP(), '%s', \
                                (SELECT bid FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND RemoveType IS NULL LIMIT 0,1))",
                                DatabasePrefix, DatabasePrefix, ServerIp, ServerPort, Name, DatabasePrefix, clientAuth[8], clientIp);
                }
                else
                {
                        FormatEx(Query, sizeof(Query), "INSERT INTO %s_banlog (sid ,time ,name ,bid) VALUES  \
                                (%d, UNIX_TIMESTAMP(), '%s', \
                                (SELECT bid FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND RemoveType IS NULL LIMIT 0,1))",
                                DatabasePrefix, serverID, Name, DatabasePrefix, clientAuth[8], clientIp);
                }

                SQL_TQuery(DB, ErrorCheckCallback, Query, client, DBPrio_High);

                FormatEx(buffer, sizeof(buffer), "banid 5 %s", clientAuth);
                ServerCommand(buffer);
                KickClient(client, "%t", "Banned Check Site", WebsiteAddress);

                return;
        }

        #if defined DEBUG
        LogToFile(logFile, "%s is NOT banned.", clientAuth);
        #endif

        PlayerStatus[client] = true;
}

public void SQL_OnIPMend(Database db, DBResultSet results, const char[] error, int iClient)
{
        if (results == null)
        {
                char sIP[32], sSteamID[32];

                GetClientAuthId(iClient, AuthId_Steam3, sSteamID, sizeof sSteamID);
                GetClientIP(iClient, sIP, sizeof sIP);

                LogToFile(logFile, "Failed to mend IP address for %s (%s): %s", sSteamID, sIP, error);
        }
}

public AdminsDone(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        //SELECT authid, srv_password , srv_group, srv_flags, user
        if (hndl == INVALID_HANDLE || strlen(error) > 0)
        {
                --curLoading;
                CheckLoadAdmins();
                LogToFile(logFile, "Failed to retrieve admins from the database, %s", error);
                return;
        }
        decl String:authType[] = "steam";
        decl String:identity[66];
        decl String:password[66];
        decl String:groups[256];
        decl String:flags[32];
        decl String:name[66];
        new admCount = 0;
        new Immunity = 0;
        new AdminId:curAdm = INVALID_ADMIN_ID;
        new Handle:adminsKV = CreateKeyValues("Admins");

        while (SQL_MoreRows(hndl))
        {
                SQL_FetchRow(hndl);
                if (SQL_IsFieldNull(hndl, 0))
                        continue; // Sometimes some rows return NULL due to some setups

                SQL_FetchString(hndl, 0, identity, 66);
                SQL_FetchString(hndl, 1, password, 66);
                SQL_FetchString(hndl, 2, groups, 256);
                SQL_FetchString(hndl, 3, flags, 32);
                SQL_FetchString(hndl, 4, name, 66);

                Immunity = SQL_FetchInt(hndl, 5);

                TrimString(name);
                TrimString(identity);
                TrimString(groups);
                TrimString(flags);

                // Disable writing to file if they chose to
                if (backupConfig)
                {
                        KvJumpToKey(adminsKV, name, true);

                        KvSetString(adminsKV, "auth", authType);
                        KvSetString(adminsKV, "identity", identity);

                        if (strlen(flags) > 0)
                                KvSetString(adminsKV, "flags", flags);

                        if (strlen(groups) > 0)
                                KvSetString(adminsKV, "group", groups);

                        if (strlen(password) > 0)
                                KvSetString(adminsKV, "password", password);

                        if (Immunity > 0)
                                KvSetNum(adminsKV, "immunity", Immunity);

                        KvRewind(adminsKV);
                }

                // find or create the admin using that identity
                if ((curAdm = FindAdminByIdentity(authType, identity)) == INVALID_ADMIN_ID)
                {
                        curAdm = CreateAdmin(name);
                        // That should never happen!
                        if (!BindAdminIdentity(curAdm, authType, identity))
                        {
                                LogToFile(logFile, "Unable to bind admin %s to identity %s", name, identity);
                                RemoveAdmin(curAdm);
                                continue;
                        }
                }

                #if defined DEBUG
                LogToFile(logFile, "Given %s (%s) admin", name, identity);
                #endif

                new curPos = 0;
                new GroupId:curGrp = INVALID_GROUP_ID;
                new numGroups;
                decl String:iterGroupName[64];

                // Who thought this comma seperated group parsing would be a good idea?!
                /*
                decl String:grp[64];
                new nextPos = 0;
                while ((nextPos = SplitString(groups[curPos],",",grp,64)) != -1)
                {
                        curPos += nextPos;
                        curGrp = FindAdmGroup(grp);
                        if (curGrp == INVALID_GROUP_ID)
                        {
                                LogToFile(logFile, "Unknown group \"%s\"",grp);
                        }
                        else
                        {
                                // Check, if he's not in the group already.
                                numGroups = GetAdminGroupCount(curAdm);
                                for(new i=0;i<numGroups;i++)
                                {
                                        GetAdminGroup(curAdm, i, iterGroupName, sizeof(iterGroupName));
                                        // Admin is already part of the group, so don't try to inherit its permissions.
                                        if(StrEqual(iterGroupName, grp))
                                        {
                                                numGroups = -2;
                                                break;
                                        }
                                }
                                // Only try to inherit the group, if it's a new one.
                                if (numGroups != -2 && !AdminInheritGroup(curAdm,curGrp))
                                {
                                        LogToFile(logFile, "Unable to inherit group \"%s\"",grp);
                                }
                        }
                }*/

                if (strcmp(groups[curPos], "") != 0)
                {
                        curGrp = FindAdmGroup(groups[curPos]);
                        if (curGrp == INVALID_GROUP_ID)
                        {
                                LogToFile(logFile, "Unknown group \"%s\"", groups[curPos]);
                        }
                        else
                        {
                                // Check, if he's not in the group already.
                                numGroups = GetAdminGroupCount(curAdm);
                                for (new i = 0; i < numGroups; i++)
                                {
                                        GetAdminGroup(curAdm, i, iterGroupName, sizeof(iterGroupName));
                                        // Admin is already part of the group, so don't try to inherit its permissions.
                                        if (StrEqual(iterGroupName, groups[curPos]))
                                        {
                                                numGroups = -2;
                                                break;
                                        }
                                }

                                // Only try to inherit the group, if it's a new one.
                                if (numGroups != -2 && !AdminInheritGroup(curAdm, curGrp))
                                {
                                        LogToFile(logFile, "Unable to inherit group \"%s\"", groups[curPos]);
                                }

                                if (GetAdminImmunityLevel(curAdm) < Immunity)
                                {
                                        SetAdminImmunityLevel(curAdm, Immunity);
                                }
                                #if defined DEBUG
                                LogToFile(logFile, "Admin %s (%s) has %d immunity", name, identity, Immunity);
                                #endif
                        }
                }

                if (strlen(password) > 0)
                        SetAdminPassword(curAdm, password);

                for (new i = 0; i < strlen(flags); ++i)
                {
                        if (flags[i] < 'a' || flags[i] > 'z')
                                continue;

                        if (g_FlagLetters[flags[i]-'a'] < Admin_Reservation)
                                continue;

                        SetAdminFlag(curAdm, g_FlagLetters[flags[i]-'a'], true);
                }
                ++admCount;
        }

        if (backupConfig)
                KeyValuesToFile(adminsKV, adminsLoc);
        CloseHandle(adminsKV);

        #if defined DEBUG
        LogToFile(logFile, "Finished loading %i admins.", admCount);
        #endif

        --curLoading;
        CheckLoadAdmins();
}

public GroupsDone(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (hndl == INVALID_HANDLE)
        {
                curLoading--;
                CheckLoadAdmins();
                LogToFile(logFile, "Failed to retrieve groups from the database, %s", error);
                return;
        }
        decl String:grpName[128], String:immuneGrpName[128];
        decl String:grpFlags[32];
        new Immunity;
        new grpCount = 0;
        new Handle:groupsKV = CreateKeyValues("Groups");

        new GroupId:curGrp = INVALID_GROUP_ID;
        while (SQL_MoreRows(hndl))
        {
                SQL_FetchRow(hndl);
                if (SQL_IsFieldNull(hndl, 0))
                        continue; // Sometimes some rows return NULL due to some setups
                SQL_FetchString(hndl, 0, grpName, 128);
                SQL_FetchString(hndl, 1, grpFlags, 32);
                Immunity = SQL_FetchInt(hndl, 2);
                SQL_FetchString(hndl, 3, immuneGrpName, 128);

                TrimString(grpName);
                TrimString(grpFlags);
                TrimString(immuneGrpName);

                // Ignore empty rows..
                if (!strlen(grpName))
                        continue;

                curGrp = CreateAdmGroup(grpName);

                if (backupConfig)
                {
                        KvJumpToKey(groupsKV, grpName, true);
                        if (strlen(grpFlags) > 0)
                                KvSetString(groupsKV, "flags", grpFlags);
                        if (Immunity > 0)
                                KvSetNum(groupsKV, "immunity", Immunity);

                        KvRewind(groupsKV);
                }

                if (curGrp == INVALID_GROUP_ID)
                {  //This occurs when the group already exists
                        curGrp = FindAdmGroup(grpName);
                }

                for (new i = 0; i < strlen(grpFlags); ++i)
                {
                        if (grpFlags[i] < 'a' || grpFlags[i] > 'z')
                                continue;

                        if (g_FlagLetters[grpFlags[i]-'a'] < Admin_Reservation)
                                continue;

                        SetAdmGroupAddFlag(curGrp, g_FlagLetters[grpFlags[i]-'a'], true);
                }

                // Set the group immunity.
                if (Immunity > 0)
                {
                        SetAdmGroupImmunityLevel(curGrp, Immunity);
                        #if defined DEBUG
                        LogToFile(logFile, "Group %s has %d immunity", grpName, Immunity);
                        #endif
                }

                grpCount++;
        }

        if (backupConfig)
                KeyValuesToFile(groupsKV, groupsLoc);
        CloseHandle(groupsKV);

        #if defined DEBUG
        LogToFile(logFile, "Finished loading %i groups.", grpCount);
        #endif

        // Load the group overrides
        decl String:query[512];
        FormatEx(query, 512, "SELECT sg.name, so.type, so.name, so.access FROM %s_srvgroups_overrides so LEFT JOIN %s_srvgroups sg ON sg.id = so.group_id ORDER BY sg.id", DatabasePrefix, DatabasePrefix);
        SQL_TQuery(DB, LoadGroupsOverrides, query);

        /*if (reparse)
        {
                decl String:query[512];
                FormatEx(query,512,"SELECT name, immunity, groups_immune FROM %s_srvgroups ORDER BY id",DatabasePrefix);
                SQL_TQuery(DB,GroupsSecondPass,query);
        }
        else
        {
                curLoading--;
                CheckLoadAdmins();
        }*/
}

// Reparse to apply inherited immunity
public GroupsSecondPass(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (hndl == INVALID_HANDLE)
        {
                curLoading--;
                CheckLoadAdmins();
                LogToFile(logFile, "Failed to retrieve groups from the database, %s", error);
                return;
        }
        decl String:grpName[128], String:immunityGrpName[128];

        new GroupId:curGrp = INVALID_GROUP_ID;
        new GroupId:immuneGrp = INVALID_GROUP_ID;
        while (SQL_MoreRows(hndl))
        {
                SQL_FetchRow(hndl);
                if (SQL_IsFieldNull(hndl, 0))
                        continue; // Sometimes some rows return NULL due to some setups

                SQL_FetchString(hndl, 0, grpName, 128);
                TrimString(grpName);
                if (strlen(grpName) == 0)
                        continue;

                SQL_FetchString(hndl, 2, immunityGrpName, sizeof(immunityGrpName));
                TrimString(immunityGrpName);

                curGrp = FindAdmGroup(grpName);
                if (curGrp == INVALID_GROUP_ID)
                        continue;

                immuneGrp = FindAdmGroup(immunityGrpName);
                if (immuneGrp == INVALID_GROUP_ID)
                        continue;

                SetAdmGroupImmuneFrom(curGrp, immuneGrp);

                #if defined DEBUG
                LogToFile(logFile, "Group %s inhertied immunity from group %s", grpName, immunityGrpName);
                #endif
        }
        --curLoading;
        CheckLoadAdmins();
}

public LoadGroupsOverrides(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (hndl == INVALID_HANDLE)
        {
                curLoading--;
                CheckLoadAdmins();
                LogToFile(logFile, "Failed to retrieve group overrides from the database, %s", error);
                return;
        }
        decl String:sGroupName[128], String:sType[16], String:sCommand[64], String:sAllowed[16];
        decl OverrideRule:iRule, OverrideType:iType;

        new Handle:groupsKV = CreateKeyValues("Groups");
        FileToKeyValues(groupsKV, groupsLoc);

        new GroupId:curGrp = INVALID_GROUP_ID;
        while (SQL_MoreRows(hndl))
        {
                SQL_FetchRow(hndl);
                if (SQL_IsFieldNull(hndl, 0))
                        continue; // Sometimes some rows return NULL due to some setups

                SQL_FetchString(hndl, 0, sGroupName, sizeof(sGroupName));
                TrimString(sGroupName);
                if (strlen(sGroupName) == 0)
                        continue;

                SQL_FetchString(hndl, 1, sType, sizeof(sType));
                SQL_FetchString(hndl, 2, sCommand, sizeof(sCommand));
                SQL_FetchString(hndl, 3, sAllowed, sizeof(sAllowed));

                curGrp = FindAdmGroup(sGroupName);
                if (curGrp == INVALID_GROUP_ID)
                        continue;

                iRule = StrEqual(sAllowed, "allow") ? Command_Allow : Command_Deny;
                iType = StrEqual(sType, "group") ? Override_CommandGroup : Override_Command;

                #if defined DEBUG
                PrintToServer("AddAdmGroupCmdOverride(%i, %s, %i, %i)", curGrp, sCommand, iType, iRule);
                #endif

                // Save overrides into admin_groups.cfg backup
                if (KvJumpToKey(groupsKV, sGroupName))
                {
                        KvJumpToKey(groupsKV, "Overrides", true);
                        if (iType == Override_Command)
                                KvSetString(groupsKV, sCommand, sAllowed);
                        else
                        {
                                Format(sCommand, sizeof(sCommand), "@%s", sCommand);
                                KvSetString(groupsKV, sCommand, sAllowed);
                        }
                        KvRewind(groupsKV);
                }

                AddAdmGroupCmdOverride(curGrp, sCommand, iType, iRule);
        }
        curLoading--;
        CheckLoadAdmins();

        if (backupConfig)
                KeyValuesToFile(groupsKV, groupsLoc);
        CloseHandle(groupsKV);
}

public OverridesDone(Handle:owner, Handle:hndl, const String:error[], any:data)
{
        if (hndl == INVALID_HANDLE)
        {
                LogToFile(logFile, "Failed to retrieve overrides from the database, %s", error);
                ParseBackupConfig_Overrides();
                return;
        }

        new Handle:hKV = CreateKeyValues("SB_Overrides");

        decl String:sFlags[32], String:sName[64], String:sType[64];
        while (SQL_FetchRow(hndl))
        {
                SQL_FetchString(hndl, 0, sType, sizeof(sType));
                SQL_FetchString(hndl, 1, sName, sizeof(sName));
                SQL_FetchString(hndl, 2, sFlags, sizeof(sFlags));

                // KeyValuesToFile won't add that key, if the value is ""..
                if (sFlags[0] == '\0')
                {
                        sFlags[0] = ' ';
                        sFlags[1] = '\0';
                }

                #if defined DEBUG
                LogToFile(logFile, "Adding override (%s, %s, %s)", sType, sName, sFlags);
                #endif

                if (StrEqual(sType, "command"))
                {
                        AddCommandOverride(sName, Override_Command, ReadFlagString(sFlags));
                        KvJumpToKey(hKV, "override_commands", true);
                        KvSetString(hKV, sName, sFlags);
                        KvGoBack(hKV);
                }
                else if (StrEqual(sType, "group"))
                {
                        AddCommandOverride(sName, Override_CommandGroup, ReadFlagString(sFlags));
                        KvJumpToKey(hKV, "override_groups", true);
                        KvSetString(hKV, sName, sFlags);
                        KvGoBack(hKV);
                }
        }

        KvRewind(hKV);

        if (backupConfig)
                KeyValuesToFile(hKV, overridesLoc);
        CloseHandle(hKV);
}

// TIMER CALL BACKS //

public Action:ClientRecheck(Handle:timer, any:client)
{
        decl String:Authid[64];
        if (!PlayerStatus[client] && IsClientConnected(client) && GetClientAuthId(client, AuthId_Steam2, Authid, sizeof(Authid)))
        {
                OnClientAuthorized(client, Authid);
        }

        PlayerRecheck[client] = INVALID_HANDLE;
        return Plugin_Stop;
}

/*
public Action:PruneBans(Handle:timer)
{
        decl String:Query[512];
        FormatEx(Query, sizeof(Query),
                        "UPDATE %s_bans SET RemovedBy = 0, RemoveType = 'E', RemovedOn = UNIX_TIMESTAMP() WHERE length != '0' AND ends < UNIX_TIMESTAMP()",
                        DatabasePrefix);

        SQL_TQuery(DB, ErrorCheckCallback, Query);
        return Plugin_Continue;
}
*/

public Action:ProcessQueue(Handle:timer, any:data)
{
        decl String:buffer[512];
        Format(buffer, sizeof(buffer), "SELECT steam_id, time, start_time, reason, name, ip, admin_id, admin_ip FROM queue");
        SQL_TQuery(SQLiteDB, ProcessQueueCallback, buffer);
}

// PARSER //

static InitializeConfigParser()
{
        if (ConfigParser == INVALID_HANDLE)
        {
                ConfigParser = SMC_CreateParser();
                SMC_SetReaders(ConfigParser, ReadConfig_NewSection, ReadConfig_KeyValue, ReadConfig_EndSection);
        }
}

static InternalReadConfig(const String:path[])
{
        ConfigState = ConfigStateNone;

        new SMCError:err = SMC_ParseFile(ConfigParser, path);

        if (err != SMCError_Okay)
        {
                decl String:buffer[64];
                PrintToServer("%s", SMC_GetErrorString(err, buffer, sizeof(buffer)) ? buffer : "Fatal parse error");
        }
}

public SMCResult:ReadConfig_NewSection(Handle:smc, const String:name[], bool:opt_quotes)
{
        if (name[0])
        {
                if (strcmp("Config", name, false) == 0) {
                        ConfigState = ConfigStateConfig;
                } else if (strcmp("BanReasons", name, false) == 0) {
                        ConfigState = ConfigStateReasons;
                } else if (strcmp("HackingReasons", name, false) == 0) {
                        ConfigState = ConfigStateHacking;
                } else if (strcmp("BanTime", name, false) == 0) {
                        ConfigState = ConfigStateTime;
                }
        }
        return SMCParse_Continue;
}

public SMCResult:ReadConfig_KeyValue(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
{
        if (!key[0])
                return SMCParse_Continue;

        switch (ConfigState)
        {
                case ConfigStateConfig:
                {
                        if (strcmp("website", key, false) == 0)
                        {
                                strcopy(WebsiteAddress, sizeof(WebsiteAddress), value);
                        }
                        else if (strcmp("Addban", key, false) == 0)
                        {
                                if (StringToInt(value) == 0)
                                {
                                        CommandDisable |= DISABLE_ADDBAN;
                                }
                        }
                        else if (strcmp("AutoAddServer", key, false) == 0)
                        {
                                AutoAdd = StringToInt(value) == 1;
                        }
                        else if (strcmp("Unban", key, false) == 0)
                        {
                                if (StringToInt(value) == 0)
                                {
                                        CommandDisable |= DISABLE_UNBAN;
                                }
                        }
                        else if (strcmp("DatabasePrefix", key, false) == 0)
                        {
                                strcopy(DatabasePrefix, sizeof(DatabasePrefix), value);

                                if (DatabasePrefix[0] == '\0')
                                {
                                        DatabasePrefix = "sb";
                                }
                        }
                        else if (strcmp("RetryTime", key, false) == 0)
                        {
                                RetryTime = StringToFloat(value);
                                if (RetryTime < 15.0)
                                {
                                        RetryTime = 15.0;
                                } else if (RetryTime > 60.0) {
                                        RetryTime = 60.0;
                                }
                        }
                        else if (strcmp("ProcessQueueTime", key, false) == 0)
                        {
                                ProcessQueueTime = StringToInt(value);
                        }
                        else if (strcmp("BackupConfigs", key, false) == 0)
                        {
                                backupConfig = StringToInt(value) == 1;
                        }
                        else if (strcmp("EnableAdmins", key, false) == 0)
                        {
                                enableAdmins = StringToInt(value) == 1;
                        }
                        else if (strcmp("RequireSiteLogin", key, false) == 0)
                        {
                                requireSiteLogin = StringToInt(value) == 1;
                        }
                        else if (strcmp("ServerID", key, false) == 0)
                        {
                                serverID = StringToInt(value);
                        }
                }

                case ConfigStateReasons:
                {
                        if (ReasonMenuHandle != INVALID_HANDLE)
                        {
                                AddMenuItem(ReasonMenuHandle, key, value);
                        }
                }
                case ConfigStateHacking:
                {
                        if (HackingMenuHandle != INVALID_HANDLE)
                        {
                                AddMenuItem(HackingMenuHandle, key, value);
                        }
                }
                case ConfigStateTime:
                {
                        if (StringToInt(key) > -1 && TimeMenuHandle != INVALID_HANDLE)
                        {
                                AddMenuItem(TimeMenuHandle, key, value);
                        }
                }
        }
        return SMCParse_Continue;
}

public SMCResult:ReadConfig_EndSection(Handle:smc)
{
        return SMCParse_Continue;
}


/*********************************************************
 * Ban Player from server
 *
 * @param client        The client index of the player to ban
 * @param time                The time to ban the player for (in minutes, 0 = permanent)
 * @param reason        The reason to ban the player from the server
 * @noreturn
 *********************************************************/
public Native_SBBanPlayer(Handle plugin, int numParams)
{
        int client = GetNativeCell(1);
        int target = GetNativeCell(2);
        int time = GetNativeCell(3);
        char reason[128];
        GetNativeString(4, reason, 128);

        if (reason[0] == '\0')
                strcopy(reason, sizeof(reason), "Banned by SourceBans");

        if (client && IsClientInGame(client))
        {
                AdminId aid = GetUserAdmin(client);
                if (aid == INVALID_ADMIN_ID)
                {
                        ThrowNativeError(SP_ERROR_NATIVE, "Ban Error: Player is not an admin.");
                        return 0;
                }

                if (!GetAdminFlag(aid, Admin_Ban))
                {
                        ThrowNativeError(SP_ERROR_NATIVE, "Ban Error: Player does not have BAN flag.");
                        return 0;
                }
        }

        PrepareBan(client, target, time, reason, sizeof(reason));
        return true;
}

public int Native_SBReportPlayer(Handle plugin, int numParams)
{
        if (numParams < 3)
        {
                ThrowNativeError(SP_ERROR_NATIVE, "Invalid amount of arguments. Received %d arguments", numParams);
                return;
        }

        int iReporter = GetNativeCell(1)
          , iTarget = GetNativeCell(2)
          , iReasonLen;

        int iTime = GetTime();

        GetNativeStringLength(3, iReasonLen);

        iReasonLen++;

        char[] sReason = new char[iReasonLen];

        GetNativeString(3, sReason, iReasonLen);

        char sRAuth[32], sTAuth[32], sRName[MAX_NAME_LENGTH + 1], sTName[MAX_NAME_LENGTH + 1],
        sRIP[16], sTIP[16], sREscapedName[MAX_NAME_LENGTH * 2 + 1], sTEscapedName[MAX_NAME_LENGTH * 2 + 1];

        char[] sEscapedReason = new char[iReasonLen * 2 + 1];

        GetClientAuthId(iReporter, AuthId_Steam2, sRAuth, sizeof sRAuth);
        GetClientAuthId(iTarget, AuthId_Steam2, sTAuth, sizeof sTAuth);

        GetClientName(iReporter, sRName, sizeof sRName);
        GetClientName(iTarget, sTName, sizeof sTName);

        GetClientIP(iReporter, sRIP, sizeof sRIP);
        GetClientIP(iTarget, sTIP, sizeof sTIP);

        DB.Escape(sRName, sREscapedName, sizeof sREscapedName);
        DB.Escape(sTName, sTEscapedName, sizeof sTEscapedName);
        DB.Escape(sReason, sEscapedReason, iReasonLen * 2 + 1);

        char[] sQuery = new char[512 + (iReasonLen * 2 + 1)];

        Format(sQuery, 512 + (iReasonLen * 2 + 1), "INSERT INTO %s_submissions (`submitted`, `modid`, `SteamId`, `name`, `email`, `reason`, `ip`, `subname`, `sip`, `archiv`, `server`)"
        ... "VALUES ('%d', 0, '%s', '%s', '%s', '%s', '%s', '%s', '%s', 0, 0)", DatabasePrefix, iTime, sTAuth, sTEscapedName, sRAuth, sEscapedReason, sRIP, sREscapedName, sTIP);

        DataPack ForwardPack = new DataPack();

        ForwardPack.WriteCell(iReporter);
        ForwardPack.WriteCell(iTarget);
        ForwardPack.WriteCell(iReasonLen);
        ForwardPack.WriteString(sReason);

        DB.Query(SQL_OnReportPlayer, sQuery, ForwardPack);
}

public void SQL_OnReportPlayer(Database db, DBResultSet results, const char[] error, DataPack ForwardPack)
{
        if (results == null)
                LogToFile(logFile, "Failed to submit report: %s", error);
        else
        {
                ForwardPack.Reset();

                int iReporter = ForwardPack.ReadCell();
                int iTarget = ForwardPack.ReadCell();
                int iReasonLen = ForwardPack.ReadCell();

                char[] sReason = new char[iReasonLen];

                ForwardPack.ReadString(sReason, iReasonLen);

                Call_StartForward(g_hFwd_OnReportAdded);
                Call_PushCell(iReporter);
                Call_PushCell(iTarget);
                Call_PushString(sReason);
                Call_Finish();
        }
}

// STOCK FUNCTIONS //

public InitializeBackupDB()
{
        char error[255];

        SQLiteDB = SQLite_UseDatabase("sourcebans-queue", error, sizeof(error));
        if (SQLiteDB == INVALID_HANDLE)
                SetFailState(error);

        SQL_LockDatabase(SQLiteDB);
        SQL_FastQuery(SQLiteDB, "CREATE TABLE IF NOT EXISTS queue (steam_id TEXT PRIMARY KEY ON CONFLICT REPLACE, time INTEGER, start_time INTEGER, reason TEXT, name TEXT, ip TEXT, admin_id TEXT, admin_ip TEXT);");
        SQL_UnlockDatabase(SQLiteDB);
}

public bool CreateBan(int client, int target, int time, const char[] reason)
{
        char adminIp[24], adminAuth[64];
        int admin = client;

        // The server is the one calling the ban
        if (!admin)
        {
                if (reason[0] == '\0')
                {
                        // We cannot pop the reason menu if the command was issued from the server
                        PrintToServer("%s%T", Prefix, "Include Reason", LANG_SERVER);
                        return false;
                }

                // setup dummy adminAuth and adminIp for server
                strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
                strcopy(adminIp, sizeof(adminIp), ServerIp);
        } else {
                GetClientIP(admin, adminIp, sizeof(adminIp));
                GetClientAuthId(admin, AuthId_Steam2, adminAuth, sizeof(adminAuth));
        }

        // target information
        char ip[24], auth[64], name[64];

        GetClientName(target, name, sizeof(name));
        GetClientIP(target, ip, sizeof(ip));
        if (!GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth)))
                return false;

        int userid = admin ? GetClientUserId(admin) : 0;

        // Pack everything into a data pack so we can retain it
        DataPack dataPack = new DataPack();
        DataPack reasonPack = new DataPack();

        WritePackString(reasonPack, reason);

        dataPack.WriteCell(admin);
        dataPack.WriteCell(target);
        dataPack.WriteCell(userid);
        dataPack.WriteCell(GetClientUserId(target));
        dataPack.WriteCell(time);
        dataPack.WriteCell( _:reasonPack);
        dataPack.WriteString(name);
        dataPack.WriteString(auth);
        dataPack.WriteString(ip);
        dataPack.WriteString(adminAuth);
        dataPack.WriteString(adminIp);

        dataPack.Reset();
        reasonPack.Reset();

        if (reason[0] != '\0')
        {
                // if we have a valid reason pass move forward with the ban
                if (DB != INVALID_HANDLE)
                {
                        UTIL_InsertBan(time, name, auth, ip, reason, adminAuth, adminIp, dataPack);
                } else {
                        UTIL_InsertTempBan(time, name, auth, ip, reason, adminAuth, adminIp, dataPack);
                }
        } else {
                // We need a reason so offer the administrator a menu of reasons
                PlayerDataPack[admin] = dataPack;
                DisplayMenu(ReasonMenuHandle, admin, MENU_TIME_FOREVER);
                ReplyToCommand(admin, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Check Menu");
        }

        Call_StartForward(g_hFwd_OnBanAdded);
        Call_PushCell(client);
        Call_PushCell(target);
        Call_PushCell(time);
        Call_PushString(reason);
        Call_Finish();

        return true;
}

stock UTIL_InsertBan(time, const String:Name[], const String:Authid[], const String:Ip[], const String:Reason[], const String:AdminAuthid[], const String:AdminIp[], Handle:Pack)
{
        //new Handle:dummy;
        //PruneBans(dummy);
        decl String:banName[128];
        decl String:banReason[256];
        decl String:Query[1024];
        SQL_EscapeString(DB, Name, banName, sizeof(banName));
        SQL_EscapeString(DB, Reason, banReason, sizeof(banReason));
        if (serverID == -1)
        {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \
                                                (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')",
                        DatabasePrefix, Ip, Authid, banName, (time * 60), (time * 60), banReason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminIp, DatabasePrefix, ServerIp, ServerPort);
        } else {
                FormatEx(Query, sizeof(Query), "INSERT INTO %s_bans (ip, authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \
                                                ('%s', '%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \
                                                %d, ' ')",
                        DatabasePrefix, Ip, Authid, banName, (time * 60), (time * 60), banReason, DatabasePrefix, AdminAuthid, AdminAuthid[8], AdminIp, serverID);
        }

        SQL_TQuery(DB, VerifyInsert, Query, Pack, DBPrio_High);
}

stock UTIL_InsertTempBan(int time, const char[] name, const char[] auth, const char[] ip, const char[] reason, const char[] adminAuth, const char[] adminIp, DataPack dataPack)
{
        dataPack.ReadCell(); // admin index

        int client = ReadPackCell(dataPack);

        dataPack.ReadCell(); // admin userid
        dataPack.ReadCell(); // target userid
        dataPack.ReadCell(); // time

        DataPack reasonPack = view_as<DataPack>(dataPack.ReadCell());

        if (reasonPack != INVALID_HANDLE)
                CloseHandle(reasonPack);
        CloseHandle(dataPack);

        // we add a temporary ban and then add the record into the queue to be processed when the database is available
        char buffer[50];

        Format(buffer, sizeof(buffer), "banid %d %s", ProcessQueueTime, auth);

        ServerCommand(buffer);

        if (IsClientInGame(client))
                KickClient(client, "%t", "Banned Check Site", WebsiteAddress);

        char banName[128], banReason[256], query[512];

        SQL_EscapeString(SQLiteDB, name, banName, sizeof(banName));
        SQL_EscapeString(SQLiteDB, reason, banReason, sizeof(banReason));

        FormatEx(query, sizeof(query), "INSERT INTO queue VALUES ('%s', %i, %i, '%s', '%s', '%s', '%s', '%s')",
                auth, time, GetTime(), banReason, banName, ip, adminAuth, adminIp);

        SQL_TQuery(SQLiteDB, ErrorCheckCallback, query);
}

stock CheckLoadAdmins()
{
        for (new i = 1; i <= MaxClients; i++)
        {
                if (IsClientInGame(i) && IsClientAuthorized(i))
                {
                        RunAdminCacheChecks(i);
                        NotifyPostAdminCheck(i);
                }
        }
}

stock InsertServerInfo()
{
    if (DB == INVALID_HANDLE) {
        return;
    }
   
    char query[100];
    int pieces[4];
    int longip = GetConVarInt(CvarHostIp);

    pieces[0] = (longip >> 24) & 0x000000FF;
    pieces[1] = (longip >> 16) & 0x000000FF;
    pieces[2] = (longip >> 8) & 0x000000FF;
    pieces[3] = longip & 0x000000FF;

    FormatEx(ServerIp, sizeof(ServerIp), "%d.%d.%d.%d", pieces[0], pieces[1], pieces[2], pieces[3]);
    GetConVarString(CvarPort, ServerPort, sizeof(ServerPort));

    if (AutoAdd != false) {
        FormatEx(query, sizeof(query), "SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s'", DatabasePrefix, ServerIp, ServerPort);
        SQL_TQuery(DB, ServerInfoCallback, query);
    }
}

stock void PrepareBan(int client, int target, int time, char[] reason, int size)
{
        #if defined DEBUG
        LogToFile(logFile, "PrepareBan()");
        #endif
        if (!target || !IsClientInGame(target))
                return;
        char authid[64], name[32], bannedSite[512];
        if (!GetClientAuthId(target, AuthId_Steam2, authid, sizeof(authid)))
                return;
        GetClientName(target, name, sizeof(name));


        if (CreateBan(client, target, time, reason))
        {
                if (!time)
                {
                        if (reason[0] == '\0')
                        {
                                ShowActivity(client, "%t", "Permabanned player", name);
                        } else {
                                ShowActivity(client, "%t", "Permabanned player reason", name, reason);
                        }
                } else {
                        if (reason[0] == '\0')
                        {
                                ShowActivity(client, "%t", "Banned player", name, time);
                        } else {
                                ShowActivity(client, "%t", "Banned player reason", name, time, reason);
                        }
                }
                LogAction(client, target, "\"%L\" banned \"%L\" (minutes \"%d\") (reason \"%s\")", client, target, time, reason);

                if (time > 5 || time == 0)
                        time = 5;
                Format(bannedSite, sizeof(bannedSite), "%T", "Banned Check Site", target, WebsiteAddress);
                BanClient(target, time, BANFLAG_AUTO, bannedSite, bannedSite, "sm_ban", client);
        }

        g_BanTarget[client] = -1;
        g_BanTime[client] = -1;
}

stock void ReadConfig()
{
        InitializeConfigParser();

        if (ConfigParser == INVALID_HANDLE)
        {
                return;
        }

        char ConfigFile[PLATFORM_MAX_PATH];
        BuildPath(Path_SM, ConfigFile, sizeof(ConfigFile), "configs/sourcebans/sourcebans.cfg");

        if (FileExists(ConfigFile))
        {
                InternalReadConfig(ConfigFile);
                PrintToServer("%sLoading configs/sourcebans.cfg config file", Prefix);
        } else {
                char Error[PLATFORM_MAX_PATH + 64];
                FormatEx(Error, sizeof(Error), "%sFATAL *** ERROR *** can not find %s", Prefix, ConfigFile);
                LogToFile(logFile, "FATAL *** ERROR *** can not find %s", ConfigFile);
                SetFailState(Error);
        }
}

stock void ResetSettings()
{
        CommandDisable = 0;

        ResetMenu();
        ReadConfig();
}

stock void ParseBackupConfig_Overrides()
{
        Handle hKV = CreateKeyValues("SB_Overrides");

        if (!FileToKeyValues(hKV, overridesLoc))
                return;

        if (!KvGotoFirstSubKey(hKV))
                return;

        char sSection[16], sFlags[32], sName[64];
        OverrideType type;

        do
        {
                KvGetSectionName(hKV, sSection, sizeof(sSection));
                if (StrEqual(sSection, "override_commands"))
                        type = Override_Command;
                else if (StrEqual(sSection, "override_groups"))
                        type = Override_CommandGroup;
                else
                        continue;

                if (KvGotoFirstSubKey(hKV, false))
                {
                        do
                        {
                                KvGetSectionName(hKV, sName, sizeof(sName));
                                KvGetString(hKV, NULL_STRING, sFlags, sizeof(sFlags));
                                AddCommandOverride(sName, type, ReadFlagString(sFlags));
                                #if defined _DEBUG
                                PrintToServer("Adding override (%s, %s, %s)", sSection, sName, sFlags);
                                #endif
                        } while (KvGotoNextKey(hKV, false));
                        KvGoBack(hKV);
                }
        }
        while (KvGotoNextKey(hKV));
        CloseHandle(hKV);
}

stock AdminFlag CreateFlagLetters()
{
        AdminFlag FlagLetters[FLAG_LETTERS_SIZE];

        FlagLetters['a'-'a'] = Admin_Reservation;
        FlagLetters['b'-'a'] = Admin_Generic;
        FlagLetters['c'-'a'] = Admin_Kick;
        FlagLetters['d'-'a'] = Admin_Ban;
        FlagLetters['e'-'a'] = Admin_Unban;
        FlagLetters['f'-'a'] = Admin_Slay;
        FlagLetters['g'-'a'] = Admin_Changemap;
        FlagLetters['h'-'a'] = Admin_Convars;
        FlagLetters['i'-'a'] = Admin_Config;
        FlagLetters['j'-'a'] = Admin_Chat;
        FlagLetters['k'-'a'] = Admin_Vote;
        FlagLetters['l'-'a'] = Admin_Password;
        FlagLetters['m'-'a'] = Admin_RCON;
        FlagLetters['n'-'a'] = Admin_Cheats;
        FlagLetters['o'-'a'] = Admin_Custom1;
        FlagLetters['p'-'a'] = Admin_Custom2;
        FlagLetters['q'-'a'] = Admin_Custom3;
        FlagLetters['r'-'a'] = Admin_Custom4;
        FlagLetters['s'-'a'] = Admin_Custom5;
        FlagLetters['t'-'a'] = Admin_Custom6;
        FlagLetters['z'-'a'] = Admin_Root;

        return FlagLetters;
}

stock AccountForLateLoading()
{
        char auth[30];

        for (new i = 1; i <= GetMaxClients(); i++)
        {
                if (IsClientConnected(i) && !IsFakeClient(i))
                {
                        PlayerStatus[i] = false;
                }
                if (IsClientInGame(i) && !IsFakeClient(i) && IsClientAuthorized(i) && GetClientAuthId(i, AuthId_Steam2, auth, sizeof(auth)))
                {
                        OnClientAuthorized(i, auth);
                }
        }
}

//Yarr!


DarkDeviL 03-10-2021 20:07

Re: Does Sourcebans not store the admin name in the database?
 
:!: Moved to SourceBans / SourceBans++ , please pay more attention to where you post, instead of sending everything to Scripting. :!:

When adding admins in SourceBans, you are entering a Steam ID and a name.

When someone places a ban from that particular Steam ID (in-game), the admin id of that particular Steam ID will be attached to the ban, and can later be used for looking up admin name, such as e.g. when people are visiting your SourceBans web panel.

If SourceBans has a ban set with e.g. admin id 1337, but admin 1337 has been deleted, then it has also lost the reference towards any actual name, and won't be able to show any name for that particular ban any more.

If you have added admins elsewhere (e.g. not using SourceBans), and one of these admins are placing a ban, and there therefore are no match in the SourceBans database (steam id -> admin id), then these bans will show up on your SourceBans installation as "CONSOLE" (under the condition that you haven't deleted that default "CONSOLE" account from your SourceBans installation, typically holding the fictive "STEAM_ID_SERVER" Steam ID).

Given the sentence above, it really makes sense to use SourceBans for what it was build for, to administer all admins AND bans on your server, and not be adding admins from elsewhere, - to make sure every reference is kept nice and tidy everywhere.


All times are GMT -4. The time now is 02:47.

Powered by vBulletin®
Copyright ©2000 - 2021, vBulletin Solutions, Inc.