Use minimal Project Zomboid RCON protocol client

This commit is contained in:
MrDevBot
2026-09-01 20:00:51 +10:00
parent 5650d1d49a
commit ffb392e48f
2 changed files with 35 additions and 6 deletions
-1
View File
@@ -11,7 +11,6 @@
<StripSymbols>true</StripSymbols> <StripSymbols>true</StripSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CoreRCON" Version="5.4.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="NetCord.Hosting" Version="1.0.0-beta.18" /> <PackageReference Include="NetCord.Hosting" Version="1.0.0-beta.18" />
<PackageReference Include="NetCord.Hosting.Services" Version="1.0.0-beta.18" /> <PackageReference Include="NetCord.Hosting.Services" Version="1.0.0-beta.18" />
+35 -5
View File
@@ -1,6 +1,8 @@
using System.Buffers.Binary;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CoreRCON;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using NetCord; using NetCord;
@@ -53,10 +55,38 @@ string Whitelist(ApplicationCommandContext c, string discordId, bool add)
async Task<string> Command(string command, bool allowDisconnect = false) async Task<string> Command(string command, bool allowDisconnect = false)
{ {
var address = (await Dns.GetHostAddressesAsync(Env("RCON_HOST", "zomboid"))).First(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork); var address = (await Dns.GetHostAddressesAsync(Env("RCON_HOST", "zomboid"))).First(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
using var rcon = new RCON(address, ushort.Parse(Env("RCON_PORT", "27015")), rconPassword, strictCommandPacketIdMatching: false); using var timeout = new CancellationTokenSource(10_000);
await rcon.ConnectAsync(); using var tcp = new TcpClient();
try { return await rcon.SendCommandAsync(command); } await tcp.ConnectAsync(address, int.Parse(Env("RCON_PORT", "27015")), timeout.Token);
catch when (allowDisconnect && !rcon.Connected) { return ""; } await using var stream = tcp.GetStream();
async Task Write(int id, int type, string body)
{
byte[] text = Encoding.UTF8.GetBytes(body), packet = new byte[text.Length + 14];
BinaryPrimitives.WriteInt32LittleEndian(packet, packet.Length - 4);
BinaryPrimitives.WriteInt32LittleEndian(packet.AsSpan(4), id);
BinaryPrimitives.WriteInt32LittleEndian(packet.AsSpan(8), type);
text.CopyTo(packet, 12);
await stream.WriteAsync(packet, timeout.Token);
}
async Task<(int Id, int Type, string Body)> Read()
{
byte[] header = new byte[4];
await stream.ReadExactlyAsync(header, timeout.Token);
int length = BinaryPrimitives.ReadInt32LittleEndian(header);
if (length is < 10 or > 1_048_576) throw new InvalidDataException("Invalid RCON packet");
byte[] packet = new byte[length];
await stream.ReadExactlyAsync(packet, timeout.Token);
return (BinaryPrimitives.ReadInt32LittleEndian(packet), BinaryPrimitives.ReadInt32LittleEndian(packet.AsSpan(4)),
Encoding.UTF8.GetString(packet, 8, length - 10));
}
await Write(1, 3, rconPassword);
(int Id, int Type, string Body) response;
do response = await Read(); while (response.Type != 2);
if (response.Id != 1) throw new UnauthorizedAccessException("RCON authentication failed");
await Write(2, 2, command);
try { do response = await Read(); while (response.Id != 2); return response.Body; }
catch (EndOfStreamException) when (allowDisconnect) { return ""; }
catch (IOException) when (allowDisconnect) { return ""; }
} }
async Task<(int Count, string[] Names)> Players() async Task<(int Count, string[] Names)> Players()
{ {