2021-01-26 20:22:32 +00:00
|
|
|
#include "connection.h"
|
|
|
|
|
2021-11-28 00:31:22 +00:00
|
|
|
#if defined(CONF_MYSQL)
|
2021-01-26 20:22:32 +00:00
|
|
|
#include <mysql.h>
|
2020-07-04 08:13:21 +00:00
|
|
|
|
2020-08-09 15:54:25 +00:00
|
|
|
#include <base/tl/threading.h>
|
2020-08-03 14:18:22 +00:00
|
|
|
#include <engine/console.h>
|
2020-07-04 17:53:27 +00:00
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
#include <atomic>
|
|
|
|
#include <memory>
|
|
|
|
#include <vector>
|
|
|
|
|
2022-07-06 15:23:32 +00:00
|
|
|
// MySQL >= 8.0.1 removed my_bool, 8.0.2 accidentally reintroduced it: https://bugs.mysql.com/bug.php?id=87337
|
|
|
|
#if !defined(LIBMARIADB) && MYSQL_VERSION_ID >= 80001 && MYSQL_VERSION_ID != 80002
|
2022-03-23 22:20:56 +00:00
|
|
|
typedef bool my_bool;
|
|
|
|
#endif
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
MYSQLSTATE_UNINITIALIZED,
|
|
|
|
MYSQLSTATE_INITIALIZED,
|
|
|
|
MYSQLSTATE_SHUTTINGDOWN,
|
|
|
|
};
|
|
|
|
|
|
|
|
std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED};
|
|
|
|
std::atomic_int g_MysqlNumConnections;
|
|
|
|
|
|
|
|
int MysqlInit()
|
|
|
|
{
|
|
|
|
dbg_assert(mysql_thread_safe(), "MySQL library without thread safety");
|
|
|
|
dbg_assert(g_MysqlState == MYSQLSTATE_UNINITIALIZED, "double MySQL initialization");
|
|
|
|
if(mysql_library_init(0, nullptr, nullptr))
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
int Uninitialized = MYSQLSTATE_UNINITIALIZED;
|
|
|
|
bool Swapped = g_MysqlState.compare_exchange_strong(Uninitialized, MYSQLSTATE_INITIALIZED);
|
|
|
|
(void)Swapped;
|
|
|
|
dbg_assert(Swapped, "MySQL double initialization");
|
|
|
|
return 0;
|
|
|
|
}
|
2020-07-14 22:12:08 +00:00
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
void MysqlUninit()
|
|
|
|
{
|
|
|
|
int Initialized = MYSQLSTATE_INITIALIZED;
|
|
|
|
bool Swapped = g_MysqlState.compare_exchange_strong(Initialized, MYSQLSTATE_SHUTTINGDOWN);
|
|
|
|
(void)Swapped;
|
|
|
|
dbg_assert(Swapped, "double MySQL free or free without initialization");
|
|
|
|
int Counter = g_MysqlNumConnections;
|
|
|
|
if(Counter != 0)
|
|
|
|
{
|
|
|
|
dbg_msg("mysql", "can't deinitialize, connections remaining: %d", Counter);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mysql_library_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
class CMysqlConnection : public IDbConnection
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CMysqlConnection(
|
|
|
|
const char *pDatabase,
|
|
|
|
const char *pPrefix,
|
|
|
|
const char *pUser,
|
|
|
|
const char *pPass,
|
|
|
|
const char *pIp,
|
2022-09-13 14:18:04 +00:00
|
|
|
const char *pBindaddr,
|
2021-01-26 20:22:32 +00:00
|
|
|
int Port,
|
|
|
|
bool Setup);
|
2022-05-19 08:35:24 +00:00
|
|
|
~CMysqlConnection();
|
2022-06-30 22:36:32 +00:00
|
|
|
void Print(IConsole *pConsole, const char *pMode) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
CMysqlConnection *Copy() override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
const char *BinaryCollate() const override { return "utf8mb4_bin"; }
|
|
|
|
void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) override;
|
|
|
|
const char *InsertTimestampAsUtc() const override { return "?"; }
|
|
|
|
const char *CollateNocase() const override { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
|
|
|
|
const char *InsertIgnore() const override { return "INSERT IGNORE"; }
|
|
|
|
const char *Random() const override { return "RAND()"; }
|
|
|
|
const char *MedianMapTime(char *pBuffer, int BufferSize) const override;
|
|
|
|
const char *False() const override { return "FALSE"; }
|
|
|
|
const char *True() const override { return "TRUE"; }
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
bool Connect(char *pError, int ErrorSize) override;
|
|
|
|
void Disconnect() override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
void BindString(int Idx, const char *pString) override;
|
|
|
|
void BindBlob(int Idx, unsigned char *pBlob, int Size) override;
|
|
|
|
void BindInt(int Idx, int Value) override;
|
|
|
|
void BindInt64(int Idx, int64_t Value) override;
|
|
|
|
void BindFloat(int Idx, float Value) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
void Print() override {}
|
|
|
|
bool Step(bool *pEnd, char *pError, int ErrorSize) override;
|
|
|
|
bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
bool IsNull(int Col) override;
|
|
|
|
float GetFloat(int Col) override;
|
|
|
|
int GetInt(int Col) override;
|
|
|
|
int64_t GetInt64(int Col) override;
|
|
|
|
void GetString(int Col, char *pBuffer, int BufferSize) override;
|
|
|
|
int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-05-19 08:35:24 +00:00
|
|
|
bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) override;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
class CStmtDeleter
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void operator()(MYSQL_STMT *pStmt) const;
|
|
|
|
};
|
|
|
|
|
|
|
|
char m_aErrorDetail[128];
|
|
|
|
void StoreErrorMysql(const char *pContext);
|
|
|
|
void StoreErrorStmt(const char *pContext);
|
|
|
|
bool ConnectImpl();
|
|
|
|
bool PrepareAndExecuteStatement(const char *pStmt);
|
|
|
|
//static void DeleteResult(MYSQL_RES *pResult);
|
|
|
|
|
|
|
|
union UParameterExtra
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned long ul;
|
|
|
|
float f;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool m_NewQuery = false;
|
|
|
|
bool m_HaveConnection = false;
|
|
|
|
MYSQL m_Mysql;
|
|
|
|
std::unique_ptr<MYSQL_STMT, CStmtDeleter> m_pStmt = nullptr;
|
2022-06-11 19:38:18 +00:00
|
|
|
std::vector<MYSQL_BIND> m_vStmtParameters;
|
|
|
|
std::vector<UParameterExtra> m_vStmtParameterExtras;
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
// copy of config vars
|
|
|
|
char m_aDatabase[64];
|
|
|
|
char m_aUser[64];
|
|
|
|
char m_aPass[64];
|
|
|
|
char m_aIp[64];
|
2022-09-13 14:18:04 +00:00
|
|
|
char m_aBindaddr[128];
|
2021-01-26 20:22:32 +00:00
|
|
|
int m_Port;
|
|
|
|
bool m_Setup;
|
|
|
|
|
|
|
|
std::atomic_bool m_InUse;
|
|
|
|
};
|
|
|
|
|
|
|
|
void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const
|
|
|
|
{
|
|
|
|
mysql_stmt_close(pStmt);
|
|
|
|
}
|
2020-07-04 08:13:21 +00:00
|
|
|
|
|
|
|
CMysqlConnection::CMysqlConnection(
|
2020-09-26 19:41:58 +00:00
|
|
|
const char *pDatabase,
|
|
|
|
const char *pPrefix,
|
|
|
|
const char *pUser,
|
|
|
|
const char *pPass,
|
|
|
|
const char *pIp,
|
2022-09-13 14:18:04 +00:00
|
|
|
const char *pBindaddr,
|
2020-09-26 19:41:58 +00:00
|
|
|
int Port,
|
|
|
|
bool Setup) :
|
2020-07-04 08:13:21 +00:00
|
|
|
IDbConnection(pPrefix),
|
|
|
|
m_Port(Port),
|
|
|
|
m_Setup(Setup),
|
|
|
|
m_InUse(false)
|
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
g_MysqlNumConnections += 1;
|
|
|
|
dbg_assert(g_MysqlState == MYSQLSTATE_INITIALIZED, "MySQL library not in initialized state");
|
|
|
|
|
|
|
|
mem_zero(m_aErrorDetail, sizeof(m_aErrorDetail));
|
|
|
|
mem_zero(&m_Mysql, sizeof(m_Mysql));
|
|
|
|
mysql_init(&m_Mysql);
|
|
|
|
|
2020-07-04 08:13:21 +00:00
|
|
|
str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase));
|
|
|
|
str_copy(m_aUser, pUser, sizeof(m_aUser));
|
|
|
|
str_copy(m_aPass, pPass, sizeof(m_aPass));
|
|
|
|
str_copy(m_aIp, pIp, sizeof(m_aIp));
|
2022-09-13 14:18:04 +00:00
|
|
|
str_copy(m_aBindaddr, pBindaddr, sizeof(m_aBindaddr));
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CMysqlConnection::~CMysqlConnection()
|
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
mysql_close(&m_Mysql);
|
|
|
|
g_MysqlNumConnections -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMysqlConnection::StoreErrorMysql(const char *pContext)
|
|
|
|
{
|
|
|
|
str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:mysql:%d): %s", pContext, mysql_errno(&m_Mysql), mysql_error(&m_Mysql));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMysqlConnection::StoreErrorStmt(const char *pContext)
|
|
|
|
{
|
|
|
|
str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:stmt:%d): %s", pContext, mysql_stmt_errno(m_pStmt.get()), mysql_stmt_error(m_pStmt.get()));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CMysqlConnection::PrepareAndExecuteStatement(const char *pStmt)
|
|
|
|
{
|
|
|
|
if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt)))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("prepare");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if(mysql_stmt_execute(m_pStmt.get()))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("execute");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2022-06-30 22:36:32 +00:00
|
|
|
void CMysqlConnection::Print(IConsole *pConsole, const char *pMode)
|
2020-08-03 14:18:22 +00:00
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
2020-09-26 19:41:58 +00:00
|
|
|
"MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
|
2022-06-30 22:36:32 +00:00
|
|
|
pMode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port);
|
2020-08-03 14:18:22 +00:00
|
|
|
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
|
|
|
}
|
|
|
|
|
2020-07-04 08:13:21 +00:00
|
|
|
CMysqlConnection *CMysqlConnection::Copy()
|
|
|
|
{
|
2022-09-13 14:18:04 +00:00
|
|
|
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_aBindaddr, m_Port, m_Setup);
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-18 18:49:34 +00:00
|
|
|
void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
|
|
|
|
{
|
|
|
|
str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp);
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:58:30 +00:00
|
|
|
bool CMysqlConnection::Connect(char *pError, int ErrorSize)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
|
|
|
if(m_InUse.exchange(true))
|
2021-01-31 15:12:38 +00:00
|
|
|
{
|
|
|
|
dbg_assert(0, "Tried connecting while the connection is in use");
|
|
|
|
}
|
2020-07-04 08:13:21 +00:00
|
|
|
|
2020-07-04 17:53:27 +00:00
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
if(ConnectImpl())
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
2021-01-26 20:22:32 +00:00
|
|
|
m_InUse.store(false);
|
2021-01-31 15:12:38 +00:00
|
|
|
return true;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-31 15:12:38 +00:00
|
|
|
return false;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
2020-07-04 17:53:27 +00:00
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
bool CMysqlConnection::ConnectImpl()
|
|
|
|
{
|
|
|
|
if(m_HaveConnection)
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
if(m_pStmt && mysql_stmt_free_result(m_pStmt.get()))
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
StoreErrorStmt("free_result");
|
|
|
|
dbg_msg("mysql", "can't free last result %s", m_aErrorDetail);
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
if(!mysql_select_db(&m_Mysql, m_aDatabase))
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
// Success.
|
|
|
|
return false;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
StoreErrorMysql("select_db");
|
|
|
|
dbg_msg("mysql", "ping error, trying to reconnect %s", m_aErrorDetail);
|
|
|
|
mysql_close(&m_Mysql);
|
|
|
|
mem_zero(&m_Mysql, sizeof(m_Mysql));
|
|
|
|
mysql_init(&m_Mysql);
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
m_pStmt = nullptr;
|
2021-12-29 09:37:19 +00:00
|
|
|
unsigned int OptConnectTimeout = 60;
|
|
|
|
unsigned int OptReadTimeout = 60;
|
|
|
|
unsigned int OptWriteTimeout = 120;
|
2021-01-26 20:22:32 +00:00
|
|
|
my_bool OptReconnect = true;
|
|
|
|
mysql_options(&m_Mysql, MYSQL_OPT_CONNECT_TIMEOUT, &OptConnectTimeout);
|
|
|
|
mysql_options(&m_Mysql, MYSQL_OPT_READ_TIMEOUT, &OptReadTimeout);
|
|
|
|
mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout);
|
|
|
|
mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect);
|
|
|
|
mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");
|
2022-09-13 14:18:04 +00:00
|
|
|
if(m_aBindaddr[0] != '\0')
|
|
|
|
{
|
|
|
|
mysql_options(&m_Mysql, MYSQL_OPT_BIND, m_aBindaddr);
|
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
if(!mysql_real_connect(&m_Mysql, m_aIp, m_aUser, m_aPass, nullptr, m_Port, nullptr, CLIENT_IGNORE_SIGPIPE))
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
StoreErrorMysql("real_connect");
|
|
|
|
return true;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
m_HaveConnection = true;
|
|
|
|
|
|
|
|
m_pStmt = std::unique_ptr<MYSQL_STMT, CStmtDeleter>(mysql_stmt_init(&m_Mysql));
|
|
|
|
|
|
|
|
// Apparently MYSQL_SET_CHARSET_NAME is not enough
|
|
|
|
if(PrepareAndExecuteStatement("SET CHARACTER SET utf8mb4"))
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
return true;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
if(m_Setup)
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
char aCreateDatabase[1024];
|
|
|
|
// create database
|
|
|
|
str_format(aCreateDatabase, sizeof(aCreateDatabase), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
|
|
|
|
if(PrepareAndExecuteStatement(aCreateDatabase))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
// Connect to specific database
|
|
|
|
if(mysql_select_db(&m_Mysql, m_aDatabase))
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
StoreErrorMysql("select_db");
|
|
|
|
return true;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
if(m_Setup)
|
|
|
|
{
|
|
|
|
char aCreateRace[1024];
|
|
|
|
char aCreateTeamrace[1024];
|
|
|
|
char aCreateMaps[1024];
|
|
|
|
char aCreateSaves[1024];
|
|
|
|
char aCreatePoints[1024];
|
|
|
|
FormatCreateRace(aCreateRace, sizeof(aCreateRace));
|
|
|
|
FormatCreateTeamrace(aCreateTeamrace, sizeof(aCreateTeamrace), "VARBINARY(16)");
|
|
|
|
FormatCreateMaps(aCreateMaps, sizeof(aCreateMaps));
|
|
|
|
FormatCreateSaves(aCreateSaves, sizeof(aCreateSaves));
|
|
|
|
FormatCreatePoints(aCreatePoints, sizeof(aCreatePoints));
|
|
|
|
|
|
|
|
if(PrepareAndExecuteStatement(aCreateRace) ||
|
|
|
|
PrepareAndExecuteStatement(aCreateTeamrace) ||
|
|
|
|
PrepareAndExecuteStatement(aCreateMaps) ||
|
|
|
|
PrepareAndExecuteStatement(aCreateSaves) ||
|
|
|
|
PrepareAndExecuteStatement(aCreatePoints))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
m_Setup = false;
|
|
|
|
}
|
|
|
|
dbg_msg("mysql", "connection established");
|
|
|
|
return false;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CMysqlConnection::Disconnect()
|
|
|
|
{
|
|
|
|
m_InUse.store(false);
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:58:30 +00:00
|
|
|
bool CMysqlConnection::PrepareStatement(const char *pStmt, char *pError, int ErrorSize)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt)))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("prepare");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
2020-07-04 17:53:27 +00:00
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
unsigned NumParameters = mysql_stmt_param_count(m_pStmt.get());
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameters.resize(NumParameters);
|
|
|
|
m_vStmtParameterExtras.resize(NumParameters);
|
|
|
|
mem_zero(&m_vStmtParameters[0], sizeof(m_vStmtParameters[0]) * m_vStmtParameters.size());
|
|
|
|
mem_zero(&m_vStmtParameterExtras[0], sizeof(m_vStmtParameterExtras[0]) * m_vStmtParameterExtras.size());
|
2021-01-31 16:58:30 +00:00
|
|
|
return false;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CMysqlConnection::BindString(int Idx, const char *pString)
|
|
|
|
{
|
2020-07-04 17:53:27 +00:00
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
Idx -= 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
2021-01-26 20:22:32 +00:00
|
|
|
|
|
|
|
int Length = str_length(pString);
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameterExtras[Idx].ul = Length;
|
|
|
|
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->buffer_type = MYSQL_TYPE_STRING;
|
|
|
|
pParam->buffer = (void *)pString;
|
|
|
|
pParam->buffer_length = Length + 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
pParam->length = &m_vStmtParameterExtras[Idx].ul;
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->is_null = nullptr;
|
|
|
|
pParam->is_unsigned = false;
|
|
|
|
pParam->error = nullptr;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-14 22:12:08 +00:00
|
|
|
void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
|
|
|
|
{
|
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
Idx -= 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameterExtras[Idx].ul = Size;
|
|
|
|
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->buffer_type = MYSQL_TYPE_BLOB;
|
|
|
|
pParam->buffer = pBlob;
|
|
|
|
pParam->buffer_length = Size;
|
2022-06-11 19:38:18 +00:00
|
|
|
pParam->length = &m_vStmtParameterExtras[Idx].ul;
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->is_null = nullptr;
|
|
|
|
pParam->is_unsigned = false;
|
|
|
|
pParam->error = nullptr;
|
2020-07-14 22:12:08 +00:00
|
|
|
}
|
|
|
|
|
2020-07-04 08:13:21 +00:00
|
|
|
void CMysqlConnection::BindInt(int Idx, int Value)
|
|
|
|
{
|
2020-07-04 17:53:27 +00:00
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
Idx -= 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameterExtras[Idx].i = Value;
|
|
|
|
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->buffer_type = MYSQL_TYPE_LONG;
|
2022-06-11 19:38:18 +00:00
|
|
|
pParam->buffer = &m_vStmtParameterExtras[Idx].i;
|
|
|
|
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->length = nullptr;
|
|
|
|
pParam->is_null = nullptr;
|
|
|
|
pParam->is_unsigned = false;
|
2021-11-19 11:29:24 +00:00
|
|
|
pParam->error = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CMysqlConnection::BindInt64(int Idx, int64_t Value)
|
|
|
|
{
|
|
|
|
m_NewQuery = true;
|
|
|
|
Idx -= 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
2021-11-19 11:29:24 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameterExtras[Idx].i = Value;
|
|
|
|
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
2021-11-19 11:29:24 +00:00
|
|
|
pParam->buffer_type = MYSQL_TYPE_LONGLONG;
|
2022-06-11 19:38:18 +00:00
|
|
|
pParam->buffer = &m_vStmtParameterExtras[Idx].i;
|
|
|
|
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
|
2021-11-19 11:29:24 +00:00
|
|
|
pParam->length = nullptr;
|
|
|
|
pParam->is_null = nullptr;
|
|
|
|
pParam->is_unsigned = false;
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->error = nullptr;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2020-07-14 22:12:08 +00:00
|
|
|
void CMysqlConnection::BindFloat(int Idx, float Value)
|
|
|
|
{
|
|
|
|
m_NewQuery = true;
|
2021-01-26 20:22:32 +00:00
|
|
|
Idx -= 1;
|
2022-06-11 19:38:18 +00:00
|
|
|
dbg_assert(0 <= Idx && Idx < (int)m_vStmtParameters.size(), "index out of bounds");
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
m_vStmtParameterExtras[Idx].f = Value;
|
|
|
|
MYSQL_BIND *pParam = &m_vStmtParameters[Idx];
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->buffer_type = MYSQL_TYPE_FLOAT;
|
2022-06-11 19:38:18 +00:00
|
|
|
pParam->buffer = &m_vStmtParameterExtras[Idx].f;
|
|
|
|
pParam->buffer_length = sizeof(m_vStmtParameterExtras[Idx].i);
|
2021-01-26 20:22:32 +00:00
|
|
|
pParam->length = nullptr;
|
|
|
|
pParam->is_null = nullptr;
|
|
|
|
pParam->is_unsigned = false;
|
|
|
|
pParam->error = nullptr;
|
2020-07-14 22:12:08 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:58:30 +00:00
|
|
|
bool CMysqlConnection::Step(bool *pEnd, char *pError, int ErrorSize)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2020-07-04 17:53:27 +00:00
|
|
|
if(m_NewQuery)
|
|
|
|
{
|
|
|
|
m_NewQuery = false;
|
2022-06-11 19:38:18 +00:00
|
|
|
if(mysql_stmt_bind_param(m_pStmt.get(), &m_vStmtParameters[0]))
|
2021-01-26 20:22:32 +00:00
|
|
|
{
|
|
|
|
StoreErrorStmt("bind_param");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
|
|
|
if(mysql_stmt_execute(m_pStmt.get()))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("execute");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
int Result = mysql_stmt_fetch(m_pStmt.get());
|
|
|
|
if(Result == 1)
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
2021-01-31 16:58:30 +00:00
|
|
|
*pEnd = (Result == MYSQL_NO_DATA);
|
2021-01-26 20:22:32 +00:00
|
|
|
// `Result` is now either `MYSQL_DATA_TRUNCATED` (which we ignore, we
|
|
|
|
// fetch our columns in a different way) or `0` aka success.
|
2021-01-31 16:58:30 +00:00
|
|
|
return false;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 16:58:30 +00:00
|
|
|
bool CMysqlConnection::ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize)
|
2021-01-20 13:22:42 +00:00
|
|
|
{
|
|
|
|
if(m_NewQuery)
|
|
|
|
{
|
|
|
|
m_NewQuery = false;
|
2022-06-11 19:38:18 +00:00
|
|
|
if(mysql_stmt_bind_param(m_pStmt.get(), &m_vStmtParameters[0]))
|
2021-01-26 20:22:32 +00:00
|
|
|
{
|
|
|
|
StoreErrorStmt("bind_param");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
|
|
|
if(mysql_stmt_execute(m_pStmt.get()))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("execute");
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, m_aErrorDetail, ErrorSize);
|
|
|
|
return true;
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
2021-01-31 16:58:30 +00:00
|
|
|
*pNumUpdated = mysql_stmt_affected_rows(m_pStmt.get());
|
|
|
|
return false;
|
2021-01-20 13:22:42 +00:00
|
|
|
}
|
2021-01-31 16:58:30 +00:00
|
|
|
str_copy(pError, "tried to execute update without query", ErrorSize);
|
|
|
|
return true;
|
2021-01-20 13:22:42 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
bool CMysqlConnection::IsNull(int Col)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
my_bool IsNull;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_NULL;
|
|
|
|
Bind.buffer = nullptr;
|
|
|
|
Bind.buffer_length = 0;
|
|
|
|
Bind.length = nullptr;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = nullptr;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:null");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in IsNull");
|
|
|
|
}
|
|
|
|
return IsNull;
|
2020-07-04 17:53:27 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
float CMysqlConnection::GetFloat(int Col)
|
2020-07-04 17:53:27 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
float Value;
|
|
|
|
my_bool IsNull;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_FLOAT;
|
|
|
|
Bind.buffer = &Value;
|
|
|
|
Bind.buffer_length = sizeof(Value);
|
|
|
|
Bind.length = nullptr;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = nullptr;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:float");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in GetFloat");
|
|
|
|
}
|
|
|
|
if(IsNull)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting float: NULL");
|
|
|
|
}
|
|
|
|
return Value;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
int CMysqlConnection::GetInt(int Col)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
int Value;
|
|
|
|
my_bool IsNull;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_LONG;
|
|
|
|
Bind.buffer = &Value;
|
|
|
|
Bind.buffer_length = sizeof(Value);
|
|
|
|
Bind.length = nullptr;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = nullptr;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:int");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in GetInt");
|
|
|
|
}
|
|
|
|
if(IsNull)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting int: NULL");
|
|
|
|
}
|
|
|
|
return Value;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 15:48:57 +00:00
|
|
|
int64_t CMysqlConnection::GetInt64(int Col)
|
|
|
|
{
|
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
int64_t Value;
|
|
|
|
my_bool IsNull;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_LONGLONG;
|
|
|
|
Bind.buffer = &Value;
|
|
|
|
Bind.buffer_length = sizeof(Value);
|
|
|
|
Bind.length = nullptr;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = nullptr;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:int64");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in GetInt64");
|
|
|
|
}
|
|
|
|
if(IsNull)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting int: NULL");
|
|
|
|
}
|
|
|
|
return Value;
|
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
for(int i = 0; i < BufferSize; i++)
|
|
|
|
{
|
|
|
|
pBuffer[i] = 'a';
|
|
|
|
}
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
unsigned long Length;
|
|
|
|
my_bool IsNull;
|
|
|
|
my_bool Error;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_STRING;
|
|
|
|
Bind.buffer = pBuffer;
|
|
|
|
Bind.buffer_length = BufferSize;
|
|
|
|
Bind.length = &Length;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = &Error;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:string");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in GetString");
|
|
|
|
}
|
|
|
|
if(IsNull)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting string: NULL");
|
|
|
|
}
|
|
|
|
if(Error)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting string: truncation occured");
|
|
|
|
}
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 20:22:32 +00:00
|
|
|
int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize)
|
2020-07-04 08:13:21 +00:00
|
|
|
{
|
2021-01-26 20:22:32 +00:00
|
|
|
Col -= 1;
|
|
|
|
|
|
|
|
MYSQL_BIND Bind;
|
|
|
|
unsigned long Length;
|
|
|
|
my_bool IsNull;
|
|
|
|
my_bool Error;
|
|
|
|
mem_zero(&Bind, sizeof(Bind));
|
|
|
|
Bind.buffer_type = MYSQL_TYPE_BLOB;
|
|
|
|
Bind.buffer = pBuffer;
|
|
|
|
Bind.buffer_length = BufferSize;
|
|
|
|
Bind.length = &Length;
|
|
|
|
Bind.is_null = &IsNull;
|
|
|
|
Bind.is_unsigned = false;
|
|
|
|
Bind.error = &Error;
|
|
|
|
if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0))
|
|
|
|
{
|
|
|
|
StoreErrorStmt("fetch_column:blob");
|
|
|
|
dbg_msg("mysql", "error fetching column %s", m_aErrorDetail);
|
|
|
|
dbg_assert(0, "error in GetBlob");
|
|
|
|
}
|
|
|
|
if(IsNull)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting blob: NULL");
|
|
|
|
}
|
|
|
|
if(Error)
|
|
|
|
{
|
|
|
|
dbg_assert(0, "error getting blob: truncation occured");
|
|
|
|
}
|
|
|
|
return Length;
|
2020-07-04 08:13:21 +00:00
|
|
|
}
|
2020-07-12 12:07:12 +00:00
|
|
|
|
2021-01-12 13:24:38 +00:00
|
|
|
const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const
|
|
|
|
{
|
|
|
|
str_format(pBuffer, BufferSize,
|
|
|
|
"SELECT MEDIAN(Time) "
|
|
|
|
"OVER (PARTITION BY Map) "
|
|
|
|
"FROM %s_race "
|
2021-01-18 22:34:11 +00:00
|
|
|
"WHERE Map = l.Map "
|
|
|
|
"LIMIT 1",
|
2021-01-12 13:24:38 +00:00
|
|
|
GetPrefix());
|
|
|
|
return pBuffer;
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:58:30 +00:00
|
|
|
bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize)
|
2020-07-12 12:07:12 +00:00
|
|
|
{
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aBuf, sizeof(aBuf),
|
2020-09-26 19:41:58 +00:00
|
|
|
"INSERT INTO %s_points(Name, Points) "
|
|
|
|
"VALUES (?, ?) "
|
2021-01-26 20:22:32 +00:00
|
|
|
"ON DUPLICATE KEY UPDATE Points=Points+?",
|
2020-09-26 19:41:58 +00:00
|
|
|
GetPrefix());
|
2021-01-31 16:58:30 +00:00
|
|
|
if(PrepareStatement(aBuf, pError, ErrorSize))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2020-07-12 12:07:12 +00:00
|
|
|
BindString(1, pPlayer);
|
|
|
|
BindInt(2, Points);
|
|
|
|
BindInt(3, Points);
|
2021-02-27 21:34:44 +00:00
|
|
|
int NumUpdated;
|
2022-05-19 08:35:24 +00:00
|
|
|
return ExecuteUpdate(&NumUpdated, pError, ErrorSize);
|
2020-07-12 12:07:12 +00:00
|
|
|
}
|
2021-01-26 20:22:32 +00:00
|
|
|
|
2021-11-28 12:33:47 +00:00
|
|
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
2021-01-26 20:22:32 +00:00
|
|
|
const char *pDatabase,
|
|
|
|
const char *pPrefix,
|
|
|
|
const char *pUser,
|
|
|
|
const char *pPass,
|
|
|
|
const char *pIp,
|
2022-09-13 14:18:04 +00:00
|
|
|
const char *pBindaddr,
|
2021-01-26 20:22:32 +00:00
|
|
|
int Port,
|
|
|
|
bool Setup)
|
|
|
|
{
|
2022-09-13 14:18:04 +00:00
|
|
|
return std::make_unique<CMysqlConnection>(pDatabase, pPrefix, pUser, pPass, pIp, pBindaddr, Port, Setup);
|
2021-01-26 20:22:32 +00:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
int MysqlInit()
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
void MysqlUninit()
|
|
|
|
{
|
|
|
|
}
|
2021-11-28 12:33:47 +00:00
|
|
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
2021-01-26 20:22:32 +00:00
|
|
|
const char *pDatabase,
|
|
|
|
const char *pPrefix,
|
|
|
|
const char *pUser,
|
|
|
|
const char *pPass,
|
|
|
|
const char *pIp,
|
2022-09-13 14:18:04 +00:00
|
|
|
const char *pBindaddr,
|
2021-01-26 20:22:32 +00:00
|
|
|
int Port,
|
|
|
|
bool Setup)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
#endif
|