Initial port
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, ""); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user