diff options
Diffstat (limited to 'src/engine')
25 files changed, 1670 insertions, 408 deletions
diff --git a/src/engine/client.h b/src/engine/client.h index 65ab761e..966e8f61 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -95,6 +95,7 @@ public: // remote console virtual void RconAuth(const char *pUsername, const char *pPassword) = 0; virtual bool RconAuthed() = 0; + virtual bool UseTempRconCommands() = 0; virtual void Rcon(const char *pLine) = 0; // server info diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 90cb2011..bec7d4d6 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1,5 +1,6 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <new> #include <stdlib.h> // qsort #include <stdarg.h> @@ -26,6 +27,7 @@ #include <engine/shared/compression.h> #include <engine/shared/datafile.h> #include <engine/shared/demo.h> +#include <engine/shared/filecollection.h> #include <engine/shared/mapchecker.h> #include <engine/shared/network.h> #include <engine/shared/packer.h> @@ -231,185 +233,6 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD } -bool CFileCollection::IsFilenameValid(const char *pFilename) -{ - if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength || - str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) || - str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt)) - return false; - - pFilename += m_FileDescLength; - if(pFilename[0] == '_' && - pFilename[1] >= '0' && pFilename[1] <= '9' && - pFilename[2] >= '0' && pFilename[2] <= '9' && - pFilename[3] >= '0' && pFilename[3] <= '9' && - pFilename[4] >= '0' && pFilename[4] <= '9' && - pFilename[5] == '-' && - pFilename[6] >= '0' && pFilename[6] <= '9' && - pFilename[7] >= '0' && pFilename[7] <= '9' && - pFilename[8] == '-' && - pFilename[9] >= '0' && pFilename[9] <= '9' && - pFilename[10] >= '0' && pFilename[10] <= '9' && - pFilename[11] == '_' && - pFilename[12] >= '0' && pFilename[12] <= '9' && - pFilename[13] >= '0' && pFilename[13] <= '9' && - pFilename[14] == '-' && - pFilename[15] >= '0' && pFilename[15] <= '9' && - pFilename[16] >= '0' && pFilename[16] <= '9' && - pFilename[17] == '-' && - pFilename[18] >= '0' && pFilename[18] <= '9' && - pFilename[19] >= '0' && pFilename[19] <= '9') - return true; - - return false; -} - -int64 CFileCollection::ExtractTimestamp(const char *pTimestring) -{ - int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[1]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[2]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[3]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[5]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[6]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[8]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[9]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[11]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[12]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[14]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[15]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[17]-'0'; Timestamp <<= 4; - Timestamp += pTimestring[18]-'0'; - - return Timestamp; -} - -void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring) -{ - pTimestring[19] = 0; - pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[16] = '-'; - pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[13] = '-'; - pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[10] = '_'; - pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[7] = '-'; - pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[4] = '-'; - pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4; - pTimestring[0] = (Timestamp&0xF)+'0'; -} - -void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) -{ - mem_zero(m_aTimestamps, sizeof(m_aTimestamps)); - m_NumTimestamps = 0; - m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES)); - str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc)); - m_FileDescLength = str_length(m_aFileDesc); - str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt)); - m_FileExtLength = str_length(m_aFileExt); - str_copy(m_aPath, pPath, sizeof(m_aPath)); - m_pStorage = pStorage; - - m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); -} - -void CFileCollection::AddEntry(int64 Timestamp) -{ - if(m_NumTimestamps == 0) - { - // empty list - m_aTimestamps[m_NumTimestamps++] = Timestamp; - } - else - { - // remove old file - if(m_NumTimestamps == m_MaxEntries) - { - char aBuf[512]; - char aTimestring[TIMESTAMP_LENGTH]; - BuildTimestring(m_aTimestamps[0], aTimestring); - str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); - m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); - } - - // add entry to the sorted list - if(m_aTimestamps[0] > Timestamp) - { - // first entry - if(m_NumTimestamps < m_MaxEntries) - { - mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64)); - m_aTimestamps[0] = Timestamp; - ++m_NumTimestamps; - } - } - else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp) - { - // last entry - if(m_NumTimestamps == m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64)); - m_aTimestamps[m_NumTimestamps-1] = Timestamp; - } - else - m_aTimestamps[m_NumTimestamps++] = Timestamp; - } - else - { - // middle entry - int Left = 0, Right = m_NumTimestamps-1; - while(Right-Left > 1) - { - int Mid = (Left+Right)/2; - if(m_aTimestamps[Mid] > Timestamp) - Right = Mid; - else - Left = Mid; - } - - if(m_NumTimestamps == m_MaxEntries) - { - mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64)); - m_aTimestamps[Right-1] = Timestamp; - } - else - { - mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64)); - m_aTimestamps[Right] = Timestamp; - ++m_NumTimestamps; - } - } - } -} - -int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) -{ - CFileCollection *pThis = static_cast<CFileCollection *>(pUser); - - // check for valid file name format - if(IsDir || !pThis->IsFilenameValid(pFilename)) - return 0; - - // extract the timestamp - int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1); - - // add the entry - pThis->AddEntry(Timestamp); - - return 0; -} - - CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta) { m_pEditor = 0; @@ -467,6 +290,7 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotD m_aServerAddressStr[0] = 0; mem_zero(m_aSnapshots, sizeof(m_aSnapshots)); + m_SnapshotStorage.Init(); m_RecivedSnapshots = 0; m_VersionInfo.m_State = CVersionInfo::STATE_INIT; @@ -536,11 +360,6 @@ void CClient::SendReady() SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); } -bool CClient::RconAuthed() -{ - return m_RconAuthed; -} - void CClient::RconAuth(const char *pName, const char *pPassword) { if(RconAuthed()) @@ -549,6 +368,7 @@ void CClient::RconAuth(const char *pName, const char *pPassword) CMsgPacker Msg(NETMSG_RCON_AUTH); Msg.AddString(pName, 32); Msg.AddString(pPassword, 32); + Msg.AddInt(1); SendMsgEx(&Msg, MSGFLAG_VITAL); } @@ -693,12 +513,12 @@ void CClient::Connect(const char *pAddress) ServerInfoRequest(); - if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, NETTYPE_ALL) != 0) + if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.NetType()) != 0) { char aBufMsg[256]; str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg); - net_host_lookup("localhost", &m_ServerAddress, NETTYPE_ALL); + net_host_lookup("localhost", &m_ServerAddress, m_NetClient.NetType()); } m_RconAuthed = 0; @@ -726,6 +546,7 @@ void CClient::DisconnectWithReason(const char *pReason) // m_RconAuthed = 0; + m_pConsole->DeregisterTempAll(); m_NetClient.Disconnect(pReason); SetState(IClient::STATE_OFFLINE); m_pMap->Unload(); @@ -1313,11 +1134,28 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) CMsgPacker Msg(NETMSG_PING_REPLY); SendMsgEx(&Msg, 0); } + else if(Msg == NETMSG_RCON_CMD_ADD) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); + } + else if(Msg == NETMSG_RCON_CMD_REM) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->DeregisterTemp(pName); + } else if(Msg == NETMSG_RCON_AUTH_STATUS) { int Result = Unpacker.GetInt(); if(Unpacker.Error() == 0) m_RconAuthed = Result; + m_UseTempRconCommands = Unpacker.GetInt(); + if(Unpacker.Error() != 0) + m_UseTempRconCommands = 0; } else if(Msg == NETMSG_RCON_LINE) { @@ -1799,7 +1637,7 @@ void CClient::VersionUpdate() { if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT) { - Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_BindAddr.type); + Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_NetClient.NetType()); m_VersionInfo.m_State = CVersionInfo::STATE_START; } else if(m_VersionInfo.m_State == CVersionInfo::STATE_START) @@ -1881,7 +1719,7 @@ void CClient::Run() Input()->Init(); // start refreshing addresses while we load - MasterServer()->RefreshAddresses(m_BindAddr.type); + MasterServer()->RefreshAddresses(m_NetClient.NetType()); // init the editor m_pEditor->Init(); @@ -2328,7 +2166,12 @@ void CClient::RegisterCommands() m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); } -static CClient m_Client; +static CClient *CreateClient() +{ + CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1)); + mem_zero(pClient, sizeof(CClient)); + return new(pClient) CClient; +} /* Server Time @@ -2359,9 +2202,10 @@ int main(int argc, const char **argv) // ignore_convention } #endif + CClient *pClient = CreateClient(); IKernel *pKernel = IKernel::Create(); - pKernel->RegisterInterface(&m_Client); - m_Client.RegisterInterfaces(); + pKernel->RegisterInterface(pClient); + pClient->RegisterInterfaces(); // create the components IEngine *pEngine = CreateEngine("Teeworlds"); @@ -2414,12 +2258,12 @@ int main(int argc, const char **argv) // ignore_convention pEngineMasterServer->Load(); // register all console commands - m_Client.RegisterCommands(); + pClient->RegisterCommands(); pKernel->RequestInterface<IGameClient>()->OnConsoleInit(); // init client's interfaces - m_Client.InitInterfaces(); + pClient->InitInterfaces(); // execute config file pConsole->ExecuteFile("settings.cfg"); @@ -2434,11 +2278,11 @@ int main(int argc, const char **argv) // ignore_convention // restore empty config strings to their defaults pConfig->RestoreStrings(); - m_Client.Engine()->InitLogfile(); + pClient->Engine()->InitLogfile(); // run the client dbg_msg("client", "starting..."); - m_Client.Run(); + pClient->Run(); // write down the config and quit pConfig->Save(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index ddcd1167..1504a4e4 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -51,36 +51,6 @@ public: }; -class CFileCollection -{ - enum - { - MAX_ENTRIES=1000, - TIMESTAMP_LENGTH=20, // _YYYY-MM-DD_HH-MM-SS - }; - - int64 m_aTimestamps[MAX_ENTRIES]; - int m_NumTimestamps; - int m_MaxEntries; - char m_aFileDesc[128]; - int m_FileDescLength; - char m_aFileExt[32]; - int m_FileExtLength; - char m_aPath[512]; - IStorage *m_pStorage; - - bool IsFilenameValid(const char *pFilename); - int64 ExtractTimestamp(const char *pTimestring); - void BuildTimestring(int64 Timestamp, char *pTimestring); - -public: - void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); - void AddEntry(int64 Timestamp); - - static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); -}; - - class CClient : public IClient, public CDemoPlayer::IListner { // needed interfaces @@ -118,7 +88,6 @@ class CClient : public IClient, public CDemoPlayer::IListner float m_FrameTimeHigh; int m_Frames; NETADDR m_ServerAddress; - NETADDR m_BindAddr; int m_WindowMustRefocus; int m_SnapCrcErrors; bool m_AutoScreenshotRecycle; @@ -129,6 +98,7 @@ class CClient : public IClient, public CDemoPlayer::IListner int m_AckGameTick; int m_CurrentRecvTick; int m_RconAuthed; + int m_UseTempRconCommands; // version-checking char m_aVersionStr[10]; @@ -221,7 +191,8 @@ public: void SendEnterGame(); void SendReady(); - virtual bool RconAuthed(); + virtual bool RconAuthed() { return m_RconAuthed != 0; } + virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; } void RconAuth(const char *pName, const char *pPassword); virtual void Rcon(const char *pCmd); diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp index 99f82b50..eca39edb 100644 --- a/src/engine/client/friends.cpp +++ b/src/engine/client/friends.cpp @@ -12,6 +12,7 @@ CFriends::CFriends() { mem_zero(m_aFriends, sizeof(m_aFriends)); + m_NumFriends = 0; } void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData) diff --git a/src/engine/client/graphics.cpp b/src/engine/client/graphics.cpp index 76fd3400..d1f0b8a8 100644 --- a/src/engine/client/graphics.cpp +++ b/src/engine/client/graphics.cpp @@ -179,7 +179,16 @@ CGraphics_OpenGL::CGraphics_OpenGL() void CGraphics_OpenGL::ClipEnable(int x, int y, int w, int h) { - //if(no_gfx) return; + if(x < 0) + w += x; + if(y < 0) + h += y; + + x = clamp(x, 0, ScreenWidth()); + y = clamp(y, 0, ScreenHeight()); + w = clamp(w, 0, ScreenWidth()-x); + h = clamp(h, 0, ScreenHeight()-y); + glScissor(x, ScreenHeight()-(y+h), w, h); glEnable(GL_SCISSOR_TEST); } diff --git a/src/engine/console.h b/src/engine/console.h index f8ec67b0..0abf4ad2 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -10,11 +10,21 @@ class IConsole : public IInterface MACRO_INTERFACE("console", 0) public: + // TODO: rework/cleanup enum { OUTPUT_LEVEL_STANDARD=0, OUTPUT_LEVEL_ADDINFO, - OUTPUT_LEVEL_DEBUG + OUTPUT_LEVEL_DEBUG, + + ACCESS_LEVEL_ADMIN=0, + ACCESS_LEVEL_MOD, + + TEMPCMD_NAME_LENGTH=32, + TEMPCMD_HELP_LENGTH=96, + TEMPCMD_PARAMS_LENGTH=16, + + MAX_PRINT_CB=4, }; // TODO: rework this interface to reduce the amount of virtual calls @@ -35,10 +45,18 @@ public: class CCommandInfo { + protected: + int m_AccessLevel; public: + CCommandInfo() { m_AccessLevel = ACCESS_LEVEL_ADMIN; } + virtual ~CCommandInfo() {} const char *m_pName; const char *m_pHelp; const char *m_pParams; + + virtual const CCommandInfo *NextCommandInfo(int AccessLevel, int FlagMask) const = 0; + + int GetAccessLevel() const { return m_AccessLevel; } }; typedef void (*FPrintCallback)(const char *pStr, void *pUser); @@ -46,12 +64,15 @@ public: typedef void (*FCommandCallback)(IResult *pResult, void *pUserData); typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData); - virtual CCommandInfo *GetCommandInfo(const char *pName, int FlagMask) = 0; - virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) = 0; + virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const = 0; + virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp) = 0; + virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) = 0; virtual void ParseArguments(int NumArgs, const char **ppArguments) = 0; - virtual void Register(const char *pName, const char *pParams, - int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; + virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) = 0; + virtual void RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp) = 0; + virtual void DeregisterTemp(const char *pName) = 0; + virtual void DeregisterTempAll() = 0; virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser) = 0; virtual void StoreCommands(bool Store) = 0; @@ -60,8 +81,11 @@ public: virtual void ExecuteLineStroked(int Stroke, const char *pStr) = 0; virtual void ExecuteFile(const char *pFilename) = 0; - virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) = 0; + virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0; + virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0; virtual void Print(int Level, const char *pFrom, const char *pStr) = 0; + + virtual void SetAccessLevel(int AccessLevel) = 0; }; extern IConsole *CreateConsole(int FlagMask); diff --git a/src/engine/masterserver.h b/src/engine/masterserver.h index 74a394dc..57433993 100644 --- a/src/engine/masterserver.h +++ b/src/engine/masterserver.h @@ -23,7 +23,6 @@ public: virtual int RefreshAddresses(int Nettype) = 0; virtual void Update() = 0; virtual int IsRefreshing() = 0; - virtual void DumpServers() = 0; virtual NETADDR GetAddr(int Index) = 0; virtual const char *GetName(int Index) = 0; virtual bool IsValid(int Index) = 0; diff --git a/src/engine/server.h b/src/engine/server.h index 28a97ecc..31134ca9 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -56,6 +56,8 @@ public: virtual bool IsAuthed(int ClientID) = 0; virtual void Kick(int ClientID, const char *pReason) = 0; + + virtual void DemoRecorder_HandleAutoStart() = 0; }; class IGameServer : public IInterface diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 0e43e73f..193547cc 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1,6 +1,7 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <base/math.h> #include <base/system.h> #include <engine/config.h> @@ -15,6 +16,8 @@ #include <engine/shared/config.h> #include <engine/shared/datafile.h> #include <engine/shared/demo.h> +#include <engine/shared/econ.h> +#include <engine/shared/filecollection.h> #include <engine/shared/mapchecker.h> #include <engine/shared/network.h> #include <engine/shared/packer.h> @@ -181,6 +184,7 @@ CServer::CServer() : m_DemoRecorder(&m_SnapshotDelta) m_MapReload = 0; m_RconClientID = -1; + m_RconAuthLevel = AUTHED_ADMIN; Init(); } @@ -280,6 +284,11 @@ void CServer::Kick(int ClientID, const char *pReason) Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't kick yourself"); return; } + else if(m_aClients[ClientID].m_Authed > m_RconAuthLevel) + { + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "kick command denied"); + return; + } m_NetServer.Drop(ClientID, pReason); } @@ -571,8 +580,9 @@ int CServer::NewClientCallback(int ClientID, void *pUser) pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; - pThis->m_aClients[ClientID].m_Authed = 0; + pThis->m_aClients[ClientID].m_Authed = AUTHED_NO; pThis->m_aClients[ClientID].m_AuthTries = 0; + pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; pThis->m_aClients[ClientID].Reset(); return 0; } @@ -596,8 +606,9 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser) pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; - pThis->m_aClients[ClientID].m_Authed = 0; + pThis->m_aClients[ClientID].m_Authed = AUTHED_NO; pThis->m_aClients[ClientID].m_AuthTries = 0; + pThis->m_aClients[ClientID].m_pRconCmdToSend = 0; pThis->m_aClients[ClientID].m_Snapshots.PurgeAll(); return 0; } @@ -635,13 +646,44 @@ void CServer::SendRconLineAuthed(const char *pLine, void *pUser) for(i = 0; i < MAX_CLIENTS; i++) { - if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed) + if(pThis->m_aClients[i].m_State != CClient::STATE_EMPTY && pThis->m_aClients[i].m_Authed >= pThis->m_RconAuthLevel) pThis->SendRconLine(i, pLine); } ReentryGuard--; } +void CServer::SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID) +{ + CMsgPacker Msg(NETMSG_RCON_CMD_ADD); + Msg.AddString(pCommandInfo->m_pName, IConsole::TEMPCMD_NAME_LENGTH); + Msg.AddString(pCommandInfo->m_pHelp, IConsole::TEMPCMD_HELP_LENGTH); + Msg.AddString(pCommandInfo->m_pParams, IConsole::TEMPCMD_PARAMS_LENGTH); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); +} + +void CServer::SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID) +{ + CMsgPacker Msg(NETMSG_RCON_CMD_REM); + Msg.AddString(pCommandInfo->m_pName, 256); + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); +} + +void CServer::UpdateClientRconCommands() +{ + int ClientID = Tick() % MAX_CLIENTS; + + if(m_aClients[ClientID].m_State != CClient::STATE_EMPTY && m_aClients[ClientID].m_Authed) + { + int ConsoleAccessLevel = m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD; + for(int i = 0; i < MAX_RCONCMD_SEND && m_aClients[ClientID].m_pRconCmdToSend; ++i) + { + SendRconCmdAdd(m_aClients[ClientID].m_pRconCmdToSend, ClientID); + m_aClients[ClientID].m_pRconCmdToSend = m_aClients[ClientID].m_pRconCmdToSend->NextCommandInfo(ConsoleAccessLevel, CFGFLAG_SERVER); + } + } +} + void CServer::ProcessClientPacket(CNetChunk *pPacket) { int ClientID = pPacket->m_ClientID; @@ -813,8 +855,12 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); m_RconClientID = ClientID; + m_RconAuthLevel = m_aClients[ClientID].m_Authed; + Console()->SetAccessLevel(m_aClients[ClientID].m_Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : IConsole::ACCESS_LEVEL_MOD); Console()->ExecuteLine(pCmd); + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = -1; + m_RconAuthLevel = AUTHED_ADMIN; } } else if(Msg == NETMSG_RCON_AUTH) @@ -825,20 +871,40 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if(Unpacker.Error() == 0) { - if(g_Config.m_SvRconPassword[0] == 0) + if(g_Config.m_SvRconPassword[0] == 0 && g_Config.m_SvRconModPassword[0] == 0) { - SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password to enable the remote console."); + SendRconLine(ClientID, "No rcon password set on server. Set sv_rcon_password and/or sv_rcon_mod_password to enable the remote console."); } - else if(str_comp(pPw, g_Config.m_SvRconPassword) == 0) + else if(g_Config.m_SvRconPassword[0] && str_comp(pPw, g_Config.m_SvRconPassword) == 0) { CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); - Msg.AddInt(1); + Msg.AddInt(1); //authed + Msg.AddInt(1); //cmdlist SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); - m_aClients[ClientID].m_Authed = 1; - SendRconLine(ClientID, "Authentication successful. Remote console access granted."); + m_aClients[ClientID].m_Authed = AUTHED_ADMIN; + int SendRconCmds = Unpacker.GetInt(); + if(Unpacker.Error() == 0 && SendRconCmds) + m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_ADMIN, CFGFLAG_SERVER); + SendRconLine(ClientID, "Admin authentication successful. Full remote console access granted."); char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "ClientID=%d authed", ClientID); + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (admin)", ClientID); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + } + else if(g_Config.m_SvRconModPassword[0] && str_comp(pPw, g_Config.m_SvRconModPassword) == 0) + { + CMsgPacker Msg(NETMSG_RCON_AUTH_STATUS); + Msg.AddInt(1); //authed + Msg.AddInt(1); //cmdlist + SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); + + m_aClients[ClientID].m_Authed = AUTHED_MOD; + int SendRconCmds = Unpacker.GetInt(); + if(Unpacker.Error() == 0 && SendRconCmds) + m_aClients[ClientID].m_pRconCmdToSend = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_MOD, CFGFLAG_SERVER); + SendRconLine(ClientID, "Moderator authentication successful. Limited remote console access granted."); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "ClientID=%d authed (moderator)", ClientID); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } else if(g_Config.m_SvRconMaxTries) @@ -1020,6 +1086,8 @@ void CServer::PumpNetwork() else ProcessClientPacket(&Packet); } + + m_Econ.Update(); } char *CServer::GetMapName() @@ -1094,7 +1162,7 @@ int CServer::Run() m_pStorage = Kernel()->RequestInterface<IStorage>(); // - Console()->RegisterPrintCallback(SendRconLineAuthed, this); + m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this); // load map if(!LoadMap(g_Config.m_SvMap)) @@ -1117,7 +1185,6 @@ int CServer::Run() BindAddr.port = g_Config.m_SvPort; } - if(!m_NetServer.Open(BindAddr, g_Config.m_SvMaxClients, g_Config.m_SvMaxClientsPerIP, 0)) { dbg_msg("server", "couldn't open socket. port might already be in use"); @@ -1126,6 +1193,8 @@ int CServer::Run() m_NetServer.SetCallbacks(NewClientCallback, DelClientCallback, this); + m_Econ.Init(Console()); + char aBuf[256]; str_format(aBuf, sizeof(aBuf), "server name is '%s'", g_Config.m_SvName); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); @@ -1220,10 +1289,12 @@ int CServer::Run() { if(g_Config.m_SvHighBandwidth || (m_CurrentGameTick%2) == 0) DoSnapshot(); + + UpdateClientRconCommands(); } // master server stuff - m_Register.RegisterUpdate(BindAddr.type); + m_Register.RegisterUpdate(m_NetServer.NetType()); PumpNetwork(); @@ -1261,6 +1332,8 @@ int CServer::Run() { if(m_aClients[i].m_State != CClient::STATE_EMPTY) m_NetServer.Drop(i, "Server shutdown"); + + m_Econ.Shutdown(); } GameServer()->OnShutdown(); @@ -1292,7 +1365,7 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) const char *pReason = "No reason given"; if(pResult->NumArguments() > 1) - Minutes = pResult->GetInteger(1); + Minutes = max(0, pResult->GetInteger(1)); if(pResult->NumArguments() > 2) pReason = pResult->GetString(2); @@ -1308,6 +1381,20 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } + + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(i == pServer->m_RconClientID) + continue; + + AddrCheck = pServer->m_NetServer.ClientAddr(i); + AddrCheck.port = 0; + if(net_addr_comp(&Addr, &AddrCheck) == 0 && pServer->m_aClients[i].m_Authed > pServer->m_RconAuthLevel) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied"); + return; + } + } } pServer->BanAdd(Addr, Minutes*60, pReason); } @@ -1325,6 +1412,11 @@ void CServer::ConBan(IConsole::IResult *pResult, void *pUser) pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "you can't ban yourself"); return; } + else if(pServer->m_aClients[ClientID].m_Authed > pServer->m_RconAuthLevel) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "ban command denied"); + return; + } Addr = pServer->m_NetServer.ClientAddr(ClientID); pServer->BanAdd(Addr, Minutes*60, pReason); @@ -1430,6 +1522,25 @@ void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser) ((CServer *)pUser)->m_RunServer = 0; } +void CServer::DemoRecorder_HandleAutoStart() +{ + if(g_Config.m_SvAutoDemoRecord) + { + m_DemoRecorder.Stop(); + char aFilename[128]; + char aDate[20]; + str_timestamp(aDate, sizeof(aDate)); + str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); + m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server"); + if(g_Config.m_SvAutoDemoMax) + { + // clean up auto recorded demos + CFileCollection AutoDemos; + AutoDemos.Init(Storage(), "demos/server", "autorecord", ".demo", g_Config.m_SvAutoDemoMax); + } + } +} + void CServer::ConRecord(IConsole::IResult *pResult, void *pUser) { CServer* pServer = (CServer *)pUser; @@ -1470,26 +1581,67 @@ void CServer::ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pU ((CServer *)pUserData)->m_NetServer.SetMaxClientsPerIP(pResult->GetInteger(0)); } +void CServer::ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + if(pResult->NumArguments() == 2) + { + CServer *pThis = static_cast<CServer *>(pUserData); + const IConsole::CCommandInfo *pInfo = pThis->Console()->GetCommandInfo(pResult->GetString(0), CFGFLAG_SERVER, false); + int OldAccessLevel; + if(pInfo) + OldAccessLevel = pInfo->GetAccessLevel(); + pfnCallback(pResult, pCallbackUserData); + if(pInfo && OldAccessLevel != pInfo->GetAccessLevel()) + { + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(pThis->m_aClients[i].m_State == CServer::CClient::STATE_EMPTY || pThis->m_aClients[i].m_Authed != CServer::AUTHED_MOD || + (pThis->m_aClients[i].m_pRconCmdToSend && str_comp(pResult->GetString(0), pThis->m_aClients[i].m_pRconCmdToSend->m_pName) >= 0)) + continue; + + if(OldAccessLevel == IConsole::ACCESS_LEVEL_ADMIN) + pThis->SendRconCmdAdd(pInfo, i); + else + pThis->SendRconCmdRem(pInfo, i); + } + } + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CServer::ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CServer *pThis = static_cast<CServer *>(pUserData); + pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); + } +} + void CServer::RegisterCommands() { m_pConsole = Kernel()->RequestInterface<IConsole>(); - Console()->Register("kick", "i?r", CFGFLAG_SERVER, ConKick, this, ""); - Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, ""); - Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, ""); - Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, ""); - Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, ""); - Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, ""); + Console()->Register("kick", "i?r", CFGFLAG_SERVER, ConKick, this, "Kick player with specified id for any reason"); + Console()->Register("ban", "s?ir", CFGFLAG_SERVER|CFGFLAG_STORE, ConBan, this, "Ban player with ip/id for x minutes for any reason"); + Console()->Register("unban", "s", CFGFLAG_SERVER|CFGFLAG_STORE, ConUnban, this, "Unban ip"); + Console()->Register("bans", "", CFGFLAG_SERVER|CFGFLAG_STORE, ConBans, this, "Show banlist"); + Console()->Register("status", "", CFGFLAG_SERVER, ConStatus, this, "List players"); + Console()->Register("shutdown", "", CFGFLAG_SERVER, ConShutdown, this, "Shut down"); - Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, ""); - Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, ""); + Console()->Register("record", "?s", CFGFLAG_SERVER|CFGFLAG_STORE, ConRecord, this, "Record to a file"); + Console()->Register("stoprecord", "", CFGFLAG_SERVER, ConStopRecord, this, "Stop recording"); - Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, ""); + Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "Reload the map"); Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); Console()->Chain("password", ConchainSpecialInfoupdate, this); Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this); + Console()->Chain("mod_command", ConchainModCommandUpdate, this); + Console()->Chain("console_output_level", ConchainConsoleOutputLevelUpdate, this); } diff --git a/src/engine/server/server.h b/src/engine/server/server.h index be36a856..d8fdd8fa 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -49,6 +49,15 @@ public: class IConsole *Console() { return m_pConsole; } class IStorage *Storage() { return m_pStorage; } + enum + { + AUTHED_NO=0, + AUTHED_MOD, + AUTHED_ADMIN, + + MAX_RCONCMD_SEND=16, + }; + class CClient { public: @@ -93,6 +102,8 @@ public: int m_Authed; int m_AuthTries; + const IConsole::CCommandInfo *m_pRconCmdToSend; + void Reset(); }; @@ -102,6 +113,7 @@ public: CSnapshotBuilder m_SnapshotBuilder; CSnapIDPool m_IDPool; CNetServer m_NetServer; + CEcon m_Econ; IEngineMap *m_pMap; @@ -110,6 +122,8 @@ public: int m_RunServer; int m_MapReload; int m_RconClientID; + int m_RconAuthLevel; + int m_PrintCBIndex; int64 m_Lastheartbeat; //static NETADDR4 master_server; @@ -134,6 +148,8 @@ public: void Kick(int ClientID, const char *pReason); + void DemoRecorder_HandleAutoStart(); + //int Tick() int64 TickStartTime(int Tick); //int TickSpeed() @@ -161,6 +177,10 @@ public: void SendRconLine(int ClientID, const char *pLine); static void SendRconLineAuthed(const char *pLine, void *pUser); + void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID); + void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID); + void UpdateClientRconCommands(); + void ProcessClientPacket(CNetChunk *pPacket); void SendServerInfo(NETADDR *pAddr, int Token); @@ -169,7 +189,6 @@ public: int BanAdd(NETADDR Addr, int Seconds, const char *pReason); int BanRemove(NETADDR Addr); - void PumpNetwork(); char *GetMapName(); @@ -189,6 +208,8 @@ public: static void ConMapReload(IConsole::IResult *pResult, void *pUser); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainModCommandUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainConsoleOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); void RegisterCommands(); diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 31a8128a..c812063a 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -82,9 +82,19 @@ MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server") MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "Use high bandwidth mode. Doubles the bandwidth required for the server. LAN use only") MACRO_CONFIG_INT(SvRegister, sv_register, 1, 0, 1, CFGFLAG_SERVER, "Register server with master server for public listing") -MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password") +MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER, "Remote console password (full access)") +MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER, "Remote console password for moderators (limited access)") MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 3, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication") MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick") +MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos") +MACRO_CONFIG_INT(SvAutoDemoMax, sv_auto_demo_max, 10, 0, 1000, CFGFLAG_SERVER, "Maximum number of automatically recorded demos (0 = no limit)") + +MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_SERVER, "Address to bind the external console to. Anything but 'localhost' is dangerous") +MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_SERVER, "Port to use for the external console") +MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_SERVER, "External console password") +MACRO_CONFIG_INT(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if econ authentication fails. 0 just closes the connection") +MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_SERVER, "Time in seconds before the the econ authentification times out") +MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_SERVER, "Adjusts the amount of information in the external console") MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode") MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems") diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 3fd73543..e4cb1991 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -1,11 +1,15 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include <new> + +#include <base/math.h> #include <base/system.h> -#include <engine/shared/protocol.h> + #include <engine/storage.h> -#include "console.h" +#include <engine/shared/protocol.h> + #include "config.h" +#include "console.h" #include "linereader.h" const char *CConsole::CResult::GetString(unsigned Index) @@ -29,6 +33,29 @@ float CConsole::CResult::GetFloat(unsigned Index) return str_tofloat(m_apArgs[Index]); } +const IConsole::CCommandInfo *CConsole::CCommand::NextCommandInfo(int AccessLevel, int FlagMask) const +{ + const CCommand *pInfo = m_pNext; + while(pInfo) + { + if(pInfo->m_Flags&FlagMask && pInfo->m_AccessLevel >= AccessLevel) + break; + pInfo = pInfo->m_pNext; + } + return pInfo; +} + +const IConsole::CCommandInfo *CConsole::FirstCommandInfo(int AccessLevel, int FlagMask) const +{ + for(const CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask && pCommand->GetAccessLevel() >= AccessLevel) + return pCommand; + } + + return 0; +} + // the maximum number of tokens occurs in a string of length CONSOLE_MAX_STR_LENGTH with tokens size 1 separated by single spaces @@ -146,20 +173,34 @@ int CConsole::ParseArgs(CResult *pResult, const char *pFormat) return Error; } -void CConsole::RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData) +int CConsole::RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) +{ + if(m_NumPrintCB == MAX_PRINT_CB) + return -1; + + m_aPrintCB[m_NumPrintCB].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); + m_aPrintCB[m_NumPrintCB].m_pfnPrintCallback = pfnPrintCallback; + m_aPrintCB[m_NumPrintCB].m_pPrintCallbackUserdata = pUserData; + return m_NumPrintCB++; +} + +void CConsole::SetPrintOutputLevel(int Index, int OutputLevel) { - m_pfnPrintCallback = pfnPrintCallback; - m_pPrintCallbackUserdata = pUserData; + if(Index >= 0 && Index < MAX_PRINT_CB) + m_aPrintCB[Index].m_OutputLevel = clamp(OutputLevel, (int)(OUTPUT_LEVEL_STANDARD), (int)(OUTPUT_LEVEL_DEBUG)); } void CConsole::Print(int Level, const char *pFrom, const char *pStr) { dbg_msg(pFrom ,"%s", pStr); - if(Level <= g_Config.m_ConsoleOutputLevel && m_pfnPrintCallback) + for(int i = 0; i < m_NumPrintCB; ++i) { - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr); - m_pfnPrintCallback(aBuf, m_pPrintCallbackUserdata); + if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback) + { + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "[%s]: %s", pFrom, pStr); + m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata); + } } } @@ -247,38 +288,47 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) if(ParseStart(&Result, pStr, (pEnd-pStr) + 1) != 0) return; - if (!*Result.m_pCommand) + if(!*Result.m_pCommand) return; CCommand *pCommand = FindCommand(Result.m_pCommand, m_FlagMask); if(pCommand) { - int IsStrokeCommand = 0; - if(Result.m_pCommand[0] == '+') - { - // insert the stroke direction token - Result.AddArgument(m_paStrokeStr[Stroke]); - IsStrokeCommand = 1; - } - - if(Stroke || IsStrokeCommand) + if(pCommand->GetAccessLevel() >= m_AccessLevel) { - if(ParseArgs(&Result, pCommand->m_pParams)) + int IsStrokeCommand = 0; + if(Result.m_pCommand[0] == '+') { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); - Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + // insert the stroke direction token + Result.AddArgument(m_paStrokeStr[Stroke]); + IsStrokeCommand = 1; } - else if(m_StoreCommands && pCommand->m_Flags&CFGFLAG_STORE) + + if(Stroke || IsStrokeCommand) { - m_ExecutionQueue.AddEntry(); - m_ExecutionQueue.m_pLast->m_pfnCommandCallback = pCommand->m_pfnCallback; - m_ExecutionQueue.m_pLast->m_pCommandUserData = pCommand->m_pUserData; - m_ExecutionQueue.m_pLast->m_Result = Result; + if(ParseArgs(&Result, pCommand->m_pParams)) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Invalid arguments... Usage: %s %s", pCommand->m_pName, pCommand->m_pParams); + Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + } + else if(m_StoreCommands && pCommand->m_Flags&CFGFLAG_STORE) + { + m_ExecutionQueue.AddEntry(); + m_ExecutionQueue.m_pLast->m_pfnCommandCallback = pCommand->m_pfnCallback; + m_ExecutionQueue.m_pLast->m_pCommandUserData = pCommand->m_pUserData; + m_ExecutionQueue.m_pLast->m_Result = Result; + } + else + pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); } - else - pCommand->m_pfnCallback(&Result, pCommand->m_pUserData); + } + else if(Stroke) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Access for command %s denied.", Result.m_pCommand); + Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); } } else if(Stroke) @@ -292,12 +342,11 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr) } } -void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) +void CConsole::PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser) { - CCommand *pCommand; - for(pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) { - if(pCommand->m_Flags&FlagMask) + if(pCommand->m_Flags&FlagMask && pCommand->m_Temp == Temp) { if(str_find_nocase(pCommand->m_pName, pStr)) pfnCallback(pCommand->m_pName, pUser); @@ -307,8 +356,7 @@ void CConsole::PossibleCommands(const char *pStr, int FlagMask, FPossibleCallbac CConsole::CCommand *CConsole::FindCommand(const char *pName, int FlagMask) { - CCommand *pCommand; - for (pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) { if(pCommand->m_Flags&FlagMask) { @@ -383,6 +431,62 @@ void CConsole::Con_Exec(IResult *pResult, void *pUserData) ((CConsole*)pUserData)->ExecuteFile(pResult->GetString(0)); } +void CConsole::ConModCommandAccess(IResult *pResult, void *pUser) +{ + CConsole* pConsole = static_cast<CConsole *>(pUser); + char aBuf[128]; + CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), CFGFLAG_SERVER); + if(pCommand) + { + if(pResult->NumArguments() == 2) + { + pCommand->SetAccessLevel(pResult->GetInteger(1)); + str_format(aBuf, sizeof(aBuf), "moderator access for '%s' is now %s", pResult->GetString(0), pCommand->GetAccessLevel() ? "enabled" : "disabled"); + } + else + str_format(aBuf, sizeof(aBuf), "moderator access for '%s' is %s", pResult->GetString(0), pCommand->GetAccessLevel() ? "enabled" : "disabled"); + } + else + str_format(aBuf, sizeof(aBuf), "No such command: '%s'.", pResult->GetString(0)); + + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); +} + +void CConsole::ConModCommandStatus(IResult *pResult, void *pUser) +{ + CConsole* pConsole = static_cast<CConsole *>(pUser); + char aBuf[240]; + mem_zero(aBuf, sizeof(aBuf)); + int Used = 0; + + for(CCommand *pCommand = pConsole->m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&pConsole->m_FlagMask && pCommand->GetAccessLevel() == ACCESS_LEVEL_MOD) + { + int Length = str_length(pCommand->m_pName); + if(Used + Length + 2 < (int)(sizeof(aBuf))) + { + if(Used > 0) + { + Used += 2; + str_append(aBuf, ", ", sizeof(aBuf)); + } + str_append(aBuf, pCommand->m_pName, sizeof(aBuf)); + Used += Length; + } + else + { + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); + mem_zero(aBuf, sizeof(aBuf)); + str_copy(aBuf, pCommand->m_pName, sizeof(aBuf)); + Used = Length; + } + } + } + if(Used > 0) + pConsole->Print(OUTPUT_LEVEL_STANDARD, "Console", aBuf); +} + struct CIntVariableData { IConsole *m_pConsole; @@ -463,14 +567,17 @@ static void StrVariableCommand(IConsole::IResult *pResult, void *pUserData) CConsole::CConsole(int FlagMask) { m_FlagMask = FlagMask; + m_AccessLevel = ACCESS_LEVEL_ADMIN; + m_pRecycleList = 0; + m_TempCommands.Reset(); m_StoreCommands = true; m_paStrokeStr[0] = "0"; m_paStrokeStr[1] = "1"; m_ExecutionQueue.Reset(); m_pFirstCommand = 0; m_pFirstExec = 0; - m_pPrintCallbackUserdata = 0; - m_pfnPrintCallback = 0; + mem_zero(m_aPrintCB, sizeof(m_aPrintCB)); + m_NumPrintCB = 0; m_pStorage = 0; @@ -478,6 +585,9 @@ CConsole::CConsole(int FlagMask) Register("echo", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Echo, this, "Echo the text"); Register("exec", "r", CFGFLAG_SERVER|CFGFLAG_CLIENT, Con_Exec, this, "Execute the specified file"); + Register("mod_command", "s?i", CFGFLAG_SERVER, ConModCommandAccess, this, "Specify command accessibility for moderators"); + Register("mod_status", "", CFGFLAG_SERVER, ConModCommandStatus, this, "List all commands which are accessible for moderators"); + // TODO: this should disappear #define MACRO_CONFIG_INT(Name,ScriptName,Def,Min,Max,Flags,Desc) \ { \ @@ -521,20 +631,131 @@ void CConsole::ParseArguments(int NumArgs, const char **ppArguments) } } +void CConsole::AddCommandSorted(CCommand *pCommand) +{ + if(!m_pFirstCommand || str_comp(pCommand->m_pName, m_pFirstCommand->m_pName) < 0) + { + if(m_pFirstCommand && m_pFirstCommand->m_pNext) + pCommand->m_pNext = m_pFirstCommand; + else + pCommand->m_pNext = 0; + m_pFirstCommand = pCommand; + } + else + { + for(CCommand *p = m_pFirstCommand; p; p = p->m_pNext) + { + if(!p->m_pNext || str_comp(pCommand->m_pName, p->m_pNext->m_pName) < 0) + { + pCommand->m_pNext = p->m_pNext; + p->m_pNext = pCommand; + break; + } + } + } +} + void CConsole::Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp) { - CCommand *pCommand = (CCommand *)mem_alloc(sizeof(CCommand), sizeof(void*)); + CCommand *pCommand = new(mem_alloc(sizeof(CCommand), sizeof(void*))) CCommand; pCommand->m_pfnCallback = pfnFunc; pCommand->m_pUserData = pUser; - pCommand->m_pHelp = pHelp; + pCommand->m_pName = pName; + pCommand->m_pHelp = pHelp; pCommand->m_pParams = pParams; + + pCommand->m_Flags = Flags; + pCommand->m_Temp = false; + + AddCommandSorted(pCommand); +} + +void CConsole::RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp) +{ + CCommand *pCommand; + if(m_pRecycleList) + { + pCommand = m_pRecycleList; + str_copy(const_cast<char *>(pCommand->m_pName), pName, TEMPCMD_NAME_LENGTH); + str_copy(const_cast<char *>(pCommand->m_pHelp), pHelp, TEMPCMD_HELP_LENGTH); + str_copy(const_cast<char *>(pCommand->m_pParams), pParams, TEMPCMD_PARAMS_LENGTH); + + m_pRecycleList = m_pRecycleList->m_pNext; + } + else + { + pCommand = new(m_TempCommands.Allocate(sizeof(CCommand))) CCommand; + char *pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_NAME_LENGTH)); + str_copy(pMem, pName, TEMPCMD_NAME_LENGTH); + pCommand->m_pName = pMem; + pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_HELP_LENGTH)); + str_copy(pMem, pHelp, TEMPCMD_HELP_LENGTH); + pCommand->m_pHelp = pMem; + pMem = static_cast<char *>(m_TempCommands.Allocate(TEMPCMD_PARAMS_LENGTH)); + str_copy(pMem, pParams, TEMPCMD_PARAMS_LENGTH); + pCommand->m_pParams = pMem; + } + + pCommand->m_pfnCallback = 0; + pCommand->m_pUserData = 0; pCommand->m_Flags = Flags; + pCommand->m_Temp = true; + + AddCommandSorted(pCommand); +} + +void CConsole::DeregisterTemp(const char *pName) +{ + if(!m_pFirstCommand) + return; + + CCommand *pRemoved = 0; + + // remove temp entry from command list + if(m_pFirstCommand->m_Temp && str_comp(m_pFirstCommand->m_pName, pName) == 0) + { + pRemoved = m_pFirstCommand; + m_pFirstCommand = m_pFirstCommand->m_pNext; + } + else + { + for(CCommand *pCommand = m_pFirstCommand; pCommand->m_pNext; pCommand = pCommand->m_pNext) + if(pCommand->m_pNext->m_Temp && str_comp(pCommand->m_pNext->m_pName, pName) == 0) + { + pRemoved = pCommand->m_pNext; + pCommand->m_pNext = pCommand->m_pNext->m_pNext; + break; + } + } + + // add to recycle list + if(pRemoved) + { + pRemoved->m_pNext = m_pRecycleList; + m_pRecycleList = pRemoved; + } +} +void CConsole::DeregisterTempAll() +{ + // set non temp as first one + for(; m_pFirstCommand && m_pFirstCommand->m_Temp; m_pFirstCommand = m_pFirstCommand->m_pNext); + + // remove temp entries from command list + for(CCommand *pCommand = m_pFirstCommand; pCommand && pCommand->m_pNext; pCommand = pCommand->m_pNext) + { + CCommand *pNext = pCommand->m_pNext; + if(pNext->m_Temp) + { + for(; pNext && pNext->m_Temp; pNext = pNext->m_pNext); + pCommand->m_pNext = pNext; + } + } - pCommand->m_pNext = m_pFirstCommand; - m_pFirstCommand = pCommand; + m_TempCommands.Reset(); + m_pRecycleList = 0; } void CConsole::Con_Chain(IResult *pResult, void *pUserData) @@ -580,9 +801,18 @@ void CConsole::StoreCommands(bool Store) } -IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask) +const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int FlagMask, bool Temp) { - return FindCommand(pName, FlagMask); + for(CCommand *pCommand = m_pFirstCommand; pCommand; pCommand = pCommand->m_pNext) + { + if(pCommand->m_Flags&FlagMask && pCommand->m_Temp == Temp) + { + if(str_comp_nocase(pCommand->m_pName, pName) == 0) + return pCommand; + } + } + + return 0; } diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 0866d8e3..6989c696 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -13,8 +13,13 @@ class CConsole : public IConsole public: CCommand *m_pNext; int m_Flags; + bool m_Temp; FCommandCallback m_pfnCallback; void *m_pUserData; + + virtual const CCommandInfo *NextCommandInfo(int AccessLevel, int FlagMask) const; + + void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); } }; @@ -41,16 +46,27 @@ class CConsole : public IConsole CExecFile *m_pFirstExec; class IStorage *m_pStorage; + int m_AccessLevel; + + CCommand *m_pRecycleList; + CHeap m_TempCommands; static void Con_Chain(IResult *pResult, void *pUserData); static void Con_Echo(IResult *pResult, void *pUserData); static void Con_Exec(IResult *pResult, void *pUserData); + static void ConModCommandAccess(IResult *pResult, void *pUser); + static void ConModCommandStatus(IConsole::IResult *pResult, void *pUser); void ExecuteFileRecurse(const char *pFilename); void ExecuteLineStroked(int Stroke, const char *pStr); - FPrintCallback m_pfnPrintCallback; - void *m_pPrintCallbackUserdata; + struct + { + int m_OutputLevel; + FPrintCallback m_pfnPrintCallback; + void *m_pPrintCallbackUserdata; + } m_aPrintCB[MAX_PRINT_CB]; + int m_NumPrintCB; enum { @@ -134,16 +150,21 @@ class CConsole : public IConsole } } m_ExecutionQueue; + void AddCommandSorted(CCommand *pCommand); CCommand *FindCommand(const char *pName, int FlagMask); public: CConsole(int FlagMask); - virtual CCommandInfo *GetCommandInfo(const char *pName, int FlagMask); - virtual void PossibleCommands(const char *pStr, int FlagMask, FPossibleCallback pfnCallback, void *pUser) ; + virtual const CCommandInfo *FirstCommandInfo(int AccessLevel, int Flagmask) const; + virtual const CCommandInfo *GetCommandInfo(const char *pName, int FlagMask, bool Temp); + virtual void PossibleCommands(const char *pStr, int FlagMask, bool Temp, FPossibleCallback pfnCallback, void *pUser); virtual void ParseArguments(int NumArgs, const char **ppArguments); virtual void Register(const char *pName, const char *pParams, int Flags, FCommandCallback pfnFunc, void *pUser, const char *pHelp); + virtual void RegisterTemp(const char *pName, const char *pParams, int Flags, const char *pHelp); + virtual void DeregisterTemp(const char *pName); + virtual void DeregisterTempAll(); virtual void Chain(const char *pName, FChainCommandCallback pfnChainFunc, void *pUser); virtual void StoreCommands(bool Store); @@ -151,8 +172,11 @@ public: virtual void ExecuteLine(const char *pStr); virtual void ExecuteFile(const char *pFilename); - virtual void RegisterPrintCallback(FPrintCallback pfnPrintCallback, void *pUserData); + virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData); + virtual void SetPrintOutputLevel(int Index, int OutputLevel); virtual void Print(int Level, const char *pFrom, const char *pStr); + + void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_MOD)); } }; #endif diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp index 00410038..e2215635 100644 --- a/src/engine/shared/datafile.cpp +++ b/src/engine/shared/datafile.cpp @@ -422,6 +422,25 @@ unsigned CDataFileReader::Crc() return m_pDataFile->m_Crc; } + +CDataFileWriter::CDataFileWriter() +{ + m_File = 0; + m_pItemTypes = static_cast<CItemTypeInfo *>(mem_alloc(sizeof(CItemTypeInfo) * MAX_ITEM_TYPES, 1)); + m_pItems = static_cast<CItemInfo *>(mem_alloc(sizeof(CItemInfo) * MAX_ITEMS, 1)); + m_pDatas = static_cast<CDataInfo *>(mem_alloc(sizeof(CDataInfo) * MAX_DATAS, 1)); +} + +CDataFileWriter::~CDataFileWriter() +{ + mem_free(m_pItemTypes); + m_pItemTypes = 0; + mem_free(m_pItems); + m_pItems = 0; + mem_free(m_pDatas); + m_pDatas = 0; +} + bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) { dbg_assert(!m_File, "a file already exists"); @@ -432,12 +451,12 @@ bool CDataFileWriter::Open(class IStorage *pStorage, const char *pFilename) m_NumItems = 0; m_NumDatas = 0; m_NumItemTypes = 0; - mem_zero(&m_aItemTypes, sizeof(m_aItemTypes)); + mem_zero(m_pItemTypes, sizeof(CItemTypeInfo) * MAX_ITEM_TYPES); - for(int i = 0; i < 0xffff; i++) + for(int i = 0; i < MAX_ITEM_TYPES; i++) { - m_aItemTypes[i].m_First = -1; - m_aItemTypes[i].m_Last = -1; + m_pItemTypes[i].m_First = -1; + m_pItemTypes[i].m_Last = -1; } return true; @@ -451,29 +470,29 @@ int CDataFileWriter::AddItem(int Type, int ID, int Size, void *pData) dbg_assert(m_NumItems < 1024, "too many items"); dbg_assert(Size%sizeof(int) == 0, "incorrect boundary"); - m_aItems[m_NumItems].m_Type = Type; - m_aItems[m_NumItems].m_ID = ID; - m_aItems[m_NumItems].m_Size = Size; + m_pItems[m_NumItems].m_Type = Type; + m_pItems[m_NumItems].m_ID = ID; + m_pItems[m_NumItems].m_Size = Size; // copy data - m_aItems[m_NumItems].m_pData = mem_alloc(Size, 1); - mem_copy(m_aItems[m_NumItems].m_pData, pData, Size); + m_pItems[m_NumItems].m_pData = mem_alloc(Size, 1); + mem_copy(m_pItems[m_NumItems].m_pData, pData, Size); - if(!m_aItemTypes[Type].m_Num) // count item types + if(!m_pItemTypes[Type].m_Num) // count item types m_NumItemTypes++; // link - m_aItems[m_NumItems].m_Prev = m_aItemTypes[Type].m_Last; - m_aItems[m_NumItems].m_Next = -1; + m_pItems[m_NumItems].m_Prev = m_pItemTypes[Type].m_Last; + m_pItems[m_NumItems].m_Next = -1; - if(m_aItemTypes[Type].m_Last != -1) - m_aItems[m_aItemTypes[Type].m_Last].m_Next = m_NumItems; - m_aItemTypes[Type].m_Last = m_NumItems; + if(m_pItemTypes[Type].m_Last != -1) + m_pItems[m_pItemTypes[Type].m_Last].m_Next = m_NumItems; + m_pItemTypes[Type].m_Last = m_NumItems; - if(m_aItemTypes[Type].m_First == -1) - m_aItemTypes[Type].m_First = m_NumItems; + if(m_pItemTypes[Type].m_First == -1) + m_pItemTypes[Type].m_First = m_NumItems; - m_aItemTypes[Type].m_Num++; + m_pItemTypes[Type].m_Num++; m_NumItems++; return m_NumItems-1; @@ -485,7 +504,7 @@ int CDataFileWriter::AddData(int Size, void *pData) dbg_assert(m_NumDatas < 1024, "too much data"); - CDataInfo *pInfo = &m_aDatas[m_NumDatas]; + CDataInfo *pInfo = &m_pDatas[m_NumDatas]; unsigned long s = compressBound(Size); void *pCompData = mem_alloc(s, 1); // temporary buffer that we use during compression @@ -540,13 +559,13 @@ int CDataFileWriter::Finish() for(int i = 0; i < m_NumItems; i++) { if(DEBUG) - dbg_msg("datafile", "item=%d size=%d (%d)", i, m_aItems[i].m_Size, m_aItems[i].m_Size+sizeof(CDatafileItem)); - ItemSize += m_aItems[i].m_Size + sizeof(CDatafileItem); + dbg_msg("datafile", "item=%d size=%d (%d)", i, m_pItems[i].m_Size, m_pItems[i].m_Size+sizeof(CDatafileItem)); + ItemSize += m_pItems[i].m_Size + sizeof(CDatafileItem); } for(int i = 0; i < m_NumDatas; i++) - DataSize += m_aDatas[i].m_CompressedSize; + DataSize += m_pDatas[i].m_CompressedSize; // calculate the complete size TypesSize = m_NumItemTypes*sizeof(CDatafileItemType); @@ -587,30 +606,30 @@ int CDataFileWriter::Finish() // write types for(int i = 0, Count = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { // write info CDatafileItemType Info; Info.m_Type = i; Info.m_Start = Count; - Info.m_Num = m_aItemTypes[i].m_Num; + Info.m_Num = m_pItemTypes[i].m_Num; if(DEBUG) dbg_msg("datafile", "writing type=%x start=%d num=%d", Info.m_Type, Info.m_Start, Info.m_Num); #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&Info, sizeof(int), sizeof(CDatafileItemType)/sizeof(int)); #endif io_write(m_File, &Info, sizeof(Info)); - Count += m_aItemTypes[i].m_Num; + Count += m_pItemTypes[i].m_Num; } } // write item offsets for(int i = 0, Offset = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { - // write all m_aItems in of this type - int k = m_aItemTypes[i].m_First; + // write all m_pItems in of this type + int k = m_pItemTypes[i].m_First; while(k != -1) { if(DEBUG) @@ -620,10 +639,10 @@ int CDataFileWriter::Finish() swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int)); #endif io_write(m_File, &Temp, sizeof(Temp)); - Offset += m_aItems[k].m_Size + sizeof(CDatafileItem); + Offset += m_pItems[k].m_Size + sizeof(CDatafileItem); // next - k = m_aItems[k].m_Next; + k = m_pItems[k].m_Next; } } } @@ -638,45 +657,45 @@ int CDataFileWriter::Finish() swap_endian(&Temp, sizeof(int), sizeof(Temp)/sizeof(int)); #endif io_write(m_File, &Temp, sizeof(Temp)); - Offset += m_aDatas[i].m_CompressedSize; + Offset += m_pDatas[i].m_CompressedSize; } // write data uncompressed sizes for(int i = 0; i < m_NumDatas; i++) { if(DEBUG) - dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_aDatas[i].m_UncompressedSize); - int UncompressedSize = m_aDatas[i].m_UncompressedSize; + dbg_msg("datafile", "writing data uncompressed size num=%d size=%d", i, m_pDatas[i].m_UncompressedSize); + int UncompressedSize = m_pDatas[i].m_UncompressedSize; #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&UncompressedSize, sizeof(int), sizeof(UncompressedSize)/sizeof(int)); #endif io_write(m_File, &UncompressedSize, sizeof(UncompressedSize)); } - // write m_aItems + // write m_pItems for(int i = 0; i < 0xffff; i++) { - if(m_aItemTypes[i].m_Num) + if(m_pItemTypes[i].m_Num) { - // write all m_aItems in of this type - int k = m_aItemTypes[i].m_First; + // write all m_pItems in of this type + int k = m_pItemTypes[i].m_First; while(k != -1) { CDatafileItem Item; - Item.m_TypeAndID = (i<<16)|m_aItems[k].m_ID; - Item.m_Size = m_aItems[k].m_Size; + Item.m_TypeAndID = (i<<16)|m_pItems[k].m_ID; + Item.m_Size = m_pItems[k].m_Size; if(DEBUG) - dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_aItems[k].m_ID, m_aItems[k].m_Size); + dbg_msg("datafile", "writing item type=%x idx=%d id=%d size=%d", i, k, m_pItems[k].m_ID, m_pItems[k].m_Size); #if defined(CONF_ARCH_ENDIAN_BIG) swap_endian(&Item, sizeof(int), sizeof(Item)/sizeof(int)); - swap_endian(m_aItems[k].m_pData, sizeof(int), m_aItems[k].m_Size/sizeof(int)); + swap_endian(m_pItems[k].m_pData, sizeof(int), m_pItems[k].m_Size/sizeof(int)); #endif io_write(m_File, &Item, sizeof(Item)); - io_write(m_File, m_aItems[k].m_pData, m_aItems[k].m_Size); + io_write(m_File, m_pItems[k].m_pData, m_pItems[k].m_Size); // next - k = m_aItems[k].m_Next; + k = m_pItems[k].m_Next; } } } @@ -685,15 +704,15 @@ int CDataFileWriter::Finish() for(int i = 0; i < m_NumDatas; i++) { if(DEBUG) - dbg_msg("datafile", "writing data id=%d size=%d", i, m_aDatas[i].m_CompressedSize); - io_write(m_File, m_aDatas[i].m_pCompressedData, m_aDatas[i].m_CompressedSize); + dbg_msg("datafile", "writing data id=%d size=%d", i, m_pDatas[i].m_CompressedSize); + io_write(m_File, m_pDatas[i].m_pCompressedData, m_pDatas[i].m_CompressedSize); } // free data for(int i = 0; i < m_NumItems; i++) - mem_free(m_aItems[i].m_pData); + mem_free(m_pItems[i].m_pData); for(int i = 0; i < m_NumDatas; ++i) - mem_free(m_aDatas[i].m_pCompressedData); + mem_free(m_pDatas[i].m_pCompressedData); io_close(m_File); m_File = 0; diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h index 9f27f968..cafce20e 100644 --- a/src/engine/shared/datafile.h +++ b/src/engine/shared/datafile.h @@ -61,16 +61,24 @@ class CDataFileWriter int m_Last; }; + enum + { + MAX_ITEM_TYPES=0xffff, + MAX_ITEMS=1024, + MAX_DATAS=1024, + }; + IOHANDLE m_File; int m_NumItems; int m_NumDatas; int m_NumItemTypes; - CItemTypeInfo m_aItemTypes[0xffff]; - CItemInfo m_aItems[1024]; - CDataInfo m_aDatas[1024]; + CItemTypeInfo *m_pItemTypes; + CItemInfo *m_pItems; + CDataInfo *m_pDatas; public: - CDataFileWriter() : m_File(0) {} + CDataFileWriter(); + ~CDataFileWriter(); bool Open(class IStorage *pStorage, const char *Filename); int AddData(int Size, void *pData); int AddDataSwapped(int Size, void *pData); diff --git a/src/engine/shared/econ.cpp b/src/engine/shared/econ.cpp new file mode 100644 index 00000000..617cdbd6 --- /dev/null +++ b/src/engine/shared/econ.cpp @@ -0,0 +1,173 @@ +#include <engine/console.h> +#include <engine/shared/config.h> + +#include "econ.h" + +int CEcon::NewClientCallback(int ClientID, void *pUser) +{ + CEcon *pThis = (CEcon *)pUser; + + NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID); + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "client accepted. cid=%d addr=%s'", ClientID, aAddrStr); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf); + + pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED; + pThis->m_aClients[ClientID].m_TimeConnected = time_get(); + pThis->m_aClients[ClientID].m_AuthTries = 0; + + pThis->m_NetConsole.Send(ClientID, "Enter password:"); + return 0; +} + +int CEcon::DelClientCallback(int ClientID, const char *pReason, void *pUser) +{ + CEcon *pThis = (CEcon *)pUser; + + NETADDR Addr = pThis->m_NetConsole.ClientAddr(ClientID); + char aAddrStr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddrStr, sizeof(aAddrStr)); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "client dropped. cid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "econ", aBuf); + + pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; + return 0; +} + +void CEcon::SendLineCB(const char *pLine, void *pUserData) +{ + static_cast<CEcon *>(pUserData)->Send(-1, pLine); +} + +void CEcon::ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CEcon *pThis = static_cast<CEcon *>(pUserData); + pThis->Console()->SetPrintOutputLevel(pThis->m_PrintCBIndex, pResult->GetInteger(0)); + } +} + +void CEcon::Init(IConsole *pConsole) +{ + m_pConsole = pConsole; + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aClients[i].m_State = CClient::STATE_EMPTY; + + m_Ready = false; + + if(g_Config.m_EcPort == 0 || g_Config.m_EcPassword[0] == 0) + return; + + NETADDR BindAddr; + if(g_Config.m_EcBindaddr[0] && net_host_lookup(g_Config.m_EcBindaddr, &BindAddr, NETTYPE_ALL) == 0) + BindAddr.port = g_Config.m_EcPort; + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.type = NETTYPE_ALL; + BindAddr.port = g_Config.m_EcPort; + } + + if(m_NetConsole.Open(BindAddr, 0)) + { + m_NetConsole.SetCallbacks(NewClientCallback, DelClientCallback, this); + m_Ready = true; + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "bound to %s:%d", g_Config.m_EcBindaddr, g_Config.m_EcPort); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", aBuf); + + Console()->Chain("ec_output_level", ConchainEconOutputLevelUpdate, this); + m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_EcOutputLevel, SendLineCB, this); + } + else + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD,"econ", "couldn't open socket. port might already be in use"); +} + +void CEcon::Update() +{ + if(!m_Ready) + return; + + m_NetConsole.Update(); + + char aBuf[NET_MAX_PACKETSIZE]; + int ClientID; + + while(m_NetConsole.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID)) + { + dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "got message from empty slot"); + if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED) + { + if(str_comp(aBuf, g_Config.m_EcPassword) == 0) + { + m_aClients[ClientID].m_State = CClient::STATE_AUTHED; + m_NetConsole.Send(ClientID, "Authentication successful. External console access granted."); + + str_format(aBuf, sizeof(aBuf), "cid=%d authed", ClientID); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "econ", aBuf); + } + else + { + m_aClients[ClientID].m_AuthTries++; + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Wrong password %d/%d.", m_aClients[ClientID].m_AuthTries, MAX_AUTH_TRIES); + m_NetConsole.Send(ClientID, aBuf); + if(m_aClients[ClientID].m_AuthTries >= MAX_AUTH_TRIES) + { + if(!g_Config.m_EcBantime) + m_NetConsole.Drop(ClientID, "Too many authentication tries"); + else + { + NETADDR Addr = m_NetConsole.ClientAddr(ClientID); + m_NetConsole.AddBan(Addr, g_Config.m_EcBantime*60); + } + } + } + } + else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + { + char aFormatted[256]; + str_format(aFormatted, sizeof(aBuf), "cid=%d cmd='%s'", ClientID, aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aFormatted); + Console()->ExecuteLine(aBuf); + } + } + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; ++i) + { + if(m_aClients[i].m_State == CClient::STATE_CONNECTED && + time_get() > m_aClients[i].m_TimeConnected + g_Config.m_EcAuthTimeout * time_freq()) + m_NetConsole.Drop(i, "authentication timeout"); + } +} + +void CEcon::Send(int ClientID, const char *pLine) +{ + if(!m_Ready) + return; + + if(ClientID == -1) + { + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aClients[i].m_State == CClient::STATE_AUTHED) + m_NetConsole.Send(i, pLine); + } + } + else if(ClientID >= 0 && ClientID < NET_MAX_CONSOLE_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED) + m_NetConsole.Send(ClientID, pLine); +} + +void CEcon::Shutdown() +{ + if(!m_Ready) + return; + + m_NetConsole.Close(); +} diff --git a/src/engine/shared/econ.h b/src/engine/shared/econ.h new file mode 100644 index 00000000..daec34c4 --- /dev/null +++ b/src/engine/shared/econ.h @@ -0,0 +1,50 @@ +#ifndef ENGINE_SHARED_ECON_H +#define ENGINE_SHARED_ECON_H + +#include "network.h" + +class CEcon +{ + enum + { + MAX_AUTH_TRIES=3, + }; + + class CClient + { + public: + enum + { + STATE_EMPTY=0, + STATE_CONNECTED, + STATE_AUTHED, + }; + + int m_State; + int64 m_TimeConnected; + int m_AuthTries; + }; + CClient m_aClients[NET_MAX_CONSOLE_CLIENTS]; + + IConsole *m_pConsole; + CNetConsole m_NetConsole; + + bool m_Ready; + int m_PrintCBIndex; + + static void SendLineCB(const char *pLine, void *pUserData); + static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + static int NewClientCallback(int ClientID, void *pUser); + static int DelClientCallback(int ClientID, const char *pReason, void *pUser); + +public: + IConsole *Console() { return m_pConsole; } + + void Init(IConsole *pConsole); + void Update(); + void Send(int ClientID, const char *pLine); + void Shutdown(); +}; + +#endif diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp new file mode 100644 index 00000000..622534f2 --- /dev/null +++ b/src/engine/shared/filecollection.cpp @@ -0,0 +1,186 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#include <base/math.h> + +#include <engine/storage.h> + +#include "filecollection.h" + +bool CFileCollection::IsFilenameValid(const char *pFilename) +{ + if(str_length(pFilename) != m_FileDescLength+TIMESTAMP_LENGTH+m_FileExtLength || + str_comp_num(pFilename, m_aFileDesc, m_FileDescLength) || + str_comp(pFilename+m_FileDescLength+TIMESTAMP_LENGTH, m_aFileExt)) + return false; + + pFilename += m_FileDescLength; + if(pFilename[0] == '_' && + pFilename[1] >= '0' && pFilename[1] <= '9' && + pFilename[2] >= '0' && pFilename[2] <= '9' && + pFilename[3] >= '0' && pFilename[3] <= '9' && + pFilename[4] >= '0' && pFilename[4] <= '9' && + pFilename[5] == '-' && + pFilename[6] >= '0' && pFilename[6] <= '9' && + pFilename[7] >= '0' && pFilename[7] <= '9' && + pFilename[8] == '-' && + pFilename[9] >= '0' && pFilename[9] <= '9' && + pFilename[10] >= '0' && pFilename[10] <= '9' && + pFilename[11] == '_' && + pFilename[12] >= '0' && pFilename[12] <= '9' && + pFilename[13] >= '0' && pFilename[13] <= '9' && + pFilename[14] == '-' && + pFilename[15] >= '0' && pFilename[15] <= '9' && + pFilename[16] >= '0' && pFilename[16] <= '9' && + pFilename[17] == '-' && + pFilename[18] >= '0' && pFilename[18] <= '9' && + pFilename[19] >= '0' && pFilename[19] <= '9') + return true; + + return false; +} + +int64 CFileCollection::ExtractTimestamp(const char *pTimestring) +{ + int64 Timestamp = pTimestring[0]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[1]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[2]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[3]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[5]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[6]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[8]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[9]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[11]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[12]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[14]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[15]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[17]-'0'; Timestamp <<= 4; + Timestamp += pTimestring[18]-'0'; + + return Timestamp; +} + +void CFileCollection::BuildTimestring(int64 Timestamp, char *pTimestring) +{ + pTimestring[19] = 0; + pTimestring[18] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[17] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[16] = '-'; + pTimestring[15] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[14] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[13] = '-'; + pTimestring[12] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[11] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[10] = '_'; + pTimestring[9] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[8] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[7] = '-'; + pTimestring[6] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[5] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[4] = '-'; + pTimestring[3] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[2] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[1] = (Timestamp&0xF)+'0'; Timestamp >>= 4; + pTimestring[0] = (Timestamp&0xF)+'0'; +} + +void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) +{ + mem_zero(m_aTimestamps, sizeof(m_aTimestamps)); + m_NumTimestamps = 0; + m_MaxEntries = clamp(MaxEntries, 1, static_cast<int>(MAX_ENTRIES)); + str_copy(m_aFileDesc, pFileDesc, sizeof(m_aFileDesc)); + m_FileDescLength = str_length(m_aFileDesc); + str_copy(m_aFileExt, pFileExt, sizeof(m_aFileExt)); + m_FileExtLength = str_length(m_aFileExt); + str_copy(m_aPath, pPath, sizeof(m_aPath)); + m_pStorage = pStorage; + + m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); +} + +void CFileCollection::AddEntry(int64 Timestamp) +{ + if(m_NumTimestamps == 0) + { + // empty list + m_aTimestamps[m_NumTimestamps++] = Timestamp; + } + else + { + // remove old file + if(m_NumTimestamps == m_MaxEntries) + { + char aBuf[512]; + char aTimestring[TIMESTAMP_LENGTH]; + BuildTimestring(m_aTimestamps[0], aTimestring); + str_format(aBuf, sizeof(aBuf), "%s/%s_%s%s", m_aPath, m_aFileDesc, aTimestring, m_aFileExt); + m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); + } + + // add entry to the sorted list + if(m_aTimestamps[0] > Timestamp) + { + // first entry + if(m_NumTimestamps < m_MaxEntries) + { + mem_move(m_aTimestamps+1, m_aTimestamps, m_NumTimestamps*sizeof(int64)); + m_aTimestamps[0] = Timestamp; + ++m_NumTimestamps; + } + } + else if(m_aTimestamps[m_NumTimestamps-1] <= Timestamp) + { + // last entry + if(m_NumTimestamps == m_MaxEntries) + { + mem_move(m_aTimestamps, m_aTimestamps+1, (m_NumTimestamps-1)*sizeof(int64)); + m_aTimestamps[m_NumTimestamps-1] = Timestamp; + } + else + m_aTimestamps[m_NumTimestamps++] = Timestamp; + } + else + { + // middle entry + int Left = 0, Right = m_NumTimestamps-1; + while(Right-Left > 1) + { + int Mid = (Left+Right)/2; + if(m_aTimestamps[Mid] > Timestamp) + Right = Mid; + else + Left = Mid; + } + + if(m_NumTimestamps == m_MaxEntries) + { + mem_move(m_aTimestamps, m_aTimestamps+1, (Right-1)*sizeof(int64)); + m_aTimestamps[Right-1] = Timestamp; + } + else + { + mem_move(m_aTimestamps+Right+1, m_aTimestamps+Right, (m_NumTimestamps-Right)*sizeof(int64)); + m_aTimestamps[Right] = Timestamp; + ++m_NumTimestamps; + } + } + } +} + +int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) +{ + CFileCollection *pThis = static_cast<CFileCollection *>(pUser); + + // check for valid file name format + if(IsDir || !pThis->IsFilenameValid(pFilename)) + return 0; + + // extract the timestamp + int64 Timestamp = pThis->ExtractTimestamp(pFilename+pThis->m_FileDescLength+1); + + // add the entry + pThis->AddEntry(Timestamp); + + return 0; +} diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h new file mode 100644 index 00000000..ac633892 --- /dev/null +++ b/src/engine/shared/filecollection.h @@ -0,0 +1,35 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef ENGINE_SHARED_FILECOLLECTION_H +#define ENGINE_SHARED_FILECOLLECTION_H + +class CFileCollection +{ + enum + { + MAX_ENTRIES=1000, + TIMESTAMP_LENGTH=20, // _YYYY-MM-DD_HH-MM-SS + }; + + int64 m_aTimestamps[MAX_ENTRIES]; + int m_NumTimestamps; + int m_MaxEntries; + char m_aFileDesc[128]; + int m_FileDescLength; + char m_aFileExt[32]; + int m_FileExtLength; + char m_aPath[512]; + IStorage *m_pStorage; + + bool IsFilenameValid(const char *pFilename); + int64 ExtractTimestamp(const char *pTimestring); + void BuildTimestring(int64 Timestamp, char *pTimestring); + +public: + void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); + void AddEntry(int64 Timestamp); + + static int FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser); +}; + +#endif diff --git a/src/engine/shared/masterserver.cpp b/src/engine/shared/masterserver.cpp index 1bf402ca..1271eeaf 100644 --- a/src/engine/shared/masterserver.cpp +++ b/src/engine/shared/masterserver.cpp @@ -21,51 +21,57 @@ public: bool m_Valid; CHostLookup m_Lookup; - } ; + }; + + enum + { + STATE_INIT, + STATE_UPDATE, + STATE_READY, + }; CMasterInfo m_aMasterServers[MAX_MASTERSERVERS]; - int m_NeedsUpdate; + int m_State; IEngine *m_pEngine; IStorage *m_pStorage; CMasterServer() { SetDefault(); - m_NeedsUpdate = -1; + m_State = STATE_INIT; m_pEngine = 0; + m_pStorage = 0; } virtual int RefreshAddresses(int Nettype) { - int i; - - if(m_NeedsUpdate != -1) - return 0; + if(m_State != STATE_INIT) + return -1; dbg_msg("engine/mastersrv", "refreshing master server addresses"); // add lookup jobs - for(i = 0; i < MAX_MASTERSERVERS; i++) + for(int i = 0; i < MAX_MASTERSERVERS; i++) { m_pEngine->HostLookup(&m_aMasterServers[i].m_Lookup, m_aMasterServers[i].m_aHostname, Nettype); m_aMasterServers[i].m_Valid = false; } - m_NeedsUpdate = 1; + m_State = STATE_UPDATE; return 0; } virtual void Update() { // check if we need to update - if(m_NeedsUpdate != 1) + if(m_State != STATE_UPDATE) return; - m_NeedsUpdate = 0; + m_State = STATE_READY; for(int i = 0; i < MAX_MASTERSERVERS; i++) { if(m_aMasterServers[i].m_Lookup.m_Job.Status() != CJob::STATE_DONE) - m_NeedsUpdate = 1; + m_State = STATE_UPDATE; else { if(m_aMasterServers[i].m_Lookup.m_Job.Result() == 0) @@ -79,7 +85,7 @@ public: } } - if(!m_NeedsUpdate) + if(m_State == STATE_READY) { dbg_msg("engine/mastersrv", "saving addresses"); Save(); @@ -88,7 +94,7 @@ public: virtual int IsRefreshing() { - return m_NeedsUpdate; + return m_State != STATE_READY; } virtual NETADDR GetAddr(int Index) @@ -106,16 +112,6 @@ public: return m_aMasterServers[Index].m_Valid; } - virtual void DumpServers() - { - for(int i = 0; i < MAX_MASTERSERVERS; i++) - { - char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); - dbg_msg("mastersrv", "#%d = %s", i, aAddrStr); - } - } - virtual void Init() { m_pEngine = Kernel()->RequestInterface<IEngine>(); @@ -131,17 +127,15 @@ public: virtual int Load() { - CLineReader LineReader; - IOHANDLE File; - int Count = 0; if(!m_pStorage) return -1; // try to open file - File = m_pStorage->OpenFile("masters.cfg", IOFLAG_READ, IStorage::TYPE_SAVE); + IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_READ, IStorage::TYPE_SAVE); if(!File) return -1; + CLineReader LineReader; LineReader.Init(File); while(1) { @@ -152,19 +146,32 @@ public: // parse line char aAddrStr[NETADDR_MAXSTRSIZE]; - if(sscanf(pLine, "%s %s", Info.m_aHostname, aAddrStr) == 2 && net_addr_from_str(&Info.m_Addr, aAddrStr) == 0) + if(sscanf(pLine, "%127s %47s", Info.m_aHostname, aAddrStr) == 2 && net_addr_from_str(&Info.m_Addr, aAddrStr) == 0) { Info.m_Addr.port = 8300; - if(Count != MAX_MASTERSERVERS) + bool Added = false; + for(int i = 0; i < MAX_MASTERSERVERS; ++i) + if(str_comp(m_aMasterServers[i].m_aHostname, Info.m_aHostname) == 0) + { + m_aMasterServers[i] = Info; + Added = true; + break; + } + + if(!Added) { - m_aMasterServers[Count] = Info; - Count++; + for(int i = 0; i < MAX_MASTERSERVERS; ++i) + if(m_aMasterServers[i].m_Addr.type == NETTYPE_INVALID) + { + m_aMasterServers[i] = Info; + Added = true; + break; + } } - //else - // dbg_msg("engine/mastersrv", "warning: skipped master server '%s' due to limit of %d", pLine, MAX_MASTERSERVERS); + + if(!Added) + break; } - //else - // dbg_msg("engine/mastersrv", "warning: couldn't parse master server '%s'", pLine); } io_close(File); @@ -173,21 +180,22 @@ public: virtual int Save() { - IOHANDLE File; - if(!m_pStorage) return -1; // try to open file - File = m_pStorage->OpenFile("masters.cfg", IOFLAG_WRITE, IStorage::TYPE_SAVE); + IOHANDLE File = m_pStorage->OpenFile("masters.cfg", IOFLAG_WRITE, IStorage::TYPE_SAVE); if(!File) return -1; for(int i = 0; i < MAX_MASTERSERVERS; i++) { char aAddrStr[NETADDR_MAXSTRSIZE]; - net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); - char aBuf[1024]; + if(m_aMasterServers[i].m_Addr.type != NETTYPE_INVALID) + net_addr_str(&m_aMasterServers[i].m_Addr, aAddrStr, sizeof(aAddrStr)); + else + aAddrStr[0] = 0; + char aBuf[256]; str_format(aBuf, sizeof(aBuf), "%s %s\n", m_aMasterServers[i].m_aHostname, aAddrStr); io_write(File, aBuf, str_length(aBuf)); diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h index 425d970a..d10c03b6 100644 --- a/src/engine/shared/network.h +++ b/src/engine/shared/network.h @@ -49,6 +49,7 @@ enum NET_MAX_CHUNKHEADERSIZE = 5, NET_PACKETHEADERSIZE = 3, NET_MAX_CLIENTS = 16, + NET_MAX_CONSOLE_CLIENTS = 4, NET_MAX_SEQUENCE = 1<<10, NET_SEQUENCE_MASK = NET_MAX_SEQUENCE-1, @@ -192,6 +193,36 @@ public: int AckSequence() const { return m_Ack; } }; +class CConsoleNetConnection +{ +private: + int m_State; + + NETADDR m_PeerAddr; + NETSOCKET m_Socket; + + char m_aBuffer[NET_MAX_PACKETSIZE]; + int m_BufferOffset; + + char m_aErrorString[256]; + + bool m_LineEndingDetected; + char m_aLineEnding[3]; + +public: + void Init(NETSOCKET Socket, const NETADDR *pAddr); + void Disconnect(const char *pReason); + + int State() const { return m_State; } + NETADDR PeerAddress() const { return m_PeerAddr; } + const char *ErrorString() const { return m_aErrorString; } + + void Reset(); + int Update(); + int Send(const char *pLine); + int Recv(char *pLine, int MaxLength); +}; + class CNetRecvUnpacker { public: @@ -285,12 +316,66 @@ public: // status requests NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } NETSOCKET Socket() const { return m_Socket; } + int NetType() { return m_Socket.type; } int MaxClients() const { return m_MaxClients; } // void SetMaxClientsPerIP(int Max); }; +class CNetConsole +{ + enum + { + MAX_BANS=128, + }; + + int FindBan(NETADDR Addr); + void UpdateBans(); + + struct CBanEntry + { + NETADDR m_Addr; + int m_Expires; + } m_aBans[MAX_BANS]; + int m_NumBans; + + struct CSlot + { + CConsoleNetConnection m_Connection; + }; + + NETSOCKET m_Socket; + CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS]; + + NETFUNC_NEWCLIENT m_pfnNewClient; + NETFUNC_DELCLIENT m_pfnDelClient; + void *m_UserPtr; + + CNetRecvUnpacker m_RecvUnpacker; + +public: + void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser); + + // + bool Open(NETADDR BindAddr, int Flags); + int Close(); + + // + int Recv(char *pLine, int MaxLength, int *pClientID = 0); + int Send(int ClientID, const char *pLine); + int Update(); + + // + int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr); + int Drop(int ClientID, const char *pReason); + + bool AddBan(NETADDR Addr, int Seconds); + + // status requests + NETADDR ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); } +}; + // client side @@ -320,6 +405,7 @@ public: int ResetErrorString(); // error and state + int NetType() { return m_Socket.type; } int State(); int GotProblems(); const char *ErrorString(); diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp new file mode 100644 index 00000000..13ed3751 --- /dev/null +++ b/src/engine/shared/network_console.cpp @@ -0,0 +1,219 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <base/system.h> +#include "network.h" + +bool CNetConsole::Open(NETADDR BindAddr, int Flags) +{ + // zero out the whole structure + mem_zero(this, sizeof(*this)); + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + + // open socket + m_Socket = net_tcp_create(BindAddr); + if(!m_Socket.type) + return false; + if(net_tcp_listen(m_Socket, NET_MAX_CONSOLE_CLIENTS)) + return false; + net_set_non_blocking(m_Socket); + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aSlots[i].m_Connection.Reset(); + + return true; +} + +void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser) +{ + m_pfnNewClient = pfnNewClient; + m_pfnDelClient = pfnDelClient; + m_UserPtr = pUser; +} + +int CNetConsole::Close() +{ + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + m_aSlots[i].m_Connection.Disconnect("closing console"); + + net_tcp_close(m_Socket); + + return 0; +} + +int CNetConsole::Drop(int ClientID, const char *pReason) +{ + if(m_pfnDelClient) + m_pfnDelClient(ClientID, pReason, m_UserPtr); + + m_aSlots[ClientID].m_Connection.Disconnect(pReason); + + return 0; +} + +int CNetConsole::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr) +{ + char aError[256] = { 0 }; + int FreeSlot = -1; + + // look for free slot or multiple client + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE) + FreeSlot = i; + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + if(net_addr_comp(pAddr, &PeerAddr) == 0) + { + str_copy(aError, "only one client per IP allowed", sizeof(aError)); + break; + } + } + } + + // accept client + if(!aError[0] && FreeSlot != -1) + { + m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr); + if(m_pfnNewClient) + m_pfnNewClient(FreeSlot, m_UserPtr); + return 0; + } + + // reject client + if(!aError[0]) + str_copy(aError, "no free slot available", sizeof(aError)); + + net_tcp_send(Socket, aError, str_length(aError)); + net_tcp_close(Socket); + + return -1; +} + +int CNetConsole::Update() +{ + NETSOCKET Socket; + NETADDR Addr; + + if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0) + { + int Index = FindBan(Addr); + if(Index == -1) + AcceptClient(Socket, &Addr); + else + { + char aBuf[128]; + if(m_aBans[Index].m_Expires > -1) + { + int Mins = (m_aBans[Index].m_Expires-time_timestamp()+ 59) / 60; + if(Mins <= 1) + str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute"); + else + str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins); + } + else + str_format(aBuf, sizeof(aBuf), "You have been banned for life"); + + net_tcp_send(Socket, aBuf, str_length(aBuf)); + net_tcp_close(Socket); + } + } + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE) + m_aSlots[i].m_Connection.Update(); + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR) + Drop(i, m_aSlots[i].m_Connection.ErrorString()); + } + + UpdateBans(); + + return 0; +} + +int CNetConsole::Recv(char *pLine, int MaxLength, int *pClientID) +{ + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength)) + { + if(pClientID) + *pClientID = i; + return 1; + } + } + return 0; +} + +int CNetConsole::Send(int ClientID, const char *pLine) +{ + if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE) + return m_aSlots[ClientID].m_Connection.Send(pLine); + else + return -1; +} + +int CNetConsole::FindBan(NETADDR Addr) +{ + Addr.port = 0; + for(int i = 0; i < m_NumBans; i++) + if(net_addr_comp(&m_aBans[i].m_Addr, &Addr) == 0) + return i; + + return -1; +} + +bool CNetConsole::AddBan(NETADDR Addr, int Seconds) +{ + if(m_NumBans == MAX_BANS) + return false; + + Addr.port = 0; + int Index = FindBan(Addr); + if(Index == -1) + { + Index = m_NumBans++; + m_aBans[Index].m_Addr = Addr; + } + m_aBans[Index].m_Expires = Seconds>0 ? time_timestamp()+Seconds : -1; + + for(int i = 0; i < NET_MAX_CONSOLE_CLIENTS; i++) + { + if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE) + { + NETADDR PeerAddr = m_aSlots[i].m_Connection.PeerAddress(); + PeerAddr.port = 0; + if(net_addr_comp(&Addr, &PeerAddr) == 0) + { + char aBuf[128]; + if(Seconds>0) + { + int Mins = (Seconds + 59) / 60; + if(Mins <= 1) + str_format(aBuf, sizeof(aBuf), "You have been banned for 1 minute"); + else + str_format(aBuf, sizeof(aBuf), "You have been banned for %d minutes", Mins); + } + else + str_format(aBuf, sizeof(aBuf), "You have been banned for life"); + Drop(i, aBuf); + } + } + } + return true; +} + +void CNetConsole::UpdateBans() +{ + int Now = time_timestamp(); + for(int i = 0; i < m_NumBans; ++i) + if(m_aBans[i].m_Expires > 0 && m_aBans[i].m_Expires < Now) + { + m_aBans[i] = m_aBans[m_NumBans-1]; + --m_NumBans; + break; + } +} diff --git a/src/engine/shared/network_console_conn.cpp b/src/engine/shared/network_console_conn.cpp new file mode 100644 index 00000000..75b581fa --- /dev/null +++ b/src/engine/shared/network_console_conn.cpp @@ -0,0 +1,186 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include <base/system.h> +#include "network.h" + +void CConsoleNetConnection::Reset() +{ + m_State = NET_CONNSTATE_OFFLINE; + mem_zero(&m_PeerAddr, sizeof(m_PeerAddr)); + m_aErrorString[0] = 0; + + m_Socket.type = NETTYPE_INVALID; + m_Socket.ipv4sock = -1; + m_Socket.ipv6sock = -1; + m_aBuffer[0] = 0; + m_BufferOffset = 0; + + m_LineEndingDetected = false; + #if defined(CONF_FAMILY_WINDOWS) + m_aLineEnding[0] = '\r'; + m_aLineEnding[1] = '\n'; + m_aLineEnding[2] = 0; + #else + m_aLineEnding[0] = '\n'; + m_aLineEnding[1] = 0; + m_aLineEnding[2] = 0; + #endif +} + +void CConsoleNetConnection::Init(NETSOCKET Socket, const NETADDR *pAddr) +{ + Reset(); + + m_Socket = Socket; + net_set_non_blocking(m_Socket); + + m_PeerAddr = *pAddr; + m_State = NET_CONNSTATE_ONLINE; +} + +void CConsoleNetConnection::Disconnect(const char *pReason) +{ + if(State() == NET_CONNSTATE_OFFLINE) + return; + + if(pReason && pReason[0]) + Send(pReason); + + net_tcp_close(m_Socket); + + Reset(); +} + +int CConsoleNetConnection::Update() +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if((int)(sizeof(m_aBuffer)) <= m_BufferOffset) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString)); + return -1; + } + + int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset); + + if(Bytes > 0) + { + m_BufferOffset += Bytes; + } + else if(Bytes < 0) + { + if(net_would_block()) // no data received + return 0; + + m_State = NET_CONNSTATE_ERROR; // error + str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString)); + return -1; + } + else + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString)); + return -1; + } + } + + return 0; +} + +int CConsoleNetConnection::Recv(char *pLine, int MaxLength) +{ + if(State() == NET_CONNSTATE_ONLINE) + { + if(m_BufferOffset) + { + // find message start + int StartOffset = 0; + while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n') + { + // detect clients line ending format + if(!m_LineEndingDetected) + { + m_aLineEnding[0] = m_aBuffer[StartOffset]; + if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') && + m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1]) + m_aLineEnding[1] = m_aBuffer[StartOffset+1]; + m_LineEndingDetected = true; + } + + if(++StartOffset >= m_BufferOffset) + { + m_BufferOffset = 0; + return 0; + } + } + + // find message end + int EndOffset = StartOffset; + while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n') + { + if(++EndOffset >= m_BufferOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + } + + // extract message and update buffer + if(MaxLength-1 < EndOffset-StartOffset) + { + if(StartOffset > 0) + { + mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset); + m_BufferOffset -= StartOffset; + } + return 0; + } + mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset); + pLine[EndOffset-StartOffset] = 0; + str_sanitize_cc(pLine); + mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset); + m_BufferOffset -= EndOffset; + return 1; + } + } + return 0; +} + +int CConsoleNetConnection::Send(const char *pLine) +{ + if(State() != NET_CONNSTATE_ONLINE) + return -1; + + char aBuf[1024]; + str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2); + int Length = str_length(aBuf); + aBuf[Length] = m_aLineEnding[0]; + aBuf[Length+1] = m_aLineEnding[1]; + aBuf[Length+2] = m_aLineEnding[2]; + Length += 3; + const char *pData = aBuf; + + while(true) + { + int Send = net_tcp_send(m_Socket, pData, Length); + if(Send < 0) + { + m_State = NET_CONNSTATE_ERROR; + str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString)); + return -1; + } + + if(Send >= Length) + break; + + pData += Send; + Length -= Send; + } + + return 0; +} diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp index f3fbfa32..b100e1a2 100644 --- a/src/engine/shared/network_server.cpp +++ b/src/engine/shared/network_server.cpp @@ -221,10 +221,10 @@ int CNetServer::BanAdd(NETADDR Addr, int Seconds, const char *pReason) char Buf[128]; NETADDR BanAddr; - int Mins = (Seconds + 59) / 60; - if(Mins) + if(Stamp > -1) { - if(Mins == 1) + int Mins = (Seconds + 59) / 60; + if(Mins <= 1) str_format(Buf, sizeof(Buf), "You have been banned for 1 minute (%s)", pReason); else str_format(Buf, sizeof(Buf), "You have been banned for %d minutes (%s)", Mins, pReason); @@ -255,7 +255,7 @@ int CNetServer::Update() } // remove expired bans - while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires < Now) + while(m_BanPool_FirstUsed && m_BanPool_FirstUsed->m_Info.m_Expires > -1 && m_BanPool_FirstUsed->m_Info.m_Expires < Now) { CBan *pBan = m_BanPool_FirstUsed; BanRemoveByObject(pBan); @@ -307,10 +307,10 @@ int CNetServer::Recv(CNetChunk *pChunk) { // banned, reply with a message char BanStr[128]; - if(pBan->m_Info.m_Expires) + if(pBan->m_Info.m_Expires > -1) { int Mins = ((pBan->m_Info.m_Expires - Now)+59)/60; - if(Mins == 1) + if(Mins <= 1) str_format(BanStr, sizeof(BanStr), "Banned for 1 minute (%s)", pBan->m_Info.m_Reason); else str_format(BanStr, sizeof(BanStr), "Banned for %d minutes (%s)", Mins, pBan->m_Info.m_Reason); diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h index 4a4895ad..ba04da8a 100644 --- a/src/engine/shared/protocol.h +++ b/src/engine/shared/protocol.h @@ -65,7 +65,11 @@ enum // sent by both NETMSG_PING, NETMSG_PING_REPLY, - NETMSG_ERROR + NETMSG_ERROR, + + // sent by server (todo: move it up) + NETMSG_RCON_CMD_ADD, + NETMSG_RCON_CMD_REM, }; // this should be revised |