Browse Source

Get Create, Find and Join Session working.

CartBlanche 1 week ago
parent
commit
cc55a3dcf5
51 changed files with 1935 additions and 1130 deletions
  1. 23 0
      MonoGame.Xna.Framework.Net/.vscode/launch.json
  2. 15 0
      MonoGame.Xna.Framework.Net/.vscode/tasks.json
  3. 37 0
      MonoGame.Xna.Framework.Net/GamerServices/Gamer.cs
  4. 10 0
      MonoGame.Xna.Framework.Net/GamerServices/GamerCollection.cs
  5. 18 0
      MonoGame.Xna.Framework.Net/GamerServices/GamerPresence.cs
  6. 63 0
      MonoGame.Xna.Framework.Net/GamerServices/GamerPresenceMode.cs
  7. 1 311
      MonoGame.Xna.Framework.Net/GamerServices/GamerServices.cs
  8. 87 0
      MonoGame.Xna.Framework.Net/GamerServices/Guide.cs
  9. 13 0
      MonoGame.Xna.Framework.Net/GamerServices/MessageBoxIcon.cs
  10. 20 0
      MonoGame.Xna.Framework.Net/GamerServices/MockAsyncResult.cs
  11. 78 0
      MonoGame.Xna.Framework.Net/GamerServices/SignedInGamer.cs
  12. 6 0
      MonoGame.Xna.Framework.Net/MonoGame.Xna.Framework.Net.csproj
  13. 30 0
      MonoGame.Xna.Framework.Net/MonoGame.Xna.Framework.Net.sln
  14. 35 0
      MonoGame.Xna.Framework.Net/Net/AsyncResultWrapper.cs
  15. 9 127
      MonoGame.Xna.Framework.Net/Net/AvailableNetworkSession.cs
  16. 42 0
      MonoGame.Xna.Framework.Net/Net/AvailableNetworkSessionCollection.cs
  17. 0 184
      MonoGame.Xna.Framework.Net/Net/Enums.cs
  18. 18 0
      MonoGame.Xna.Framework.Net/Net/Enums/GamerType.cs
  19. 36 0
      MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionEndReason.cs
  20. 32 0
      MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionJoinError.cs
  21. 28 0
      MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionState.cs
  22. 28 0
      MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionType.cs
  23. 28 0
      MonoGame.Xna.Framework.Net/Net/Enums/SendDataOptions.cs
  24. 0 122
      MonoGame.Xna.Framework.Net/Net/EventArgs.cs
  25. 10 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/GameEndedEventArgs.cs
  26. 10 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/GameStartedEventArgs.cs
  27. 18 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/GamerJoinedEventArgs.cs
  28. 18 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/GamerLeftEventArgs.cs
  29. 26 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/InviteAcceptedEventArgs.cs
  30. 17 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/MessageReceivedEventArgs.cs
  31. 18 0
      MonoGame.Xna.Framework.Net/Net/EventArgs/NetworkSessionEndedEventArgs.cs
  32. 12 0
      MonoGame.Xna.Framework.Net/Net/Exceptions/GamerPrivilegeException.cs
  33. 12 0
      MonoGame.Xna.Framework.Net/Net/Exceptions/NetworkException.cs
  34. 38 0
      MonoGame.Xna.Framework.Net/Net/Exceptions/NetworkSessionJoinException.cs
  35. 43 0
      MonoGame.Xna.Framework.Net/Net/GamerCollection.cs
  36. 25 0
      MonoGame.Xna.Framework.Net/Net/INetworkMessage.cs
  37. 38 0
      MonoGame.Xna.Framework.Net/Net/INetworkTransport.cs
  38. 43 0
      MonoGame.Xna.Framework.Net/Net/LocalGamerCollection.cs
  39. 27 0
      MonoGame.Xna.Framework.Net/Net/LocalNetworkGamer.cs
  40. 45 0
      MonoGame.Xna.Framework.Net/Net/LocalSessionRegistry.cs
  41. 102 127
      MonoGame.Xna.Framework.Net/Net/NetworkGamer.cs
  42. 13 0
      MonoGame.Xna.Framework.Net/Net/NetworkMachine.cs
  43. 33 0
      MonoGame.Xna.Framework.Net/Net/NetworkMessageRegistry.cs
  44. 299 259
      MonoGame.Xna.Framework.Net/Net/NetworkSession.cs
  45. 18 0
      MonoGame.Xna.Framework.Net/Net/NetworkSessionProperties.cs
  46. 28 0
      MonoGame.Xna.Framework.Net/Net/PlayerMoveMessage.cs
  47. 38 0
      MonoGame.Xna.Framework.Net/Net/QualityOfService.cs
  48. 118 0
      MonoGame.Xna.Framework.Net/Net/SystemLinkSessionManager.cs
  49. 73 0
      MonoGame.Xna.Framework.Net/Net/UdpTransport.cs
  50. 18 0
      MonoGame.Xna.Framework.Net/Tests/MonoGame.Xna.Framework.Net.Tests.csproj
  51. 138 0
      MonoGame.Xna.Framework.Net/Tests/NetworkSessionTests.cs

+ 23 - 0
MonoGame.Xna.Framework.Net/.vscode/launch.json

@@ -0,0 +1,23 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Build Project",
+            "type": "coreclr",
+            "request": "launch",
+            "program": "dotnet",
+            "args": ["build", "MonoGame.Xna.Framework.Net.csproj"],
+            "cwd": "${workspaceFolder}",
+            "console": "integratedTerminal"
+        },
+        {
+            "name": "Run Unit Tests",
+            "type": "coreclr",
+            "request": "launch",
+            "program": "dotnet",
+            "args": ["test", "Tests/MonoGame.Xna.Framework.Net.Tests.csproj"],
+            "cwd": "${workspaceFolder}",
+            "console": "integratedTerminal"
+        }
+    ]
+}

+ 15 - 0
MonoGame.Xna.Framework.Net/.vscode/tasks.json

@@ -0,0 +1,15 @@
+{
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"type": "shell",
+			"label": "Build MonoGame.Xna.Framework.Net",
+			"command": "dotnet build MonoGame.Xna.Framework.Net.csproj",
+			"group": "build",
+			"isBackground": false,
+			"problemMatcher": [
+				"$msCompile"
+			]
+		}
+	]
+}

+ 37 - 0
MonoGame.Xna.Framework.Net/GamerServices/Gamer.cs

@@ -0,0 +1,37 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Base class for all gamer types.
+	/// </summary>
+	public abstract class Gamer
+    {
+        /// <summary>
+        /// Gets the gamertag for this gamer.
+        /// </summary>
+        public abstract string Gamertag { get; }
+
+        /// <summary>
+        /// Gets the display name for this gamer.
+        /// </summary>
+        public virtual string DisplayName => Gamertag;
+
+        /// <summary>
+        /// Gets custom data associated with this gamer.
+        /// </summary>
+        public object Tag { get; set; }
+
+        /// <summary>
+        /// Gets the signed-in gamers.
+        /// </summary>
+        public static GamerCollection<SignedInGamer> SignedInGamers => signedInGamers;
+
+        private static readonly GamerCollection<SignedInGamer> signedInGamers;
+
+        static Gamer()
+        {
+            // Initialize with current signed-in gamer
+            var gamers = new List<SignedInGamer> { SignedInGamer.Current };
+            signedInGamers = new GamerCollection<SignedInGamer>(gamers);
+        }
+    }
+}

+ 10 - 0
MonoGame.Xna.Framework.Net/GamerServices/GamerCollection.cs

@@ -0,0 +1,10 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Collection of gamers.
+	/// </summary>
+	public class GamerCollection<T> : System.Collections.ObjectModel.ReadOnlyCollection<T>
+    {
+        public GamerCollection(IList<T> list) : base(list) { }
+    }
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/GamerServices/GamerPresence.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Gamer presence information.
+	/// </summary>
+	public class GamerPresence
+    {
+        /// <summary>
+        /// Gets or sets the presence mode.
+        /// </summary>
+        public GamerPresenceMode PresenceMode { get; set; } = GamerPresenceMode.Online;
+
+        /// <summary>
+        /// Gets or sets the presence value.
+        /// </summary>
+        public int PresenceValue { get; set; }
+    }
+}

+ 63 - 0
MonoGame.Xna.Framework.Net/GamerServices/GamerPresenceMode.cs

@@ -0,0 +1,63 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Defines gamer presence modes.
+	/// </summary>
+	public enum GamerPresenceMode
+    {
+        /// <summary>
+        /// Not online.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// Online and available.
+        /// </summary>
+        Online,
+
+        /// <summary>
+        /// Away from keyboard.
+        /// </summary>
+        Away,
+
+        /// <summary>
+        /// Busy playing a game.
+        /// </summary>
+        Busy,
+
+        /// <summary>
+        /// Playing a specific game.
+        /// </summary>
+        PlayingGame,
+
+        /// <summary>
+        /// At the main menu.
+        /// </summary>
+        AtMenu,
+
+        /// <summary>
+        /// Waiting for other players.
+        /// </summary>
+        WaitingForPlayers,
+
+        /// <summary>
+        /// Waiting in lobby.
+        /// </summary>
+        WaitingInLobby,
+
+        /// <summary>
+        /// Currently winning.
+        /// </summary>
+        Winning,
+
+        /// <summary>
+        /// Currently losing.
+        /// </summary>
+        Losing,
+
+        /// <summary>
+        /// Score is tied.
+        /// </summary>
+        ScoreIsTied
+    }
+}

+ 1 - 311
MonoGame.Xna.Framework.Net/GamerServices/GamerServices.cs

@@ -4,273 +4,6 @@ using Microsoft.Xna.Framework;
 
 namespace Microsoft.Xna.Framework.GamerServices
 {
-    /// <summary>
-    /// Defines gamer presence modes.
-    /// </summary>
-    public enum GamerPresenceMode
-    {
-        /// <summary>
-        /// Not online.
-        /// </summary>
-        None,
-
-        /// <summary>
-        /// Online and available.
-        /// </summary>
-        Online,
-
-        /// <summary>
-        /// Away from keyboard.
-        /// </summary>
-        Away,
-
-        /// <summary>
-        /// Busy playing a game.
-        /// </summary>
-        Busy,
-
-        /// <summary>
-        /// Playing a specific game.
-        /// </summary>
-        PlayingGame,
-
-        /// <summary>
-        /// At the main menu.
-        /// </summary>
-        AtMenu,
-
-        /// <summary>
-        /// Waiting for other players.
-        /// </summary>
-        WaitingForPlayers,
-
-        /// <summary>
-        /// Waiting in lobby.
-        /// </summary>
-        WaitingInLobby,
-
-        /// <summary>
-        /// Currently winning.
-        /// </summary>
-        Winning,
-
-        /// <summary>
-        /// Currently losing.
-        /// </summary>
-        Losing,
-
-        /// <summary>
-        /// Score is tied.
-        /// </summary>
-        ScoreIsTied
-    }
-
-    /// <summary>
-    /// Gamer presence information.
-    /// </summary>
-    public class GamerPresence
-    {
-        /// <summary>
-        /// Gets or sets the presence mode.
-        /// </summary>
-        public GamerPresenceMode PresenceMode { get; set; } = GamerPresenceMode.Online;
-
-        /// <summary>
-        /// Gets or sets the presence value.
-        /// </summary>
-        public int PresenceValue { get; set; }
-    }
-
-    /// <summary>
-    /// Represents a signed-in gamer.
-    /// </summary>
-    public class SignedInGamer : Gamer
-    {
-        private static SignedInGamer current;
-
-        /// <summary>
-        /// Gets the current signed-in gamer.
-        /// </summary>
-        public static SignedInGamer Current
-        {
-            get
-            {
-                if (current == null)
-                {
-                    current = new SignedInGamer();
-                    current.SetGamertag(Environment.UserName);
-                }
-                return current;
-            }
-            internal set => current = value;
-        }
-
-        private string gamertag;
-
-        /// <summary>
-        /// Gets or sets the gamertag for this gamer.
-        /// </summary>
-        public override string Gamertag 
-        { 
-            get => gamertag;
-        }
-
-        /// <summary>
-        /// Sets the gamertag for this gamer.
-        /// </summary>
-        internal void SetGamertag(string value)
-        {
-            gamertag = value;
-        }
-
-        /// <summary>
-        /// Gets whether this gamer is signed in to a live service.
-        /// </summary>
-        public bool IsSignedInToLive => false; // Mock implementation
-
-        /// <summary>
-        /// Gets whether this gamer is a guest.
-        /// </summary>
-        public bool IsGuest => false;
-
-        /// <summary>
-        /// Gets the display name for this gamer.
-        /// </summary>
-        public new string DisplayName => Gamertag;
-
-        /// <summary>
-        /// Gets the presence information for this gamer.
-        /// </summary>
-        public GamerPresence Presence { get; } = new GamerPresence();
-
-        /// <summary>
-        /// Gets the player index for this gamer.
-        /// </summary>
-        public PlayerIndex PlayerIndex { get; internal set; } = PlayerIndex.One;
-
-        internal SignedInGamer() { }
-
-		public GamerPrivileges Privileges
-		{
-			get;
-			private set;
-		}
-	}
-
-    /// <summary>
-    /// Provides access to platform services.
-    /// </summary>
-    public static class Guide
-    {
-        /// <summary>
-        /// Gets whether the current game is running in trial mode.
-        /// </summary>
-        public static bool IsTrialMode => false; // Mock implementation - not in trial mode
-
-        /// <summary>
-        /// Gets whether the Guide is visible.
-        /// </summary>
-        public static bool IsVisible => false; // Mock implementation
-
-        /// <summary>
-        /// Gets whether screen saver is enabled.
-        /// </summary>
-        public static bool IsScreenSaverEnabled
-        {
-            get => false;
-            set { /* Mock implementation */ }
-        }
-
-        /// <summary>
-        /// Shows a message box to the user.
-        /// </summary>
-        public static IAsyncResult BeginShowMessageBox(
-            string title,
-            string text,
-            IEnumerable<string> buttons,
-            int focusButton,
-            MessageBoxIcon icon,
-            AsyncCallback callback,
-            object state)
-        {
-            // Mock implementation - for now just return a completed result
-            var result = new MockAsyncResult(state, true);
-            callback?.Invoke(result);
-            return result;
-        }
-
-        /// <summary>
-        /// Ends the message box operation.
-        /// </summary>
-        public static int? EndShowMessageBox(IAsyncResult result)
-        {
-            return 0; // Mock implementation - first button selected
-        }
-
-        /// <summary>
-        /// Shows the sign-in interface.
-        /// </summary>
-        public static IAsyncResult BeginShowSignIn(int paneCount, bool onlineOnly, AsyncCallback callback, object state)
-        {
-            var result = new MockAsyncResult(state, true);
-            callback?.Invoke(result);
-            return result;
-        }
-
-        /// <summary>
-        /// Ends the sign-in operation.
-        /// </summary>
-        public static void EndShowSignIn(IAsyncResult result)
-        {
-            // Mock implementation
-        }
-
-        /// <summary>
-        /// Shows the sign-in interface (synchronous version).
-        /// </summary>
-        public static void ShowSignIn(int paneCount, bool onlineOnly)
-        {
-            // Mock implementation
-        }
-
-        /// <summary>
-        /// Shows the marketplace.
-        /// </summary>
-        public static void ShowMarketplace(int playerIndex)
-        {
-            // Mock implementation
-        }
-    }
-
-    /// <summary>
-    /// Message box icon types.
-    /// </summary>
-    public enum MessageBoxIcon
-    {
-        None,
-        Error,
-        Warning,
-        Alert
-    }
-
-    /// <summary>
-    /// Mock implementation of IAsyncResult for testing.
-    /// </summary>
-    internal class MockAsyncResult : IAsyncResult
-    {
-        public MockAsyncResult(object asyncState, bool isCompleted)
-        {
-            AsyncState = asyncState;
-            IsCompleted = isCompleted;
-            CompletedSynchronously = isCompleted;
-        }
-
-        public object AsyncState { get; }
-        public WaitHandle AsyncWaitHandle => new ManualResetEvent(IsCompleted);
-        public bool CompletedSynchronously { get; }
-        public bool IsCompleted { get; }
-    }
-
     /// <summary>
     /// Game component that provides gamer services functionality.
     /// </summary>
@@ -302,47 +35,4 @@ namespace Microsoft.Xna.Framework.GamerServices
             // Mock implementation
         }
     }
