diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-07-01 20:41:31 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-07-01 20:41:31 -0300 |
| commit | aaf198773fd8e041b0976cc0682e83f8acf394d0 (patch) | |
| tree | 5fcf0b6b1b927dda842ae98b041463cdafd14126 /src | |
| parent | 11ae3d0e42293d01b4d729ed5b6fceb8f0ec3fa5 (diff) | |
| download | game-aaf198773fd8e041b0976cc0682e83f8acf394d0.tar.gz game-aaf198773fd8e041b0976cc0682e83f8acf394d0.zip | |
beginning of `query.cc`
Diffstat (limited to 'src')
| -rw-r--r-- | src/communication.cc | 10 | ||||
| -rw-r--r-- | src/crplayer.cc | 2 | ||||
| -rw-r--r-- | src/query.cc | 382 | ||||
| -rw-r--r-- | src/query.hh | 39 | ||||
| -rw-r--r-- | src/stubs.hh | 16 |
5 files changed, 421 insertions, 28 deletions
diff --git a/src/communication.cc b/src/communication.cc index 99c891a..bb71327 100644 --- a/src/communication.cc +++ b/src/communication.cc @@ -273,11 +273,11 @@ bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size){ int BytesToWrite = Size + 2; uint8 *WritePtr = Buffer; while(BytesToWrite > 0){ - int ret = (int)write(Connection->GetSocket(), WritePtr, BytesToWrite); - if(ret > 0){ - BytesToWrite -= ret; - WritePtr += ret; - }else if(ret == 0){ + int BytesWritten = (int)write(Connection->GetSocket(), WritePtr, BytesToWrite); + if(BytesWritten > 0){ + BytesToWrite -= BytesWritten; + WritePtr += BytesWritten; + }else if(BytesWritten == 0){ // TODO(fusion): Can this even happen without an error? error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n", errno, Connection->GetSocket()); diff --git a/src/crplayer.cc b/src/crplayer.cc index a53e676..e5874be 100644 --- a/src/crplayer.cc +++ b/src/crplayer.cc @@ -8,8 +8,6 @@ #include "threads.hh" #include "writer.hh" -#include "stubs.hh" - static Semaphore PlayerMutex(1); static vector<TPlayer*> PlayerList(0, 100, 10, NULL); static int FirstFreePlayer; diff --git a/src/query.cc b/src/query.cc index 697a3ce..a31b9b0 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,3 +1,383 @@ #include "query.hh" +#include "config.hh" -// TODO +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <sys/socket.h> + +static int ApplicationType; +static char LoginData[30]; + +void SetQueryManagerLoginData(int Type, const char *Data){ + ApplicationType = Type; + if(Data != NULL){ + strncpy(LoginData, Data, sizeof(LoginData)); + LoginData[sizeof(LoginData) - 1] = 0; + }else{ + LoginData[0] = 0; + } +} + +// TODO(fusion): An initializer list executes in the same order fields are +// declared. This initialization should work as long as `BufferSize` is the +// first field declared in `TQueryManagerConnection`, which it is. +TQueryManagerConnection::TQueryManagerConnection(int QueryBufferSize) : + BufferSize(std::max<int>(QueryBufferSize, KB(16))), + Buffer(new uint8[this->BufferSize]), + ReadBuffer(this->Buffer, this->BufferSize), + WriteBuffer(this->Buffer, this->BufferSize), + Socket(-1), + QueryOk(false) +{ + this->connect(); +} + +TQueryManagerConnection::~TQueryManagerConnection(void){ + this->disconnect(); + delete[] this->Buffer; +} + +bool ResolveHostNameAddress(const char *HostName, in_addr_t *OutAddr){ + // TODO(fusion): Just use `getaddrinfo`? + int ErrorCode; + hostent HostEnt; + hostent *HostEntResult; + char Buffer[2048]; + int Ret = gethostbyname_r(HostName, &HostEnt, + Buffer, sizeof(Buffer), &HostEntResult, &ErrorCode); + if(Ret != 0 || HostEntResult == NULL){ + return false; + } + + if(OutAddr){ + *OutAddr = ((in_addr_t)HostEntResult->h_addr_list[0][0] << 24) + | ((in_addr_t)HostEntResult->h_addr_list[0][1] << 16) + | ((in_addr_t)HostEntResult->h_addr_list[0][2] << 8) + | ((in_addr_t)HostEntResult->h_addr_list[0][3] << 0); + } + + return true; +} + +void TQueryManagerConnection::connect(void){ + for(int i = 0; i < NumberOfQueryManagers; i += 1){ + in_addr_t Addr = inet_addr(QUERY_MANAGER[i].Host); + if(Addr == 0xFFFFFFFF && !ResolveHostNameAddress(QUERY_MANAGER[i].Host, &Addr)){ + print(2, "TQueryManagerConnection::connect: Kann Rechnernamen nicht auflösen.\n"); + continue; + } + + this->Socket = socket(AF_INET, SOCK_STREAM, 0); + if(this->Socket == -1){ + print(2, "TQueryManagerConnection::connect: Kann Socket nicht öffnen.\n"); + continue; + } + + struct sockaddr_in QueryManagerAddress = {}; + QueryManagerAddress.sin_family = AF_INET; + QueryManagerAddress.sin_port = htons(QUERY_MANAGER[i].Port); + QueryManagerAddress.sin_addr.s_addr = Addr; + if(::connect(this->Socket, (struct sockaddr*)&QueryManagerAddress, sizeof(QueryManagerAddress)) == -1){ + this->disconnect(); + continue; + } + + this->prepareQuery(0); + this->sendByte((uint8)ApplicationType); + this->sendString(QUERY_MANAGER[i].Password); + if(ApplicationType == 1){ + this->sendString(LoginData); + } + + int Ret = this->executeQuery(30, false); + if(Ret != 0){ + print(2, "TQueryManagerConnection::connect: Anmeldung fehlgeschlagen (%d).\n", Ret); + this->disconnect(); + continue; + } + + break; + } + + if(!this->isConnected()){ + print(2, "TQueryManagerConnection::connect: Kein Query-Manager verfügbar.\n"); + } +} + +void TQueryManagerConnection::disconnect(void){ + if(this->Socket != -1){ + if(close(this->Socket) == -1){ + error("TQueryManagerConnection::disconnect: Fehler %d beim Schließen der Socket.\n", errno); + } + this->Socket = -1; + } +} + +int TQueryManagerConnection::write(const uint8 *Buffer, int Size){ + int Attempts = 50; + int BytesToWrite = Size; + const uint8 *WritePtr = Buffer; + while(BytesToWrite > 0){ + int BytesWritten = (int)::write(this->Socket, WritePtr, BytesToWrite); + if(BytesWritten > 0){ + BytesToWrite -= BytesWritten; + WritePtr += BytesWritten; + }else if(BytesWritten == 0){ + break; + }else{ + // TODO(fusion): We don't set the socket as non blocking so I don't + // think we can even get `EAGAIN` here. + if(errno != EAGAIN || Attempts <= 0){ + return -1; + } + + DelayThread(0, 100000); + Attempts -= 1; + } + } + return Size - BytesToWrite; +} + +int TQueryManagerConnection::read(uint8 *Buffer, int Size, int Timeout){ + int Attempts = 50; + int BytesToRead = Size; + uint8 *ReadPtr = Buffer; + while(BytesToRead > 0){ + struct pollfd pollfd = {}; + pollfd.fd = this->Socket; + pollfd.events = POLLIN; + if(poll(&pollfd, 1, Timeout * 1000) != 1){ + return -2; + } + + int BytesRead = ::read(this->Socket, ReadPtr, BytesToRead); + if(BytesRead > 0){ + BytesToRead -= BytesRead; + ReadPtr += BytesRead; + }else if(BytesRead == 0){ + break; + }else if(errno != EINTR){ + if(errno != EAGAIN || BytesToRead == Size || Attempts <= 0){ + return -1; + } + + DelayThread(0, 100000); + Attempts -= 1; + } + } + return Size - BytesToRead; +} + +void TQueryManagerConnection::prepareQuery(int QueryType){ + this->QueryOk = true; + this->WriteBuffer.Position = 0; + + try{ + this->WriteBuffer.writeWord(0); + this->WriteBuffer.writeByte((uint8)QueryType); + }catch(const char *str){ + error("TQueryManagerConnection::prepareQuery: %s.\n", str); + this->QueryOk = false; + } +} + +void TQueryManagerConnection::sendFlag(bool Flag){ + this->sendByte(Flag ? 0x01 : 0x00); +} + +void TQueryManagerConnection::sendByte(uint8 Byte){ + try{ + this->WriteBuffer.writeByte(Byte); + }catch(const char *str){ + error("TQueryManagerConnection::sendByte: %s.\n", str); + this->QueryOk = false; + } +} + +void TQueryManagerConnection::sendWord(uint16 Word){ + try{ + this->WriteBuffer.writeWord(Word); + }catch(const char *str){ + error("TQueryManagerConnection::sendWord: %s.\n", str); + this->QueryOk = false; + } +} + +void TQueryManagerConnection::sendQuad(uint32 Quad){ + try{ + this->WriteBuffer.writeQuad(Quad); + }catch(const char *str){ + error("TQueryManagerConnection::sendQuad: %s.\n", str); + this->QueryOk = false; + } +} + +void TQueryManagerConnection::sendString(const char *String){ + try{ + this->WriteBuffer.writeString(String); + }catch(const char *str){ + error("TQueryManagerConnection::sendString: %s.\n", str); + this->QueryOk = false; + } +} + +void TQueryManagerConnection::sendBytes(const uint8 *Buffer, int Count){ + try{ + this->WriteBuffer.writeBytes(Buffer, Count); + }catch(const char *str){ + error("TQueryManagerConnection::sendBytes: %s.\n", str); + this->QueryOk = false; + } +} + +bool TQueryManagerConnection::getFlag(void){ + return this->getByte() != 0; +} + +uint8 TQueryManagerConnection::getByte(void){ + uint8 Result = 0; + try{ + Result = this->ReadBuffer.readByte(); + }catch(const char *str){ + error("TQueryManagerConnection::getByte: %s.\n", str); + } + return Result; +} + +uint16 TQueryManagerConnection::getWord(void){ + uint16 Result = 0; + try{ + Result = this->ReadBuffer.readWord(); + }catch(const char *str){ + error("TQueryManagerConnection::getWord: %s.\n", str); + } + return Result; +} + +uint32 TQueryManagerConnection::getQuad(void){ + uint32 Result = 0; + try{ + Result = this->ReadBuffer.readQuad(); + }catch(const char *str){ + error("TQueryManagerConnection::getQuad: %s.\n", str); + } + return Result; +} + +void TQueryManagerConnection::getString(char *Buffer, int MaxLength){ + try{ + this->ReadBuffer.readString(Buffer, MaxLength); + }catch(const char *str){ + error("TQueryManagerConnection::getString: %s.\n", str); + if(MaxLength > 0){ + Buffer[0] = 0; + } + } +} + +void TQueryManagerConnection::getBytes(uint8 *Buffer, int Count){ + try{ + this->ReadBuffer.readBytes(Buffer, Count); + }catch(const char *str){ + error("TQueryManagerConnection::getBytes: %s.\n", str); + } +} + +int TQueryManagerConnection::executeQuery(int Timeout, bool AutoReconnect){ + // NOTE(fusion): `prepareQuery` will already leave two extra bytes at the + // beginning of the buffer for writing the request's length. + int PacketSize = this->WriteBuffer.Position; + int PayloadSize = PacketSize - 2; + this->WriteBuffer.Position = 0; + if(PayloadSize < 0xFFFF){ + this->sendWord((uint16)PayloadSize); + }else{ + PacketSize += 4; + if(PacketSize > this->BufferSize){ + error("TQueryManagerConnection::executeQuery: Puffer zu klein.\n"); + return QUERY_FAILED; + } + + memmove(&this->Buffer[6], &this->Buffer[2], PayloadSize); + this->sendWord(0xFFFF); + this->sendQuad((uint32)PayloadSize); + } + + if(!this->QueryOk){ + error("TQueryManagerConnection::executeQuery: Fehler beim Zusammenbauen der Anfrage.\n"); + return QUERY_FAILED; + } + + for(int Attempt = 0; true; Attempt += 1){ + if(!this->isConnected()){ + if(!AutoReconnect){ + return QUERY_FAILED; + } + + // TODO(fusion): There was also a "fast" path that allocated on the + // stack with variable length arrays. It would be a problem to mimic + // that with `alloca` since its memory is only released at the end + // of the function and we're in a loop. + uint8 *TempBuffer = new uint8[PacketSize]; + memcpy(TempBuffer, this->Buffer, PacketSize); + this->connect(); + memcpy(this->Buffer, TempBuffer, PacketSize); + delete[] TempBuffer; + + if(!this->isConnected()){ + return QUERY_FAILED; + } + } + + if(this->write(this->Buffer, PacketSize) != PacketSize){ + this->disconnect(); + if(Attempt > 0){ + error("TQueryManagerConnection::executeQuery: Fehler beim Abschicken der Anfrage.\n"); + return QUERY_FAILED; + } + continue; + } + + uint8 Help[4]; + int BytesRead = this->read(Help, 2, Timeout); + if(BytesRead != 2){ + this->disconnect(); + if(BytesRead == -2 || Attempt > 0){ + return QUERY_FAILED; + } + continue; + } + + int ResponseSize = ((int)Help[0]) | ((int)Help[1] << 8); + if(ResponseSize == 0xFFFF){ + if(this->read(Help, 4, Timeout) != 4){ + this->disconnect(); + return QUERY_FAILED; + } + + ResponseSize = ((int)Help[0]) | ((int)Help[1] << 8) + | ((int)Help[2] << 16) | ((int)Help[3] << 24); + } + + if(ResponseSize <= 0 || ResponseSize > this->BufferSize){ + this->disconnect(); + error("TQueryManagerConnection::executeQuery: Ungültige Datengröße %d.\n", ResponseSize); + return QUERY_FAILED; + } + + if(this->read(this->Buffer, ResponseSize, Timeout) != ResponseSize){ + this->disconnect(); + error("TQueryManagerConnection::executeQuery: Fehler beim Auslesen der Daten.\n"); + return QUERY_FAILED; + } + + int Status = this->getByte(); + if(Status == QUERY_FAILED){ + error("TQueryManagerConnection::executeQuery: Anfrage fehlgeschlagen.\n"); + } + + return Status; + } +} diff --git a/src/query.hh b/src/query.hh index a69413a..8bdd527 100644 --- a/src/query.hh +++ b/src/query.hh @@ -4,8 +4,43 @@ #include "common.hh" #include "threads.hh" +enum : int { + QUERY_OK = 0, + QUERY_ERROR = 1, + QUERY_FAILED = 3, +}; + struct TQueryManagerConnection{ TQueryManagerConnection(int QueryBufferSize); + ~TQueryManagerConnection(void); + + void connect(void); + void disconnect(void); + int write(const uint8 *Buffer, int Size); + int read(uint8 *Buffer, int Size, int Timeout); + bool isConnected(void){ + return this->Socket != -1; + } + + void prepareQuery(int QueryType); + void sendFlag(bool Flag); + void sendByte(uint8 Byte); + void sendWord(uint16 Word); + void sendQuad(uint32 Quad); + void sendString(const char *String); + void sendBytes(const uint8 *Buffer, int Count); + + bool getFlag(void); + uint8 getByte(void); + uint16 getWord(void); + uint32 getQuad(void); + void getString(char *Buffer, int MaxLength); + void getBytes(uint8 *Buffer, int Count); + + int executeQuery(int Timeout, bool AutoReconnect); + +//== + int loadWorldConfig(int *WorldType, int *RebootTime, int *IPAddress, int *Port, int *MaxPlayers, int *PremiumPlayerBuffer, @@ -71,10 +106,6 @@ struct TQueryManagerConnection{ int insertHouseOwner(uint16 HouseID, uint32 OwnerID, int PaidUntil); int updateHouseOwner(uint16 HouseID, uint32 OwnerID, int PaidUntil); - bool isConnected(void){ - return this->Socket >= 0; - } - // DATA // ================= int BufferSize; diff --git a/src/stubs.hh b/src/stubs.hh deleted file mode 100644 index 0be5c77..0000000 --- a/src/stubs.hh +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TIBIA_STUBS_HH_ -#define TIBIA_STUBS_HH_ 1 - -#include "common.hh" -#include "enums.hh" -#include "cr.hh" -#include "map.hh" - -// IMPORTANT(fusion): These function definitions exist to test compilation. They're -// not yet implemented and will cause the linker to fail with unresolved symbols. - -extern void SavePlayerData(TPlayerData *Slot); -extern bool LoadPlayerData(TPlayerData *Slot); -extern bool PlayerDataExists(uint32 CharacterID); - -#endif //TIBIA_STUBS_HH_ |
