mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 07:42:58 +01:00
Compare commits
3 Commits
398bb14742
...
25d0e2b0d1
Author | SHA1 | Date | |
---|---|---|---|
25d0e2b0d1 | |||
792f633a09 | |||
04c3a53f95 |
@ -3,11 +3,28 @@ using System.Buffers.Binary;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Phantom.Utils.Logging;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Agent.Minecraft.Server;
|
namespace Phantom.Agent.Minecraft.Server;
|
||||||
|
|
||||||
public static class ServerStatusProtocol {
|
public sealed class ServerStatusProtocol {
|
||||||
public static async Task<int> GetOnlinePlayerCount(ushort serverPort, CancellationToken cancellationToken) {
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public ServerStatusProtocol(string loggerName) {
|
||||||
|
this.logger = PhantomLogger.Create<ServerStatusProtocol>(loggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int?> GetOnlinePlayerCount(int serverPort, CancellationToken cancellationToken) {
|
||||||
|
try {
|
||||||
|
return await GetOnlinePlayerCountOrThrow(serverPort, cancellationToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.Error(e, "Caught exception checking online player count.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int?> GetOnlinePlayerCountOrThrow(int serverPort, CancellationToken cancellationToken) {
|
||||||
using var tcpClient = new TcpClient();
|
using var tcpClient = new TcpClient();
|
||||||
await tcpClient.ConnectAsync(IPAddress.Loopback, serverPort, cancellationToken);
|
await tcpClient.ConnectAsync(IPAddress.Loopback, serverPort, cancellationToken);
|
||||||
var tcpStream = tcpClient.GetStream();
|
var tcpStream = tcpClient.GetStream();
|
||||||
@ -16,22 +33,24 @@ public static class ServerStatusProtocol {
|
|||||||
tcpStream.WriteByte(0xFE);
|
tcpStream.WriteByte(0xFE);
|
||||||
await tcpStream.FlushAsync(cancellationToken);
|
await tcpStream.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
short messageLength = await ReadStreamHeader(tcpStream, cancellationToken);
|
short? messageLength = await ReadStreamHeader(tcpStream, cancellationToken);
|
||||||
return await ReadOnlinePlayerCount(tcpStream, messageLength * 2, cancellationToken);
|
return messageLength == null ? null : await ReadOnlinePlayerCount(tcpStream, messageLength.Value * 2, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<short> ReadStreamHeader(NetworkStream tcpStream, CancellationToken cancellationToken) {
|
private async Task<short?> ReadStreamHeader(NetworkStream tcpStream, CancellationToken cancellationToken) {
|
||||||
var headerBuffer = ArrayPool<byte>.Shared.Rent(3);
|
var headerBuffer = ArrayPool<byte>.Shared.Rent(3);
|
||||||
try {
|
try {
|
||||||
await tcpStream.ReadExactlyAsync(headerBuffer, 0, 3, cancellationToken);
|
await tcpStream.ReadExactlyAsync(headerBuffer, 0, 3, cancellationToken);
|
||||||
|
|
||||||
if (headerBuffer[0] != 0xFF) {
|
if (headerBuffer[0] != 0xFF) {
|
||||||
throw new ProtocolException("Unexpected first byte in response from server: " + headerBuffer[0]);
|
logger.Error("Unexpected first byte in response from server: {FirstByte}.", headerBuffer[0]);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
short messageLength = BinaryPrimitives.ReadInt16BigEndian(headerBuffer.AsSpan(1));
|
short messageLength = BinaryPrimitives.ReadInt16BigEndian(headerBuffer.AsSpan(1));
|
||||||
if (messageLength <= 0) {
|
if (messageLength <= 0) {
|
||||||
throw new ProtocolException("Unexpected message length in response from server: " + messageLength);
|
logger.Error("Unexpected message length in response from server: {MessageLength}.", messageLength);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageLength;
|
return messageLength;
|
||||||
@ -40,7 +59,7 @@ public static class ServerStatusProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<int> ReadOnlinePlayerCount(NetworkStream tcpStream, int messageLength, CancellationToken cancellationToken) {
|
private async Task<int?> ReadOnlinePlayerCount(NetworkStream tcpStream, int messageLength, CancellationToken cancellationToken) {
|
||||||
var messageBuffer = ArrayPool<byte>.Shared.Rent(messageLength);
|
var messageBuffer = ArrayPool<byte>.Shared.Rent(messageLength);
|
||||||
try {
|
try {
|
||||||
await tcpStream.ReadExactlyAsync(messageBuffer, 0, messageLength, cancellationToken);
|
await tcpStream.ReadExactlyAsync(messageBuffer, 0, messageLength, cancellationToken);
|
||||||
@ -55,21 +74,20 @@ public static class ServerStatusProtocol {
|
|||||||
int separator2 = Array.LastIndexOf(messageBuffer, SeparatorSecondByte);
|
int separator2 = Array.LastIndexOf(messageBuffer, SeparatorSecondByte);
|
||||||
int separator1 = separator2 == -1 ? -1 : Array.LastIndexOf(messageBuffer, SeparatorSecondByte, separator2 - 1);
|
int separator1 = separator2 == -1 ? -1 : Array.LastIndexOf(messageBuffer, SeparatorSecondByte, separator2 - 1);
|
||||||
if (!IsValidSeparator(messageBuffer, separator1) || !IsValidSeparator(messageBuffer, separator2)) {
|
if (!IsValidSeparator(messageBuffer, separator1) || !IsValidSeparator(messageBuffer, separator2)) {
|
||||||
throw new ProtocolException("Could not find message separators in response from server.");
|
logger.Error("Could not find message separators in response from server.");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
string onlinePlayerCountStr = Encoding.BigEndianUnicode.GetString(messageBuffer.AsSpan((separator1 + 1)..(separator2 - 1)));
|
string onlinePlayerCountStr = Encoding.BigEndianUnicode.GetString(messageBuffer.AsSpan((separator1 + 1)..(separator2 - 1)));
|
||||||
if (!int.TryParse(onlinePlayerCountStr, out int onlinePlayerCount)) {
|
if (!int.TryParse(onlinePlayerCountStr, out int onlinePlayerCount)) {
|
||||||
throw new ProtocolException("Could not parse online player count in response from server: " + onlinePlayerCountStr);
|
logger.Error("Could not parse online player count in response from server: {OnlinePlayerCount}.", onlinePlayerCountStr);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debug("Detected {OnlinePlayerCount} online player(s).", onlinePlayerCount);
|
||||||
return onlinePlayerCount;
|
return onlinePlayerCount;
|
||||||
} finally {
|
} finally {
|
||||||
ArrayPool<byte>.Shared.Return(messageBuffer);
|
ArrayPool<byte>.Shared.Return(messageBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProtocolException : Exception {
|
|
||||||
internal ProtocolException(string message) : base(message) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
|
|||||||
private readonly InstanceProcess process;
|
private readonly InstanceProcess process;
|
||||||
private readonly ushort serverPort;
|
private readonly ushort serverPort;
|
||||||
|
|
||||||
|
private readonly ServerStatusProtocol serverStatusProtocol;
|
||||||
|
|
||||||
private readonly TaskCompletionSource firstDetection = AsyncTasks.CreateCompletionSource();
|
private readonly TaskCompletionSource firstDetection = AsyncTasks.CreateCompletionSource();
|
||||||
private readonly ManualResetEventSlim serverOutputEvent = new ();
|
private readonly ManualResetEventSlim serverOutputEvent = new ();
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
|
|||||||
public InstancePlayerCountTracker(InstanceContext context, InstanceProcess process, ushort serverPort) : base(PhantomLogger.Create<InstancePlayerCountTracker>(context.ShortName)) {
|
public InstancePlayerCountTracker(InstanceContext context, InstanceProcess process, ushort serverPort) : base(PhantomLogger.Create<InstancePlayerCountTracker>(context.ShortName)) {
|
||||||
this.process = process;
|
this.process = process;
|
||||||
this.serverPort = serverPort;
|
this.serverPort = serverPort;
|
||||||
|
this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
|
|||||||
while (!CancellationToken.IsCancellationRequested) {
|
while (!CancellationToken.IsCancellationRequested) {
|
||||||
serverOutputEvent.Reset();
|
serverOutputEvent.Reset();
|
||||||
|
|
||||||
OnlinePlayerCount = await TryGetOnlinePlayerCount();
|
OnlinePlayerCount = await serverStatusProtocol.GetOnlinePlayerCount(serverPort, CancellationToken);
|
||||||
|
|
||||||
if (!firstDetection.Task.IsCompleted) {
|
if (!firstDetection.Task.IsCompleted) {
|
||||||
firstDetection.SetResult();
|
firstDetection.SetResult();
|
||||||
@ -64,21 +67,7 @@ sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
|
|||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
|
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
|
||||||
await serverOutputEvent.WaitHandle.WaitOneAsync(CancellationToken);
|
await serverOutputEvent.WaitHandle.WaitOneAsync(CancellationToken);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), CancellationToken);
|
await Task.Delay(TimeSpan.FromSeconds(2), CancellationToken);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int?> TryGetOnlinePlayerCount() {
|
|
||||||
try {
|
|
||||||
int newOnlinePlayerCount = await ServerStatusProtocol.GetOnlinePlayerCount(serverPort, CancellationToken);
|
|
||||||
Logger.Debug("Detected {OnlinePlayerCount} online player(s).", newOnlinePlayerCount);
|
|
||||||
return newOnlinePlayerCount;
|
|
||||||
} catch (ServerStatusProtocol.ProtocolException e) {
|
|
||||||
Logger.Error(e.Message);
|
|
||||||
return null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Error(e, "Caught exception while checking online player count.");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user