8000 Provide an alternative to SourceHook by Kenzzer · Pull Request #223 · alliedmodders/metamod-source · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Provide an alternative to SourceHook #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

Kenzzer
Copy link
Member
@Kenzzer Kenzzer commented Apr 17, 2025

What is the motivation behind this ?

With the advent of source 2 a lot of the modding rules have changed. Unlike source 1 games with sourcemod, there's currently no 'winner' over the modding space. Instead we have a ton of competing framework CounterStrikeSharp, CS2Fixes, Swiftly to name a few, each shipping with their own detour library. This of course, much like source 1 early days, is creating a ton of conflicts and unfortunately Metamod is currently not suited at handling those conflicts. While Source 1 modding primarily involved hooking virtual functions, this is not really the case anymore on Source 2. SourceHook needs to evolve into a proper detouring library.

How does the new detour library work ?

Calling the new framework a detour library would be technically incorrect as it doesn't perform any actual detouring, that work is left to its backend SafetyHook. Instead it adds an extra layer of logic where each detour address is catalogued so that only one detour exists per address. However for the sake of public clarity I will refer to the whole as a detour library.

Unlike SourceHook which relied on the consumer to implement the callback calling logic, the new framework is capable of handling that on its own. In order to achieve this the detour function is JIT'd and it makes a few assumptions such as :

  • The detoured function probably use all general purpose registers
  • The detoured function probably use all float point registers (x86_64 only)
  • The detoured function assumes the stack is 112 bytes tall

Note

All of that data is saved when the detour function is first invoked and then restored back before each detour callback invocation.

Inspired by SourceHook the callback logic is similar. There are PRE & POST callbacks, called before and after the detour'd function respectively. Each callback is given the option to

  • Do nothing to/Ignore the return value
  • Override the return value
  • Override the return value and prevent the original function from being called

Two extra functions are invoked during the callback logic process. Because the framework is unaware which calling convention is being used by the original function it doesn't know how to store the return value if the original function is called, and it doesn't know how to perform the final return. That job is instead left to the detour consumers, a hook will be randomly selected to perform the original function call and store the value, and another hook will be randomly selected to perform the final return.

Helper functions are provided for the various callbacks :

KHOOK_API void* GetCurrent();
KHOOK_API void* GetOriginalFunction();
KHOOK_API void* GetOriginalValuePtr(bool pop = false);
KHOOK_API void* GetOverrideValuePtr(bool pop = false);

Note

Each of those functions are thread-local. Meaning the value they return is for that thread only, this is a major difference with
SourceHook since it enables hook callbacks to be thread-safe.

Helper class

All of this was just the nitty gritty of how the new detouring library works internally, but much like SourceHook the consumers are not required to know all of this. Helper class are provided to use the detouring library in a straight forward manner.

KHook::Virtual hook1(&IServerGameDLL::GameInit, context, &SourceProvider::Hook_GameInit_PRE, nullptr);
KHook::Virtual hook2(&IServerGameDLL::LevelInit, context, nullptr, &SourceProvider::Hook_LevelInit_POST);
KHook::Function hook3(0xDEADBEEF, context, nullptr, &SourceProvider::Hook_SteamCallbacks_POST);
KHook::Member hook4(0xDEADBEEF, context, &SourceProvider::Hook_ClientCommand_PRE, nullptr);

Each hook can be a global variable or member of a class, clean-up logic is handled automatically upon the hook's class destruction. There's also no macro-hell, everything is templated.

KHook::Return<bool> SourceProvider::Hook_ClientCommand_PRE(IServerGameClients*, edict_t* client, const CCommand& _cmd)
{
	GlobCommand cmd(&_cmd);
	if (strcmp(cmd.GetArg(0), "meta") == 0)
	{
		return { KHook::Action::Supercede, true };
	}

	return { KHook::Action::Ignore };
}

Callbacks are also simplified, you do not need to use a macro anymore you only need to return a valid KHook::Return struct and the hook override/supercede logic will be handled automatically.

