[TUT] MySql - Запазване / Зареждане / Полезни неща

В този раздел можете да намерите полезни кодове и уроци свързани с PAWN скриптинга.
Отговори
Потребителски аватар

Автор на темата
JustInCase AMXX
Потребител
Потребител
Мнения: 565
Регистриран: 14 окт 2016, 23:31
Се отблагодари: 3 пъти
Получена благодарност: 3 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от JustInCase AMXX » 06 яну 2017, 15:34

Здравейте, в този урок ще ви покажа как да използвате MySql в вашите плъгини. Било то как се запазва как се зарежда и други полезни работи.

За пример ще ползваме прост плъгин за XP Mod.

Внимание, имайте предвид, че за да разберете нещо трябва да имате познания по PAWN.


Ще започнем с главната част от плъгина.

Код: Избери всички

#include <amxmodx>

#define PLUGIN "Tutorial"
#define VERSION "1.0"
#define AUTHOR "Just In Case"

new iExp[33]

// Pcvar-ове
new cKill
new cHeadshot
new cDeath

public plugin_init() {
    register_plugin(PLUGIN, VERSION, AUTHOR)
    
    register_event("DeathMsg", "Event_DeathMsg", "a") // Register death event
    
    // Регистрираме PCVAR-овете
    cKill = register_cvar("exp_kill", "2")
    cHeadshot = register_cvar("exp_headshot", "4")
    cDeath = register_cvar("exp_death", "1")
}

public Event_DeathMsg()
{
    new iKiller = read_data(1) // Взимаме информация за убиеца и жертвата
    new iVictim = read_data(2)
    
    if(is_user_alive(iKiller)) // Проверяваме дали убиеца е жив в случай, че се е самоубил
    {
        if(read_data(3)) // Проверяваме дали е HEADSHOT
        {
            iExp[iKiller] += get_pcvar_num(cHeadshot) // Добавяме PCvar стойността за HEADSHOT към iExp (XP-то)
        }
        else
        {
            iExp[iKiller] += get_pcvar_num(cKill)
        }
    }
    iExp[iVictim] -= get_pcvar_num(cDeath) // Махаме стойността при смърт
}
И започваме да добавяме MySql към него

Първо вкарваме библиотеката :

Код: Избери всички

#include <sqlx>
И след това започваме да правим MySql Възела ( Където ще установим връзката към MySql сървъра )

Код: Избери всички

public MySql_Init()
{
    //Казваме на API, че това е информацията към която е нужна за свързването
    // just not yet. basically it's like storing it in global variables
    //не още. Всъщност съхраняваме я във глобални променливи
    g_SqlTuple = SQL_MakeDbTuple(Host,User,Pass,Db)
    
    // Добре, вече сме готови да се свържем
    new ErrorCode,Handle:SqlConnection = SQL_Connect(g_SqlTuple,ErrorCode,g_Error,charsmax(g_Error))
    if(SqlConnection == Empty_Handle)
        // Спираме плъгина с съобщение ГРЕШКА (Error)
    set_fail_state(g_Error)
    
    new Handle:Queries
    // we must now prepare some random queries
    // Сега трябва да се подготвим за някой произволни запитвания
    Queries = SQL_PrepareQuery(SqlConnection,"CREATE TABLE IF NOT EXISTS tutorial (steamid varchar(32),exp INT(11))")
    
    if(!SQL_Execute(Queries))
    {
        // Ако има някакви проблеми плъгина ще се зададе като Bad Load (Лошо зареден - Плъгина няма да тръгне)
        SQL_QueryError(Queries,g_Error,charsmax(g_Error))
        set_fail_state(g_Error)
        
    }
    
    // Освобождаваме запитванията
    SQL_FreeHandle(Queries)
    
    // Освобождаваме всичко с SQL_FreeHandle
    SQL_FreeHandle(SqlConnection)   
}
За да избегнем грешки използваме FreeHandle в plugin_end()

Код: Избери всички

public plugin_end()
{
    SQL_FreeHandle(g_SqlTuple)
}
Сега ни трябва код, с който да запазваме / взимаме неговото XP

