aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-20 06:00:16 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-20 06:00:16 -0300
commitd3b92e110a06c6bb2bc0214319b390059bdf7c4a (patch)
tree51293060ad930091625645cc62a1df26eb90fa59
parentbd3a6a04601749cbde007eaabf803fb602ee2783 (diff)
downloadgame-d3b92e110a06c6bb2bc0214319b390059bdf7c4a.tar.gz
game-d3b92e110a06c6bb2bc0214319b390059bdf7c4a.zip
more work on `communication.cc`
-rw-r--r--TODO.md2
-rw-r--r--reference/game.c1165
-rw-r--r--src/communication.cc925
-rw-r--r--src/communication.hh19
-rw-r--r--src/connections.hh2
-rw-r--r--src/crypto.hh37
-rw-r--r--src/magic.cc2
-rw-r--r--src/operate.cc8
-rw-r--r--src/query.hh25
-rw-r--r--src/stubs.hh1
10 files changed, 971 insertions, 1215 deletions
diff --git a/TODO.md b/TODO.md
index c623139..b347d45 100644
--- a/TODO.md
+++ b/TODO.md
@@ -23,6 +23,8 @@ I'm not sure whether synchronization is done properly. The implementation of the
## Threading
After some initial dive into `communication.cc`, it has become clear that the original codebase relied on `LinuxThreads` which, among its quirks, assigns different PIDs to different threads, explaining the usage of `getpid()`. It also seems it could lack automatically managed thread stacks which explains `CommunicationThreadStacks`.
+We'll have to address this BEFORE trying to run the server.
+
## Estimate
The decompiled file has ~115K lines of C. If we take ~15K lines to be rubbish, this can be round to ~100K. Considering a low estimate of 200 lines per day, the whole process could take up to 500 days which is quite a bit but not impossible. Now considering a high estimate of 1K lines per day, it could take 100 days which is also quite a bit.
diff --git a/reference/game.c b/reference/game.c
index acbdbfa..71343d6 100644
--- a/reference/game.c
+++ b/reference/game.c
@@ -37401,1171 +37401,6 @@ void __static_initialization_and_destruction_0(int __initialize_p,int __priority
return;
}
-bool SendData(TConnection *Connection)
-
-{
- int iVar1;
- undefined1 *puVar2;
- bool bVar3;
- uint uVar4;
- int Offset;
- int iVar5;
- int DataSize;
- uint uVar6;
- undefined4 uStackY_50;
- uint auStack_48 [2];
- undefined1 auStack_40 [8];
- undefined1 *local_38;
- uchar_0_ *Buffer;
- int local_30;
- int PacketSize;
- TWriteBuffer WriteBuffer;
-
- Buffer = &stack0xffffffb4;
- if (Connection == (TConnection *)0x0) {
- uStackY_50 = 0x80dc573;
- error("SendData: Verbindung ist NULL.\n");
- }
- else {
- uVar6 = Connection->NextToCommit - Connection->NextToSend;
- uVar4 = uVar6 + 0xe;
- if (-1 < (int)(uVar6 + 7)) {
- uVar4 = uVar6 + 7;
- }
- uVar4 = uVar4 & 0xfffffff8;
- local_30 = uVar4 + 2;
- iVar1 = -(uVar4 + 0x13 & 0xfffffff0);
- local_38 = auStack_40 + iVar1;
- *(uint *)((int)auStack_48 + iVar1 + 4) = uVar4 + 4;
- *(undefined1 **)((int)auStack_48 + iVar1) = auStack_40 + iVar1;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc48e;
- TWriteBuffer::TWriteBuffer
- (*(TWriteBuffer **)(&stack0xffffffb4 + iVar1),*(uchar **)((int)auStack_48 + iVar1)
- ,*(int *)((int)auStack_48 + iVar1 + 4));
- *(undefined4 *)((int)auStack_48 + iVar1) = 0;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc4a1;
- TWriteBuffer::writeWord
- (*(TWriteBuffer **)(&stack0xffffffb4 + iVar1),*(ushort *)((int)auStack_48 + iVar1)
- );
- *(uint *)((int)auStack_48 + iVar1) = uVar6 & 0xffff;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc4b3;
- TWriteBuffer::writeWord
- (*(TWriteBuffer **)(&stack0xffffffb4 + iVar1),*(ushort *)((int)auStack_48 + iVar1)
- );
- iVar5 = Connection->NextToSend % 0x4000;
- if ((int)(uVar6 + iVar5) < 0x4001) {
- *(uint *)((int)auStack_48 + iVar1 + 4) = uVar6;
- *(uchar **)((int)auStack_48 + iVar1) = Connection->OutData + iVar5;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- }
- else {
- *(uchar **)((int)auStack_48 + iVar1) = Connection->OutData + iVar5;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- *(int *)((int)auStack_48 + iVar1 + 4) = 0x4000 - iVar5;
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc4f9;
- TWriteBuffer::writeBytes
- (*(TWriteBuffer **)(&stack0xffffffb4 + iVar1),
- *(uchar **)((int)auStack_48 + iVar1),*(int *)((int)auStack_48 + iVar1 + 4));
- *(uint *)((int)auStack_48 + iVar1 + 4) = uVar6 + iVar5 + -0x4000;
- *(uchar **)((int)auStack_48 + iVar1) = Connection->OutData;
- *(int **)(&stack0xffffffb4 + iVar1) = &PacketSize;
- }
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc518;
- TWriteBuffer::writeBytes
- (*(TWriteBuffer **)(&stack0xffffffb4 + iVar1),*(uchar **)((int)auStack_48 + iVar1)
- ,*(int *)((int)auStack_48 + iVar1 + 4));
- *(int *)((int)auStack_48 + iVar1 + 4) = local_30;
- puVar2 = local_38;
- *(TConnection **)(&stack0xffffffb4 + iVar1) = Connection;
- *(undefined1 **)((int)auStack_48 + iVar1) = puVar2;
- *(undefined4 *)((int)&uStackY_50 + iVar1) = 0x80dc52e;
- bVar3 = WriteToSocket(*(TConnection **)(&stack0xffffffb4 + iVar1),
- *(uchar **)((int)auStack_48 + iVar1),
- *(int *)((int)auStack_48 + iVar1 + 4));
- if (bVar3) {
- Connection->NextToSend = Connection->NextToSend + uVar6;
- return true;
- }
- }
- return false;
-}
-
-
-
-bool SendData(TConnection *Connection,char *Data,int Size)
-
-{
- bool bVar1;
-
- if (Connection == (TConnection *)0x0) {
- error("SendData: Connection ist NULL.\n");
- bVar1 = false;
- }
- else if (Data == (char *)0x0) {
- error("SendData: Data ist NULL.\n");
- bVar1 = true;
- }
- else {
- memcpy(Connection->OutData + 2,Data,Size);
- Connection->NextToSend = 0;
- Connection->NextToCommit = Size + 2;
- bVar1 = SendData(Connection);
- }
- return bVar1;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-bool GetWaitinglistEntry(void)
-
-{
- TWaitinglistEntry *pTVar1;
- int iVar2;
- TWaitinglistEntry *Search;
- char *in_stack_00000004;
- ulong *in_stack_00000008;
- undefined1 *in_stack_0000000c;
- undefined1 *in_stack_00000010;
-
- Semaphore::down(&CommunicationThreadMutex);
- pTVar1 = WaitinglistHead;
- while( true ) {
- if (pTVar1 == (TWaitinglistEntry *)0x0) {
- Semaphore::up(&CommunicationThreadMutex);
- return false;
- }
- iVar2 = stricmp(in_stack_00000004,pTVar1->Name,-1);
- if (iVar2 == 0) break;
- pTVar1 = pTVar1->Next;
- }
- *in_stack_00000008 = pTVar1->NextTry;
- *in_stack_0000000c = pTVar1->FreeAccount;
- *in_stack_00000010 = pTVar1->Newbie;
- Semaphore::up(&CommunicationThreadMutex);
- return true;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: NextTry_local
-
-void InsertWaitinglistEntry(char *Name,ulong NextTry,bool FreeAccount,bool Newbie)
-
-{
- TWaitinglistEntry *pTVar1;
- int iVar2;
- TWaitinglistEntry *pTVar3;
- TWaitinglistEntry *Entry;
- TWaitinglistEntry *Last;
- bool Newbie_local;
- bool FreeAccount_local;
- ulong NextTry_local;
-
- Semaphore::down(&CommunicationThreadMutex);
- pTVar1 = (TWaitinglistEntry *)0x0;
- pTVar3 = WaitinglistHead;
- while( true ) {
- if (pTVar3 == (TWaitinglistEntry *)0x0) {
- pTVar3 = store<>::getFreeItem(&Waitinglist);
- strcpy(pTVar3->Name,Name);
- pTVar3->NextTry = NextTry;
- pTVar3->FreeAccount = FreeAccount;
- pTVar3->Next = (TWaitinglistEntry *)0x0;
- pTVar3->Sleeping = false;
- pTVar3->Newbie = Newbie;
- if (pTVar1 != (TWaitinglistEntry *)0x0) {
- pTVar1->Next = pTVar3;
- pTVar3 = WaitinglistHead;
- }
- WaitinglistHead = pTVar3;
- Semaphore::up(&CommunicationThreadMutex);
- Log("queue",&DAT_0810bc80);
- return;
- }
- iVar2 = stricmp(Name,pTVar3->Name,-1);
- if (iVar2 == 0) break;
- pTVar1 = pTVar3;
- pTVar3 = pTVar3->Next;
- }
- pTVar3->NextTry = NextTry;
- pTVar3->FreeAccount = FreeAccount;
- pTVar3->Newbie = Newbie;
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-void DeleteWaitinglistEntry(char *Name)
-
-{
- TWaitinglistEntry *pTVar1;
- TWaitinglistEntry *pTVar2;
- int iVar3;
- TWaitinglistEntry *Search;
- TWaitinglistEntry *Entry;
- storeitem<> *psVar4;
-
- psVar4 = (storeitem<> *)0x0;
- Semaphore::down(&CommunicationThreadMutex);
- if (WaitinglistHead != (TWaitinglistEntry *)0x0) {
- iVar3 = stricmp(Name,WaitinglistHead->Name,-1);
- if (iVar3 == 0) {
- psVar4 = (storeitem<> *)WaitinglistHead;
- WaitinglistHead = WaitinglistHead->Next;
- }
- else if (WaitinglistHead != (TWaitinglistEntry *)0x0) {
- pTVar1 = WaitinglistHead->Next;
- pTVar2 = WaitinglistHead;
- while (pTVar1 != (TWaitinglistEntry *)0x0) {
- iVar3 = stricmp(Name,pTVar2->Next->Name,-1);
- if (iVar3 == 0) {
- psVar4 = (storeitem<> *)pTVar2->Next;
- pTVar2->Next = (TWaitinglistEntry *)psVar4->next;
- break;
- }
- pTVar2 = pTVar2->Next;
- pTVar1 = pTVar2->Next;
- }
- }
- }
- if (psVar4 != (storeitem<> *)0x0) {
- psVar4->next = Waitinglist.firstFreeItem;
- Waitinglist.firstFreeItem = psVar4;
- }
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: Newbie_local
-
-int GetWaitinglistPosition(char *Name,bool FreeAccount,bool Newbie)
-
-{
- int iVar1;
- int iVar2;
- storeitem<> *psVar3;
- TWaitinglistEntry *Search;
- storeitem<> *psVar4;
- int PremiumVeterans;
- int iVar5;
- int PremiumNewbies;
- int iVar6;
- int local_1c;
- int FreeVeterans;
- int FreeNewbies;
- bool Newbie_local;
- bool FreeAccount_local;
-
- iVar6 = 0;
- iVar5 = 0;
- local_1c = 0;
- FreeVeterans = 0;
- Semaphore::down(&CommunicationThreadMutex);
- psVar3 = (storeitem<> *)WaitinglistHead;
- if ((WaitinglistHead != (TWaitinglistEntry *)0x0) && (WaitinglistHead->NextTry + 0x3c < RoundNr)
- ) {
- do {
- psVar4 = psVar3;
- Log("queue",&DAT_0810bcc0,(psVar4->data).Name);
- psVar3 = (storeitem<> *)WaitinglistHead->Next;
- WaitinglistHead = (TWaitinglistEntry *)psVar3;
- psVar4->next = Waitinglist.firstFreeItem;
- Waitinglist.firstFreeItem = psVar4;
- if (psVar3 == (storeitem<> *)0x0) break;
- } while ((psVar3->data).NextTry + 0x3c < RoundNr);
- }
- if (psVar3 != (storeitem<> *)0x0) {
- iVar5 = 0;
- do {
- iVar1 = stricmp(Name,(psVar3->data).Name,-1);
- if (iVar1 == 0) break;
- iVar1 = local_1c;
- if ((psVar3->data).Sleeping == false) {
- if ((psVar3->data).NextTry + 5 < RoundNr) {
- (psVar3->data).Sleeping = true;
- }
- else if ((psVar3->data).FreeAccount == false) {
- if ((psVar3->data).Newbie != false) {
- iVar6 = iVar6 + 1;
- }
- if ((psVar3->data).Newbie == false) {
- iVar5 = iVar5 + 1;
- }
- }
- else {
- iVar2 = FreeVeterans + 1;
- if ((psVar3->data).Newbie == false) {
- iVar2 = FreeVeterans;
- }
- iVar1 = local_1c + 1;
- FreeVeterans = iVar2;
- if ((psVar3->data).Newbie != false) {
- iVar1 = local_1c;
- }
- }
- }
- local_1c = iVar1;
- psVar3 = psVar3->next;
- } while (psVar3 != (storeitem<> *)0x0);
- }
- Semaphore::up(&CommunicationThreadMutex);
- if (FreeAccount) {
- if (Newbie) {
- local_1c = iVar5 + 1 + FreeVeterans + local_1c + iVar6;
- }
- else {
- local_1c = iVar5 + 1 + local_1c;
- iVar5 = GetNewbiesOnline();
- if (iVar5 < MaxNewbies - PremiumNewbieBuffer) {
- local_1c = FreeVeterans + local_1c;
- }
- }
- }
- else {
- local_1c = iVar5 + 1 + iVar6;
- if (!Newbie) {
- iVar1 = GetNewbiesOnline();
- local_1c = iVar5 + 1;
- if (iVar1 < MaxNewbies) {
- local_1c = iVar6 + iVar5 + 1;
- }
- }
- }
- return local_1c;
-}
-
-
-
-int CheckWaitingTime(char *Name,TConnection *Connection,bool FreeAccount,bool Newbie)
-
-{
- int iVar1;
- int iVar2;
- int iVar3;
- int iVar4;
- int PlayersOnline;
- int Position;
- int WaitingTime;
- int WaitingTime_00;
- int NewbiesOnline;
- char *Reason;
- char local_11c [2];
- bool Newbie_local;
- bool FreeAccount_local;
- char Message [250];
-
- WaitingTime_00 = 0;
- NewbiesOnline = 0;
- iVar1 = GetWaitinglistPosition(Name,FreeAccount,Newbie);
- iVar2 = GetPlayersOnline();
- iVar3 = GetNewbiesOnline();
- iVar4 = GetOrderBufferSpace();
- if (iVar4 - iVar1 < iVar2) {
- print();
- NewbiesOnline = (int)s_The_server_is_overloaded__0810d5ea;
- WaitingTime_00 = iVar1 / 2 + 10;
- goto LAB_080dcb50;
- }
- if (FreeAccount) {
- if (RoundNr < EarliestFreeAccountAdmissionRound) {
- print();
- NewbiesOnline = (int)s_The_server_is_overloaded__Only_p_0810bf60;
- WaitingTime_00 = iVar1 / 2 + (EarliestFreeAccountAdmissionRound - RoundNr);
- goto LAB_080dcb50;
- }
- if (iVar2 <= (MaxPlayers - PremiumPlayerBuffer) - iVar1) goto LAB_080dcaff;
- print();
- NewbiesOnline = (int)s_Too_many_players_online__Only_pl_0810bec0;
-LAB_080dcbd5:
- WaitingTime_00 = iVar1 / 2 + 5;
- }
- else {
- if (MaxPlayers - iVar1 < iVar2) {
- print();
- NewbiesOnline = (int)s_There_are_too_many_players_onlin_0810bfc0;
- }
- else {
-LAB_080dcaff:
- if (!Newbie) goto LAB_080dcb50;
- if ((FreeAccount) || (iVar3 <= MaxNewbies - iVar1)) {
- if ((!Newbie) ||
- ((!FreeAccount || (iVar3 <= (MaxNewbies - PremiumNewbieBuffer) - iVar1))))
- goto LAB_080dcb50;
- print();
- NewbiesOnline = (int)s_There_are_too_many_players_onlin_0810bd80;
- goto LAB_080dcbd5;
- }
- print();
- NewbiesOnline = (int)s_There_are_too_many_players_onlin_0810be20;
- }
- WaitingTime_00 = iVar1 / 2 + 3;
- }
-LAB_080dcb50:
- if (0xf0 < WaitingTime_00) {
- WaitingTime_00 = 0xf0;
- }
- if (0 < WaitingTime_00) {
- snprintf(local_11c,0xfa,"%s\n\nYou are at place %d on the waiting list.",NewbiesOnline,iVar1
- );
- SendLoginMessage(Connection,0x16,local_11c,WaitingTime_00);
- }
- return WaitingTime_00;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: Chances
-
-int ReadFromSocket(TConnection *Connection,uchar *Buffer,int Size)
-
-{
- int __fd;
- ssize_t sVar1;
- int *piVar2;
- int nread;
- int nleft;
- size_t __nbytes;
- int local_14;
- int Chances;
-
- local_14 = 0x32;
- __nbytes = Size;
- if (0 < Size) {
- do {
- __fd = TConnection::GetSocket(Connection);
- sVar1 = read(__fd,Buffer,__nbytes);
- if (sVar1 == 0) break;
- if (sVar1 < 0) {
- piVar2 = __errno_location();
- if (*piVar2 != 4) {
- if (*piVar2 != 0xb) {
- return sVar1;
- }
- if (__nbytes == Size) {
- return sVar1;
- }
- if (local_14 == 0) {
- return sVar1;
- }
- local_14 = local_14 + -1;
- usleep(100000);
- }
- }
- else {
- __nbytes = __nbytes - sVar1;
- Buffer = Buffer + sVar1;
- }
- } while (0 < (int)__nbytes);
- }
- return Size - __nbytes;
-}
-
-
-
-bool CallGameThread(TConnection *Connection)
-
-{
- bool bVar1;
- pid_t pVar2;
- int iVar3;
-
- bVar1 = GameRunning();
- if (bVar1) {
- Connection->WaitingForACK = true;
- pVar2 = GetGameThreadPID();
- iVar3 = kill(pVar2,10);
- if (iVar3 != 0) {
- pVar2 = GetGameThreadPID();
- error("CallGameThread: Can\'t send SIGUSR1 to pid %d\n",pVar2);
- SendLoginMessage(Connection,0x14,"The server is not online.\nPlease try again later.",-1
- );
- return false;
- }
- }
- return true;
-}
-
-
-
-bool CheckConnection(TConnection *Connection)
-
-{
- int iVar1;
- bool bVar2;
- undefined1 local_14 [4];
- pollfd Status;
-
- bVar2 = false;
- local_14 = (undefined1 [4])TConnection::GetSocket(Connection);
- Status.fd._0_2_ = 1;
- iVar1 = poll((pollfd *)local_14,1,0);
- if ((-1 < iVar1) && ((Status.fd._2_2_ & 1) == 0)) {
- bVar2 = true;
- }
- return bVar2;
-}
-
-
-
-TPlayerData *
-PerformRegistration(TConnection *Connection,char *PlayerName,ulong AccountID,char *PlayerPassword,
- bool GamemasterClient)
-
-{
- int iVar1;
- char *pcVar2;
- TPlayerData *pTVar3;
- __pid_t _Var4;
- TPlayerData *PlayerData;
- char (*__dest) [30];
- bool bVar5;
- int iVar6;
- int local_e18;
- int local_e14;
- int i;
- undefined1 auStack_e0c [2];
- bool ReadOnly;
- bool GamemasterClient_local;
- pollfd Status_1;
- ulong CharacterID;
- int Sex;
- int NumberOfBuddies;
- undefined1 auStack_df4 [3];
- bool PremiumAccountActivated;
- pollfd Status;
- char BuddyNames [100] [30];
- char local_22c [4];
- char Title [31];
- char Rank [31];
- char Guild [31];
- ulong BuddyIDs [100];
- uchar Rights [12];
- undefined1 local_2c [4];
- TQueryManagerPoolConnection QueryManagerConnection;
-
- TQueryManagerPoolConnection::TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)local_2c,&QueryManagerConnectionPool);
- if (QueryManagerConnection.QueryManagerConnectionPool == (TQueryManagerConnectionPool *)0x0) {
- pcVar2 = "PerformRegistration: Kann Verbindung zum Query-Manager nicht herstellen.\n";
-LAB_080dd3a0:
- error(pcVar2);
-switchD_080dcfed_caseD_0:
- pcVar2 = "Internal error, closing connection.";
- goto LAB_080dd020;
- }
- // try { // try from 080dceb0 to 080dd02f has its CatchHandler @ 080dd590
- _auStack_df4 = TConnection::GetSocket(Connection);
- bVar5 = false;
- Status.fd._0_2_ = 1;
- iVar6 = 1;
- iVar1 = poll((pollfd *)auStack_df4,1,0);
- if ((-1 < iVar1) && ((Status.fd._2_2_ & 1) == 0)) {
- bVar5 = true;
- }
- if (!bVar5) goto LAB_080dd030;
- print();
- BuddyIDs[99] = 0;
- pcVar2 = TConnection::GetIPAddress(Connection);
- iVar6 = TQueryManagerConnection::loginGame
- ((TQueryManagerConnection *)QueryManagerConnection.QueryManagerConnectionPool,
- AccountID,PlayerName,PlayerPassword,pcVar2,PrivateWorld,false,
- GamemasterClient,(ulong *)&Status_1.events,(int *)&CharacterID,Rank + 0x1c,
- Title + 0x1c,local_22c,&Sex,(ulong *)(Guild + 0x1c),
- (char (*) [30])&Status.events,(uchar *)(BuddyIDs + 99),
- (bool *)((int)&NumberOfBuddies + 3));
- switch(iVar6) {
- case 0:
- // try { // try from 080dd05c to 080dd11d has its CatchHandler @ 080dd590
- _auStack_e0c = TConnection::GetSocket(Connection);
- bVar5 = false;
- Status_1.fd._0_2_ = 1;
- iVar6 = poll((pollfd *)auStack_e0c,1,0);
- if ((-1 < iVar6) && ((Status_1.fd._2_2_ & 1) == 0)) {
- bVar5 = true;
- }
- if (!bVar5) {
- TQueryManagerConnection::decrementIsOnline
- ((TQueryManagerConnection *)QueryManagerConnection.QueryManagerConnectionPool,
- Status_1._4_4_);
- iVar6 = Status_1._4_4_;
- goto LAB_080dd030;
- }
- if (AccountID == 0) {
- error("PerformRegistration: Spieler %s wurde noch keinem Account zugewiesen.\n",
- PlayerName);
- pcVar2 =
- "Character is not assigned to an account.\nPerform this on the Tibia website\nat \"www.tibia.com\"."
- ;
- }
- else {
- PlayerName[0x1d] = '\0';
- if (NumberOfBuddies._3_1_ != '\0') {
- SendLoginMessage(Connection,0x15,
- "Your Premium Account is now activated.\nHave a lot of fun in Tibia."
- ,-1);
- }
- pcVar2 = TConnection::GetIPAddress(Connection);
- iVar6 = TConnection::GetSocket(Connection);
- Log("game","Spieler %s loggt ein an Socket %d von %s.\n",PlayerName,iVar6,pcVar2);
- pcVar2 = (char *)0x1;
- pTVar3 = AssignPlayerPoolSlot(Status_1._4_4_,true);
- if (pTVar3 != (TPlayerData *)0x0) {
- _Var4 = getpid();
- bVar5 = pTVar3->Locked == _Var4;
- if ((bVar5) || (pTVar3->AccountID == 0)) {
- pTVar3->Sex = CharacterID;
- pTVar3->AccountID = AccountID;
- strcpy(pTVar3->Name,PlayerName);
- *(ulong *)pTVar3->Rights = BuddyIDs[99];
- pTVar3->Rights[4] = '\0';
- pTVar3->Rights[5] = '\0';
- pTVar3->Rights[6] = '\0';
- pTVar3->Rights[7] = '\0';
- pTVar3->Rights[8] = '\0';
- pTVar3->Rights[9] = '\0';
- pTVar3->Rights[10] = '\0';
- pTVar3->Rights[0xb] = '\0';
- strcpy(pTVar3->Guild,Rank + 0x1c);
- strcpy(pTVar3->Rank,Title + 0x1c);
- pcVar2 = local_22c;
- strcpy(pTVar3->Title,pcVar2);
- }
- if ((bVar5) && (pTVar3->Buddies == 0)) {
- pTVar3->Buddies = Sex;
- local_e14 = 0;
- if (0 < Sex) {
- local_e18 = 0;
- __dest = pTVar3->BuddyName;
- do {
- pTVar3->Buddy[local_e14] = *(ulong *)(Guild + local_e14 * 4 + 0x1c);
- pcVar2 = BuddyNames[-1] + local_e18 + 0x1a;
- strcpy(*__dest,pcVar2);
- local_e14 = local_e14 + 1;
- local_e18 = local_e18 + 0x1e;
- __dest = __dest + 1;
- } while (local_e14 < Sex);
- }
- }
- TQueryManagerPoolConnection::~TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)local_2c,(int)pcVar2);
- return pTVar3;
- }
- // try { // try from 080dd27e to 080dd569 has its CatchHandler @ 080dd590
- error(&DAT_0810c160);
- TQueryManagerConnection::decrementIsOnline
- ((TQueryManagerConnection *)QueryManagerConnection.QueryManagerConnectionPool,
- Status_1._4_4_);
- pcVar2 = "There are too many players online.\nPlease try again later.";
- }
- break;
- case 1:
- print();
- pcVar2 =
- "Character doesn\'t exist.\nCreate a new character on the Tibia website\nat \"www.tibia.com\"."
- ;
- break;
- case 2:
- print();
- pcVar2 = "Character doesn\'t exist.\nCreate a new character on the Tibia website.";
- break;
- case 3:
- print();
- pcVar2 = "Character doesn\'t live on this world.\nPlease login on the right world.";
- break;
- case 4:
- print();
- pcVar2 = "This world is private and you have not been invited to play on it.";
- break;
- default:
- pcVar2 = &DAT_0810c4a0;
- goto LAB_080dd3a0;
- case 6:
- pcVar2 = TConnection::GetIPAddress(Connection);
- Log("game",&DAT_0810c500,PlayerName,pcVar2);
- goto LAB_080dd3d1;
- case 7:
- pcVar2 = TConnection::GetIPAddress(Connection);
- Log("game","Spieler %s blockiert; Login von %s.\n",PlayerName,pcVar2);
- pcVar2 = "Account disabled for five minutes. Please wait.";
- break;
- case 8:
- Log("game",&DAT_0810c600,PlayerName);
-LAB_080dd3d1:
- pcVar2 = "Accountnumber or password is not correct.";
- break;
- case 9:
- pcVar2 = TConnection::GetIPAddress(Connection);
- Log("game",&DAT_0810c640,pcVar2,PlayerName);
- pcVar2 = "IP address blocked for 30 minutes. Please wait.";
- break;
- case 10:
- print();
- pcVar2 = "Your account is banished.";
- break;
- case 0xb:
- print();
- pcVar2 = "Your character is banished because of his/her name.";
- break;
- case 0xc:
- print();
- pcVar2 = "Your IP address is banished.";
- break;
- case 0xd:
- print();
- pcVar2 = "You may only login with one character\nof your account at the same time.";
- break;
- case 0xe:
- print();
- pcVar2 = "You may only login with a Gamemaster account.";
- break;
- case 0xf:
- Log("game",&DAT_0810c860,AccountID,PlayerName);
- pcVar2 = "Login failed due to corrupt data.";
- break;
- case -1:
- goto switchD_080dcfed_caseD_0;
- }
-LAB_080dd020:
- iVar6 = 0x14;
- SendLoginMessage(Connection,0x14,pcVar2,-1);
-LAB_080dd030:
- TQueryManagerPoolConnection::~TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)local_2c,iVar6);
- return (TPlayerData *)0x0;
-}
-
-
-
-bool HandleLogin(TConnection *Connection)
-
-{
- TReadBuffer *this;
- int *ctx;
- TWaitinglistEntry *pTVar1;
- uchar Command;
- uchar uVar2;
- bool bVar3;
- bool bVar4;
- ushort w;
- ushort w_00;
- ulong AccountID_00;
- int iVar5;
- TPlayerData *Slot;
- __pid_t _Var6;
- pid_t pVar7;
- bool FreeAccount_00;
- bool Newbie_00;
- int WaitingTime;
- TPlayerData *PlayerData;
- int Type;
- TQueryManagerConnectionPool *CharacterID;
- uchar *out;
- char *pcVar8;
- size_t *in_stack_fffffed0;
- uchar *in_stack_fffffed4;
- size_t in_stack_fffffed8;
- bool ReadOnly;
- ulong AccountID;
- bool GamemasterClient;
- ushort TerminalVersion;
- ushort TerminalType;
- bool local_102;
- bool local_101;
- bool Newbie;
- ulong uStack_100;
- bool FreeAccount;
- ulong NextTry;
- TWriteBuffer WriteBuffer;
- uchar AssymmetricData [128];
- char PlayerPassword [31];
- char PlayerName [31];
- TReadBuffer Buffer;
-
- this = (TReadBuffer *)(PlayerName + 0x1c);
- TReadBuffer::TReadBuffer(this,Connection->InData + 2,0x7fe);
- // try { // try from 080dd5e8 to 080dd5ec has its CatchHandler @ 080ddd00
- uVar2 = TReadBuffer::readByte(this);
- if (uVar2 == '\n') {
- out = (uchar *)0x80;
- ctx = &WriteBuffer.Position;
- // try { // try from 080dd635 to 080dd645 has its CatchHandler @ 080ddde8
- TReadBuffer::readBytes(this,(uchar *)ctx,0x80);
- Semaphore::down(&RSAMutex);
- // try { // try from 080dd651 to 080dd655 has its CatchHandler @ 080ddd3f
- TRSAPrivateKey::decrypt
- (&PrivateKey,(EVP_PKEY_CTX *)ctx,out,in_stack_fffffed0,in_stack_fffffed4,
- in_stack_fffffed8);
- // try { // try from 080dd65d to 080dd74c has its CatchHandler @ 080ddde8
- Semaphore::up(&RSAMutex);
- TReadBuffer::TReadBuffer((TReadBuffer *)&NextTry,(uchar *)ctx,0x80);
- TReadBuffer::readByte((TReadBuffer *)&NextTry);
- TXTEASymmetricKey::init
- (&Connection->SymmetricKey,(EVP_PKEY_CTX *)((int)ctx + WriteBuffer.Size));
- TReadBuffer::skip((TReadBuffer *)&NextTry,0x10);
- w = TReadBuffer::readWord((TReadBuffer *)&NextTry);
- w_00 = TReadBuffer::readWord((TReadBuffer *)&NextTry);
- uVar2 = TReadBuffer::readByte((TReadBuffer *)&NextTry);
- AccountID_00 = TReadBuffer::readQuad((TReadBuffer *)&NextTry);
- pcVar8 = PlayerPassword + 0x1c;
- TReadStream::readString((TReadStream *)&NextTry,pcVar8,0x1e);
- TReadStream::readString((TReadStream *)&NextTry,(char *)(AssymmetricData + 0x7c),0x1e);
- if (PlayerPassword[0x1c] == '\0') {
- pcVar8 = "You must enter a character name.";
- }
- else if ((w < 3) && (TERMINALVERSION[w] <= (int)(uint)w_00)) {
- bVar3 = GameRunning();
- if (bVar3) {
- bVar3 = GameStarting();
- if (bVar3) {
- pcVar8 = "The game is just starting.\nPlease try again later.";
- }
- else {
- bVar3 = GameEnding();
- if (!bVar3) {
- do {
- Semaphore::down(&CommunicationThreadMutex);
- for (pTVar1 = WaitinglistHead; pTVar1 != (TWaitinglistEntry *)0x0;
- pTVar1 = pTVar1->Next) {
- iVar5 = stricmp(pcVar8,pTVar1->Name,-1);
- if (iVar5 == 0) {
- uStack_100 = pTVar1->NextTry;
- local_101 = pTVar1->FreeAccount;
- local_102 = pTVar1->Newbie;
- Semaphore::up(&CommunicationThreadMutex);
- bVar3 = true;
- goto LAB_080dd827;
- }
- }
- Semaphore::up(&CommunicationThreadMutex);
- bVar3 = false;
-LAB_080dd827:
- if (!bVar3) {
- print();
- Slot = PerformRegistration(Connection,pcVar8,AccountID_00,
- (char *)(AssymmetricData + 0x7c),
- uVar2 != '\0');
- if (Slot == (TPlayerData *)0x0) {
- return false;
- }
- _Var6 = getpid();
- bVar3 = Slot->Locked != _Var6;
- FreeAccount_00 = (bool)((Slot->Rights[0] ^ 1) & 1);
- Newbie_00 = false;
- if ((Slot->Profession == 0) && ((Slot->Rights[8] & 0x40) == 0)) {
- Newbie_00 = true;
- }
- if ((FreeAccount_00 == false) ||
- ((MaxPlayers != PremiumPlayerBuffer &&
- ((Newbie_00 == false || (MaxNewbies != PremiumNewbieBuffer))))))
- {
- bVar4 = IsPlayerOnline(pcVar8);
- if ((bVar4) ||
- (iVar5 = CheckWaitingTime(pcVar8,Connection,FreeAccount_00,
- Newbie_00), iVar5 < 1))
- goto LAB_080dd929;
- InsertWaitinglistEntry
- (pcVar8,iVar5 + RoundNr,FreeAccount_00,Newbie_00);
- CharacterID = &QueryManagerConnectionPool;
- TQueryManagerPoolConnection::TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)&NextTry,
- &QueryManagerConnectionPool);
- if (WriteBuffer.super_TWriteStream._vptr_TWriteStream ==
- (_func_int_varargs **)0x0) {
- error(
- "HandleLogin: Kann Verbindung zum Query-Manager nicht herstellen.\n"
- );
- }
- else {
- CharacterID = (TQueryManagerConnectionPool *)
- Slot->CharacterID;
- // try { // try from 080ddbdb to 080ddbf0 has its CatchHandler @ 080dde0a
- TQueryManagerConnection::decrementIsOnline
- ((TQueryManagerConnection *)
- WriteBuffer.super_TWriteStream._vptr_TWriteStream
- ,(ulong)CharacterID);
- }
- if (bVar3) {
- DecreasePlayerPoolSlotSticky(Slot);
- }
- else {
- // try { // try from 080ddc07 to 080ddc19 has its CatchHandler @ 080dde0a
- ReleasePlayerPoolSlot(Slot);
- }
- }
- else {
- SendLoginMessage(Connection,0x14,
-
- "Only players with premium accounts\nare allowed to enter this world."
- ,-1);
- CharacterID = &QueryManagerConnectionPool;
- TQueryManagerPoolConnection::TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)&NextTry,
- &QueryManagerConnectionPool);
- if (WriteBuffer.super_TWriteStream._vptr_TWriteStream ==
- (_func_int_varargs **)0x0) {
- error(
- "HandleLogin: Kann Verbindung zum Query-Manager nicht herstellen.\n"
- );
- }
- else {
- CharacterID = (TQueryManagerConnectionPool *)
- Slot->CharacterID;
- // try { // try from 080ddc65 to 080ddc9e has its CatchHandler @ 080dddf5
- TQueryManagerConnection::decrementIsOnline
- ((TQueryManagerConnection *)
- WriteBuffer.super_TWriteStream._vptr_TWriteStream
- ,(ulong)CharacterID);
- }
- if (bVar3) {
- DecreasePlayerPoolSlotSticky(Slot);
- }
- else {
- ReleasePlayerPoolSlot(Slot);
- }
- }
- TQueryManagerPoolConnection::~TQueryManagerPoolConnection
- ((TQueryManagerPoolConnection *)&NextTry,(int)CharacterID)
- ;
- return false;
- }
- print();
- iVar5 = uStack_100 - RoundNr;
- if (0 < iVar5) {
- Log("queue",&DAT_0810ca20,pcVar8,iVar5);
- pcVar8 = "It\'s not your turn yet.";
- Type = 0x16;
- goto LAB_080dd78e;
- }
- if (-0x3d < iVar5) {
- iVar5 = CheckWaitingTime(pcVar8,Connection,local_101,local_102);
- if (0 < iVar5) {
- InsertWaitinglistEntry
- (pcVar8,iVar5 + RoundNr,local_101,local_102);
- return false;
- }
- DeleteWaitinglistEntry(pcVar8);
- Slot = PerformRegistration(Connection,pcVar8,AccountID_00,
- (char *)(AssymmetricData + 0x7c),
- uVar2 != '\0');
- if (Slot == (TPlayerData *)0x0) {
- return false;
- }
- _Var6 = getpid();
- bVar3 = Slot->Locked != _Var6;
-LAB_080dd929:
- if (!bVar3) {
- IncreasePlayerPoolSlotSticky(Slot);
- ReleasePlayerPoolSlot(Slot);
- }
- TWriteBuffer::TWriteBuffer
- ((TWriteBuffer *)&NextTry,Connection->InData + 2,0x7fe);
- // try { // try from 080dd967 to 080dd9af has its CatchHandler @ 080dde17
- TWriteBuffer::writeByte((TWriteBuffer *)&NextTry,'\v');
- TWriteBuffer::writeWord((TWriteBuffer *)&NextTry,w);
- TWriteBuffer::writeWord((TWriteBuffer *)&NextTry,w_00);
- TWriteBuffer::writeQuad((TWriteBuffer *)&NextTry,Slot->CharacterID);
- Connection->NextToSend = 0;
- Connection->NextToCommit = 0;
- Connection->InDataSize = WriteBuffer.Size;
- Connection->NextToWrite = 0;
- TConnection::Login(Connection);
- bVar3 = GameRunning();
- if (bVar3) {
- Connection->WaitingForACK = true;
- pVar7 = GetGameThreadPID();
- iVar5 = kill(pVar7,10);
- if (iVar5 != 0) {
- pVar7 = GetGameThreadPID();
- error("CallGameThread: Can\'t send SIGUSR1 to pid %d\n",
- pVar7);
- SendLoginMessage(Connection,0x14,
-
- "The server is not online.\nPlease try again later."
- ,-1);
- return false;
- }
- }
- return true;
- }
- Log("queue",&DAT_0810c9e0,pcVar8,-iVar5);
- DeleteWaitinglistEntry(pcVar8);
- } while( true );
- }
- pcVar8 = "The game is just going down.\nPlease try again later.";
- }
- }
- else {
- pcVar8 = "The server is not online.\nPlease try again later.";
- }
- }
- else {
- pcVar8 =
- "Your terminal version is too old.\nPlease get a new version at\nhttp://www.tibia.com.";
- }
- iVar5 = -1;
- Type = 0x14;
-LAB_080dd78e:
- SendLoginMessage(Connection,Type,pcVar8,iVar5);
- }
- else {
- print();
- }
- return false;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: Help
-
-bool ReceiveCommand(TConnection *Connection)
-
-{
- uint uVar1;
- bool bVar2;
- int n;
- int iVar3;
- uchar *puVar4;
- EVP_PKEY_CTX *ctx;
- pid_t pVar5;
- ssize_t sVar6;
- int Offset;
- int Count;
- uchar *Size;
- uchar *out;
- size_t *in_stack_ffffffe0;
- uchar *in_stack_ffffffe4;
- size_t in_stack_ffffffe8;
- byte local_12;
- byte local_11;
- uchar Help [2];
-
- if (Connection == (TConnection *)0x0) {
- error("ReceiveCommand: Connection ist NULL.\n");
-LAB_080de0d3:
- bVar2 = false;
- }
- else {
- do {
- do {
- while( true ) {
- iVar3 = ReadFromSocket(Connection,&local_12,2);
- if (iVar3 == 0) {
- return false;
- }
- if (iVar3 < 0) {
- return true;
- }
- if (iVar3 < 2) {
- Semaphore::down(&CommunicationThreadMutex);
- TotalRecv = TotalRecv + 0x31;
- Semaphore::up(&CommunicationThreadMutex);
- TConnection::GetSocket(Connection);
- print();
- goto LAB_080de003;
- }
- Size = (uchar *)((uint)local_11 * 0x100 + (uint)local_12);
- Connection->InDataSize = (int)Size;
- if (((uchar *)0x7fd < Size) || (Size == (uchar *)0x0)) {
- TConnection::GetSocket(Connection);
- print();
- if (Size != (uchar *)0x0) {
- do {
- Size = Size + -0x800;
- iVar3 = TConnection::GetSocket(Connection);
- sVar6 = read(iVar3,Connection,0x800);
- Semaphore::down(&CommunicationThreadMutex);
- TotalRecv = TotalRecv + sVar6 + 0x30;
- Semaphore::up(&CommunicationThreadMutex);
- } while (0 < (int)Size);
- }
- goto LAB_080de003;
- }
- if (Connection->State == CONNECTION_CONNECTED) break;
- out = Size;
- puVar4 = (uchar *)ReadFromSocket(Connection,Connection->InData,(int)Size);
- if ((int)puVar4 < 0) {
-LAB_080de0b0:
- SendLoginMessage(Connection,0x14,"Internal error, closing connection.",-1);
- goto LAB_080de0d3;
- }
- Semaphore::down(&CommunicationThreadMutex);
- TotalRecv = (int)puVar4 + TotalRecv + 0x30;
- Semaphore::up(&CommunicationThreadMutex);
- if (puVar4 == Size) {
- if (((uint)Size & 7) == 0) {
- iVar3 = 0;
- if (Size != (uchar *)0x0) {
- do {
- ctx = (EVP_PKEY_CTX *)(Connection->InData + iVar3);
- iVar3 = iVar3 + 8;
- TXTEASymmetricKey::decrypt
- (&Connection->SymmetricKey,ctx,out,in_stack_ffffffe0,
- in_stack_ffffffe4,in_stack_ffffffe8);
- } while (iVar3 < (int)Size);
- }
- uVar1 = (uint)Connection->InData[1] * 0x100 +
- (uint)Connection->InData[0];
- Connection->InDataSize = uVar1;
- if ((uVar1 < 0x7fe) && (uVar1 != 0)) goto LAB_080ddfc8;
- in_stack_ffffffe0 = (size_t *)TConnection::GetSocket(Connection);
- }
- else {
- in_stack_ffffffe0 = (size_t *)TConnection::GetName(Connection);
- }
- print();
- }
- }
- iVar3 = ReadFromSocket(Connection,Connection->InData + 2,(int)Size);
- if (iVar3 < 0) goto LAB_080de0b0;
- Semaphore::down(&CommunicationThreadMutex);
- TotalRecv = TotalRecv + iVar3 + 0x30;
- Semaphore::up(&CommunicationThreadMutex);
- } while (iVar3 != Connection->InDataSize);
-LAB_080ddfc8:
- if (Connection->State == CONNECTION_CONNECTED) {
- alarm(0);
- bVar2 = HandleLogin(Connection);
- }
- else {
- bVar2 = GameRunning();
- if (bVar2) {
- Connection->WaitingForACK = true;
- pVar5 = GetGameThreadPID();
- iVar3 = kill(pVar5,10);
- if (iVar3 != 0) {
- pVar5 = GetGameThreadPID();
- error("CallGameThread: Can\'t send SIGUSR1 to pid %d\n",pVar5);
- in_stack_ffffffe0 = (size_t *)0xffffffff;
- SendLoginMessage(Connection,0x14,
- "The server is not online.\nPlease try again later.",-1);
- bVar2 = false;
- goto LAB_080ddfe6;
- }
- }
- bVar2 = true;
- }
-LAB_080ddfe6:
- if (bVar2 == false) {
- return false;
- }
- } while (Connection->WaitingForACK == false);
- Connection->SigIOPending = true;
-LAB_080de003:
- bVar2 = true;
- }
- return bVar2;
-}
-
-
-
// WARNING: Unknown calling convention -- yet parameter storage is locked
void IncrementActiveConnections(void)
diff --git a/src/communication.cc b/src/communication.cc
index dc28566..d3a5e2c 100644
--- a/src/communication.cc
+++ b/src/communication.cc
@@ -8,12 +8,19 @@
#include "stubs.hh"
+#include <poll.h>
#include <signal.h>
+// NOTE(fusion): We seem to add this value of 48 every time `NetLoad` is called,
+// and I assume it is to account for IPv4 (20 bytes) and TCP (20 bytes) headers
+// although there are still 8 bytes on the table that I'm not sure where are
+// comming from.
+#define PACKET_AVERAGE_SIZE_OVERHEAD 48
+
#define MAX_COMMUNICATION_THREADS 1100
#define THREAD_OWN_STACK_SIZE ((int)KB(64))
-static int TERMINAL_VERSION[3] = {770, 770, 770};
+static int TERMINALVERSION[3] = {770, 770, 770};
static int TCPSocket;
static ThreadHandle AcceptorThread;
static pid_t AcceptorThreadPID;
@@ -202,7 +209,7 @@ void NetLoadCheck(void){
// coincidence.
int FreeAccountAdmissionDelay = 60;
if(PremiumPlayerBuffer != 0){
- FreeAccountAdmissionDelay = (PlayersOnline + PremiumPlayerBuffer * 2 - MaxPlayers);
+ FreeAccountAdmissionDelay = (PlayersOnline - (MaxPlayers - PremiumPlayerBuffer * 2));
FreeAccountAdmissionDelay = (FreeAccountAdmissionDelay * 30) / PremiumPlayerBuffer;
if(FreeAccountAdmissionDelay < 0){
FreeAccountAdmissionDelay = 0;
@@ -225,12 +232,13 @@ void NetLoadCheck(void){
}
}
-// Communication Handling
+// Connection Output
// =============================================================================
bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size){
- // TODO(fusion): I think `Size` refers to the payload but `Buffer` also has
- // room for writing the packet's length at the beginning and enough room for
- // padding (supposedly).
+ // IMPORTANT(fusion): The caller must ensure `Buffer` have two extra bytes
+ // at the beginning and enough room at the end to properly add padding for
+ // XTEA encryption when needed. `Size` refers to the payload size which
+ // starts after the first two extra bytes. See note in `SendData`.
while((Size % 8) != 0){
Buffer[Size + 2] = rand_r(&Connection->RandomSeed);
@@ -253,15 +261,11 @@ bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size){
BytesToWrite -= ret;
WritePtr += ret;
}else if(ret == 0){
- // TODO(fusion): Can this even happen?
+ // TODO(fusion): Can this even happen without an error?
error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n",
errno, Connection->GetSocket());
return false;
- }else{
- if(errno == EINTR){
- continue;
- }
-
+ }else if(errno != EINTR){
if(errno != EAGAIN || Attempts <= 0){
if(errno == ECONNRESET || errno == EPIPE || errno == EAGAIN){
Log("game", "Verbindung an Socket %d zusammengebrochen.\n",
@@ -278,18 +282,18 @@ bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size){
}
}
- // TODO(fusion): Do we add 50 extra bytes to account for TCP segment headers?
- // This does make sense If we assume packets are split into ~2.5 segments on
- // average, with each segment header being 20 bytes.
- NetLoad(Size + 50, true);
+ NetLoad(PACKET_AVERAGE_SIZE_OVERHEAD + Size + 2, true);
return true;
}
-bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int WaitingTime){
+bool SendLoginMessage(TConnection *Connection, int Type, const char *Message, int WaitingTime){
+ // TODO(fusion): Why do we return true on invalid parameters?
+
// TODO(fusion):
// LOGIN_MESSAGE_ERROR = 20
// LOGIN_MESSAGE_? = 21
// LOGIN_MESSAGE_WAITING_LIST = 22
+
if(Type != 20 && Type != 21 && Type != 22){
error("SendLoginMessage: Ungültiger Meldungstyp %d.\n", Type);
return true;
@@ -310,15 +314,9 @@ bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int Wait
return true;
}
- // TODO(fusion): Writing output messages should have more robust helpers
- // to avoid all sorts of memory bugs but since we're only doing it in two
- // places from what I've seen, I'm not actually gonna bother (for now at
- // least).
-
- // NOTE(fusion): We make sure we leave two extra bytes at the beginning so
- // `WriteToSocket` can write the packet size. We also make sure that the
- // remainder of the buffer has a size that is multiple of 8 so `WriteToSocket`
- // can add any necessary padding for XTEA encryption without overflowing it.
+ // IMPORTANT(fusion): This is doing the same thing as `SendData` but in a
+ // smaller scale and building the packet directly instead of using the
+ // connection's write buffer.
uint8 Data[302]; // 2 + 300
TWriteBuffer WriteBuffer(Data + 2, sizeof(Data) - 2);
WriteBuffer.writeWord(0);
@@ -333,3 +331,878 @@ bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int Wait
WriteBuffer.writeWord((uint16)(Size - 2));
return WriteToSocket(Connection, Data, Size);
}
+
+bool SendData(TConnection *Connection){
+ if(Connection == NULL){
+ error("SendData: Verbindung ist NULL.\n");
+ return false;
+ }
+
+ //
+ // IMPORTANT(fusion): The final packet will have this layout:
+ // PLAIN:
+ // 0 .. 2 => Encrypted Size
+ // ENCRYPTED:
+ // 2 .. 4 => Data Size
+ // 4 .. => Data + Padding
+ //
+ // The ENCRYPTED size needs to be a multiple of 8 for XTEA encryption and
+ // `WriteToSocket` will add padding to ensure it is. This also requires us
+ // to ensure the passed buffer has enough room for it, so we don't write out
+ // of bounds.
+ //
+
+ int DataSize = Connection->NextToCommit - Connection->NextToSend;
+ int EncryptedSize = (DataSize + 2);
+ int EncryptedSizeAligned = (EncryptedSize + 7) & ~7;
+ int PacketSize = EncryptedSizeAligned + 2;
+
+ // TODO(fusion): The maximum size of a packet depends on the size of
+ // `Connection->OutData` and I don't think we'd have a problem with
+ // having a constant size buffer on the stack here although with such
+ // a small stack size (64KB with our own stacks) it could become a
+ // problem.
+ uint8 *Buffer = (uint8*)alloca(PacketSize);
+ TWriteBuffer WriteBuffer(Buffer, PacketSize);
+ WriteBuffer.writeWord(0);
+ WriteBuffer.writeWord((uint16)DataSize);
+
+ // NOTE(fusion): `Connection->OutData` is a ring buffer so we need to check
+ // if the data we're currently sending is wrapping around, in which case we'd
+ // need to copy two separate regions instead of a single contiguous one.
+ constexpr int OutDataSize = sizeof(Connection->OutData);
+ int DataStart = Connection->NextToSend % OutDataSize;
+ int DataEnd = DataStart + DataSize;
+ if(DataEnd < OutDataSize){
+ WriteBuffer.writeBytes(&Connection->OutData[DataStart], DataSize);
+ }else{
+ WriteBuffer.writeBytes(&Connection->OutData[DataStart], OutDataSize - DataStart);
+ WriteBuffer.writeBytes(&Connection->OutData[0], DataEnd - OutDataSize);
+ }
+
+ bool Result = WriteToSocket(Connection, Buffer, DataSize);
+ if(Result){
+ Connection->NextToSend += DataSize;
+ }
+ return Result;
+}
+
+bool SendData(TConnection *Connection, const uint8 *Data, int Size){
+ if(Connection == NULL){
+ error("SendData: Connection ist NULL.\n");
+ return false;
+ }
+
+ // TODO(fusion): Why are we returning true for invalid parameters?
+ if(Data == NULL){
+ error("SendData: Data ist NULL.\n");
+ return true;
+ }
+
+ // TODO(fusion): Why do we leave extra two bytes unassigned at the beginning?
+ // `SendData` should already take care of building the packet properly so this
+ // is something else or a bug, if this function is even used.
+ memcpy(&Connection->OutData[2], Data, Size);
+ Connection->NextToSend = 0;
+ Connection->NextToCommit = Size + 2;
+ return SendData(Connection);
+}
+
+// Waiting List
+// =============================================================================
+bool GetWaitinglistEntry(const char *Name, int *NextTry, bool *FreeAccount, bool *Newbie){
+ bool Result = false;
+ CommunicationThreadMutex.down();
+ TWaitinglistEntry *Entry = WaitinglistHead;
+ while(Entry != NULL){
+ if(stricmp(Entry->Name, Name) == 0){
+ break;
+ }
+ Entry = Entry->Next;
+ }
+
+ if(Entry != NULL){
+ *NextTry = Entry->NextTry;
+ *FreeAccount = Entry->FreeAccount;
+ *Newbie = Entry->Newbie;
+ Result = true;
+ }
+ CommunicationThreadMutex.up();
+ return Result;
+}
+
+void InsertWaitinglistEntry(const char *Name, uint32 NextTry, bool FreeAccount, bool Newbie){
+ bool NewEntry = false;
+ CommunicationThreadMutex.down();
+ TWaitinglistEntry *Prev = NULL;
+ TWaitinglistEntry *Entry = WaitinglistHead;
+ while(Entry != NULL){
+ if(stricmp(Entry->Name, Name) == 0){
+ break;
+ }
+ Prev = Entry;
+ Entry = Entry->Next;
+ }
+
+ if(Entry == NULL){
+ Entry = Waitinglist.getFreeItem();
+ Entry->Next = NULL;
+ strcpy(Entry->Name, Name);
+ Entry->NextTry = NextTry;
+ Entry->FreeAccount = FreeAccount;
+ Entry->Newbie = Newbie;
+ Entry->Sleeping = false;
+ if(Prev != NULL){
+ Prev->Next = Entry;
+ }else{
+ WaitinglistHead = Entry;
+ }
+ NewEntry = true;
+ }else{
+ Entry->NextTry = NextTry;
+ Entry->FreeAccount = FreeAccount;
+ Entry->Newbie = Newbie;
+ }
+ CommunicationThreadMutex.up();
+
+ if(NewEntry){
+ Log("queue", "Füge %s in die Warteschlange ein.\n", Name);
+ }
+}
+
+void DeleteWaitinglistEntry(const char *Name){
+ CommunicationThreadMutex.down();
+ TWaitinglistEntry *Prev = NULL;
+ TWaitinglistEntry *Entry = WaitinglistHead;
+ while(Entry != NULL){
+ if(stricmp(Entry->Name, Name) == 0){
+ break;
+ }
+ Prev = Entry;
+ Entry = Entry->Next;
+ }
+
+ if(Entry != NULL){
+ if(Prev != NULL){
+ Prev->Next = Entry->Next;
+ }else{
+ WaitinglistHead = Entry->Next;
+ }
+ Waitinglist.putFreeItem(Entry);
+ }
+ CommunicationThreadMutex.up();
+}
+
+int GetWaitinglistPosition(const char *Name, bool FreeAccount, bool Newbie){
+ int FreeNewbies = 0;
+ int FreeVeterans = 0;
+ int PremiumNewbies = 0;
+ int PremiumVeterans = 0;
+
+ CommunicationThreadMutex.down();
+ // NOTE(fusion): Remove inactive entries from the start of the list.
+ while(WaitinglistHead != NULL && RoundNr > (WaitinglistHead->NextTry + 60)){
+ TWaitinglistEntry *Next = WaitinglistHead->Next;
+ Waitinglist.putFreeItem(WaitinglistHead);
+ WaitinglistHead = Next;
+ }
+
+ // NOTE(fusion): Count players up until we find the player's entry or reach
+ // the end of the queue.
+ TWaitinglistEntry *Entry = WaitinglistHead;
+ while(Entry != NULL){
+ if(stricmp(Entry->Name, Name) == 0){
+ break;
+ }
+
+ if(!Entry->Sleeping){
+ if(RoundNr > (Entry->NextTry + 5)){
+ Entry->Sleeping = true;
+ }else if(Entry->FreeAccount){
+ if(Entry->Newbie){
+ FreeNewbies += 1;
+ }else{
+ FreeVeterans += 1;
+ }
+ }else{
+ if(Entry->Newbie){
+ PremiumNewbies += 1;
+ }else{
+ PremiumVeterans += 1;
+ }
+ }
+ }
+
+ Entry = Entry->Next;
+ }
+ CommunicationThreadMutex.up();
+
+ int Result = 1;
+ if(FreeAccount){
+ Result += PremiumVeterans + FreeVeterans;
+ if(Newbie){
+ Result += PremiumNewbies + FreeNewbies;
+ }else if(GetNewbiesOnline() < (MaxNewbies - PremiumNewbieBuffer)){
+ Result += FreeNewbies;
+ }
+ }else{
+ Result += PremiumVeterans;
+ if(Newbie || GetNewbiesOnline() < MaxNewbies){
+ Result += PremiumNewbies;
+ }
+ }
+ return Result;
+}
+
+int CheckWaitingTime(const char *Name, TConnection *Connection, bool FreeAccount, bool Newbie){
+ int WaitingTime = 0;
+ const char *Reason = NULL;
+ int Position = GetWaitinglistPosition(Name, FreeAccount, Newbie);
+ int PlayersOnline = GetPlayersOnline();
+ int NewbiesOnline = GetNewbiesOnline();
+ if((PlayersOnline + Position) > GetOrderBufferSpace()){
+ print(3, "Order-Puffer ist fast voll.\n");
+ Reason = "The server is overloaded.";
+ WaitingTime = (Position / 2) + 10;
+ }else if(FreeAccount){
+ if(EarliestFreeAccountAdmissionRound > RoundNr){
+ print(3, "Keine FreeAccounts zugelassen nach MassKick.\n");
+ Reason = "The server is overloaded.\n"
+ "Only players with premium accounts\n"
+ "are admitted at the moment.";
+ WaitingTime = (int)(EarliestFreeAccountAdmissionRound - RoundNr);
+ WaitingTime += Position / 2;
+ }else if((PlayersOnline + Position) > (MaxPlayers - PremiumPlayerBuffer)){
+ print(3, "Kein Platz mehr für FreeAccounts.\n");
+ Reason = "Too many players online.\n"
+ "Only players with premium accounts\n"
+ "are admitted at the moment.";
+ WaitingTime = Position / 2 + 5;
+ }else if(Newbie && (NewbiesOnline + Position) > (MaxNewbies - PremiumNewbieBuffer)){
+ print(3, "Kein Platz mehr für Newbies mit FreeAccount.\n");
+ Reason = "There are too many players online\n"
+ "on the beginners' island, Rookgaard.\n"
+ "Only players with premium accounts\n"
+ "are admitted at the moment.";
+ WaitingTime = Position / 2 + 5;
+ }
+ }else{
+ if((PlayersOnline + Position) > MaxPlayers){
+ print(3, "Zu viele Spieler online.\n");
+ Reason = "There are too many players online.";
+ WaitingTime = Position / 2 + 3;
+ }else if(Newbie && (NewbiesOnline + Position) > MaxNewbies){
+ print(3, "Kein Platz mehr für Newbies.\n");
+ Reason = "There are too many players online\n"
+ "on the beginners' island, Rookgaard.";
+ WaitingTime = Position / 2 + 3;
+ }
+ }
+
+ if(WaitingTime > 240){
+ WaitingTime = 240;
+ }
+
+ if(WaitingTime > 0){
+ char Message[250];
+ snprintf(Message, sizeof(Message),
+ "%s\n\nYou are at place %d on the waiting list.",
+ Reason, Position);
+ SendLoginMessage(Connection, 22, Message, WaitingTime);
+ }
+
+ return WaitingTime;
+}
+
+// Connection Input
+// =============================================================================
+int ReadFromSocket(TConnection *Connection, uint8 *Buffer, int Size){
+ int Attempts = 50;
+ int BytesToRead = Size;
+ uint8 *ReadPtr = Buffer;
+ while(BytesToRead > 0){
+ int BytesRead = (int)read(Connection->GetSocket(), ReadPtr, BytesToRead);
+ if(BytesRead > 0){
+ BytesToRead -= BytesRead;
+ ReadPtr += BytesRead;
+ }else if(BytesRead == 0){
+ // NOTE(fusion): TCP FIN with no more data to read.
+ break;
+ }else if(errno != EINTR){
+ if(errno != EAGAIN || BytesToRead == Size || Attempts <= 0){
+ return -1;
+ }
+ DelayThread(0, 100000);
+ Attempts -= 1;
+ }
+ }
+ return Size - BytesToRead;
+}
+
+bool DrainSocket(TConnection *Connection, int Size){
+ uint8 DiscardBuffer[KB(2)];
+ while(Size > 0){
+ int BytesToRead = std::min<int>(Size, sizeof(DiscardBuffer));
+ int BytesRead = ReadFromSocket(Connection, DiscardBuffer, BytesToRead);
+ if(BytesRead <= 0){
+ return false;
+ }
+ Size -= BytesRead;
+ NetLoad(PACKET_AVERAGE_SIZE_OVERHEAD + BytesRead, false);
+ }
+ return true;
+}
+
+bool CallGameThread(TConnection *Connection){
+ if(GameRunning()){
+ Connection->WaitingForACK = true;
+ if(kill(GetGameThreadPID(), SIGUSR1) != 0){
+ error("CallGameThread: Can't send SIGUSR1 to pid %d\n", GetGameThreadPID());
+ SendLoginMessage(Connection, 20,
+ "The server is not online.\nPlease try again later.", -1);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CheckConnection(TConnection *Connection){
+ // TODO(fusion): Check if there is no input data?
+ struct pollfd pollfd = {};
+ pollfd.fd = Connection->GetSocket();
+ pollfd.events = POLLIN;
+ return poll(&pollfd, 1, 0) >= 0
+ && (pollfd.revents & POLLIN) == 0;
+}
+
+// NOTE(fusion): `PlayerName` is an input and output parameter. It will contain
+// the correct player name with upper and lower case letters if the player is
+// actually logged in.
+TPlayerData *PerformRegistration(TConnection *Connection, char *PlayerName,
+ uint32 AccountID, const char *PlayerPassword, bool GamemasterClient){
+ TQueryManagerPoolConnection QueryManagerConnection(&QueryManagerConnectionPool);
+ if(!QueryManagerConnection){
+ error("PerformRegistration: Kann Verbindung zum Query-Manager nicht herstellen.\n");
+ SendLoginMessage(Connection, 20, "Internal error, closing connection.", -1);
+ return NULL;
+ }
+
+ if(!CheckConnection(Connection)){
+ return NULL;
+ }
+
+ // TODO(fusion): What a disaster. The size of these arrays are are hardcoded
+ // and should be declared constants somewhere.
+ uint32 CharacterID;
+ int Sex;
+ char Guild[31]; // MAX_NAME_LENGTH ?
+ char Rank[31]; // MAX_NAME_LENGTH ?
+ char Title[31]; // MAX_NAME_LENGTH ?
+ int NumberOfBuddies;
+ uint32 BuddyIDs[100]; // MAX_BUDDIES ?
+ char BuddyNames[100][30]; // MAX_BUDDIES, MAX_NAME_LENGTH ?
+ uint8 Rights[12]; // MAX_RIGHT_BYTES ?
+ bool PremiumAccountActivated;
+ int Status = QueryManagerConnection->loginGame(AccountID, PlayerName,
+ PlayerPassword, Connection->GetIPAddress(), PrivateWorld, false,
+ GamemasterClient, &CharacterID, &Sex, Guild, Rank, Title,
+ &NumberOfBuddies, BuddyIDs, BuddyNames, Rights,
+ &PremiumAccountActivated);
+ switch(Status){
+ case 0:{
+ // NOTE(fusion): Login successful.
+ break;
+ }
+
+ case 1:{
+ print(3, "Spieler existiert nicht.\n");
+ SendLoginMessage(Connection, 20,
+ "Character doesn't exist.\n"
+ "Create a new character on the Tibia website\n"
+ "at \"www.tibia.com\".", -1);
+ return NULL;
+ }
+
+ case 2:{
+ print(3, "Spieler wurde gelöscht.\n");
+ SendLoginMessage(Connection, 20,
+ "Character doesn't exist.\n"
+ "Create a new character on the Tibia website.", -1);
+ return NULL;
+ }
+
+ case 3:{
+ print(3, "Spieler lebt nicht auf dieser Welt.\n");
+ SendLoginMessage(Connection, 20,
+ "Character doesn't live on this world.\n"
+ "Please login on the right world.", -1);
+ return NULL;
+ }
+
+ case 4:{
+ print(3, "Spieler ist nicht eingeladen.\n");
+ SendLoginMessage(Connection, 20,
+ "This world is private and you have not been invited to play on it.", -1);
+ return NULL;
+ }
+
+ case 6:{
+ Log("game", "Falsches Paßwort für Spieler %s; Login von %s.\n",
+ PlayerName, Connection->GetIPAddress());
+ SendLoginMessage(Connection, 20,
+ "Accountnumber or password is not correct.", -1);
+ return NULL;
+ }
+
+ case 7:{
+ Log("game", "Spieler %s blockiert; Login von %s.\n",
+ PlayerName, Connection->GetIPAddress());
+ SendLoginMessage(Connection, 20,
+ "Account disabled for five minutes. Please wait.", -1);
+ return NULL;
+ }
+
+ case 8:{
+ Log("game", "Account von Spieler %s wurde gelöscht.\n", PlayerName);
+ SendLoginMessage(Connection, 20,
+ "Accountnumber or password is not correct.", -1);
+ return NULL;
+ }
+
+ case 9:{
+ Log("game", "IP-Adresse %s für Spieler %s blockiert.\n",
+ Connection->GetIPAddress(), PlayerName);
+ SendLoginMessage(Connection, 20,
+ "IP address blocked for 30 minutes. Please wait.", -1);
+ return NULL;
+ }
+
+ case 10:{
+ print(3, "Account ist verbannt.\n");
+ SendLoginMessage(Connection, 20,
+ "Your account is banished.", -1);
+ return NULL;
+ }
+
+ case 11:{
+ print(3, "Character muss umbenannt werden.\n");
+ SendLoginMessage(Connection, 20,
+ "Your character is banished because of his/her name.", -1);
+ return NULL;
+ }
+
+ case 12:{
+ print(3, "IP-Adresse ist gesperrt.\n");
+ SendLoginMessage(Connection, 20,
+ "Your IP address is banished.", -1);
+ return NULL;
+ }
+
+ case 13:{
+ print(3, "Schon andere Charaktere desselben Accounts eingeloggt.\n");
+ SendLoginMessage(Connection, 20,
+ "You may only login with one character\n"
+ "of your account at the same time.", -1);
+ return NULL;
+ }
+
+ case 14:{
+ print(3, "Login mit Gamemaster-Client auf Nicht-Gamemaster-Account.\n");
+ SendLoginMessage(Connection, 20,
+ "You may only login with a Gamemaster account.", -1);
+ return NULL;
+ }
+
+ case 15:{
+ print(3, "Falsche Accountnummer %u für %s.\n", AccountID, PlayerName);
+ SendLoginMessage(Connection, 20,
+ "Login failed due to corrupt data.", -1);
+ return NULL;
+ }
+
+ case -1:{
+ SendLoginMessage(Connection, 20, "Internal error, closing connection.", -1);
+ return NULL;
+ }
+
+ default:{
+ error("PerformRegistration: Unbekannter Rückgabewert vom QueryManager.\n");
+ SendLoginMessage(Connection, 20, "Internal error, closing connection.", -1);
+ return NULL;
+ }
+ }
+
+ // TODO(fusion): Again?
+ if(!CheckConnection(Connection)){
+ QueryManagerConnection->decrementIsOnline(CharacterID);
+ return NULL;
+ }
+
+ if(AccountID == 0){
+ error("PerformRegistration: Spieler %s wurde noch keinem Account zugewiesen.\n", PlayerName);
+ SendLoginMessage(Connection, 20,
+ "Character is not assigned to an account.\n"
+ "Perform this on the Tibia website\n"
+ "at \"www.tibia.com\".", -1);
+ return NULL;
+ }
+
+ // TODO(fusion): We were also adding a null terminator to `PlayerName` here
+ // for whatever reason. I assume it is a compiler artifact because we already
+ // use it in the condition block just above so...
+ // PlayerName[29] = 0;
+
+ if(PremiumAccountActivated){
+ SendLoginMessage(Connection, 21,
+ "Your Premium Account is now activated.\n"
+ "Have a lot of fun in Tibia.", -1);
+ }
+
+ Log("game", "Spieler %s loggt ein an Socket %d von %s.\n",
+ PlayerName, Connection->GetSocket(), Connection->GetIPAddress());
+
+ TPlayerData *PlayerData = AssignPlayerPoolSlot(CharacterID, true);
+ if(PlayerData == NULL){
+ error("PerformRegistration: Kann keinen Slot für Spielerdaten zuweisen.\n");
+ QueryManagerConnection->decrementIsOnline(CharacterID);
+ SendLoginMessage(Connection, 20,
+ "There are too many players online.\n"
+ "Please try again later.", -1);
+ return NULL;
+ }
+
+ bool Locked = PlayerData->Locked == getpid();
+
+ // TODO(fusion): How come this isn't a race condition? Perhaps these are
+ // constant and can only change when loaded from the database?
+ if(Locked || PlayerData->AccountID == 0){
+ PlayerData->AccountID = AccountID;
+ PlayerData->Sex = Sex;
+ strcpy(PlayerData->Name, PlayerName);
+ memcpy(PlayerData->Rights, Rights, sizeof(Rights));
+ strcpy(PlayerData->Guild, Guild);
+ strcpy(PlayerData->Rank, Rank);
+ strcpy(PlayerData->Title, Title);
+ }
+
+ if(Locked && PlayerData->Buddies == 0){
+ PlayerData->Buddies = NumberOfBuddies;
+ for(int i = 0; i < NumberOfBuddies; i += 1){
+ PlayerData->Buddy[i] = BuddyIDs[i];
+ strcpy(PlayerData->BuddyName[i], BuddyNames[i]);
+ }
+ }
+
+ return PlayerData;
+}
+
+bool HandleLogin(TConnection *Connection){
+ // TODO(fusion): Exception handling keeps getting crazier.
+
+ // TODO(fusion): We should probably use the size of the actual packet here.
+ TReadBuffer InputBuffer(Connection->InData + 2,
+ sizeof(Connection->InData) - 2);
+
+ try{
+ uint8 Command = InputBuffer.readByte();
+ if(Command != 10){
+ print(3, "Ungültiges Init-Kommando %d.\n", Command);
+ return false;
+ }
+ }catch(const char *str){
+ error("HandleLogin: Fehler beim Auslesen des Kommandos (%s).\n", str);
+ return false;
+ }
+
+ int TerminalType;
+ int TerminalVersion;
+ bool GamemasterClient;
+ uint32 AccountID;
+ char PlayerName[30];
+ char PlayerPassword[30];
+ try{
+ uint8 AssymmetricData[128];
+ InputBuffer.readBytes(AssymmetricData, 128);
+ RSAMutex.down();
+ try{
+ PrivateKey.decrypt(AssymmetricData);
+ }catch(const char *str){
+ RSAMutex.up();
+ error("HandleLogin: Fehler beim Entschlüsseln (%s).\n", str);
+ SendLoginMessage(Connection, 20,
+ "Login failed due to corrupt data.",-1);
+ return false;
+ }
+ RSAMutex.up();
+
+ TReadBuffer ReadBuffer(AssymmetricData, 128);
+ ReadBuffer.readByte();
+ Connection->SymmetricKey.init(&ReadBuffer);
+ TerminalType = (int)ReadBuffer.readWord();
+ TerminalVersion = (int)ReadBuffer.readWord();
+ GamemasterClient = ReadBuffer.readByte() != 0;
+ AccountID = ReadBuffer.readQuad();
+ ReadBuffer.readString(PlayerName, sizeof(PlayerName));
+ ReadBuffer.readString(PlayerPassword, sizeof(PlayerPassword));
+ }catch(const char *str){
+ print(3, "Fehler beim Auslesen der Login-Daten (%s).\n", str);
+ SendLoginMessage(Connection, 20,
+ "Login failed due to corrupt data.",-1);
+ return false;
+ }
+
+ if(PlayerName[0] == 0){
+ SendLoginMessage(Connection, 20,
+ "You must enter a character name.", -1);
+ return false;
+ }
+
+ if(TerminalType < 0 || TerminalType >= NARRAY(TERMINALVERSION)
+ || TERMINALVERSION[TerminalType] != TerminalVersion){
+ SendLoginMessage(Connection, 20,
+ "Your terminal version is too old.\n"
+ "Please get a new version at\n"
+ "http://www.tibia.com.", -1);
+ return false;
+ }
+
+ if(!GameRunning()){
+ SendLoginMessage(Connection, 20,
+ "The server is not online.\n"
+ "Please try again later.", -1);
+ return false;
+ }
+
+ if(GameStarting()){
+ SendLoginMessage(Connection, 20,
+ "The game is just starting.\n"
+ "Please try again later.", -1);
+ return false;
+ }
+
+ if(GameEnding()){
+ SendLoginMessage(Connection, 20,
+ "The game is just going down.\n"
+ "Please try again later.", -1);
+ return false;
+ }
+
+ // NOTE(fusion): Check waitlist entry.
+ bool WasInWaitingList = false;
+ while(true){
+ uint32 NextTry;
+ bool FreeAccount;
+ bool Newbie;
+ if(!GetWaitinglistEntry(PlayerName, &NextTry, &FreeAccount, &Newbie)){
+ print(3, "Spieler nicht auf Warteliste.\n");
+ break;
+ }
+
+ print(3, "Spieler auf Warteliste.\n");
+ if(NextTry > RoundNr){
+ int WaitingTime = (int)(NextTry - RoundNr);
+ Log("queue", "%s meldet sich %d Sekunden zu früh an.\n",
+ PlayerName, WaitingTime);
+ SendLoginMessage(Connection, 22,
+ "It's not your turn yet.", WaitingTime);
+ return false;
+ }
+
+ if(RoundNr > (NextTry + 60)){
+ Log("queue", "%s meldet sich %d Sekunden zu spät an.\n",
+ PlayerName, (RoundNr - NextTry));
+ DeleteWaitinglistEntry(PlayerName);
+ continue;
+ }
+
+ int WaitingTime = CheckWaitingTime(PlayerName, Connection, FreeAccount, Newbie);
+ if(WaitingTime > 0){
+ NextTry = RoundNr + (uint32)WaitingTime;
+ InsertWaitinglistEntry(PlayerName, NextTry, FreeAccount, Newbie);
+ return false;
+ }
+
+ DeleteWaitinglistEntry(PlayerName);
+ WasInWaitingList = true;
+ break;
+ }
+
+ TPlayerData *Slot = PerformRegistration(Connection,
+ PlayerName, AccountID, PlayerPassword, GamemasterClient);
+ if(Slot == NULL){
+ return false;
+ }
+
+ bool SlotLocked = Slot->Locked == getpid();
+
+ // NOTE(fusion): These checks would have been already made if the player was
+ // in the waiting list so they'd be redundant.
+ if(!WasInWaitingList){
+ bool BlockLogin = false;
+ bool FreeAccount = !CheckBit(Slot->Rights, PREMIUM_ACCOUNT);
+ bool Newbie = Slot->Profession == PROFESSION_NONE
+ && !CheckBit(Slot->Rights, NO_LOGOUT_BLOCK);
+
+ bool PremiumOnly = (MaxPlayers == PremiumPlayerBuffer)
+ || (Newbie && MaxNewbies == PremiumNewbieBuffer);
+ if(!BlockLogin && FreeAccount && PremiumOnly){
+ SendLoginMessage(Connection, 20,
+ "Only players with premium accounts\n"
+ "are allowed to enter this world.", -1);
+ BlockLogin = true;
+ }
+
+ if(!BlockLogin && !IsPlayerOnline(PlayerName)){
+ int WaitingTime = CheckWaitingTime(PlayerName, Connection, FreeAccount, Newbie);
+ if(WaitingTime > 0){
+ uint32 NextTry = RoundNr + (uint32)WaitingTime;
+ InsertWaitinglistEntry(PlayerName, NextTry, FreeAccount, Newbie);
+ BlockLogin = true;
+ }
+ }
+
+ if(BlockLogin){
+ // TODO(fusion): This is probably an inlined function.
+ TQueryManagerPoolConnection QueryManagerConnection(&QueryManagerConnectionPool);
+ if(QueryManagerConnection){
+ QueryManagerConnection->decrementIsOnline(Slot->CharacterID);
+ }else{
+ error("HandleLogin: Kann Verbindung zum Query-Manager nicht herstellen.\n");
+ }
+
+ if(SlotLocked){
+ ReleasePlayerPoolSlot(Slot);
+ }else{
+ DecreasePlayerPoolSlotSticky(Slot);
+ }
+ return false;
+ }
+ }
+
+ if(SlotLocked){
+ IncreasePlayerPoolSlotSticky(Slot);
+ ReleasePlayerPoolSlot(Slot);
+ }
+
+ // TODO(fusion): I'm not sure what the actual fuck is going on here. Do we
+ // rewrite the packet for the main thread to re-interpret it?
+ TWriteBuffer WriteBuffer(Connection->InData + 2,
+ sizeof(Connection->InData) - 2);
+ try{
+ WriteBuffer.writeByte(11);
+ WriteBuffer.writeWord((int)TerminalType);
+ WriteBuffer.writeWord((int)TerminalVersion);
+ WriteBuffer.writeQuad(Slot->CharacterID);
+ }catch(const char *str){
+ error("HandleLogin: Fehler beim Zusammenbauen des Login-Pakets (%s).\n", str);
+ SendLoginMessage(Connection, 20,
+ "Internal error, closing connection.",-1);
+ return false;
+ }
+
+ Connection->NextToSend = 0;
+ Connection->NextToCommit = 0;
+ Connection->InDataSize = WriteBuffer.Position;
+ Connection->NextToWrite = 0;
+
+ Connection->Login();
+ return CallGameThread(Connection);
+}
+
+bool ReceiveCommand(TConnection *Connection){
+ // IMPORTANT(fusion): The return value of this function is used to determine
+ // whether the connection should be closed. Returning true will maintain it
+ // open. It is a weird convention but w/e.
+
+ if(Connection == NULL){
+ error("ReceiveCommand: Connection ist NULL.\n");
+ return false;
+ }
+
+ while(!Connection->WaitingForACK){
+ uint8 Help[2];
+ int BytesRead = ReadFromSocket(Connection, Help, 2);
+ if(BytesRead == 0){
+ return false;
+ }else if(BytesRead < 0){
+ return true;
+ }else if(BytesRead == 1){
+ NetLoad(PACKET_AVERAGE_SIZE_OVERHEAD + 1, false);
+ print(3, "Zu wenig Daten an Socket %d.\n", BytesRead);
+ return true;
+ }
+
+ // TODO(fusion): Size is encoded as a little endian uint16. We should
+ // have a few helper functions to assist with buffer reading.
+ int Size = ((uint16)Help[0] | ((uint16)Help[1] << 8));
+
+ if(Size == 0 || Size >= (int)sizeof(Connection->InData)){
+ // TODO(fusion): We should definitely close the connection here.
+ // Nevertheless, the original handling of this edge case was just
+ // terrible, reading from the socket recklessly until at least `Size`
+ // bytes were discarded. I replaced it with a small helper function
+ // function `DrainSocket`.
+ print(3, "Paket an Socket %d zu groß oder leer, wird verworfen (%d Bytes)\n",
+ Connection->GetSocket(), Size);
+ return DrainSocket(Connection, Size);
+ }
+
+ if(Connection->State == CONNECTION_CONNECTED){
+ BytesRead = ReadFromSocket(Connection, &Connection->InData[2], Size);
+ if(BytesRead < 0){
+ // NOTE(fusion): The original function would try to use `SendLoginMessage`
+ // which doesn't make sense since we didn't do the encryption handshake yet.
+ return false;
+ }
+
+ NetLoad(PACKET_AVERAGE_SIZE_OVERHEAD + BytesRead, false);
+
+ alarm(0); // TODO(fusion): Not sure why.
+ if(!HandleLogin(Connection)){
+ return false;
+ }
+ }else{
+ BytesRead = ReadFromSocket(Connection, &Connection->InData[0], Size);
+ if(BytesRead < 0){
+ SendLoginMessage(Connection, 20,
+ "Internal error, closing connection.", -1);
+ return false;
+ }
+
+ NetLoad(PACKET_AVERAGE_SIZE_OVERHEAD + BytesRead, false);
+
+ // TODO(fusion): How come we don't close the connection if we didn't
+ // read the whole packet?
+ if(BytesRead != Size){
+ continue;
+ }
+
+ // TODO(fusion): Or if the packet size is invalid?
+ if((Size % 8) != 0){
+ print(3, "Ungültige Paketlänge %d für verschlüsseltes Paket von %s.\n",
+ Size, Connection->GetName());
+ continue;
+ }
+
+ for(int i = 0; i < Size; i += 8){
+ Connection->SymmetricKey.decrypt(&Connection->InData[i]);
+ }
+
+ // NOTE(fusion): Or if the plain text size is invalid?
+ int PlainSize = ((uint16)Connection->InData[0])
+ | ((uint16)Connection->InData[1] << 8);
+ if(PlainSize == 0 || PlainSize > Size){
+ print(3, "Nutzdaten (%d Bytes) von Paket an Socket %d zu groß oder leer.\n",
+ PlainSize, Connection->GetSocket());
+ continue;
+ }
+
+ if(!CallGameThread(Connection)){
+ return false;
+ }
+ }
+ }
+
+ Connection->SigIOPending = true;
+ return true;
+}
diff --git a/src/communication.hh b/src/communication.hh
index a54f704..bfeeeb4 100644
--- a/src/communication.hh
+++ b/src/communication.hh
@@ -3,6 +3,7 @@
#include "common.hh"
#include "connections.hh"
+#include "cr.hh"
struct TWaitinglistEntry {
TWaitinglistEntry *Next;
@@ -26,6 +27,22 @@ void NetLoadSummary(void);
void NetLoadCheck(void);
bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size);
-bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int WaitingTime);
+bool SendLoginMessage(TConnection *Connection, int Type, const char *Message, int WaitingTime);
+bool SendData(TConnection *Connection);
+bool SendData(TConnection *Connection, const uint8 *Data, int Size);
+
+bool GetWaitinglistEntry(const char *Name, uint32 *NextTry, bool *FreeAccount, bool *Newbie);
+void InsertWaitinglistEntry(const char *Name, uint32 NextTry, bool FreeAccount, bool Newbie);
+void DeleteWaitinglistEntry(const char *Name);
+int GetWaitinglistPosition(const char *Name, bool FreeAccount, bool Newbie);
+int CheckWaitingTime(const char *Name, TConnection *Connection, bool FreeAccount, bool Newbie);
+
+int ReadFromSocket(TConnection *Connection, uint8 *Buffer, int Size);
+bool CallGameThread(TConnection *Connection);
+bool CheckConnection(TConnection *Connection);
+TPlayerData *PerformRegistration(TConnection *Connection, char *PlayerName,
+ uint32 AccountID, const char *PlayerPassword, bool GamemasterClient);
+bool HandleLogin(TConnection *Connection);
+bool ReceiveCommand(TConnection *Connection);
#endif //TIBIA_COMMUNICATION_HH_
diff --git a/src/connections.hh b/src/connections.hh
index 0aef82b..021943b 100644
--- a/src/connections.hh
+++ b/src/connections.hh
@@ -25,6 +25,8 @@ struct TConnection {
KNOWNCREATURESTATE KnownCreature(uint32 CreatureID, bool UpdateFollows);
int GetSocket(void);
void EmergencyPing(void);
+ void Login(void);
+ const char *GetName(void);
// TODO(fusion): Maybe rename this later?
bool Live(void) const {
diff --git a/src/crypto.hh b/src/crypto.hh
index e90996c..7561ce5 100644
--- a/src/crypto.hh
+++ b/src/crypto.hh
@@ -6,28 +6,28 @@
// TODO(fusion): We might want to scrap this implementation and use OpenSSL instead.
struct vlong_flex_unit{
- uint32 n;
- uint32 *a;
- uint32 z;
+ uint32 n;
+ uint32 *a;
+ uint32 z;
};
struct vlong_value: vlong_flex_unit {
- uint32 share;
+ uint32 share;
};
struct vlong{
- vlong_value *value;
- int negative;
+ vlong_value *value;
+ int negative;
};
struct vlong_montgomery{
- vlong R;
- vlong R1;
- vlong m;
- vlong n1;
- vlong T;
- vlong k;
- uint32 N;
+ vlong R;
+ vlong R1;
+ vlong m;
+ vlong n1;
+ vlong T;
+ vlong k;
+ uint32 N;
};
struct TRSAPrivateKey{
@@ -37,16 +37,17 @@ struct TRSAPrivateKey{
// DATA
// =================
- vlong m_PrimeP;
- vlong m_PrimeQ;
- vlong m_U;
- vlong m_DP;
- vlong m_DQ;
+ vlong m_PrimeP;
+ vlong m_PrimeQ;
+ vlong m_U;
+ vlong m_DP;
+ vlong m_DQ;
};
struct TXTEASymmetricKey{
TXTEASymmetricKey(void);
~TXTEASymmetricKey(void);
+ void init(TReadBuffer *Buffer);
void encrypt(uint8 *Data); // single 8 bytes block
void decrypt(uint8 *Data); // single 8 bytes block
diff --git a/src/magic.cc b/src/magic.cc
index 0b161ec..4ed1826 100644
--- a/src/magic.cc
+++ b/src/magic.cc
@@ -2566,7 +2566,7 @@ void ChangeData(TCreature *Actor, const char *Param){
GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_GREEN);
}else{
SendMessage(Actor->Connection, TALK_FAILURE_MESSAGE,
- "You can\'t change %s in this way.", GetName(Obj));
+ "You can't change %s in this way.", GetName(Obj));
}
}
}
diff --git a/src/operate.cc b/src/operate.cc
index aa061a8..e019d36 100644
--- a/src/operate.cc
+++ b/src/operate.cc
@@ -2061,7 +2061,7 @@ void Look(uint32 CreatureID, Object Obj){
strcat(Description, Temp);
}
- strcat(Description, ". It\'s an \"");
+ strcat(Description, ". It's an \"");
strcat(Description, Help);
strcat(Description, "\"-spell");
@@ -2087,7 +2087,7 @@ void Look(uint32 CreatureID, Object Obj){
// TODO(fusion): Make `GetHouseOwner` return "Nobody" when there
// is no owner name.
snprintf(Help, sizeof(Help),
- ". It belongs to house \'%s\'. %s owns this house",
+ ". It belongs to house '%s'. %s owns this house",
GetHouseName(HouseID), GetHouseOwner(HouseID));
strcat(Description, Help);
}else{
@@ -3452,7 +3452,7 @@ const char *GetChannelName(int ChannelID, uint32 CharacterID){
case 7: return "Help";
default:{
if(ChannelID > 7){
- snprintf(ChannelName, sizeof(ChannelName), "%s\'s Channel",
+ snprintf(ChannelName, sizeof(ChannelName), "%s's Channel",
Channel.at(ChannelID)->ModeratorName);
return ChannelName;
}else{
@@ -4109,7 +4109,7 @@ void JoinParty(uint32 GuestID, uint32 HostID){
Guest->JoinParty(HostID);
SendMessage(Guest->Connection, TALK_INFO_MESSAGE,
- "You have joined %s\'s party.", Host->Name);
+ "You have joined %s's party.", Host->Name);
for(int i = 0; i < P->Members; i += 1){
uint32 MemberID = *P->Member.at(i);
SendCreatureParty(Guest->Connection, MemberID);
diff --git a/src/query.hh b/src/query.hh
index 7d3c2a8..92c3828 100644
--- a/src/query.hh
+++ b/src/query.hh
@@ -13,6 +13,14 @@ struct TQueryManagerConnection{
int loadPlayers(uint32 MinimumCharacterID, int *NumberOfPlayers,
char (*Names)[30], uint32 *CharacterIDs);
+ int loginGame(uint32 AccountID, const char *PlayerName, const char *Password,
+ const char *IPAddress, bool PrivateWorld, bool PremiumAccountRequired,
+ bool GamemasterRequired, uint32 *CharacterID, int *Sex, char *Guild,
+ char *Rank, char *Title, int *NumberOfBuddies, uint32 *BuddyIDs,
+ char (*BuddyNames)[30], uint8 *Rights, bool *PremiumAccountActivated);
+ void decrementIsOnline(uint32 CharacterID);
+
+
bool isConnected(void){
return this->Socket >= 0;
}
@@ -40,6 +48,23 @@ struct TQueryManagerConnectionPool{
};
struct TQueryManagerPoolConnection{
+ TQueryManagerPoolConnection(TQueryManagerConnectionPool *Pool);
+ ~TQueryManagerPoolConnection(void);
+
+ // TODO(fusion): I don't know if this was the indended way to access this
+ // structure but it is only a small wrapper and accessing members directly
+ // would be too verbose, specially when it is also named `QueryManagerConnection`.
+
+ TQueryManagerConnection *operator->(void){
+ return this->QueryManagerConnection;
+ }
+
+ operator bool(void) const {
+ return this->QueryManagerConnection != NULL;
+ }
+
+ // DATA
+ // =================
TQueryManagerConnectionPool *QueryManagerConnectionPool;
TQueryManagerConnection *QueryManagerConnection;
};
diff --git a/src/stubs.hh b/src/stubs.hh
index 4363b0a..cc4b261 100644
--- a/src/stubs.hh
+++ b/src/stubs.hh
@@ -30,6 +30,7 @@ extern void DeleteGamemasterRequest(const char *Name);
extern void GetExitPosition(uint16 HouseID, int *x, int *y, int *z);
extern TConnection *GetFirstConnection(void);
extern TConnection *GetNextConnection(void);
+extern int GetOrderBufferSpace(void);
extern const char *GetHouseName(uint16 HouseID);
extern const char *GetHouseOwner(uint16 HouseID);
extern void InitLog(const char *ProtocolName);