diff options
| -rw-r--r-- | Makefile | 16 | ||||
| -rw-r--r-- | TODO.md | 3 | ||||
| -rw-r--r-- | reference/game.c | 406 | ||||
| -rw-r--r-- | reference/types.hh | 52 | ||||
| -rw-r--r-- | src/common.hh | 2 | ||||
| -rw-r--r-- | src/communication.cc | 335 | ||||
| -rw-r--r-- | src/communication.hh | 31 | ||||
| -rw-r--r-- | src/connections.hh (renamed from src/connection.hh) | 13 | ||||
| -rw-r--r-- | src/cr.hh | 2 | ||||
| -rw-r--r-- | src/crmain.cc | 1 | ||||
| -rw-r--r-- | src/crplayer.cc | 2 | ||||
| -rw-r--r-- | src/crypto.cc | 3 | ||||
| -rw-r--r-- | src/crypto.hh | 58 | ||||
| -rw-r--r-- | src/main.cc | 19 | ||||
| -rw-r--r-- | src/operate.cc | 13 | ||||
| -rw-r--r-- | src/query.hh | 6 | ||||
| -rw-r--r-- | src/shm.cc | 2 | ||||
| -rw-r--r-- | src/strings.cc | 2 | ||||
| -rw-r--r-- | src/stubs.hh | 5 | ||||
| -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
@@ -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 $@ $< @@ -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; @@ -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; @@ -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 |
