diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-10-07 18:50:02 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-10-07 18:50:02 -0300 |
| commit | aa31732158d3e457dfe630f3b2e56f53de89e2b1 (patch) | |
| tree | 685b2fe7cecb53a659e5f7b48581c6736318ea7e /src | |
| parent | f3c2d53f1837a7bd8e9e1517579b7354af91ef8c (diff) | |
| download | querymanager-aa31732158d3e457dfe630f3b2e56f53de89e2b1.tar.gz querymanager-aa31732158d3e457dfe630f3b2e56f53de89e2b1.zip | |
make connections properly handle async queries
This moves queries from `src/connections.cc` to `src/query.cc` and
adds a few missing details. The only thing left is to review query
processing and tie database operations to the `TDatabase` struct.
Diffstat (limited to 'src')
| -rw-r--r-- | src/connections.cc | 2007 | ||||
| -rw-r--r-- | src/database_sqlite.cc | 99 | ||||
| -rw-r--r-- | src/hostcache.cc | 12 | ||||
| -rw-r--r-- | src/query.cc | 1856 | ||||
| -rw-r--r-- | src/querymanager.cc | 101 | ||||
| -rw-r--r-- | src/querymanager.hh | 397 |
6 files changed, 2381 insertions, 2091 deletions
diff --git a/src/connections.cc b/src/connections.cc index 26630cc..ecd8d4e 100644 --- a/src/connections.cc +++ b/src/connections.cc @@ -88,7 +88,7 @@ int ListenerAccept(int Listener, uint32 *OutAddr, uint16 *OutPort){ uint32 Addr = ntohl(SocketAddr.sin_addr.s_addr); uint16 Port = ntohs(SocketAddr.sin_port); if(Addr != INADDR_LOOPBACK){ - LOG_ERR("Rejecting remote connection from %08X:%d", Addr, Port); + LOG_ERR("Rejecting connection %08X:%d: remote connection", Addr, Port); close(Socket); continue; } @@ -125,22 +125,9 @@ void CloseConnection(TConnection *Connection){ } } -void EnsureConnectionBuffer(TConnection *Connection){ - if(Connection->Buffer == NULL){ - Connection->Buffer = (uint8*)malloc(g_MaxConnectionPacketSize); - } -} - -void DeleteConnectionBuffer(TConnection *Connection){ - if(Connection->Buffer != NULL){ - free(Connection->Buffer); - Connection->Buffer = NULL; - } -} - TConnection *AssignConnection(int Socket, uint32 Addr, uint16 Port){ int ConnectionIndex = -1; - for(int i = 0; i < g_MaxConnections; i += 1){ + for(int i = 0; i < g_Config.MaxConnections; i += 1){ if(g_Connections[i].State == CONNECTION_FREE){ ConnectionIndex = i; break; @@ -172,7 +159,7 @@ void ReleaseConnection(TConnection *Connection){ if(Connection->State != CONNECTION_FREE){ LOG("Connection %s released", Connection->RemoteAddress); CloseConnection(Connection); - DeleteConnectionBuffer(Connection); + QueryDone(Connection->Query); memset(Connection, 0, sizeof(TConnection)); Connection->State = CONNECTION_FREE; } @@ -190,7 +177,12 @@ void CheckConnectionInput(TConnection *Connection, int Events){ return; } - EnsureConnectionBuffer(Connection); + if(Connection->Query == NULL){ + Connection->Query = QueryNew(); + } + + uint8 *Buffer = Connection->Query->Buffer; + int BufferSize = Connection->Query->BufferSize; while(true){ int ReadSize = Connection->RWSize; if(ReadSize == 0){ @@ -203,8 +195,8 @@ void CheckConnectionInput(TConnection *Connection, int Events){ } int BytesRead = read(Connection->Socket, - (Connection->Buffer + Connection->RWPosition), - (ReadSize - Connection->RWPosition)); + (Buffer + Connection->RWPosition), + (ReadSize - Connection->RWPosition)); if(BytesRead == -1){ if(errno != EAGAIN){ // NOTE(fusion): Connection error. @@ -220,12 +212,13 @@ void CheckConnectionInput(TConnection *Connection, int Events){ Connection->RWPosition += BytesRead; if(Connection->RWPosition >= ReadSize){ if(Connection->RWSize != 0){ - Connection->State = CONNECTION_PENDING_QUERY; + Connection->State = CONNECTION_REQUEST; Connection->LastActive = g_MonotonicTimeMS; + Connection->Query->Request = TReadBuffer(Buffer, Connection->RWSize); break; }else if(Connection->RWPosition == 2){ - int PayloadSize = BufferRead16LE(Connection->Buffer); - if(PayloadSize <= 0 || PayloadSize > g_MaxConnectionPacketSize){ + int PayloadSize = BufferRead16LE(Buffer); + if(PayloadSize <= 0 || PayloadSize > BufferSize){ CloseConnection(Connection); break; } @@ -235,8 +228,8 @@ void CheckConnectionInput(TConnection *Connection, int Events){ Connection->RWPosition = 0; } }else if(Connection->RWPosition == 6){ - int PayloadSize = (int)BufferRead32LE(Connection->Buffer + 2); - if(PayloadSize <= 0 || PayloadSize > g_MaxConnectionPacketSize){ + int PayloadSize = (int)BufferRead32LE(Buffer + 2); + if(PayloadSize <= 0 || PayloadSize > BufferSize){ CloseConnection(Connection); break; } @@ -251,16 +244,197 @@ void CheckConnectionInput(TConnection *Connection, int Events){ } } -void CheckConnectionQuery(TConnection *Connection){ - if(Connection->State == CONNECTION_PENDING_QUERY){ - //ProcessConnectionQuery(Connection); - }else if(Connection->State == CONNECTION_PROCESSING_QUERY - && QueryRefCount(Connection->Query) == 1){ - // - //Connection->State = CONNECTION_WRITING; +void SendQueryResponse(TConnection *Connection){ + ASSERT(Connection->Query != NULL); + if(Connection->State != CONNECTION_RESPONSE){ + LOG_ERR("Connection %s is not in a RESPONSE state (State: %d)", + Connection->RemoteAddress, Connection->State); + CloseConnection(Connection); + return; + } + + TWriteBuffer *Response = &Connection->Query->Response; + if(!Response->Overflowed()){ + Connection->State = CONNECTION_WRITING; + Connection->RWSize = Response->Position; + Connection->RWPosition = 0; + }else{ + LOG_ERR("Query buffer overflowed when writing to %s", + Connection->RemoteAddress); + CloseConnection(Connection); + } +} + +void SendQueryOk(TConnection *Connection){ + ASSERT(Connection->Query != NULL); + QueryOk(Connection->Query); + SendQueryResponse(Connection); +} + +void SendQueryError(TConnection *Connection, int ErrorCode){ + ASSERT(Connection->Query != NULL); + QueryError(Connection->Query, ErrorCode); + SendQueryResponse(Connection); +} + +void SendQueryFailed(TConnection *Connection){ + ASSERT(Connection->Query != NULL); + QueryFailed(Connection->Query); + SendQueryResponse(Connection); +} + +void CheckConnectionQueryRequest(TConnection *Connection){ + if(Connection->State != CONNECTION_REQUEST){ + return; + } + + ASSERT(Connection->Query != NULL); + TQuery *Query = Connection->Query; + TReadBuffer Request = Query->Request; + int QueryType = Request.Read8(); + if(!Connection->Authorized){ + if(QueryType != QUERY_LOGIN){ + LOG_ERR("Unauthorized query %d from %s", QueryType, Connection->RemoteAddress); + CloseConnection(Connection); + return; + } + + char Password[30] = {}; + char LoginData[30] = {}; + int ApplicationType = Request.Read8(); + Request.ReadString(Password, sizeof(Password)); + if(ApplicationType == APPLICATION_TYPE_GAME){ + Request.ReadString(LoginData, sizeof(LoginData)); + } + + if(!StringEq(g_Config.QueryManagerPassword, Password)){ + LOG_WARN("Invalid login attempt from %s", Connection->RemoteAddress); + SendQueryFailed(Connection); + return; + } + + // 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. + 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; + QueryEnqueue(Query); + }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); + SendQueryFailed(Connection); + } + }else if(ApplicationType == APPLICATION_TYPE_LOGIN){ + LOG("Connection %s AUTHORIZED to login server", Connection->RemoteAddress); + Connection->Authorized = true; + Connection->ApplicationType = APPLICATION_TYPE_LOGIN; + SendQueryOk(Connection); + }else if(ApplicationType == APPLICATION_TYPE_WEB){ + LOG("Connection %s AUTHORIZED to web server", Connection->RemoteAddress); + Connection->Authorized = true; + Connection->ApplicationType = APPLICATION_TYPE_WEB; + SendQueryOk(Connection); + }else{ + LOG_WARN("Rejecting connection %s: unknown application type %d", + Connection->RemoteAddress, ApplicationType); + 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 + || QueryType == QUERY_BANISH_ACCOUNT + || QueryType == QUERY_SET_NOTATION + || QueryType == QUERY_REPORT_STATEMENT + || QueryType == QUERY_BANISH_IP_ADDRESS + || QueryType == QUERY_LOG_CHARACTER_DEATH + || QueryType == QUERY_ADD_BUDDY + || QueryType == QUERY_REMOVE_BUDDY + || QueryType == QUERY_DECREMENT_IS_ONLINE + || QueryType == QUERY_FINISH_AUCTIONS + || QueryType == QUERY_TRANSFER_HOUSES + || QueryType == QUERY_EVICT_FREE_ACCOUNTS + || QueryType == QUERY_EVICT_DELETED_CHARACTERS + || QueryType == QUERY_EVICT_EX_GUILDLEADERS + || QueryType == QUERY_INSERT_HOUSE_OWNER + || QueryType == QUERY_UPDATE_HOUSE_OWNER + || QueryType == QUERY_DELETE_HOUSE_OWNER + || QueryType == QUERY_GET_HOUSE_OWNERS + || QueryType == QUERY_GET_AUCTIONS + || QueryType == QUERY_START_AUCTION + || QueryType == QUERY_INSERT_HOUSES + || QueryType == QUERY_CLEAR_IS_ONLINE + || QueryType == QUERY_CREATE_PLAYERLIST + || QueryType == QUERY_LOG_KILLED_CREATURES + || QueryType == QUERY_LOAD_PLAYERS + || QueryType == QUERY_EXCLUDE_FROM_AUCTIONS + || QueryType == QUERY_CANCEL_HOUSE_TRANSFER + || QueryType == QUERY_LOAD_WORLD_CONFIG){ + QueryEnqueue(Connection->Query); + }else{ + LOG_ERR("Unknown GAME query %d from %s", + QueryType, Connection->RemoteAddress); + SendQueryFailed(Connection); + } + }else if(Connection->ApplicationType == APPLICATION_TYPE_LOGIN){ + if(QueryType == QUERY_LOGIN_ACCOUNT){ + QueryEnqueue(Connection->Query); + }else{ + LOG_ERR("Unknown LOGIN query %d from %s", + QueryType, Connection->RemoteAddress); + SendQueryFailed(Connection); + } + }else if(Connection->ApplicationType == APPLICATION_TYPE_WEB){ + if(QueryType == QUERY_CHECK_ACCOUNT_PASSWORD + || QueryType == QUERY_CREATE_ACCOUNT + || QueryType == QUERY_CREATE_CHARACTER + || QueryType == QUERY_GET_ACCOUNT_SUMMARY + || QueryType == QUERY_GET_CHARACTER_PROFILE + || QueryType == QUERY_GET_WORLDS + || QueryType == QUERY_GET_ONLINE_CHARACTERS + || QueryType == QUERY_GET_KILL_STATISTICS){ + QueryEnqueue(Connection->Query); + }else{ + LOG_ERR("Unknown WEB query %d from %s", + QueryType, Connection->RemoteAddress); + SendQueryFailed(Connection); + } } } +void CheckConnectionQueryResponse(TConnection *Connection){ + if(Connection->State != CONNECTION_RESPONSE){ + return; + } + + ASSERT(Connection->Query != NULL); + TQuery *Query = Connection->Query; + if(QueryRefCount(Connection->Query) != 1){ + return; + } + + if(Query->QueryType == QUERY_INTERNAL_RESOLVE_WORLD){ + if(Query->QueryStatus == QUERY_STATUS_OK){ + ASSERT(Query->WorldID != 0); + SendQueryOk(Connection); + }else{ + LOG_WARN("Dropping connection %s: unknown game world", + Connection->RemoteAddress); + SendQueryFailed(Connection); + } + }else{ + SendQueryResponse(Connection); + } +} + + void CheckConnectionOutput(TConnection *Connection, int Events){ if((Events & POLLOUT) == 0 || Connection->Socket == -1){ return; @@ -270,9 +444,11 @@ void CheckConnectionOutput(TConnection *Connection, int Events){ return; } + ASSERT(Connection->Query != NULL); + uint8 *Buffer = Connection->Query->Buffer; while(true){ int BytesWritten = write(Connection->Socket, - (Connection->Buffer + Connection->RWPosition), + (Buffer + Connection->RWPosition), (Connection->RWSize - Connection->RWPosition)); if(BytesWritten == -1){ if(errno != EAGAIN){ @@ -286,6 +462,12 @@ void CheckConnectionOutput(TConnection *Connection, int Events){ Connection->State = CONNECTION_READING; Connection->RWSize = 0; Connection->RWPosition = 0; + + // NOTE(fusion): Close the connection if it's not authorized after + // the first query. + if(!Connection->Authorized){ + CloseConnection(Connection); + } break; } } @@ -298,9 +480,9 @@ void CheckConnection(TConnection *Connection, int Events){ CloseConnection(Connection); } - if(g_MaxConnectionIdleTime > 0){ + if(g_Config.MaxConnectionIdleTime > 0){ int IdleTime = (g_MonotonicTimeMS - Connection->LastActive); - if(IdleTime >= g_MaxConnectionIdleTime){ + if(IdleTime >= g_Config.MaxConnectionIdleTime){ LOG_WARN("Dropping connection %s due to inactivity", Connection->RemoteAddress); CloseConnection(Connection); @@ -323,17 +505,18 @@ void ProcessConnections(void){ } if(AssignConnection(Socket, Addr, Port) == NULL){ - LOG_ERR("Rejecting connection %08X:%d due to max number of" - " connections being reached (%d)", Addr, Port, g_MaxConnections); + LOG_ERR("Rejecting connection %08X:%d:" + " reached max number of connections (%d)", + Addr, Port, g_Config.MaxConnections); close(Socket); } } // NOTE(fusion): Gather active connections. int NumConnections = 0; - int *ConnectionIndices = (int*)alloca(g_MaxConnections * sizeof(int)); - pollfd *ConnectionFds = (pollfd*)alloca(g_MaxConnections * sizeof(pollfd)); - for(int i = 0; i < g_MaxConnections; i += 1){ + int *ConnectionIndices = (int*)alloca(g_Config.MaxConnections * sizeof(int)); + pollfd *ConnectionFds = (pollfd*)alloca(g_Config.MaxConnections * sizeof(pollfd)); + for(int i = 0; i < g_Config.MaxConnections; i += 1){ if(g_Connections[i].State == CONNECTION_FREE || g_Connections[i].Socket == -1){ continue; } @@ -361,7 +544,8 @@ void ProcessConnections(void){ TConnection *Connection = &g_Connections[ConnectionIndices[i]]; int Events = (int)ConnectionFds[i].revents; CheckConnectionInput(Connection, Events); - CheckConnectionQuery(Connection); + CheckConnectionQueryRequest(Connection); + CheckConnectionQueryResponse(Connection); CheckConnectionOutput(Connection, Events); CheckConnection(Connection, Events); } @@ -371,20 +555,20 @@ bool InitConnections(void){ ASSERT(g_Listener == -1); ASSERT(g_Connections == NULL); - LOG("Query manager port: %d", g_QueryManagerPort); - LOG("Max connections: %d", g_MaxConnections); - LOG("Max connection idle time: %dms", g_MaxConnectionIdleTime); - LOG("Max connection packet size: %d", g_MaxConnectionPacketSize); + LOG("Query manager port: %d", g_Config.QueryManagerPort); + LOG("Query buffer size: %d", g_Config.QueryBufferSize); + LOG("Max connections: %d", g_Config.MaxConnections); + LOG("Max connection idle time: %dms", g_Config.MaxConnectionIdleTime); - g_Listener = ListenerBind((uint16)g_QueryManagerPort); + g_Listener = ListenerBind((uint16)g_Config.QueryManagerPort); if(g_Listener == -1){ LOG_ERR("Failed to bind listener"); return false; } g_Connections = (TConnection*)calloc( - g_MaxConnections, sizeof(TConnection)); - for(int i = 0; i < g_MaxConnections; i += 1){ + g_Config.MaxConnections, sizeof(TConnection)); + for(int i = 0; i < g_Config.MaxConnections; i += 1){ g_Connections[i].State = CONNECTION_FREE; } @@ -398,7 +582,7 @@ void ExitConnections(void){ } if(g_Connections != NULL){ - for(int i = 0; i < g_MaxConnections; i += 1){ + for(int i = 0; i < g_Config.MaxConnections; i += 1){ ReleaseConnection(&g_Connections[i]); } @@ -407,1730 +591,3 @@ void ExitConnections(void){ } } -// Connection Queries -//============================================================================== -void CompoundBanishment(TBanishmentStatus Status, int *Days, bool *FinalWarning){ - // TODO(fusion): We might want to add all these constants as config values. - ASSERT(Days != NULL && FinalWarning != NULL); - if(Status.FinalWarning){ - *FinalWarning = false; - *Days = 0; // permanent - }else if(Status.TimesBanished > 5 || *FinalWarning){ - *FinalWarning = true; - if(*Days < 30){ - *Days = 30; - }else{ - *Days *= 2; - } - } -} - -TWriteBuffer PrepareResponse(TConnection *Connection, int Status){ - if(Connection->State != CONNECTION_PROCESSING_QUERY){ - LOG_ERR("Connection %s is not processing query (State: %d)", - Connection->RemoteAddress, Connection->State); - CloseConnection(Connection); - return TWriteBuffer(NULL, 0); - } - - TWriteBuffer WriteBuffer(Connection->Buffer, g_MaxConnectionPacketSize); - WriteBuffer.Write16(0); - WriteBuffer.Write8((uint8)Status); - return WriteBuffer; -} - -void SendResponse(TConnection *Connection, TWriteBuffer *WriteBuffer){ - if(Connection->State != CONNECTION_PROCESSING_QUERY){ - LOG_ERR("Connection %s is not processing query (State: %d)", - Connection->RemoteAddress, Connection->State); - CloseConnection(Connection); - return; - } - - ASSERT(WriteBuffer != NULL - && WriteBuffer->Buffer == Connection->Buffer - && WriteBuffer->Size == g_MaxConnectionPacketSize - && WriteBuffer->Position > 2); - - int PayloadSize = WriteBuffer->Position - 2; - if(PayloadSize < 0xFFFF){ - WriteBuffer->Rewrite16(0, (uint16)PayloadSize); - }else{ - WriteBuffer->Rewrite16(0, 0xFFFF); - WriteBuffer->Insert32(2, (uint32)PayloadSize); - } - - if(!WriteBuffer->Overflowed()){ - Connection->State = CONNECTION_WRITING; - Connection->RWSize = WriteBuffer->Position; - Connection->RWPosition = 0; - }else{ - LOG_ERR("Write buffer overflowed when writing response to %s", - Connection->RemoteAddress); - CloseConnection(Connection); - } -} - -void SendQueryStatusOk(TConnection *Connection){ - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - SendResponse(Connection, &WriteBuffer); -} - -void SendQueryStatusError(TConnection *Connection, int ErrorCode){ - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_ERROR); - WriteBuffer.Write8((uint8)ErrorCode); - SendResponse(Connection, &WriteBuffer); -} - -void SendQueryStatusFailed(TConnection *Connection){ - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_FAILED); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessLoginQuery(TConnection *Connection, TReadBuffer *Buffer){ - char Password[30]; - char LoginData[30]; - int ApplicationType = Buffer->Read8(); - Buffer->ReadString(Password, sizeof(Password)); - if(ApplicationType == APPLICATION_TYPE_GAME){ - Buffer->ReadString(LoginData, sizeof(LoginData)); - } - - // TODO(fusion): Probably just disconnect on failed login attempt? Implement - // write then disconnect? - if(!StringEq(g_QueryManagerPassword, Password)){ - LOG_WARN("Invalid login attempt from %s", Connection->RemoteAddress); - SendQueryStatusFailed(Connection); - return; - } - - int WorldID = 0; - if(ApplicationType == APPLICATION_TYPE_GAME){ - WorldID = GetWorldID(LoginData); - if(WorldID == 0){ - LOG_WARN("Rejecting connection %s from unknown game server \"%s\"", - Connection->RemoteAddress, LoginData); - SendQueryStatusFailed(Connection); - return; - } - LOG("Connection %s AUTHORIZED to game server \"%s\" (%d)", - Connection->RemoteAddress, LoginData, WorldID); - }else if(ApplicationType == APPLICATION_TYPE_LOGIN){ - LOG("Connection %s AUTHORIZED to login server", Connection->RemoteAddress); - }else if(ApplicationType == APPLICATION_TYPE_WEB){ - LOG("Connection %s AUTHORIZED to web server", Connection->RemoteAddress); - }else{ - LOG_WARN("Rejecting connection %s from unknown application type %d", - Connection->RemoteAddress, ApplicationType); - SendQueryStatusFailed(Connection); - return; - } - - Connection->Authorized = true; - Connection->ApplicationType = ApplicationType; - Connection->WorldID = WorldID; - SendQueryStatusOk(Connection); -} - -static int CheckAccountPasswordTransaction(int AccountID, const char *Password, int IPAddress){ - TransactionScope Tx("CheckAccountPassword"); - if(!Tx.Begin()){ - return -1; - } - - TAccount Account; - if(!GetAccountData(AccountID, &Account)){ - return -1; - } - - if(Account.AccountID == 0){ - return 1; - } - - if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ - return 2; - } - - if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ - return 3; - } - - if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ - return 4; - } - - if(!Tx.Commit()){ - return -1; - } - - return 0; -} - -void ProcessCheckAccountPasswordQuery(TConnection *Connection, TReadBuffer *Buffer){ - char Password[30]; - char IPString[16]; - int AccountID = (int)Buffer->Read32(); - Buffer->ReadString(Password, sizeof(Password)); - Buffer->ReadString(IPString, sizeof(IPString)); - - int IPAddress = 0; - if(!ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - // NOTE(fusion): Similar to `ProcessLoginAccountQuery`. - int Result = CheckAccountPasswordTransaction(AccountID, Password, IPAddress); - InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); - if(Result == -1){ - SendQueryStatusFailed(Connection); - }else if(Result != 0){ - SendQueryStatusError(Connection, Result); - }else{ - SendQueryStatusOk(Connection); - } -} - -int LoginAccountTransaction(int AccountID, const char *Password, int IPAddress, - DynamicArray<TCharacterEndpoint> *Characters, int *PremiumDays){ - TransactionScope Tx("LoginAccount"); - if(!Tx.Begin()){ - return -1; - } - - TAccount Account; - if(!GetAccountData(AccountID, &Account)){ - return -1; - } - - if(Account.AccountID == 0){ - return 1; - } - - if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ - return 2; - } - - if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ - return 3; - } - - if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ - return 4; - } - - if(IsAccountBanished(Account.AccountID)){ - return 5; - } - - if(IsIPBanished(IPAddress)){ - return 6; - } - - if(!GetCharacterEndpoints(Account.AccountID, Characters)){ - return -1; - } - - if(!Tx.Commit()){ - return -1; - } - - *PremiumDays = Account.PremiumDays + Account.PendingPremiumDays; - return 0; -} - -void ProcessLoginAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ - char Password[30]; - char IPString[16]; - int AccountID = (int)Buffer->Read32(); - Buffer->ReadString(Password, sizeof(Password)); - Buffer->ReadString(IPString, sizeof(IPString)); - - int IPAddress = 0; - if(!ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - int PremiumDays = 0; - DynamicArray<TCharacterEndpoint> Characters; - int Result = LoginAccountTransaction(AccountID, Password, - IPAddress, &Characters, &PremiumDays); - - // NOTE(fusion): Similar to `ProcessLoginGameQuery` except we don't modify - // any tables inside the login transaction. - // TODO(fusion): Maybe have different login attempt tables or types? - InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); - - if(Result == -1){ - SendQueryStatusFailed(Connection); - return; - } - - if(Result != 0){ - SendQueryStatusError(Connection, Result); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumCharacters = std::min<int>(Characters.Length(), UINT8_MAX); - WriteBuffer.Write8((uint8)NumCharacters); - for(int i = 0; i < NumCharacters; i += 1){ - WriteBuffer.WriteString(Characters[i].Name); - WriteBuffer.WriteString(Characters[i].WorldName); - WriteBuffer.Write32BE((uint32)Characters[i].WorldAddress); - WriteBuffer.Write16((uint16)Characters[i].WorldPort); - } - WriteBuffer.Write16((uint16)PremiumDays); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessLoginAdminQuery(TConnection *Connection, TReadBuffer *Buffer){ - // TODO(fusion): I thought for a second this could be the query used with - // the login server but it doesn't take a password or ip address for basic - // checks. Even if it's used in combination with `CheckAccountPassword`, - // it doesn't make sense to split what should have been a single query which - // is what the new `LoginAccount` query does. - SendQueryStatusFailed(Connection); -} - -static int LoginGameTransaction(int WorldID, int AccountID, const char *CharacterName, - const char *Password, int IPAddress, bool PrivateWorld, bool GamemasterRequired, - TCharacterLoginData *Character, DynamicArray<TAccountBuddy> *Buddies, - DynamicArray<TCharacterRight> *Rights, bool *PremiumAccountActivated){ - TransactionScope Tx("LoginGame"); - if(!Tx.Begin()){ - return -1; - } - - if(!GetCharacterLoginData(CharacterName, Character)){ - return -1; - } - - if(Character->CharacterID == 0){ - return 1; - } - - if(Character->Deleted){ - return 2; - } - - if(Character->WorldID != WorldID){ - return 3; - } - - if(PrivateWorld){ - if(!GetWorldInvitation(WorldID, Character->CharacterID)){ - return 4; - } - } - - TAccount Account; - if(!GetAccountData(AccountID, &Account)){ - return -1; - } - - if(Account.AccountID == 0 || Account.AccountID != Character->AccountID){ - // NOTE(fusion): This is correct, there is no error code 5. - return 15; - } - - if(Account.Deleted){ - return 8; - } - - if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ - return 6; - } - - if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ - return 7; - } - - if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ - return 9; - } - - if(IsAccountBanished(Account.AccountID)){ - return 10; - } - - if(IsCharacterNamelocked(Character->CharacterID)){ - return 11; - } - - if(IsIPBanished(IPAddress)){ - return 12; - } - - // TODO(fusion): Probably merge these into a single operation? - if(!GetCharacterRight(Character->CharacterID, "ALLOW_MULTICLIENT") - && GetAccountOnlineCharacters(Account.AccountID) > 0 - && !IsCharacterOnline(Character->CharacterID)){ - return 13; - } - - if(GamemasterRequired){ - if(!GetCharacterRight(Character->CharacterID, "GAMEMASTER_OUTFIT")){ - return 14; - } - } - - if(!GetBuddies(WorldID, Account.AccountID, Buddies)){ - return -1; - } - - if(!GetCharacterRights(Character->CharacterID, Rights)){ - return -1; - } - - if(Account.PremiumDays == 0 && Account.PendingPremiumDays > 0){ - if(!ActivatePendingPremiumDays(Account.AccountID)){ - return -1; - } - - Account.PremiumDays += Account.PendingPremiumDays; - Account.PendingPremiumDays = 0; - *PremiumAccountActivated = true; - } - - if(Account.PremiumDays > 0){ - Rights->Push(TCharacterRight{"PREMIUM_ACCOUNT"}); - } - - if(!IncrementIsOnline(WorldID, Character->CharacterID)){ - return -1; - } - - if(!Tx.Commit()){ - return -1; - } - - return 0; -} - -void ProcessLoginGameQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char Password[30]; - char IPString[16]; - int AccountID = (int)Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(Password, sizeof(Password)); - Buffer->ReadString(IPString, sizeof(IPString)); - bool PrivateWorld = Buffer->ReadFlag(); - Buffer->ReadFlag(); // "PremiumAccountRequired" unused - bool GamemasterRequired = Buffer->ReadFlag(); - - int IPAddress = 0; - if(!ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - TCharacterLoginData Character; - DynamicArray<TAccountBuddy> Buddies; - DynamicArray<TCharacterRight> Rights; - bool PremiumAccountActivated = false; - int Result = LoginGameTransaction(Connection->WorldID, AccountID, - CharacterName, Password, IPAddress, PrivateWorld, - GamemasterRequired, &Character, &Buddies, &Rights, - &PremiumAccountActivated); - - // IMPORTANT(fusion): We need to insert login attempts outside the login game - // transaction or we could end up not having it recorded at all due to rollbacks. - // It is also the reason the whole transaction had to be pulled to its own function. - // IMPORTANT(fusion): Don't return if we fail to insert the login attempt as the - // result of the whole operation was already determined by the transaction function. - InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); - - if(Result == -1){ - SendQueryStatusFailed(Connection); - return; - } - - if(Result != 0){ - SendQueryStatusError(Connection, Result); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write32((uint32)Character.CharacterID); - WriteBuffer.WriteString(Character.Name); - WriteBuffer.Write8((uint8)Character.Sex); - WriteBuffer.WriteString(Character.Guild); - WriteBuffer.WriteString(Character.Rank); - WriteBuffer.WriteString(Character.Title); - - int NumBuddies = std::min<int>(Buddies.Length(), UINT8_MAX); - WriteBuffer.Write8((uint8)NumBuddies); - for(int i = 0; i < NumBuddies; i += 1){ - WriteBuffer.Write32((uint32)Buddies[i].CharacterID); - WriteBuffer.WriteString(Buddies[i].Name); - } - - int NumRights = std::min<int>(Rights.Length(), UINT8_MAX); - WriteBuffer.Write8((uint8)NumRights); - for(int i = 0; i < NumRights; i += 1){ - WriteBuffer.WriteString(Rights[i].Name); - } - - WriteBuffer.WriteFlag(PremiumAccountActivated); - - SendResponse(Connection, &WriteBuffer); -} - -void ProcessLogoutGameQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char Profession[30]; - char Residence[30]; - int CharacterID = (int)Buffer->Read32(); - int Level = Buffer->Read16(); - Buffer->ReadString(Profession, sizeof(Profession)); - Buffer->ReadString(Residence, sizeof(Residence)); - int LastLoginTime = (int)Buffer->Read32(); - int TutorActivities = Buffer->Read16(); - - if(!LogoutCharacter(Connection->WorldID, CharacterID, Level, - Profession, Residence, LastLoginTime, TutorActivities)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char IPString[16]; - char Reason[200]; - char Comment[200]; - int GamemasterID = (int)Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(IPString, sizeof(IPString)); - Buffer->ReadString(Reason, sizeof(Reason)); - Buffer->ReadString(Comment, sizeof(Comment)); - - int IPAddress = 0; - if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("SetNamelock"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); - if(CharacterID == 0){ - SendQueryStatusError(Connection, 1); - return; - } - - // TODO(fusion): Might be `NO_BANISHMENT`. - if(GetCharacterRight(CharacterID, "NAMELOCK")){ - SendQueryStatusError(Connection, 2); - return; - } - - TNamelockStatus Status = GetNamelockStatus(CharacterID); - if(Status.Namelocked){ - SendQueryStatusError(Connection, (Status.Approved ? 4 : 3)); - return; - } - - if(!InsertNamelock(CharacterID, IPAddress, GamemasterID, Reason, Comment)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessBanishAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char IPString[16]; - char Reason[200]; - char Comment[200]; - int GamemasterID = (int)Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(IPString, sizeof(IPString)); - Buffer->ReadString(Reason, sizeof(Reason)); - Buffer->ReadString(Comment, sizeof(Comment)); - bool FinalWarning = Buffer->ReadFlag(); - - int IPAddress = 0; - if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("BanishAccount"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); - if(CharacterID == 0){ - SendQueryStatusError(Connection, 1); - return; - } - - // TODO(fusion): Might be `NO_BANISHMENT`. - if(GetCharacterRight(CharacterID, "BANISHMENT")){ - SendQueryStatusError(Connection, 2); - return; - } - - TBanishmentStatus Status = GetBanishmentStatus(CharacterID); - if(Status.Banished){ - SendQueryStatusError(Connection, 3); - return; - } - - int BanishmentID = 0; - int Days = 7; - CompoundBanishment(Status, &Days, &FinalWarning); - if(!InsertBanishment(CharacterID, IPAddress, GamemasterID, - Reason, Comment, FinalWarning, Days * 86400, &BanishmentID)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write32((uint32)BanishmentID); - WriteBuffer.Write8(Days > 0 ? Days : 0xFF); - WriteBuffer.WriteFlag(FinalWarning); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessSetNotationQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char IPString[16]; - char Reason[200]; - char Comment[200]; - int GamemasterID = Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(IPString, sizeof(IPString)); - Buffer->ReadString(Reason, sizeof(Reason)); - Buffer->ReadString(Comment, sizeof(Comment)); - - int IPAddress = 0; - if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("SetNotation"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); - if(CharacterID == 0){ - SendQueryStatusError(Connection, 1); - return; - } - - // TODO(fusion): Might be `NO_BANISHMENT`. - if(GetCharacterRight(CharacterID, "NOTATION")){ - SendQueryStatusError(Connection, 2); - return; - } - - int BanishmentID = 0; - if(GetNotationCount(CharacterID) >= 5){ - int BanishmentDays = 7; - bool FinalWarning = false; - TBanishmentStatus Status = GetBanishmentStatus(CharacterID); - CompoundBanishment(Status, &BanishmentDays, &FinalWarning); - if(!InsertBanishment(CharacterID, IPAddress, 0, "Excessive Notations", - "", FinalWarning, BanishmentDays, &BanishmentID)){ - SendQueryStatusFailed(Connection); - return; - } - } - - if(!InsertNotation(CharacterID, IPAddress, GamemasterID, Reason, Comment)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write32((uint32)BanishmentID); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessReportStatementQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char Reason[200]; - char Comment[200]; - int ReporterID = Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(Reason, sizeof(Reason)); - Buffer->ReadString(Comment, sizeof(Comment)); - int BanishmentID = Buffer->Read32(); - int StatementID = Buffer->Read32(); - int NumStatements = Buffer->Read16(); - - if(StatementID == 0){ - LOG_ERR("Missing reported statement id"); - SendQueryStatusFailed(Connection); - return; - } - - if(NumStatements == 0){ - LOG_ERR("Missing report statements"); - SendQueryStatusFailed(Connection); - return; - } - - TStatement *ReportedStatement = NULL; - TStatement *Statements = (TStatement*)alloca(NumStatements * sizeof(TStatement)); - for(int i = 0; i < NumStatements; i += 1){ - Statements[i].StatementID = (int)Buffer->Read32(); - Statements[i].Timestamp = (int)Buffer->Read32(); - Statements[i].CharacterID = (int)Buffer->Read32(); - Buffer->ReadString(Statements[i].Channel, sizeof(Statements[i].Channel)); - Buffer->ReadString(Statements[i].Text, sizeof(Statements[i].Text)); - - if(Statements[i].StatementID == StatementID){ - if(ReportedStatement != NULL){ - LOG_WARN("Reported statement (%d, %d, %d) appears multiple times", - Connection->WorldID, Statements[i].Timestamp, - Statements[i].StatementID); - } - ReportedStatement = &Statements[i]; - } - } - - if(ReportedStatement == NULL){ - LOG_ERR("Missing reported statement"); - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("ReportStatement"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); - if(CharacterID == 0){ - SendQueryStatusError(Connection, 1); - return; - }else if(ReportedStatement->CharacterID != CharacterID){ - LOG_ERR("Reported statement character mismatch"); - SendQueryStatusFailed(Connection); - return; - } - - if(IsStatementReported(Connection->WorldID, ReportedStatement)){ - SendQueryStatusError(Connection, 2); - return; - } - - if(!InsertStatements(Connection->WorldID, NumStatements, Statements)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!InsertReportedStatement(Connection->WorldID, ReportedStatement, - BanishmentID, ReporterID, Reason, Comment)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessBanishIPAddressQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char CharacterName[30]; - char IPString[16]; - char Reason[200]; - char Comment[200]; - int GamemasterID = Buffer->Read16(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - Buffer->ReadString(IPString, sizeof(IPString)); - Buffer->ReadString(Reason, sizeof(Reason)); - Buffer->ReadString(Comment, sizeof(Comment)); - - int IPAddress = 0; - if(!ParseIPAddress(IPString, &IPAddress)){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("BanishIP"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); - if(CharacterID == 0){ - SendQueryStatusError(Connection, 1); - return; - } - - // TODO(fusion): Might be `NO_BANISHMENT`. - if(GetCharacterRight(CharacterID, "IP_BANISHMENT")){ - SendQueryStatusError(Connection, 2); - return; - } - - // IMPORTANT(fusion): It is not a good idea to ban an IP address, specially - // V4 addresses, as they may be dynamically assigned or represent the address - // of a public ISP router that manages multiple clients. - int BanishmentDays = 3; - if(!InsertIPBanishment(CharacterID, IPAddress, GamemasterID, - Reason, Comment, BanishmentDays * 86400)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessLogCharacterDeathQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - char Remark[30]; - int CharacterID = (int)Buffer->Read32(); - int Level = Buffer->Read16(); - int OffenderID = (int)Buffer->Read32(); - Buffer->ReadString(Remark, sizeof(Remark)); - bool Unjustified = Buffer->ReadFlag(); - int Timestamp = (int)Buffer->Read32(); - if(!InsertCharacterDeath(Connection->WorldID, CharacterID, Level, - OffenderID, Remark, Unjustified, Timestamp)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessAddBuddyQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int AccountID = (int)Buffer->Read32(); - int BuddyID = (int)Buffer->Read32(); - if(!InsertBuddy(Connection->WorldID, AccountID, BuddyID)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessRemoveBuddyQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int AccountID = (int)Buffer->Read32(); - int BuddyID = (int)Buffer->Read32(); - if(!DeleteBuddy(Connection->WorldID, AccountID, BuddyID)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessDecrementIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = (int)Buffer->Read32(); - if(!DecrementIsOnline(Connection->WorldID, CharacterID)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessFinishAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<THouseAuction> Auctions; - if(!FinishHouseAuctions(Connection->WorldID, &Auctions)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumAuctions = std::min<int>(Auctions.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumAuctions); - for(int i = 0; i < NumAuctions; i += 1){ - WriteBuffer.Write16((uint16)Auctions[i].HouseID); - WriteBuffer.Write32((uint32)Auctions[i].BidderID); - WriteBuffer.WriteString(Auctions[i].BidderName); - WriteBuffer.Write32((uint32)Auctions[i].BidAmount); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessTransferHousesQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<THouseTransfer> Transfers; - if(!FinishHouseTransfers(Connection->WorldID, &Transfers)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumTransfers = std::min<int>(Transfers.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumTransfers); - for(int i = 0; i < NumTransfers; i += 1){ - WriteBuffer.Write16((uint16)Transfers[i].HouseID); - WriteBuffer.Write32((uint32)Transfers[i].NewOwnerID); - WriteBuffer.WriteString(Transfers[i].NewOwnerName); - WriteBuffer.Write32((uint32)Transfers[i].Price); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessEvictFreeAccountsQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<THouseEviction> Evictions; - if(!GetFreeAccountEvictions(Connection->WorldID, &Evictions)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumEvictions); - for(int i = 0; i < NumEvictions; i += 1){ - WriteBuffer.Write16((uint16)Evictions[i].HouseID); - WriteBuffer.Write32((uint32)Evictions[i].OwnerID); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessEvictDeletedCharactersQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<THouseEviction> Evictions; - if(!GetDeletedCharacterEvictions(Connection->WorldID, &Evictions)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumEvictions); - for(int i = 0; i < NumEvictions; i += 1){ - WriteBuffer.Write16((uint16)Evictions[i].HouseID); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessEvictExGuildleadersQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - // NOTE(fusion): This is a bit different from the other eviction functions. - // The server doesn't maintain guild information for characters so it will - // send a list of guild houses with their owners and we're supposed to check - // whether the owner is still a guild leader. I don't think we should check - // any other information as the server is authoritative on house information. - DynamicArray<int> Evictions; - int NumGuildHouses = Buffer->Read16(); - for(int i = 0; i < NumGuildHouses; i += 1){ - int HouseID = Buffer->Read16(); - int OwnerID = (int)Buffer->Read32(); - if(!GetGuildLeaderStatus(Connection->WorldID, OwnerID)){ - Evictions.Push(HouseID); - } - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumEvictions); - for(int i = 0; i < NumEvictions; i += 1){ - WriteBuffer.Write16((uint16)Evictions[i]); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessInsertHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int HouseID = Buffer->Read16(); - int OwnerID = (int)Buffer->Read32(); - int PaidUntil = (int)Buffer->Read32(); - if(!InsertHouseOwner(Connection->WorldID, HouseID, OwnerID, PaidUntil)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessUpdateHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int HouseID = Buffer->Read16(); - int OwnerID = (int)Buffer->Read32(); - int PaidUntil = (int)Buffer->Read32(); - if(!UpdateHouseOwner(Connection->WorldID, HouseID, OwnerID, PaidUntil)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessDeleteHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int HouseID = Buffer->Read16(); - if(!DeleteHouseOwner(Connection->WorldID, HouseID)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessGetHouseOwnersQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<THouseOwner> Owners; - if(!GetHouseOwners(Connection->WorldID, &Owners)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumOwners = std::min<int>(Owners.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumOwners); - for(int i = 0; i < NumOwners; i += 1){ - WriteBuffer.Write16((uint16)Owners[i].HouseID); - WriteBuffer.Write32((uint32)Owners[i].OwnerID); - WriteBuffer.WriteString(Owners[i].OwnerName); - WriteBuffer.Write32((uint32)Owners[i].PaidUntil); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessGetAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<int> Auctions; - if(!GetHouseAuctions(Connection->WorldID, &Auctions)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumAuctions = std::min<int>(Auctions.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumAuctions); - for(int i = 0; i < NumAuctions; i += 1){ - WriteBuffer.Write16((uint16)Auctions[i]); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessStartAuctionQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int HouseID = Buffer->Read16(); - if(!StartHouseAuction(Connection->WorldID, HouseID)){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessInsertHousesQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("InsertHouses"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - if(!DeleteHouses(Connection->WorldID)){ - SendQueryStatusFailed(Connection); - return; - } - - int NumHouses = Buffer->Read16(); - if(NumHouses > 0){ - THouse *Houses = (THouse*)alloca(NumHouses * sizeof(THouse)); - for(int i = 0; i < NumHouses; i += 1){ - Houses[i].HouseID = Buffer->Read16(); - Buffer->ReadString(Houses[i].Name, sizeof(Houses[i].Name)); - Houses[i].Rent = (int)Buffer->Read32(); - Buffer->ReadString(Houses[i].Description, sizeof(Houses[i].Description)); - Houses[i].Size = Buffer->Read16(); - Houses[i].PositionX = Buffer->Read16(); - Houses[i].PositionY = Buffer->Read16(); - Houses[i].PositionZ = Buffer->Read8(); - Buffer->ReadString(Houses[i].Town, sizeof(Houses[i].Town)); - Houses[i].GuildHouse = Buffer->ReadFlag(); - } - - if(!InsertHouses(Connection->WorldID, NumHouses, Houses)){ - SendQueryStatusFailed(Connection); - return; - } - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessClearIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int NumAffectedCharacters; - if(!ClearIsOnline(Connection->WorldID, &NumAffectedCharacters)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write16((uint16)NumAffectedCharacters); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessCreatePlayerlistQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("OnlineList"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - if(!DeleteOnlineCharacters(Connection->WorldID)){ - SendQueryStatusFailed(Connection); - return; - } - - // TODO(fusion): I think `NumCharacters` may be used to signal that the - // server is going OFFLINE, in which case we'd have to add an `Online` - // column to `Worlds` and update it here. - - bool NewRecord = false; - int NumCharacters = Buffer->Read16(); - if(NumCharacters != 0xFFFF && NumCharacters > 0){ - TOnlineCharacter *Characters = (TOnlineCharacter*)alloca(NumCharacters * sizeof(TOnlineCharacter)); - for(int i = 0; i < NumCharacters; i += 1){ - Buffer->ReadString(Characters[i].Name, sizeof(Characters[i].Name)); - Characters[i].Level = Buffer->Read16(); - Buffer->ReadString(Characters[i].Profession, sizeof(Characters[i].Profession)); - } - - if(!InsertOnlineCharacters(Connection->WorldID, NumCharacters, Characters)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!CheckOnlineRecord(Connection->WorldID, NumCharacters, &NewRecord)){ - SendQueryStatusFailed(Connection); - return; - } - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.WriteFlag(NewRecord); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessLogKilledCreaturesQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - int NumStats = Buffer->Read16(); - TKillStatistics *Stats = (TKillStatistics*)alloca(NumStats * sizeof(TKillStatistics)); - for(int i = 0; i < NumStats; i += 1){ - Buffer->ReadString(Stats[i].RaceName, sizeof(Stats[i].RaceName)); - Stats[i].PlayersKilled = (int)Buffer->Read32(); - Stats[i].TimesKilled = (int)Buffer->Read32(); - } - - if(NumStats > 0){ - TransactionScope Tx("LogKilledCreatures"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - if(!MergeKillStatistics(Connection->WorldID, NumStats, Stats)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - } - - SendQueryStatusOk(Connection); -} - -void ProcessLoadPlayersQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - // IMPORTANT(fusion): The server expect 10K entries at most. It is probably - // some shared hard coded constant. - int NumEntries; - TCharacterIndexEntry Entries[10000]; - int MinimumCharacterID = (int)Buffer->Read32(); - if(!GetCharacterIndexEntries(Connection->WorldID, - MinimumCharacterID, NARRAY(Entries), &NumEntries, Entries)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write32((uint32)NumEntries); - for(int i = 0; i < NumEntries; i += 1){ - WriteBuffer.WriteString(Entries[i].Name); - WriteBuffer.Write32((uint32)Entries[i].CharacterID); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessExcludeFromAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("ExcludeFromAuctions"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int CharacterID = (int)Buffer->Read32(); - bool Banish = Buffer->ReadFlag(); - int ExclusionDays = 7; - int BanishmentID = 0; - if(Banish){ - int BanishmentDays = 7; - bool FinalWarning = false; - TBanishmentStatus Status = GetBanishmentStatus(CharacterID); - CompoundBanishment(Status, &BanishmentDays, &FinalWarning); - if(!InsertBanishment(CharacterID, 0, 0, "Spoiling Auction", - "", FinalWarning, BanishmentDays * 86400, &BanishmentID)){ - SendQueryStatusFailed(Connection); - return; - } - } - - if(!ExcludeFromAuctions(Connection->WorldID, - CharacterID, ExclusionDays * 86400, BanishmentID)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessCancelHouseTransferQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - // TODO(fusion): Not sure what this is used for. Maybe house transfer rows - // are kept permanently and this query is used to delete/flag it, in case - // the it didn't complete. We might need to refine `FinishHouseTransfers`. - //int HouseID = Buffer->Read16(); - SendQueryStatusOk(Connection); -} - -void ProcessLoadWorldConfigQuery(TConnection *Connection, TReadBuffer *Buffer){ - if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ - SendQueryStatusFailed(Connection); - return; - } - - TWorldConfig WorldConfig = {}; - if(!GetWorldConfig(Connection->WorldID, &WorldConfig)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.Write8((uint8)WorldConfig.Type); - WriteBuffer.Write8((uint8)WorldConfig.RebootTime); - WriteBuffer.Write32BE((uint32)WorldConfig.IPAddress); - WriteBuffer.Write16((uint16)WorldConfig.Port); - WriteBuffer.Write16((uint16)WorldConfig.MaxPlayers); - WriteBuffer.Write16((uint16)WorldConfig.PremiumPlayerBuffer); - WriteBuffer.Write16((uint16)WorldConfig.MaxNewbies); - WriteBuffer.Write16((uint16)WorldConfig.PremiumNewbieBuffer); - SendResponse(Connection, &WriteBuffer); -} - -void 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. - if(AccountID <= 0 || StringEmpty(Email) || StringEmpty(Password)){ - SendQueryStatusFailed(Connection); - return; - } - - uint8 Auth[64]; - if(!GenerateAuth(Password, Auth, sizeof(Auth))){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("CreateAccount"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - if(AccountNumberExists(AccountID)){ - SendQueryStatusError(Connection, 1); - return; - } - - if(AccountEmailExists(Email)){ - SendQueryStatusError(Connection, 2); - return; - } - - if(!CreateAccount(AccountID, Email, Auth, sizeof(Auth))){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessCreateCharacterQuery(TConnection *Connection, TReadBuffer *Buffer){ - char WorldName[30]; - char CharacterName[30]; - Buffer->ReadString(WorldName, sizeof(WorldName)); - int AccountID = (int)Buffer->Read32(); - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - int Sex = Buffer->Read8(); - - // NOTE(fusion): Inputs should be checked before hand. - if(AccountID <= 0 || (Sex != 1 && Sex != 2) - || StringEmpty(WorldName) - || StringEmpty(CharacterName)){ - SendQueryStatusFailed(Connection); - return; - } - - TransactionScope Tx("CreateCharacter"); - if(!Tx.Begin()){ - SendQueryStatusFailed(Connection); - return; - } - - int WorldID = GetWorldID(WorldName); - if(WorldID == 0){ - SendQueryStatusError(Connection, 1); - return; - } - - if(!AccountNumberExists(AccountID)){ - SendQueryStatusError(Connection, 2); - return; - } - - if(CharacterNameExists(CharacterName)){ - SendQueryStatusError(Connection, 3); - return; - } - - if(!CreateCharacter(WorldID, AccountID, CharacterName, Sex)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!Tx.Commit()){ - SendQueryStatusFailed(Connection); - return; - } - - SendQueryStatusOk(Connection); -} - -void ProcessGetAccountSummaryQuery(TConnection *Connection, TReadBuffer *Buffer){ - int AccountID = (int)Buffer->Read32(); - - if(AccountID <= 0){ - SendQueryStatusFailed(Connection); - return; - } - - TAccount Account; - if(!GetAccountData(AccountID, &Account)){ - SendQueryStatusFailed(Connection); - return; - } - - if(Account.AccountID != AccountID){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<TCharacterSummary> Characters; - if(!GetCharacterSummaries(AccountID, &Characters)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.WriteString(Account.Email); - WriteBuffer.Write16((uint16)Account.PremiumDays); - WriteBuffer.Write16((uint16)Account.PendingPremiumDays); - WriteBuffer.WriteFlag(Account.Deleted); - int NumCharacters = std::min<int>(Characters.Length(), UINT8_MAX); - WriteBuffer.Write8((uint8)NumCharacters); - for(int i = 0; i < NumCharacters; i += 1){ - WriteBuffer.WriteString(Characters[i].Name); - WriteBuffer.WriteString(Characters[i].World); - WriteBuffer.Write16((uint16)Characters[i].Level); - WriteBuffer.WriteString(Characters[i].Profession); - WriteBuffer.WriteFlag(Characters[i].Online); - WriteBuffer.WriteFlag(Characters[i].Deleted); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessGetCharacterProfileQuery(TConnection *Connection, TReadBuffer *Buffer){ - char CharacterName[30]; - Buffer->ReadString(CharacterName, sizeof(CharacterName)); - - if(StringEmpty(CharacterName)){ - SendQueryStatusFailed(Connection); - return; - } - - TCharacterProfile Character; - if(!GetCharacterProfile(CharacterName, &Character)){ - SendQueryStatusFailed(Connection); - return; - } - - if(!StringEqCI(Character.Name, CharacterName)){ - SendQueryStatusError(Connection, 1); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - WriteBuffer.WriteString(Character.Name); - WriteBuffer.WriteString(Character.World); - WriteBuffer.Write8((uint8)Character.Sex); - WriteBuffer.WriteString(Character.Guild); - WriteBuffer.WriteString(Character.Rank); - WriteBuffer.WriteString(Character.Title); - WriteBuffer.Write16((uint16)Character.Level); - WriteBuffer.WriteString(Character.Profession); - WriteBuffer.WriteString(Character.Residence); - WriteBuffer.Write32((uint32)Character.LastLogin); - WriteBuffer.Write16((uint16)Character.PremiumDays); - WriteBuffer.WriteFlag(Character.Online); - WriteBuffer.WriteFlag(Character.Deleted); - SendResponse(Connection, &WriteBuffer); -} - -void ProcessGetWorldsQuery(TConnection *Connection, TReadBuffer *Buffer){ - DynamicArray<TWorld> Worlds; - if(!GetWorlds(&Worlds)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumWorlds = std::min<int>(Worlds.Length(), UINT8_MAX); - WriteBuffer.Write8((uint8)NumWorlds); - for(int i = 0; i < NumWorlds; i += 1){ - WriteBuffer.WriteString(Worlds[i].Name); - WriteBuffer.Write8((uint8)Worlds[i].Type); - WriteBuffer.Write16((uint16)Worlds[i].NumPlayers); - WriteBuffer.Write16((uint16)Worlds[i].MaxPlayers); - WriteBuffer.Write16((uint16)Worlds[i].OnlineRecord); - WriteBuffer.Write32((uint32)Worlds[i].OnlineRecordTimestamp); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessGetOnlineCharactersQuery(TConnection *Connection, TReadBuffer *Buffer){ - char WorldName[30]; - Buffer->ReadString(WorldName, sizeof(WorldName)); - - int WorldID = GetWorldID(WorldName); - if(WorldID == 0){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<TOnlineCharacter> Characters; - if(!GetOnlineCharacters(WorldID, &Characters)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumCharacters = std::min<int>(Characters.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumCharacters); - for(int i = 0; i < NumCharacters; i += 1){ - WriteBuffer.WriteString(Characters[i].Name); - WriteBuffer.Write16((uint16)Characters[i].Level); - WriteBuffer.WriteString(Characters[i].Profession); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessGetKillStatisticsQuery(TConnection *Connection, TReadBuffer *Buffer){ - char WorldName[30]; - Buffer->ReadString(WorldName, sizeof(WorldName)); - - int WorldID = GetWorldID(WorldName); - if(WorldID == 0){ - SendQueryStatusFailed(Connection); - return; - } - - DynamicArray<TKillStatistics> Stats; - if(!GetKillStatistics(WorldID, &Stats)){ - SendQueryStatusFailed(Connection); - return; - } - - TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); - int NumStats = std::min<int>(Stats.Length(), UINT16_MAX); - WriteBuffer.Write16((uint16)NumStats); - for(int i = 0; i < NumStats; i += 1){ - WriteBuffer.WriteString(Stats[i].RaceName); - WriteBuffer.Write32((uint32)Stats[i].PlayersKilled); - WriteBuffer.Write32((uint32)Stats[i].TimesKilled); - } - SendResponse(Connection, &WriteBuffer); -} - -void ProcessConnectionQuery(TConnection *Connection){ - // TODO(fusion): Ideally we'd create a new query and dispatch it to the - // database thread for processing. Realistically, it wouldn't make a big - // difference since we're already handling connections asynchronously and - // the only blocking system calls are done by SQLite when interacting with - // the disk. - - TReadBuffer Buffer(Connection->Buffer, Connection->RWSize); - int Query = Buffer.Read8(); - if(!Connection->Authorized){ - if(Query == QUERY_LOGIN){ - ProcessLoginQuery(Connection, &Buffer); - }else{ - LOG_ERR("Expected login query"); - CloseConnection(Connection); - } - return; - } - - switch(Query){ - case QUERY_CHECK_ACCOUNT_PASSWORD: ProcessCheckAccountPasswordQuery(Connection, &Buffer); break; - case QUERY_LOGIN_ACCOUNT: ProcessLoginAccountQuery(Connection, &Buffer); break; - case QUERY_LOGIN_ADMIN: ProcessLoginAdminQuery(Connection, &Buffer); break; - case QUERY_LOGIN_GAME: ProcessLoginGameQuery(Connection, &Buffer); break; - case QUERY_LOGOUT_GAME: ProcessLogoutGameQuery(Connection, &Buffer); break; - case QUERY_SET_NAMELOCK: ProcessSetNamelockQuery(Connection, &Buffer); break; - case QUERY_BANISH_ACCOUNT: ProcessBanishAccountQuery(Connection, &Buffer); break; - case QUERY_SET_NOTATION: ProcessSetNotationQuery(Connection, &Buffer); break; - case QUERY_REPORT_STATEMENT: ProcessReportStatementQuery(Connection, &Buffer); break; - case QUERY_BANISH_IP_ADDRESS: ProcessBanishIPAddressQuery(Connection, &Buffer); break; - case QUERY_LOG_CHARACTER_DEATH: ProcessLogCharacterDeathQuery(Connection, &Buffer); break; - case QUERY_ADD_BUDDY: ProcessAddBuddyQuery(Connection, &Buffer); break; - case QUERY_REMOVE_BUDDY: ProcessRemoveBuddyQuery(Connection, &Buffer); break; - case QUERY_DECREMENT_IS_ONLINE: ProcessDecrementIsOnlineQuery(Connection, &Buffer); break; - case QUERY_FINISH_AUCTIONS: ProcessFinishAuctionsQuery(Connection, &Buffer); break; - case QUERY_TRANSFER_HOUSES: ProcessTransferHousesQuery(Connection, &Buffer); break; - case QUERY_EVICT_FREE_ACCOUNTS: ProcessEvictFreeAccountsQuery(Connection, &Buffer); break; - case QUERY_EVICT_DELETED_CHARACTERS: ProcessEvictDeletedCharactersQuery(Connection, &Buffer); break; - case QUERY_EVICT_EX_GUILDLEADERS: ProcessEvictExGuildleadersQuery(Connection, &Buffer); break; - case QUERY_INSERT_HOUSE_OWNER: ProcessInsertHouseOwnerQuery(Connection, &Buffer); break; - case QUERY_UPDATE_HOUSE_OWNER: ProcessUpdateHouseOwnerQuery(Connection, &Buffer); break; - case QUERY_DELETE_HOUSE_OWNER: ProcessDeleteHouseOwnerQuery(Connection, &Buffer); break; - case QUERY_GET_HOUSE_OWNERS: ProcessGetHouseOwnersQuery(Connection, &Buffer); break; - case QUERY_GET_AUCTIONS: ProcessGetAuctionsQuery(Connection, &Buffer); break; - case QUERY_START_AUCTION: ProcessStartAuctionQuery(Connection, &Buffer); break; - case QUERY_INSERT_HOUSES: ProcessInsertHousesQuery(Connection, &Buffer); break; - case QUERY_CLEAR_IS_ONLINE: ProcessClearIsOnlineQuery(Connection, &Buffer); break; - case QUERY_CREATE_PLAYERLIST: ProcessCreatePlayerlistQuery(Connection, &Buffer); break; - case QUERY_LOG_KILLED_CREATURES: ProcessLogKilledCreaturesQuery(Connection, &Buffer); break; - case QUERY_LOAD_PLAYERS: ProcessLoadPlayersQuery(Connection, &Buffer); break; - case QUERY_EXCLUDE_FROM_AUCTIONS: ProcessExcludeFromAuctionsQuery(Connection, &Buffer); break; - case QUERY_CANCEL_HOUSE_TRANSFER: ProcessCancelHouseTransferQuery(Connection, &Buffer); break; - case QUERY_LOAD_WORLD_CONFIG: ProcessLoadWorldConfigQuery(Connection, &Buffer); break; - case QUERY_CREATE_ACCOUNT: ProcessCreateAccountQuery(Connection, &Buffer); break; - case QUERY_CREATE_CHARACTER: ProcessCreateCharacterQuery(Connection, &Buffer); break; - case QUERY_GET_ACCOUNT_SUMMARY: ProcessGetAccountSummaryQuery(Connection, &Buffer); break; - case QUERY_GET_CHARACTER_PROFILE: ProcessGetCharacterProfileQuery(Connection, &Buffer); break; - case QUERY_GET_WORLDS: ProcessGetWorldsQuery(Connection, &Buffer); break; - case QUERY_GET_ONLINE_CHARACTERS: ProcessGetOnlineCharactersQuery(Connection, &Buffer); break; - case QUERY_GET_KILL_STATISTICS: ProcessGetKillStatisticsQuery(Connection, &Buffer); break; - default:{ - LOG_ERR("Unknown query %d from %s", Query, Connection->RemoteAddress); - SendQueryStatusFailed(Connection); - break; - } - } -} diff --git a/src/database_sqlite.cc b/src/database_sqlite.cc index 83f5bd9..37e4e09 100644 --- a/src/database_sqlite.cc +++ b/src/database_sqlite.cc @@ -53,7 +53,7 @@ sqlite3_stmt *PrepareQuery(const char *Text){ int LeastRecentlyUsed = 0; int64 LeastRecentlyUsedTime = g_CachedStatements[0].LastUsed; uint32 Hash = HashText(Text); - for(int i = 0; i < g_MaxCachedStatements; i += 1){ + for(int i = 0; i < g_Config.MaxCachedStatements; i += 1){ TCachedStatement *Entry = &g_CachedStatements[i]; if(Entry->LastUsed < LeastRecentlyUsedTime){ @@ -106,13 +106,13 @@ sqlite3_stmt *PrepareQuery(const char *Text){ bool InitStatementCache(void){ ASSERT(g_CachedStatements == NULL); g_CachedStatements = (TCachedStatement*)calloc( - g_MaxCachedStatements, sizeof(TCachedStatement)); + g_Config.MaxCachedStatements, sizeof(TCachedStatement)); return true; } void ExitStatementCache(void){ if(g_CachedStatements != NULL){ - for(int i = 0; i < g_MaxCachedStatements; i += 1){ + for(int i = 0; i < g_Config.MaxCachedStatements; i += 1){ TCachedStatement *Entry = &g_CachedStatements[i]; if(Entry->Stmt != NULL){ sqlite3_finalize(Entry->Stmt); @@ -215,8 +215,7 @@ bool GetWorlds(DynamicArray<TWorld> *Worlds){ AutoStmtReset StmtReset(Stmt); while(sqlite3_step(Stmt) == SQLITE_ROW){ TWorld World = {}; - StringCopy(World.Name, sizeof(World.Name), - (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(World.Name, (const char*)sqlite3_column_text(Stmt, 0)); World.Type = sqlite3_column_int(Stmt, 1); World.NumPlayers = sqlite3_column_int(Stmt, 2); World.MaxPlayers = sqlite3_column_int(Stmt, 3); @@ -399,8 +398,7 @@ bool GetAccountData(int AccountID, TAccount *Account){ memset(Account, 0, sizeof(TAccount)); if(ErrorCode == SQLITE_ROW){ Account->AccountID = sqlite3_column_int(Stmt, 0); - StringCopy(Account->Email, sizeof(Account->Email), - (const char*)sqlite3_column_text(Stmt, 1)); + StringBufCopy(Account->Email, (const char*)sqlite3_column_text(Stmt, 1)); if(sqlite3_column_bytes(Stmt, 2) == sizeof(Account->Auth)){ memcpy(Account->Auth, sqlite3_column_blob(Stmt, 2), sizeof(Account->Auth)); } @@ -517,8 +515,8 @@ bool GetCharacterEndpoints(int AccountID, DynamicArray<TCharacterEndpoint> *Char } TCharacterEndpoint Character = {}; - StringCopy(Character.Name, sizeof(Character.Name), CharacterName); - StringCopy(Character.WorldName, sizeof(Character.WorldName), WorldName); + StringBufCopy(Character.Name, CharacterName); + StringBufCopy(Character.WorldName, WorldName); Character.WorldAddress = WorldAddress; Character.WorldPort = sqlite3_column_int(Stmt, 3); Characters->Push(Character); @@ -551,13 +549,10 @@ bool GetCharacterSummaries(int AccountID, DynamicArray<TCharacterSummary> *Chara while(sqlite3_step(Stmt) == SQLITE_ROW){ TCharacterSummary Character = {}; - StringCopy(Character.Name, sizeof(Character.Name), - (const char*)sqlite3_column_text(Stmt, 0)); - StringCopy(Character.World, sizeof(Character.World), - (const char*)sqlite3_column_text(Stmt, 1)); + StringBufCopy(Character.Name, (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(Character.World, (const char*)sqlite3_column_text(Stmt, 1)); Character.Level = sqlite3_column_int(Stmt, 2); - StringCopy(Character.Profession, sizeof(Character.Profession), - (const char*)sqlite3_column_text(Stmt, 3)); + StringBufCopy(Character.Profession, (const char*)sqlite3_column_text(Stmt, 3)); Character.Online = (sqlite3_column_int(Stmt, 4) != 0); Character.Deleted = (sqlite3_column_int(Stmt, 5) != 0); Characters->Push(Character); @@ -677,15 +672,11 @@ bool GetCharacterLoginData(const char *CharacterName, TCharacterLoginData *Chara Character->WorldID = sqlite3_column_int(Stmt, 0); Character->CharacterID = sqlite3_column_int(Stmt, 1); Character->AccountID = sqlite3_column_int(Stmt, 2); - StringCopy(Character->Name, sizeof(Character->Name), - (const char*)sqlite3_column_text(Stmt, 3)); + StringBufCopy(Character->Name, (const char*)sqlite3_column_text(Stmt, 3)); Character->Sex = sqlite3_column_int(Stmt, 4); - StringCopy(Character->Guild, sizeof(Character->Guild), - (const char*)sqlite3_column_text(Stmt, 5)); - StringCopy(Character->Rank, sizeof(Character->Rank), - (const char*)sqlite3_column_text(Stmt, 6)); - StringCopy(Character->Title, sizeof(Character->Title), - (const char*)sqlite3_column_text(Stmt, 7)); + StringBufCopy(Character->Guild, (const char*)sqlite3_column_text(Stmt, 5)); + StringBufCopy(Character->Rank, (const char*)sqlite3_column_text(Stmt, 6)); + StringBufCopy(Character->Title, (const char*)sqlite3_column_text(Stmt, 7)); Character->Deleted = (sqlite3_column_int(Stmt, 8) != 0); } @@ -724,22 +715,15 @@ bool GetCharacterProfile(const char *CharacterName, TCharacterProfile *Character memset(Character, 0, sizeof(TCharacterProfile)); if(ErrorCode == SQLITE_ROW){ - StringCopy(Character->Name, sizeof(Character->Name), - (const char*)sqlite3_column_text(Stmt, 0)); - StringCopy(Character->World, sizeof(Character->World), - (const char*)sqlite3_column_text(Stmt, 1)); + StringBufCopy(Character->Name, (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(Character->World, (const char*)sqlite3_column_text(Stmt, 1)); Character->Sex = sqlite3_column_int(Stmt, 2); - StringCopy(Character->Guild, sizeof(Character->Guild), - (const char*)sqlite3_column_text(Stmt, 3)); - StringCopy(Character->Rank, sizeof(Character->Rank), - (const char*)sqlite3_column_text(Stmt, 4)); - StringCopy(Character->Title, sizeof(Character->Title), - (const char*)sqlite3_column_text(Stmt, 5)); + StringBufCopy(Character->Guild, (const char*)sqlite3_column_text(Stmt, 3)); + StringBufCopy(Character->Rank, (const char*)sqlite3_column_text(Stmt, 4)); + StringBufCopy(Character->Title, (const char*)sqlite3_column_text(Stmt, 5)); Character->Level = sqlite3_column_int(Stmt, 6); - StringCopy(Character->Profession, sizeof(Character->Profession), - (const char*)sqlite3_column_text(Stmt, 7)); - StringCopy(Character->Residence, sizeof(Character->Residence), - (const char*)sqlite3_column_text(Stmt, 8)); + StringBufCopy(Character->Profession, (const char*)sqlite3_column_text(Stmt, 7)); + StringBufCopy(Character->Residence, (const char*)sqlite3_column_text(Stmt, 8)); Character->LastLogin = sqlite3_column_int(Stmt, 9); Character->Online = (sqlite3_column_int(Stmt, 10) != 0); Character->Deleted = (sqlite3_column_int(Stmt, 11) != 0); @@ -792,8 +776,7 @@ bool GetCharacterRights(int CharacterID, DynamicArray<TCharacterRight> *Rights){ while(sqlite3_step(Stmt) == SQLITE_ROW){ TCharacterRight Right = {}; - StringCopy(Right.Name, sizeof(Right.Name), - (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(Right.Name, (const char*)sqlite3_column_text(Stmt, 0)); Rights->Push(Right); } @@ -979,8 +962,7 @@ bool GetCharacterIndexEntries(int WorldID, int MinimumCharacterID, int EntryIndex = 0; while(sqlite3_step(Stmt) == SQLITE_ROW && EntryIndex < MaxEntries){ Entries[EntryIndex].CharacterID = sqlite3_column_int(Stmt, 0); - StringCopy(Entries[EntryIndex].Name, - sizeof(Entries[EntryIndex].Name), + StringBufCopy(Entries[EntryIndex].Name, (const char*)sqlite3_column_text(Stmt, 1)); EntryIndex += 1; } @@ -1107,8 +1089,7 @@ bool GetBuddies(int WorldID, int AccountID, DynamicArray<TAccountBuddy> *Buddies while(sqlite3_step(Stmt) == SQLITE_ROW){ TAccountBuddy Buddy = {}; Buddy.CharacterID = sqlite3_column_int(Stmt, 0); - StringCopy(Buddy.Name, sizeof(Buddy.Name), - (const char*)sqlite3_column_text(Stmt, 1)); + StringBufCopy(Buddy.Name, (const char*)sqlite3_column_text(Stmt, 1)); Buddies->Push(Buddy); } @@ -1247,8 +1228,7 @@ bool FinishHouseAuctions(int WorldID, DynamicArray<THouseAuction> *Auctions){ Auction.BidderID = sqlite3_column_int(Stmt, 1); Auction.BidAmount = sqlite3_column_int(Stmt, 2); Auction.FinishTime = sqlite3_column_int(Stmt, 3); - StringCopy(Auction.BidderName, sizeof(Auction.BidderName), - (const char*)sqlite3_column_text(Stmt, 4)); + StringBufCopy(Auction.BidderName, (const char*)sqlite3_column_text(Stmt, 4)); Auctions->Push(Auction); } @@ -1284,8 +1264,7 @@ bool FinishHouseTransfers(int WorldID, DynamicArray<THouseTransfer> *Transfers){ Transfer.HouseID = sqlite3_column_int(Stmt, 0); Transfer.NewOwnerID = sqlite3_column_int(Stmt, 1); Transfer.Price = sqlite3_column_int(Stmt, 2); - StringCopy(Transfer.NewOwnerName, sizeof(Transfer.NewOwnerName), - (const char*)sqlite3_column_text(Stmt, 4)); + StringBufCopy(Transfer.NewOwnerName, (const char*)sqlite3_column_text(Stmt, 4)); Transfers->Push(Transfer); } @@ -1464,8 +1443,7 @@ bool GetHouseOwners(int WorldID, DynamicArray<THouseOwner> *Owners){ THouseOwner Owner = {}; Owner.HouseID = sqlite3_column_int(Stmt, 0); Owner.OwnerID = sqlite3_column_int(Stmt, 1); - StringCopy(Owner.OwnerName, sizeof(Owner.OwnerName), - (const char*)sqlite3_column_text(Stmt, 2)); + StringBufCopy(Owner.OwnerName, (const char*)sqlite3_column_text(Stmt, 2)); Owner.PaidUntil = sqlite3_column_int(Stmt, 3); Owners->Push(Owner); } @@ -2023,8 +2001,7 @@ bool GetKillStatistics(int WorldID, DynamicArray<TKillStatistics> *Stats){ while(sqlite3_step(Stmt) == SQLITE_ROW){ TKillStatistics Entry = {}; - StringCopy(Entry.RaceName, sizeof(Entry.RaceName), - (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(Entry.RaceName, (const char*)sqlite3_column_text(Stmt, 0)); Entry.TimesKilled = sqlite3_column_int(Stmt, 1); Entry.PlayersKilled = sqlite3_column_int(Stmt, 2); Stats->Push(Entry); @@ -2094,11 +2071,9 @@ bool GetOnlineCharacters(int WorldID, DynamicArray<TOnlineCharacter> *Characters while(sqlite3_step(Stmt) == SQLITE_ROW){ TOnlineCharacter Character = {}; - StringCopy(Character.Name, sizeof(Character.Name), - (const char*)sqlite3_column_text(Stmt, 0)); + StringBufCopy(Character.Name, (const char*)sqlite3_column_text(Stmt, 0)); Character.Level = sqlite3_column_int(Stmt, 1); - StringCopy(Character.Profession, sizeof(Character.Profession), - (const char*)sqlite3_column_text(Stmt, 2)); + StringBufCopy(Character.Profession, (const char*)sqlite3_column_text(Stmt, 2)); Characters->Push(Character); } @@ -2398,23 +2373,23 @@ bool CheckDatabaseSchema(void){ } bool InitDatabase(void){ - LOG("Database file: \"%s\"", g_DatabaseFile); - LOG("Max cached statements: %d", g_MaxCachedStatements); + LOG("Database file: \"%s\"", g_Config.DatabaseFile); + LOG("Max cached statements: %d", g_Config.MaxCachedStatements); int Flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; - if(sqlite3_open_v2(g_DatabaseFile, &g_Database, Flags, NULL) != SQLITE_OK){ + if(sqlite3_open_v2(g_Config.DatabaseFile, &g_Database, Flags, NULL) != SQLITE_OK){ LOG_ERR("Failed to open database at \"%s\": %s\n", - g_DatabaseFile, sqlite3_errmsg(g_Database)); + 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 the file has the appropriate permissions and is" - " owned by the same user running the query manager.", - g_DatabaseFile); + " Make sure it has the appropriate permissions and is owned" + " by the same user running the query manager.", + g_Config.DatabaseFile); return false; } diff --git a/src/hostcache.cc b/src/hostcache.cc index 5d5d46a..55c8663 100644 --- a/src/hostcache.cc +++ b/src/hostcache.cc @@ -18,10 +18,10 @@ static THostCacheEntry *g_CachedHostNames; bool InitHostCache(void){ ASSERT(g_CachedHostNames == NULL); - LOG("Max cached host names: %d", g_MaxCachedHostNames); - LOG("Host name expire time: %dms", g_HostNameExpireTime); + LOG("Max cached host names: %d", g_Config.MaxCachedHostNames); + LOG("Host name expire time: %dms", g_Config.HostNameExpireTime); g_CachedHostNames = (THostCacheEntry*)calloc( - g_MaxCachedHostNames, sizeof(THostCacheEntry)); + g_Config.MaxCachedHostNames, sizeof(THostCacheEntry)); return true; } @@ -64,10 +64,10 @@ bool ResolveHostName(const char *HostName, int *OutAddr){ THostCacheEntry *Entry = NULL; int LeastRecentlyUsedIndex = 0; int LeastRecentlyUsedTime = g_CachedHostNames[0].ResolveTime; - for(int i = 0; i < g_MaxCachedHostNames; i += 1){ + for(int i = 0; i < g_Config.MaxCachedHostNames; i += 1){ THostCacheEntry *Current = &g_CachedHostNames[i]; - if((g_MonotonicTimeMS - Current->ResolveTime) >= g_HostNameExpireTime){ + if((g_MonotonicTimeMS - Current->ResolveTime) >= g_Config.HostNameExpireTime){ memset(Current, 0, sizeof(THostCacheEntry)); } @@ -85,7 +85,7 @@ bool ResolveHostName(const char *HostName, int *OutAddr){ if(Entry == NULL){ // NOTE(fusion): We also cache failures. Entry = &g_CachedHostNames[LeastRecentlyUsedIndex]; - if(!StringCopy(Entry->HostName, sizeof(Entry->HostName), HostName)){ + if(!StringBufCopy(Entry->HostName, HostName)){ LOG_WARN("Hostname \"%s\" was improperly cached because it was" " too long (Length: %d, MaxLength: %d)", HostName, (int)strlen(HostName), (int)sizeof(Entry->HostName)); diff --git a/src/query.cc b/src/query.cc index d1042e1..9d21833 100644 --- a/src/query.cc +++ b/src/query.cc @@ -4,8 +4,8 @@ struct TQueryQueue{ pthread_mutex_t Mutex; - pthread_cond_t EmptyCond; - pthread_cond_t FullCond; + pthread_cond_t WorkAvailable; + pthread_cond_t RoomAvailable; uint32 ReadPos; uint32 WritePos; uint32 MaxQueries; @@ -27,17 +27,21 @@ static TQueryQueue *g_QueryQueue; TQuery *QueryNew(void){ TQuery *Query = (TQuery*)calloc(1, sizeof(TQuery)); AtomicStore(&Query->RefCount, 1); - Query->BufferSize = g_MaxConnectionPacketSize; + Query->BufferSize = g_Config.QueryBufferSize; Query->Buffer = (uint8*)calloc(1, Query->BufferSize); + Query->Request = TReadBuffer{}; + Query->Response = TWriteBuffer{}; return Query; } void QueryDone(TQuery *Query){ - int RefCount = AtomicFetchAdd(&Query->RefCount, -1); - ASSERT(RefCount >= 1); - if(RefCount == 1){ - free(Query->Buffer); - free(Query); + if(Query != NULL){ + int RefCount = AtomicFetchAdd(&Query->RefCount, -1); + ASSERT(RefCount >= 1); + if(RefCount == 1){ + free(Query->Buffer); + free(Query); + } } } @@ -64,13 +68,13 @@ void QueryEnqueue(TQuery *Query){ while(NumQueries >= MaxQueries){ LOG_WARN("Execution stalled: queue is full (%u / %u)...", NumQueries, MaxQueries); - pthread_cond_wait(&g_QueryQueue->FullCond, &g_QueryQueue->Mutex); + pthread_cond_wait(&g_QueryQueue->RoomAvailable, &g_QueryQueue->Mutex); NumQueries = g_QueryQueue->WritePos - g_QueryQueue->ReadPos; MaxQueries = g_QueryQueue->MaxQueries; } if(NumQueries == 0){ - pthread_cond_signal(&g_QueryQueue->EmptyCond); + pthread_cond_signal(&g_QueryQueue->WorkAvailable); } g_QueryQueue->Queries[g_QueryQueue->WritePos % MaxQueries] = Query; @@ -86,14 +90,14 @@ TQuery *QueryDequeue(AtomicInt *Running){ pthread_mutex_lock(&g_QueryQueue->Mutex); uint32 NumQueries = g_QueryQueue->WritePos - g_QueryQueue->ReadPos; while(NumQueries == 0 && AtomicLoad(Running)){ - pthread_cond_wait(&g_QueryQueue->EmptyCond, &g_QueryQueue->Mutex); + pthread_cond_wait(&g_QueryQueue->WorkAvailable, &g_QueryQueue->Mutex); NumQueries = g_QueryQueue->WritePos - g_QueryQueue->ReadPos; } if(NumQueries > 0 && AtomicLoad(Running)){ uint32 MaxQueries = g_QueryQueue->MaxQueries; if(NumQueries == MaxQueries){ - pthread_cond_signal(&g_QueryQueue->FullCond); + pthread_cond_signal(&g_QueryQueue->RoomAvailable); } Query = g_QueryQueue->Queries[g_QueryQueue->ReadPos % MaxQueries]; @@ -119,10 +123,53 @@ static void *WorkerThread(void *Data){ LOG("%d: Running...", Worker->WorkerID); while(TQuery *Query = QueryDequeue(&Worker->Running)){ - //TODO + Query->QueryType = Query->Request.Read8(); + //bool (*ProcessQuery)(TDatabase*, TQuery*) = NULL; switch(Query->QueryType){ + case QUERY_INTERNAL_RESOLVE_WORLD: ProcessInternalResolveWorld(Database, Query); break; + case QUERY_CHECK_ACCOUNT_PASSWORD: ProcessCheckAccountPassword(Database, Query); break; + case QUERY_LOGIN_ACCOUNT: ProcessLoginAccount(Database, Query); break; + case QUERY_LOGIN_ADMIN: ProcessLoginAdmin(Database, Query); break; + case QUERY_LOGIN_GAME: ProcessLoginGame(Database, Query); break; + case QUERY_LOGOUT_GAME: ProcessLogoutGame(Database, Query); break; + case QUERY_SET_NAMELOCK: ProcessSetNamelock(Database, Query); break; + case QUERY_BANISH_ACCOUNT: ProcessBanishAccount(Database, Query); break; + case QUERY_SET_NOTATION: ProcessSetNotation(Database, Query); break; + case QUERY_REPORT_STATEMENT: ProcessReportStatement(Database, Query); break; + case QUERY_BANISH_IP_ADDRESS: ProcessBanishIpAddress(Database, Query); break; + case QUERY_LOG_CHARACTER_DEATH: ProcessLogCharacterDeath(Database, Query); break; + case QUERY_ADD_BUDDY: ProcessAddBuddy(Database, Query); break; + case QUERY_REMOVE_BUDDY: ProcessRemoveBuddy(Database, Query); break; + case QUERY_DECREMENT_IS_ONLINE: ProcessDecrementIsOnline(Database, Query); break; + case QUERY_FINISH_AUCTIONS: ProcessFinishAuctions(Database, Query); break; + case QUERY_TRANSFER_HOUSES: ProcessTransferHouses(Database, Query); break; + case QUERY_EVICT_FREE_ACCOUNTS: ProcessEvictFreeAccounts(Database, Query); break; + case QUERY_EVICT_DELETED_CHARACTERS: ProcessEvictDeletedCharacters(Database, Query); break; + case QUERY_EVICT_EX_GUILDLEADERS: ProcessEvictExGuildleaders(Database, Query); break; + case QUERY_INSERT_HOUSE_OWNER: ProcessInsertHouseOwner(Database, Query); break; + case QUERY_UPDATE_HOUSE_OWNER: ProcessUpdateHouseOwner(Database, Query); break; + case QUERY_DELETE_HOUSE_OWNER: ProcessDeleteHouseOwner(Database, Query); break; + case QUERY_GET_HOUSE_OWNERS: ProcessGetHouseOwners(Database, Query); break; + case QUERY_GET_AUCTIONS: ProcessGetAuctions(Database, Query); break; + case QUERY_START_AUCTION: ProcessStartAuction(Database, Query); break; + case QUERY_INSERT_HOUSES: ProcessInsertHouses(Database, Query); break; + case QUERY_CLEAR_IS_ONLINE: ProcessClearIsOnline(Database, Query); break; + case QUERY_CREATE_PLAYERLIST: ProcessCreatePlayerlist(Database, Query); break; + case QUERY_LOG_KILLED_CREATURES: ProcessLogKilledCreatures(Database, Query); break; + case QUERY_LOAD_PLAYERS: ProcessLoadPlayers(Database, Query); break; + case QUERY_EXCLUDE_FROM_AUCTIONS: ProcessExcludeFromAuctions(Database, Query); break; + case QUERY_CANCEL_HOUSE_TRANSFER: ProcessCancelHouseTransfer(Database, Query); break; + case QUERY_LOAD_WORLD_CONFIG: ProcessLoadWorldConfig(Database, Query); break; + case QUERY_CREATE_ACCOUNT: ProcessCreateAccount(Database, Query); break; + case QUERY_CREATE_CHARACTER: ProcessCreateCharacter(Database, Query); break; + case QUERY_GET_ACCOUNT_SUMMARY: ProcessGetAccountSummary(Database, Query); break; + case QUERY_GET_CHARACTER_PROFILE: ProcessGetCharacterProfile(Database, Query); break; + case QUERY_GET_WORLDS: ProcessGetWorlds(Database, Query); break; + case QUERY_GET_ONLINE_CHARACTERS: ProcessGetOnlineCharacters(Database, Query); break; + case QUERY_GET_KILL_STATISTICS: ProcessGetKillStatistics(Database, Query); break; default:{ - // + QueryFailed(Query); + break; } } @@ -144,9 +191,9 @@ bool InitQuery(void){ // in flight. g_QueryQueue = (TQueryQueue*)calloc(1, sizeof(TQueryQueue)); pthread_mutex_init(&g_QueryQueue->Mutex, NULL); - pthread_cond_init(&g_QueryQueue->EmptyCond, NULL); - pthread_cond_init(&g_QueryQueue->FullCond, NULL); - g_QueryQueue->MaxQueries = 2 * g_MaxConnections; + pthread_cond_init(&g_QueryQueue->WorkAvailable, NULL); + pthread_cond_init(&g_QueryQueue->RoomAvailable, NULL); + g_QueryQueue->MaxQueries = 2 * g_Config.MaxConnections; g_QueryQueue->Queries = (TQuery**)calloc(g_QueryQueue->MaxQueries, sizeof(TQuery*)); g_NumWorkers = 1; @@ -173,6 +220,10 @@ void ExitQuery(void){ AtomicStore(&g_Workers[i].Running, 0); } + if(g_QueryQueue != NULL){ + pthread_cond_broadcast(&g_QueryQueue->WorkAvailable); + } + for(int i = 0; i < g_NumWorkers; i += 1){ // IMPORTANT(fusion): The `WorkerID` will be set to -1 if we fail // to spawn its thread. @@ -187,8 +238,8 @@ void ExitQuery(void){ if(g_QueryQueue != NULL){ pthread_mutex_destroy(&g_QueryQueue->Mutex); - pthread_cond_destroy(&g_QueryQueue->EmptyCond); - pthread_cond_destroy(&g_QueryQueue->FullCond); + pthread_cond_destroy(&g_QueryQueue->WorkAvailable); + pthread_cond_destroy(&g_QueryQueue->RoomAvailable); // TODO(fusion): Abort queries instead? uint32 MaxQueries = g_QueryQueue->MaxQueries; @@ -203,9 +254,36 @@ void ExitQuery(void){ } } -// Queries +// 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); @@ -246,3 +324,1741 @@ void QueryFailed(TQuery *Query){ QueryFinishResponse(Query); } +// Query Processing +//============================================================================== +void ProcessInternalResolveWorld(TDatabase *Database, TQuery *Query); +void ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query); +void ProcessLoginAccount(TDatabase *Database, TQuery *Query); +void ProcessLoginAdmin(TDatabase *Database, TQuery *Query); +void ProcessLoginGame(TDatabase *Database, TQuery *Query); +void ProcessLogoutGame(TDatabase *Database, TQuery *Query); +void ProcessSetNamelock(TDatabase *Database, TQuery *Query); +void ProcessBanishAccount(TDatabase *Database, TQuery *Query); +void ProcessSetNotation(TDatabase *Database, TQuery *Query); +void ProcessReportStatement(TDatabase *Database, TQuery *Query); +void ProcessBanishIpAddress(TDatabase *Database, TQuery *Query); +void ProcessLogCharacterDeath(TDatabase *Database, TQuery *Query); +void ProcessAddBuddy(TDatabase *Database, TQuery *Query); +void ProcessRemoveBuddy(TDatabase *Database, TQuery *Query); +void ProcessDecrementIsOnline(TDatabase *Database, TQuery *Query); +void ProcessFinishAuctions(TDatabase *Database, TQuery *Query); +void ProcessTransferHouses(TDatabase *Database, TQuery *Query); +void ProcessEvictFreeAccounts(TDatabase *Database, TQuery *Query); +void ProcessEvictDeletedCharacters(TDatabase *Database, TQuery *Query); +void ProcessEvictExGuildleaders(TDatabase *Database, TQuery *Query); +void ProcessInsertHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessUpdateHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessDeleteHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessGetHouseOwners(TDatabase *Database, TQuery *Query); +void ProcessGetAuctions(TDatabase *Database, TQuery *Query); +void ProcessStartAuction(TDatabase *Database, TQuery *Query); +void ProcessInsertHouses(TDatabase *Database, TQuery *Query); +void ProcessClearIsOnline(TDatabase *Database, TQuery *Query); +void ProcessCreatePlayerlist(TDatabase *Database, TQuery *Query); +void ProcessLogKilledCreatures(TDatabase *Database, TQuery *Query); +void ProcessLoadPlayers(TDatabase *Database, TQuery *Query); +void ProcessExcludeFromAuctions(TDatabase *Database, TQuery *Query); +void ProcessCancelHouseTransfer(TDatabase *Database, TQuery *Query); +void ProcessLoadWorldConfig(TDatabase *Database, TQuery *Query); +void ProcessCreateAccount(TDatabase *Database, TQuery *Query); +void ProcessCreateCharacter(TDatabase *Database, TQuery *Query); +void ProcessGetAccountSummary(TDatabase *Database, TQuery *Query); +void ProcessGetCharacterProfile(TDatabase *Database, TQuery *Query); +void ProcessGetWorlds(TDatabase *Database, TQuery *Query); +void ProcessGetOnlineCharacters(TDatabase *Database, TQuery *Query); +void 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 +// a certain amount of times before failing. +// bool ProcessX(Database, Query){ +// // Execute database queries, returning false on failure. The +// // response should only be written at the very end when we know +// // the query SUCCEEDED, to keep the request data intact if a retry +// // is needed (because they share the query buffer). +// } +// +// ... +// +// void DatabaseCheckpoint(Database){ +// // Check whether there was a database error or if the database is +// // still connected and make sure it is ready for processing a query. +// } +// +// ... +// +// int NumAttempts = MaxAttempts; +// DatabaseCheckpoint(Database); +// while(!ProcessX(Database, Query)){ +// if(Attempts <= 0){ +// QueryFailed(Query); +// break; +// } +// DatabaseCheckpoint(Database); +// NumAttempts -= 1; +// } + + +#if 0 +// Connection Queries +//============================================================================== +void CompoundBanishment(TBanishmentStatus Status, int *Days, bool *FinalWarning){ + // TODO(fusion): We might want to add all these constants as config values. + ASSERT(Days != NULL && FinalWarning != NULL); + if(Status.FinalWarning){ + *FinalWarning = false; + *Days = 0; // permanent + }else if(Status.TimesBanished > 5 || *FinalWarning){ + *FinalWarning = true; + if(*Days < 30){ + *Days = 30; + }else{ + *Days *= 2; + } + } +} + + +void ProcessLoginQuery(TConnection *Connection, TReadBuffer *Buffer){ + char Password[30]; + char LoginData[30]; + int ApplicationType = Buffer->Read8(); + Buffer->ReadString(Password, sizeof(Password)); + if(ApplicationType == APPLICATION_TYPE_GAME){ + Buffer->ReadString(LoginData, sizeof(LoginData)); + } + + // TODO(fusion): Probably just disconnect on failed login attempt? Implement + // write then disconnect? + if(!StringEq(g_QueryManagerPassword, Password)){ + LOG_WARN("Invalid login attempt from %s", Connection->RemoteAddress); + SendQueryStatusFailed(Connection); + return; + } + + int WorldID = 0; + if(ApplicationType == APPLICATION_TYPE_GAME){ + WorldID = GetWorldID(LoginData); + if(WorldID == 0){ + LOG_WARN("Rejecting connection %s from unknown game server \"%s\"", + Connection->RemoteAddress, LoginData); + SendQueryStatusFailed(Connection); + return; + } + LOG("Connection %s AUTHORIZED to game server \"%s\" (%d)", + Connection->RemoteAddress, LoginData, WorldID); + }else if(ApplicationType == APPLICATION_TYPE_LOGIN){ + LOG("Connection %s AUTHORIZED to login server", Connection->RemoteAddress); + }else if(ApplicationType == APPLICATION_TYPE_WEB){ + LOG("Connection %s AUTHORIZED to web server", Connection->RemoteAddress); + }else{ + LOG_WARN("Rejecting connection %s from unknown application type %d", + Connection->RemoteAddress, ApplicationType); + SendQueryStatusFailed(Connection); + return; + } + + Connection->Authorized = true; + Connection->ApplicationType = ApplicationType; + Connection->WorldID = WorldID; + SendQueryStatusOk(Connection); +} + +static int CheckAccountPasswordTransaction(int AccountID, const char *Password, int IPAddress){ + TransactionScope Tx("CheckAccountPassword"); + if(!Tx.Begin()){ + return -1; + } + + TAccount Account; + if(!GetAccountData(AccountID, &Account)){ + return -1; + } + + if(Account.AccountID == 0){ + return 1; + } + + if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ + return 2; + } + + if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ + return 3; + } + + if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ + return 4; + } + + if(!Tx.Commit()){ + return -1; + } + + return 0; +} + +void ProcessCheckAccountPasswordQuery(TConnection *Connection, TReadBuffer *Buffer){ + char Password[30]; + char IPString[16]; + int AccountID = (int)Buffer->Read32(); + Buffer->ReadString(Password, sizeof(Password)); + Buffer->ReadString(IPString, sizeof(IPString)); + + int IPAddress = 0; + if(!ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + // NOTE(fusion): Similar to `ProcessLoginAccountQuery`. + int Result = CheckAccountPasswordTransaction(AccountID, Password, IPAddress); + InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); + if(Result == -1){ + SendQueryStatusFailed(Connection); + }else if(Result != 0){ + SendQueryStatusError(Connection, Result); + }else{ + SendQueryStatusOk(Connection); + } +} + +int LoginAccountTransaction(int AccountID, const char *Password, int IPAddress, + DynamicArray<TCharacterEndpoint> *Characters, int *PremiumDays){ + TransactionScope Tx("LoginAccount"); + if(!Tx.Begin()){ + return -1; + } + + TAccount Account; + if(!GetAccountData(AccountID, &Account)){ + return -1; + } + + if(Account.AccountID == 0){ + return 1; + } + + if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ + return 2; + } + + if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ + return 3; + } + + if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ + return 4; + } + + if(IsAccountBanished(Account.AccountID)){ + return 5; + } + + if(IsIPBanished(IPAddress)){ + return 6; + } + + if(!GetCharacterEndpoints(Account.AccountID, Characters)){ + return -1; + } + + if(!Tx.Commit()){ + return -1; + } + + *PremiumDays = Account.PremiumDays + Account.PendingPremiumDays; + return 0; +} + +void ProcessLoginAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ + char Password[30]; + char IPString[16]; + int AccountID = (int)Buffer->Read32(); + Buffer->ReadString(Password, sizeof(Password)); + Buffer->ReadString(IPString, sizeof(IPString)); + + int IPAddress = 0; + if(!ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + int PremiumDays = 0; + DynamicArray<TCharacterEndpoint> Characters; + int Result = LoginAccountTransaction(AccountID, Password, + IPAddress, &Characters, &PremiumDays); + + // NOTE(fusion): Similar to `ProcessLoginGameQuery` except we don't modify + // any tables inside the login transaction. + // TODO(fusion): Maybe have different login attempt tables or types? + InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); + + if(Result == -1){ + SendQueryStatusFailed(Connection); + return; + } + + if(Result != 0){ + SendQueryStatusError(Connection, Result); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumCharacters = std::min<int>(Characters.Length(), UINT8_MAX); + WriteBuffer.Write8((uint8)NumCharacters); + for(int i = 0; i < NumCharacters; i += 1){ + WriteBuffer.WriteString(Characters[i].Name); + WriteBuffer.WriteString(Characters[i].WorldName); + WriteBuffer.Write32BE((uint32)Characters[i].WorldAddress); + WriteBuffer.Write16((uint16)Characters[i].WorldPort); + } + WriteBuffer.Write16((uint16)PremiumDays); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessLoginAdminQuery(TConnection *Connection, TReadBuffer *Buffer){ + // TODO(fusion): I thought for a second this could be the query used with + // the login server but it doesn't take a password or ip address for basic + // checks. Even if it's used in combination with `CheckAccountPassword`, + // it doesn't make sense to split what should have been a single query which + // is what the new `LoginAccount` query does. + SendQueryStatusFailed(Connection); +} + +static int LoginGameTransaction(int WorldID, int AccountID, const char *CharacterName, + const char *Password, int IPAddress, bool PrivateWorld, bool GamemasterRequired, + TCharacterLoginData *Character, DynamicArray<TAccountBuddy> *Buddies, + DynamicArray<TCharacterRight> *Rights, bool *PremiumAccountActivated){ + TransactionScope Tx("LoginGame"); + if(!Tx.Begin()){ + return -1; + } + + if(!GetCharacterLoginData(CharacterName, Character)){ + return -1; + } + + if(Character->CharacterID == 0){ + return 1; + } + + if(Character->Deleted){ + return 2; + } + + if(Character->WorldID != WorldID){ + return 3; + } + + if(PrivateWorld){ + if(!GetWorldInvitation(WorldID, Character->CharacterID)){ + return 4; + } + } + + TAccount Account; + if(!GetAccountData(AccountID, &Account)){ + return -1; + } + + if(Account.AccountID == 0 || Account.AccountID != Character->AccountID){ + // NOTE(fusion): This is correct, there is no error code 5. + return 15; + } + + if(Account.Deleted){ + return 8; + } + + if(!TestPassword(Account.Auth, sizeof(Account.Auth), Password)){ + return 6; + } + + if(GetAccountFailedLoginAttempts(Account.AccountID, 5 * 60) > 10){ + return 7; + } + + if(GetIPAddressFailedLoginAttempts(IPAddress, 30 * 60) > 20){ + return 9; + } + + if(IsAccountBanished(Account.AccountID)){ + return 10; + } + + if(IsCharacterNamelocked(Character->CharacterID)){ + return 11; + } + + if(IsIPBanished(IPAddress)){ + return 12; + } + + // TODO(fusion): Probably merge these into a single operation? + if(!GetCharacterRight(Character->CharacterID, "ALLOW_MULTICLIENT") + && GetAccountOnlineCharacters(Account.AccountID) > 0 + && !IsCharacterOnline(Character->CharacterID)){ + return 13; + } + + if(GamemasterRequired){ + if(!GetCharacterRight(Character->CharacterID, "GAMEMASTER_OUTFIT")){ + return 14; + } + } + + if(!GetBuddies(WorldID, Account.AccountID, Buddies)){ + return -1; + } + + if(!GetCharacterRights(Character->CharacterID, Rights)){ + return -1; + } + + if(Account.PremiumDays == 0 && Account.PendingPremiumDays > 0){ + if(!ActivatePendingPremiumDays(Account.AccountID)){ + return -1; + } + + Account.PremiumDays += Account.PendingPremiumDays; + Account.PendingPremiumDays = 0; + *PremiumAccountActivated = true; + } + + if(Account.PremiumDays > 0){ + Rights->Push(TCharacterRight{"PREMIUM_ACCOUNT"}); + } + + if(!IncrementIsOnline(WorldID, Character->CharacterID)){ + return -1; + } + + if(!Tx.Commit()){ + return -1; + } + + return 0; +} + +void ProcessLoginGameQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char Password[30]; + char IPString[16]; + int AccountID = (int)Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(Password, sizeof(Password)); + Buffer->ReadString(IPString, sizeof(IPString)); + bool PrivateWorld = Buffer->ReadFlag(); + Buffer->ReadFlag(); // "PremiumAccountRequired" unused + bool GamemasterRequired = Buffer->ReadFlag(); + + int IPAddress = 0; + if(!ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TCharacterLoginData Character; + DynamicArray<TAccountBuddy> Buddies; + DynamicArray<TCharacterRight> Rights; + bool PremiumAccountActivated = false; + int Result = LoginGameTransaction(Connection->WorldID, AccountID, + CharacterName, Password, IPAddress, PrivateWorld, + GamemasterRequired, &Character, &Buddies, &Rights, + &PremiumAccountActivated); + + // IMPORTANT(fusion): We need to insert login attempts outside the login game + // transaction or we could end up not having it recorded at all due to rollbacks. + // It is also the reason the whole transaction had to be pulled to its own function. + // IMPORTANT(fusion): Don't return if we fail to insert the login attempt as the + // result of the whole operation was already determined by the transaction function. + InsertLoginAttempt(AccountID, IPAddress, (Result != 0)); + + if(Result == -1){ + SendQueryStatusFailed(Connection); + return; + } + + if(Result != 0){ + SendQueryStatusError(Connection, Result); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write32((uint32)Character.CharacterID); + WriteBuffer.WriteString(Character.Name); + WriteBuffer.Write8((uint8)Character.Sex); + WriteBuffer.WriteString(Character.Guild); + WriteBuffer.WriteString(Character.Rank); + WriteBuffer.WriteString(Character.Title); + + int NumBuddies = std::min<int>(Buddies.Length(), UINT8_MAX); + WriteBuffer.Write8((uint8)NumBuddies); + for(int i = 0; i < NumBuddies; i += 1){ + WriteBuffer.Write32((uint32)Buddies[i].CharacterID); + WriteBuffer.WriteString(Buddies[i].Name); + } + + int NumRights = std::min<int>(Rights.Length(), UINT8_MAX); + WriteBuffer.Write8((uint8)NumRights); + for(int i = 0; i < NumRights; i += 1){ + WriteBuffer.WriteString(Rights[i].Name); + } + + WriteBuffer.WriteFlag(PremiumAccountActivated); + + SendResponse(Connection, &WriteBuffer); +} + +void ProcessLogoutGameQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char Profession[30]; + char Residence[30]; + int CharacterID = (int)Buffer->Read32(); + int Level = Buffer->Read16(); + Buffer->ReadString(Profession, sizeof(Profession)); + Buffer->ReadString(Residence, sizeof(Residence)); + int LastLoginTime = (int)Buffer->Read32(); + int TutorActivities = Buffer->Read16(); + + if(!LogoutCharacter(Connection->WorldID, CharacterID, Level, + Profession, Residence, LastLoginTime, TutorActivities)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char IPString[16]; + char Reason[200]; + char Comment[200]; + int GamemasterID = (int)Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(IPString, sizeof(IPString)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + + int IPAddress = 0; + if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("SetNamelock"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + // TODO(fusion): Might be `NO_BANISHMENT`. + if(GetCharacterRight(CharacterID, "NAMELOCK")){ + SendQueryStatusError(Connection, 2); + return; + } + + TNamelockStatus Status = GetNamelockStatus(CharacterID); + if(Status.Namelocked){ + SendQueryStatusError(Connection, (Status.Approved ? 4 : 3)); + return; + } + + if(!InsertNamelock(CharacterID, IPAddress, GamemasterID, Reason, Comment)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessBanishAccountQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char IPString[16]; + char Reason[200]; + char Comment[200]; + int GamemasterID = (int)Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(IPString, sizeof(IPString)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + bool FinalWarning = Buffer->ReadFlag(); + + int IPAddress = 0; + if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("BanishAccount"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + // TODO(fusion): Might be `NO_BANISHMENT`. + if(GetCharacterRight(CharacterID, "BANISHMENT")){ + SendQueryStatusError(Connection, 2); + return; + } + + TBanishmentStatus Status = GetBanishmentStatus(CharacterID); + if(Status.Banished){ + SendQueryStatusError(Connection, 3); + return; + } + + int BanishmentID = 0; + int Days = 7; + CompoundBanishment(Status, &Days, &FinalWarning); + if(!InsertBanishment(CharacterID, IPAddress, GamemasterID, + Reason, Comment, FinalWarning, Days * 86400, &BanishmentID)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write32((uint32)BanishmentID); + WriteBuffer.Write8(Days > 0 ? Days : 0xFF); + WriteBuffer.WriteFlag(FinalWarning); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessSetNotationQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char IPString[16]; + char Reason[200]; + char Comment[200]; + int GamemasterID = Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(IPString, sizeof(IPString)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + + int IPAddress = 0; + if(!StringEmpty(IPString) && !ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("SetNotation"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + // TODO(fusion): Might be `NO_BANISHMENT`. + if(GetCharacterRight(CharacterID, "NOTATION")){ + SendQueryStatusError(Connection, 2); + return; + } + + int BanishmentID = 0; + if(GetNotationCount(CharacterID) >= 5){ + int BanishmentDays = 7; + bool FinalWarning = false; + TBanishmentStatus Status = GetBanishmentStatus(CharacterID); + CompoundBanishment(Status, &BanishmentDays, &FinalWarning); + if(!InsertBanishment(CharacterID, IPAddress, 0, "Excessive Notations", + "", FinalWarning, BanishmentDays, &BanishmentID)){ + SendQueryStatusFailed(Connection); + return; + } + } + + if(!InsertNotation(CharacterID, IPAddress, GamemasterID, Reason, Comment)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write32((uint32)BanishmentID); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessReportStatementQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char Reason[200]; + char Comment[200]; + int ReporterID = Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + int BanishmentID = Buffer->Read32(); + int StatementID = Buffer->Read32(); + int NumStatements = Buffer->Read16(); + + if(StatementID == 0){ + LOG_ERR("Missing reported statement id"); + SendQueryStatusFailed(Connection); + return; + } + + if(NumStatements == 0){ + LOG_ERR("Missing report statements"); + SendQueryStatusFailed(Connection); + return; + } + + TStatement *ReportedStatement = NULL; + TStatement *Statements = (TStatement*)alloca(NumStatements * sizeof(TStatement)); + for(int i = 0; i < NumStatements; i += 1){ + Statements[i].StatementID = (int)Buffer->Read32(); + Statements[i].Timestamp = (int)Buffer->Read32(); + Statements[i].CharacterID = (int)Buffer->Read32(); + Buffer->ReadString(Statements[i].Channel, sizeof(Statements[i].Channel)); + Buffer->ReadString(Statements[i].Text, sizeof(Statements[i].Text)); + + if(Statements[i].StatementID == StatementID){ + if(ReportedStatement != NULL){ + LOG_WARN("Reported statement (%d, %d, %d) appears multiple times", + Connection->WorldID, Statements[i].Timestamp, + Statements[i].StatementID); + } + ReportedStatement = &Statements[i]; + } + } + + if(ReportedStatement == NULL){ + LOG_ERR("Missing reported statement"); + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("ReportStatement"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + }else if(ReportedStatement->CharacterID != CharacterID){ + LOG_ERR("Reported statement character mismatch"); + SendQueryStatusFailed(Connection); + return; + } + + if(IsStatementReported(Connection->WorldID, ReportedStatement)){ + SendQueryStatusError(Connection, 2); + return; + } + + if(!InsertStatements(Connection->WorldID, NumStatements, Statements)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!InsertReportedStatement(Connection->WorldID, ReportedStatement, + BanishmentID, ReporterID, Reason, Comment)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessBanishIPAddressQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char CharacterName[30]; + char IPString[16]; + char Reason[200]; + char Comment[200]; + int GamemasterID = Buffer->Read16(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + Buffer->ReadString(IPString, sizeof(IPString)); + Buffer->ReadString(Reason, sizeof(Reason)); + Buffer->ReadString(Comment, sizeof(Comment)); + + int IPAddress = 0; + if(!ParseIPAddress(IPString, &IPAddress)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("BanishIP"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = GetCharacterID(Connection->WorldID, CharacterName); + if(CharacterID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + // TODO(fusion): Might be `NO_BANISHMENT`. + if(GetCharacterRight(CharacterID, "IP_BANISHMENT")){ + SendQueryStatusError(Connection, 2); + return; + } + + // IMPORTANT(fusion): It is not a good idea to ban an IP address, specially + // V4 addresses, as they may be dynamically assigned or represent the address + // of a public ISP router that manages multiple clients. + int BanishmentDays = 3; + if(!InsertIPBanishment(CharacterID, IPAddress, GamemasterID, + Reason, Comment, BanishmentDays * 86400)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessLogCharacterDeathQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + char Remark[30]; + int CharacterID = (int)Buffer->Read32(); + int Level = Buffer->Read16(); + int OffenderID = (int)Buffer->Read32(); + Buffer->ReadString(Remark, sizeof(Remark)); + bool Unjustified = Buffer->ReadFlag(); + int Timestamp = (int)Buffer->Read32(); + if(!InsertCharacterDeath(Connection->WorldID, CharacterID, Level, + OffenderID, Remark, Unjustified, Timestamp)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessAddBuddyQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int AccountID = (int)Buffer->Read32(); + int BuddyID = (int)Buffer->Read32(); + if(!InsertBuddy(Connection->WorldID, AccountID, BuddyID)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessRemoveBuddyQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int AccountID = (int)Buffer->Read32(); + int BuddyID = (int)Buffer->Read32(); + if(!DeleteBuddy(Connection->WorldID, AccountID, BuddyID)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessDecrementIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = (int)Buffer->Read32(); + if(!DecrementIsOnline(Connection->WorldID, CharacterID)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessFinishAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<THouseAuction> Auctions; + if(!FinishHouseAuctions(Connection->WorldID, &Auctions)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumAuctions = std::min<int>(Auctions.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumAuctions); + for(int i = 0; i < NumAuctions; i += 1){ + WriteBuffer.Write16((uint16)Auctions[i].HouseID); + WriteBuffer.Write32((uint32)Auctions[i].BidderID); + WriteBuffer.WriteString(Auctions[i].BidderName); + WriteBuffer.Write32((uint32)Auctions[i].BidAmount); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessTransferHousesQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<THouseTransfer> Transfers; + if(!FinishHouseTransfers(Connection->WorldID, &Transfers)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumTransfers = std::min<int>(Transfers.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumTransfers); + for(int i = 0; i < NumTransfers; i += 1){ + WriteBuffer.Write16((uint16)Transfers[i].HouseID); + WriteBuffer.Write32((uint32)Transfers[i].NewOwnerID); + WriteBuffer.WriteString(Transfers[i].NewOwnerName); + WriteBuffer.Write32((uint32)Transfers[i].Price); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessEvictFreeAccountsQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<THouseEviction> Evictions; + if(!GetFreeAccountEvictions(Connection->WorldID, &Evictions)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumEvictions); + for(int i = 0; i < NumEvictions; i += 1){ + WriteBuffer.Write16((uint16)Evictions[i].HouseID); + WriteBuffer.Write32((uint32)Evictions[i].OwnerID); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessEvictDeletedCharactersQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<THouseEviction> Evictions; + if(!GetDeletedCharacterEvictions(Connection->WorldID, &Evictions)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumEvictions); + for(int i = 0; i < NumEvictions; i += 1){ + WriteBuffer.Write16((uint16)Evictions[i].HouseID); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessEvictExGuildleadersQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + // NOTE(fusion): This is a bit different from the other eviction functions. + // The server doesn't maintain guild information for characters so it will + // send a list of guild houses with their owners and we're supposed to check + // whether the owner is still a guild leader. I don't think we should check + // any other information as the server is authoritative on house information. + DynamicArray<int> Evictions; + int NumGuildHouses = Buffer->Read16(); + for(int i = 0; i < NumGuildHouses; i += 1){ + int HouseID = Buffer->Read16(); + int OwnerID = (int)Buffer->Read32(); + if(!GetGuildLeaderStatus(Connection->WorldID, OwnerID)){ + Evictions.Push(HouseID); + } + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumEvictions = std::min<int>(Evictions.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumEvictions); + for(int i = 0; i < NumEvictions; i += 1){ + WriteBuffer.Write16((uint16)Evictions[i]); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessInsertHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int HouseID = Buffer->Read16(); + int OwnerID = (int)Buffer->Read32(); + int PaidUntil = (int)Buffer->Read32(); + if(!InsertHouseOwner(Connection->WorldID, HouseID, OwnerID, PaidUntil)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessUpdateHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int HouseID = Buffer->Read16(); + int OwnerID = (int)Buffer->Read32(); + int PaidUntil = (int)Buffer->Read32(); + if(!UpdateHouseOwner(Connection->WorldID, HouseID, OwnerID, PaidUntil)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessDeleteHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int HouseID = Buffer->Read16(); + if(!DeleteHouseOwner(Connection->WorldID, HouseID)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessGetHouseOwnersQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<THouseOwner> Owners; + if(!GetHouseOwners(Connection->WorldID, &Owners)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumOwners = std::min<int>(Owners.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumOwners); + for(int i = 0; i < NumOwners; i += 1){ + WriteBuffer.Write16((uint16)Owners[i].HouseID); + WriteBuffer.Write32((uint32)Owners[i].OwnerID); + WriteBuffer.WriteString(Owners[i].OwnerName); + WriteBuffer.Write32((uint32)Owners[i].PaidUntil); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessGetAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<int> Auctions; + if(!GetHouseAuctions(Connection->WorldID, &Auctions)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumAuctions = std::min<int>(Auctions.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumAuctions); + for(int i = 0; i < NumAuctions; i += 1){ + WriteBuffer.Write16((uint16)Auctions[i]); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessStartAuctionQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int HouseID = Buffer->Read16(); + if(!StartHouseAuction(Connection->WorldID, HouseID)){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessInsertHousesQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("InsertHouses"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + if(!DeleteHouses(Connection->WorldID)){ + SendQueryStatusFailed(Connection); + return; + } + + int NumHouses = Buffer->Read16(); + if(NumHouses > 0){ +G THouse *Houses = (THouse*)alloca(NumHouses * sizeof(THouse)); + for(int i = 0; i < NumHouses; i += 1){ + Houses[i].HouseID = Buffer->Read16(); + Buffer->ReadString(Houses[i].Name, sizeof(Houses[i].Name)); + Houses[i].Rent = (int)Buffer->Read32(); + Buffer->ReadString(Houses[i].Description, sizeof(Houses[i].Description)); + Houses[i].Size = Buffer->Read16(); + Houses[i].PositionX = Buffer->Read16(); + Houses[i].PositionY = Buffer->Read16(); + Houses[i].PositionZ = Buffer->Read8(); + Buffer->ReadString(Houses[i].Town, sizeof(Houses[i].Town)); + Houses[i].GuildHouse = Buffer->ReadFlag(); + } + + if(!InsertHouses(Connection->WorldID, NumHouses, Houses)){ + SendQueryStatusFailed(Connection); + return; + } + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessClearIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int NumAffectedCharacters; + if(!ClearIsOnline(Connection->WorldID, &NumAffectedCharacters)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write16((uint16)NumAffectedCharacters); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessCreatePlayerlistQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("OnlineList"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + if(!DeleteOnlineCharacters(Connection->WorldID)){ + SendQueryStatusFailed(Connection); + return; + } + + // TODO(fusion): I think `NumCharacters` may be used to signal that the + // server is going OFFLINE, in which case we'd have to add an `Online` + // column to `Worlds` and update it here. + + bool NewRecord = false; + int NumCharacters = Buffer->Read16(); + if(NumCharacters != 0xFFFF && NumCharacters > 0){ + TOnlineCharacter *Characters = (TOnlineCharacter*)alloca(NumCharacters * sizeof(TOnlineCharacter)); + for(int i = 0; i < NumCharacters; i += 1){ + Buffer->ReadString(Characters[i].Name, sizeof(Characters[i].Name)); + Characters[i].Level = Buffer->Read16(); + Buffer->ReadString(Characters[i].Profession, sizeof(Characters[i].Profession)); + } + + if(!InsertOnlineCharacters(Connection->WorldID, NumCharacters, Characters)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!CheckOnlineRecord(Connection->WorldID, NumCharacters, &NewRecord)){ + SendQueryStatusFailed(Connection); + return; + } + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.WriteFlag(NewRecord); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessLogKilledCreaturesQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + int NumStats = Buffer->Read16(); + TKillStatistics *Stats = (TKillStatistics*)alloca(NumStats * sizeof(TKillStatistics)); + for(int i = 0; i < NumStats; i += 1){ + Buffer->ReadString(Stats[i].RaceName, sizeof(Stats[i].RaceName)); + Stats[i].PlayersKilled = (int)Buffer->Read32(); + Stats[i].TimesKilled = (int)Buffer->Read32(); + } + + if(NumStats > 0){ + TransactionScope Tx("LogKilledCreatures"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + if(!MergeKillStatistics(Connection->WorldID, NumStats, Stats)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + } + + SendQueryStatusOk(Connection); +} + +void ProcessLoadPlayersQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + // IMPORTANT(fusion): The server expect 10K entries at most. It is probably + // some shared hard coded constant. + int NumEntries; + TCharacterIndexEntry Entries[10000]; + int MinimumCharacterID = (int)Buffer->Read32(); + if(!GetCharacterIndexEntries(Connection->WorldID, + MinimumCharacterID, NARRAY(Entries), &NumEntries, Entries)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write32((uint32)NumEntries); + for(int i = 0; i < NumEntries; i += 1){ + WriteBuffer.WriteString(Entries[i].Name); + WriteBuffer.Write32((uint32)Entries[i].CharacterID); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessExcludeFromAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("ExcludeFromAuctions"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int CharacterID = (int)Buffer->Read32(); + bool Banish = Buffer->ReadFlag(); + int ExclusionDays = 7; + int BanishmentID = 0; + if(Banish){ + int BanishmentDays = 7; + bool FinalWarning = false; + TBanishmentStatus Status = GetBanishmentStatus(CharacterID); + CompoundBanishment(Status, &BanishmentDays, &FinalWarning); + if(!InsertBanishment(CharacterID, 0, 0, "Spoiling Auction", + "", FinalWarning, BanishmentDays * 86400, &BanishmentID)){ + SendQueryStatusFailed(Connection); + return; + } + } + + if(!ExcludeFromAuctions(Connection->WorldID, + CharacterID, ExclusionDays * 86400, BanishmentID)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessCancelHouseTransferQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + // TODO(fusion): Not sure what this is used for. Maybe house transfer rows + // are kept permanently and this query is used to delete/flag it, in case + // the it didn't complete. We might need to refine `FinishHouseTransfers`. + //int HouseID = Buffer->Read16(); + SendQueryStatusOk(Connection); +} + +void ProcessLoadWorldConfigQuery(TConnection *Connection, TReadBuffer *Buffer){ + if(Connection->ApplicationType != APPLICATION_TYPE_GAME){ + SendQueryStatusFailed(Connection); + return; + } + + TWorldConfig WorldConfig = {}; + if(!GetWorldConfig(Connection->WorldID, &WorldConfig)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.Write8((uint8)WorldConfig.Type); + WriteBuffer.Write8((uint8)WorldConfig.RebootTime); + WriteBuffer.Write32BE((uint32)WorldConfig.IPAddress); + WriteBuffer.Write16((uint16)WorldConfig.Port); + WriteBuffer.Write16((uint16)WorldConfig.MaxPlayers); + WriteBuffer.Write16((uint16)WorldConfig.PremiumPlayerBuffer); + WriteBuffer.Write16((uint16)WorldConfig.MaxNewbies); + WriteBuffer.Write16((uint16)WorldConfig.PremiumNewbieBuffer); + SendResponse(Connection, &WriteBuffer); +} + +void 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. + if(AccountID <= 0 || StringEmpty(Email) || StringEmpty(Password)){ + SendQueryStatusFailed(Connection); + return; + } + + uint8 Auth[64]; + if(!GenerateAuth(Password, Auth, sizeof(Auth))){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("CreateAccount"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + if(AccountNumberExists(AccountID)){ + SendQueryStatusError(Connection, 1); + return; + } + + if(AccountEmailExists(Email)){ + SendQueryStatusError(Connection, 2); + return; + } + + if(!CreateAccount(AccountID, Email, Auth, sizeof(Auth))){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessCreateCharacterQuery(TConnection *Connection, TReadBuffer *Buffer){ + char WorldName[30]; + char CharacterName[30]; + Buffer->ReadString(WorldName, sizeof(WorldName)); + int AccountID = (int)Buffer->Read32(); + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + int Sex = Buffer->Read8(); + + // NOTE(fusion): Inputs should be checked before hand. + if(AccountID <= 0 || (Sex != 1 && Sex != 2) + || StringEmpty(WorldName) + || StringEmpty(CharacterName)){ + SendQueryStatusFailed(Connection); + return; + } + + TransactionScope Tx("CreateCharacter"); + if(!Tx.Begin()){ + SendQueryStatusFailed(Connection); + return; + } + + int WorldID = GetWorldID(WorldName); + if(WorldID == 0){ + SendQueryStatusError(Connection, 1); + return; + } + + if(!AccountNumberExists(AccountID)){ + SendQueryStatusError(Connection, 2); + return; + } + + if(CharacterNameExists(CharacterName)){ + SendQueryStatusError(Connection, 3); + return; + } + + if(!CreateCharacter(WorldID, AccountID, CharacterName, Sex)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!Tx.Commit()){ + SendQueryStatusFailed(Connection); + return; + } + + SendQueryStatusOk(Connection); +} + +void ProcessGetAccountSummaryQuery(TConnection *Connection, TReadBuffer *Buffer){ + int AccountID = (int)Buffer->Read32(); + + if(AccountID <= 0){ + SendQueryStatusFailed(Connection); + return; + } + + TAccount Account; + if(!GetAccountData(AccountID, &Account)){ + SendQueryStatusFailed(Connection); + return; + } + + if(Account.AccountID != AccountID){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<TCharacterSummary> Characters; + if(!GetCharacterSummaries(AccountID, &Characters)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.WriteString(Account.Email); + WriteBuffer.Write16((uint16)Account.PremiumDays); + WriteBuffer.Write16((uint16)Account.PendingPremiumDays); + WriteBuffer.WriteFlag(Account.Deleted); + int NumCharacters = std::min<int>(Characters.Length(), UINT8_MAX); + WriteBuffer.Write8((uint8)NumCharacters); + for(int i = 0; i < NumCharacters; i += 1){ + WriteBuffer.WriteString(Characters[i].Name); + WriteBuffer.WriteString(Characters[i].World); + WriteBuffer.Write16((uint16)Characters[i].Level); + WriteBuffer.WriteString(Characters[i].Profession); + WriteBuffer.WriteFlag(Characters[i].Online); + WriteBuffer.WriteFlag(Characters[i].Deleted); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessGetCharacterProfileQuery(TConnection *Connection, TReadBuffer *Buffer){ + char CharacterName[30]; + Buffer->ReadString(CharacterName, sizeof(CharacterName)); + + if(StringEmpty(CharacterName)){ + SendQueryStatusFailed(Connection); + return; + } + + TCharacterProfile Character; + if(!GetCharacterProfile(CharacterName, &Character)){ + SendQueryStatusFailed(Connection); + return; + } + + if(!StringEqCI(Character.Name, CharacterName)){ + SendQueryStatusError(Connection, 1); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + WriteBuffer.WriteString(Character.Name); + WriteBuffer.WriteString(Character.World); + WriteBuffer.Write8((uint8)Character.Sex); + WriteBuffer.WriteString(Character.Guild); + WriteBuffer.WriteString(Character.Rank); + WriteBuffer.WriteString(Character.Title); + WriteBuffer.Write16((uint16)Character.Level); + WriteBuffer.WriteString(Character.Profession); + WriteBuffer.WriteString(Character.Residence); + WriteBuffer.Write32((uint32)Character.LastLogin); + WriteBuffer.Write16((uint16)Character.PremiumDays); + WriteBuffer.WriteFlag(Character.Online); + WriteBuffer.WriteFlag(Character.Deleted); + SendResponse(Connection, &WriteBuffer); +} + +void ProcessGetWorldsQuery(TConnection *Connection, TReadBuffer *Buffer){ + DynamicArray<TWorld> Worlds; + if(!GetWorlds(&Worlds)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumWorlds = std::min<int>(Worlds.Length(), UINT8_MAX); + WriteBuffer.Write8((uint8)NumWorlds); + for(int i = 0; i < NumWorlds; i += 1){ + WriteBuffer.WriteString(Worlds[i].Name); + WriteBuffer.Write8((uint8)Worlds[i].Type); + WriteBuffer.Write16((uint16)Worlds[i].NumPlayers); + WriteBuffer.Write16((uint16)Worlds[i].MaxPlayers); + WriteBuffer.Write16((uint16)Worlds[i].OnlineRecord); + WriteBuffer.Write32((uint32)Worlds[i].OnlineRecordTimestamp); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessGetOnlineCharactersQuery(TConnection *Connection, TReadBuffer *Buffer){ + char WorldName[30]; + Buffer->ReadString(WorldName, sizeof(WorldName)); + + int WorldID = GetWorldID(WorldName); + if(WorldID == 0){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<TOnlineCharacter> Characters; + if(!GetOnlineCharacters(WorldID, &Characters)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumCharacters = std::min<int>(Characters.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumCharacters); + for(int i = 0; i < NumCharacters; i += 1){ + WriteBuffer.WriteString(Characters[i].Name); + WriteBuffer.Write16((uint16)Characters[i].Level); + WriteBuffer.WriteString(Characters[i].Profession); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessGetKillStatisticsQuery(TConnection *Connection, TReadBuffer *Buffer){ + char WorldName[30]; + Buffer->ReadString(WorldName, sizeof(WorldName)); + + int WorldID = GetWorldID(WorldName); + if(WorldID == 0){ + SendQueryStatusFailed(Connection); + return; + } + + DynamicArray<TKillStatistics> Stats; + if(!GetKillStatistics(WorldID, &Stats)){ + SendQueryStatusFailed(Connection); + return; + } + + TWriteBuffer WriteBuffer = PrepareResponse(Connection, QUERY_STATUS_OK); + int NumStats = std::min<int>(Stats.Length(), UINT16_MAX); + WriteBuffer.Write16((uint16)NumStats); + for(int i = 0; i < NumStats; i += 1){ + WriteBuffer.WriteString(Stats[i].RaceName); + WriteBuffer.Write32((uint32)Stats[i].PlayersKilled); + WriteBuffer.Write32((uint32)Stats[i].TimesKilled); + } + SendResponse(Connection, &WriteBuffer); +} + +void ProcessConnectionQuery(TConnection *Connection){ + TReadBuffer Buffer(Connection->Buffer, Connection->RWSize); + int Query = Buffer.Read8(); + if(!Connection->Authorized){ + if(Query == QUERY_LOGIN){ + ProcessLoginQuery(Connection, &Buffer); + }else{ + LOG_ERR("Unauthorized query %d from %s", QueryType, Connection->RemoteAddress); + CloseConnection(Connection); + } + return; + } + + switch(Query){ + case QUERY_CHECK_ACCOUNT_PASSWORD: ProcessCheckAccountPasswordQuery(Connection, &Buffer); break; + case QUERY_LOGIN_ACCOUNT: ProcessLoginAccountQuery(Connection, &Buffer); break; + case QUERY_LOGIN_ADMIN: ProcessLoginAdminQuery(Connection, &Buffer); break; + case QUERY_LOGIN_GAME: ProcessLoginGameQuery(Connection, &Buffer); break; + case QUERY_LOGOUT_GAME: ProcessLogoutGameQuery(Connection, &Buffer); break; + case QUERY_SET_NAMELOCK: ProcessSetNamelockQuery(Connection, &Buffer); break; + case QUERY_BANISH_ACCOUNT: ProcessBanishAccountQuery(Connection, &Buffer); break; + case QUERY_SET_NOTATION: ProcessSetNotationQuery(Connection, &Buffer); break; + case QUERY_REPORT_STATEMENT: ProcessReportStatementQuery(Connection, &Buffer); break; + case QUERY_BANISH_IP_ADDRESS: ProcessBanishIPAddressQuery(Connection, &Buffer); break; + case QUERY_LOG_CHARACTER_DEATH: ProcessLogCharacterDeathQuery(Connection, &Buffer); break; + case QUERY_ADD_BUDDY: ProcessAddBuddyQuery(Connection, &Buffer); break; + case QUERY_REMOVE_BUDDY: ProcessRemoveBuddyQuery(Connection, &Buffer); break; + case QUERY_DECREMENT_IS_ONLINE: ProcessDecrementIsOnlineQuery(Connection, &Buffer); break; + case QUERY_FINISH_AUCTIONS: ProcessFinishAuctionsQuery(Connection, &Buffer); break; + case QUERY_TRANSFER_HOUSES: ProcessTransferHousesQuery(Connection, &Buffer); break; + case QUERY_EVICT_FREE_ACCOUNTS: ProcessEvictFreeAccountsQuery(Connection, &Buffer); break; + case QUERY_EVICT_DELETED_CHARACTERS: ProcessEvictDeletedCharactersQuery(Connection, &Buffer); break; + case QUERY_EVICT_EX_GUILDLEADERS: ProcessEvictExGuildleadersQuery(Connection, &Buffer); break; + case QUERY_INSERT_HOUSE_OWNER: ProcessInsertHouseOwnerQuery(Connection, &Buffer); break; + case QUERY_UPDATE_HOUSE_OWNER: ProcessUpdateHouseOwnerQuery(Connection, &Buffer); break; + case QUERY_DELETE_HOUSE_OWNER: ProcessDeleteHouseOwnerQuery(Connection, &Buffer); break; + case QUERY_GET_HOUSE_OWNERS: ProcessGetHouseOwnersQuery(Connection, &Buffer); break; + case QUERY_GET_AUCTIONS: ProcessGetAuctionsQuery(Connection, &Buffer); break; + case QUERY_START_AUCTION: ProcessStartAuctionQuery(Connection, &Buffer); break; + case QUERY_INSERT_HOUSES: ProcessInsertHousesQuery(Connection, &Buffer); break; + case QUERY_CLEAR_IS_ONLINE: ProcessClearIsOnlineQuery(Connection, &Buffer); break; + case QUERY_CREATE_PLAYERLIST: ProcessCreatePlayerlistQuery(Connection, &Buffer); break; + case QUERY_LOG_KILLED_CREATURES: ProcessLogKilledCreaturesQuery(Connection, &Buffer); break; + case QUERY_LOAD_PLAYERS: ProcessLoadPlayersQuery(Connection, &Buffer); break; + case QUERY_EXCLUDE_FROM_AUCTIONS: ProcessExcludeFromAuctionsQuery(Connection, &Buffer); break; + case QUERY_CANCEL_HOUSE_TRANSFER: ProcessCancelHouseTransferQuery(Connection, &Buffer); break; + case QUERY_LOAD_WORLD_CONFIG: ProcessLoadWorldConfigQuery(Connection, &Buffer); break; + case QUERY_CREATE_ACCOUNT: ProcessCreateAccountQuery(Connection, &Buffer); break; + case QUERY_CREATE_CHARACTER: ProcessCreateCharacterQuery(Connection, &Buffer); break; + case QUERY_GET_ACCOUNT_SUMMARY: ProcessGetAccountSummaryQuery(Connection, &Buffer); break; + case QUERY_GET_CHARACTER_PROFILE: ProcessGetCharacterProfileQuery(Connection, &Buffer); break; + case QUERY_GET_WORLDS: ProcessGetWorldsQuery(Connection, &Buffer); break; + case QUERY_GET_ONLINE_CHARACTERS: ProcessGetOnlineCharactersQuery(Connection, &Buffer); break; + case QUERY_GET_KILL_STATISTICS: ProcessGetKillStatisticsQuery(Connection, &Buffer); break; + default:{ + LOG_ERR("Unknown query %d from %s", Query, Connection->RemoteAddress); + SendQueryStatusFailed(Connection); + break; + } + } +} +#endif diff --git a/src/querymanager.cc b/src/querymanager.cc index 7d613b9..0088d66 100644 --- a/src/querymanager.cc +++ b/src/querymanager.cc @@ -9,27 +9,9 @@ # error "Operating system not currently supported." #endif -// Shutdown Signal -int g_ShutdownSignal = 0; - -// Time -int g_MonotonicTimeMS = 0; - -// Database Config -char g_DatabaseFile[1024] = "tibia.db"; -int g_MaxCachedStatements = 100; - -// HostCache Config -int g_MaxCachedHostNames = 100; -int g_HostNameExpireTime = 30 * 60 * 1000; // milliseconds - -// Connection Config -int g_UpdateRate = 20; -int g_QueryManagerPort = 7174; -char g_QueryManagerPassword[30] = ""; -int g_MaxConnections = 50; -int g_MaxConnectionIdleTime = 60 * 1000; // milliseconds -int g_MaxConnectionPacketSize = (int)MB(1); +int g_ShutdownSignal = 0; +int g_MonotonicTimeMS = 0; +TConfig g_Config = {}; void LogAdd(const char *Prefix, const char *Format, ...){ char Entry[4096]; @@ -275,7 +257,7 @@ bool ReadStringConfig(char *Dest, int DestCapacity, const char *Val){ &Val[ValStart], (ValEnd - ValStart)); } -bool ReadConfig(const char *FileName){ +bool ReadConfig(const char *FileName, TConfig *Config){ FILE *File = fopen(FileName, "rb"); if(File == NULL){ LOG_ERR("Failed to open config file \"%s\"", FileName); @@ -361,39 +343,52 @@ bool ReadConfig(const char *FileName){ // NOTE(fusion): Parse KV pair. char Key[256]; - if(!StringCopyN(Key, (int)sizeof(Key), &Line[KeyStart], (KeyEnd - KeyStart))){ + if(!StringBufCopyN(Key, &Line[KeyStart], (KeyEnd - KeyStart))){ LOG_WARN("%s:%d: Exceeded key size limit of %d characters", FileName, LineNumber, (int)(sizeof(Key) - 1)); continue; } char Val[256]; - if(!StringCopyN(Val, (int)sizeof(Val), &Line[ValStart], (ValEnd - ValStart))){ + if(!StringBufCopyN(Val, &Line[ValStart], (ValEnd - ValStart))){ LOG_WARN("%s:%d: Exceeded value size limit of %d characters", FileName, LineNumber, (int)(sizeof(Val) - 1)); continue; } - if(StringEqCI(Key, "DatabaseFile")){ - ReadStringConfig(g_DatabaseFile, (int)sizeof(g_DatabaseFile), Val); - }else if(StringEqCI(Key, "MaxCachedStatements")){ - ReadIntegerConfig(&g_MaxCachedStatements, Val); - }else if(StringEqCI(Key, "MaxCachedHostNames")){ - ReadIntegerConfig(&g_MaxCachedHostNames, Val); + if(StringEqCI(Key, "MaxCachedHostNames")){ + ReadIntegerConfig(&Config->MaxCachedHostNames, Val); }else if(StringEqCI(Key, "HostNameExpireTime")){ - ReadDurationConfig(&g_HostNameExpireTime, Val); + ReadDurationConfig(&Config->HostNameExpireTime, Val); + }else if(StringEqCI(Key, "MaxCachedStatements")){ + ReadIntegerConfig(&Config->MaxCachedStatements, Val); + }else if(StringEqCI(Key, "DatabaseFile")){ + ReadStringBufConfig(Config->DatabaseFile, Val); + }else if(StringEqCI(Key, "DatabaseHost")){ + ReadStringBufConfig(Config->DatabaseHost, Val); + }else if(StringEqCI(Key, "DatabasePort")){ + ReadIntegerConfig(&Config->DatabasePort, Val); + }else if(StringEqCI(Key, "DatabaseUser")){ + ReadStringBufConfig(Config->DatabaseUser, Val); + }else if(StringEqCI(Key, "DatabasePassword")){ + ReadStringBufConfig(Config->DatabasePassword, Val); + }else if(StringEqCI(Key, "DatabaseName")){ + ReadStringBufConfig(Config->DatabaseName, Val); + }else if(StringEqCI(Key, "DatabaseTLS")){ + ReadBooleanConfig(&Config->DatabaseTLS, Val); }else if(StringEqCI(Key, "UpdateRate")){ - ReadIntegerConfig(&g_UpdateRate, Val); + ReadIntegerConfig(&Config->UpdateRate, Val); }else if(StringEqCI(Key, "QueryManagerPort")){ - ReadIntegerConfig(&g_QueryManagerPort, Val); + ReadIntegerConfig(&Config->QueryManagerPort, Val); }else if(StringEqCI(Key, "QueryManagerPassword")){ - ReadStringConfig(g_QueryManagerPassword, (int)sizeof(g_QueryManagerPassword), Val); + ReadStringBufConfig(Config->QueryManagerPassword, Val); + }else if(StringEqCI(Key, "QueryBufferSize") + || StringEqCI(Key, "MaxConnectionPacketSize")){ + ReadSizeConfig(&Config->QueryBufferSize, Val); }else if(StringEqCI(Key, "MaxConnections")){ - ReadIntegerConfig(&g_MaxConnections, Val); + ReadIntegerConfig(&Config->MaxConnections, Val); }else if(StringEqCI(Key, "MaxConnectionIdleTime")){ - ReadDurationConfig(&g_MaxConnectionIdleTime, Val); - }else if(StringEqCI(Key, "MaxConnectionPacketSize")){ - ReadSizeConfig(&g_MaxConnectionPacketSize, Val); + ReadDurationConfig(&Config->MaxConnectionIdleTime, Val); }else{ LOG_WARN("Unknown config \"%s\"", Key); } @@ -433,8 +428,30 @@ int main(int argc, const char **argv){ int64 StartTime = GetClockMonotonicMS(); g_MonotonicTimeMS = 0; - LOG("Tibia Query Manager v0.1"); - if(!ReadConfig("config.cfg")){ + // HostCache Config + g_Config.MaxCachedHostNames = 100; + g_Config.HostNameExpireTime = 30 * 60 * 1000; // milliseconds + + // Database Config + g_Config.MaxCachedStatements = 100; + StringBufCopy(g_Config.DatabaseFile, "tibia.db"); + StringBufCopy(g_Config.DatabaseHost, "localhost"); + g_Config.DatabasePort = 5432; + StringBufCopy(g_Config.DatabaseUser, "tibia"); + StringBufCopy(g_Config.DatabasePassword, ""); + StringBufCopy(g_Config.DatabaseName, ""); + g_Config.DatabaseTLS = true; + + // Connection Config + g_Config.UpdateRate = 20; + g_Config.QueryManagerPort = 7174; + StringBufCopy(g_Config.QueryManagerPassword, ""); + g_Config.QueryBufferSize = (int)MB(1); + g_Config.MaxConnections = 50; + g_Config.MaxConnectionIdleTime = 60 * 1000; // milliseconds + + LOG("Tibia Query Manager v0.2"); + if(!ReadConfig("config.cfg", &g_Config)){ return EXIT_FAILURE; } @@ -451,8 +468,8 @@ int main(int argc, const char **argv){ return EXIT_FAILURE; } - LOG("Running at %d updates per second...", g_UpdateRate); - int64 UpdateInterval = 1000 / (int64)g_UpdateRate; + LOG("Running at %d updates per second...", g_Config.UpdateRate); + int64 UpdateInterval = 1000 / (int64)g_Config.UpdateRate; while(g_ShutdownSignal == 0){ int64 UpdateStart = GetClockMonotonicMS(); g_MonotonicTimeMS = (int)(UpdateStart - StartTime); diff --git a/src/querymanager.hh b/src/querymanager.hh index 5066e18..9cfcbce 100644 --- a/src/querymanager.hh +++ b/src/querymanager.hh @@ -75,24 +75,32 @@ typedef size_t usize; TRAP(); \ }while(0) -// Time -extern int g_MonotonicTimeMS; - -// Database Config -extern char g_DatabaseFile[1024]; -extern int g_MaxCachedStatements; - -// HostCache Config -extern int g_MaxCachedHostNames; -extern int g_HostNameExpireTime; +struct TConfig{ + // HostCache Config + int MaxCachedHostNames; + int HostNameExpireTime; + + // Database Config + int MaxCachedStatements; + char DatabaseFile[100]; + char DatabaseHost[100]; + int DatabasePort; + char DatabaseUser[30]; + char DatabasePassword[30]; + char DatabaseName[30]; + bool DatabaseTLS; + + // Connection Config + int UpdateRate; + int QueryManagerPort; + char QueryManagerPassword[30]; + int QueryBufferSize; + int MaxConnections; + int MaxConnectionIdleTime; +}; -// Connection Config -extern int g_UpdateRate; -extern int g_QueryManagerPort; -extern char g_QueryManagerPassword[30]; -extern int g_MaxConnections; -extern int g_MaxConnectionIdleTime; -extern int g_MaxConnectionPacketSize; +extern int g_MonotonicTimeMS; +extern TConfig g_Config; void LogAdd(const char *Prefix, const char *Format, ...) ATTR_PRINTF(2, 3); void LogAddVerbose(const char *Prefix, const char *Function, @@ -115,7 +123,14 @@ bool ReadBooleanConfig(bool *Dest, const char *Val); bool ReadIntegerConfig(int *Dest, const char *Val); bool ReadSizeConfig(int *Dest, const char *Val); bool ReadStringConfig(char *Dest, int DestCapacity, const char *Val); -bool ReadConfig(const char *FileName); +bool ReadConfig(const char *FileName, TConfig *Config); + +// IMPORTANT(fusion): These macros should only be used when `Dest` is a char array +// to simplify the call to `StringCopy` where we'd use `sizeof(Dest)` to determine +// the size of the destination anyways. +#define StringBufCopy(Dest, Src) StringCopy(Dest, sizeof(Dest), Src) +#define StringBufCopyN(Dest, Src, SrcLength) StringCopyN(Dest, sizeof(Dest), Src, SrcLength) +#define ReadStringBufConfig(Dest, Val) ReadStringConfig(Dest, sizeof(Dest), Val) // AtomicInt //============================================================================== @@ -254,6 +269,7 @@ struct TReadBuffer{ TReadBuffer(uint8 *Buffer, int Size) : Buffer(Buffer), Size(Size), Position(0) {} + TReadBuffer(void) : TReadBuffer(NULL, 0) {} bool CanRead(int Bytes){ return (this->Position + Bytes) <= this->Size; @@ -338,6 +354,7 @@ struct TWriteBuffer{ TWriteBuffer(uint8 *Buffer, int Size) : Buffer(Buffer), Size(Size), Position(0) {} + TWriteBuffer(void) : TWriteBuffer(NULL, 0) {} bool CanWrite(int Bytes){ return (this->Position + Bytes) <= this->Size; @@ -561,175 +578,20 @@ public: const T *end(void) const { return m_Data + m_Length; } }; +// sha256.cc +//============================================================================== +void SHA256(const uint8 *Input, int InputBytes, uint8 *Digest); +bool TestPassword(const uint8 *Auth, int AuthSize, const char *Password); +bool GenerateAuth(const char *Password, uint8 *Auth, int AuthSize); +bool CheckSHA256(void); + // hostcache.cc //============================================================================== bool InitHostCache(void); void ExitHostCache(void); bool ResolveHostName(const char *HostName, int *OutAddr); -// query.cc -//============================================================================== -struct TQuery{ - AtomicInt RefCount; - int QueryType; - int WorldID; - int BufferSize; - uint8 *Buffer; - TReadBuffer Request; - TWriteBuffer Response; -}; - -TQuery *QueryNew(void); -void QueryDone(TQuery *Query); -int QueryRefCount(TQuery *Query); -void QueryEnqueue(TQuery *Query); -TQuery *QueryDequeue(AtomicInt *Running); - -bool InitQuery(void); -void ExitQuery(void); - -// connections.cc -//============================================================================== -enum : int { - APPLICATION_TYPE_GAME = 1, - APPLICATION_TYPE_LOGIN = 2, - APPLICATION_TYPE_WEB = 3, -}; - -enum : int { - QUERY_STATUS_OK = 0, - QUERY_STATUS_ERROR = 1, - QUERY_STATUS_FAILED = 3, -}; - -enum : int { - QUERY_LOGIN = 0, - QUERY_CHECK_ACCOUNT_PASSWORD = 10, - QUERY_LOGIN_ACCOUNT = 11, - QUERY_LOGIN_ADMIN = 12, - QUERY_LOGIN_GAME = 20, - QUERY_LOGOUT_GAME = 21, - QUERY_SET_NAMELOCK = 23, - QUERY_BANISH_ACCOUNT = 25, - QUERY_SET_NOTATION = 26, - QUERY_REPORT_STATEMENT = 27, - QUERY_BANISH_IP_ADDRESS = 28, - QUERY_LOG_CHARACTER_DEATH = 29, - QUERY_ADD_BUDDY = 30, - QUERY_REMOVE_BUDDY = 31, - QUERY_DECREMENT_IS_ONLINE = 32, - QUERY_FINISH_AUCTIONS = 33, - QUERY_TRANSFER_HOUSES = 35, - QUERY_EVICT_FREE_ACCOUNTS = 36, - QUERY_EVICT_DELETED_CHARACTERS = 37, - QUERY_EVICT_EX_GUILDLEADERS = 38, - QUERY_INSERT_HOUSE_OWNER = 39, - QUERY_UPDATE_HOUSE_OWNER = 40, - QUERY_DELETE_HOUSE_OWNER = 41, - QUERY_GET_HOUSE_OWNERS = 42, - QUERY_GET_AUCTIONS = 43, - QUERY_START_AUCTION = 44, - QUERY_INSERT_HOUSES = 45, - QUERY_CLEAR_IS_ONLINE = 46, - QUERY_CREATE_PLAYERLIST = 47, - QUERY_LOG_KILLED_CREATURES = 48, - QUERY_LOAD_PLAYERS = 50, - QUERY_EXCLUDE_FROM_AUCTIONS = 51, - QUERY_CANCEL_HOUSE_TRANSFER = 52, - QUERY_LOAD_WORLD_CONFIG = 53, - QUERY_CREATE_ACCOUNT = 100, - QUERY_CREATE_CHARACTER = 101, - QUERY_GET_ACCOUNT_SUMMARY = 102, - QUERY_GET_CHARACTER_PROFILE = 103, - QUERY_GET_WORLDS = 150, - QUERY_GET_ONLINE_CHARACTERS = 151, - QUERY_GET_KILL_STATISTICS = 152, -}; - -enum ConnectionState: int { - CONNECTION_FREE = 0, - CONNECTION_READING = 1, - CONNECTION_PENDING_QUERY = 2, - CONNECTION_PROCESSING_QUERY = 3, - CONNECTION_WRITING = 4, -}; - -struct TConnection{ - ConnectionState State; - int Socket; - int LastActive; - int RWSize; - int RWPosition; - uint8 *Buffer; - bool Authorized; - int ApplicationType; - int WorldID; - char RemoteAddress[30]; - TQuery *Query; // TODO -}; - -int ListenerBind(uint16 Port); -int ListenerAccept(int Listener, uint32 *OutAddr, uint16 *OutPort); -void CloseConnection(TConnection *Connection); -void EnsureConnectionBuffer(TConnection *Connection); -void DeleteConnectionBuffer(TConnection *Connection); -TConnection *AssignConnection(int Socket, uint32 Addr, uint16 Port); -void ReleaseConnection(TConnection *Connection); -void CheckConnectionInput(TConnection *Connection, int Events); -void CheckConnectionOutput(TConnection *Connection, int Events); -void CheckConnection(TConnection *Connection, int Events); -void ProcessConnections(void); -bool InitConnections(void); -void ExitConnections(void); - -TWriteBuffer PrepareResponse(TConnection *Connection, int Status); -void SendResponse(TConnection *Connection, TWriteBuffer *WriteBuffer); -void SendQueryStatusOk(TConnection *Connection); -void SendQueryStatusError(TConnection *Connection, int ErrorCode); -void SendQueryStatusFailed(TConnection *Connection); -void ProcessLoginQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessCheckAccountPasswordQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLoginAccountQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLoginAdminQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLoginGameQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLogoutGameQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessSetNamelockQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessBanishAccountQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessSetNotationQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessReportStatementQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessBanishIPAddressQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLogCharacterDeathQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessAddBuddyQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessRemoveBuddyQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessDecrementIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessFinishAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessTransferHousesQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessEvictFreeAccountsQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessEvictDeletedCharactersQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessEvictExGuildleadersQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessInsertHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessUpdateHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessDeleteHouseOwnerQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetHouseOwnersQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessStartAuctionQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessInsertHousesQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessClearIsOnlineQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessCreatePlayerlistQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLogKilledCreaturesQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLoadPlayersQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessExcludeFromAuctionsQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessCancelHouseTransferQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessLoadWorldConfigQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessCreateAccountQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessCreateCharacterQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetAccountSummaryQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetCharacterProfileQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetWorldsQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessGetOnlineCharactersQuery(TConnection *Connection, TReadBuffer *Buffer); -void ProcessConnectionQuery(TConnection *Connection); - -// database.cc +// database*.cc //============================================================================== struct TWorld{ char Name[30]; @@ -1001,11 +863,174 @@ void ExitDatabase(void); TDatabase *OpenDatabase(void); void CloseDatabase(TDatabase *Database); -// sha256.cc +// query.cc //============================================================================== -void SHA256(const uint8 *Input, int InputBytes, uint8 *Digest); -bool TestPassword(const uint8 *Auth, int AuthSize, const char *Password); -bool GenerateAuth(const char *Password, uint8 *Auth, int AuthSize); -bool CheckSHA256(void); +enum : int { + QUERY_STATUS_OK = 0, + QUERY_STATUS_ERROR = 1, + QUERY_STATUS_FAILED = 3, +}; + +enum : int { + QUERY_LOGIN = 0, + QUERY_INTERNAL_RESOLVE_WORLD = 1, + QUERY_CHECK_ACCOUNT_PASSWORD = 10, + QUERY_LOGIN_ACCOUNT = 11, + QUERY_LOGIN_ADMIN = 12, + QUERY_LOGIN_GAME = 20, + QUERY_LOGOUT_GAME = 21, + QUERY_SET_NAMELOCK = 23, + QUERY_BANISH_ACCOUNT = 25, + QUERY_SET_NOTATION = 26, + QUERY_REPORT_STATEMENT = 27, + QUERY_BANISH_IP_ADDRESS = 28, + QUERY_LOG_CHARACTER_DEATH = 29, + QUERY_ADD_BUDDY = 30, + QUERY_REMOVE_BUDDY = 31, + QUERY_DECREMENT_IS_ONLINE = 32, + QUERY_FINISH_AUCTIONS = 33, + QUERY_TRANSFER_HOUSES = 35, + QUERY_EVICT_FREE_ACCOUNTS = 36, + QUERY_EVICT_DELETED_CHARACTERS = 37, + QUERY_EVICT_EX_GUILDLEADERS = 38, + QUERY_INSERT_HOUSE_OWNER = 39, + QUERY_UPDATE_HOUSE_OWNER = 40, + QUERY_DELETE_HOUSE_OWNER = 41, + QUERY_GET_HOUSE_OWNERS = 42, + QUERY_GET_AUCTIONS = 43, + QUERY_START_AUCTION = 44, + QUERY_INSERT_HOUSES = 45, + QUERY_CLEAR_IS_ONLINE = 46, + QUERY_CREATE_PLAYERLIST = 47, + QUERY_LOG_KILLED_CREATURES = 48, + QUERY_LOAD_PLAYERS = 50, + QUERY_EXCLUDE_FROM_AUCTIONS = 51, + QUERY_CANCEL_HOUSE_TRANSFER = 52, + QUERY_LOAD_WORLD_CONFIG = 53, + QUERY_CREATE_ACCOUNT = 100, + QUERY_CREATE_CHARACTER = 101, + QUERY_GET_ACCOUNT_SUMMARY = 102, + QUERY_GET_CHARACTER_PROFILE = 103, + QUERY_GET_WORLDS = 150, + QUERY_GET_ONLINE_CHARACTERS = 151, + QUERY_GET_KILL_STATISTICS = 152, +}; + +struct TQuery{ + AtomicInt RefCount; + int QueryType; + int QueryStatus; + int WorldID; + int BufferSize; + uint8 *Buffer; + TReadBuffer Request; + TWriteBuffer Response; +}; + +TQuery *QueryNew(void); +void QueryDone(TQuery *Query); +int QueryRefCount(TQuery *Query); +void QueryEnqueue(TQuery *Query); +TQuery *QueryDequeue(AtomicInt *Running); +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); + +TWriteBuffer *QueryBeginResponse(TQuery *Query, int Status); +bool QueryFinishResponse(TQuery *Query); +void QueryOk(TQuery *Query); +void QueryError(TQuery *Query, int ErrorCode); +void QueryFailed(TQuery *Query); + +void ProcessInternalResolveWorld(TDatabase *Database, TQuery *Query); +void ProcessCheckAccountPassword(TDatabase *Database, TQuery *Query); +void ProcessLoginAccount(TDatabase *Database, TQuery *Query); +void ProcessLoginAdmin(TDatabase *Database, TQuery *Query); +void ProcessLoginGame(TDatabase *Database, TQuery *Query); +void ProcessLogoutGame(TDatabase *Database, TQuery *Query); +void ProcessSetNamelock(TDatabase *Database, TQuery *Query); +void ProcessBanishAccount(TDatabase *Database, TQuery *Query); +void ProcessSetNotation(TDatabase *Database, TQuery *Query); +void ProcessReportStatement(TDatabase *Database, TQuery *Query); +void ProcessBanishIpAddress(TDatabase *Database, TQuery *Query); +void ProcessLogCharacterDeath(TDatabase *Database, TQuery *Query); +void ProcessAddBuddy(TDatabase *Database, TQuery *Query); +void ProcessRemoveBuddy(TDatabase *Database, TQuery *Query); +void ProcessDecrementIsOnline(TDatabase *Database, TQuery *Query); +void ProcessFinishAuctions(TDatabase *Database, TQuery *Query); +void ProcessTransferHouses(TDatabase *Database, TQuery *Query); +void ProcessEvictFreeAccounts(TDatabase *Database, TQuery *Query); +void ProcessEvictDeletedCharacters(TDatabase *Database, TQuery *Query); +void ProcessEvictExGuildleaders(TDatabase *Database, TQuery *Query); +void ProcessInsertHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessUpdateHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessDeleteHouseOwner(TDatabase *Database, TQuery *Query); +void ProcessGetHouseOwners(TDatabase *Database, TQuery *Query); +void ProcessGetAuctions(TDatabase *Database, TQuery *Query); +void ProcessStartAuction(TDatabase *Database, TQuery *Query); +void ProcessInsertHouses(TDatabase *Database, TQuery *Query); +void ProcessClearIsOnline(TDatabase *Database, TQuery *Query); +void ProcessCreatePlayerlist(TDatabase *Database, TQuery *Query); +void ProcessLogKilledCreatures(TDatabase *Database, TQuery *Query); +void ProcessLoadPlayers(TDatabase *Database, TQuery *Query); +void ProcessExcludeFromAuctions(TDatabase *Database, TQuery *Query); +void ProcessCancelHouseTransfer(TDatabase *Database, TQuery *Query); +void ProcessLoadWorldConfig(TDatabase *Database, TQuery *Query); +void ProcessCreateAccount(TDatabase *Database, TQuery *Query); +void ProcessCreateCharacter(TDatabase *Database, TQuery *Query); +void ProcessGetAccountSummary(TDatabase *Database, TQuery *Query); +void ProcessGetCharacterProfile(TDatabase *Database, TQuery *Query); +void ProcessGetWorlds(TDatabase *Database, TQuery *Query); +void ProcessGetOnlineCharacters(TDatabase *Database, TQuery *Query); +void ProcessGetKillStatistics(TDatabase *Database, TQuery *Query); + +// connections.cc +//============================================================================== +enum : int { + APPLICATION_TYPE_GAME = 1, + APPLICATION_TYPE_LOGIN = 2, + APPLICATION_TYPE_WEB = 3, +}; + +enum ConnectionState: int { + CONNECTION_FREE = 0, + CONNECTION_READING = 1, + CONNECTION_REQUEST = 2, + CONNECTION_RESPONSE = 3, + CONNECTION_WRITING = 4, +}; + +struct TConnection{ + ConnectionState State; + int Socket; + int LastActive; + int RWSize; + int RWPosition; + bool Authorized; + int ApplicationType; + TQuery *Query; + char RemoteAddress[30]; +}; + +int ListenerBind(uint16 Port); +int ListenerAccept(int Listener, uint32 *OutAddr, uint16 *OutPort); +void CloseConnection(TConnection *Connection); +TConnection *AssignConnection(int Socket, uint32 Addr, uint16 Port); +void ReleaseConnection(TConnection *Connection); +void CheckConnectionInput(TConnection *Connection, int Events); +void SendQueryResponse(TConnection *Connection); +void SendQueryOk(TConnection *Connection); +void SendQueryError(TConnection *Connection, int ErrorCode); +void SendQueryFailed(TConnection *Connection); +void CheckConnectionQueryRequest(TConnection *Connection); +void CheckConnectionQueryResponse(TConnection *Connection); +void CheckConnectionOutput(TConnection *Connection, int Events); +void CheckConnection(TConnection *Connection, int Events); +void ProcessConnections(void); +bool InitConnections(void); +void ExitConnections(void); #endif //TIBIA_QUERYMANAGER_HH_ |