Код: Избери всички

public Save_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    // Тук ще ъпдейтваме XP-то на играча но само ако името съвпадне с някое от имената в таблицата
    format(szTemp,charsmax(szTemp),"UPDATE `tutorial` SET `exp` = '%i' WHERE `tutorial`.`steamid` = '%s';",iExp[id], szSteamId)
    SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
}
public Load_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    new Data[1]
    Data[0] = id
    
    //Сега ще вземем от таблица "tutorial" информация, когато Nick-a
    format(szTemp,charsmax(szTemp),"SELECT * FROM `tutorial` WHERE (`tutorial`.`steamid` = '%s')", szName)
    SQL_ThreadQuery(g_SqlTuple,"register_client",szTemp,Data,1)
}public register_client(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
    {
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    }
    else if(FailState == TQUERY_QUERY_FAILED)
    {
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    }
    
    new id
    id = Data[0]
    
    if(SQL_NumResults(Query) < 1) 
    {
        
        new szSteamId[32]
        get_user_authid(id, szSteamId, charsmax(szSteamId)) // get user's steamid
        
        //  Ако още изчакваме не може да направим нищо
        if (equal(szSteamId,"ID_PENDING"))
            return PLUGIN_HANDLED
        
        new szTemp[512]
        
        // Сега ще сложим нашите данни в нашата таблица
        format(szTemp,charsmax(szTemp),"INSERT INTO `tutorial` ( `steamid` , `exp`)VALUES ('%s','0');",szName)
        SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
    } 
    else 
    {
        // Ако няма намерени резултати
        iExp[id]         = SQL_ReadResult(Query, 1)
    }
    
    return PLUGIN_HANDLED
}
Крайният код, който получихме изглежда така.

Код: Избери всички

#include <amxmodx>
#include <sqlx>

#define PLUGIN "Tutorial"
#define VERSION "1.0"
#define AUTHOR "Just In Case"

// Ur Mysql Information - Тук попълвате данните на SQL сървърът ви
new Host[]     = "hostname"
new User[]    = "username"
new Pass[]     = "password"
new Db[]     = "database"


new Handle:g_SqlTuple
new g_Error[512]

new iExp[33]

// Pcvar-ове
new cKill
new cHeadshot
new cDeath

