diff --git a/core/Logger.cpp b/core/Logger.cpp
index 031e5d96..bd8cbe8f 100644
--- a/core/Logger.cpp
+++ b/core/Logger.cpp
@@ -434,7 +434,7 @@ void Logger::_PrintToGameLog(const char *fmt, va_list ap)
msg[len++] = '\n';
msg[len] = '\0';
- engine->LogPrint(msg);
+ Engine_LogPrintWrapper(msg);
}
const char *Logger::GetLogFileName(LogType type) const
@@ -505,3 +505,16 @@ void Logger::LogFatal(const char *msg, ...)
}
}
+bool g_in_game_log_hook = false;
+
+void Engine_LogPrintWrapper(const char *msg)
+{
+ if (g_in_game_log_hook)
+ {
+ ENGINE_CALL(LogPrint)(msg);
+ }
+ else
+ {
+ engine->LogPrint(msg);
+ }
+}
diff --git a/core/Logger.h b/core/Logger.h
index 0f6e22ed..fb643a2e 100644
--- a/core/Logger.h
+++ b/core/Logger.h
@@ -98,6 +98,9 @@ private:
bool m_InitialState;
};
+void Engine_LogPrintWrapper(const char *msg);
+
+extern bool g_in_game_log_hook;
extern Logger g_Logger;
#endif // _INCLUDE_SOURCEMOD_CLOGGER_H_
diff --git a/core/smn_filesystem.cpp b/core/smn_filesystem.cpp
index 124371a4..08630af3 100644
--- a/core/smn_filesystem.cpp
+++ b/core/smn_filesystem.cpp
@@ -37,22 +37,40 @@
#include "Logger.h"
#include "PluginSys.h"
#include "sourcemm_api.h"
+#include "ForwardSys.h"
+
+SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, false, const char *);
HandleType_t g_FileType;
HandleType_t g_DirType;
+IChangeableForward *g_pLogHook = NULL;
class FileNatives :
public SMGlobalClass,
- public IHandleTypeDispatch
+ public IHandleTypeDispatch,
+ public IPluginsListener
{
public:
+ FileNatives()
+ {
+ m_bIsLoggingHooked = false;
+ }
virtual void OnSourceModAllInitialized()
{
g_FileType = g_HandleSys.CreateType("File", this, 0, NULL, NULL, g_pCoreIdent, NULL);
g_DirType = g_HandleSys.CreateType("Directory", this, 0, NULL, NULL, g_pCoreIdent, NULL);
+ g_pLogHook = g_Forwards.CreateForwardEx(NULL, ET_Hook, 1, NULL, Param_String);
+ g_PluginSys.AddPluginsListener(this);
}
virtual void OnSourceModShutdown()
{
+ g_PluginSys.RemovePluginsListener(this);
+ if (m_bIsLoggingHooked)
+ {
+ SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, LogPrint, engine, this, &FileNatives::LogPrint, false);
+ m_bIsLoggingHooked = false;
+ }
+ g_Forwards.ReleaseForward(g_pLogHook);
g_HandleSys.RemoveType(g_DirType, g_pCoreIdent);
g_HandleSys.RemoveType(g_FileType, g_pCoreIdent);
g_DirType = 0;
@@ -64,11 +82,59 @@ public:
{
FILE *fp = (FILE *)object;
fclose(fp);
- } else if (type == g_DirType) {
+ }
+ else if (type == g_DirType)
+ {
IDirectory *pDir = (IDirectory *)object;
g_LibSys.CloseDirectory(pDir);
}
}
+ virtual void OnPluginDestroyed(IPlugin *plugin)
+ {
+ if (m_bIsLoggingHooked && g_pLogHook->GetFunctionCount() == 0)
+ {
+ SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, LogPrint, engine, this, &FileNatives::LogPrint, false);
+ m_bIsLoggingHooked = false;
+ }
+ }
+ virtual void AddLogHook(IPluginFunction *pFunc)
+ {
+ if (!m_bIsLoggingHooked)
+ {
+ SH_ADD_HOOK_MEMFUNC(IVEngineServer, LogPrint, engine, this, &FileNatives::LogPrint, false);
+ m_bIsLoggingHooked = true;
+ }
+
+ g_pLogHook->AddFunction(pFunc);
+ }
+ virtual void RemoveLogHook(IPluginFunction *pFunc)
+ {
+ g_pLogHook->RemoveFunction(pFunc);
+
+ if (m_bIsLoggingHooked && g_pLogHook->GetFunctionCount() == 0)
+ {
+ SH_REMOVE_HOOK_MEMFUNC(IVEngineServer, LogPrint, engine, this, &FileNatives::LogPrint, false);
+ m_bIsLoggingHooked = false;
+ }
+ }
+ virtual void LogPrint(const char *msg)
+ {
+ cell_t result;
+
+ result = 0;
+
+ g_in_game_log_hook = true;
+ g_pLogHook->PushString(msg);
+ g_pLogHook->Execute(&result);
+ g_in_game_log_hook = false;
+
+ if (result >= Pl_Handled)
+ {
+ RETURN_META(MRES_SUPERCEDE);
+ }
+ }
+private:
+ bool m_bIsLoggingHooked;
} s_FileNatives;
static cell_t sm_OpenDirectory(IPluginContext *pContext, const cell_t *params)
@@ -521,7 +587,7 @@ static cell_t sm_LogToGame(IPluginContext *pContext, const cell_t *params)
buffer[len] = '\0';
}
- engine->LogPrint(buffer);
+ Engine_LogPrintWrapper(buffer);
return 1;
}
@@ -835,6 +901,34 @@ static cell_t sm_WriteFileString(IPluginContext *pContext, const cell_t *params)
return (fwrite(buffer, sizeof(char), len, pFile) == len) ? 1 : 0;
}
+static cell_t sm_AddGameLogHook(IPluginContext *pContext, const cell_t *params)
+{
+ IPluginFunction *pFunction;
+
+ if ((pFunction=pContext->GetFunctionById(params[1])) == NULL)
+ {
+ return pContext->ThrowNativeError("Function id %x is invalid", params[1]);
+ }
+
+ s_FileNatives.AddLogHook(pFunction);
+
+ return 1;
+}
+
+static cell_t sm_RemoveGameLogHook(IPluginContext *pContext, const cell_t *params)
+{
+ IPluginFunction *pFunction;
+
+ if ((pFunction=pContext->GetFunctionById(params[1])) == NULL)
+ {
+ return pContext->ThrowNativeError("Function id %x is invalid", params[1]);
+ }
+
+ s_FileNatives.RemoveLogHook(pFunction);
+
+ return 1;
+}
+
REGISTER_NATIVES(filesystem)
{
{"OpenDirectory", sm_OpenDirectory},
@@ -863,5 +957,7 @@ REGISTER_NATIVES(filesystem)
{"ReadFileString", sm_ReadFileString},
{"WriteFile", sm_WriteFile},
{"WriteFileString", sm_WriteFileString},
+ {"AddGameLogHook", sm_AddGameLogHook},
+ {"RemoveGameLogHook", sm_RemoveGameLogHook},
{NULL, NULL},
};
diff --git a/plugins/include/logging.inc b/plugins/include/logging.inc
new file mode 100644
index 00000000..14b0e906
--- /dev/null
+++ b/plugins/include/logging.inc
@@ -0,0 +1,153 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This file is part of the SourceMod/SourcePawn SDK.
+ *
+ * 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$
+ */
+
+#if defined _sm_logging_included
+ #endinput
+#endif
+#define _sm_logging_included
+
+/**
+ * Logs a plugin message to the SourceMod logs. The log message will be
+ * prefixed by the plugin's logtag (filename).
+ *
+ * @param format String format.
+ * @param ... Format arguments.
+ * @noreturn
+ */
+native LogMessage(const String:format[], any:...);
+
+/**
+ * Logs a message to the SourceMod logs without any plugin logtag. This is
+ * useful for re-routing messages from other plugins, for example, messages
+ * from LogAction().
+ *
+ * @param format String format.
+ * @param ... Format arguments.
+ * @noreturn
+ */
+native LogMessageEx(const String:format[], any:...);
+
+/**
+ * Logs a message to any file. The log message will be in the normal
+ * SourceMod format, with the plugin logtag prepended.
+ *
+ * @param file File to write the log message in.
+ * @param format String format.
+ * @param ... Format arguments.
+ * @noreturn
+ * @error File could not be opened/written.
+ */
+native LogToFile(const String:file[], const String:format[], any:...);
+
+/**
+ * Same as LogToFile(), except no plugin logtag is prepended.
+ *
+ * @param file File to write the log message in.
+ * @param format String format.
+ * @param ... Format arguments.
+ * @noreturn
+ * @error File could not be opened/written.
+ */
+native LogToFileEx(const String:file[], const String:format[], any:...);
+
+/**
+ * Logs an action from a command or event whereby interception and routing may
+ * be important. This is intended to be a logging version of ShowActivity().
+ *
+ * @param client Client performing the action, 0 for server, or -1 if not
+ * applicable.
+ * @param target Client being targetted, or -1 if not applicable.
+ * @param message Message format.
+ * @param ... Message formatting parameters.
+ * @noreturn
+ */
+native LogAction(client, target, const String:message[], any:...);
+
+/**
+ * Logs a plugin error message to the SourceMod logs.
+ *
+ * @param format String format.
+ * @param ... Format arguments.
+ * @noreturn
+ */
+native LogError(const String:format[], any:...);
+
+/**
+ * Called when an action is going to be logged.
+ *
+ * @param source Handle to the object logging the action, or INVALID_HANDLE
+ * if Core is logging the action.
+ * @param ident Type of object logging the action (plugin, ext, or core).
+ * @param client Client the action is from; 0 for server, -1 if not applicable.
+ * @param target Client the action is targetting, or -1 if not applicable.
+ * @param message Message that is being logged.
+ * @return Plugin_Continue will cause Core to defaulty log the message.
+ * Plugin_Handled will stop Core from logging the message.
+ * Plugin_Stop is the same as Handled, but prevents any other
+ * plugins from handling the message.
+ */
+forward Action:OnLogAction(Handle:source,
+ Identity:ident,
+ client,
+ target,
+ const String:message[]);
+
+/**
+ * Called when a game log message is received.
+ *
+ * Any Log*() functions called within this callback will not recursively
+ * pass through. That is, they will log directly, bypassing this callback.
+ *
+ * Note that this does not capture log messages from the engine. It only
+ * captures log messages being sent from the game/mod itself.
+ *
+ * @param message Message contents.
+ * @return Plugin_Handled or Plugin_Stop will prevent the message
+ * from being written to the log file.
+ */
+functag GameLogHook Action:public(const String:message[]);
+
+/**
+ * Adds a game log hook.
+ *
+ * @param hook Hook function.
+ * @noreturn
+ */
+native AddGameLogHook(GameLogHook:hook);
+
+/**
+ * Removes a game log hook.
+ *
+ * @param hook Hook function.
+ * @noreturn
+ */
+native RemoveGameLogHook(GameLogHook:hook);
diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc
index afc93521..dea00ddb 100644
--- a/plugins/include/sourcemod.inc
+++ b/plugins/include/sourcemod.inc
@@ -54,6 +54,7 @@ struct Plugin
#include
#include
#include
+#include
#include
#include
#include
@@ -92,6 +93,10 @@ public Plugin:myinfo;
* If any run-time error is thrown during this callback, the plugin will be marked
* as failed.
*
+ * It is not necessary to close any handles or remove hooks in this function.
+ * SourceMod guarantees that plugin shutdown automatically and correctly releases
+ * all resources.
+ *
* @noreturn
*/
forward OnPluginStart();
@@ -176,26 +181,6 @@ forward OnServerCfg();
*/
forward OnAllPluginsLoaded();
-/**
- * Called when an action is going to be logged.
- *
- * @param source Handle to the object logging the action, or INVALID_HANDLE
- * if Core is logging the action.
- * @param ident Type of object logging the action (plugin, ext, or core).
- * @param client Client the action is from; 0 for server, -1 if not applicable.
- * @param target Client the action is targetting, or -1 if not applicable.
- * @param message Message that is being logged.
- * @return Plugin_Continue will cause Core to defaulty log the message.
- * Plugin_Handled will stop Core from logging the message.
- * Plugin_Stop is the same as Handled, but prevents any other
- * plugins from handling the message.
- */
-forward Action:OnLogAction(Handle:source,
- Identity:ident,
- client,
- target,
- const String:message[]);
-
/**
* Returns the calling plugin's Handle.
*
@@ -315,72 +300,6 @@ native SetFailState(const String:string[], any:...);
*/
native ThrowError(const String:fmt[], any:...);
-/**
- * Logs a plugin message to the SourceMod logs. The log message will be
- * prefixed by the plugin's logtag (filename).
- *
- * @param format String format.
- * @param ... Format arguments.
- * @noreturn
- */
-native LogMessage(const String:format[], any:...);
-
-/**
- * Logs a message to the SourceMod logs without any plugin logtag. This is
- * useful for re-routing messages from other plugins, for example, messages
- * from LogAction().
- *
- * @param format String format.
- * @param ... Format arguments.
- * @noreturn
- */
-native LogMessageEx(const String:format[], any:...);
-
-/**
- * Logs a message to any file. The log message will be in the normal
- * SourceMod format, with the plugin logtag prepended.
- *
- * @param file File to write the log message in.
- * @param format String format.
- * @param ... Format arguments.
- * @noreturn
- * @error File could not be opened/written.
- */
-native LogToFile(const String:file[], const String:format[], any:...);
-
-/**
- * Same as LogToFile(), except no plugin logtag is prepended.
- *
- * @param file File to write the log message in.
- * @param format String format.
- * @param ... Format arguments.
- * @noreturn
- * @error File could not be opened/written.
- */
-native LogToFileEx(const String:file[], const String:format[], any:...);
-
-/**
- * Logs an action from a command or event whereby interception and routing may
- * be important. This is intended to be a logging version of ShowActivity().
- *
- * @param client Client performing the action, 0 for server, or -1 if not
- * applicable.
- * @param target Client being targetted, or -1 if not applicable.
- * @param message Message format.
- * @param ... Message formatting parameters.
- * @noreturn
- */
-native LogAction(client, target, const String:message[], any:...);
-
-/**
- * Logs a plugin error message to the SourceMod logs.
- *
- * @param format String format.
- * @param ... Format arguments.
- * @noreturn
- */
-native LogError(const String:format[], any:...);
-
/**
* Gets the system time as a unix timestamp.
*