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;
|
||||
}
|
||||
|
||||
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},
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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:
|
||||
|
||||
|
@ -697,6 +697,9 @@ void C_ServerDeactivate_Post()
|
||||
g_vault.clear();
|
||||
g_xvars.clear();
|
||||
g_plugins.clear();
|
||||
|
||||
g_CvarManager.OnPluginUnloaded();
|
||||
|
||||
ClearPluginLibraries();
|
||||
modules_callPluginsUnloaded();
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user