aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-10-07 18:50:02 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-10-07 18:50:02 -0300
commitaa31732158d3e457dfe630f3b2e56f53de89e2b1 (patch)
tree685b2fe7cecb53a659e5f7b48581c6736318ea7e
parentf3c2d53f1837a7bd8e9e1517579b7354af91ef8c (diff)
downloadquerymanager-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.
-rw-r--r--src/connections.cc2007
-rw-r--r--src/database_sqlite.cc99
-rw-r--r--src/hostcache.cc12
-rw-r--r--src/query.cc1856
-rw-r--r--src/querymanager.cc101
-rw-r--r--src/querymanager.hh397
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_