diff --git a/src/Beetlejuice.csproj b/src/Beetlejuice.csproj
index 8791824..7604237 100644
--- a/src/Beetlejuice.csproj
+++ b/src/Beetlejuice.csproj
@@ -11,7 +11,6 @@
true
-
diff --git a/src/Program.cs b/src/Program.cs
index 5add336..162683a 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,6 +1,8 @@
+using System.Buffers.Binary;
using System.Net;
+using System.Net.Sockets;
+using System.Text;
using System.Text.RegularExpressions;
-using CoreRCON;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NetCord;
@@ -53,10 +55,38 @@ string Whitelist(ApplicationCommandContext c, string discordId, bool add)
async Task Command(string command, bool allowDisconnect = false)
{
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);
- await rcon.ConnectAsync();
- try { return await rcon.SendCommandAsync(command); }
- catch when (allowDisconnect && !rcon.Connected) { return ""; }
+ using var timeout = new CancellationTokenSource(10_000);
+ using var tcp = new TcpClient();
+ await tcp.ConnectAsync(address, int.Parse(Env("RCON_PORT", "27015")), timeout.Token);
+ 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()
{