-
-    /// <summary>
-    /// Base class for all gamer types.
-    /// </summary>
-    public abstract class Gamer
-    {
-        /// <summary>
-        /// Gets the gamertag for this gamer.
-        /// </summary>
-        public abstract string Gamertag { get; }
-
-        /// <summary>
-        /// Gets the display name for this gamer.
-        /// </summary>
-        public virtual string DisplayName => Gamertag;
-
-        /// <summary>
-        /// Gets custom data associated with this gamer.
-        /// </summary>
-        public object Tag { get; set; }
-
-        /// <summary>
-        /// Gets the signed-in gamers.
-        /// </summary>
-        public static GamerCollection<SignedInGamer> SignedInGamers => signedInGamers;
-
-        private static readonly GamerCollection<SignedInGamer> signedInGamers;
-
-        static Gamer()
-        {
-            // Initialize with current signed-in gamer
-            var gamers = new List<SignedInGamer> { SignedInGamer.Current };
-            signedInGamers = new GamerCollection<SignedInGamer>(gamers);
-        }
-    }
-
-    /// <summary>
-    /// Collection of gamers.
-    /// </summary>
-    public class GamerCollection<T> : System.Collections.ObjectModel.ReadOnlyCollection<T>
-    {
-        public GamerCollection(IList<T> list) : base(list) { }
-    }
-}
+}

+ 87 - 0
MonoGame.Xna.Framework.Net/GamerServices/Guide.cs

@@ -0,0 +1,87 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Provides access to platform services.
+	/// </summary>
+	public static class Guide
+    {
+        /// <summary>
+        /// Gets whether the current game is running in trial mode.
+        /// </summary>
+        public static bool IsTrialMode => false; // Mock implementation - not in trial mode
+
+        /// <summary>
+        /// Gets whether the Guide is visible.
+        /// </summary>
+        public static bool IsVisible => false; // Mock implementation
+
+        /// <summary>
+        /// Gets whether screen saver is enabled.
+        /// </summary>
+        public static bool IsScreenSaverEnabled
+        {
+            get => false;
+            set { /* Mock implementation */ }
+        }
+
+        /// <summary>
+        /// Shows a message box to the user.
+        /// </summary>
+        public static IAsyncResult BeginShowMessageBox(
+            string title,
+            string text,
+            IEnumerable<string> buttons,
+            int focusButton,
+            MessageBoxIcon icon,
+            AsyncCallback callback,
+            object state)
+        {
+            // Mock implementation - for now just return a completed result
+            var result = new MockAsyncResult(state, true);
+            callback?.Invoke(result);
+            return result;
+        }
+
+        /// <summary>
+        /// Ends the message box operation.
+        /// </summary>
+        public static int? EndShowMessageBox(IAsyncResult result)
+        {
+            return 0; // Mock implementation - first button selected
+        }
+
+        /// <summary>
+        /// Shows the sign-in interface.
+        /// </summary>
+        public static IAsyncResult BeginShowSignIn(int paneCount, bool onlineOnly, AsyncCallback callback, object state)
+        {
+            var result = new MockAsyncResult(state, true);
+            callback?.Invoke(result);
+            return result;
+        }
+
+        /// <summary>
+        /// Ends the sign-in operation.
+        /// </summary>
+        public static void EndShowSignIn(IAsyncResult result)
+        {
+            // Mock implementation
+        }
+
+        /// <summary>
+        /// Shows the sign-in interface (synchronous version).
+        /// </summary>
+        public static void ShowSignIn(int paneCount, bool onlineOnly)
+        {
+            // Mock implementation
+        }
+
+        /// <summary>
+        /// Shows the marketplace.
+        /// </summary>
+        public static void ShowMarketplace(PlayerIndex playerIndex)
+        {
+            // Mock implementation
+        }
+    }
+}

+ 13 - 0
MonoGame.Xna.Framework.Net/GamerServices/MessageBoxIcon.cs

@@ -0,0 +1,13 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Message box icon types.
+	/// </summary>
+	public enum MessageBoxIcon
+    {
+        None,
+        Error,
+        Warning,
+        Alert
+    }
+}

+ 20 - 0
MonoGame.Xna.Framework.Net/GamerServices/MockAsyncResult.cs

@@ -0,0 +1,20 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Mock implementation of IAsyncResult for testing.
+	/// </summary>
+	internal class MockAsyncResult : IAsyncResult
+    {
+        public MockAsyncResult(object asyncState, bool isCompleted)
+        {
+            AsyncState = asyncState;
+            IsCompleted = isCompleted;
+            CompletedSynchronously = isCompleted;
+        }
+
+        public object AsyncState { get; }
+        public WaitHandle AsyncWaitHandle => new ManualResetEvent(IsCompleted);
+        public bool CompletedSynchronously { get; }
+        public bool IsCompleted { get; }
+    }
+}

+ 78 - 0
MonoGame.Xna.Framework.Net/GamerServices/SignedInGamer.cs

@@ -0,0 +1,78 @@
+namespace Microsoft.Xna.Framework.GamerServices
+{
+	/// <summary>
+	/// Represents a signed-in gamer.
+	/// </summary>
+	public class SignedInGamer : Gamer
+    {
+        private static SignedInGamer current;
+
+        /// <summary>
+        /// Gets the current signed-in gamer.
+        /// </summary>
+        public static SignedInGamer Current
+        {
+            get
+            {
+                if (current == null)
+                {
+                    current = new SignedInGamer();
+                    current.SetGamertag(Environment.UserName);
+                }
+                return current;
+            }
+            internal set => current = value;
+        }
+
+        private string gamertag;
+
+        /// <summary>
+        /// Gets or sets the gamertag for this gamer.
+        /// </summary>
+        public override string Gamertag 
+        { 
+            get => gamertag;
+        }
+
+        /// <summary>
+        /// Sets the gamertag for this gamer.
+        /// </summary>
+        internal void SetGamertag(string value)
+        {
+            gamertag = value;
+        }
+
+        /// <summary>
+        /// Gets whether this gamer is signed in to a live service.
+        /// </summary>
+        public bool IsSignedInToLive => false; // Mock implementation
+
+        /// <summary>
+        /// Gets whether this gamer is a guest.
+        /// </summary>
+        public bool IsGuest => false;
+
+        /// <summary>
+        /// Gets the display name for this gamer.
+        /// </summary>
+        public new string DisplayName => Gamertag;
+
+        /// <summary>
+        /// Gets the presence information for this gamer.
+        /// </summary>
+        public GamerPresence Presence { get; } = new GamerPresence();
+
+        /// <summary>
+        /// Gets the player index for this gamer.
+        /// </summary>
+        public PlayerIndex PlayerIndex { get; internal set; } = PlayerIndex.One;
+
+        internal SignedInGamer() { }
+
+		public GamerPrivileges Privileges
+		{
+			get;
+			private set;
+		}
+	}
+}

+ 6 - 0
MonoGame.Xna.Framework.Net/MonoGame.Xna.Framework.Net.csproj

@@ -7,6 +7,12 @@
     <RootNamespace>Microsoft.Xna.Framework</RootNamespace>
   </PropertyGroup>
 
+  <ItemGroup>
+    <Compile Remove="Tests\**" />
+    <EmbeddedResource Remove="Tests\**" />
+    <None Remove="Tests\**" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
     <PackageReference Include="System.Text.Json" Version="8.0.*" />

+ 30 - 0
MonoGame.Xna.Framework.Net/MonoGame.Xna.Framework.Net.sln

