Source code for memory.hooks
# ../memory/hooks.py
"""Provides memory hooking functionality."""
# =============================================================================
# >> IMPORTS
# =============================================================================
# Python
from contextlib import contextmanager
# Source.Python Imports
# Core
from core import AutoUnload
# Memory
from _memory import HookType
from _memory import set_hooks_disabled
from _memory import get_hooks_disabled
from memory import Function
# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = ('HookType',
'PostHook',
'PreHook',
'set_hooks_disabled',
'get_hooks_disabled',
'hooks_disabled',
'use_pre_registers',
)
# =============================================================================
# >> CLASSES
# =============================================================================
class _Hook(AutoUnload):
"""Create pre and post hooks that auto unload."""
def __init__(self, function):
"""Verify the given function is a Function object and store it."""
# Is the function to be hooked a Function instance?
if not isinstance(function, Function):
# Raise an error as we can only hook Function instances
raise TypeError(
"'" + type(function).__name__ +
"' object is not a Function instance.")
# Store the function
self.callback = None
self.function = function
def __call__(self, callback):
"""Store the callback and hook it."""
# Store the callback
self.callback = callback
# Hook the callback to the Function
self.function.add_hook(self.hook_type, self.callback)
# Return the callback
return self.callback
@property
def hook_type(self):
"""Raise an error if the inheriting class does not have their own."""
raise NotImplementedError('No hook_type defined for class.')
def _unload_instance(self):
"""Unregister the hook on script unload."""
self.function.remove_hook(self.hook_type, self.callback)
[docs]class PreHook(_Hook):
"""Decorator class used to create pre hooks that auto unload."""
hook_type = HookType.PRE
[docs]class PostHook(_Hook):
"""Decorator class used to create post hooks that auto unload."""
hook_type = HookType.POST
# =============================================================================
# >> FUNCTIONS
# =============================================================================
@contextmanager
[docs]def use_pre_registers(stack_data, value=True):
"""Temporarily set ``StackData.use_pre_registers`` to the given value.
When the context ends, the previous value is restored.
Some functions overwrite CPU registers during their execution with values
from their internal calculations. In a post-hook, you have access to the
modified CPU registers, but in some cases you might want to access the
registers that were saved before the pre-hook was called. In that case you
can use this context manager to get access to the previous state of the
registers. On Windows this is often required when hooking THISCALL
functions, because the this-pointer is saved in the CPU register ``ECX``,
but gets overwritten during the execution of the hooked function. So, in a
post-hook you won't have access to the this-pointer anymore.
Example (CS:S/Windows):
.. code:: python
from entities.hooks import EntityCondition
from entities.hooks import EntityPostHook
from entities.hooks import EntityPreHook
from memory.hooks import use_pre_registers
@EntityPreHook(EntityCondition.is_player, 'drop_weapon')
def pre_on_drop_weapon(stack_data):
print(f'PRE: this = {stack_data[0].address}')
@EntityPostHook(EntityCondition.is_player, 'drop_weapon')
def post_on_drop_weapon(stack_data, ret_val):
print(f'POST FALSE: this = {stack_data[0].address}')
with use_pre_registers(stack_data):
print(f'POST CORRECT: this = {stack_data[0].address}')
Output:
.. code::
PRE: this = 546778280
POST FALSE: this = 16439007
POST CORRECT: this = 546778280
"""
old = stack_data.use_pre_registers
stack_data.use_pre_registers = value
try:
yield
finally:
stack_data.use_pre_registers = old
@contextmanager
[docs]def hooks_disabled(disabled=True):
"""Temporarily disable or enable all hook callbacks. By default hooks are
enabled. Thus, this context manager is mainly used to temporarily disable
hook callbacks. If the context ends, the original value is restored. This
can be used e. g. to avoid recursive calls when damaging a player in a
``on_take_damage`` hook or ``player_hurt`` event.
.. note::
This would only disable hooks created with Source.Python. Hooks that
have been created by other server plugins will still be called.
Example:
.. code:: python
from players.entity import Player
from memory.hooks import hooks_disabled
# Get a Player instance of the player with index 1
player = Player(1)
# Damage the player. This would call e. g. on_take_damage hooks
player.take_damage(5)
# To avoid calling the on_take_damage hooks, use the following:
with hooks_disabled()
player.take_damage(5)
"""
old = get_hooks_disabled()
set_hooks_disabled(disabled)
try:
yield
finally:
set_hooks_disabled(old)