Writting GPCI into a database causes the client to crash
#1

Hey guys. I was exercising with MySQL. Downloaded the example script from Github, and I tried to add IP and serial code.
The problem is, the server makes my client crash once I complete the query that writes in the table.

NOTES:
  • There were no compiling errors or warnings, the MySQL log doesn't show any errors either, and the server started correctly.
  • Te problem is related to the GPCI function. More specifically it appears to occur when assigning the serial value collected from the player.
  • I bolded the code at that line 204. Removing that specific text, will result a fully functional GM script.

Here is an image of my client crashing. Notice that colors are going crazy. That happens once I hit the spawn button:
http://i.imgur.com/luUDmEl.png?1

The server also fails to write the player's data into the database.


Please
Код:
#include <a_samp>
#include <a_mysql>

native WP_Hash(buffer[], len, const str[]);
native gpci (playerid, serial [], len);

#define WHITE 0xFFFFFFAA
#define GREY 0xAFAFAFAA
#define RED 0xFF0000AA
#define YELLOW 0xFFFF00AA
#define LIGHTBLUE 0x33CCFFAA

#define CHAT_WHITE "{FFFFFF}"
#define CHAT_GREY "{AFAFAF}"
#define CHAT_RED "{FF0000}"
#define CHAT_YELLOW "{FFFF00}"
#define CHAT_LIGHTBLUE "{33CCFF}"

forward OnPlayerDataLoaded(playerid, race_check);
forward OnPlayerRegister(playerid);


#define SQL_HOST "127.0.0.1" //lets leave it because this is your local host
#define SQL_DB "samp"
#define SQL_USER "root"
#define SQL_PASS ""

stock PlayerIP(playerid)
{
    new IP[16];
    GetPlayerIp(playerid, IP, sizeof(IP));
    return IP;
}
stock PlayerName(playerid)
{
    new pName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, pName, sizeof(pName));
    return pName;
}
stock PlayerSerial(playerid)
{
    new serial[64];
    gpci(playerid,serial, sizeof(serial));
    return serial;
}

//MySQL connection handle
new g_SQL = -1;

//player data
enum E_PLAYERS
{
    ID,
    Name[MAX_PLAYER_NAME],
    Password[129],
    Money,
    bool:IsLoggedIn,
    bool:IsRegistered,
    LoginAttempts,
    LoginTimer
};
new Player[MAX_PLAYERS][E_PLAYERS];

new g_MysqlRaceCheck[MAX_PLAYERS];

//dialog data
enum 
{
    DIALOG_INVALID,
    DIALOG_UNUSED,
    
    DIALOG_LOGIN,
    DIALOG_REGISTER,
};


/*
 * SA-MP callbacks
 */

public OnGameModeInit()
{
    mysql_log(LOG_ERROR | LOG_WARNING, LOG_TYPE_HTML); //logs errors and warnings into a nice .html log file
    g_SQL = mysql_connect(SQL_HOST, SQL_USER, SQL_DB, SQL_PASS);
    
    SetupPlayerTable();
    return 1;
}

public OnGameModeExit()
{
    //save all player data before closing connection
    for(new p=0; p < MAX_PLAYERS; ++p)
        if(IsPlayerConnected(p))
            UpdatePlayerData(p);

    mysql_close();
    return 1;
}

public OnPlayerConnect(playerid)
{
    g_MysqlRaceCheck[playerid]++;
    //reset player data
    for(new E_PLAYERS:e; e < E_PLAYERS; ++e)
        Player[playerid][e] = 0;
    new query[128];
    GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME);

    //send a query to recieve all the stored player data from the table
    mysql_format(g_SQL, query, sizeof(query), "SELECT * FROM `players` WHERE `username` = '%e' LIMIT 1", Player[playerid][Name]);
    mysql_tquery(g_SQL, query, "OnPlayerDataLoaded", "dd", playerid, g_MysqlRaceCheck[playerid]);
    return 1;
}