@@ -0,0 +1,30 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.2.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Xna.Framework.Net", "MonoGame.Xna.Framework.Net.csproj", "{820B80E2-083E-FF74-BAB2-82A0265063D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Xna.Framework.Net.Tests", "Tests\MonoGame.Xna.Framework.Net.Tests.csproj", "{0FCF5874-4B4D-6344-29C1-DEDA757ECEAF}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{820B80E2-083E-FF74-BAB2-82A0265063D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{820B80E2-083E-FF74-BAB2-82A0265063D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{820B80E2-083E-FF74-BAB2-82A0265063D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{820B80E2-083E-FF74-BAB2-82A0265063D2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0FCF5874-4B4D-6344-29C1-DEDA757ECEAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0FCF5874-4B4D-6344-29C1-DEDA757ECEAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0FCF5874-4B4D-6344-29C1-DEDA757ECEAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0FCF5874-4B4D-6344-29C1-DEDA757ECEAF}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {30F96BFA-F238-4F05-B227-45D68B4EE0BF}
+	EndGlobalSection
+EndGlobal

+ 35 - 0
MonoGame.Xna.Framework.Net/Net/AsyncResultWrapper.cs

@@ -0,0 +1,35 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Wrapper for async operations to provide XNA-compatible IAsyncResult interface.
+	/// </summary>
+	internal class AsyncResultWrapper<T> : IAsyncResult
+    {
+        private readonly Task<T> task;
+        private readonly object asyncState;
+
+        public AsyncResultWrapper(Task<T> task, AsyncCallback callback, object asyncState)
+        {
+            this.task = task;
+            this.asyncState = asyncState;
+
+            if (callback != null)
+            {
+                task.ContinueWith(t => callback(this));
+            }
+        }
+
+        public object AsyncState => asyncState;
+
+        public System.Threading.WaitHandle AsyncWaitHandle => ((IAsyncResult)task).AsyncWaitHandle;
+
+        public bool CompletedSynchronously => task.IsCompletedSuccessfully;
+
+        public bool IsCompleted => task.IsCompleted;
+
+        public T GetResult()
+        {
+            return task.Result;
+        }
+    }
+}

+ 9 - 127
MonoGame.Xna.Framework.Net/Net/AvailableNetworkSession.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using System.Threading.Tasks;
 
 namespace Microsoft.Xna.Framework.Net
@@ -10,14 +9,15 @@ namespace Microsoft.Xna.Framework.Net
     /// </summary>
     public class AvailableNetworkSession
     {
-        internal AvailableNetworkSession(
+        public AvailableNetworkSession(
             string sessionName,
             string hostGamertag,
             int currentGamerCount,
             int openPublicGamerSlots,
             int openPrivateGamerSlots,
             NetworkSessionType sessionType,
-            IDictionary<string, object> sessionProperties)
+            IDictionary<string, object> sessionProperties,
+            string sessionId)
         {
             SessionName = sessionName;
             HostGamertag = hostGamertag;
@@ -26,7 +26,12 @@ namespace Microsoft.Xna.Framework.Net
             OpenPrivateGamerSlots = openPrivateGamerSlots;
             SessionType = sessionType;
             SessionProperties = new Dictionary<string, object>(sessionProperties);
+            SessionId = sessionId;
         }
+        /// <summary>
+        /// Gets the unique session ID.
+        /// </summary>
+        public string SessionId { get; }
 
         /// <summary>
         /// Gets the name of the session.
@@ -68,127 +73,4 @@ namespace Microsoft.Xna.Framework.Net
         /// </summary>
         public QualityOfService QualityOfService { get; internal set; } = new QualityOfService();
     }
-
-    /// <summary>
-    /// Collection of available network sessions.
-    /// </summary>
-    public class AvailableNetworkSessionCollection : ReadOnlyCollection<AvailableNetworkSession>, IDisposable
-    {
-        private bool disposed = false;
-
-        internal AvailableNetworkSessionCollection() : base(new List<AvailableNetworkSession>()) { }
-        
-        internal AvailableNetworkSessionCollection(IList<AvailableNetworkSession> sessions) : base(sessions) { }
-
-        /// <summary>
-        /// Disposes of the collection resources.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        /// <summary>
-        /// Disposes of the collection resources.
-        /// </summary>
-        /// <param name="disposing">True if disposing managed resources.</param>
-        protected virtual void Dispose(bool disposing)
-        {
-            if (!disposed)
-            {
-                if (disposing)
-                {
-                    // Clean up managed resources if needed
-                    // The ReadOnlyCollection doesn't need special cleanup
-                }
-                disposed = true;
-            }
-        }
-    }
-
-    /// <summary>
-    /// Properties used when creating or searching for network sessions.
-    /// </summary>
-    public class NetworkSessionProperties : Dictionary<string, object>
-    {
-        /// <summary>
-        /// Initializes a new NetworkSessionProperties.
-        /// </summary>
-        public NetworkSessionProperties() : base() { }
-
-        /// <summary>
-        /// Initializes a new NetworkSessionProperties with a specified capacity.
-        /// </summary>
-        public NetworkSessionProperties(int capacity) : base(capacity) { }
-    }
-
-    /// <summary>
-    /// Quality of service information for a network session.
-    /// </summary>
-    public class QualityOfService
-    {
-        /// <summary>
-        /// Gets the average round trip time in milliseconds.
-        /// </summary>
-        public TimeSpan AverageRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(50);
-
-        /// <summary>
-        /// Gets the minimum round trip time in milliseconds.
-        /// </summary>
-        public TimeSpan MinimumRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(20);
-
-        /// <summary>
-        /// Gets the maximum round trip time in milliseconds.
-        /// </summary>
-        public TimeSpan MaximumRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(100);
-
-        /// <summary>
-        /// Gets the bytes per second being sent.
-        /// </summary>
-        public int BytesPerSecondSent { get; internal set; } = 0;
-
-        /// <summary>
-        /// Gets the bytes per second being received.
-        /// </summary>
-        public int BytesPerSecondReceived { get; internal set; } = 0;
-
-        /// <summary>
-        /// Gets whether the connection is available.
-        /// </summary>
-        public bool IsAvailable { get; internal set; } = true;
-    }
-
-    /// <summary>
-    /// Wrapper for async operations to provide XNA-compatible IAsyncResult interface.
-    /// </summary>
-    internal class AsyncResultWrapper<T> : IAsyncResult
-    {
-        private readonly Task<T> task;
-        private readonly object asyncState;
-
-        public AsyncResultWrapper(Task<T> task, AsyncCallback callback, object asyncState)
-        {
-            this.task = task;
-            this.asyncState = asyncState;
-
-            if (callback != null)
-            {
-                task.ContinueWith(t => callback(this));
-            }
-        }
-
-        public object AsyncState => asyncState;
-
-        public System.Threading.WaitHandle AsyncWaitHandle => ((IAsyncResult)task).AsyncWaitHandle;
-
-        public bool CompletedSynchronously => task.IsCompletedSuccessfully;
-
-        public bool IsCompleted => task.IsCompleted;
-
-        public T GetResult()
-        {
-            return task.Result;
-        }
-    }
-}
+}

+ 42 - 0
MonoGame.Xna.Framework.Net/Net/AvailableNetworkSessionCollection.cs

@@ -0,0 +1,42 @@
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Collection of available network sessions.
+	/// </summary>
+	public class AvailableNetworkSessionCollection : ReadOnlyCollection<AvailableNetworkSession>, IDisposable
+    {
+        private bool disposed = false;
+
+        internal AvailableNetworkSessionCollection() : base(new List<AvailableNetworkSession>()) { }
+        
+        internal AvailableNetworkSessionCollection(IList<AvailableNetworkSession> sessions) : base(sessions) { }
+
+        /// <summary>
+        /// Disposes of the collection resources.
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Disposes of the collection resources.
+        /// </summary>
+        /// <param name="disposing">True if disposing managed resources.</param>
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!disposed)
+            {
+                if (disposing)
+                {
+                    // Clean up managed resources if needed
+                    // The ReadOnlyCollection doesn't need special cleanup
+                }
+                disposed = true;
+            }
+        }
+    }
+}

+ 0 - 184
MonoGame.Xna.Framework.Net/Net/Enums.cs

@@ -1,184 +0,0 @@
-using System;
-using Microsoft.Xna.Framework;
-
-namespace Microsoft.Xna.Framework.Net
-{
-    /// <summary>
-    /// Types of network sessions that can be created or joined.
-    /// </summary>
-    public enum NetworkSessionType
-    {
-        /// <summary>
-        /// Local session for single-machine multiplayer.
-        /// </summary>
-        Local,
-
-        /// <summary>
-        /// System link session for LAN multiplayer.
-        /// </summary>
-        SystemLink,
-
-        /// <summary>
-        /// Player match session for online multiplayer.
-        /// </summary>
-        PlayerMatch,
-
-        /// <summary>
-        /// Ranked session for competitive online multiplayer.
-        /// </summary>
-        Ranked
-    }
-
-    /// <summary>
-    /// Options for sending data over the network.
-    /// </summary>
-    public enum SendDataOptions
-    {
-        /// <summary>
-        /// No special options - unreliable delivery.
-        /// </summary>
-        None,
-
-        /// <summary>
-        /// Reliable delivery - guarantees the data will arrive.
-        /// </summary>
-        Reliable,
-
-        /// <summary>
-        /// In-order delivery - data arrives in the order sent.
-        /// </summary>
-        InOrder,
-
-        /// <summary>
-        /// Reliable and in-order delivery.
-        /// </summary>
-        ReliableInOrder = Reliable | InOrder
-    }
-
-    /// <summary>
-    /// Current state of a network session.
-    /// </summary>
-    public enum NetworkSessionState
-    {
-        /// <summary>
-        /// Session is being created.
-        /// </summary>
-        Creating,
-
-        /// <summary>
-        /// Session is in the lobby, waiting for players.
-        /// </summary>
-        Lobby,
-
-        /// <summary>
-        /// Session is in an active game.
-        /// </summary>
-        Playing,
-
-        /// <summary>
-        /// Session has ended.
-        /// </summary>
-        Ended
-    }
-
-    /// <summary>
-    /// Defines the different types of gamers in a networked session.
-    /// </summary>
-    public enum GamerType
-    {
-        /// <summary>
-        /// A local gamer.
-        /// </summary>
-        Local,
-
-        /// <summary>
-        /// A remote gamer.
-        /// </summary>
-        Remote
-    }
-}
-
-namespace Microsoft.Xna.Framework.Net
-{
-    /// <summary>
-    /// Exception thrown when network operations fail.
-    /// </summary>
-    public class NetworkException : Exception
-    {
-        public NetworkException() : base() { }
-        public NetworkException(string message) : base(message) { }
-        public NetworkException(string message, Exception innerException) : base(message, innerException) { }
-    }
-
-    /// <summary>
-    /// Exception thrown when gamer privilege operations fail.
-    /// </summary>
-    public class GamerPrivilegeException : Exception
-    {
-        public GamerPrivilegeException() : base() { }
-        public GamerPrivilegeException(string message) : base(message) { }
-        public GamerPrivilegeException(string message, Exception innerException) : base(message, innerException) { }
-    }
-
-    /// <summary>
-    /// Exception thrown when network session join operations fail.
-    /// </summary>
-    public class NetworkSessionJoinException : Exception
-    {
-        /// <summary>
-        /// Gets the join error type.
-        /// </summary>
-        public NetworkSessionJoinError JoinError { get; }
-
-        public NetworkSessionJoinException() : base() 
-        {
-            JoinError = NetworkSessionJoinError.Unknown;
-        }
-
-        public NetworkSessionJoinException(string message) : base(message) 
-        {
-            JoinError = NetworkSessionJoinError.Unknown;
-        }
-
-        public NetworkSessionJoinException(string message, Exception innerException) : base(message, innerException) 
-        {
-            JoinError = NetworkSessionJoinError.Unknown;
-        }
-
-        public NetworkSessionJoinException(NetworkSessionJoinError joinError) : base() 
-        {
-            JoinError = joinError;
-        }
-
-        public NetworkSessionJoinException(string message, NetworkSessionJoinError joinError) : base(message) 
-        {
-            JoinError = joinError;
-        }
-    }
-
-    /// <summary>
-    /// Types of network session join errors.
-    /// </summary>
-    public enum NetworkSessionJoinError
-    {
-        /// <summary>
-        /// Unknown error.
-        /// </summary>
-        Unknown,
-
-        /// <summary>
-        /// Session is full.
-        /// </summary>
-        SessionFull,
-
-        /// <summary>
-        /// Session not found.
-        /// </summary>
-        SessionNotFound,
-
-        /// <summary>
-        /// Session not joinable.
-        /// </summary>
-        SessionNotJoinable
-    }
-}

+ 18 - 0
MonoGame.Xna.Framework.Net/Net/Enums/GamerType.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Defines the different types of gamers in a networked session.
+	/// </summary>
+	public enum GamerType
+    {
+        /// <summary>
+        /// A local gamer.
+        /// </summary>
+        Local,
+
+        /// <summary>
+        /// A remote gamer.
+        /// </summary>
+        Remote
+    }
+}

+ 36 - 0
MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionEndReason.cs

@@ -0,0 +1,36 @@
+using System;
+
+namespace Microsoft.Xna.Framework.Net
+{
+
+    /// <summary>
+    /// Reasons why a network session ended.
+    /// </summary>
+    public enum NetworkSessionEndReason
+    {
+        /// <summary>
+        /// The session ended normally.
+        /// </summary>
+        ClientSignedOut,
+
+        /// <summary>
+        /// The host ended the session.
+        /// </summary>
+        HostEndedSession,
+
+		/// <summary>
+		/// The host removed the user.
+		/// </summary>
+		RemovedByHost,
+
+		/// <summary>
+		/// The session was disconnected.
+		/// </summary>
+		Disconnected,
+
+        /// <summary>
+        /// A network error occurred.
+        /// </summary>
+        NetworkError
+    }
+}

+ 32 - 0
MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionJoinError.cs

@@ -0,0 +1,32 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace Microsoft.Xna.Framework.Net
+{
+
+    /// <summary>
+    /// Types of network session join errors.
+    /// </summary>
+    public enum NetworkSessionJoinError
+    {
+        /// <summary>
+        /// Unknown error.
+        /// </summary>
+        Unknown,
+
+        /// <summary>
+        /// Session is full.
+        /// </summary>
+        SessionFull,
+
+        /// <summary>
+        /// Session not found.
+        /// </summary>
+        SessionNotFound,
+
+        /// <summary>
+        /// Session not joinable.
+        /// </summary>
+        SessionNotJoinable
+    }
+}

+ 28 - 0
MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionState.cs

@@ -0,0 +1,28 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Current state of a network session.
+	/// </summary>
+	public enum NetworkSessionState
+    {
+        /// <summary>
+        /// Session is being created.
+        /// </summary>
+        Creating,
+
+        /// <summary>
+        /// Session is in the lobby, waiting for players.
+        /// </summary>
+        Lobby,
+
+        /// <summary>
+        /// Session is in an active game.
+        /// </summary>
+        Playing,
+
+        /// <summary>
+        /// Session has ended.
+        /// </summary>
+        Ended
+    }
+}

+ 28 - 0
MonoGame.Xna.Framework.Net/Net/Enums/NetworkSessionType.cs

@@ -0,0 +1,28 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Types of network sessions that can be created or joined.
+	/// </summary>
+	public enum NetworkSessionType
+    {
+        /// <summary>
+        /// Local session for single-machine multiplayer.
+        /// </summary>
+        Local,
+
+        /// <summary>
+        /// System link session for LAN multiplayer.
+        /// </summary>
+        SystemLink,
+
+        /// <summary>
+        /// Player match session for online multiplayer.
+        /// </summary>
+        PlayerMatch,
+
+        /// <summary>
+        /// Ranked session for competitive online multiplayer.
+        /// </summary>
+        Ranked
+    }
+}

+ 28 - 0
MonoGame.Xna.Framework.Net/Net/Enums/SendDataOptions.cs

@@ -0,0 +1,28 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Options for sending data over the network.
+	/// </summary>
+	public enum SendDataOptions
+    {
+        /// <summary>
+        /// No special options - unreliable delivery.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// Reliable delivery - guarantees the data will arrive.
+        /// </summary>
+        Reliable,
+
+        /// <summary>
+        /// In-order delivery - data arrives in the order sent.
+        /// </summary>
+        InOrder,
+
+        /// <summary>
+        /// Reliable and in-order delivery.
+        /// </summary>
+        ReliableInOrder = Reliable | InOrder
+    }
+}

+ 0 - 122
MonoGame.Xna.Framework.Net/Net/EventArgs.cs

