hzqst / MetaHookSv

MetaHook (https://github.com/nagist/metahook) porting for SvEngine (GoldSrc engine modified by Sven-Coop)
MIT License
147 stars 36 forks source link

Feature Request: Possibility to detect a Metahook client serverside #89

Closed incognico closed 3 years ago

incognico commented 3 years ago

I want to be able to detect clients who use metahooksv from the server side via AngelScript. This would allow me to enable several dlight stuff for metahooksv players without destroying the performance for players who use the vanilla client for example.

Maybe it could add a custom networked Userinfo key https://github.com/dreamstalker/rehlds/wiki/Userinfo-keys _metahook true or something, that way it could work.

Additionally, because this could be also used to ban metahooksv players, a client config variable to hide the userinfo key could be added, so a player can prefer not to send this variable to be stealth. But it should be sent by default, otherwise it would be useless for my intentional use.

hzqst commented 3 years ago

idk if AngelScript exposes registration of pfnCvarValue and pfnCvarValue2 to modders or not, but there are two server-to-client requests :

#define svc_sendcvarvalue   57
#define svc_sendcvarvalue2  58

Servers could send svc_sendcvarvalue/svc_sendcvarvalue2 request to client to query for value of specified cvar

NetworkMessage message( MSG_ONE, 57, pPlayer.edict() );//57=svc_sendcvarvalue
    message.WriteString("r_light_dynamic");
message.End();
NetworkMessage message( MSG_ONE, 58, pPlayer.edict() );//58=svc_sendcvarvalue2
    message.WriteLong(1234);//Request ID
    message.WriteString("r_light_dynamic");
message.End();

and wait for result in:

void SV_ParseCvarValue(client_t *cl)
{
    char *value;

    value = MSG_ReadString();

    if (gNewDLLFunctions.pfnCvarValue)
        gNewDLLFunctions.pfnCvarValue(cl->edict, value);

    Con_DPrintf("Cvar query response: name:%s, value:%s\n", cl->name, value);
}

void SV_ParseCvarValue2(client_t *cl)
{
    char *value;
    char cvarName[255];
    int requestID;

    requestID = MSG_ReadLong();

    Q_strncpy(cvarName, MSG_ReadString(), sizeof(cvarName));
    cvarName[sizeof(cvarName) - 1] = 0;

    value = MSG_ReadString();

    if (gNewDLLFunctions.pfnCvarValue2)
        gNewDLLFunctions.pfnCvarValue2(cl->edict, requestID, cvarName, value);

    Con_DPrintf("Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\n", cl->name, requestID, cvarName, value);
}
typedef struct{
void            (*pfnOnFreeEntPrivateData)(edict_t *pEnt);
void            (*pfnGameShutdown)(void);
int             (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther );
void            (*pfnCvarValue)( const edict_t *pEnt, const char *value );
void            (*pfnCvarValue2)( const edict_t *pEnt, int requestID, const char *cvarName, const char *value );
} NEW_DLL_FUNCTIONS;

It's definitely okay to intercept or handle those server.dll interfaces with metamod-p.

hzqst commented 3 years ago

A new plugin called CommunicationDemo.dll has been added into the default plugin list.

//#define svc_director 51

NetworkMessage message( MSG_ONE, 51, pPlayer.edict() );//58=svc_sendcvarvalue2
    message.WriteLong(100);//MetaHookSv
    message.WriteLong(1);//Query plugin list
message.End();

plugins will be reported with server command mh_reportplugin index apiver "name" "version"

CClientCommand g_HelloWorld("mh_reportplugin", "ReportPluginInfo", @ReportPluginInfo);
void ReportPluginInfo(const CCommand@ pArgs) 
{

}
incognico commented 3 years ago

I'm testing with:

void PluginInit() {
  g_Module.ScriptInfo.SetAuthor("incognico");
  g_Module.ScriptInfo.SetContactInfo("https://discord.gg/qfZxWAd");

  g_Hooks.RegisterHook(Hooks::Player::ClientPutInServer, @ClientPutInServer);
}

CClientCommand g_ReportMetahookPlugin("mh_reportplugin", "ReportPluginInfo", @ReportPluginInfo);

HookReturnCode ClientPutInServer( CBasePlayer@ plr ) {
  g_Scheduler.SetTimeout("RequestPlugins", 6.66f, EHandle(plr));

  return HOOK_CONTINUE;
}

void ReportPluginInfo(const CCommand@ args) {
  CBasePlayer@ plr = g_ConCommandSystem.GetCurrentPlayer();
  g_EngineFuncs.ServerPrint( "[MetaHookPlugins " + plr.pev.netname + "] #" + args[1] + " :: apiver: " + args[2] + " :: name: " + args[3] + " :: ver: " + args[4] + "\n" );
}

void RequestPlugins( EHandle eplr ) {
  if (!eplr) return;

  CBasePlayer@ plr = cast<CBasePlayer@>(eplr.GetEntity());

  NetworkMessage message( MSG_ONE, NetworkMessages::NetworkMessageType(51), plr.edict() ); // 58 = svc_sendcvarvalue2
    message.WriteLong(100); //MetaHookSv
    message.WriteLong(1);   //Query plugin list
  message.End();
}

Using 58 / svc_sendcvarvalue2 results in svc_bad with client disconnect - not working.

Using 51 / svc_director results in other random errors with the client also disconnecting: Seeing weird stuff like Host_Error: CL_Parse_Version: Server is protocol 2015367138 instead of 48 or Host_Error: DispatchUserMsg: User Msg PrtlUpdt/129 sent too much data (28714 bytes), 512 bytes max. Those really look random.

From the client .mh_reportplugin is working (manually).

wootguy commented 3 years ago

Maybe those messages don't work in sven, or have other undocumented arguments? Could use something else like TE_STREAK_SPLASH with no-op args (0 count, 1337 speed) which do nothing but act as a secret message to metahook.

incognico commented 3 years ago

Would a simple detection work via a custom setinfo? MH client could do setinfo _metahook <version>, that should theoretically be possible to read from the server via KeyValueBuffer::GetValue(). It won't report single plugins but it would be a way to detect MetaHook.

hzqst commented 3 years ago

Would a simple detection work via a custom setinfo? MH client could do setinfo _metahook <version>, that should theoretically be possible to read from the server via KeyValueBuffer::GetValue(). It won't report single plugins but it would be a way to detect MetaHook.

setinfo has a limitation of 512 bytes (or 256?), and it leaves key there even when metahook is completed removed.

hzqst commented 3 years ago

Maybe those messages don't work in sven, or have other undocumented arguments? Could use something else like TE_STREAK_SPLASH with no-op args (0 count, 1337 speed) which do nothing but act as a secret message to metahook.

svc_sendcvarvalue and svc_sendcvarvalue2 are still there in Sven image image image

This is my test result :

image

image

Test plugin : https://github.com/hzqst/metamod-fallguys/tree/main/querycvar Hook : https://github.com/hzqst/metamod-fallguys/blob/94e77ebb3e3c12cadafb834b2c2d908b00e012d4/querycvar/dllapi.cpp#L119

hzqst commented 3 years ago

The svc_director seems to be removed from SvEngine

image

svc_hltv too

image

You can register your own usermsg :

HookReturnCode ClientPutInServer(CBasePlayer@ pPlayer)
{
    //svc_newusermsg = 39
    NetworkMessage message( MSG_ONE, NetworkMessages::NetworkMessageType(39), pPlayer.edict() );
    message.WriteByte(146);//64 ~ 145 = SelAmmo ~ VModelPos, all of them are reserved or used by Sven Co-op
    message.WriteByte(255);//255 = variable length
    message.WriteLong(0x6174654D);//`ateM`
    message.WriteLong(0x6B6F6F48);//`kooH`
    message.WriteLong(0);
    message.WriteLong(0);
    message.End();
    return HOOK_CONTINUE;
}

8JXQEZAZF~G0HVK%HF5%5N2

image

image

Players connected without usermsg MetaHook registered will do nothing more than reporting UserMsg: No pfn MetaHook 146 to the console (only when developer is non-zero).

image

incognico commented 3 years ago

Working now. Demo: https://github.com/incognico/svencoop-plugins/blob/master/MetaHook.as and https://github.com/incognico/svencoop-plugins/blob/master/inc/MetaChads.as