aboutsummaryrefslogtreecommitdiff
path: root/src/connections.cc
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 /src/connections.cc
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.
Diffstat (limited to 'src/connections.cc')
-rw-r--r--src/connections.cc2007
1 files changed, 232 insertions, 1775 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;
- }
- }
-}