public OnPlayerDataLoaded(playerid, race_check)
{
    /*  race condition check:
        player A connects -> SELECT query is fired -> this query takes very long
        while the query is still processing, player A with playerid 2 disconnects
        player B joins now with playerid 2 -> our laggy SELECT query is finally finished, but for the wrong player
        
        what do we do against it?
        we create a connection count for each playerid and increase it everytime the playerid connects or disconnects
        we also pass the current value of the connection count to our OnPlayerDataLoaded callback
        then we check if current connection count is the same as connection count we passed to the callback
        if yes, everything is okay, if not, we just kick the player
    */
    if(race_check != g_MysqlRaceCheck[playerid])
        return Kick(playerid);
    
    
    new string[128];
    if(cache_num_rows() > 0)
    {
        AssignPlayerData(playerid);
        
        format(string, sizeof(string), CHAT_WHITE "This account (" CHAT_YELLOW "%s" CHAT_WHITE ") is registered. Please login by entering your password in the field below:", Player[playerid][Name]);
        ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", string, "Login", "Abort");
        Player[playerid][IsRegistered] = true;
    }
    else
    {
        format(string, sizeof(string), CHAT_WHITE "Welcome " CHAT_YELLOW "%s" CHAT_WHITE ", you can register by entering your password in the field below:", Player[playerid][Name]);
        ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration", string, "Register", "Abort");
        Player[playerid][IsRegistered] = false;
    }
    return 1;
}

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    if(dialogid == DIALOG_INVALID || dialogid == DIALOG_UNUSED)
        return 1;

    switch(dialogid)
    {
        case DIALOG_LOGIN:
        {
            if(!response)
                return Kick(playerid);

            if(strlen(inputtext) <= 5)
                return ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login",
                    CHAT_RED "Your password must be longer than 5 characters!\n" CHAT_WHITE "Please enter your password in the field below:",
                    "Login", "Abort");

            new hashed_pass[129];
            WP_Hash(hashed_pass, sizeof(hashed_pass), inputtext);
            
            if(strcmp(hashed_pass, Player[playerid][Password]) == 0)
            {
                //correct password, spawn the player
                ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "You have been successfully logged in.", "Okay", "");
                Player[playerid][IsLoggedIn] = true;
                
                SetSpawnInfo(playerid, 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
                SpawnPlayer(playerid);
            }
            else
            {
                Player[playerid][LoginAttempts]++;
                if(Player[playerid][LoginAttempts] >= 3)
                {
                    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", CHAT_RED "You have mistyped your password too often (3 times).", "Okay", "");
                    DelayedKick(playerid);
                }
                else
                    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", CHAT_RED "Wrong password!\n" CHAT_WHITE "Please enter your password in the field below:", "Login", "Abort");
            }
        }
        
        case DIALOG_REGISTER:
        {
            if(!response)
                return Kick(playerid);
                
            if(strlen(inputtext) <= 5)
                return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration",
                    CHAT_RED "Your password must be longer than 5 characters!\n" CHAT_WHITE "Please enter your password in the field below:",
                    "Register", "Abort");
            new query[256];
            WP_Hash(Player[playerid][Password], 129, inputtext);
            mysql_format(g_SQL, query, sizeof(query), "INSERT INTO `players` (`username`, `password`, `IP`, `serial`]) VALUES ('%e', '%s', '%s','%S')", Player[playerid][Name], Player[playerid][Password], PlayerIP(playerid), PlayerSerial(playerid));
            mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
        }
        
        default:
            return 0;
    }
    return 1;
}

