diff --git a/core/logic/Logger.cpp b/core/logic/Logger.cpp
new file mode 100644
index 00000000..9cfd7246
--- /dev/null
+++ b/core/logic/Logger.cpp
@@ -0,0 +1,551 @@
+/**
+ * vim: set ts=4 sw=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#include
+#include
+#include "Logger.h"
+#include
+#include
+
+Logger g_Logger;
+
+/**
+ * :TODO: This should be creating the log folder if it doesn't exist
+ */
+
+ConfigResult Logger::OnSourceModConfigChanged(const char *key,
+ const char *value,
+ ConfigSource source,
+ char *error,
+ size_t maxlength)
+{
+ if (strcasecmp(key, "Logging") == 0)
+ {
+ bool state;
+
+ if (strcasecmp(value, "on") == 0)
+ {
+ state = true;
+ } else if (strcasecmp(value, "off") == 0) {
+ state = false;
+ } else {
+ smcore.Format(error, maxlength, "Invalid value: must be \"on\" or \"off\"");
+ return ConfigResult_Reject;
+ }
+
+ if (source == ConfigSource_Console)
+ {
+ state ? EnableLogging() : DisableLogging();
+ } else {
+ m_InitialState = state;
+ }
+
+ return ConfigResult_Accept;
+ } else if (strcasecmp(key, "LogMode") == 0) {
+ if (strcasecmp(value, "daily") == 0)
+ {
+ m_Mode = LoggingMode_Daily;
+ } else if (strcasecmp(value, "map") == 0) {
+ m_Mode = LoggingMode_PerMap;
+ } else if (strcasecmp(value, "game") == 0) {
+ m_Mode = LoggingMode_Game;
+ } else {
+ smcore.Format(error, maxlength, "Invalid value: must be [daily|map|game]");
+ return ConfigResult_Reject;
+ }
+
+ return ConfigResult_Accept;
+ }
+
+ return ConfigResult_Ignore;
+}
+
+void Logger::OnSourceModStartup(bool late)
+{
+ InitLogger(m_Mode);
+}
+
+void Logger::OnSourceModAllShutdown()
+{
+ CloseLogger();
+}
+
+void Logger::OnSourceModLevelChange(const char *mapName)
+{
+ MapChange(mapName);
+}
+
+void Logger::_NewMapFile()
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ /* Append "Log file closed" to previous log file */
+ _CloseFile();
+
+ char _filename[256];
+ int i = 0;
+
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+
+ while (true)
+ {
+ g_pSM->BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%02d%02d%03d.log", curtime->tm_mon + 1, curtime->tm_mday, i);
+ FILE *fp = fopen(_filename, "r");
+ if (!fp)
+ {
+ break;
+ }
+ fclose(fp);
+ i++;
+ }
+ m_NrmFileName.assign(_filename);
+
+ FILE *fp = fopen(m_NrmFileName.c_str(), "w");
+ if (!fp)
+ {
+ char error[255];
+ libsys->GetPlatformError(error, sizeof(error));
+ LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str());
+ LogFatal("[SM] Platform returned error: \"%s\"", error);
+ LogFatal("[SM] Logging has been disabled.");
+ m_Active = false;
+ return;
+ } else {
+ char date[32];
+ strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime);
+ fprintf(fp, "L %s: SourceMod log file started (file \"L%02d%02d%03d.log\") (Version \"%s\")\n", date, curtime->tm_mon + 1, curtime->tm_mday, i, SOURCEMOD_VERSION);
+ fclose(fp);
+ }
+}
+
+void Logger::_CloseFile()
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ FILE *fp = NULL;
+ if (!m_NrmFileName.empty())
+ {
+ fp = fopen(m_NrmFileName.c_str(), "r+");
+ if (fp)
+ {
+ fseek(fp, 0, SEEK_END);
+ LogMessage("Log file closed.");
+ fclose(fp);
+ }
+ m_NrmFileName.clear();
+ }
+
+ if (!m_ErrMapStart)
+ {
+ return;
+ }
+ fp = fopen(m_ErrFileName.c_str(), "r+");
+ if (fp)
+ {
+ fseek(fp, 0, SEEK_END);
+ LogError("Error log file session closed.");
+ fclose(fp);
+ }
+ m_ErrFileName.clear();
+}
+
+void Logger::InitLogger(LoggingMode mode)
+{
+ m_Mode = mode;
+ m_Active = m_InitialState;
+
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+ m_NrmCurDay = curtime->tm_mday;
+ m_ErrCurDay = curtime->tm_mday;
+
+ char _filename[256];
+ g_pSM->BuildPath(Path_SM, _filename, sizeof(_filename), "logs/errors_%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday);
+ m_ErrFileName.assign(_filename);
+
+ switch (m_Mode)
+ {
+ case LoggingMode_PerMap:
+ {
+ if (!m_Active)
+ {
+ m_DelayedStart = true;
+ }
+ break;
+ }
+ case LoggingMode_Daily:
+ {
+ g_pSM->BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday);
+ m_NrmFileName.assign(_filename);
+ m_DailyPrintHdr = true;
+ break;
+ }
+ default:
+ {
+ /* do nothing... */
+ break;
+ }
+ }
+}
+
+void Logger::CloseLogger()
+{
+ _CloseFile();
+}
+
+void Logger::LogToOpenFile(FILE *fp, const char *msg, ...)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, msg);
+ LogToOpenFileEx(fp, msg, ap);
+ va_end(ap);
+}
+
+void Logger::LogToFileOnly(FILE *fp, const char *msg, ...)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, msg);
+ LogToFileOnlyEx(fp, msg, ap);
+ va_end(ap);
+}
+
+void Logger::LogToOpenFileEx(FILE *fp, const char *msg, va_list ap)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ static ConVar *sv_logecho = smcore.FindConVar("sv_logecho");
+
+ char buffer[3072];
+ smcore.FormatArgs(buffer, sizeof(buffer), msg, ap);
+
+ char date[32];
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+ strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime);
+
+ fprintf(fp, "L %s: %s\n", date, buffer);
+
+ if (!sv_logecho || smcore.GetCvarBool(sv_logecho))
+ {
+ static char conBuffer[4096];
+ smcore.Format(conBuffer, sizeof(conBuffer), "L %s: %s\n", date, buffer);
+ smcore.ConPrint(conBuffer);
+ }
+}
+
+void Logger::LogToFileOnlyEx(FILE *fp, const char *msg, va_list ap)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ char buffer[3072];
+ smcore.FormatArgs(buffer, sizeof(buffer), msg, ap);
+
+ char date[32];
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+ strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime);
+
+ fprintf(fp, "L %s: %s\n", date, buffer);
+ fflush(fp);
+}
+
+void Logger::LogMessage(const char *vafmt, ...)
+{
+ va_list ap;
+ va_start(ap, vafmt);
+ LogMessageEx(vafmt, ap);
+ va_end(ap);
+}
+
+void Logger::LogMessageEx(const char *vafmt, va_list ap)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ if (m_Mode == LoggingMode_Game)
+ {
+ _PrintToGameLog(vafmt, ap);
+ return;
+ }
+
+ if (m_DelayedStart)
+ {
+ m_DelayedStart = false;
+ _NewMapFile();
+ }
+
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+
+ FILE *fp = NULL;
+ if (m_Mode == LoggingMode_PerMap)
+ {
+ fp = fopen(m_NrmFileName.c_str(), "a+");
+ if (!fp)
+ {
+ _NewMapFile();
+ fp = fopen(m_NrmFileName.c_str(), "a+");
+ if (!fp)
+ {
+ goto print_error;
+ }
+ }
+ } else {
+ if (m_NrmCurDay != curtime->tm_mday)
+ {
+ char _filename[256];
+ g_pSM->BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday);
+ m_NrmFileName.assign(_filename);
+ m_NrmCurDay = curtime->tm_mday;
+ m_DailyPrintHdr = true;
+ }
+ fp = fopen(m_NrmFileName.c_str(), "a+");
+ }
+
+ if (fp)
+ {
+ if (m_DailyPrintHdr)
+ {
+ char date[32];
+ m_DailyPrintHdr = false;
+ strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime);
+ fprintf(fp, "L %s: SourceMod log file session started (file \"L%04d%02d%02d.log\") (Version \"%s\")\n", date, curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday, SOURCEMOD_VERSION);
+ }
+ LogToOpenFileEx(fp, vafmt, ap);
+ fclose(fp);
+ } else {
+ goto print_error;
+ }
+
+ return;
+print_error:
+ char error[255];
+ libsys->GetPlatformError(error, sizeof(error));
+ LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str());
+ LogFatal("[SM] Platform returned error: \"%s\"", error);
+ LogFatal("[SM] Logging has been disabled.");
+ m_Active = false;
+}
+
+void Logger::LogError(const char *vafmt, ...)
+{
+ va_list ap;
+ va_start(ap, vafmt);
+ LogErrorEx(vafmt, ap);
+ va_end(ap);
+}
+
+void Logger::LogErrorEx(const char *vafmt, va_list ap)
+{
+ if (!m_Active)
+ {
+ return;
+ }
+
+ time_t t = g_pSM->GetAdjustedTime();
+ tm *curtime = localtime(&t);
+
+ if (curtime->tm_mday != m_ErrCurDay)
+ {
+ char _filename[256];
+ g_pSM->BuildPath(Path_SM, _filename, sizeof(_filename), "logs/errors_%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday);
+ m_ErrFileName.assign(_filename);
+ m_ErrCurDay = curtime->tm_mday;
+ m_ErrMapStart = false;
+ }
+
+ FILE *fp = fopen(m_ErrFileName.c_str(), "a+");
+ if (fp)
+ {
+ if (!m_ErrMapStart)
+ {
+ char date[32];
+ strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime);
+ fprintf(fp, "L %s: SourceMod error session started\n", date);
+ fprintf(fp, "L %s: Info (map \"%s\") (file \"errors_%04d%02d%02d.log\")\n", date, m_CurMapName.c_str(), curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday);
+ m_ErrMapStart = true;
+ }
+ LogToOpenFileEx(fp, vafmt, ap);
+ fclose(fp);
+ }
+ else
+ {
+ char error[255];
+ libsys->GetPlatformError(error, sizeof(error));
+ LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str());
+ LogFatal("[SM] Platform returned error: \"%s\"", error);
+ LogFatal("[SM] Logging has been disabled.");
+ m_Active = false;
+ return;
+ }
+}
+
+void Logger::MapChange(const char *mapname)
+{
+ m_CurMapName.assign(mapname);
+
+ switch (m_Mode)
+ {
+ case LoggingMode_Daily:
+ {
+ LogMessage("-------- Mapchange to %s --------", mapname);
+ break;
+ }
+ case LoggingMode_PerMap:
+ {
+ _NewMapFile();
+ break;
+ }
+ default:
+ {
+ /* Do nothing... */
+ break;
+ }
+ }
+
+ if (m_ErrMapStart)
+ {
+ LogError("Error log file session closed.");
+ }
+ m_ErrMapStart = false;
+}
+
+void Logger::_PrintToGameLog(const char *fmt, va_list ap)
+{
+ char msg[3072];
+ size_t len;
+
+ len = vsnprintf(msg, sizeof(msg)-2, fmt, ap);
+ len = (len >= sizeof(msg)) ? (sizeof(msg) - 2) : len;
+
+ msg[len++] = '\n';
+ msg[len] = '\0';
+
+ Engine_LogPrintWrapper(msg);
+}
+
+const char *Logger::GetLogFileName(LogType type) const
+{
+ switch (type)
+ {
+ case LogType_Normal:
+ {
+ return m_NrmFileName.c_str();
+ }
+ case LogType_Error:
+ {
+ return m_ErrFileName.c_str();
+ }
+ default:
+ {
+ return "";
+ }
+ }
+}
+
+LoggingMode Logger::GetLoggingMode() const
+{
+ return m_Mode;
+}
+
+void Logger::EnableLogging()
+{
+ if (m_Active)
+ {
+ return;
+ }
+ m_Active = true;
+ LogMessage("[SM] Logging enabled manually by user.");
+}
+
+void Logger::DisableLogging()
+{
+ if (!m_Active)
+ {
+ return;
+ }
+ LogMessage("[SM] Logging disabled manually by user.");
+ m_Active = false;
+}
+
+void Logger::LogFatal(const char *msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ LogFatalEx(msg, ap);
+ va_end(ap);
+}
+
+void Logger::LogFatalEx(const char *msg, va_list ap)
+{
+ /* :TODO: make this print all pretty-like
+ * In fact, the pretty log printing function should be abstracted.
+ * It's already implemented twice which is bad.
+ */
+
+ char path[PLATFORM_MAX_PATH];
+
+ g_pSM->BuildPath(Path_Game, path, sizeof(path), "sourcemod_fatal.log");
+
+ FILE *fp = fopen(path, "at");
+ if (fp)
+ {
+ m_Active = true;
+ LogToOpenFileEx(fp, msg, ap);
+ m_Active = false;
+ fclose(fp);
+ }
+}
diff --git a/core/logic/Logger.h b/core/logic/Logger.h
new file mode 100644
index 00000000..e472b404
--- /dev/null
+++ b/core/logic/Logger.h
@@ -0,0 +1,113 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#ifndef _INCLUDE_SOURCEMOD_CLOGGER_H_
+#define _INCLUDE_SOURCEMOD_CLOGGER_H_
+
+#include
+#include
+
+#include "common_logic.h"
+
+using namespace SourceHook;
+
+enum LogType
+{
+ LogType_Normal,
+ LogType_Error
+};
+
+enum LoggingMode
+{
+ LoggingMode_Daily,
+ LoggingMode_PerMap,
+ LoggingMode_Game
+};
+
+class Logger : public SMGlobalClass, public ILogger
+{
+public:
+ Logger() : m_Mode(LoggingMode_Daily), m_ErrMapStart(false),
+ m_Active(false), m_DelayedStart(false), m_DailyPrintHdr(false),
+ m_InitialState(true)
+ {
+ }
+public: //SMGlobalClass
+ ConfigResult OnSourceModConfigChanged(const char *key,
+ const char *value,
+ ConfigSource source,
+ char *error,
+ size_t maxlength);
+ void OnSourceModStartup(bool late);
+ void OnSourceModAllShutdown();
+ void OnSourceModLevelChange(const char *mapName);
+public:
+ void InitLogger(LoggingMode mode);
+ void CloseLogger();
+ void EnableLogging();
+ void DisableLogging();
+ void LogMessage(const char *msg, ...);
+ void LogMessageEx(const char *msg, va_list ap);
+ void LogError(const char *msg, ...);
+ void LogErrorEx(const char *msg, va_list ap);
+ void LogFatal(const char *msg, ...);
+ void LogFatalEx(const char *msg, va_list ap);
+ void LogToOpenFile(FILE *fp, const char *msg, ...);
+ void LogToOpenFileEx(FILE *fp, const char *msg, va_list ap);
+ /* This version does not print to console, and is thus thread-safe */
+ void LogToFileOnly(FILE *fp, const char *msg, ...);
+ void LogToFileOnlyEx(FILE *fp, const char *msg, va_list ap);
+ void MapChange(const char *mapname);
+ const char *GetLogFileName(LogType type) const;
+ LoggingMode GetLoggingMode() const;
+private:
+ void _CloseFile();
+ void _NewMapFile();
+ void _PrintToGameLog(const char *fmt, va_list ap);
+private:
+ String m_NrmFileName;
+ String m_ErrFileName;
+ String m_CurMapName;
+ LoggingMode m_Mode;
+ int m_NrmCurDay;
+ int m_ErrCurDay;
+ bool m_ErrMapStart;
+ bool m_Active;
+ bool m_DelayedStart;
+ bool m_DailyPrintHdr;
+ bool m_InitialState;
+};
+
+void Engine_LogPrintWrapper(const char *msg);
+
+extern Logger g_Logger;
+
+#endif // _INCLUDE_SOURCEMOD_CLOGGER_H_
diff --git a/core/logic/smn_core.cpp b/core/logic/smn_core.cpp
new file mode 100644
index 00000000..50eb0e5d
--- /dev/null
+++ b/core/logic/smn_core.cpp
@@ -0,0 +1,777 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or .
+ *
+ * Version: $Id$
+ */
+
+#include
+#include
+#include
+#include "common_logic.h"
+#include "Logger.h"
+
+#include
+#include
+
+#include
+#include
+
+#if defined PLATFORM_WINDOWS
+#include
+#elif defined PLATFORM_POSIX
+#include
+#include
+#include
+#endif
+
+HandleType_t g_PlIter;
+
+IForward *g_OnLogAction = NULL;
+
+static ConVar *sm_datetime_format = NULL;
+
+class CoreNativeHelpers :
+ public SMGlobalClass,
+ public IHandleTypeDispatch
+{
+public:
+ void OnSourceModAllInitialized()
+ {
+ HandleAccess hacc;
+ handlesys->InitAccessDefaults(NULL, &hacc);
+ hacc.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY|HANDLE_RESTRICT_OWNER;
+
+ g_PlIter = handlesys->CreateType("PluginIterator", this, 0, NULL, NULL, g_pCoreIdent, NULL);
+
+ g_OnLogAction = forwardsys->CreateForward("OnLogAction",
+ ET_Hook,
+ 5,
+ NULL,
+ Param_Cell,
+ Param_Cell,
+ Param_Cell,
+ Param_Cell,
+ Param_String);
+
+ sm_datetime_format = smcore.FindConVar("sm_datetime_format");
+ }
+ void OnHandleDestroy(HandleType_t type, void *object)
+ {
+ IPluginIterator *iter = (IPluginIterator *)object;
+ iter->Release();
+ }
+ void OnSourceModShutdown()
+ {
+ forwardsys->ReleaseForward(g_OnLogAction);
+ handlesys->RemoveType(g_PlIter, g_pCoreIdent);
+ }
+} g_CoreNativeHelpers;
+
+void LogAction(Handle_t hndl, int type, int client, int target, const char *message)
+{
+ if (g_OnLogAction->GetFunctionCount())
+ {
+ cell_t result = 0;
+ g_OnLogAction->PushCell(hndl);
+ g_OnLogAction->PushCell(type);
+ g_OnLogAction->PushCell(client);
+ g_OnLogAction->PushCell(target);
+ g_OnLogAction->PushString(message);
+ g_OnLogAction->Execute(&result);
+
+ if (result >= (ResultType)Pl_Handled)
+ {
+ return;
+ }
+ }
+
+ const char *logtag = "SM";
+ if (type == 2)
+ {
+ HandleError err;
+ IPlugin *pPlugin = scripts->FindPluginByHandle(hndl, &err);
+ if (pPlugin)
+ {
+ logtag = pPlugin->GetFilename();
+ }
+ }
+
+ g_Logger.LogMessage("[%s] %s", logtag, message);
+}
+
+ static cell_t ThrowError(IPluginContext *pContext, const cell_t *params)
+{
+ char buffer[512];
+
+ g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
+
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
+
+ if (pContext->GetLastNativeError() == SP_ERROR_NONE)
+ {
+ pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", buffer);
+ }
+
+ return 0;
+}
+
+static cell_t GetTime(IPluginContext *pContext, const cell_t *params)
+{
+ time_t t = g_pSM->GetAdjustedTime();
+ cell_t *addr;
+ pContext->LocalToPhysAddr(params[1], &addr);
+
+ *(time_t *)addr = t;
+
+ return static_cast(t);
+}
+
+#if defined SUBPLATFORM_SECURECRT
+void _ignore_invalid_parameter(
+ const wchar_t * expression,
+ const wchar_t * function,
+ const wchar_t * file,
+ unsigned int line,
+ uintptr_t pReserved
+ )
+{
+ /* Wow we don't care, thanks Microsoft. */
+}
+#endif
+
+static cell_t FormatTime(IPluginContext *pContext, const cell_t *params)
+{
+ char *format, *buffer;
+ pContext->LocalToString(params[1], &buffer);
+ pContext->LocalToStringNULL(params[3], &format);
+
+ if (format == NULL)
+ {
+ format = const_cast(smcore.GetCvarString(sm_datetime_format));
+ }
+
+#if defined SUBPLATFORM_SECURECRT
+ _invalid_parameter_handler handler = _set_invalid_parameter_handler(_ignore_invalid_parameter);
+#endif
+
+ time_t t = (params[4] == -1) ? g_pSM->GetAdjustedTime() : (time_t)params[4];
+ size_t written = strftime(buffer, params[2], format, localtime(&t));
+
+#if defined SUBPLATFORM_SECURECRT
+ _set_invalid_parameter_handler(handler);
+#endif
+
+ if (params[2] && format[0] != '\0' && !written)
+ {
+ pContext->ThrowNativeError("Invalid time format or buffer too small");
+ return 0;
+ }
+
+ return 1;
+}
+
+static cell_t GetPluginIterator(IPluginContext *pContext, const cell_t *params)
+{
+ IPluginIterator *iter = scripts->GetPluginIterator();
+
+ Handle_t hndl = handlesys->CreateHandle(g_PlIter, iter, pContext->GetIdentity(), g_pCoreIdent, NULL);
+
+ if (hndl == BAD_HANDLE)
+ {
+ iter->Release();
+ }
+
+ return hndl;
+}
+
+static cell_t MorePlugins(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl = (Handle_t)params[1];
+ HandleError err;
+ IPluginIterator *pIter;
+
+ HandleSecurity sec;
+ sec.pIdentity = g_pCoreIdent;
+ sec.pOwner = pContext->GetIdentity();
+
+ if ((err=handlesys->ReadHandle(hndl, g_PlIter, &sec, (void **)&pIter)) != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err);
+ }
+
+ return pIter->MorePlugins() ? 1 : 0;
+}
+
+static cell_t ReadPlugin(IPluginContext *pContext, const cell_t *params)
+{
+ Handle_t hndl = (Handle_t)params[1];
+ HandleError err;
+ IPluginIterator *pIter;
+
+ HandleSecurity sec;
+ sec.pIdentity = g_pCoreIdent;
+ sec.pOwner = pContext->GetIdentity();
+
+ if ((err=handlesys->ReadHandle(hndl, g_PlIter, &sec, (void **)&pIter)) != HandleError_None)
+ {
+ return pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err);
+ }
+
+ IPlugin *pPlugin = pIter->GetPlugin();
+ if (!pPlugin)
+ {
+ return BAD_HANDLE;
+ }
+
+ pIter->NextPlugin();
+
+ return pPlugin->GetMyHandle();
+}
+
+IPlugin *GetPluginFromHandle(IPluginContext *pContext, Handle_t hndl)
+{
+ if (hndl == BAD_HANDLE)
+ {
+ return scripts->FindPluginByContext(pContext->GetContext());
+ } else {
+ HandleError err;
+ IPlugin *pPlugin = scripts->FindPluginByHandle(hndl, &err);
+ if (!pPlugin)
+ {
+ pContext->ThrowNativeError("Could not read Handle %x (error %d)", hndl, err);
+ }
+ return pPlugin;
+ }
+}
+
+static cell_t GetPluginStatus(IPluginContext *pContext, const cell_t *params)
+{
+ IPlugin *pPlugin = GetPluginFromHandle(pContext, params[1]);
+ if (!pPlugin)
+ {
+ return 0;
+ }
+
+ return pPlugin->GetStatus();
+}
+
+static cell_t GetPluginFilename(IPluginContext *pContext, const cell_t *params)
+{
+ IPlugin *pPlugin = GetPluginFromHandle(pContext, params[1]);
+ if (!pPlugin)
+ {
+ return 0;
+ }
+
+ pContext->StringToLocalUTF8(params[2], params[3], pPlugin->GetFilename(), NULL);
+
+ return 1;
+}
+
+static cell_t IsPluginDebugging(IPluginContext *pContext, const cell_t *params)
+{
+ IPlugin *pPlugin = GetPluginFromHandle(pContext, params[1]);
+ if (!pPlugin)
+ {
+ return 0;
+ }
+
+ return pPlugin->IsDebugging() ? 1 : 0;
+}
+
+/* Local to plugins only */
+enum PluginInfo
+{
+ PlInfo_Name, /**< Plugin name */
+ PlInfo_Author, /**< Plugin author */
+ PlInfo_Description, /**< Plugin description */
+ PlInfo_Version, /**< Plugin verison */
+ PlInfo_URL, /**< Plugin URL */
+};
+
+static cell_t GetPluginInfo(IPluginContext *pContext, const cell_t *params)
+{
+ IPlugin *pPlugin = GetPluginFromHandle(pContext, params[1]);
+ if (!pPlugin)
+ {
+ return 0;
+ }
+
+ const sm_plugininfo_t *info = pPlugin->GetPublicInfo();
+
+ if (!info)
+ {
+ return 0;
+ }
+
+ const char *str = NULL;
+
+ switch ((PluginInfo)params[2])
+ {
+ case PlInfo_Name:
+ {
+ str = info->name;
+ break;
+ }
+ case PlInfo_Author:
+ {
+ str = info->author;
+ break;
+ }
+ case PlInfo_Description:
+ {
+ str = info->description;
+ break;
+ }
+ case PlInfo_Version:
+ {
+ str = info->version;
+ break;
+ }
+ case PlInfo_URL:
+ {
+ str = info->url;
+ break;
+ }
+ }
+
+ if (!str || str[0] == '\0')
+ {
+ return 0;
+ }
+
+ pContext->StringToLocalUTF8(params[3], params[4], str, NULL);
+
+ return 1;
+}
+
+static cell_t SetFailState(IPluginContext *pContext, const cell_t *params)
+{
+ char *str;
+ SMPlugin *pPlugin;
+
+ pContext->LocalToString(params[1], &str);
+ pPlugin = scripts->FindPluginByContext(pContext->GetContext());
+
+ if (params[0] == 1)
+ {
+ pPlugin->SetErrorState(Plugin_Error, "%s", str);
+
+ return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", str);
+ }
+ else
+ {
+ char buffer[2048];
+
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 1);
+ if (pContext->GetLastNativeError() != SP_ERROR_NONE)
+ {
+ pPlugin->SetErrorState(Plugin_Error, "%s", str);
+ return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "Formatting error (%s)", str);
+ }
+ else
+ {
+ pPlugin->SetErrorState(Plugin_Error, "%s", buffer);
+ return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", buffer);
+ }
+ }
+
+ return 0;
+}
+
+static cell_t GetSysTickCount(IPluginContext *pContext, const cell_t *params)
+{
+#if defined PLATFORM_WINDOWS
+ return (cell_t)GetTickCount();
+#elif defined PLATFORM_POSIX
+ tms tm;
+ clock_t ticks = times(&tm);
+ long ticks_per_sec = sysconf(_SC_CLK_TCK);
+ double fticks = (double)ticks / (double)ticks_per_sec;
+ fticks *= 1000.0f;
+ if (fticks > INT_MAX)
+ {
+ double r = (int)(fticks / INT_MAX) * (double)INT_MAX;
+ fticks -= r;
+ }
+ return (cell_t)fticks;
+#endif
+}
+
+static cell_t AutoExecConfig(IPluginContext *pContext, const cell_t *params)
+{
+ SMPlugin *plugin = scripts->FindPluginByContext(pContext->GetContext());
+
+ char *cfg, *folder;
+ pContext->LocalToString(params[2], &cfg);
+ pContext->LocalToString(params[3], &folder);
+
+ if (cfg[0] == '\0')
+ {
+ static char temp_str[255];
+ static char temp_file[PLATFORM_MAX_PATH];
+ char *ptr;
+
+ libsys->GetFileFromPath(temp_str, sizeof(temp_str), plugin->GetFilename());
+ if ((ptr = strstr(temp_str, ".smx")) != NULL)
+ {
+ *ptr = '\0';
+ }
+
+ /* We have the raw filename! */
+ g_pSM->Format(temp_file, sizeof(temp_file), "plugin.%s", temp_str);
+ cfg = temp_file;
+ }
+
+ plugin->AddConfig(params[1] ? true : false, cfg, folder);
+
+ return 1;
+}
+
+static cell_t MarkNativeAsOptional(IPluginContext *pContext, const cell_t *params)
+{
+ char *name;
+ uint32_t idx;
+ sp_native_t *native;
+
+ pContext->LocalToString(params[1], &name);
+ if (pContext->FindNativeByName(name, &idx) != SP_ERROR_NONE)
+ {
+ /* Oops! This HAS to silently fail! */
+ return 0;
+ }
+
+ pContext->GetNativeByIndex(idx, &native);
+
+ native->flags |= SP_NTVFLAG_OPTIONAL;
+
+ return 1;
+}
+
+static cell_t RegPluginLibrary(IPluginContext *pContext, const cell_t *params)
+{
+ char *name;
+ SMPlugin *pl = scripts->FindPluginByContext(pContext->GetContext());
+
+ pContext->LocalToString(params[1], &name);
+
+ pl->AddLibrary(name);
+ return 1;
+}
+
+static cell_t LibraryExists(IPluginContext *pContext, const cell_t *params)
+{
+ char *str;
+ pContext->LocalToString(params[1], &str);
+
+ if (strcmp(str, "__CanTestFeatures__") == 0)
+ {
+ return 1;
+ }
+
+ if (scripts->LibraryExists(str))
+ {
+ return 1;
+ }
+
+ if (extsys->LibraryExists(str))
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+static cell_t sm_LogAction(IPluginContext *pContext, const cell_t *params)
+{
+ char buffer[2048];
+ g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3);
+
+ if (pContext->GetLastNativeError() != SP_ERROR_NONE)
+ {
+ return 0;
+ }
+
+ IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
+
+ LogAction(pPlugin->GetMyHandle(), 2, params[1], params[2], buffer);
+
+ return 1;
+}
+
+static cell_t LogToFile(IPluginContext *pContext, const cell_t *params)
+{
+ char *file;
+ pContext->LocalToString(params[1], &file);
+
+ char path[PLATFORM_MAX_PATH];
+ g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file);
+
+ FILE *fp = fopen(path, "at");
+ if (!fp)
+ {
+ return pContext->ThrowNativeError("Could not open file \"%s\"", path);
+ }
+
+ char buffer[2048];
+ g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
+
+ if (pContext->GetLastNativeError() != SP_ERROR_NONE)
+ {
+ fclose(fp);
+ return 0;
+ }
+
+ IPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
+
+ g_Logger.LogToOpenFile(fp, "[%s] %s", pPlugin->GetFilename(), buffer);
+
+ fclose(fp);
+
+ return 1;
+}
+
+static cell_t LogToFileEx(IPluginContext *pContext, const cell_t *params)
+{
+ char *file;
+ pContext->LocalToString(params[1], &file);
+
+ char path[PLATFORM_MAX_PATH];
+ g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file);
+
+ FILE *fp = fopen(path, "at");
+ if (!fp)
+ {
+ return pContext->ThrowNativeError("Could not open file \"%s\"", path);
+ }
+
+ char buffer[2048];
+ g_pSM->SetGlobalTarget(SOURCEMOD_SERVER_LANGUAGE);
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 2);
+
+ if (pContext->GetLastNativeError() != SP_ERROR_NONE)
+ {
+ fclose(fp);
+ return 0;
+ }
+
+ g_Logger.LogToOpenFile(fp, "%s", buffer);
+
+ fclose(fp);
+
+ return 1;
+}
+
+static cell_t GetExtensionFileStatus(IPluginContext *pContext, const cell_t *params)
+{
+ char *str;
+ pContext->LocalToString(params[1], &str);
+
+ IExtension *pExtension = extsys->FindExtensionByFile(str);
+
+ if (!pExtension)
+ {
+ return -2;
+ }
+
+ if (!pExtension->IsLoaded())
+ {
+ return -1;
+ }
+
+ char *error;
+ pContext->LocalToString(params[2], &error);
+ if (!pExtension->IsRunning(error, params[3]))
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+static cell_t FindPluginByNumber(IPluginContext *pContext, const cell_t *params)
+{
+ IPlugin *pPlugin = scripts->FindPluginByOrder(params[1]);
+
+ if (pPlugin == NULL)
+ {
+ return BAD_HANDLE;
+ }
+
+ return pPlugin->GetMyHandle();
+}
+
+static cell_t VerifyCoreVersion(IPluginContext *pContext, const cell_t *params)
+{
+ return 4;
+}
+
+static cell_t GetFeatureStatus(IPluginContext *pContext, const cell_t *params)
+{
+ FeatureType type = (FeatureType)params[1];
+ char *name;
+
+ pContext->LocalToString(params[2], &name);
+
+ return sharesys->TestFeature(pContext->GetRuntime(), type, name);
+}
+
+static cell_t RequireFeature(IPluginContext *pContext, const cell_t *params)
+{
+ FeatureType type = (FeatureType)params[1];
+ char *name;
+
+ pContext->LocalToString(params[2], &name);
+
+ if (sharesys->TestFeature(pContext->GetRuntime(), type, name) != FeatureStatus_Available)
+ {
+ char buffer[255];
+ char *msg = buffer;
+ char default_message[255];
+ SMPlugin *pPlugin = scripts->FindPluginByContext(pContext->GetContext());
+
+ g_pSM->FormatString(buffer, sizeof(buffer), pContext, params, 3);
+ if (pContext->GetLastNativeError() != SP_ERROR_NONE || buffer[0] == '\0')
+ {
+ g_pSM->Format(default_message, sizeof(default_message), "Feature \"%s\" not available", name);
+ msg = default_message;
+ }
+ pPlugin->SetErrorState(Plugin_Error, "%s", msg);
+ return pContext->ThrowNativeErrorEx(SP_ERROR_ABORTED, "%s", msg);
+ }
+
+ return 1;
+}
+
+enum NumberType
+{
+ NumberType_Int8,
+ NumberType_Int16,
+ NumberType_Int32
+};
+
+//memory addresses below 0x10000 are automatically considered invalid for dereferencing
+#define VALID_MINIMUM_MEMORY_ADDRESS 0x10000
+
+static cell_t LoadFromAddress(IPluginContext *pContext, const cell_t *params)
+{
+ void *addr = reinterpret_cast(params[1]);
+
+ if (addr == NULL)
+ {
+ return pContext->ThrowNativeError("Address cannot be null");
+ }
+ else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS)
+ {
+ return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr);
+ }
+ NumberType size = static_cast(params[2]);
+
+ switch(size)
+ {
+ case NumberType_Int8:
+ return *reinterpret_cast(addr);
+ case NumberType_Int16:
+ return *reinterpret_cast(addr);
+ case NumberType_Int32:
+ return *reinterpret_cast(addr);
+ default:
+ return pContext->ThrowNativeError("Invalid number types %d", size);
+ }
+}
+
+
+static cell_t StoreToAddress(IPluginContext *pContext, const cell_t *params)
+{
+ void *addr = reinterpret_cast(params[1]);
+
+ if (addr == NULL)
+ {
+ return pContext->ThrowNativeError("Address cannot be null");
+ }
+ else if (reinterpret_cast(addr) < VALID_MINIMUM_MEMORY_ADDRESS)
+ {
+ return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory.", addr);
+ }
+ cell_t data = params[2];
+
+ NumberType size = static_cast(params[3]);
+
+ switch(size)
+ {
+ case NumberType_Int8:
+ SourceHook::SetMemAccess(addr, sizeof(uint8_t), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC);
+ *reinterpret_cast(addr) = data;
+ break;
+ case NumberType_Int16:
+ SourceHook::SetMemAccess(addr, sizeof(uint16_t), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC);
+ *reinterpret_cast(addr) = data;
+ break;
+ case NumberType_Int32:
+ SourceHook::SetMemAccess(addr, sizeof(uint32_t), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC);
+ *reinterpret_cast(addr) = data;
+ break;
+ default:
+ return pContext->ThrowNativeError("Invalid number types %d", size);
+ }
+
+ return 0;
+}
+
+REGISTER_NATIVES(coreNatives)
+{
+ {"ThrowError", ThrowError},
+ {"GetTime", GetTime},
+ {"FormatTime", FormatTime},
+ {"GetPluginIterator", GetPluginIterator},
+ {"MorePlugins", MorePlugins},
+ {"ReadPlugin", ReadPlugin},
+ {"GetPluginStatus", GetPluginStatus},
+ {"GetPluginFilename", GetPluginFilename},
+ {"IsPluginDebugging", IsPluginDebugging},
+ {"GetPluginInfo", GetPluginInfo},
+ {"SetFailState", SetFailState},
+ {"GetSysTickCount", GetSysTickCount},
+ {"AutoExecConfig", AutoExecConfig},
+ {"MarkNativeAsOptional", MarkNativeAsOptional},
+ {"RegPluginLibrary", RegPluginLibrary},
+ {"LibraryExists", LibraryExists},
+ {"LogAction", sm_LogAction},
+ {"LogToFile", LogToFile},
+ {"LogToFileEx", LogToFileEx},
+ {"GetExtensionFileStatus", GetExtensionFileStatus},
+ {"FindPluginByNumber", FindPluginByNumber},
+ {"VerifyCoreVersion", VerifyCoreVersion},
+ {"GetFeatureStatus", GetFeatureStatus},
+ {"RequireFeature", RequireFeature},
+ {"LoadFromAddress", LoadFromAddress},
+ {"StoreToAddress", StoreToAddress},
+ {NULL, NULL},
+};
\ No newline at end of file