Skip to content

Commit

Permalink
Block disconnect messages of reconnecting clients
Browse files Browse the repository at this point in the history
Specifically "loop shutdown"
  • Loading branch information
xen-000 committed Feb 3, 2025
1 parent 126dd36 commit 3a23438
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 7 deletions.
11 changes: 9 additions & 2 deletions AMBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ for sdk_target in MMSPlugin.sdk_targets:

binary = MMSPlugin.HL2Library(builder, cxx, MMSPlugin.plugin_name, sdk)

version = subprocess.check_output(['git', 'describe', '--tags', '--long']).decode('ascii').strip()
try:
version = subprocess.check_output(['git', 'describe', '--tags', '--long']).decode('ascii').strip()
except subprocess.SubprocessError as e:
version = "1.3-dev"
print("git describe failed as there are no tags")

print(f'Setting version to "{version}"')
binary.compiler.defines += ['MULTIADDONMANAGER_VERSION="%s"'%(version)]

target_folder = 'Debug' if builder.options.debug else 'Release'
Expand Down Expand Up @@ -49,7 +55,8 @@ for sdk_target in MMSPlugin.sdk_targets:

protoc_builder = builder.tools.Protoc(protoc = sdk_target.protoc, sources = [
os.path.join(sdk['path'], 'common', 'network_connection.proto'),
os.path.join(sdk['path'], 'common', 'networkbasetypes.proto')
os.path.join(sdk['path'], 'common', 'networkbasetypes.proto'),
os.path.join(sdk['path'], 'game', 'shared', 'gameevents.proto')
])
protoc_builder.protoc.includes += [
os.path.join(sdk['path'], 'common')
Expand Down
1 change: 1 addition & 0 deletions cfg/multiaddonmanager/multiaddonmanager.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mm_extra_addons "" // The workshop IDs of extra addons, separated by commas
mm_extra_addons_timeout 10 // How long until clients are timed out in between connects for extra addons, requires mm_extra_addons to be used
mm_addon_mount_download 0 // Whether to download an addon upon mounting even if it's installed
mm_cache_clients_with_addons 0 // Whether to cache clients who downloaded all addons, this will prevent reconnects on mapchange/rejoin
mm_block_disconnect_messages 0 // Whether to block "loop shutdown" disconnect messages
65 changes: 60 additions & 5 deletions src/multiaddonmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
*/

#include "networkbasetypes.pb.h"
#include "gameevents.pb.h"

#include <stdio.h>
#include "multiaddonmanager.h"
#include "module.h"
#include "utils/plat.h"
#include "networksystem/inetworkserializer.h"
#include "networksystem/inetworkmessages.h"
#include "igameeventsystem.h"
#include "serversideclient.h"
#include "funchook.h"
#include "filesystem.h"
Expand All @@ -43,7 +45,7 @@ void Message(const char *msg, ...)
char buf[1024] = {};
V_vsnprintf(buf, sizeof(buf) - 1, msg, args);

ConColorMsg(Color(0, 255, 200), "[MultiAddonManager] %s", buf);
LoggingSystem_Log(0, LS_MESSAGE, Color(0, 255, 200), "[MultiAddonManager] %s", buf);

va_end(args);
}
Expand Down Expand Up @@ -104,21 +106,24 @@ HostStateRequest_t g_pfnHostStateRequest = nullptr;
funchook_t *g_pSendNetMessageHook = nullptr;
funchook_t *g_pHostStateRequestHook = nullptr;

int g_iSendNetMessage;

class GameSessionConfiguration_t { };

