Cvars: Add hook_cvar_change, [enable|disable]_cvar_hook natives
This commit is contained in:
parent
0db5963641
commit
768fa2c3bc
@ -112,6 +112,36 @@ static cell AMX_NATIVE_CALL emit_sound(AMX *amx, cell *params) /* 7 param */
|
|||||||
return 1;
|
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 */
|
static cell AMX_NATIVE_CALL server_print(AMX *amx, cell *params) /* 1 param */
|
||||||
{
|
{
|
||||||
int len;
|
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));
|
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)
|
static cell AMX_NATIVE_CALL is_rukia_a_hag(AMX *amx, cell *params)
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
@ -4867,6 +4920,8 @@ AMX_NATIVE_INFO amxmodx_Natives[] =
|
|||||||
{"console_print", console_print},
|
{"console_print", console_print},
|
||||||
{"cvar_exists", cvar_exists},
|
{"cvar_exists", cvar_exists},
|
||||||
{"emit_sound", emit_sound},
|
{"emit_sound", emit_sound},
|
||||||
|
{"enable_cvar_hook", enable_cvar_hook},
|
||||||
|
{"disable_cvar_hook", disable_cvar_hook},
|
||||||
{"engclient_cmd", engclient_cmd},
|
{"engclient_cmd", engclient_cmd},
|
||||||
{"engclient_print", engclient_print},
|
{"engclient_print", engclient_print},
|
||||||
{"find_player", find_player},
|
{"find_player", find_player},
|
||||||
@ -4940,6 +4995,7 @@ AMX_NATIVE_INFO amxmodx_Natives[] =
|
|||||||
{"get_xvar_id", get_xvar_id},
|
{"get_xvar_id", get_xvar_id},
|
||||||
{"get_xvar_num", get_xvar_num},
|
{"get_xvar_num", get_xvar_num},
|
||||||
{"has_map_ent_class", has_map_ent_class},
|
{"has_map_ent_class", has_map_ent_class},
|
||||||
|
{"hook_cvar_change", hook_cvar_change},
|
||||||
{"int3", int3},
|
{"int3", int3},
|
||||||
{"is_amd64_server", is_amd64_server},
|
{"is_amd64_server", is_amd64_server},
|
||||||
{"is_dedicated_server", is_dedicated_server},
|
{"is_dedicated_server", is_dedicated_server},
|
||||||
|
@ -13,19 +13,48 @@
|
|||||||
|
|
||||||
CvarManager g_CvarManager;
|
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.
|
CvarInfo* info = nullptr;
|
||||||
if (var && value)
|
|
||||||
{
|
|
||||||
// Make sure old and new values are different to not trigger callbacks.
|
|
||||||
if (strcmp(var->string, value) != 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
{
|
||||||
|
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)
|
CvarManager::CvarManager() : m_AmxmodxCvars(0), m_HookDetour(nullptr)
|
||||||
@ -34,19 +63,7 @@ CvarManager::CvarManager() : m_AmxmodxCvars(0), m_HookDetour(nullptr)
|
|||||||
|
|
||||||
CvarManager::~CvarManager()
|
CvarManager::~CvarManager()
|
||||||
{
|
{
|
||||||
CvarsList::iterator iter = m_Cvars.begin();
|
OnAmxxShutdown();
|
||||||
|
|
||||||
while (iter != m_Cvars.end())
|
|
||||||
{
|
|
||||||
CvarInfo* info = (*iter);
|
|
||||||
|
|
||||||
iter = m_Cvars.erase(iter);
|
|
||||||
|
|
||||||
delete info;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Cache.clear();
|
|
||||||
m_HookDetour->Destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CvarManager::CreateCvarHook(void)
|
void CvarManager::CreateCvarHook(void)
|
||||||
@ -103,8 +120,7 @@ cvar_t* CvarManager::CreateCvar(const char* name, const char* value, float fvalu
|
|||||||
cvar_t* var = nullptr;
|
cvar_t* var = nullptr;
|
||||||
CvarInfo* info = nullptr;
|
CvarInfo* info = nullptr;
|
||||||
|
|
||||||
// Is cvar already cached ?
|
if (!CacheLookup(name, &info))
|
||||||
if (!m_Cache.retrieve(name, &info))
|
|
||||||
{
|
{
|
||||||
// Not cached - Is cvar already exist?
|
// Not cached - Is cvar already exist?
|
||||||
var = CVAR_GET_POINTER(name);
|
var = CVAR_GET_POINTER(name);
|
||||||
@ -174,7 +190,7 @@ cvar_t* CvarManager::FindCvar(const char* name)
|
|||||||
CvarInfo* info = nullptr;
|
CvarInfo* info = nullptr;
|
||||||
|
|
||||||
// Do we have already cvar in cache?
|
// Do we have already cvar in cache?
|
||||||
if (m_Cache.retrieve(name, &info))
|
if (CacheLookup(name, &info))
|
||||||
{
|
{
|
||||||
return info->var;
|
return info->var;
|
||||||
}
|
}
|
||||||
@ -218,6 +234,55 @@ CvarInfo* CvarManager::FindCvar(size_t index)
|
|||||||
return nullptr;
|
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()
|
size_t CvarManager::GetRegCvarsCount()
|
||||||
{
|
{
|
||||||
return m_AmxmodxCvars;
|
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();
|
||||||
|
}
|
||||||
|
@ -10,12 +10,46 @@
|
|||||||
#ifndef CVARS_H
|
#ifndef CVARS_H
|
||||||
#define CVARS_H
|
#define CVARS_H
|
||||||
|
|
||||||
#include "cvardef.h"
|
#include "amxmodx.h"
|
||||||
|
#include <am-vector.h>
|
||||||
#include <am-inlinelist.h>
|
#include <am-inlinelist.h>
|
||||||
#include <sm_namehashset.h>
|
#include <sm_namehashset.h>
|
||||||
|
|
||||||
class CDetour;
|
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>
|
struct CvarInfo : public ke::InlineListNode<CvarInfo>
|
||||||
{
|
{
|
||||||
cvar_t* var;
|
cvar_t* var;
|
||||||
@ -23,6 +57,7 @@ struct CvarInfo : public ke::InlineListNode<CvarInfo>
|
|||||||
ke::AString defaultval;
|
ke::AString defaultval;
|
||||||
ke::AString plugin;
|
ke::AString plugin;
|
||||||
int pluginId;
|
int pluginId;
|
||||||
|
CvarsHook hooks;
|
||||||
bool amxmodx;
|
bool amxmodx;
|
||||||
|
|
||||||
static inline bool matches(const char *name, const CvarInfo* info)
|
static inline bool matches(const char *name, const CvarInfo* info)
|
||||||
@ -44,11 +79,19 @@ class CvarManager
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
void CreateCvarHook();
|
void CreateCvarHook();
|
||||||
|
|
||||||
cvar_t* CreateCvar(const char* name, const char* value, float fvalue, int flags, const char* plugin, int plugnId);
|
cvar_t* CreateCvar(const char* name, const char* value, float fvalue, int flags, const char* plugin, int plugnId);
|
||||||
cvar_t* FindCvar(const char* name);
|
cvar_t* FindCvar(const char* name);
|
||||||
CvarInfo* FindCvar(size_t index);
|
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();
|
size_t GetRegCvarsCount();
|
||||||
|
|
||||||
void OnConsoleCommand();
|
void OnConsoleCommand();
|
||||||
|
void OnPluginUnloaded();
|
||||||
|
void OnAmxxShutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -697,6 +697,9 @@ void C_ServerDeactivate_Post()
|
|||||||
g_vault.clear();
|
g_vault.clear();
|
||||||
g_xvars.clear();
|
g_xvars.clear();
|
||||||
g_plugins.clear();
|
g_plugins.clear();
|
||||||
|
|
||||||
|
g_CvarManager.OnPluginUnloaded();
|
||||||
|
|
||||||
ClearPluginLibraries();
|
ClearPluginLibraries();
|
||||||
modules_callPluginsUnloaded();
|
modules_callPluginsUnloaded();
|
||||||
|
|
||||||
|
@ -13,12 +13,40 @@
|
|||||||
#define _cvars_included
|
#define _cvars_included
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a console variable's value is changed.
|
* Creates a hook for when a console variable's value is changed.
|
||||||
*
|
*
|
||||||
* @param cvarHandle Handle to the cvar that was changed
|
* @note Callback will be called in the following manner:
|
||||||
* @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
|
|
||||||
*
|
*
|
||||||
* public OnCvarChanged(cvarHandle, const oldValue[], const newValue[], const cvarName[]);
|
* 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);
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user