diff --git a/CMakeLists.txt b/CMakeLists.txt index 10899340..1762caf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -603,6 +603,7 @@ set(BASE src/base/hash_ctxt.h src/base/hash_libtomcrypt.cpp src/base/hash_openssl.cpp + src/base/lock.h src/base/lock_scope.h src/base/log.cpp src/base/log.h @@ -652,6 +653,8 @@ set_glob(ENGINE_INTERFACE GLOB src/engine warning.h ) set_glob(ENGINE_SHARED GLOB src/engine/shared + assertion_logger.cpp + assertion_logger.h compression.cpp compression.h config.cpp diff --git a/autoexec.cfg b/autoexec.cfg index 244e20c9..b7baafbc 100644 --- a/autoexec.cfg +++ b/autoexec.cfg @@ -8,6 +8,9 @@ sv_port 8303 # Server name sv_name "My InfClass server" +# Password for joining the server, empty for no password +password "" + # rcon (F2) passwords for admin. If you don't set one, a random one will be # created and shown in the terminal window of the server. sv_rcon_password "" @@ -30,12 +33,18 @@ sv_server_info_per_second 100 # File where server log will be stored logfile infclassr.log +# Log level (-3 = None, -2 = Error, -1 = Warn, 0 = Info, 1 = Debug, 2 = Trace) +loglevel 0 + # Max players on server sv_max_clients 64 # Max players with the same IP address sv_max_clients_per_ip 4 +# Tournament mode - when enabled players joins the server as spectator +sv_tournament_mode 0 + # SERVER CUSTOMIZATION # -------------------- diff --git a/src/base/lock.h b/src/base/lock.h new file mode 100644 index 00000000..ecebfc20 --- /dev/null +++ b/src/base/lock.h @@ -0,0 +1,135 @@ +#ifndef BASE_LOCK_H +#define BASE_LOCK_H + +#include + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(__clang__) && (!defined(SWIG)) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SCOPED_CAPABILITY \ + THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define PT_GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define RELEASE_GENERIC(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define EXCLUDES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define ASSERT_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define RETURN_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +/** + * @defgroup Locks + * @see Threads + */ + +/** + * Wrapper for `std::mutex`. + * + * @ingroup Locks + * + * @remark This wrapper is only necessary because the clang thread-safety attributes + * are not available for `std::mutex` except when explicitly using libc++. + */ +class CAPABILITY("mutex") CLock +{ +public: + CLock() = default; + + void lock() ACQUIRE() + { + m_Mutex.lock(); + } + + void unlock() RELEASE() + { + m_Mutex.unlock(); + } + + // To support negative capabilities, otherwise EXCLUDES(m_Lock) must be used instead of REQUIRES(!m_Lock) + const CLock &operator!() const { return *this; } + +private: + std::mutex m_Mutex; +}; + +/** + * RAII-style wrapper for owning a `CLock`. + * + * @ingroup Locks + * + * @remark This wrapper is only necessary because the clang thread-safety attributes + * are not available for `std::lock_guard` except when explicitly using libc++. + */ +class SCOPED_CAPABILITY CLockScope +{ +public: + explicit CLockScope(CLock &Lock) ACQUIRE(Lock, m_Lock) : + m_Lock(Lock) + { + m_Lock.lock(); + } + + ~CLockScope() RELEASE() REQUIRES(m_Lock) + { + m_Lock.unlock(); + } + +private: + CLock &m_Lock; +}; + +#endif diff --git a/src/base/log.cpp b/src/base/log.cpp index 04e32a08..b0b167c6 100644 --- a/src/base/log.cpp +++ b/src/base/log.cpp @@ -5,6 +5,7 @@ #include #include +#include #if defined(CONF_FAMILY_WINDOWS) #define WIN32_LEAN_AND_MEAN @@ -19,16 +20,10 @@ #include #endif -std::atomic loglevel = LEVEL_INFO; std::atomic global_logger = nullptr; thread_local ILogger *scope_logger = nullptr; thread_local bool in_logger = false; -void log_set_loglevel(LEVEL level) -{ - loglevel.store(level, std::memory_order_release); -} - void log_set_global_logger(ILogger *logger) { ILogger *null = nullptr; @@ -41,7 +36,9 @@ void log_set_global_logger(ILogger *logger) void log_global_logger_finish() { - global_logger.load(std::memory_order_acquire)->GlobalFinish(); + ILogger *logger = global_logger.load(std::memory_order_acquire); + if(logger) + logger->GlobalFinish(); } void log_set_global_logger_default() @@ -73,11 +70,12 @@ void log_set_scope_logger(ILogger *logger) } } +// Separate declaration, as attributes are not allowed on function definitions void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args) -{ - if(level > loglevel.load(std::memory_order_acquire)) - return; + GNUC_ATTRIBUTE((format(printf, 5, 0))); +void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys, const char *fmt, va_list args) +{ // Make sure we're not logging recursively. if(in_logger) { @@ -109,18 +107,7 @@ void log_log_impl(LEVEL level, bool have_color, LOG_COLOR color, const char *sys char *pMessage = Msg.m_aLine + Msg.m_LineMessageOffset; int MessageSize = sizeof(Msg.m_aLine) - Msg.m_LineMessageOffset; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif -#if defined(CONF_FAMILY_WINDOWS) - _vsprintf_p(pMessage, MessageSize, fmt, args); -#else - vsnprintf(pMessage, MessageSize, fmt, args); -#endif -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif + str_format_v(pMessage, MessageSize, fmt, args); Msg.m_LineLength = str_length(Msg.m_aLine); scope_logger->Log(&Msg); in_logger = false; @@ -152,12 +139,21 @@ void log_log_color(LEVEL level, LOG_COLOR color, const char *sys, const char *fm va_end(args); } +bool CLogFilter::Filters(const CLogMessage *pMessage) +{ + return pMessage->m_Level > m_MaxLevel.load(std::memory_order_relaxed); +} + #if defined(CONF_PLATFORM_ANDROID) class CLoggerAndroid : public ILogger { public: void Log(const CLogMessage *pMessage) override { + if(m_Filter.Filters(pMessage)) + { + return; + } int AndroidLevel; switch(pMessage->m_Level) { @@ -190,9 +186,14 @@ class CLoggerCollection : public ILogger CLoggerCollection(std::vector> &&vpLoggers) : m_vpLoggers(std::move(vpLoggers)) { + m_Filter.m_MaxLevel.store(LEVEL_TRACE, std::memory_order_relaxed); } void Log(const CLogMessage *pMessage) override { + if(m_Filter.Filters(pMessage)) + { + return; + } for(auto &pLogger : m_vpLoggers) { pLogger->Log(pMessage); @@ -227,6 +228,10 @@ class CLoggerAsync : public ILogger } void Log(const CLogMessage *pMessage) override { + if(m_Filter.Filters(pMessage)) + { + return; + } aio_lock(m_pAio); if(m_AnsiTruecolor) { @@ -251,7 +256,7 @@ class CLoggerAsync : public ILogger aio_write_newline_unlocked(m_pAio); aio_unlock(m_pAio); } - ~CLoggerAsync() + ~CLoggerAsync() override { if(m_Close) { @@ -314,7 +319,7 @@ class CWindowsConsoleLogger : public ILogger HANDLE m_pConsole; int m_BackgroundColor; int m_ForegroundColor; - std::mutex m_OutputLock; + CLock m_OutputLock; bool m_Finished = false; public: @@ -333,14 +338,13 @@ class CWindowsConsoleLogger : public ILogger m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; } } - void Log(const CLogMessage *pMessage) override + void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock) { - int WLen = MultiByteToWideChar(CP_UTF8, 0, pMessage->m_aLine, pMessage->m_LineLength, NULL, 0); - dbg_assert(WLen > 0, "MultiByteToWideChar failure"); - WCHAR *pWide = (WCHAR *)malloc((WLen + 2) * sizeof(*pWide)); - dbg_assert(MultiByteToWideChar(CP_UTF8, 0, pMessage->m_aLine, pMessage->m_LineLength, pWide, WLen) == WLen, "MultiByteToWideChar failure"); - pWide[WLen++] = '\r'; - pWide[WLen++] = '\n'; + if(m_Filter.Filters(pMessage)) + { + return; + } + const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine) + L"\r\n"; int Color = m_BackgroundColor; if(pMessage->m_HaveColor) @@ -354,41 +358,41 @@ class CWindowsConsoleLogger : public ILogger else Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; - m_OutputLock.lock(); + const CLockScope LockScope(m_OutputLock); if(!m_Finished) { SetConsoleTextAttribute(m_pConsole, Color); - WriteConsoleW(m_pConsole, pWide, WLen, NULL, NULL); + WriteConsoleW(m_pConsole, WideMessage.c_str(), WideMessage.length(), NULL, NULL); } - m_OutputLock.unlock(); - free(pWide); } - void GlobalFinish() override + void GlobalFinish() override REQUIRES(!m_OutputLock) { // Restore original color - m_OutputLock.lock(); + const CLockScope LockScope(m_OutputLock); SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor); m_Finished = true; - m_OutputLock.unlock(); } }; class CWindowsFileLogger : public ILogger { HANDLE m_pFile; - std::mutex m_OutputLock; + CLock m_OutputLock; public: CWindowsFileLogger(HANDLE pFile) : m_pFile(pFile) { } - void Log(const CLogMessage *pMessage) override + void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock) { - m_OutputLock.lock(); + if(m_Filter.Filters(pMessage)) + { + return; + } + const CLockScope LockScope(m_OutputLock); DWORD Written; // we don't care about the value, but Windows 7 crashes if we pass NULL WriteFile(m_pFile, pMessage->m_aLine, pMessage->m_LineLength, &Written, NULL); WriteFile(m_pFile, "\r\n", 2, &Written, NULL); - m_OutputLock.unlock(); } }; #endif @@ -420,12 +424,12 @@ class CLoggerWindowsDebugger : public ILogger public: void Log(const CLogMessage *pMessage) override { - int WLen = MultiByteToWideChar(CP_UTF8, 0, pMessage->m_aLine, -1, NULL, 0); - dbg_assert(WLen > 0, "MultiByteToWideChar failure"); - WCHAR *pWide = (WCHAR *)malloc(WLen * sizeof(*pWide)); - dbg_assert(MultiByteToWideChar(CP_UTF8, 0, pMessage->m_aLine, -1, pWide, WLen) == WLen, "MultiByteToWideChar failure"); - OutputDebugStringW(pWide); - free(pWide); + if(m_Filter.Filters(pMessage)) + { + return; + } + const std::wstring WideMessage = windows_utf8_to_wide(pMessage->m_aLine); + OutputDebugStringW(WideMessage.c_str()); } }; std::unique_ptr log_logger_windows_debugger() @@ -440,42 +444,56 @@ std::unique_ptr log_logger_windows_debugger() } #endif -void CFutureLogger::Set(std::unique_ptr &&pLogger) +void CFutureLogger::Set(std::shared_ptr pLogger) { - ILogger *null = nullptr; - m_PendingLock.lock(); - ILogger *pLoggerRaw = pLogger.release(); - if(!m_pLogger.compare_exchange_strong(null, pLoggerRaw, std::memory_order_acq_rel)) + const CLockScope LockScope(m_PendingLock); + std::shared_ptr pNullLogger; + if(!std::atomic_compare_exchange_strong_explicit(&m_pLogger, &pNullLogger, pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel)) { dbg_assert(false, "future logger has already been set and can only be set once"); } + m_pLogger = std::move(pLogger); + for(const auto &Pending : m_vPending) { - pLoggerRaw->Log(&Pending); + m_pLogger->Log(&Pending); } m_vPending.clear(); m_vPending.shrink_to_fit(); - m_PendingLock.unlock(); } void CFutureLogger::Log(const CLogMessage *pMessage) { - ILogger *pLogger = m_pLogger.load(std::memory_order_acquire); + auto pLogger = std::atomic_load_explicit(&m_pLogger, std::memory_order_acquire); + if(pLogger) + { + pLogger->Log(pMessage); + return; + } + const CLockScope LockScope(m_PendingLock); + pLogger = std::atomic_load_explicit(&m_pLogger, std::memory_order_relaxed); if(pLogger) { pLogger->Log(pMessage); return; } - m_PendingLock.lock(); m_vPending.push_back(*pMessage); - m_PendingLock.unlock(); } void CFutureLogger::GlobalFinish() { - ILogger *pLogger = m_pLogger.load(std::memory_order_acquire); + auto pLogger = std::atomic_load_explicit(&m_pLogger, std::memory_order_acquire); if(pLogger) { pLogger->GlobalFinish(); } } + +void CFutureLogger::OnFilterChange() +{ + auto pLogger = std::atomic_load_explicit(&m_pLogger, std::memory_order_acquire); + if(pLogger) + { + pLogger->SetFilter(m_Filter); + } +} diff --git a/src/base/logger.h b/src/base/logger.h index b1055e2a..cc6575b4 100644 --- a/src/base/logger.h +++ b/src/base/logger.h @@ -1,11 +1,11 @@ #ifndef BASE_LOGGER_H #define BASE_LOGGER_H +#include "lock.h" #include "log.h" #include #include -#include #include typedef void *IOHANDLE; @@ -51,11 +51,36 @@ class CLogMessage } }; +class CLogFilter +{ +public: + /** + * The highest `LEVEL` that is still logged, -1 corresponds to no + * printing at all. + */ + std::atomic_int m_MaxLevel{LEVEL_INFO}; + + bool Filters(const CLogMessage *pMessage); +}; + class ILogger { +protected: + CLogFilter m_Filter; + public: virtual ~ILogger() {} + /** + * Set a new filter. It's up to the logger implementation to actually + * use the filter. + */ + void SetFilter(const CLogFilter &Filter) + { + m_Filter.m_MaxLevel.store(Filter.m_MaxLevel.load(std::memory_order_relaxed), std::memory_order_relaxed); + OnFilterChange(); + } + /** * Send the specified message to the logging backend. * @@ -78,18 +103,12 @@ class ILogger * @see log_global_logger_finish */ virtual void GlobalFinish() {} + /** + * Notifies the logger of a changed `m_Filter`. + */ + virtual void OnFilterChange() {} }; -/** - * @ingroup Log - * - * Sets the global loglevel to the given value. Log messages with a less severe - * loglevel will not be forwarded to loggers. - * - * @param level The loglevel. - */ -void log_set_loglevel(LEVEL level); - /** * @ingroup Log * @@ -199,22 +218,26 @@ std::unique_ptr log_logger_windows_debugger(); * * Useful when you want to set a global logger without all logging targets * being configured. + * + * This logger forwards `SetFilter` calls, `SetFilter` calls before a logger is + * set have no effect. */ class CFutureLogger : public ILogger { private: - std::atomic m_pLogger; + std::shared_ptr m_pLogger; std::vector m_vPending; - std::mutex m_PendingLock; + CLock m_PendingLock; public: /** * Replace the `CFutureLogger` instance with the given logger. It'll * receive all log messages sent to the `CFutureLogger` so far. */ - void Set(std::unique_ptr &&pLogger); - void Log(const CLogMessage *pMessage) override; + void Set(std::shared_ptr pLogger) REQUIRES(!m_PendingLock); + void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock); void GlobalFinish() override; + void OnFilterChange() override; }; /** diff --git a/src/base/tl/threading.h b/src/base/tl/threading.h index 7c3b3cc5..bcedd27f 100644 --- a/src/base/tl/threading.h +++ b/src/base/tl/threading.h @@ -28,44 +28,4 @@ class CSemaphore } }; -class SCOPED_CAPABILITY CLock -{ - LOCK m_Lock; - -public: - CLock() ACQUIRE(m_Lock) - { - m_Lock = lock_create(); - } - - ~CLock() RELEASE() - { - lock_destroy(m_Lock); - } - - CLock(const CLock &) = delete; - - void Take() ACQUIRE(m_Lock) { lock_wait(m_Lock); } - void Release() RELEASE() { lock_unlock(m_Lock); } -}; - -class CScopeLock -{ - CLock *m_pLock; - -public: - CScopeLock(CLock *pLock) - { - m_pLock = pLock; - m_pLock->Take(); - } - - ~CScopeLock() - { - m_pLock->Release(); - } - - CScopeLock(const CScopeLock &) = delete; -}; - #endif // BASE_TL_THREADING_H diff --git a/src/engine/console.h b/src/engine/console.h index 03189839..62af45a5 100644 --- a/src/engine/console.h +++ b/src/engine/console.h @@ -7,6 +7,8 @@ #include #include +#include + static const ColorRGBA gs_ConsoleDefaultColor(1, 1, 1, 1); enum LEVEL : char; @@ -123,13 +125,16 @@ class IConsole : public IInterface virtual void ResetServerGameSettings() = 0; static LEVEL ToLogLevel(int ConsoleLevel); + static int ToLogLevelFilter(int ConsoleLevel); // DDRace - bool m_Cheated; + virtual bool Cheated() const = 0; + + virtual int FlagMask() const = 0; virtual void SetFlagMask(int FlagMask) = 0; }; -extern IConsole *CreateConsole(int FlagMask); +std::unique_ptr CreateConsole(int FlagMask); #endif // FILE_ENGINE_CONSOLE_H diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 73165fc6..c7bf3127 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -308,6 +308,30 @@ void CServerBan::ConBanRegionRange(IConsole::IResult *pResult, void *pUser) ConBanRange(pResult, static_cast(pServerBan)); } +// Not thread-safe! +class CRconClientLogger : public ILogger +{ + CServer *m_pServer; + int m_ClientID; + +public: + CRconClientLogger(CServer *pServer, int ClientID) : + m_pServer(pServer), + m_ClientID(ClientID) + { + } + void Log(const CLogMessage *pMessage) override; +}; + +void CRconClientLogger::Log(const CLogMessage *pMessage) +{ + if(m_Filter.Filters(pMessage)) + { + return; + } + m_pServer->SendRconLogLine(m_ClientID, pMessage); +} + void CServer::CClient::Reset(bool ResetScore) { // reset input @@ -382,10 +406,11 @@ CServer::CServer() m_ServerInfoFirstRequest = 0; m_ServerInfoNumRequests = 0; - m_ServerInfoHighLoad = false; + m_ServerInfoNeedsUpdate = false; - m_ServerInfoRequestLogTick = 0; - m_ServerInfoRequestLogRecords = 0; +#ifdef CONF_FAMILY_UNIX + m_ConnLoggingSocketCreated = false; +#endif m_pConnectionPool = new CDbConnectionPool(); m_pRegister = nullptr; @@ -1886,7 +1911,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "ClientID=%d rcon='%s'", ClientID, pCmd); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); m_RconClientID = ClientID; m_RconAuthLevel = m_aClients[ClientID].m_Authed; switch(m_aClients[ClientID].m_Authed) @@ -1899,8 +1924,12 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) break; default: Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); - } - Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER, ClientID, false); + } + { + CRconClientLogger Logger(this, ClientID); + CLogScope Scope(&Logger); + Console()->ExecuteLineFlag(pCmd, CFGFLAG_SERVER, ClientID); + } Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); m_RconClientID = IServer::RCON_CID_SERV; m_RconAuthLevel = AUTHED_ADMIN; @@ -3820,6 +3849,97 @@ void CServer::LogoutClient(int ClientID, const char *pReason) Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } +void CServer::ConchainRconPasswordChangeGeneric(int Level, const char *pCurrent, IConsole::IResult *pResult) +{ +} + +void CServer::ConchainRconPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CServer *pThis = static_cast(pUserData); + pThis->ConchainRconPasswordChangeGeneric(AUTHED_ADMIN, pThis->Config()->m_SvRconPassword, pResult); + pfnCallback(pResult, pCallbackUserData); +} + +void CServer::ConchainRconModPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CServer *pThis = static_cast(pUserData); + pThis->ConchainRconPasswordChangeGeneric(AUTHED_MOD, pThis->Config()->m_SvRconModPassword, pResult); + pfnCallback(pResult, pCallbackUserData); +} + +void CServer::ConchainRconHelperPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CServer *pThis = static_cast(pUserData); + pThis->ConchainRconPasswordChangeGeneric(AUTHED_HELPER, pThis->Config()->m_SvRconHelperPassword, pResult); + pfnCallback(pResult, pCallbackUserData); +} + +void CServer::ConchainMapUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() >= 1) + { + CServer *pThis = static_cast(pUserData); + pThis->m_MapReload = str_comp(pThis->Config()->m_SvMap, pThis->m_aCurrentMap) != 0; + } +} + +void CServer::ConchainSixupUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + CServer *pThis = static_cast(pUserData); + if(pResult->NumArguments() >= 1 && pThis->m_aCurrentMap[0] != '\0') + pThis->m_MapReload |= (pThis->m_apCurrentMapData[MAP_TYPE_SIXUP] != 0) != (pResult->GetInteger(0) != 0); +} + +void CServer::ConchainLoglevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CServer *pSelf = (CServer *)pUserData; + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + { + pSelf->m_pFileLogger->SetFilter(CLogFilter{IConsole::ToLogLevelFilter(g_Config.m_Loglevel)}); + } +} + +void CServer::ConchainStdoutOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CServer *pSelf = (CServer *)pUserData; + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() && pSelf->m_pStdoutLogger) + { + pSelf->m_pStdoutLogger->SetFilter(CLogFilter{IConsole::ToLogLevelFilter(g_Config.m_StdoutOutputLevel)}); + } +} + +#if defined(CONF_FAMILY_UNIX) +void CServer::ConchainConnLoggingServerChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() == 1) + { + CServer *pServer = (CServer *)pUserData; + + // open socket to send new connections + if(!pServer->m_ConnLoggingSocketCreated) + { + pServer->m_ConnLoggingSocket = net_unix_create_unnamed(); + if(pServer->m_ConnLoggingSocket == -1) + { + pServer->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "Failed to created socket for communication with the connection logging server."); + } + else + { + pServer->m_ConnLoggingSocketCreated = true; + } + } + + // set the destination address for the connection logging + net_unix_set_addr(&pServer->m_ConnLoggingDestAddr, pResult->GetString(0)); + } +} +#endif + void CServer::RegisterCommands() { m_pConsole = Kernel()->RequestInterface(); @@ -5700,3 +5820,9 @@ void CServer::SetErrorShutdown(const char *pReason) { str_copy(m_aErrorShutdownReason, pReason); } + +void CServer::SetLoggers(std::shared_ptr &&pFileLogger, std::shared_ptr &&pStdoutLogger) +{ + m_pFileLogger = pFileLogger; + m_pStdoutLogger = pStdoutLogger; +} diff --git a/src/engine/server/server.h b/src/engine/server/server.h index fc19a151..ba73d28e 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -101,6 +101,16 @@ class CServer : public IServer class IStorage *m_pStorage; class IRegister *m_pRegister; +#if defined(CONF_UPNP) + CUPnP m_UPnP; +#endif + +#if defined(CONF_FAMILY_UNIX) + UNIXSOCKETADDR m_ConnLoggingDestAddr; + bool m_ConnLoggingSocketCreated; + UNIXSOCKET m_ConnLoggingSocket; +#endif + class CDbConnectionPool *m_pConnectionPool; public: @@ -254,6 +264,13 @@ class CServer : public IServer NUM_MAP_TYPES }; + enum + { + RECORDER_MANUAL = MAX_CLIENTS, + RECORDER_AUTO = MAX_CLIENTS + 1, + NUM_RECORDERS = MAX_CLIENTS + 2, + }; + char m_aPreviousMap[IO_MAX_PATH_LENGTH]; char m_aCurrentMap[IO_MAX_PATH_LENGTH]; SHA256_DIGEST m_aCurrentMapSha256[NUM_MAP_TYPES]; @@ -261,13 +278,10 @@ class CServer : public IServer unsigned char *m_apCurrentMapData[NUM_MAP_TYPES]; unsigned int m_aCurrentMapSize[NUM_MAP_TYPES]; - CDemoRecorder m_aDemoRecorder[MAX_CLIENTS + 1]; + CDemoRecorder m_aDemoRecorder[NUM_RECORDERS]; - bool m_ServerInfoHighLoad; int64_t m_ServerInfoFirstRequest; int m_ServerInfoNumRequests; - int64_t m_ServerInfoRequestLogTick; - int m_ServerInfoRequestLogRecords; char m_aErrorShutdownReason[128]; @@ -277,6 +291,9 @@ class CServer : public IServer std::vector m_vAnnouncements; char m_aAnnouncementFile[IO_MAX_PATH_LENGTH]; + std::shared_ptr m_pFileLogger = nullptr; + std::shared_ptr m_pStdoutLogger = nullptr; + CServer(); ~CServer(); @@ -432,6 +449,19 @@ class CServer : public IServer void LogoutClient(int ClientID, const char *pReason); + void ConchainRconPasswordChangeGeneric(int Level, const char *pCurrent, IConsole::IResult *pResult); + static void ConchainRconPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainRconModPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainRconHelperPasswordChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainMapUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainSixupUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainLoglevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainStdoutOutputLevel(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + +#if defined(CONF_FAMILY_UNIX) + static void ConchainConnLoggingServerChange(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); +#endif + void RegisterCommands(); int SnapNewID() override; @@ -460,6 +490,17 @@ class CServer : public IServer bool IsSixup(int ClientID) const override { return ClientID != SERVER_DEMO_CLIENT && m_aClients[ClientID].m_Sixup; } + void SetLoggers(std::shared_ptr &&pFileLogger, std::shared_ptr &&pStdoutLogger); + +#ifdef CONF_FAMILY_UNIX + enum CONN_LOGGING_CMD + { + OPEN_SESSION = 1, + CLOSE_SESSION = 2, + }; + + void SendConnLoggingCommand(CONN_LOGGING_CMD Cmd, const NETADDR *pAddr); +#endif /* INFECTION MODIFICATION START ***************************************/ public: int GetClientInfclassVersion(int ClientID) const override; diff --git a/src/engine/shared/assertion_logger.cpp b/src/engine/shared/assertion_logger.cpp index cea3801a..b6b2510b 100644 --- a/src/engine/shared/assertion_logger.cpp +++ b/src/engine/shared/assertion_logger.cpp @@ -1,36 +1,40 @@ #include "assertion_logger.h" +#include #include #include + #include #include -#include - class CAssertionLogger : public ILogger { - void Dump(); - struct SDebugMessageItem { char m_aMessage[1024]; }; - std::mutex m_DbgMessageMutex; + CLock m_DbgMessageMutex; CStaticRingBuffer m_DbgMessages; char m_aAssertLogPath[IO_MAX_PATH_LENGTH]; char m_aGameName[256]; + void Dump() REQUIRES(!m_DbgMessageMutex); + public: CAssertionLogger(const char *pAssertLogPath, const char *pGameName); - void Log(const CLogMessage *pMessage) override; - void GlobalFinish() override; + void Log(const CLogMessage *pMessage) override REQUIRES(!m_DbgMessageMutex); + void GlobalFinish() override REQUIRES(!m_DbgMessageMutex); }; void CAssertionLogger::Log(const CLogMessage *pMessage) { - std::unique_lock Lock(m_DbgMessageMutex); + if(m_Filter.Filters(pMessage)) + { + return; + } + const CLockScope LockScope(m_DbgMessageMutex); SDebugMessageItem *pMsgItem = (SDebugMessageItem *)m_DbgMessages.Allocate(sizeof(SDebugMessageItem)); str_copy(pMsgItem->m_aMessage, pMessage->m_aLine); } @@ -48,8 +52,8 @@ void CAssertionLogger::Dump() char aAssertLogFile[IO_MAX_PATH_LENGTH]; char aDate[64]; str_timestamp(aDate, sizeof(aDate)); - str_format(aAssertLogFile, std::size(aAssertLogFile), "%s%s_assert_log_%d_%s.txt", m_aAssertLogPath, m_aGameName, pid(), aDate); - std::unique_lock Lock(m_DbgMessageMutex); + str_format(aAssertLogFile, std::size(aAssertLogFile), "%s%s_assert_log_%s_%d.txt", m_aAssertLogPath, m_aGameName, aDate, pid()); + const CLockScope LockScope(m_DbgMessageMutex); IOHANDLE FileHandle = io_open(aAssertLogFile, IOFLAG_WRITE); if(FileHandle) { diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 3a66b05a..99b43e48 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -9,7 +9,8 @@ MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Password to the server") MACRO_CONFIG_STR(Logfile, logfile, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Filename to log all output to") -MACRO_CONFIG_INT(Loglevel, loglevel, 2, 0, 4, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Log level (0 = Error, 1 = Warn, 2 = Info, 3 = Debug, 4 = Trace)") +MACRO_CONFIG_INT(Loglevel, loglevel, 0, -3, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Adjusts the amount of information in the logfile (-3 = none, -2 = error only, -1 = warn, 0 = info, 1 = debug, 2 = trace)") +MACRO_CONFIG_INT(StdoutOutputLevel, stdout_output_level, 0, -3, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Adjusts the amount of information in the system console (-3 = none, -2 = error only, -1 = warn, 0 = info, 1 = debug, 2 = trace)") MACRO_CONFIG_INT(ConsoleOutputLevel, console_output_level, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Adjusts the amount of information in the console") MACRO_CONFIG_INT(ConsoleEnableColors, console_enable_colors, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Enable colors in console output") @@ -27,6 +28,7 @@ MACRO_CONFIG_STR(SvRegisterExtra, sv_register_extra, 256, "", CFGFLAG_SERVER, "E MACRO_CONFIG_STR(SvRegisterUrl, sv_register_url, 128, "https://master1.ddnet.org/ddnet/15/register", CFGFLAG_SERVER, "Masterserver URL to register to") 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_STR(SvRconHelperPassword, sv_rcon_helper_password, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for helpers (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(SvRconTokenCheck, sv_rcon_token_check, 1, 0, 1, CFGFLAG_SERVER, "Require the use of a client with tokenized protection against IP address spoofing to permit access to the console") diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 01f06a6f..cbe5f48a 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -330,6 +330,15 @@ LEVEL IConsole::ToLogLevel(int Level) return LEVEL_INFO; } +int IConsole::ToLogLevelFilter(int Level) +{ + if(!(-3 <= Level && Level <= 2)) + { + dbg_assert(0, "invalid log level filter"); + } + return Level + 2; +} + LOG_COLOR ColorToLogColor(ColorRGBA Color) { return LOG_COLOR{ @@ -1265,7 +1274,7 @@ const IConsole::CCommandInfo *CConsole::GetCommandInfo(const char *pName, int Fl return 0; } -extern IConsole *CreateConsole(int FlagMask) { return new CConsole(FlagMask); } +std::unique_ptr CreateConsole(int FlagMask) { return std::make_unique(FlagMask); } void CConsole::ResetServerGameSettings() { diff --git a/src/engine/shared/console.h b/src/engine/shared/console.h index 713a181a..6f223f53 100644 --- a/src/engine/shared/console.h +++ b/src/engine/shared/console.h @@ -199,6 +199,8 @@ class CConsole : public IConsole void AddCommandSorted(CCommand *pCommand); CCommand *FindCommand(const char *pName, int FlagMask); + bool m_Cheated; + public: CConfig *Config() { return m_pConfig; } @@ -235,6 +237,10 @@ class CConsole : public IConsole // DDRace static void ConUserCommandStatus(IConsole::IResult *pResult, void *pUser); + + bool Cheated() const override { return m_Cheated; } + + int FlagMask() const override { return m_FlagMask; } void SetFlagMask(int FlagMask) override { m_FlagMask = FlagMask; } }; diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 60682475..8d6cf0a8 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -457,6 +457,44 @@ class CStorage : public IStorage return 0; } + template + bool GenericExists(const char *pFilename, int Type, F &&CheckFunction) + { + TranslateType(Type, pFilename); + + char aBuffer[IO_MAX_PATH_LENGTH]; + if(Type == TYPE_ALL) + { + // check all available directories + for(int i = TYPE_SAVE; i < m_NumPaths; ++i) + { + if(CheckFunction(GetPath(i, pFilename, aBuffer, sizeof(aBuffer)))) + return true; + } + return false; + } + else if(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths)) + { + // check wanted directory + return CheckFunction(GetPath(Type, pFilename, aBuffer, sizeof(aBuffer))); + } + else + { + dbg_assert(false, "Type invalid"); + return false; + } + } + + bool FileExists(const char *pFilename, int Type) override + { + return GenericExists(pFilename, Type, fs_is_file); + } + + bool FolderExists(const char *pFilename, int Type) override + { + return GenericExists(pFilename, Type, fs_is_dir); + } + bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) override { IOHANDLE File = OpenFile(pFilename, IOFLAG_READ, Type); diff --git a/src/engine/storage.h b/src/engine/storage.h index 58385c0d..6ee70fa0 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -45,6 +45,8 @@ class IStorage : public IInterface virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0; virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0; + virtual bool FileExists(const char *pFilename, int Type) = 0; + virtual bool FolderExists(const char *pFilename, int Type) = 0; virtual bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) = 0; virtual char *ReadFileStr(const char *pFilename, int Type) = 0; virtual bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) = 0; diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index c3170541..fe66f5c6 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -48,6 +48,10 @@ void CClientChatLogger::Log(const CLogMessage *pMessage) { if(str_comp(pMessage->m_aSystem, "chatresp") == 0) { + if(m_Filter.Filters(pMessage)) + { + return; + } m_pGameServer->SendChatTarget(m_ClientID, pMessage->Message()); } else diff --git a/src/game/server/main_server.cpp b/src/game/server/main_server.cpp index 346c5eac..297be844 100644 --- a/src/game/server/main_server.cpp +++ b/src/game/server/main_server.cpp @@ -39,14 +39,19 @@ int main(int argc, const char **argv) // ignore_convention #endif std::vector> vpLoggers; + std::shared_ptr pStdoutLogger; #if defined(CONF_PLATFORM_ANDROID) - vpLoggers.push_back(std::shared_ptr(log_logger_android())); + pStdoutLogger = std::shared_ptr(log_logger_android()); #else if(!Silent) { - vpLoggers.push_back(std::shared_ptr(log_logger_stdout())); + pStdoutLogger = std::shared_ptr(log_logger_stdout()); } #endif + if(pStdoutLogger) + { + vpLoggers.push_back(pStdoutLogger); + } std::shared_ptr pFutureFileLogger = std::make_shared(); vpLoggers.push_back(pFutureFileLogger); std::shared_ptr pFutureConsoleLogger = std::make_shared(); @@ -68,15 +73,43 @@ int main(int argc, const char **argv) // ignore_convention #endif CServer *pServer = CreateServer(); + pServer->SetLoggers(pFutureFileLogger, std::move(pStdoutLogger)); + IKernel *pKernel = IKernel::Create(); + pKernel->RegisterInterface(pServer); // create the components - IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2); + IEngine *pEngine = CreateEngine(GAME_NAME, pFutureConsoleLogger, 2 * std::thread::hardware_concurrency() + 2); + pKernel->RegisterInterface(pEngine); + + IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv); + pKernel->RegisterInterface(pStorage); + + pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME)); + +#if defined(CONF_EXCEPTION_HANDLING) + char aBuf[IO_MAX_PATH_LENGTH]; + char aBufName[IO_MAX_PATH_LENGTH]; + char aDate[64]; + str_timestamp(aDate, sizeof(aDate)); + str_format(aBufName, sizeof(aBufName), "dumps/" GAME_NAME "-Server_%s_crash_log_%s_%d_%s.RTP", CONF_PLATFORM_STRING, aDate, pid(), GIT_SHORTREV_HASH != nullptr ? GIT_SHORTREV_HASH : ""); + pStorage->GetCompletePath(IStorage::TYPE_SAVE, aBufName, aBuf, sizeof(aBuf)); + set_exception_handler_log_file(aBuf); +#endif + + IConsole *pConsole = CreateConsole(CFGFLAG_SERVER | CFGFLAG_ECON).release(); + pKernel->RegisterInterface(pConsole); + + IConfigManager *pConfigManager = CreateConfigManager(); + pKernel->RegisterInterface(pConfigManager); + IEngineMap *pEngineMap = CreateEngineMap(); + pKernel->RegisterInterface(pEngineMap); // IEngineMap + pKernel->RegisterInterface(static_cast(pEngineMap), false); + + // create the components IGameServer *pGameServer = CreateGameServer(); - IConsole *pConsole = CreateConsole(CFGFLAG_SERVER|CFGFLAG_ECON); - IStorage *pStorage = CreateStorage(IStorage::STORAGETYPE_SERVER, argc, argv); // ignore_convention - IConfigManager *pConfigManager = CreateConfigManager(); + pKernel->RegisterInterface(pGameServer); pServer->m_pLocalization = new CLocalization(pStorage); pServer->m_pLocalization->InitConfig(0, NULL); @@ -86,25 +119,6 @@ int main(int argc, const char **argv) // ignore_convention return -1; } - { - bool RegisterFail = false; - - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pServer); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // register as both - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pGameServer); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfigManager); - - if(RegisterFail) - { - delete pKernel; - return -1; - } - } - pEngine->Init(); pConfigManager->Init(); pConsole->Init(); @@ -113,13 +127,19 @@ int main(int argc, const char **argv) // ignore_convention pServer->RegisterCommands(); // execute autoexec file - pConsole->ExecuteFile("autoexec.cfg"); + if(pStorage->FileExists(AUTOEXEC_SERVER_FILE, IStorage::TYPE_ALL)) + { + pConsole->ExecuteFile(AUTOEXEC_SERVER_FILE); + } + else // fallback + { + pConsole->ExecuteFile(AUTOEXEC_FILE); + } // parse the command line arguments if(argc > 1) pConsole->ParseArguments(argc - 1, &argv[1]); - log_set_loglevel((LEVEL)g_Config.m_Loglevel); if(g_Config.m_Logfile[0]) { IOHANDLE Logfile = pStorage->OpenFile(g_Config.m_Logfile, IOFLAG_WRITE, IStorage::TYPE_SAVE_OR_ABSOLUTE); diff --git a/src/tools/map_convert_for_client.cpp b/src/tools/map_convert_for_client.cpp index b3c1c4c9..09f8f749 100644 --- a/src/tools/map_convert_for_client.cpp +++ b/src/tools/map_convert_for_client.cpp @@ -22,7 +22,7 @@ int main(int argc, const char **argv) IStorage *pStorage = CreateLocalStorage(); IEngineMap *pMap = CreateEngineMap(); - IConsole *pConsole = CreateConsole(0); + IConsole *pConsole = CreateConsole(0).release(); if(!pStorage) {