Files
2026-08-27 11:22:54 -06:00

431 lines
10 KiB
C#

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<CrysomeClient, string> _clientKeys = new ConcurrentDictionary<CrysomeClient, string>();
private readonly ConcurrentDictionary<uint, CrysomeClient> _legacySessions = new ConcurrentDictionary<uint, CrysomeClient>();
private readonly ConcurrentDictionary<uint, string> _legacySessionKeys = new ConcurrentDictionary<uint, string>();
private int _nextUdpToken = 1;
private readonly ConcurrentDictionary<uint, CrysomeClient> _udpSessionMap = new ConcurrentDictionary<uint, CrysomeClient>();
public ConcurrentDictionary<string, CrysomeClient> 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<PacketEventArgs> PacketReceived;
public event EventHandler<ClientEventArgs> ClientConnected;
public event EventHandler<ClientEventArgs> ClientDisconnected;
public event EventHandler<UdpFrameEventArgs> UdpFrameReceived;
public CrysomeServer()
{
PacketChannel = new PacketChannel();
ConnectedClients = new ConcurrentDictionary<string, CrysomeClient>();
}
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<string, CrysomeClient> 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<uint, CrysomeClient> 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<CrysomeClient> GetClientSnapshot()
{
return new List<CrysomeClient>(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);
}
}