@@ -1,122 +0,0 @@
-using Microsoft.Xna.Framework.GamerServices;
-using System;
-
-namespace Microsoft.Xna.Framework.Net
-{
-    /// <summary>
-    /// Event arguments for when an invite is accepted.
-    /// </summary>
-    public class InviteAcceptedEventArgs : EventArgs
-    {
-        /// <summary>
-        /// Gets the gamer who accepted the invite.
-        /// </summary>
-        public SignedInGamer Gamer { get; internal set; }
-
-        /// <summary>
-        /// Gets whether the operation completed successfully.
-        /// </summary>
-        public bool IsSignedInGamer { get; internal set; }
-
-        internal InviteAcceptedEventArgs(SignedInGamer gamer, bool isSignedInGamer)
-        {
-            Gamer = gamer;
-            IsSignedInGamer = isSignedInGamer;
-        }
-    }
-
-    /// <summary>
-    /// Event arguments for when a game ends.
-    /// </summary>
-    public class GameEndedEventArgs : EventArgs
-    {
-        internal GameEndedEventArgs() { }
-    }
-
-    /// <summary>
-    /// Event arguments for when a network session ends.
-    /// </summary>
-    public class NetworkSessionEndedEventArgs : EventArgs
-    {
-        /// <summary>
-        /// Gets the reason the session ended.
-        /// </summary>
-        public NetworkSessionEndReason EndReason { get; internal set; }
-
-        internal NetworkSessionEndedEventArgs(NetworkSessionEndReason endReason)
-        {
-            EndReason = endReason;
-        }
-    }
-
-    /// <summary>
-    /// Event arguments for when a gamer leaves the session.
-    /// </summary>
-    public class GamerLeftEventArgs : EventArgs
-    {
-        /// <summary>
-        /// Gets the gamer who left.
-        /// </summary>
-        public NetworkGamer Gamer { get; internal set; }
-
-        internal GamerLeftEventArgs(NetworkGamer gamer)
-        {
-            Gamer = gamer;
-        }
-    }
-
-    /// <summary>
-    /// Event arguments for when a game starts.
-    /// </summary>
-    public class GameStartedEventArgs : EventArgs
-    {
-        internal GameStartedEventArgs() { }
-    }
-
-    /// <summary>
-    /// Event arguments for when a gamer joins the session.
-    /// </summary>
-    public class GamerJoinedEventArgs : EventArgs
-    {
-        /// <summary>
-        /// Gets the gamer who joined.
-        /// </summary>
-        public NetworkGamer Gamer { get; internal set; }
-
-        internal GamerJoinedEventArgs(NetworkGamer gamer)
-        {
-            Gamer = gamer;
-        }
-    }
-
-    /// <summary>
-    /// Reasons why a network session ended.
-    /// </summary>
-    public enum NetworkSessionEndReason
-    {
-        /// <summary>
-        /// The session ended normally.
-        /// </summary>
-        ClientSignedOut,
-
-        /// <summary>
-        /// The host ended the session.
-        /// </summary>
-        HostEndedSession,
-
-		/// <summary>
-		/// The host removed the user.
-		/// </summary>
-		RemovedByHost,
-
-		/// <summary>
-		/// The session was disconnected.
-		/// </summary>
-		Disconnected,
-
-        /// <summary>
-        /// A network error occurred.
-        /// </summary>
-        NetworkError
-    }
-}

+ 10 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/GameEndedEventArgs.cs

@@ -0,0 +1,10 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when a game ends.
+	/// </summary>
+	public class GameEndedEventArgs : EventArgs
+    {
+        internal GameEndedEventArgs() { }
+    }
+}

+ 10 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/GameStartedEventArgs.cs

@@ -0,0 +1,10 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when a game starts.
+	/// </summary>
+	public class GameStartedEventArgs : EventArgs
+    {
+        internal GameStartedEventArgs() { }
+    }
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/GamerJoinedEventArgs.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when a gamer joins the session.
+	/// </summary>
+	public class GamerJoinedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the gamer who joined.
+        /// </summary>
+        public NetworkGamer Gamer { get; internal set; }
+
+        internal GamerJoinedEventArgs(NetworkGamer gamer)
+        {
+            Gamer = gamer;
+        }
+    }
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/GamerLeftEventArgs.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when a gamer leaves the session.
+	/// </summary>
+	public class GamerLeftEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the gamer who left.
+        /// </summary>
+        public NetworkGamer Gamer { get; internal set; }
+
+        internal GamerLeftEventArgs(NetworkGamer gamer)
+        {
+            Gamer = gamer;
+        }
+    }
+}

+ 26 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/InviteAcceptedEventArgs.cs

@@ -0,0 +1,26 @@
+using Microsoft.Xna.Framework.GamerServices;
+
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when an invite is accepted.
+	/// </summary>
+	public class InviteAcceptedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the gamer who accepted the invite.
+        /// </summary>
+        public SignedInGamer Gamer { get; internal set; }
+
+        /// <summary>
+        /// Gets whether the operation completed successfully.
+        /// </summary>
+        public bool IsSignedInGamer { get; internal set; }
+
+        internal InviteAcceptedEventArgs(SignedInGamer gamer, bool isSignedInGamer)
+        {
+            Gamer = gamer;
+            IsSignedInGamer = isSignedInGamer;
+        }
+    }
+}

+ 17 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/MessageReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Net;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    public class MessageReceivedEventArgs : EventArgs
+    {
+        public INetworkMessage Message { get; }
+        public IPEndPoint RemoteEndPoint { get; }
+
+        public MessageReceivedEventArgs(INetworkMessage message, IPEndPoint remoteEndPoint)
+        {
+            Message = message;
+            RemoteEndPoint = remoteEndPoint;
+        }
+    }
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/Net/EventArgs/NetworkSessionEndedEventArgs.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Event arguments for when a network session ends.
+	/// </summary>
+	public class NetworkSessionEndedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Gets the reason the session ended.
+        /// </summary>
+        public NetworkSessionEndReason EndReason { get; internal set; }
+
+        internal NetworkSessionEndedEventArgs(NetworkSessionEndReason endReason)
+        {
+            EndReason = endReason;
+        }
+    }
+}

+ 12 - 0
MonoGame.Xna.Framework.Net/Net/Exceptions/GamerPrivilegeException.cs

@@ -0,0 +1,12 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Exception thrown when gamer privilege operations fail.
+	/// </summary>
+	public class GamerPrivilegeException : Exception
+    {
+        public GamerPrivilegeException() : base() { }
+        public GamerPrivilegeException(string message) : base(message) { }
+        public GamerPrivilegeException(string message, Exception innerException) : base(message, innerException) { }
+    }
+}

+ 12 - 0
MonoGame.Xna.Framework.Net/Net/Exceptions/NetworkException.cs

@@ -0,0 +1,12 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Exception thrown when network operations fail.
+	/// </summary>
+	public class NetworkException : Exception
+    {
+        public NetworkException() : base() { }
+        public NetworkException(string message) : base(message) { }
+        public NetworkException(string message, Exception innerException) : base(message, innerException) { }
+    }
+}

+ 38 - 0
MonoGame.Xna.Framework.Net/Net/Exceptions/NetworkSessionJoinException.cs

@@ -0,0 +1,38 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Exception thrown when network session join operations fail.
+	/// </summary>
+	public class NetworkSessionJoinException : Exception
+    {
+        /// <summary>
+        /// Gets the join error type.
+        /// </summary>
+        public NetworkSessionJoinError JoinError { get; }
+
+        public NetworkSessionJoinException() : base() 
+        {
+            JoinError = NetworkSessionJoinError.Unknown;
+        }
+
+        public NetworkSessionJoinException(string message) : base(message) 
+        {
+            JoinError = NetworkSessionJoinError.Unknown;
+        }
+
+        public NetworkSessionJoinException(string message, Exception innerException) : base(message, innerException) 
+        {
+            JoinError = NetworkSessionJoinError.Unknown;
+        }
+
+        public NetworkSessionJoinException(NetworkSessionJoinError joinError) : base() 
+        {
+            JoinError = joinError;
+        }
+
+        public NetworkSessionJoinException(string message, NetworkSessionJoinError joinError) : base(message) 
+        {
+            JoinError = joinError;
+        }
+    }
+}

+ 43 - 0
MonoGame.Xna.Framework.Net/Net/GamerCollection.cs

@@ -0,0 +1,43 @@
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Collection of network gamers in a session.
+	/// </summary>
+	public class GamerCollection : ReadOnlyCollection<NetworkGamer>
+    {
+        internal GamerCollection(IList<NetworkGamer> list) : base(list) { }
+
+        /// <summary>
+        /// Finds a gamer by their gamertag.
+        /// </summary>
+        /// <param name="gamertag">The gamertag to search for.</param>
+        /// <returns>The gamer with the specified gamertag, or null if not found.</returns>
+        public NetworkGamer FindGamerById(string id)
+        {
+            foreach (var gamer in this)
+            {
+                if (gamer.Id == id)
+                    return gamer;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Gets the host gamer of the session.
+        /// </summary>
+        public NetworkGamer Host
+        {
+            get
+            {
+                foreach (var gamer in this)
+                {
+                    if (gamer.IsHost)
+                        return gamer;
+                }
+                return null;
+            }
+        }
+    }
+}

+ 25 - 0
MonoGame.Xna.Framework.Net/Net/INetworkMessage.cs

@@ -0,0 +1,25 @@
+namespace Microsoft.Xna.Framework.Net
+{
+    /// <summary>
+    /// Represents a network message that can be serialized and deserialized for transmission.
+    /// </summary>
+    public interface INetworkMessage
+    {
+        /// <summary>
+        /// Gets the type identifier of the network message.
+        /// </summary>
+        byte MessageType { get; }
+
+        /// <summary>
+        /// Serializes the network message into a <see cref="PacketWriter"/> for transmission.
+        /// </summary>
+        /// <param name="writer">The <see cref="PacketWriter"/> to serialize the message into.</param>
+        void Serialize(PacketWriter writer);
+
+        /// <summary>
+        /// Deserializes the network message from a <see cref="PacketReader"/>.
+        /// </summary>
+        /// <param name="reader">The <see cref="PacketReader"/> to deserialize the message from.</param>
+        void Deserialize(PacketReader reader);
+    }
+}

+ 38 - 0
MonoGame.Xna.Framework.Net/Net/INetworkTransport.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Net;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    /// <summary>
+    /// Defines the contract for a network transport layer, providing methods for sending and receiving data.
+    /// </summary>
+    public interface INetworkTransport : IDisposable
+    {
+        /// <summary>
+        /// Receives data from the network in a blocking manner.
+        /// </summary>
+        /// <returns>A tuple containing the received data and the sender's endpoint.</returns>
+        (byte[] data, IPEndPoint sender) Receive();
+
+        /// <summary>
+        /// Receives data from the network asynchronously.
+        /// </summary>
+        /// <returns>A task that represents the asynchronous operation. The result contains the received data and the sender's endpoint.</returns>
+        Task<(byte[] data, IPEndPoint sender)> ReceiveAsync();
+
+        /// <summary>
+        /// Sends data to the specified endpoint in a blocking manner.
+        /// </summary>
+        /// <param name="data">The data to send.</param>
+        /// <param name="endpoint">The endpoint to send the data to.</param>
+        void Send(byte[] data, IPEndPoint endpoint);
+
+        /// <summary>
+        /// Sends data to the specified endpoint asynchronously.
+        /// </summary>
+        /// <param name="data">The data to send.</param>
+        /// <param name="endpoint">The endpoint to send the data to.</param>
+        /// <returns>A task that represents the asynchronous operation.</returns>
+        Task SendAsync(byte[] data, IPEndPoint endpoint);
+    }
+}

+ 43 - 0
MonoGame.Xna.Framework.Net/Net/LocalGamerCollection.cs

@@ -0,0 +1,43 @@
+using System.Collections.ObjectModel;
+
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Collection of local network gamers in a session.
+	/// </summary>
+	public class LocalGamerCollection : ReadOnlyCollection<LocalNetworkGamer>
+    {
+        internal LocalGamerCollection(IList<LocalNetworkGamer> list) : base(list) { }
+
+        /// <summary>
+        /// Finds a local gamer by their ID.
+        /// </summary>
+        /// <param name="id">The ID to search for.</param>
+        /// <returns>The local gamer with the specified ID, or null if not found.</returns>
+        public LocalNetworkGamer FindGamerById(string id)
+        {
+            foreach (var gamer in this)
+            {
+                if (gamer.Id == id)
+                    return gamer;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Gets the host gamer of the session (if local).
+        /// </summary>
+        public LocalNetworkGamer Host
+        {
+            get
+            {
+                foreach (var gamer in this)
+                {
+                    if (gamer.IsHost)
+                        return gamer;
+                }
+                return null;
+            }
+        }
+    }
+}

+ 27 - 0
MonoGame.Xna.Framework.Net/Net/LocalNetworkGamer.cs

@@ -0,0 +1,27 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Represents a local network gamer.
+	/// </summary>
+	public class LocalNetworkGamer : NetworkGamer
+    {
+        internal LocalNetworkGamer(NetworkSession session, string id, bool isHost, string gamertag)
+            : base(session, id, true, isHost, gamertag)
+        {
+        }
+
+        /// <summary>
+        /// Gets whether this local gamer is sending data.
+        /// </summary>
+        public bool IsSendingData => false; // Mock implementation
+
+        /// <summary>
+        /// Enables or disables sending data for this local gamer.
+        /// </summary>
+        /// <param name="options">Send data options.</param>
+        public void EnableSendData(SendDataOptions options)
+        {
+            // Mock implementation
+        }
+    }
+}

+ 45 - 0
MonoGame.Xna.Framework.Net/Net/LocalSessionRegistry.cs

@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    internal static class LocalSessionRegistry
+    {
+        private static readonly List<NetworkSession> sessions = new List<NetworkSession>();
+
+        public static void RegisterSession(NetworkSession session)
+        {
+            lock (sessions)
+            {
+                sessions.Add(session);
+            }
+        }
+
+        public static IEnumerable<AvailableNetworkSession> FindSessions(int maxLocalGamers)
+        {
+            lock (sessions)
+            {
+                return sessions
+                    .Where(s => s.MaxGamers >= maxLocalGamers && s.SessionState == NetworkSessionState.Lobby)
+                    .Select(s => new AvailableNetworkSession(
+                        sessionName: "LocalSession",
+                        hostGamertag: s.Host?.Gamertag ?? "Host",
+                        currentGamerCount: s.AllGamers.Count,
+                        openPublicGamerSlots: s.MaxGamers - s.AllGamers.Count,
+                        openPrivateGamerSlots: s.PrivateGamerSlots,
+                        sessionType: s.SessionType,
+                        sessionProperties: s.SessionProperties,
+                        sessionId: s.sessionId))
+                    .ToList();
+            }
+        }
+
+        public static NetworkSession GetSessionById(string sessionId)
+        {
+            lock (sessions)
+            {
+                return sessions.FirstOrDefault(s => s.sessionId == sessionId);
+            }
+        }
+    }
+}

