From e51540d986d7d6c54ca46a057bdeeefb65573602 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sat, 22 Nov 2025 21:11:52 +0000 Subject: [PATCH 1/4] Make some parts of session code more robust --- GameServer/Implementation/Common/Session.cs | 43 ++++++++++++--------- GameServer/Models/PlayerData/SessionInfo.cs | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/GameServer/Implementation/Common/Session.cs b/GameServer/Implementation/Common/Session.cs index 8bc8ff3..055ebdf 100644 --- a/GameServer/Implementation/Common/Session.cs +++ b/GameServer/Implementation/Common/Session.cs @@ -1,17 +1,18 @@ -using GameServer.Models; -using GameServer.Models.PlayerData; -using GameServer.Models.Response; +using System; using System.Collections.Generic; -using System; -using GameServer.Utils; +using System.IO; using System.Linq; -using NPTicket; -using Serilog; +using GameServer.Models; using GameServer.Models.Config; -using System.IO; +using GameServer.Models.PlayerData; +using GameServer.Models.Response; +using GameServer.Utils; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Newtonsoft.Json; +using NPTicket; using NPTicket.Verification; using NPTicket.Verification.Keys; +using Serilog; namespace GameServer.Implementation.Common { @@ -22,6 +23,17 @@ public class Session public static string Login(Database database, string ip, Platform platform, string ticket, string hmac, string console_id, Guid SessionID) { ClearSessions(); + + if (!Sessions.ContainsKey(SessionID)) + { + var errorResp = new Response + { + status = new ResponseStatus { id = -130, message = "The player doesn't exist" }, + response = new EmptyResponse { } + }; + return errorResp.Serialize(); + } + byte[] ticketData = Convert.FromBase64String(ticket.Trim('\n').Trim('\0')); List whitelist = []; if (ServerConfig.Instance.Whitelist) @@ -279,18 +291,11 @@ public static void StartSession(Guid SessionID) private static void ClearSessions() { - foreach (var Session in Sessions.Where(match => match.Value.Authenticated - && (TimeUtils.Now > match.Value.LastPing.AddMinutes(60) /*|| TimeUtils.Now > match.Value.ExpiryDate*/))) + foreach (var Session in Sessions.Where(match => TimeUtils.Now > match.Value.LastPing.AddMinutes(60) /*|| TimeUtils.Now > match.Value.ExpiryDate*/)) { - Sessions.Remove(Session.Key); - ServerCommunication.NotifySessionDestroyed(Session.Key); - } - - foreach (var Session in Sessions.Where(match => !match.Value.Authenticated - && TimeUtils.Now > match.Value.LastPing.AddHours(3))) - { - Sessions.Remove(Session.Key); - ServerCommunication.NotifySessionDestroyed(Session.Key); + var sessionKey = Session.Key; + Sessions.Remove(sessionKey); + ServerCommunication.NotifySessionDestroyed(sessionKey); } } diff --git a/GameServer/Models/PlayerData/SessionInfo.cs b/GameServer/Models/PlayerData/SessionInfo.cs index c041d11..28cfb1d 100644 --- a/GameServer/Models/PlayerData/SessionInfo.cs +++ b/GameServer/Models/PlayerData/SessionInfo.cs @@ -8,7 +8,7 @@ public class SessionInfo { public string Username => Authenticated ? Ticket.Username : ""; public Presence Presence { get; set; } = Presence.OFFLINE; - public DateTime ExpiryDate => Ticket.ExpiryDate.DateTime; + public DateTime ExpiryDate => Authenticated ? Ticket.ExpiryDate.DateTime : TimeUtils.Now; public Ticket Ticket { get; set; } public bool Authenticated => Ticket != null; public bool PolicyAccepted { get; set; } = false; From 7d745d56e20ecfdcb9af048e6aace3eb7c2e7eaa Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:03:05 +0000 Subject: [PATCH 2/4] Further more robust session null checks --- GameServer/Implementation/Common/Session.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GameServer/Implementation/Common/Session.cs b/GameServer/Implementation/Common/Session.cs index 055ebdf..527a7f2 100644 --- a/GameServer/Implementation/Common/Session.cs +++ b/GameServer/Implementation/Common/Session.cs @@ -138,7 +138,7 @@ public static string Login(Database database, string ip, Platform platform, stri Quota = 30, CreatedAt = TimeUtils.Now, UpdatedAt = TimeUtils.Now, - PolicyAccepted = Sessions[SessionID].PolicyAccepted, + PolicyAccepted = Sessions.ContainsKey(SessionID) ? Sessions[SessionID].PolicyAccepted : false, }; if (IsPSN) newUser.PSNID = NPTicket.UserId; @@ -166,8 +166,8 @@ public static string Login(Database database, string ip, Platform platform, stri return errorResp.Serialize(); } - foreach (var Session in Sessions.Where(match => match.Value.Username == user.Username - && match.Key != SessionID && match.Value.Platform == platform)) + foreach (var Session in Sessions.Where(match => match.Value == null || (match.Value.Username == user.Username + && match.Key != SessionID && match.Value.Platform == platform))) { Sessions.Remove(Session.Key); ServerCommunication.NotifySessionDestroyed(Session.Key); @@ -291,7 +291,7 @@ public static void StartSession(Guid SessionID) private static void ClearSessions() { - foreach (var Session in Sessions.Where(match => TimeUtils.Now > match.Value.LastPing.AddMinutes(60) /*|| TimeUtils.Now > match.Value.ExpiryDate*/)) + foreach (var Session in Sessions.Where(match => match.Value == null || (TimeUtils.Now > match.Value.LastPing.AddMinutes(60) /*|| TimeUtils.Now > match.Value.ExpiryDate*/))) { var sessionKey = Session.Key; Sessions.Remove(sessionKey); From 7e4ff8ee4a080db870fb66997da1214566031c68 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:55:28 +0000 Subject: [PATCH 3/4] More null checks, idk why sessions are going null? --- GameServer/Controllers/Api/PlayerCountApiController.cs | 1 + GameServer/Implementation/Common/Session.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/GameServer/Controllers/Api/PlayerCountApiController.cs b/GameServer/Controllers/Api/PlayerCountApiController.cs index cd98457..29b6ee8 100644 --- a/GameServer/Controllers/Api/PlayerCountApiController.cs +++ b/GameServer/Controllers/Api/PlayerCountApiController.cs @@ -21,6 +21,7 @@ public IActionResult GetSessionCount(bool? isMnr = null) { return Content($"{Session.GetSessions() .Where(x => + x != null && // TODO: Why are session objects becoming null? (isMnr != null ? x.IsMNR == isMnr : true) && x.Authenticated && x.LastPing.AddMinutes(1) > DateTime.Now) diff --git a/GameServer/Implementation/Common/Session.cs b/GameServer/Implementation/Common/Session.cs index 527a7f2..e46efc5 100644 --- a/GameServer/Implementation/Common/Session.cs +++ b/GameServer/Implementation/Common/Session.cs @@ -248,7 +248,7 @@ public static string SetPresence(string presence, Guid SessionID) public static Presence GetPresence(string Username) { ClearSessions(); - var Session = Sessions.FirstOrDefault(match => match.Value.Username == Username).Value; + var Session = Sessions.FirstOrDefault(match => match.Value != null && match.Value.Username == Username).Value; if (Session == null) { return Presence.OFFLINE; From eec9b9a18f021d51f7e7ce6640f1e91833620e22 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:14:22 +0000 Subject: [PATCH 4/4] Move over to a ConcurrentDictionary for sessions, attempt to eliminate concurrency as the issue --- GameServer/Implementation/Common/Session.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/GameServer/Implementation/Common/Session.cs b/GameServer/Implementation/Common/Session.cs index e46efc5..0e97e81 100644 --- a/GameServer/Implementation/Common/Session.cs +++ b/GameServer/Implementation/Common/Session.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -18,7 +19,7 @@ namespace GameServer.Implementation.Common { public class Session { - private static readonly Dictionary Sessions = []; + private static readonly ConcurrentDictionary Sessions = []; public static string Login(Database database, string ip, Platform platform, string ticket, string hmac, string console_id, Guid SessionID) { @@ -169,7 +170,7 @@ public static string Login(Database database, string ip, Platform platform, stri foreach (var Session in Sessions.Where(match => match.Value == null || (match.Value.Username == user.Username && match.Key != SessionID && match.Value.Platform == platform))) { - Sessions.Remove(Session.Key); + Sessions.Remove(Session.Key, out _); ServerCommunication.NotifySessionDestroyed(Session.Key); } @@ -282,7 +283,7 @@ public static string Ping(Guid SessionID) public static void StartSession(Guid SessionID) { - Sessions.Add(SessionID, new SessionInfo + Sessions.TryAdd(SessionID, new SessionInfo { LastPing = TimeUtils.Now, Presence = Presence.OFFLINE @@ -294,7 +295,7 @@ private static void ClearSessions() foreach (var Session in Sessions.Where(match => match.Value == null || (TimeUtils.Now > match.Value.LastPing.AddMinutes(60) /*|| TimeUtils.Now > match.Value.ExpiryDate*/))) { var sessionKey = Session.Key; - Sessions.Remove(sessionKey); + Sessions.Remove(sessionKey, out _); ServerCommunication.NotifySessionDestroyed(sessionKey); } } @@ -353,7 +354,7 @@ public static void DestroyAllSessions() { foreach (var sessionID in Sessions.Keys.ToList()) { - Sessions.Remove(sessionID); + Sessions.Remove(sessionID, out _); ServerCommunication.NotifySessionDestroyed(sessionID); } }