diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-11-18 15:35:22 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-11-18 15:35:22 -0300 |
| commit | 6036d4f37ad97ee1a9a830b36fb2edf9b82e3a3b (patch) | |
| tree | ce91fb0a9acd8d95fbbe58a9d19664162a30a7cf /src/status.cc | |
| parent | b101f3809e509df16c71e1415e9ebb0c687601b8 (diff) | |
| download | login-6036d4f37ad97ee1a9a830b36fb2edf9b82e3a3b.tar.gz login-6036d4f37ad97ee1a9a830b36fb2edf9b82e3a3b.zip | |
overall improvements + support STATUS requests
Diffstat (limited to 'src/status.cc')
| -rw-r--r-- | src/status.cc | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/src/status.cc b/src/status.cc new file mode 100644 index 0000000..cb960b8 --- /dev/null +++ b/src/status.cc @@ -0,0 +1,222 @@ +#include "common.hh" + +// IMPORTANT(fusion): We want connections to use the status string directly for +// output, but because it could be refreshed while connections are still using +// it, we need to have them buffered, to allow old versions to remain valid, at +// least for a couple refreshes. Note that ` + +static int g_LastStatusRefresh = 0; +static int g_StatusStringIndex = 0; +static char g_StatusString[3][KB(2)]; + +struct XMLBuffer{ + char *Data; + int Size; + int Position; +}; + +static void XMLNullTerminate(XMLBuffer *Buffer){ + ASSERT(Buffer->Size > 0); + if(Buffer->Position < Buffer->Size){ + Buffer->Data[Buffer->Position] = 0; + }else{ + Buffer->Data[Buffer->Size - 1] = 0; + } +} + +static void XMLAppendChar(XMLBuffer *Buffer, char Ch){ + if(Buffer->Position < Buffer->Size){ + Buffer->Data[Buffer->Position] = Ch; + } + Buffer->Position += 1; +} + +static void XMLAppendNumber(XMLBuffer *Buffer, int64 Num){ + if(Num == 0){ + XMLAppendChar(Buffer, '0'); + return; + } + + if(Num < 0){ + XMLAppendChar(Buffer, '-'); + Num = -Num; + } + + char String[64] = {}; + int StringLen = 0; + while(Num > 0){ + ASSERT(StringLen < (int)sizeof(String)); + String[StringLen] = (Num % 10) + '0'; + Num = (Num / 10); + StringLen += 1; + } + + for(int i = 0; i < StringLen; i += 1){ + XMLAppendChar(Buffer, String[(StringLen - 1) - i]); + } +} + +static void XMLAppendString(XMLBuffer *Buffer, const char *String){ + const char *P = String; + while(P[0]){ + XMLAppendChar(Buffer, P[0]); + P += 1; + } +} + +static void XMLAppendStringEscaped(XMLBuffer *Buffer, const char *String){ + const char *P = String; + while(P[0]){ + switch(P[0]){ + case '\t': XMLAppendString(Buffer, "	"); break; + case '\n': XMLAppendString(Buffer, " "); break; + case '"': XMLAppendString(Buffer, """); break; + case '&': XMLAppendString(Buffer, "&"); break; + case '\'': XMLAppendString(Buffer, "'"); break; + case '<': XMLAppendString(Buffer, "<"); break; + case '>': XMLAppendString(Buffer, ">"); break; + default: XMLAppendChar(Buffer, P[0]); break; + } + P += 1; + } +} + +static void XMLAppendStringFV(XMLBuffer *Buffer, const char *Format, va_list Args){ + const char *P = Format; + while(P[0]){ + // IMPORTANT(fusion): This function only implements a small subset of + // the printf syntax. + if(P[0] == '%'){ + P += 1; + + // IMPORTANT(fusion): Small integral parameters such as char and short + // are promoted to int, similar to how they're promoted in arithmetic + // operations. This means there are only really 4 and 8 bytes integers. + int Length = 4; + if(P[0] == 'h' && P[1] == 'h'){ + P += 2; + }else if(P[0] == 'l' && P[1] == 'l'){ + Length = 8; + P += 2; + }else if(P[0] == 'h'){ + P += 1; + }else if(P[0] == 'l'){ + Length = 8; + P += 1; + } + + switch(P[0]){ + case '%':{ + XMLAppendChar(Buffer, '%'); + break; + } + + case 'c':{ + int Ch = va_arg(Args, int); + XMLAppendChar(Buffer, (char)Ch); + break; + } + + case 'd':{ + int64 Num = (Length == 4 ? va_arg(Args, int) : va_arg(Args, int64)); + XMLAppendNumber(Buffer, Num); + break; + } + + case 's':{ + const char *String = va_arg(Args, const char*); + XMLAppendStringEscaped(Buffer, String); + break; + } + + default:{ + LOG_ERR("Invalid XML format specifier \"%c\"", P[0]); + break; + } + } + + if(P[0]){ + P += 1; + } + + }else{ + XMLAppendChar(Buffer, P[0]); + P += 1; + } + } +} + +static void XMLAppendStringF(XMLBuffer *Buffer, const char *Format, ...){ + va_list Args; + va_start(Args, Format); + XMLAppendStringFV(Buffer, Format, Args); + va_end(Args); +} + +const char *GetStatusString(void){ + int TimeNow = (int)time(NULL); + if((TimeNow - g_LastStatusRefresh) >= g_Config.MinStatusInterval){ + const char *WorldName = ""; + int Uptime = 0; + int NumPlayers = 0; + int MaxPlayers = 0; + int OnlinePeak = 0; + + TWorld World = {}; + if(GetWorld(g_Config.StatusWorld, &World) == 0){ + WorldName = World.Name; + if(World.LastStartup != 0 && World.LastStartup > World.LastShutdown){ + Uptime = (int)time(NULL) - World.LastStartup; + } + NumPlayers = World.NumPlayers; + MaxPlayers = World.MaxPlayers; + OnlinePeak = World.OnlinePeak; + + // IMPORTANT(fusion): This could be a common behaviour but, on OTSERVLIST, + // the server will show as OFFLINE if the the online peak is less than + // the number of online players. This shouldn't usually be a problem since + // the online character list and online peak are updated together in the + // same CREATE_PLAYERLIST query, but is something to keep in mind. + if(OnlinePeak < NumPlayers){ + OnlinePeak = NumPlayers; + } + }else{ + LOG_ERR("Failed to query world data..."); + } + + // NOTE(fusion): Skip line with MOTD hash. + const char *Motd = g_Config.Motd; + while(Motd[0]){ + if(Motd[0] == '\n'){ + Motd += 1; + break; + } + Motd += 1; + } + + g_StatusStringIndex = (g_StatusStringIndex + 1) % NARRAY(g_StatusString); + XMLBuffer Buffer = {}; + Buffer.Data = g_StatusString[g_StatusStringIndex]; + Buffer.Size = sizeof(g_StatusString[g_StatusStringIndex]); + XMLAppendString(&Buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + XMLAppendString(&Buffer, "<tsqp version=\"1.0\">"); + XMLAppendStringF(&Buffer, + "<serverinfo servername=\"%s\" uptime=\"%d\" url=\"%s\"" + " location=\"%s\" server=\"%s\" version=\"%s\"" + " client=\"%s\"/>", + WorldName, Uptime, g_Config.Url, g_Config.Location, + g_Config.ServerType, g_Config.ServerVersion, + g_Config.ClientVersion); + XMLAppendStringF(&Buffer, + "<players online=\"%d\" max=\"%d\" peak=\"%d\"/>", + NumPlayers, MaxPlayers, OnlinePeak); + XMLAppendStringF(&Buffer, "<motd>%s</motd>", Motd); + XMLAppendString(&Buffer, "</tsqp>"); + XMLNullTerminate(&Buffer); + + g_LastStatusRefresh = TimeNow; + } + + return g_StatusString[g_StatusStringIndex]; +} + |
