From d59cdf47b571bac5840b3f4cc1eaa347539951b3 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 4 Jul 2020 10:13:21 +0200 Subject: [PATCH 01/37] Define new database interface --- CMakeLists.txt | 5 +- src/engine/server/databases/connection.h | 58 ++++++++++++++++ src/engine/server/databases/mysql.cpp | 88 ++++++++++++++++++++++++ src/engine/server/databases/mysql.h | 67 ++++++++++++++++++ 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 src/engine/server/databases/connection.h create mode 100644 src/engine/server/databases/mysql.cpp create mode 100644 src/engine/server/databases/mysql.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 865603b34..9e7d6c57c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1834,11 +1834,14 @@ set_src(ANTIBOT_SRC GLOB src/antibot antibot_null.cpp ) -set_src(ENGINE_SERVER GLOB src/engine/server +set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server antibot.cpp antibot.h authmanager.cpp authmanager.h + databases/connection.h + databases/mysql.cpp + databases/mysql.h name_ban.cpp name_ban.h register.cpp diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h new file mode 100644 index 000000000..8a5d990b2 --- /dev/null +++ b/src/engine/server/databases/connection.h @@ -0,0 +1,58 @@ +#ifndef ENGINE_SERVER_DATABASES_CONNECTION_H +#define ENGINE_SERVER_DATABASES_CONNECTION_H + +#include + +// can hold one PreparedStatement with Results +class IDbConnection +{ +public: + IDbConnection(const char *pPrefix) + { + str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix)); + } + virtual ~IDbConnection() {} + + // copies the credentials, not the active connection + virtual IDbConnection *Copy() = 0; + + // returns the database prefix + const char *GetPrefix() { return m_aPrefix; } + + enum Status + { + IN_USE, + SUCCESS, + ERROR, + }; + // returns true if connection was established + virtual Status Connect() = 0; + virtual void Disconnect() = 0; + + // get exclusive read/write access to the database + virtual void Lock() = 0; + virtual void Unlock() = 0; + + // ? for Placeholders, connection has to be established, can overwrite previous prepared statements + virtual void PrepareStatement(const char *pStmt) = 0; + + // PrepareStatement has to be called beforehand, + virtual void BindString(int Idx, const char *pString) = 0; + virtual void BindInt(int Idx, int Value) = 0; + + virtual void Execute() = 0; + + // if true if another result row exists and selects it + virtual bool Step() = 0; + + virtual int GetInt(int Col) const = 0; + // ensures that the string is null terminated + virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0; + // returns number of bytes read into the buffer + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0; + +protected: + char m_aPrefix[64]; +}; + +#endif // ENGINE_SERVER_DATABASES_CONNECTION_H diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp new file mode 100644 index 000000000..cebce7127 --- /dev/null +++ b/src/engine/server/databases/mysql.cpp @@ -0,0 +1,88 @@ +#include "mysql.h" + +#include + +CMysqlConnection::CMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup) : + IDbConnection(pPrefix), + m_pDriver(nullptr), + m_Port(Port), + m_Setup(Setup), + m_InUse(false) +{ + 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)); +} + +CMysqlConnection::~CMysqlConnection() +{ +} + +CMysqlConnection *CMysqlConnection::Copy() +{ + return new CMysqlConnection(m_aDatabase, m_aPrefix, m_aUser, m_aPass, m_aIp, m_Port, m_Setup); +} + +IDbConnection::Status CMysqlConnection::Connect() +{ + if(m_InUse.exchange(true)) + return Status::IN_USE; + + return Status::ERROR; +} + +void CMysqlConnection::Disconnect() +{ + m_InUse.store(false); +} + +void CMysqlConnection::Lock() +{ +} + +void CMysqlConnection::Unlock() +{ +} + +void CMysqlConnection::PrepareStatement(const char *pStmt) +{ +} + +void CMysqlConnection::BindString(int Idx, const char *pString) +{ +} + +void CMysqlConnection::BindInt(int Idx, int Value) +{ +} + +void CMysqlConnection::Execute() +{ +} + +bool CMysqlConnection::Step() +{ + return false; +} + +int CMysqlConnection::GetInt(int Col) const +{ + return 0; +} + +void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const +{ +} + +int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const +{ + return 0; +} diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h new file mode 100644 index 000000000..f9885569b --- /dev/null +++ b/src/engine/server/databases/mysql.h @@ -0,0 +1,67 @@ +#ifndef ENGINE_SERVER_DATABASES_MYSQL_H +#define ENGINE_SERVER_DATABASES_MYSQL_H + +#include "connection.h" +#include +#include + +#include +#include +#include + +namespace sql { +class Driver; +} /* namespace sql */ + +class CMysqlConnection : public IDbConnection +{ +public: + CMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup); + virtual ~CMysqlConnection(); + + virtual CMysqlConnection *Copy(); + + virtual Status Connect(); + virtual void Disconnect(); + + virtual void Lock(); + virtual void Unlock(); + + virtual void PrepareStatement(const char *pStmt); + + virtual void BindString(int Idx, const char *pString); + virtual void BindInt(int Idx, int Value); + + virtual void Execute(); + + virtual bool Step(); + + virtual int GetInt(int Col) const; + virtual void GetString(int Col, char *pBuffer, int BufferSize) const; + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; + +private: + std::unique_ptr m_pConnection; + sql::Driver *m_pDriver; + std::unique_ptr m_pPreparedStmt; + std::unique_ptr m_pResults; + + // copy of config vars + char m_aDatabase[64]; + char m_aUser[64]; + char m_aPass[64]; + char m_aIp[64]; + int m_Port; + bool m_Setup; + + std::atomic_bool m_InUse; +}; + +#endif // ENGINE_SERVER_DATABASES_MYSQL_H From 295ce95d64f54c1894c53b5a186fd3e33628043d Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 4 Jul 2020 12:09:40 +0200 Subject: [PATCH 02/37] Roughly define database pool interface --- CMakeLists.txt | 2 + src/engine/server/databases/connection.h | 3 +- .../server/databases/connection_pool.cpp | 42 +++++++++++++++++ src/engine/server/databases/connection_pool.h | 46 +++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/engine/server/databases/connection_pool.cpp create mode 100644 src/engine/server/databases/connection_pool.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e7d6c57c..ab44769fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1840,6 +1840,8 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server authmanager.cpp authmanager.h databases/connection.h + databases/connection_pool.cpp + databases/connection_pool.h databases/mysql.cpp databases/mysql.h name_ban.cpp diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 8a5d990b2..a0aff2453 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -25,8 +25,9 @@ public: SUCCESS, ERROR, }; - // returns true if connection was established + // tries to allocate the connection from the pool established virtual Status Connect() = 0; + // has to be called to return the connection back to the pool virtual void Disconnect() = 0; // get exclusive read/write access to the database diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp new file mode 100644 index 000000000..a787b1b29 --- /dev/null +++ b/src/engine/server/databases/connection_pool.cpp @@ -0,0 +1,42 @@ +#include "connection_pool.h" + +// helper struct to hold thread data +struct CSqlExecData +{ + CSqlExecData( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + const ISqlData *pSqlRequestData + ); + ~CSqlExecData(); + + bool (*m_pFuncPtr) (IDbConnection*, const ISqlData *); + std::unique_ptr m_pSqlRequestData; +}; + +CDbConnectionPool::CDbConnectionPool() +{ +} + +CDbConnectionPool::~CDbConnectionPool() +{ +} + +void CDbConnectionPool::RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode) +{ +} + +void CDbConnectionPool::Execute( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + std::unique_ptr pSqlRequestData) +{ +} + +void CDbConnectionPool::ExecuteWrite( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + std::unique_ptr pSqlRequestData) +{ +} + +void CDbConnectionPool::Shutdown() +{ +} diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h new file mode 100644 index 000000000..8497ccc98 --- /dev/null +++ b/src/engine/server/databases/connection_pool.h @@ -0,0 +1,46 @@ +#ifndef ENGINE_SERVER_DATABASES_CONNECTION_POOL_H +#define ENGINE_SERVER_DATABASES_CONNECTION_POOL_H + +#include "connection.h" + +#include +#include +#include + +struct ISqlData +{ + virtual ~ISqlData(); +}; + +class CDbConnectionPool +{ +public: + CDbConnectionPool(); + ~CDbConnectionPool(); + + enum Mode + { + READ, + WRITE, + WRITE_BACKUP, + NUM_MODES, + }; + + void RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode); + + void Execute( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + std::unique_ptr pSqlRequestData); + // writes to WRITE_BACKUP server in case of failure + void ExecuteWrite( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + std::unique_ptr pSqlRequestData); + + void Shutdown(); + +private: + std::vector> m_aapDbConnections[NUM_MODES]; + lock m_ConnectionLookupLock[NUM_MODES]; +}; + +#endif // ENGINE_SERVER_DATABASES_CONNECTION_POOL_H From 2eb3d23ef46661e748d3de3d8332856d186c02ba Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 4 Jul 2020 19:53:27 +0200 Subject: [PATCH 03/37] Rewrite score using the new interface delete file_score for now as it will be replaced by a sqlite backend --- CMakeLists.txt | 4 - src/engine/server.h | 2 +- src/engine/server/databases/connection.h | 69 +- .../server/databases/connection_pool.cpp | 167 +- src/engine/server/databases/connection_pool.h | 24 +- src/engine/server/databases/mysql.cpp | 195 +- src/engine/server/databases/mysql.h | 21 +- src/engine/server/server.cpp | 74 +- src/engine/server/server.h | 17 +- src/engine/server/sql_string_helpers.cpp | 17 + src/engine/server/sql_string_helpers.h | 3 + src/engine/shared/config_variables.h | 4 +- src/game/server/gamecontext.cpp | 23 +- src/game/server/gamecontext.h | 8 +- src/game/server/score.cpp | 1497 +++++++++++++ src/game/server/score.h | 206 +- src/game/server/score/file_score.cpp | 372 ---- src/game/server/score/file_score.h | 92 - src/game/server/score/sql_score.cpp | 1886 ----------------- src/game/server/score/sql_score.h | 220 -- 20 files changed, 2181 insertions(+), 2720 deletions(-) delete mode 100644 src/game/server/score/file_score.cpp delete mode 100644 src/game/server/score/file_score.h delete mode 100644 src/game/server/score/sql_score.cpp delete mode 100644 src/game/server/score/sql_score.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab44769fe..d1cde6fbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1902,10 +1902,6 @@ set_src(GAME_SERVER GLOB_RECURSE src/game/server save.h score.cpp score.h - score/file_score.cpp - score/file_score.h - score/sql_score.cpp - score/sql_score.h teams.cpp teams.h teehistorian.cpp diff --git a/src/engine/server.h b/src/engine/server.h index fb1192f19..3e3e35b1f 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -252,7 +252,7 @@ public: virtual void OnMapChange(char *pNewMapName, int MapNameSize) = 0; // FullShutdown is true if the program is about to exit (not if the map is changed) - virtual void OnShutdown(bool FullShutdown = false) = 0; + virtual void OnShutdown() = 0; virtual void OnTick() = 0; virtual void OnPreSnap() = 0; diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index a0aff2453..0d67e5f65 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -12,6 +12,7 @@ public: str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix)); } virtual ~IDbConnection() {} + IDbConnection& operator=(const IDbConnection&) = delete; // copies the credentials, not the active connection virtual IDbConnection *Copy() = 0; @@ -31,7 +32,7 @@ public: virtual void Disconnect() = 0; // get exclusive read/write access to the database - virtual void Lock() = 0; + virtual void Lock(const char *pTable) = 0; virtual void Unlock() = 0; // ? for Placeholders, connection has to be established, can overwrite previous prepared statements @@ -41,11 +42,12 @@ public: virtual void BindString(int Idx, const char *pString) = 0; virtual void BindInt(int Idx, int Value) = 0; - virtual void Execute() = 0; - - // if true if another result row exists and selects it + // executes the query and returns if a result row exists and selects it + // when called multiple times the next row is selected virtual bool Step() = 0; + virtual bool IsNull(int Col) const = 0; + virtual float GetFloat(int Col) const = 0; virtual int GetInt(int Col) const = 0; // ensures that the string is null terminated virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0; @@ -54,6 +56,65 @@ public: protected: char m_aPrefix[64]; + + static constexpr const char *m_pCreateRace = + "CREATE TABLE IF NOT EXISTS %s_race (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Name VARCHAR(%d) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Time FLOAT DEFAULT 0, " + "Server CHAR(4), " + "cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, " + "cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, " + "cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, " + "cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, " + "cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, " + "cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, " + "cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, " + "cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, " + "cp25 FLOAT DEFAULT 0, " + "GameID VARCHAR(64), " + "DDNet7 BOOL DEFAULT FALSE, " + "KEY (Map, Name)" + ") CHARACTER SET utf8mb4;"; + static constexpr const char *m_pCreateTeamrace = + "CREATE TABLE IF NOT EXISTS %s_teamrace (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Name VARCHAR(%d) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Time FLOAT DEFAULT 0, " + "ID VARBINARY(16) NOT NULL, " + "GameID VARCHAR(64), " + "DDNet7 BOOL DEFAULT FALSE, " + "KEY Map (Map)" + ") CHARACTER SET utf8mb4;"; + static constexpr const char *m_pCreateMaps = + "CREATE TABLE IF NOT EXISTS %s_maps (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Server VARCHAR(32) BINARY NOT NULL, " + "Mapper VARCHAR(128) BINARY NOT NULL, " + "Points INT DEFAULT 0, " + "Stars INT DEFAULT 0, " + "Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + "UNIQUE KEY Map (Map)" + ") CHARACTER SET utf8mb4;"; + static constexpr const char *m_pCreateSaves = + "CREATE TABLE IF NOT EXISTS %s_saves (" + "Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, " + "Map VARCHAR(128) BINARY NOT NULL, " + "Code VARCHAR(128) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Server CHAR(4), " + "DDNet7 BOOL DEFAULT FALSE, " + "SaveID VARCHAR(36) DEFAULT NULL, " + "UNIQUE KEY (Map, Code)" + ") CHARACTER SET utf8mb4;"; + static constexpr const char *m_pCreatePoints = + "CREATE TABLE IF NOT EXISTS %s_points (" + "Name VARCHAR(%d) BINARY NOT NULL, " + "Points INT DEFAULT 0, " + "UNIQUE KEY Name (Name)" + ") CHARACTER SET utf8mb4;"; }; #endif // ENGINE_SERVER_DATABASES_CONNECTION_H diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index a787b1b29..5ff648fcc 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -1,20 +1,65 @@ #include "connection_pool.h" +#if defined(CONF_SQL) +#include +#endif + // helper struct to hold thread data struct CSqlExecData { CSqlExecData( bool (*pFuncPtr) (IDbConnection *, const ISqlData *), - const ISqlData *pSqlRequestData - ); - ~CSqlExecData(); + std::unique_ptr pThreadData, + const char *pName); + CSqlExecData( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + std::unique_ptr pThreadData, + const char *pName); + ~CSqlExecData() {} - bool (*m_pFuncPtr) (IDbConnection*, const ISqlData *); - std::unique_ptr m_pSqlRequestData; + enum + { + READ_ACCESS, + WRITE_ACCESS, + } m_Mode; + union + { + bool (*m_pWriteFunc) (IDbConnection*, const ISqlData *, bool); + bool (*m_pReadFunc) (IDbConnection*, const ISqlData *); + } m_Ptr; + + std::unique_ptr m_pThreadData; + const char *m_pName; }; -CDbConnectionPool::CDbConnectionPool() +CSqlExecData::CSqlExecData( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + std::unique_ptr pThreadData, + const char *pName) : + m_Mode(READ_ACCESS), + m_pThreadData(std::move(pThreadData)), + m_pName(pName) { + m_Ptr.m_pReadFunc = pFuncPtr; +} + +CSqlExecData::CSqlExecData( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + std::unique_ptr pThreadData, + const char *pName) : + m_Mode(WRITE_ACCESS), + m_pThreadData(std::move(pThreadData)), + m_pName(pName) +{ + m_Ptr.m_pWriteFunc = pFuncPtr; +} + +CDbConnectionPool::CDbConnectionPool() : + m_NumElem(), + FirstElem(0), + LastElem(0) +{ + thread_init_and_detach(CDbConnectionPool::SqlWorker, this, "database worker thread"); } CDbConnectionPool::~CDbConnectionPool() @@ -23,20 +68,122 @@ CDbConnectionPool::~CDbConnectionPool() void CDbConnectionPool::RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode) { + if(DatabaseMode < 0 || NUM_MODES <= DatabaseMode) + return; + m_aapDbConnections[DatabaseMode].push_back(std::move(pDatabase)); } void CDbConnectionPool::Execute( bool (*pFuncPtr) (IDbConnection *, const ISqlData *), - std::unique_ptr pSqlRequestData) + std::unique_ptr pThreadData, + const char *pName) { + m_aTasks[FirstElem++].reset(new CSqlExecData(pFuncPtr, std::move(pThreadData), pName)); + FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); + m_NumElem.signal(); } void CDbConnectionPool::ExecuteWrite( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), - std::unique_ptr pSqlRequestData) + bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + std::unique_ptr pThreadData, + const char *pName) +{ + m_aTasks[FirstElem++].reset(new CSqlExecData(pFuncPtr, std::move(pThreadData), pName)); + FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); + m_NumElem.signal(); +} + +void CDbConnectionPool::OnShutdown() { } -void CDbConnectionPool::Shutdown() +void CDbConnectionPool::SqlWorker(void *pUser) { + CDbConnectionPool *pThis = (CDbConnectionPool *)pUser; + pThis->SqlWorker(); } + +void CDbConnectionPool::SqlWorker() +{ + while(1) + { + m_NumElem.wait(); + auto pThreadData = std::move(m_aTasks[LastElem++]); + LastElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); + bool Success = false; + switch(pThreadData->m_Mode) + { + case CSqlExecData::READ_ACCESS: + { + for(int i = 0; i < (int)m_aapDbConnections[Mode::READ].size(); i++) + { + if(ExecSqlFunc(m_aapDbConnections[Mode::READ][i].get(), pThreadData.get(), false)) + { + Success = true; + break; + } + } + } break; + case CSqlExecData::WRITE_ACCESS: + { + for(int i = 0; i < (int)m_aapDbConnections[Mode::WRITE].size(); i++) + { + if(ExecSqlFunc(m_aapDbConnections[Mode::WRITE][i].get(), pThreadData.get(), false)) + { + Success = true; + break; + } + } + if(!Success) + { + for(int i = 0; i < (int)m_aapDbConnections[Mode::WRITE_BACKUP].size(); i++) + { + if(ExecSqlFunc(m_aapDbConnections[Mode::WRITE_BACKUP][i].get(), pThreadData.get(), true)) + { + Success = true; + break; + } + } + } + } break; + } + if(Success) + dbg_msg("sql", "%s done", pThreadData->m_pName); + } +} + +bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pData, bool Failure) +{ + if(pConnection->Connect() != IDbConnection::SUCCESS) + return false; + bool Success = false; + try { + switch(pData->m_Mode) + { + case CSqlExecData::READ_ACCESS: + if(pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get())) + Success = true; + break; + case CSqlExecData::WRITE_ACCESS: + if(pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), Failure)) + Success = true; + break; + } + } +#if defined(CONF_SQL) + catch (sql::SQLException &e) + { + dbg_msg("sql", "MySQL Error: %s", e.what()); + } +#endif + catch (...) + { + dbg_msg("sql", "Unexpected exception caught"); + } + pConnection->Unlock(); + pConnection->Disconnect(); + if(!Success) + dbg_msg("sql", "%s failed", pData->m_pName); + return Success; +} + diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h index 8497ccc98..364c6fa4f 100644 --- a/src/engine/server/databases/connection_pool.h +++ b/src/engine/server/databases/connection_pool.h @@ -4,12 +4,13 @@ #include "connection.h" #include +#include #include #include struct ISqlData { - virtual ~ISqlData(); + virtual ~ISqlData() {}; }; class CDbConnectionPool @@ -17,6 +18,7 @@ class CDbConnectionPool public: CDbConnectionPool(); ~CDbConnectionPool(); + CDbConnectionPool& operator=(const CDbConnectionPool&) = delete; enum Mode { @@ -30,17 +32,27 @@ public: void Execute( bool (*pFuncPtr) (IDbConnection *, const ISqlData *), - std::unique_ptr pSqlRequestData); + std::unique_ptr pSqlRequestData, + const char *pName); // writes to WRITE_BACKUP server in case of failure void ExecuteWrite( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), - std::unique_ptr pSqlRequestData); + bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + std::unique_ptr pSqlRequestData, + const char *pName); - void Shutdown(); + void OnShutdown(); private: std::vector> m_aapDbConnections[NUM_MODES]; - lock m_ConnectionLookupLock[NUM_MODES]; + + static void SqlWorker(void *pUser); + void SqlWorker(); + bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, bool Failure); + + semaphore m_NumElem; + int FirstElem; + int LastElem; + std::unique_ptr m_aTasks[512]; }; #endif // ENGINE_SERVER_DATABASES_CONNECTION_POOL_H diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index cebce7127..d4e3c0f43 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -1,6 +1,11 @@ #include "mysql.h" +#if defined(CONF_SQL) #include +#include +#endif + +lock CMysqlConnection::m_SqlDriverLock; CMysqlConnection::CMysqlConnection( const char *pDatabase, @@ -11,7 +16,10 @@ CMysqlConnection::CMysqlConnection( int Port, bool Setup) : IDbConnection(pPrefix), - m_pDriver(nullptr), +#if defined(CONF_SQL) + m_NewQuery(false), + m_Locked(false), +#endif m_Port(Port), m_Setup(Setup), m_InUse(false) @@ -20,6 +28,9 @@ CMysqlConnection::CMysqlConnection( str_copy(m_aUser, pUser, sizeof(m_aUser)); str_copy(m_aPass, pPass, sizeof(m_aPass)); str_copy(m_aIp, pIp, sizeof(m_aIp)); +#if not defined(CONF_SQL) + dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time"); +#endif } CMysqlConnection::~CMysqlConnection() @@ -33,9 +44,119 @@ CMysqlConnection *CMysqlConnection::Copy() IDbConnection::Status CMysqlConnection::Connect() { +#if defined(CONF_SQL) if(m_InUse.exchange(true)) return Status::IN_USE; + m_NewQuery = true; + if(m_pConnection != nullptr) + { + try + { + // Connect to specific database + m_pConnection->setSchema(m_aDatabase); + return Status::SUCCESS; + } + catch (sql::SQLException &e) + { + dbg_msg("sql", "MySQL Error: %s", e.what()); + } + catch (const std::exception& ex) + { + dbg_msg("sql", "MySQL Error: %s", ex.what()); + } + catch (const std::string& ex) + { + dbg_msg("sql", "MySQL Error: %s", ex.c_str()); + } + catch (...) + { + dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); + } + + m_InUse.store(false); + dbg_msg("sql", "ERROR: SQL connection failed"); + return Status::ERROR; + } + + try + { + m_pConnection.release(); + m_pPreparedStmt.release(); + m_pResults.release(); + + sql::ConnectOptionsMap connection_properties; + connection_properties["hostName"] = sql::SQLString(m_aIp); + connection_properties["port"] = m_Port; + connection_properties["userName"] = sql::SQLString(m_aUser); + connection_properties["password"] = sql::SQLString(m_aPass); + connection_properties["OPT_CONNECT_TIMEOUT"] = 10; + connection_properties["OPT_READ_TIMEOUT"] = 10; + connection_properties["OPT_WRITE_TIMEOUT"] = 20; + connection_properties["OPT_RECONNECT"] = true; + connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4"); + connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4"); + + // Create connection + { + scope_lock GlobalLockScope(&m_SqlDriverLock); + sql::Driver *pDriver = get_driver_instance(); + m_pConnection.reset(pDriver->connect(connection_properties)); + } + + // Create Statement + m_pStmt = std::unique_ptr(m_pConnection->createStatement()); + + // Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough + m_pStmt->execute("SET CHARACTER SET utf8mb4;"); + + if(m_Setup) + { + char aBuf[1024]; + // create database + str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase); + m_pStmt->execute(aBuf); + // Connect to specific database + m_pConnection->setSchema(m_aDatabase); + str_format(aBuf, sizeof(aBuf), m_pCreateRace, GetPrefix(), MAX_NAME_LENGTH); + m_pStmt->execute(aBuf); + str_format(aBuf, sizeof(aBuf), m_pCreateTeamrace, GetPrefix(), MAX_NAME_LENGTH); + m_pStmt->execute(aBuf); + str_format(aBuf, sizeof(aBuf), m_pCreateMaps, GetPrefix()); + m_pStmt->execute(aBuf); + str_format(aBuf, sizeof(aBuf), m_pCreateSaves, GetPrefix()); + m_pStmt->execute(aBuf); + str_format(aBuf, sizeof(aBuf), m_pCreatePoints, GetPrefix(), MAX_NAME_LENGTH); + m_pStmt->execute(aBuf); + m_Setup = false; + } + else + { + // Connect to specific database + m_pConnection->setSchema(m_aDatabase); + } + dbg_msg("sql", "sql connection established"); + return Status::SUCCESS; + } + catch (sql::SQLException &e) + { + dbg_msg("sql", "MySQL Error: %s", e.what()); + } + catch (const std::exception& ex) + { + dbg_msg("sql", "MySQL Error: %s", ex.what()); + } + catch (const std::string& ex) + { + dbg_msg("sql", "MySQL Error: %s", ex.c_str()); + } + catch (...) + { + dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); + } + +#endif + dbg_msg("sql", "ERROR: sql connection failed"); return Status::ERROR; } @@ -44,45 +165,107 @@ void CMysqlConnection::Disconnect() m_InUse.store(false); } -void CMysqlConnection::Lock() +void CMysqlConnection::Lock(const char *pTable) { +#if defined(CONF_SQL) + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "lock tables %s write;", pTable); + m_pStmt->execute(aBuf); + m_Locked = true; +#endif } void CMysqlConnection::Unlock() { +#if defined(CONF_SQL) + if(m_Locked) + { + m_pStmt->execute("unlock tables;"); + m_Locked = false; + } +#endif } void CMysqlConnection::PrepareStatement(const char *pStmt) { +#if defined(CONF_SQL) + m_pPreparedStmt.reset(m_pConnection->prepareStatement(pStmt)); + m_NewQuery = true; +#endif } void CMysqlConnection::BindString(int Idx, const char *pString) { +#if defined(CONF_SQL) + m_pPreparedStmt->setString(Idx, pString); + m_NewQuery = true; +#endif } void CMysqlConnection::BindInt(int Idx, int Value) { -} - -void CMysqlConnection::Execute() -{ +#if defined(CONF_SQL) + m_pPreparedStmt->setInt(Idx, Value); + m_NewQuery = true; +#endif } bool CMysqlConnection::Step() { +#if defined(CONF_SQL) + if(m_NewQuery) + { + m_NewQuery = false; + m_pResults.reset(m_pPreparedStmt->executeQuery()); + } + return m_pResults->next(); +#else return false; +#endif +} + +bool CMysqlConnection::IsNull(int Col) const +{ +#if defined(CONF_SQL) + return m_pResults->isNull(Col); +#else + return false; +#endif +} + +float CMysqlConnection::GetFloat(int Col) const +{ +#if defined(CONF_SQL) + return (float)m_pResults->getDouble(Col); +#else + return 0.0; +#endif } int CMysqlConnection::GetInt(int Col) const { +#if defined(CONF_SQL) + return m_pResults->getInt(Col); +#else return 0; +#endif } void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const { +#if defined(CONF_SQL) + auto String = m_pResults->getString(Col); + str_copy(pBuffer, String.c_str(), BufferSize); +#endif } int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const { +#if defined(CONF_SQL) + auto Blob = m_pResults->getBlob(Col); + Blob->read((char *)pBuffer, BufferSize); + return Blob->gcount(); +#else return 0; +#endif } diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index f9885569b..5cd5cb2a2 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -2,16 +2,16 @@ #define ENGINE_SERVER_DATABASES_MYSQL_H #include "connection.h" +#include + #include #include +#if defined(CONF_SQL) #include #include #include - -namespace sql { -class Driver; -} /* namespace sql */ +#endif class CMysqlConnection : public IDbConnection { @@ -31,7 +31,7 @@ public: virtual Status Connect(); virtual void Disconnect(); - virtual void Lock(); + virtual void Lock(const char *pTable); virtual void Unlock(); virtual void PrepareStatement(const char *pStmt); @@ -39,19 +39,23 @@ public: virtual void BindString(int Idx, const char *pString); virtual void BindInt(int Idx, int Value); - virtual void Execute(); - virtual bool Step(); + virtual bool IsNull(int Col) const; + virtual float GetFloat(int Col) const; virtual int GetInt(int Col) const; virtual void GetString(int Col, char *pBuffer, int BufferSize) const; virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; private: +#if defined(CONF_SQL) std::unique_ptr m_pConnection; - sql::Driver *m_pDriver; std::unique_ptr m_pPreparedStmt; + std::unique_ptr m_pStmt; std::unique_ptr m_pResults; + bool m_NewQuery; + bool m_Locked; +#endif // copy of config vars char m_aDatabase[64]; @@ -62,6 +66,7 @@ private: bool m_Setup; std::atomic_bool m_InUse; + static lock m_SqlDriverLock; }; #endif // ENGINE_SERVER_DATABASES_MYSQL_H diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 7c9d09d14..cd7a5634c 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -44,6 +44,9 @@ #include #endif +#include +#include + CSnapIDPool::CSnapIDPool() { @@ -297,16 +300,7 @@ CServer::CServer(): m_Register(false), m_RegSixup(true) m_ConnLoggingSocketCreated = false; #endif -#if defined (CONF_SQL) - for (int i = 0; i < MAX_SQLSERVERS; i++) - { - m_apSqlReadServers[i] = 0; - m_apSqlWriteServers[i] = 0; - } - - CSqlConnector::SetReadServers(m_apSqlReadServers); - CSqlConnector::SetWriteServers(m_apSqlWriteServers); -#endif + m_pConnectionPool = new CDbConnectionPool(); m_aErrorShutdownReason[0] = 0; @@ -2584,22 +2578,14 @@ int CServer::Run() m_Fifo.Shutdown(); #endif - GameServer()->OnShutdown(true); + GameServer()->OnShutdown(); m_pMap->Unload(); for(int i = 0; i < 2; i++) free(m_apCurrentMapData[i]); -#if defined (CONF_SQL) - for (int i = 0; i < MAX_SQLSERVERS; i++) - { - if (m_apSqlReadServers[i]) - delete m_apSqlReadServers[i]; - - if (m_apSqlWriteServers[i]) - delete m_apSqlWriteServers[i]; - } -#endif + DbPool()->OnShutdown(); + delete m_pConnectionPool; #if defined (CONF_UPNP) m_UPnP.Shutdown(); @@ -3084,8 +3070,6 @@ void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser) } } -#if defined (CONF_SQL) - void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) { CServer *pSelf = (CServer *)pUserData; @@ -3109,34 +3093,31 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true; - CSqlServer** apSqlServers = ReadOnly ? pSelf->m_apSqlReadServers : pSelf->m_apSqlWriteServers; + auto pSqlServers = std::unique_ptr(new CMysqlConnection( + pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), + pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), + SetUpDb)); - for (int i = 0; i < MAX_SQLSERVERS; i++) + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", + ReadOnly ? "Read" : "Write", + pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), + pResult->GetString(5), pResult->GetInteger(6)); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + if(SetUpDb) { - if (!apSqlServers[i]) - { - apSqlServers[i] = new CSqlServer(pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), &pSelf->m_GlobalSqlLock, ReadOnly, SetUpDb); - - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), - "Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", - ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), - apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), - apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); - if(SetUpDb) - { - if(!apSqlServers[i]->CreateTables()) - pSelf->SetErrorShutdown("database create tables failed"); - } - return; - } + /* TODO + if(!pSqlServers->CreateTables()) + pSelf->SetErrorShutdown("database create tables failed"); + */ } - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "failed to add new sqlserver: limit of sqlservers reached"); + pSelf->DbPool()->RegisterDatabase(std::move(pSqlServers), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE); } void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData) { + /* TODO CServer *pSelf = (CServer *)pUserData; bool ReadOnly; @@ -3159,10 +3140,9 @@ void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData) str_format(aBuf, sizeof(aBuf), "SQL-%s %d: DB: '%s' Prefix: '%s' User: '%s' Pass: '%s' IP: <{'%s'}> Port: %d", ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), apSqlServers[i]->GetPass(), apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } + */ } -#endif - void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); @@ -3363,10 +3343,8 @@ void CServer::RegisterCommands() Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "Reload the map"); -#if defined(CONF_SQL) Console()->Register("add_sqlserver", "s['r'|'w'] s[Database] s[Prefix] s[User] s[Password] s[IP] i[Port] ?i[SetUpDatabase ?]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAddSqlServer, this, "add a sqlserver"); Console()->Register("dump_sqlservers", "s['r'|'w']", CFGFLAG_SERVER, ConDumpSqlServers, this, "dumps all sqlservers readservers = r, writeservers = w"); -#endif Console()->Register("auth_add", "s[ident] s[level] s[pw]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAuthAdd, this, "Add a rcon key"); Console()->Register("auth_add_p", "s[ident] s[level] s[hash] s[salt]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAuthAddHashed, this, "Add a prehashed rcon key"); diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 4e8fc87c9..c68bb1f9b 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -33,11 +33,6 @@ #include "upnp.h" #endif -#if defined (CONF_SQL) - #include "sql_connector.h" - #include "sql_server.h" -#endif - class CSnapIDPool { enum @@ -102,24 +97,20 @@ class CServer : public IServer CUPnP m_UPnP; #endif -#if defined(CONF_SQL) - lock m_GlobalSqlLock; - - CSqlServer *m_apSqlReadServers[MAX_SQLSERVERS]; - CSqlServer *m_apSqlWriteServers[MAX_SQLSERVERS]; -#endif - #if defined(CONF_FAMILY_UNIX) UNIXSOCKETADDR m_ConnLoggingDestAddr; bool m_ConnLoggingSocketCreated; UNIXSOCKET m_ConnLoggingSocket; #endif + class CDbConnectionPool *m_pConnectionPool; + public: class IGameServer *GameServer() { return m_pGameServer; } class IConsole *Console() { return m_pConsole; } class IStorage *Storage() { return m_pStorage; } class IEngineAntibot *Antibot() { return m_pAntibot; } + class CDbConnectionPool *DbPool() { return m_pConnectionPool; } enum { @@ -395,11 +386,9 @@ public: static void ConNameUnban(IConsole::IResult *pResult, void *pUser); static void ConNameBans(IConsole::IResult *pResult, void *pUser); -#if defined (CONF_SQL) // console commands for sqlmasters static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData); static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData); -#endif static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); diff --git a/src/engine/server/sql_string_helpers.cpp b/src/engine/server/sql_string_helpers.cpp index 44b7d8a1e..961561c45 100644 --- a/src/engine/server/sql_string_helpers.cpp +++ b/src/engine/server/sql_string_helpers.cpp @@ -24,6 +24,23 @@ void sqlstr::FuzzyString(char *pString, int size) delete [] newString; } +int sqlstr::EscapeLike(char *pDst, const char *pSrc, int DstSize) +{ + int Pos = 0; + int DstPos = 0; + while(DstPos + 2 < DstSize) + { + if(pSrc[Pos] == '\0') + break; + if(pSrc[Pos] == '\\' || pSrc[Pos] == '%' || pSrc[Pos] == '_' || pSrc[Pos] == '[') + pDst[DstPos++] = '\\'; + pDst[DstPos++] = pSrc[Pos++]; + + } + pDst[DstPos++] = '\0'; + return DstPos; +} + // anti SQL injection void sqlstr::ClearString(char *pString, int size) { diff --git a/src/engine/server/sql_string_helpers.h b/src/engine/server/sql_string_helpers.h index c20408e4b..edbaa1118 100644 --- a/src/engine/server/sql_string_helpers.h +++ b/src/engine/server/sql_string_helpers.h @@ -11,6 +11,9 @@ void FuzzyString(char *pString, int size); // anti SQL injection void ClearString(char *pString, int size = 32); +// written number of added bytes +int EscapeLike(char *pDst, const char *pSrc, int DstSize); + void AgoTimeToString(int agoTime, char *pAgoString); template diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index c62a1eeec..b4b19f01d 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -217,11 +217,9 @@ MACRO_CONFIG_STR(SvSqlServerName, sv_sql_servername, 5, "UNK", CFGFLAG_SERVER, " MACRO_CONFIG_STR(SvSqlValidServerNames, sv_sql_valid_servernames, 64, "UNK", CFGFLAG_SERVER, "Comma separated list of valid server names for saving a game to ([A-Z][A-Z][A-Z].?") MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables savegames (/save and /load)") MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame") -#if defined(CONF_SQL) MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file") -MACRO_CONFIG_STR(SvSqlFailureFile, sv_sql_failure_file, 64, "failed_sql.sql", CFGFLAG_SERVER, "File to store failed Sql-Inserts (ranks)") MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player") -#endif +MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off") #if defined(CONF_UPNP) MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.") diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 09a2c6754..c588a222f 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -24,10 +24,6 @@ #include "gamemodes/DDRace.h" #include "score.h" -#include "score/file_score.h" -#if defined(CONF_SQL) -#include "score/sql_score.h" -#endif enum { @@ -3116,17 +3112,11 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/) } } - // delete old score object - if(m_pScore) - delete m_pScore; + if(!m_pScore) + { + m_pScore = new CScore(this, ((CServer *)Server())->DbPool()); + } - // create score object (add sql later) -#if defined(CONF_SQL) - if(g_Config.m_SvUseSQL) - m_pScore = new CSqlScore(this); - else -#endif - m_pScore = new CFileScore(this); // setup core world //for(int i = 0; i < MAX_CLIENTS; i++) // game.players[i].core.world = &game.world.core; @@ -3376,11 +3366,8 @@ void CGameContext::OnMapChange(char *pNewMapName, int MapNameSize) str_copy(m_aDeleteTempfile, aTemp, sizeof(m_aDeleteTempfile)); } -void CGameContext::OnShutdown(bool FullShutdown) +void CGameContext::OnShutdown() { - if (FullShutdown) - Score()->OnShutdown(); - Antibot()->RoundEnd(); if(m_TeeHistorianActive) diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 3e64ca7d7..ce7266ea2 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -18,7 +18,6 @@ #include "player.h" #include "teehistorian.h" -#include "score.h" #ifdef _MSC_VER typedef __int32 int32_t; typedef unsigned __int32 uint32_t; @@ -54,6 +53,7 @@ enum NUM_TUNEZONES = 256 }; +class CScore; class IConsole; class IEngine; class IStorage; @@ -227,7 +227,7 @@ public: virtual void OnInit(); virtual void OnConsoleInit(); virtual void OnMapChange(char *pNewMapName, int MapNameSize); - virtual void OnShutdown(bool FullShutdown = false); + virtual void OnShutdown(); virtual void OnTick(); virtual void OnPreSnap(); @@ -278,7 +278,7 @@ public: private: bool m_VoteWillPass; - class IScore *m_pScore; + class CScore *m_pScore; //DDRace Console Commands @@ -411,7 +411,7 @@ private: public: CLayers *Layers() { return &m_Layers; } - class IScore *Score() { return m_pScore; } + class CScore *Score() { return m_pScore; } enum { diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 39edec3ad..615c22fdb 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -1,4 +1,21 @@ #include "score.h" +#include "entities/character.h" +#include "gamemodes/DDRace.h" +#include "save.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include CScorePlayerResult::CScorePlayerResult() : m_Done(false) @@ -33,3 +50,1483 @@ void CScorePlayerResult::SetVariant(Variant v) m_Data.m_Info.m_CpTime[i] = 0; } } + +std::shared_ptr CScore::NewSqlPlayerResult(int ClientID) +{ + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + if(pCurPlayer->m_ScoreQueryResult != nullptr) // TODO: send player a message: "too many requests" + return nullptr; + pCurPlayer->m_ScoreQueryResult = std::make_shared(); + return pCurPlayer->m_ScoreQueryResult; +} + +void CScore::ExecPlayerThread( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + const char *pThreadName, + int ClientID, + const char *pName, + int Offset) +{ + auto pResult = NewSqlPlayerResult(ClientID); + if(pResult == nullptr) + return; + auto Tmp = std::unique_ptr(new CSqlPlayerRequest(pResult)); + str_copy(Tmp->m_Name, pName, sizeof(Tmp->m_Name)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + str_copy(Tmp->m_RequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer)); + Tmp->m_Offset = Offset; + + m_pPool->Execute(pFuncPtr, std::move(Tmp), pThreadName); +} + +bool CScore::RateLimitPlayer(int ClientID) +{ + CPlayer *pPlayer = GameServer()->m_apPlayers[ClientID]; + if(pPlayer == 0) + return true; + if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * Server()->TickSpeed() >= Server()->Tick()) + return true; + pPlayer->m_LastSQLQuery = Server()->Tick(); + return false; +} + +void CScore::GeneratePassphrase(char *pBuf, int BufSize) +{ + for(int i = 0; i < 3; i++) + { + if(i != 0) + str_append(pBuf, " ", BufSize); + // TODO: decide if the slight bias towards lower numbers is ok + int Rand = m_Prng.RandomBits() % m_aWordlist.size(); + str_append(pBuf, m_aWordlist[Rand].c_str(), BufSize); + } +} + +CScore::CScore(CGameContext *pGameServer, CDbConnectionPool *pPool) : + m_pPool(pPool), + m_pGameServer(pGameServer), + m_pServer(pGameServer->Server()) +{ + auto InitResult = std::make_shared(); + auto Tmp = std::unique_ptr(new CSqlInitData(InitResult)); + ((CGameControllerDDRace*)(pGameServer->m_pController))->m_pInitResult = InitResult; + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + + IOHANDLE File = GameServer()->Storage()->OpenFile("wordlist.txt", IOFLAG_READ, IStorage::TYPE_ALL); + if(!File) + { + dbg_msg("sql", "failed to open wordlist"); + Server()->SetErrorShutdown("sql open wordlist error"); + return; + } + + uint64 aSeed[2]; + secure_random_fill(aSeed, sizeof(aSeed)); + m_Prng.Seed(aSeed); + CLineReader LineReader; + LineReader.Init(File); + char *pLine; + while((pLine = LineReader.Get())) + { + char Word[32] = {0}; + sscanf(pLine, "%*s %31s", Word); + Word[31] = 0; + m_aWordlist.push_back(Word); + } + if(m_aWordlist.size() < 1000) + { + dbg_msg("sql", "too few words in wordlist"); + Server()->SetErrorShutdown("sql too few words in wordlist"); + return; + } + m_pPool->Execute(Init, std::move(Tmp), "load best time"); +} + +bool CScore::Init(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlInitData *pData = dynamic_cast(pGameData); + + char aBuf[512]; + // get the best time + str_format(aBuf, sizeof(aBuf), + "SELECT Time FROM %s_race WHERE Map=? ORDER BY `Time` ASC LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + + if(pSqlServer->Step()) + pData->m_pResult->m_CurrentRecord = pSqlServer->GetFloat(1); + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::LoadPlayerData(int ClientID) +{ + ExecPlayerThread(LoadPlayerDataThread, "load player data", ClientID, "", 0); +} + +// update stuff +bool CScore::LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + pData->m_pResult->SetVariant(CScorePlayerResult::PLAYER_INFO); + + char aBuf[512]; + // get best race time + str_format(aBuf, sizeof(aBuf), + "SELECT Time, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, " + "cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, " + "cp21, cp22, cp23, cp24, cp25 " + "FROM %s_race " + "WHERE Map = ? AND Name = ? " + "ORDER BY Time ASC " + "LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_RequestingPlayer); + + if(pSqlServer->Step()) + { + // get the best time + float Time = pSqlServer->GetFloat(1); + pData->m_pResult->m_Data.m_Info.m_Time = Time; + pData->m_pResult->m_Data.m_Info.m_Score = -Time; + pData->m_pResult->m_Data.m_Info.m_HasFinishScore = true; + + if(g_Config.m_SvCheckpointSave) + { + for(int i = 0; i < NUM_CHECKPOINTS; i++) + { + pData->m_pResult->m_Data.m_Info.m_CpTime[i] = pSqlServer->GetFloat(i+2); + } + } + } + + // birthday check + str_format(aBuf, sizeof(aBuf), + "SELECT YEAR(Current) - YEAR(Stamp) AS YearsAgo " + "FROM (" + "SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp " + "FROM %s_race " + "WHERE Name=?" + ") AS l " + "WHERE DAYOFMONTH(Current) = DAYOFMONTH(Stamp) AND MONTH(Current) = MONTH(Stamp) " + "AND YEAR(Current) > YEAR(Stamp);", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_RequestingPlayer); + + if(pSqlServer->Step()) + { + int YearsAgo = pSqlServer->GetInt(1); + pData->m_pResult->m_Data.m_Info.m_Birthday = YearsAgo; + } + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::MapVote(int ClientID, const char* MapName) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(MapVoteThread, "map vote", ClientID, MapName, 0); +} + +bool CScore::MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + char aFuzzyMap[128]; + str_copy(aFuzzyMap, pData->m_Name, sizeof(aFuzzyMap)); + sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); + + char aMapPrefix[128]; + str_copy(aMapPrefix, pData->m_Name, sizeof(aMapPrefix)); + str_append(aMapPrefix, "%", sizeof(aMapPrefix)); + + char aBuf[768]; + str_format(aBuf, sizeof(aBuf), + "SELECT Map, Server " + "FROM %s_maps " + "WHERE Map LIKE convert(? using utf8mb4) COLLATE utf8mb4_general_ci " + "ORDER BY " + "CASE WHEN Map = ? THEN 0 ELSE 1 END, " + "CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, " + "LENGTH(Map), Map " + "LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, aFuzzyMap); + pSqlServer->BindString(2, pData->m_Name); + pSqlServer->BindString(3, aMapPrefix); + + if(pSqlServer->Step()) + { + pData->m_pResult->SetVariant(CScorePlayerResult::MAP_VOTE); + auto MapVote = &pData->m_pResult->m_Data.m_MapVote; + pSqlServer->GetString(1, MapVote->m_Map, sizeof(MapVote->m_Map)); + pSqlServer->GetString(2, MapVote->m_Server, sizeof(MapVote->m_Server)); + strcpy(MapVote->m_Reason, "/map"); + + for(char *p = MapVote->m_Server; *p; p++) // lower case server + *p = tolower(*p); + } + else + { + pData->m_pResult->SetVariant(CScorePlayerResult::DIRECT); + str_format(paMessages[0], sizeof(paMessages[0]), + "No map like \"%s\" found. " + "Try adding a '%%' at the start if you don't know the first character. " + "Example: /map %%castle for \"Out of Castle\"", + pData->m_Name); + } + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::MapInfo(int ClientID, const char* MapName) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(MapInfoThread, "map info", ClientID, MapName, 0); +} + +bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + + char aFuzzyMap[128]; + str_copy(aFuzzyMap, pData->m_Name, sizeof(aFuzzyMap)); + sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); + + char aMapPrefix[128]; + str_copy(aMapPrefix, pData->m_Name, sizeof(aMapPrefix)); + str_append(aMapPrefix, "%", sizeof(aMapPrefix)); + + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), + "SELECT l.Map, l.Server, Mapper, Points, Stars, " + "(SELECT COUNT(Name) FROM %s_race WHERE Map = l.Map) AS Finishes, " + "(SELECT COUNT(DISTINCT Name) FROM %s_race WHERE Map = l.Map) AS Finishers, " + "(SELECT ROUND(AVG(Time)) FROM %s_race WHERE Map = l.Map) AS Average, " + "UNIX_TIMESTAMP(l.Timestamp) AS Stamp, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(l.Timestamp) AS Ago, " + "(SELECT MIN(Time) FROM %s_race WHERE Map = l.Map AND Name = ?) AS OwnTime " + "FROM (" + "SELECT * FROM %s_maps " + "WHERE Map LIKE convert(? using utf8mb4) COLLATE utf8mb4_general_ci " + "ORDER BY " + "CASE WHEN Map = ? THEN 0 ELSE 1 END, " + "CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, " + "LENGTH(Map), " + "Map " + "LIMIT 1" + ") as l;", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->GetPrefix() + ); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_RequestingPlayer); + pSqlServer->BindString(2, aFuzzyMap); + pSqlServer->BindString(3, pData->m_Name); + pSqlServer->BindString(4, aMapPrefix); + + if(pSqlServer->Step()) + { + char aMap[MAX_MAP_LENGTH]; + pSqlServer->GetString(1, aMap, sizeof(aMap)); + char aServer[32]; + pSqlServer->GetString(2, aServer, sizeof(aServer)); + char aMapper[128]; + pSqlServer->GetString(3, aMapper, sizeof(aMapper)); + int Points = pSqlServer->GetInt(4); + int Stars = pSqlServer->GetInt(5); + int Finishes = pSqlServer->GetInt(6); + int Finishers = pSqlServer->GetInt(7); + int Average = pSqlServer->GetInt(8); + int Stamp = pSqlServer->GetInt(9); + int Ago = pSqlServer->GetInt(10); + float OwnTime = pSqlServer->GetFloat(11); + + char aAgoString[40] = "\0"; + char aReleasedString[60] = "\0"; + if(Stamp != 0) + { + sqlstr::AgoTimeToString(Ago, aAgoString); + str_format(aReleasedString, sizeof(aReleasedString), ", released %s ago", aAgoString); + } + + char aAverageString[60] = "\0"; + if(Average > 0) + { + str_format(aAverageString, sizeof(aAverageString), " in %d:%02d average", Average / 60, Average % 60); + } + + char aStars[20]; + switch(Stars) + { + case 0: strcpy(aStars, "✰✰✰✰✰"); break; + case 1: strcpy(aStars, "★✰✰✰✰"); break; + case 2: strcpy(aStars, "★★✰✰✰"); break; + case 3: strcpy(aStars, "★★★✰✰"); break; + case 4: strcpy(aStars, "★★★★✰"); break; + case 5: strcpy(aStars, "★★★★★"); break; + default: aStars[0] = '\0'; + } + + char aOwnFinishesString[40] = "\0"; + if(OwnTime > 0) + { + str_format(aOwnFinishesString, sizeof(aOwnFinishesString), + ", your time: %02d:%05.2f", (int)(OwnTime/60), OwnTime-((int)OwnTime/60*60) + ); + } + + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", + aMap, aMapper, aServer, aStars, + Points, Points == 1 ? "point" : "points", + aReleasedString, + Finishes, Finishes == 1 ? "finish" : "finishes", + Finishers, Finishers == 1 ? "tee" : "tees", + aAverageString, aOwnFinishesString + ); + } + else + { + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "No map like \"%s\" found.", pData->m_Name); + } + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible) +{ + CConsole* pCon = (CConsole*)GameServer()->Console(); + if(pCon->m_Cheated || NotEligible) + return; + + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + if(pCurPlayer->m_ScoreFinishResult != nullptr) + dbg_msg("sql", "WARNING: previous save score result didn't complete, overwriting it now"); + pCurPlayer->m_ScoreFinishResult = std::make_shared(); + auto Tmp = std::unique_ptr(new CSqlScoreData(pCurPlayer->m_ScoreFinishResult)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); + Tmp->m_ClientID = ClientID; + str_copy(Tmp->m_Name, Server()->ClientName(ClientID), sizeof(Tmp->m_Name)); + Tmp->m_Time = Time; + str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); + for(int i = 0; i < NUM_CHECKPOINTS; i++) + Tmp->m_aCpCurrent[i] = CpTime[i]; + + m_pPool->ExecuteWrite(SaveScoreThread, std::move(Tmp), "save score"); +} + +bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +{ + const CSqlScoreData *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + char aBuf[1024]; + + str_format(aBuf, sizeof(aBuf), + "SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map=? AND Name=? ORDER BY time ASC LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_Name); + + pSqlServer->Step(); + int NumFinished = pSqlServer->GetInt(1); + if(NumFinished == 0) + { + str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map=?", pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + + if(pSqlServer->Step()) + { + int Points = pSqlServer->GetInt(1); + str_format(paMessages[0], sizeof(paMessages[0]), + "You earned %d point%s for finishing this map!", + Points, Points == 1 ? "" : "s"); + + str_format(aBuf, sizeof(aBuf), + "INSERT INTO %s_points(Name, Points) " + "VALUES (?, ?) " + "ON duplicate key " + "UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Name); + pSqlServer->BindInt(1, Points); + pSqlServer->Step(); + } + } + + // save score + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_race(" + "Map, Name, Timestamp, Time, Server, " + "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " + "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " + "GameID, DDNet7) " + "VALUES (?, ?, ?, %.2f, ?, " + "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " + "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " + "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " + "'%s', false);", + pSqlServer->GetPrefix(), pData->m_Time, + pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], + pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], + pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], + pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], + pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], + pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], + pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], + pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], + pData->m_aCpCurrent[24], pData->m_GameUuid); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_Name); + pSqlServer->BindString(3, pData->m_aTimestamp); + pSqlServer->BindString(4, g_Config.m_SvSqlServerName); + pSqlServer->Step(); + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp) +{ + CConsole* pCon = (CConsole*)GameServer()->Console(); + if(pCon->m_Cheated) + return; + for(unsigned int i = 0; i < Size; i++) + { + if(GameServer()->m_apPlayers[aClientIDs[i]]->m_NotEligibleForFinish) + return; + } + auto Tmp = std::unique_ptr(new CSqlTeamScoreData()); + for(unsigned int i = 0; i < Size; i++) + str_copy(Tmp->m_aNames[i], Server()->ClientName(aClientIDs[i]), sizeof(Tmp->m_aNames[i])); + Tmp->m_Size = Size; + Tmp->m_Time = Time; + str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); + FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + + m_pPool->ExecuteWrite(SaveTeamScoreThread, std::move(Tmp), "save team score"); +} + +bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +{ + const CSqlTeamScoreData *pData = dynamic_cast(pGameData); + + char aBuf[2300]; + + // get the names sorted in a tab separated string + std::vector aNames; + for(unsigned int i = 0; i < pData->m_Size; i++) + aNames.push_back(pData->m_aNames[i]); + + std::sort(aNames.begin(), aNames.end()); + char aSortedNames[2048] = {0}; + for(unsigned int i = 0; i < pData->m_Size; i++) + { + if(i != 0) + str_append(aSortedNames, "\t", sizeof(aSortedNames)); + str_append(aSortedNames, aNames[i].c_str(), sizeof(aSortedNames)); + } + str_format(aBuf, sizeof(aBuf), + "SELECT l.ID, Time " + "FROM ((" // preselect teams with first name in team + "SELECT ID " + "FROM %s_teamrace " + "WHERE Map = ? AND Name = ? AND DDNet7 = false" + ") as l" + ") INNER JOIN %s_teamrace AS r ON l.ID = r.ID " + "GROUP BY ID " + "HAVING GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') = ?", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_aNames[0]); + pSqlServer->BindString(3, aSortedNames); + + if (pSqlServer->Step()) + { + char aGameID[UUID_MAXSTRSIZE]; + pSqlServer->GetString(1, aGameID, sizeof(aGameID)); + float Time = pSqlServer->GetFloat(2); + dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time); + if(pData->m_Time < Time) + { + str_format(aBuf, sizeof(aBuf), + "UPDATE %s_teamrace SET Time=%.2f, Timestamp=?, DDNet7=false, GameID=? WHERE ID = ?;", + pSqlServer->GetPrefix(), pData->m_Time); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_aTimestamp); + pSqlServer->BindString(2, pData->m_GameUuid); + pSqlServer->BindString(3, aGameID); + pSqlServer->Step(); + } + } + else + { + CUuid GameID = RandomUuid(); + char aGameID[UUID_MAXSTRSIZE]; + FormatUuid(GameID, aGameID, sizeof(aGameID)); + + for(unsigned int i = 0; i < pData->m_Size; i++) + { + // if no entry found... create a new one + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " + "VALUES (?, ?, ?, %.2f, ?, ?, false);", + pSqlServer->GetPrefix(), pData->m_Time); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_aNames[i]); + pSqlServer->BindString(3, pData->m_aTimestamp); + pSqlServer->BindString(4, aGameID); + pSqlServer->BindString(5, pData->m_GameUuid); + pSqlServer->Step(); + } + } + + return true; +} + +void CScore::ShowRank(int ClientID, const char* pName) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowRankThread, "show rank", ClientID, pName, 0); +} + +bool CScore::ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + + // check sort method + char aBuf[600]; + + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Name, Time " + "FROM (" + "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " + "FROM %s_race " + "WHERE Map = ? " + "GROUP BY Name " + "WINDOW w AS (ORDER BY Time)" + ") as a " + "WHERE Name = ?;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_Name); + + if(pSqlServer->Step()) + { + int Rank = pSqlServer->GetInt(1); + float Time = pSqlServer->GetFloat(3); + if(g_Config.m_SvHideScore) + { + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "Your time: %02d:%05.2f", (int)(Time/60), Time-((int)Time/60*60)); + } + else + { + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(2, aName, sizeof(aName)); + pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "%d. %s Time: %02d:%05.2f, requested by %s", + Rank, aName, (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer); + } + } + else + { + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "%s is not ranked", pData->m_Name); + } + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowTeamRank(int ClientID, const char* pName) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTeamRankThread, "show team rank", ClientID, pName, 0); +} + +bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + + // check sort method + char aBuf[2400]; + + str_format(aBuf, sizeof(aBuf), + "SELECT GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') AS Names, COUNT(Name) AS NumNames, Time, Rank " + "FROM (" // teamrank score board + "SELECT RANK() OVER w AS Rank, ID " + "FROM %s_teamrace " + "WHERE Map = ? " + "GROUP BY ID " + "WINDOW w AS (ORDER BY Time)" + ") AS TeamRank INNER JOIN (" // select rank with Name in team + "SELECT ID " + "FROM %s_teamrace " + "WHERE Map = ? AND Name = ? " + "ORDER BY Time " + "LIMIT 1" + ") AS l ON TeamRank.ID = l.ID " + "INNER JOIN %s_teamrace AS r ON l.ID = r.ID " + "GROUP BY l.ID", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_Map); + pSqlServer->BindString(3, pData->m_Name); + + if(pSqlServer->Step()) + { + char aNames[512]; + pSqlServer->GetString(1, aNames, sizeof(aNames)); + int NumNames = pSqlServer->GetInt(2); + float Time = pSqlServer->GetFloat(3); + int Rank = pSqlServer->GetInt(4); + + char aFormattedNames[512] = ""; + int StrPos = 0; + for(int Name = 0; Name < NumNames; Name++) + { + int NameStart = StrPos; + while(aNames[StrPos] != '\t' && aNames[StrPos] != '\0') + StrPos++; + aNames[StrPos] = '\0'; + + str_append(aFormattedNames, &aNames[NameStart], sizeof(aFormattedNames)); + + if (Name < NumNames - 2) + str_append(aFormattedNames, ", ", sizeof(aFormattedNames)); + else if (Name < NumNames - 1) + str_append(aFormattedNames, " & ", sizeof(aFormattedNames)); + StrPos++; + } + + if(g_Config.m_SvHideScore) + { + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "Your team time: %02d:%05.02f", (int)(Time/60), Time-((int)Time/60*60)); + } + else + { + pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "%d. %s Team time: %02d:%05.02f, requested by %s", + Rank, aFormattedNames, (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer); + } + } + else + { + str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), + "%s has no team ranks", pData->m_Name); + } + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowTop5(int ClientID, int Offset) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTop5Thread, "show top5", ClientID, "", Offset); +} + +bool CScore::ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; + + // check sort method + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SELECT Name, Time, Rank " + "FROM (" + "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " + "FROM %s_race " + "WHERE Map = ? " + "GROUP BY Name " + "WINDOW w AS (ORDER BY Time)" + ") as a " + "ORDER BY Rank %s " + "LIMIT %d, 5;", + pSqlServer->GetPrefix(), + pOrder, + LimitStart); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + + // show top5 + strcpy(pData->m_pResult->m_Data.m_aaMessages[0], "----------- Top 5 -----------"); + + int Line = 1; + while(pSqlServer->Step()) + { + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(1, aName, sizeof(aName)); + float Time = pSqlServer->GetFloat(2); + int Rank = pSqlServer->GetInt(3); + str_format(pData->m_pResult->m_Data.m_aaMessages[Line], sizeof(pData->m_pResult->m_Data.m_aaMessages[Line]), + "%d. %s Time: %02d:%05.2f", + Rank, aName, (int)(Time/60), Time-((int)Time/60*60) + ); + Line++; + } + strcpy(pData->m_pResult->m_Data.m_aaMessages[Line], "-------------------------------"); + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowTeamTop5(int ClientID, int Offset) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTeamTop5Thread, "show team top5", ClientID, "", Offset); +} + +bool CScore::ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; + + // check sort method + char aBuf[512]; + + str_format(aBuf, sizeof(aBuf), + "SELECT Name, Time, Rank, TeamSize " + "FROM (" // limit to 5 + "SELECT TeamSize, Rank, ID " + "FROM (" // teamrank score board + "SELECT RANK() OVER w AS Rank, ID, COUNT(*) AS Teamsize " + "FROM %s_teamrace " + "WHERE Map = ? " + "GROUP BY Id " + "WINDOW w AS (ORDER BY Time)" + ") as l1 " + "ORDER BY Rank %s " + "LIMIT %d, 5" + ") as l2 " + "INNER JOIN %s_teamrace as r ON l2.ID = r.ID " + "ORDER BY Rank %s, r.ID, Name ASC;", + pSqlServer->GetPrefix(), pOrder, LimitStart, pSqlServer->GetPrefix(), pOrder); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + + // show teamtop5 + int Line = 0; + strcpy(paMessages[Line++], "------- Team Top 5 -------"); + + if(pSqlServer->Step()) + { + for(Line = 1; Line < 6; Line++) // print + { + bool Break = false; + float Time = pSqlServer->GetFloat(2); + int Rank = pSqlServer->GetInt(3); + int TeamSize = pSqlServer->GetInt(4); + + char aNames[2300] = { 0 }; + for(int i = 0; i < TeamSize; i++) + { + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(1, aName, sizeof(aName)); + str_append(aNames, aName, sizeof(aNames)); + if (i < TeamSize - 2) + str_append(aNames, ", ", sizeof(aNames)); + else if (i == TeamSize - 2) + str_append(aNames, " & ", sizeof(aNames)); + if(!pSqlServer->Step()) + { + Break = true; + break; + } + } + str_format(paMessages[Line], sizeof(paMessages[Line]), "%d. %s Team Time: %02d:%05.2f", + Rank, aNames, (int)(Time/60), Time-((int)Time/60*60)); + if(Break) + break; + } + } + + strcpy(paMessages[Line], "-------------------------------"); + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowTimes(int ClientID, int Offset) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTimesThread, "show times", ClientID, "", Offset); +} + +void CScore::ShowTimes(int ClientID, const char* pName, int Offset) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTimesThread, "show times", ClientID, pName, Offset); +} + +bool CScore::ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "DESC" : "ASC"; + + char aBuf[512]; + + if(pData->m_Name[0] != '\0') // last 5 times of a player + { + str_format(aBuf, sizeof(aBuf), + "SELECT Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp " + "FROM %s_race " + "WHERE Map = ? AND Name = ? " + "ORDER BY Timestamp %s " + "LIMIT ?, 5;", + pSqlServer->GetPrefix(), pOrder); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pData->m_Name); + pSqlServer->BindInt(3, LimitStart); + } + else // last 5 times of server + { + str_format(aBuf, sizeof(aBuf), + "SELECT Time, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, " + "UNIX_TIMESTAMP(Timestamp) as Stamp, " + "Name " + "FROM %s_race " + "WHERE Map = ? " + "ORDER BY Timestamp %s " + "LIMIT ?, 5;", + pSqlServer->GetPrefix(), pOrder); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindInt(2, LimitStart); + } + + // show top5 + if(!pSqlServer->Step()) + { + strcpy(paMessages[0], "There are no times in the specified range"); + pData->m_pResult->m_Done = true; + return true; + } + + strcpy(paMessages[0], "------------- Last Times -------------"); + int Line = 1; + + do + { + float Time = pSqlServer->GetFloat(1); + int Ago = pSqlServer->GetInt(2); + int Stamp = pSqlServer->GetInt(3); + + char aAgoString[40] = "\0"; + sqlstr::AgoTimeToString(Ago, aAgoString); + + if(pData->m_Name[0] != '\0') // last 5 times of a player + { + if(Stamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet + str_format(paMessages[Line], sizeof(paMessages[Line]), + "%02d:%05.02f, don't know how long ago", + (int)(Time/60), Time-((int)Time/60*60)); + else + str_format(paMessages[Line], sizeof(paMessages[Line]), + "%s ago, %02d:%05.02f", + aAgoString, (int)(Time/60), Time-((int)Time/60*60)); + } + else // last 5 times of the server + { + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(4, aName, sizeof(aName)); + if(Stamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet + { + str_format(paMessages[Line], sizeof(paMessages[Line]), + "%s, %02d:%05.02f, don't know when", + aName, (int)(Time/60), Time-((int)Time/60*60)); + } + else + { + str_format(paMessages[Line], sizeof(paMessages[Line]), + "%s, %s ago, %02d:%05.02f", + aName, aAgoString, (int)(Time/60), Time-((int)Time/60*60)); + } + } + Line++; + } while(pSqlServer->Step()); + strcpy(paMessages[Line], "----------------------------------------------------"); + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowPoints(int ClientID, const char* pName) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowPointsThread, "show points", ClientID, pName, 0); +} + +bool CScore::ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Points, Name " + "FROM (" + "SELECT RANK() OVER w AS Rank, Points, Name " + "FROM %s_points " + "WINDOW w as (ORDER BY Points DESC)" + ") as a " + "WHERE Name = ?;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Name); + + if(pSqlServer->Step()) + { + int Rank = pSqlServer->GetInt(1); + int Count = pSqlServer->GetInt(2); + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(3, aName, sizeof(aName)); + pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; + str_format(paMessages[0], sizeof(paMessages[0]), + "%d. %s Points: %d, requested by %s", + Rank, aName, Count, pData->m_RequestingPlayer); + } + else + { + str_format(paMessages[0], sizeof(paMessages[0]), + "%s has not collected any points so far", pData->m_Name); + } + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::ShowTopPoints(int ClientID, int Offset) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(ShowTopPointsThread, "show top points", ClientID, "", Offset); +} + +bool CScore::ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Points, Name " + "FROM (" + "SELECT RANK() OVER w AS Rank, Points, Name " + "FROM %s_points " + "WINDOW w as (ORDER BY Points DESC)" + ") as a " + "ORDER BY Rank %s " + "LIMIT ?, 5;", + pSqlServer->GetPrefix(), pOrder); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindInt(1, LimitStart); + + // show top points + strcpy(paMessages[0], "-------- Top Points --------"); + + int Line = 1; + while(pSqlServer->Step()) + { + int Rank = pSqlServer->GetInt(1); + int Points = pSqlServer->GetInt(2); + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(3, aName, sizeof(aName)); + str_format(paMessages[Line], sizeof(paMessages[Line]), + "%d. %s Points: %d", Rank, aName, Points); + Line++; + } + strcpy(paMessages[Line], "-------------------------------"); + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::RandomMap(int ClientID, int Stars) +{ + auto pResult = std::make_shared(ClientID); + GameServer()->m_SqlRandomMapResult = pResult; + + auto Tmp = std::unique_ptr(new CSqlRandomMapRequest(pResult)); + Tmp->m_Stars = Stars; + str_copy(Tmp->m_CurrentMap, g_Config.m_SvMap, sizeof(Tmp->m_CurrentMap)); + str_copy(Tmp->m_ServerType, g_Config.m_SvServerType, sizeof(Tmp->m_ServerType)); + str_copy(Tmp->m_RequestingPlayer, GameServer()->Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer)); + + m_pPool->Execute(RandomMapThread, std::move(Tmp), "random map"); +} + +bool CScore::RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); + + char aBuf[512]; + if(0 <= pData->m_Stars && pData->m_Stars <= 5) + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map FROM %s_maps " + "WHERE Server = ? AND Map != ? AND Stars = ? " + "ORDER BY RAND() LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindInt(3, pData->m_Stars); + } + else + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map FROM %s_maps " + "WHERE Server = ? AND Map != ? " + "ORDER BY RAND() LIMIT 1;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + } + pSqlServer->BindString(1, pData->m_ServerType); + pSqlServer->BindString(2, pData->m_CurrentMap); + + if(pSqlServer->Step()) + { + pSqlServer->GetString(1, pData->m_pResult->m_Map, sizeof(pData->m_pResult->m_Map)); + } + else + { + str_copy(pData->m_pResult->m_aMessage, "No maps found on this server!", sizeof(pData->m_pResult->m_aMessage)); + } + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::RandomUnfinishedMap(int ClientID, int Stars) +{ + auto pResult = std::make_shared(ClientID); + GameServer()->m_SqlRandomMapResult = pResult; + + auto Tmp = std::unique_ptr(new CSqlRandomMapRequest(pResult)); + Tmp->m_Stars = Stars; + str_copy(Tmp->m_CurrentMap, g_Config.m_SvMap, sizeof(Tmp->m_CurrentMap)); + str_copy(Tmp->m_ServerType, g_Config.m_SvServerType, sizeof(Tmp->m_ServerType)); + str_copy(Tmp->m_RequestingPlayer, GameServer()->Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer)); + + m_pPool->Execute(RandomUnfinishedMapThread, std::move(Tmp), "random unfinished map"); +} + +bool CScore::RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); + + char aBuf[512]; + if(pData->m_Stars >= 0) + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map " + "FROM %s_maps " + "WHERE Server = ? AND Map != ? AND Stars = ? AND Map NOT IN (" + "SELECT Map " + "FROM %s_race " + "WHERE Name = ?" + ") ORDER BY RAND() " + "LIMIT 1;", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_ServerType); + pSqlServer->BindString(2, pData->m_CurrentMap); + pSqlServer->BindInt(3, pData->m_Stars); + pSqlServer->BindString(4, pData->m_RequestingPlayer); + } + else + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map " + "FROM %s_maps AS maps " + "WHERE Server = ? AND Map != ? AND Map NOT IN (" + "SELECT Map " + "FROM %s_race as race " + "WHERE Name = ?" + ") ORDER BY RAND() " + "LIMIT 1;", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_ServerType); + pSqlServer->BindString(2, pData->m_CurrentMap); + pSqlServer->BindString(3, pData->m_RequestingPlayer); + } + + if(pSqlServer->Step()) + { + pSqlServer->GetString(1, pData->m_pResult->m_Map, sizeof(pData->m_pResult->m_Map)); + } + else + { + str_copy(pData->m_pResult->m_aMessage, "You have no more unfinished maps on this server!", sizeof(pData->m_pResult->m_aMessage)); + } + + pData->m_pResult->m_Done = true; + return true; +} + +void CScore::SaveTeam(int ClientID, const char* Code, const char* Server) +{ + if(RateLimitPlayer(ClientID)) + return; + auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); + int Team = pController->m_Teams.m_Core.Team(ClientID); + if(pController->m_Teams.GetSaving(Team)) + return; + + auto SaveResult = std::make_shared(ClientID, pController); + int Result = SaveResult->m_SavedTeam.save(Team); + if(CSaveTeam::HandleSaveError(Result, ClientID, GameServer())) + return; + pController->m_Teams.SetSaving(Team, SaveResult); + + auto Tmp = std::unique_ptr(new CSqlTeamSave(SaveResult)); + str_copy(Tmp->m_Code, Code, sizeof(Tmp->m_Code)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + Tmp->m_pResult->m_SaveID = RandomUuid(); + str_copy(Tmp->m_Server, Server, sizeof(Tmp->m_Server)); + str_copy(Tmp->m_ClientName, this->Server()->ClientName(ClientID), sizeof(Tmp->m_ClientName)); + Tmp->m_aGeneratedCode[0] = '\0'; + GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode)); + + pController->m_Teams.KillSavedTeam(ClientID, Team); + m_pPool->ExecuteWrite(SaveTeamThread, std::move(Tmp), "save team"); +} + +bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +{ + const CSqlTeamSave *pData = dynamic_cast(pGameData); + + char aSaveID[UUID_MAXSTRSIZE]; + FormatUuid(pData->m_pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE); + + char *pSaveState = pData->m_pResult->m_SavedTeam.GetString(); + char aBuf[65536]; + + char aTable[512]; + str_format(aTable, sizeof(aTable), "%s_saves", pSqlServer->GetPrefix()); + pSqlServer->Lock(aTable); + + char Code[128] = {0}; + str_format(aBuf, sizeof(aBuf), "SELECT Savegame FROM %s_saves WHERE Code = ? AND Map = ?", pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + bool UseCode = false; + if(pData->m_Code[0] != '\0' && !Failure) + { + pSqlServer->BindString(1, pData->m_Code); + pSqlServer->BindString(2, pData->m_Map); + // only allow saving when save code does not already exist + if(!pSqlServer->Step()) + { + UseCode = true; + str_copy(Code, pData->m_Code, sizeof(Code)); + } + } + if(!UseCode) + { + // use random generated passphrase if save code exists or no save code given + pSqlServer->BindString(1, pData->m_aGeneratedCode); + pSqlServer->BindString(2, pData->m_Map); + if(!pSqlServer->Step()) + { + UseCode = true; + str_copy(Code, pData->m_aGeneratedCode, sizeof(Code)); + } + } + + if(UseCode) + { + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " + "VALUES (?, ?, ?, CURRENT_TIMESTAMP(), ?, ?, false)", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pSaveState); + pSqlServer->BindString(2, pData->m_Map); + pSqlServer->BindString(3, Code); + pSqlServer->BindString(4, pData->m_Server); + pSqlServer->BindString(5, aSaveID); + pSqlServer->Step(); + + if(!Failure) + { + if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. Use '/load %s' to continue", + pData->m_ClientName, Code); + } + else + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. Use '/load %s' on %s to continue", + pData->m_ClientName, Code, pData->m_Server); + } + } + else + { + strcpy(pData->m_pResult->m_aBroadcast, + "Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days."); + if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' to continue", + pData->m_ClientName, Code); + } + else + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' on %s to continue", + pData->m_ClientName, Code, pData->m_Server); + } + } + + pData->m_pResult->m_Status = CScoreSaveResult::SAVE_SUCCESS; + } + else + { + dbg_msg("sql", "ERROR: This save-code already exists"); + pData->m_pResult->m_Status = CScoreSaveResult::SAVE_FAILED; + strcpy(pData->m_pResult->m_aMessage, "This save-code already exists"); + } + + pSqlServer->Unlock(); + return true; +} + +void CScore::LoadTeam(const char* Code, int ClientID) +{ + if(RateLimitPlayer(ClientID)) + return; + auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); + int Team = pController->m_Teams.m_Core.Team(ClientID); + if(pController->m_Teams.GetSaving(Team)) + return; + if(Team < TEAM_FLOCK || Team >= MAX_CLIENTS || (g_Config.m_SvTeam != 3 && Team == TEAM_FLOCK)) + { + GameServer()->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); + return; + } + if(pController->m_Teams.GetTeamState(Team) != CGameTeams::TEAMSTATE_OPEN) + { + GameServer()->SendChatTarget(ClientID, "Team can't be loaded while racing"); + return; + } + auto SaveResult = std::make_shared(ClientID, pController); + SaveResult->m_Status = CScoreSaveResult::LOAD_FAILED; + pController->m_Teams.SetSaving(Team, SaveResult); + auto Tmp = std::unique_ptr(new CSqlTeamLoad(SaveResult)); + str_copy(Tmp->m_Code, Code, sizeof(Tmp->m_Code)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + Tmp->m_ClientID = ClientID; + str_copy(Tmp->m_RequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer)); + Tmp->m_NumPlayer = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(pController->m_Teams.m_Core.Team(i) == Team) + { + // put all names at the beginning of the array + str_copy(Tmp->m_aClientNames[Tmp->m_NumPlayer], Server()->ClientName(i), sizeof(Tmp->m_aClientNames[Tmp->m_NumPlayer])); + Tmp->m_aClientID[Tmp->m_NumPlayer] = i; + Tmp->m_NumPlayer++; + } + } + m_pPool->Execute(LoadTeamThread, std::move(Tmp), "load team"); +} + +bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlTeamLoad *pData = dynamic_cast(pGameData); + pData->m_pResult->m_Status = CScoreSaveResult::LOAD_FAILED; + + char aTable[512]; + str_format(aTable, sizeof(aTable), "%s_saves", pSqlServer->GetPrefix()); + pSqlServer->Lock(aTable); + + { + char aSaveLike[128] = ""; + str_append(aSaveLike, "%\n", sizeof(aSaveLike)); + sqlstr::EscapeLike(aSaveLike + str_length(aSaveLike), + pData->m_RequestingPlayer, + sizeof(aSaveLike) - str_length(aSaveLike)); + str_append(aSaveLike, "\t%", sizeof(aSaveLike)); + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SELECT " + "Savegame, Server, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) AS Ago, " + "(UNHEX(REPLACE(SaveID, '-',''))) AS SaveID " + "FROM %s_saves " + "where Code = ? AND Map = ? AND DDNet7 = false AND Savegame LIKE ?;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Code); + pSqlServer->BindString(2, pData->m_Map); + pSqlServer->BindString(3, aSaveLike); + + if(!pSqlServer->Step()) + { + strcpy(pData->m_pResult->m_aMessage, "No such savegame for this map"); + goto end; + } + char aServerName[32]; + pSqlServer->GetString(2, aServerName, sizeof(aServerName)); + if(str_comp(aServerName, g_Config.m_SvSqlServerName) != 0) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "You have to be on the '%s' server to load this savegame", aServerName); + goto end; + } + + int Since = pSqlServer->GetInt(3); + if(Since < g_Config.m_SvSaveGamesDelay) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "You have to wait %d seconds until you can load this savegame", + g_Config.m_SvSaveGamesDelay - Since); + goto end; + } + if(pSqlServer->IsNull(4)) + { + memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData)); + } + else + { + if(pSqlServer->GetBlob(4, pData->m_pResult->m_SaveID.m_aData, sizeof(pData->m_pResult->m_SaveID.m_aData)) != 16) + { + strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: SaveID corrupted"); + goto end; + } + } + + char aSaveString[65536]; + pSqlServer->GetString(1, aSaveString, sizeof(aSaveString)); + int Num = pData->m_pResult->m_SavedTeam.LoadString(aSaveString); + + if(Num != 0) + { + strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: data corrupted"); + goto end; + } + + bool CanLoad = pData->m_pResult->m_SavedTeam.MatchPlayers( + pData->m_aClientNames, pData->m_aClientID, pData->m_NumPlayer, + pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage)); + + if(!CanLoad) + goto end; + + str_format(aBuf, sizeof(aBuf), + "DELETE FROM %s_saves " + "WHERE Code = ? AND Map = ?;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Code); + pSqlServer->BindString(2, pData->m_Map); + pSqlServer->Step(); + + pData->m_pResult->m_Status = CScoreSaveResult::LOAD_SUCCESS; + strcpy(pData->m_pResult->m_aMessage, "Loading successfully done"); + } +end: + pSqlServer->Unlock(); + return true; +} + +void CScore::GetSaves(int ClientID) +{ + if(RateLimitPlayer(ClientID)) + return; + ExecPlayerThread(GetSavesThread, "get saves", ClientID, "", 0); +} + +bool CScore::GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +{ + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_Data.m_aaMessages; + + char aSaveLike[128] = ""; + str_append(aSaveLike, "%\n", sizeof(aSaveLike)); + sqlstr::EscapeLike(aSaveLike + str_length(aSaveLike), + pData->m_RequestingPlayer, + sizeof(aSaveLike) - str_length(aSaveLike)); + str_append(aSaveLike, "\t%", sizeof(aSaveLike)); + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SELECT COUNT(*) AS NumSaves, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(MAX(Timestamp)) AS Ago " + "FROM %s_saves " + "WHERE Map = ? AND Savegame LIKE ?;", + pSqlServer->GetPrefix()); + pSqlServer->PrepareStatement(aBuf); + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, aSaveLike); + + if(pSqlServer->Step()) + { + int NumSaves = pSqlServer->GetInt(1); + int Ago = pSqlServer->GetInt(2); + char aAgoString[40] = "\0"; + char aLastSavedString[60] = "\0"; + if(Ago) + { + sqlstr::AgoTimeToString(Ago, aAgoString); + str_format(aLastSavedString, sizeof(aLastSavedString), ", last saved %s ago", aAgoString); + } + + str_format(paMessages[0], sizeof(paMessages[0]), + "%s has %d save%s on %s%s", + pData->m_RequestingPlayer, + NumSaves, NumSaves == 1 ? "" : "s", + pData->m_Map, aLastSavedString); + } + + pData->m_pResult->m_Done = true; + return true; +} diff --git a/src/game/server/score.h b/src/game/server/score.h index ae1b87087..0eab0cb40 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -4,10 +4,18 @@ #include #include -#include #include +#include +#include +#include + #include "save.h" +struct ISqlData; +class IDbConnection; +class IServer; +class CGameContext; + enum { NUM_CHECKPOINTS = 25, @@ -132,43 +140,193 @@ public: float m_aBestCpTime[NUM_CHECKPOINTS]; }; -class IScore +struct CSqlInitData : ISqlData +{ + CSqlInitData(std::shared_ptr pResult) : + m_pResult(pResult) + {} + std::shared_ptr m_pResult; + + // current map + char m_Map[MAX_MAP_LENGTH]; +}; + +struct CSqlPlayerRequest : ISqlData +{ + CSqlPlayerRequest(std::shared_ptr pResult) : + m_pResult(pResult) + {} + std::shared_ptr m_pResult; + // object being requested, either map (128 bytes) or player (16 bytes) + char m_Name[MAX_MAP_LENGTH]; + // current map + char m_Map[MAX_MAP_LENGTH]; + char m_RequestingPlayer[MAX_NAME_LENGTH]; + // relevant for /top5 kind of requests + int m_Offset; +}; + +struct CSqlRandomMapRequest : ISqlData +{ + CSqlRandomMapRequest(std::shared_ptr pResult) : + m_pResult(pResult) + {} + std::shared_ptr m_pResult; + + char m_ServerType[32]; + char m_CurrentMap[MAX_MAP_LENGTH]; + char m_RequestingPlayer[MAX_NAME_LENGTH]; + int m_Stars; +}; + +struct CSqlScoreData : ISqlData +{ + CSqlScoreData(std::shared_ptr pResult) : + m_pResult(pResult) + {} + virtual ~CSqlScoreData() {}; + + std::shared_ptr m_pResult; + + char m_Map[MAX_MAP_LENGTH]; + char m_GameUuid[UUID_MAXSTRSIZE]; + char m_Name[MAX_MAP_LENGTH]; + + int m_ClientID; + float m_Time; + char m_aTimestamp[TIMESTAMP_STR_LENGTH]; + float m_aCpCurrent[NUM_CHECKPOINTS]; + int m_Num; + bool m_Search; + char m_aRequestingPlayer[MAX_NAME_LENGTH]; +}; + +struct CSqlTeamScoreData : ISqlData +{ + char m_GameUuid[UUID_MAXSTRSIZE]; + char m_Map[MAX_MAP_LENGTH]; + float m_Time; + char m_aTimestamp[TIMESTAMP_STR_LENGTH]; + unsigned int m_Size; + char m_aNames[MAX_CLIENTS][MAX_NAME_LENGTH]; +}; + +struct CSqlTeamSave : ISqlData +{ + CSqlTeamSave(std::shared_ptr pResult) : + m_pResult(pResult) + {} + virtual ~CSqlTeamSave() {}; + + std::shared_ptr m_pResult; + + char m_ClientName[MAX_NAME_LENGTH]; + char m_Map[MAX_MAP_LENGTH]; + char m_Code[128]; + char m_aGeneratedCode[128]; + char m_Server[5]; +}; + +struct CSqlTeamLoad : ISqlData +{ + CSqlTeamLoad(std::shared_ptr pResult) : + m_pResult(pResult) + {} + virtual ~CSqlTeamLoad() {}; + + std::shared_ptr m_pResult; + + char m_Code[128]; + char m_Map[MAX_MAP_LENGTH]; + char m_RequestingPlayer[MAX_NAME_LENGTH]; + int m_ClientID; + // struct holding all player names in the team or an empty string + char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int m_aClientID[MAX_CLIENTS]; + int m_NumPlayer; +}; + +class CScore { CPlayerData m_aPlayerData[MAX_CLIENTS]; + CDbConnectionPool *m_pPool; + + static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData); + + static bool RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + + static bool LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + + static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); + static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + + static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); + static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); + + CGameContext *GameServer() const { return m_pGameServer; } + IServer *Server() const { return m_pServer; } + CGameContext *m_pGameServer; + IServer *m_pServer; + + std::vector m_aWordlist; + CPrng m_Prng; + void GeneratePassphrase(char *pBuf, int BufSize); + + // returns new SqlResult bound to the player, if no current Thread is active for this player + std::shared_ptr NewSqlPlayerResult(int ClientID); + // Creates for player database requests + void ExecPlayerThread( + bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + const char *pThreadName, + int ClientID, + const char *pName, + int Offset); + + // returns true if the player should be rate limited + bool RateLimitPlayer(int ClientID); public: - virtual ~IScore() {} + CScore(CGameContext *pGameServer, CDbConnectionPool *pPool); + ~CScore() {} CPlayerData *PlayerData(int ID) { return &m_aPlayerData[ID]; } - virtual void MapInfo(int ClientID, const char *pMapName) = 0; - virtual void MapVote(int ClientID, const char *pMapName) = 0; - virtual void LoadPlayerData(int ClientID) = 0; - virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0; + void MapInfo(int ClientID, const char *pMapName); + void MapVote(int ClientID, const char *pMapName); + void LoadPlayerData(int ClientID); + void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible); - virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0; + void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp); - virtual void ShowTop5(int ClientID, int Offset=1) = 0; - virtual void ShowRank(int ClientID, const char *pName) = 0; + void ShowTop5(int ClientID, int Offset=1); + void ShowRank(int ClientID, const char *pName); - virtual void ShowTeamTop5(int ClientID, int Offset=1) = 0; - virtual void ShowTeamRank(int ClientID, const char *pName) = 0; + void ShowTeamTop5(int ClientID, int Offset=1); + void ShowTeamRank(int ClientID, const char *pName); - virtual void ShowTopPoints(int ClientID, int Offset=1) = 0; - virtual void ShowPoints(int ClientID, const char *pName) = 0; + void ShowTopPoints(int ClientID, int Offset=1); + void ShowPoints(int ClientID, const char *pName); - virtual void ShowTimes(int ClientID, const char *pName, int Offset = 1) = 0; - virtual void ShowTimes(int ClientID, int Offset = 1) = 0; + void ShowTimes(int ClientID, const char *pName, int Offset = 1); + void ShowTimes(int ClientID, int Offset = 1); - virtual void RandomMap(int ClientID, int Stars) = 0; - virtual void RandomUnfinishedMap(int ClientID, int Stars) = 0; + void RandomMap(int ClientID, int Stars); + void RandomUnfinishedMap(int ClientID, int Stars); - virtual void SaveTeam(int ClientID, const char *pCode, const char *pServer) = 0; - virtual void LoadTeam(const char *pCode, int ClientID) = 0; - virtual void GetSaves(int ClientID) = 0; - - // called when the server is shut down but not on mapchange/reload - virtual void OnShutdown() = 0; + void SaveTeam(int ClientID, const char *pCode, const char *pServer); + void LoadTeam(const char *pCode, int ClientID); + void GetSaves(int ClientID); }; #endif // GAME_SERVER_SCORE_H diff --git a/src/game/server/score/file_score.cpp b/src/game/server/score/file_score.cpp deleted file mode 100644 index 7518ae50c..000000000 --- a/src/game/server/score/file_score.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ -/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ -/* copyright (c) 2008 rajh and gregwar. Score stuff */ -#include - -#include -#include -#include -#include "../gamemodes/DDRace.h" -#include "file_score.h" -#include - -static LOCK gs_ScoreLock = 0; - -CFileScore::CPlayerScore::CPlayerScore(const char *pName, float Score, - float aCpTime[NUM_CHECKPOINTS]) -{ - str_copy(m_aName, pName, sizeof(m_aName)); - m_Score = Score; - for (int i = 0; i < NUM_CHECKPOINTS; i++) - m_aCpTime[i] = aCpTime[i]; -} - -CFileScore::CFileScore(CGameContext *pGameServer) : - m_pGameServer(pGameServer), m_pServer(pGameServer->Server()) -{ - if (gs_ScoreLock == 0) - gs_ScoreLock = lock_create(); - - Init(); -} - -CFileScore::~CFileScore() -{ - lock_wait(gs_ScoreLock); - - // clear list - m_Top.clear(); - - lock_unlock(gs_ScoreLock); -} - -std::string CFileScore::SaveFile() -{ - std::ostringstream oss; - char aBuf[256]; - str_copy(aBuf, Server()->GetMapName(), sizeof(aBuf)); - for(int i = 0; i < 256; i++) if(aBuf[i] == '/') aBuf[i] = '-'; - if (g_Config.m_SvScoreFolder[0]) - oss << g_Config.m_SvScoreFolder << "/" << aBuf << "_record.dtb"; - else - oss << Server()->GetMapName() << "_record.dtb"; - return oss.str(); -} - -void CFileScore::MapInfo(int ClientID, const char* MapName) -{ - // TODO: implement -} - -void CFileScore::MapVote(int ClientID, const char* MapName) -{ - // TODO: implement -} - -void CFileScore::SaveScoreThread(void *pUser) -{ - CFileScore *pSelf = (CFileScore *) pUser; - lock_wait(gs_ScoreLock); - std::fstream f; - f.open(pSelf->SaveFile().c_str(), std::ios::out); - if(f.fail()) - { - dbg_msg("filescore", "opening '%s' for writing failed", pSelf->SaveFile().c_str()); - } - else - { - int t = 0; - for (sorted_array::range r = pSelf->m_Top.all(); - !r.empty(); r.pop_front()) - { - f << r.front().m_aName << std::endl << r.front().m_Score - << std::endl; - if (g_Config.m_SvCheckpointSave) - { - for (int c = 0; c < NUM_CHECKPOINTS; c++) - f << r.front().m_aCpTime[c] << " "; - f << std::endl; - } - t++; - if (t % 50 == 0) - thread_sleep(1000); - } - } - f.close(); - lock_unlock(gs_ScoreLock); -} - -void CFileScore::Save() -{ - thread_init_and_detach(SaveScoreThread, this, "FileScore save"); -} - -void CFileScore::Init() -{ - lock_wait(gs_ScoreLock); - - // create folder if not exist - if (g_Config.m_SvScoreFolder[0]) - fs_makedir(g_Config.m_SvScoreFolder); - - std::fstream f; - f.open(SaveFile().c_str(), std::ios::in); - - if(f.fail()) - { - dbg_msg("filescore", "opening '%s' for reading failed", SaveFile().c_str()); - } - while (!f.eof() && !f.fail()) - { - std::string TmpName, TmpScore, TmpCpLine; - std::getline(f, TmpName); - if (!f.eof() && TmpName != "") - { - std::getline(f, TmpScore); - float aTmpCpTime[NUM_CHECKPOINTS] = - { 0 }; - if (g_Config.m_SvCheckpointSave) - { - std::getline(f, TmpCpLine); - - std::istringstream iss(TmpCpLine); - int i = 0; - for(std::string p; std::getline(iss, p, ' '); i++) - aTmpCpTime[i] = std::stof(p, NULL); - } - m_Top.add( - *new CPlayerScore(TmpName.c_str(), atof(TmpScore.c_str()), - aTmpCpTime)); - } - } - f.close(); - lock_unlock(gs_ScoreLock); - - // save the current best score - if (m_Top.size()) - ((CGameControllerDDRace*) GameServer()->m_pController)->m_CurrentRecord = - m_Top[0].m_Score; -} - -CFileScore::CPlayerScore *CFileScore::SearchName(const char *pName, - int *pPosition, bool NoCase) -{ - CPlayerScore *pPlayer = 0; - int Pos = 1; - int Found = 0; - for (sorted_array::range r = m_Top.all(); !r.empty(); - r.pop_front()) - { - if (str_find_nocase(r.front().m_aName, pName)) - { - if (pPosition) - *pPosition = Pos; - if (NoCase) - { - Found++; - pPlayer = &r.front(); - } - if (!str_comp(r.front().m_aName, pName)) - return &r.front(); - } - Pos++; - } - if (Found > 1) - { - if (pPosition) - *pPosition = -1; - return 0; - } - return pPlayer; -} - -void CFileScore::UpdatePlayer(int ID, float Score, - float aCpTime[NUM_CHECKPOINTS]) -{ - const char *pName = Server()->ClientName(ID); - - lock_wait(gs_ScoreLock); - CPlayerScore *pPlayer = SearchScore(ID, 0); - - if (pPlayer) - { - for (int c = 0; c < NUM_CHECKPOINTS; c++) - pPlayer->m_aCpTime[c] = aCpTime[c]; - - pPlayer->m_Score = Score; - str_copy(pPlayer->m_aName, pName, sizeof(pPlayer->m_aName)); - - sort(m_Top.all()); - } - else - m_Top.add(*new CPlayerScore(pName, Score, aCpTime)); - - lock_unlock(gs_ScoreLock); - Save(); -} - -void CFileScore::LoadPlayerData(int ClientID) -{ - CPlayerScore *pPlayer = SearchScore(ClientID, 0); - if (pPlayer) - { - lock_wait(gs_ScoreLock); - lock_unlock(gs_ScoreLock); - Save(); - } - - // set score - if (pPlayer) - { - PlayerData(ClientID)->Set(pPlayer->m_Score, pPlayer->m_aCpTime); - GameServer()->m_apPlayers[ClientID]->m_HasFinishScore = true; - } -} - -void CFileScore::SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp) -{ - dbg_msg("filescore", "saveteamscore not implemented for filescore"); -} - -void CFileScore::SaveScore(int ClientID, float Time, const char *pTimestamp, - float CpTime[NUM_CHECKPOINTS], bool NotEligible) -{ - CConsole* pCon = (CConsole*) GameServer()->Console(); - if (!pCon->m_Cheated || g_Config.m_SvRankCheats) - UpdatePlayer(ClientID, Time, CpTime); -} - -void CFileScore::ShowTop5(int ClientID, int Offset) -{ - char aBuf[512]; - Offset = maximum(1, Offset < 0 ? m_Top.size() + Offset - 3 : Offset); - GameServer()->SendChatTarget(ClientID, "----------- Top 5 -----------"); - for (int i = 0; i < 5; i++) - { - if (i + Offset > m_Top.size()) - break; - CPlayerScore *r = &m_Top[i + Offset - 1]; - str_format(aBuf, sizeof(aBuf), - "%d. %s Time: %d minute(s) %5.2f second(s)", i + Offset, - r->m_aName, (int)r->m_Score / 60, - r->m_Score - ((int)r->m_Score / 60 * 60)); - GameServer()->SendChatTarget(ClientID, aBuf); - } - GameServer()->SendChatTarget(ClientID, "------------------------------"); -} - -void CFileScore::ShowRank(int ClientID, const char* pName) -{ - CPlayerScore *pScore; - int Pos = -2; - char aBuf[512]; - - pScore = SearchName(pName, &Pos, 1); - - if (pScore && Pos > -1) - { - float Time = pScore->m_Score; - if (g_Config.m_SvHideScore) - str_format(aBuf, sizeof(aBuf), - "Your time: %d minute(s) %5.2f second(s)", (int)Time / 60, - Time - ((int)Time / 60 * 60)); - else - str_format(aBuf, sizeof(aBuf), - "%d. %s Time: %d minute(s) %5.2f second(s), requested by (%s)", Pos, - pScore->m_aName, (int)Time / 60, - Time - ((int)Time / 60 * 60), Server()->ClientName(ClientID)); - if (g_Config.m_SvHideScore) - GameServer()->SendChatTarget(ClientID, aBuf); - else - GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID); - return; - } - else if (Pos == -1) - str_format(aBuf, sizeof(aBuf), "Several players were found."); - else - str_format(aBuf, sizeof(aBuf), "%s is not ranked", pName); - - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowTeamTop5(int ClientID, int Offset) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowTeamRank(int ClientID, const char* pName) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowTopPoints(int ClientID, int Offset) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowPoints(int ClientID, const char* pName) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Points not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowTimes(int ClientID, const char *pName, int Offset) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Show times not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::ShowTimes(int ClientID, int Offset) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Show times not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::RandomMap(int ClientID, int Stars) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Random map not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::RandomUnfinishedMap(int ClientID, int Stars) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Random unfinished map not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::SaveTeam(int ClientID, const char* Code, const char* Server) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::LoadTeam(const char* Code, int ClientID) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::GetSaves(int ClientID) -{ - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers"); - GameServer()->SendChatTarget(ClientID, aBuf); -} - -void CFileScore::OnShutdown() -{ - ; -} diff --git a/src/game/server/score/file_score.h b/src/game/server/score/file_score.h deleted file mode 100644 index 429cc37b6..000000000 --- a/src/game/server/score/file_score.h +++ /dev/null @@ -1,92 +0,0 @@ -/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ -/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ -/* copyright (c) 2008 rajh and gregwar. Score stuff */ -#ifndef GAME_SERVER_SCORE_FILE_SCORE_H -#define GAME_SERVER_SCORE_FILE_SCORE_H - -#include - -#include -#include "../score.h" - -class CFileScore: public IScore -{ - CGameContext *m_pGameServer; - IServer *m_pServer; - - class CPlayerScore - { - public: - char m_aName[MAX_NAME_LENGTH]; - float m_Score; - float m_aCpTime[NUM_CHECKPOINTS]; - - CPlayerScore() {} - - CPlayerScore(const char *pName, float Score, - float aCpTime[NUM_CHECKPOINTS]); - - bool operator<(const CPlayerScore& other) - { - return (this->m_Score < other.m_Score); - } - }; - - sorted_array m_Top; - - CGameContext *GameServer() - { - return m_pGameServer; - } - IServer *Server() - { - return m_pServer; - } - - CPlayerScore *SearchScore(int ID, int *pPosition) - { - return SearchName(Server()->ClientName(ID), pPosition, 0); - } - - CPlayerScore *SearchName(const char *pName, int *pPosition, bool MatchCase); - void UpdatePlayer(int ID, float Score, float aCpTime[NUM_CHECKPOINTS]); - - void Init(); - void Save(); - static void SaveScoreThread(void *pUser); - -public: - - CFileScore(CGameContext *pGameServer); - ~CFileScore(); - - virtual void LoadPlayerData(int ClientID); - virtual void MapInfo(int ClientID, const char* MapName); - virtual void MapVote(int ClientID, const char* MapName); - virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, - float CpTime[NUM_CHECKPOINTS], bool NotEligible); - virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp); - - virtual void ShowTop5(int ClientID, int Offset = 1); - virtual void ShowRank(int ClientID, const char* pName); - - virtual void ShowTeamTop5(int ClientID, int Offset = 1); - virtual void ShowTeamRank(int ClientID, const char* pName); - - virtual void ShowTopPoints(int ClientID, int Offset); - virtual void ShowPoints(int ClientID, const char* pName); - virtual void ShowTimes(int ClientID, const char *pName, int Offset = 1); - virtual void ShowTimes(int ClientID, int Offset = 1); - virtual void RandomMap(int ClientID, int Stars); - virtual void RandomUnfinishedMap(int ClientID, int Stars); - virtual void SaveTeam(int ClientID, const char* Code, const char* Server); - virtual void LoadTeam(const char* Code, int ClientID); - virtual void GetSaves(int ClientID); - - virtual void OnShutdown(); - -private: - std::string SaveFile(); -}; - -#endif // GAME_SERVER_SCORE_FILE_SCORE_H diff --git a/src/game/server/score/sql_score.cpp b/src/game/server/score/sql_score.cpp deleted file mode 100644 index 02e64d497..000000000 --- a/src/game/server/score/sql_score.cpp +++ /dev/null @@ -1,1886 +0,0 @@ -/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ -/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ -/* CSqlScore class by Sushi */ -#if defined(CONF_SQL) -#include "sql_score.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../entities/character.h" -#include "../gamemodes/DDRace.h" -#include "../save.h" - -std::atomic_int CSqlScore::ms_InstanceCount(0); - -template < typename TResult > -CSqlExecData::CSqlExecData( - bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), - CSqlData *pSqlResult, - bool ReadOnly -) : - m_pFuncPtr(pFuncPtr), - m_pSqlData(pSqlResult), - m_ReadOnly(ReadOnly) -{ - ++CSqlScore::ms_InstanceCount; -} - -template < typename TResult > -CSqlExecData::~CSqlExecData() -{ - --CSqlScore::ms_InstanceCount; -} - -std::shared_ptr CSqlScore::NewSqlPlayerResult(int ClientID) -{ - CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; - if(pCurPlayer->m_ScoreQueryResult != nullptr) // TODO: send player a message: "too many requests" - return nullptr; - pCurPlayer->m_ScoreQueryResult = std::make_shared(); - return pCurPlayer->m_ScoreQueryResult; -} - -void CSqlScore::ExecPlayerThread( - bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), - const char* pThreadName, - int ClientID, - const char* pName, - int Offset -) { - auto pResult = NewSqlPlayerResult(ClientID); - if(pResult == nullptr) - return; - CSqlPlayerRequest *Tmp = new CSqlPlayerRequest(pResult); - Tmp->m_Name = pName; - Tmp->m_Map = g_Config.m_SvMap; - Tmp->m_RequestingPlayer = Server()->ClientName(ClientID); - Tmp->m_Offset = Offset; - - thread_init_and_detach(CSqlExecData::ExecSqlFunc, - new CSqlExecData(pFuncPtr, Tmp), - pThreadName); -} - -bool CSqlScore::RateLimitPlayer(int ClientID) -{ - CPlayer *pPlayer = GameServer()->m_apPlayers[ClientID]; - if(pPlayer == 0) - return true; - if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * Server()->TickSpeed() >= Server()->Tick()) - return true; - pPlayer->m_LastSQLQuery = Server()->Tick(); - return false; -} - -void CSqlScore::GeneratePassphrase(char *pBuf, int BufSize) -{ - for(int i = 0; i < 3; i++) - { - if(i != 0) - str_append(pBuf, " ", BufSize); - // TODO: decide if the slight bias towards lower numbers is ok - int Rand = m_Prng.RandomBits() % m_aWordlist.size(); - str_append(pBuf, m_aWordlist[Rand].c_str(), BufSize); - } -} - -LOCK CSqlScore::ms_FailureFileLock = lock_create(); - -void CSqlScore::OnShutdown() -{ - int i = 0; - while (CSqlScore::ms_InstanceCount != 0) - { - if (i > 600) { - dbg_msg("sql", "Waited 60 seconds for score-threads to complete, quitting anyway"); - break; - } - - // print a log about every two seconds - if (i % 20 == 0) - dbg_msg("sql", "Waiting for score-threads to complete (%d left)", CSqlScore::ms_InstanceCount.load()); - ++i; - thread_sleep(100000); - } - - lock_destroy(ms_FailureFileLock); -} - -template < typename TResult > -void CSqlExecData::ExecSqlFunc(void *pUser) -{ - CSqlExecData* pData = (CSqlExecData *)pUser; - - CSqlConnector connector; - - bool Success = false; - - try { - // try to connect to a working database server - while (!Success && !connector.MaxTriesReached(pData->m_ReadOnly) && connector.ConnectSqlServer(pData->m_ReadOnly)) - { - try { - if (pData->m_pFuncPtr(connector.SqlServer(), pData->m_pSqlData, false)) - Success = true; - } catch (...) { - dbg_msg("sql", "Unexpected exception caught"); - } - - // disconnect from database server - connector.SqlServer()->Disconnect(); - } - - // handle failures - // eg write inserts to a file and print a nice error message - if (!Success) - pData->m_pFuncPtr(0, pData->m_pSqlData, true); - } catch (...) { - dbg_msg("sql", "Unexpected exception caught"); - } - - delete pData->m_pSqlData; - delete pData; -} - -CSqlScore::CSqlScore(CGameContext *pGameServer) : - m_pGameServer(pGameServer), - m_pServer(pGameServer->Server()) -{ - CSqlConnector::ResetReachable(); - - auto InitResult = std::make_shared(); - CSqlInitData *Tmp = new CSqlInitData(InitResult); - ((CGameControllerDDRace*)(pGameServer->m_pController))->m_pInitResult = InitResult; - Tmp->m_Map = g_Config.m_SvMap; - - IOHANDLE File = GameServer()->Storage()->OpenFile("wordlist.txt", IOFLAG_READ, IStorage::TYPE_ALL); - if(!File) - { - dbg_msg("sql", "failed to open wordlist"); - Server()->SetErrorShutdown("sql open wordlist error"); - return; - } - - uint64 aSeed[2]; - secure_random_fill(aSeed, sizeof(aSeed)); - m_Prng.Seed(aSeed); - CLineReader LineReader; - LineReader.Init(File); - char *pLine; - while((pLine = LineReader.Get())) - { - char Word[32] = {0}; - sscanf(pLine, "%*s %31s", Word); - Word[31] = 0; - m_aWordlist.push_back(Word); - } - if(m_aWordlist.size() < 1000) - { - dbg_msg("sql", "too few words in wordlist"); - Server()->SetErrorShutdown("sql too few words in wordlist"); - return; - } - thread_init_and_detach(CSqlExecData::ExecSqlFunc, - new CSqlExecData(Init, Tmp), - "SqlScore constructor"); -} - -bool CSqlScore::Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlInitData *pData = dynamic_cast(pGameData); - - if (HandleFailure) - { - dbg_msg("sql", "FATAL ERROR: Could not init SqlScore"); - return true; - } - - try - { - char aBuf[512]; - // get the best time - str_format(aBuf, sizeof(aBuf), - "SELECT Time FROM %s_race WHERE Map='%s' ORDER BY `Time` ASC LIMIT 1;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->next()) - pData->m_pResult->m_CurrentRecord = (float)pSqlServer->GetResults()->getDouble("Time"); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Getting best time on server done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - } - return false; -} - -void CSqlScore::LoadPlayerData(int ClientID) -{ - ExecPlayerThread(LoadPlayerDataThread, "load player data", ClientID, "", 0); -} - -// update stuff -bool CSqlScore::LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - pData->m_pResult->SetVariant(CScorePlayerResult::PLAYER_INFO); - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - // get best race time - str_format(aBuf, sizeof(aBuf), - "SELECT * " - "FROM %s_race " - "WHERE Map='%s' AND Name='%s' " - "ORDER BY Time ASC " - "LIMIT 1;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_RequestingPlayer.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - if(pSqlServer->GetResults()->next()) - { - // get the best time - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - pData->m_pResult->m_Data.m_Info.m_Time = Time; - pData->m_pResult->m_Data.m_Info.m_Score = -Time; - pData->m_pResult->m_Data.m_Info.m_HasFinishScore = true; - - char aColumn[8]; - if(g_Config.m_SvCheckpointSave) - { - for(int i = 0; i < NUM_CHECKPOINTS; i++) - { - str_format(aColumn, sizeof(aColumn), "cp%d", i+1); - pData->m_pResult->m_Data.m_Info.m_CpTime[i] = (float)pSqlServer->GetResults()->getDouble(aColumn); - } - } - } - - // birthday check - str_format(aBuf, sizeof(aBuf), - "SELECT YEAR(Current) - YEAR(Stamp) AS YearsAgo " - "FROM (" - "SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp " - "FROM %s_race " - "WHERE Name='%s'" - ") AS l " - "WHERE DAYOFMONTH(Current) = DAYOFMONTH(Stamp) AND MONTH(Current) = MONTH(Stamp) " - "AND YEAR(Current) > YEAR(Stamp);", - pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->next()) - { - int YearsAgo = pSqlServer->GetResults()->getInt("YearsAgo"); - pData->m_pResult->m_Data.m_Info.m_Birthday = YearsAgo; - } - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Finished loading player data"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not update account"); - } - return false; -} - -void CSqlScore::MapVote(int ClientID, const char* MapName) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(MapVoteThread, "map vote", ClientID, MapName, 0); -} - -bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if (HandleFailure) - return true; - - try - { - char aFuzzyMap[128]; - str_copy(aFuzzyMap, pData->m_Name.Str(), sizeof(aFuzzyMap)); - sqlstr::ClearString(aFuzzyMap, sizeof(aFuzzyMap)); - sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); - - char aBuf[768]; - str_format(aBuf, sizeof(aBuf), - "SELECT Map, Server " - "FROM %s_maps " - "WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci " - "ORDER BY " - "CASE WHEN Map = '%s' THEN 0 ELSE 1 END, " - "CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, " - "LENGTH(Map), Map " - "LIMIT 1;", - pSqlServer->GetPrefix(), aFuzzyMap, - pData->m_Name.ClrStr(), pData->m_Name.ClrStr() - ); - pSqlServer->executeSqlQuery(aBuf); - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_format(paMessages[0], sizeof(paMessages[0]), - "No map like \"%s\" found. " - "Try adding a '%%' at the start if you don't know the first character. " - "Example: /map %%castle for \"Out of Castle\"", - pData->m_Name.Str()); - } - else - { - pSqlServer->GetResults()->first(); - auto Server = pSqlServer->GetResults()->getString("Server"); - auto Map = pSqlServer->GetResults()->getString("Map"); - pData->m_pResult->SetVariant(CScorePlayerResult::MAP_VOTE); - auto MapVote = &pData->m_pResult->m_Data.m_MapVote; - strcpy(MapVote->m_Reason, "/map"); - str_copy(MapVote->m_Server, Server.c_str(), sizeof(MapVote->m_Server)); - str_copy(MapVote->m_Map, Map.c_str(), sizeof(MapVote->m_Map)); - - for(char *p = MapVote->m_Server; *p; p++) // lower case server - *p = tolower(*p); - } - pData->m_pResult->m_Done = true; - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not start Mapvote"); - } - return false; -} - -void CSqlScore::MapInfo(int ClientID, const char* MapName) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(MapInfoThread, "map info", ClientID, MapName, 0); -} - -bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - - if (HandleFailure) - return true; - - try - { - char aFuzzyMap[128]; - str_copy(aFuzzyMap, pData->m_Name.Str(), sizeof(aFuzzyMap)); - sqlstr::ClearString(aFuzzyMap, sizeof(aFuzzyMap)); - sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); - - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), - "SELECT l.Map, l.Server, Mapper, Points, Stars, " - "(select count(Name) from %s_race where Map = l.Map) as Finishes, " - "(select count(distinct Name) from %s_race where Map = l.Map) as Finishers, " - "(select round(avg(Time)) from %s_race where Map = l.Map) as Average, " - "UNIX_TIMESTAMP(l.Timestamp) as Stamp, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(l.Timestamp) as Ago, " - "(select min(Time) from %s_race where Map = l.Map and Name = '%s') as OwnTime " - "FROM (" - "SELECT * FROM %s_maps " - "WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci " - "ORDER BY " - "CASE WHEN Map = '%s' THEN 0 ELSE 1 END, " - "CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, " - "LENGTH(Map), " - "Map " - "LIMIT 1" - ") as l;", - pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), - pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), - pData->m_RequestingPlayer.ClrStr(), - pSqlServer->GetPrefix(), - aFuzzyMap, - pData->m_Name.ClrStr(), - pData->m_Name.ClrStr() - ); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "No map like \"%s\" found.", pData->m_Name.Str()); - } - else - { - pSqlServer->GetResults()->next(); - int Points = pSqlServer->GetResults()->getInt("Points"); - int Stars = pSqlServer->GetResults()->getInt("Stars"); - int finishes = pSqlServer->GetResults()->getInt("Finishes"); - int finishers = pSqlServer->GetResults()->getInt("Finishers"); - int average = pSqlServer->GetResults()->getInt("Average"); - char aMap[128]; - strcpy(aMap, pSqlServer->GetResults()->getString("Map").c_str()); - char aServer[32]; - strcpy(aServer, pSqlServer->GetResults()->getString("Server").c_str()); - char aMapper[128]; - strcpy(aMapper, pSqlServer->GetResults()->getString("Mapper").c_str()); - int stamp = pSqlServer->GetResults()->getInt("Stamp"); - int ago = pSqlServer->GetResults()->getInt("Ago"); - float ownTime = (float)pSqlServer->GetResults()->getDouble("OwnTime"); - - char aAgoString[40] = "\0"; - char aReleasedString[60] = "\0"; - if(stamp != 0) - { - sqlstr::AgoTimeToString(ago, aAgoString); - str_format(aReleasedString, sizeof(aReleasedString), ", released %s ago", aAgoString); - } - - char aAverageString[60] = "\0"; - if(average > 0) - { - str_format(aAverageString, sizeof(aAverageString), " in %d:%02d average", average / 60, average % 60); - } - - char aStars[20]; - switch(Stars) - { - case 0: strcpy(aStars, "✰✰✰✰✰"); break; - case 1: strcpy(aStars, "★✰✰✰✰"); break; - case 2: strcpy(aStars, "★★✰✰✰"); break; - case 3: strcpy(aStars, "★★★✰✰"); break; - case 4: strcpy(aStars, "★★★★✰"); break; - case 5: strcpy(aStars, "★★★★★"); break; - default: aStars[0] = '\0'; - } - - char aOwnFinishesString[40] = "\0"; - if(ownTime > 0) - { - str_format(aOwnFinishesString, sizeof(aOwnFinishesString), - ", your time: %02d:%05.2f", (int)(ownTime/60), ownTime-((int)ownTime/60*60) - ); - } - - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", - aMap, aMapper, aServer, aStars, - Points, Points == 1 ? "point" : "points", - aReleasedString, - finishes, finishes == 1 ? "finish" : "finishes", - finishers, finishers == 1 ? "tee" : "tees", - aAverageString, aOwnFinishesString - ); - } - pData->m_pResult->m_Done = true; - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not get Mapinfo"); - } - return false; -} - -void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible) -{ - CConsole* pCon = (CConsole*)GameServer()->Console(); - if(pCon->m_Cheated || NotEligible) - return; - - CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; - if(pCurPlayer->m_ScoreFinishResult != nullptr) - dbg_msg("sql", "WARNING: previous save score result didn't complete, overwriting it now"); - pCurPlayer->m_ScoreFinishResult = std::make_shared(); - CSqlScoreData *Tmp = new CSqlScoreData(pCurPlayer->m_ScoreFinishResult); - Tmp->m_Map = g_Config.m_SvMap; - FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = Server()->ClientName(ClientID); - Tmp->m_Time = Time; - str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); - for(int i = 0; i < NUM_CHECKPOINTS; i++) - Tmp->m_aCpCurrent[i] = CpTime[i]; - - thread_init_and_detach(CSqlExecData::ExecSqlFunc, - new CSqlExecData(SaveScoreThread, Tmp), - "save score"); -} - -bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlScoreData *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if(HandleFailure) - { - if(!g_Config.m_SvSqlFailureFile[0]) - return true; - - lock_wait(ms_FailureFileLock); - IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); - if(File == 0) - { - lock_unlock(ms_FailureFileLock); - dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file"); - return false; - } - dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now..."); - - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, " - "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " - "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " - "GameID, DDNet7) " - "VALUES ('%s', '%s', '%s', '%.2f', '%s'," - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%.2f', '%s', false);", - pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), - pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, - pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], - pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], - pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], - pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], - pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], - pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], - pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], - pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], - pData->m_aCpCurrent[24], - pData->m_GameUuid); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - io_close(File); - lock_unlock(ms_FailureFileLock); - - pData->m_pResult->SetVariant(CScorePlayerResult::BROADCAST); - strcpy(pData->m_pResult->m_Data.m_Broadcast, - "Database connection failed, score written to a file instead. Admins will add it manually in a few days."); - pData->m_pResult->m_Done = true; - return true; - } - - try - { - char aBuf[1024]; - - str_format(aBuf, sizeof(aBuf), - "SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - pSqlServer->GetResults()->first(); - int NumFinished = pSqlServer->GetResults()->getInt("NumFinished"); - if(NumFinished == 0) - { - str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map='%s'", pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() == 1) - { - pSqlServer->GetResults()->next(); - int Points = pSqlServer->GetResults()->getInt("Points"); - str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point%s for finishing this map!", Points, Points == 1 ? "" : "s"); - - str_format(aBuf, sizeof(aBuf), - "INSERT INTO %s_points(Name, Points) " - "VALUES ('%s', '%d') " - "ON duplicate key " - "UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", - pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), Points); - pSqlServer->executeSql(aBuf); - } - } - - // save score - str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_race(" - "Map, Name, Timestamp, Time, Server, " - "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " - "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " - "GameID, DDNet7) " - "VALUES ('%s', '%s', '%s', '%.2f', '%s', " - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " - "'%s', false);", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), - pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, - pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], - pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], - pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], - pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], - pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], - pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], - pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], - pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], - pData->m_aCpCurrent[24], pData->m_GameUuid); - dbg_msg("sql", "%s", aBuf); - pSqlServer->executeSql(aBuf); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Saving score done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not insert time"); - } - return false; -} - -void CSqlScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp) -{ - CConsole* pCon = (CConsole*)GameServer()->Console(); - if(pCon->m_Cheated) - return; - for(unsigned int i = 0; i < Size; i++) - { - if(GameServer()->m_apPlayers[aClientIDs[i]]->m_NotEligibleForFinish) - return; - } - CSqlTeamScoreData *Tmp = new CSqlTeamScoreData(nullptr); - for(unsigned int i = 0; i < Size; i++) - Tmp->m_aNames[i] = Server()->ClientName(aClientIDs[i]); - Tmp->m_Size = Size; - Tmp->m_Time = Time; - str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); - FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); - Tmp->m_Map = g_Config.m_SvMap; - - thread_init_and_detach(CSqlExecData::ExecSqlFunc, - new CSqlExecData(SaveTeamScoreThread, Tmp), - "save team score"); -} - -bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlTeamScoreData *pData = dynamic_cast(pGameData); - - if(HandleFailure) - { - if(!g_Config.m_SvSqlFailureFile[0]) - return true; - - dbg_msg("sql", "ERROR: Could not save TeamScore, writing insert to a file now..."); - - lock_wait(ms_FailureFileLock); - IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); - if(File) - { - const char aUUID[] = "SET @id = UUID();"; - io_write(File, aUUID, sizeof(aUUID) - 1); - io_write_newline(File); - - char aBuf[2300]; - for(unsigned int i = 0; i < pData->m_Size; i++) - { - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - } - io_close(File); - lock_unlock(ms_FailureFileLock); - return true; - } - lock_unlock(ms_FailureFileLock); - return false; - } - - try - { - char aBuf[2300]; - - // get the names sorted in a tab separated string - const sqlstr::CSqlString *apNames[MAX_CLIENTS]; - for(unsigned int i = 0; i < pData->m_Size; i++) - apNames[i] = &pData->m_aNames[i]; - std::sort(apNames, apNames+pData->m_Size); - char aSortedNames[2048] = {0}; - for(unsigned int i = 0; i < pData->m_Size; i++) - { - if(i != 0) - str_append(aSortedNames, "\t", sizeof(aSortedNames)); - str_append(aSortedNames, apNames[i]->ClrStr(), sizeof(aSortedNames)); - } - str_format(aBuf, sizeof(aBuf), - "SELECT l.ID, Time " - "FROM ((" // preselect teams with first name in team - "SELECT ID " - "FROM %s_teamrace " - "WHERE Map = '%s' AND Name = '%s' AND DDNet7 = false" - ") as l" - ") INNER JOIN %s_teamrace AS r ON l.ID = r.ID " - "GROUP BY ID " - "HAVING GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') = '%s'", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[0].ClrStr(), - pSqlServer->GetPrefix(), aSortedNames); - pSqlServer->executeSqlQuery(aBuf); - - if (pSqlServer->GetResults()->rowsCount() > 0) - { - pSqlServer->GetResults()->first(); - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - auto ID = pSqlServer->GetResults()->getString("ID"); - dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time); - if(pData->m_Time < Time) - { - str_format(aBuf, sizeof(aBuf), - "UPDATE %s_teamrace SET Time='%.2f', Timestamp='%s', DDNet7=false, GameID='%s' WHERE ID = '%s';", - pSqlServer->GetPrefix(), pData->m_Time, pData->m_aTimestamp, pData->m_GameUuid, ID.c_str()); - dbg_msg("sql", "%s", aBuf); - pSqlServer->executeSql(aBuf); - } - } - else - { - pSqlServer->executeSql("SET @id = UUID();"); - - for(unsigned int i = 0; i < pData->m_Size; i++) - { - // if no entry found... create a new one - str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " - "VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), - pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid); - dbg_msg("sql", "%s", aBuf); - pSqlServer->executeSql(aBuf); - } - } - - dbg_msg("sql", "Updating team time done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not update time"); - } - return false; -} - -void CSqlScore::ShowRank(int ClientID, const char* pName) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowRankThread, "show rank", ClientID, pName, 0); -} - -bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - if (HandleFailure) - { - pData->m_pResult->m_Done = true; - return true; - } - - try - { - // check sort method - char aBuf[600]; - - str_format(aBuf, sizeof(aBuf), - "SELECT Rank, Name, Time " - "FROM (" - "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " - "FROM %s_race " - "WHERE Map = '%s' " - "GROUP BY Name " - "WINDOW w AS (ORDER BY Time)" - ") as a " - "WHERE Name = '%s';", - pSqlServer->GetPrefix(), - pData->m_Map.ClrStr(), - pData->m_Name.ClrStr() - ); - - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "%s is not ranked", pData->m_Name.Str()); - } - else - { - pSqlServer->GetResults()->next(); - - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - int Rank = pSqlServer->GetResults()->getInt("Rank"); - if(g_Config.m_SvHideScore) - { - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "Your time: %02d:%05.2f", (int)(Time/60), Time-((int)Time/60*60)); - } - else - { - pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "%d. %s Time: %02d:%05.2f, requested by %s", - Rank, pSqlServer->GetResults()->getString("Name").c_str(), - (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer.Str()); - } - } - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing rank done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show rank"); - } - return false; -} - -void CSqlScore::ShowTeamRank(int ClientID, const char* pName) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTeamRankThread, "show team rank", ClientID, pName, 0); -} - -bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - if (HandleFailure) - { - pData->m_pResult->m_Done = true; - return true; - } - - try - { - // check sort method - char aBuf[2400]; - char aNames[2300]; - aNames[0] = '\0'; - - str_format(aBuf, sizeof(aBuf), - "SELECT Time, Rank, Name " - "FROM (" // teamrank score board - "SELECT RANK() OVER w AS Rank, Id " - "FROM %s_teamrace " - "WHERE Map = '%s' " - "GROUP BY Id " - "WINDOW w AS (ORDER BY Time)" - ") as l " - "INNER JOIN %s_teamrace as r ON l.ID = r.ID " - "WHERE l.ID = (" // find id for top teamrank of player - "SELECT Id " - "FROM %s_teamrace " - "WHERE Map = '%s' AND Name = '%s' " - "ORDER BY Time " - "LIMIT 1" - ") " - "ORDER BY Name;", - pSqlServer->GetPrefix(), - pData->m_Map.ClrStr(), - pSqlServer->GetPrefix(), - pSqlServer->GetPrefix(), - pData->m_Map.ClrStr(), - pData->m_Name.ClrStr() - ); - - pSqlServer->executeSqlQuery(aBuf); - - int Rows = pSqlServer->GetResults()->rowsCount(); - - if(Rows < 1) - { - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "%s has no team ranks", pData->m_Name.Str()); - } - else - { - pSqlServer->GetResults()->first(); - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - int Rank = pSqlServer->GetResults()->getInt("Rank"); - - for(int Row = 0; Row < Rows; Row++) - { - str_append(aNames, pSqlServer->GetResults()->getString("Name").c_str(), sizeof(aNames)); - pSqlServer->GetResults()->next(); - - if (Row < Rows - 2) - str_append(aNames, ", ", sizeof(aNames)); - else if (Row < Rows - 1) - str_append(aNames, " & ", sizeof(aNames)); - } - - if(g_Config.m_SvHideScore) - { - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "Your team time: %02d:%05.02f", (int)(Time/60), Time-((int)Time/60*60)); - } - else - { - pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; - str_format(pData->m_pResult->m_Data.m_aaMessages[0], sizeof(pData->m_pResult->m_Data.m_aaMessages[0]), - "%d. %s Team time: %02d:%05.02f, requested by %s", - Rank, aNames, (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer.Str()); - } - } - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing teamrank done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show team rank"); - } - return false; -} - -void CSqlScore::ShowTop5(int ClientID, int Offset) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTop5Thread, "show top5", ClientID, "", Offset); -} - -bool CSqlScore::ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - if (HandleFailure) - return true; - - int LimitStart = maximum(abs(pData->m_Offset)-1, 0); - const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; - - try - { - // check sort method - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), - "SELECT Name, Time, Rank " - "FROM (" - "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " - "FROM %s_race " - "WHERE Map = '%s' " - "GROUP BY Name " - "WINDOW w AS (ORDER BY Time)" - ") as a " - "ORDER BY Rank %s " - "LIMIT %d, 5;", - pSqlServer->GetPrefix(), - pData->m_Map.ClrStr(), - pOrder, - LimitStart - ); - pSqlServer->executeSqlQuery(aBuf); - - // show top5 - strcpy(pData->m_pResult->m_Data.m_aaMessages[0], "----------- Top 5 -----------"); - - int Line = 1; - while(pSqlServer->GetResults()->next()) - { - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - int Rank = pSqlServer->GetResults()->getInt("Rank"); - str_format(pData->m_pResult->m_Data.m_aaMessages[Line], sizeof(pData->m_pResult->m_Data.m_aaMessages[Line]), - "%d. %s Time: %02d:%05.2f", - Rank, pSqlServer->GetResults()->getString("Name").c_str(), - (int)(Time/60), Time-((int)Time/60*60) - ); - Line++; - } - strcpy(pData->m_pResult->m_Data.m_aaMessages[Line], "-------------------------------"); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing top5 done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show top5"); - } - - return false; -} - -void CSqlScore::ShowTeamTop5(int ClientID, int Offset) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTeamTop5Thread, "show team top5", ClientID, "", Offset); -} - -bool CSqlScore::ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - if (HandleFailure) - return true; - - int LimitStart = maximum(abs(pData->m_Offset)-1, 0); - const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; - - try - { - // check sort method - char aBuf[512]; - - str_format(aBuf, sizeof(aBuf), - "SELECT Name, Time, Rank, TeamSize " - "FROM (" // limit to 5 - "SELECT Rank, ID, TeamSize " - "FROM (" // teamrank score board - "SELECT RANK() OVER w AS Rank, ID, COUNT(*) AS Teamsize " - "FROM %s_teamrace " - "WHERE Map = '%s' " - "GROUP BY Id " - "WINDOW w AS (ORDER BY Time)" - ") as l1 " - "ORDER BY Rank %s " - "LIMIT %d, 5" - ") as l2 " - "INNER JOIN %s_teamrace as r ON l2.ID = r.ID " - "ORDER BY Rank %s, r.ID, Name ASC;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart, pSqlServer->GetPrefix(), pOrder - ); - pSqlServer->executeSqlQuery(aBuf); - - // show teamtop5 - int Line = 0; - strcpy(paMessages[Line++], "------- Team Top 5 -------"); - - int Rows = pSqlServer->GetResults()->rowsCount(); - - if(Rows > 0) - { - pSqlServer->GetResults()->first(); - - for(Line = 1; Line < 6; Line++) // print - { - if(pSqlServer->GetResults()->isAfterLast()) - break; - int TeamSize = pSqlServer->GetResults()->getInt("TeamSize"); - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - int Rank = pSqlServer->GetResults()->getInt("Rank"); - - char aNames[2300] = { 0 }; - for(int i = 0; i < TeamSize; i++) - { - auto Name = pSqlServer->GetResults()->getString("Name"); - str_append(aNames, Name.c_str(), sizeof(aNames)); - if (i < TeamSize - 2) - str_append(aNames, ", ", sizeof(aNames)); - else if (i == TeamSize - 2) - str_append(aNames, " & ", sizeof(aNames)); - pSqlServer->GetResults()->next(); - } - str_format(paMessages[Line], sizeof(paMessages[Line]), "%d. %s Team Time: %02d:%05.2f", - Rank, aNames, (int)(Time/60), Time-((int)Time/60*60)); - } - } - - strcpy(paMessages[Line], "-------------------------------"); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing teamtop5 done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show teamtop5"); - } - return false; -} - -void CSqlScore::ShowTimes(int ClientID, int Offset) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTimesThread, "show times", ClientID, "", Offset); -} - -void CSqlScore::ShowTimes(int ClientID, const char* pName, int Offset) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTimesThread, "show times", ClientID, pName, Offset); -} - -bool CSqlScore::ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if (HandleFailure) - return true; - - int LimitStart = maximum(abs(pData->m_Offset)-1, 0); - const char *pOrder = pData->m_Offset >= 0 ? "DESC" : "ASC"; - - try - { - char aBuf[512]; - - if(pData->m_Name.Str()[0] != '\0') // last 5 times of a player - { - str_format(aBuf, sizeof(aBuf), - "SELECT Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp " - "FROM %s_race " - "WHERE Map = '%s' AND Name = '%s' " - "ORDER BY Timestamp %s " - "LIMIT %d, 5;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pOrder, LimitStart - ); - } - else // last 5 times of server - { - str_format(aBuf, sizeof(aBuf), - "SELECT Name, Time, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, " - "UNIX_TIMESTAMP(Timestamp) as Stamp " - "FROM %s_race " - "WHERE Map = '%s' " - "ORDER BY Timestamp %s " - "LIMIT %d, 5;", - pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart - ); - } - pSqlServer->executeSqlQuery(aBuf); - - // show top5 - if(pSqlServer->GetResults()->rowsCount() == 0) - { - strcpy(paMessages[0], "There are no times in the specified range"); - pData->m_pResult->m_Done = true; - return true; - } - - strcpy(paMessages[0], "------------- Last Times -------------"); - int Line = 1; - while(pSqlServer->GetResults()->next()) - { - char aAgoString[40] = "\0"; - int pSince = pSqlServer->GetResults()->getInt("Ago"); - int pStamp = pSqlServer->GetResults()->getInt("Stamp"); - float pTime = (float)pSqlServer->GetResults()->getDouble("Time"); - - sqlstr::AgoTimeToString(pSince, aAgoString); - - if(pData->m_Name.Str()[0] != '\0') // last 5 times of a player - { - if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet - str_format(paMessages[Line], sizeof(paMessages[Line]), - "%02d:%05.02f, don't know how long ago", - (int)(pTime/60), pTime-((int)pTime/60*60)); - else - str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s ago, %02d:%05.02f", - aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); - } - else // last 5 times of the server - { - auto Name = pSqlServer->GetResults()->getString("Name"); - if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet - str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s, %02d:%05.02f, don't know when", - Name.c_str(), (int)(pTime/60), pTime-((int)pTime/60*60)); - else - str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s, %s ago, %02d:%05.02f", - Name.c_str(), aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); - } - Line++; - } - strcpy(paMessages[Line], "----------------------------------------------------"); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing times done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show times"); - } - return false; -} - -void CSqlScore::ShowPoints(int ClientID, const char* pName) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowPointsThread, "show points", ClientID, pName, 0); -} - -bool CSqlScore::ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), - "SELECT Rank, Points, Name " - "FROM (" - "SELECT RANK() OVER w AS Rank, Points, Name " - "FROM %s_points " - "WINDOW w as (ORDER BY Points DESC)" - ") as a " - "WHERE Name = '%s';", - pSqlServer->GetPrefix(), pData->m_Name.ClrStr() - ); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_format(paMessages[0], sizeof(paMessages[0]), - "%s has not collected any points so far", pData->m_Name.Str()); - } - else - { - pSqlServer->GetResults()->next(); - int Count = pSqlServer->GetResults()->getInt("Points"); - int Rank = pSqlServer->GetResults()->getInt("Rank"); - auto Name = pSqlServer->GetResults()->getString("Name"); - pData->m_pResult->m_MessageKind = CScorePlayerResult::ALL; - str_format(paMessages[0], sizeof(paMessages[0]), - "%d. %s Points: %d, requested by %s", - Rank, Name.c_str(), Count, pData->m_RequestingPlayer.Str()); - } - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing points done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show points"); - } - return false; -} - -void CSqlScore::ShowTopPoints(int ClientID, int Offset) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(ShowTopPointsThread, "show top points", ClientID, "", Offset); -} - -bool CSqlScore::ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if (HandleFailure) - return true; - - int LimitStart = maximum(abs(pData->m_Offset)-1, 0); - const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; - - try - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), - "SELECT Rank, Points, Name " - "FROM (" - "SELECT RANK() OVER w AS Rank, Points, Name " - "FROM %s_points " - "WINDOW w as (ORDER BY Points DESC)" - ") as a " - "ORDER BY Rank %s " - "LIMIT %d, 5;", - pSqlServer->GetPrefix(), pOrder, LimitStart - ); - - pSqlServer->executeSqlQuery(aBuf); - - // show top points - strcpy(paMessages[0], "-------- Top Points --------"); - - int Line = 1; - while(pSqlServer->GetResults()->next()) - { - int Rank = pSqlServer->GetResults()->getInt("Rank"); - auto Name = pSqlServer->GetResults()->getString("Name"); - int Points = pSqlServer->GetResults()->getInt("Points"); - str_format(paMessages[Line], sizeof(paMessages[Line]), - "%d. %s Points: %d", Rank, Name.c_str(), Points); - Line++; - } - strcpy(paMessages[Line], "-------------------------------"); - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing toppoints done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not show toppoints"); - } - return false; -} - -void CSqlScore::RandomMap(int ClientID, int Stars) -{ - auto pResult = std::make_shared(ClientID); - GameServer()->m_SqlRandomMapResult = pResult; - - auto *Tmp = new CSqlRandomMapRequest(pResult); - Tmp->m_Stars = Stars; - Tmp->m_CurrentMap = g_Config.m_SvMap; - Tmp->m_ServerType = g_Config.m_SvServerType; - Tmp->m_RequestingPlayer = GameServer()->Server()->ClientName(ClientID); - - thread_init_and_detach( - CSqlExecData::ExecSqlFunc, - new CSqlExecData(RandomMapThread, Tmp), - "random map"); -} - -bool CSqlScore::RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - if(0 <= pData->m_Stars && pData->m_Stars <= 5) - { - str_format(aBuf, sizeof(aBuf), - "SELECT * FROM %s_maps " - "WHERE Server = \"%s\" AND Map != \"%s\" AND Stars = \"%d\" " - "ORDER BY RAND() LIMIT 1;", - pSqlServer->GetPrefix(), - pData->m_ServerType.ClrStr(), - pData->m_CurrentMap.ClrStr(), - pData->m_Stars - ); - } - else - { - str_format(aBuf, sizeof(aBuf), - "SELECT * FROM %s_maps " - "WHERE Server = \"%s\" AND Map != \"%s\" " - "ORDER BY RAND() LIMIT 1;", - pSqlServer->GetPrefix(), - pData->m_ServerType.ClrStr(), - pData->m_CurrentMap.ClrStr() - ); - } - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_copy(pData->m_pResult->m_aMessage, "No maps found on this server!", sizeof(pData->m_pResult->m_aMessage)); - } - else - { - pSqlServer->GetResults()->next(); - auto Map = pSqlServer->GetResults()->getString("Map"); - str_copy(pData->m_pResult->m_Map, Map.c_str(), sizeof(pData->m_pResult->m_Map)); - } - - dbg_msg("sql", "voting random map done"); - pData->m_pResult->m_Done = true; - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not vote random map"); - } - return false; -} - -void CSqlScore::RandomUnfinishedMap(int ClientID, int Stars) -{ - auto pResult = std::make_shared(ClientID); - GameServer()->m_SqlRandomMapResult = pResult; - - auto *Tmp = new CSqlRandomMapRequest(pResult); - Tmp->m_Stars = Stars; - Tmp->m_CurrentMap = g_Config.m_SvMap; - Tmp->m_ServerType = g_Config.m_SvServerType; - Tmp->m_RequestingPlayer = GameServer()->Server()->ClientName(ClientID); - - thread_init_and_detach( - CSqlExecData::ExecSqlFunc, - new CSqlExecData(RandomUnfinishedMapThread, Tmp), - "random unfinished map"); -} - -bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - if(pData->m_Stars >= 0) - { - str_format(aBuf, sizeof(aBuf), - "SELECT Map " - "FROM %s_maps " - "WHERE Server = \"%s\" AND Map != \"%s\" AND Stars = \"%d\" AND Map NOT IN (" - "SELECT Map " - "FROM %s_race " - "WHERE Name = \"%s\"" - ") ORDER BY RAND() " - "LIMIT 1;", - pSqlServer->GetPrefix(), pData->m_ServerType.ClrStr(), pData->m_CurrentMap.ClrStr(), - pData->m_Stars, pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); - } - else - { - str_format(aBuf, sizeof(aBuf), - "SELECT Map " - "FROM %s_maps AS maps " - "WHERE Server = \"%s\" AND Map != \"%s\" AND Map NOT IN (" - "SELECT Map " - "FROM %s_race as race " - "WHERE Name = \"%s\"" - ") ORDER BY RAND() " - "LIMIT 1;", - pSqlServer->GetPrefix(), pData->m_ServerType.ClrStr(), pData->m_CurrentMap.ClrStr(), - pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); - } - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() != 1) - { - str_copy(pData->m_pResult->m_aMessage, "You have no more unfinished maps on this server!", sizeof(pData->m_pResult->m_aMessage)); - } - else - { - pSqlServer->GetResults()->next(); - auto Map = pSqlServer->GetResults()->getString("Map"); - str_copy(pData->m_pResult->m_Map, Map.c_str(), sizeof(pData->m_pResult->m_Map)); - } - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "voting random unfinished map done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not vote random unfinished map"); - } - return false; -} - -void CSqlScore::SaveTeam(int ClientID, const char* Code, const char* Server) -{ - if(RateLimitPlayer(ClientID)) - return; - auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); - int Team = pController->m_Teams.m_Core.Team(ClientID); - if(pController->m_Teams.GetSaving(Team)) - return; - - auto SaveResult = std::make_shared(ClientID, pController); - int Result = SaveResult->m_SavedTeam.save(Team); - if(CSaveTeam::HandleSaveError(Result, ClientID, GameServer())) - return; - pController->m_Teams.SetSaving(Team, SaveResult); - - CSqlTeamSave *Tmp = new CSqlTeamSave(SaveResult); - str_copy(Tmp->m_Code, Code, sizeof(Tmp->m_Code)); - str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); - Tmp->m_pResult->m_SaveID = RandomUuid(); - str_copy(Tmp->m_Server, Server, sizeof(Tmp->m_Server)); - str_copy(Tmp->m_ClientName, this->Server()->ClientName(ClientID), sizeof(Tmp->m_ClientName)); - Tmp->m_aGeneratedCode[0] = '\0'; - GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode)); - - pController->m_Teams.KillSavedTeam(ClientID, Team); - thread_init_and_detach( - CSqlExecData::ExecSqlFunc, - new CSqlExecData(SaveTeamThread, Tmp, false), - "save team"); -} - -bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlTeamSave *pData = dynamic_cast(pGameData); - - char aSaveID[UUID_MAXSTRSIZE]; - FormatUuid(pData->m_pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE); - - char *pSaveState = pData->m_pResult->m_SavedTeam.GetString(); - if(HandleFailure) - { - if (!g_Config.m_SvSqlFailureFile[0]) - return true; - - lock_wait(ms_FailureFileLock); - IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); - if(File) - { - dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now..."); - sqlstr::CSqlString<65536> SaveState = pSaveState; - sqlstr::CSqlString<128> Code = pData->m_aGeneratedCode; - sqlstr::CSqlString<128> Map = pData->m_Map; - - char aBuf[65536]; - str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " - "VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false);", - SaveState.ClrStr(), Map.ClrStr(), - Code.ClrStr(), pData->m_Server, aSaveID - ); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - io_close(File); - lock_unlock(ms_FailureFileLock); - - pData->m_pResult->m_Status = CScoreSaveResult::SAVE_SUCCESS; - strcpy(pData->m_pResult->m_aBroadcast, - "Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days."); - if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0) - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' to continue", - pData->m_ClientName, Code.ClrStr()); - } - else - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' on %s to continue", - pData->m_ClientName, Code.ClrStr(), pData->m_Server); - } - return true; - } - lock_unlock(ms_FailureFileLock); - dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file"); - return false; - } - - try - { - char aBuf[65536]; - str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix()); - pSqlServer->executeSql(aBuf); - - char Code[128] = {0}; - str_format(aBuf, sizeof(aBuf), "SELECT Savegame FROM %s_saves WHERE Code = ? AND Map = ?", pSqlServer->GetPrefix()); - std::unique_ptr pPrepStmt; - std::unique_ptr pResult; - pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf)); - bool UseCode = false; - if(pData->m_Code[0] != '\0') - { - pPrepStmt->setString(1, pData->m_Code); - pPrepStmt->setString(2, pData->m_Map); - pResult.reset(pPrepStmt->executeQuery()); - if(pResult->rowsCount() == 0) - { - UseCode = true; - str_copy(Code, pData->m_Code, sizeof(Code)); - } - } - if(!UseCode) - { - // use random generated passphrase if save code exists or no save code given - pPrepStmt->setString(1, pData->m_aGeneratedCode); - pPrepStmt->setString(2, pData->m_Map); - pResult.reset(pPrepStmt->executeQuery()); - if(pResult->rowsCount() == 0) - { - UseCode = true; - str_copy(Code, pData->m_aGeneratedCode, sizeof(Code)); - } - } - - if(UseCode) - { - str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " - "VALUES (?, ?, ?, CURRENT_TIMESTAMP(), ?, ?, false)", - pSqlServer->GetPrefix()); - pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf)); - pPrepStmt->setString(1, pSaveState); - pPrepStmt->setString(2, pData->m_Map); - pPrepStmt->setString(3, Code); - pPrepStmt->setString(4, pData->m_Server); - pPrepStmt->setString(5, aSaveID); - dbg_msg("sql", "%s", aBuf); - pPrepStmt->execute(); - - if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0) - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "Team successfully saved by %s. Use '/load %s' to continue", - pData->m_ClientName, Code); - } - else - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "Team successfully saved by %s. Use '/load %s' on %s to continue", - pData->m_ClientName, Code, pData->m_Server); - } - pData->m_pResult->m_Status = CScoreSaveResult::SAVE_SUCCESS; - } - else - { - dbg_msg("sql", "ERROR: This save-code already exists"); - pData->m_pResult->m_Status = CScoreSaveResult::SAVE_FAILED; - strcpy(pData->m_pResult->m_aMessage, "This save-code already exists"); - } - } - catch (sql::SQLException &e) - { - pData->m_pResult->m_Status = CScoreSaveResult::SAVE_FAILED; - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not save the team"); - - strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not save the team"); - pSqlServer->executeSql("unlock tables;"); - return false; - } - - pSqlServer->executeSql("unlock tables;"); - return true; -} - -void CSqlScore::LoadTeam(const char* Code, int ClientID) -{ - if(RateLimitPlayer(ClientID)) - return; - auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); - int Team = pController->m_Teams.m_Core.Team(ClientID); - if(pController->m_Teams.GetSaving(Team)) - return; - if(Team < TEAM_FLOCK || Team >= MAX_CLIENTS || (g_Config.m_SvTeam != 3 && Team == TEAM_FLOCK)) - { - GameServer()->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); - return; - } - if(pController->m_Teams.GetTeamState(Team) != CGameTeams::TEAMSTATE_OPEN) - { - GameServer()->SendChatTarget(ClientID, "Team can't be loaded while racing"); - return; - } - auto SaveResult = std::make_shared(ClientID, pController); - pController->m_Teams.SetSaving(Team, SaveResult); - CSqlTeamLoad *Tmp = new CSqlTeamLoad(SaveResult); - Tmp->m_Code = Code; - Tmp->m_Map = g_Config.m_SvMap; - Tmp->m_ClientID = ClientID; - Tmp->m_RequestingPlayer = Server()->ClientName(ClientID); - Tmp->m_NumPlayer = 0; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(pController->m_Teams.m_Core.Team(i) == Team) - { - // put all names at the beginning of the array - str_copy(Tmp->m_aClientNames[Tmp->m_NumPlayer], Server()->ClientName(i), sizeof(Tmp->m_aClientNames[Tmp->m_NumPlayer])); - Tmp->m_aClientID[Tmp->m_NumPlayer] = i; - Tmp->m_NumPlayer++; - } - } - thread_init_and_detach( - CSqlExecData::ExecSqlFunc, - new CSqlExecData(LoadTeamThread, Tmp, false), - "load team"); -} - -bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlTeamLoad *pData = dynamic_cast(pGameData); - pData->m_pResult->m_Status = CScoreSaveResult::LOAD_FAILED; - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix()); - pSqlServer->executeSql(aBuf); - str_format(aBuf, sizeof(aBuf), - "SELECT " - "Savegame, Server, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) AS Ago, " - "(UNHEX(REPLACE(SaveID, '-',''))) AS SaveID " - "FROM %s_saves " - "where Code = '%s' AND Map = '%s' AND DDNet7 = false AND Savegame LIKE '%%\\n%s\\t%%';", - pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr(), pData->m_RequestingPlayer.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->rowsCount() == 0) - { - strcpy(pData->m_pResult->m_aMessage, "No such savegame for this map"); - goto end; - } - pSqlServer->GetResults()->first(); - auto ServerName = pSqlServer->GetResults()->getString("Server"); - if(str_comp(ServerName.c_str(), g_Config.m_SvSqlServerName) != 0) - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "You have to be on the '%s' server to load this savegame", ServerName.c_str()); - goto end; - } - - int Since = pSqlServer->GetResults()->getInt("Ago"); - if(Since < g_Config.m_SvSaveGamesDelay) - { - str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), - "You have to wait %d seconds until you can load this savegame", - g_Config.m_SvSaveGamesDelay - Since); - goto end; - } - if(pSqlServer->GetResults()->isNull("SaveID")) - { - memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData)); - } - else - { - auto SaveID = pSqlServer->GetResults()->getBlob("SaveID"); - SaveID->read((char *) pData->m_pResult->m_SaveID.m_aData, 16); - if(SaveID->gcount() != 16) - { - strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: SaveID corrupted"); - goto end; - } - } - - auto SaveString = pSqlServer->GetResults()->getString("Savegame"); - int Num = pData->m_pResult->m_SavedTeam.LoadString(SaveString.c_str()); - - if(Num != 0) - { - strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: data corrupted"); - goto end; - } - - bool CanLoad = pData->m_pResult->m_SavedTeam.MatchPlayers( - pData->m_aClientNames, pData->m_aClientID, pData->m_NumPlayer, - pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage)); - - if(!CanLoad) - goto end; - - str_format(aBuf, sizeof(aBuf), - "DELETE FROM %s_saves " - "WHERE Code='%s' AND Map='%s';", - pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr()); - pSqlServer->executeSql(aBuf); - - pData->m_pResult->m_Status = CScoreSaveResult::LOAD_SUCCESS; - strcpy(pData->m_pResult->m_aMessage, "Loading successfully done"); - - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not load the team"); - strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not load the team"); - pSqlServer->executeSql("unlock tables;"); - - return false; - } - -end: - pSqlServer->executeSql("unlock tables;"); - return true; -} - -void CSqlScore::GetSaves(int ClientID) -{ - if(RateLimitPlayer(ClientID)) - return; - ExecPlayerThread(GetSavesThread, "get saves", ClientID, "", 0); -} - -bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerRequest *pData = dynamic_cast(pGameData); - auto paMessages = pData->m_pResult->m_Data.m_aaMessages; - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - - str_format(aBuf, sizeof(aBuf), - "SELECT COUNT(*) as NumSaves, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(max(Timestamp)) as Ago " - "FROM %s_saves " - "WHERE Map='%s' AND Savegame LIKE '%%\\n%s\\t%%';", - pSqlServer->GetPrefix(), - pData->m_Map.ClrStr(), - pData->m_RequestingPlayer.ClrStr() - ); - pSqlServer->executeSqlQuery(aBuf); - if(pSqlServer->GetResults()->next()) - { - int NumSaves = pSqlServer->GetResults()->getInt("NumSaves"); - - int Ago = pSqlServer->GetResults()->getInt("Ago"); - char aAgoString[40] = "\0"; - char aLastSavedString[60] = "\0"; - if(Ago) - { - sqlstr::AgoTimeToString(Ago, aAgoString); - str_format(aLastSavedString, sizeof(aLastSavedString), ", last saved %s ago", aAgoString); - } - - str_format(paMessages[0], sizeof(paMessages[0]), - "%s has %d save%s on %s%s", - pData->m_RequestingPlayer.Str(), - NumSaves, NumSaves == 1 ? "" : "s", - pData->m_Map.Str(), aLastSavedString); - } - - pData->m_pResult->m_Done = true; - dbg_msg("sql", "Showing saves done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not get saves"); - } - return false; -} - -#endif diff --git a/src/game/server/score/sql_score.h b/src/game/server/score/sql_score.h deleted file mode 100644 index e72e08f53..000000000 --- a/src/game/server/score/sql_score.h +++ /dev/null @@ -1,220 +0,0 @@ -/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ -/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ -/* CSqlScore Class by Sushi Tee*/ -#ifndef GAME_SERVER_SCORE_SQL_H -#define GAME_SERVER_SCORE_SQL_H - -#include -#include - -#include -#include -#include - -#include "../score.h" - -class CSqlServer; - -// holding relevant data for one thread, and function pointer for return values -template < typename TResult > -struct CSqlData -{ - CSqlData(std::shared_ptr pSqlResult) : - m_pResult(pSqlResult) - { } - std::shared_ptr m_pResult; - virtual ~CSqlData() = default; -}; - -struct CSqlInitData : CSqlData -{ - using CSqlData::CSqlData; - // current map - sqlstr::CSqlString m_Map; -}; - -struct CSqlPlayerRequest : CSqlData -{ - using CSqlData::CSqlData; - // object being requested, either map (128 bytes) or player (16 bytes) - sqlstr::CSqlString m_Name; - // current map - sqlstr::CSqlString m_Map; - sqlstr::CSqlString m_RequestingPlayer; - // relevant for /top5 kind of requests - int m_Offset; -}; - -struct CSqlRandomMapRequest : CSqlData -{ - using CSqlData::CSqlData; - sqlstr::CSqlString<32> m_ServerType; - sqlstr::CSqlString m_CurrentMap; - sqlstr::CSqlString m_RequestingPlayer; - int m_Stars; -}; - -struct CSqlScoreData : CSqlData -{ - using CSqlData::CSqlData; - - sqlstr::CSqlString m_Map; - char m_GameUuid[UUID_MAXSTRSIZE]; - sqlstr::CSqlString m_Name; - - int m_ClientID; - float m_Time; - char m_aTimestamp[TIMESTAMP_STR_LENGTH]; - float m_aCpCurrent[NUM_CHECKPOINTS]; - int m_Num; - bool m_Search; - char m_aRequestingPlayer[MAX_NAME_LENGTH]; -}; - -struct CSqlTeamScoreData : CSqlData -{ - using CSqlData::CSqlData; - char m_GameUuid[UUID_MAXSTRSIZE]; - sqlstr::CSqlString m_Map; - float m_Time; - char m_aTimestamp[TIMESTAMP_STR_LENGTH]; - unsigned int m_Size; - sqlstr::CSqlString m_aNames[MAX_CLIENTS]; -}; - -struct CSqlTeamSave : CSqlData -{ - using CSqlData::CSqlData; - virtual ~CSqlTeamSave() {}; - - char m_ClientName[MAX_NAME_LENGTH]; - - char m_Map[MAX_MAP_LENGTH]; - char m_Code[128]; - char m_aGeneratedCode[128]; - char m_Server[5]; -}; - -struct CSqlTeamLoad : CSqlData -{ - using CSqlData::CSqlData; - sqlstr::CSqlString<128> m_Code; - sqlstr::CSqlString m_Map; - sqlstr::CSqlString m_RequestingPlayer; - int m_ClientID; - // struct holding all player names in the team or an empty string - char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH]; - int m_aClientID[MAX_CLIENTS]; - int m_NumPlayer; -}; - -// controls one thread -template < typename TResult > -struct CSqlExecData -{ - CSqlExecData( - bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), - CSqlData *pSqlResult, - bool ReadOnly = true - ); - ~CSqlExecData(); - - bool (*m_pFuncPtr) (CSqlServer*, const CSqlData *, bool); - CSqlData *m_pSqlData; - bool m_ReadOnly; - - static void ExecSqlFunc(void *pUser); -}; - -class IServer; -class CGameContext; - -class CSqlScore: public IScore -{ - static LOCK ms_FailureFileLock; - - static bool Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure); - - static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - - static bool LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - - static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - - static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - - CGameContext *GameServer() { return m_pGameServer; } - IServer *Server() { return m_pServer; } - - CGameContext *m_pGameServer; - IServer *m_pServer; - - std::vector m_aWordlist; - CPrng m_Prng; - void GeneratePassphrase(char *pBuf, int BufSize); - - // returns new SqlResult bound to the player, if no current Thread is active for this player - std::shared_ptr NewSqlPlayerResult(int ClientID); - // Creates for player database requests - void ExecPlayerThread( - bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), - const char* pThreadName, - int ClientID, - const char* pName, - int Offset - ); - - // returns true if the player should be rate limited - bool RateLimitPlayer(int ClientID); - -public: - // keeps track of score-threads - static std::atomic_int ms_InstanceCount; - - CSqlScore(CGameContext *pGameServer); - ~CSqlScore() {} - - // Requested by game context, shouldn't fail in case the player started another thread - virtual void RandomMap(int ClientID, int Stars); - virtual void RandomUnfinishedMap(int ClientID, int Stars); - virtual void MapVote(int ClientID, const char* MapName); - - virtual void LoadPlayerData(int ClientID); - // Requested by players (fails if another request by this player is active) - virtual void MapInfo(int ClientID, const char* MapName); - virtual void ShowRank(int ClientID, const char* pName); - virtual void ShowTeamRank(int ClientID, const char* pName); - virtual void ShowPoints(int ClientID, const char* pName); - virtual void ShowTimes(int ClientID, const char* pName, int Offset = 1); - virtual void ShowTimes(int ClientID, int Offset = 1); - virtual void ShowTop5(int ClientID, int Offset = 1); - virtual void ShowTeamTop5(int ClientID, int Offset = 1); - virtual void ShowTopPoints(int ClientID, int Offset = 1); - virtual void GetSaves(int ClientID); - - // requested by teams - virtual void SaveTeam(int ClientID, const char* Code, const char* Server); - virtual void LoadTeam(const char* Code, int ClientID); - - // Game relevant not allowed to fail due to an ongoing SQL request. - virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, - float CpTime[NUM_CHECKPOINTS], bool NotEligible); - virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp); - - virtual void OnShutdown(); -}; - -#endif // GAME_SERVER_SCORE_SQL_H From ad21ec1269e001f061dde167f798826a43376c7a Mon Sep 17 00:00:00 2001 From: Zwelf Date: Wed, 8 Jul 2020 21:09:52 +0200 Subject: [PATCH 04/37] Add SQLite to build system and CI --- .github/workflows/build.yaml | 2 +- cmake/FindSQLite3.cmake | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 cmake/FindSQLite3.cmake diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index da2024c5a..b9e0ae859 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -62,7 +62,7 @@ jobs: if: contains(matrix.os, 'ubuntu') run: | sudo apt-get update -y - sudo apt-get install pkg-config cmake libfreetype6-dev libnotify-dev libsdl2-dev -y + sudo apt-get install pkg-config cmake libfreetype6-dev libnotify-dev libsdl2-dev libsqlite3-dev -y - name: Prepare Linux (fancy) if: contains(matrix.os, 'ubuntu') && matrix.fancy diff --git a/cmake/FindSQLite3.cmake b/cmake/FindSQLite3.cmake new file mode 100644 index 000000000..1c46c3843 --- /dev/null +++ b/cmake/FindSQLite3.cmake @@ -0,0 +1,45 @@ +if(NOT PREFER_BUNDLED_LIBS) + set(CMAKE_MODULE_PATH ${ORIGINAL_CMAKE_MODULE_PATH}) + find_package(SQLite3) + set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH}) +endif() + +if(NOT SQLite3_FOUND) + if(NOT CMAKE_CROSSCOMPILING) + find_package(PkgConfig QUIET) + pkg_check_modules(PC_SQLite3 sqlite3) + endif() + + set_extra_dirs_lib(SQLite3 sqlite3) + find_library(SQLite3_LIBRARY + NAMES sqlite3 + HINTS ${HINTS_SQLite3_LIBDIR} ${PC_SQLite3_LIBDIR} ${PC_SQLite3_LIBRARY_DIRS} + PATHS ${PATHS_SQLite3_LIBDIR} + ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} + ) + set_extra_dirs_include(SQLite3 sqlite3 "${SQLite3_LIBRARY}") + find_path(SQLite3_INCLUDEDIR sqlite3.h + PATH_SUFFIXES sqlite3 + HINTS ${HINTS_SQLite3_INCLUDEDIR} ${PC_SQLite3_INCLUDEDIR} ${PC_SQLite3_INCLUDE_DIRS} + PATHS ${PATHS_SQLite3_INCLUDEDIR} + ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLite3_INCLUDEDIR) + + mark_as_advanced(SQLite3_INCLUDEDIR SQLite3_LIBRARY) + + if(SQLite3_FOUND) + set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDEDIR}) + if(SQLite3_LIBRARY) + set(SQLite3_LIBRARIES ${SQLite3_LIBRARY}) + else() + set(SQLite3_LIBRARIES) + endif() + endif() +endif() + +if(SQLite3_FOUND) + is_bundled(SQLite3_BUNDLED "${SQLite3_LIBRARY}") +endif() From 7c31a15c93b34bd0557039ded65ea9456a72e33b Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 7 Jul 2020 19:29:44 +0200 Subject: [PATCH 05/37] Add SQLite interface --- CMakeLists.txt | 8 + .../server/databases/connection_pool.cpp | 5 + src/engine/server/databases/sqlite.cpp | 186 ++++++++++++++++++ src/engine/server/databases/sqlite.h | 54 +++++ src/engine/server/server.cpp | 22 +++ src/engine/shared/config_variables.h | 2 +- 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/engine/server/databases/sqlite.cpp create mode 100644 src/engine/server/databases/sqlite.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d1cde6fbc..8e98b1944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ endif() find_package(Pnglite) find_package(PythonInterp 3) find_package(SDL2) +find_package(SQLite3) if(VIDEORECORDER) find_package(FFMPEG) endif() @@ -417,6 +418,7 @@ endif() show_dependency_status("Pnglite" PNGLITE) show_dependency_status("PythonInterp" PYTHONINTERP) show_dependency_status("SDL2" SDL2) +show_dependency_status("SQLite3" SQLite3) if(VIDEORECORDER) show_dependency_status("FFmpeg" FFMPEG) endif() @@ -432,6 +434,9 @@ endif() if(NOT(PYTHONINTERP_FOUND)) message(SEND_ERROR "You must install Python to compile DDNet") endif() +if(NOT(SQLite3_FOUND)) + message(SEND_ERROR "You must install SQLite3 to compile the DDNet server") +endif() if(MYSQL AND NOT(MYSQL_FOUND)) message(SEND_ERROR "You must install MySQL to compile the DDNet server with MySQL support") @@ -1844,6 +1849,8 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server databases/connection_pool.h databases/mysql.cpp databases/mysql.h + databases/sqlite.cpp + databases/sqlite.h name_ban.cpp name_ban.h register.cpp @@ -1931,6 +1938,7 @@ endif() set(LIBS_SERVER ${LIBS} ${MYSQL_LIBRARIES} + ${SQLite3_LIBRARIES} ${TARGET_ANTIBOT} ${MINIUPNPC_LIBRARIES} # Add pthreads (on non-Windows) at the end, so that other libraries can depend diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index 5ff648fcc..98f8400b4 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -3,6 +3,7 @@ #if defined(CONF_SQL) #include #endif +#include // helper struct to hold thread data struct CSqlExecData @@ -176,6 +177,10 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD dbg_msg("sql", "MySQL Error: %s", e.what()); } #endif + catch (std::runtime_error &e) + { + dbg_msg("sql", "SQLite Error: %s", e.what()); + } catch (...) { dbg_msg("sql", "Unexpected exception caught"); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp new file mode 100644 index 000000000..2d7105c22 --- /dev/null +++ b/src/engine/server/databases/sqlite.cpp @@ -0,0 +1,186 @@ +#include "sqlite.h" + +#include +#include + +#include +#include + +CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) : + IDbConnection("record"), + m_Setup(Setup), + m_pDb(nullptr), + m_pStmt(nullptr), + m_Done(true), + m_InUse(false) +{ + str_copy(m_aFilename, pFilename, sizeof(m_aFilename)); +} + +CSqliteConnection::~CSqliteConnection() +{ + if(m_pStmt != nullptr) + sqlite3_finalize(m_pStmt); + sqlite3_close(m_pDb); + m_pDb = nullptr; +} + +CSqliteConnection *CSqliteConnection::Copy() +{ + return new CSqliteConnection(m_aFilename, m_Setup); +} + +IDbConnection::Status CSqliteConnection::Connect() +{ + if(m_InUse.exchange(true)) + return Status::IN_USE; + + if(m_pDb != nullptr) + return Status::SUCCESS; + + int Result = sqlite3_open(m_aFilename, &m_pDb); + if(Result != SQLITE_OK) + { + dbg_msg("sql", "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb)); + return Status::ERROR; + } + + // wait for database to unlock so we don't have to handle SQLITE_BUSY errors + sqlite3_busy_timeout(m_pDb, -1); + + if(m_Setup) + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), m_pCreateRace, GetPrefix(), MAX_NAME_LENGTH); + if(!Execute(aBuf)) + return Status::ERROR; + str_format(aBuf, sizeof(aBuf), m_pCreateTeamrace, GetPrefix(), MAX_NAME_LENGTH); + if(!Execute(aBuf)) + return Status::ERROR; + str_format(aBuf, sizeof(aBuf), m_pCreateMaps, GetPrefix()); + if(!Execute(aBuf)) + return Status::ERROR; + str_format(aBuf, sizeof(aBuf), m_pCreateSaves, GetPrefix()); + if(!Execute(aBuf)) + return Status::ERROR; + str_format(aBuf, sizeof(aBuf), m_pCreatePoints, GetPrefix(), MAX_NAME_LENGTH); + if(!Execute(aBuf)) + return Status::ERROR; + m_Setup = false; + } + return Status::SUCCESS; +} + +void CSqliteConnection::Disconnect() +{ + if(m_pStmt != nullptr) + sqlite3_finalize(m_pStmt); + m_pStmt = nullptr; + m_InUse.store(false); +} + +void CSqliteConnection::Lock(const char *pTable) +{ +} + +void CSqliteConnection::Unlock() +{ +} + +void CSqliteConnection::PrepareStatement(const char *pStmt) +{ + if(m_pStmt != nullptr) + sqlite3_finalize(m_pStmt); + m_pStmt = nullptr; + int Result = sqlite3_prepare_v2( + m_pDb, + pStmt, + -1, // pStmt can be any length + &m_pStmt, + NULL); + ExceptionOnError(Result); + m_Done = false; +} + +void CSqliteConnection::BindString(int Idx, const char *pString) +{ + int Result = sqlite3_bind_text(m_pStmt, Idx, pString, -1, NULL); + ExceptionOnError(Result); + m_Done = false; +} + +void CSqliteConnection::BindInt(int Idx, int Value) +{ + int Result = sqlite3_bind_int(m_pStmt, Idx, Value); + ExceptionOnError(Result); + m_Done = false; +} + +bool CSqliteConnection::Step() +{ + if(m_Done) + return false; + int Result = sqlite3_step(m_pStmt); + if(Result == SQLITE_ROW) + { + return true; + } + else if(Result == SQLITE_DONE) + { + m_Done = true; + return false; + } + else + { + ExceptionOnError(Result); + } + return false; +} + +bool CSqliteConnection::IsNull(int Col) const +{ + return sqlite3_column_type(m_pStmt, Col - 1) == SQLITE_NULL; +} + +float CSqliteConnection::GetFloat(int Col) const +{ + return (float)sqlite3_column_double(m_pStmt, Col - 1); +} + +int CSqliteConnection::GetInt(int Col) const +{ + return sqlite3_column_int(m_pStmt, Col - 1); +} + +void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) const +{ + str_copy(pBuffer, (const char *)sqlite3_column_text(m_pStmt, Col - 1), BufferSize); +} + +int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const +{ + int Size = sqlite3_column_bytes(m_pStmt, Col - 1); + Size = minimum(Size, BufferSize); + mem_copy(pBuffer, sqlite3_column_blob(m_pStmt, Col - 1), Size); + return Size; +} + +bool CSqliteConnection::Execute(const char *pQuery) +{ + char *pErrorMsg; + int Result = sqlite3_exec(m_pDb, pQuery, NULL, NULL, &pErrorMsg); + if(Result != SQLITE_OK) + { + dbg_msg("sql", "error executing query: '%s'", pErrorMsg); + sqlite3_free(pErrorMsg); + } + return Result == SQLITE_OK; +} + +void CSqliteConnection::ExceptionOnError(int Result) +{ + if(Result != SQLITE_OK) + { + throw std::runtime_error(sqlite3_errmsg(m_pDb)); + } +} diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h new file mode 100644 index 000000000..d080472ee --- /dev/null +++ b/src/engine/server/databases/sqlite.h @@ -0,0 +1,54 @@ +#ifndef ENGINE_SERVER_DATABASES_SQLITE_H +#define ENGINE_SERVER_DATABASES_SQLITE_H + +#include "connection.h" +#include + +struct sqlite3; +struct sqlite3_stmt; + +class CSqliteConnection : public IDbConnection +{ +public: + CSqliteConnection(const char *pFilename, bool Setup); + virtual ~CSqliteConnection(); + + virtual CSqliteConnection *Copy(); + + virtual Status Connect(); + virtual void Disconnect(); + + virtual void Lock(const char *pTable); + virtual void Unlock(); + + virtual void PrepareStatement(const char *pStmt); + + virtual void BindString(int Idx, const char *pString); + virtual void BindInt(int Idx, int Value); + + virtual bool Step(); + + virtual bool IsNull(int Col) const; + virtual float GetFloat(int Col) const; + virtual int GetInt(int Col) const; + virtual void GetString(int Col, char *pBuffer, int BufferSize) const; + // passing a negative buffer size is undefined behavior + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; + +private: + // copy of config vars + char m_aFilename[512]; + bool m_Setup; + + sqlite3 *m_pDb; + sqlite3_stmt *m_pStmt; + bool m_Done; // no more rows available for Step + // returns true, if the query succeded + bool Execute(const char *pQuery); + + void ExceptionOnError(int Result); + + std::atomic_bool m_InUse; +}; + +#endif // ENGINE_SERVER_DATABASES_SQLITE_H diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index cd7a5634c..86039638e 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -45,6 +45,7 @@ #endif #include +#include #include @@ -2301,6 +2302,23 @@ int CServer::Run() return -1; } + if(g_Config.m_SvSqliteFile[0] != '\0') + { + auto pSqlServers = std::unique_ptr(new CSqliteConnection( + g_Config.m_SvSqliteFile, true)); + + if(g_Config.m_SvUseSQL) + { + DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::WRITE_BACKUP); + } + else + { + auto pCopy = std::unique_ptr(pSqlServers->Copy()); + DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ); + DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE); + } + } + // start server NETADDR BindAddr; int NetType = g_Config.m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL; @@ -3051,6 +3069,8 @@ void CServer::ConLogout(IConsole::IResult *pResult, void *pUser) void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser) { + if(!g_Config.m_SvUseSQL) + return; CServer *pServer = (CServer *)pUser; if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS && @@ -3072,6 +3092,8 @@ void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser) void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) { + if(!g_Config.m_SvUseSQL) + return; CServer *pSelf = (CServer *)pUserData; if (pResult->NumArguments() != 7 && pResult->NumArguments() != 8) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index b4b19f01d..4d59d3e50 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -219,7 +219,7 @@ MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables sa MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame") MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file") MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player") -MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off") +MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server") #if defined(CONF_UPNP) MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.") From b898f8c7c204e035b4e8583331aa60ec418bc8b6 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 7 Jul 2020 20:28:50 +0200 Subject: [PATCH 06/37] Remove unused MySQL interface --- CMakeLists.txt | 4 - src/engine/server/sql_connector.cpp | 41 ----- src/engine/server/sql_connector.h | 47 ----- src/engine/server/sql_server.cpp | 223 ----------------------- src/engine/server/sql_server.h | 58 ------ src/engine/server/sql_string_helpers.cpp | 41 +---- src/engine/server/sql_string_helpers.h | 39 ---- 7 files changed, 3 insertions(+), 450 deletions(-) delete mode 100644 src/engine/server/sql_connector.cpp delete mode 100644 src/engine/server/sql_connector.h delete mode 100644 src/engine/server/sql_server.cpp delete mode 100644 src/engine/server/sql_server.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e98b1944..a1c6a4093 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1857,10 +1857,6 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server register.h server.cpp server.h - sql_connector.cpp - sql_connector.h - sql_server.cpp - sql_server.h sql_string_helpers.cpp sql_string_helpers.h upnp.cpp diff --git a/src/engine/server/sql_connector.cpp b/src/engine/server/sql_connector.cpp deleted file mode 100644 index a702d44d5..000000000 --- a/src/engine/server/sql_connector.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#if defined(CONF_SQL) - -#include - -#include "sql_connector.h" - -CSqlServer** CSqlConnector::ms_ppSqlReadServers = 0; -CSqlServer** CSqlConnector::ms_ppSqlWriteServers = 0; - -int CSqlConnector::ms_ReachableReadServer = 0; -int CSqlConnector::ms_ReachableWriteServer = 0; - -CSqlConnector::CSqlConnector() : -m_pSqlServer(0), -m_NumReadRetries(0), -m_NumWriteRetries(0) -{} - -bool CSqlConnector::ConnectSqlServer(bool ReadOnly) -{ - ReadOnly ? ++m_NumReadRetries : ++m_NumWriteRetries; - int& ReachableServer = ReadOnly ? ms_ReachableReadServer : ms_ReachableWriteServer; - int NumServers = ReadOnly ? CSqlServer::ms_NumReadServer : CSqlServer::ms_NumWriteServer; - - for (int i = ReachableServer, ID = ReachableServer; i < ReachableServer + NumServers && SqlServer(i % NumServers, ReadOnly); i++, ID = i % NumServers) - { - if (SqlServer(ID, ReadOnly) && SqlServer(ID, ReadOnly)->Connect()) - { - m_pSqlServer = SqlServer(ID, ReadOnly); - ReachableServer = ID; - return true; - } - if (SqlServer(ID, ReadOnly)) - dbg_msg("sql", "Warning: Unable to connect to Sql%sServer %d ('%s'), trying next...", ReadOnly ? "Read" : "Write", ID, SqlServer(ID, ReadOnly)->GetIP()); - } - dbg_msg("sql", "FATAL ERROR: No Sql%sServers available", ReadOnly ? "Read" : "Write"); - m_pSqlServer = 0; - return false; -} - -#endif diff --git a/src/engine/server/sql_connector.h b/src/engine/server/sql_connector.h deleted file mode 100644 index 4d24fdff5..000000000 --- a/src/engine/server/sql_connector.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef ENGINE_SERVER_SQL_CONNECTOR_H -#define ENGINE_SERVER_SQL_CONNECTOR_H - -#include "sql_server.h" - -enum -{ - MAX_SQLSERVERS=15 -}; - -// implementation to provide sqlservers -class CSqlConnector -{ -public: - CSqlConnector(); - - CSqlServer* SqlServer(int i, bool ReadOnly = true) { return ReadOnly ? ms_ppSqlReadServers[i] : ms_ppSqlWriteServers[i]; } - - // always returns the last connected sql-server - CSqlServer* SqlServer() { return m_pSqlServer; } - - static void SetReadServers(CSqlServer** ppReadServers) { ms_ppSqlReadServers = ppReadServers; } - static void SetWriteServers(CSqlServer** ppWriteServers) { ms_ppSqlWriteServers = ppWriteServers; } - - static void ResetReachable() { ms_ReachableReadServer = 0; ms_ReachableWriteServer = 0; } - - bool ConnectSqlServer(bool ReadOnly = true); - - bool MaxTriesReached(bool ReadOnly = true) { return ReadOnly ? m_NumReadRetries >= CSqlServer::ms_NumReadServer : m_NumWriteRetries >= CSqlServer::ms_NumWriteServer; } - -private: - - CSqlServer *m_pSqlServer; - static CSqlServer **ms_ppSqlReadServers; - static CSqlServer **ms_ppSqlWriteServers; - - static int ms_NumReadServer; - static int ms_NumWriteServer; - - static int ms_ReachableReadServer; - static int ms_ReachableWriteServer; - - int m_NumReadRetries; - int m_NumWriteRetries; -}; - -#endif diff --git a/src/engine/server/sql_server.cpp b/src/engine/server/sql_server.cpp deleted file mode 100644 index 285d59aba..000000000 --- a/src/engine/server/sql_server.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#if defined(CONF_SQL) - -#include -#include -#include - -#include "sql_server.h" - - -int CSqlServer::ms_NumReadServer = 0; -int CSqlServer::ms_NumWriteServer = 0; - -CSqlServer::CSqlServer(const char *pDatabase, const char *pPrefix, const char *pUser, const char *pPass, const char *pIp, int Port, lock *pGlobalLock, bool ReadOnly, bool SetUpDb) : - m_Port(Port), - m_SetUpDB(SetUpDb), - m_SqlLock(), - m_pGlobalLock(pGlobalLock) -{ - str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase)); - str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix)); - str_copy(m_aUser, pUser, sizeof(m_aUser)); - str_copy(m_aPass, pPass, sizeof(m_aPass)); - str_copy(m_aIp, pIp, sizeof(m_aIp)); - - m_pDriver = 0; - m_pConnection = 0; - m_pResults = 0; - m_pStatement = 0; - - ReadOnly ? ms_NumReadServer++ : ms_NumWriteServer++; -} - -CSqlServer::~CSqlServer() -{ - scope_lock LockScope(&m_SqlLock); - try - { - if (m_pResults) - delete m_pResults; - if (m_pConnection) - { - delete m_pConnection; - m_pConnection = 0; - } - dbg_msg("sql", "SQL connection disconnected"); - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "ERROR: No SQL connection: %s", e.what()); - } - catch (const std::exception& ex) - { - dbg_msg("sql", "ERROR: No SQL connection: %s", ex.what()); - } - catch (const std::string& ex) - { - dbg_msg("sql", "ERROR: No SQL connection: %s", ex.c_str()); - } - catch (...) - { - dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); - } -} - -bool CSqlServer::Connect() -{ - m_SqlLock.take(); - - if (m_pDriver != NULL && m_pConnection != NULL) - { - try - { - // Connect to specific database - m_pConnection->setSchema(m_aDatabase); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - } - catch (const std::exception& ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.what()); - } - catch (const std::string& ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.c_str()); - } - catch (...) - { - dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); - } - - m_SqlLock.release(); - dbg_msg("sql", "ERROR: SQL connection failed"); - return false; - } - - try - { - m_pDriver = 0; - m_pConnection = 0; - m_pStatement = 0; - - sql::ConnectOptionsMap connection_properties; - connection_properties["hostName"] = sql::SQLString(m_aIp); - connection_properties["port"] = m_Port; - connection_properties["userName"] = sql::SQLString(m_aUser); - connection_properties["password"] = sql::SQLString(m_aPass); - connection_properties["OPT_CONNECT_TIMEOUT"] = 10; - connection_properties["OPT_READ_TIMEOUT"] = 10; - connection_properties["OPT_WRITE_TIMEOUT"] = 20; - connection_properties["OPT_RECONNECT"] = true; - connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4"); - connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4"); - - // Create connection - { - scope_lock GlobalLockScope(m_pGlobalLock); - m_pDriver = get_driver_instance(); - } - m_pConnection = m_pDriver->connect(connection_properties); - - // Create Statement - m_pStatement = m_pConnection->createStatement(); - - // Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough - m_pStatement->execute("SET CHARACTER SET utf8mb4;"); - - if (m_SetUpDB) - { - char aBuf[128]; - // create database - str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase); - m_pStatement->execute(aBuf); - } - - // Connect to specific database - m_pConnection->setSchema(m_aDatabase); - dbg_msg("sql", "sql connection established"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - } - catch (const std::exception& ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.what()); - } - catch (const std::string& ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.c_str()); - } - catch (...) - { - dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); - } - - dbg_msg("sql", "ERROR: sql connection failed"); - m_SqlLock.release(); - return false; -} - -void CSqlServer::Disconnect() -{ - m_SqlLock.release(); -} - -bool CSqlServer::CreateTables() -{ - if (!Connect()) - return false; - - bool Success = false; - try - { - char aBuf[1024]; - - // create tables - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_race (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Time FLOAT DEFAULT 0, Server CHAR(4), cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, cp25 FLOAT DEFAULT 0, GameID VARCHAR(64), DDNet7 BOOL DEFAULT FALSE, KEY (Map, Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH); - executeSql(aBuf); - - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_teamrace (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Time FLOAT DEFAULT 0, ID VARBINARY(16) NOT NULL, GameID VARCHAR(64), DDNet7 BOOL DEFAULT FALSE, KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH); - executeSql(aBuf); - - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix); - executeSql(aBuf); - - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, SaveID VARCHAR(36) DEFAULT NULL, UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix); - executeSql(aBuf); - - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH); - executeSql(aBuf); - - dbg_msg("sql", "Tables were created successfully"); - Success = true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - } - - Disconnect(); - return Success; -} - -void CSqlServer::executeSql(const char *pCommand) -{ - m_pStatement->execute(pCommand); -} - -void CSqlServer::executeSqlQuery(const char *pQuery) -{ - if (m_pResults) - delete m_pResults; - - // set it to 0, so exceptions raised from executeQuery can not make m_pResults point to invalid memory - m_pResults = 0; - m_pResults = m_pStatement->executeQuery(pQuery); -} - -#endif diff --git a/src/engine/server/sql_server.h b/src/engine/server/sql_server.h deleted file mode 100644 index 3b6a39a8e..000000000 --- a/src/engine/server/sql_server.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef ENGINE_SERVER_SQL_SERVER_H -#define ENGINE_SERVER_SQL_SERVER_H - -#include - -#include - -#include -#include -#include - -class CSqlServer -{ -public: - CSqlServer(const char *pDatabase, const char *pPrefix, const char *pUser, const char *pPass, const char *pIp, int Port, lock *pGlobalLock, bool ReadOnly = true, bool SetUpDb = false); - ~CSqlServer(); - - bool Connect(); - void Disconnect(); - bool CreateTables(); - - void executeSql(const char *pCommand); - void executeSqlQuery(const char *pQuery); - - sql::ResultSet* GetResults() { return m_pResults; } - - const char* GetDatabase() { return m_aDatabase; } - const char* GetPrefix() { return m_aPrefix; } - const char* GetUser() { return m_aUser; } - const char* GetPass() { return m_aPass; } - const char* GetIP() { return m_aIp; } - int GetPort() { return m_Port; } - sql::Connection *Connection() const { return m_pConnection; } - - static int ms_NumReadServer; - static int ms_NumWriteServer; - -private: - sql::Driver *m_pDriver; - sql::Connection *m_pConnection; - sql::Statement *m_pStatement; - sql::ResultSet *m_pResults; - - // copy of config vars - char m_aDatabase[64]; - char m_aPrefix[64]; - char m_aUser[64]; - char m_aPass[64]; - char m_aIp[64]; - int m_Port; - - bool m_SetUpDB; - - lock m_SqlLock; - lock *m_pGlobalLock; -}; - -#endif diff --git a/src/engine/server/sql_string_helpers.cpp b/src/engine/server/sql_string_helpers.cpp index 961561c45..5de0c4bf2 100644 --- a/src/engine/server/sql_string_helpers.cpp +++ b/src/engine/server/sql_string_helpers.cpp @@ -1,8 +1,8 @@ +#include "sql_string_helpers.h" + +#include #include #include -#include - -#include "sql_string_helpers.h" void sqlstr::FuzzyString(char *pString, int size) { @@ -41,41 +41,6 @@ int sqlstr::EscapeLike(char *pDst, const char *pSrc, int DstSize) return DstPos; } -// anti SQL injection -void sqlstr::ClearString(char *pString, int size) -{ - char *newString = new char [size * 2 - 1]; - int pos = 0; - - for(int i = 0; i < size; i++) - { - if(pString[i] == '\\') - { - newString[pos++] = '\\'; - newString[pos++] = '\\'; - } - else if(pString[i] == '\'') - { - newString[pos++] = '\\'; - newString[pos++] = '\''; - } - else if(pString[i] == '"') - { - newString[pos++] = '\\'; - newString[pos++] = '"'; - } - else - { - newString[pos++] = pString[i]; - } - } - - newString[pos] = '\0'; - - str_copy(pString, newString, size); - delete [] newString; -} - void sqlstr::AgoTimeToString(int AgoTime, char *pAgoString) { char aBuf[20]; diff --git a/src/engine/server/sql_string_helpers.h b/src/engine/server/sql_string_helpers.h index edbaa1118..a827343e5 100644 --- a/src/engine/server/sql_string_helpers.h +++ b/src/engine/server/sql_string_helpers.h @@ -1,55 +1,16 @@ #ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H #define ENGINE_SERVER_SQL_STRING_HELPERS_H -#include - namespace sqlstr { void FuzzyString(char *pString, int size); -// anti SQL injection -void ClearString(char *pString, int size = 32); - // written number of added bytes int EscapeLike(char *pDst, const char *pSrc, int DstSize); void AgoTimeToString(int agoTime, char *pAgoString); -template -class CSqlString -{ -public: - CSqlString() {} - - CSqlString(const char *pStr) - { - str_copy(m_aString, pStr, size); - str_copy(m_aClearString, pStr, size); - ClearString(m_aClearString, sizeof(m_aClearString)); - } - - const char* Str() const { return m_aString; } - const char* ClrStr() const { return m_aClearString; } - - CSqlString& operator=(const char *pStr) - { - str_copy(m_aString, pStr, size); - str_copy(m_aClearString, pStr, size); - ClearString(m_aClearString, sizeof(m_aClearString)); - return *this; - } - - bool operator<(const CSqlString& other) const - { - return str_comp(m_aString, other.m_aString) < 0; - } - -private: - char m_aString[size]; - char m_aClearString[size * 2 - 1]; -}; - } #endif From 2c5f8922050dbc0b02c073d9a236872e1ad2f22c Mon Sep 17 00:00:00 2001 From: Zwelf Date: Wed, 8 Jul 2020 21:31:34 +0200 Subject: [PATCH 07/37] Apply suggestions from code review --- CMakeLists.txt | 2 +- .../server/databases/connection_pool.cpp | 35 ++++++++++--------- src/engine/server/databases/connection_pool.h | 11 +++--- src/engine/shared/config_variables.h | 2 +- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1c6a4093..50a5960ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,7 +435,7 @@ if(NOT(PYTHONINTERP_FOUND)) message(SEND_ERROR "You must install Python to compile DDNet") endif() if(NOT(SQLite3_FOUND)) - message(SEND_ERROR "You must install SQLite3 to compile the DDNet server") + message(SEND_ERROR "You must install SQLite3 to compile DDNet") endif() if(MYSQL AND NOT(MYSQL_FOUND)) diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index 98f8400b4..89df7511d 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -9,11 +9,11 @@ struct CSqlExecData { CSqlExecData( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + CDbConnectionPool::FRead pFunc, std::unique_ptr pThreadData, const char *pName); CSqlExecData( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + CDbConnectionPool::FWrite pFunc, std::unique_ptr pThreadData, const char *pName); ~CSqlExecData() {} @@ -25,8 +25,8 @@ struct CSqlExecData } m_Mode; union { - bool (*m_pWriteFunc) (IDbConnection*, const ISqlData *, bool); - bool (*m_pReadFunc) (IDbConnection*, const ISqlData *); + CDbConnectionPool::FRead m_pReadFunc; + CDbConnectionPool::FWrite m_pWriteFunc; } m_Ptr; std::unique_ptr m_pThreadData; @@ -34,25 +34,25 @@ struct CSqlExecData }; CSqlExecData::CSqlExecData( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + CDbConnectionPool::FRead pFunc, std::unique_ptr pThreadData, const char *pName) : m_Mode(READ_ACCESS), m_pThreadData(std::move(pThreadData)), m_pName(pName) { - m_Ptr.m_pReadFunc = pFuncPtr; + m_Ptr.m_pReadFunc = pFunc; } CSqlExecData::CSqlExecData( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + CDbConnectionPool::FWrite pFunc, std::unique_ptr pThreadData, const char *pName) : m_Mode(WRITE_ACCESS), m_pThreadData(std::move(pThreadData)), m_pName(pName) { - m_Ptr.m_pWriteFunc = pFuncPtr; + m_Ptr.m_pWriteFunc = pFunc; } CDbConnectionPool::CDbConnectionPool() : @@ -60,7 +60,7 @@ CDbConnectionPool::CDbConnectionPool() : FirstElem(0), LastElem(0) { - thread_init_and_detach(CDbConnectionPool::SqlWorker, this, "database worker thread"); + thread_init_and_detach(CDbConnectionPool::Worker, this, "database worker thread"); } CDbConnectionPool::~CDbConnectionPool() @@ -75,21 +75,21 @@ void CDbConnectionPool::RegisterDatabase(std::unique_ptr pDatabas } void CDbConnectionPool::Execute( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + FRead pFunc, std::unique_ptr pThreadData, const char *pName) { - m_aTasks[FirstElem++].reset(new CSqlExecData(pFuncPtr, std::move(pThreadData), pName)); + m_aTasks[FirstElem++].reset(new CSqlExecData(pFunc, std::move(pThreadData), pName)); FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); m_NumElem.signal(); } void CDbConnectionPool::ExecuteWrite( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + FWrite pFunc, std::unique_ptr pThreadData, const char *pName) { - m_aTasks[FirstElem++].reset(new CSqlExecData(pFuncPtr, std::move(pThreadData), pName)); + m_aTasks[FirstElem++].reset(new CSqlExecData(pFunc, std::move(pThreadData), pName)); FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); m_NumElem.signal(); } @@ -98,13 +98,13 @@ void CDbConnectionPool::OnShutdown() { } -void CDbConnectionPool::SqlWorker(void *pUser) +void CDbConnectionPool::Worker(void *pUser) { CDbConnectionPool *pThis = (CDbConnectionPool *)pUser; - pThis->SqlWorker(); + pThis->Worker(); } -void CDbConnectionPool::SqlWorker() +void CDbConnectionPool::Worker() { while(1) { @@ -158,7 +158,8 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pD if(pConnection->Connect() != IDbConnection::SUCCESS) return false; bool Success = false; - try { + try + { switch(pData->m_Mode) { case CSqlExecData::READ_ACCESS: diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h index 364c6fa4f..4eebc693f 100644 --- a/src/engine/server/databases/connection_pool.h +++ b/src/engine/server/databases/connection_pool.h @@ -20,6 +20,9 @@ public: ~CDbConnectionPool(); CDbConnectionPool& operator=(const CDbConnectionPool&) = delete; + typedef bool (*FRead)(IDbConnection *, const ISqlData *); + typedef bool (*FWrite)(IDbConnection *, const ISqlData *, bool); + enum Mode { READ, @@ -31,12 +34,12 @@ public: void RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode); void Execute( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *), + FRead pFunc, std::unique_ptr pSqlRequestData, const char *pName); // writes to WRITE_BACKUP server in case of failure void ExecuteWrite( - bool (*pFuncPtr) (IDbConnection *, const ISqlData *, bool), + FWrite pFunc, std::unique_ptr pSqlRequestData, const char *pName); @@ -45,8 +48,8 @@ public: private: std::vector> m_aapDbConnections[NUM_MODES]; - static void SqlWorker(void *pUser); - void SqlWorker(); + static void Worker(void *pUser); + void Worker(); bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, bool Failure); semaphore m_NumElem; diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 4d59d3e50..3d27fccb6 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -219,7 +219,7 @@ MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables sa MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame") MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file") MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player") -MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server") +MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet-server.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server") #if defined(CONF_UPNP) MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.") From 452017a58b6e444d95d586b18b5523cd64612553 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Thu, 9 Jul 2020 19:02:28 +0200 Subject: [PATCH 08/37] Move format string to CREATE TABLE into own file --- CMakeLists.txt | 1 + src/engine/server/databases/connection.cpp | 86 ++++++++++++++++++++++ src/engine/server/databases/connection.h | 66 ++--------------- src/engine/server/databases/mysql.cpp | 13 ++-- src/engine/server/databases/sqlite.cpp | 11 ++- 5 files changed, 105 insertions(+), 72 deletions(-) create mode 100644 src/engine/server/databases/connection.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 50a5960ac..91085269e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1844,6 +1844,7 @@ set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server antibot.h authmanager.cpp authmanager.h + databases/connection.cpp databases/connection.h databases/connection_pool.cpp databases/connection_pool.h diff --git a/src/engine/server/databases/connection.cpp b/src/engine/server/databases/connection.cpp new file mode 100644 index 000000000..80eb923a2 --- /dev/null +++ b/src/engine/server/databases/connection.cpp @@ -0,0 +1,86 @@ +#include "connection.h" + +#include + +void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, + "CREATE TABLE IF NOT EXISTS %s_race (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Name VARCHAR(%d) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Time FLOAT DEFAULT 0, " + "Server CHAR(4), " + "cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, " + "cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, " + "cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, " + "cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, " + "cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, " + "cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, " + "cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, " + "cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, " + "cp25 FLOAT DEFAULT 0, " + "GameID VARCHAR(64), " + "DDNet7 BOOL DEFAULT FALSE, " + "KEY (Map, Name)" + ") CHARACTER SET utf8mb4;", + GetPrefix(), MAX_NAME_LENGTH); +} + +void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, + "CREATE TABLE IF NOT EXISTS %s_teamrace (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Name VARCHAR(%d) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Time FLOAT DEFAULT 0, " + "ID VARBINARY(16) NOT NULL, " + "GameID VARCHAR(64), " + "DDNet7 BOOL DEFAULT FALSE, " + "KEY Map (Map)" + ") CHARACTER SET utf8mb4;", + GetPrefix(), MAX_NAME_LENGTH); +} + +void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, + "CREATE TABLE IF NOT EXISTS %s_maps (" + "Map VARCHAR(128) BINARY NOT NULL, " + "Server VARCHAR(32) BINARY NOT NULL, " + "Mapper VARCHAR(128) BINARY NOT NULL, " + "Points INT DEFAULT 0, " + "Stars INT DEFAULT 0, " + "Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + "UNIQUE KEY Map (Map)" + ") CHARACTER SET utf8mb4;", + GetPrefix()); +} + +void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, + "CREATE TABLE IF NOT EXISTS %s_saves (" + "Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, " + "Map VARCHAR(128) BINARY NOT NULL, " + "Code VARCHAR(128) BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Server CHAR(4), " + "DDNet7 BOOL DEFAULT FALSE, " + "SaveID VARCHAR(36) DEFAULT NULL, " + "UNIQUE KEY (Map, Code)" + ") CHARACTER SET utf8mb4;", + GetPrefix()); +} + +void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, + "CREATE TABLE IF NOT EXISTS %s_points (" + "Name VARCHAR(%d) BINARY NOT NULL, " + "Points INT DEFAULT 0, " + "UNIQUE KEY Name (Name)" + ") CHARACTER SET utf8mb4;", + GetPrefix(), MAX_NAME_LENGTH); +} diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 0d67e5f65..c58d03c95 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -54,67 +54,15 @@ public: // returns number of bytes read into the buffer virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0; -protected: +private: char m_aPrefix[64]; - static constexpr const char *m_pCreateRace = - "CREATE TABLE IF NOT EXISTS %s_race (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Name VARCHAR(%d) BINARY NOT NULL, " - "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " - "Time FLOAT DEFAULT 0, " - "Server CHAR(4), " - "cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, " - "cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, " - "cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, " - "cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, " - "cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, " - "cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, " - "cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, " - "cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, " - "cp25 FLOAT DEFAULT 0, " - "GameID VARCHAR(64), " - "DDNet7 BOOL DEFAULT FALSE, " - "KEY (Map, Name)" - ") CHARACTER SET utf8mb4;"; - static constexpr const char *m_pCreateTeamrace = - "CREATE TABLE IF NOT EXISTS %s_teamrace (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Name VARCHAR(%d) BINARY NOT NULL, " - "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " - "Time FLOAT DEFAULT 0, " - "ID VARBINARY(16) NOT NULL, " - "GameID VARCHAR(64), " - "DDNet7 BOOL DEFAULT FALSE, " - "KEY Map (Map)" - ") CHARACTER SET utf8mb4;"; - static constexpr const char *m_pCreateMaps = - "CREATE TABLE IF NOT EXISTS %s_maps (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Server VARCHAR(32) BINARY NOT NULL, " - "Mapper VARCHAR(128) BINARY NOT NULL, " - "Points INT DEFAULT 0, " - "Stars INT DEFAULT 0, " - "Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " - "UNIQUE KEY Map (Map)" - ") CHARACTER SET utf8mb4;"; - static constexpr const char *m_pCreateSaves = - "CREATE TABLE IF NOT EXISTS %s_saves (" - "Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, " - "Map VARCHAR(128) BINARY NOT NULL, " - "Code VARCHAR(128) BINARY NOT NULL, " - "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " - "Server CHAR(4), " - "DDNet7 BOOL DEFAULT FALSE, " - "SaveID VARCHAR(36) DEFAULT NULL, " - "UNIQUE KEY (Map, Code)" - ") CHARACTER SET utf8mb4;"; - static constexpr const char *m_pCreatePoints = - "CREATE TABLE IF NOT EXISTS %s_points (" - "Name VARCHAR(%d) BINARY NOT NULL, " - "Points INT DEFAULT 0, " - "UNIQUE KEY Name (Name)" - ") CHARACTER SET utf8mb4;"; +protected: + void FormatCreateRace(char *aBuf, unsigned int BufferSize); + void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize); + void FormatCreateMaps(char *aBuf, unsigned int BufferSize); + void FormatCreateSaves(char *aBuf, unsigned int BufferSize); + void FormatCreatePoints(char *aBuf, unsigned int BufferSize); }; #endif // ENGINE_SERVER_DATABASES_CONNECTION_H diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index d4e3c0f43..b86530e5f 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -2,7 +2,6 @@ #if defined(CONF_SQL) #include -#include #endif lock CMysqlConnection::m_SqlDriverLock; @@ -39,7 +38,7 @@ CMysqlConnection::~CMysqlConnection() CMysqlConnection *CMysqlConnection::Copy() { - return new CMysqlConnection(m_aDatabase, m_aPrefix, m_aUser, m_aPass, m_aIp, m_Port, m_Setup); + return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup); } IDbConnection::Status CMysqlConnection::Connect() @@ -118,15 +117,15 @@ IDbConnection::Status CMysqlConnection::Connect() m_pStmt->execute(aBuf); // Connect to specific database m_pConnection->setSchema(m_aDatabase); - str_format(aBuf, sizeof(aBuf), m_pCreateRace, GetPrefix(), MAX_NAME_LENGTH); + FormatCreateRace(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); - str_format(aBuf, sizeof(aBuf), m_pCreateTeamrace, GetPrefix(), MAX_NAME_LENGTH); + FormatCreateTeamrace(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); - str_format(aBuf, sizeof(aBuf), m_pCreateMaps, GetPrefix()); + FormatCreateMaps(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); - str_format(aBuf, sizeof(aBuf), m_pCreateSaves, GetPrefix()); + FormatCreateSaves(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); - str_format(aBuf, sizeof(aBuf), m_pCreatePoints, GetPrefix(), MAX_NAME_LENGTH); + FormatCreatePoints(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); m_Setup = false; } diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index 2d7105c22..007bb60d8 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -1,7 +1,6 @@ #include "sqlite.h" #include -#include #include #include @@ -51,19 +50,19 @@ IDbConnection::Status CSqliteConnection::Connect() if(m_Setup) { char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), m_pCreateRace, GetPrefix(), MAX_NAME_LENGTH); + FormatCreateRace(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; - str_format(aBuf, sizeof(aBuf), m_pCreateTeamrace, GetPrefix(), MAX_NAME_LENGTH); + FormatCreateTeamrace(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; - str_format(aBuf, sizeof(aBuf), m_pCreateMaps, GetPrefix()); + FormatCreateMaps(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; - str_format(aBuf, sizeof(aBuf), m_pCreateSaves, GetPrefix()); + FormatCreateSaves(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; - str_format(aBuf, sizeof(aBuf), m_pCreatePoints, GetPrefix(), MAX_NAME_LENGTH); + FormatCreatePoints(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; m_Setup = false; From 00da45191c15d2488f6446aa71881466248adc57 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Thu, 9 Jul 2020 19:18:18 +0200 Subject: [PATCH 09/37] Make CREATE TABLE compatible with SQLite --- src/engine/server/databases/connection.cpp | 58 +++++++++++----------- src/engine/server/databases/connection.h | 3 +- src/engine/server/databases/mysql.cpp | 2 +- src/engine/server/databases/mysql.h | 2 + src/engine/server/databases/sqlite.cpp | 2 +- src/engine/server/databases/sqlite.h | 2 + 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/engine/server/databases/connection.cpp b/src/engine/server/databases/connection.cpp index 80eb923a2..c8b32c5d2 100644 --- a/src/engine/server/databases/connection.cpp +++ b/src/engine/server/databases/connection.cpp @@ -6,8 +6,8 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_race (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Name VARCHAR(%d) BINARY NOT NULL, " + "Map VARCHAR(128) COLLATE %s NOT NULL, " + "Name VARCHAR(%d) COLLATE %s NOT NULL, " "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " "Time FLOAT DEFAULT 0, " "Server CHAR(4), " @@ -21,66 +21,64 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize) "cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, " "cp25 FLOAT DEFAULT 0, " "GameID VARCHAR(64), " - "DDNet7 BOOL DEFAULT FALSE, " - "KEY (Map, Name)" - ") CHARACTER SET utf8mb4;", - GetPrefix(), MAX_NAME_LENGTH); + "DDNet7 BOOL DEFAULT FALSE" + ");", + GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate()); } -void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize) +void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType) { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_teamrace (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Name VARCHAR(%d) BINARY NOT NULL, " + "Map VARCHAR(128) COLLATE %s NOT NULL, " + "Name VARCHAR(%d) COLLATE %s NOT NULL, " "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " "Time FLOAT DEFAULT 0, " - "ID VARBINARY(16) NOT NULL, " + "ID %s NOT NULL, " // VARBINARY(16) for MySQL and BLOB for SQLite "GameID VARCHAR(64), " - "DDNet7 BOOL DEFAULT FALSE, " - "KEY Map (Map)" - ") CHARACTER SET utf8mb4;", - GetPrefix(), MAX_NAME_LENGTH); + "DDNet7 BOOL DEFAULT FALSE" + ");", + GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType); } void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_maps (" - "Map VARCHAR(128) BINARY NOT NULL, " - "Server VARCHAR(32) BINARY NOT NULL, " - "Mapper VARCHAR(128) BINARY NOT NULL, " + "Map VARCHAR(128) COLLATE %s NOT NULL, " + "Server VARCHAR(32) COLLATE %s NOT NULL, " + "Mapper VARCHAR(128) COLLATE %s NOT NULL, " "Points INT DEFAULT 0, " "Stars INT DEFAULT 0, " "Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " - "UNIQUE KEY Map (Map)" - ") CHARACTER SET utf8mb4;", - GetPrefix()); + "PRIMARY KEY (Map)" + ");", + GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate()); } void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_saves (" - "Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, " - "Map VARCHAR(128) BINARY NOT NULL, " - "Code VARCHAR(128) BINARY NOT NULL, " + "Savegame TEXT COLLATE %s NOT NULL, " + "Map VARCHAR(128) COLLATE %s NOT NULL, " + "Code VARCHAR(128) COLLATE %s NOT NULL, " "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " "Server CHAR(4), " "DDNet7 BOOL DEFAULT FALSE, " "SaveID VARCHAR(36) DEFAULT NULL, " - "UNIQUE KEY (Map, Code)" - ") CHARACTER SET utf8mb4;", - GetPrefix()); + "PRIMARY KEY (Map, Code)" + ");", + GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate()); } void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "CREATE TABLE IF NOT EXISTS %s_points (" - "Name VARCHAR(%d) BINARY NOT NULL, " + "Name VARCHAR(%d) COLLATE %s NOT NULL, " "Points INT DEFAULT 0, " - "UNIQUE KEY Name (Name)" - ") CHARACTER SET utf8mb4;", - GetPrefix(), MAX_NAME_LENGTH); + "PRIMARY KEY (Name)" + ");", + GetPrefix(), MAX_NAME_LENGTH, BinaryCollate()); } diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index c58d03c95..9b14e7bc9 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -19,6 +19,7 @@ public: // returns the database prefix const char *GetPrefix() { return m_aPrefix; } + virtual const char *BinaryCollate() const = 0; enum Status { @@ -59,7 +60,7 @@ private: protected: void FormatCreateRace(char *aBuf, unsigned int BufferSize); - void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize); + void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType); void FormatCreateMaps(char *aBuf, unsigned int BufferSize); void FormatCreateSaves(char *aBuf, unsigned int BufferSize); void FormatCreatePoints(char *aBuf, unsigned int BufferSize); diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index b86530e5f..991394078 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -119,7 +119,7 @@ IDbConnection::Status CMysqlConnection::Connect() m_pConnection->setSchema(m_aDatabase); FormatCreateRace(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); - FormatCreateTeamrace(aBuf, sizeof(aBuf)); + FormatCreateTeamrace(aBuf, sizeof(aBuf), "VARBINARY(16)"); m_pStmt->execute(aBuf); FormatCreateMaps(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index 5cd5cb2a2..a9fd55fb3 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -28,6 +28,8 @@ public: virtual CMysqlConnection *Copy(); + virtual const char *BinaryCollate() const { return "utf8mb4_bin"; } + virtual Status Connect(); virtual void Disconnect(); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index 007bb60d8..f3cd29d4d 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -53,7 +53,7 @@ IDbConnection::Status CSqliteConnection::Connect() FormatCreateRace(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) return Status::ERROR; - FormatCreateTeamrace(aBuf, sizeof(aBuf)); + FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB"); if(!Execute(aBuf)) return Status::ERROR; FormatCreateMaps(aBuf, sizeof(aBuf)); diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index d080472ee..8e45c0ded 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -15,6 +15,8 @@ public: virtual CSqliteConnection *Copy(); + virtual const char *BinaryCollate() const { return "BINARY"; } + virtual Status Connect(); virtual void Disconnect(); From a9c09124084e347026fba2da34b8a92a2fac181f Mon Sep 17 00:00:00 2001 From: Zwelf Date: Thu, 9 Jul 2020 21:10:54 +0200 Subject: [PATCH 10/37] Sort cmake output alphabetical --- CMakeLists.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91085269e..a7d5a1fa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -317,12 +317,18 @@ endif() find_package(ZLIB) find_package(Crypto) find_package(Curl) +if(VIDEORECORDER) + find_package(FFMPEG) +endif() find_package(Freetype) if(DOWNLOAD_GTEST) find_package(Git) endif() find_package(GLEW) find_package(GTest) +if(UPNP) + find_package(Miniupnpc) +endif() if(MYSQL) find_package(MySQL) else() @@ -331,16 +337,10 @@ endif() find_package(Ogg) find_package(Opus) find_package(Opusfile) -if(UPNP) - find_package(Miniupnpc) -endif() find_package(Pnglite) find_package(PythonInterp 3) find_package(SDL2) find_package(SQLite3) -if(VIDEORECORDER) - find_package(FFMPEG) -endif() find_package(Threads) find_package(Wavpack) if(WEBSOCKETS) @@ -396,6 +396,9 @@ show_dependency_status("Curl" CURL) if(TARGET_OS AND TARGET_OS STREQUAL "mac") show_dependency_status("Dmg tools" DMGTOOLS) endif() +if(VIDEORECORDER) + show_dependency_status("FFmpeg" FFMPEG) +endif() show_dependency_status("Freetype" FREETYPE) if(DOWNLOAD_GTEST) show_dependency_status("Git" GIT) @@ -405,6 +408,9 @@ show_dependency_status("GTest" GTEST) if(TARGET_OS AND TARGET_OS STREQUAL "mac") show_dependency_status("Hdiutil" HDIUTIL) endif() +if(UPNP) + show_dependency_status("Miniupnpc" MINIUPNPC) +endif() if(MYSQL) show_dependency_status("MySQL" MYSQL) endif() @@ -412,16 +418,10 @@ show_dependency_status("Ogg" OGG) show_dependency_status("OpenSSL Crypto" CRYPTO) show_dependency_status("Opus" OPUS) show_dependency_status("Opusfile" OPUSFILE) -if(UPNP) - show_dependency_status("Miniupnpc" MINIUPNPC) -endif() show_dependency_status("Pnglite" PNGLITE) show_dependency_status("PythonInterp" PYTHONINTERP) show_dependency_status("SDL2" SDL2) show_dependency_status("SQLite3" SQLite3) -if(VIDEORECORDER) - show_dependency_status("FFmpeg" FFMPEG) -endif() show_dependency_status("Wavpack" WAVPACK) show_dependency_status("Zlib" ZLIB) if(WEBSOCKETS) From 89ee5944b4cf46f55b1abcf45d3cb5c193a3284d Mon Sep 17 00:00:00 2001 From: Zwelf Date: Thu, 9 Jul 2020 23:47:28 +0200 Subject: [PATCH 11/37] Add missing include --- src/game/server/score.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/server/score.h b/src/game/server/score.h index 0eab0cb40..c972693d3 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include From 040bec225873c695587c528967000bf0b1bb468b Mon Sep 17 00:00:00 2001 From: Zwelf Date: Thu, 9 Jul 2020 23:50:40 +0200 Subject: [PATCH 12/37] Fix FindSQLite3.cmake --- cmake/FindSQLite3.cmake | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cmake/FindSQLite3.cmake b/cmake/FindSQLite3.cmake index 1c46c3843..dc251d576 100644 --- a/cmake/FindSQLite3.cmake +++ b/cmake/FindSQLite3.cmake @@ -29,17 +29,12 @@ if(NOT SQLite3_FOUND) find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLite3_INCLUDEDIR) mark_as_advanced(SQLite3_INCLUDEDIR SQLite3_LIBRARY) - - if(SQLite3_FOUND) - set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDEDIR}) - if(SQLite3_LIBRARY) - set(SQLite3_LIBRARIES ${SQLite3_LIBRARY}) - else() - set(SQLite3_LIBRARIES) - endif() - endif() endif() if(SQLite3_FOUND) - is_bundled(SQLite3_BUNDLED "${SQLite3_LIBRARY}") + if(SQLite3_LIBRARY) + set(SQLite3_LIBRARIES ${SQLite3_LIBRARY}) + else() + set(SQLite3_LIBRARIES) + endif() endif() From 1677e1fed5632dc09bea1855bd0b9b49715f638e Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 11 Jul 2020 01:28:37 +0200 Subject: [PATCH 13/37] Gracefully shutdown database pool --- .../server/databases/connection_pool.cpp | 22 +++++++++++++++++++ src/engine/server/databases/connection_pool.h | 1 + src/engine/server/databases/mysql.cpp | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index 89df7511d..5641c9781 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -96,6 +96,22 @@ void CDbConnectionPool::ExecuteWrite( void CDbConnectionPool::OnShutdown() { + m_Shutdown.store(true); + m_NumElem.signal(); + int i = 0; + while(m_Shutdown.load()) + { + if (i > 600) { + dbg_msg("sql", "Waited 60 seconds for score-threads to complete, quitting anyway"); + break; + } + + // print a log about every two seconds + if (i % 20 == 0) + dbg_msg("sql", "Waiting for score-threads to complete (%ds)", i / 10); + ++i; + thread_sleep(100000); + } } void CDbConnectionPool::Worker(void *pUser) @@ -110,6 +126,12 @@ void CDbConnectionPool::Worker() { m_NumElem.wait(); auto pThreadData = std::move(m_aTasks[LastElem++]); + // work through all database jobs after OnShutdown is called before exiting the thread + if(pThreadData == nullptr) + { + m_Shutdown.store(false); + return; + } LastElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]); bool Success = false; switch(pThreadData->m_Mode) diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h index 4eebc693f..dbf16fc01 100644 --- a/src/engine/server/databases/connection_pool.h +++ b/src/engine/server/databases/connection_pool.h @@ -52,6 +52,7 @@ private: void Worker(); bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, bool Failure); + std::atomic_bool m_Shutdown; semaphore m_NumElem; int FirstElem; int LastElem; diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 991394078..803e9a879 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -34,6 +34,11 @@ CMysqlConnection::CMysqlConnection( CMysqlConnection::~CMysqlConnection() { +#if defined(CONF_SQL) + m_pStmt.release(); + m_pPreparedStmt.release(); + m_pConnection.release(); +#endif } CMysqlConnection *CMysqlConnection::Copy() From 17de42a947872aebd6cbaa6aa472aa04f32d0e24 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 12 Jul 2020 12:21:41 +0200 Subject: [PATCH 14/37] Implement locking in SQLite --- src/engine/server/databases/sqlite.cpp | 10 ++++++++++ src/engine/server/databases/sqlite.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index f3cd29d4d..c15227980 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -11,6 +11,7 @@ CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) : m_pDb(nullptr), m_pStmt(nullptr), m_Done(true), + m_Locked(false), m_InUse(false) { str_copy(m_aFilename, pFilename, sizeof(m_aFilename)); @@ -67,6 +68,7 @@ IDbConnection::Status CSqliteConnection::Connect() return Status::ERROR; m_Setup = false; } + m_Locked = false; return Status::SUCCESS; } @@ -80,10 +82,18 @@ void CSqliteConnection::Disconnect() void CSqliteConnection::Lock(const char *pTable) { + // locks the whole database read/write + Execute("BEGIN EXCLUSIVE TRANSACTION;"); + m_Locked = true; } void CSqliteConnection::Unlock() { + if(m_Locked) + { + Execute("COMMIT TRANSACTION;"); + m_Locked = false; + } } void CSqliteConnection::PrepareStatement(const char *pStmt) diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index 8e45c0ded..f68c05185 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -45,6 +45,7 @@ private: sqlite3 *m_pDb; sqlite3_stmt *m_pStmt; bool m_Done; // no more rows available for Step + bool m_Locked; // returns true, if the query succeded bool Execute(const char *pQuery); From eb4d77f0719f7770ad7f5c2000582ce5bea19d5b Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 12 Jul 2020 14:07:12 +0200 Subject: [PATCH 15/37] Make inserting ranks compatible with SQLite --- src/engine/server/databases/connection.h | 3 +++ src/engine/server/databases/mysql.cpp | 15 +++++++++++++++ src/engine/server/databases/mysql.h | 2 ++ src/engine/server/databases/sqlite.cpp | 15 +++++++++++++++ src/engine/server/databases/sqlite.h | 2 ++ src/game/server/score.cpp | 21 ++++++--------------- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 9b14e7bc9..17ad211d1 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -55,6 +55,9 @@ public: // returns number of bytes read into the buffer virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0; + // SQL statements, that can't be abstracted, has side effects to the result + virtual void AddPoints(const char *pPlayer, int Points) = 0; + private: char m_aPrefix[64]; diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 803e9a879..b19f7e169 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -273,3 +273,18 @@ int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) c return 0; #endif } + +void CMysqlConnection::AddPoints(const char *pPlayer, int Points) +{ + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "INSERT INTO %s_points(Name, Points) " + "VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE Points=Points+?;", + GetPrefix()); + PrepareStatement(aBuf); + BindString(1, pPlayer); + BindInt(2, Points); + BindInt(3, Points); + Step(); +} diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index a9fd55fb3..50d6c475b 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -49,6 +49,8 @@ public: virtual void GetString(int Col, char *pBuffer, int BufferSize) const; virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; + virtual void AddPoints(const char *pPlayer, int Points); + private: #if defined(CONF_SQL) std::unique_ptr m_pConnection; diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index c15227980..6e12da6b4 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -193,3 +193,18 @@ void CSqliteConnection::ExceptionOnError(int Result) throw std::runtime_error(sqlite3_errmsg(m_pDb)); } } + +void CSqliteConnection::AddPoints(const char *pPlayer, int Points) +{ + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "INSERT INTO %s_points(Name, Points) " + "VALUES (?, ?) " + "ON CONFLICT(Name) UPDATE SET Points=Points+?;", + GetPrefix()); + PrepareStatement(aBuf); + BindString(1, pPlayer); + BindInt(2, Points); + BindInt(3, Points); + Step(); +} diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index f68c05185..d31eb2442 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -37,6 +37,8 @@ public: // passing a negative buffer size is undefined behavior virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; + virtual void AddPoints(const char *pPlayer, int Points); + private: // copy of config vars char m_aFilename[512]; diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 615c22fdb..e7ab990bb 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -453,26 +453,16 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat if(pSqlServer->Step()) { int Points = pSqlServer->GetInt(1); + pSqlServer->AddPoints(pData->m_Name, Points); str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point%s for finishing this map!", Points, Points == 1 ? "" : "s"); - - str_format(aBuf, sizeof(aBuf), - "INSERT INTO %s_points(Name, Points) " - "VALUES (?, ?) " - "ON duplicate key " - "UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", - pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); - pSqlServer->BindString(1, pData->m_Name); - pSqlServer->BindInt(1, Points); - pSqlServer->Step(); } } - // save score + // save score. Can't fail, because no UNIQUE/PRIMARY KEY constrain is defined. str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_race(" + "INSERT INTO %s_race(" "Map, Name, Timestamp, Time, Server, " "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " @@ -481,7 +471,7 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " - "'%s', false);", + "?, false);", pSqlServer->GetPrefix(), pData->m_Time, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], @@ -491,12 +481,13 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], - pData->m_aCpCurrent[24], pData->m_GameUuid); + pData->m_aCpCurrent[24]); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Name); pSqlServer->BindString(3, pData->m_aTimestamp); pSqlServer->BindString(4, g_Config.m_SvSqlServerName); + pSqlServer->BindString(5, pData->m_GameUuid); pSqlServer->Step(); pData->m_pResult->m_Done = true; From a6df9be98e368fc26b96654548256e48c43c6455 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Mon, 13 Jul 2020 21:19:40 +0200 Subject: [PATCH 16/37] Make getting birthday compatible with SQLite --- src/game/server/score.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index e7ab990bb..cd3962a9b 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -206,22 +206,25 @@ bool CScore::LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGa // birthday check str_format(aBuf, sizeof(aBuf), - "SELECT YEAR(Current) - YEAR(Stamp) AS YearsAgo " - "FROM (" - "SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp " - "FROM %s_race " - "WHERE Name=?" - ") AS l " - "WHERE DAYOFMONTH(Current) = DAYOFMONTH(Stamp) AND MONTH(Current) = MONTH(Stamp) " - "AND YEAR(Current) > YEAR(Stamp);", + "SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp " + "FROM %s_race " + "WHERE Name = ?", pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_RequestingPlayer); - if(pSqlServer->Step()) + if(pSqlServer->Step() && !pSqlServer->IsNull(2)) { - int YearsAgo = pSqlServer->GetInt(1); - pData->m_pResult->m_Data.m_Info.m_Birthday = YearsAgo; + char aCurrent[TIMESTAMP_STR_LENGTH]; + pSqlServer->GetString(1, aCurrent, sizeof(aCurrent)); + char aStamp[TIMESTAMP_STR_LENGTH]; + pSqlServer->GetString(2, aStamp, sizeof(aStamp)); + int CurrentYear, CurrentMonth, CurrentDay; + int StampYear, StampMonth, StampDay; + if(sscanf(aCurrent, "%d-%d-%d", &CurrentYear, &CurrentMonth, &CurrentDay) == 3 + && sscanf(aStamp, "%d-%d-%d", &StampYear, &StampMonth, &StampDay) == 3 + && CurrentMonth == StampMonth && CurrentDay == StampDay) + pData->m_pResult->m_Data.m_Info.m_Birthday = CurrentYear - StampYear; } pData->m_pResult->m_Done = true; return true; From 26a2c91235f4b165e62d51266fc0427c81538f3c Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 14 Jul 2020 16:59:09 +0200 Subject: [PATCH 17/37] Trying to reconnect when ongoing MySQL connection failed --- src/engine/server/databases/mysql.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index b19f7e169..149d9148e 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -78,9 +78,7 @@ IDbConnection::Status CMysqlConnection::Connect() dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); } - m_InUse.store(false); - dbg_msg("sql", "ERROR: SQL connection failed"); - return Status::ERROR; + dbg_msg("sql", "ERROR: SQL connection failed, trying to reconnect"); } try @@ -158,6 +156,7 @@ IDbConnection::Status CMysqlConnection::Connect() { dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); } + m_InUse.store(false); #endif dbg_msg("sql", "ERROR: sql connection failed"); From e3a78a7a31eb25de1cf93a8f9a1e182f49041c63 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Wed, 15 Jul 2020 00:12:08 +0200 Subject: [PATCH 18/37] Add BindBlob and BindFloat function to IDbConnection interface --- src/engine/server/databases/connection.h | 2 ++ src/engine/server/databases/mysql.cpp | 20 ++++++++++++++++++++ src/engine/server/databases/mysql.h | 2 ++ src/engine/server/databases/sqlite.cpp | 14 ++++++++++++++ src/engine/server/databases/sqlite.h | 2 ++ 5 files changed, 40 insertions(+) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 17ad211d1..0285452fc 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -41,7 +41,9 @@ public: // PrepareStatement has to be called beforehand, virtual void BindString(int Idx, const char *pString) = 0; + virtual void BindBlob(int Idx, unsigned char *pBlob, int Size) = 0; virtual void BindInt(int Idx, int Value) = 0; + virtual void BindFloat(int Idx, float Value) = 0; // executes the query and returns if a result row exists and selects it // when called multiple times the next row is selected diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 149d9148e..998c0cfa9 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -4,6 +4,8 @@ #include #endif +#include + lock CMysqlConnection::m_SqlDriverLock; CMysqlConnection::CMysqlConnection( @@ -205,6 +207,16 @@ void CMysqlConnection::BindString(int Idx, const char *pString) #endif } +void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size) +{ +#if defined(CONF_SQL) + // copy blob into string + auto Blob = std::string(pBlob, pBlob+Size); + m_pPreparedStmt->setString(Idx, Blob); + m_NewQuery = true; +#endif +} + void CMysqlConnection::BindInt(int Idx, int Value) { #if defined(CONF_SQL) @@ -213,6 +225,14 @@ void CMysqlConnection::BindInt(int Idx, int Value) #endif } +void CMysqlConnection::BindFloat(int Idx, float Value) +{ +#if defined(CONF_SQL) + m_pPreparedStmt->setDouble(Idx, (double)Value); + m_NewQuery = true; +#endif +} + bool CMysqlConnection::Step() { #if defined(CONF_SQL) diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index 50d6c475b..b7c1a78be 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -39,7 +39,9 @@ public: virtual void PrepareStatement(const char *pStmt); virtual void BindString(int Idx, const char *pString); + virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); virtual void BindInt(int Idx, int Value); + virtual void BindFloat(int Idx, float Value); virtual bool Step(); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index 6e12da6b4..b8e4c6bff 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -118,6 +118,13 @@ void CSqliteConnection::BindString(int Idx, const char *pString) m_Done = false; } +void CSqliteConnection::BindBlob(int Idx, unsigned char *pBlob, int Size) +{ + int Result = sqlite3_bind_blob(m_pStmt, Idx, pBlob, Size, NULL); + ExceptionOnError(Result); + m_Done = false; +} + void CSqliteConnection::BindInt(int Idx, int Value) { int Result = sqlite3_bind_int(m_pStmt, Idx, Value); @@ -125,6 +132,13 @@ void CSqliteConnection::BindInt(int Idx, int Value) m_Done = false; } +void CSqliteConnection::BindFloat(int Idx, float Value) +{ + int Result = sqlite3_bind_double(m_pStmt, Idx, (double)Value); + ExceptionOnError(Result); + m_Done = false; +} + bool CSqliteConnection::Step() { if(m_Done) diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index d31eb2442..0d6763a37 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -26,7 +26,9 @@ public: virtual void PrepareStatement(const char *pStmt); virtual void BindString(int Idx, const char *pString); + virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); virtual void BindInt(int Idx, int Value); + virtual void BindFloat(int Idx, float Value); virtual bool Step(); From 044edbe42b2115d42a05b8a33d82b52501ace7a3 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Wed, 15 Jul 2020 00:13:54 +0200 Subject: [PATCH 19/37] Make storing teamrank compatible with SQLite --- src/engine/server/databases/mysql.cpp | 4 +- src/game/server/score.cpp | 101 +++++++++++++++++++------- 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 998c0cfa9..b106e8abb 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -174,7 +174,7 @@ void CMysqlConnection::Lock(const char *pTable) { #if defined(CONF_SQL) char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "lock tables %s write;", pTable); + str_format(aBuf, sizeof(aBuf), "LOCK TABLES %s;", pTable); m_pStmt->execute(aBuf); m_Locked = true; #endif @@ -185,7 +185,7 @@ void CMysqlConnection::Unlock() #if defined(CONF_SQL) if(m_Locked) { - m_pStmt->execute("unlock tables;"); + m_pStmt->execute("UNLOCK TABLES;"); m_Locked = false; } #endif diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index cd3962a9b..97d8fcad0 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -523,42 +523,91 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam { const CSqlTeamScoreData *pData = dynamic_cast(pGameData); - char aBuf[2300]; + char aBuf[512]; // get the names sorted in a tab separated string std::vector aNames; for(unsigned int i = 0; i < pData->m_Size; i++) aNames.push_back(pData->m_aNames[i]); + char aTable[512]; + str_format(aTable, sizeof(aTable), + "%s_teamrace WRITE, %s_teamrace AS r WRITE", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + pSqlServer->Lock(aTable); std::sort(aNames.begin(), aNames.end()); - char aSortedNames[2048] = {0}; - for(unsigned int i = 0; i < pData->m_Size; i++) - { - if(i != 0) - str_append(aSortedNames, "\t", sizeof(aSortedNames)); - str_append(aSortedNames, aNames[i].c_str(), sizeof(aSortedNames)); - } str_format(aBuf, sizeof(aBuf), - "SELECT l.ID, Time " - "FROM ((" // preselect teams with first name in team + "SELECT l.ID, Time, Name " + "FROM (" // preselect teams with first name in team "SELECT ID " "FROM %s_teamrace " "WHERE Map = ? AND Name = ? AND DDNet7 = false" - ") as l" - ") INNER JOIN %s_teamrace AS r ON l.ID = r.ID " - "GROUP BY ID " - "HAVING GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') = ?", + ") as l INNER JOIN %s_teamrace AS r ON l.ID = r.ID " + "ORDER BY l.ID, Name ", pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_aNames[0]); - pSqlServer->BindString(3, aSortedNames); - if (pSqlServer->Step()) + CUuid GameID; + bool SearchTeam = true; + bool FoundTeam = false; + float Time; + if(pSqlServer->Step()) + { + while(SearchTeam) + { + CUuid LastGameID; + pSqlServer->GetBlob(1, GameID.m_aData, sizeof(GameID.m_aData)); + Time = pSqlServer->GetFloat(2); + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(3, aName, sizeof(aName)); + if(str_comp(aName, aNames[0].c_str()) != 0) + { + dbg_msg("sql", "insert team rank logic error: " + "first team member from sql (%s) should be first name in array (%s), " + "because both are sorted by binary values", aName, aNames[0].c_str()); + return false; + } + if(!pSqlServer->Step()) + { + SearchTeam = false; + break; + } + pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); + // check if all team members are equal + for(unsigned int i = 1; i < pData->m_Size; i++) + { + if(GameID != LastGameID) + break; + pSqlServer->GetString(3, aName, sizeof(aName)); + if(str_comp(aName, aNames[i].c_str()) != 0) + { + dbg_msg("sql", "unequal '%s' != '%s'", aName, aNames[i].c_str()); + break; + } + else if(i == pData->m_Size - 1) + FoundTeam = true; + if(!pSqlServer->Step()) + { + SearchTeam = false; + break; + } + pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); + } + // skip to next team + while(SearchTeam && GameID == LastGameID) + { + if(pSqlServer->Step()) + pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); + else + SearchTeam = false; + FoundTeam = false; + } + } + } + if(FoundTeam) { - char aGameID[UUID_MAXSTRSIZE]; - pSqlServer->GetString(1, aGameID, sizeof(aGameID)); - float Time = pSqlServer->GetFloat(2); dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time); if(pData->m_Time < Time) { @@ -568,32 +617,30 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_aTimestamp); pSqlServer->BindString(2, pData->m_GameUuid); - pSqlServer->BindString(3, aGameID); + pSqlServer->BindBlob(3, GameID.m_aData, sizeof(GameID.m_aData)); pSqlServer->Step(); } } else { CUuid GameID = RandomUuid(); - char aGameID[UUID_MAXSTRSIZE]; - FormatUuid(GameID, aGameID, sizeof(aGameID)); - for(unsigned int i = 0; i < pData->m_Size; i++) { // if no entry found... create a new one str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " + "INSERT INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " "VALUES (?, ?, ?, %.2f, ?, ?, false);", pSqlServer->GetPrefix(), pData->m_Time); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_aNames[i]); pSqlServer->BindString(3, pData->m_aTimestamp); - pSqlServer->BindString(4, aGameID); + pSqlServer->BindBlob(4, GameID.m_aData, sizeof(GameID.m_aData)); pSqlServer->BindString(5, pData->m_GameUuid); pSqlServer->Step(); } } + pSqlServer->Unlock(); return true; } @@ -1242,7 +1289,7 @@ bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData char aBuf[65536]; char aTable[512]; - str_format(aTable, sizeof(aTable), "%s_saves", pSqlServer->GetPrefix()); + str_format(aTable, sizeof(aTable), "%s_saves WRITE", pSqlServer->GetPrefix()); pSqlServer->Lock(aTable); char Code[128] = {0}; @@ -1378,7 +1425,7 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData pData->m_pResult->m_Status = CScoreSaveResult::LOAD_FAILED; char aTable[512]; - str_format(aTable, sizeof(aTable), "%s_saves", pSqlServer->GetPrefix()); + str_format(aTable, sizeof(aTable), "%s_saves WRITE", pSqlServer->GetPrefix()); pSqlServer->Lock(aTable); { From c8d710926f12942db36e345f353a91d6d8afb387 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 15:18:55 +0200 Subject: [PATCH 20/37] Fix missing last line --- src/game/server/score.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 97d8fcad0..e4ae43f07 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -889,7 +889,7 @@ bool CScore::ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGame { for(Line = 1; Line < 6; Line++) // print { - bool Break = false; + bool Last = false; float Time = pSqlServer->GetFloat(2); int Rank = pSqlServer->GetInt(3); int TeamSize = pSqlServer->GetInt(4); @@ -906,14 +906,17 @@ bool CScore::ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGame str_append(aNames, " & ", sizeof(aNames)); if(!pSqlServer->Step()) { - Break = true; + Last = true; break; } } str_format(paMessages[Line], sizeof(paMessages[Line]), "%d. %s Team Time: %02d:%05.2f", Rank, aNames, (int)(Time/60), Time-((int)Time/60*60)); - if(Break) + if(Last) + { + Line++; break; + } } } From aee73b99dc1cfc8db5ef44f8cbd75e3a02ce7531 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 15:19:26 +0200 Subject: [PATCH 21/37] Rewrite insert teamrank making code reuse easier --- src/game/server/score.cpp | 87 +++++++++++++++++++++------------------ src/game/server/score.h | 20 +++++++++ 2 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index e4ae43f07..39beb4d86 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -51,6 +51,43 @@ void CScorePlayerResult::SetVariant(Variant v) } } +CTeamrank::CTeamrank() : + m_NumNames(0) +{ + for(int i = 0; i < MAX_CLIENTS; i++) + m_aaNames[i][0] = '\0'; + mem_zero(&m_TeamID.m_aData, sizeof(m_TeamID)); +} + +bool CTeamrank::NextSqlResult(IDbConnection *pSqlServer) +{ + pSqlServer->GetBlob(1, m_TeamID.m_aData, sizeof(m_TeamID.m_aData)); + pSqlServer->GetString(2, m_aaNames[0], sizeof(m_aaNames[0])); + m_NumNames = 1; + while(pSqlServer->Step()) + { + CUuid TeamID; + pSqlServer->GetBlob(1, TeamID.m_aData, sizeof(TeamID.m_aData)); + if(m_TeamID != TeamID) + return true; + pSqlServer->GetString(2, m_aaNames[m_NumNames], sizeof(m_aaNames[m_NumNames])); + m_NumNames++; + } + return false; +} + +bool CTeamrank::SamePlayers(const std::vector *aSortedNames) +{ + if(aSortedNames->size() != m_NumNames) + return false; + for(unsigned int i = 0; i < m_NumNames; i++) + { + if(str_comp(aSortedNames->at(i).c_str(), m_aaNames[i]) != 0) + return false; + } + return true; +} + std::shared_ptr CScore::NewSqlPlayerResult(int ClientID) { CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; @@ -537,7 +574,7 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam pSqlServer->Lock(aTable); std::sort(aNames.begin(), aNames.end()); str_format(aBuf, sizeof(aBuf), - "SELECT l.ID, Time, Name " + "SELECT l.ID, Name, Time " "FROM (" // preselect teams with first name in team "SELECT ID " "FROM %s_teamrace " @@ -550,60 +587,28 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam pSqlServer->BindString(2, pData->m_aNames[0]); CUuid GameID; - bool SearchTeam = true; bool FoundTeam = false; float Time; + CTeamrank Teamrank; if(pSqlServer->Step()) { + bool SearchTeam = true; while(SearchTeam) { - CUuid LastGameID; - pSqlServer->GetBlob(1, GameID.m_aData, sizeof(GameID.m_aData)); - Time = pSqlServer->GetFloat(2); - char aName[MAX_NAME_LENGTH]; - pSqlServer->GetString(3, aName, sizeof(aName)); - if(str_comp(aName, aNames[0].c_str()) != 0) + Time = pSqlServer->GetFloat(3); + SearchTeam = Teamrank.NextSqlResult(pSqlServer); + if(str_comp(Teamrank.m_aaNames[0], aNames[0].c_str()) != 0) { dbg_msg("sql", "insert team rank logic error: " "first team member from sql (%s) should be first name in array (%s), " - "because both are sorted by binary values", aName, aNames[0].c_str()); + "because both are sorted by binary values", Teamrank.m_aaNames[0], aNames[0].c_str()); return false; } - if(!pSqlServer->Step()) + if(Teamrank.SamePlayers(&aNames)) { - SearchTeam = false; + FoundTeam = true; break; } - pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); - // check if all team members are equal - for(unsigned int i = 1; i < pData->m_Size; i++) - { - if(GameID != LastGameID) - break; - pSqlServer->GetString(3, aName, sizeof(aName)); - if(str_comp(aName, aNames[i].c_str()) != 0) - { - dbg_msg("sql", "unequal '%s' != '%s'", aName, aNames[i].c_str()); - break; - } - else if(i == pData->m_Size - 1) - FoundTeam = true; - if(!pSqlServer->Step()) - { - SearchTeam = false; - break; - } - pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); - } - // skip to next team - while(SearchTeam && GameID == LastGameID) - { - if(pSqlServer->Step()) - pSqlServer->GetBlob(1, LastGameID.m_aData, sizeof(LastGameID.m_aData)); - else - SearchTeam = false; - FoundTeam = false; - } } } if(FoundTeam) diff --git a/src/game/server/score.h b/src/game/server/score.h index c972693d3..83ecbe3a4 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -248,6 +248,26 @@ struct CSqlTeamLoad : ISqlData int m_NumPlayer; }; +struct CTeamrank { + CUuid m_TeamID; + char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + unsigned int m_NumNames; + CTeamrank(); + + // Assumes that a database query equivalent to + // + // SELECT TeamID, Name [, ...] -- the order is important + // FROM record_teamrace + // ORDER BY TeamID, Name + // + // was executed and that the result line of the first team member is already selected. + // Afterwards the team member of the next team is selected. + // Returns if another team can be extracted + bool NextSqlResult(IDbConnection *pSqlServer); + + bool SamePlayers(const std::vector *aSortedNames); +}; + class CScore { CPlayerData m_aPlayerData[MAX_CLIENTS]; From 41b5ee9dd8cd0cd8ef581fac2ec2f6af07b881c9 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 15:36:16 +0200 Subject: [PATCH 22/37] Make `/teamrank` compatible with SQLite --- src/game/server/score.cpp | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 39beb4d86..05da79aa2 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -723,7 +723,7 @@ bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGame char aBuf[2400]; str_format(aBuf, sizeof(aBuf), - "SELECT GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') AS Names, COUNT(Name) AS NumNames, Time, Rank " + "SELECT l.ID, Name, Time, Rank " "FROM (" // teamrank score board "SELECT RANK() OVER w AS Rank, ID " "FROM %s_teamrace " @@ -737,8 +737,7 @@ bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGame "ORDER BY Time " "LIMIT 1" ") AS l ON TeamRank.ID = l.ID " - "INNER JOIN %s_teamrace AS r ON l.ID = r.ID " - "GROUP BY l.ID", + "INNER JOIN %s_teamrace AS r ON l.ID = r.ID ", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); @@ -747,28 +746,20 @@ bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGame if(pSqlServer->Step()) { - char aNames[512]; - pSqlServer->GetString(1, aNames, sizeof(aNames)); - int NumNames = pSqlServer->GetInt(2); float Time = pSqlServer->GetFloat(3); int Rank = pSqlServer->GetInt(4); + CTeamrank Teamrank; + Teamrank.NextSqlResult(pSqlServer); char aFormattedNames[512] = ""; - int StrPos = 0; - for(int Name = 0; Name < NumNames; Name++) + for(unsigned int Name = 0; Name < Teamrank.m_NumNames; Name++) { - int NameStart = StrPos; - while(aNames[StrPos] != '\t' && aNames[StrPos] != '\0') - StrPos++; - aNames[StrPos] = '\0'; + str_append(aFormattedNames, Teamrank.m_aaNames[Name], sizeof(aFormattedNames)); - str_append(aFormattedNames, &aNames[NameStart], sizeof(aFormattedNames)); - - if (Name < NumNames - 2) + if (Name < Teamrank.m_NumNames - 2) str_append(aFormattedNames, ", ", sizeof(aFormattedNames)); - else if (Name < NumNames - 1) + else if (Name < Teamrank.m_NumNames - 1) str_append(aFormattedNames, " & ", sizeof(aFormattedNames)); - StrPos++; } if(g_Config.m_SvHideScore) From d16bb5cc086b0b03f26763034e809dd1bceaf952 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 20:49:34 +0200 Subject: [PATCH 23/37] Make timestamps compatible with SQLite works for `/mapinfo`, `/load`, `/save`, `/times` --- src/engine/server/databases/connection.h | 6 +++ src/engine/server/databases/mysql.cpp | 5 ++ src/engine/server/databases/mysql.h | 2 + src/engine/server/databases/sqlite.cpp | 5 ++ src/engine/server/databases/sqlite.h | 2 + src/game/server/score.cpp | 61 +++++++++++++++--------- 6 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 0285452fc..0fb16366e 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -20,6 +20,12 @@ public: // returns the database prefix const char *GetPrefix() { return m_aPrefix; } virtual const char *BinaryCollate() const = 0; + // can be inserted into queries to convert a timestamp variable to the unix timestamp + virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) = 0; + // since MySQL automatically converts timestamps to utc, meanwhile sqlite code has to + // explicitly convert before inserting timestamps, NOTE: CURRENT_TIMESTAMP in SQLite is UTC by + // default and doesn't have to be converted + virtual const char *InsertTimestampAsUtc() const = 0; enum Status { diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index b106e8abb..784e186a0 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -48,6 +48,11 @@ CMysqlConnection *CMysqlConnection::Copy() return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup); } +void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp); +} + IDbConnection::Status CMysqlConnection::Connect() { #if defined(CONF_SQL) diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index b7c1a78be..f99cc816b 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -29,6 +29,8 @@ public: virtual CMysqlConnection *Copy(); virtual const char *BinaryCollate() const { return "utf8mb4_bin"; } + virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); + virtual const char *InsertTimestampAsUtc() const { return "?"; } virtual Status Connect(); virtual void Disconnect(); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index b8e4c6bff..cc0c0898c 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -25,6 +25,11 @@ CSqliteConnection::~CSqliteConnection() m_pDb = nullptr; } +void CSqliteConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) +{ + str_format(aBuf, BufferSize, "strftime('%%s', %s)", pTimestamp); +} + CSqliteConnection *CSqliteConnection::Copy() { return new CSqliteConnection(m_aFilename, m_Setup); diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index 0d6763a37..186c9fdbe 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -16,6 +16,8 @@ public: virtual CSqliteConnection *Copy(); virtual const char *BinaryCollate() const { return "BINARY"; } + virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); + virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; } virtual Status Connect(); virtual void Disconnect(); diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 05da79aa2..0e5264fa8 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -346,14 +346,19 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) str_copy(aMapPrefix, pData->m_Name, sizeof(aMapPrefix)); str_append(aMapPrefix, "%", sizeof(aMapPrefix)); + char aCurrentTimestamp[512]; + pSqlServer->ToUnixTimestamp("CURRENT_TIMESTAMP", aCurrentTimestamp, sizeof(aCurrentTimestamp)); + char aTimestamp[512]; + pSqlServer->ToUnixTimestamp("l.Timestamp", aTimestamp, sizeof(aTimestamp)); + char aBuf[1024]; str_format(aBuf, sizeof(aBuf), "SELECT l.Map, l.Server, Mapper, Points, Stars, " "(SELECT COUNT(Name) FROM %s_race WHERE Map = l.Map) AS Finishes, " "(SELECT COUNT(DISTINCT Name) FROM %s_race WHERE Map = l.Map) AS Finishers, " "(SELECT ROUND(AVG(Time)) FROM %s_race WHERE Map = l.Map) AS Average, " - "UNIX_TIMESTAMP(l.Timestamp) AS Stamp, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(l.Timestamp) AS Ago, " + "%s AS Stamp, " + "%s-%s AS Ago, " "(SELECT MIN(Time) FROM %s_race WHERE Map = l.Map AND Name = ?) AS OwnTime " "FROM (" "SELECT * FROM %s_maps " @@ -365,9 +370,9 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) "Map " "LIMIT 1" ") as l;", - pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), - pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), - pSqlServer->GetPrefix() + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + aTimestamp, aCurrentTimestamp, aTimestamp, + pSqlServer->GetPrefix(), pSqlServer->GetPrefix() ); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_RequestingPlayer); @@ -507,12 +512,12 @@ bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameDat "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " "GameID, DDNet7) " - "VALUES (?, ?, ?, %.2f, ?, " + "VALUES (?, ?, %s, %.2f, ?, " "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " "%.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, " "?, false);", - pSqlServer->GetPrefix(), pData->m_Time, + pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc(), pData->m_Time, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], @@ -634,8 +639,8 @@ bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGam // if no entry found... create a new one str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " - "VALUES (?, ?, ?, %.2f, ?, ?, false);", - pSqlServer->GetPrefix(), pData->m_Time); + "VALUES (?, ?, %s, %.2f, ?, ?, false);", + pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc(), pData->m_Time); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_aNames[i]); @@ -943,16 +948,20 @@ bool CScore::ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameDat int LimitStart = maximum(abs(pData->m_Offset)-1, 0); const char *pOrder = pData->m_Offset >= 0 ? "DESC" : "ASC"; + char aCurrentTimestamp[512]; + pSqlServer->ToUnixTimestamp("CURRENT_TIMESTAMP", aCurrentTimestamp, sizeof(aCurrentTimestamp)); + char aTimestamp[512]; + pSqlServer->ToUnixTimestamp("Timestamp", aTimestamp, sizeof(aTimestamp)); char aBuf[512]; - if(pData->m_Name[0] != '\0') // last 5 times of a player { str_format(aBuf, sizeof(aBuf), - "SELECT Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp " + "SELECT Time, (%s-%s) as Ago, %s as Stamp " "FROM %s_race " "WHERE Map = ? AND Name = ? " "ORDER BY Timestamp %s " "LIMIT ?, 5;", + aCurrentTimestamp, aTimestamp, aTimestamp, pSqlServer->GetPrefix(), pOrder); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); @@ -962,14 +971,12 @@ bool CScore::ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameDat else // last 5 times of server { str_format(aBuf, sizeof(aBuf), - "SELECT Time, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, " - "UNIX_TIMESTAMP(Timestamp) as Stamp, " - "Name " + "SELECT Time, (%s-%s) as Ago, %s as Stamp, Name " "FROM %s_race " "WHERE Map = ? " "ORDER BY Timestamp %s " "LIMIT ?, 5;", + aCurrentTimestamp, aTimestamp, aTimestamp, pSqlServer->GetPrefix(), pOrder); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); @@ -1321,8 +1328,8 @@ bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData if(UseCode) { str_format(aBuf, sizeof(aBuf), - "INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " - "VALUES (?, ?, ?, CURRENT_TIMESTAMP(), ?, ?, false)", + "INSERT INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " + "VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, false)", pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pSaveState); @@ -1435,14 +1442,18 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData sizeof(aSaveLike) - str_length(aSaveLike)); str_append(aSaveLike, "\t%", sizeof(aSaveLike)); + char aCurrentTimestamp[512]; + pSqlServer->ToUnixTimestamp("CURRENT_TIMESTAMP", aCurrentTimestamp, sizeof(aCurrentTimestamp)); + char aTimestamp[512]; + pSqlServer->ToUnixTimestamp("Timestamp", aTimestamp, sizeof(aTimestamp)); + char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "SELECT " - "Savegame, Server, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) AS Ago, " + "SELECT Savegame, Server, %s-%s AS Ago, " "(UNHEX(REPLACE(SaveID, '-',''))) AS SaveID " "FROM %s_saves " "where Code = ? AND Map = ? AND DDNet7 = false AND Savegame LIKE ?;", + aCurrentTimestamp, aTimestamp, pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Code); @@ -1537,12 +1548,18 @@ bool CScore::GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData sizeof(aSaveLike) - str_length(aSaveLike)); str_append(aSaveLike, "\t%", sizeof(aSaveLike)); + + char aCurrentTimestamp[512]; + pSqlServer->ToUnixTimestamp("CURRENT_TIMESTAMP", aCurrentTimestamp, sizeof(aCurrentTimestamp)); + char aMaxTimestamp[512]; + pSqlServer->ToUnixTimestamp("MAX(Timestamp)", aMaxTimestamp, sizeof(aMaxTimestamp)); + char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "SELECT COUNT(*) AS NumSaves, " - "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(MAX(Timestamp)) AS Ago " + "SELECT COUNT(*) AS NumSaves, %s-%s AS Ago " "FROM %s_saves " "WHERE Map = ? AND Savegame LIKE ?;", + aCurrentTimestamp, aMaxTimestamp, pSqlServer->GetPrefix()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_Map); From c6b1b08355d3b4a914cc498348d46b466879e672 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 21:13:40 +0200 Subject: [PATCH 24/37] Database specific collate utf8 nocase making and work --- src/engine/server/databases/connection.h | 2 ++ src/engine/server/databases/mysql.h | 1 + src/engine/server/databases/sqlite.h | 1 + src/game/server/score.cpp | 9 +++++---- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 0fb16366e..6ed6ce63e 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -26,6 +26,8 @@ public: // explicitly convert before inserting timestamps, NOTE: CURRENT_TIMESTAMP in SQLite is UTC by // default and doesn't have to be converted virtual const char *InsertTimestampAsUtc() const = 0; + // can be used in the context of `LIKE Map`, adds `? COLLATE` + virtual const char *CollateNocase() const = 0; enum Status { diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index f99cc816b..4c1e6e4b9 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -31,6 +31,7 @@ public: virtual const char *BinaryCollate() const { return "utf8mb4_bin"; } virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); virtual const char *InsertTimestampAsUtc() const { return "?"; } + virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; } virtual Status Connect(); virtual void Disconnect(); diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index 186c9fdbe..fda14f2fb 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -18,6 +18,7 @@ public: virtual const char *BinaryCollate() const { return "BINARY"; } virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; } + virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; } virtual Status Connect(); virtual void Disconnect(); diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 0e5264fa8..d88529df5 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -291,13 +291,13 @@ bool CScore::MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData) str_format(aBuf, sizeof(aBuf), "SELECT Map, Server " "FROM %s_maps " - "WHERE Map LIKE convert(? using utf8mb4) COLLATE utf8mb4_general_ci " + "WHERE Map LIKE %s " "ORDER BY " "CASE WHEN Map = ? THEN 0 ELSE 1 END, " "CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, " "LENGTH(Map), Map " "LIMIT 1;", - pSqlServer->GetPrefix()); + pSqlServer->GetPrefix(), pSqlServer->CollateNocase()); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, aFuzzyMap); pSqlServer->BindString(2, pData->m_Name); @@ -362,7 +362,7 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) "(SELECT MIN(Time) FROM %s_race WHERE Map = l.Map AND Name = ?) AS OwnTime " "FROM (" "SELECT * FROM %s_maps " - "WHERE Map LIKE convert(? using utf8mb4) COLLATE utf8mb4_general_ci " + "WHERE Map LIKE %s " "ORDER BY " "CASE WHEN Map = ? THEN 0 ELSE 1 END, " "CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, " @@ -372,7 +372,8 @@ bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) ") as l;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), aTimestamp, aCurrentTimestamp, aTimestamp, - pSqlServer->GetPrefix(), pSqlServer->GetPrefix() + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->CollateNocase() ); pSqlServer->PrepareStatement(aBuf); pSqlServer->BindString(1, pData->m_RequestingPlayer); From 3e1324dd0ad0765dc57d7d91fce87324a52d9884 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 18 Jul 2020 21:35:59 +0200 Subject: [PATCH 25/37] Add uuid parsing --- src/engine/shared/uuid_manager.cpp | 8 ++++++++ src/engine/shared/uuid_manager.h | 1 + src/game/server/score.cpp | 16 ++++++++-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/engine/shared/uuid_manager.cpp b/src/engine/shared/uuid_manager.cpp index a27f3482e..b4a69eb89 100644 --- a/src/engine/shared/uuid_manager.cpp +++ b/src/engine/shared/uuid_manager.cpp @@ -55,6 +55,14 @@ void FormatUuid(CUuid Uuid, char *pBuffer, unsigned BufferLength) p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); } +void ParseUuid(CUuid *pUuid, char *pBuffer) +{ + unsigned char *p = pUuid->m_aData; + sscanf(pBuffer, "%02hhX%02hhX%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX", + &p[0], &p[1], &p[2], &p[3], &p[4], &p[5], &p[6], &p[7], + &p[8], &p[9], &p[10], &p[11], &p[12], &p[13], &p[14], &p[15]); +} + bool CUuid::operator==(const CUuid& Other) { return mem_comp(this, &Other, sizeof(*this)) == 0; diff --git a/src/engine/shared/uuid_manager.h b/src/engine/shared/uuid_manager.h index e05ad2d3e..0e45cbda0 100644 --- a/src/engine/shared/uuid_manager.h +++ b/src/engine/shared/uuid_manager.h @@ -25,6 +25,7 @@ CUuid RandomUuid(); CUuid CalculateUuid(const char *pName); // The buffer length should be at least UUID_MAXSTRSIZE. void FormatUuid(CUuid Uuid, char *pBuffer, unsigned BufferLength); +void ParseUuid(CUuid *pUuid, char *pBuffer); struct CName { diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index d88529df5..1b3253d35 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -1450,8 +1450,7 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "SELECT Savegame, Server, %s-%s AS Ago, " - "(UNHEX(REPLACE(SaveID, '-',''))) AS SaveID " + "SELECT Savegame, Server, %s-%s AS Ago, SaveID " "FROM %s_saves " "where Code = ? AND Map = ? AND DDNet7 = false AND Savegame LIKE ?;", aCurrentTimestamp, aTimestamp, @@ -1483,17 +1482,18 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData g_Config.m_SvSaveGamesDelay - Since); goto end; } - if(pSqlServer->IsNull(4)) + + char aSaveID[UUID_MAXSTRSIZE]; + memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData)); + if(!pSqlServer->IsNull(4)) { - memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData)); - } - else - { - if(pSqlServer->GetBlob(4, pData->m_pResult->m_SaveID.m_aData, sizeof(pData->m_pResult->m_SaveID.m_aData)) != 16) + pSqlServer->GetString(4, aSaveID, sizeof(aSaveID)); + if(str_length(aSaveID) + 1 != UUID_MAXSTRSIZE) { strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: SaveID corrupted"); goto end; } + ParseUuid(&pData->m_pResult->m_SaveID, aSaveID); } char aSaveString[65536]; From d71bf5cef1afe7f5ac9e0acc1afc8494a5dd0079 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 19 Jul 2020 12:22:40 +0200 Subject: [PATCH 26/37] Use DB write server for /load --- src/game/server/score.cpp | 4 ++-- src/game/server/score.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 1b3253d35..80b155484 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -1423,10 +1423,10 @@ void CScore::LoadTeam(const char* Code, int ClientID) Tmp->m_NumPlayer++; } } - m_pPool->Execute(LoadTeamThread, std::move(Tmp), "load team"); + m_pPool->ExecuteWrite(LoadTeamThread, std::move(Tmp), "load team"); } -bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) { const CSqlTeamLoad *pData = dynamic_cast(pGameData); pData->m_pResult->m_Status = CScoreSaveResult::LOAD_FAILED; diff --git a/src/game/server/score.h b/src/game/server/score.h index 83ecbe3a4..305f1d11b 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -291,7 +291,7 @@ class CScore static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData); static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); - static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); From e483011df66ab34062e29f1008abc4fee2696046 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 21 Jul 2020 10:51:25 +0200 Subject: [PATCH 27/37] Add script to move SQLite ranks to MySQL --- scripts/move_sqlite.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100755 scripts/move_sqlite.py diff --git a/scripts/move_sqlite.py b/scripts/move_sqlite.py new file mode 100755 index 000000000..01e7bcf0b --- /dev/null +++ b/scripts/move_sqlite.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# This script is intended to be called automatically every day (e.g. via cron). +# It only output stuff if new ranks have to be inserted. Therefore the output +# may be redirected to email notifying about manual action to transfer the +# ranks to MySQL. +# +# Configure cron as the user running the DDNet-Server processes +# +# $ crontab -e +# 30 5 * * * /path/to/this/script/move_sqlite.py --from /path/to/ddnet-server.sqlite +# +# Afterwards configure a MTA (e.g. postfix) and the users email address. + +import sqlite3 +import argparse +from time import strftime +import os + +def sqlite_num_transfer(conn, table): + c = conn.cursor() + c.execute('SELECT COUNT(*) FROM {}'.format(table)) + num = c.fetchone()[0] + return num + +def transfer(file_from, file_to): + conn_to = sqlite3.connect(file_to, isolation_level='EXCLUSIVE') + cursor_to = conn_to.cursor() + + conn_from = sqlite3.connect(file_from, isolation_level='EXCLUSIVE') + for line in conn_from.iterdump(): + cursor_to.execute(line) + print(line) + cursor_to.close() + conn_to.commit() + conn_to.close() + + cursor_from = conn_from.cursor() + cursor_from.execute('DELETE FROM record_race') + cursor_from.execute('DELETE FROM record_teamrace') + cursor_from.execute('DELETE FROM record_saves') + cursor_from.close() + conn_from.commit() + conn_from.close() + +def main(): + default_output = 'ddnet-server-' + strftime('%Y-%m-%d') + '.sqlite' + parser = argparse.ArgumentParser( + description='Move DDNet ranks, teamranks and saves from a possible active SQLite3 to a new one', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--from', '-f', dest='f', + default='ddnet-server.sqlite', + help='Input file where ranks are deleted from when moved successfully (default: ddnet-server.sqlite)') + parser.add_argument('--to', '-t', + default=default_output, + help='Output file where ranks are saved adds current date by default') + args = parser.parse_args() + + conn = sqlite3.connect(args.f) + num_ranks = sqlite_num_transfer(conn, 'record_race') + num_teamranks = sqlite_num_transfer(conn, 'record_teamrace') + num_saves = sqlite_num_transfer(conn, 'record_saves') + num = num_ranks + num_teamranks + num_saves + conn.close() + if num == 0: + return + + print('{} new entries in backup database found ({} ranks, {} teamranks, {} saves'.format(num, num_ranks, num_teamranks, num_saves)) + print('Moving entries from {} to {}'.format( + os.path.abspath(args.f), + os.path.abspath(args.to))) + sql_file = 'ddnet-server-' + strftime('%Y-%m-%d') + '.sql' + print("You can use the following commands to import the entries to MySQL (use sed 's/record_/prefix_/' for other database prefixes):") + print() + print((" echo '.dump --preserve-rowids' | sqlite3 {} | " + # including rowids, this forces sqlite to name all columns in each INSERT statement + "grep -E '^INSERT INTO record_(race|teamrace|saves)' | " + # filter out inserts + "sed -e 's/rowid,//' -e 's/VALUES([0-9]*,/VALUES(/' > {}") # filter out rowids again + .format(os.path.abspath(args.to), sql_file)) + print(" mysql -u teeworlds -p'PW2' teeworlds < {}".format(sql_file)) + print() + print("When the ranks are transfered successfully to mysql {} and {} can be removed".format( + os.path.abspath(args.f), os.path.abspath(args.to))) + print() + print("Log of the transfer:") + print() + + transfer(args.f, args.to) + +if __name__ == '__main__': + main() From 7834c2c1d88d592f14af17b5d18ef7cedcc9f505 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Tue, 21 Jul 2020 19:22:06 +0200 Subject: [PATCH 28/37] Add file score import script by heinrich5991 --- scripts/import_file_score.py | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 scripts/import_file_score.py diff --git a/scripts/import_file_score.py b/scripts/import_file_score.py new file mode 100755 index 000000000..72564a03d --- /dev/null +++ b/scripts/import_file_score.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +from collections import namedtuple +from decimal import Decimal +import os.path +import re +import sqlite3 +import sys + +def chunks(l, n): + for i in range(0, len(l), n): + yield l[i:i+n] + +class Record(namedtuple('Record', 'name time checkpoints')): + @staticmethod + def parse(lines): + if len(lines) != 3: + raise ValueError("wrong amount of lines for record") + name = lines[0] + time = Decimal(lines[1]) + checkpoints_str = lines[2].split(' ') + if len(checkpoints_str) != 26 or checkpoints_str[25] != "": + raise ValueError("wrong amount of checkpoint times: {}".format(len(checkpoints_str))) + checkpoints_str = checkpoints_str[:25] + checkpoints = tuple(Decimal(c) for c in checkpoints_str) + return Record(name=name, time=time, checkpoints=checkpoints) + + def unparse(self): + return "\n".join([self.name, str(self.time), " ".join([str(cp) for cp in self.checkpoints] + [""]), ""]) + +def read_records(file): + contents = file.read().splitlines() + return [Record.parse(c) for c in chunks(contents, 3)] + +MAP_RE=re.compile(r"^(?P.*)_record\.dtb$") +def main(): + import argparse + p = argparse.ArgumentParser(description="Merge multiple DDNet race database files", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + p.add_argument("--out", default="ddnet-server.sqlite", help="Output SQLite database") + p.add_argument("in_", metavar="IN", nargs='+', help="Text score databases to import; must have the format MAPNAME_record.dtb") + p.add_argument("--dry-run", "-n", action='store_true', help="Don't write out the resulting SQLite database") + p.add_argument("--stats", action='store_true', help="Display some stats at the end of the import process") + args = p.parse_args() + + records = {} + for in_ in args.in_: + m = MAP_RE.match(os.path.basename(in_)) + if not m: + raise ValueError("Invalid text score database name, does not end in '_record.dtb': {}".format(in_)) + map = m.group("map") + if map in records: + raise ValueError("Two text score databases refer to the same map: {}".format(in_)) + with open(in_) as f: + records[map] = read_records(f) + + if not args.dry_run: + conn = sqlite3.connect(args.out) + c = conn.cursor() + c.execute("CREATE TABLE IF NOT EXISTS record_race (" + "Map VARCHAR(128) COLLATE BINARY NOT NULL, " + "Name VARCHAR(16) COLLATE BINARY NOT NULL, " + "Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + "Time FLOAT DEFAULT 0, " + "Server CHAR(4), " + + "".join("cp{} FLOAT DEFAULT 0, ".format(i + 1) for i in range(25)) + + "GameID VARCHAR(64), " + "DDNet7 BOOL DEFAULT FALSE" + ");"); + c.executemany( + "INSERT INTO record_race (Map, Name, Time, Server, " + + "".join("cp{}, ".format(i + 1) for i in range(25)) + + "GameID, DDNet7) " + + "VALUES ({})".format(",".join("?" * 31)), + [(map, r.name, float(r.time), "TEXT", *[float(c) for c in r.checkpoints], None, False) for map in records for r in records[map]] + ) + conn.commit() + conn.close() + + if args.stats: + print("Number of imported text databases: {}".format(len(records)), file=sys.stderr) + print("Number of imported ranks: {}".format(sum(len(r) for r in records.values()), file=sys.stderr)) + +if __name__ == '__main__': + sys.exit(main()) From 9e1979f5618f031241e516c8d8120452412d3cf5 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Mon, 3 Aug 2020 13:11:52 +0200 Subject: [PATCH 29/37] Rename Database connect result from ERROR to FAILURE --- src/engine/server/databases/connection.h | 2 +- src/engine/server/databases/mysql.cpp | 6 +++--- src/engine/server/databases/sqlite.cpp | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index 6ed6ce63e..f3f60bbf9 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -33,7 +33,7 @@ public: { IN_USE, SUCCESS, - ERROR, + FAILURE, }; // tries to allocate the connection from the pool established virtual Status Connect() = 0; diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 784e186a0..b86a9a07c 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -85,7 +85,7 @@ IDbConnection::Status CMysqlConnection::Connect() dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); } - dbg_msg("sql", "ERROR: SQL connection failed, trying to reconnect"); + dbg_msg("sql", "FAILURE: SQL connection failed, trying to reconnect"); } try @@ -166,8 +166,8 @@ IDbConnection::Status CMysqlConnection::Connect() m_InUse.store(false); #endif - dbg_msg("sql", "ERROR: sql connection failed"); - return Status::ERROR; + dbg_msg("sql", "FAILURE: sql connection failed"); + return Status::FAILURE; } void CMysqlConnection::Disconnect() diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index cc0c0898c..106b158c8 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -47,7 +47,7 @@ IDbConnection::Status CSqliteConnection::Connect() if(Result != SQLITE_OK) { dbg_msg("sql", "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb)); - return Status::ERROR; + return Status::FAILURE; } // wait for database to unlock so we don't have to handle SQLITE_BUSY errors @@ -58,19 +58,19 @@ IDbConnection::Status CSqliteConnection::Connect() char aBuf[1024]; FormatCreateRace(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) - return Status::ERROR; + return Status::FAILURE; FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB"); if(!Execute(aBuf)) - return Status::ERROR; + return Status::FAILURE; FormatCreateMaps(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) - return Status::ERROR; + return Status::FAILURE; FormatCreateSaves(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) - return Status::ERROR; + return Status::FAILURE; FormatCreatePoints(aBuf, sizeof(aBuf)); if(!Execute(aBuf)) - return Status::ERROR; + return Status::FAILURE; m_Setup = false; } m_Locked = false; From 0fa13f6bf9daab64e6924453c3c7f6faadb320f9 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Mon, 3 Aug 2020 16:07:36 +0200 Subject: [PATCH 30/37] Fix include directories for SQLite by heinrich5991 --- CMakeLists.txt | 2 +- cmake/FindSQLite3.cmake | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7d5a1fa9..18dab8390 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2510,7 +2510,7 @@ foreach(target ${TARGETS_OWN}) target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src) target_include_directories(${target} PRIVATE src) target_compile_definitions(${target} PRIVATE $<$:CONF_DEBUG>) - target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_include_directories(${target} PRIVATE ${SQLite3_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) target_compile_definitions(${target} PRIVATE GLEW_STATIC) if(CRYPTO_FOUND) target_compile_definitions(${target} PRIVATE CONF_OPENSSL) diff --git a/cmake/FindSQLite3.cmake b/cmake/FindSQLite3.cmake index dc251d576..722a96ed9 100644 --- a/cmake/FindSQLite3.cmake +++ b/cmake/FindSQLite3.cmake @@ -26,15 +26,12 @@ if(NOT SQLite3_FOUND) ) include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLite3_INCLUDEDIR) - + find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLite3_INCLUDEDIR SQLite3_LIBRARY) mark_as_advanced(SQLite3_INCLUDEDIR SQLite3_LIBRARY) endif() if(SQLite3_FOUND) - if(SQLite3_LIBRARY) - set(SQLite3_LIBRARIES ${SQLite3_LIBRARY}) - else() - set(SQLite3_LIBRARIES) - endif() + is_bundled(SQLite3_BUNDLED "${SQLite3_LIBRARY}") + set(SQLite3_LIBRARIES ${SQLite3_LIBRARY}) + set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDEDIR}) endif() From b2cf3cafc5f5074e1dfaa77e94bc8a007b0b453e Mon Sep 17 00:00:00 2001 From: Zwelf Date: Mon, 3 Aug 2020 16:18:22 +0200 Subject: [PATCH 31/37] Implement dump_sqlserver --- src/engine/server/databases/connection.h | 3 ++ .../server/databases/connection_pool.cpp | 10 ++++++ src/engine/server/databases/connection_pool.h | 4 +++ src/engine/server/databases/mysql.cpp | 10 ++++++ src/engine/server/databases/mysql.h | 1 + src/engine/server/databases/sqlite.cpp | 12 +++++++ src/engine/server/databases/sqlite.h | 1 + src/engine/server/server.cpp | 32 ++++++------------- 8 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/engine/server/databases/connection.h b/src/engine/server/databases/connection.h index f3f60bbf9..b1467ef18 100644 --- a/src/engine/server/databases/connection.h +++ b/src/engine/server/databases/connection.h @@ -3,6 +3,8 @@ #include +class IConsole; + // can hold one PreparedStatement with Results class IDbConnection { @@ -13,6 +15,7 @@ public: } virtual ~IDbConnection() {} IDbConnection& operator=(const IDbConnection&) = delete; + virtual void Print(IConsole *pConsole, const char *Mode) = 0; // copies the credentials, not the active connection virtual IDbConnection *Copy() = 0; diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index 5641c9781..b601c3656 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -1,5 +1,6 @@ #include "connection_pool.h" +#include #if defined(CONF_SQL) #include #endif @@ -67,6 +68,15 @@ CDbConnectionPool::~CDbConnectionPool() { } +void CDbConnectionPool::Print(IConsole *pConsole, Mode DatabaseMode) +{ + const char *ModeDesc[] = {"Read", "Write", "WriteBackup"}; + for(unsigned int i = 0; i < m_aapDbConnections[DatabaseMode].size(); i++) + { + m_aapDbConnections[DatabaseMode][i]->Print(pConsole, ModeDesc[DatabaseMode]); + } +} + void CDbConnectionPool::RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode) { if(DatabaseMode < 0 || NUM_MODES <= DatabaseMode) diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h index dbf16fc01..40c4510ab 100644 --- a/src/engine/server/databases/connection_pool.h +++ b/src/engine/server/databases/connection_pool.h @@ -13,6 +13,8 @@ struct ISqlData virtual ~ISqlData() {}; }; +class IConsole; + class CDbConnectionPool { public: @@ -31,6 +33,8 @@ public: NUM_MODES, }; + void Print(IConsole *pConsole, Mode DatabaseMode); + void RegisterDatabase(std::unique_ptr pDatabase, Mode DatabaseMode); void Execute( diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index b86a9a07c..3fbb20764 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -1,5 +1,6 @@ #include "mysql.h" +#include #if defined(CONF_SQL) #include #endif @@ -43,6 +44,15 @@ CMysqlConnection::~CMysqlConnection() #endif } +void CMysqlConnection::Print(IConsole *pConsole, const char *Mode) +{ + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' Pass: '%s' IP: <{'%s'}> Port: %d", + Mode, m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); +} + CMysqlConnection *CMysqlConnection::Copy() { return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup); diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index 4c1e6e4b9..0c4d09876 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -25,6 +25,7 @@ public: int Port, bool Setup); virtual ~CMysqlConnection(); + virtual void Print(IConsole *pConsole, const char *Mode); virtual CMysqlConnection *Copy(); diff --git a/src/engine/server/databases/sqlite.cpp b/src/engine/server/databases/sqlite.cpp index 106b158c8..aac948983 100644 --- a/src/engine/server/databases/sqlite.cpp +++ b/src/engine/server/databases/sqlite.cpp @@ -1,6 +1,7 @@ #include "sqlite.h" #include +#include #include #include @@ -25,6 +26,17 @@ CSqliteConnection::~CSqliteConnection() m_pDb = nullptr; } + +void CSqliteConnection::Print(IConsole *pConsole, const char *Mode) +{ + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "SQLite-%s: DB: '%s'", + Mode, m_aFilename); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); +} + + void CSqliteConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "strftime('%%s', %s)", pTimestamp); diff --git a/src/engine/server/databases/sqlite.h b/src/engine/server/databases/sqlite.h index fda14f2fb..e3c43efe3 100644 --- a/src/engine/server/databases/sqlite.h +++ b/src/engine/server/databases/sqlite.h @@ -12,6 +12,7 @@ class CSqliteConnection : public IDbConnection public: CSqliteConnection(const char *pFilename, bool Setup); virtual ~CSqliteConnection(); + virtual void Print(IConsole *pConsole, const char *Mode); virtual CSqliteConnection *Copy(); diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 86039638e..cdf4cba1a 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -3127,42 +3127,28 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(5), pResult->GetInteger(6)); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); - if(SetUpDb) - { - /* TODO - if(!pSqlServers->CreateTables()) - pSelf->SetErrorShutdown("database create tables failed"); - */ - } pSelf->DbPool()->RegisterDatabase(std::move(pSqlServers), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE); } void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData) { - /* TODO CServer *pSelf = (CServer *)pUserData; - bool ReadOnly; - if (str_comp_nocase(pResult->GetString(0), "w") == 0) - ReadOnly = false; - else if (str_comp_nocase(pResult->GetString(0), "r") == 0) - ReadOnly = true; + if(str_comp_nocase(pResult->GetString(0), "w") == 0) + { + pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::WRITE); + pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::WRITE_BACKUP); + } + else if(str_comp_nocase(pResult->GetString(0), "r") == 0) + { + pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::READ); + } else { pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer"); return; } - CSqlServer** apSqlServers = ReadOnly ? pSelf->m_apSqlReadServers : pSelf->m_apSqlWriteServers; - - for (int i = 0; i < MAX_SQLSERVERS; i++) - if (apSqlServers[i]) - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "SQL-%s %d: DB: '%s' Prefix: '%s' User: '%s' Pass: '%s' IP: <{'%s'}> Port: %d", ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), apSqlServers[i]->GetPass(), apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); - } - */ } void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) From 61ffd88f346f4a1c9cc134a7e54714928dc74d43 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sat, 8 Aug 2020 18:38:38 +0200 Subject: [PATCH 32/37] Reformulate `#if not defined()` because MSVC errors on this expression --- src/engine/server/databases/mysql.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 3fbb20764..ab2543f47 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -30,7 +30,7 @@ CMysqlConnection::CMysqlConnection( str_copy(m_aUser, pUser, sizeof(m_aUser)); str_copy(m_aPass, pPass, sizeof(m_aPass)); str_copy(m_aIp, pIp, sizeof(m_aIp)); -#if not defined(CONF_SQL) +#ifndef CONF_SQL dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time"); #endif } From 20e8bfd12aaa5a055c7f2d5f8956198578140f32 Mon Sep 17 00:00:00 2001 From: def Date: Sun, 9 Aug 2020 15:19:13 +0200 Subject: [PATCH 33/37] Try to fix Windows build --- src/game/server/entities/character.h | 1 - src/game/server/entities/plasma.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h index 40114cb79..8a7657a9a 100644 --- a/src/game/server/entities/character.h +++ b/src/game/server/entities/character.h @@ -13,7 +13,6 @@ class CAntibot; class CGameTeams; -class CSaveTee; struct CAntibotCharacterData; enum diff --git a/src/game/server/entities/plasma.cpp b/src/game/server/entities/plasma.cpp index 6172173c8..b71ff5faa 100644 --- a/src/game/server/entities/plasma.cpp +++ b/src/game/server/entities/plasma.cpp @@ -7,7 +7,7 @@ #include #include "plasma.h" -const float ACCEL = 1.1f; +const float PLASMA_ACCEL = 1.1f; CPlasma::CPlasma(CGameWorld *pGameWorld, vec2 Pos, vec2 Dir, bool Freeze, bool Explosive, int ResponsibleTeam) : @@ -44,7 +44,7 @@ bool CPlasma::HitCharacter() void CPlasma::Move() { m_Pos += m_Core; - m_Core *= ACCEL; + m_Core *= PLASMA_ACCEL; } void CPlasma::Reset() From 5b53a05a404043968167d4a7d2f6aa8eefec3531 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 9 Aug 2020 16:52:18 +0200 Subject: [PATCH 34/37] Rename LoadString to FromString to fix windows build --- src/game/server/save.cpp | 6 +++--- src/game/server/save.h | 4 ++-- src/game/server/score.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index f5563c021..dba931519 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -290,7 +290,7 @@ char* CSaveTee::GetString(const CSaveTeam *pTeam) return m_aString; } -int CSaveTee::LoadString(const char* String) +int CSaveTee::FromString(const char* String) { int Num; Num = sscanf(String, @@ -552,7 +552,7 @@ char* CSaveTeam::GetString() return m_aString; } -int CSaveTeam::LoadString(const char* String) +int CSaveTeam::FromString(const char* String) { char TeamStats[MAX_CLIENTS]; char Switcher[64]; @@ -636,7 +636,7 @@ int CSaveTeam::LoadString(const char* String) if(StrSize < sizeof(SaveTee)) { str_copy(SaveTee, CopyPos, StrSize); - int Num = m_pSavedTees[n].LoadString(SaveTee); + int Num = m_pSavedTees[n].FromString(SaveTee); if(Num) { dbg_msg("load", "failed to load tee"); diff --git a/src/game/server/save.h b/src/game/server/save.h index 7b6e48bd2..6eaafd53c 100644 --- a/src/game/server/save.h +++ b/src/game/server/save.h @@ -18,7 +18,7 @@ public: void save(CCharacter* pchr); void load(CCharacter* pchr, int Team); char* GetString(const CSaveTeam *pTeam); - int LoadString(const char* String); + int FromString(const char* String); void LoadHookedPlayer(const CSaveTeam *pTeam); vec2 GetPos() const { return m_Pos; } const char* GetName() const { return m_aName; } @@ -115,7 +115,7 @@ public: char* GetString(); int GetMembersCount() const { return m_MembersCount; } // MatchPlayers has to be called afterwards - int LoadString(const char* String); + int FromString(const char* String); // returns true if a team can load, otherwise writes a nice error Message in pMessage bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen); int save(int Team); diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 80b155484..7231d5ba4 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -1498,7 +1498,7 @@ bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData char aSaveString[65536]; pSqlServer->GetString(1, aSaveString, sizeof(aSaveString)); - int Num = pData->m_pResult->m_SavedTeam.LoadString(aSaveString); + int Num = pData->m_pResult->m_SavedTeam.FromString(aSaveString); if(Num != 0) { From 5893913922ce93d441385d62c007c827fc354701 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 9 Aug 2020 17:54:25 +0200 Subject: [PATCH 35/37] Clean up includes in sql backend --- src/engine/server/databases/connection_pool.cpp | 1 + src/engine/server/databases/connection_pool.h | 4 ++-- src/engine/server/databases/mysql.cpp | 4 ++++ src/engine/server/databases/mysql.h | 16 ++++++++-------- src/game/server/score.h | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/engine/server/databases/connection_pool.cpp b/src/engine/server/databases/connection_pool.cpp index b601c3656..b48f51181 100644 --- a/src/engine/server/databases/connection_pool.cpp +++ b/src/engine/server/databases/connection_pool.cpp @@ -1,4 +1,5 @@ #include "connection_pool.h" +#include "connection.h" #include #if defined(CONF_SQL) diff --git a/src/engine/server/databases/connection_pool.h b/src/engine/server/databases/connection_pool.h index 40c4510ab..7fb87e76c 100644 --- a/src/engine/server/databases/connection_pool.h +++ b/src/engine/server/databases/connection_pool.h @@ -1,13 +1,13 @@ #ifndef ENGINE_SERVER_DATABASES_CONNECTION_POOL_H #define ENGINE_SERVER_DATABASES_CONNECTION_POOL_H -#include "connection.h" - #include #include #include #include +class IDbConnection; + struct ISqlData { virtual ~ISqlData() {}; diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index ab2543f47..0f5ae43b8 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -1,8 +1,12 @@ #include "mysql.h" +#include #include #if defined(CONF_SQL) #include +#include +#include +#include #endif #include diff --git a/src/engine/server/databases/mysql.h b/src/engine/server/databases/mysql.h index 0c4d09876..effc691ea 100644 --- a/src/engine/server/databases/mysql.h +++ b/src/engine/server/databases/mysql.h @@ -1,17 +1,17 @@ #ifndef ENGINE_SERVER_DATABASES_MYSQL_H #define ENGINE_SERVER_DATABASES_MYSQL_H -#include "connection.h" -#include - +#include #include #include -#if defined(CONF_SQL) -#include -#include -#include -#endif +class lock; +namespace sql { +class Connection; +class PreparedStatement; +class ResultSet; +class Statement; +} /* namespace sql */ class CMysqlConnection : public IDbConnection { diff --git a/src/game/server/score.h b/src/game/server/score.h index 305f1d11b..c2031fa36 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include #include From 81e5bb924c169d2a9e3916b9e232e391fc027ab9 Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 9 Aug 2020 17:56:47 +0200 Subject: [PATCH 36/37] Move sync_barrier to only used place to remove `#include ` from header file preventing compiling on windows --- src/base/tl/threading.h | 58 ------------------------------- src/engine/client/backend_sdl.cpp | 19 ++++++++++ 2 files changed, 19 insertions(+), 58 deletions(-) diff --git a/src/base/tl/threading.h b/src/base/tl/threading.h index c5a972adc..55b69e3cf 100644 --- a/src/base/tl/threading.h +++ b/src/base/tl/threading.h @@ -3,64 +3,6 @@ #include "../system.h" -/* - atomic_inc - should return the value after increment - atomic_dec - should return the value after decrement - atomic_compswap - should return the value before the eventual swap - sync_barrier - creates a full hardware fence -*/ - -#if defined(__GNUC__) - - inline unsigned atomic_inc(volatile unsigned *pValue) - { - return __sync_add_and_fetch(pValue, 1); - } - - inline unsigned atomic_dec(volatile unsigned *pValue) - { - return __sync_add_and_fetch(pValue, -1); - } - - inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value) - { - return __sync_val_compare_and_swap(pValue, comperand, value); - } - - inline void sync_barrier() - { - __sync_synchronize(); - } - -#elif defined(_MSC_VER) - #include - - #define WIN32_LEAN_AND_MEAN - #include - - inline unsigned atomic_inc(volatile unsigned *pValue) - { - return _InterlockedIncrement((volatile long *)pValue); - } - - inline unsigned atomic_dec(volatile unsigned *pValue) - { - return _InterlockedDecrement((volatile long *)pValue); - } - - inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value) - { - return _InterlockedCompareExchange((volatile long *)pValue, (long)value, (long)comperand); - } - - inline void sync_barrier() - { - MemoryBarrier(); - } -#else - #error missing atomic implementation for this compiler -#endif - class semaphore { SEMAPHORE sem; diff --git a/src/engine/client/backend_sdl.cpp b/src/engine/client/backend_sdl.cpp index e44cb87ba..d6b6177dc 100644 --- a/src/engine/client/backend_sdl.cpp +++ b/src/engine/client/backend_sdl.cpp @@ -43,6 +43,25 @@ extern "C" } #endif +/* + sync_barrier - creates a full hardware fence +*/ +#if defined(__GNUC__) + inline void sync_barrier() + { + __sync_synchronize(); + } +#elif defined(_MSC_VER) + #define WIN32_LEAN_AND_MEAN + #include + inline void sync_barrier() + { + MemoryBarrier(); + } +#else + #error missing atomic implementation for this compiler +#endif + // ------------ CGraphicsBackend_Threaded void CGraphicsBackend_Threaded::ThreadFunc(void *pUser) From 961ad077fb5ba2559c5b388e6d4b7926b1befd4f Mon Sep 17 00:00:00 2001 From: Zwelf Date: Sun, 9 Aug 2020 18:07:18 +0200 Subject: [PATCH 37/37] Don't print MySQL password to console --- src/engine/server/databases/mysql.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/server/databases/mysql.cpp b/src/engine/server/databases/mysql.cpp index 0f5ae43b8..6a90a20f3 100644 --- a/src/engine/server/databases/mysql.cpp +++ b/src/engine/server/databases/mysql.cpp @@ -52,8 +52,8 @@ void CMysqlConnection::Print(IConsole *pConsole, const char *Mode) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' Pass: '%s' IP: <{'%s'}> Port: %d", - Mode, m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port); + "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", + Mode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port); pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); }