using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Net.Quic; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Crysome.Common.Network; using Crysome.Common.Network.Packets; using Crysome.Common.Network.Transport; using Crysome.Server.Data; namespace Crysome.Server.Network; public class CrysomeServer { public readonly PacketChannel PacketChannel; private QuicListener _listener; private CancellationTokenSource _cts; private Task _acceptTask; private X509Certificate2 _serverCert; private UdpClient _legacySock; private Task _legacyTask; private readonly ConcurrentDictionary _clientKeys = new ConcurrentDictionary(); private readonly ConcurrentDictionary _legacySessions = new ConcurrentDictionary(); private readonly ConcurrentDictionary _legacySessionKeys = new ConcurrentDictionary(); private int _nextUdpToken = 1; private readonly ConcurrentDictionary _udpSessionMap = new ConcurrentDictionary(); public ConcurrentDictionary ConnectedClients { get; private set; } public bool Listening { get; private set; } public int Port { get; set; } = 7777; public int QuicPort { get; set; } = 7778; public AppDataStore Store { get; set; } public int ClientCount => ConnectedClients.Count; public event EventHandler PacketReceived; public event EventHandler ClientConnected; public event EventHandler ClientDisconnected; public event EventHandler UdpFrameReceived; public CrysomeServer() { PacketChannel = new PacketChannel(); ConnectedClients = new ConcurrentDictionary(); } public uint RegisterUdpSession(CrysomeClient client) { uint num = (uint)Interlocked.Increment(ref _nextUdpToken); _udpSessionMap[num] = client; client.UdpSessionToken = num; return num; } public void UnregisterUdpSession(CrysomeClient client) { if (client != null && client.UdpSessionToken != 0) { _udpSessionMap.TryRemove(client.UdpSessionToken, out var _); } } public void Start() { if (Listening) { Stop(); } bool num = QuicPort > 0; bool flag = Port > 0; if (!num && !flag) { throw new InvalidOperationException("At least one of Port (RUDP) or QuicPort (QUIC) must be greater than 0."); } _cts = new CancellationTokenSource(); if (num) { if (!QuicListener.IsSupported) { if (!flag) { throw new InvalidOperationException("QUIC listener is not supported. Use Windows 11 / Server 2022 or newer with MsQuic enabled, or set QuicPort to 0 and use RUDP only on Port."); } } else { _serverCert = QuicCertificateHelper.CreateServerCertificate(); _listener = QuicTransportSession.StartListenerAsync(QuicPort, _serverCert, _cts.Token).GetAwaiter().GetResult(); } } if (flag) { _legacySock = new UdpClient(Port); _legacySock.Client.ReceiveBufferSize = 8388608; _legacySock.Client.SendBufferSize = 8388608; _legacyTask = Task.Run(() => LegacyReceiveLoop(_cts.Token)); } Listening = true; if (_listener != null) { _acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token)); } } public void Stop() { Listening = false; try { _cts?.Cancel(); } catch { } try { _listener?.DisposeAsync().AsTask().Wait(3000); } catch { } _listener = null; try { _legacySock?.Close(); } catch { } _legacySock = null; foreach (KeyValuePair connectedClient in ConnectedClients) { try { connectedClient.Value.Disconnect(); } catch { } } ConnectedClients.Clear(); _clientKeys.Clear(); _legacySessions.Clear(); _legacySessionKeys.Clear(); _udpSessionMap.Clear(); } private async Task LegacyReceiveLoop(CancellationToken ct) { uint sessionId = default(uint); byte b = default(byte); uint num = default(uint); uint num2 = default(uint); ushort num3 = default(ushort); ushort num4 = default(ushort); byte b2 = default(byte); while (!ct.IsCancellationRequested && Listening && _legacySock != null) { try { UdpReceiveResult udpReceiveResult = await _legacySock.ReceiveAsync().ConfigureAwait(continueOnCapturedContext: false); byte[] buffer = udpReceiveResult.Buffer; if (!RudpChannel.ParseHeader(buffer, buffer.Length, out sessionId, out b, out num, out num2, out num3, out num4, out b2)) { continue; } if (_legacySessions.TryGetValue(sessionId, out var client)) { goto IL_0275; } if ((b & 9) == 0 || sessionId == 0) { continue; } RudpChannel val = new RudpChannel(sessionId, udpReceiveResult.RemoteEndPoint, _legacySock); val.UnreliableFrameReceived += delegate(byte typeId, byte[] data) { if (_legacySessions.TryGetValue(sessionId, out var value)) { try { this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(value, typeId, data)); } catch { } } }; val.Disconnected += delegate { LegacyClientGone(sessionId); }; val.Start(); client = new CrysomeClient(val); string key = Guid.NewGuid().ToString("N"); _legacySessions[sessionId] = client; _legacySessionKeys[sessionId] = key; ConnectedClients[key] = client; Task.Run(delegate { ClientReadLoop(key, client, ct); }, ct); OnClientConnected(client); goto IL_0275; IL_0275: CrysomeClient obj = client; if (obj != null) { RudpChannel rudpChannel = obj.GetRudpChannel(); if (rudpChannel != null) { rudpChannel.FeedDatagram(buffer); } } } catch (ObjectDisposedException) { break; } catch { } } } private void LegacyClientGone(uint sessionId) { if (_legacySessions.TryRemove(sessionId, out var value)) { if (_legacySessionKeys.TryRemove(sessionId, out var value2)) { ConnectedClients.TryRemove(value2, out var _); } UnregisterUdpSession(value); OnClientDisconnected(value); } } private async Task AcceptLoopAsync(CancellationToken ct) { while (!ct.IsCancellationRequested && Listening && _listener != null) { try { QuicConnection conn = await _listener.AcceptConnectionAsync(ct).ConfigureAwait(continueOnCapturedContext: false); Task.Run(() => HandleConnectionAsync(conn, ct), ct); } catch (ObjectDisposedException) { break; } catch { } } } private async Task HandleConnectionAsync(QuicConnection conn, CancellationToken ct) { QuicTransportSession session = null; CrysomeClient client = null; string key = null; try { session = await QuicTransportSession.AcceptServerAsync(conn, ct).ConfigureAwait(continueOnCapturedContext: false); client = new CrysomeClient((ITransportSession)(object)session, true); key = Guid.NewGuid().ToString("N"); _clientKeys[client] = key; ConnectedClients[key] = client; session.UnreliableFrameReceived += delegate(byte typeId, byte[] data) { try { this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(client, typeId, data)); } catch { } }; Task.Run(delegate { ClientReadLoop(key, client, ct); }, ct); OnClientConnected(client); } catch { try { QuicTransportSession obj2 = session; if (obj2 != null) { obj2.Dispose(); } } catch { } if (client != null && key != null) { ClientGone(client, key); } } } private void ClientGone(CrysomeClient client, string key) { if (client == null) { return; } CrysomeClient value; string value2; foreach (KeyValuePair legacySession in _legacySessions) { if (legacySession.Value == client) { _legacySessions.TryRemove(legacySession.Key, out value); _legacySessionKeys.TryRemove(legacySession.Key, out value2); break; } } _clientKeys.TryRemove(client, out value2); if (key != null) { ConnectedClients.TryRemove(key, out value); } UnregisterUdpSession(client); OnClientDisconnected(client); } private void ClientReadLoop(string key, CrysomeClient client, CancellationToken ct) { try { while (!ct.IsCancellationRequested && client.IsConnected) { IPacket packet = client.ReadPacket(); OnPacketReceived(client, packet); } } catch (IOException) { } catch { } finally { ClientGone(client, key); } } public void DisconnectClient(CrysomeClient client) { if (client == null) { return; } try { client.Disconnect(); } catch { } } public List GetClientSnapshot() { return new List(ConnectedClients.Values); } private void OnClientConnected(CrysomeClient client) { this.ClientConnected?.Invoke(this, new ClientEventArgs(client)); if (Store == null || !Store.Settings.TelegramEnabled || string.IsNullOrEmpty(Store.Settings.TelegramToken) || string.IsNullOrEmpty(Store.Settings.TelegramChatId)) { return; } string ip = client.RemoteAddress?.Address?.ToString() ?? "Unknown"; Task.Run(async delegate { try { string stringToEscape = "\ud83d\udd14 *New Client Connected*\nIP: " + ip; string requestUri = $"https://api.telegram.org/bot{Store.Settings.TelegramToken}/sendMessage?chat_id={Store.Settings.TelegramChatId}&text={Uri.EscapeDataString(stringToEscape)}&parse_mode=Markdown"; using HttpClient hc = new HttpClient(); await hc.GetAsync(requestUri); } catch { } }); } private void OnClientDisconnected(CrysomeClient client) { this.ClientDisconnected?.Invoke(this, new ClientEventArgs(client)); } private void OnPacketReceived(CrysomeClient client, IPacket packet) { this.PacketReceived?.Invoke(this, new PacketEventArgs(client, packet)); PacketChannel.HandlePacket(client, packet); } }