31.03.2019, 22:24
This is a hook method I came up with long ago (before hook method 7, i.e. modern ALS). It uses states to determine if a hook exists or not. This was previously documented, but that documentation is both deleted and obsolete - the technique is the same, but the implementation has evolved a little bit.
y_hooks are simpler to write, and faster to run. Normal ALS hooks are slightly faster, but lack two properties. Because this version uses states the next item in the chain always exists, and the entry point to the chain function is in a known format and location in the AMX. These properties together allow us to detect and intercept these calls at run-time. And so introduces "pre-hooks". The standard call order for hooks is as follows:
This assumes you have YSI (and thus y_prehook, part of YSI_Core). Otherwise, check the file for the macro definitions:
https://github.com/pawn-lang/YSI-Inc.../y_prehook.inc
It also defines macros to simplify the forwarding, and add custom return values:
Again, check y_prehook for more information. These macros are also mostly in fixes.inc, but currently misnamed.
PHP Code:
#include <a_samp>
/*
First define several states for "hooked" and "unhooked". These functions are
purely to define states, they are never used. The states only need to be
defined once per mode, and both fixes.inc and YSI already define them. The
function name is unimportant, but the state specifiers must be exactly as below:
*/
static stock Library_IncludeStates() <_ALS : _ALS_x0, _ALS : _ALS_x1, _ALS : _ALS_x2, _ALS : _ALS_x3>
{
}
static stock Library_IncludeStates() <_ALS : _ALS_go>
{
}
/*
Here is our first hooked function, it is always a good idea to hook a ScriptInit
function so you can set "_ALS_" state, but you can also do that in any other
callback, this is just merely a small optimisation.
*/
public OnGameModeInit()
{
/*
Set the `_ALS_` state to `_ALS_go`. The basic idea is that this state is
ALWAYS `_ALS_go`, if the next callback in a callback chain exists it will
have an implementation defined for the `_ALS_go` state; if there is no next
callback, only the fallback will exist. This should be done before the
first chain call - it only NEEDs to be done once but multiple times is fine,
and multiple libraries can (and do) do this.
*/
state _ALS : _ALS_go;
/*
Now call the next callback in the chain. With the normal ALS method this
requires a pre-processor `#if` directive to protect against the case where
the next item in the chain doesn't exist. With this version, the next
chain callback always exists in some form even if the user didn't define it.
*/
return Library_OnGameModeInit();
}
/*
Forward the next callback in the chain.
*/
forward Library_OnGameModeInit();
/*
Normal ALS redefinition checks.
*/
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif
/*
Now this is where it starts getting interesting. Because we unconditionally
call `Library_OnGameModeInit`, the code MUST include this function, but because
we are hooking it, we don't know if it will or not - so we create it! But if we
create it, then what's in the next library (or mode) in the chain? The answer
is they are BOTH the next function in the chain thanks to the magic of states.
The first line here defines `OnGameModeInit` when `_ALS` is set to two of the
other values - this is actually NEVER the case, but is important as it tells the
compiler which automata our `Library_OnGameModeInit` function is controlled by
(i.e. `_ALS`). The second line is the "fallback" function - if `_ALS` is in a
state for which there is no specific implementation, this one will get called
instead and just instantly return (maybe you can now see the trick). If `_ALS`
is set to `_ALS_go` and there is no other function, then the compiler will
identify this fallback function as the correct one to call (or rather the
runtime will). If the hooked function DOES exist, then that more specialised
version will be called instead.
If we didn't have the first function to define states, then the fallback
wouldn't compile.
*/
public Library_OnGameModeInit() <_ALS : _ALS_x0, _ALS : _ALS_x1> { return 1; }
public Library_OnGameModeInit() <> { return 1; }
/*
Because we are now using states, we need a slightly more complex redefinition of
the next callback's function definition - we need to transparently add the
`_ALS : _ALS_go` state to it, so that's exactly what this line does.
Remember that `_ALS` only needs to be defined once in a mode, and `Library_` is
just the prefix I've used in this example - it needs to be unique.
*/
#define OnGameModeInit(%0) Library_OnGameModeInit(%0) <_ALS : _ALS_go>
PHP Code:
/*
This is just another `OnGameModeInit` that will be correctly called if it
exists. It shouldn't be part of your library (i.e. this is user code).
*/
public OnGameModeInit()
{
printf("Hello");
return 1;
}
- y_hooks, in include order.
- ALS hooks, in include order.
- User code.
- Pre-hooks.
- y_hooks, in include order.
- ALS hooks, in include order.
- User code.
PHP Code:
CHAIN_ORDER(Library);
#undef CHAIN_ORDER
#define CHAIN_ORDER CHAIN_NEXT(Library)
https://github.com/pawn-lang/YSI-Inc.../y_prehook.inc
It also defines macros to simplify the forwarding, and add custom return values:
PHP Code:
/*
Normal ALS definitions.
*/
#if defined _ALS_OnGameModeInit
#undef OnGameModeInit
#else
#define _ALS_OnGameModeInit
#endif
/*
All that is required for forwarding. The `= 1` is to define the default return
value, so in `OnPlayerCommandText` this would be `= 0`.
*/
CHAIN_FORWARD:Library_OnGameModeInit() = 1;
/*
Now the redefinition.
*/
#define OnGameModeInit(%0) CHAIN_PUBLIC:Library_OnGameModeInit(%0)