+ 102 - 127
MonoGame.Xna.Framework.Net/Net/NetworkGamer.cs

@@ -1,7 +1,8 @@
+using Microsoft.Xna.Framework.GamerServices;
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using Microsoft.Xna.Framework.GamerServices;
+using System.Diagnostics;
+using System.Net;
 
 namespace Microsoft.Xna.Framework.Net
 {
@@ -22,15 +23,6 @@ namespace Microsoft.Xna.Framework.Net
         // Network performance properties
         private TimeSpan roundtripTime = TimeSpan.Zero;
 
-        internal NetworkGamer(NetworkSession session, string id, bool isLocal, bool isHost, string gamertag)
-        {
-            this.session = session;
-            this.id = id;
-            this.isLocal = isLocal;
-            this.isHost = isHost;
-            this.gamertag = gamertag;
-        }
-
         /// <summary>
         /// Gets the unique identifier for this gamer.
         /// </summary>
@@ -92,12 +84,12 @@ namespace Microsoft.Xna.Framework.Net
         /// <summary>
         /// Gets whether data is available to be received from this gamer.
         /// </summary>
-        public bool IsDataAvailable => false; // Mock implementation
+        public bool IsDataAvailable => incomingPackets.Count > 0;
 
         /// <summary>
         /// Gets the SignedInGamer associated with this NetworkGamer.
         /// </summary>
-        public GamerServices.SignedInGamer SignedInGamer => GamerServices.SignedInGamer.Current;
+        public SignedInGamer SignedInGamer => SignedInGamer.Current;
 
         /// <summary>
         /// Gets whether this gamer is muted by the local user.
@@ -114,6 +106,18 @@ namespace Microsoft.Xna.Framework.Net
         /// </summary>
         public bool HasVoice => false; // Mock implementation
 
+        // Add to NetworkGamer class
+        private readonly Queue<(byte[] Data, NetworkGamer Sender)> incomingPackets = new Queue<(byte[], NetworkGamer)>();
+
+        internal NetworkGamer(NetworkSession session, string id, bool isLocal, bool isHost, string gamertag)
+        {
+            this.session = session;
+            this.id = id;
+            this.isLocal = isLocal;
+            this.isHost = isHost;
+            this.gamertag = gamertag;
+        }
+
         /// <summary>
         /// Receives data from this gamer.
         /// </summary>
@@ -122,8 +126,20 @@ namespace Microsoft.Xna.Framework.Net
         /// <returns>The number of bytes received.</returns>
         public int ReceiveData(byte[] data, out NetworkGamer sender)
         {
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+
             sender = null;
-            return 0; // Mock implementation
+
+            // Check if data is available
+            if (!IsDataAvailable)
+                return 0;
+
+            var packet = incomingPackets.Dequeue();
+            sender = packet.Sender;
+            int length = Math.Min(data.Length, packet.Data.Length);
+            Array.Copy(packet.Data, data, length);
+            return length;
         }
 
         /// <summary>
@@ -132,10 +148,23 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="data">Array to receive the data into.</param>
         /// <param name="sender">The gamer who sent the data.</param>
         /// <returns>The number of bytes received.</returns>
-        public int ReceiveData(PacketReader data, out NetworkGamer sender)
+        public int ReceiveData(out PacketReader reader, out NetworkGamer sender)
         {
+            // Ensure the session is valid
+            if (session == null)
+                throw new InvalidOperationException("Network session is not initialized.");
+
+            reader = null;
             sender = null;
-            return 0; // Mock implementation
+
+            // Check if data is available
+            if (!IsDataAvailable)
+                return 0;
+
+            var packet = incomingPackets.Dequeue();
+            sender = packet.Sender;
+            reader = new PacketReader(packet.Data);
+            return packet.Data.Length;
         }
 
         /// <summary>
@@ -145,7 +174,13 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="options">Send options.</param>
         public void SendData(byte[] data, SendDataOptions options)
         {
-            // Mock implementation
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+
+            if (!Enum.IsDefined(typeof(SendDataOptions), options))
+                throw new ArgumentOutOfRangeException(nameof(options), "Invalid send data option.");
+
+            SendDataInternal(data, options, session.AllGamers);
         }
 
         /// <summary>
@@ -156,7 +191,13 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="recipients">The gamers to send to.</param>
         public void SendData(byte[] data, SendDataOptions options, IEnumerable<NetworkGamer> recipients)
         {
-            // Mock implementation
+            if (data == null)
+                throw new ArgumentNullException(nameof(data));
+
+            if (!Enum.IsDefined(typeof(SendDataOptions), options))
+                throw new ArgumentOutOfRangeException(nameof(options), "Invalid send data option.");
+
+            SendDataInternal(data, options, recipients);
         }
 
         /// <summary>
@@ -166,7 +207,14 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="options">Send options.</param>
         public void SendData(PacketWriter data, SendDataOptions options)
         {
-            // Mock implementation
+            if (data == null)
+                throw new ArgumentNullException(nameof(data), "PacketWriter cannot be null.");
+
+            if (!Enum.IsDefined(typeof(SendDataOptions), options))
+                throw new ArgumentOutOfRangeException(nameof(options), "Invalid send data option.");
+
+            byte[] serializedData = data.GetData();
+            SendDataInternal(serializedData, options, session.AllGamers);
         }
 
         /// <summary>
@@ -177,7 +225,14 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="recipients">The gamers to send to.</param>
         public void SendData(PacketWriter data, SendDataOptions options, IEnumerable<NetworkGamer> recipients)
         {
-            // Mock implementation
+            if (data == null)
+                throw new ArgumentNullException(nameof(data), "PacketWriter cannot be null.");
+
+            if (recipients == null)
+                throw new ArgumentNullException(nameof(recipients));
+
+            byte[] serializedData = data.GetData();
+            SendDataInternal(serializedData, options, recipients);
         }
 
         /// <summary>
@@ -188,7 +243,6 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="recipient">The gamer to send to.</param>
         public void SendData(PacketWriter data, SendDataOptions options, NetworkGamer recipient)
         {
-            // Mock implementation - convert single gamer to enumerable
             SendData(data, options, new[] { recipient });
         }
 
@@ -200,7 +254,6 @@ namespace Microsoft.Xna.Framework.Net
         /// <param name="recipient">The gamer to send to.</param>
         public void SendData(byte[] data, SendDataOptions options, NetworkGamer recipient)
         {
-            // Mock implementation - convert single gamer to enumerable
             SendData(data, options, new[] { recipient });
         }
 
@@ -235,10 +288,10 @@ namespace Microsoft.Xna.Framework.Net
         /// <summary>
         /// Gets the local gamer.
         /// </summary>
-        public static NetworkGamer LocalGamer 
-        { 
-            get => localGamer; 
-            internal set => localGamer = value; 
+        public static NetworkGamer LocalGamer
+        {
+            get => localGamer;
+            internal set => localGamer = value;
         }
 
         /// <summary>
@@ -248,119 +301,41 @@ namespace Microsoft.Xna.Framework.Net
         {
             return gamer?.SignedInGamer;
         }
-    }
 
-    /// <summary>
-    /// Collection of network gamers in a session.
-    /// </summary>
-    public class GamerCollection : ReadOnlyCollection<NetworkGamer>
-    {
-        internal GamerCollection(IList<NetworkGamer> list) : base(list) { }
-
-        /// <summary>
-        /// Finds a gamer by their gamertag.
-        /// </summary>
-        /// <param name="gamertag">The gamertag to search for.</param>
-        /// <returns>The gamer with the specified gamertag, or null if not found.</returns>
-        public NetworkGamer FindGamerById(string id)
+        private void SendDataInternal(byte[] data, SendDataOptions options, IEnumerable<NetworkGamer> recipients)
         {
-            foreach (var gamer in this)
+            switch (session.SessionType)
             {
-                if (gamer.Id == id)
-                    return gamer;
-            }
-            return null;
-        }
+                case NetworkSessionType.SystemLink:
+                    foreach (var recipient in recipients)
+                    {
+                        session.SendDataToGamer(recipient, data, options);
+                    }
+                    break;
 
-        /// <summary>
-        /// Gets the host gamer of the session.
-        /// </summary>
-        public NetworkGamer Host
-        {
-            get
-            {
-                foreach (var gamer in this)
-                {
-                    if (gamer.IsHost)
-                        return gamer;
-                }
-                return null;
-            }
-        }
-    }
+                case NetworkSessionType.Local:
+                    // TODO: session.ProcessIncomingMessages(); // Simulate message delivery
+                    break;
 
-    /// <summary>
-    /// Represents a machine in a network session.
-    /// </summary>
-    public class NetworkMachine
-    {
-        /// <summary>
-        /// Gets the gamers on this machine.
-        /// </summary>
-        public GamerCollection Gamers { get; } = new GamerCollection(new List<NetworkGamer>());
-    }
+                case NetworkSessionType.PlayerMatch:
+                    // Placeholder for future implementation
+                    throw new NotImplementedException("PlayerMatch session type is not yet supported.");
 
-    /// <summary>
-    /// Collection of local network gamers in a session.
-    /// </summary>
-    public class LocalGamerCollection : ReadOnlyCollection<LocalNetworkGamer>
-    {
-        internal LocalGamerCollection(IList<LocalNetworkGamer> list) : base(list) { }
+                case NetworkSessionType.Ranked:
+                    // Placeholder for future implementation
+                    throw new NotImplementedException("Ranked session type is not yet supported.");
 
-        /// <summary>
-        /// Finds a local gamer by their ID.
-        /// </summary>
-        /// <param name="id">The ID to search for.</param>
-        /// <returns>The local gamer with the specified ID, or null if not found.</returns>
-        public LocalNetworkGamer FindGamerById(string id)
-        {
-            foreach (var gamer in this)
-            {
-                if (gamer.Id == id)
-                    return gamer;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(session.SessionType), $"Unsupported session type: {session.SessionType}");
             }
-            return null;
         }
 
-        /// <summary>
-        /// Gets the host gamer of the session (if local).
-        /// </summary>
-        public LocalNetworkGamer Host
+        internal void EnqueueIncomingPacket(byte[] data, NetworkGamer sender)
         {
-            get
+            lock (incomingPackets)
             {
-                foreach (var gamer in this)
-                {
-                    if (gamer.IsHost)
-                        return gamer;
-                }
-                return null;
+                incomingPackets.Enqueue((data, sender));
             }
         }
     }
-
-    /// <summary>
-    /// Represents a local network gamer.
-    /// </summary>
-    public class LocalNetworkGamer : NetworkGamer
-    {
-        internal LocalNetworkGamer(NetworkSession session, string id, bool isHost, string gamertag)
-            : base(session, id, true, isHost, gamertag)
-        {
-        }
-
-        /// <summary>
-        /// Gets whether this local gamer is sending data.
-        /// </summary>
-        public bool IsSendingData => false; // Mock implementation
-
-        /// <summary>
-        /// Enables or disables sending data for this local gamer.
-        /// </summary>
-        /// <param name="options">Send data options.</param>
-        public void EnableSendData(SendDataOptions options)
-        {
-            // Mock implementation
-        }
-    }
-}
+}

+ 13 - 0
MonoGame.Xna.Framework.Net/Net/NetworkMachine.cs

@@ -0,0 +1,13 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Represents a machine in a network session.
+	/// </summary>
+	public class NetworkMachine
+    {
+        /// <summary>
+        /// Gets the gamers on this machine.
+        /// </summary>
+        public GamerCollection Gamers { get; } = new GamerCollection(new List<NetworkGamer>());
+    }
+}

+ 33 - 0
MonoGame.Xna.Framework.Net/Net/NetworkMessageRegistry.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    /// <summary>
+    /// Provides a registry for network message types, allowing messages to be registered and created dynamically.
+    /// </summary>
+    public static class NetworkMessageRegistry
+    {
+        private static readonly Dictionary<byte, Func<INetworkMessage>> registry = new();
+
+        /// <summary>
+        /// Registers a network message type with the specified type identifier.
+        /// </summary>
+        /// <typeparam name="T">The type of the network message to register. Must implement <see cref="INetworkMessage"/> and have a parameterless constructor.</typeparam>
+        /// <param name="typeId">The unique identifier for the message type.</param>
+        public static void Register<T>(byte typeId) where T : INetworkMessage, new()
+        {
+            registry[typeId] = () => new T();
+        }
+
+        /// <summary>
+        /// Creates a network message instance based on the specified type identifier.
+        /// </summary>
+        /// <param name="typeId">The unique identifier for the message type.</param>
+        /// <returns>An instance of the network message, or <c>null</c> if the type identifier is not registered.</returns>
+        public static INetworkMessage CreateMessage(byte typeId)
+        {
+            return registry.TryGetValue(typeId, out var ctor) ? ctor() : null;
+        }
+    }
+}

+ 299 - 259
MonoGame.Xna.Framework.Net/Net/NetworkSession.cs

@@ -1,7 +1,8 @@
 using System;
+using System.Net;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
-using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
@@ -12,21 +13,23 @@ namespace Microsoft.Xna.Framework.Net
     /// <summary>
     /// Represents a network session for multiplayer gaming.
     /// </summary>