public plugin_init() {
    register_plugin(PLUGIN, VERSION, AUTHOR)
    
    register_event("DeathMsg", "Event_DeathMsg", "a") // Register death event
    
    // Регистрираме PCVAR-овете
    cKill = register_cvar("exp_kill", "2")
    cHeadshot = register_cvar("exp_headshot", "4")
    cDeath = register_cvar("exp_death", "1")
}
public MySql_Init()
{
    //Казваме на API, че това е информацията към която е нужна за свързването
    // just not yet. basically it's like storing it in global variables
    //не още. Всъщност съхраняваме я във глобални променливи
    g_SqlTuple = SQL_MakeDbTuple(Host,User,Pass,Db)
    
    // Добре, вече сме готови да се свържем
    new ErrorCode,Handle:SqlConnection = SQL_Connect(g_SqlTuple,ErrorCode,g_Error,charsmax(g_Error))
    if(SqlConnection == Empty_Handle)
        // Спираме плъгина с съобщение ГРЕШКА (Error)
    set_fail_state(g_Error)
    
    new Handle:Queries
    // we must now prepare some random queries
    // Сега трябва да се подготвим за някой произволни запитвания
    Queries = SQL_PrepareQuery(SqlConnection,"CREATE TABLE IF NOT EXISTS tutorial (name varchar(32),exp INT(11))")
    
    if(!SQL_Execute(Queries))
    {
        // Ако има някакви проблеми плъгина ще се зададе като Bad Load (Лошо зареден - Плъгина няма да тръгне)
        SQL_QueryError(Queries,g_Error,charsmax(g_Error))
        set_fail_state(g_Error)
        
    }
    
    // Освобождаваме запитванията
    SQL_FreeHandle(Queries)
    
    // Освобождаваме всичко с SQL_FreeHandle
    SQL_FreeHandle(SqlConnection)   
}
public plugin_end()
{
    SQL_FreeHandle(g_SqlTuple)
}
public Save_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    // Тук ще ъпдейтваме XP-то на играча но само ако името съвпадне с някое от имената в таблицата
    format(szTemp,charsmax(szTemp),"UPDATE `tutorial` SET `exp` = '%i' WHERE `tutorial`.`name` = '%s';",iExp[id], szName)
    SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
}
public Load_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    new Data[1]
    Data[0] = id
    
    //Сега ще вземем от таблица "tutorial" информация, когато Nick-a
    format(szTemp,charsmax(szTemp),"SELECT * FROM `tutorial` WHERE (`tutorial`.`name` = '%s')", szName)
    SQL_ThreadQuery(g_SqlTuple,"register_client",szTemp,Data,1)
}
public Event_DeathMsg()
{
    new iKiller = read_data(1) // Взимаме информация за убиеца и жертвата
    new iVictim = read_data(2)
    
    if(is_user_alive(iKiller)) // Проверяваме дали убиеца е жив в случай, че се е самоубил
    {
        if(read_data(3)) // Проверяваме дали е HEADSHOT
        {
            iExp[iKiller] += get_pcvar_num(cHeadshot) // Добавяме PCvar стойността за HEADSHOT към iExp (XP-то)
        }
        else
        {
            iExp[iKiller] += get_pcvar_num(cKill)
        }
    }
    iExp[iVictim] -= get_pcvar_num(cDeath) // Махаме стойността при смърт (от PCVAR-a за взимане на XP при смърт) 
}
public register_client(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
    {
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    }
    else if(FailState == TQUERY_QUERY_FAILED)
    {
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    }
    
    new id
    id = Data[0]
    
    if(SQL_NumResults(Query) < 1) 
    {
        
        new szName[32]
        get_user_name(id, szName, charsmax(szName)) // Взимаме името на играча
        
        //  Ако още изчакваме не може да направим нищо
        if (equal(szName,"ID_PENDING"))
            return PLUGIN_HANDLED
        
        new szTemp[512]
        
        // Сега ще сложим нашите данни в нашата таблица
        format(szTemp,charsmax(szTemp),"INSERT INTO `tutorial` ( `name` , `exp`)VALUES ('%s','0');",szName)
        SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
    } 
    else 
    {
        // Ако няма намерени резултати
        iExp[id]         = SQL_ReadResult(Query, 1)
    }
    
    return PLUGIN_HANDLED
}

Добре е да знаете също :

Взимане на Ранк:

Код: Избери всички

public Show_Rank(id) // Трябв да регистрирате команда за тази функция
{
    for(new i; i < MaxPlayers; i++)
    {
        if(is_user_connected(i))
            Save_MySql(i) // Запазваме всички данни за да дадем точен rank
    }
    
    new Data[1]
    Data[0] = id
    
    new szTemp[512]
    format(szTemp,charsmax(szTemp),"SELECT COUNT(*) FROM `tutorial` WHERE `exp` >= %d", Exp[id])
    // Select the count where the exp is matching or higher (Incase of equal exp)
    // Избираме броя където XP-то е еднакво или по високо ( В случай с еднакво XP )
    SQL_ThreadQuery(g_SqlTuple,"Sql_Rank",szTemp,Data,1)
    
    return PLUGIN_CONTINUE
}

public Sql_Rank(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    else if(FailState == TQUERY_QUERY_FAILED)
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    
    new count = 0
    count = SQL_ReadResult(Query,0)
    if(count == 0)
        count = 1
    
    new id
    id = Data[0]
    
    client_print(id, print_chat, "You're rank is %i with %i exp", count, Exp[id]); //Съобщението с което изписваме на играча, кой ранк е
    
    return PLUGIN_HANDLED
}
Как да запазваме плаващи числа :

Код: Избери всички

//Пример
Queries[0] = SQL_PrepareQuery(SqlConnection, "CREATE TABLE IF NOT EXISTS %s (name varchar(32), yourfloat FLOAT(11,3))", szTableName);

