Skip to content

Commit 3a23438

Browse files
committed
Block disconnect messages of reconnecting clients
Specifically "loop shutdown"
1 parent 126dd36 commit 3a23438

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

AMBuilder

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ for sdk_target in MMSPlugin.sdk_targets:
1616

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

19-
version = subprocess.check_output(['git', 'describe', '--tags', '--long']).decode('ascii').strip()
19+
try:
20+
version = subprocess.check_output(['git', 'describe', '--tags', '--long']).decode('ascii').strip()
21+
except subprocess.SubprocessError as e:
22+
version = "1.3-dev"
23+
print("git describe failed as there are no tags")
24+
25+
print(f'Setting version to "{version}"')
2026
binary.compiler.defines += ['MULTIADDONMANAGER_VERSION="%s"'%(version)]
2127

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

5056
protoc_builder = builder.tools.Protoc(protoc = sdk_target.protoc, sources = [
5157
os.path.join(sdk['path'], 'common', 'network_connection.proto'),
52-
os.path.join(sdk['path'], 'common', 'networkbasetypes.proto')
58+
os.path.join(sdk['path'], 'common', 'networkbasetypes.proto'),
59+
os.path.join(sdk['path'], 'game', 'shared', 'gameevents.proto')
5360
])
5461
protoc_builder.protoc.includes += [
5562
os.path.join(sdk['path'], 'common')

cfg/multiaddonmanager/multiaddonmanager.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ mm_extra_addons "" // The workshop IDs of extra addons, separated by commas
33
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
44
mm_addon_mount_download 0 // Whether to download an addon upon mounting even if it's installed
55
mm_cache_clients_with_addons 0 // Whether to cache clients who downloaded all addons, this will prevent reconnects on mapchange/rejoin
6+
mm_block_disconnect_messages 0 // Whether to block "loop shutdown" disconnect messages

src/multiaddonmanager.cpp

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
*/
1919

2020
#include "networkbasetypes.pb.h"
21+
#include "gameevents.pb.h"
2122

2223
#include <stdio.h>
2324
#include "multiaddonmanager.h"
2425
#include "module.h"
2526
#include "utils/plat.h"
2627
#include "networksystem/inetworkserializer.h"
2728
#include "networksystem/inetworkmessages.h"
29+
#include "igameeventsystem.h"
2830
#include "serversideclient.h"
2931
#include "funchook.h"
3032
#include "filesystem.h"
@@ -43,7 +45,7 @@ void Message(const char *msg, ...)
4345
char buf[1024] = {};
4446
V_vsnprintf(buf, sizeof(buf) - 1, msg, args);
4547

46-
ConColorMsg(Color(0, 255, 200), "[MultiAddonManager] %s", buf);
48+
LoggingSystem_Log(0, LS_MESSAGE, Color(0, 255, 200), "[MultiAddonManager] %s", buf);
4749

4850
va_end(args);
4951
}
@@ -104,21 +106,24 @@ HostStateRequest_t g_pfnHostStateRequest = nullptr;
104106
funchook_t *g_pSendNetMessageHook = nullptr;
105107
funchook_t *g_pHostStateRequestHook = nullptr;
106108

107-
int g_iSendNetMessage;
108-
109109
class GameSessionConfiguration_t { };
110110

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

116119
#ifdef PLATFORM_WINDOWS
117120
constexpr int g_iSendNetMessageOffset = 15;
118121
#else
119122
constexpr int g_iSendNetMessageOffset = 16;
120123
#endif
121124

125+
int g_iLoadEventsFromFileId = -1;
126+
122127
struct ClientJoinInfo_t
123128
{
124129
uint64 steamid;
@@ -127,12 +132,14 @@ struct ClientJoinInfo_t
127132
};
128133

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

132137
MultiAddonManager g_MultiAddonManager;
133138
INetworkGameServer *g_pNetworkGameServer = nullptr;
134139
CSteamGameServerAPIContext g_SteamAPI;
135140
CGlobalVars *gpGlobals = nullptr;
141+
IGameEventSystem *g_pGameEventSystem = nullptr;
142+
IGameEventManager2 *g_pGameEventManager = nullptr;
136143

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

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

156164
CModule engineModule(ROOTBIN, "engine2");
165+
CModule serverModule(GAMEBIN, "server");
157166

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