-    public class NetworkSession : IDisposable
+    public class NetworkSession : IDisposable, IAsyncDisposable
     {
+        // Event for received messages
+        public event EventHandler<MessageReceivedEventArgs> MessageReceived;
         private readonly List<NetworkGamer> gamers;
         private readonly GamerCollection gamerCollection;
         private readonly NetworkSessionType sessionType;
         private readonly int maxGamers;
         private readonly int privateGamerSlots;
-        private readonly UdpClient udpClient;
         private readonly Dictionary<string, IPEndPoint> gamerEndpoints;
         private readonly object lockObject = new object();
-        
-        private NetworkSessionState sessionState;
+
+        private INetworkTransport networkTransport;
+        internal NetworkSessionState sessionState;
         private bool disposed;
         private bool isHost;
-        private string sessionId;
+        internal string sessionId;
         private Task receiveTask;
         private CancellationTokenSource cancellationTokenSource;
 
@@ -40,30 +43,56 @@ namespace Microsoft.Xna.Framework.Net
         // Static event for invite acceptance
         public static event EventHandler<InviteAcceptedEventArgs> InviteAccepted;
 
+        /// <summary>
+        /// Allows changing the network transport implementation.
+        /// </summary>
+        public INetworkTransport NetworkTransport
+        {
+            get => networkTransport;
+            set => networkTransport = value ?? throw new ArgumentNullException(nameof(value));
+        }
+
         /// <summary>
         /// Initializes a new NetworkSession.
         /// </summary>
         private NetworkSession(NetworkSessionType sessionType, int maxGamers, int privateGamerSlots, bool isHost)
         {
+            // Register message types (can be moved to static constructor)
+            NetworkMessageRegistry.Register<PlayerMoveMessage>(1);
+
             this.sessionType = sessionType;
             this.maxGamers = maxGamers;
             this.privateGamerSlots = privateGamerSlots;
             this.isHost = isHost;
             this.sessionId = Guid.NewGuid().ToString();
             this.sessionState = NetworkSessionState.Creating;
-            
+
             gamers = new List<NetworkGamer>();
             gamerCollection = new GamerCollection(gamers);
             gamerEndpoints = new Dictionary<string, IPEndPoint>();
-            
-            // Initialize UDP client for networking
-            udpClient = new UdpClient();
+
+            networkTransport = new UdpTransport();
             cancellationTokenSource = new CancellationTokenSource();
-            
+
             // Add local gamer
-            var localGamer = new LocalNetworkGamer(this, Guid.NewGuid().ToString(), isHost, SignedInGamer.Current?.Gamertag ?? "Player");
+            var gamerGuid = Guid.NewGuid().ToString();
+            var localGamer = new LocalNetworkGamer(this, gamerGuid, isHost, $"{SignedInGamer.Current?.Gamertag ?? "Player"}_{gamerGuid.Substring(0, 8)}");
             NetworkGamer.LocalGamer = localGamer;
             AddGamer(localGamer);
+
+            // Start receive loop for SystemLink sessions
+            if (sessionType == NetworkSessionType.SystemLink)
+            {
+                receiveTask = Task.Run(() => ReceiveLoopAsync(cancellationTokenSource.Token));
+            }
+        }
+
+        // Internal constructor for SystemLink join
+        internal NetworkSession(NetworkSessionType sessionType, int maxGamers, int privateGamerSlots, bool isHost, string sessionId)
+            : this(sessionType, maxGamers, privateGamerSlots, isHost)
+        {
+            this.sessionId = sessionId;
+            this.sessionState = NetworkSessionState.Lobby;
         }
 
         /// <summary>
@@ -150,10 +179,24 @@ namespace Microsoft.Xna.Framework.Net
         /// </summary>
         public bool AllowJoinInProgress { get; set; } = true;
 
+        private IDictionary<string, object> sessionProperties = new Dictionary<string, object>();
         /// <summary>
         /// Gets or sets custom session properties.
         /// </summary>
-        public IDictionary<string, object> SessionProperties { get; set; } = new Dictionary<string, object>();
+        public IDictionary<string, object> SessionProperties
+        {
+            get => sessionProperties;
+            set
+            {
+                sessionProperties = value ?? throw new ArgumentNullException(nameof(value));
+
+                // Automatically broadcast changes if this machine is the host
+                if (IsHost)
+                {
+                    BroadcastSessionProperties();
+                }
+            }
+        }
 
         // Simulation properties for testing network conditions
         private TimeSpan simulatedLatency = TimeSpan.Zero;
@@ -162,300 +205,153 @@ namespace Microsoft.Xna.Framework.Net
         /// <summary>
         /// Gets or sets the simulated network latency for testing purposes.
         /// </summary>
-        public TimeSpan SimulatedLatency 
-        { 
-            get => simulatedLatency; 
-            set => simulatedLatency = value; 
+        public TimeSpan SimulatedLatency
+        {
+            get => simulatedLatency;
+            set => simulatedLatency = value;
         }
 
         /// <summary>
         /// Gets or sets the simulated packet loss percentage for testing purposes.
         /// </summary>
-        public float SimulatedPacketLoss 
-        { 
-            get => simulatedPacketLoss; 
-            set => simulatedPacketLoss = Math.Max(0.0f, Math.Min(1.0f, value)); 
-        }
-
-        /// <summary>
-        /// Begins creating a new network session.
-        /// </summary>
-		public static IAsyncResult BeginCreate(
-            NetworkSessionType sessionType,
-            IEnumerable<SignedInGamer> localGamers,
-            int maxGamers,
-            int privateGamerSlots,
-            NetworkSessionProperties sessionProperties,
-            AsyncCallback callback,
-            object asyncState
-        )
-		{
-			if (maxGamers < 1 || maxGamers > 4)
-			{
-				throw new ArgumentOutOfRangeException(nameof(maxGamers));
-			}
-			if (privateGamerSlots < 0 || privateGamerSlots > maxGamers)
-			{
-                throw new ArgumentOutOfRangeException(nameof(privateGamerSlots));
-			}
-
-            throw new NotImplementedException("Async creation with callback is not implemented yet.");
-		}
-
-		public static IAsyncResult BeginCreate(
-            NetworkSessionType sessionType,
-            int maxLocalGamers,
-            int maxGamers,
-            AsyncCallback callback = null,
-            object asyncState = null
-        )
-		{
-            if (maxLocalGamers < 1 || maxLocalGamers > 4)
-            {
-                throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
-            }
-
-			throw new NotImplementedException("Async creation with callback is not implemented yet.");
-		}
-
-		public static IAsyncResult BeginCreate(
-            NetworkSessionType sessionType,
-            int maxLocalGamers,
-            int maxGamers,
-            int privateGamerSlots,
-            NetworkSessionProperties sessionProperties,
-            AsyncCallback callback = null,
-            object asyncState = null
-        )
-		{
-			if (maxLocalGamers < 1 || maxLocalGamers > 4)
-			{
-				throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
-			}
-
-			if (maxLocalGamers < 1 || maxLocalGamers > 4)
-			{
-				throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
-			}
-			if (privateGamerSlots < 0 || privateGamerSlots > maxGamers)
-			{
-				throw new ArgumentOutOfRangeException(nameof(privateGamerSlots));
-			}
-
-			var task = Task.Run(() =>
-			{
-				var session = new NetworkSession(sessionType, maxGamers, privateGamerSlots, true);
-				session.sessionState = NetworkSessionState.Lobby;
-				return session;
-			});
-
-			return new AsyncResultWrapper<NetworkSession>(task, null, null);
-		}
-
-		/// <summary>
-		/// Ends the create session operation.
-		/// </summary>
-		public static NetworkSession EndCreate(IAsyncResult asyncResult)
-        {
-            if (asyncResult is AsyncResultWrapper<NetworkSession> wrapper)
-            {
-                return wrapper.GetResult();
-            }
-            throw new ArgumentException("Invalid async result", nameof(asyncResult));
+        public float SimulatedPacketLoss
+        {
+            get => simulatedPacketLoss;
+            set => simulatedPacketLoss = Math.Max(0.0f, Math.Min(1.0f, value));
         }
 
         /// <summary>
-        /// Begins finding available network sessions.
+        /// Cancels all ongoing async operations for this session.
         /// </summary>
-        public static IAsyncResult BeginFind(
-            NetworkSessionType sessionType,
-            int maxLocalGamers,
-            NetworkSessionProperties searchProperties,
-            AsyncCallback callback,
-            object asyncState)
+        public void Cancel()
         {
-            var task = Task.Run(() =>
-            {
-                // Mock implementation - return empty collection for now
-                // In a real implementation, this would search for available sessions
-                return new AvailableNetworkSessionCollection();
-            });
-
-            return new AsyncResultWrapper<AvailableNetworkSessionCollection>(task, callback, asyncState);
-        }
-
-		public static IAsyncResult BeginFind(
-            NetworkSessionType sessionType,
-            IEnumerable<SignedInGamer> localGamers,
-            NetworkSessionProperties searchProperties,
-            AsyncCallback callback = null,
-            object asyncState = null
-        )
-		{
-			var task = Task.Run(() =>
-			{
-				// Mock implementation - return empty collection for now
-				// In a real implementation, this would search for available sessions
-				return new AvailableNetworkSessionCollection();
-			});
-
-			return new AsyncResultWrapper<AvailableNetworkSessionCollection>(task, callback, asyncState);
-		}
-
-		/// <summary>
-		/// Ends the find sessions operation.
-		/// </summary>
-		public static AvailableNetworkSessionCollection EndFind(IAsyncResult asyncResult)
-        {
-            if (asyncResult is AsyncResultWrapper<AvailableNetworkSessionCollection> wrapper)
-            {
-                return wrapper.GetResult();
-            }
-            throw new ArgumentException("Invalid async result", nameof(asyncResult));
+            cancellationTokenSource?.Cancel();
         }
 
         /// <summary>
-        /// Begins joining a network session.
+        /// Asynchronously creates a new network session.
         /// </summary>
-        public static IAsyncResult BeginJoin(
-            AvailableNetworkSession availableSession,
-            AsyncCallback callback,
-            object asyncState)
+        public static async Task<NetworkSession> CreateAsync(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers, int privateGamerSlots, IDictionary<string, object> sessionProperties, CancellationToken cancellationToken = default)
         {
-            var task = Task.Run(() =>
-            {
-                // Mock implementation - create a new session as a client
-                var session = new NetworkSession(availableSession.SessionType, availableSession.CurrentGamerCount, 0, false);
-                session.sessionState = NetworkSessionState.Lobby;
-                return session;
-            });
+            if (maxLocalGamers < 1 || maxLocalGamers > 4)
+                throw new ArgumentOutOfRangeException(nameof(maxLocalGamers));
+            if (privateGamerSlots < 0 || privateGamerSlots > maxGamers)
+                throw new ArgumentOutOfRangeException(nameof(privateGamerSlots));
 
-            return new AsyncResultWrapper<NetworkSession>(task, callback, asyncState);
+            NetworkSession session = null;
+            switch (sessionType)
+            {
+                case NetworkSessionType.Local:
+                    // Local session: in-memory only
+                    await Task.Delay(5, cancellationToken);
+                    session = new NetworkSession(sessionType, maxGamers, privateGamerSlots, true);
+                    session.sessionState = NetworkSessionState.Lobby;
+                    // Register in local session list for FindAsync
+                    LocalSessionRegistry.RegisterSession(session);
+                    break;
+                case NetworkSessionType.SystemLink:
+                    // SystemLink: start UDP listener and broadcast session
+                    session = new NetworkSession(sessionType, maxGamers, privateGamerSlots, true);
+                    session.sessionState = NetworkSessionState.Lobby;
+                    _ = SystemLinkSessionManager.AdvertiseSessionAsync(session, cancellationToken); // Fire-and-forget
+                    break;
+                default:
+                    // Not implemented
+                    throw new NotSupportedException($"SessionType {sessionType} not supported yet.");
+            }
+            return session;
         }
 
         /// <summary>
-        /// Ends the join session operation.
+        /// Synchronous wrapper for CreateAsync (for XNA compatibility).
         /// </summary>
-        public static NetworkSession EndJoin(IAsyncResult asyncResult)
+        public static NetworkSession Create(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers, int privateGamerSlots, IDictionary<string, object> sessionProperties)
         {
-            if (asyncResult is AsyncResultWrapper<NetworkSession> wrapper)
-            {
-                return wrapper.GetResult();
-            }
-            throw new ArgumentException("Invalid async result", nameof(asyncResult));
+            return CreateAsync(sessionType, maxLocalGamers, maxGamers, privateGamerSlots, sessionProperties).GetAwaiter().GetResult();
         }
 
         /// <summary>
-        /// Begins joining an invited session.
+        /// Asynchronously finds available network sessions.
         /// </summary>
-        public static IAsyncResult BeginJoinInvited(int maxLocalGamers, AsyncCallback callback, object asyncState)
+        public static async Task<AvailableNetworkSessionCollection> FindAsync(NetworkSessionType sessionType, int maxLocalGamers, IDictionary<string, object> sessionProperties, CancellationToken cancellationToken = default)
         {
-            var task = Task.Run(() =>
+            switch (sessionType)
             {
-                // Mock implementation
-                var session = new NetworkSession(NetworkSessionType.PlayerMatch, 8, 0, false);
-                session.sessionState = NetworkSessionState.Lobby;
-                // Fire invite accepted event - pass a mock gamer
-                InviteAccepted?.Invoke(null, new InviteAcceptedEventArgs(GamerServices.SignedInGamer.Current, false));
-                return session;
-            });
-
-            return new AsyncResultWrapper<NetworkSession>(task, callback, asyncState);
-        }
-
-        public static IAsyncResult BeginJoinInvited(
-            IEnumerable<SignedInGamer> localGamers,
-            AsyncCallback callback,
-            object asyncState
-        )
-        {
-            throw new NotImplementedException();
+                case NetworkSessionType.Local:
+                    await Task.Delay(5, cancellationToken);
+                    // Return sessions in local registry
+                    var localSessions = LocalSessionRegistry.FindSessions(maxLocalGamers).ToList();
+                    return new AvailableNetworkSessionCollection(localSessions);
+                case NetworkSessionType.SystemLink:
+                    // Discover sessions via UDP broadcast
+                    var systemLinkSessions = (await SystemLinkSessionManager.DiscoverSessionsAsync(maxLocalGamers, cancellationToken)).ToList();
+                    return new AvailableNetworkSessionCollection(systemLinkSessions);
+                default:
+                    throw new NotSupportedException($"SessionType {sessionType} not supported yet.");
+            }
         }
 
-		/// <summary>
-		/// Ends the join invited session operation.
-		/// </summary>
-		public static NetworkSession EndJoinInvited(IAsyncResult asyncResult)
+        /// <summary>
+        /// Synchronous wrapper for FindAsync (for XNA compatibility).
+        /// </summary>
+        public static AvailableNetworkSessionCollection Find(NetworkSessionType sessionType, int maxLocalGamers, IDictionary<string, object> sessionProperties)
         {
-            return EndJoin(asyncResult);
+            return FindAsync(sessionType, maxLocalGamers, sessionProperties).GetAwaiter().GetResult();
         }
 
         /// <summary>
-        /// Creates a new network session synchronously.
+        /// Asynchronously joins an available network session.
         /// </summary>
-        /// <param name="sessionType">The type of session to create.</param>
-        /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
-        /// <param name="maxGamers">Maximum total number of gamers.</param>
-        /// <param name="privateGamerSlots">Number of private gamer slots.</param>
-        /// <param name="sessionProperties">Session properties.</param>
-        /// <returns>The created network session.</returns>
-        public static NetworkSession Create(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers, int privateGamerSlots, IDictionary<string, object> sessionProperties)
+        public static async Task<NetworkSession> JoinAsync(AvailableNetworkSession availableSession, CancellationToken cancellationToken = default)
         {
-            var props = new NetworkSessionProperties();
-            if (sessionProperties != null)
+            switch (availableSession.SessionType)
             {
-                foreach (var kvp in sessionProperties)
-                {
-                    props[kvp.Key] = kvp.Value;
-                }
+                case NetworkSessionType.Local:
+                    // Attach to local session
+                    var localSession = LocalSessionRegistry.GetSessionById(availableSession.SessionId);
+                    if (localSession == null)
+                        throw new NetworkSessionJoinException(NetworkSessionJoinError.SessionNotFound);
+                    // Add local gamer
+                    var newGamer = new LocalNetworkGamer(localSession, Guid.NewGuid().ToString(), false, SignedInGamer.Current?.Gamertag ?? "Player");
+                    localSession.AddGamer(newGamer);
+                    return localSession;
+                case NetworkSessionType.SystemLink:
+                    // Connect to host via network
+                    var joinedSession = await SystemLinkSessionManager.JoinSessionAsync(availableSession, cancellationToken);
+                    return joinedSession;
+                default:
+                    throw new NotSupportedException($"SessionType {availableSession.SessionType} not supported yet.");
             }
-            var asyncResult = BeginCreate(sessionType, maxLocalGamers, maxGamers, privateGamerSlots, props);
-            return EndCreate(asyncResult);
         }
 
+        /// <summary>
+
         /// <summary>
         /// Creates a new network session synchronously with default properties.
         /// </summary>
-        /// <param name="sessionType">The type of session to create.</param>
-        /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
-        /// <param name="maxGamers">Maximum total number of gamers.</param>
-        /// <returns>The created network session.</returns>
         public static NetworkSession Create(NetworkSessionType sessionType, int maxLocalGamers, int maxGamers)
         {
-            return Create(sessionType, maxLocalGamers, maxGamers, 0, new Dictionary<string, object>());
+            return CreateAsync(sessionType, maxLocalGamers, maxGamers, 0, new Dictionary<string, object>()).GetAwaiter().GetResult();
         }
 
         /// <summary>
-        /// Finds available network sessions synchronously.
-        /// </summary>
-        /// <param name="sessionType">The type of sessions to find.</param>
-        /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
-        /// <param name="sessionProperties">Session properties to search for.</param>
-        /// <returns>Collection of available sessions.</returns>
-        public static AvailableNetworkSessionCollection Find(NetworkSessionType sessionType, int maxLocalGamers, IDictionary<string, object> sessionProperties)
-        {
-            var props = new NetworkSessionProperties();
-            if (sessionProperties != null)
-            {
-                foreach (var kvp in sessionProperties)
-                {
-                    props[kvp.Key] = kvp.Value;
-                }
-            }
-            var asyncResult = BeginFind(sessionType, maxLocalGamers, props, null, null);
-            return EndFind(asyncResult);
-        }
 
         /// <summary>
         /// Finds available network sessions synchronously with default properties.
         /// </summary>
-        /// <param name="sessionType">The type of sessions to find.</param>
-        /// <param name="maxLocalGamers">Maximum number of local gamers.</param>
-        /// <returns>Collection of available sessions.</returns>
         public static AvailableNetworkSessionCollection Find(NetworkSessionType sessionType, int maxLocalGamers)
         {
-            return Find(sessionType, maxLocalGamers, new Dictionary<string, object>());
+            return FindAsync(sessionType, maxLocalGamers, new Dictionary<string, object>()).GetAwaiter().GetResult();
         }
 
+        /// <summary>
+
         /// <summary>
         /// Joins an available network session synchronously.
         /// </summary>
-        /// <param name="availableSession">The session to join.</param>
-        /// <returns>The joined network session.</returns>
         public static NetworkSession Join(AvailableNetworkSession availableSession)
         {
-            var asyncResult = BeginJoin(availableSession, null, null);
-            return EndJoin(asyncResult);
+            return JoinAsync(availableSession).GetAwaiter().GetResult();
         }
 
         /// <summary>
@@ -487,7 +383,7 @@ namespace Microsoft.Xna.Framework.Net
                 throw new ArgumentNullException(nameof(writer));
 
             byte[] data = writer.GetData();
-            
+
             lock (lockObject)
             {
                 foreach (var gamer in gamers)
@@ -548,18 +444,17 @@ namespace Microsoft.Xna.Framework.Net
             SendDataToGamer(gamer, writer.GetData(), options);
         }
 
-        private void SendDataToGamer(NetworkGamer gamer, byte[] data, SendDataOptions options)
+        internal void SendDataToGamer(NetworkGamer gamer, byte[] data, SendDataOptions options)
         {
             if (gamerEndpoints.TryGetValue(gamer.Id, out IPEndPoint endpoint))
             {
                 try
                 {
-                    udpClient.Send(data, data.Length, endpoint);
+                    networkTransport.Send(data, endpoint);
                 }
                 catch (Exception ex)
                 {
-                    // Log the error but don't crash the game
-                    System.Diagnostics.Debug.WriteLine($"Failed to send data to {gamer.Gamertag}: {ex.Message}");
+                    Debug.WriteLine($"Failed to send data to {gamer.Gamertag}: {ex.Message}");
                 }
             }
         }
@@ -583,9 +478,53 @@ namespace Microsoft.Xna.Framework.Net
             OnGamerLeft(gamer);
         }
 
-        private void ProcessIncomingMessages()
+        // Modern async receive loop for SystemLink
+        private async Task ReceiveLoopAsync(CancellationToken cancellationToken)
         {
-            // Mock implementation - in a real scenario this would process UDP messages
+            using var udpClient = new UdpClient();
+            udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, 0)); // Use a dynamic port for tests
+            while (!cancellationToken.IsCancellationRequested)
+            {
+                try
+                {
+                    var result = await udpClient.ReceiveAsync();
+                    var data = result.Buffer;
+                    if (data.Length > 1)
+                    {
+                        var senderEndpoint = result.RemoteEndPoint;
+
+                        NetworkGamer senderGamer = null;
+                        lock (lockObject)
+                        {
+                            senderGamer = gamers.FirstOrDefault(g => gamerEndpoints.TryGetValue(g.Id, out var ep) && ep.Equals(senderEndpoint));
+                        }
+
+                        if (senderGamer != null)
+                        {
+                            senderGamer.EnqueueIncomingPacket(data, senderGamer);
+                        }
+
+                        var typeId = data[0];
+                        var reader = new PacketReader(data); // Use only the byte array
+                        var message = NetworkMessageRegistry.CreateMessage(typeId);
+                        message?.Deserialize(reader);
+                        OnMessageReceived(new MessageReceivedEventArgs(message, result.RemoteEndPoint));
+                    }
+                }
+                catch (ObjectDisposedException) { break; }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine($"ReceiveLoop error: {ex.Message}");
+                }
+            }
+        }
+
+        private void OnMessageReceived(MessageReceivedEventArgs e)
+        {
+            // Raise the MessageReceived event
+            var handler = MessageReceived;
+            if (handler != null)
+                handler(this, e);
         }
 
         private void OnGameStarted()