// ПЛАВАЩО число ( 11,3 )
// 11 = Максималното число
// 3 = Максималното число зад запетаята

// Зареждане на информация

new Float: FloatA = 11,3
SQL_ReadResult( Query, Column, FloatA );
Как да трием редове в таблицата :

Код: Избери всички

new szTemp[256], Data[1];

Data[0] = id

format(szTemp,charsmax(szTemp),"DELETE FROM `%s` WHERE `Name` = '%s'", szMainTable, szName);
SQL_ThreadQuery(SqlTuple,"IgnoreHandle",szTemp,Data,1);
Най-често срещани проблеми поради, които урокът няма да стане:
  • SQL сървърът ви не поддържа външни връзки.
  • 1 от 4 данни за връзка са грешни (username, password, hostname, databasename)

Ако имате някакви допълнения ще се радвам да ги споделите за да може да ги добавим към урока.
Ако откриете грешки ги споделете за да ги отстраня.


Урокът е взет от ТУК


Преведен е от мен : JustInCase


И ЗАБРАНЯВАМ КОПИРАНЕТО НА ПРЕВОДА ПО ДРУГИ САЙТОВЕ И ФОРУМИ !!!

:bg:
MANSION - HNS : 93.123.18.46:27016
ONLINE

Потребителски аватар

x7s
Потребител
Потребител
Мнения: 43
Регистриран: 20 яну 2018, 11:00
Местоположение: България
Се отблагодари: 19 пъти
Години: 32
Контакти:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от x7s » 20 яну 2018, 19:39

Ще е хубаво, ако Автора на темата я обнови.
Благодаря предварително.
Става въпрос за библиотеката която е използвал в урока:

Код: Избери всички

#include <mysql>  
Трябва да е:

Код: Избери всички

#include <sqlx>
:tnx:

Потребителски аватар

sianbg gta5-bg
VIP
VIP
Мнения: 230
Регистриран: 13 ное 2017, 12:18
Контакти:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg gta5-bg » 07 фев 2018, 19:04

Благодаря за урока. Има нещо, което ме притеснява. Никъде не виждам prepare stmt или някакъв escape на входните данни. Специално обръщам внимание на името на играча.

Код: Избери всички

format(szTemp,charsmax(szTemp),"DELETE FROM `%s` WHERE `Name` = '%s'", szMainTable, szName);
така си форматираш заявката. Формата обаче мисля, че няма да ескейпне ',",; и др. Следователно, ако имам sql инжекция в името мога да изтрия всички таблици и данни. Мога да променям и общо взето каквото си искам. Потърсих няколко плъгина, които се ползват и учудващо навсякъде е така. Няма да споменавам плъгините, защото някой може да се възползва. Не че не казах достатъчно, но все пак ... Тук проблема е малко по-голям от колкото на php старото mysql_query. Там не можеш да изпълняваш 2 заявки в 1. Тук обаче можеш. Следователно имаш пълен достъп до mysql-a.

https://forums.alliedmods.net/showthread.php?p=1453064 - това мисля, че е хубава библиотека за работа с mysql. Има ескейп функция. Можете ръчно да си направите. Функцията е взета от amxbans,където програмистите са се сетили за това :)

Код: Избери всички

stock mysql_escape_string(dest[],len)
{
    //copy(dest, len, source);
    replace_all(dest,len,"\\","\\\\");
    replace_all(dest,len,"\0","\\0");
    replace_all(dest,len,"\n","\\n");
    replace_all(dest,len,"\r","\\r");
    replace_all(dest,len,"\x1a","\Z");
    replace_all(dest,len,"'","\'");
    replace_all(dest,len,"^"","\^"");
    return dest;
}  
Използване

Код: Избери всички

new szName[96]
mysql_escape_string(szName,charsmax(szName))
format(szTemp,charsmax(szTemp),"INSERT INTO `orewest` ( `auth` , `minutes`) VALUES ('%s','0');",szName)  

Линк към темата с информацията, която намерих. https://forums.alliedmods.net/showthread.php?t=251556 Просто и аз се бях заблудил, че това е сигурно, но не е.

