From bd3a6a04601749cbde007eaabf803fb602ee2783 Mon Sep 17 00:00:00 2001 From: fusion32 Date: Thu, 19 Jun 2025 03:13:48 -0300 Subject: beginning of `communication.cc` --- Makefile | 16 +- TODO.md | 3 +- reference/game.c | 406 ----------------------------------------- reference/types.hh | 52 ------ src/common.hh | 2 +- src/communication.cc | 335 ++++++++++++++++++++++++++++++++++ src/communication.hh | 31 ++++ src/connection.hh | 67 ------- src/connections.hh | 78 ++++++++ src/cr.hh | 2 +- src/crmain.cc | 1 + src/crplayer.cc | 2 +- src/crypto.cc | 3 + src/crypto.hh | 58 ++++++ src/main.cc | 19 +- src/operate.cc | 13 +- src/query.hh | 6 +- src/shm.cc | 2 +- src/strings.cc | 2 +- src/stubs.hh | 5 +- src/thread.cc | 158 ---------------- src/thread.hh | 33 ---- src/threads.cc | 158 ++++++++++++++++ src/threads.hh | 33 ++++ src/util.cc | 501 --------------------------------------------------- src/utils.cc | 501 +++++++++++++++++++++++++++++++++++++++++++++++++++ 26 files changed, 1227 insertions(+), 1260 deletions(-) create mode 100644 src/communication.cc create mode 100644 src/communication.hh delete mode 100644 src/connection.hh create mode 100644 src/connections.hh create mode 100644 src/crypto.cc create mode 100644 src/crypto.hh delete mode 100644 src/thread.cc delete mode 100644 src/thread.hh create mode 100644 src/threads.cc create mode 100644 src/threads.hh delete mode 100644 src/util.cc create mode 100644 src/utils.cc 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 + +#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 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/connection.hh deleted file mode 100644 index 5e2b1ba..0000000 --- a/src/connection.hh +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef TIBIA_CONNECTION_HH_ -#define TIBIA_CONNECTION_HH_ 1 - -#include "common.hh" -#include "enums.hh" - -struct TConnection; -struct TPlayer; - -struct TKnownCreature { - KNOWNCREATURESTATE State; - uint32 CreatureID; - TKnownCreature *Next; - TConnection *Connection; -}; - -struct TConnection { - TPlayer *GetPlayer(void); - void Logout(int Delay, bool StopFight); - bool IsVisible(int x, int y, int z); - void EnterGame(void); - const char *GetIPAddress(void); - void Die(void); - KNOWNCREATURESTATE KnownCreature(uint32 CreatureID, bool UpdateFollows); - - // DATA - // ================= - uint8 InData[2048]; - int InDataSize; - bool SigIOPending; - bool WaitingForACK; - uint8 OutData[16384]; - uint8 field5_0x4806; - uint8 field6_0x4807; - int NextToSend; - int NextToCommit; - int NextToWrite; - bool Overflow; - bool WillingToSend; - uint8 field12_0x4816; - uint8 field13_0x4817; - TConnection *NextSendingConnection; - uint32 RandomSeed; - CONNECTIONSTATE State; - pid_t PID; - int Socket; - char IPAddress[16]; - // TXTEASymmetricKey SymmetricKey; // TODO - bool ConnectionIsOk; - bool ClosingIsDelayed; - uint8 field23_0x4852; - uint8 field24_0x4853; - uint32 TimeStamp; - uint32 TimeStampAction; - int TerminalType; - int TerminalVersion; - int TerminalOffsetX; - int TerminalOffsetY; - int TerminalWidth; - int TerminalHeight; - uint32 CharacterID; - char Name[31]; - uint8 field35_0x4897; - TKnownCreature KnownCreatureTable[150]; -}; - -#endif // TIBIA_CONNECTION_HH_ diff --git a/src/connections.hh b/src/connections.hh new file mode 100644 index 0000000..0aef82b --- /dev/null +++ b/src/connections.hh @@ -0,0 +1,78 @@ +#ifndef TIBIA_CONNECTION_HH_ +#define TIBIA_CONNECTION_HH_ 1 + +#include "common.hh" +#include "crypto.hh" +#include "enums.hh" + +struct TConnection; +struct TPlayer; + +struct TKnownCreature { + KNOWNCREATURESTATE State; + uint32 CreatureID; + TKnownCreature *Next; + TConnection *Connection; +}; + +struct TConnection { + TPlayer *GetPlayer(void); + void Logout(int Delay, bool StopFight); + bool IsVisible(int x, int y, int z); + void EnterGame(void); + 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 + // ================= + uint8 InData[2048]; + int InDataSize; + bool SigIOPending; + bool WaitingForACK; + uint8 OutData[16384]; + uint8 field5_0x4806; + uint8 field6_0x4807; + int NextToSend; + int NextToCommit; + int NextToWrite; + bool Overflow; + bool WillingToSend; + uint8 field12_0x4816; + uint8 field13_0x4817; + TConnection *NextSendingConnection; + uint32 RandomSeed; + CONNECTIONSTATE State; + pid_t PID; + int Socket; + char IPAddress[16]; + TXTEASymmetricKey SymmetricKey; + bool ConnectionIsOk; + bool ClosingIsDelayed; + uint8 field23_0x4852; + uint8 field24_0x4853; + uint32 TimeStamp; + uint32 TimeStampAction; + int TerminalType; + int TerminalVersion; + int TerminalOffsetX; + int TerminalOffsetY; + int TerminalWidth; + int TerminalHeight; + uint32 CharacterID; + char Name[31]; + uint8 field35_0x4897; + TKnownCreature KnownCreatureTable[150]; +}; + +#endif // TIBIA_CONNECTION_HH_ 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/thread.cc deleted file mode 100644 index 95ce1a6..0000000 --- a/src/thread.cc +++ /dev/null @@ -1,158 +0,0 @@ -#include "thread.hh" - -struct TThreadStarter { - ThreadFunction *Function; - void *Argument; - bool Detach; -}; - -static void *ThreadStarter(void *Pointer){ - TThreadStarter *Starter = (TThreadStarter*)Pointer; - ThreadFunction *Function = Starter->Function; - void *Argument = Starter->Argument; - bool Detach = Starter->Detach; - delete Starter; - - int Result = Function(Argument); - - // TODO(fusion): Just store the integer as a pointer and avoid allocation? - int *ResultPointer = NULL; - if(!Detach){ - ResultPointer = new int; - *ResultPointer = Result; - } - pthread_exit(ResultPointer); - - return NULL; // Unreachable. -} - -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach){ - TThreadStarter *Starter = new TThreadStarter; - Starter->Function = Function; - Starter->Argument = Argument; - Starter->Detach = Detach; - - pthread_t Handle; - int err = pthread_create(&Handle, NULL, ThreadStarter, Starter); - if(err != 0){ - error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); - return INVALID_THREAD_HANDLE; - } - - if(Detach){ - pthread_detach(Handle); - } - - return (ThreadHandle)Handle; -} - -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach){ - TThreadStarter *Starter = new TThreadStarter; - Starter->Function = Function; - Starter->Argument = Argument; - Starter->Detach = Detach; - - pthread_t Handle; - pthread_attr_t Attr; - pthread_attr_init(&Attr); - pthread_attr_setstacksize(&Attr, StackSize); - int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); - pthread_attr_destroy(&Attr); - if(err != 0){ - error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); - return INVALID_THREAD_HANDLE; - } - - if(Detach){ - pthread_detach(Handle); - } - - return (ThreadHandle)Handle; -} - -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach){ - TThreadStarter *Starter = new TThreadStarter; - Starter->Function = Function; - Starter->Argument = Argument; - Starter->Detach = Detach; - - pthread_t Handle; - pthread_attr_t Attr; - pthread_attr_init(&Attr); - pthread_attr_setstack(&Attr, Stack, StackSize); - int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); - pthread_attr_destroy(&Attr); - if(err != 0){ - error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); - return INVALID_THREAD_HANDLE; - } - - if(Detach){ - pthread_detach(Handle); - } - - return (ThreadHandle)Handle; -} - -int JoinThread(ThreadHandle Handle){ - int Result = 0; - int *ResultPointer; - - pthread_join((pthread_t)Handle, (void**)&ResultPointer); - if(ResultPointer != NULL){ - Result = *ResultPointer; - delete ResultPointer; - } - - return Result; -} - -void DelayThread(int Seconds, int MicroSeconds){ - if(Seconds == 0 && MicroSeconds == 0){ - sched_yield(); - }else if(MicroSeconds == 0){ - sleep(Seconds); - }else{ - usleep(MicroSeconds + Seconds * 1000000); - } -} - -Semaphore::Semaphore(int Value){ - this->value = Value; - - // TODO(fusion): These should probably be non-recoverable errors. - - if(pthread_mutex_init(&this->mutex, NULL) != 0){ - error("Semaphore::Semaphore: Kann Mutex nicht einrichten.\n"); - } - - if(pthread_cond_init(&this->condition, NULL) != 0){ - error("Semaphore::Semaphore: Kann Wartebedingung nicht einrichten.\n"); - } -} - -Semaphore::~Semaphore(void){ - if(pthread_mutex_destroy(&this->mutex) != 0){ - error("Semaphore::~Semaphore: Kann Mutex nicht freigeben.\n"); - } - - if(pthread_cond_destroy(&this->condition) != 0){ - error("Semaphore::~Semaphore: Kann Wartebedingung nicht freigeben.\n"); - } -} - -void Semaphore::down(void){ - pthread_mutex_lock(&this->mutex); - while(this->value <= 0){ // TODO(fusion): Make sure this is always a load operation? - pthread_cond_wait(&this->condition, &this->mutex); - } - this->value -= 1; - pthread_mutex_unlock(&this->mutex); -} - -void Semaphore::up(void){ - pthread_mutex_lock(&this->mutex); - this->value += 1; - pthread_mutex_unlock(&this->mutex); - pthread_cond_signal(&this->condition); -} diff --git a/src/thread.hh b/src/thread.hh deleted file mode 100644 index 17e46e9..0000000 --- a/src/thread.hh +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TIBIA_THREAD_HH_ -#define TIBIA_THREAD_HH_ 1 - -#include "common.hh" -#include - -typedef pthread_t ThreadHandle; -typedef int (ThreadFunction)(void *); - -// TODO(fusion): Probably have another way to tell whether the thread was -// successfully spawned or not? -constexpr ThreadHandle INVALID_THREAD_HANDLE = 0; - -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach); -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach); -ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach); -int JoinThread(ThreadHandle Handle); -void DelayThread(int Seconds, int MicroSeconds); - -struct Semaphore { - Semaphore(int Value); - ~Semaphore(void); - void up(void); - void down(void); - - // DATA - // ================= - int value; - pthread_mutex_t mutex; - pthread_cond_t condition; -}; - -#endif //TIBIA_THREAD_HH_ diff --git a/src/threads.cc b/src/threads.cc new file mode 100644 index 0000000..a7acb41 --- /dev/null +++ b/src/threads.cc @@ -0,0 +1,158 @@ +#include "threads.hh" + +struct TThreadStarter { + ThreadFunction *Function; + void *Argument; + bool Detach; +}; + +static void *ThreadStarter(void *Pointer){ + TThreadStarter *Starter = (TThreadStarter*)Pointer; + ThreadFunction *Function = Starter->Function; + void *Argument = Starter->Argument; + bool Detach = Starter->Detach; + delete Starter; + + int Result = Function(Argument); + + // TODO(fusion): Just store the integer as a pointer and avoid allocation? + int *ResultPointer = NULL; + if(!Detach){ + ResultPointer = new int; + *ResultPointer = Result; + } + pthread_exit(ResultPointer); + + return NULL; // Unreachable. +} + +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach){ + TThreadStarter *Starter = new TThreadStarter; + Starter->Function = Function; + Starter->Argument = Argument; + Starter->Detach = Detach; + + pthread_t Handle; + int err = pthread_create(&Handle, NULL, ThreadStarter, Starter); + if(err != 0){ + error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); + return INVALID_THREAD_HANDLE; + } + + if(Detach){ + pthread_detach(Handle); + } + + return (ThreadHandle)Handle; +} + +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach){ + TThreadStarter *Starter = new TThreadStarter; + Starter->Function = Function; + Starter->Argument = Argument; + Starter->Detach = Detach; + + pthread_t Handle; + pthread_attr_t Attr; + pthread_attr_init(&Attr); + pthread_attr_setstacksize(&Attr, StackSize); + int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); + pthread_attr_destroy(&Attr); + if(err != 0){ + error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); + return INVALID_THREAD_HANDLE; + } + + if(Detach){ + pthread_detach(Handle); + } + + return (ThreadHandle)Handle; +} + +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach){ + TThreadStarter *Starter = new TThreadStarter; + Starter->Function = Function; + Starter->Argument = Argument; + Starter->Detach = Detach; + + pthread_t Handle; + pthread_attr_t Attr; + pthread_attr_init(&Attr); + pthread_attr_setstack(&Attr, Stack, StackSize); + int err = pthread_create(&Handle, &Attr, ThreadStarter, Starter); + pthread_attr_destroy(&Attr); + if(err != 0){ + error("StartThread: Kann Thread nicht anlegen; Fehlercode %d.\n", err); + return INVALID_THREAD_HANDLE; + } + + if(Detach){ + pthread_detach(Handle); + } + + return (ThreadHandle)Handle; +} + +int JoinThread(ThreadHandle Handle){ + int Result = 0; + int *ResultPointer; + + pthread_join((pthread_t)Handle, (void**)&ResultPointer); + if(ResultPointer != NULL){ + Result = *ResultPointer; + delete ResultPointer; + } + + return Result; +} + +void DelayThread(int Seconds, int MicroSeconds){ + if(Seconds == 0 && MicroSeconds == 0){ + sched_yield(); + }else if(MicroSeconds == 0){ + sleep(Seconds); + }else{ + usleep(MicroSeconds + Seconds * 1000000); + } +} + +Semaphore::Semaphore(int Value){ + this->value = Value; + + // TODO(fusion): These should probably be non-recoverable errors. + + if(pthread_mutex_init(&this->mutex, NULL) != 0){ + error("Semaphore::Semaphore: Kann Mutex nicht einrichten.\n"); + } + + if(pthread_cond_init(&this->condition, NULL) != 0){ + error("Semaphore::Semaphore: Kann Wartebedingung nicht einrichten.\n"); + } +} + +Semaphore::~Semaphore(void){ + if(pthread_mutex_destroy(&this->mutex) != 0){ + error("Semaphore::~Semaphore: Kann Mutex nicht freigeben.\n"); + } + + if(pthread_cond_destroy(&this->condition) != 0){ + error("Semaphore::~Semaphore: Kann Wartebedingung nicht freigeben.\n"); + } +} + +void Semaphore::down(void){ + pthread_mutex_lock(&this->mutex); + while(this->value <= 0){ // TODO(fusion): Make sure this is always a load operation? + pthread_cond_wait(&this->condition, &this->mutex); + } + this->value -= 1; + pthread_mutex_unlock(&this->mutex); +} + +void Semaphore::up(void){ + pthread_mutex_lock(&this->mutex); + this->value += 1; + pthread_mutex_unlock(&this->mutex); + pthread_cond_signal(&this->condition); +} diff --git a/src/threads.hh b/src/threads.hh new file mode 100644 index 0000000..9eafe11 --- /dev/null +++ b/src/threads.hh @@ -0,0 +1,33 @@ +#ifndef TIBIA_THREADS_HH_ +#define TIBIA_THREADS_HH_ 1 + +#include "common.hh" +#include + +typedef pthread_t ThreadHandle; +typedef int (ThreadFunction)(void *); + +// TODO(fusion): Probably have another way to tell whether the thread was +// successfully spawned or not? +constexpr ThreadHandle INVALID_THREAD_HANDLE = 0; + +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, bool Detach); +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, size_t StackSize, bool Detach); +ThreadHandle StartThread(ThreadFunction *Function, void *Argument, void *Stack, size_t StackSize, bool Detach); +int JoinThread(ThreadHandle Handle); +void DelayThread(int Seconds, int MicroSeconds); + +struct Semaphore { + Semaphore(int Value); + ~Semaphore(void); + void up(void); + void down(void); + + // DATA + // ================= + int value; + pthread_mutex_t mutex; + pthread_cond_t condition; +}; + +#endif //TIBIA_THREADS_HH_ diff --git a/src/util.cc b/src/util.cc deleted file mode 100644 index 4a3523b..0000000 --- a/src/util.cc +++ /dev/null @@ -1,501 +0,0 @@ -#include "common.hh" - -#include - -static void (*ErrorFunction)(const char *Text) = NULL; -static void (*PrintFunction)(int Level, const char *Text) = NULL; - -void SetErrorFunction(TErrorFunction *Function){ - ErrorFunction = Function; -} - -void SetPrintFunction(TPrintFunction *Function){ - PrintFunction = Function; -} - -void error(const char *Text, ...){ - char s[1024]; - - va_list ap; - va_start(ap, Text); - vsnprintf(s, sizeof(s), Text, ap); - va_end(ap); - - if(ErrorFunction){ - ErrorFunction(s); - }else{ - printf("%s", s); - } -} - -void print(int Level, const char *Text, ...){ - char s[1024]; - - va_list ap; - va_start(ap, Text); - vsnprintf(s, sizeof(s), Text, ap); - va_end(ap); - - if(PrintFunction){ - PrintFunction(Level, s); - }else{ - printf("%s", s); - } -} - -int random(int Min, int Max){ - int Range = (Max - Min) + 1; - int Result = Min; - if(Range > 0){ - Result += rand() % Range; - } - return Result; -} - -bool FileExists(const char *FileName){ - struct stat Buffer; - bool Result = true; - if(stat(FileName, &Buffer) != 0){ - if(errno != ENOENT){ - error("FileExists: Unerwarteter Fehlercode %d.\n", errno); - } - Result = false; - } - return Result; -} - -// String Utility -// ============================================================================= -bool isSpace(int c){ - return c == ' ' - || c == '\t' - || c == '\n' - || c == '\r' - || c == '\v' - || c == '\f'; -} - -bool isAlpha(int c){ - // TODO(fusion): This is most likely wrong! We're assuming a direct conversion - // from `char` to `int` which will cause sign extension for negative values. This - // wouldn't be a problem if we expected to parse only streams of `char[]` but can - // be problematic for the output of `getc` which returns bytes as `unsigned char` - // converted to `int`. - // TLDR: The parameter `c` should be `uint8`. - return ('A' <= c && c <= 'Z') - || ('a' <= c && c <= 'z') - || c == -0x1C // E4 => ä - || c == -0x0A // F6 => ö - || c == -0x04 // FC => ü - || c == -0x3C // C4 => Ä - || c == -0x2A // D6 => Ö - || c == -0x24 // DC => Ü - || c == -0x21; // DF => ß -} - -bool isEngAlpha(int c){ - return ('A' <= c && c <= 'Z') - || ('a' <= c && c <= 'z'); -} - -bool isDigit(int c){ - return ('0' <= c && c <= '9'); -} - -int toLower(int c){ - // TODO(fusion): Same problem as `isAlpha`. - if(('A' <= c && c <= 'Z') || (0xC0 <= c && c <= 0xDE && c != 0xD7)){ - c += 32; - } - return c; -} - -int toUpper(int c){ - // TODO(fusion): Same problem as `isAlpha`. - if(('A' <= c && c <= 'Z') || (0xE0 <= c && c <= 0xFE && c != 0xF7)){ - c -= 32; - } - return c; -} - -char *strLower(char *s){ - for(int i = 0; s[i] != 0; i += 1){ - s[i] = (char)toLower(s[i]); - } - return s; -} - -char *strUpper(char *s){ - for(int i = 0; s[i] != 0; i += 1){ - s[i] = (char)toUpper(s[i]); - } - return s; -} - -int stricmp(const char *s1, const char *s2, int Max /*= INT_MAX*/){ - for(int i = 0; i < Max; i += 1){ - int c1 = toLower(s1[i]); - int c2 = toLower(s2[i]); - if(c1 > c2){ - return 1; - }else if(c1 < c2){ - return -1; - }else{ - ASSERT(c1 == c2); - if(c1 == 0){ - break; - } - } - } - return 0; -} - -char *findFirst(char *s, char c){ - return strchr(s, (int)c); -} - -char *findLast(char *s, char c){ - char *Current = s; - char *Last = NULL; - while(true){ - Current = strchr(Current, (int)c); - if(Current == NULL) - break; - Last = Current; - Current += 1; // skip character - } - return Last; -} - -// BitSet Utility -// ============================================================================= -bool CheckBitIndex(int BitSetBytes, int Index){ - return Index >= 0 && Index < (BitSetBytes * 8); -} - -bool CheckBit(uint8 *BitSet, int Index){ - int ByteIndex = (int)(Index / 8); - uint8 BitMask = (uint8)(1 << (Index % 8)); - return (BitSet[ByteIndex] & BitMask) != 0; -} - -void SetBit(uint8 *BitSet, int Index){ - int ByteIndex = (int)(Index / 8); - uint8 BitMask = (uint8)(1 << (Index % 8)); - BitSet[ByteIndex] |= BitMask; -} - -void ClearBit(uint8 *BitSet, int Index){ - int ByteIndex = (int)(Index / 8); - uint8 BitMask = (uint8)(1 << (Index % 8)); - BitSet[ByteIndex] &= ~BitMask; -} - - -// TReadStream -// ============================================================================= -bool TReadStream::readFlag(void){ - return this->readByte() != 0; -} - -uint16 TReadStream::readWord(void){ - // NOTE(fusion): Data is encoded in little endian. - uint8 Byte0 = this->readByte(); - uint8 Byte1 = this->readByte(); - return ((uint16)Byte1 << 8) | (uint16)Byte0; -} - -uint32 TReadStream::readQuad(void){ - // NOTE(fusion): Data is encoded in little endian. - uint8 Byte0 = this->readByte(); - uint8 Byte1 = this->readByte(); - uint8 Byte2 = this->readByte(); - uint8 Byte3 = this->readByte(); - return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) - | ((uint32)Byte1 << 8) | (uint32)Byte0; -} - -void TReadStream::readString(char *Buffer, int MaxLength){ - if(Buffer == NULL || MaxLength == 0){ - error("TReadStream::readString: Übergebener Puffer existiert nicht.\n"); - throw "internal error"; - } - - int Length = (int)this->readWord(); - if(Length == 0xFFFF){ - Length = (int)this->readQuad(); - } - - if(Length > 0){ - if(MaxLength < 0 || MaxLength > Length){ - this->readBytes((uint8*)Buffer, Length); - Buffer[Length] = 0; - }else{ - this->readBytes((uint8*)Buffer, MaxLength - 1); - this->skip(Length - MaxLength + 1); - Buffer[MaxLength - 1] = 0; - } - }else{ - Buffer[0] = 0; - } -} - -void TReadStream::readBytes(uint8 *Buffer, int Count){ - if(Buffer == NULL){ - error("TReadStream::readBytes: Übergebener Puffer existiert nicht.\n"); - throw "internal error"; - } - - for(int i = 0; i < Count; i += 1){ - Buffer[i] = this->readByte(); - } -} - -// TReadBuffer -// ============================================================================= -TReadBuffer::TReadBuffer(const uint8 *Data, int Size){ - if(Data == NULL){ - error("TReadBuffer::TReadBuffer: data ist NULL.\n"); - Size = 0; - }else if(Size < 0){ - error("TReadBuffer::TReadBuffer: Ungültige Datengröße %d.\n", Size); - Size = 0; - } - - this->Data = Data; - this->Size = Size; - this->Position = 0; -} - -uint8 TReadBuffer::readByte(void){ - if((this->Size - this->Position) < 1){ - throw "buffer empty"; - } - - uint8 Byte = this->Data[this->Position]; - this->Position += 1; - return Byte; -} - -uint16 TReadBuffer::readWord(void){ - if((this->Size - this->Position) < 2){ - throw "buffer empty"; - } - - uint8 Byte0 = this->Data[this->Position]; - uint8 Byte1 = this->Data[this->Position + 1]; - this->Position += 2; - return ((uint16)Byte1 << 8) | (uint16)Byte0; -} - -uint32 TReadBuffer::readQuad(void){ - if((this->Size - this->Position) < 4){ - throw "buffer empty"; - } - - uint8 Byte0 = this->Data[this->Position]; - uint8 Byte1 = this->Data[this->Position + 1]; - uint8 Byte2 = this->Data[this->Position + 2]; - uint8 Byte3 = this->Data[this->Position + 3]; - this->Position += 4; - return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) - | ((uint32)Byte1 << 8) | (uint32)Byte0; -} - -void TReadBuffer::readBytes(uint8 *Buffer, int Count){ - if(Buffer == NULL || Count <= 0){ - error("TReadBuffer::readBytes: Übergebener Puffer existiert nicht.\n"); - throw "buffer not existing"; - } - - if((this->Size - this->Position) < Count){ - throw "buffer empty"; - } - - memcpy(Buffer, &this->Data[this->Position], Count); - this->Position += Count; -} - -bool TReadBuffer::eof(void){ - return this->Size <= this->Position; -} - -void TReadBuffer::skip(int Count){ - if((this->Size - this->Position) < Count){ - throw "buffer empty"; - } - - this->Position += Count; -} - -// TWriteStream -// ============================================================================= -void TWriteStream::writeFlag(bool Flag){ - this->writeByte((uint8)Flag); -} - -void TWriteStream::writeWord(uint16 Word){ - this->writeByte((uint8)(Word)); - this->writeByte((uint8)(Word >> 8)); -} - -void TWriteStream::writeQuad(uint32 Quad){ - this->writeByte((uint8)(Quad)); - this->writeByte((uint8)(Quad >> 8)); - this->writeByte((uint8)(Quad >> 16)); - this->writeByte((uint8)(Quad >> 24)); -} - -void TWriteStream::writeString(const char *String){ - if(String == NULL){ - this->writeWord(0); - return; - } - - int StringLength = (int)strlen(String); - ASSERT(StringLength >= 0); - if(StringLength < 0xFFFF){ - this->writeWord((uint16)StringLength); - }else{ - this->writeWord(0xFFFF); - this->writeQuad((uint32)StringLength); - } - - if(StringLength > 0){ - this->writeBytes((const uint8*)String, StringLength); - } -} - -void TWriteStream::writeBytes(const uint8 *Buffer, int Count){ - if(Buffer == NULL){ - error("TWriteStream::writeBytes: Übergebener Puffer existiert nicht.\n"); - throw "internal error"; - } - - for(int i = 0; i < Count; i += 1){ - this->writeByte(Buffer[i]); - } -} - -// TWriteBuffer -// ============================================================================= -TWriteBuffer::TWriteBuffer(uint8 *Data, int Size){ - if(Data == NULL){ - error("TWriteBuffer::TWriteBuffer: data ist NULL.\n"); - Size = 0; - }else if(Size < 0){ - error("TWriteBuffer::TWriteBuffer: Ungültige Datengröße %d.\n", Size); - Size = 0; - } - - this->Data = Data; - this->Size = Size; - this->Position = 0; -} - -void TWriteBuffer::writeByte(uint8 Byte){ - if((this->Size - this->Position) < 1){ - throw "buffer full"; - } - - this->Data[this->Position] = Byte; - this->Position += 1; -} - -void TWriteBuffer::writeWord(uint16 Word){ - if((this->Size - this->Position) < 2){ - throw "buffer full"; - } - - this->Data[this->Position] = (uint8)(Word); - this->Data[this->Position + 1] = (uint8)(Word >> 8); - this->Position += 2; -} - -void TWriteBuffer::writeQuad(uint32 Quad){ - if((this->Size - this->Position) < 4){ - throw "buffer full"; - } - - this->Data[this->Position] = (uint8)(Quad); - this->Data[this->Position + 1] = (uint8)(Quad >> 8); - this->Data[this->Position + 2] = (uint8)(Quad >> 16); - this->Data[this->Position + 3] = (uint8)(Quad >> 24); - this->Position += 4; -} - -void TWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ - if((this->Size - this->Position) < Count){ - throw "buffer full"; - } - - memcpy(&this->Data[this->Position], Buffer, Count); - this->Position += Count; -} - -// TDynamicWriteBuffer -// ============================================================================= -TDynamicWriteBuffer::TDynamicWriteBuffer(int InitialSize) - : TWriteBuffer(new uint8[InitialSize], InitialSize) -{ - // no-op -} - -void TDynamicWriteBuffer::resizeBuffer(void){ - ASSERT(this->Size > 0); - int Size = this->Size * 2; - uint8 *Data = new uint8[Size]; - if(this->Data != NULL){ - memcpy(Data, this->Data, this->Size); - delete[] this->Data; - } - - this->Data = Data; - this->Size = Size; -} - -void TDynamicWriteBuffer::writeByte(uint8 Byte){ - while((this->Size - this->Position) < 1){ - this->resizeBuffer(); - } - - this->Data[this->Position] = Byte; - this->Position += 1; -} - -void TDynamicWriteBuffer::writeWord(uint16 Word){ - while((this->Size - this->Position) < 2){ - this->resizeBuffer(); - } - - this->Data[this->Position] = (uint8)(Word); - this->Data[this->Position + 1] = (uint8)(Word >> 8); - this->Position += 2; -} - -void TDynamicWriteBuffer::writeQuad(uint32 Quad){ - while((this->Size - this->Position) < 4){ - this->resizeBuffer(); - } - - this->Data[this->Position] = (uint8)(Quad); - this->Data[this->Position + 1] = (uint8)(Quad >> 8); - this->Data[this->Position + 2] = (uint8)(Quad >> 16); - this->Data[this->Position + 3] = (uint8)(Quad >> 24); - this->Position += 4; -} - -void TDynamicWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ - while((this->Size - this->Position) < Count){ - this->resizeBuffer(); - } - - memcpy(&this->Data[this->Position], Buffer, Count); - this->Position += Count; -} - -TDynamicWriteBuffer::~TDynamicWriteBuffer(void){ - if(this->Data != NULL){ - delete[] this->Data; - } -} diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 0000000..4a3523b --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,501 @@ +#include "common.hh" + +#include + +static void (*ErrorFunction)(const char *Text) = NULL; +static void (*PrintFunction)(int Level, const char *Text) = NULL; + +void SetErrorFunction(TErrorFunction *Function){ + ErrorFunction = Function; +} + +void SetPrintFunction(TPrintFunction *Function){ + PrintFunction = Function; +} + +void error(const char *Text, ...){ + char s[1024]; + + va_list ap; + va_start(ap, Text); + vsnprintf(s, sizeof(s), Text, ap); + va_end(ap); + + if(ErrorFunction){ + ErrorFunction(s); + }else{ + printf("%s", s); + } +} + +void print(int Level, const char *Text, ...){ + char s[1024]; + + va_list ap; + va_start(ap, Text); + vsnprintf(s, sizeof(s), Text, ap); + va_end(ap); + + if(PrintFunction){ + PrintFunction(Level, s); + }else{ + printf("%s", s); + } +} + +int random(int Min, int Max){ + int Range = (Max - Min) + 1; + int Result = Min; + if(Range > 0){ + Result += rand() % Range; + } + return Result; +} + +bool FileExists(const char *FileName){ + struct stat Buffer; + bool Result = true; + if(stat(FileName, &Buffer) != 0){ + if(errno != ENOENT){ + error("FileExists: Unerwarteter Fehlercode %d.\n", errno); + } + Result = false; + } + return Result; +} + +// String Utility +// ============================================================================= +bool isSpace(int c){ + return c == ' ' + || c == '\t' + || c == '\n' + || c == '\r' + || c == '\v' + || c == '\f'; +} + +bool isAlpha(int c){ + // TODO(fusion): This is most likely wrong! We're assuming a direct conversion + // from `char` to `int` which will cause sign extension for negative values. This + // wouldn't be a problem if we expected to parse only streams of `char[]` but can + // be problematic for the output of `getc` which returns bytes as `unsigned char` + // converted to `int`. + // TLDR: The parameter `c` should be `uint8`. + return ('A' <= c && c <= 'Z') + || ('a' <= c && c <= 'z') + || c == -0x1C // E4 => ä + || c == -0x0A // F6 => ö + || c == -0x04 // FC => ü + || c == -0x3C // C4 => Ä + || c == -0x2A // D6 => Ö + || c == -0x24 // DC => Ü + || c == -0x21; // DF => ß +} + +bool isEngAlpha(int c){ + return ('A' <= c && c <= 'Z') + || ('a' <= c && c <= 'z'); +} + +bool isDigit(int c){ + return ('0' <= c && c <= '9'); +} + +int toLower(int c){ + // TODO(fusion): Same problem as `isAlpha`. + if(('A' <= c && c <= 'Z') || (0xC0 <= c && c <= 0xDE && c != 0xD7)){ + c += 32; + } + return c; +} + +int toUpper(int c){ + // TODO(fusion): Same problem as `isAlpha`. + if(('A' <= c && c <= 'Z') || (0xE0 <= c && c <= 0xFE && c != 0xF7)){ + c -= 32; + } + return c; +} + +char *strLower(char *s){ + for(int i = 0; s[i] != 0; i += 1){ + s[i] = (char)toLower(s[i]); + } + return s; +} + +char *strUpper(char *s){ + for(int i = 0; s[i] != 0; i += 1){ + s[i] = (char)toUpper(s[i]); + } + return s; +} + +int stricmp(const char *s1, const char *s2, int Max /*= INT_MAX*/){ + for(int i = 0; i < Max; i += 1){ + int c1 = toLower(s1[i]); + int c2 = toLower(s2[i]); + if(c1 > c2){ + return 1; + }else if(c1 < c2){ + return -1; + }else{ + ASSERT(c1 == c2); + if(c1 == 0){ + break; + } + } + } + return 0; +} + +char *findFirst(char *s, char c){ + return strchr(s, (int)c); +} + +char *findLast(char *s, char c){ + char *Current = s; + char *Last = NULL; + while(true){ + Current = strchr(Current, (int)c); + if(Current == NULL) + break; + Last = Current; + Current += 1; // skip character + } + return Last; +} + +// BitSet Utility +// ============================================================================= +bool CheckBitIndex(int BitSetBytes, int Index){ + return Index >= 0 && Index < (BitSetBytes * 8); +} + +bool CheckBit(uint8 *BitSet, int Index){ + int ByteIndex = (int)(Index / 8); + uint8 BitMask = (uint8)(1 << (Index % 8)); + return (BitSet[ByteIndex] & BitMask) != 0; +} + +void SetBit(uint8 *BitSet, int Index){ + int ByteIndex = (int)(Index / 8); + uint8 BitMask = (uint8)(1 << (Index % 8)); + BitSet[ByteIndex] |= BitMask; +} + +void ClearBit(uint8 *BitSet, int Index){ + int ByteIndex = (int)(Index / 8); + uint8 BitMask = (uint8)(1 << (Index % 8)); + BitSet[ByteIndex] &= ~BitMask; +} + + +// TReadStream +// ============================================================================= +bool TReadStream::readFlag(void){ + return this->readByte() != 0; +} + +uint16 TReadStream::readWord(void){ + // NOTE(fusion): Data is encoded in little endian. + uint8 Byte0 = this->readByte(); + uint8 Byte1 = this->readByte(); + return ((uint16)Byte1 << 8) | (uint16)Byte0; +} + +uint32 TReadStream::readQuad(void){ + // NOTE(fusion): Data is encoded in little endian. + uint8 Byte0 = this->readByte(); + uint8 Byte1 = this->readByte(); + uint8 Byte2 = this->readByte(); + uint8 Byte3 = this->readByte(); + return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) + | ((uint32)Byte1 << 8) | (uint32)Byte0; +} + +void TReadStream::readString(char *Buffer, int MaxLength){ + if(Buffer == NULL || MaxLength == 0){ + error("TReadStream::readString: Übergebener Puffer existiert nicht.\n"); + throw "internal error"; + } + + int Length = (int)this->readWord(); + if(Length == 0xFFFF){ + Length = (int)this->readQuad(); + } + + if(Length > 0){ + if(MaxLength < 0 || MaxLength > Length){ + this->readBytes((uint8*)Buffer, Length); + Buffer[Length] = 0; + }else{ + this->readBytes((uint8*)Buffer, MaxLength - 1); + this->skip(Length - MaxLength + 1); + Buffer[MaxLength - 1] = 0; + } + }else{ + Buffer[0] = 0; + } +} + +void TReadStream::readBytes(uint8 *Buffer, int Count){ + if(Buffer == NULL){ + error("TReadStream::readBytes: Übergebener Puffer existiert nicht.\n"); + throw "internal error"; + } + + for(int i = 0; i < Count; i += 1){ + Buffer[i] = this->readByte(); + } +} + +// TReadBuffer +// ============================================================================= +TReadBuffer::TReadBuffer(const uint8 *Data, int Size){ + if(Data == NULL){ + error("TReadBuffer::TReadBuffer: data ist NULL.\n"); + Size = 0; + }else if(Size < 0){ + error("TReadBuffer::TReadBuffer: Ungültige Datengröße %d.\n", Size); + Size = 0; + } + + this->Data = Data; + this->Size = Size; + this->Position = 0; +} + +uint8 TReadBuffer::readByte(void){ + if((this->Size - this->Position) < 1){ + throw "buffer empty"; + } + + uint8 Byte = this->Data[this->Position]; + this->Position += 1; + return Byte; +} + +uint16 TReadBuffer::readWord(void){ + if((this->Size - this->Position) < 2){ + throw "buffer empty"; + } + + uint8 Byte0 = this->Data[this->Position]; + uint8 Byte1 = this->Data[this->Position + 1]; + this->Position += 2; + return ((uint16)Byte1 << 8) | (uint16)Byte0; +} + +uint32 TReadBuffer::readQuad(void){ + if((this->Size - this->Position) < 4){ + throw "buffer empty"; + } + + uint8 Byte0 = this->Data[this->Position]; + uint8 Byte1 = this->Data[this->Position + 1]; + uint8 Byte2 = this->Data[this->Position + 2]; + uint8 Byte3 = this->Data[this->Position + 3]; + this->Position += 4; + return ((uint32)Byte3 << 24) | ((uint32)Byte2 << 16) + | ((uint32)Byte1 << 8) | (uint32)Byte0; +} + +void TReadBuffer::readBytes(uint8 *Buffer, int Count){ + if(Buffer == NULL || Count <= 0){ + error("TReadBuffer::readBytes: Übergebener Puffer existiert nicht.\n"); + throw "buffer not existing"; + } + + if((this->Size - this->Position) < Count){ + throw "buffer empty"; + } + + memcpy(Buffer, &this->Data[this->Position], Count); + this->Position += Count; +} + +bool TReadBuffer::eof(void){ + return this->Size <= this->Position; +} + +void TReadBuffer::skip(int Count){ + if((this->Size - this->Position) < Count){ + throw "buffer empty"; + } + + this->Position += Count; +} + +// TWriteStream +// ============================================================================= +void TWriteStream::writeFlag(bool Flag){ + this->writeByte((uint8)Flag); +} + +void TWriteStream::writeWord(uint16 Word){ + this->writeByte((uint8)(Word)); + this->writeByte((uint8)(Word >> 8)); +} + +void TWriteStream::writeQuad(uint32 Quad){ + this->writeByte((uint8)(Quad)); + this->writeByte((uint8)(Quad >> 8)); + this->writeByte((uint8)(Quad >> 16)); + this->writeByte((uint8)(Quad >> 24)); +} + +void TWriteStream::writeString(const char *String){ + if(String == NULL){ + this->writeWord(0); + return; + } + + int StringLength = (int)strlen(String); + ASSERT(StringLength >= 0); + if(StringLength < 0xFFFF){ + this->writeWord((uint16)StringLength); + }else{ + this->writeWord(0xFFFF); + this->writeQuad((uint32)StringLength); + } + + if(StringLength > 0){ + this->writeBytes((const uint8*)String, StringLength); + } +} + +void TWriteStream::writeBytes(const uint8 *Buffer, int Count){ + if(Buffer == NULL){ + error("TWriteStream::writeBytes: Übergebener Puffer existiert nicht.\n"); + throw "internal error"; + } + + for(int i = 0; i < Count; i += 1){ + this->writeByte(Buffer[i]); + } +} + +// TWriteBuffer +// ============================================================================= +TWriteBuffer::TWriteBuffer(uint8 *Data, int Size){ + if(Data == NULL){ + error("TWriteBuffer::TWriteBuffer: data ist NULL.\n"); + Size = 0; + }else if(Size < 0){ + error("TWriteBuffer::TWriteBuffer: Ungültige Datengröße %d.\n", Size); + Size = 0; + } + + this->Data = Data; + this->Size = Size; + this->Position = 0; +} + +void TWriteBuffer::writeByte(uint8 Byte){ + if((this->Size - this->Position) < 1){ + throw "buffer full"; + } + + this->Data[this->Position] = Byte; + this->Position += 1; +} + +void TWriteBuffer::writeWord(uint16 Word){ + if((this->Size - this->Position) < 2){ + throw "buffer full"; + } + + this->Data[this->Position] = (uint8)(Word); + this->Data[this->Position + 1] = (uint8)(Word >> 8); + this->Position += 2; +} + +void TWriteBuffer::writeQuad(uint32 Quad){ + if((this->Size - this->Position) < 4){ + throw "buffer full"; + } + + this->Data[this->Position] = (uint8)(Quad); + this->Data[this->Position + 1] = (uint8)(Quad >> 8); + this->Data[this->Position + 2] = (uint8)(Quad >> 16); + this->Data[this->Position + 3] = (uint8)(Quad >> 24); + this->Position += 4; +} + +void TWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ + if((this->Size - this->Position) < Count){ + throw "buffer full"; + } + + memcpy(&this->Data[this->Position], Buffer, Count); + this->Position += Count; +} + +// TDynamicWriteBuffer +// ============================================================================= +TDynamicWriteBuffer::TDynamicWriteBuffer(int InitialSize) + : TWriteBuffer(new uint8[InitialSize], InitialSize) +{ + // no-op +} + +void TDynamicWriteBuffer::resizeBuffer(void){ + ASSERT(this->Size > 0); + int Size = this->Size * 2; + uint8 *Data = new uint8[Size]; + if(this->Data != NULL){ + memcpy(Data, this->Data, this->Size); + delete[] this->Data; + } + + this->Data = Data; + this->Size = Size; +} + +void TDynamicWriteBuffer::writeByte(uint8 Byte){ + while((this->Size - this->Position) < 1){ + this->resizeBuffer(); + } + + this->Data[this->Position] = Byte; + this->Position += 1; +} + +void TDynamicWriteBuffer::writeWord(uint16 Word){ + while((this->Size - this->Position) < 2){ + this->resizeBuffer(); + } + + this->Data[this->Position] = (uint8)(Word); + this->Data[this->Position + 1] = (uint8)(Word >> 8); + this->Position += 2; +} + +void TDynamicWriteBuffer::writeQuad(uint32 Quad){ + while((this->Size - this->Position) < 4){ + this->resizeBuffer(); + } + + this->Data[this->Position] = (uint8)(Quad); + this->Data[this->Position + 1] = (uint8)(Quad >> 8); + this->Data[this->Position + 2] = (uint8)(Quad >> 16); + this->Data[this->Position + 3] = (uint8)(Quad >> 24); + this->Position += 4; +} + +void TDynamicWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ + while((this->Size - this->Position) < Count){ + this->resizeBuffer(); + } + + memcpy(&this->Data[this->Position], Buffer, Count); + this->Position += Count; +} + +TDynamicWriteBuffer::~TDynamicWriteBuffer(void){ + if(this->Data != NULL){ + delete[] this->Data; + } +} -- cgit v1.2.3