Initial port

This commit is contained in:
2026-02-16 22:46:21 +00:00
commit 47d1baae27
12 changed files with 965 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
using System;
using System.Net.Sockets;
using System.Threading;
namespace MinecraftClient
{
public class MinecraftClient : IDisposable
{
private const int MaxMessageSize = 4110; // 4096 + 14 bytes of header data.
private TcpClient client;
private NetworkStream conn;
private int lastID = 0;
public MinecraftClient(string host, int port)
{
client = new TcpClient(host, port);
conn = client.GetStream();
}
public void Dispose()
{
this.Close();
}
public void Close()
{
conn.Close();
client.Close();
}
public bool Authenticate(string password)
{
Message resp;
return sendMessage(new Message(
password.Length + Encoder.HeaderLength,
Interlocked.Increment(ref lastID),
MessageType.Authenticate,
password
), out resp);
}
public bool SendCommand(string command, out Message resp)
{
return sendMessage(new Message(
command.Length + Encoder.HeaderLength,
Interlocked.Increment(ref lastID),
MessageType.Command,
command
), out resp);
}
private bool sendMessage(Message req, out Message resp)
{
// Send the message.
byte[] encoded = Encoder.EncodeMessage(req);
conn.Write(encoded, 0, encoded.Length);
// Receive the response.
byte[] respBytes = new byte[MaxMessageSize];
int bytesRead = conn.Read(respBytes, 0, respBytes.Length);
Array.Resize(ref respBytes, bytesRead);
// Decode the response and check for errors before returning.
resp = Encoder.DecodeMessage(respBytes);
if (req.ID != resp.ID) { return false; };
return true;
}
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MinecraftClient
{
public enum MessageType : int
{
Response,
_,
Command,
Authenticate
}
public readonly struct Message
{
public readonly int Length;
public readonly int ID;
public readonly MessageType Type;
public readonly String Body;
public Message(int length, int id, MessageType type, String body)
{
Length = length;
ID = id;
Type = type;
Body = body;
}
}
public class Encoder
{
public const int HeaderLength = 10; // Does not include 4-byte message length.
public static byte[] EncodeMessage(Message msg)
{
List<byte> bytes = new List<byte>();
bytes.AddRange(BitConverter.GetBytes(msg.Length));
bytes.AddRange(BitConverter.GetBytes(msg.ID));
bytes.AddRange(BitConverter.GetBytes((int)msg.Type));
bytes.AddRange(Encoding.ASCII.GetBytes(msg.Body));
bytes.AddRange(new byte[] { 0, 0 });
return bytes.ToArray();
}
public static Message DecodeMessage(byte[] bytes)
{
int len = BitConverter.ToInt32(bytes, 0);
int id = BitConverter.ToInt32(bytes, 4);
int type = BitConverter.ToInt32(bytes, 8);
int bodyLen = bytes.Length - (HeaderLength + 4);
if (bodyLen > 0)
{
byte[] bodyBytes = new byte[bodyLen];
Array.Copy(bytes, 12, bodyBytes, 0, bodyLen);
Array.Resize(ref bodyBytes, bodyLen);
return new Message(len, id, (MessageType)type, Encoding.ASCII.GetString(bodyBytes));
}
else { return new Message(len, id, (MessageType)type, ""); }
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
</Project>
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
namespace MinecraftClient
{
class Shell
{
private const string DefaultHost = "127.0.0.1";
private const int DefaultPort = 25575;
private const string DefaultPassword = "minecraft";
static void Main(string[] args)
{
string host = DefaultHost;
int port = DefaultPort;
string password = DefaultPassword;
// Parse arguments.
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "--host":
host = args[i + 1];
break;
case "--port":
port = int.Parse(args[i + 1]);
break;
case "--password":
password = args[i + 1];
break;
default:
break;
}
}
// Connect and authenticate.
using (MinecraftClient client = new MinecraftClient(host, port))
{
if (!client.Authenticate(password))
{
Console.WriteLine("authentication failure");
return;
}
// Start RCON shell.
List<String> quitCommands = new List<String> { "exit", "quit" };
Console.WriteLine("Starting RCON shell. Use 'exit', 'quit', or Ctrl-C to exit.");
while (true)
{
Console.Write("> ");
String command = Console.ReadLine();
if (quitCommands.Contains(command)) { break; }
Message resp;
if (client.SendCommand(command, out resp))
{
Console.WriteLine(resp.Body);
}
else
{
Console.WriteLine("Error sending command.");
break;
}
}
}
}
}
}