public OnPlayerRegister(playerid)
{
    Player[playerid][ID] = cache_insert_id();
    
    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Registration", "Account successfully registered, you have been automatically logged in.", "Okay", "");
    Player[playerid][IsLoggedIn] = true;
    Player[playerid][IsRegistered] = true;
    
    SetSpawnInfo(playerid, 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
    SpawnPlayer(playerid);
    return 1;
}

public OnPlayerSpawn(playerid)
{
    ResetPlayerMoney(playerid);
    GivePlayerMoney(playerid, Player[playerid][Money]);
    return 1;
}

public OnPlayerDisconnect(playerid,reason)
{
    g_MysqlRaceCheck[playerid]++;
    UpdatePlayerData(playerid);
    return 1;
}


/*
 * functions
 */

AssignPlayerData(playerid)
{
    Player[playerid][ID] = cache_get_field_content_int(0, "id");
    cache_get_field_content(0, "password", Player[playerid][Password], g_SQL, 129);
    Player[playerid][Money] = cache_get_field_content_int(0, "money");
    return 1;
}

UpdatePlayerData(playerid)
{
    if(Player[playerid][IsLoggedIn] == false)
        return 0;
        
    new query[128];
    mysql_format(g_SQL, query, sizeof(query), "UPDATE `players` SET `money` = '%d' WHERE `username` = '%e' LIMIT 1", Player[playerid][Money], Player[playerid][Name]);//
    mysql_tquery(g_SQL, query);
    return 1;
}

SetupPlayerTable()
{
    mysql_query(g_SQL, "CREATE TABLE IF NOT EXISTS `players` (`id` int(11) NOT NULL auto_increment PRIMARY KEY,`username` varchar(30) NOT NULL,`password` varchar(130) NOT NULL,`money` int(10) NOT NULL default '0',  `IP` varchar(16) NOT NULL),  `serial` varchar(64) NOT NULL)", false);
    return 1;
}


DelayedKick(playerid, time=500)
{
    SetTimerEx("_KickPlayerDelayed", time, false, "d", playerid);
    return 1;
}

forward _KickPlayerDelayed(playerid);
public _KickPlayerDelayed(playerid)
{
    Kick(playerid);
    return 1;
}

main() {}
Reply
#2

You MUST add at least one AddPlayerClass.
Reply
#3

You need to add AddPlayerClass, even if you SetPlayerPos after OnPlayerSpawn if there's no AddPlayerClass it'll state you're out of the world boundaries.
Reply
#4

Thanks for replying!
So far, without that code I was spawned at the map's origin (Blueberry Acres) below ground level.

Adding the player class got rid of the crash, but I still spawn as CJ in Bluebery Acres, and the serial column remains empty.

Код:
#include <a_samp>
#include <a_mysql>
#include <crashdetect>

native WP_Hash(buffer[], len, const str[]);
native gpci (playerid, serial [], len);

#define WHITE 0xFFFFFFAA
#define GREY 0xAFAFAFAA
#define RED 0xFF0000AA
#define YELLOW 0xFFFF00AA
#define LIGHTBLUE 0x33CCFFAA

#define CHAT_WHITE "{FFFFFF}"
#define CHAT_GREY "{AFAFAF}"
#define CHAT_RED "{FF0000}"
#define CHAT_YELLOW "{FFFF00}"
#define CHAT_LIGHTBLUE "{33CCFF}"

forward OnPlayerDataLoaded(playerid, race_check);
forward OnPlayerRegister(playerid);


#define SQL_HOST "127.0.0.1" //lets leave it because this is your local host
#define SQL_DB "samp"
#define SQL_USER "root"
#define SQL_PASS ""

stock PlayerIP(playerid)
{
    new IP[16];
    GetPlayerIp(playerid, IP, sizeof(IP));
    return IP;
}
stock PlayerName(playerid)
{
    new pName[MAX_PLAYER_NAME];
    GetPlayerName(playerid, pName, sizeof(pName));
    return pName;
}
stock PlayerSerial(playerid)
{
    new serial[64];
    gpci(playerid,serial, sizeof(serial));
    return serial;
}

//MySQL connection handle
new g_SQL = -1;

//player data
enum E_PLAYERS
{
    ID,
    Name[MAX_PLAYER_NAME],
    Password[129],
    Money,
    bool:IsLoggedIn,
    bool:IsRegistered,
    LoginAttempts,
    LoginTimer
};
new Player[MAX_PLAYERS][E_PLAYERS];

new g_MysqlRaceCheck[MAX_PLAYERS];

//dialog data
enum 
{
    DIALOG_INVALID,
    DIALOG_UNUSED,
    
    DIALOG_LOGIN,
    DIALOG_REGISTER,
};


/*
 * SA-MP callbacks
 */

public OnGameModeInit()
{
    AddPlayerClass(1, 1958.33, 1343.12, 15.36, 269.15, 26, 36, 28, 150, 0, 0);
    mysql_log(LOG_ERROR | LOG_WARNING, LOG_TYPE_HTML); //logs errors and warnings into a nice .html log file
    g_SQL = mysql_connect(SQL_HOST, SQL_USER, SQL_DB, SQL_PASS);
    SetupPlayerTable();
    return 1;
}

public OnGameModeExit()
{
    //save all player data before closing connection
    for(new p=0; p < MAX_PLAYERS; ++p)
        if(IsPlayerConnected(p))
            UpdatePlayerData(p);

    mysql_close();
    return 1;
}

public OnPlayerConnect(playerid)
{
    g_MysqlRaceCheck[playerid]++;
    //reset player data
    for(new E_PLAYERS:e; e < E_PLAYERS; ++e)
        Player[playerid][e] = 0;
    new query[128];
    GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME);

    //send a query to recieve all the stored player data from the table
    mysql_format(g_SQL, query, sizeof(query), "SELECT * FROM `players` WHERE `username` = '%e' LIMIT 1", Player[playerid][Name]);
    mysql_tquery(g_SQL, query, "OnPlayerDataLoaded", "dd", playerid, g_MysqlRaceCheck[playerid]);

    return 1;
}