193+
// 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
184194
void **pServerSideClientVTable = (void **)engineModule.FindVirtualTable("CServerSideClient");
185195
g_pfnSendNetMessage = (SendNetMessage_t)pServerSideClientVTable[g_iSendNetMessageOffset];
186196

@@ -192,6 +202,14 @@ bool MultiAddonManager::Load(PluginId id, ISmmAPI *ismm, char *error, size_t max
192202
SH_ADD_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &MultiAddonManager::Hook_StartupServer), true);
193203
SH_ADD_HOOK(IServerGameClients, ClientConnect, g_pSource2GameClients, SH_MEMBER(this, &MultiAddonManager::Hook_ClientConnect), false);
194204
SH_ADD_HOOK(IServerGameDLL, GameFrame, g_pSource2Server, SH_MEMBER(this, &MultiAddonManager::Hook_GameFrame), true);
205+
SH_ADD_HOOK(IGameEventSystem, PostEventAbstract, g_pGameEventSystem, SH_MEMBER(this, &MultiAddonManager::Hook_PostEvent), false);
206+
207+
auto pCGameEventManagerVTable = (IGameEventManager2*)serverModule.FindVirtualTable("CGameEventManager");
208+
209+
if (!pCGameEventManagerVTable)
210+
return false;
211+
212+
g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable, SH_MEMBER(this, &MultiAddonManager::Hook_LoadEventsFromFile), false);
195213

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

223242
if (g_pHostStateRequestHook)
224243
{
@@ -791,6 +810,42 @@ void MultiAddonManager::Hook_GameFrame(bool simulating, bool bFirstTick, bool bL
791810
}
792811
}
793812

813+
bool g_bBlockDisconnectMsgs = false;
814+
FAKE_BOOL_CVAR(mm_block_disconnect_messages, "Whether to block \"loop shutdown\" disconnect messages", g_bBlockDisconnectMsgs, false, false);
815+
816+
void MultiAddonManager::Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64 *clients,
817+
INetworkMessageInternal *pEvent, const CNetMessage *pData, unsigned long nSize, NetChannelBufType_t bufType)
818+
{
819+
NetMessageInfo_t *info = pEvent->GetNetMessageInfo();
820+
821+
if (g_bBlockDisconnectMsgs && info->m_MessageId == GE_Source1LegacyGameEvent)
822+
{
823+
auto pMsg = pData->ToPB<CMsgSource1LegacyGameEvent>();
824+
825+
static int sDisconnectId = g_pGameEventManager->LookupEventId("player_disconnect");
826+
827+
if (pMsg->eventid() == sDisconnectId)
828+
{
829+
IGameEvent *pEvent = g_pGameEventManager->UnserializeEvent(*pMsg);
830+
831+
// This will prevent "loop shutdown" messages in the chat when clients reconnect
832+
// As far as we're aware, there are no other cases where this reason is used
833+
if (pEvent->GetInt("reason") == NETWORK_DISCONNECT_LOOPSHUTDOWN)
834+
*(uint64*)clients = 0;
835+
}
836+
}
837+
838+
RETURN_META(MRES_IGNORED);
839+
}
840+
841+
int MultiAddonManager::Hook_LoadEventsFromFile(const char *filename, bool bSearchAll)
842+
{
843+
if (!g_pGameEventManager)
844+
g_pGameEventManager = META_IFACEPTR(IGameEventManager2);
845+
846+
RETURN_META_VALUE(MRES_IGNORED, 0);
847+
}
848+
794849
const char *MultiAddonManager::GetLicense()
795850
{
796851
return "GPL v3 License";

src/multiaddonmanager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ class MultiAddonManager : public ISmmPlugin, public IMetamodListener
7878
void Hook_StartupServer(const GameSessionConfiguration_t &config, ISource2WorldSession *, const char *);
7979
bool Hook_ClientConnect(CPlayerSlot slot, const char *pszName, uint64 xuid, const char *pszNetworkID, bool unk1, CBufferString *pRejectReason);
8080
void Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick);
81+
void Hook_PostEvent(CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64 *clients,
82+
INetworkMessageInternal *pEvent, const CNetMessage *pData, unsigned long nSize, NetChannelBufType_t bufType);
83+
int Hook_LoadEventsFromFile(const char *filename, bool bSearchAll);
8184

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

0 commit comments

Comments
 (0)