From 412f6d37d852cbbe7d5528a2c65f0ba636756903 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 16:43:22 +0800 Subject: [PATCH 01/10] fix: Resolve crash issue caused by duplicate CPlayer initialization --- src/server/players/manager.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/server/players/manager.cpp b/src/server/players/manager.cpp index f88c4b68d..5442edb03 100644 --- a/src/server/players/manager.cpp +++ b/src/server/players/manager.cpp @@ -275,12 +275,21 @@ bool ClientConnectHook(void* _this, CPlayerSlot slot, const char* pszName, uint6 static auto playermanager = g_ifaceService.FetchInterface(PLAYERMANAGER_INTERFACE_VERSION); auto playerid = slot.Get(); auto player = playermanager->RegisterPlayer(playerid); - player->Initialize(playerid); + // player->Initialize(playerid); + if (!player) + { + return false; + } + player->SetUnauthorizedSteamID(xuid); if (g_pOnClientConnectCallback) + { if (reinterpret_cast(g_pOnClientConnectCallback)(playerid) == false) + { return false; + } + } return reinterpret_cast(g_pClientConnectHook->GetOriginal())(_this, slot, pszName, xuid, pszNetworkID, unk1, pRejectReason); } @@ -291,8 +300,8 @@ void OnClientConnectedHook(void* _this, CPlayerSlot slot, const char* pszName, u auto playerid = slot.Get(); if (bFakePlayer) { - auto player = playermanager->RegisterPlayer(playerid); - player->Initialize(playerid); + playermanager->RegisterPlayer(playerid); + // player->Initialize(playerid); } else { From aff0b5aa0054938d985b5496307353fdff4c9ccc Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 16:46:33 +0800 Subject: [PATCH 02/10] fix: Add null check for invalid player in CheckTransmitHook --- src/server/players/manager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/players/manager.cpp b/src/server/players/manager.cpp index 5442edb03..7011b2e0e 100644 --- a/src/server/players/manager.cpp +++ b/src/server/players/manager.cpp @@ -210,8 +210,14 @@ void CheckTransmitHook(void* _this, CCheckTransmitInfo** ppInfoList, int infoCou auto& pInfo = ppInfoList[i]; int playerid = pInfo->m_nPlayerSlot.Get(); if (!playermanager->IsPlayerOnline(playerid)) + { continue; + } auto player = playermanager->GetPlayer(playerid); + if (!player) + { + continue; + } auto& blockedBits = player->GetBlockedTransmittingBits(); From 500ab4026576c21e679ec1faa992adc6dc4ffc30 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 17:34:27 +0800 Subject: [PATCH 03/10] chore: Add async submenu loading test code --- managed/src/TestPlugin/TestPlugin.cs | 39 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index d29722c08..f289708cf 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -36,6 +36,7 @@ using System.Collections.Concurrent; using Dia2Lib; using System.Reflection.Metadata; +using Microsoft.Diagnostics.Tracing.Parsers.MicrosoftWindowsTCPIP; namespace TestPlugin; @@ -645,16 +646,42 @@ public void TestMenuCommand( ICommandContext context ) .AddOption(new ButtonMenuOption("Cancel") { CloseAfterClick = true }) .Build(); - var menu = Core.MenusAPI + var shopMenu = Core.MenusAPI .CreateBuilder() .Design.SetMenuTitle("Shop Menu") - .AddOption(new SubmenuMenuOption("Item 1", confirmMenu)) - .AddOption(new SubmenuMenuOption("Item 2", confirmMenu)) - .AddOption(new SubmenuMenuOption("Item 3", confirmMenu)) - .AddOption(new SubmenuMenuOption("Item 4", confirmMenu)) + .AddOption(new SubmenuMenuOption("Item 1", async () => + { + await Task.Delay(100); + return confirmMenu; + })) + .AddOption(new SubmenuMenuOption("Item 2", async () => + { + await Task.Delay(100); + return confirmMenu; + })) + .AddOption(new SubmenuMenuOption("Item 3", async () => + { + await Task.Delay(100); + return confirmMenu; + })) + .AddOption(new SubmenuMenuOption("Item 4", async () => + { + await Task.Delay(100); + return confirmMenu; + })) + .Build(); + + var mainMenu = Core.MenusAPI + .CreateBuilder() + .Design.SetMenuTitle("Menu") + .AddOption(new SubmenuMenuOption("Shop", async () => + { + await Task.Delay(100); + return shopMenu; + })) .Build(); - Core.MenusAPI.OpenMenu(menu, ( player, menu ) => + Core.MenusAPI.OpenMenu(mainMenu, ( player, menu ) => { Console.WriteLine($"{menu.Configuration.Title} closed for player: {player.Controller.PlayerName}"); }); From 6b61ed9b067ad8c115576b8506ca55dada230c6e Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 18:16:47 +0800 Subject: [PATCH 04/10] fix: Resolve menu cycling issue caused by failure to distinguish close triggers --- .../Modules/Menus/MenuManagerAPI.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs index 1e61e5006..ee8eacdab 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs @@ -162,7 +162,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) } else if (exitKey.HasFlag(@event.Key.ToKeyBind())) { - CloseMenuForPlayer(player, menu); + CloseMenuForPlayerInternal(player, menu, true); if (menu.Configuration.PlaySound) { @@ -213,7 +213,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) } else if (KeyBind.A.HasFlag(@event.Key.ToKeyBind())) { - CloseMenuForPlayer(player, menu); + CloseMenuForPlayerInternal(player, menu, true); if (menu.Configuration.PlaySound) { exitSound.Recipients.AddRecipient(@event.PlayerId); @@ -247,7 +247,7 @@ private void OnClientDisconnected( IOnClientDisconnectedEvent @event ) openMenus .Where(kvp => kvp.Key == player) .ToList() - .ForEach(kvp => CloseMenuForPlayer(player, kvp.Value)); + .ForEach(kvp => CloseMenuForPlayerInternal(player, kvp.Value, true)); } } @@ -322,7 +322,7 @@ public void OpenMenuForPlayer( IPlayer player, IMenuAPI menu ) if (menu.Parent.ParentMenu == currentMenu) { // We are transitioning from the current menu to one of its submenus. - // To show the submenu, we first need to close the current (parent) menu, see CloseMenuForPlayer. + // To show the submenu, we first need to close the current (parent) menu. // The parent menu may have an onClosed callback registered in onClosedCallbacks. // If we do not remove that callback temporarily, closing the parent menu here // would incorrectly invoke the callback even though the user is only navigating @@ -333,13 +333,9 @@ public void OpenMenuForPlayer( IPlayer player, IMenuAPI menu ) // 3. Re-register the callback so it will only be invoked later, when the // logical end of the menu flow is reached and the menu is truly closed. _ = onClosedCallbacks.TryRemove((player, currentMenu), out var callback); - CloseMenuForPlayer(player, currentMenu); + CloseMenuForPlayerInternal(player, currentMenu, false); _ = onClosedCallbacks.AddOrUpdate((player, currentMenu), callback, ( _, _ ) => callback); } - else - { - CloseMenuForPlayer(player, currentMenu); - } } _ = openMenus.AddOrUpdate(player, menu, ( _, _ ) => menu); @@ -358,15 +354,36 @@ public void CloseMenu( IMenuAPI menu ) Core.PlayerManager .GetAllPlayers() .ToList() - .ForEach(player => CloseMenuForPlayer(player, menu)); + .ForEach(player => CloseMenuForPlayerInternal(player, menu, true)); } public void CloseMenuForPlayer( IPlayer player, IMenuAPI menu ) + { + CloseMenuForPlayerInternal(player, menu, true); + } + + public void CloseAllMenus() + { + openMenus.ToList().ForEach(kvp => + { + var currentMenu = kvp.Value; + while (currentMenu != null) + { + currentMenu.HideForPlayer(kvp.Key); + MenuClosed?.Invoke(this, new MenuManagerEventArgs { Player = kvp.Key, Menu = currentMenu }); + currentMenu = currentMenu.Parent.ParentMenu; + } + _ = openMenus.TryRemove(kvp.Key, out _); + }); + } + + private void CloseMenuForPlayerInternal( IPlayer player, IMenuAPI menu, bool reopenParent ) { if (!openMenus.TryGetValue(player, out var currentMenu) || currentMenu != menu) { return; } + if (onClosedCallbacks.TryRemove((player, menu), out var onClosed) && onClosed != null) { onClosed(player, menu); @@ -377,25 +394,10 @@ public void CloseMenuForPlayer( IPlayer player, IMenuAPI menu ) menu.HideForPlayer(player); MenuClosed?.Invoke(this, new MenuManagerEventArgs { Player = player, Menu = menu }); - if (menu.Parent.ParentMenu != null) + if (reopenParent && menu.Parent.ParentMenu != null) { OpenMenuForPlayer(player, menu.Parent.ParentMenu); } } } - - public void CloseAllMenus() - { - openMenus.ToList().ForEach(kvp => - { - var currentMenu = kvp.Value; - while (currentMenu != null) - { - currentMenu.HideForPlayer(kvp.Key); - MenuClosed?.Invoke(this, new MenuManagerEventArgs { Player = kvp.Key, Menu = currentMenu }); - currentMenu = currentMenu.Parent.ParentMenu; - } - _ = openMenus.TryRemove(kvp.Key, out _); - }); - } } \ No newline at end of file From 8b5d64aacf44df1c3bd98cd715c476d1d9fa66ff Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 18:36:09 +0800 Subject: [PATCH 05/10] fix: Fix TerminateRound --- managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs | 6 +++--- managed/src/TestPlugin/TestPlugin.cs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs index d6ef2a5d6..8491f8658 100644 --- a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs +++ b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs @@ -16,7 +16,7 @@ internal static class GameFunctions public static unsafe delegate* unmanaged< nint, nint, float, void > pSetOrAddAttribute; public static unsafe delegate* unmanaged< int, nint, nint > pGetWeaponCSDataFromKey; public static unsafe delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void > pDispatchParticleEffect; - public static unsafe delegate* unmanaged< nint, uint, float, nint, byte, void > pTerminateRound; + public static unsafe delegate* unmanaged< nint, float, uint, nint, byte, void > pTerminateRound; public static unsafe delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void > pTeleport; public static int TeleportOffset => NativeOffsets.Fetch("CBaseEntity::Teleport"); public static int CommitSuicideOffset => NativeOffsets.Fetch("CBasePlayerPawn::CommitSuicide"); @@ -43,7 +43,7 @@ public static void Initialize() pSetOrAddAttribute = (delegate* unmanaged< nint, IntPtr, float, void >)NativeSignatures.Fetch("CAttributeList::SetOrAddAttributeValueByName"); pGetWeaponCSDataFromKey = (delegate* unmanaged< int, nint, nint >)NativeSignatures.Fetch("GetWeaponCSDataFromKey"); pDispatchParticleEffect = (delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void >)NativeSignatures.Fetch("DispatchParticleEffect"); - pTerminateRound = (delegate* unmanaged< nint, uint, float, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + pTerminateRound = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); pTeleport = (delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void >)((void**)NativeMemoryHelpers.GetVirtualTableAddress("server", "CBaseEntity"))[TeleportOffset]; } } @@ -60,7 +60,7 @@ public static void TerminateRound( nint gameRules, uint reason, float delay ) { unsafe { - pTerminateRound(gameRules, reason, delay, 0, 0); + pTerminateRound(gameRules, delay, reason, 0, 0); } } catch (Exception e) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index f289708cf..a80b93994 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -621,6 +621,12 @@ public void GetIpCommand( ICommandContext context ) // Core.Menus.OpenMenu(player, settingsMenu); // } + [Command("ed")] + public void EndRoundCommand( ICommandContext _ ) + { + var gameRules = Core.EntitySystem.GetGameRules()!; + gameRules.TerminateRound(RoundEndReason.CTsWin, 10.0f); + } [Command("tm")] public void TestMenuCommand( ICommandContext context ) From e5b1f5c39ca23adea90056ff50fe76b9acda7957 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 19:08:33 +0800 Subject: [PATCH 06/10] fix: Resolve platform differences for TerminateRound --- .../SwiftlyS2.Core/Natives/GameFunctions.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs index 8491f8658..18f99c9dd 100644 --- a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs +++ b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs @@ -16,7 +16,8 @@ internal static class GameFunctions public static unsafe delegate* unmanaged< nint, nint, float, void > pSetOrAddAttribute; public static unsafe delegate* unmanaged< int, nint, nint > pGetWeaponCSDataFromKey; public static unsafe delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void > pDispatchParticleEffect; - public static unsafe delegate* unmanaged< nint, float, uint, nint, byte, void > pTerminateRound; + public static unsafe delegate* unmanaged< nint, uint, float, nint, byte, void > pTerminateRoundLinux; + public static unsafe delegate* unmanaged< nint, float, uint, nint, byte, void > pTerminateRoundWindows; public static unsafe delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void > pTeleport; public static int TeleportOffset => NativeOffsets.Fetch("CBaseEntity::Teleport"); public static int CommitSuicideOffset => NativeOffsets.Fetch("CBasePlayerPawn::CommitSuicide"); @@ -43,7 +44,15 @@ public static void Initialize() pSetOrAddAttribute = (delegate* unmanaged< nint, IntPtr, float, void >)NativeSignatures.Fetch("CAttributeList::SetOrAddAttributeValueByName"); pGetWeaponCSDataFromKey = (delegate* unmanaged< int, nint, nint >)NativeSignatures.Fetch("GetWeaponCSDataFromKey"); pDispatchParticleEffect = (delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void >)NativeSignatures.Fetch("DispatchParticleEffect"); - pTerminateRound = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + { + pTerminateRoundLinux = (delegate* unmanaged< nint, uint, float, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + } + else + { + pTerminateRoundWindows = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + } + pTeleport = (delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void >)((void**)NativeMemoryHelpers.GetVirtualTableAddress("server", "CBaseEntity"))[TeleportOffset]; } } @@ -60,7 +69,14 @@ public static void TerminateRound( nint gameRules, uint reason, float delay ) { unsafe { - pTerminateRound(gameRules, delay, reason, 0, 0); + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + { + pTerminateRoundLinux(gameRules, reason, delay, 0, 0); + } + else + { + pTerminateRoundWindows(gameRules, delay, reason, 0, 0); + } } } catch (Exception e) From eb9ba0d2cd48fda9a15bdd1dfe40dc9eb4c9d319 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 19:13:50 +0800 Subject: [PATCH 07/10] chore: Avoid unnecessary overhead --- .../src/SwiftlyS2.Core/Natives/GameFunctions.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs index 18f99c9dd..c975c2f01 100644 --- a/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs +++ b/managed/src/SwiftlyS2.Core/Natives/GameFunctions.cs @@ -2,12 +2,12 @@ using System.Text; using Spectre.Console; using SwiftlyS2.Shared.Natives; -using SwiftlyS2.Shared.SchemaDefinitions; namespace SwiftlyS2.Core.Natives; internal static class GameFunctions { + private static readonly bool IsWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); public static unsafe delegate* unmanaged< CTakeDamageInfo*, nint, nint, nint, Vector*, Vector*, float, int, int, void*, void > pCTakeDamageInfo_Constructor; public static unsafe delegate* unmanaged< nint, Ray_t*, Vector, Vector, CTraceFilter*, CGameTrace*, void > pTraceShape; public static unsafe delegate* unmanaged< Vector, Vector, BBox_t, CTraceFilter*, CGameTrace*, void > pTracePlayerBBox; @@ -44,15 +44,14 @@ public static void Initialize() pSetOrAddAttribute = (delegate* unmanaged< nint, IntPtr, float, void >)NativeSignatures.Fetch("CAttributeList::SetOrAddAttributeValueByName"); pGetWeaponCSDataFromKey = (delegate* unmanaged< int, nint, nint >)NativeSignatures.Fetch("GetWeaponCSDataFromKey"); pDispatchParticleEffect = (delegate* unmanaged< nint, uint, nint, byte, CUtlSymbolLarge, byte, int, nint, nint, void >)NativeSignatures.Fetch("DispatchParticleEffect"); - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + if (IsWindows) { - pTerminateRoundLinux = (delegate* unmanaged< nint, uint, float, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + pTerminateRoundWindows = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); } else { - pTerminateRoundWindows = (delegate* unmanaged< nint, float, uint, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); + pTerminateRoundLinux = (delegate* unmanaged< nint, uint, float, nint, byte, void >)NativeSignatures.Fetch("CGameRules::TerminateRound"); } - pTeleport = (delegate* unmanaged< nint, Vector*, QAngle*, Vector*, void >)((void**)NativeMemoryHelpers.GetVirtualTableAddress("server", "CBaseEntity"))[TeleportOffset]; } } @@ -69,13 +68,13 @@ public static void TerminateRound( nint gameRules, uint reason, float delay ) { unsafe { - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) + if (IsWindows) { - pTerminateRoundLinux(gameRules, reason, delay, 0, 0); + pTerminateRoundWindows(gameRules, delay, reason, 0, 0); } else { - pTerminateRoundWindows(gameRules, delay, reason, 0, 0); + pTerminateRoundLinux(gameRules, reason, delay, 0, 0); } } } From f4e753f8fd2d9e9295f431909726c2d43579878c Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 23:20:53 +0800 Subject: [PATCH 08/10] chore: Add defensive programming checks across all player methods --- src/server/players/player.cpp | 168 +++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 44 deletions(-) diff --git a/src/server/players/player.cpp b/src/server/players/player.cpp index 80361cbfc..bc8307ee6 100644 --- a/src/server/players/player.cpp +++ b/src/server/players/player.cpp @@ -18,19 +18,19 @@ #include "player.h" -#include #include +#include -#include #include +#include -#include #include +#include #include "usermessages.pb.h" #define CBaseEntity_m_iTeamNum 0x9DC483B8A5BFEFB3 -#define CBaseEntity_m_fFlags 0x9DC483B8A4A37590 +#define CBaseEntity_m_fFlags 0x9DC483B8A4A37590 #define CBasePlayerController_m_hPawn 0x3979FF6E7C628C1D #define CCSPlayerController_m_hPlayerPawn 0x28ECD7A1D6C93E7C @@ -119,8 +119,11 @@ void CPlayer::Shutdown() m_iPlayerId = -1; m_bAuthorized = false; - if (centerMessageEvent) { + if (centerMessageEvent) + { static auto eventmanager = g_ifaceService.FetchInterface(GAMEEVENTMANAGER_INTERFACE_VERSION); + if (!eventmanager) + return; eventmanager->GetGameEventManager()->FreeEvent(centerMessageEvent); centerMessageEvent = nullptr; } @@ -130,39 +133,55 @@ extern INetworkMessages* networkMessages; void CPlayer::SendMsg(MessageType type, const std::string& message, int duration = 5000) { - if (IsFakeClient()) return; + if (IsFakeClient()) + return; - if (type == MessageType::CenterHTML) { - if (message == "") centerMessageEndTime = 0; - else { + if (type == MessageType::CenterHTML) + { + if (message == "") + centerMessageEndTime = 0; + else + { centerMessageEndTime = GetTime() + duration; centerMessageText = message; } } - else if (type == MessageType::Console) { - if (message.size() == 0) return; + else if (type == MessageType::Console) + { + if (message.size() == 0) + return; auto msg = ClearColors(message); auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return; engine->ClientPrintf(CPlayerSlot(m_iPlayerId), msg.c_str()); } - else { + else + { auto msg = RemoveHtmlTags(message); - if (msg.size() > 0) { - if (msg.ends_with("\n")) msg.pop_back(); + if (msg.size() > 0) + { + if (msg.ends_with("\n")) + msg.pop_back(); msg += "\x01"; bool startsWithColor = (msg.at(0) == '['); auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); + if (!schema) + return; msg = ProcessColor(message, *(int*)(schema->GetPropPtr(GetController(), CBaseEntity_m_iTeamNum))); - if (startsWithColor) msg = " " + msg; + if (startsWithColor) + msg = " " + msg; } auto gameEventSystem = g_ifaceService.FetchInterface(GAMEEVENTSYSTEM_INTERFACE_VERSION); + if (!gameEventSystem) + return; auto netmsg = networkMessages->FindNetworkMessagePartial("TextMsg"); auto pmsg = netmsg->AllocateMessage()->ToPB(); @@ -186,10 +205,15 @@ bool CPlayer::IsAuthorized() bool CPlayer::IsFakeClient() { auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); - if (!GetController()) return true; + if (!schema) + return true; + + if (!GetController()) + return true; uint32_t* flagsPtr = (uint32_t*)schema->GetPropPtr(GetController(), CBaseEntity_m_fFlags); - if (flagsPtr == nullptr) return true; + if (flagsPtr == nullptr) + return true; return (*flagsPtr & Flags_t::FL_FAKECLIENT); } @@ -211,12 +235,16 @@ void CPlayer::SetUnauthorizedSteamID(uint64_t steamID) uint64_t CPlayer::GetUnauthorizedSteamID() { - if (IsFakeClient()) return 0; + if (IsFakeClient()) + return 0; auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return m_uUnauthorizedSteamID; auto steamid = engine->GetClientSteamID(m_iPlayerId); - if (!steamid) return m_uUnauthorizedSteamID; + if (!steamid) + return m_uUnauthorizedSteamID; return steamid->ConvertToUint64(); } @@ -224,21 +252,30 @@ uint64_t CPlayer::GetUnauthorizedSteamID() uint64_t CPlayer::GetSteamID() { auto config = g_ifaceService.FetchInterface(CONFIGURATION_INTERFACE_VERSION); + if (!config) + return 0; auto s = std::get_if(&config->GetValue("core.SteamAuth.Mode")); - if (m_bAuthorized) { - if (IsFakeClient()) return 0; + if (m_bAuthorized) + { + if (IsFakeClient()) + return 0; auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return 0; auto steamid = engine->GetClientSteamID(m_iPlayerId); - if (!steamid) return 0; + if (!steamid) + return 0; return steamid->ConvertToUint64(); } - else if (*s == "flexible") { + else if (*s == "flexible") + { return GetUnauthorizedSteamID(); } - else return 0; + else + return 0; } extern void* g_pOnClientSteamAuthorizeCallback; @@ -248,13 +285,15 @@ void CPlayer::ChangeAuthorizationState(bool bAuthorized) { m_bAuthorized = bAuthorized; - if (bAuthorized) { + if (bAuthorized) + { if (g_pOnClientSteamAuthorizeCallback) - reinterpret_cast(g_pOnClientSteamAuthorizeCallback)(m_iPlayerId); + reinterpret_cast(g_pOnClientSteamAuthorizeCallback)(m_iPlayerId); } - else { + else + { if (g_pOnClientSteamAuthorizeFailCallback) - reinterpret_cast(g_pOnClientSteamAuthorizeFailCallback)(m_iPlayerId); + reinterpret_cast(g_pOnClientSteamAuthorizeFailCallback)(m_iPlayerId); } } @@ -266,18 +305,30 @@ std::string& CPlayer::GetLanguage() void* CPlayer::GetController() { static auto entsystem = g_ifaceService.FetchInterface(ENTITYSYSTEM_INTERFACE_VERSION); - CEntityInstance* controller = entsystem->GetEntitySystem()->GetEntityInstance(CEntityIndex(m_iPlayerId + 1)); + if (!entsystem) + return nullptr; + + auto entitySystem = entsystem->GetEntitySystem(); + if (!entitySystem) + return nullptr; + + CEntityInstance* controller = entitySystem->GetEntityInstance(CEntityIndex(m_iPlayerId + 1)); return controller; } void* CPlayer::GetPawn() { static auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); + if (!schema) + return nullptr; + auto controller = GetController(); - if (!controller) return nullptr; + if (!controller) + return nullptr; auto pawn = schema->GetPropPtr(controller, CBasePlayerController_m_hPawn); - if (!pawn) return nullptr; + if (!pawn) + return nullptr; CHandle& pawnHandle = *(CHandle*)pawn; return pawnHandle.Get(); @@ -286,11 +337,16 @@ void* CPlayer::GetPawn() void* CPlayer::GetPlayerPawn() { static auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); + if (!schema) + return nullptr; + auto controller = GetController(); - if (!controller) return nullptr; + if (!controller) + return nullptr; auto playerPawn = schema->GetPropPtr(controller, CCSPlayerController_m_hPlayerPawn); - if (!playerPawn) return nullptr; + if (!playerPawn) + return nullptr; CHandle& playerPawnHandle = *(CHandle*)playerPawn; return playerPawnHandle.Get(); @@ -319,13 +375,19 @@ uint64_t& CPlayer::GetPressedButtons() void CPlayer::PerformCommand(const std::string& command) { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return; engine->ClientCommand(CPlayerSlot(m_iPlayerId), command.c_str()); } std::string CPlayer::GetIPAddress() { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return ""; auto pNetChan = engine->GetPlayerNetInfo(m_iPlayerId); + if (!pNetChan) + return ""; return explode(pNetChan->GetAddress(), ":")[0]; } @@ -333,6 +395,8 @@ std::string CPlayer::GetIPAddress() void CPlayer::Kick(const std::string& sReason, int uReason) { auto engine = g_ifaceService.FetchInterface(INTERFACEVERSION_VENGINESERVER); + if (!engine) + return; engine->DisconnectClient(m_iPlayerId, uReason, sReason.c_str()); } @@ -347,7 +411,11 @@ typedef IGameEventListener2* (*GetLegacyGameEventListener)(CPlayerSlot slot); void CPlayer::Think() { static auto gamedata = g_ifaceService.FetchInterface(GAMEDATA_INTERFACE_VERSION); + if (!gamedata) + return; static auto eventmanager = g_ifaceService.FetchInterface(GAMEEVENTMANAGER_INTERFACE_VERSION); + if (!eventmanager) + return; static auto pListenerSig = gamedata->GetSignatures()->Fetch("LegacyGameEventListener"); if (pListenerSig) @@ -355,26 +423,31 @@ void CPlayer::Think() auto listener = reinterpret_cast(pListenerSig)(m_iPlayerId); if (listener) { - if (!centerMessageEvent) centerMessageEvent = eventmanager->GetGameEventManager()->CreateEvent("show_survival_respawn_status"); + if (!centerMessageEvent) + centerMessageEvent = eventmanager->GetGameEventManager()->CreateEvent("show_survival_respawn_status"); if (centerMessageEvent) { - if (centerMenuText != "") { + if (centerMenuText != "") + { centerMessageEvent->SetString("loc_token", centerMenuText.c_str()); centerMessageEvent->SetInt("duration", 1); centerMessageEvent->SetInt("userid", m_iPlayerId); listener->FireGameEvent(centerMessageEvent); } - else { - if (centerMessageEndTime >= GetTime()) { + else + { + if (centerMessageEndTime >= GetTime()) + { centerMessageEvent->SetString("loc_token", centerMessageText.c_str()); centerMessageEvent->SetInt("duration", 1); centerMessageEvent->SetInt("userid", m_iPlayerId); listener->FireGameEvent(centerMessageEvent); } - else { + else + { centerMessageEndTime = 0; } } @@ -385,7 +458,12 @@ void CPlayer::Think() auto pawn = GetPawn(); static auto sdkschema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); + if (!sdkschema) + return; + static auto vgui = g_ifaceService.FetchInterface(VGUI_INTERFACE_VERSION); + if (!vgui) + return; if (pawn) { @@ -401,13 +479,15 @@ void CPlayer::Think() { for (int i = 0; i < 64; i++) { - if ((m_uPressedButtons & (1ULL << i)) == 0 && (newButtons & (1ULL << i)) != 0) { + if ((m_uPressedButtons & (1ULL << i)) == 0 && (newButtons & (1ULL << i)) != 0) + { if (g_pOnClientKeyStateChangedCallback) - reinterpret_cast(g_pOnClientKeyStateChangedCallback)(m_iPlayerId, i, true); + reinterpret_cast(g_pOnClientKeyStateChangedCallback)(m_iPlayerId, i, true); } - else if ((m_uPressedButtons & (1ULL << i)) != 0 && (newButtons & (1ULL << i)) == 0) { + else if ((m_uPressedButtons & (1ULL << i)) != 0 && (newButtons & (1ULL << i)) == 0) + { if (g_pOnClientKeyStateChangedCallback) - reinterpret_cast(g_pOnClientKeyStateChangedCallback)(m_iPlayerId, i, false); + reinterpret_cast(g_pOnClientKeyStateChangedCallback)(m_iPlayerId, i, false); } } @@ -417,12 +497,12 @@ void CPlayer::Think() } auto& observerServices = *(void**)sdkschema->GetPropPtr(pawn, 14568842447348147577); // CBasePlayerPawn::m_pObserverServices - if (observerServices) { + if (observerServices) + { CHandle& observerTarget = *(CHandle*)sdkschema->GetPropPtr(observerServices, 1590106406667131980); // CPlayer_ObserverServices::m_hObserverTarget vgui->CheckRenderForPlayer(this, observerTarget); } } - } void CPlayer::RenderMenuCenterText(const std::string& text) From 2a17c901cb165cb6b52371e84e040291f196ff77 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 16 Nov 2025 23:46:19 +0800 Subject: [PATCH 09/10] fix: Prevent race condition in player lifecycle management --- src/server/players/manager.cpp | 17 ++++++++++------- src/server/players/player.cpp | 18 ------------------ 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/server/players/manager.cpp b/src/server/players/manager.cpp index 7011b2e0e..8a6f8061e 100644 --- a/src/server/players/manager.cpp +++ b/src/server/players/manager.cpp @@ -341,10 +341,11 @@ IPlayer* CPlayerManager::RegisterPlayer(int playerid) if (g_Players[playerid] != nullptr) UnregisterPlayer(playerid); - g_Players[playerid] = new CPlayer(); - g_Players[playerid]->Initialize(playerid); + auto player = new CPlayer(); + player->Initialize(playerid); + g_Players[playerid] = player; - return g_Players[playerid]; + return player; } void CPlayerManager::UnregisterPlayer(int playerid) @@ -356,11 +357,13 @@ void CPlayerManager::UnregisterPlayer(int playerid) static auto vgui = g_ifaceService.FetchInterface(VGUI_INTERFACE_VERSION); - vgui->UnregisterForPlayer(g_Players[playerid]); - - g_Players[playerid]->Shutdown(); - delete g_Players[playerid]; + auto player = g_Players[playerid]; g_Players[playerid] = nullptr; + + vgui->UnregisterForPlayer(player); + + player->Shutdown(); + delete player; } IPlayer* CPlayerManager::GetPlayer(int playerid) diff --git a/src/server/players/player.cpp b/src/server/players/player.cpp index bc8307ee6..e3928fe87 100644 --- a/src/server/players/player.cpp +++ b/src/server/players/player.cpp @@ -122,8 +122,6 @@ void CPlayer::Shutdown() if (centerMessageEvent) { static auto eventmanager = g_ifaceService.FetchInterface(GAMEEVENTMANAGER_INTERFACE_VERSION); - if (!eventmanager) - return; eventmanager->GetGameEventManager()->FreeEvent(centerMessageEvent); centerMessageEvent = nullptr; } @@ -305,8 +303,6 @@ std::string& CPlayer::GetLanguage() void* CPlayer::GetController() { static auto entsystem = g_ifaceService.FetchInterface(ENTITYSYSTEM_INTERFACE_VERSION); - if (!entsystem) - return nullptr; auto entitySystem = entsystem->GetEntitySystem(); if (!entitySystem) @@ -319,8 +315,6 @@ void* CPlayer::GetController() void* CPlayer::GetPawn() { static auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); - if (!schema) - return nullptr; auto controller = GetController(); if (!controller) @@ -337,8 +331,6 @@ void* CPlayer::GetPawn() void* CPlayer::GetPlayerPawn() { static auto schema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); - if (!schema) - return nullptr; auto controller = GetController(); if (!controller) @@ -411,12 +403,7 @@ typedef IGameEventListener2* (*GetLegacyGameEventListener)(CPlayerSlot slot); void CPlayer::Think() { static auto gamedata = g_ifaceService.FetchInterface(GAMEDATA_INTERFACE_VERSION); - if (!gamedata) - return; static auto eventmanager = g_ifaceService.FetchInterface(GAMEEVENTMANAGER_INTERFACE_VERSION); - if (!eventmanager) - return; - static auto pListenerSig = gamedata->GetSignatures()->Fetch("LegacyGameEventListener"); if (pListenerSig) { @@ -458,12 +445,7 @@ void CPlayer::Think() auto pawn = GetPawn(); static auto sdkschema = g_ifaceService.FetchInterface(SDKSCHEMA_INTERFACE_VERSION); - if (!sdkschema) - return; - static auto vgui = g_ifaceService.FetchInterface(VGUI_INTERFACE_VERSION); - if (!vgui) - return; if (pawn) { From 794eb5e38c0302ccb3f016f2731f26b329c998fe Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Mon, 17 Nov 2025 14:44:53 +0800 Subject: [PATCH 10/10] fix: Properly close menus --- .../Modules/Menus/MenuManagerAPI.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs index ee8eacdab..85f95a964 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManagerAPI.cs @@ -145,7 +145,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { scrollSound.Recipients.AddRecipient(@event.PlayerId); - scrollSound.Emit(); + _ = scrollSound.Emit(); scrollSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -156,7 +156,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { scrollSound.Recipients.AddRecipient(@event.PlayerId); - scrollSound.Emit(); + _ = scrollSound.Emit(); scrollSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -167,7 +167,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { exitSound.Recipients.AddRecipient(@event.PlayerId); - exitSound.Emit(); + _ = exitSound.Emit(); exitSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -181,7 +181,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound && option.PlaySound) { useSound.Recipients.AddRecipient(@event.PlayerId); - useSound.Emit(); + _ = useSound.Emit(); useSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -196,7 +196,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { scrollSound.Recipients.AddRecipient(@event.PlayerId); - scrollSound.Emit(); + _ = scrollSound.Emit(); scrollSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -207,7 +207,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { scrollSound.Recipients.AddRecipient(@event.PlayerId); - scrollSound.Emit(); + _ = scrollSound.Emit(); scrollSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -217,7 +217,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound) { exitSound.Recipients.AddRecipient(@event.PlayerId); - exitSound.Emit(); + _ = exitSound.Emit(); exitSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -231,7 +231,7 @@ private void KeyStateChange( IOnClientKeyStateChangedEvent @event ) if (menu.Configuration.PlaySound && option.PlaySound) { useSound.Recipients.AddRecipient(@event.PlayerId); - useSound.Emit(); + _ = useSound.Emit(); useSound.Recipients.RemoveRecipient(@event.PlayerId); } } @@ -336,6 +336,10 @@ public void OpenMenuForPlayer( IPlayer player, IMenuAPI menu ) CloseMenuForPlayerInternal(player, currentMenu, false); _ = onClosedCallbacks.AddOrUpdate((player, currentMenu), callback, ( _, _ ) => callback); } + else + { + CloseMenuForPlayerInternal(player, currentMenu, false); + } } _ = openMenus.AddOrUpdate(player, menu, ( _, _ ) => menu);