SH_DECL_HOOK0_void(IServerGameDLL, GameServerSteamAPIActivated, SH_NOATTRIB, 0);
SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t &, ISource2WorldSession *, const char *);
SH_DECL_HOOK6(IServerGameClients, ClientConnect, SH_NOATTRIB, 0, bool, CPlayerSlot, const char*, uint64, const char *, bool, CBufferString *);
SH_DECL_HOOK3_void(IServerGameDLL, GameFrame, SH_NOATTRIB, 0, bool, bool, bool);
SH_DECL_HOOK8_void(IGameEventSystem, PostEventAbstract, SH_NOATTRIB, 0, CSplitScreenSlot, bool, int, const uint64 *,
INetworkMessageInternal *, const CNetMessage *, unsigned long, NetChannelBufType_t);
SH_DECL_HOOK2(IGameEventManager2, LoadEventsFromFile, SH_NOATTRIB, 0, int, const char *, bool);

#ifdef PLATFORM_WINDOWS
constexpr int g_iSendNetMessageOffset = 15;
#else
constexpr int g_iSendNetMessageOffset = 16;
#endif

int g_iLoadEventsFromFileId = -1;

struct ClientJoinInfo_t
{
uint64 steamid;
Expand All @@ -127,12 +132,14 @@ struct ClientJoinInfo_t
};

CUtlVector<ClientJoinInfo_t> g_ClientsPendingAddon; // List of clients who are still downloading addons
std::set<uint64> g_ClientsWithAddons; // List of clients who already downloaded everything so they don't get reconnects on mapchange/rejoin
std::unordered_set<uint64> g_ClientsWithAddons; // List of clients who already downloaded everything so they don't get reconnects on mapchange/rejoin

MultiAddonManager g_MultiAddonManager;
INetworkGameServer *g_pNetworkGameServer = nullptr;
CSteamGameServerAPIContext g_SteamAPI;
CGlobalVars *gpGlobals = nullptr;
IGameEventSystem *g_pGameEventSystem = nullptr;
IGameEventManager2 *g_pGameEventManager = nullptr;

// Interface to other plugins
CAddonManagerInterface g_AddonManagerInterface;
Expand All @@ -148,12 +155,14 @@ bool MultiAddonManager::Load(PluginId id, ISmmAPI *ismm, char *error, size_t max
GET_V_IFACE_ANY(GetServerFactory, g_pSource2Server, ISource2Server, SOURCE2SERVER_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkServerService, INetworkServerService, NETWORKSERVERSERVICE_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkMessages, INetworkMessages, NETWORKMESSAGES_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pGameEventSystem, IGameEventSystem, GAMEEVENTSYSTEM_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetFileSystemFactory, g_pFullFileSystem, IFileSystem, FILESYSTEM_INTERFACE_VERSION);

// Required to get the IMetamodListener events
g_SMAPI->AddListener( this, this );

CModule engineModule(ROOTBIN, "engine2");
CModule serverModule(GAMEBIN, "server");

// "Discarding pending request '%s, %u'\n"
#ifdef PLATFORM_WINDOWS
Expand Down Expand Up @@ -181,6 +190,7 @@ bool MultiAddonManager::Load(PluginId id, ISmmAPI *ismm, char *error, size_t max
funchook_prepare(g_pHostStateRequestHook, (void**)&g_pfnHostStateRequest, (void*)Hook_HostStateRequest);
funchook_install(g_pHostStateRequestHook, 0);

// We're using funchook even though it's a virtual function because it can be called on a different thread and SourceHook isn't thread-safe
void **pServerSideClientVTable = (void **)engineModule.FindVirtualTable("CServerSideClient");
g_pfnSendNetMessage = (SendNetMessage_t)pServerSideClientVTable[g_iSendNetMessageOffset];

Expand All @@ -192,6 +202,14 @@ bool MultiAddonManager::Load(PluginId id, ISmmAPI *ismm, char *error, size_t max
SH_ADD_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &MultiAddonManager::Hook_StartupServer), true);
SH_ADD_HOOK(IServerGameClients, ClientConnect, g_pSource2GameClients, SH_MEMBER(this, &MultiAddonManager::Hook_ClientConnect), false);
SH_ADD_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &MultiAddonManager::Hook_GameFrame), true);
SH_ADD_HOOK(IGameEventSystem, PostEventAbstract, g_pGameEventSystem, SH_MEMBER(this, &MultiAddonManager::Hook_PostEvent), false);