@@ -623,14 +562,115 @@ namespace Microsoft.Xna.Framework.Net
             {
                 cancellationTokenSource?.Cancel();
                 receiveTask?.Wait(1000); // Wait up to 1 second
-                
-                udpClient?.Close();
-                udpClient?.Dispose();
+                networkTransport?.Dispose();
+                cancellationTokenSource?.Dispose();
+                OnSessionEnded(NetworkSessionEndReason.ClientSignedOut);
+                disposed = true;
+            }
+        }
+
+        public async ValueTask DisposeAsync()
+        {
+            if (!disposed)
+            {
+                cancellationTokenSource?.Cancel();
+                if (receiveTask != null)
+                    await receiveTask;
+                if (networkTransport is IAsyncDisposable asyncTransport)
+                    await asyncTransport.DisposeAsync();
+                else
+                    networkTransport?.Dispose();
                 cancellationTokenSource?.Dispose();
-                
                 OnSessionEnded(NetworkSessionEndReason.ClientSignedOut);
                 disposed = true;
             }
         }
+
+        internal byte[] SerializeSessionPropertiesBinary()
+        {
+            using (var ms = new MemoryStream())
+            using (var writer = new BinaryWriter(ms))
+            {
+                writer.Write(SessionProperties.Count);
+                foreach (var kvp in SessionProperties)
+                {
+                    writer.Write(kvp.Key);
+                    // Write type info and value
+                    if (kvp.Value is int i)
+                    {
+                        writer.Write((byte)1); // type marker
+                        writer.Write(i);
+                    }
+                    else if (kvp.Value is bool b)
+                    {
+                        writer.Write((byte)2);
+                        writer.Write(b);
+                    }
+                    else if (kvp.Value is string s)
+                    {
+                        writer.Write((byte)3);
+                        writer.Write(s ?? "");
+                    }
+                    // Add more types as needed
+                    else
+                    {
+                        writer.Write((byte)255); // unknown type
+                        writer.Write(kvp.Value?.ToString() ?? "");
+                    }
+                }
+                return ms.ToArray();
+            }
+        }
+
+        internal void DeserializeSessionPropertiesBinary(byte[] data)
+        {
+            using (var ms = new MemoryStream(data))
+            using (var reader = new BinaryReader(ms))
+            {
+                SessionProperties.Clear();
+                int count = reader.ReadInt32();
+                for (int i = 0; i < count; i++)
+                {
+                    string key = reader.ReadString();
+                    byte type = reader.ReadByte();
+                    object value = null;
+                    switch (type)
+                    {
+                        case 1: value = reader.ReadInt32(); break;
+                        case 2: value = reader.ReadBoolean(); break;
+                        case 3: value = reader.ReadString(); break;
+                        default: value = reader.ReadString(); break;
+                    }
+                    SessionProperties[key] = value;
+                }
+            }
+        }
+
+        private void BroadcastSessionProperties()
+        {
+            var writer = new PacketWriter();
+            writer.Write("SessionPropertiesUpdate");
+            writer.Write(SerializeSessionPropertiesBinary());
+            SendToAll(writer, SendDataOptions.Reliable);
+        }
+
+        private void ProcessIncomingMessages()
+        {
+            foreach (var gamer in gamers)
+            {
+                while (gamer.IsDataAvailable)
+                {
+                    var reader = new PacketReader();
+                    gamer.ReceiveData(out reader, out var sender);
+
+                    var messageType = reader.ReadString();
+                    if (messageType == "SessionPropertiesUpdate")
+                    {
+                        var propertiesData = reader.ReadBytes();
+                        DeserializeSessionPropertiesBinary(propertiesData);
+                    }
+                }
+            }
+        }
     }
-}
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/Net/NetworkSessionProperties.cs

@@ -0,0 +1,18 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Properties used when creating or searching for network sessions.
+	/// </summary>
+	public class NetworkSessionProperties : Dictionary<string, object>
+    {
+        /// <summary>
+        /// Initializes a new NetworkSessionProperties.
+        /// </summary>
+        public NetworkSessionProperties() : base() { }
+
+        /// <summary>
+        /// Initializes a new NetworkSessionProperties with a specified capacity.
+        /// </summary>
+        public NetworkSessionProperties(int capacity) : base(capacity) { }
+    }
+}

+ 28 - 0
MonoGame.Xna.Framework.Net/Net/PlayerMoveMessage.cs

@@ -0,0 +1,28 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	public class PlayerMoveMessage : INetworkMessage
+    {
+        public byte MessageType => 1;
+        public int PlayerId { get; set; }
+        public float X { get; set; }
+        public float Y { get; set; }
+        public float Z { get; set; }
+
+        public void Serialize(PacketWriter writer)
+        {
+            writer.Write(MessageType);
+            writer.Write(PlayerId);
+            writer.Write(X);
+            writer.Write(Y);
+            writer.Write(Z);
+        }
+
+        public void Deserialize(PacketReader reader)
+        {
+            PlayerId = reader.ReadInt32();
+            X = reader.ReadSingle();
+            Y = reader.ReadSingle();
+            Z = reader.ReadSingle();
+        }
+    }
+}