public OnPlayerDataLoaded(playerid, race_check)
{
    /*  race condition check:
        player A connects -> SELECT query is fired -> this query takes very long
        while the query is still processing, player A with playerid 2 disconnects
        player B joins now with playerid 2 -> our laggy SELECT query is finally finished, but for the wrong player
        
        what do we do against it?
        we create a connection count for each playerid and increase it everytime the playerid connects or disconnects
        we also pass the current value of the connection count to our OnPlayerDataLoaded callback
        then we check if current connection count is the same as connection count we passed to the callback
        if yes, everything is okay, if not, we just kick the player
    */
    if(race_check != g_MysqlRaceCheck[playerid])
        return Kick(playerid);
    
    
    new string[128];
    if(cache_num_rows() > 0)
    {
        AssignPlayerData(playerid);
        
        format(string, sizeof(string), CHAT_WHITE "This account (" CHAT_YELLOW "%s" CHAT_WHITE ") is registered. Please login by entering your password in the field below:", Player[playerid][Name]);
        ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", string, "Login", "Abort");
        Player[playerid][IsRegistered] = true;
    }
    else
    {
        format(string, sizeof(string), CHAT_WHITE "Welcome " CHAT_YELLOW "%s" CHAT_WHITE ", you can register by entering your password in the field below:", Player[playerid][Name]);
        ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration", string, "Register", "Abort");
        Player[playerid][IsRegistered] = false;
    }
    return 1;
}

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    if(dialogid == DIALOG_INVALID || dialogid == DIALOG_UNUSED)
        return 1;

    switch(dialogid)
    {
        case DIALOG_LOGIN:
        {
            if(!response)
                return Kick(playerid);

            if(strlen(inputtext) <= 5)
                return ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login",
                    CHAT_RED "Your password must be longer than 5 characters!\n" CHAT_WHITE "Please enter your password in the field below:",
                    "Login", "Abort");

            new hashed_pass[129];
            WP_Hash(hashed_pass, sizeof(hashed_pass), inputtext);
            
            if(strcmp(hashed_pass, Player[playerid][Password]) == 0)
            {
                //correct password, spawn the player
                ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "You have been successfully logged in.", "Okay", "");
                Player[playerid][IsLoggedIn] = true;
                
                SetSpawnInfo(playerid, 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
                SpawnPlayer(playerid);
            }
            else
            {
                Player[playerid][LoginAttempts]++;
                if(Player[playerid][LoginAttempts] >= 3)
                {
                    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", CHAT_RED "You have mistyped your password too often (3 times).", "Okay", "");
                    DelayedKick(playerid);
                }
                else
                    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", CHAT_RED "Wrong password!\n" CHAT_WHITE "Please enter your password in the field below:", "Login", "Abort");
            }
        }
        
        case DIALOG_REGISTER:
        {
            if(!response)
                return Kick(playerid);
                
            if(strlen(inputtext) <= 5)
                return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration",
                    CHAT_RED "Your password must be longer than 5 characters!\n" CHAT_WHITE "Please enter your password in the field below:",
                    "Register", "Abort");
            new query[256];
            WP_Hash(Player[playerid][Password], 129, inputtext);
            mysql_format(g_SQL, query, sizeof(query), "INSERT INTO `players` (`username`, `password`, `IP`, `serial`) VALUES ('%e', '%s', '%s','%s')", Player[playerid][Name], Player[playerid][Password], PlayerIP(playerid), PlayerSerial(playerid));
            //mysql_format(g_SQL, query, sizeof(query), "INSERT INTO `players` (`username`, `password`, `IP`) VALUES ('%e', '%s', '%s')", Player[playerid][Name], Player[playerid][Password], PlayerIP(playerid));
            mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
        }
        
        default:
            return 0;
    }
    return 1;
}

