diff options
| -rw-r--r-- | src/connections.cc | 49 | ||||
| -rw-r--r-- | src/database_sqlite.cc | 1512 | ||||
| -rw-r--r-- | src/query.cc | 392 | ||||
| -rw-r--r-- | src/querymanager.cc | 10 | ||||
| -rw-r--r-- | src/querymanager.hh | 236 |
5 files changed, 1228 insertions, 971 deletions
diff --git a/src/connections.cc b/src/connections.cc index 91fde30..e22bc6f 100644 --- a/src/connections.cc +++ b/src/connections.cc @@ -300,7 +300,9 @@ void CheckConnectionQueryRequest(TConnection *Connection){ int QueryType = Request.Read8(); if(!Connection->Authorized){ if(QueryType != QUERY_LOGIN){ - LOG_ERR("Unauthorized query %d from %s", QueryType, Connection->RemoteAddress); + LOG_ERR("Unauthorized query (%d) %s from %s", + QueryType, QueryName(QueryType), + Connection->RemoteAddress); CloseConnection(Connection); return; } @@ -321,19 +323,16 @@ void CheckConnectionQueryRequest(TConnection *Connection){ // NOTE(fusion): The connection is AUTHORIZED at this point but we still // need to check whether the application type is valid, and for the case - // of a game server, check whether the world name is valid. + // of a game server, whether the world is valid. if(ApplicationType == APPLICATION_TYPE_GAME){ if(QueryInternalResolveWorld(Query, LoginData)){ - LOG("Connection %s AUTHORIZED to game server \"%s\"", - Connection->RemoteAddress, LoginData); - Connection->Authorized = true; Connection->ApplicationType = APPLICATION_TYPE_GAME; + StringBufCopy(Connection->LoginData, LoginData); ProcessQuery(Connection); }else{ // TODO(fusion): This should probably be a PANIC? LOG_ERR("Rejecting connection %s: unable to rewrite login query..." - " Try increasing the query buffer size", - Connection->RemoteAddress); + " Try increasing the query buffer size", Connection->RemoteAddress); SendQueryFailed(Connection); } }else if(ApplicationType == APPLICATION_TYPE_LOGIN){ @@ -352,7 +351,6 @@ void CheckConnectionQueryRequest(TConnection *Connection){ SendQueryFailed(Connection); } }else if(Connection->ApplicationType == APPLICATION_TYPE_GAME){ - // minimal check to see if the query type is valid and enqueue if(QueryType == QUERY_LOGIN_GAME || QueryType == QUERY_LOGOUT_GAME || QueryType == QUERY_SET_NAMELOCK @@ -385,16 +383,18 @@ void CheckConnectionQueryRequest(TConnection *Connection){ || QueryType == QUERY_LOAD_WORLD_CONFIG){ ProcessQuery(Connection); }else{ - LOG_ERR("Unknown GAME query %d from %s", - QueryType, Connection->RemoteAddress); + LOG_ERR("Invalid GAME query (%d) %s from %s", + QueryType, QueryName(QueryType), + Connection->RemoteAddress); SendQueryFailed(Connection); } }else if(Connection->ApplicationType == APPLICATION_TYPE_LOGIN){ if(QueryType == QUERY_LOGIN_ACCOUNT){ ProcessQuery(Connection); }else{ - LOG_ERR("Unknown LOGIN query %d from %s", - QueryType, Connection->RemoteAddress); + LOG_ERR("Invalid LOGIN query %d (%s) from %s", + QueryType, QueryName(QueryType), + Connection->RemoteAddress); SendQueryFailed(Connection); } }else if(Connection->ApplicationType == APPLICATION_TYPE_WEB){ @@ -408,8 +408,9 @@ void CheckConnectionQueryRequest(TConnection *Connection){ || QueryType == QUERY_GET_KILL_STATISTICS){ ProcessQuery(Connection); }else{ - LOG_ERR("Unknown WEB query %d from %s", - QueryType, Connection->RemoteAddress); + LOG_ERR("Invalid WEB query (%d) %s from %s", + QueryType, QueryName(QueryType), + Connection->RemoteAddress); SendQueryFailed(Connection); } } @@ -428,14 +429,26 @@ void CheckConnectionQueryResponse(TConnection *Connection){ if(Query->QueryType == QUERY_INTERNAL_RESOLVE_WORLD){ if(Query->QueryStatus == QUERY_STATUS_OK){ - ASSERT(Query->WorldID != 0); + ASSERT(Query->WorldID > 0); + LOG("Connection %s AUTHORIZED to game server \"%s\"", + Connection->RemoteAddress, Connection->LoginData); + Connection->Authorized = true; SendQueryOk(Connection); }else{ - LOG_WARN("Dropping connection %s: unknown game world", - Connection->RemoteAddress); + // NOTE(fusion): The connection is automatically dropped if it + // hasn't been authorized by the end of the first query. + LOG_WARN("Rejecting connection %s: unknown game server \"%s\"", + Connection->RemoteAddress, Connection->LoginData); SendQueryFailed(Connection); } }else{ + if(Query->QueryStatus == QUERY_STATUS_FAILED){ + LOG_WARN("Query (%d) %s from %s has FAILED", + Query->QueryType, + QueryName(Query->QueryType), + Connection->RemoteAddress); + } + SendQueryResponse(Connection); } } @@ -512,7 +525,7 @@ void ProcessConnections(void){ if(AssignConnection(Socket, Addr, Port) == NULL){ LOG_ERR("Rejecting connection %08X:%d:" - " reached max number of connections (%d)", + " max number of connections reached (%d)", Addr, Port, g_Config.MaxConnections); close(Socket); } diff --git a/src/database_sqlite.cc b/src/database_sqlite.cc index 1ced471..77d3dfc 100644 --- a/src/database_sqlite.cc +++ b/src/database_sqlite.cc @@ -2,13 +2,16 @@ #include "sqlite3.h" struct TCachedStatement{ - sqlite3_stmt *Stmt; - int64 LastUsed; - uint32 Hash; + sqlite3_stmt *Stmt; + int64 LastUsed; + uint32 Hash; }; -static sqlite3 *g_Database = NULL; -static TCachedStatement *g_CachedStatements = NULL; +struct TDatabase{ + sqlite3 *Handle; + int MaxCachedStatements; + TCachedStatement *CachedStatements; +}; // NOTE(fusion): SQLite's application id. We're currently setting it to ASCII // "TiDB" for "Tibia Database". @@ -38,23 +41,44 @@ public: } }; -uint32 HashText(const char *Text){ - // FNV1a 32-bits - uint32 Hash = 0x811C9DC5U; - for(int i = 0; Text[i] != 0; i += 1){ - Hash ^= (uint32)Text[i]; - Hash *= 0x01000193U; +static void EnsureStatementCache(TDatabase *Database){ + if(Database->CachedStatements == NULL){ + ASSERT(g_Config.MaxCachedStatements > 0); + Database->MaxCachedStatements = g_Config.MaxCachedStatements; + Database->CachedStatements = (TCachedStatement*)calloc( + Database->MaxCachedStatements, sizeof(TCachedStatement)); + } +} + +static void DeleteStatementCache(TDatabase *Database){ + if(Database->CachedStatements != NULL){ + ASSERT(Database->MaxCachedStatements > 0); + for(int i = 0; i < Database->MaxCachedStatements; i += 1){ + TCachedStatement *Entry = &Database->CachedStatements[i]; + if(Entry->Stmt != NULL){ + sqlite3_finalize(Entry->Stmt); + Entry->Stmt = NULL; + } + + Entry->LastUsed = 0; + Entry->Hash = 0; + } + + free(Database->CachedStatements); + Database->MaxCachedStatements = 0; + Database->CachedStatements = NULL; } - return Hash; } -sqlite3_stmt *PrepareQuery(const char *Text){ +static sqlite3_stmt *PrepareQuery(TDatabase *Database, const char *Text){ + EnsureStatementCache(Database); + sqlite3_stmt *Stmt = NULL; int LeastRecentlyUsed = 0; - int64 LeastRecentlyUsedTime = g_CachedStatements[0].LastUsed; - uint32 Hash = HashText(Text); - for(int i = 0; i < g_Config.MaxCachedStatements; i += 1){ - TCachedStatement *Entry = &g_CachedStatements[i]; + int64 LeastRecentlyUsedTime = Database->CachedStatements[0].LastUsed; + uint32 Hash = HashString(Text); + for(int i = 0; i < Database->MaxCachedStatements; i += 1){ + TCachedStatement *Entry = &Database->CachedStatements[i]; if(Entry->LastUsed < LeastRecentlyUsedTime){ LeastRecentlyUsed = i; @@ -64,7 +88,7 @@ sqlite3_stmt *PrepareQuery(const char *Text){ if(Entry->Stmt != NULL && Entry->Hash == Hash){ const char *EntryText = sqlite3_sql(Entry->Stmt); ASSERT(EntryText != NULL); - if(strcmp(EntryText, Text) == 0){ +if(strcmp(EntryText, Text) == 0){ Stmt = Entry->Stmt; Entry->LastUsed = g_MonotonicTimeMS; break; @@ -73,13 +97,13 @@ sqlite3_stmt *PrepareQuery(const char *Text){ } if(Stmt == NULL){ - if(sqlite3_prepare_v3(g_Database, Text, -1, + if(sqlite3_prepare_v3(Database->Handle, Text, -1, SQLITE_PREPARE_PERSISTENT, &Stmt, NULL) != SQLITE_OK){ - LOG_ERR("Failed to prepare query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to prepare query: %s", sqlite3_errmsg(Database->Handle)); return NULL; } - TCachedStatement *Entry = &g_CachedStatements[LeastRecentlyUsed]; + TCachedStatement *Entry = &Database->CachedStatements[LeastRecentlyUsed]; if(Entry->Stmt != NULL){ sqlite3_finalize(Entry->Stmt); } @@ -103,79 +127,323 @@ sqlite3_stmt *PrepareQuery(const char *Text){ return Stmt; } -bool InitStatementCache(void){ - ASSERT(g_CachedStatements == NULL); - g_CachedStatements = (TCachedStatement*)calloc( - g_Config.MaxCachedStatements, sizeof(TCachedStatement)); +// Database Initialization +//============================================================================== +// NOTE(fusion): From `https://www.sqlite.org/pragma.html`: +// "Some pragmas take effect during the SQL compilation stage, not the execution +// stage. This means if using the C-language sqlite3_prepare(), sqlite3_step(), +// sqlite3_finalize() API (or similar in a wrapper interface), the pragma may run +// during the sqlite3_prepare() call, not during the sqlite3_step() call as normal +// SQL statements do. Or the pragma might run during sqlite3_step() just like normal +// SQL statements. Whether or not the pragma runs during sqlite3_prepare() or +// sqlite3_step() depends on the pragma and on the specific release of SQLite." +// +// Depending on the pragma, queries will fail in the sqlite3_prepare() stage when +// using bound parameters. This means we need to assemble the entire query before +// hand with snprintf or other similar formatting functions. In particular, this +// rule apply for `application_id` and `user_version` which we modify. + +static bool FileExists(const char *FileName){ + FILE *File = fopen(FileName, "rb"); + bool Result = (File != NULL); + if(Result){ + fclose(File); + } + return Result; +} + +static bool ExecFile(TDatabase *Database, const char *FileName){ + FILE *File = fopen(FileName, "rb"); + if(File == NULL){ + LOG_ERR("Failed to open file \"%s\"", FileName); + return false; + } + + fseek(File, 0, SEEK_END); + usize FileSize = (usize)ftell(File); + fseek(File, 0, SEEK_SET); + + bool Result = true; + if(FileSize > 0){ + char *Text = (char*)malloc(FileSize + 1); + Text[FileSize] = 0; + if(Result && fread(Text, 1, FileSize, File) != FileSize){ + LOG_ERR("Failed to read \"%s\" (ferror: %d, feof: %d)", + FileName, ferror(File), feof(File)); + Result = false; + } + + if(Result && sqlite3_exec(Database->Handle, Text, NULL, NULL, NULL) != SQLITE_OK){ + LOG_ERR("Failed to execute \"%s\": %s", + FileName, sqlite3_errmsg(Database->Handle)); + Result = false; + } + + free(Text); + } + + fclose(File); + return Result; +} + +static bool ExecInternal(TDatabase *Database, const char *Format, ...){ + va_list ap; + va_start(ap, Format); + char Text[1024]; + int Written = vsnprintf(Text, sizeof(Text), Format, ap); + va_end(ap); + + if(Written >= (int)sizeof(Text)){ + LOG_ERR("Query is too long"); + return false; + } + + if(sqlite3_exec(Database->Handle, Text, NULL, NULL, NULL) != SQLITE_OK){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; + } + return true; } -void ExitStatementCache(void){ - if(g_CachedStatements != NULL){ - for(int i = 0; i < g_Config.MaxCachedStatements; i += 1){ - TCachedStatement *Entry = &g_CachedStatements[i]; - if(Entry->Stmt != NULL){ - sqlite3_finalize(Entry->Stmt); - Entry->Stmt = NULL; +static bool GetPragmaInt(TDatabase *Database, const char *Name, int *OutValue){ + char Text[1024]; + snprintf(Text, sizeof(Text), "PRAGMA %s", Name); + + sqlite3_stmt *Stmt; + if(sqlite3_prepare_v2(Database->Handle, Text, -1, &Stmt, NULL) != SQLITE_OK){ + LOG_ERR("Failed to retrieve %s (PREP): %s", Name, sqlite3_errmsg(Database->Handle)); + return false; + } + + bool Result = (sqlite3_step(Stmt) == SQLITE_ROW); + if(!Result){ + LOG_ERR("Failed to retrieve %s (STEP): %s", Name, sqlite3_errmsg(Database->Handle)); + }else if(OutValue){ + *OutValue = sqlite3_column_int(Stmt, 0); + } + + sqlite3_finalize(Stmt); + return Result; +} + +static bool InitDatabaseSchema(TDatabase *Database){ + TransactionScope Tx("SchemaInit"); + if(!Tx.Begin(Database)){ + return false; + } + + if(!ExecFile(Database, "sql/schema.sql")){ + LOG_ERR("Failed to execute \"sql/schema.sql\""); + return false; + } + + if(!ExecInternal(Database, "PRAGMA application_id = %d", g_ApplicationID)){ + LOG_ERR("Failed to set application id"); + return false; + } + + if(!ExecInternal(Database, "PRAGMA user_version = 1")){ + LOG_ERR("Failed to set user version"); + return false; + } + + return Tx.Commit(); +} + +static bool UpgradeDatabaseSchema(TDatabase *Database, int UserVersion){ + char FileName[256]; + int NewVersion = UserVersion; + while(true){ + snprintf(FileName, sizeof(FileName), "sql/upgrade-%d.sql", NewVersion); + if(FileExists(FileName)){ + NewVersion += 1; + }else{ + break; + } + } + + if(UserVersion != NewVersion){ + LOG("Upgrading database schema to version %d", NewVersion); + + TransactionScope Tx("SchemaUpgrade"); + if(!Tx.Begin(Database)){ + return false; + } + + while(UserVersion < NewVersion){ + snprintf(FileName, sizeof(FileName), "upgrade-%d.sql", UserVersion); + if(!ExecFile(Database, FileName)){ + LOG_ERR("Failed to execute \"%s\"", FileName); + return false; } + UserVersion += 1; + } - Entry->LastUsed = 0; - Entry->Hash = 0; + if(!ExecInternal(Database, "PRAGMA user_version = %d", UserVersion)){ + LOG_ERR("Failed to set user version"); + return false; + } + + if(!Tx.Commit()){ + return false; } + } + + return true; +} - free(g_CachedStatements); - g_CachedStatements = NULL; +static bool CheckDatabaseSchema(TDatabase *Database){ + int ApplicationID, UserVersion; + if(!GetPragmaInt(Database, "application_id", &ApplicationID) + || !GetPragmaInt(Database, "user_version", &UserVersion)){ + return false; } + + if(ApplicationID != g_ApplicationID){ + if(ApplicationID != 0){ + LOG_ERR("Database has unknown application id %08X (expected %08X)", + ApplicationID, g_ApplicationID); + return false; + }else if(UserVersion != 0){ + LOG_ERR("Database has non zero user version %d", UserVersion); + return false; + }else if(!InitDatabaseSchema(Database)){ + LOG_ERR("Failed to initialize database schema"); + return false; + } + + UserVersion = 1; + } + + if(!UpgradeDatabaseSchema(Database, UserVersion)){ + LOG_ERR("Failed to upgrade database schema"); + return false; + } + + LOG("Database version: %d", UserVersion); + return true; +} + +void DatabaseClose(TDatabase *Database){ + if(Database != NULL){ + DeleteStatementCache(Database); + + // NOTE(fusion): `sqlite3_close` can only fail if there are associated + // prepared statements, blob handles, or backup objects that have not + // been finalized. It should NEVER happen unless there is a BUG. + if(Database->Handle != NULL){ + if(sqlite3_close(Database->Handle) != SQLITE_OK){ + PANIC("Failed to close database: %s", sqlite3_errmsg(Database->Handle)); + } + Database->Handle = NULL; + } + + free(Database); + } +} + +TDatabase *DatabaseOpen(void){ + TDatabase *Database = (TDatabase*)calloc(1, sizeof(TDatabase)); + int Flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; + if(sqlite3_open_v2(g_Config.DatabaseFile, &Database->Handle, Flags, NULL) != SQLITE_OK){ + LOG_ERR("Failed to open database at \"%s\": %s\n", + g_Config.DatabaseFile, sqlite3_errmsg(Database->Handle)); + DatabaseClose(Database); + return NULL; + } + + if(sqlite3_db_readonly(Database->Handle, NULL)){ + LOG_ERR("Failed to open database file \"%s\" with WRITE PERMISSIONS." + " Make sure it has the appropriate permissions and is owned" + " by the same user running the query manager.", + g_Config.DatabaseFile); + DatabaseClose(Database); + return NULL; + } + + if(!CheckDatabaseSchema(Database)){ + LOG_ERR("Failed to check database schema"); + DatabaseClose(Database); + return NULL; + } + + return Database; +} + +int DatabaseChanges(TDatabase *Database){ + ASSERT(Database != NULL); + return sqlite3_changes(Database->Handle); +} + +bool DatabaseCheckpoint(TDatabase *Database){ + // IMPORTANT(fusion): Since SQLite is a local database, we don't need to check + // whether the connection is still valid or needs reconnecting. + return true; +} + +int DatabaseMaxConcurrency(void){ + // IMPORTANT(fusion): Running queries from separate threads is possible with + // different database handles, but there is an inherent limit because access + // to the underlying database file must be synchronized by the operating system. + // Also, there can only be one writer, which may cause spurious `SQLITE_BUSY` + // errors to happen if the database wasn't available for reading/writing. + return 1; } // TransactionScope //============================================================================== TransactionScope::TransactionScope(const char *Context){ m_Context = (Context != NULL ? Context : "NOCONTEXT"); - m_Running = false; + m_Database = NULL; } TransactionScope::~TransactionScope(void){ - if(m_Running && !ExecInternal("ROLLBACK")){ - LOG_ERR("Failed to rollback transaction (%s)", m_Context); + if(m_Database != NULL){ + if(!ExecInternal(m_Database, "ROLLBACK")){ + LOG_ERR("Failed to rollback transaction (%s)", m_Context); + } + + m_Database = NULL; } } -bool TransactionScope::Begin(void){ - if(m_Running){ +bool TransactionScope::Begin(TDatabase *Database){ + if(m_Database != NULL){ LOG_ERR("Transaction (%s) already running", m_Context); return false; } - if(!ExecInternal("BEGIN")){ + if(!ExecInternal(Database, "BEGIN")){ LOG_ERR("Failed to begin transaction (%s)", m_Context); return false; } - m_Running = true; + m_Database = Database; return true; } bool TransactionScope::Commit(void){ - if(!m_Running){ + if(m_Database == NULL){ LOG_ERR("Transaction (%s) not running", m_Context); return false; } - if(!ExecInternal("COMMIT")){ + // TODO(fusion): Does the transaction automatically rollback if commiting fails? + if(!ExecInternal(m_Database, "COMMIT")){ LOG_ERR("Failed to commit transaction (%s)", m_Context); return false; } - m_Running = false; + m_Database = NULL; return true; } // Primary tables //============================================================================== -int GetWorldID(const char *WorldName){ - ASSERT(WorldName != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetWorldID(TDatabase *Database, const char *World, int *WorldID){ + ASSERT(Database != NULL && World != NULL && WorldID != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT WorldID FROM Worlds WHERE Name = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -183,23 +451,24 @@ int GetWorldID(const char *WorldName){ } AutoStmtReset StmtReset(Stmt); - if(sqlite3_bind_text(Stmt, 1, WorldName, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldName: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_bind_text(Stmt, 1, World, -1, NULL) != SQLITE_OK){ + LOG_ERR("Failed to bind WorldName: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return (ErrorCode == SQLITE_ROW ? sqlite3_column_int(Stmt, 0) : 0); + *WorldID = (ErrorCode == SQLITE_ROW ? sqlite3_column_int(Stmt, 0) : 0); + return true; } -bool GetWorlds(DynamicArray<TWorld> *Worlds){ - ASSERT(Worlds != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetWorlds(TDatabase *Database, DynamicArray<TWorld> *Worlds){ + ASSERT(Database != NULL && Worlds != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "WITH N (WorldID, NumPlayers) AS (" "SELECT WorldID, COUNT(*) FROM OnlineCharacters GROUP BY WorldID" ")" @@ -224,17 +493,17 @@ bool GetWorlds(DynamicArray<TWorld> *Worlds){ Worlds->Push(World); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetWorldConfig(int WorldID, TWorldConfig *WorldConfig){ - ASSERT(WorldConfig != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetWorldConfig(TDatabase *Database, int WorldID, TWorldConfig *WorldConfig){ + ASSERT(Database != NULL && WorldConfig != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT Type, RebootTime, Host, Port, MaxPlayers," " PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer" " FROM Worlds WHERE WorldID = ?1"); @@ -245,25 +514,18 @@ bool GetWorldConfig(int WorldID, TWorldConfig *WorldConfig){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return false; - } - - int IPAddress; - const char *HostName = (const char*)sqlite3_column_text(Stmt, 2); - if(HostName == NULL || !ResolveHostName(HostName, &IPAddress)){ - LOG_ERR("Failed to resolve world %d host name \"%s\"", WorldID, HostName); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } WorldConfig->Type = sqlite3_column_int(Stmt, 0); WorldConfig->RebootTime = sqlite3_column_int(Stmt, 1); - WorldConfig->IPAddress = IPAddress; + StringBufCopy(WorldConfig->HostName, (const char*)sqlite3_column_text(Stmt, 2)); WorldConfig->Port = sqlite3_column_int(Stmt, 3); WorldConfig->MaxPlayers = sqlite3_column_int(Stmt, 4); WorldConfig->PremiumPlayerBuffer = sqlite3_column_int(Stmt, 5); @@ -272,9 +534,9 @@ bool GetWorldConfig(int WorldID, TWorldConfig *WorldConfig){ return true; } -bool AccountExists(int AccountID, const char *Email){ - ASSERT(Email != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool AccountExists(TDatabase *Database, int AccountID, const char *Email, bool *Result){ + ASSERT(Database != NULL && Email != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Accounts WHERE AccountID = ?1 OR Email = ?2"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -284,21 +546,23 @@ bool AccountExists(int AccountID, const char *Email){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK || sqlite3_bind_text(Stmt, 2, Email, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_ROW && ErrCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrCode == SQLITE_ROW); + *Result = (ErrCode == SQLITE_ROW); + return true; } -bool AccountNumberExists(int AccountID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool AccountNumberExists(TDatabase *Database, int AccountID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Accounts WHERE AccountID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -307,22 +571,23 @@ bool AccountNumberExists(int AccountID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID)!= SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_ROW && ErrCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrCode == SQLITE_ROW); + *Result = (ErrCode == SQLITE_ROW); + return true; } -bool AccountEmailExists(const char *Email){ - ASSERT(Email != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool AccountEmailExists(TDatabase *Database, const char *Email, bool *Result){ + ASSERT(Database != NULL && Email != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Accounts WHERE Email = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -331,22 +596,25 @@ bool AccountEmailExists(const char *Email){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_text(Stmt, 1, Email, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind Email: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind Email: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_ROW && ErrCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrCode == SQLITE_ROW); + *Result = (ErrCode == SQLITE_ROW); + return true; } -bool CreateAccount(int AccountID, const char *Email, const uint8 *Auth, int AuthSize){ - ASSERT(Email != NULL && Auth != NULL && AuthSize > 0); - sqlite3_stmt *Stmt = PrepareQuery( +bool CreateAccount(TDatabase *Database, int AccountID, + const char *Email, const uint8 *Auth, int AuthSize){ + ASSERT(Database != NULL && Email != NULL + && Auth != NULL && AuthSize > 0); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Accounts (AccountID, Email, Auth)" " VALUES (?1, ?2, ?3)"); if(Stmt == NULL){ @@ -358,26 +626,27 @@ bool CreateAccount(int AccountID, const char *Email, const uint8 *Auth, int Auth if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK || sqlite3_bind_text(Stmt, 2, Email, -1, NULL) != SQLITE_OK || sqlite3_bind_blob(Stmt, 3, Auth, AuthSize, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_DONE && ErrCode != SQLITE_CONSTRAINT){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } + // TODO(fusion): Maybe have a `ContraintError` output param? return (ErrCode == SQLITE_DONE); } -bool GetAccountData(int AccountID, TAccount *Account){ - ASSERT(Account != NULL); - sqlite3_stmt *Stmt = PrepareQuery( - "SELECT AccountID, Email, Auth," - " MAX(PremiumEnd - UNIXEPOCH(), 0)," - " PendingPremiumDays, Deleted" - " FROM Accounts WHERE AccountID = ?1"); +bool GetAccountData(TDatabase *Database, int AccountID, TAccount *Account){ + ASSERT(Database != NULL && Account != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "SELECT AccountID, Email, Auth," + " MAX(PremiumEnd - UNIXEPOCH(), 0)," + " PendingPremiumDays, Deleted" + " FROM Accounts WHERE AccountID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); return false; @@ -385,13 +654,13 @@ bool GetAccountData(int AccountID, TAccount *Account){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -410,31 +679,34 @@ bool GetAccountData(int AccountID, TAccount *Account){ return true; } -int GetAccountOnlineCharacters(int AccountID){ - sqlite3_stmt *Stmt = PrepareQuery( - "SELECT COUNT(*) FROM Characters" - " WHERE AccountID = ?1 AND IsOnline != 0"); +bool GetAccountOnlineCharacters(TDatabase *Database, int AccountID, int *OnlineCharacters){ + ASSERT(Database != NULL && OnlineCharacters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "SELECT COUNT(*) FROM Characters" + " WHERE AccountID = ?1 AND IsOnline != 0"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); + return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return sqlite3_column_int(Stmt, 0); + *OnlineCharacters = sqlite3_column_int(Stmt, 0); + return true; } -bool IsCharacterOnline(int CharacterID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool IsCharacterOnline(TDatabase *Database, int CharacterID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT IsOnline FROM Characters WHERE CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -443,30 +715,28 @@ bool IsCharacterOnline(int CharacterID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind CharacterID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind CharacterID: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - bool Result = false; - if(ErrorCode == SQLITE_ROW){ - Result = (sqlite3_column_int(Stmt, 0) != 0); - } - - return Result; + *Result = (ErrorCode == SQLITE_ROW) + && (sqlite3_column_int(Stmt, 0) != 0); + return true; } -bool ActivatePendingPremiumDays(int AccountID){ - sqlite3_stmt *Stmt = PrepareQuery( - "UPDATE Accounts" - " SET PremiumEnd = MAX(PremiumEnd, UNIXEPOCH()) + PendingPremiumDays * 86400," - " PendingPremiumDays = 0" - " WHERE AccountID = ?1 AND PendingPremiumDays > 0"); +bool ActivatePendingPremiumDays(TDatabase *Database, int AccountID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "UPDATE Accounts" + " SET PremiumEnd = MAX(PremiumEnd, UNIXEPOCH()) + PendingPremiumDays * 86400," + " PendingPremiumDays = 0" + " WHERE AccountID = ?1 AND PendingPremiumDays > 0"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); return false; @@ -474,20 +744,22 @@ bool ActivatePendingPremiumDays(int AccountID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetCharacterEndpoints(int AccountID, DynamicArray<TCharacterEndpoint> *Characters){ - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterEndpoints(TDatabase *Database, int AccountID, + DynamicArray<TCharacterEndpoint> *Characters){ + ASSERT(Database != NULL && Characters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT C.Name, W.Name, W.Host, W.Port" " FROM Characters AS C" " INNER JOIN Worlds AS W ON W.WorldID = C.WorldID" @@ -499,7 +771,7 @@ bool GetCharacterEndpoints(int AccountID, DynamicArray<TCharacterEndpoint> *Char AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -522,16 +794,18 @@ bool GetCharacterEndpoints(int AccountID, DynamicArray<TCharacterEndpoint> *Char Characters->Push(Character); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetCharacterSummaries(int AccountID, DynamicArray<TCharacterSummary> *Characters){ - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterSummaries(TDatabase *Database, int AccountID, + DynamicArray<TCharacterSummary> *Characters){ + ASSERT(Database != NULL && Characters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT C.Name, W.Name, C.Level, C.Profession, C.IsOnline, C.Deleted" " FROM Characters AS C" " LEFT JOIN Worlds AS W ON W.WorldID = C.WorldID" @@ -543,7 +817,7 @@ bool GetCharacterSummaries(int AccountID, DynamicArray<TCharacterSummary> *Chara AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -558,17 +832,17 @@ bool GetCharacterSummaries(int AccountID, DynamicArray<TCharacterSummary> *Chara Characters->Push(Character); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool CharacterNameExists(const char *Name){ - ASSERT(Name != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool CharacterNameExists(TDatabase *Database, const char *Name, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Characters WHERE Name = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -577,22 +851,24 @@ bool CharacterNameExists(const char *Name){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_text(Stmt, 1, Name, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind Email: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind Email: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_ROW && ErrCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrCode == SQLITE_ROW); + *Result = (ErrCode == SQLITE_ROW); + return false; } -bool CreateCharacter(int WorldID, int AccountID, const char *Name, int Sex){ - ASSERT(Name != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool CreateCharacter(TDatabase *Database, int WorldID, + int AccountID, const char *Name, int Sex){ + ASSERT(Database != NULL && Name != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Characters (WorldID, AccountID, Name, Sex)" " VALUES (?1, ?2, ?3, ?4)"); if(Stmt == NULL){ @@ -605,66 +881,70 @@ bool CreateCharacter(int WorldID, int AccountID, const char *Name, int Sex){ || sqlite3_bind_int(Stmt, 2, AccountID) != SQLITE_OK || sqlite3_bind_text(Stmt, 3, Name, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 4, Sex) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrCode = sqlite3_step(Stmt); if(ErrCode != SQLITE_DONE && ErrCode != SQLITE_CONSTRAINT){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } + // TODO(fusion): Same as `CreateAccount`? return (ErrCode == SQLITE_DONE); } -int GetCharacterID(int WorldID, const char *CharacterName){ - ASSERT(CharacterName != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterID(TDatabase *Database, int WorldID, + const char *CharacterName, int *CharacterID){ + ASSERT(Database != NULL && CharacterName != NULL && CharacterID != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT CharacterID FROM Characters" " WHERE WorldID = ?1 AND Name = ?2"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_text(Stmt, 2, CharacterName, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return (ErrorCode == SQLITE_ROW ? sqlite3_column_int(Stmt, 0) : 0); + *CharacterID = (ErrorCode == SQLITE_ROW ? sqlite3_column_int(Stmt, 0) : 0); + return true; } -bool GetCharacterLoginData(const char *CharacterName, TCharacterLoginData *Character){ - ASSERT(CharacterName != NULL && Character != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterLoginData(TDatabase *Database, + const char *CharacterName, TCharacterLoginData *Character){ + ASSERT(Database != NULL && CharacterName != NULL && Character != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT WorldID, CharacterID, AccountID, Name," " Sex, Guild, Rank, Title, Deleted" " FROM Characters WHERE Name = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_text(Stmt, 1, CharacterName, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind CharacterName: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind CharacterName: %s", sqlite3_errmsg(Database->Handle)); + return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } memset(Character, 0, sizeof(TCharacterLoginData)); @@ -683,9 +963,10 @@ bool GetCharacterLoginData(const char *CharacterName, TCharacterLoginData *Chara return true; } -bool GetCharacterProfile(const char *CharacterName, TCharacterProfile *Character){ - ASSERT(CharacterName != NULL && Character != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterProfile(TDatabase *Database, + const char *CharacterName, TCharacterProfile *Character){ + ASSERT(Database != NULL && CharacterName != NULL && Character != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT C.Name, W.Name, C.Sex, C.Guild, C.Rank, C.Title, C.Level," " C.Profession, C.Residence, C.LastLoginTime, C.IsOnline," " C.Deleted, MAX(A.PremiumEnd - UNIXEPOCH(), 0)" @@ -703,13 +984,13 @@ bool GetCharacterProfile(const char *CharacterName, TCharacterProfile *Character AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_text(Stmt, 1, CharacterName, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind CharacterName: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind CharacterName: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -733,9 +1014,10 @@ bool GetCharacterProfile(const char *CharacterName, TCharacterProfile *Character return true; } -bool GetCharacterRight(int CharacterID, const char *Right){ - ASSERT(Right != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetCharacterRight(TDatabase *Database, + int CharacterID, const char *Right, bool *Result){ + ASSERT(Database != NULL && Right != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM CharacterRights" " WHERE CharacterID = ?1 AND Right = ?2"); if(Stmt == NULL){ @@ -746,23 +1028,24 @@ bool GetCharacterRight(int CharacterID, const char *Right){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK || sqlite3_bind_text(Stmt, 2, Right, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrorCode == SQLITE_ROW); + *Result = (ErrorCode == SQLITE_ROW); + return true; } -bool GetCharacterRights(int CharacterID, DynamicArray<TCharacterRight> *Rights){ - ASSERT(Rights != NULL); - sqlite3_stmt *Stmt = PrepareQuery( - "SELECT Right FROM CharacterRights WHERE CharacterID = ?1"); +bool GetCharacterRights(TDatabase *Database, int CharacterID, DynamicArray<TCharacterRight> *Rights){ + ASSERT(Database != NULL && Rights != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "SELECT Right FROM CharacterRights WHERE CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); return false; @@ -770,7 +1053,7 @@ bool GetCharacterRights(int CharacterID, DynamicArray<TCharacterRight> *Rights){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind CharacterID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind CharacterID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -780,17 +1063,19 @@ bool GetCharacterRights(int CharacterID, DynamicArray<TCharacterRight> *Rights){ Rights->Push(Right); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetGuildLeaderStatus(int WorldID, int CharacterID){ +bool GetGuildLeaderStatus(TDatabase *Database, + int WorldID, int CharacterID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); // NOTE(fusion): Same as `DecrementIsOnline`. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT Guild, Rank FROM Characters" " WHERE WorldID = ?1 AND CharacterID = ?2"); if(Stmt == NULL){ @@ -801,30 +1086,32 @@ bool GetGuildLeaderStatus(int WorldID, int CharacterID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - bool Result = false; + *Result = false; if(ErrorCode == SQLITE_ROW){ const char *Guild = (const char*)sqlite3_column_text(Stmt, 0); const char *Rank = (const char*)sqlite3_column_text(Stmt, 1); if(Guild != NULL && !StringEmpty(Guild) && Rank != NULL && StringEqCI(Rank, "Leader")){ - Result = true; + *Result = true; } } - return Result; + + return true; } -bool IncrementIsOnline(int WorldID, int CharacterID){ +bool IncrementIsOnline(TDatabase *Database, int WorldID, int CharacterID){ + ASSERT(Database != NULL); // NOTE(fusion): Same as `DecrementIsOnline`. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE Characters SET IsOnline = IsOnline + 1" " WHERE WorldID = ?1 AND CharacterID = ?2"); if(Stmt == NULL){ @@ -835,23 +1122,24 @@ bool IncrementIsOnline(int WorldID, int CharacterID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool DecrementIsOnline(int WorldID, int CharacterID){ +bool DecrementIsOnline(TDatabase *Database, int WorldID, int CharacterID){ + ASSERT(Database != NULL); // NOTE(fusion): A character is uniquely identified by its id. The world id // check is purely to avoid a world from modifying a character from another // world. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE Characters SET IsOnline = IsOnline - 1" " WHERE WorldID = ?1 AND CharacterID = ?2"); if(Stmt == NULL){ @@ -862,21 +1150,21 @@ bool DecrementIsOnline(int WorldID, int CharacterID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool ClearIsOnline(int WorldID, int *NumAffectedCharacters){ - ASSERT(NumAffectedCharacters != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool ClearIsOnline(TDatabase *Database, int WorldID, int *NumAffectedCharacters){ + ASSERT(Database != NULL && NumAffectedCharacters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE Characters SET IsOnline = 0" " WHERE WorldID = ?1 AND IsOnline != 0"); if(Stmt == NULL){ @@ -886,24 +1174,24 @@ bool ClearIsOnline(int WorldID, int *NumAffectedCharacters){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - *NumAffectedCharacters = sqlite3_changes(g_Database); + *NumAffectedCharacters = sqlite3_changes(Database->Handle); return true; } -bool LogoutCharacter(int WorldID, int CharacterID, int Level, +bool LogoutCharacter(TDatabase *Database, int WorldID, int CharacterID, int Level, const char *Profession, const char *Residence, int LastLoginTime, int TutorActivities){ - ASSERT(Profession != NULL && Residence != NULL); - sqlite3_stmt *Stmt = PrepareQuery( + ASSERT(Database != NULL && Profession != NULL && Residence != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE Characters" " SET Level = ?3," " Profession = ?4," @@ -925,22 +1213,22 @@ bool LogoutCharacter(int WorldID, int CharacterID, int Level, || sqlite3_bind_text(Stmt, 5, Residence, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 6, LastLoginTime) != SQLITE_OK || sqlite3_bind_int(Stmt, 7, TutorActivities) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, +bool GetCharacterIndexEntries(TDatabase *Database, int WorldID, int MinimumCharacterID, int MaxEntries, int *NumEntries, TCharacterIndexEntry *Entries){ - ASSERT(MaxEntries > 0 && NumEntries != NULL && Entries != NULL); - sqlite3_stmt *Stmt = PrepareQuery( + ASSERT(Database != NULL && MaxEntries > 0 && NumEntries != NULL && Entries != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT CharacterID, Name FROM Characters" " WHERE WorldID = ?1 AND CharacterID >= ?2" " ORDER BY CharacterID ASC LIMIT ?3"); @@ -953,7 +1241,7 @@ bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, MinimumCharacterID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, MaxEntries) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -967,8 +1255,8 @@ bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, EntryIndex += 1; } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -976,11 +1264,11 @@ bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, return true; } -bool InsertCharacterDeath(int WorldID, int CharacterID, int Level, +bool InsertCharacterDeath(TDatabase *Database, int WorldID, int CharacterID, int Level, int OffenderID, const char *Remark, bool Unjustified, int Timestamp){ - ASSERT(Remark != NULL); + ASSERT(Database != NULL && Remark != NULL); // NOTE(fusion): Same as `DecrementIsOnline`. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO CharacterDeaths (CharacterID, Level," " OffenderID, Remark, Unjustified, Timestamp)" " SELECT ?2, ?3, ?4, ?5, ?6, ?7 FROM Characters" @@ -998,23 +1286,24 @@ bool InsertCharacterDeath(int WorldID, int CharacterID, int Level, || sqlite3_bind_text(Stmt, 5, Remark, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 6, (Unjustified ? 1 : 0)) != SQLITE_OK || sqlite3_bind_int(Stmt, 7, Timestamp) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool InsertBuddy(int WorldID, int AccountID, int BuddyID){ +bool InsertBuddy(TDatabase *Database, int WorldID, int AccountID, int BuddyID){ + ASSERT(Database != NULL); // NOTE(fusion): Same as `DecrementIsOnline`. // NOTE(fusion): Use the `IGNORE` conflict resolution to make duplicate row // errors appear as successful insertions. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT OR IGNORE INTO Buddies (WorldID, AccountID, BuddyID)" " SELECT ?1, ?2, ?3 FROM Characters" " WHERE WorldID = ?1 AND CharacterID = ?3"); @@ -1027,20 +1316,21 @@ bool InsertBuddy(int WorldID, int AccountID, int BuddyID){ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, AccountID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, BuddyID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool DeleteBuddy(int WorldID, int AccountID, int BuddyID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool DeleteBuddy(TDatabase *Database, int WorldID, int AccountID, int BuddyID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM Buddies" " WHERE WorldID = ?1 AND AccountID = ?2 AND BuddyID = ?3"); if(Stmt == NULL){ @@ -1052,12 +1342,12 @@ bool DeleteBuddy(int WorldID, int AccountID, int BuddyID){ if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, AccountID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, BuddyID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1066,9 +1356,9 @@ bool DeleteBuddy(int WorldID, int AccountID, int BuddyID){ return true; } -bool GetBuddies(int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies){ - ASSERT(Buddies != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetBuddies(TDatabase *Database, int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies){ + ASSERT(Database != NULL && Buddies != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT B.BuddyID, C.Name" " FROM Buddies AS B" " INNER JOIN Characters AS C" @@ -1082,7 +1372,7 @@ bool GetBuddies(int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1093,16 +1383,17 @@ bool GetBuddies(int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies Buddies->Push(Buddy); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetWorldInvitation(int WorldID, int CharacterID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool GetWorldInvitation(TDatabase *Database, int WorldID, int CharacterID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM WorldInvitations" " WHERE WorldID = ?1 AND CharacterID = ?2"); if(Stmt == NULL){ @@ -1113,21 +1404,23 @@ bool GetWorldInvitation(int WorldID, int CharacterID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrorCode == SQLITE_ROW); + *Result = (ErrorCode == SQLITE_ROW); + return true; } -bool InsertLoginAttempt(int AccountID, int IPAddress, bool Failed){ - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertLoginAttempt(TDatabase *Database, int AccountID, int IPAddress, bool Failed){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO LoginAttempts (AccountID, IPAddress, Timestamp, Failed)" " VALUES (?1, ?2, UNIXEPOCH(), ?3)"); if(Stmt == NULL){ @@ -1139,74 +1432,78 @@ bool InsertLoginAttempt(int AccountID, int IPAddress, bool Failed){ if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, IPAddress) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, (Failed ? 1 : 0)) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -int GetAccountFailedLoginAttempts(int AccountID, int TimeWindow){ - sqlite3_stmt *Stmt = PrepareQuery( - "SELECT COUNT(*) FROM LoginAttempts" - " WHERE AccountID = ?1 AND Timestamp >= (UNIXEPOCH() - ?2) AND Failed != 0"); +bool GetAccountFailedLoginAttempts(TDatabase *Database, int AccountID, int TimeWindow, int *Result){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "SELECT COUNT(*) FROM LoginAttempts" + " WHERE AccountID = ?1 AND Timestamp >= (UNIXEPOCH() - ?2) AND Failed != 0"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, TimeWindow) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return sqlite3_column_int(Stmt, 0); + *Result = sqlite3_column_int(Stmt, 0); + return true; } -int GetIPAddressFailedLoginAttempts(int IPAddress, int TimeWindow){ - sqlite3_stmt *Stmt = PrepareQuery( - "SELECT COUNT(*) FROM LoginAttempts" - " WHERE IPAddress = ?1 AND Timestamp >= (UNIXEPOCH() - ?2) AND Failed != 0"); +bool GetIPAddressFailedLoginAttempts(TDatabase *Database, int IPAddress, int TimeWindow, int *Result){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, + "SELECT COUNT(*) FROM LoginAttempts" + " WHERE IPAddress = ?1 AND Timestamp >= (UNIXEPOCH() - ?2) AND Failed != 0"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, IPAddress) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, TimeWindow) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return sqlite3_column_int(Stmt, 0); + *Result = sqlite3_column_int(Stmt, 0); + return true; } // House tables //============================================================================== -bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions){ - ASSERT(Auctions != NULL); +bool FinishHouseAuctions(TDatabase *Database, int WorldID, DynamicArray<THouseAuction> *Auctions){ + ASSERT(Database != NULL && Auctions != NULL); // TODO(fusion): If the application crashes while processing finished auctions, // non processed auctions will be lost but with no other side-effects. It could // be an inconvenience but it's not a big problem. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM HouseAuctions" " WHERE WorldID = ?1 AND FinishTime IS NOT NULL AND FinishTime <= UNIXEPOCH()" " RETURNING HouseID, BidderID, BidAmount, FinishTime," @@ -1218,7 +1515,7 @@ bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1232,18 +1529,18 @@ bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions){ Auctions->Push(Auction); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers){ - ASSERT(Transfers != NULL); +bool FinishHouseTransfers(TDatabase *Database, int WorldID, DynamicArray<THouseTransfer> *Transfers){ + ASSERT(Database != NULL && Transfers != NULL); // TODO(fusion): Same as `FinishHouseAuctions` but with house transfers. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM HouseTransfers" " WHERE WorldID = ?1" " RETURNING HouseID, NewOwnerID, Price," @@ -1255,7 +1552,7 @@ bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1268,17 +1565,17 @@ bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers){ Transfers->Push(Transfer); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetFreeAccountEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions){ - ASSERT(Evictions != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetFreeAccountEvictions(TDatabase *Database, int WorldID, DynamicArray<THouseEviction> *Evictions){ + ASSERT(Database != NULL && Evictions != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT O.HouseID, O.OwnerID" " FROM HouseOwners AS O" " LEFT JOIN Characters AS C ON C.CharacterID = O.OwnerID" @@ -1292,7 +1589,7 @@ bool GetFreeAccountEvictions(int WorldID, DynamicArray<THouseEviction> *Eviction AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1303,17 +1600,17 @@ bool GetFreeAccountEvictions(int WorldID, DynamicArray<THouseEviction> *Eviction Evictions->Push(Eviction); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetDeletedCharacterEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions){ - ASSERT(Evictions != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetDeletedCharacterEvictions(TDatabase *Database, int WorldID, DynamicArray<THouseEviction> *Evictions){ + ASSERT(Database != NULL && Evictions != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT O.HouseID, O.OwnerID" " FROM HouseOwners AS O" " LEFT JOIN Characters AS C ON C.CharacterID = O.OwnerID" @@ -1326,7 +1623,7 @@ bool GetDeletedCharacterEvictions(int WorldID, DynamicArray<THouseEviction> *Evi AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1337,16 +1634,17 @@ bool GetDeletedCharacterEvictions(int WorldID, DynamicArray<THouseEviction> *Evi Evictions->Push(Eviction); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool InsertHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){ - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertHouseOwner(TDatabase *Database, int WorldID, int HouseID, int OwnerID, int PaidUntil){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO HouseOwners (WorldID, HouseID, OwnerID, PaidUntil)" " VALUES (?1, ?2, ?3, ?4)"); if(Stmt == NULL){ @@ -1359,20 +1657,21 @@ bool InsertHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, OwnerID) != SQLITE_OK || sqlite3_bind_int(Stmt, 4, PaidUntil) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool UpdateHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){ - sqlite3_stmt *Stmt = PrepareQuery( +bool UpdateHouseOwner(TDatabase *Database, int WorldID, int HouseID, int OwnerID, int PaidUntil){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE HouseOwners SET OwnerID = ?3, PaidUntil = ?4" " WHERE WorldID = ?1 AND HouseID = ?2"); if(Stmt == NULL){ @@ -1385,20 +1684,21 @@ bool UpdateHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil){ || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, OwnerID) != SQLITE_OK || sqlite3_bind_int(Stmt, 4, PaidUntil) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool DeleteHouseOwner(int WorldID, int HouseID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool DeleteHouseOwner(TDatabase *Database, int WorldID, int HouseID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM HouseOwners" " WHERE WorldID = ?1 AND HouseID = ?2"); if(Stmt == NULL){ @@ -1409,21 +1709,21 @@ bool DeleteHouseOwner(int WorldID, int HouseID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } -bool GetHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners){ - ASSERT(Owners != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetHouseOwners(TDatabase *Database, int WorldID, DynamicArray<THouseOwner> *Owners){ + ASSERT(Database != NULL && Owners != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT O.HouseID, O.OwnerID, C.Name, O.PaidUntil" " FROM HouseOwners AS O" " LEFT JOIN Characters AS C ON C.CharacterID = O.OwnerID" @@ -1435,7 +1735,7 @@ bool GetHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1448,17 +1748,17 @@ bool GetHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners){ Owners->Push(Owner); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool GetHouseAuctions(int WorldID, DynamicArray<int> *Auctions){ - ASSERT(Auctions != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetHouseAuctions(TDatabase *Database, int WorldID, DynamicArray<int> *Auctions){ + ASSERT(Database != NULL && Auctions != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT HouseID FROM HouseAuctions WHERE WorldID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -1467,7 +1767,7 @@ bool GetHouseAuctions(int WorldID, DynamicArray<int> *Auctions){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1475,16 +1775,17 @@ bool GetHouseAuctions(int WorldID, DynamicArray<int> *Auctions){ Auctions->Push(sqlite3_column_int(Stmt, 0)); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool StartHouseAuction(int WorldID, int HouseID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool StartHouseAuction(TDatabase *Database, int WorldID, int HouseID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO HouseAuctions (WorldID, HouseID) VALUES (?1, ?2)"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -1494,20 +1795,21 @@ bool StartHouseAuction(int WorldID, int HouseID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, HouseID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool DeleteHouses(int WorldID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool DeleteHouses(TDatabase *Database, int WorldID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM Houses WHERE WorldID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -1516,21 +1818,21 @@ bool DeleteHouses(int WorldID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool InsertHouses(int WorldID, int NumHouses, THouse *Houses){ - ASSERT(NumHouses > 0 && Houses != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertHouses(TDatabase *Database, int WorldID, int NumHouses, THouse *Houses){ + ASSERT(Database != NULL && NumHouses > 0 && Houses != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Houses (WorldID, HouseID, Name, Rent, Description," " Size, PositionX, PositionY, PositionZ, Town, GuildHouse)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)"); @@ -1541,7 +1843,7 @@ bool InsertHouses(int WorldID, int NumHouses, THouse *Houses){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1557,13 +1859,13 @@ bool InsertHouses(int WorldID, int NumHouses, THouse *Houses){ || sqlite3_bind_text(Stmt, 10, Houses[i].Town, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 11, Houses[i].GuildHouse ? 1: 0) != SQLITE_OK){ LOG_ERR("Failed to bind parameters for house %d: %s", - Houses[i].HouseID, sqlite3_errmsg(g_Database)); + Houses[i].HouseID, sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ LOG_ERR("Failed to insert house %d: %s", - Houses[i].HouseID, sqlite3_errmsg(g_Database)); + Houses[i].HouseID, sqlite3_errmsg(Database->Handle)); return false; } @@ -1573,9 +1875,10 @@ bool InsertHouses(int WorldID, int NumHouses, THouse *Houses){ return true; } -bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int BanishmentID){ +bool ExcludeFromAuctions(TDatabase *Database, int WorldID, int CharacterID, int Duration, int BanishmentID){ + ASSERT(Database != NULL); // NOTE(fusion): Same as `DecrementIsOnline`. - sqlite3_stmt *Stmt = PrepareQuery( + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO HouseAuctionExclusions (CharacterID, Issued, Until, BanishmentID)" " SELECT ?2, UNIXEPOCH(), (UNIXEPOCH() + ?3), ?4 FROM Characters" " WHERE WorldID = ?1 AND CharacterID = ?2"); @@ -1589,57 +1892,65 @@ bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int Banishm || sqlite3_bind_int(Stmt, 2, CharacterID) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, Duration) != SQLITE_OK || sqlite3_bind_int(Stmt, 4, BanishmentID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return sqlite3_changes(g_Database) > 0; + return sqlite3_changes(Database->Handle) > 0; } // Banishment tables //============================================================================== -bool IsCharacterNamelocked(int CharacterID){ - TNamelockStatus Status = GetNamelockStatus(CharacterID); - return Status.Namelocked && !Status.Approved; +bool IsCharacterNamelocked(TDatabase *Database, int CharacterID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + TNamelockStatus Status; + if(!GetNamelockStatus(Database, CharacterID, &Status)){ + return false; + } + + *Result = Status.Namelocked && !Status.Approved; + return true; } -TNamelockStatus GetNamelockStatus(int CharacterID){ - TNamelockStatus Status = {}; - sqlite3_stmt *Stmt = PrepareQuery( +bool GetNamelockStatus(TDatabase *Database, int CharacterID, TNamelockStatus *Status){ + ASSERT(Database != NULL && Status != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT Approved FROM Namelocks WHERE CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return Status; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return Status; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return Status; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - Status.Namelocked = (ErrorCode == SQLITE_ROW); - if(Status.Namelocked){ - Status.Approved = (sqlite3_column_int(Stmt, 0) != 0); + memset(Status, 0, sizeof(TNamelockStatus)); + Status->Namelocked = (ErrorCode == SQLITE_ROW); + if(Status->Namelocked){ + Status->Approved = (sqlite3_column_int(Stmt, 0) != 0); } - return Status; + + return true; } -bool InsertNamelock(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment){ - ASSERT(Reason != NULL && Comment != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertNamelock(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment){ + ASSERT(Database != NULL && Reason != NULL && Comment != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Namelocks (CharacterID, IPAddress, GamemasterID, Reason, Comment)" " VALUES (?1, ?2, ?3, ?4, ?5)"); if(Stmt == NULL){ @@ -1653,20 +1964,21 @@ bool InsertNamelock(int CharacterID, int IPAddress, int GamemasterID, || sqlite3_bind_int(Stmt, 3, GamemasterID) != SQLITE_OK || sqlite3_bind_text(Stmt, 4, Reason, -1, NULL) != SQLITE_OK || sqlite3_bind_text(Stmt, 5, Comment, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool IsAccountBanished(int AccountID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool IsAccountBanished(TDatabase *Database, int AccountID, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Banishments" " WHERE AccountID = ?1" " AND (Until = Issued OR Until > UNIXEPOCH())"); @@ -1677,62 +1989,63 @@ bool IsAccountBanished(int AccountID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, AccountID) != SQLITE_OK){ - LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind AccountID: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrorCode == SQLITE_ROW); + *Result = (ErrorCode == SQLITE_ROW); + return true; } -TBanishmentStatus GetBanishmentStatus(int CharacterID){ - TBanishmentStatus Status = {}; - sqlite3_stmt *Stmt = PrepareQuery( +bool GetBanishmentStatus(TDatabase *Database, int CharacterID, TBanishmentStatus *Status){ + ASSERT(Database != NULL && Status != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT B.FinalWarning, (B.Until = B.Issued OR B.Until > UNIXEPOCH())" " FROM Banishments AS B" " LEFT JOIN Characters AS C ON C.AccountID = B.AccountID" " WHERE C.CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return Status; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return Status; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } + memset(Status, 0, sizeof(TBanishmentStatus)); while(sqlite3_step(Stmt) == SQLITE_ROW){ - Status.TimesBanished += 1; + Status->TimesBanished += 1; if(sqlite3_column_int(Stmt, 0) != 0){ - Status.FinalWarning = true; + Status->FinalWarning = true; } if(sqlite3_column_int(Stmt, 1) != 0){ - Status.Banished = true; + Status->Banished = true; } } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return Status; + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return Status; + return true; } -bool InsertBanishment(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment, bool FinalWarning, - int Duration, int *BanishmentID){ - ASSERT(Reason != NULL && Comment != NULL && BanishmentID != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertBanishment(TDatabase *Database, int CharacterID, int IPAddress, int GamemasterID, + const char *Reason, const char *Comment, bool FinalWarning, int Duration, int *BanishmentID){ + ASSERT(Database != NULL && Reason != NULL && Comment != NULL && BanishmentID != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Banishments (AccountID, IPAddress, GamemasterID," " Reason, Comment, FinalWarning, Issued, Until)" " SELECT AccountID, ?2, ?3, ?4, ?5, ?6, UNIXEPOCH(), UNIXEPOCH() + ?7" @@ -1751,12 +2064,12 @@ bool InsertBanishment(int CharacterID, int IPAddress, int GamemasterID, || sqlite3_bind_text(Stmt, 5, Comment, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 6, FinalWarning) != SQLITE_OK || sqlite3_bind_int(Stmt, 7, Duration) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1764,32 +2077,34 @@ bool InsertBanishment(int CharacterID, int IPAddress, int GamemasterID, return true; } -int GetNotationCount(int CharacterID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool GetNotationCount(TDatabase *Database, int CharacterID, int *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT COUNT(*) FROM Notations WHERE CharacterID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, CharacterID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } if(sqlite3_step(Stmt) != SQLITE_ROW){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); + return false; } - return sqlite3_column_int(Stmt, 0); + *Result = sqlite3_column_int(Stmt, 0); + return true; } -bool InsertNotation(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment){ - ASSERT(Reason != NULL && Comment != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertNotation(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment){ + ASSERT(Database != NULL && Reason != NULL && Comment != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO Notations (CharacterID, IPAddress," " GamemasterID, Reason, Comment)" " VALUES (?1, ?2, ?3, ?4, ?5)"); @@ -1804,20 +2119,21 @@ bool InsertNotation(int CharacterID, int IPAddress, int GamemasterID, || sqlite3_bind_int(Stmt, 3, GamemasterID) != SQLITE_OK || sqlite3_bind_text(Stmt, 4, Reason, -1, NULL) != SQLITE_OK || sqlite3_bind_text(Stmt, 5, Comment, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool IsIPBanished(int IPAddress){ - sqlite3_stmt *Stmt = PrepareQuery( +bool IsIPBanished(TDatabase *Database, int IPAddress, bool *Result){ + ASSERT(Database != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM IPBanishments" " WHERE IPAddress = ?1" " AND (Until = Issued OR Until > UNIXEPOCH())"); @@ -1828,23 +2144,24 @@ bool IsIPBanished(int IPAddress){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, IPAddress) != SQLITE_OK){ - LOG_ERR("Failed to bind IPAddress: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind IPAddress: %s", sqlite3_errmsg(Database->Handle)); return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrorCode == SQLITE_ROW); + *Result = (ErrorCode == SQLITE_ROW); + return true; } -bool InsertIPBanishment(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment, int Duration){ - ASSERT(Reason != NULL && Comment != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertIPBanishment(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment, int Duration){ + ASSERT(Database != NULL && Reason != NULL && Comment != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO IPBanishments (CharacterID, IPAddress," " GamemasterID, Reason, Comment, Issued, Until)" " VALUES (?1, ?2, ?3, ?4, ?5, UNIXEPOCH(), UNIXEPOCH() + ?6)"); @@ -1860,51 +2177,52 @@ bool InsertIPBanishment(int CharacterID, int IPAddress, int GamemasterID, || sqlite3_bind_text(Stmt, 4, Reason, -1, NULL) != SQLITE_OK || sqlite3_bind_text(Stmt, 5, Comment, -1, NULL) != SQLITE_OK || sqlite3_bind_int(Stmt, 6, Duration) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool IsStatementReported(int WorldID, TStatement *Statement){ - ASSERT(Statement != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool IsStatementReported(TDatabase *Database, int WorldID, TStatement *Statement, bool *Result){ + ASSERT(Database != NULL && Statement != NULL && Result != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT 1 FROM Statements" " WHERE WorldID = ?1 AND Timestamp = ?2 AND StatementID = ?3"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); - return 0; + return false; } AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, Statement->Timestamp) != SQLITE_OK || sqlite3_bind_int(Stmt, 3, Statement->StatementID) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); - return 0; + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); + return false; } int ErrorCode = sqlite3_step(Stmt); if(ErrorCode != SQLITE_ROW && ErrorCode != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - return (ErrorCode == SQLITE_ROW); + *Result = (ErrorCode == SQLITE_ROW); + return true; } -bool InsertStatements(int WorldID, int NumStatements, TStatement *Statements){ +bool InsertStatements(TDatabase *Database, int WorldID, int NumStatements, TStatement *Statements){ // NOTE(fusion): Use the `IGNORE` conflict resolution because different // reports may include the same statements for context and I assume it's // not uncommon to see overlaps. - ASSERT(NumStatements > 0 && Statements != NULL); - sqlite3_stmt *Stmt = PrepareQuery( + ASSERT(Database != NULL && NumStatements > 0 && Statements != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT OR IGNORE INTO Statements (WorldID, Timestamp," " StatementID, CharacterID, Channel, Text)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); @@ -1915,7 +2233,7 @@ bool InsertStatements(int WorldID, int NumStatements, TStatement *Statements){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1931,13 +2249,13 @@ bool InsertStatements(int WorldID, int NumStatements, TStatement *Statements){ || sqlite3_bind_text(Stmt, 5, Statements[i].Channel, -1, NULL) != SQLITE_OK || sqlite3_bind_text(Stmt, 6, Statements[i].Text, -1, NULL) != SQLITE_OK){ LOG_ERR("Failed to bind parameters for statement %d: %s", - Statements[i].StatementID, sqlite3_errmsg(g_Database)); + Statements[i].StatementID, sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ LOG_ERR("Failed to insert statement %d: %s", - Statements[i].StatementID, sqlite3_errmsg(g_Database)); + Statements[i].StatementID, sqlite3_errmsg(Database->Handle)); return false; } @@ -1947,10 +2265,10 @@ bool InsertStatements(int WorldID, int NumStatements, TStatement *Statements){ return true; } -bool InsertReportedStatement(int WorldID, TStatement *Statement, int BanishmentID, - int ReporterID, const char *Reason, const char *Comment){ - ASSERT(Statement != NULL && Reason != NULL && Comment != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertReportedStatement(TDatabase *Database, int WorldID, TStatement *Statement, + int BanishmentID, int ReporterID, const char *Reason, const char *Comment){ + ASSERT(Database != NULL && Statement != NULL && Reason != NULL && Comment != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO ReportedStatements (WorldID, Timestamp," " StatementID, CharacterID, BanishmentID, ReporterID," " Reason, Comment)" @@ -1969,12 +2287,12 @@ bool InsertReportedStatement(int WorldID, TStatement *Statement, int BanishmentI || sqlite3_bind_int(Stmt, 6, ReporterID) != SQLITE_OK || sqlite3_bind_text(Stmt, 7, Reason, -1, NULL) != SQLITE_OK || sqlite3_bind_text(Stmt, 8, Comment, -1, NULL) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -1983,9 +2301,9 @@ bool InsertReportedStatement(int WorldID, TStatement *Statement, int BanishmentI // Info tables //============================================================================== -bool GetKillStatistics(int WorldID, DynamicArray<TKillStatistics> *Stats){ - ASSERT(Stats != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetKillStatistics(TDatabase *Database, int WorldID, DynamicArray<TKillStatistics> *Stats){ + ASSERT(Database != NULL && Stats != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT RaceName, TimesKilled, PlayersKilled" " FROM KillStatistics WHERE WorldID = ?1"); if(Stmt == NULL){ @@ -1995,7 +2313,7 @@ bool GetKillStatistics(int WorldID, DynamicArray<TKillStatistics> *Stats){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -2007,16 +2325,17 @@ bool GetKillStatistics(int WorldID, DynamicArray<TKillStatistics> *Stats){ Stats->Push(Entry); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats){ - sqlite3_stmt *Stmt = PrepareQuery( +bool MergeKillStatistics(TDatabase *Database, int WorldID, int NumStats, TKillStatistics *Stats){ + ASSERT(Database != NULL && NumStats > 0 && Stats != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO KillStatistics (WorldID, RaceName, TimesKilled, PlayersKilled)" " VALUES (?1, ?2, ?3, ?4)" " ON CONFLICT DO UPDATE SET TimesKilled = TimesKilled + Excluded.TimesKilled," @@ -2028,7 +2347,7 @@ bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -2037,13 +2356,13 @@ bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats){ || sqlite3_bind_int(Stmt, 3, Stats[i].TimesKilled) != SQLITE_OK || sqlite3_bind_int(Stmt, 4, Stats[i].PlayersKilled) != SQLITE_OK){ LOG_ERR("Failed to bind parameters for \"%s\" stats: %s", - Stats[i].RaceName, sqlite3_errmsg(g_Database)); + Stats[i].RaceName, sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ LOG_ERR("Failed to insert \"%s\" stats: %s", - Stats[i].RaceName, sqlite3_errmsg(g_Database)); + Stats[i].RaceName, sqlite3_errmsg(Database->Handle)); return false; } @@ -2053,9 +2372,9 @@ bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats){ return true; } -bool GetOnlineCharacters(int WorldID, DynamicArray<TOnlineCharacter> *Characters){ - ASSERT(Characters != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool GetOnlineCharacters(TDatabase *Database, int WorldID, DynamicArray<TOnlineCharacter> *Characters){ + ASSERT(Database != NULL && Characters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "SELECT Name, Level, Profession" " FROM OnlineCharacters WHERE WorldID = ?1"); if(Stmt == NULL){ @@ -2065,7 +2384,7 @@ bool GetOnlineCharacters(int WorldID, DynamicArray<TOnlineCharacter> *Characters AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -2077,16 +2396,17 @@ bool GetOnlineCharacters(int WorldID, DynamicArray<TOnlineCharacter> *Characters Characters->Push(Character); } - if(sqlite3_errcode(g_Database) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + if(sqlite3_errcode(Database->Handle) != SQLITE_DONE){ + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool DeleteOnlineCharacters(int WorldID){ - sqlite3_stmt *Stmt = PrepareQuery( +bool DeleteOnlineCharacters(TDatabase *Database, int WorldID){ + ASSERT(Database != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "DELETE FROM OnlineCharacters WHERE WorldID = ?1"); if(Stmt == NULL){ LOG_ERR("Failed to prepare query"); @@ -2095,20 +2415,22 @@ bool DeleteOnlineCharacters(int WorldID){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } return true; } -bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Characters){ - sqlite3_stmt *Stmt = PrepareQuery( +bool InsertOnlineCharacters(TDatabase *Database, int WorldID, + int NumCharacters, TOnlineCharacter *Characters){ + ASSERT(Database != NULL && Characters != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "INSERT INTO OnlineCharacters (WorldID, Name, Level, Profession)" " VALUES (?1, ?2, ?3, ?4)"); if(Stmt == NULL){ @@ -2118,7 +2440,7 @@ bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Ch AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK){ - LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind WorldID: %s", sqlite3_errmsg(Database->Handle)); return false; } @@ -2127,13 +2449,13 @@ bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Ch || sqlite3_bind_int(Stmt, 3, Characters[i].Level) != SQLITE_OK || sqlite3_bind_text(Stmt, 4, Characters[i].Profession, -1, NULL) != SQLITE_OK){ LOG_ERR("Failed to bind parameters for character \"%s\": %s", - Characters[i].Name, sqlite3_errmsg(g_Database)); + Characters[i].Name, sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ LOG_ERR("Failed to insert character \"%s\": %s", - Characters[i].Name, sqlite3_errmsg(g_Database)); + Characters[i].Name, sqlite3_errmsg(Database->Handle)); return false; } @@ -2143,9 +2465,9 @@ bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Ch return true; } -bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){ - ASSERT(NewRecord != NULL); - sqlite3_stmt *Stmt = PrepareQuery( +bool CheckOnlineRecord(TDatabase *Database, int WorldID, int NumCharacters, bool *NewRecord){ + ASSERT(Database != NULL && NewRecord != NULL); + sqlite3_stmt *Stmt = PrepareQuery(Database, "UPDATE Worlds SET OnlineRecord = ?2," " OnlineRecordTimestamp = UNIXEPOCH()" " WHERE WorldID = ?1 AND OnlineRecord < ?2"); @@ -2157,279 +2479,15 @@ bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord){ AutoStmtReset StmtReset(Stmt); if(sqlite3_bind_int(Stmt, 1, WorldID) != SQLITE_OK || sqlite3_bind_int(Stmt, 2, NumCharacters) != SQLITE_OK){ - LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to bind parameters: %s", sqlite3_errmsg(Database->Handle)); return false; } if(sqlite3_step(Stmt) != SQLITE_DONE){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); + LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(Database->Handle)); return false; } - *NewRecord = sqlite3_changes(g_Database) > 0; + *NewRecord = sqlite3_changes(Database->Handle) > 0; return true; } - -// Database Initialization -//============================================================================== -// NOTE(fusion): From `https://www.sqlite.org/pragma.html`: -// "Some pragmas take effect during the SQL compilation stage, not the execution -// stage. This means if using the C-language sqlite3_prepare(), sqlite3_step(), -// sqlite3_finalize() API (or similar in a wrapper interface), the pragma may run -// during the sqlite3_prepare() call, not during the sqlite3_step() call as normal -// SQL statements do. Or the pragma might run during sqlite3_step() just like normal -// SQL statements. Whether or not the pragma runs during sqlite3_prepare() or -// sqlite3_step() depends on the pragma and on the specific release of SQLite." -// -// Depending on the pragma, queries will fail in the sqlite3_prepare() stage when -// using bound parameters. This means we need to assemble the entire query before -// hand with snprintf or other similar formatting functions. In particular, this -// rule apply for `application_id` and `user_version` which we modify. - -bool FileExists(const char *FileName){ - FILE *File = fopen(FileName, "rb"); - bool Result = (File != NULL); - if(Result){ - fclose(File); - } - return Result; -} - -bool ExecFile(const char *FileName){ - FILE *File = fopen(FileName, "rb"); - if(File == NULL){ - LOG_ERR("Failed to open file \"%s\"", FileName); - return false; - } - - fseek(File, 0, SEEK_END); - usize FileSize = (usize)ftell(File); - fseek(File, 0, SEEK_SET); - - bool Result = true; - if(FileSize > 0){ - char *Text = (char*)malloc(FileSize + 1); - Text[FileSize] = 0; - if(Result && fread(Text, 1, FileSize, File) != FileSize){ - LOG_ERR("Failed to read \"%s\" (ferror: %d, feof: %d)", - FileName, ferror(File), feof(File)); - Result = false; - } - - if(Result && sqlite3_exec(g_Database, Text, NULL, NULL, NULL) != SQLITE_OK){ - LOG_ERR("Failed to execute \"%s\": %s", - FileName, sqlite3_errmsg(g_Database)); - Result = false; - } - - free(Text); - } - - fclose(File); - return Result; -} - -bool ExecInternal(const char *Format, ...){ - va_list ap; - va_start(ap, Format); - char Text[1024]; - int Written = vsnprintf(Text, sizeof(Text), Format, ap); - va_end(ap); - - if(Written >= (int)sizeof(Text)){ - LOG_ERR("Query is too long"); - return false; - } - - if(sqlite3_exec(g_Database, Text, NULL, NULL, NULL) != SQLITE_OK){ - LOG_ERR("Failed to execute query: %s", sqlite3_errmsg(g_Database)); - return false; - } - - return true; -} - -bool GetPragmaInt(const char *Name, int *OutValue){ - char Text[1024]; - snprintf(Text, sizeof(Text), "PRAGMA %s", Name); - - sqlite3_stmt *Stmt; - if(sqlite3_prepare_v2(g_Database, Text, -1, &Stmt, NULL) != SQLITE_OK){ - LOG_ERR("Failed to retrieve %s (PREP): %s", Name, sqlite3_errmsg(g_Database)); - return false; - } - - bool Result = (sqlite3_step(Stmt) == SQLITE_ROW); - if(!Result){ - LOG_ERR("Failed to retrieve %s (PREP): %s", Name, sqlite3_errmsg(g_Database)); - }else if(OutValue){ - *OutValue = sqlite3_column_int(Stmt, 0); - } - - sqlite3_finalize(Stmt); - return Result; -} - -bool InitDatabaseSchema(void){ - TransactionScope Tx("SchemaInit"); - if(!Tx.Begin()){ - return false; - } - - if(!ExecFile("sql/schema.sql")){ - LOG_ERR("Failed to execute \"sql/schema.sql\""); - return false; - } - - if(!ExecInternal("PRAGMA application_id = %d", g_ApplicationID)){ - LOG_ERR("Failed to set application id"); - return false; - } - - if(!ExecInternal("PRAGMA user_version = 1")){ - LOG_ERR("Failed to set user version"); - return false; - } - - if(!Tx.Commit()){ - return false; - } - - return true; -} - -bool UpgradeDatabaseSchema(int UserVersion){ - char FileName[256]; - int NewVersion = UserVersion; - while(true){ - snprintf(FileName, sizeof(FileName), "sql/upgrade-%d.sql", NewVersion); - if(FileExists(FileName)){ - NewVersion += 1; - }else{ - break; - } - } - - if(UserVersion != NewVersion){ - LOG("Upgrading database schema to version %d", NewVersion); - - TransactionScope Tx("SchemaUpgrade"); - if(!Tx.Begin()){ - return false; - } - - while(UserVersion < NewVersion){ - snprintf(FileName, sizeof(FileName), "upgrade-%d.sql", UserVersion); - if(!ExecFile(FileName)){ - LOG_ERR("Failed to execute \"%s\"", FileName); - return false; - } - UserVersion += 1; - } - - if(!ExecInternal("PRAGMA user_version = %d", UserVersion)){ - LOG_ERR("Failed to set user version"); - return false; - } - - if(!Tx.Commit()){ - return false; - } - } - - return true; -} - -bool CheckDatabaseSchema(void){ - int ApplicationID, UserVersion; - if(!GetPragmaInt("application_id", &ApplicationID) - || !GetPragmaInt("user_version", &UserVersion)){ - return false; - } - - if(ApplicationID != g_ApplicationID){ - if(ApplicationID != 0){ - LOG_ERR("Database has unknown application id %08X (expected %08X)", - ApplicationID, g_ApplicationID); - return false; - }else if(UserVersion != 0){ - LOG_ERR("Database has non zero user version %d", UserVersion); - return false; - }else if(!InitDatabaseSchema()){ - LOG_ERR("Failed to initialize database schema"); - return false; - } - - UserVersion = 1; - } - - if(!UpgradeDatabaseSchema(UserVersion)){ - LOG_ERR("Failed to upgrade database schema"); - return false; - } - - LOG("Database version: %d", UserVersion); - return true; -} - -bool InitDatabase(void){ - int Flags = SQLITE_OPEN_READWRITE - | SQLITE_OPEN_CREATE - | SQLITE_OPEN_NOMUTEX; - if(sqlite3_open_v2(g_Config.DatabaseFile, &g_Database, Flags, NULL) != SQLITE_OK){ - LOG_ERR("Failed to open database at \"%s\": %s\n", - g_Config.DatabaseFile, sqlite3_errmsg(g_Database)); - return false; - } - - if(sqlite3_db_readonly(g_Database, NULL)){ - LOG_ERR("Failed to open database file \"%s\" with WRITE PERMISSIONS." - " Make sure it has the appropriate permissions and is owned" - " by the same user running the query manager.", - g_Config.DatabaseFile); - return false; - } - - if(!InitStatementCache()){ - LOG_ERR("Failed to initialize statement cache"); - return false; - } - - if(!CheckDatabaseSchema()){ - LOG_ERR("Failed to check database schema"); - return false; - } - - return true; -} - -void ExitDatabase(void){ - ExitStatementCache(); - - if(g_Database != NULL){ - // NOTE(fusion): `sqlite3_close` can only fail if there are associated - // prepared statements, blob handles, or backup objects that were not - // finalized. - if(sqlite3_close(g_Database) == SQLITE_OK){ - g_Database = NULL; - }else{ - LOG_ERR("Failed to close database: %s", sqlite3_errmsg(g_Database)); - } - } -} - -TDatabase *DatabaseOpen(void){ - //TODO - //return NULL; - return (TDatabase*)1; -} - -void DatabaseClose(TDatabase *Database){ - //TODO -} - -bool DatabaseCheckpoint(TDatabase *Database){ - //TODO - return false; -} - diff --git a/src/query.cc b/src/query.cc index 35b9aca..c55806d 100644 --- a/src/query.cc +++ b/src/query.cc @@ -25,124 +25,62 @@ struct TWorker{ pthread_t Thread; }; +static int g_NumWorkers; static TWorker *g_Workers; static TQueryQueue *g_QueryQueue; -// Query Request +// Query Name //============================================================================== -TWriteBuffer QueryBeginRequest(TQuery *Query, int QueryType){ - Query->Request = TReadBuffer{}; - TWriteBuffer WriteBuffer = TWriteBuffer(Query->Buffer, Query->BufferSize); - WriteBuffer.Write8((uint8)QueryType); - return WriteBuffer; -} - -bool QueryFinishRequest(TQuery *Query, TWriteBuffer WriteBuffer){ - ASSERT(WriteBuffer.Buffer == Query->Buffer - && WriteBuffer.Size == Query->BufferSize - && WriteBuffer.Position >= 1); - bool Result = !WriteBuffer.Overflowed(); - if(Result){ - Query->Request = TReadBuffer(WriteBuffer.Buffer, WriteBuffer.Position); - } - return Result; +const char *QueryName(int QueryType){ + const char *Name = "UNKNOWN"; + switch(QueryType){ + case QUERY_LOGIN: Name = "LOGIN"; break; + case QUERY_INTERNAL_RESOLVE_WORLD: Name = "INTERNAL_RESOLVE_WORLD"; break; + case QUERY_CHECK_ACCOUNT_PASSWORD: Name = "CHECK_ACCOUNT_PASSWORD"; break; + case QUERY_LOGIN_ACCOUNT: Name = "LOGIN_ACCOUNT"; break; + case QUERY_LOGIN_ADMIN: Name = "LOGIN_ADMIN"; break; + case QUERY_LOGIN_GAME: Name = "LOGIN_GAME"; break; + case QUERY_LOGOUT_GAME: Name = "LOGOUT_GAME"; break; + case QUERY_SET_NAMELOCK: Name = "SET_NAMELOCK"; break; + case QUERY_BANISH_ACCOUNT: Name = "BANISH_ACCOUNT"; break; + case QUERY_SET_NOTATION: Name = "SET_NOTATION"; break; + case QUERY_REPORT_STATEMENT: Name = "REPORT_STATEMENT"; break; + case QUERY_BANISH_IP_ADDRESS: Name = "BANISH_IP_ADDRESS"; break; + case QUERY_LOG_CHARACTER_DEATH: Name = "LOG_CHARACTER_DEATH"; break; + case QUERY_ADD_BUDDY: Name = "ADD_BUDDY"; break; + case QUERY_REMOVE_BUDDY: Name = "REMOVE_BUDDY"; break; + case QUERY_DECREMENT_IS_ONLINE: Name = "DECREMENT_IS_ONLINE"; break; + case QUERY_FINISH_AUCTIONS: Name = "FINISH_AUCTIONS"; break; + case QUERY_TRANSFER_HOUSES: Name = "TRANSFER_HOUSES"; break; + case QUERY_EVICT_FREE_ACCOUNTS: Name = "EVICT_FREE_ACCOUNTS"; break; + case QUERY_EVICT_DELETED_CHARACTERS: Name = "EVICT_DELETED_CHARACTERS"; break; + case QUERY_EVICT_EX_GUILDLEADERS: Name = "EVICT_EX_GUILDLEADERS"; break; + case QUERY_INSERT_HOUSE_OWNER: Name = "INSERT_HOUSE_OWNER"; break; + case QUERY_UPDATE_HOUSE_OWNER: Name = "UPDATE_HOUSE_OWNER"; break; + case QUERY_DELETE_HOUSE_OWNER: Name = "DELETE_HOUSE_OWNER"; break; + case QUERY_GET_HOUSE_OWNERS: Name = "GET_HOUSE_OWNERS"; break; + case QUERY_GET_AUCTIONS: Name = "GET_AUCTIONS"; break; + case QUERY_START_AUCTION: Name = "START_AUCTION"; break; + case QUERY_INSERT_HOUSES: Name = "INSERT_HOUSES"; break; + case QUERY_CLEAR_IS_ONLINE: Name = "CLEAR_IS_ONLINE"; break; + case QUERY_CREATE_PLAYERLIST: Name = "CREATE_PLAYERLIST"; break; + case QUERY_LOG_KILLED_CREATURES: Name = "LOG_KILLED_CREATURES"; break; + case QUERY_LOAD_PLAYERS: Name = "LOAD_PLAYERS"; break; + case QUERY_EXCLUDE_FROM_AUCTIONS: Name = "EXCLUDE_FROM_AUCTIONS"; break; + case QUERY_CANCEL_HOUSE_TRANSFER: Name = "CANCEL_HOUSE_TRANSFER"; break; + case QUERY_LOAD_WORLD_CONFIG: Name = "LOAD_WORLD_CONFIG"; break; + case QUERY_CREATE_ACCOUNT: Name = "CREATE_ACCOUNT"; break; + case QUERY_CREATE_CHARACTER: Name = "CREATE_CHARACTER"; break; + case QUERY_GET_ACCOUNT_SUMMARY: Name = "GET_ACCOUNT_SUMMARY"; break; + case QUERY_GET_CHARACTER_PROFILE: Name = "GET_CHARACTER_PROFILE"; break; + case QUERY_GET_WORLDS: Name = "GET_WORLDS"; break; + case QUERY_GET_ONLINE_CHARACTERS: Name = "GET_ONLINE_CHARACTERS"; break; + case QUERY_GET_KILL_STATISTICS: Name = "GET_KILL_STATISTICS"; break; + default: Name = "UNKNOWN"; break; + } + return Name; } -bool QueryInternalResolveWorld(TQuery *Query, const char *World){ - TWriteBuffer WriteBuffer = QueryBeginRequest(Query, QUERY_INTERNAL_RESOLVE_WORLD); - WriteBuffer.WriteString(World); - return QueryFinishRequest(Query, WriteBuffer); -} - -// Query Response -//============================================================================== -TWriteBuffer *QueryBeginResponse(TQuery *Query, int Status){ - Query->QueryStatus = Status; - Query->Response = TWriteBuffer(Query->Buffer, Query->BufferSize); - Query->Response.Write16(0); - Query->Response.Write8((uint8)Status); - return &Query->Response; -} - -bool QueryFinishResponse(TQuery *Query){ - TWriteBuffer *Response = &Query->Response; - if(Response->Position <= 2){ - LOG_ERR("Invalid response size"); - return false; - } - - int PayloadSize = Response->Position - 2; - if(PayloadSize < 0xFFFF){ - Response->Rewrite16(0, (uint16)PayloadSize); - }else{ - Response->Rewrite16(0, 0xFFFF); - Response->Insert32(2, (uint32)PayloadSize); - } - - return !Response->Overflowed(); -} - -void QueryOk(TQuery *Query){ - QueryBeginResponse(Query, QUERY_STATUS_OK); - QueryFinishResponse(Query); -} - -void QueryError(TQuery *Query, int ErrorCode){ - TWriteBuffer *Response = QueryBeginResponse(Query, QUERY_STATUS_ERROR); - Response->Write8((uint8)ErrorCode); - QueryFinishResponse(Query); -} - -void QueryFailed(TQuery *Query){ - QueryBeginResponse(Query, QUERY_STATUS_FAILED); - QueryFinishResponse(Query); -} - - -// Query Processing -//============================================================================== -static bool ProcessInternalResolveWorld(TDatabase *Database, TQuery *Query); -static bool ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query); -static bool ProcessLoginAccount(TDatabase *Database, TQuery *Query); -static bool ProcessLoginAdmin(TDatabase *Database, TQuery *Query); -static bool ProcessLoginGame(TDatabase *Database, TQuery *Query); -static bool ProcessLogoutGame(TDatabase *Database, TQuery *Query); -static bool ProcessSetNamelock(TDatabase *Database, TQuery *Query); -static bool ProcessBanishAccount(TDatabase *Database, TQuery *Query); -static bool ProcessSetNotation(TDatabase *Database, TQuery *Query); -static bool ProcessReportStatement(TDatabase *Database, TQuery *Query); -static bool ProcessBanishIpAddress(TDatabase *Database, TQuery *Query); -static bool ProcessLogCharacterDeath(TDatabase *Database, TQuery *Query); -static bool ProcessAddBuddy(TDatabase *Database, TQuery *Query); -static bool ProcessRemoveBuddy(TDatabase *Database, TQuery *Query); -static bool ProcessDecrementIsOnline(TDatabase *Database, TQuery *Query); -static bool ProcessFinishAuctions(TDatabase *Database, TQuery *Query); -static bool ProcessTransferHouses(TDatabase *Database, TQuery *Query); -static bool ProcessEvictFreeAccounts(TDatabase *Database, TQuery *Query); -static bool ProcessEvictDeletedCharacters(TDatabase *Database, TQuery *Query); -static bool ProcessEvictExGuildleaders(TDatabase *Database, TQuery *Query); -static bool ProcessInsertHouseOwner(TDatabase *Database, TQuery *Query); -static bool ProcessUpdateHouseOwner(TDatabase *Database, TQuery *Query); -static bool ProcessDeleteHouseOwner(TDatabase *Database, TQuery *Query); -static bool ProcessGetHouseOwners(TDatabase *Database, TQuery *Query); -static bool ProcessGetAuctions(TDatabase *Database, TQuery *Query); -static bool ProcessStartAuction(TDatabase *Database, TQuery *Query); -static bool ProcessInsertHouses(TDatabase *Database, TQuery *Query); -static bool ProcessClearIsOnline(TDatabase *Database, TQuery *Query); -static bool ProcessCreatePlayerlist(TDatabase *Database, TQuery *Query); -static bool ProcessLogKilledCreatures(TDatabase *Database, TQuery *Query); -static bool ProcessLoadPlayers(TDatabase *Database, TQuery *Query); -static bool ProcessExcludeFromAuctions(TDatabase *Database, TQuery *Query); -static bool ProcessCancelHouseTransfer(TDatabase *Database, TQuery *Query); -static bool ProcessLoadWorldConfig(TDatabase *Database, TQuery *Query); -static bool ProcessCreateAccount(TDatabase *Database, TQuery *Query); -static bool ProcessCreateCharacter(TDatabase *Database, TQuery *Query); -static bool ProcessGetAccountSummary(TDatabase *Database, TQuery *Query); -static bool ProcessGetCharacterProfile(TDatabase *Database, TQuery *Query); -static bool ProcessGetWorlds(TDatabase *Database, TQuery *Query); -static bool ProcessGetOnlineCharacters(TDatabase *Database, TQuery *Query); -static bool ProcessGetKillStatistics(TDatabase *Database, TQuery *Query); - // Query Queue and Workers //============================================================================== TQuery *QueryNew(void){ @@ -232,26 +170,26 @@ static void *WorkerThread(void *Data){ ASSERT(Data != NULL); TWorker *Worker = (TWorker*)Data; if(AtomicLoad(&Worker->Stop)){ - LOG_WARN("Worker#%d Stopping on entry...", Worker->WorkerID); + LOG_WARN("Worker#%d: Stopping on entry...", Worker->WorkerID); AtomicStore(&Worker->Status, WORKER_STATUS_DONE); return NULL; } TDatabase *Database = DatabaseOpen(); if(Database == NULL){ - LOG_ERR("Worker#%d Failed to connect to database", Worker->WorkerID); + LOG_ERR("Worker#%d: Failed to connect to database", Worker->WorkerID); AtomicStore(&Worker->Status, WORKER_STATUS_DONE); return NULL; } - LOG("Worker#%d ACTIVE...", Worker->WorkerID); + LOG("Worker#%d: ACTIVE...", Worker->WorkerID); AtomicStore(&Worker->Status, WORKER_STATUS_ACTIVE); while(TQuery *Query = QueryDequeue(&Worker->Stop)){ bool (*ProcessQuery)(TDatabase*, TQuery*) = NULL; Query->QueryType = Query->Request.Read8(); - /*switch(Query->QueryType){ + switch(Query->QueryType){ case QUERY_INTERNAL_RESOLVE_WORLD: ProcessQuery = ProcessInternalResolveWorld; break; - case QUERY_CHECK_ACCOUNT_PASSWORD: ProcessQuery = ProcessCheckAccountPassword; break; +/* case QUERY_CHECK_ACCOUNT_PASSWORD: ProcessQuery = ProcessCheckAccountPassword; break; case QUERY_LOGIN_ACCOUNT: ProcessQuery = ProcessLoginAccount; break; case QUERY_LOGIN_ADMIN: ProcessQuery = ProcessLoginAdmin; break; case QUERY_LOGIN_GAME: ProcessQuery = ProcessLoginGame; break; @@ -291,7 +229,8 @@ static void *WorkerThread(void *Data){ case QUERY_GET_WORLDS: ProcessQuery = ProcessGetWorlds; break; case QUERY_GET_ONLINE_CHARACTERS: ProcessQuery = ProcessGetOnlineCharacters; break; case QUERY_GET_KILL_STATISTICS: ProcessQuery = ProcessGetKillStatistics; break; - }*/ +*/ + } if(ProcessQuery != NULL && DatabaseCheckpoint(Database)){ // NOTE(fusion): A minimum of 1 attempt is ASSUMED. @@ -302,6 +241,12 @@ static void *WorkerThread(void *Data){ break; } Attempts -= 1; + + // NOTE(fusion): This one is important because we want to know + // whether some query is failing too often, in which case there + // may be a problem with it. + LOG_WARN("Worker#%d: Query %s failed, retrying...", + Worker->WorkerID, QueryName(Query->QueryType)); } }else{ QueryFailed(Query); @@ -310,7 +255,7 @@ static void *WorkerThread(void *Data){ QueryDone(Query); } - LOG("Worker#%d DONE...", Worker->WorkerID); + LOG("Worker#%d: DONE...", Worker->WorkerID); DatabaseClose(Database); AtomicStore(&Worker->Status, WORKER_STATUS_DONE); return NULL; @@ -331,9 +276,13 @@ bool InitQuery(void){ g_QueryQueue->MaxQueries = 2 * g_Config.MaxConnections; g_QueryQueue->Queries = (TQuery**)calloc(g_QueryQueue->MaxQueries, sizeof(TQuery*)); - int NumWorkers = g_Config.QueryWorkerThreads; - g_Workers = (TWorker*)calloc(NumWorkers, sizeof(TWorker)); - for(int i = 0; i < NumWorkers; i += 1){ + g_NumWorkers = g_Config.QueryWorkerThreads; + if(g_NumWorkers > DatabaseMaxConcurrency()){ + g_NumWorkers = DatabaseMaxConcurrency(); + } + + g_Workers = (TWorker*)calloc(g_NumWorkers, sizeof(TWorker)); + for(int i = 0; i < g_NumWorkers; i += 1){ TWorker *Worker = &g_Workers[i]; Worker->WorkerID = i; AtomicStore(&Worker->Status, WORKER_STATUS_SPAWNING); @@ -350,7 +299,7 @@ bool InitQuery(void){ int NumWorkersSpawning = 0; int NumWorkersActive = 0; int NumWorkersDone = 0; - for(int i = 0; i < NumWorkers; i += 1){ + for(int i = 0; i < g_NumWorkers; i += 1){ int Status = AtomicLoad(&g_Workers[i].Status); if(Status == WORKER_STATUS_SPAWNING){ NumWorkersSpawning += 1; @@ -373,7 +322,7 @@ bool InitQuery(void){ return false; } - ASSERT(NumWorkersActive == NumWorkers); + ASSERT(NumWorkersActive == g_NumWorkers); break; } @@ -384,15 +333,15 @@ void ExitQuery(void){ if(g_Workers != NULL){ ASSERT(g_QueryQueue != NULL); - for(int i = 0; i < g_Config.QueryWorkerThreads; i += 1){ + for(int i = 0; i < g_NumWorkers; i += 1){ AtomicStore(&g_Workers[i].Stop, 1); } pthread_cond_broadcast(&g_QueryQueue->WorkAvailable); - for(int i = 0; i < g_Config.QueryWorkerThreads; i += 1){ + for(int i = 0; i < g_NumWorkers; i += 1){ // IMPORTANT(fusion): There is no "invalid" pthread handle so this // is non-standard behaviour. Nevertheless the game server uses it - // and seems to be consistent on Linux, which is what matters at + // and it seems to be consistent on Linux, which is what matters at // the end of the day. if(g_Workers[i].Thread != 0){ pthread_join(g_Workers[i].Thread, NULL); @@ -420,6 +369,164 @@ void ExitQuery(void){ } } +// Query Request +//============================================================================== +TWriteBuffer QueryBeginRequest(TQuery *Query, int QueryType){ + Query->Request = TReadBuffer{}; + TWriteBuffer WriteBuffer = TWriteBuffer(Query->Buffer, Query->BufferSize); + WriteBuffer.Write8((uint8)QueryType); + return WriteBuffer; +} + +bool QueryFinishRequest(TQuery *Query, TWriteBuffer WriteBuffer){ + ASSERT(WriteBuffer.Buffer == Query->Buffer + && WriteBuffer.Size == Query->BufferSize + && WriteBuffer.Position >= 1); + bool Result = !WriteBuffer.Overflowed(); + if(Result){ + Query->Request = TReadBuffer(WriteBuffer.Buffer, WriteBuffer.Position); + } + return Result; +} + +bool QueryInternalResolveWorld(TQuery *Query, const char *World){ + TWriteBuffer WriteBuffer = QueryBeginRequest(Query, QUERY_INTERNAL_RESOLVE_WORLD); + WriteBuffer.WriteString(World); + return QueryFinishRequest(Query, WriteBuffer); +} + +// Query Response +//============================================================================== +TWriteBuffer *QueryBeginResponse(TQuery *Query, int Status){ + Query->QueryStatus = Status; + Query->Response = TWriteBuffer(Query->Buffer, Query->BufferSize); + Query->Response.Write16(0); + Query->Response.Write8((uint8)Status); + return &Query->Response; +} + +bool QueryFinishResponse(TQuery *Query){ + TWriteBuffer *Response = &Query->Response; + if(Response->Position <= 2){ + LOG_ERR("Invalid response size"); + return false; + } + + int PayloadSize = Response->Position - 2; + if(PayloadSize < 0xFFFF){ + Response->Rewrite16(0, (uint16)PayloadSize); + }else{ + Response->Rewrite16(0, 0xFFFF); + Response->Insert32(2, (uint32)PayloadSize); + } + + return !Response->Overflowed(); +} + +void QueryOk(TQuery *Query){ + QueryBeginResponse(Query, QUERY_STATUS_OK); + QueryFinishResponse(Query); +} + +void QueryError(TQuery *Query, int ErrorCode){ + TWriteBuffer *Response = QueryBeginResponse(Query, QUERY_STATUS_ERROR); + Response->Write8((uint8)ErrorCode); + QueryFinishResponse(Query); +} + +void QueryFailed(TQuery *Query){ + QueryBeginResponse(Query, QUERY_STATUS_FAILED); + QueryFinishResponse(Query); +} + +// Query Processing +//============================================================================== +// IMPORTANT(fusion): Query functions will return TRUE when they executed normally, +// and FALSE when they were interrupted by some unexpected error such as a database +// failure. The query response should only be written at the very end, when we know +// it has SUCCEEDED, so that request data is kept intact (because they share the +// query buffer) if the caller decides to retry the operation. + +// NOTE(fusion): These should be used with query functions to reduce clutter. +#define QUERY_STOP_IF(Cond) \ + do{ \ + if(Cond){ \ + return false; \ + } \ + }while(0) + +#define QUERY_ERROR_IF(Cond, ErrorCode) \ + do{ \ + if(Cond){ \ + QueryError(Query, ErrorCode); \ + return true; \ + } \ + }while(0) + +#define QUERY_FAIL_IF(Cond) \ + do{ \ + if(Cond){ \ + QueryFailed(Query); \ + return true; \ + } \ + }while(0) + +bool ProcessInternalResolveWorld(TDatabase *Database, TQuery *Query){ + char World[30]; + TReadBuffer Request = Query->Request; + Request.ReadString(World, sizeof(World)); + + int WorldID; + QUERY_STOP_IF(!GetWorldID(Database, World, &WorldID)); + QUERY_FAIL_IF(WorldID <= 0); + + Query->WorldID = WorldID; + QueryOk(Query); + return true; +} + +bool ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query); +bool ProcessLoginAccount(TDatabase *Database, TQuery *Query); +bool ProcessLoginAdmin(TDatabase *Database, TQuery *Query); +bool ProcessLoginGame(TDatabase *Database, TQuery *Query); +bool ProcessLogoutGame(TDatabase *Database, TQuery *Query); +bool ProcessSetNamelock(TDatabase *Database, TQuery *Query); +bool ProcessBanishAccount(TDatabase *Database, TQuery *Query); +bool ProcessSetNotation(TDatabase *Database, TQuery *Query); +bool ProcessReportStatement(TDatabase *Database, TQuery *Query); +bool ProcessBanishIpAddress(TDatabase *Database, TQuery *Query); +bool ProcessLogCharacterDeath(TDatabase *Database, TQuery *Query); +bool ProcessAddBuddy(TDatabase *Database, TQuery *Query); +bool ProcessRemoveBuddy(TDatabase *Database, TQuery *Query); +bool ProcessDecrementIsOnline(TDatabase *Database, TQuery *Query); +bool ProcessFinishAuctions(TDatabase *Database, TQuery *Query); +bool ProcessTransferHouses(TDatabase *Database, TQuery *Query); +bool ProcessEvictFreeAccounts(TDatabase *Database, TQuery *Query); +bool ProcessEvictDeletedCharacters(TDatabase *Database, TQuery *Query); +bool ProcessEvictExGuildleaders(TDatabase *Database, TQuery *Query); +bool ProcessInsertHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessUpdateHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessDeleteHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessGetHouseOwners(TDatabase *Database, TQuery *Query); +bool ProcessGetAuctions(TDatabase *Database, TQuery *Query); +bool ProcessStartAuction(TDatabase *Database, TQuery *Query); +bool ProcessInsertHouses(TDatabase *Database, TQuery *Query); +bool ProcessClearIsOnline(TDatabase *Database, TQuery *Query); +bool ProcessCreatePlayerlist(TDatabase *Database, TQuery *Query); +bool ProcessLogKilledCreatures(TDatabase *Database, TQuery *Query); +bool ProcessLoadPlayers(TDatabase *Database, TQuery *Query); +bool ProcessExcludeFromAuctions(TDatabase *Database, TQuery *Query); +bool ProcessCancelHouseTransfer(TDatabase *Database, TQuery *Query); +bool ProcessLoadWorldConfig(TDatabase *Database, TQuery *Query); +bool ProcessCreateAccount(TDatabase *Database, TQuery *Query); +bool ProcessCreateCharacter(TDatabase *Database, TQuery *Query); +bool ProcessGetAccountSummary(TDatabase *Database, TQuery *Query); +bool ProcessGetCharacterProfile(TDatabase *Database, TQuery *Query); +bool ProcessGetWorlds(TDatabase *Database, TQuery *Query); +bool ProcessGetOnlineCharacters(TDatabase *Database, TQuery *Query); +bool ProcessGetKillStatistics(TDatabase *Database, TQuery *Query); + + // TODO(fusion): These are the old query processing functions. The new ones will // be very similar, except that we want a way to tell whether there was a database // failure, such as a connection reset, so we can automatically retry them up to @@ -1846,6 +1953,37 @@ void ProcessCreateAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ SendQueryStatusOk(Connection); } +bool ProcessCreateAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ + // TODO(fusion): We'd ideally want to automatically generate an account number + // and return it in case of success but that would also require a more robust + // website infrastructure with verification e-mails, etc... + char Email[100]; + char Password[30]; + int AccountID = (int)Buffer->Read32(); + Buffer->ReadString(Email, sizeof(Email)); + Buffer->ReadString(Password, sizeof(Password)); + + // NOTE(fusion): Inputs should be checked before hand. + QUERY_FAIL_IF(AccountID <= 0); + QUERY_FAIL_IF(StringEmpty(Email)); + QUERY_FAIL_IF(StringEmpty(Password)); + + uint8 Auth[64]; + QUERY_FAIL_IF(!GenerateAuth(Password, Auth, sizeof(Auth))); + + TransactionScope Tx("CreateAccount"); + QUERY_RETRY_IF(!Tx.Begin(Database)); + QUERY_RETRY_IF(!AccountNumberExists(Database, AccountID, &AccountExists)); + QUERY_ERROR_IF(AccountExists, 1); + QUERY_RETRY_IF(!AccountEmailExists(Database, Email, &AccountExists)); + QUERY_ERROR_IF(AccountExists, 2); + QUERY_FAIL_IF(!CreateAccount(AccountID, Email, Auth, sizeof(Auth))); + QUERY_RETRY_IF(!Tx.Commit()); + + QueryOk(Query); + return true; +} + void ProcessCreateCharacterQuery(TConnection *Connection, TReadBuffer *Buffer){ char WorldName[30]; char CharacterName[30]; diff --git a/src/querymanager.cc b/src/querymanager.cc index 84d3625..d9f7ab8 100644 --- a/src/querymanager.cc +++ b/src/querymanager.cc @@ -153,6 +153,16 @@ bool StringCopy(char *Dest, int DestCapacity, const char *Src){ return StringCopyN(Dest, DestCapacity, Src, SrcLength); } +uint32 HashString(const char *String){ + // FNV1a 32-bits + uint32 Hash = 0x811C9DC5U; + for(int i = 0; String[i] != 0; i += 1){ + Hash ^= (uint32)String[i]; + Hash *= 0x01000193U; + } + return Hash; +} + bool ParseIPAddress(const char *String, int *OutAddr){ if(StringEmpty(String)){ LOG_ERR("Empty IP Address"); diff --git a/src/querymanager.hh b/src/querymanager.hh index 43cd6bc..51c8ad1 100644 --- a/src/querymanager.hh +++ b/src/querymanager.hh @@ -119,6 +119,7 @@ bool StringEq(const char *A, const char *B); bool StringEqCI(const char *A, const char *B); bool StringCopyN(char *Dest, int DestCapacity, const char *Src, int SrcLength); bool StringCopy(char *Dest, int DestCapacity, const char *Src); +uint32 HashString(const char *String); bool ParseIPAddress(const char *String, int *OutAddr); bool ReadBooleanConfig(bool *Dest, const char *Val); @@ -607,7 +608,7 @@ struct TWorld{ struct TWorldConfig{ int Type; int RebootTime; - int IPAddress; + char HostName[100]; int Port; int MaxPlayers; int PremiumPlayerBuffer; @@ -754,117 +755,108 @@ struct TOnlineCharacter{ }; struct TDatabase; -//struct TDatabaseTx; struct TransactionScope{ private: const char *m_Context; - bool m_Running; + TDatabase *m_Database; public: TransactionScope(const char *Context); ~TransactionScope(void); - bool Begin(void); + bool Begin(TDatabase *Database); bool Commit(void); }; +// NOTE(fusion): Database management. +TDatabase *DatabaseOpen(void); +void DatabaseClose(TDatabase *Database); +int DatabaseChanges(TDatabase *Database); +bool DatabaseCheckpoint(TDatabase *Database); +int DatabaseMaxConcurrency(void); + // NOTE(fusion): Primary tables. -int GetWorldID(const char *WorldName); -bool GetWorlds(DynamicArray<TWorld> *Worlds); -bool GetWorldConfig(int WorldID, TWorldConfig *WorldConfig); -bool AccountExists(int AccountID, const char *Email); -bool AccountNumberExists(int AccountID); -bool AccountEmailExists(const char *Email); -bool CreateAccount(int AccountID, const char *Email, const uint8 *Auth, int AuthSize); -bool GetAccountData(int AccountID, TAccount *Account); -int GetAccountOnlineCharacters(int AccountID); -bool IsCharacterOnline(int CharacterID); -bool ActivatePendingPremiumDays(int AccountID); -bool GetCharacterEndpoints(int AccountID, DynamicArray<TCharacterEndpoint> *Characters); -bool GetCharacterSummaries(int AccountID, DynamicArray<TCharacterSummary> *Characters); -bool CharacterNameExists(const char *Name); -bool CreateCharacter(int WorldID, int AccountID, const char *Name, int Sex); -int GetCharacterID(int WorldID, const char *CharacterName); -bool GetCharacterLoginData(const char *CharacterName, TCharacterLoginData *Character); -bool GetCharacterProfile(const char *CharacterName, TCharacterProfile *Character); -bool GetCharacterRight(int CharacterID, const char *Right); -bool GetCharacterRights(int CharacterID, DynamicArray<TCharacterRight> *Rights); -bool GetGuildLeaderStatus(int WorldID, int CharacterID); -bool IncrementIsOnline(int WorldID, int CharacterID); -bool DecrementIsOnline(int WorldID, int CharacterID); -bool ClearIsOnline(int WorldID, int *NumAffectedCharacters); -bool LogoutCharacter(int WorldID, int CharacterID, int Level, +bool GetWorldID(TDatabase *Database, const char *World, int *WorldID); +bool GetWorlds(TDatabase *Database, DynamicArray<TWorld> *Worlds); +bool GetWorldConfig(TDatabase *Database, int WorldID, TWorldConfig *WorldConfig); +bool AccountExists(TDatabase *Database, int AccountID, const char *Email, bool *Result); +bool AccountNumberExists(TDatabase *Database, int AccountID, bool *Result); +bool AccountEmailExists(TDatabase *Database, const char *Email, bool *Result); +bool CreateAccount(TDatabase *Database, int AccountID, const char *Email, const uint8 *Auth, int AuthSize); +bool GetAccountData(TDatabase *Database, int AccountID, TAccount *Account); +bool GetAccountOnlineCharacters(TDatabase *Database, int AccountID, int *OnlineCharacters); +bool IsCharacterOnline(TDatabase *Database, int CharacterID, bool *Result); +bool ActivatePendingPremiumDays(TDatabase *Database, int AccountID); +bool GetCharacterEndpoints(TDatabase *Database, int AccountID, DynamicArray<TCharacterEndpoint> *Characters); +bool GetCharacterSummaries(TDatabase *Database, int AccountID, DynamicArray<TCharacterSummary> *Characters); +bool CharacterNameExists(TDatabase *Database, const char *Name, bool *Result); +bool CreateCharacter(TDatabase *Database, int WorldID, int AccountID, const char *Name, int Sex); +bool GetCharacterID(TDatabase *Database, int WorldID, const char *CharacterName, int *CharacterID); +bool GetCharacterLoginData(TDatabase *Database, const char *CharacterName, TCharacterLoginData *Character); +bool GetCharacterProfile(TDatabase *Database, const char *CharacterName, TCharacterProfile *Character); +bool GetCharacterRight(TDatabase *Database, int CharacterID, const char *Right, bool *Result); +bool GetCharacterRights(TDatabase *Database, int CharacterID, DynamicArray<TCharacterRight> *Rights); +bool GetGuildLeaderStatus(TDatabase *Database, int WorldID, int CharacterID, bool *Result); +bool IncrementIsOnline(TDatabase *Database, int WorldID, int CharacterID); +bool DecrementIsOnline(TDatabase *Database, int WorldID, int CharacterID); +bool ClearIsOnline(TDatabase *Database, int WorldID, int *NumAffectedCharacters); +bool LogoutCharacter(TDatabase *Database, int WorldID, int CharacterID, int Level, const char *Profession, const char *Residence, int LastLoginTime, int TutorActivities); -bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, +bool GetCharacterIndexEntries(TDatabase *Database, int WorldID, int MinimumCharacterID, int MaxEntries, int *NumEntries, TCharacterIndexEntry *Entries); -bool InsertCharacterDeath(int WorldID, int CharacterID, int Level, +bool InsertCharacterDeath(TDatabase *Database, int WorldID, int CharacterID, int Level, int OffenderID, const char *Remark, bool Unjustified, int Timestamp); -bool InsertBuddy(int WorldID, int AccountID, int BuddyID); -bool DeleteBuddy(int WorldID, int AccountID, int BuddyID); -bool GetBuddies(int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies); -bool GetWorldInvitation(int WorldID, int CharacterID); -bool InsertLoginAttempt(int AccountID, int IPAddress, bool Failed); -int GetAccountFailedLoginAttempts(int AccountID, int TimeWindow); -int GetIPAddressFailedLoginAttempts(int IPAddress, int TimeWindow); +bool InsertBuddy(TDatabase *Database, int WorldID, int AccountID, int BuddyID); +bool DeleteBuddy(TDatabase *Database, int WorldID, int AccountID, int BuddyID); +bool GetBuddies(TDatabase *Database, int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies); +bool GetWorldInvitation(TDatabase *Database, int WorldID, int CharacterID, bool *Result); +bool InsertLoginAttempt(TDatabase *Database, int AccountID, int IPAddress, bool Failed); +bool GetAccountFailedLoginAttempts(TDatabase *Database, int AccountID, int TimeWindow, int *Result); +bool GetIPAddressFailedLoginAttempts(TDatabase *Database, int IPAddress, int TimeWindow, int *Result); // NOTE(fusion): House tables. -bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions); -bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers); -bool GetFreeAccountEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions); -bool GetDeletedCharacterEvictions(int WorldID, DynamicArray<THouseEviction> *Evictions); -bool InsertHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil); -bool UpdateHouseOwner(int WorldID, int HouseID, int OwnerID, int PaidUntil); -bool DeleteHouseOwner(int WorldID, int HouseID); -bool GetHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners); -bool GetHouseAuctions(int WorldID, DynamicArray<int> *Auctions); -bool StartHouseAuction(int WorldID, int HouseID); -bool DeleteHouses(int WorldID); -bool InsertHouses(int WorldID, int NumHouses, THouse *Houses); -bool ExcludeFromAuctions(int WorldID, int CharacterID, int Duration, int BanishmentID); +bool FinishHouseAuctions(TDatabase *Database, int WorldID, DynamicArray<THouseAuction> *Auctions); +bool FinishHouseTransfers(TDatabase *Database, int WorldID, DynamicArray<THouseTransfer> *Transfers); +bool GetFreeAccountEvictions(TDatabase *Database, int WorldID, DynamicArray<THouseEviction> *Evictions); +bool GetDeletedCharacterEvictions(TDatabase *Database, int WorldID, DynamicArray<THouseEviction> *Evictions); +bool InsertHouseOwner(TDatabase *Database, int WorldID, int HouseID, int OwnerID, int PaidUntil); +bool UpdateHouseOwner(TDatabase *Database, int WorldID, int HouseID, int OwnerID, int PaidUntil); +bool DeleteHouseOwner(TDatabase *Database, int WorldID, int HouseID); +bool GetHouseOwners(TDatabase *Database, int WorldID, DynamicArray<THouseOwner> *Owners); +bool GetHouseAuctions(TDatabase *Database, int WorldID, DynamicArray<int> *Auctions); +bool StartHouseAuction(TDatabase *Database, int WorldID, int HouseID); +bool DeleteHouses(TDatabase *Database, int WorldID); +bool InsertHouses(TDatabase *Database, int WorldID, int NumHouses, THouse *Houses); +bool ExcludeFromAuctions(TDatabase *Database, int WorldID, int CharacterID, int Duration, int BanishmentID); // NOTE(fusion): Banishment tables. -bool IsCharacterNamelocked(int CharacterID); -TNamelockStatus GetNamelockStatus(int CharacterID); -bool InsertNamelock(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment); -bool IsAccountBanished(int AccountID); -TBanishmentStatus GetBanishmentStatus(int CharacterID); -bool InsertBanishment(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment, bool FinalWarning, - int Duration, int *BanishmentID); -int GetNotationCount(int CharacterID); -bool InsertNotation(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment); -bool IsIPBanished(int IPAddress); -bool InsertIPBanishment(int CharacterID, int IPAddress, int GamemasterID, - const char *Reason, const char *Comment, int Duration); -bool IsStatementReported(int WorldID, TStatement *Statement); -bool InsertStatements(int WorldID, int NumStatements, TStatement *Statements); -bool InsertReportedStatement(int WorldID, TStatement *Statement, int BanishmentID, - int ReporterID, const char *Reason, const char *Comment); +bool IsCharacterNamelocked(TDatabase *Database, int CharacterID, bool *Result); +bool GetNamelockStatus(TDatabase *Database, int CharacterID, TNamelockStatus *Status); +bool InsertNamelock(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment); +bool IsAccountBanished(TDatabase *Database, int AccountID, bool *Result); +bool GetBanishmentStatus(TDatabase *Database, int CharacterID, TBanishmentStatus *Status); +bool InsertBanishment(TDatabase *Database, int CharacterID, int IPAddress, int GamemasterID, + const char *Reason, const char *Comment, bool FinalWarning, int Duration, int *BanishmentID); +bool GetNotationCount(TDatabase *Database, int CharacterID, int *Result); +bool InsertNotation(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment); +bool IsIPBanished(TDatabase *Database, int IPAddress, bool *Result); +bool InsertIPBanishment(TDatabase *Database, int CharacterID, int IPAddress, + int GamemasterID, const char *Reason, const char *Comment, int Duration); +bool IsStatementReported(TDatabase *Database, int WorldID, TStatement *Statement, bool *Result); +bool InsertStatements(TDatabase *Database, int WorldID, int NumStatements, TStatement *Statements); +bool InsertReportedStatement(TDatabase *Database, int WorldID, TStatement *Statement, + int BanishmentID, int ReporterID, const char *Reason, const char *Comment); // NOTE(fusion): Info tables. -bool GetKillStatistics(int WorldID, DynamicArray<TKillStatistics> *Stats); -bool MergeKillStatistics(int WorldID, int NumStats, TKillStatistics *Stats); -bool GetOnlineCharacters(int WorldID, DynamicArray<TOnlineCharacter> *Characters); -bool DeleteOnlineCharacters(int WorldID); -bool InsertOnlineCharacters(int WorldID, int NumCharacters, TOnlineCharacter *Characters); -bool CheckOnlineRecord(int WorldID, int NumCharacters, bool *NewRecord); - -// NOTE(fusion): Internal database utility and initialization. -bool FileExists(const char *FileName); -bool ExecFile(const char *FileName); -bool ExecInternal(const char *Format, ...) ATTR_PRINTF(1, 2); -bool GetPragmaInt(const char *Name, int *OutValue); -bool InitDatabaseSchema(void); -bool UpgradeDatabaseSchema(int UserVersion); -bool CheckDatabaseSchema(void); -bool InitDatabase(void); -void ExitDatabase(void); - -TDatabase *DatabaseOpen(void); -void DatabaseClose(TDatabase *Database); -bool DatabaseCheckpoint(TDatabase *Database); +bool GetKillStatistics(TDatabase *Database, int WorldID, DynamicArray<TKillStatistics> *Stats); +bool MergeKillStatistics(TDatabase *Database, int WorldID, int NumStats, TKillStatistics *Stats); +bool GetOnlineCharacters(TDatabase *Database, int WorldID, DynamicArray<TOnlineCharacter> *Characters); +bool DeleteOnlineCharacters(TDatabase *Database, int WorldID); +bool InsertOnlineCharacters(TDatabase *Database, int WorldID, + int NumCharacters, TOnlineCharacter *Characters); +bool CheckOnlineRecord(TDatabase *Database, int WorldID, int NumCharacters, bool *NewRecord); // query.cc //============================================================================== @@ -930,6 +922,16 @@ struct TQuery{ TWriteBuffer Response; }; +const char *QueryName(int QueryType); + +TQuery *QueryNew(void); +void QueryDone(TQuery *Query); +int QueryRefCount(TQuery *Query); +void QueryEnqueue(TQuery *Query); +TQuery *QueryDequeue(AtomicInt *Stop); +bool InitQuery(void); +void ExitQuery(void); + TWriteBuffer QueryBeginRequest(TQuery *Query, int QueryType); bool QueryFinishRequest(TQuery *Query, TWriteBuffer WriteBuffer); bool QueryInternalResolveWorld(TQuery *Query, const char *World); @@ -940,13 +942,47 @@ void QueryOk(TQuery *Query); void QueryError(TQuery *Query, int ErrorCode); void QueryFailed(TQuery *Query); -TQuery *QueryNew(void); -void QueryDone(TQuery *Query); -int QueryRefCount(TQuery *Query); -void QueryEnqueue(TQuery *Query); -TQuery *QueryDequeue(AtomicInt *Stop); -bool InitQuery(void); -void ExitQuery(void); +bool ProcessInternalResolveWorld(TDatabase *Database, TQuery *Query); +bool ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query); +bool ProcessLoginAccount(TDatabase *Database, TQuery *Query); +bool ProcessLoginAdmin(TDatabase *Database, TQuery *Query); +bool ProcessLoginGame(TDatabase *Database, TQuery *Query); +bool ProcessLogoutGame(TDatabase *Database, TQuery *Query); +bool ProcessSetNamelock(TDatabase *Database, TQuery *Query); +bool ProcessBanishAccount(TDatabase *Database, TQuery *Query); +bool ProcessSetNotation(TDatabase *Database, TQuery *Query); +bool ProcessReportStatement(TDatabase *Database, TQuery *Query); +bool ProcessBanishIpAddress(TDatabase *Database, TQuery *Query); +bool ProcessLogCharacterDeath(TDatabase *Database, TQuery *Query); +bool ProcessAddBuddy(TDatabase *Database, TQuery *Query); +bool ProcessRemoveBuddy(TDatabase *Database, TQuery *Query); +bool ProcessDecrementIsOnline(TDatabase *Database, TQuery *Query); +bool ProcessFinishAuctions(TDatabase *Database, TQuery *Query); +bool ProcessTransferHouses(TDatabase *Database, TQuery *Query); +bool ProcessEvictFreeAccounts(TDatabase *Database, TQuery *Query); +bool ProcessEvictDeletedCharacters(TDatabase *Database, TQuery *Query); +bool ProcessEvictExGuildleaders(TDatabase *Database, TQuery *Query); +bool ProcessInsertHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessUpdateHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessDeleteHouseOwner(TDatabase *Database, TQuery *Query); +bool ProcessGetHouseOwners(TDatabase *Database, TQuery *Query); +bool ProcessGetAuctions(TDatabase *Database, TQuery *Query); +bool ProcessStartAuction(TDatabase *Database, TQuery *Query); +bool ProcessInsertHouses(TDatabase *Database, TQuery *Query); +bool ProcessClearIsOnline(TDatabase *Database, TQuery *Query); +bool ProcessCreatePlayerlist(TDatabase *Database, TQuery *Query); +bool ProcessLogKilledCreatures(TDatabase *Database, TQuery *Query); +bool ProcessLoadPlayers(TDatabase *Database, TQuery *Query); +bool ProcessExcludeFromAuctions(TDatabase *Database, TQuery *Query); +bool ProcessCancelHouseTransfer(TDatabase *Database, TQuery *Query); +bool ProcessLoadWorldConfig(TDatabase *Database, TQuery *Query); +bool ProcessCreateAccount(TDatabase *Database, TQuery *Query); +bool ProcessCreateCharacter(TDatabase *Database, TQuery *Query); +bool ProcessGetAccountSummary(TDatabase *Database, TQuery *Query); +bool ProcessGetCharacterProfile(TDatabase *Database, TQuery *Query); +bool ProcessGetWorlds(TDatabase *Database, TQuery *Query); +bool ProcessGetOnlineCharacters(TDatabase *Database, TQuery *Query); +bool ProcessGetKillStatistics(TDatabase *Database, TQuery *Query); // connections.cc //============================================================================== @@ -970,9 +1006,10 @@ struct TConnection{ int LastActive; int RWSize; int RWPosition; + TQuery *Query; bool Authorized; int ApplicationType; - TQuery *Query; + char LoginData[30]; char RemoteAddress[30]; }; @@ -982,6 +1019,7 @@ void CloseConnection(TConnection *Connection); TConnection *AssignConnection(int Socket, uint32 Addr, uint16 Port); void ReleaseConnection(TConnection *Connection); void CheckConnectionInput(TConnection *Connection, int Events); +void ProcessQuery(TConnection *Connection); void SendQueryResponse(TConnection *Connection); void SendQueryOk(TConnection *Connection); void SendQueryError(TConnection *Connection, int ErrorCode); |
