Cvars: Add hook_cvar_change, [enable|disable]_cvar_hook natives

This commit is contained in:
Arkshine 2015-01-21 20:42:41 +01:00
parent 0db5963641
commit 768fa2c3bc
5 changed files with 261 additions and 31 deletions

View File

@ -112,6 +112,36 @@ static cell AMX_NATIVE_CALL emit_sound(AMX *amx, cell *params) /* 7 param */
return 1;
}
static cell AMX_NATIVE_CALL enable_cvar_hook(AMX *amx, cell *params) /* 7 param */
{
Forward* forward = reinterpret_cast<Forward*>(params[1]);
if (!forward)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid cvar hook handle: %p", forward);
return 0;
}
forward->state = Forward::FSTATE_OK;
return 1;
}
static cell AMX_NATIVE_CALL disable_cvar_hook(AMX *amx, cell *params) /* 7 param */
{
Forward* forward = reinterpret_cast<Forward*>(params[1]);
if (!forward)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid cvar hook handle: %p", forward);
return 0;
}
forward->state = Forward::FSTATE_STOP;
return 1;
}
static cell AMX_NATIVE_CALL server_print(AMX *amx, cell *params) /* 1 param */
{
int len;
@ -4831,6 +4861,29 @@ static cell AMX_NATIVE_CALL has_map_ent_class(AMX *amx, cell *params)
return len && !FNullEnt(FIND_ENTITY_BY_STRING(NULL, "classname", name));
};
// hook_cvar_change(cvarHandle, const callback[])
static cell AMX_NATIVE_CALL hook_cvar_change(AMX *amx, cell *params)
{
cvar_t* var = reinterpret_cast<cvar_t*>(params[1]);
if (!var)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid cvar handle: %p", var);
return 0;
}
const char* callback;
Forward* forward = g_CvarManager.HookCvarChange(var, amx, params[2], &callback);
if (!forward)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid callback function: %s", callback);
return 0;
}
return reinterpret_cast<cell>(forward);
}
static cell AMX_NATIVE_CALL is_rukia_a_hag(AMX *amx, cell *params)
{
return 1;
@ -4867,6 +4920,8 @@ AMX_NATIVE_INFO amxmodx_Natives[] =
{"console_print", console_print},
{"cvar_exists", cvar_exists},
{"emit_sound", emit_sound},
{"enable_cvar_hook", enable_cvar_hook},
{"disable_cvar_hook", disable_cvar_hook},
{"engclient_cmd", engclient_cmd},
{"engclient_print", engclient_print},
{"find_player", find_player},
@ -4940,6 +4995,7 @@ AMX_NATIVE_INFO amxmodx_Natives[] =
{"get_xvar_id", get_xvar_id},
{"get_xvar_num", get_xvar_num},
{"has_map_ent_class", has_map_ent_class},
{"hook_cvar_change", hook_cvar_change},
{"int3", int3},
{"is_amd64_server", is_amd64_server},
{"is_dedicated_server", is_dedicated_server},

View File

@ -13,19 +13,48 @@
CvarManager g_CvarManager;
DETOUR_DECL_STATIC2(Cvar_DirectSet, void, struct cvar_s*, var, const char*, value)
/**
* Returns true to call original function, otherwise false to block it.
*/
bool Cvar_DirectSet_Custom(cvar_t* var, const char* value)
{
// Sanity checks against bogus pointers.
if (var && value)
CvarInfo* info = nullptr;
if (!var || !value // Sanity checks against bogus pointers.
|| strcmp(var->string, value) == 0 // Make sure old and new values are different to not trigger callbacks.
|| !g_CvarManager.CacheLookup(var->name, &info) // No data in cache, nothing to call.
|| info->hooks.empty()) // No hooked cvars, nothing to call.
{
// Make sure old and new values are different to not trigger callbacks.
if (strcmp(var->string, value) != 0)
return true;
}
int lastResult = 0;
int result;
for (size_t i = 0; i < info->hooks.length(); ++i)
{
CvarPlugin* p = info->hooks[i];
if (p->forward->state == Forward::FSTATE_OK) // Our callback can be enable/disabled by natives.
{
result = executeForwards(p->forward->id, reinterpret_cast<cvar_t*>(var), var->string, value);
if (result >= lastResult)
{
lastResult = result;
}
}
}
DETOUR_STATIC_CALL(Cvar_DirectSet)(var, value);
return !!!lastResult;
}
DETOUR_DECL_STATIC2(Cvar_DirectSet, void, struct cvar_s*, var, const char*, value)
{
if (Cvar_DirectSet_Custom(var, value))
{
DETOUR_STATIC_CALL(Cvar_DirectSet)(var, value);
}
}
CvarManager::CvarManager() : m_AmxmodxCvars(0), m_HookDetour(nullptr)
@ -34,19 +63,7 @@ CvarManager::CvarManager() : m_AmxmodxCvars(0), m_HookDetour(nullptr)
CvarManager::~CvarManager()
{
CvarsList::iterator iter = m_Cvars.begin();
while (iter != m_Cvars.end())
{
CvarInfo* info = (*iter);
iter = m_Cvars.erase(iter);
delete info;
}
m_Cache.clear();
m_HookDetour->Destroy();
OnAmxxShutdown();
}
void CvarManager::CreateCvarHook(void)
@ -103,8 +120,7 @@ cvar_t* CvarManager::CreateCvar(const char* name, const char* value, float fvalu
cvar_t* var = nullptr;
CvarInfo* info = nullptr;
// Is cvar already cached ?
if (!m_Cache.retrieve(name, &info))
if (!CacheLookup(name, &info))
{
// Not cached - Is cvar already exist?
var = CVAR_GET_POINTER(name);
@ -174,7 +190,7 @@ cvar_t* CvarManager::FindCvar(const char* name)
CvarInfo* info = nullptr;
// Do we have already cvar in cache?
if (m_Cache.retrieve(name, &info))
if (CacheLookup(name, &info))
{
return info->var;
}
@ -218,6 +234,55 @@ CvarInfo* CvarManager::FindCvar(size_t index)
return nullptr;
}
bool CvarManager::CacheLookup(const char* name, CvarInfo** info)
{
return m_Cache.retrieve(name, info);
}
Forward* CvarManager::HookCvarChange(cvar_t* var, AMX* amx, cell param, const char** callback)
{
CvarInfo* info = nullptr;
// A cvar is guaranteed to be in cache if pointer is got from
// get_cvar_pointer and register_cvar natives. Though it might be
// provided by another way. If by any chance we run in such
// situation, we create a new entry right now.
if (!CacheLookup(var->name, &info))
{
// Create a new entry.
info = new CvarInfo();
info->var = var;
info->name = var->name;
info->plugin = "";
info->pluginId = -1;
info->amxmodx = false;
// Add entry in the caches.
m_Cvars.append(info);
m_Cache.insert(info->name.chars(), info);
}
int length;
*callback = get_amxstring(amx, param, 0, length);
int forwardId = registerSPForwardByName(amx, *callback, FP_CELL, FP_STRING, FP_STRING, FP_DONE);
// Invalid callback, it could be: not a public function, wrongly named, or simply missing.
if (forwardId == -1)
{
return nullptr;
}
// Detour is disabled on map change.
m_HookDetour->EnableDetour();
Forward* forward = new Forward(forwardId, *callback);
info->hooks.append(new CvarPlugin(g_plugins.findPlugin(amx)->getId(), forward));
return forward;
}
size_t CvarManager::GetRegCvarsCount()
{
return m_AmxmodxCvars;
@ -246,3 +311,38 @@ void CvarManager::OnConsoleCommand()
}
}
}
void CvarManager::OnPluginUnloaded()
{
// Clear only plugin hooks list.
for (CvarsList::iterator cvar = m_Cvars.begin(); cvar != m_Cvars.end(); cvar++)
{
for (size_t i = 0; i < (*cvar)->hooks.length(); ++i)
{
delete (*cvar)->hooks[i];
}
(*cvar)->hooks.clear();
}
// There is no point to enable detour if at next map change
// no plugins hook cvars.
m_HookDetour->DisableDetour();
}
void CvarManager::OnAmxxShutdown()
{
// Free everything.
for (CvarsList::iterator cvar = m_Cvars.begin(); cvar != m_Cvars.end(); cvar = m_Cvars.erase(cvar))
{
for (size_t i = 0; i < (*cvar)->hooks.length(); ++i)
{
delete (*cvar)->hooks[i];
}
delete (*cvar);
}
m_Cache.clear();
m_HookDetour->Destroy();
}