Why blow up SourceHook ?

Before answering, I would like to stress that SourceHook is a good framework in the context of why it was built i.e unifying virtual hooking. But as explained in the introduction, that's no longer the case for Source 2 modding and metamod must provide a unifying hooking interface not just limited to virtual functions.

Upgrading SourceHook was not viable, a lot of its core concepts are tightly bound to virtual hooking so there is a ton of internal rework necessary and you would be essentially rewriting 90% of the callback logic, might as well start from scratch.
However providing two detouring framework, or introducing some kind of backwards compatibility between the two is just going to enable the initial problem to continue. Metamod 2.0 is a thing, we are allowed to make such a huge breaking change and force everyone to abandon their own detour library flavor.

Another reason for blowing up SourceHook is Hookmangen, it's hard for me to describe what hookmangen is for. It creates the function that calls sourcehook callbacks on the fly, but it's abstracted away behind a ton of SourceHook specific flags. Why not just provide an assembly emitter to the consumer and let them implement the callbacks automatically, also why burden the consumer with the callback logic when it should have been SourceHook's job from the start. As of today, this portion of SourceHook has single handedly prevented Sourcemod's complete support of x86_64. But with the new framework, consumers will be able to JIT their own functions with whatever assembly instructions they desire.

Naming/Licensing

Everything about that work is placeholder, I will transfer the whole repository to AM once this is ready for merge. I can't create repositories under AM and didn't want to bother anyone with something that might not see the light of day. Finally it's called KHook merely because of an inside joke (cook), we can rename to anything else if need be, it just made the code removal of SourceHook easier.

@GAMMACASE
Copy link
Member
GAMMACASE commented Apr 17, 2025

Wouldn't it be better to put that dependency at a third_party subfolder instead of core? (You also seems to have multiple entries defined in .gitmodules)

@Kenzzer
Copy link
Member Author
Kenzzer commented Apr 17, 2025

Wouldn't it be better to put that dependency at a third_party subfolder instead of core?

As things stand right now, yes it would make more sense to have it under third_party. But as I intend to transfer the repo to AM later, and also because core is where SH used to live I decided to leave it here.

But you made me realise core is an awkward spot, whether its this new framework or sourcehook. Might make sense to move it to the root, in a similar fashion to sourcemod's with sourcepawn repo ? It is weird for metamod plugins/sourcemod extensions to pull metamod-source/core/sourcehook in their include paths.

@GAMMACASE
Copy link
Member

Well amtl is also an am project, yet it's in the third_party subfolder, thus the question. Only if you mean you would transfer the code under the mm repo then I guess sure.

@Kenzzer Kenzzer force-pushed the k/sourcehook_alternative branch from cce4a9b to 7c45817 Compare April 19, 2025 18:32
@psychonic
Copy link
Member

For library dir location, I agree that core has always felt wonky. The history there though was that SourceHook's API/ABI effectively because part of MM:S's API/ABI, so it stayed versioned separately with core and core_legacy (now gone, no longer supported).

I think either dropping it in the root or in a third_party dir is fine. I don't have a strong opinion of which specifically

@Kenzzer Kenzzer force-pushed the k/sourcehook_alternative branch from 199215b to ffedcab Compare April 23, 2025 08:21
@Kenzzer Kenzzer marked this pull request as ready for review April 29, 2025 09:59
@Kenzzer Kenzzer changed the title [Draft] Provide an alternative to SourceHook Provide an alternative to SourceHook Apr 29, 2025
@Kenzzer Kenzzer force-pushed the k/sourcehook_alternative branch from 383e92d to 6fd40ab Compare April 29, 2025 10:12
@Kenzzer Kenzzer force-pushed the k/sourcehook_alternative branch 4 times, most recently from cec6816 to 10c3948 Compare April 29, 2025 14:03
@Kenzzer Kenzzer force-pushed the k/sourcehook_alternative branch from 10c3948 to 28fb48b Compare April 29, 2025 14:08
harmonyjoe

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0