Compare commits

...

10 Commits

Author SHA1 Message Date
fusion32
386fa9b807 minor tweak to script recursion depth checks 2026-06-20 21:50:45 -03:00
fusion32
a8d26b6ce3 fix player state desync on login (#59) 2026-06-01 01:54:15 -03:00
fusion32
e2ba7cd296 fix issues and warnings when compiling with glibc < 2.32 (#57, #58)
This also fixes some inconsistencies such as mixing both strerror
and strerrordesc_np, and possible formatting issues.
2026-05-28 23:56:29 -03:00
fusion32
d1c4dce3be minor fix to monster targeting logic 2026-05-25 18:19:39 -03:00
fusion32
54c2169ec9 fix issues with TMonster constructor + Search functions + UH formula
The issue with the TMonster constructor was obvious, but the others
were slightly different from the original.
2026-05-09 12:40:41 -03:00
fusion32
7beaa081de fix issue with CUMULATIVE + THROW loot (#56) 2026-05-05 21:54:32 -03:00
fusion32
a9f70ed1f1 small change to monster talking
Monsters will prefix their lines with "#Y" when they're supposed
to yell. The minotaur mage from the leaked files uses a "#W" prefix
which is either a mistake or some removed feature? Either way, it
wouldn't be a problem with the original binary because it would only
check for a leading "#" before removing a three character prefix.
2026-04-16 07:34:02 -03:00
fusion32
f9beda0832 missing NEWLINE on non-patched fields + fix accept error on shutdown 2026-03-30 15:54:52 -03:00
fusion32
995f50a984 fix issue with whisper "pspsps" condition 2026-03-25 17:53:42 -03:00
fusion32
370842756a support for proxy headers + minor tweaks 2026-03-14 16:07:56 -03:00
26 changed files with 247 additions and 99 deletions

View File

@ -4,13 +4,13 @@ OUTPUTEXE = game
CC = g++
CFLAGS = -m64 -fno-strict-aliasing -pedantic -Wall -Wextra -Wno-deprecated-declarations -Wno-unused-parameter -Wno-format-truncation -std=c++11 -pthread -DOS_LINUX=1 -DARCH_X64=1
LFLAGS = -Wl,-t -lcrypto
LFLAGS = -Wl,-t -lrt -lcrypto
DEBUG ?= 0
ifneq ($(DEBUG), 0)
CFLAGS += -g -Og -DENABLE_ASSERTIONS=1
CFLAGS += -g -Og -DENABLE_ASSERTIONS=1
else
CFLAGS += -O2
CFLAGS += -O2
endif
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)/houses.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/moveuse.hh $(SRCDIR)/objects.hh $(SRCDIR)/operate.hh $(SRCDIR)/query.hh $(SRCDIR)/reader.hh $(SRCDIR)/script.hh $(SRCDIR)/threads.hh $(SRCDIR)/writer.hh

View File

@ -15,13 +15,25 @@ I had a small *TODO* list with a few things to attempt after the server was up a
## Compiling
The server uses a few Linux specific features so it will **ONLY** compile on Linux. It's possible to port to Windows but it would require a few design changes. Originally it would have no dependencies but with the RSA routines change it now requires OpenSSL's `libcrypto` as its **ONLY** dependency. The makefile is very simple and should work as long as libcrypto is installed. You can add the `-j N` switch to make it compile across N processes.
```
make # build in release mode
make DEBUG=1 # build in debug mode
make clean # remove `build` directory
make clean && make # full rebuild in release mode (recommended)
make clean && make DEBUG=1 # full rebuild in debug mode (recommended)
make # build in release mode
make DEBUG=1 # build in debug mode
make clean # remove `build` directory
make -B # full rebuild in release mode (recommended)
make -B DEBUG=1 # full rebuild in debug mode (recommended)
```
### Extra Features (Advanced)
There are also some extra features that can be enabled by appending the following definitions to the `CFLAGS` variable in your `Makefile`. They're completely optional but can be useful in specific scenarios.
- `-DTIBIA772=1`
- Modify the protocol to match Tibia 7.72. It's not a 1:1 relation so 7.7 clients won't be able to connect.
- `-DALLOW_LOCAL_PROXY=1`
- Allow **local** connections to send a proxy header. Ideally we'd have a list of trusted proxies to prevent clients from spoofing their addresses. In reality, it is almost always simpler to have a **local** relay. This is also the only way to get a multi-route proxy without having to drastically change the way connections are handled.
- `-DBIND_ACCEPTOR_TO_GAME_ADDRESS=1`
- Make the acceptor socket bind to the interface specified by the game address, rather than all available network interfaces. This is the same address stored in the database and may cause startup issues if no such interface exists, or connection issues if the address is not publicly reachable.
## Running
This repository contains only the source code for the game server. After the first decompilation pass, it was clear the server would need a few supporting services. They're fairly simple but each one will have a separate *README* file with a short description on how to compile and run them.
- [Query Manager](https://github.com/fusion32/tibia-querymanager)

View File

@ -182,6 +182,8 @@ void error(const char *Text, ...) ATTR_PRINTF(1, 2);
void print(int Level, const char *Text, ...) ATTR_PRINTF(2, 3);
int random(int Min, int Max);
bool FileExists(const char *FileName);
const char *GetSignalDescription(int SigNr);
const char *GetErrorDescription(int SigNr);
bool isSpace(int c);
bool isAlpha(int c);

View File

@ -652,7 +652,7 @@ bool CallGameThread(TConnection *Connection){
Connection->WaitingForACK = true;
if(tgkill(GetGameProcessID(), GetGameThreadID(), SIGUSR1) == -1){
error("CallGameThread: Can't send SIGUSR1 to thread %d/%d: (%d) %s\n",
GetGameProcessID(), GetGameThreadID(), errno, strerrordesc_np(errno));
GetGameProcessID(), GetGameThreadID(), errno, GetErrorDescription(errno));
SendLoginMessage(Connection, LOGIN_MESSAGE_ERROR,
"The server is not online.\nPlease try again later.", -1);
return false;
@ -1130,7 +1130,12 @@ bool ReceiveCommand(TConnection *Connection){
}
while(!Connection->WaitingForACK){
#if ALLOW_LOCAL_PROXY
uint8 Help[32];
#else
uint8 Help[2];
#endif
int BytesRead = ReadFromSocket(Connection, Help, 2);
if(BytesRead == 0){
// NOTE(fusion): Peer has closed the connection and there was no
@ -1153,6 +1158,62 @@ bool ReceiveCommand(TConnection *Connection){
return false;
}
#if ALLOW_LOCAL_PROXY
if(Connection->State == CONNECTION_CONNECTED){
uint8_t *SourceAddr = NULL;
if(Help[0] == 0x0D && Help[1] == 0x0A){
// NOTE(fusion): HAProxy-V2 header.
static const uint8_t HAProxyV2Signature[] = {
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51,
0x55, 0x49, 0x54, 0x0A,
};
if(ReadFromSocket(Connection, Help + 2, 26) != 26){
error("Unable to read proxy header from %s (Socket=%d)\n",
Connection->IPAddress, Connection->GetSocket());
return false;
}
if(memcmp(Help, HAProxyV2Signature, 12) != 0){
error("Invalid proxy header from %s (Socket=%d)\n",
Connection->IPAddress, Connection->GetSocket());
return false;
}
SourceAddr = Help + 16;
}else if(Help[0] == 0xD9 && Help[1] == 0xFF){
// NOTE(fusion): Our custom proxy header.
if(ReadFromSocket(Connection, Help + 2, 4) != 4){
error("Unable to read proxy header from %s (Socket=%d)\n",
Connection->IPAddress, Connection->GetSocket());
return false;
}
SourceAddr = Help + 2;
}
if(SourceAddr != NULL){
// NOTE(fusion): Ideally we'd have a list of trusted proxies to
// prevent clients from spoofing their addresses. In reality, it
// is almost always simpler to have a localhost "receiver". This
// is also the only way to get a multi-route proxy without having
// to drastically change the way connections are handled.
if(strcmp(Connection->IPAddress, "127.0.0.1") != 0){
error("Received proxy header (%02X, %02X) from unknown host %s\n",
Help[0], Help[1], Connection->IPAddress);
return false;
}
// NOTE(fusion): Update connection IP-Address and resume reading.
snprintf(Connection->IPAddress, sizeof(Connection->IPAddress),
"%d.%d.%d.%d",
(int)SourceAddr[0], (int)SourceAddr[1],
(int)SourceAddr[2], (int)SourceAddr[3]);
continue;
}
}
#endif
// TODO(fusion): Size is encoded as a little endian uint16. We should
// have a few helper functions to assist with buffer reading.
int Size = ((uint16)Help[0] | ((uint16)Help[1] << 8));
@ -1428,21 +1489,21 @@ bool OpenSocket(void){
Linger.l_linger = 0;
if(setsockopt(TCPSocket, SOL_SOCKET, SO_LINGER, &Linger, sizeof(Linger)) == -1){
error("LaunchServer: Failed to set SO_LINGER=(0, 0): (%d) %s.\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
return false;
}
int ReuseAddr = 1;
if(setsockopt(TCPSocket, SOL_SOCKET, SO_REUSEADDR, &ReuseAddr, sizeof(ReuseAddr)) == -1){
error("LaunchServer: Failed to set SO_REUSEADDR=1: (%d) %s.\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
return false;
}
int NoDelay = 1;
if(setsockopt(TCPSocket, IPPROTO_TCP, TCP_NODELAY, &NoDelay, sizeof(NoDelay)) == -1){
error("LaunchServer: Failed to set TCP_NODELAY=1: (%d) %s.\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
return false;
}
@ -1456,7 +1517,7 @@ bool OpenSocket(void){
#endif
if(bind(TCPSocket, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress)) == -1){
error("LaunchServer: Failed to bind to acceptor to %s:%d: (%d) %s.\n",
inet_ntoa(ServerAddress.sin_addr), GamePort, errno, strerrordesc_np(errno));
inet_ntoa(ServerAddress.sin_addr), GamePort, errno, GetErrorDescription(errno));
return false;
}
@ -1471,9 +1532,13 @@ bool OpenSocket(void){
int AcceptorThreadLoop(void *Unused){
AcceptorThreadID = gettid();
print(1, "Warte auf Clients...\n");
while(GameRunning()){
while(true){
int Socket = accept(TCPSocket, NULL, NULL);
if(Socket == -1){
if(!GameRunning()){
break;
}
error("AcceptorThreadLoop: Fehler %d beim Accept.\n", errno);
continue;
}

View File

@ -105,7 +105,7 @@ bool TConnection::SetLoginTimer(int Timeout){
SigEvent.sigev_notify_thread_id = this->ThreadID;
if(timer_create(CLOCK_MONOTONIC, &SigEvent, &this->LoginTimer) == -1){
error("TConnection::SetLoginTimer: Failed to create timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
return false;
}
@ -113,7 +113,7 @@ bool TConnection::SetLoginTimer(int Timeout){
TimerSpec.it_value.tv_sec = Timeout;
if(timer_settime(this->LoginTimer, 0, &TimerSpec, NULL) == -1){
error("TConnection::SetLoginTimer: Failed to start timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
return false;
}
@ -133,7 +133,7 @@ void TConnection::StopLoginTimer(void){
if(timer_delete(this->LoginTimer) == -1){
error("TConnection::StopLoginTimer: Failed to delete timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
}
this->LoginTimer = 0;

View File

@ -857,6 +857,7 @@ struct TPlayer: TCreature {
void SetQuestValue(int QuestNr, int Value);
void CheckOutfit(void);
void CheckState(void);
void SyncState(void);
void AddBuddy(const char *Name);
void RemoveBuddy(uint32 CharacterID);
void SendBuddies(void);

View File

@ -1016,7 +1016,7 @@ void TCreature::ToDoGo(int DestX, int DestY, int DestZ, bool MustReach, int MaxS
// NOTE(fusion): The number of steps between two points is the same as the
// their manhattan distance, if we exclude diagonal movement. We can skip
// the path finder if we know we're step away from the destination.
// the path finder if we know we're one step away from the destination.
if(DistanceX + DistanceY == 1){
TToDoEntry TD = {};
TD.Code = TDGo;

View File

@ -2082,7 +2082,7 @@ void ProcessMonsterRaids(void){
|| ItemType.getFlag(WEAROUT)
|| ItemType.getFlag(EXPIRE)
|| ItemType.getFlag(EXPIRESTOP)){
Item = Create(Bag, ItemType, 0);
Item = Create(Bag, ItemType, Amount);
}else{
Item = CreateAtCreature(Creature->ID, ItemType, Amount);
}

View File

@ -1760,9 +1760,17 @@ void TNPC::IdleStimulus(void){
case 3: DestY += 1; break;
}
if(this->MovePossible(DestX, DestY, DestZ, true, false)){
DestFound = true;
break;
// NOTE(fusion): TNPC::MovePossible in particular doesn't throw
// exceptions so we don't need a try-catch block here like in the
// case of TMonster::IdleStimulus, nor does the original function
// has it. That said, it's still better to be safe.
try{
if(this->MovePossible(DestX, DestY, DestZ, true, false)){
DestFound = true;
break;
}
}catch(RESULT r){
// no-op
}
}
@ -1976,8 +1984,8 @@ TMonster::TMonster(int Race, int x, int y, int z, int Home, uint32 MasterID) :
this->starty = y;
this->startz = z;
this->posx = x;
this->posx = y;
this->posx = z;
this->posy = y;
this->posz = z;
this->State = IDLE;
this->Home = Home;
this->Master = MasterID;
@ -2050,7 +2058,7 @@ TMonster::TMonster(int Race, int x, int y, int z, int Home, uint32 MasterID) :
|| ItemType.getFlag(WEAROUT)
|| ItemType.getFlag(EXPIRE)
|| ItemType.getFlag(EXPIRESTOP)){
Item = Create(Bag, ItemType, 0);
Item = Create(Bag, ItemType, Amount);
}else{
Item = CreateAtCreature(this->ID, ItemType, Amount);
}
@ -2384,13 +2392,14 @@ void TMonster::IdleStimulus(void){
int TalkNr = random(1, RaceData[this->Race].Talks);
const char *Text = GetDynamicString(*RaceData[this->Race].Talk.at(TalkNr));
if(Text != 0 && Text[0] != 0){
// TODO(fusion): We were only checking for a `#` but it could be
// problematic for any two character nul terminated string, since
// adding 3 would make it point out of bounds. Poor string management
// is a recurring theme.
// NOTE(fusion): The original would only check for a leading '#' before
// advancing 3 characters. This would be a problem for any 2 character
// string since we'd end up pointing beyond the null terminator.
int Mode = TALK_ANIMAL_LOW;
if(Text[0] == '#' && (Text[1] == 'y' || Text[1] == 'Y') && Text[2] == ' '){
Mode = TALK_ANIMAL_LOUD;
if(Text[0] == '#' && Text[1] != 0 && Text[2] == ' '){
if(Text[1] == 'y' || Text[1] == 'Y'){
Mode = TALK_ANIMAL_LOUD;
}
Text += 3;
}
@ -2450,7 +2459,7 @@ void TMonster::IdleStimulus(void){
int DistanceX = std::abs(Target->posx - this->posx);
int DistanceY = std::abs(Target->posy - this->posy);
if((Target->posz != this->posz || DistanceX > 10 || DistanceY > 10)
||(Target->Type == PLAYER && CheckRight(Target->ID, IGNORED_BY_MONSTERS))
|| (Target->Type == PLAYER && CheckRight(Target->ID, IGNORED_BY_MONSTERS))
|| (Target->IsInvisible() && !RaceData[this->Race].SeeInvisible)
|| IsProtectionZone(Target->posx, Target->posy, Target->posz)
|| IsHouse(Target->posx, Target->posy, Target->posz)
@ -2475,7 +2484,7 @@ void TMonster::IdleStimulus(void){
// NOTE(fusion): We're looking for the creature that maximizes
// the strategy parameter.
int TieBreaker = random(0, 99);
if(StrategyParam >= BestStrategyParam
if(StrategyParam > BestStrategyParam
||(StrategyParam == BestStrategyParam && TieBreaker > BestTieBreaker)){
this->Target = TargetID;
BestTieBreaker = TieBreaker;
@ -2702,7 +2711,7 @@ void TMonster::IdleStimulus(void){
// TODO(fusion): We're doing something similar to `TCombat::CanToDoAttack`
// with added random steps around the attacking position. This function is
// too convoluted and we should definitely split it into smaller ones. I
// don't have a problem with large functions but this it too much.
// don't have a problem with large functions but this is too much.
// NOTE(fusion): Max distance.
int Distance = std::max<int>(
@ -2777,6 +2786,8 @@ void TMonster::IdleStimulus(void){
if(this->State == ATTACKING || this->State == PANIC){
this->Rotate(Target);
// TODO(fusion): This condition should be always true because we're
// already running on the ELSE branch of `this->Master == this->Target`.
if(this->Master != this->Target){
this->ToDoAttack();
}else{
@ -2832,6 +2843,9 @@ void TMonster::IdleStimulus(void){
this->ToDoWait(1000);
this->ToDoStart();
}catch(RESULT r){
// TODO(fusion): This shouldn't be possible? Otherwise the creature
// might become stuck since we're not calling ToDoStart or ToDoYield.
// Probably just fallback to ToDoWait(1000)?
error("TMonster::IdleStimulus: Exception %d.\n", r);
}
}else{
@ -2936,7 +2950,7 @@ bool TMonster::KickCreature(TCreature *Creature){
print(3, "%s verschiebt %s.\n", this->Name, Creature->Name);
// TODO(fusion): Declare these here so they can be used within the catch
// NOTE(fusion): Declare these here so they can be used within the catch
// block to print out what's on the last position we tried to move the
// creature to.
int DestX = Creature->posx;

View File

@ -205,7 +205,7 @@ TPlayer::TPlayer(TConnection *Connection, uint32 CharacterID):
SendAmbiente(Connection);
AnnounceChangedCreature(CharacterID, CREATURE_LIGHT_CHANGED);
SendPlayerSkills(Connection);
this->CheckState();
this->SyncState();
this->SendBuddies();
if(PlayerData->LastLoginTime != 0){
@ -750,7 +750,6 @@ void TPlayer::TakeOver(TConnection *Connection){
strcpy(this->Guild, PlayerData->Guild);
strcpy(this->Rank, PlayerData->Rank);
strcpy(this->Title, PlayerData->Title);
this->OldState = 0;
this->CheckOutfit();
this->ClearRequest();
@ -770,7 +769,7 @@ void TPlayer::TakeOver(TConnection *Connection){
SendAmbiente(Connection);
SendPlayerData(Connection);
SendPlayerSkills(Connection);
this->CheckState();
this->SyncState();
this->SendBuddies();
}
@ -1254,6 +1253,16 @@ void TPlayer::CheckState(void){
}
}
void TPlayer::SyncState(void){
// NOTE(fusion): This is only called after a new connection is established,
// when we know that the current client state is ZERO but `OldState` may not.
// It'll typically be the case in `TPlayer::TakeOver` but can also happen
// in the player constructor if `TPlayer::CheckState` is called from within
// any helper function before `TConnection::EnterGame` (see `BeginSendData`).
this->OldState = 0;
this->CheckState();
}
void TPlayer::AddBuddy(const char *Name){
if(Name == NULL){
error("TPlayer::AddBuddy: Name ist NULL.\n");
@ -2845,7 +2854,7 @@ void AttachPlayerPoolSlot(TPlayerData *Slot, bool DontWait){
}
PlayerDataPoolMutex.up();
DelayThread(0,100);
DelayThread(0, 100);
}
}

View File

@ -107,9 +107,6 @@ void TSkill::Load(int Act, int Max, int Min, int DAct, int MDAct,
TCreature *Master = this->Master;
if(Master && Cycle != 0){
// NOTE(fusion): It seems we had `TSkillBase::SetTimer` inlined here.
// For whatever reason I hadn't noticed the error message referencing
// it, LOL.
Master->SetTimer(this->SkNr, Cycle, Count, MaxCount, FactorPercent);
}
}

View File

@ -798,8 +798,8 @@ static Object CreateTempDepot(void){
return TempDepot;
}
// NOTE(fusion): This is used inside house processing functions to empty the depot
// container after processing a player's depot.
// NOTE(fusion): This is used by house processing functions to clear the temporary
// depot container after processing player depots.
static void DeleteContainerObjects(Object Con){
Object Obj = GetFirstContainerObject(Con);
while(Obj != NONE){

View File

@ -768,19 +768,14 @@ bool SearchFreeField(int *x, int *y, int *z, int Distance, uint16 HouseID, bool
int FieldY = *y + OffsetY;
int FieldZ = *z;
// TODO(fusion): This is probably some form of the `TCreature::MovePossible`
// function inlined.
bool MovePossible;
bool MovePossible = false;
if(Jump){
MovePossible = JumpPossible(FieldX, FieldY, FieldZ, true);
}else{
MovePossible = CoordinateFlag(FieldX, FieldY, FieldZ, BANK)
&& !CoordinateFlag(FieldX, FieldY, FieldZ, UNPASS);
// TODO(fusion): This one I'm not so sure.
if(MovePossible && CoordinateFlag(FieldX, FieldY, FieldZ, AVOID)){
MovePossible = CoordinateFlag(FieldX, FieldY, FieldZ, BED);
}
&& !CoordinateFlag(FieldX, FieldY, FieldZ, UNPASS)
&& (!CoordinateFlag(FieldX, FieldY, FieldZ, AVOID)
|| CoordinateFlag(FieldX, FieldY, FieldZ, BED));
}
if(MovePossible){
@ -792,11 +787,9 @@ bool SearchFreeField(int *x, int *y, int *z, int Distance, uint16 HouseID, bool
}
}
// NOTE(fusion): We're spiraling out from the initial coordinate.
// TODO(fusion): This function used directions different from the ones
// used by creatures and defined in `enums.hh` so I made it use them
// instead, LOL.
// NOTE(fusion): We're spiraling out from the initial coordinate. The
// original function used direction values different from the ones used
// by creatures and defined in `enums.hh` so I changed it for consistency.
if(CurrentDirection == DIRECTION_NORTH){
OffsetY -= 1;
if(OffsetY <= -CurrentDistance){
@ -979,9 +972,8 @@ bool SearchSpawnField(int *x, int *y, int *z, int Distance, bool Player){
if(ObjType.getFlag(UNMOVE)){
ExpansionPossible = false;
LoginPossible = LoginPossible && !Player;
}else{
LoginBad = true;
}
LoginBad = true;
}
Obj = Obj.getNextObject();

View File

@ -4119,7 +4119,7 @@ void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest){
throw NOCREATURE;
}
int Amount = ComputeDamage(Actor, SpellNr, 250, 30);
int Amount = ComputeDamage(Actor, SpellNr, 250, 0);
Heal(Target, -1, 0, Amount); // -1 ?
break;
}

View File

@ -53,7 +53,8 @@ static void SigBlock(int SigNr){
sigaddset(&Set, SigNr);
if(sigprocmask(SIG_BLOCK, &Set, NULL) == -1){
error("SigBlock: Failed to block signal %d (%s): (%d) %s\n",
SigNr, sigdescr_np(SigNr), errno, strerrordesc_np(errno));
SigNr, GetSignalDescription(SigNr),
errno, GetErrorDescription(errno));
}
}
@ -63,18 +64,18 @@ static void SigWaitAny(void){
sigsuspend(&Set);
}
static void SigHupHandler(int signr){
static void SigHupHandler(int SigNr){
// no-op (?)
}
static void SigAbortHandler(int signr){
static void SigAbortHandler(int SigNr){
print(1, "SigAbortHandler: schalte Writer-Thread ab.\n");
AbortWriter();
}
static void DefaultHandler(int signr){
static void DefaultHandler(int SigNr){
print(1, "DefaultHandler: Beende Game-Server (SigNr. %d: %s).\n",
signr, sigdescr_np(signr));
SigNr, GetSignalDescription(SigNr));
SigHandler(SIGINT, SIG_IGN);
SigHandler(SIGQUIT, SIG_IGN);
@ -83,8 +84,8 @@ static void DefaultHandler(int signr){
SigHandler(SIGXFSZ, SIG_IGN);
SigHandler(SIGPWR, SIG_IGN);
SaveMapOn = (signr == SIGQUIT) || (signr == SIGTERM) || (signr == SIGPWR);
if(signr == SIGTERM){
SaveMapOn = (SigNr == SIGQUIT) || (SigNr == SIGTERM) || (SigNr == SIGPWR);
if(SigNr == SIGTERM){
int Hour, Minute;
GetRealTime(&Hour, &Minute);
RebootTime = (Hour * 60 + Minute + 6) % 1440;
@ -98,8 +99,8 @@ static void DefaultHandler(int signr){
#if 0
// TODO(fusion): This function was exported in the binary but not referenced anywhere.
static void ErrorHandler(int signr){
error("ErrorHandler: SigNr. %d: %s\n", signr, sigdescr_np(signr));
static void ErrorHandler(int SigNr){
error("ErrorHandler: SigNr. %d: %s\n", SigNr, GetSignalDescription(SigNr));
EndGame();
LogoutAllPlayers();
exit(EXIT_FAILURE);
@ -151,7 +152,7 @@ static void InitTime(void){
SigEvent.sigev_notify_thread_id = gettid();
if(timer_create(CLOCK_MONOTONIC, &SigEvent, &BeatTimer) == -1){
error("InitTime: Failed to create beat timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
throw "cannot create beat timer";
}
@ -161,7 +162,7 @@ static void InitTime(void){
TimerSpec.it_value = TimerSpec.it_interval;
if(timer_settime(BeatTimer, 0, &TimerSpec, NULL) == -1){
error("InitTime: Failed to start beat timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
throw "cannot start beat timer";
}
}
@ -169,7 +170,7 @@ static void InitTime(void){
static void ExitTime(void){
if(timer_delete(BeatTimer) == -1){
error("ExitTime: Failed to delete beat timer: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
}
SigHandler(SIGALRM, SIG_IGN);
@ -448,7 +449,7 @@ static void AdvanceGame(int Delay){
SendAll();
}
static void SigUsr1Handler(int signr){
static void SigUsr1Handler(int SigNr){
SigUsr1Counter += 1;
}
@ -508,7 +509,12 @@ static bool DaemonInit(bool NoFork){
}
umask(0177);
chdir(SAVEPATH);
// NOTE(fusion): The original binary would change directories to `SAVEPATH`
// here, but because this function is called very early at startup, no config
// values would have been loaded, leaving `SAVEPATH` empty and causing this
// next call to `chdir` to always fail with ENOENT.
//chdir(SAVEPATH);
int OpenMax = sysconf(_SC_OPEN_MAX);
if(OpenMax < 0){

View File

@ -1569,6 +1569,10 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector,
}
if(IN.Token == BYTES){
if(OffsetX != -1 && OffsetY != -1 && !FieldPatched[OffsetX][OffsetY]){
OUT.writeLn();
}
uint8 *SectorOffset = IN.getBytesequence();
OffsetX = (int)SectorOffset[0];
OffsetY = (int)SectorOffset[1];
@ -1634,6 +1638,10 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector,
IN.error("unknown map flag");
}
}
if(OffsetX != -1 && OffsetY != -1 && !FieldPatched[OffsetX][OffsetY]){
OUT.writeLn();
}
}
// NOTE(fusion): Step 4.
@ -1705,7 +1713,7 @@ void PatchSector(int SectorX, int SectorY, int SectorZ, bool FullSector,
if(rename(FileNameBak, FileName) != 0){
int ErrCode = errno;
error("PatchSector: Fehler %d beim Umbenennen von %s.\n", ErrCode, FileNameBak);
error("# Fehler %d: %s.\n", ErrCode, strerror(ErrCode));
error("# Fehler %d: %s.\n", ErrCode, GetErrorDescription(ErrCode));
throw "cannot patch ORIGMAP";
}
}

View File

@ -2367,7 +2367,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text,
continue;
}
if(DistanceX > 1 && DistanceY > 1){
if(DistanceX > 1 || DistanceY > 1){
SendTalk(Spectator->Connection, 0, Creature->Name, Mode,
Creature->posx, Creature->posy, Creature->posz,
"pspsps");
@ -2406,7 +2406,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text,
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
if(Connection->Live()){
SendMessage(Connection, TALK_ADMIN_MESSAGE, Text);
SendMessage(Connection, TALK_ADMIN_MESSAGE, "%s", Text);
}
Connection = GetNextConnection();

View File

@ -95,7 +95,7 @@ void TQueryManagerConnection::connect(void){
int SocketFlags = fcntl(this->Socket, F_GETFL);
if(SocketFlags == -1 || fcntl(this->Socket, F_SETFL, (SocketFlags | O_NONBLOCK)) == -1){
error("TQueryManagerConnection::connect: Failed to set socket as non-blocking: (%d) %s\n",
errno, strerrordesc_np(errno));
errno, GetErrorDescription(errno));
this->disconnect();
continue;
}

View File

@ -92,7 +92,7 @@ TReadScriptFile::~TReadScriptFile(void){
void TReadScriptFile::open(const char *FileName){
int Depth = this->RecursionDepth + 1;
if((Depth + 1) >= NARRAY(this->File)){
if(Depth >= NARRAY(this->File)){
::error("TReadScriptFile::open: Rekursionstiefe zu groß.\n");
throw "Recursion depth too high";
}
@ -115,7 +115,7 @@ void TReadScriptFile::open(const char *FileName){
if(this->File[Depth] == NULL){
int ErrCode = errno;
::error("TReadScriptFile::open: Kann Datei %s nicht öffnen.\n", this->Filename[Depth]);
::error("Fehler %d: %s.\n", ErrCode, strerror(ErrCode));
::error("Fehler %d: %s.\n", ErrCode, GetErrorDescription(ErrCode));
throw "Cannot open script-file";
}
@ -139,7 +139,7 @@ void TReadScriptFile::close(void){
void TReadScriptFile::error(const char *Text){
int Depth = this->RecursionDepth;
ASSERT(Depth >= 0 && Depth <= NARRAY(this->File));
ASSERT(Depth >= 0 && Depth < NARRAY(this->File));
const char *Filename = this->Filename[Depth];
if(const char *Slash = findLast(this->Filename[Depth], '/')){
@ -582,7 +582,7 @@ void TWriteScriptFile::open(const char *FileName){
if(this->File == NULL){
int ErrCode = errno;
::error("TWriteScriptFile: Kann Datei %s nicht anlegen.\n", FileName);
::error("Fehler %d: %s.\n", ErrCode, strerror(ErrCode));
::error("Fehler %d: %s.\n", ErrCode, GetErrorDescription(ErrCode));
throw "Cannot create script-file";
}
@ -751,7 +751,7 @@ void TReadBinaryFile::close(void){
int ErrCode = errno;
::error("TReadBinaryFile::close: Fehler beim Schließen der Datei.\n");
::error("# Datei: %s, Fehlercode: %d (%s)\n",
this->Filename, ErrCode, strerror(ErrCode));
this->Filename, ErrCode, GetErrorDescription(ErrCode));
}
this->File = NULL;
}
@ -811,7 +811,7 @@ uint8 TReadBinaryFile::readByte(void){
int Position = this->getPosition();
::error("TReadBinaryFile::readByte: Fehler beim Lesen eines Bytes\n");
::error("# Datei: %s, Position: %d, Rückgabewert: %d, Fehlercode: %d (%s)\n",
this->Filename, Position, Result, ErrCode, strerror(ErrCode));
this->Filename, Position, Result, ErrCode, GetErrorDescription(ErrCode));
// NOTE(fusion): Close file and make a backup, possibly for further inspection.
if(fclose(this->File) != 0){
@ -832,7 +832,7 @@ void TReadBinaryFile::readBytes(uint8 *Buffer, int Count){
int Position = this->getPosition();
::error("TReadBinaryFile::readBytes: Fehler beim Lesen von %d Bytes\n", Count);
::error("# Datei: %s, Position %d, Rückgabewert: %d, Fehlercode: %d (%s)\n",
this->Filename, Position, Result, ErrCode, strerror(ErrCode));
this->Filename, Position, Result, ErrCode, GetErrorDescription(ErrCode));
// NOTE(fusion): Close file and make a backup, possibly for further inspection.
if(fclose(this->File) != 0){
@ -885,7 +885,7 @@ void TWriteBinaryFile::open(const char *FileName){
if(this->File == NULL){
int ErrCode = errno;
::error("TWriteBinaryFile::open: Kann Datei %s nicht anlegen.\n", FileName);
::error("Fehler %d: %s.\n", ErrCode, strerror(ErrCode));
::error("Fehler %d: %s.\n", ErrCode, GetErrorDescription(ErrCode));
snprintf(ErrorString, sizeof(ErrorString),
"Cannot create file %s.", FileName);
@ -902,7 +902,7 @@ void TWriteBinaryFile::close(void){
int ErrCode = errno;
::error("TWriteBinaryFile::close: Fehler beim Schließen der Datei.\n");
::error("# Datei: %s, Fehlercode: %d (%s)\n",
this->Filename, ErrCode, strerror(ErrCode));
this->Filename, ErrCode, GetErrorDescription(ErrCode));
}
this->File = NULL;
}
@ -928,7 +928,7 @@ void TWriteBinaryFile::writeByte(uint8 Byte){
int ErrCode = errno;
::error("TWriteBinaryFile::writeByte: Fehler beim Schreiben eines Bytes\n");
::error("# Datei: %s, Rückgabewert: %d, Fehlercode: %d (%s)\n",
this->Filename, Result, ErrCode, strerror(ErrCode));
this->Filename, Result, ErrCode, GetErrorDescription(ErrCode));
// NOTE(fusion): Close file and make a backup, possibly for further inspection.
if(fclose(this->File) != 0){
@ -947,7 +947,7 @@ void TWriteBinaryFile::writeBytes(const uint8 *Buffer, int Count){
int ErrCode = errno;
::error("TWriteBinaryFile::writeBytes: Fehler beim Schreiben von %d Bytes\n", Count);
::error("# Datei: %s, Rückgabewert: %d, Fehlercode: %d (%s)\n",
this->Filename, Result, ErrCode, strerror(ErrCode));
this->Filename, Result, ErrCode, GetErrorDescription(ErrCode));
// NOTE(fusion): Close file and make a backup, possibly for further inspection.
if(fclose(this->File) != 0){

View File

@ -349,7 +349,7 @@ void SendResult(TConnection *Connection, RESULT r){
}
if(Message != NULL){
SendMessage(Connection, TALK_FAILURE_MESSAGE, Message);
SendMessage(Connection, TALK_FAILURE_MESSAGE, "%s", Message);
if(r == ENTERPROTECTIONZONE || r == NOTINVITED || r == MOVENOTPOSSIBLE){
SendSnapback(Connection);
}
@ -1721,7 +1721,7 @@ void BroadcastMessage(int Mode, const char *Text, ...){
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
if(Connection->Live()){
SendMessage(Connection, Mode, Message);
SendMessage(Connection, Mode, "%s", Message);
}
Connection = GetNextConnection();
}

View File

@ -126,7 +126,7 @@ static void ErrorHandler(const char *Text){
if(SHM != NULL){
SHM->Errors += 1;
if(SHM->Errors <= 0x8000){
Log("error", Text);
Log("error", "%s", Text);
if(SHM->Errors == 0x8000){
Log("error", "Zu viele Fehler. Keine weitere Protokollierung.\n");
}

View File

@ -146,10 +146,10 @@ Semaphore::~Semaphore(void){
int ErrorCode;
if((ErrorCode = pthread_mutex_destroy(&this->mutex)) != 0){
error("Semaphore::~Semaphore: Kann Mutex nicht freigeben: (%d) %s.\n",
ErrorCode, strerrordesc_np(ErrorCode));
ErrorCode, GetErrorDescription(ErrorCode));
}else if((ErrorCode = pthread_cond_destroy(&this->condition)) != 0){
error("Semaphore::~Semaphore: Kann Wartebedingung nicht freigeben: (%d) %s.\n",
ErrorCode, strerrordesc_np(ErrorCode));
ErrorCode, GetErrorDescription(ErrorCode));
}
}

View File

@ -1,5 +1,6 @@
#include "common.hh"
#include <signal.h>
#include <sys/stat.h>
static TErrorFunction *ErrorFunction;
@ -95,6 +96,46 @@ bool FileExists(const char *FileName){
return Result;
}
#if !defined(_GNU_SOURCE) || (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 32))
# if defined(_BSD_SOURCE) || defined(_DEFAULT_SOURCE)
static const char *sigdescr_np(int SigNr){
const char *Description = NULL;
if(SigNr >= 0 && SigNr < NSIG){
Description = sys_siglist[SigNr];
}
return Description;
}
static const char *strerrordesc_np(int ErrCode){
const char *Description = NULL;
if(ErrCode >= 0 && ErrCode < sys_nerr){
Description = sys_errlist[ErrCode];
}
return Description;
}
# else // defined(_BSD_SOURCE) || defined(_DEFAULT_SOURCE)
#warning "Current LIBC/GLIBC version doesn't expose error/signal description tables."
static const char *sigdescr_np(int SigNr){ return "No signal description"; }
static const char *strerrordesc_np(int ErrCode){ return "No error description"; }
# endif // defined(_BSD_SOURCE) || defined(_DEFAULT_SOURCE)
#endif //!defined(_GNU_SOURCE) || (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 32))
const char *GetSignalDescription(int SigNr){
const char *Description = sigdescr_np(SigNr);
if(Description == NULL){
Description = "Invalid signal";
}
return Description;
}
const char *GetErrorDescription(int ErrCode){
const char *Description = strerrordesc_np(ErrCode);
if(Description == NULL){
Description = "Invalid error";
}
return Description;
}
// String Utility
// =============================================================================
bool isSpace(int c){

View File

@ -921,7 +921,7 @@ void ProcessBroadcastReply(TBroadcastReplyData *Data){
return;
}
BroadcastMessage(TALK_STATUS_MESSAGE, Data->Message);
BroadcastMessage(TALK_STATUS_MESSAGE, "%s", Data->Message);
delete Data;
}

View File

@ -19,7 +19,7 @@ RestartSec=10
# After a SIGTERM, the server will issue shutdown warnings for 5 minutes and
# take another minute or two to save everything and exit. Not waiting for this
# process to complete would cause data to not be properly saved.
TimeoutStopSec=600
TimeoutStopSec=900
LimitCORE=infinity
StandardOutput=journal
StandardError=journal

View File

@ -30,6 +30,7 @@ var (
releaseOptions = []string{"-O2"}
linkerOptions = []string{
"-Wl,-t",
"-lrt",
"-lcrypto",
}
)
@ -80,9 +81,9 @@ func main() {
// DEBUG SWITCH
fmt.Fprint(&output, "DEBUG ?= 0\n")
fmt.Fprint(&output, "ifneq ($(DEBUG), 0)\n")
fmt.Fprintf(&output, "\tCFLAGS += %v\n", strings.Join(debugOptions, " "))
fmt.Fprintf(&output, " CFLAGS += %v\n", strings.Join(debugOptions, " "))
fmt.Fprint(&output, "else\n")
fmt.Fprintf(&output, "\tCFLAGS += %v\n", strings.Join(releaseOptions, " "))
fmt.Fprintf(&output, " CFLAGS += %v\n", strings.Join(releaseOptions, " "))
fmt.Fprint(&output, "endif\n\n")
// HEADERS