Потребителски аватар

TheRedShoko ReShoko
Модератор
Модератор
Мнения: 994
Регистриран: 06 окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 4 пъти
Получена благодарност: 53 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko ReShoko » 07 фев 2018, 22:34

Без проба не може да се разбере, но разгледах source на AMXX-a и видях, че в SQL модула има функция за escape-ване на SQL query-та. Все пак трябва някой да го пробва да направи SQL injection и да каже дали е успял.

Потребителски аватар

sianbg gta5-bg
VIP
VIP
Мнения: 230
Регистриран: 13 ное 2017, 12:18
Контакти:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg gta5-bg » 08 фев 2018, 13:41

Нямаше нужда да се тества, защото аз виждам теоретично нещата и съм на 100% сигурен, че е дупка. Реших да тествам и отново да докажа думите си с доказателства.

Плъгина с който тествах. https://forums.alliedmods.net/showthread.php?t=89782 . Казаха ми, че е един от най-добрите автори на плъгини. Еми sql не му е силата явно.

Потребителското име с, което изпълних sql injection. Само да кажа, че sqlx подържа multi query, а не е както в php mysql_query да може само 1 заявка.

Код: Избери всички

sianbg'); DROP TABLE mytable; --
Така си изтрих таблицата. Мога да изтрия цялата база дори и други бази, ако потребителя има достъп. Ако не спазвате принципите разделяй и владей и ползвате 1 потребител за всички бази ...


Мога да намеря 20 сървъра сега и да ги хакна. Просто това е много голяма дупка в сигурността и се чудя как така 10 години никой не се сети.

Добавено преди 3 минути 33 секунди:
сега се сетих да проверя плъгините за vip. Там 100% ще е mysql, защото масово се купуват от сайт-а. Демек тряя сървъра да ги вземе през mysql. Ако може да ми дадете най-използваните плъгини за това да ги прегледам.

Потребителски аватар

TheRedShoko ReShoko
Модератор
Модератор
Мнения: 994
Регистриран: 06 окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 4 пъти
Получена благодарност: 53 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko ReShoko » 08 фев 2018, 13:53

С коя версия на AMXX си?

Потребителски аватар

sianbg gta5-bg
VIP
VIP
Мнения: 230
Регистриран: 13 ное 2017, 12:18
Контакти:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg gta5-bg » 08 фев 2018, 13:57

1.8.3 - Това няма значение. Sql-a си е sql.

Добавено преди 6 минути 59 секунди:
https://forums.alliedmods.net/showthread.php?p=1637334 - тук същата история. Има още много много плъгини с тази огромна грешка ....

Потребителски аватар

TheRedShoko ReShoko
Модератор
Модератор
Мнения: 994
Регистриран: 06 окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 4 пъти
Получена благодарност: 53 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko ReShoko » 08 фев 2018, 14:55

Пробвай последния build. Има значение как работи отдолу. Видях, че се извиква функция за обезопасяване на параметрите и би трябвало да не прави проблеми...

Потребителски аватар

sianbg gta5-bg
VIP
VIP
Мнения: 230
Регистриран: 13 ное 2017, 12:18
Контакти:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg gta5-bg » 08 фев 2018, 15:06

Последен build е. Има функция за ескейп, но програмиста не го е използвал. Това е проблема. Няма как самият модул да ескейпва всичко. Просто не работи така mysql. Правилно е да се използва SQL_PrepareQuery. В урока странното е,че се ползва за създаване на таблицата, но за добавяне на информация се ползва format. Проблема не е amxmodx или нещо подобно. Проблема е в програмистите на плъгини. Трябва да се ползва prepare stmt за всичко, а не format на заявката. Това е фундаментална грешка. Проверих най-използваните плъгини като mysql gag на kostov. Просто всеки, който слага нещо с mysql моля да провери това в плъгина. И моля да се промени в урока, защото това ще обърка много млади програмисти.

Отговори

Върни се в “Полезни кодове/уроци”

Кой е на линия

Потребители, разглеждащи този форум: Няма регистрирани потребители и 2 госта