baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
44
src/ConnectServer/CheckMaximumConnectionsPlugin.cs
Normal file
44
src/ConnectServer/CheckMaximumConnectionsPlugin.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// <copyright file="CheckMaximumConnectionsPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin which checks if the maximum number of connections got exceeded. Refuses the connection to new clients, if that happens.
|
||||
/// </summary>
|
||||
internal class CheckMaximumConnectionsPlugin : IAfterSocketAcceptPlugin
|
||||
{
|
||||
private readonly ILogger<CheckMaximumConnectionsPlugin> _logger;
|
||||
private readonly ClientListener _clientListener;
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CheckMaximumConnectionsPlugin" /> class.
|
||||
/// </summary>
|
||||
/// <param name="server">The server.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public CheckMaximumConnectionsPlugin(ConnectServer server, ILogger<CheckMaximumConnectionsPlugin> logger)
|
||||
{
|
||||
this._logger = logger;
|
||||
this._clientListener = server.ClientListener;
|
||||
this._connectServerSettings = server.Settings;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OnAfterSocketAccept(Socket socket)
|
||||
{
|
||||
var maxConnections = this._connectServerSettings.MaxConnections;
|
||||
if (maxConnections <= this._clientListener.Clients.Count)
|
||||
{
|
||||
this._logger.LogWarning("Connection refused from {0}: maximum connections ({1}) reached.", socket.RemoteEndPoint, maxConnections);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
138
src/ConnectServer/Client.cs
Normal file
138
src/ConnectServer/Client.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
// <copyright file="Client.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ConnectServer;
|
||||
using Nito.AsyncEx.Synchronous;
|
||||
|
||||
/// <summary>
|
||||
/// The client which connected to the connect server.
|
||||
/// </summary>
|
||||
internal sealed class Client : IDisposable
|
||||
{
|
||||
private readonly ILogger<Client> _logger;
|
||||
private readonly byte[] _receiveBuffer;
|
||||
private readonly Timer _onlineTimer;
|
||||
private readonly IPacketHandler<Client> _packetHandler;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private DateTime _lastReceive;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Client" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection.</param>
|
||||
/// <param name="timeout">The timeout.</param>
|
||||
/// <param name="packetHandler">The packet handler.</param>
|
||||
/// <param name="maxPacketSize">Maximum size of the packet. This value is also used to initialize the receive buffer.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public Client(IConnection connection, TimeSpan timeout, IPacketHandler<Client> packetHandler, byte maxPacketSize, ILogger<Client> logger)
|
||||
{
|
||||
this.Connection = connection;
|
||||
this.Connection.PacketReceived += this.OnPacketReceivedAsync;
|
||||
this.Timeout = timeout;
|
||||
this._packetHandler = packetHandler;
|
||||
this._logger = logger;
|
||||
this._lastReceive = DateTime.Now;
|
||||
var checkInterval = new TimeSpan(0, 0, 20);
|
||||
this._onlineTimer = new Timer(this.OnOnlineTimerElapsed, null, checkInterval, checkInterval);
|
||||
this._receiveBuffer = new byte[maxPacketSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout after which the client gets disconnected if he is inactive.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server information request count.
|
||||
/// </summary>
|
||||
/// <remarks>Used for DOS protection.</remarks>
|
||||
public int ServerInfoRequestCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the FTP request count.
|
||||
/// </summary>
|
||||
/// <remarks>Used for DOS protection.</remarks>
|
||||
public int FtpRequestCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server list request count.
|
||||
/// </summary>
|
||||
/// <remarks>Used for DOS protection.</remarks>
|
||||
public int ServerListRequestCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ip from which the client is connecting.
|
||||
/// </summary>
|
||||
public IPAddress Address { get; set; } = IPAddress.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the port from which the client is connecting.
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection from/to the client.
|
||||
/// </summary>
|
||||
internal IConnection Connection { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!this._disposed)
|
||||
{
|
||||
this._disposed = true;
|
||||
this._onlineTimer.Dispose();
|
||||
this.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the hello packet.
|
||||
/// </summary>
|
||||
internal ValueTask SendHelloAsync()
|
||||
{
|
||||
return this.Connection.SendHelloAsync();
|
||||
}
|
||||
|
||||
private void OnOnlineTimerElapsed(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.Connection.Connected && DateTime.Now.Subtract(this._lastReceive) > this.Timeout)
|
||||
{
|
||||
this._logger.LogDebug("Connection Timeout ({0}): Address {1}:{2} will be disconnected.", this.Timeout, this.Address, this.Port);
|
||||
this.Connection.DisconnectAsync().AsTask().WaitAndUnwrapException();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error when disconnecting client. Address {1}:{2}", this.Address, this.Port);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnPacketReceivedAsync(ReadOnlySequence<byte> sequence)
|
||||
{
|
||||
this._lastReceive = DateTime.Now;
|
||||
if (sequence.Length > this._receiveBuffer.Length)
|
||||
{
|
||||
this._logger.LogInformation($"Client {this.Address}:{this.Port} will be disconnected because it sent a packet which was too big (size of {sequence.Length}");
|
||||
await this.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
sequence.CopyTo(this._receiveBuffer);
|
||||
await this._packetHandler
|
||||
.HandlePacketAsync(this, this._receiveBuffer.AsMemory(0, this._receiveBuffer.GetPacketSize()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
60
src/ConnectServer/ClientConnectionCountPlugin.cs
Normal file
60
src/ConnectServer/ClientConnectionCountPlugin.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// <copyright file="ClientConnectionCountPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// The client connection count plugin.
|
||||
/// </summary>
|
||||
internal class ClientConnectionCountPlugin : IAfterSocketAcceptPlugin, IAfterDisconnectPlugin
|
||||
{
|
||||
private readonly ILogger<ClientConnectionCountPlugin> _logger;
|
||||
private readonly ClientConnectionCounter _clientCounter;
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientConnectionCountPlugin" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServerSettings">The settings.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public ClientConnectionCountPlugin(IConnectServerSettings connectServerSettings, ILogger<ClientConnectionCountPlugin> logger)
|
||||
{
|
||||
this._connectServerSettings = connectServerSettings;
|
||||
this._logger = logger;
|
||||
this._clientCounter = new ClientConnectionCounter();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OnAfterSocketAccept(Socket socket)
|
||||
{
|
||||
var ipAddress = (socket.RemoteEndPoint as IPEndPoint)?.Address;
|
||||
if (ipAddress is null)
|
||||
{
|
||||
// should never happen - but who knows. In this case, we allow the connection.
|
||||
this._logger.LogDebug($"Non-IPEndPoint connected: {socket.RemoteEndPoint}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._connectServerSettings.CheckMaxConnectionsPerAddress
|
||||
&& this._clientCounter.GetConnectionCount(ipAddress) >= this._connectServerSettings.MaxConnectionsPerAddress)
|
||||
{
|
||||
this._logger.LogWarning("Maximum Connections per IP reached: {0}, Connection refused.", ipAddress);
|
||||
return false;
|
||||
}
|
||||
|
||||
this._clientCounter.AddConnection(ipAddress);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnAfterDisconnect(Client client)
|
||||
{
|
||||
this._clientCounter.RemoveConnection(client.Address);
|
||||
}
|
||||
}
|
||||
72
src/ConnectServer/ClientConnectionCounter.cs
Normal file
72
src/ConnectServer/ClientConnectionCounter.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
// <copyright file="ClientConnectionCounter.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Counts the connections per ip address.
|
||||
/// </summary>
|
||||
internal class ClientConnectionCounter
|
||||
{
|
||||
private readonly IDictionary<IPAddress, int> _connections = new Dictionary<IPAddress, int>();
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection count of the specified ip address.
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">The ip address.</param>
|
||||
/// <returns>The counted connections of the ip address.</returns>
|
||||
public int GetConnectionCount(IPAddress ipAddress)
|
||||
{
|
||||
int count;
|
||||
lock (this._syncRoot)
|
||||
{
|
||||
this._connections.TryGetValue(ipAddress, out count);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the connection and increases its count for the specified ip address.
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">The ip address.</param>
|
||||
public void AddConnection(IPAddress ipAddress)
|
||||
{
|
||||
lock (this._syncRoot)
|
||||
{
|
||||
if (this._connections.ContainsKey(ipAddress))
|
||||
{
|
||||
this._connections[ipAddress]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._connections.Add(ipAddress, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the connection and decreases its count for the specified ip address.
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">The ip address.</param>
|
||||
public void RemoveConnection(IPAddress ipAddress)
|
||||
{
|
||||
lock (this._syncRoot)
|
||||
{
|
||||
if (!this._connections.ContainsKey(ipAddress))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._connections[ipAddress]--;
|
||||
if (this._connections[ipAddress] == 0)
|
||||
{
|
||||
this._connections.Remove(ipAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/ConnectServer/ClientListener.cs
Normal file
133
src/ConnectServer/ClientListener.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
// <copyright file="ClientListener.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Network;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
/// <summary>
|
||||
/// The listener which is waiting for new connecting clients.
|
||||
/// </summary>
|
||||
internal class ClientListener
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger<ClientListener> _logger;
|
||||
private readonly AsyncLock _clientListLock = new();
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
private readonly IPacketHandler<Client> _packetHandler;
|
||||
private Listener? _listener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientListener" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServer">The connect server.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ClientListener(IConnectServer connectServer, ILoggerFactory loggerFactory)
|
||||
{
|
||||
this._loggerFactory = loggerFactory;
|
||||
this._connectServerSettings = connectServer.Settings;
|
||||
this._logger = this._loggerFactory.CreateLogger<ClientListener>();
|
||||
this._packetHandler = new ClientPacketHandler(connectServer, loggerFactory);
|
||||
this.Clients = new List<Client>();
|
||||
this.ClientSocketAcceptPlugins = new List<IAfterSocketAcceptPlugin>();
|
||||
this.ClientSocketDisconnectPlugins = new List<IAfterDisconnectPlugin>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the number of connected clients changed.
|
||||
/// </summary>
|
||||
public event EventHandler? ConnectedClientsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connected clients.
|
||||
/// </summary>
|
||||
public ICollection<Client> Clients { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client socket accept plugins.
|
||||
/// </summary>
|
||||
public IList<IAfterSocketAcceptPlugin> ClientSocketAcceptPlugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client socket disconnect plugins.
|
||||
/// </summary>
|
||||
public ICollection<IAfterDisconnectPlugin> ClientSocketDisconnectPlugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listener.
|
||||
/// </summary>
|
||||
public void StartListener()
|
||||
{
|
||||
this._listener = new Listener(this._connectServerSettings.ClientListenerPort, null, null, this._loggerFactory);
|
||||
this._listener.ClientAccepting += this.OnClientAcceptingAsync;
|
||||
this._listener.ClientAccepted += this.OnClientAcceptedAsync;
|
||||
this._listener.Start(this._connectServerSettings.ListenerBacklog);
|
||||
|
||||
this._logger.LogInformation("Client Listener started, Port {0}", this._connectServerSettings.ClientListenerPort);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the listener.
|
||||
/// </summary>
|
||||
public void StopListener()
|
||||
{
|
||||
this._listener?.Stop();
|
||||
this._logger.LogInformation("Client Listener stopped");
|
||||
}
|
||||
|
||||
private async ValueTask OnClientAcceptingAsync(ClientAcceptingEventArgs e)
|
||||
{
|
||||
for (var i = 0; i < this.ClientSocketAcceptPlugins.Count; ++i)
|
||||
{
|
||||
var plugin = this.ClientSocketAcceptPlugins[i];
|
||||
if (!plugin.OnAfterSocketAccept(e.AcceptingSocket))
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnClientAcceptedAsync(ClientAcceptedEventArgs e)
|
||||
{
|
||||
var connection = e.AcceptedConnection;
|
||||
var client = new Client(connection, this._connectServerSettings.Timeout, this._packetHandler, this._connectServerSettings.MaximumReceiveSize, this._loggerFactory.CreateLogger<Client>());
|
||||
var ipEndpoint = connection.EndPoint as IPEndPoint;
|
||||
client.Address = ipEndpoint?.Address ?? IPAddress.None;
|
||||
client.Port = ipEndpoint?.Port ?? 0;
|
||||
client.Timeout = this._connectServerSettings.Timeout;
|
||||
|
||||
using (await this._clientListLock.LockAsync().ConfigureAwait(false))
|
||||
{
|
||||
this.Clients.Add(client);
|
||||
}
|
||||
|
||||
client.Connection.Disconnected += async () => await this.OnClientDisconnectAsync(client).ConfigureAwait(false);
|
||||
this._logger.LogDebug("Client connected: {0}, current client count: {1}", connection.EndPoint, this.Clients.Count);
|
||||
await client.SendHelloAsync().ConfigureAwait(false);
|
||||
_ = Task.Run(() => client.Connection.BeginReceiveAsync());
|
||||
this.ConnectedClientsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async ValueTask OnClientDisconnectAsync(Client client)
|
||||
{
|
||||
foreach (var plugin in this.ClientSocketDisconnectPlugins)
|
||||
{
|
||||
plugin.OnAfterDisconnect(client);
|
||||
}
|
||||
|
||||
this._logger.LogDebug("Connection to Client {0}:{1} disconnected.", client.Address, client.Port);
|
||||
using (await this._clientListLock.LockAsync().ConfigureAwait(false))
|
||||
{
|
||||
this.Clients.Remove(client);
|
||||
}
|
||||
|
||||
this.ConnectedClientsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
242
src/ConnectServer/ConnectServer.cs
Normal file
242
src/ConnectServer/ConnectServer.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
// <copyright file="ConnectServer.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The connect server.
|
||||
/// </summary>
|
||||
public class ConnectServer : IConnectServer, OpenMU.Interfaces.IConnectServer
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ServerList _serverList;
|
||||
private ServerState _serverState;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectServer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServerSettings">The settings.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ConnectServer(IConnectServerSettings connectServerSettings, ILoggerFactory loggerFactory)
|
||||
{
|
||||
this._loggerFactory = loggerFactory;
|
||||
this.Settings = connectServerSettings;
|
||||
this.ClientVersion = new ClientVersion(this.Settings.Client.Season, this.Settings.Client.Episode, ClientLanguage.Invariant);
|
||||
this.ConfigurationId = this.Settings.ConfigurationId;
|
||||
|
||||
this._logger = this._loggerFactory.CreateLogger<ConnectServer>();
|
||||
|
||||
this.ConnectInfos = new ConcurrentDictionary<ushort, byte[]>();
|
||||
this._serverList = new ServerList(this.ClientVersion);
|
||||
|
||||
this.ClientListener = new ClientListener(this, loggerFactory);
|
||||
this.ClientListener.ConnectedClientsChanged += (_, _) =>
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(this.CurrentConnections));
|
||||
};
|
||||
this.CreatePlugins();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ServerState ServerState
|
||||
{
|
||||
get => this._serverState;
|
||||
private set
|
||||
{
|
||||
if (value != this._serverState)
|
||||
{
|
||||
this._serverState = value;
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ServerType Type => ServerType.ConnectServer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description => this.Settings.Description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Id => SpecialServerIds.ConnectServer + this.Settings.ServerId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid ConfigurationId { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConcurrentDictionary<ushort, byte[]> ConnectInfos { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
ServerList IConnectServer.ServerList => this._serverList;
|
||||
|
||||
/// <inheritdoc cref="IConnectServer"/>
|
||||
public IConnectServerSettings Settings { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ClientVersion ClientVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum allowed connections.
|
||||
/// </summary>
|
||||
public int MaximumConnections => this.Settings.MaxConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current connection count.
|
||||
/// </summary>
|
||||
public int CurrentConnections => this.ClientListener.Clients.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game server connection count.
|
||||
/// </summary>
|
||||
public int CurrentGameServerConnections => this._serverList.TotalConnectionCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered game servers.
|
||||
/// </summary>
|
||||
public IEnumerable<IGameServerEntry> RegisteredGameServers => this._serverList.Items;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client listener.
|
||||
/// </summary>
|
||||
internal ClientListener ClientListener { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask StartAsync()
|
||||
{
|
||||
if (this.ServerState != ServerState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Begin starting");
|
||||
var oldState = this.ServerState;
|
||||
this.ServerState = OpenMU.Interfaces.ServerState.Starting;
|
||||
try
|
||||
{
|
||||
this.ClientListener.StartListener();
|
||||
this.ServerState = OpenMU.Interfaces.ServerState.Started;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, ex.Message);
|
||||
this.ServerState = oldState;
|
||||
}
|
||||
|
||||
this._logger.LogInformation("Finished starting");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.ShutdownAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask ShutdownAsync()
|
||||
{
|
||||
this._logger.LogInformation("Begin stopping");
|
||||
this.ServerState = OpenMU.Interfaces.ServerState.Stopping;
|
||||
this.ClientListener.StopListener();
|
||||
this.ServerState = OpenMU.Interfaces.ServerState.Stopped;
|
||||
this._logger.LogInformation("Finished stopping");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterGameServer(ServerInfo gameServer, IPEndPoint publicEndPoint)
|
||||
{
|
||||
this._logger.LogInformation("GameServer {0} is registering with endpoint {1}", gameServer, publicEndPoint);
|
||||
try
|
||||
{
|
||||
if (this.ConnectInfos.ContainsKey(gameServer.Id))
|
||||
{
|
||||
this._logger.LogInformation("GameServer {0} was already registered and needs to be removed before...", gameServer);
|
||||
this.UnregisterGameServer(gameServer.Id);
|
||||
}
|
||||
|
||||
var serverListItem = new ServerListItem(this._serverList)
|
||||
{
|
||||
ServerId = gameServer.Id,
|
||||
EndPoint = publicEndPoint,
|
||||
MaximumConnections = gameServer.MaximumConnections,
|
||||
CurrentConnections = gameServer.CurrentConnections,
|
||||
};
|
||||
|
||||
if (this.ConnectInfos.TryAdd(serverListItem.ServerId, serverListItem.ConnectInfo))
|
||||
{
|
||||
this._serverList.Add(serverListItem);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogError(ex, "Error during registration process");
|
||||
throw;
|
||||
}
|
||||
|
||||
this._logger.LogInformation("GameServer {0} has registered with endpoint {1}", gameServer, publicEndPoint);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterGameServer(ushort gameServerId)
|
||||
{
|
||||
this._logger.LogInformation("GameServer {0} is unregistering", gameServerId);
|
||||
var serverListItem = this._serverList.GetItem(gameServerId);
|
||||
if (serverListItem != null)
|
||||
{
|
||||
this.ConnectInfos.Remove(serverListItem.ServerId, out _);
|
||||
this._serverList.Remove(serverListItem);
|
||||
}
|
||||
|
||||
this._logger.LogInformation("GameServer {0} has unregistered", gameServerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CurrentConnectionsChanged(ushort serverId, int currentConnections)
|
||||
{
|
||||
var serverListItem = this._serverList.GetItem(serverId);
|
||||
if (serverListItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
serverListItem.CurrentConnections = currentConnections;
|
||||
}
|
||||
|
||||
private void CreatePlugins()
|
||||
{
|
||||
this._logger.LogDebug("Begin creating plugins");
|
||||
this.ClientListener.ClientSocketAcceptPlugins.Add(new CheckMaximumConnectionsPlugin(this, this._loggerFactory.CreateLogger<CheckMaximumConnectionsPlugin>()));
|
||||
var clientCountPlugin = new ClientConnectionCountPlugin(this.Settings, this._loggerFactory.CreateLogger<ClientConnectionCountPlugin>());
|
||||
this.ClientListener.ClientSocketAcceptPlugins.Add(clientCountPlugin);
|
||||
this.ClientListener.ClientSocketDisconnectPlugins.Add(clientCountPlugin);
|
||||
this._logger.LogDebug("Finished creating plugins");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
private void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
37
src/ConnectServer/ConnectServerFactory.cs
Normal file
37
src/ConnectServer/ConnectServerFactory.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// <copyright file="ConnectServerFactory.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// The connect server factory.
|
||||
/// </summary>
|
||||
public class ConnectServerFactory
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectServerFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ConnectServerFactory(ILoggerFactory loggerFactory)
|
||||
{
|
||||
this._loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new connect server instance.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>
|
||||
/// The new connect server instance.
|
||||
/// </returns>
|
||||
public OpenMU.Interfaces.IConnectServer CreateConnectServer(IConnectServerSettings settings)
|
||||
{
|
||||
return new ConnectServer(settings, this._loggerFactory);
|
||||
}
|
||||
}
|
||||
17
src/ConnectServer/IAfterDisconnectPlugin.cs
Normal file
17
src/ConnectServer/IAfterDisconnectPlugin.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// <copyright file="IAfterDisconnectPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin which is executed after a client disconnected.
|
||||
/// </summary>
|
||||
internal interface IAfterDisconnectPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Called after a client disconnected.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
void OnAfterDisconnect(Client client);
|
||||
}
|
||||
20
src/ConnectServer/IAfterSocketAcceptPlugin.cs
Normal file
20
src/ConnectServer/IAfterSocketAcceptPlugin.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// <copyright file="IAfterSocketAcceptPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net.Sockets;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin which is executed when a client socket got accepted by the listener.
|
||||
/// </summary>
|
||||
internal interface IAfterSocketAcceptPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Called after the client socket got accepted by the listener.
|
||||
/// </summary>
|
||||
/// <param name="socket">The socket.</param>
|
||||
/// <returns>Flag that indicates if the socket is allowed to connect.</returns>
|
||||
bool OnAfterSocketAccept(Socket socket);
|
||||
}
|
||||
35
src/ConnectServer/IConnectServer.cs
Normal file
35
src/ConnectServer/IConnectServer.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// <copyright file="IConnectServer.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The internal interface of a connect server.
|
||||
/// </summary>
|
||||
internal interface IConnectServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the connect infos.
|
||||
/// </summary>
|
||||
ConcurrentDictionary<ushort, byte[]> ConnectInfos { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server list.
|
||||
/// </summary>
|
||||
ServerList ServerList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connectServerSettings.
|
||||
/// </summary>
|
||||
IConnectServerSettings Settings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client version.
|
||||
/// </summary>
|
||||
ClientVersion ClientVersion { get; }
|
||||
}
|
||||
33
src/ConnectServer/IGameServerEntry.cs
Normal file
33
src/ConnectServer/IGameServerEntry.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// <copyright file="IGameServerEntry.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for an entry of a gameserver in the connect server.
|
||||
/// </summary>
|
||||
public interface IGameServerEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the server identifier.
|
||||
/// </summary>
|
||||
ushort ServerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end point under which the server is accessible.
|
||||
/// </summary>
|
||||
IPEndPoint EndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server load percentage.
|
||||
/// </summary>
|
||||
byte ServerLoadPercentage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the count of current connections.
|
||||
/// </summary>
|
||||
int CurrentConnections { get; }
|
||||
}
|
||||
26
src/ConnectServer/MUnique.OpenMU.ConnectServer.csproj
Normal file
26
src/ConnectServer/MUnique.OpenMU.ConnectServer.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\..\bin\Debug\</OutputPath>
|
||||
<DocumentationFile>..\..\bin\Debug\MUnique.OpenMU.ConnectServer.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\..\bin\Release\</OutputPath>
|
||||
<DocumentationFile>..\..\bin\Release\MUnique.OpenMU.ConnectServer.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Interfaces\MUnique.OpenMU.Interfaces.csproj" />
|
||||
<ProjectReference Include="..\Network\MUnique.OpenMU.Network.csproj" />
|
||||
<ProjectReference Include="..\Network\Packets\MUnique.OpenMU.Network.Packets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
82
src/ConnectServer/PacketHandler/ClientPacketHandler.cs
Normal file
82
src/ConnectServer/PacketHandler/ClientPacketHandler.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
// <copyright file="ClientPacketHandler.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
using System.Net.Sockets;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using IConnectServer = MUnique.OpenMU.ConnectServer.IConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// The handler of packets coming from the client.
|
||||
/// </summary>
|
||||
internal class ClientPacketHandler : IPacketHandler<Client>
|
||||
{
|
||||
private readonly ILogger<ClientPacketHandler> _logger;
|
||||
|
||||
private readonly IDictionary<byte, IPacketHandler<Client>> _packetHandlers = new Dictionary<byte, IPacketHandler<Client>>();
|
||||
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientPacketHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServer">The connect server.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ClientPacketHandler(IConnectServer connectServer, ILoggerFactory loggerFactory)
|
||||
{
|
||||
this._logger = loggerFactory.CreateLogger<ClientPacketHandler>();
|
||||
this._connectServerSettings = connectServer.Settings;
|
||||
|
||||
// TODO: Is 0x05 correct? PatchCheckRequest has Code 0x02
|
||||
this._packetHandlers.Add(0x05, new FtpRequestHandler(connectServer.Settings, loggerFactory.CreateLogger<FtpRequestHandler>()));
|
||||
this._packetHandlers.Add(0xF4, new ServerListHandler(connectServer, loggerFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandlePacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (packet.Length > this._connectServerSettings.MaximumReceiveSize || packet.Length < 4)
|
||||
{
|
||||
await this.DisconnectClientUnknownPacketAsync(client, packet).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var packetType = packet.Span[2];
|
||||
if (this._packetHandlers.TryGetValue(packetType, out var packetHandler))
|
||||
{
|
||||
await packetHandler.HandlePacketAsync(client, packet).ConfigureAwait(false);
|
||||
}
|
||||
else if (this._connectServerSettings.DisconnectOnUnknownPacket)
|
||||
{
|
||||
await this.DisconnectClientUnknownPacketAsync(client, packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (this._logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._logger.LogDebug("SocketException occured in Client.ReceivePacket, Client Address: {0}:{1}, Packet: [{2}], Exception: {3}", client.Address, client.Port, packet.ToArray().ToHexString(), ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._logger.LogWarning("Exception occured in Client.ReceivePacket, Client Address: {0}:{1}, Packet: [{2}], Exception: {3}", client.Address, client.Port, packet.ToArray().ToHexString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask DisconnectClientUnknownPacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
this._logger.LogInformation("Client {0}:{1} will be disconnected because it sent an unknown packet: {2}", client.Address, client.Port, packet.ToArray().ToHexString());
|
||||
await client.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
154
src/ConnectServer/PacketHandler/FtpRequestHandler.cs
Normal file
154
src/ConnectServer/PacketHandler/FtpRequestHandler.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
// <copyright file="FtpRequestHandler.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the ftp related request. The client is sending its version, and the server answers
|
||||
/// with the current version and the ftp address where the client can load a patch.
|
||||
/// </summary>
|
||||
internal class FtpRequestHandler : IPacketHandler<Client>
|
||||
{
|
||||
private static readonly byte[] PatchOk = { 0xC1, 4, 2, 0 };
|
||||
|
||||
private static readonly byte[] Xor3Keys = { 0xFC, 0xCF, 0xAB };
|
||||
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
private readonly ILogger<FtpRequestHandler> _logger;
|
||||
|
||||
private byte[]? _patchPacket;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FtpRequestHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServerSettings">The settings.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public FtpRequestHandler(IConnectServerSettings connectServerSettings, ILogger<FtpRequestHandler> logger)
|
||||
{
|
||||
this._connectServerSettings = connectServerSettings;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The version compare result.
|
||||
/// </summary>
|
||||
private enum VersionCompareResult
|
||||
{
|
||||
VersionTooLow = -1,
|
||||
VersionMatch = 0,
|
||||
VersionHigher = 1,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandlePacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
if (packet.Length < 6)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void LogVersion(Span<byte> span)
|
||||
{
|
||||
this._logger.LogDebug($"Client {client.Address}:{client.Port} version: {span[3]}.{span[4]}.{span[5]}");
|
||||
}
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
LogVersion(packet.Span);
|
||||
}
|
||||
|
||||
if (client.FtpRequestCount >= this._connectServerSettings.MaxFtpRequests)
|
||||
{
|
||||
if (this._logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._logger.LogDebug("Client {0}:{1} reached maxFtpRequests", client.Address, client.Port);
|
||||
}
|
||||
|
||||
await client.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int WritePatchPacket()
|
||||
{
|
||||
if (this._patchPacket is { } cachedPacket)
|
||||
{
|
||||
var span = client.Connection.Output.GetSpan(cachedPacket.Length);
|
||||
cachedPacket.CopyTo(span);
|
||||
}
|
||||
else
|
||||
{
|
||||
var length = ClientNeedsPatchRef.Length;
|
||||
var span = client.Connection.Output.GetSpan(length)[..length];
|
||||
var packet = new ClientNeedsPatchRef(span);
|
||||
packet.PatchAddress = this._connectServerSettings.PatchAddress;
|
||||
var addressSize = Encoding.UTF8.GetByteCount(this._connectServerSettings.PatchAddress);
|
||||
|
||||
Xor3Bytes(span.Slice(6), addressSize);
|
||||
packet.PatchVersion = this._connectServerSettings.CurrentPatchVersion[2];
|
||||
|
||||
this._patchPacket = span.ToArray();
|
||||
}
|
||||
|
||||
return this._patchPacket.Length;
|
||||
}
|
||||
|
||||
int WriteOkayPacket()
|
||||
{
|
||||
var span = client.Connection.Output.GetSpan(PatchOk.Length)[..PatchOk.Length];
|
||||
PatchOk.CopyTo(span);
|
||||
return PatchOk.Length;
|
||||
}
|
||||
|
||||
if (VersionCompare(this._connectServerSettings.CurrentPatchVersion, 0, packet.Span, 3, this._connectServerSettings.CurrentPatchVersion.Length) == VersionCompareResult.VersionTooLow)
|
||||
{
|
||||
await client.Connection.SendAsync(WritePatchPacket).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.Connection.SendAsync(WriteOkayPacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
client.FtpRequestCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the actual version of the client with the expected version.
|
||||
/// </summary>
|
||||
/// <param name="expectedVersion">The expected version.</param>
|
||||
/// <param name="expectedIndex">The expected index.</param>
|
||||
/// <param name="actualVersion">The actual version.</param>
|
||||
/// <param name="actualIndex">The actual index.</param>
|
||||
/// <param name="count">The count.</param>
|
||||
/// <returns>The compare result.</returns>
|
||||
private static VersionCompareResult VersionCompare(byte[] expectedVersion, int expectedIndex, Span<byte> actualVersion, int actualIndex, int count)
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (expectedVersion[i + expectedIndex] > actualVersion[i + actualIndex])
|
||||
{
|
||||
return VersionCompareResult.VersionTooLow;
|
||||
}
|
||||
|
||||
if (expectedVersion[i + expectedIndex] < actualVersion[i + actualIndex])
|
||||
{
|
||||
return VersionCompareResult.VersionHigher;
|
||||
}
|
||||
}
|
||||
|
||||
return VersionCompareResult.VersionMatch;
|
||||
}
|
||||
|
||||
private static void Xor3Bytes(Span<byte> data, int size)
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
data[i] ^= Xor3Keys[i % 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/ConnectServer/PacketHandler/IPacketHandler{T}.cs
Normal file
19
src/ConnectServer/PacketHandler/IPacketHandler{T}.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// <copyright file="IPacketHandler{T}.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The interface for a packet handler with a type which is passed as context argument.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the context argument.</typeparam>
|
||||
internal interface IPacketHandler<in T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the packet.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="packet">The packet.</param>
|
||||
ValueTask HandlePacketAsync(T obj, Memory<byte> packet);
|
||||
}
|
||||
115
src/ConnectServer/PacketHandler/ServerInfoRequestHandler.cs
Normal file
115
src/ConnectServer/PacketHandler/ServerInfoRequestHandler.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
// <copyright file="ServerInfoRequestHandler.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Network;
|
||||
using MUnique.OpenMU.Network.Packets.ConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the server info request of a client, which means the client wants to know the connect data of the server it just clicked on.
|
||||
/// </summary>
|
||||
internal class ServerInfoRequestHandler : IPacketHandler<Client>
|
||||
{
|
||||
private readonly IConnectServer _connectServer;
|
||||
private readonly ILogger<ServerInfoRequestHandler> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerInfoRequestHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServer">The connect server.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public ServerInfoRequestHandler(IConnectServer connectServer, ILogger<ServerInfoRequestHandler> logger)
|
||||
{
|
||||
this._connectServer = connectServer;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandlePacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
var serverId = GetServerId(packet.Span);
|
||||
this._logger.LogDebug("Client {0}:{1} requested Connection Info of ServerId {2}", client.Address, client.Port, serverId);
|
||||
if (client.ServerInfoRequestCount >= this._connectServer.Settings.MaxIpRequests)
|
||||
{
|
||||
this._logger.LogDebug($"Client {client.Address}:{client.Port} reached max ip requests.");
|
||||
await client.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// First we look, if we can just use the IP address which the client connected to.
|
||||
// If the game server is running on the same ip as the connect server, we can use that.
|
||||
// This way, we can be sure, that the client can connect to it, too.
|
||||
var localIpEndPoint = client.Connection.LocalEndPoint as IPEndPoint;
|
||||
var serverItem = this._connectServer.ServerList.GetItem(serverId);
|
||||
var isGameServerOnSameMachineAsConnectServer = (serverItem?.EndPoint.Address).IsOnSameHost();
|
||||
var isClientConnectedOnNonRegisteredAddress = !object.Equals(serverItem?.EndPoint.Address, localIpEndPoint?.Address);
|
||||
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), out var isRunningOnDocker);
|
||||
|
||||
// Only if we can't use the cached data.
|
||||
if (isGameServerOnSameMachineAsConnectServer
|
||||
&& !isRunningOnDocker
|
||||
&& isClientConnectedOnNonRegisteredAddress)
|
||||
{
|
||||
int WritePacket()
|
||||
{
|
||||
var data = client.Connection.Output.GetSpan(ConnectionInfoRef.Length)[..ConnectionInfoRef.Length];
|
||||
_ = new ConnectionInfoRef(data)
|
||||
{
|
||||
IpAddress = localIpEndPoint!.Address.ToString(),
|
||||
Port = (ushort)serverItem!.EndPoint.Port,
|
||||
};
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
await client.Connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||
}
|
||||
else if (this._connectServer.ConnectInfos.TryGetValue(serverId, out var connectInfo))
|
||||
{
|
||||
// more optimal way, because the serialized data was cached.
|
||||
int WritePacket()
|
||||
{
|
||||
var span = client.Connection.Output.GetSpan(connectInfo.Length)[..connectInfo.Length];
|
||||
connectInfo.CopyTo(span);
|
||||
return span.Length;
|
||||
}
|
||||
|
||||
await client.Connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._logger.LogDebug($"Client {client.Address}:{client.Port}: Connection Info not found, sending Server List instead.");
|
||||
int WritePacket()
|
||||
{
|
||||
var serverList = this._connectServer.ServerList.Serialize();
|
||||
var span = client.Connection.Output.GetSpan(serverList.Length)[..serverList.Length];
|
||||
serverList.CopyTo(span);
|
||||
return span.Length;
|
||||
}
|
||||
|
||||
await client.Connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await client.SendHelloAsync().ConfigureAwait(false);
|
||||
client.ServerInfoRequestCount++;
|
||||
}
|
||||
|
||||
private static ushort GetServerId(Span<byte> packet)
|
||||
{
|
||||
if (packet.Length == ConnectionInfoRequestRef.Length)
|
||||
{
|
||||
ConnectionInfoRequestRef data = packet;
|
||||
return data.ServerId;
|
||||
}
|
||||
|
||||
if (packet.Length == ConnectionInfoRequest075Ref.Length)
|
||||
{
|
||||
ConnectionInfoRequest075Ref data = packet;
|
||||
return data.ServerId;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unknown packet length C1 {packet.Length} F4 03 ...");
|
||||
}
|
||||
}
|
||||
54
src/ConnectServer/PacketHandler/ServerListHandler.cs
Normal file
54
src/ConnectServer/PacketHandler/ServerListHandler.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// <copyright file="ServerListHandler.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using IConnectServer = MUnique.OpenMU.ConnectServer.IConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the requests of server data.
|
||||
/// </summary>
|
||||
internal class ServerListHandler : IPacketHandler<Client>
|
||||
{
|
||||
private readonly ILogger<ServerListHandler> _logger;
|
||||
private readonly IConnectServerSettings _connectServerSettings;
|
||||
private readonly IDictionary<byte, IPacketHandler<Client>> _packetHandlers = new Dictionary<byte, IPacketHandler<Client>>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerListHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServer">The connect server.</param>
|
||||
/// <param name="loggerFactory">The logger factory.</param>
|
||||
public ServerListHandler(IConnectServer connectServer, ILoggerFactory loggerFactory)
|
||||
{
|
||||
this._logger = loggerFactory.CreateLogger<ServerListHandler>();
|
||||
this._connectServerSettings = connectServer.Settings;
|
||||
this._packetHandlers.Add(0x03, new ServerInfoRequestHandler(connectServer, loggerFactory.CreateLogger<ServerInfoRequestHandler>()));
|
||||
this._packetHandlers.Add(0x06, new ServerListRequestHandler(connectServer, loggerFactory.CreateLogger<ServerListRequestHandler>()));
|
||||
|
||||
// old protocol:
|
||||
this._packetHandlers.Add(0x02, new ServerListRequestHandler(connectServer, loggerFactory.CreateLogger<ServerListRequestHandler>()));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandlePacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
var packetSubType = packet.Span[3];
|
||||
if (this._packetHandlers.TryGetValue(packetSubType, out var packetHandler))
|
||||
{
|
||||
await packetHandler.HandlePacketAsync(client, packet).ConfigureAwait(false);
|
||||
}
|
||||
else if (this._connectServerSettings.DisconnectOnUnknownPacket)
|
||||
{
|
||||
this._logger.LogInformation("Client {0}:{1} will be disconnected because it sent an unknown packet: {2}", client.Address, client.Port, packet.ToArray().ToHexString());
|
||||
await client.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/ConnectServer/PacketHandler/ServerListRequestHandler.cs
Normal file
51
src/ConnectServer/PacketHandler/ServerListRequestHandler.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// <copyright file="ServerListRequestHandler.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer.PacketHandler;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MUnique.OpenMU.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the request of the server list.
|
||||
/// </summary>
|
||||
internal class ServerListRequestHandler : IPacketHandler<Client>
|
||||
{
|
||||
private readonly IConnectServer _connectServer;
|
||||
private readonly ILogger<ServerListRequestHandler> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerListRequestHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="connectServer">The connect server.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public ServerListRequestHandler(IConnectServer connectServer, ILogger<ServerListRequestHandler> logger)
|
||||
{
|
||||
this._connectServer = connectServer;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask HandlePacketAsync(Client client, Memory<byte> packet)
|
||||
{
|
||||
this._logger.LogDebug("Client {0}:{1} requested Server List", client.Address, client.Port);
|
||||
if (client.ServerListRequestCount >= this._connectServer.Settings.MaxServerListRequests)
|
||||
{
|
||||
this._logger.LogDebug("Client {0}:{1} reached maxListRequests", client.Address, client.Port);
|
||||
await client.Connection.DisconnectAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
client.ServerListRequestCount++;
|
||||
|
||||
int WritePacket()
|
||||
{
|
||||
var serverList = this._connectServer.ServerList.Serialize();
|
||||
var span = client.Connection.Output.GetSpan(serverList.Length)[..serverList.Length];
|
||||
serverList.CopyTo(span);
|
||||
return span.Length;
|
||||
}
|
||||
|
||||
await client.Connection.SendAsync(WritePacket).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
10
src/ConnectServer/Properties/AssemblyInfo.cs
Normal file
10
src/ConnectServer/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// <copyright file="AssemblyInfo.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MUnique.OpenMU.ConnectServer")]
|
||||
214
src/ConnectServer/ServerList.cs
Normal file
214
src/ConnectServer/ServerList.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
// <copyright file="ServerList.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Threading;
|
||||
using MUnique.OpenMU.Network.Packets.ConnectServer;
|
||||
using MUnique.OpenMU.Network.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// The server list.
|
||||
/// </summary>
|
||||
internal class ServerList
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _lock = new();
|
||||
|
||||
private readonly ICollection<ServerListItem> _servers = new SortedSet<ServerListItem>(new ServerListItemComparer());
|
||||
|
||||
private readonly ClientVersion _clientVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerList" /> class.
|
||||
/// </summary>
|
||||
/// <param name="clientVersion">The client version.</param>
|
||||
public ServerList(ClientVersion clientVersion)
|
||||
{
|
||||
this._clientVersion = clientVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total connection count.
|
||||
/// </summary>
|
||||
public int TotalConnectionCount
|
||||
{
|
||||
get
|
||||
{
|
||||
this._lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return this._servers.Sum(s => s.CurrentConnections);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache of the available servers.
|
||||
/// </summary>
|
||||
public byte[]? Cache { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IGameServerEntry"/>s of this list.
|
||||
/// </summary>
|
||||
public IEnumerable<IGameServerEntry> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
this._lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return this._servers.ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to this instance.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
public void Add(ServerListItem item)
|
||||
{
|
||||
this._lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
this._servers.Add(item);
|
||||
this.InvalidateCache();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified item from this instance.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
public void Remove(ServerListItem item)
|
||||
{
|
||||
this._lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
this._servers.Remove(item);
|
||||
this.InvalidateCache();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ServerListItem"/> of the specified server id.
|
||||
/// </summary>
|
||||
/// <param name="gameServerId">The game server identifier.</param>
|
||||
/// <returns>The found <see cref="ServerListItem"/>.</returns>
|
||||
public ServerListItem? GetItem(ushort gameServerId)
|
||||
{
|
||||
this._lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return this._servers.FirstOrDefault(s => s.ServerId == gameServerId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes this instance to a server list packet, which can be sent to the client.
|
||||
/// </summary>
|
||||
/// <returns>The serialized server list.</returns>
|
||||
public byte[] Serialize()
|
||||
{
|
||||
var result = this.Cache;
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
this._lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
result = this.Cache;
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
byte[] packet;
|
||||
if (this._clientVersion.Season == 0)
|
||||
{
|
||||
packet = new byte[ServerListResponseOld.GetRequiredSize(this._servers.Count)];
|
||||
var response = new ServerListResponseOld(packet)
|
||||
{
|
||||
ServerCount = (byte)this._servers.Count,
|
||||
};
|
||||
var i = 0;
|
||||
foreach (var server in this._servers)
|
||||
{
|
||||
var serverBlock = response[i];
|
||||
serverBlock.ServerId = (byte)server.ServerId;
|
||||
serverBlock.LoadPercentage = server.ServerLoadPercentage;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = new byte[ServerListResponse.GetRequiredSize(this._servers.Count)];
|
||||
var response = new ServerListResponse(packet)
|
||||
{
|
||||
ServerCount = (ushort)this._servers.Count,
|
||||
};
|
||||
var i = 0;
|
||||
foreach (var server in this._servers)
|
||||
{
|
||||
var serverBlock = response[i];
|
||||
serverBlock.ServerId = server.ServerId;
|
||||
serverBlock.LoadPercentage = server.ServerLoadPercentage;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
this.Cache = packet;
|
||||
return packet;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this._lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cache.
|
||||
/// </summary>
|
||||
private void InvalidateCache()
|
||||
{
|
||||
this.Cache = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comparer for <see cref="ServerListItem"/>s.
|
||||
/// </summary>
|
||||
private class ServerListItemComparer : IComparer<ServerListItem>
|
||||
{
|
||||
/// <summary>Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.</summary>
|
||||
/// <returns>A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.Value Meaning Less than zero<paramref name="x" /> is less than <paramref name="y" />.Zero<paramref name="x" /> equals <paramref name="y" />.Greater than zero<paramref name="x" /> is greater than <paramref name="y" />.</returns>
|
||||
/// <param name="x">The first object to compare.</param>
|
||||
/// <param name="y">The second object to compare.</param>
|
||||
public int Compare(ServerListItem? x, ServerListItem? y)
|
||||
{
|
||||
return x?.ServerId.CompareTo(y?.ServerId) ?? int.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/ConnectServer/ServerListItem.cs
Normal file
113
src/ConnectServer/ServerListItem.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
// <copyright file="ServerListItem.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
using System.Net;
|
||||
using MUnique.OpenMU.Network.Packets.ConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// A list item of an available server.
|
||||
/// </summary>
|
||||
internal class ServerListItem : IGameServerEntry
|
||||
{
|
||||
private readonly ServerList _owner;
|
||||
|
||||
private byte _serverLoadPercentage;
|
||||
private int _currentConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerListItem"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
public ServerListItem(ServerList owner)
|
||||
{
|
||||
this.LoadIndex = -1;
|
||||
this._owner = owner;
|
||||
this.ConnectInfo = new byte[] { 0xC1, 0x16, 0xF4, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the server in the load (usage rate) array.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The index of the server in the load (usage rate) array.
|
||||
/// </value>
|
||||
public int LoadIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server identifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The server identifier.
|
||||
/// </value>
|
||||
public ushort ServerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server load (usage rate).
|
||||
/// </summary>
|
||||
public byte ServerLoadPercentage
|
||||
{
|
||||
get => this._serverLoadPercentage;
|
||||
|
||||
private set
|
||||
{
|
||||
this._serverLoadPercentage = value;
|
||||
var cache = this._owner.Cache;
|
||||
if (cache != null && this.LoadIndex != -1)
|
||||
{
|
||||
cache[this.LoadIndex] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connect information.
|
||||
/// </summary>
|
||||
public byte[] ConnectInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ip end point.
|
||||
/// </summary>
|
||||
public IPEndPoint EndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
ConnectionInfoRef connectInfo = this.ConnectInfo.AsSpan();
|
||||
return new IPEndPoint(IPAddress.Parse(connectInfo.IpAddress), connectInfo.Port);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.ConnectInfo.AsSpan().Clear();
|
||||
ConnectionInfo connectInfo = new ConnectionInfo(this.ConnectInfo);
|
||||
connectInfo.IpAddress = value.Address.ToString();
|
||||
connectInfo.Port = (ushort)value.Port;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum connection count.
|
||||
/// </summary>
|
||||
public int MaximumConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current connection count.
|
||||
/// </summary>
|
||||
public int CurrentConnections
|
||||
{
|
||||
get => this._currentConnections;
|
||||
set
|
||||
{
|
||||
this._currentConnections = value;
|
||||
this.ServerLoadPercentage = (byte)(this._currentConnections * 100f / this.MaximumConnections);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ServerId={this.ServerId}, ServerLoadPercentage={this.ServerLoadPercentage}";
|
||||
}
|
||||
}
|
||||
21
src/ConnectServer/Utilities.cs
Normal file
21
src/ConnectServer/Utilities.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// <copyright file="Utilities.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.ConnectServer;
|
||||
|
||||
/// <summary>
|
||||
/// Some utility functions.
|
||||
/// </summary>
|
||||
internal static class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a byte array to a hexadecimal string, each byte separated by space.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array.</param>
|
||||
/// <returns>The hexadecimal string.</returns>
|
||||
public static string ToHexString(this byte[] bytes)
|
||||
{
|
||||
return BitConverter.ToString(bytes).Replace('-', ' ');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user