Page 1 of 1

ConVar - equivalent to RegisterConVarChanges

Posted: Mon Jun 08, 2015 5:35 am
by joshtrav
I have been browsing the wiki, as well as the source on convars.

https://github.com/Source-Python-Dev-Team/Source.Python/blob/master/src/core/modules/cvars/cvars_wrap_python.cpp
http://wiki.sourcepython.com/pages/cvars

I haven't found anything that would resemble a way to hook a convar change, is there a current wrapper for an equivalent? If not, would it be feasible as a feature request?

Posted: Mon Jun 08, 2015 6:15 am
by satoon101
If you don't need to stop the change from happening, you could always just add the NOTIFY flag to the ConVar and listen to the event server_cvar.

Outside of that, we do have an experimental branch currently that will allow you to hook exposed methods of a class.
https://github.com/Source-Python-Dev-Team/Source.Python/tree/exposed_function_hooks

Here is an example of its usage:
https://github.com/Source-Python-Dev-Team/Source.Python/commit/408e95b0e0cc3ab25a5551eb46169709ece35d8a#commitcomment-11510933

Posted: Mon Jun 08, 2015 3:08 pm
by joshtrav
Thanks for the info. I think the first option will work just fine. I assume I could then just set the bDontBroadcast value to True in the server_cvar event?

Posted: Mon Jun 08, 2015 3:21 pm
by satoon101
To set the bDontBroadcast, you will have to hook fire_game_event. Basic event calling does not have that implemented. We do plan on having a PreEvent decorator at some point, probably once the experimental branch is finalized. Currently, you have to do it more like:

Syntax: Select all

from core import PLATFORM
from events import GameEvent
from events.manager import GameEventManager
from memory import Convention
from memory import DataType
from memory import get_object_pointer
from memory import make_object
from memory.hooks import PreHook

FIRE_EVENT_FUNC = get_object_pointer(
GameEventManager).make_virtual_function(
7 if PLATFORM == 'windows' else 8,
Convention.THISCALL,
(DataType.POINTER, DataType.POINTER, DataType.BOOL),
DataType.VOID
)

@PreHook(FIRE_EVENT_FUNC)
def pre_fire_event(arguments):
game_event = make_object(GameEvent, arguments[1])
if game_event.get_name() == 'server_cvar':
arguments[2] = True



Though, hopefully fairly soon, you will just be able to do something more like:

Syntax: Select all

from events.hooks import PreEvent


@PreEvent
def server_cvar(game_event):
# Block the broadcasting of the event to clients
return False

Posted: Mon Jun 08, 2015 4:09 pm
by stonedegg
satoon101 wrote:Though, hopefully fairly soon, you will just be able to do something more like:

[python]from events.hooks import PreEvent


@PreEvent
def server_cvar(game_event):
# Block the broadcasting of the event to clients
return False[/python]


Yes please!!

Posted: Mon Jun 08, 2015 5:34 pm
by Ayuto
I would like to take this chance to show another feature of SP, which is already available since a long time. Though, we have improved this feature a few weeks ago.

You can create functions, which have an address in memory, so you can pass them to any function you want. This means that you can create C++ functions with any calling convention on the fly!

Syntax: Select all

# This example has been made for Windows (CS:S)
import memory

from memory import DataType
from memory import Convention
from memory import Callback

from cvars import cvar

from _cvars import _IConVar

# Get the cvar pointer
cvar_ptr = memory.get_object_pointer(cvar)

# virtual void ICvar::InstallGlobalChangeCallback(FnChangeCallback_t callback) = 0;
InstallGlobalChangeCallback = cvar_ptr.make_virtual_function(
18,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER],
DataType.VOID
)

# virtual void ICvar::RemoveGlobalChangeCallback(FnChangeCallback_t callback) = 0;
RemoveGlobalChangeCallback = cvar_ptr.make_virtual_function(
19,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER],
DataType.VOID
)

# Create a function in memory, which can be treated just like a function
# pointer. Awesome, eh? :P
# typedef void ( *FnChangeCallback_t )( IConVar *var, const char *pOldValue, float flOldValue );
@Callback(
Convention.CDECL,
[DataType.POINTER, DataType.STRING, DataType.FLOAT],
DataType.VOID)
def on_cvar_changed(args):
"""Called whenever a ConVar changes."""
convar = memory.make_object(_IConVar, args[0])
old_str_value = args[1]
old_float_value = args[2]
print(convar.get_name(), 'has changed')

def load():
# Install the callback
InstallGlobalChangeCallback(cvar_ptr, on_cvar_changed)

def unload():
# Remove the callback
RemoveGlobalChangeCallback(cvar_ptr, on_cvar_changed)

Posted: Tue Jun 09, 2015 3:44 am
by joshtrav
satoon101 wrote:
Though, hopefully fairly soon, you will just be able to do something more like:

Syntax: Select all

from events.hooks import PreEvent


@PreEvent
def server_cvar(game_event):
# Block the broadcasting of the event to clients
return False


This will be very nice, and thanks for the current way as well!

Ayuto wrote:I would like to take this chance to show another feature of SP, which is already available since a long time. Though, we have improved this feature a few weeks ago.

You can create functions, which have an address in memory, so you can pass them to any function you want. This means that you can create C++ functions with any calling convention on the fly!

Syntax: Select all

# This example has been made for Windows (CS:S)
import memory

from memory import DataType
from memory import Convention
from memory import Callback

from cvars import cvar

from _cvars import _IConVar

# Get the cvar pointer
cvar_ptr = memory.get_object_pointer(cvar)

# virtual void ICvar::InstallGlobalChangeCallback(FnChangeCallback_t callback) = 0;
InstallGlobalChangeCallback = cvar_ptr.make_virtual_function(
18,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER],
DataType.VOID
)

# virtual void ICvar::RemoveGlobalChangeCallback(FnChangeCallback_t callback) = 0;
RemoveGlobalChangeCallback = cvar_ptr.make_virtual_function(
19,
Convention.THISCALL,
[DataType.POINTER, DataType.POINTER],
DataType.VOID
)

# Create a function in memory, which can be treated just like a function
# pointer. Awesome, eh? :P
# typedef void ( *FnChangeCallback_t )( IConVar *var, const char *pOldValue, float flOldValue );
@Callback(
Convention.CDECL,
[DataType.POINTER, DataType.STRING, DataType.FLOAT],
DataType.VOID)
def on_cvar_changed(args):
"""Called whenever a ConVar changes."""
convar = memory.make_object(_IConVar, args[0])
old_str_value = args[1]
old_float_value = args[2]
print(convar.get_name(), 'has changed')

def load():
# Install the callback
InstallGlobalChangeCallback(cvar_ptr, on_cvar_changed)

def unload():
# Remove the callback
RemoveGlobalChangeCallback(cvar_ptr, on_cvar_changed)


This seems very involved and powerful, is there a substantial amount of info on the wiki in regards to this that I missed?

Posted: Tue Jun 09, 2015 6:24 am
by Ayuto
We haven't added that to the wiki yet. But here is the source code: https://github.com/Source-Python-Dev-Team/Source.Python/blob/master/addons/source-python/packages/source-python/memory/__init__.py#L77

I can add that to the wiki today. :)

Posted: Wed Jun 10, 2015 2:08 am
by joshtrav
Ayuto, would you still need to hook the GameEventManager to stop the broadcast using the method you posted?

Posted: Wed Jun 10, 2015 6:54 am
by Ayuto
The callback gets called everytime a ConVar changes and it doesn't need the NOTIFY flag. But yes, if it has this flag, you would need to hook FireEvent.