+ 38 - 0
MonoGame.Xna.Framework.Net/Net/QualityOfService.cs

@@ -0,0 +1,38 @@
+namespace Microsoft.Xna.Framework.Net
+{
+	/// <summary>
+	/// Quality of service information for a network session.
+	/// </summary>
+	public class QualityOfService
+    {
+        /// <summary>
+        /// Gets the average round trip time in milliseconds.
+        /// </summary>
+        public TimeSpan AverageRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(50);
+
+        /// <summary>
+        /// Gets the minimum round trip time in milliseconds.
+        /// </summary>
+        public TimeSpan MinimumRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(20);
+
+        /// <summary>
+        /// Gets the maximum round trip time in milliseconds.
+        /// </summary>
+        public TimeSpan MaximumRoundTripTime { get; internal set; } = TimeSpan.FromMilliseconds(100);
+
+        /// <summary>
+        /// Gets the bytes per second being sent.
+        /// </summary>
+        public int BytesPerSecondSent { get; internal set; } = 0;
+
+        /// <summary>
+        /// Gets the bytes per second being received.
+        /// </summary>
+        public int BytesPerSecondReceived { get; internal set; } = 0;
+
+        /// <summary>
+        /// Gets whether the connection is available.
+        /// </summary>
+        public bool IsAvailable { get; internal set; } = true;
+    }
+}

+ 118 - 0
MonoGame.Xna.Framework.Net/Net/SystemLinkSessionManager.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    internal static class SystemLinkSessionManager
+    {
+        private const int BroadcastPort = 31337;
+        private static readonly List<AvailableNetworkSession> discoveredSessions = new List<AvailableNetworkSession>();
+
+        public static Task AdvertiseSessionAsync(NetworkSession session, CancellationToken cancellationToken)
+        {
+            // Periodically broadcast session info on LAN until session is full or ended
+            return Task.Run(async () =>
+            {
+                using (var udpClient = new UdpClient())
+                {
+                    var endpoint = new IPEndPoint(IPAddress.Broadcast, BroadcastPort);
+                    while (!cancellationToken.IsCancellationRequested && session.AllGamers.Count < session.MaxGamers && session.sessionState != NetworkSessionState.Ended)
+                    {
+                        var propertiesBytes = session.SerializeSessionPropertiesBinary();
+                        var header = $"SESSION:{session.sessionId}:{session.MaxGamers}:{session.PrivateGamerSlots}:{session.Host?.Gamertag ?? "Host"}:";
+                        var headerBytes = Encoding.UTF8.GetBytes(header);
+                        var message = new byte[headerBytes.Length + propertiesBytes.Length];
+                        Buffer.BlockCopy(headerBytes, 0, message, 0, headerBytes.Length);
+                        Buffer.BlockCopy(propertiesBytes, 0, message, headerBytes.Length, propertiesBytes.Length);
+                        await udpClient.SendAsync(message, message.Length, endpoint);
+                        await Task.Delay(1000, cancellationToken); // Broadcast every second
+                    }
+                }
+            }, cancellationToken);
+        }
+
+        public static async Task<IEnumerable<AvailableNetworkSession>> DiscoverSessionsAsync(int maxLocalGamers, CancellationToken cancellationToken)
+        {
+            var sessions = new List<AvailableNetworkSession>();
+            using (var udpClient = new UdpClient(BroadcastPort))
+            {
+                udpClient.EnableBroadcast = true;
+                var receiveTask = udpClient.ReceiveAsync();
+                var completedTask = await Task.WhenAny(receiveTask, Task.Delay(100, cancellationToken));
+                if (completedTask == receiveTask)
+                {
+                    var result = receiveTask.Result;
+                    var buffer = result.Buffer;
+
+                    // Find the header delimiter (the last colon of the header)
+                    int headerEnd = 0;
+                    int colonCount = 0;
+                    for (int i = 0; i < buffer.Length; i++)
+                    {
+                        if (buffer[i] == (byte)':')
+                        {
+                            colonCount++;
+                            if (colonCount == 5)
+                            {
+                                headerEnd = i + 1; // header ends after 5th colon
+                                break;
+                            }
+                        }
+                    }
+
+                    if (colonCount == 5)
+                    {
+                        var headerString = Encoding.UTF8.GetString(buffer, 0, headerEnd);
+                        var parts = headerString.Split(':');
+                        var sessionId = parts[1];
+                        var maxGamers = int.Parse(parts[2]);
+                        var privateSlots = int.Parse(parts[3]);
+                        var hostGamertag = parts[4];
+
+                        // Binary session properties start after headerEnd
+                        var propertiesBytes = new byte[buffer.Length - headerEnd];
+                        Buffer.BlockCopy(buffer, headerEnd, propertiesBytes, 0, propertiesBytes.Length);
+
+                        var dummySession = new NetworkSession(NetworkSessionType.SystemLink, maxGamers, privateSlots, false, sessionId);
+                        dummySession.DeserializeSessionPropertiesBinary(propertiesBytes);
+                        var sessionProperties = dummySession.SessionProperties as Dictionary<string, object>;
+
+                        sessions.Add(new AvailableNetworkSession(
+                            sessionName: "SystemLinkSession",
+                            hostGamertag: hostGamertag,
+                            currentGamerCount: 1,
+                            openPublicGamerSlots: maxGamers - 1,
+                            openPrivateGamerSlots: privateSlots,
+                            sessionType: NetworkSessionType.SystemLink,
+                            sessionProperties: sessionProperties,
+                            sessionId: sessionId));
+                    }
+                }
+            }
+            return sessions;
+        }
+
+        public static async Task<NetworkSession> JoinSessionAsync(AvailableNetworkSession availableSession, CancellationToken cancellationToken)
+        {
+            // For demo: create a new session instance
+            await Task.Delay(10, cancellationToken);
+            var session = new NetworkSession(NetworkSessionType.SystemLink,
+                availableSession.OpenPublicGamerSlots + availableSession.CurrentGamerCount,
+                availableSession.OpenPrivateGamerSlots,
+                false,
+                availableSession.SessionId);
+            session.sessionState = NetworkSessionState.Lobby;
+
+            // Copy session properties from AvailableNetworkSession to NetworkSession
+            foreach (var kvp in availableSession.SessionProperties)
+                session.SessionProperties[kvp.Key] = kvp.Value;
+
+            return session;
+        }
+    }
+}

+ 73 - 0
MonoGame.Xna.Framework.Net/Net/UdpTransport.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Microsoft.Xna.Framework.Net
+{
+    /// <summary>
+    /// Provides a UDP-based implementation of the <see cref="INetworkTransport"/> interface for sending and receiving network data.
+    /// </summary>
+    public class UdpTransport : INetworkTransport
+    {
+        private readonly UdpClient udpClient;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UdpTransport"/> class.
+        /// </summary>
+        public UdpTransport()
+        {
+            udpClient = new UdpClient();
+        }
+
+        /// <summary>
+        /// Receives data from the network in a blocking manner.
+        /// </summary>
+        /// <returns>A tuple containing the received data and the sender's endpoint.</returns>
+        public (byte[] data, IPEndPoint sender) Receive()
+        {
+            // Call the async version and block until it completes
+            return ReceiveAsync().GetAwaiter().GetResult();
+        }
+
+        /// <summary>
+        /// Receives data from the network asynchronously.
+        /// </summary>
+        /// <returns>A task that represents the asynchronous operation. The result contains the received data and the sender's endpoint.</returns>
+        public async Task<(byte[] data, IPEndPoint sender)> ReceiveAsync()
+        {
+            var result = await udpClient.ReceiveAsync();
+            return (result.Buffer, result.RemoteEndPoint);
+        }
+
+        /// <summary>
+        /// Sends data to the specified endpoint in a blocking manner.
+        /// </summary>
+        /// <param name="data">The data to send.</param>
+        /// <param name="endpoint">The endpoint to send the data to.</param>
+        public void Send(byte[] data, IPEndPoint endpoint)
+        {
+            // Call the async version and block until it completes
+            SendAsync(data, endpoint).GetAwaiter().GetResult();
+        }
+
+        /// <summary>
+        /// Sends data to the specified endpoint asynchronously.
+        /// </summary>
+        /// <param name="data">The data to send.</param>
+        /// <param name="endpoint">The endpoint to send the data to.</param>
+        /// <returns>A task that represents the asynchronous operation.</returns>
+        public async Task SendAsync(byte[] data, IPEndPoint endpoint)
+        {
+            await udpClient.SendAsync(data, data.Length, endpoint);
+        }
+
+        /// <summary>
+        /// Releases all resources used by the <see cref="UdpTransport"/> class.
+        /// </summary>
+        public void Dispose()
+        {
+            udpClient?.Close();
+            udpClient?.Dispose();
+        }
+    }
+}

+ 18 - 0
MonoGame.Xna.Framework.Net/Tests/MonoGame.Xna.Framework.Net.Tests.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NUnitLite" Version="3.13.2" />
+    <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\MonoGame.Xna.Framework.Net.csproj" />
+  </ItemGroup>
+
+</Project>

+ 138 - 0
MonoGame.Xna.Framework.Net/Tests/NetworkSessionTests.cs

@@ -0,0 +1,138 @@
+using NUnit.Framework;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework.Net;
+using System.Collections.Generic;
+using System;
+
+namespace Microsoft.Xna.Framework.Net.Tests
+{
+    [TestFixture]
+    public class NetworkSessionTests
+    {
+        [Test]
+        public async Task MessageReceived_EventIsFired()
+        {
+            var session = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            var tcs = new TaskCompletionSource<MessageReceivedEventArgs>();
+
+            session.MessageReceived += (sender, args) =>
+            {
+                tcs.TrySetResult(args);
+            };
+
+            // Simulate a message being received (this requires a real or mock transport)
+            // For demonstration, we'll invoke the event manually:
+            session.GetType().GetMethod("OnMessageReceived", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+                .Invoke(session, new object[] { new MessageReceivedEventArgs(null, null) });
+
+            var result = await tcs.Task;
+            Assert.IsNotNull(result);
+        }
+
+        [Test]
+        public async Task GamerJoined_EventIsFired()
+        {
+            var session = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            var tcs = new TaskCompletionSource<GamerJoinedEventArgs>();
+
+            session.GamerJoined += (sender, args) =>
+            {
+                tcs.TrySetResult(args);
+            };
+
+            // Simulate a gamer joining
+            var gamerType = typeof(NetworkGamer);
+            var gamer = (NetworkGamer)System.Runtime.Serialization.FormatterServices.GetUninitializedObject(gamerType);
+            session.GetType().GetMethod("OnGamerJoined", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+                .Invoke(session, new object[] { gamer });
+
+            var result = await tcs.Task;
+            Assert.IsNotNull(result);
+        }
+
+        [Test]
+        public async Task SessionEnded_EventIsFired()
+        {
+            var session = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            var tcs = new TaskCompletionSource<NetworkSessionEndedEventArgs>();
+
+            session.SessionEnded += (sender, args) =>
+            {
+                tcs.TrySetResult(args);
+            };
+
+            // Simulate session ending
+            var endReasonType = typeof(NetworkSessionEndReason);
+            var reason = Enum.GetValues(endReasonType).GetValue(0);
+            session.GetType().GetMethod("OnSessionEnded", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+                .Invoke(session, new object[] { reason });
+
+            var result = await tcs.Task;
+            Assert.IsNotNull(result);
+        }
+
+        [Test]
+        public async Task CreateAsync_CreatesSessionSuccessfully()
+        {
+            var session = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            Assert.IsNotNull(session);
+            Assert.AreEqual(NetworkSessionState.Lobby, session.SessionState);
+            Assert.AreEqual(4, session.MaxGamers);
+        }
+
+        [Test]
+        public void Create_Synchronous_CreatesSessionSuccessfully()
+        {
+            var session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            Assert.IsNotNull(session);
+            Assert.AreEqual(NetworkSessionState.Lobby, session.SessionState);
+            Assert.AreEqual(4, session.MaxGamers);
+        }
+
+        [Test]
+        public void Cancel_CancelsSessionWithoutException()
+        {
+            var session = NetworkSession.Create(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            Assert.DoesNotThrow(() => session.Cancel());
+        }
+
+        [Test]
+        public async Task DisposeAsync_CleansUpResources()
+        {
+            var session = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            await session.DisposeAsync();
+            Assert.Pass(); // If no exception, disposal succeeded
+        }
+
+        [Test]
+        public async Task CreateFindJoin_SimulatesSessionLifecycle()
+        {
+            // Create host session
+            var hostSession = await NetworkSession.CreateAsync(NetworkSessionType.SystemLink, 1, 4, 0, new Dictionary<string, object>());
+            Assert.IsNotNull(hostSession);
+            Assert.AreEqual(NetworkSessionState.Lobby, hostSession.SessionState);
+
+            // Simulate finding available sessions (mocked)
+            var foundSessions = await NetworkSession.FindAsync(NetworkSessionType.SystemLink, 1, new Dictionary<string, object>());
+            Assert.IsNotNull(foundSessions);
+
+            // Simulate joining the session as a client
+            var availableSession = new AvailableNetworkSession(
+                sessionName: "TestSession",
+                hostGamertag: "HostPlayer",
+                currentGamerCount: 1,
+                openPublicGamerSlots: 3,
+                openPrivateGamerSlots: 0,
+                sessionType: NetworkSessionType.SystemLink,
+                sessionProperties: new Dictionary<string, object>(),
+                sessionId: "test-session-id");
+            var clientSession = await NetworkSession.JoinAsync(availableSession);
+            Assert.IsNotNull(clientSession);
+            Assert.AreEqual(NetworkSessionState.Lobby, clientSession.SessionState);
+
+            // Clean up
+            await hostSession.DisposeAsync();
+            await clientSession.DisposeAsync();
+        }
+    }
+}