public OnPlayerRegister(playerid)
{
    Player[playerid][ID] = cache_insert_id();
    
    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Registration", "Account successfully registered, you have been automatically logged in.", "Okay", "");
    Player[playerid][IsLoggedIn] = true;
    Player[playerid][IsRegistered] = true;
    
    SetSpawnInfo(playerid, 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
    SpawnPlayer(playerid);
    return 1;
}

public OnPlayerSpawn(playerid)
{
    ResetPlayerMoney(playerid);
    GivePlayerMoney(playerid, Player[playerid][Money]);
    return 1;
}

public OnPlayerDisconnect(playerid,reason)
{
    g_MysqlRaceCheck[playerid]++;
    UpdatePlayerData(playerid);
    return 1;
}


/*
 * functions
 */

AssignPlayerData(playerid)
{
    Player[playerid][ID] = cache_get_field_content_int(0, "id");
    cache_get_field_content(0, "password", Player[playerid][Password], g_SQL, 129);
    Player[playerid][Money] = cache_get_field_content_int(0, "money");
    return 1;
}

UpdatePlayerData(playerid)
{
    if(Player[playerid][IsLoggedIn] == false)
        return 0;
        
    new query[128];
    mysql_format(g_SQL, query, sizeof(query), "UPDATE `players` SET `money` = '%d' WHERE `username` = '%e' LIMIT 1", Player[playerid][Money], Player[playerid][Name]);//
    mysql_tquery(g_SQL, query);
    return 1;
}

SetupPlayerTable()
{
    mysql_query(g_SQL, "CREATE TABLE IF NOT EXISTS `players` (`id` int(11) NOT NULL auto_increment PRIMARY KEY,`username` varchar(30) NOT NULL,`password` varchar(130) NOT NULL,`money` int(10) NOT NULL default '0',  `IP` varchar(16) NOT NULL,  `serial` varchar(64) NOT NULL)", false);
    return 1;
}


DelayedKick(playerid, time=500)
{
    SetTimerEx("_KickPlayerDelayed", time, false, "d", playerid);
    return 1;
}

forward _KickPlayerDelayed(playerid);
public _KickPlayerDelayed(playerid)
{
    Kick(playerid);
    return 1;
}

main() {}
Reply
#5

Gpci doesn't work as you think it works, it's not intended to be a function which gives you an unique ID for each individual player.
Reply
#6

Quote:
Originally Posted by admantis
Посмотреть сообщение
Gpci doesn't work as you think it works, it's not intended to be a function which gives you an unique ID for each individual player.
I've read that it's not 100% accurate, but I really don't feel like banning entire IP ranges just because of some kids with crappy behavior.
Edit: I solved the serial problem. The classes still don't work.
Reply
#7

I am now derailing the topic a bit, and while there may be problems in your SQL syntax, you should NOT use Gpci for that purpose. Here's why:
Quote:
Originally Posted by Kalcor
It is not an SHA1. The fact that you think it is makes me think you are connected to some cheater/bot websites, which is exactly why the function exists and why it's not (and will never be) documented.

Here is all anyone needs to know about gpci:
- It is a non-reversible (lossy) hash derived from information about your San Andreas installation path.
- It is not a unique ID.
- It was added to assist owners of large servers who deal with constant attacks from cheaters and botters.
- It has been in SA-MP for 2 years.
Stick to range-banning the last 3 digits of IP (example: 192.168.1.***).
Reply
#8

Quote:
Originally Posted by admantis
Посмотреть сообщение
I am now derailing the topic a bit, and while there may be problems in your SQL syntax, you should NOT use Gpci for that purpose. Here's why:

Stick to range-banning the last 3 digits of IP (example: 192.168.1.***).
Very inefficient. In my country, and I'm sure not only here, users have 3 IP ranges assigned, not only one.
Banning 3 ranges for one cheater is not a solution. I end up banning dozens of ranges. At this rate, I ban half of Romania.

I solved the serial problem.
Reply
#9

I's an efficient way to stop evaders that are not aware of this function.
Reply
#10

I won't use the serial for banning, but it may provide an efficient way to track the IP's for each user.
I do keep an open mind. If anyone has a better solution for ban evaders, feel free to post.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)