aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile16
-rw-r--r--TODO.md3
-rw-r--r--reference/game.c406
-rw-r--r--reference/types.hh52
-rw-r--r--src/common.hh2
-rw-r--r--src/communication.cc335
-rw-r--r--src/communication.hh31
-rw-r--r--src/connections.hh (renamed from src/connection.hh)13
-rw-r--r--src/cr.hh2
-rw-r--r--src/crmain.cc1
-rw-r--r--src/crplayer.cc2
-rw-r--r--src/crypto.cc3
-rw-r--r--src/crypto.hh58
-rw-r--r--src/main.cc19
-rw-r--r--src/operate.cc13
-rw-r--r--src/query.hh6
-rw-r--r--src/shm.cc2
-rw-r--r--src/strings.cc2
-rw-r--r--src/stubs.hh5
-rw-r--r--src/threads.cc (renamed from src/thread.cc)2
-rw-r--r--src/threads.hh (renamed from src/thread.hh)6
-rw-r--r--src/utils.cc (renamed from src/util.cc)0
22 files changed, 473 insertions, 506 deletions
diff --git a/Makefile b/Makefile
index a10d2e8..d4fb72a 100644
--- a/Makefile
+++ b/Makefile
@@ -13,11 +13,15 @@ else
CFLAGS += -O2
endif
-HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/config.hh $(SRCDIR)/connection.hh $(SRCDIR)/containers.hh $(SRCDIR)/cr.hh $(SRCDIR)/enums.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/moveuse.hh $(SRCDIR)/objects.hh $(SRCDIR)/operate.hh $(SRCDIR)/query.hh $(SRCDIR)/script.hh $(SRCDIR)/stubs.hh $(SRCDIR)/thread.hh
+HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/communication.hh $(SRCDIR)/config.hh $(SRCDIR)/connections.hh $(SRCDIR)/containers.hh $(SRCDIR)/cr.hh $(SRCDIR)/crypto.hh $(SRCDIR)/enums.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/moveuse.hh $(SRCDIR)/objects.hh $(SRCDIR)/operate.hh $(SRCDIR)/query.hh $(SRCDIR)/script.hh $(SRCDIR)/stubs.hh $(SRCDIR)/threads.hh
-$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/config.obj $(BUILDDIR)/cract.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/moveuse.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/operate.obj $(BUILDDIR)/query.obj $(BUILDDIR)/script.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/thread.obj $(BUILDDIR)/time.obj $(BUILDDIR)/util.obj
+$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/communication.obj $(BUILDDIR)/config.obj $(BUILDDIR)/cract.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/crypto.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/moveuse.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/operate.obj $(BUILDDIR)/query.obj $(BUILDDIR)/script.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/threads.obj $(BUILDDIR)/time.obj $(BUILDDIR)/utils.obj
@echo $(CC) $(CFLAGS) $(LFLAGS) -o $@ $^
+$(BUILDDIR)/communication.obj: $(SRCDIR)/communication.cc $(HEADERS)
+ @mkdir -p $(@D)
+ $(CC) -c $(CFLAGS) -o $@ $<
+
$(BUILDDIR)/config.obj: $(SRCDIR)/config.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
@@ -42,6 +46,10 @@ $(BUILDDIR)/crskill.obj: $(SRCDIR)/crskill.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
+$(BUILDDIR)/crypto.obj: $(SRCDIR)/crypto.cc $(HEADERS)
+ @mkdir -p $(@D)
+ $(CC) -c $(CFLAGS) -o $@ $<
+
$(BUILDDIR)/info.obj: $(SRCDIR)/info.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
@@ -86,7 +94,7 @@ $(BUILDDIR)/strings.obj: $(SRCDIR)/strings.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
-$(BUILDDIR)/thread.obj: $(SRCDIR)/thread.cc $(HEADERS)
+$(BUILDDIR)/threads.obj: $(SRCDIR)/threads.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
@@ -94,7 +102,7 @@ $(BUILDDIR)/time.obj: $(SRCDIR)/time.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
-$(BUILDDIR)/util.obj: $(SRCDIR)/util.cc $(HEADERS)
+$(BUILDDIR)/utils.obj: $(SRCDIR)/utils.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
diff --git a/TODO.md b/TODO.md
index eed0a40..c623139 100644
--- a/TODO.md
+++ b/TODO.md
@@ -20,7 +20,8 @@ I didn't dive into how exceptions are handled, but it seems that the ones relate
## Synchronization
I'm not sure whether synchronization is done properly. The implementation of the first few `crplayer.cc` functions left me with a taste of race conditions although integer loads on x86 are generally atomic.
-I'm also under the impression that `getpid()` is used for retrieving the current thread id, so we'll have to wait and see how things unfold outside the main thread.
+## 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`.
## 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 3711e19..acbdbfa 100644
--- a/reference/game.c
+++ b/reference/game.c
@@ -37401,412 +37401,6 @@ void __static_initialization_and_destruction_0(int __initialize_p,int __priority
return;
}
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-void InitCommunicationThreadStacks(void)
-
-{
- int i;
- int iVar1;
-
- if (UseOwnStacks) {
- memset(CommunicationThreadStacks,0xaa,0x44c0000);
- iVar1 = 0;
- do {
- LastUsingCommunicationThread[iVar1] = 0;
- FreeCommunicationThreadStacks[iVar1] = iVar1;
- iVar1 = iVar1 + 1;
- } while (iVar1 < 0x44c);
- NumberOfFreeCommunicationThreadStacks = 0x44c;
- }
- return;
-}
-
-
-
-// WARNING: Variable defined which should be unmapped: Stack_local
-
-void GetCommunicationThreadStack(int *StackNumber,void **Stack)
-
-{
- int iVar1;
- int iVar2;
- int Number;
- int i;
- int iVar3;
- bool bVar4;
- void **Stack_local;
-
- if (!UseOwnStacks) {
- error(&DAT_0810b9a0);
- *StackNumber = -1;
- *Stack = (void *)0x0;
- return;
- }
- iVar3 = 0;
- Semaphore::down(&CommunicationThreadMutex);
- *StackNumber = -1;
- bVar4 = 0 < NumberOfFreeCommunicationThreadStacks;
- *Stack = (void *)0x0;
- if (bVar4) {
- do {
- iVar1 = FreeCommunicationThreadStacks[iVar3];
- if (LastUsingCommunicationThread[iVar1] == 0) {
-LAB_080dbcd6:
- *StackNumber = iVar1;
- NumberOfFreeCommunicationThreadStacks = NumberOfFreeCommunicationThreadStacks + -1;
- *Stack = CommunicationThreadStacks + iVar1 * 0x10000;
- FreeCommunicationThreadStacks[iVar3] =
- FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks];
- break;
- }
- iVar2 = kill(LastUsingCommunicationThread[iVar1],0);
- if (iVar2 == -1) goto LAB_080dbcd6;
- iVar3 = iVar3 + 1;
- } while (iVar3 < NumberOfFreeCommunicationThreadStacks);
- }
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-void AttachCommunicationThreadStack(int StackNumber)
-
-{
- __pid_t _Var1;
-
- _Var1 = getpid();
- LastUsingCommunicationThread[StackNumber] = _Var1;
- return;
-}
-
-
-
-void ReleaseCommunicationThreadStack(int StackNumber)
-
-{
- Semaphore::down(&CommunicationThreadMutex);
- FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks] = StackNumber;
- NumberOfFreeCommunicationThreadStacks = NumberOfFreeCommunicationThreadStacks + 1;
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-void ExitCommunicationThreadStacks(void)
-
-{
- int i;
- int iVar1;
- int HighestStackAddress;
- int iVar2;
- int LowestStackAddress;
- int iVar3;
- int Stack;
- int iVar4;
-
- if (UseOwnStacks) {
- if (NumberOfFreeCommunicationThreadStacks != 0x44c) {
- error("FreeCommunicationThreadStacks: Nicht alle Stacks freigegeben.\n");
- }
- iVar2 = -1;
- iVar3 = 0x10000;
- iVar4 = 0;
- do {
- iVar1 = 0;
- do {
- if (CommunicationThreadStacks[iVar4 * 0x10000 + iVar1] != 0xaa) {
- if (iVar2 < iVar1) {
- iVar2 = iVar1;
- }
- if (iVar1 < iVar3) {
- iVar3 = iVar1;
- }
- }
- iVar1 = iVar1 + 1;
- } while (iVar1 < 0x10000);
- iVar4 = iVar4 + 1;
- } while (iVar4 < 0x44c);
- if (0x10000 - (iVar2 - iVar3) < 0x8000) {
- error("Maximale Stack-Ausdehnung: %d..%d\n",iVar3,iVar2);
- }
- }
- return;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-void InitLoadHistory(void)
-
-{
- int i;
- int iVar1;
-
- iVar1 = 0;
- do {
- LoadHistory[iVar1] = 0;
- iVar1 = iVar1 + 1;
- } while (iVar1 < 0x168);
- LoadHistoryPointer = 0;
- TotalSend = 0;
- TotalLoad = 0;
- LagEnd = 0;
- TotalRecv = 0;
- EarliestFreeAccountAdmissionRound = 0;
- InitLog("netload");
- return;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-bool LagDetected(void)
-
-{
- return RoundNr <= LagEnd;
-}
-
-
-
-void NetLoad(int Amount,bool Send)
-
-{
- Semaphore::down(&CommunicationThreadMutex);
- if (Send) {
- TotalSend = TotalSend + Amount;
- }
- else {
- TotalRecv = TotalRecv + Amount;
- }
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-void NetLoadSummary(void)
-
-{
- Semaphore::down(&CommunicationThreadMutex);
- Log("netload","gesendet: %d Bytes.\n",TotalSend);
- Log("netload","empfangen: %d Bytes.\n",TotalRecv);
- TotalSend = 0;
- TotalRecv = 0;
- Semaphore::up(&CommunicationThreadMutex);
- return;
-}
-
-
-
-// WARNING: Unknown calling convention -- yet parameter storage is locked
-
-void NetLoadCheck(void)
-
-{
- int Delay;
- int iVar1;
- TConnection *this;
- ulong *puVar2;
- TConnection *Connection;
- int iVar3;
- int DeltaRecv;
- int iVar4;
- uint local_c;
-
- iVar4 = NetLoadCheck::LastRecv;
- NetLoadCheck::LastRecv = TotalRecv;
- iVar4 = TotalRecv - iVar4;
- if (-1 < iVar4) {
- iVar1 = GetPlayersOnline();
- if (iVar1 == 0) {
- iVar4 = 0;
- }
- else {
- iVar1 = GetPlayersOnline();
- iVar4 = iVar4 / iVar1;
- }
- iVar1 = LoadHistory[LoadHistoryPointer];
- LoadHistory[LoadHistoryPointer] = iVar4;
- iVar3 = LoadHistoryPointer + 1;
- TotalLoad = (TotalLoad - iVar1) + iVar4;
- LoadHistoryPointer = 0;
- if (iVar3 != 0x168) {
- LoadHistoryPointer = iVar3;
- }
- if (((0xe0f < RoundNr) && (iVar1 = GetPlayersOnline(), 0x31 < iVar1)) &&
- (iVar4 < TotalLoad / 0x2d0)) {
- Log("game","Lag erkannt!\n");
- LagEnd = RoundNr + 0x1e;
- if (PremiumPlayerBuffer == 0) {
- iVar4 = 0x3c;
- }
- else {
- iVar4 = GetPlayersOnline();
- iVar4 = ((iVar4 - (MaxPlayers + PremiumPlayerBuffer * -2)) * 0x1e) /
- PremiumPlayerBuffer;
- }
- local_c = iVar4 + RoundNr;
- puVar2 = &EarliestFreeAccountAdmissionRound;
- if (EarliestFreeAccountAdmissionRound < iVar4 + RoundNr) {
- puVar2 = &local_c;
- }
- EarliestFreeAccountAdmissionRound = *puVar2;
- this = GetFirstConnection();
- while (this != (TConnection *)0x0) {
- if (this->State - CONNECTION_LOGIN < 4) {
- TConnection::EmergencyPing(this);
- }
- this = GetNextConnection();
- }
- }
- }
- return;
-}
-
-
-
-bool WriteToSocket(TConnection *Connection,uchar *Buffer,int Size)
-
-{
- uchar *__block;
- int iVar1;
- ssize_t sVar2;
- int *piVar3;
- int Offset;
- size_t __n;
- int in_stack_ffffffbc;
- int local_30;
- int Chances;
- TWriteBuffer WriteBuffer;
-
- if ((Size & 7U) != 0) {
- do {
- iVar1 = rand_r(&Connection->RandomSeed);
- Buffer[Size + 2] = (uchar)iVar1;
- Size = Size + 1;
- } while ((Size & 7U) != 0);
- }
- __n = Size + 2;
- iVar1 = 2;
- if (2 < (int)__n) {
- do {
- __block = Buffer + iVar1;
- iVar1 = iVar1 + 8;
- TXTEASymmetricKey::encrypt(&Connection->SymmetricKey,(char *)__block,in_stack_ffffffbc);
- } while (iVar1 < (int)__n);
- }
- TWriteBuffer::TWriteBuffer((TWriteBuffer *)&Chances,Buffer,__n);
- TWriteBuffer::writeWord((TWriteBuffer *)&Chances,(ushort)Size);
- local_30 = 0x32;
-joined_r0x080dc15b:
- if ((int)__n < 1) {
- Semaphore::down(&CommunicationThreadMutex);
- TotalSend = TotalSend + Size + 0x32U;
- Semaphore::up(&CommunicationThreadMutex);
- return true;
- }
- iVar1 = TConnection::GetSocket(Connection);
- sVar2 = write(iVar1,Buffer,__n);
- if (sVar2 == -1) goto LAB_080dc1f0;
- goto LAB_080dc182;
-LAB_080dc1f0:
- piVar3 = __errno_location();
- if (*piVar3 != 4) {
- if ((*piVar3 != 0xb) || (local_30 < 1)) {
- piVar3 = __errno_location();
- iVar1 = *piVar3;
- if (((iVar1 == 0x68) || (iVar1 == 0x20)) || (iVar1 == 0xb)) {
- iVar1 = TConnection::GetSocket(Connection);
- Log("game","Verbindung an Socket %d zusammengebrochen.\n",iVar1);
- }
- else {
-LAB_080dc182:
- if (0 < sVar2) {
- __n = __n - sVar2;
- Buffer = Buffer + sVar2;
- goto joined_r0x080dc15b;
- }
- iVar1 = TConnection::GetSocket(Connection);
- piVar3 = __errno_location();
- error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n",*piVar3,iVar1);
- }
- return false;
- }
- DelayThread(0,100000);
- local_30 = local_30 + -1;
- }
- goto joined_r0x080dc15b;
-}
-
-
-
-bool SendLoginMessage(TConnection *Connection,int Type,char *Message,int WaitingTime)
-
-{
- int Size;
- bool bVar1;
- size_t sVar2;
- char *Text;
- undefined1 local_15c [4];
- TWriteBuffer Buffer;
- uchar Data [302];
-
- if (Type - 0x14U < 3) {
- if (Message == (char *)0x0) {
- error("SendLoginMessage: Message ist NULL.\n");
- return true;
- }
- if (Type == 0x16) {
- bVar1 = false;
- if ((0 < WaitingTime) && (WaitingTime < 0x100)) {
- bVar1 = true;
- }
- if (!bVar1) {
- Text = &DAT_0810bb40;
- goto LAB_080dc3c0;
- }
- }
- // try { // try from 080dc2d0 to 080dc390 has its CatchHandler @ 080dc3f0
- TWriteBuffer::TWriteBuffer
- ((TWriteBuffer *)local_15c,(uchar *)((int)&Buffer.Position + 2),300);
- TWriteBuffer::writeWord((TWriteBuffer *)local_15c,0);
- TWriteBuffer::writeByte((TWriteBuffer *)local_15c,(uchar)Type);
- sVar2 = strlen(Message);
- if (sVar2 < 0x123) {
- TWriteStream::writeString((TWriteStream *)local_15c,Message);
- if (Type == 0x16) {
- TWriteBuffer::writeByte((TWriteBuffer *)local_15c,(uchar)WaitingTime);
- }
- Size = Buffer.Size;
- Buffer.Size = 0;
- TWriteBuffer::writeWord((TWriteBuffer *)local_15c,(short)Size - 2);
- bVar1 = WriteToSocket(Connection,(uchar *)&Buffer.Position,Size);
- return bVar1;
- }
- error("SendLoginMessage: Botschaft zu lang (%s).\n",Message);
- }
- else {
- Text = &DAT_0810bbc0;
- WaitingTime = Type;
-LAB_080dc3c0:
- error(Text,WaitingTime);
- }
- return true;
-}
-
-
-
bool SendData(TConnection *Connection)
{
diff --git a/reference/types.hh b/reference/types.hh
index ef66baf..8e749d8 100644
--- a/reference/types.hh
+++ b/reference/types.hh
@@ -1,9 +1,4 @@
-struct TXTEASymmetricKey {
- int (**_vptr.TXTEASymmetricKey)(...); // VTABLE?
- uchar m_SymmetricKey[16];
-};
-
struct TDatabasePoolConnection {
TDatabaseConnectionPool *DatabaseConnectionPool;
TDatabaseConnection *DatabaseConnection;
@@ -60,15 +55,6 @@ struct THouse {
int Help;
};
-struct TWaitinglistEntry {
- struct TWaitinglistEntry *Next;
- char Name[30];
- ulong NextTry;
- bool FreeAccount;
- bool Newbie;
- bool Sleeping;
-};
-
struct TDelayedMail {
ulong CharacterID;
int DepotNumber;
@@ -224,41 +210,3 @@ struct TReaderThreadReply {
uchar *Data;
int Size;
};
-
-// NOTE(fusion): Probably bigint structures for RSA decoding.
-struct vlong_flex_unit {
- uint n;
- uint *a;
- uint z;
-};
-
-struct vlong {
- int (**_vptr.vlong)(...);
- struct vlong_value *value;
- int negative;
-};
-
-struct vlong_value {
- struct vlong_flex_unit super_vlong_flex_unit;
- uint share;
-};
-
-struct vlong_montgomery {
- struct vlong R;
- struct vlong R1;
- struct vlong m;
- struct vlong n1;
- struct vlong T;
- struct vlong k;
- uint N;
-};
-
-// NOTE(fusion): Oh yes.
-struct TRSAPrivateKey {
- int (**_vptr.TRSAPrivateKey)(...);
- struct vlong m_PrimeP;
- struct vlong m_PrimeQ;
- struct vlong m_U;
- struct vlong m_DP;
- struct vlong m_DQ;
-};
diff --git a/src/common.hh b/src/common.hh
index 384d76e..e0bfb81 100644
--- a/src/common.hh
+++ b/src/common.hh
@@ -150,7 +150,7 @@ void GetAmbiente(int *Brightness, int *Color);
uint32 GetRoundAtTime(int Hour, int Minute);
uint32 GetRoundForNextMinute(void);
-// util.cc
+// utils.cc
// =============================================================================
typedef void TErrorFunction(const char *Text);
typedef void TPrintFunction(int Level, const char *Text);
diff --git a/src/communication.cc b/src/communication.cc
new file mode 100644
index 0000000..dc28566
--- /dev/null
+++ b/src/communication.cc
@@ -0,0 +1,335 @@
+#include "communication.hh"
+#include "config.hh"
+#include "connections.hh"
+#include "containers.hh"
+#include "crypto.hh"
+#include "query.hh"
+#include "threads.hh"
+
+#include "stubs.hh"
+
+#include <signal.h>
+
+#define MAX_COMMUNICATION_THREADS 1100
+#define THREAD_OWN_STACK_SIZE ((int)KB(64))
+
+static int TERMINAL_VERSION[3] = {770, 770, 770};
+static int TCPSocket;
+static ThreadHandle AcceptorThread;
+static pid_t AcceptorThreadPID;
+static int ActiveConnections;
+
+static Semaphore RSAMutex(1);
+static TRSAPrivateKey PrivateKey;
+
+static TQueryManagerConnectionPool QueryManagerConnectionPool(10);
+static int LoadHistory[360];
+static int LoadHistoryPointer;
+static int TotalLoad;
+static int TotalSend;
+static int TotalRecv;
+static uint32 LagEnd;
+static uint32 EarliestFreeAccountAdmissionRound;
+static store<TWaitinglistEntry, 100> Waitinglist;
+static TWaitinglistEntry *WaitinglistHead;
+
+static Semaphore CommunicationThreadMutex(1);
+static bool UseOwnStacks;
+static uint8 CommunicationThreadStacks[MAX_COMMUNICATION_THREADS][THREAD_OWN_STACK_SIZE];
+static pid_t LastUsingCommunicationThread[MAX_COMMUNICATION_THREADS];
+static int FreeCommunicationThreadStacks[MAX_COMMUNICATION_THREADS];
+static int NumberOfFreeCommunicationThreadStacks;
+
+// Communication Thread Stacks
+// =============================================================================
+void GetCommunicationThreadStack(int *StackNumber, void **Stack){
+ *StackNumber = -1;
+ *Stack = NULL;
+
+ if(!UseOwnStacks){
+ error("GetCommunicationThreadStack: Bibliothek unterstützt keine eigenen Stacks.\n");
+ return;
+ }
+
+ CommunicationThreadMutex.down();
+ for(int i = 0; i < NumberOfFreeCommunicationThreadStacks; i += 1){
+ int FreeStack = FreeCommunicationThreadStacks[i];
+ if(LastUsingCommunicationThread[FreeStack] == 0
+ || kill(LastUsingCommunicationThread[FreeStack], 0) == -1){
+ // NOTE(fusion): A little swap and pop action.
+ NumberOfFreeCommunicationThreadStacks -= 1;
+ FreeCommunicationThreadStacks[i] = FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks];
+
+ *StackNumber = FreeStack;
+ *Stack = CommunicationThreadStacks[FreeStack];
+ break;
+ }
+ }
+ CommunicationThreadMutex.up();
+}
+
+void AttachCommunicationThreadStack(int StackNumber){
+ LastUsingCommunicationThread[StackNumber] = getpid();
+}
+
+void ReleaseCommunicationThreadStack(int StackNumber){
+ CommunicationThreadMutex.down();
+ FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks] = StackNumber;
+ NumberOfFreeCommunicationThreadStacks += 1;
+ CommunicationThreadMutex.up();
+}
+
+void InitCommunicationThreadStacks(void){
+ if(UseOwnStacks){
+ memset(CommunicationThreadStacks, 0xAA, sizeof(CommunicationThreadStacks));
+ for(int i = 0; i < MAX_COMMUNICATION_THREADS; i += 1){
+ LastUsingCommunicationThread[i] = 0;
+ FreeCommunicationThreadStacks[i] = i;
+ }
+ NumberOfFreeCommunicationThreadStacks = MAX_COMMUNICATION_THREADS;
+ }
+}
+
+void ExitCommunicationThreadStacks(void){
+ if(UseOwnStacks){
+ if(NumberOfFreeCommunicationThreadStacks != MAX_COMMUNICATION_THREADS){
+ error("FreeCommunicationThreadStacks: Nicht alle Stacks freigegeben.\n");
+ }
+
+ int HighestStackAddress = -1;
+ int LowestStackAddress = THREAD_OWN_STACK_SIZE;
+ for(int i = 0; i < MAX_COMMUNICATION_THREADS; i += 1){
+ for(int Addr = 0; Addr < THREAD_OWN_STACK_SIZE; Addr += 1){
+ if(CommunicationThreadStacks[i][Addr] != 0xAA){
+ if(Addr < LowestStackAddress){
+ LowestStackAddress = Addr;
+ }
+
+ if(Addr > HighestStackAddress){
+ HighestStackAddress = Addr;
+ }
+ }
+ }
+ }
+
+ // NOTE(fusion): It seems we want to track whether the stack size is
+ // too small but I'd argue the method is not very robust.
+ if((HighestStackAddress - LowestStackAddress) > (THREAD_OWN_STACK_SIZE / 2)){
+ error("Maximale Stack-Ausdehnung: %d..%d\n", LowestStackAddress, HighestStackAddress);
+ }
+ }
+}
+
+// Load History
+// =============================================================================
+void InitLoadHistory(void){
+ for(int i = 0; i < NARRAY(LoadHistory); i += 1){
+ LoadHistory[i] = 0;
+ }
+ LoadHistoryPointer = 0;
+ TotalLoad = 0;
+ TotalSend = 0;
+ TotalRecv = 0;
+ LagEnd = 0;
+ EarliestFreeAccountAdmissionRound = 0;
+ InitLog("netload");
+}
+
+bool LagDetected(void){
+ return RoundNr <= LagEnd;
+}
+
+void NetLoad(int Amount, bool Send){
+ CommunicationThreadMutex.down();
+ if(Send){
+ TotalSend += Amount;
+ }else{
+ TotalRecv += Amount;
+ }
+ CommunicationThreadMutex.up();
+}
+
+void NetLoadSummary(void){
+ CommunicationThreadMutex.down();
+ Log("netload", "gesendet: %d Bytes.\n", TotalSend);
+ Log("netload", "empfangen: %d Bytes.\n", TotalRecv);
+ TotalSend = 0;
+ TotalRecv = 0;
+ CommunicationThreadMutex.up();
+}
+
+void NetLoadCheck(void){
+ static int LastRecv;
+
+ int DeltaRecv = TotalRecv - LastRecv;
+ LastRecv = TotalRecv;
+ if(DeltaRecv < 0){
+ return;
+ }
+
+ int DeltaRecvPerPlayer = 0;
+ int PlayersOnline = GetPlayersOnline();
+ if(PlayersOnline > 0){
+ DeltaRecvPerPlayer = DeltaRecv / PlayersOnline;
+ }
+
+ TotalLoad -= LoadHistory[LoadHistoryPointer];
+ TotalLoad += DeltaRecvPerPlayer;
+ LoadHistory[LoadHistoryPointer] = DeltaRecvPerPlayer;
+ LoadHistoryPointer += 1;
+ if(LoadHistoryPointer >= NARRAY(LoadHistory)){
+ LoadHistoryPointer = 0;
+ }
+
+ // NOTE(fusion): Running this lag check only makes sense if `LoadHistory`
+ // is filled up which won't be the case until this function executes at
+ // least as many times as there are entries in `LoadHistory`.
+ // Looking at `AdvanceGame`, we see that this function is called every
+ // 10 rounds, giving us the value of `EarliestLagCheckRound`.
+ constexpr uint32 EarliestLagCheckRound = 10 * NARRAY(LoadHistory);
+ if(RoundNr >= EarliestLagCheckRound && PlayersOnline >= 50){
+ int AvgDeltaRecvPerPlayer = (TotalLoad / NARRAY(LoadHistory));
+ if(DeltaRecvPerPlayer < (AvgDeltaRecvPerPlayer / 2)){
+ Log("game", "Lag erkannt!\n");
+ LagEnd = RoundNr + 30;
+
+ // NOTE(fusion): This formula looks weird but when we take `MaxPlayers`
+ // and `PremiumPlayerBuffer` to be 950 and 150 (taken from the config
+ // file, although their values are now loaded from the database), we
+ // get a line with negative values when the number of players online
+ // is less than 650, and exactly 60 when it is 950. Since the delay
+ // is 60 when `PremiumPlayerBuffer` is zero, I don't think this is a
+ // coincidence.
+ int FreeAccountAdmissionDelay = 60;
+ if(PremiumPlayerBuffer != 0){
+ FreeAccountAdmissionDelay = (PlayersOnline + PremiumPlayerBuffer * 2 - MaxPlayers);
+ FreeAccountAdmissionDelay = (FreeAccountAdmissionDelay * 30) / PremiumPlayerBuffer;
+ if(FreeAccountAdmissionDelay < 0){
+ FreeAccountAdmissionDelay = 0;
+ }
+ }
+
+ uint32 FreeAccountAdmissionRound = RoundNr + (uint32)FreeAccountAdmissionDelay;
+ if(EarliestFreeAccountAdmissionRound < FreeAccountAdmissionRound){
+ EarliestFreeAccountAdmissionRound = FreeAccountAdmissionRound;
+ }
+
+ TConnection *Connection = GetFirstConnection();
+ while(Connection != NULL){
+ if(Connection->Live()){
+ Connection->EmergencyPing();
+ }
+ Connection = GetNextConnection();
+ }
+ }
+ }
+}
+
+// Communication Handling
+// =============================================================================
+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).
+
+ while((Size % 8) != 0){
+ Buffer[Size + 2] = rand_r(&Connection->RandomSeed);
+ Size += 1;
+ }
+
+ for(int i = 0; i < Size; i += 8){
+ Connection->SymmetricKey.encrypt(&Buffer[i + 2]);
+ }
+
+ TWriteBuffer WriteBuffer(Buffer, 2);
+ WriteBuffer.writeWord((uint16)Size);
+
+ int Attempts = 50;
+ 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){
+ // TODO(fusion): Can this even happen?
+ error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n",
+ errno, Connection->GetSocket());
+ return false;
+ }else{
+ if(errno == EINTR){
+ continue;
+ }
+
+ if(errno != EAGAIN || Attempts <= 0){
+ if(errno == ECONNRESET || errno == EPIPE || errno == EAGAIN){
+ Log("game", "Verbindung an Socket %d zusammengebrochen.\n",
+ Connection->GetSocket());
+ }else{
+ error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n",
+ errno, Connection->GetSocket());
+ }
+ return false;
+ }
+
+ DelayThread(0, 100000);
+ Attempts -= 1;
+ }
+ }
+
+ // 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);
+ return true;
+}
+
+bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int WaitingTime){
+ // 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;
+ }
+
+ if(Message == NULL){
+ error("SendLoginMessage: Message ist NULL.\n");
+ return true;
+ }
+
+ if(Type == 22 && (WaitingTime < 0 || WaitingTime > UINT8_MAX)){
+ error("SendLoginMessage: Ungültige Wartezeit %d.\n", WaitingTime);
+ return true;
+ }
+
+ if(strlen(Message) > 290){
+ error("SendLoginMessage: Botschaft zu lang (%s).\n", Message);
+ 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.
+ uint8 Data[302]; // 2 + 300
+ TWriteBuffer WriteBuffer(Data + 2, sizeof(Data) - 2);
+ WriteBuffer.writeWord(0);
+ WriteBuffer.writeByte((uint8)Type);
+ WriteBuffer.writeString(Message);
+ if(Type == 22){
+ WriteBuffer.writeByte(WaitingTime);
+ }
+
+ int Size = WriteBuffer.Position;
+ WriteBuffer.Position = 0;
+ WriteBuffer.writeWord((uint16)(Size - 2));
+ return WriteToSocket(Connection, Data, Size);
+}
diff --git a/src/communication.hh b/src/communication.hh
new file mode 100644
index 0000000..a54f704
--- /dev/null
+++ b/src/communication.hh
@@ -0,0 +1,31 @@
+#ifndef TIBIA_COMMUNICATION_HH_
+#define TIBIA_COMMUNICATION_HH_ 1
+
+#include "common.hh"
+#include "connections.hh"
+
+struct TWaitinglistEntry {
+ TWaitinglistEntry *Next;
+ char Name[30];
+ uint32 NextTry;
+ bool FreeAccount;
+ bool Newbie;
+ bool Sleeping;
+};
+
+void GetCommunicationThreadStack(int *StackNumber, void **Stack);
+void AttachCommunicationThreadStack(int StackNumber);
+void ReleaseCommunicationThreadStack(int StackNumber);
+void InitCommunicationThreadStacks(void);
+void ExitCommunicationThreadStacks(void);
+
+void InitLoadHistory(void);
+bool LagDetected(void);
+void NetLoad(int Amount, bool Send);
+void NetLoadSummary(void);
+void NetLoadCheck(void);
+
+bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size);
+bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int WaitingTime);
+
+#endif //TIBIA_COMMUNICATION_HH_
diff --git a/src/connection.hh b/src/connections.hh
index 5e2b1ba..0aef82b 100644
--- a/src/connection.hh
+++ b/src/connections.hh
@@ -2,6 +2,7 @@
#define TIBIA_CONNECTION_HH_ 1
#include "common.hh"
+#include "crypto.hh"
#include "enums.hh"
struct TConnection;
@@ -22,6 +23,16 @@ struct TConnection {
const char *GetIPAddress(void);
void Die(void);
KNOWNCREATURESTATE KnownCreature(uint32 CreatureID, bool UpdateFollows);
+ int GetSocket(void);
+ void EmergencyPing(void);
+
+ // TODO(fusion): Maybe rename this later?
+ bool Live(void) const {
+ return this->State == CONNECTION_LOGIN
+ || this->State == CONNECTION_GAME
+ || this->State == CONNECTION_DEAD
+ || this->State == CONNECTION_LOGOUT;
+ }
// DATA
// =================
@@ -45,7 +56,7 @@ struct TConnection {
pid_t PID;
int Socket;
char IPAddress[16];
- // TXTEASymmetricKey SymmetricKey; // TODO
+ TXTEASymmetricKey SymmetricKey;
bool ConnectionIsOk;
bool ClosingIsDelayed;
uint8 field23_0x4852;
diff --git a/src/cr.hh b/src/cr.hh
index 37671b8..a971b3b 100644
--- a/src/cr.hh
+++ b/src/cr.hh
@@ -2,7 +2,7 @@
#define TIBIA_CREATURE_HH_ 1
#include "common.hh"
-#include "connection.hh"
+#include "connections.hh"
#include "containers.hh"
#include "enums.hh"
#include "map.hh"
diff --git a/src/crmain.cc b/src/crmain.cc
index a98f33e..6193515 100644
--- a/src/crmain.cc
+++ b/src/crmain.cc
@@ -1,4 +1,5 @@
#include "cr.hh"
+#include "communication.hh"
#include "config.hh"
#include "enums.hh"
#include "info.hh"
diff --git a/src/crplayer.cc b/src/crplayer.cc
index 24fcd56..5c28855 100644
--- a/src/crplayer.cc
+++ b/src/crplayer.cc
@@ -3,7 +3,7 @@
#include "info.hh"
#include "operate.hh"
#include "query.hh"
-#include "thread.hh"
+#include "threads.hh"
#include "stubs.hh"
diff --git a/src/crypto.cc b/src/crypto.cc
new file mode 100644
index 0000000..b6df866
--- /dev/null
+++ b/src/crypto.cc
@@ -0,0 +1,3 @@
+#include "crypto.hh"
+
+// TODO
diff --git a/src/crypto.hh b/src/crypto.hh
new file mode 100644
index 0000000..e90996c
--- /dev/null
+++ b/src/crypto.hh
@@ -0,0 +1,58 @@
+#ifndef TIBIA_CRYPTO_HH_
+#define TIBIA_CRYPTO_HH_ 1
+
+#include "common.hh"
+
+// TODO(fusion): We might want to scrap this implementation and use OpenSSL instead.
+
+struct vlong_flex_unit{
+ uint32 n;
+ uint32 *a;
+ uint32 z;
+};
+
+struct vlong_value: vlong_flex_unit {
+ uint32 share;
+};
+
+struct vlong{
+ vlong_value *value;
+ int negative;
+};
+
+struct vlong_montgomery{
+ vlong R;
+ vlong R1;
+ vlong m;
+ vlong n1;
+ vlong T;
+ vlong k;
+ uint32 N;
+};
+
+struct TRSAPrivateKey{
+ TRSAPrivateKey(void);
+ ~TRSAPrivateKey(void);
+ void decrypt(uint8 *Data); // single 128 bytes block
+
+ // DATA
+ // =================
+ vlong m_PrimeP;
+ vlong m_PrimeQ;
+ vlong m_U;
+ vlong m_DP;
+ vlong m_DQ;
+};
+
+struct TXTEASymmetricKey{
+ TXTEASymmetricKey(void);
+ ~TXTEASymmetricKey(void);
+ void encrypt(uint8 *Data); // single 8 bytes block
+ void decrypt(uint8 *Data); // single 8 bytes block
+
+ // DATA
+ // =================
+ uint32 m_SymmetricKey[4];
+};
+
+#endif //TIBIA_CRYPTO_HH_
diff --git a/src/main.cc b/src/main.cc
index c0180c2..8d5fcf5 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,4 +1,5 @@
#include "common.hh"
+#include "communication.hh"
#include "config.hh"
#include "info.hh"
#include "map.hh"
@@ -327,23 +328,7 @@ static void AdvanceGame(int Delay){
OldAmbiente = Brightness;
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
- // TODO(fusion): This is probably an inlined function that checks
- // whether the connection is still going. The exact decompiled condition
- // was `Connection->State - CONDITION_LOGIN < 4` but I think that's
- // just a compiler optimization that wouldn't work properly on the
- // decompiled version. That comparison in the disassembly is unsigned
- // (`JBE`) but the enum is signed which would probably generate an
- // invalid signed comparison (`JLE`).
- //
- // MOV EAX, dword ptr [Connection + Connection->State]
- // SUB EAX, 0x3
- // CMP EAX, 0x3
- // JBE ... -> SendAmbiente(Connection)
- //
- if(Connection->State == CONNECTION_LOGIN
- || Connection->State == CONNECTION_GAME
- || Connection->State == CONNECTION_DEAD
- || Connection->State == CONNECTION_LOGOUT){
+ if(Connection->Live()){
SendAmbiente(Connection);
}
Connection = GetNextConnection();
diff --git a/src/operate.cc b/src/operate.cc
index 7dc1fef..aa061a8 100644
--- a/src/operate.cc
+++ b/src/operate.cc
@@ -2393,12 +2393,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text,
uint32 StatementID = LogCommunication(CreatureID, Mode, 0, Text);
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
- // TODO(fusion): Same as `AdvanceGame`. Definitely some inlined
- // function to check whether the connection is in a valid state.
- if(Connection->State == CONNECTION_LOGIN
- || Connection->State == CONNECTION_GAME
- || Connection->State == CONNECTION_DEAD
- || Connection->State == CONNECTION_LOGOUT){
+ if(Connection->Live()){
SendTalk(Connection,
LogListener(StatementID, Connection->GetPlayer()),
Creature->Name, Mode, Text, 0);
@@ -2409,11 +2404,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text,
}else if(Mode == TALK_ANONYMOUS_BROADCAST){
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
- // TODO(fusion): Same as above.
- if(Connection->State == CONNECTION_LOGIN
- || Connection->State == CONNECTION_GAME
- || Connection->State == CONNECTION_DEAD
- || Connection->State == CONNECTION_LOGOUT){
+ if(Connection->Live()){
SendMessage(Connection, TALK_ADMIN_MESSAGE, Text);
}
diff --git a/src/query.hh b/src/query.hh
index f9cd8de..7d3c2a8 100644
--- a/src/query.hh
+++ b/src/query.hh
@@ -2,7 +2,7 @@
#define TIBIA_QUERY_HH_ 1
#include "common.hh"
-#include "thread.hh"
+#include "threads.hh"
struct TQueryManagerConnection{
TQueryManagerConnection(int QueryBufferSize);
@@ -28,6 +28,10 @@ struct TQueryManagerConnection{
};
struct TQueryManagerConnectionPool{
+ TQueryManagerConnectionPool(int Connections);
+
+ // DATA
+ // =================
int NumberOfConnections;
TQueryManagerConnection *QueryManagerConnection;
bool *QueryManagerConnectionFree;
diff --git a/src/shm.cc b/src/shm.cc
index c68ce1f..ff5f603 100644
--- a/src/shm.cc
+++ b/src/shm.cc
@@ -1,7 +1,7 @@
#include "common.hh"
#include "config.hh"
#include "enums.hh"
-#include "thread.hh"
+#include "threads.hh"
#include "stubs.hh"
diff --git a/src/strings.cc b/src/strings.cc
index aef9a81..d0bfe70 100644
--- a/src/strings.cc
+++ b/src/strings.cc
@@ -218,7 +218,7 @@ void ExitStrings(void){
// String Utility
// =============================================================================
// TODO(fusion): I'm not sure why we have these separate from the other string
-// utility defined in `util.cc`. We should probably just move them all here.
+// utility defined in `utils.cc`. We should probably just move them all here.
bool IsCountable(const char *s){
if(strncmp(s, "some ", 5) == 0){
diff --git a/src/stubs.hh b/src/stubs.hh
index 561fc95..4363b0a 100644
--- a/src/stubs.hh
+++ b/src/stubs.hh
@@ -3,7 +3,7 @@
#include "common.hh"
#include "enums.hh"
-#include "connection.hh"
+#include "connections.hh"
#include "cr.hh"
#include "magic.hh"
#include "map.hh"
@@ -36,12 +36,9 @@ extern void InitLog(const char *ProtocolName);
extern bool IsInvited(uint16 HouseID, TPlayer *Player, int TimeStamp);
extern void KickGuest(uint16 HouseID, TPlayer *Host, TPlayer *Guest);
extern void KillStatisticsOrder(int NumberOfRaces, const char *RaceNames, int *KilledPlayers, int *KilledCreatures);
-extern bool LagDetected(void);
extern void LoadSectorOrder(int SectorX, int SectorY, int SectorZ);
extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3);
extern void LogoutOrder(TPlayer *Player);
-extern void NetLoadCheck(void);
-extern void NetLoadSummary(void);
extern void PrepareHouseCleanup(void);
extern void FinishHouseCleanup(void);
extern void PlayerlistOrder(int NumberOfPlayers, char *PlayerNames, int *PlayerLevels, int *PlayerProfessions);
diff --git a/src/thread.cc b/src/threads.cc
index 95ce1a6..a7acb41 100644
--- a/src/thread.cc
+++ b/src/threads.cc
@@ -1,4 +1,4 @@
-#include "thread.hh"
+#include "threads.hh"
struct TThreadStarter {
ThreadFunction *Function;
diff --git a/src/thread.hh b/src/threads.hh
index 17e46e9..9eafe11 100644
--- a/src/thread.hh
+++ b/src/threads.hh
@@ -1,5 +1,5 @@
-#ifndef TIBIA_THREAD_HH_
-#define TIBIA_THREAD_HH_ 1
+#ifndef TIBIA_THREADS_HH_
+#define TIBIA_THREADS_HH_ 1
#include "common.hh"
#include <pthread.h>
@@ -30,4 +30,4 @@ struct Semaphore {
pthread_cond_t condition;
};
-#endif //TIBIA_THREAD_HH_
+#endif //TIBIA_THREADS_HH_
diff --git a/src/util.cc b/src/utils.cc
index 4a3523b..4a3523b 100644
--- a/src/util.cc
+++ b/src/utils.cc