View File

@ -10,12 +10,46 @@
#ifndef CVARS_H
#define CVARS_H
#include "cvardef.h"
#include "amxmodx.h"
#include <am-vector.h>
#include <am-inlinelist.h>
#include <sm_namehashset.h>
class CDetour;
struct Forward
{
enum fwdstate
{
FSTATE_INVALID = 0,
FSTATE_OK,
FSTATE_STOP,
};
Forward(int id_, const char* handler) : id(id_), state(FSTATE_OK), callback(handler) {};
Forward() : id(-1) , state(FSTATE_INVALID) {};
~Forward()
{
unregisterSPForward(id);
}
int id;
fwdstate state;
ke::AString callback;
};
struct CvarPlugin
{
CvarPlugin(int id, Forward* fwd) : pluginId(id), forward(fwd) {};
CvarPlugin(int id) : pluginId(id), forward(new Forward()) {};
int pluginId;
ke::AutoPtr<Forward> forward;
};
typedef ke::Vector<CvarPlugin*> CvarsHook;
struct CvarInfo : public ke::InlineListNode<CvarInfo>
{
cvar_t* var;
@ -23,6 +57,7 @@ struct CvarInfo : public ke::InlineListNode<CvarInfo>
ke::AString defaultval;
ke::AString plugin;
int pluginId;
CvarsHook hooks;
bool amxmodx;
static inline bool matches(const char *name, const CvarInfo* info)
@ -44,11 +79,19 @@ class CvarManager
public:
void CreateCvarHook();
cvar_t* CreateCvar(const char* name, const char* value, float fvalue, int flags, const char* plugin, int plugnId);
cvar_t* FindCvar(const char* name);
CvarInfo* FindCvar(size_t index);
bool CacheLookup(const char* name, CvarInfo** info);
Forward* HookCvarChange(cvar_t* var, AMX* amx, cell param, const char** callback);
size_t GetRegCvarsCount();
void OnConsoleCommand();
void OnPluginUnloaded();
void OnAmxxShutdown();
private:

View File

@ -697,6 +697,9 @@ void C_ServerDeactivate_Post()
g_vault.clear();
g_xvars.clear();
g_plugins.clear();
g_CvarManager.OnPluginUnloaded();
ClearPluginLibraries();
modules_callPluginsUnloaded();

View File

@ -13,12 +13,40 @@
#define _cvars_included
/**
* Called when a console variable's value is changed.
*
* @param cvarHandle Handle to the cvar that was changed
* @param oldValue String containing the value of the cvar before it was changed
* @param newValue String containing the new value of the cvar
* @param cvarName String containing the name of the cvar
* Creates a hook for when a console variable's value is changed.
*
* public OnCvarChanged(cvarHandle, const oldValue[], const newValue[], const cvarName[]);
* @note Callback will be called in the following manner:
*
* public cvar_change_callback(pcvar, const old_value[], const new_value[])
*
* pcvar - Pointer to cvar that was changed
* old_value - String containing the value of the cvar before it was changed
* new_value - String containing the new value of the cvar
*
* @param pcvar Pointer to cvar
* @param callback Name of callback function
* @error Invalid pointer or invalid callback function
*/
native cvarhook:hook_cvar_change(pcvar, const callback[]);
/**
* Stops a cvar hook forward from triggering.
*
* @note Use the return value from hook_cvar_change as the parameter here.
*
* @param handle Forward to stop
* @error Invalid hook handle
*/
native disable_cvar_hook(cvarhook:handle);
/**
* Starts a cvar hook forward back up.
*
* @note Use the return value from hook_cvar_change as the parameter here.
*
* @param handle Forward to back up
* @error Invalid hook handle
*/
native enable_cvar_hook(cvarhook:handle);