auto pCGameEventManagerVTable = (IGameEventManager2*)serverModule.FindVirtualTable("CGameEventManager");

if (!pCGameEventManagerVTable)
return false;

g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable, SH_MEMBER(this, &MultiAddonManager::Hook_LoadEventsFromFile), false);

if (late)
{
Expand All @@ -218,7 +236,8 @@ bool MultiAddonManager::Unload(char *error, size_t maxlen)
SH_REMOVE_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &MultiAddonManager::Hook_StartupServer), true);
SH_REMOVE_HOOK(IServerGameClients, ClientConnect, g_pSource2GameClients, SH_MEMBER(this, &MultiAddonManager::Hook_ClientConnect), false);
SH_REMOVE_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &MultiAddonManager::Hook_GameFrame), true);
SH_REMOVE_HOOK_ID(g_iSendNetMessage);
SH_REMOVE_HOOK(IGameEventSystem, PostEventAbstract, g_pGameEventSystem, SH_MEMBER(this, &MultiAddonManager::Hook_PostEvent), false);
SH_REMOVE_HOOK_ID(g_iLoadEventsFromFileId);

if (g_pHostStateRequestHook)
{
Expand Down Expand Up @@ -791,6 +810,42 @@ void MultiAddonManager::Hook_GameFrame(bool simulating, bool bFirstTick, bool bL
}
}

bool g_bBlockDisconnectMsgs = false;
FAKE_BOOL_CVAR(mm_block_disconnect_messages, "Whether to block \"loop shutdown\" disconnect messages", g_bBlockDisconnectMsgs, false, false);

void MultiAddonManager::Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64 *clients,
INetworkMessageInternal *pEvent, const CNetMessage *pData, unsigned long nSize, NetChannelBufType_t bufType)
{
NetMessageInfo_t *info = pEvent->GetNetMessageInfo();

if (g_bBlockDisconnectMsgs && info->m_MessageId == GE_Source1LegacyGameEvent)
{
auto pMsg = pData->ToPB<CMsgSource1LegacyGameEvent>();

static int sDisconnectId = g_pGameEventManager->LookupEventId("player_disconnect");

if (pMsg->eventid() == sDisconnectId)
{
IGameEvent *pEvent = g_pGameEventManager->UnserializeEvent(*pMsg);

// This will prevent "loop shutdown" messages in the chat when clients reconnect
// As far as we're aware, there are no other cases where this reason is used
if (pEvent->GetInt("reason") == NETWORK_DISCONNECT_LOOPSHUTDOWN)
*(uint64*)clients = 0;
}
}

RETURN_META(MRES_IGNORED);
}

int MultiAddonManager::Hook_LoadEventsFromFile(const char *filename, bool bSearchAll)
{
if (!g_pGameEventManager)
g_pGameEventManager = META_IFACEPTR(IGameEventManager2);

RETURN_META_VALUE(MRES_IGNORED, 0);
}

const char *MultiAddonManager::GetLicense()
{
return "GPL v3 License";
Expand Down
3 changes: 3 additions & 0 deletions src/multiaddonmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class MultiAddonManager : public ISmmPlugin, public IMetamodListener
void Hook_StartupServer(const GameSessionConfiguration_t &config, ISource2WorldSession *, const char *);
bool Hook_ClientConnect(CPlayerSlot slot, const char *pszName, uint64 xuid, const char *pszNetworkID, bool unk1, CBufferString *pRejectReason);
void Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick);
void Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64 *clients,
INetworkMessageInternal *pEvent, const CNetMessage *pData, unsigned long nSize, NetChannelBufType_t bufType);
int Hook_LoadEventsFromFile(const char *filename, bool bSearchAll);

void BuildAddonPath(const char *pszAddon, char *buf, size_t len);
bool MountAddon(const char *pszAddon, bool bAddToTail);
Expand Down

0 comments on commit 3a23438

Please sign in to comment.