Skip to content

Instantly share code, notes, and snippets.

@csh
Last active September 11, 2023 05:13
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save csh/2480d14fbbb33b4bbae3 to your computer and use it in GitHub Desktop.
Save csh/2480d14fbbb33b4bbae3 to your computer and use it in GitHub Desktop.
Server ping written in C#, complete with coloured console output.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
#if DEBUG
using System.Diagnostics;
#endif
namespace MCServerPing
{
class ServerPing
{
private static readonly Dictionary<char, ConsoleColor> Colours = new Dictionary<char, ConsoleColor>
{
{ '0', ConsoleColor.Black },
{ '1', ConsoleColor.DarkBlue },
{ '2', ConsoleColor.DarkGreen },
{ '3', ConsoleColor.DarkCyan },
{ '4', ConsoleColor.DarkRed },
{ '5', ConsoleColor.DarkMagenta },
{ '6', ConsoleColor.Yellow },
{ '7', ConsoleColor.Gray },
{ '8', ConsoleColor.DarkGray },
{ '9', ConsoleColor.Blue },
{ 'a', ConsoleColor.Green },
{ 'b', ConsoleColor.Cyan },
{ 'c', ConsoleColor.Red },
{ 'd', ConsoleColor.Magenta },
{ 'e', ConsoleColor.Yellow },
{ 'f', ConsoleColor.White },
{ 'k', Console.ForegroundColor },
{ 'l', Console.ForegroundColor },
{ 'm', Console.ForegroundColor },
{ 'n', Console.ForegroundColor },
{ 'o', Console.ForegroundColor },
{ 'r', ConsoleColor.White }
};
private static NetworkStream _stream;
private static List<byte> _buffer;
private static int _offset;
private static void Main(string[] args)
{
Console.Title = "Minecraft Server Ping";
var client = new TcpClient();
var task = client.ConnectAsync("localhost", 25565);
Console.WriteLine("Connecting to Minecraft server..");
while (!task.IsCompleted)
{
#if DEBUG
Debug.WriteLine("Connecting..");
#endif
Thread.Sleep(250);
}
if (!client.Connected)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Unable to connect to the server");
Console.ResetColor();
Console.ReadKey(true);
Environment.Exit(1);
}
_buffer = new List<byte>();
_stream = client.GetStream();
Console.WriteLine("Sending status request");
/*
* Send a "Handshake" packet
* http://wiki.vg/Server_List_Ping#Ping_Process
*/
WriteVarInt(47);
WriteString("localhost");
WriteShort(25565);
WriteVarInt(1);
Flush(0);
/*
* Send a "Status Request" packet
* http://wiki.vg/Server_List_Ping#Ping_Process
*/
Flush(0);
/*
* If you are using a modded server then use a larger buffer to account,
* see link for explanation and a motd to HTML snippet
* https://gist.github.com/csh/2480d14fbbb33b4bbae3#gistcomment-2672658
*/
var buffer = new byte[Int16.MaxValue];
// var buffer = new byte[4096];
_stream.Read(buffer, 0, buffer.Length);
try
{
var length = ReadVarInt(buffer);
var packet = ReadVarInt(buffer);
var jsonLength = ReadVarInt(buffer);
#if DEBUG
Console.WriteLine("Received packet 0x{0} with a length of {1}", packet.ToString("X2"), length);
#endif
var json = ReadString(buffer, jsonLength);
var ping = JsonConvert.DeserializeObject<PingPayload>(json);
Console.WriteLine("Software: {0}", ping.Version.Name);
Console.WriteLine("Protocol: {0}", ping.Version.Protocol);
Console.WriteLine("Players Online: {0}/{1}", ping.Players.Online, ping.Players.Max);
WriteMotd(ping);
Console.ReadKey(true);
}
catch (IOException ex)
{
/*
* If an IOException is thrown then the server didn't
* send us a VarInt or sent us an invalid one.
*/
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Unable to read packet length from server,");
Console.WriteLine("are you sure it's a Minecraft server?");
#if DEBUG
Console.WriteLine("Here are the details:");
Console.WriteLine(ex.ToString());
#endif
Console.ResetColor();
}
}
private static void WriteMotd(PingPayload ping)
{
Console.Write("Motd: ");
var chars = ping.Motd.ToCharArray();
for (var i = 0; i < ping.Motd.Length; i++)
{
try
{
if (chars[i] == '\u00A7' && Colours.ContainsKey(chars[i + 1]))
{
Console.ForegroundColor = Colours[chars[i + 1]];
continue;
}
if (chars[i - 1] == '\u00A7' && Colours.ContainsKey(chars[i]))
{
continue;
}
}
catch (IndexOutOfRangeException)
{
// End of string
}
Console.Write(chars[i]);
}
Console.WriteLine();
Console.ResetColor();
}
#region Read/Write methods
internal static byte ReadByte(byte[] buffer)
{
var b = buffer[_offset];
_offset += 1;
return b;
}
internal static byte[] Read(byte[] buffer, int length)
{
var data = new byte[length];
Array.Copy(buffer, _offset, data, 0, length);
_offset += length;
return data;
}
internal static int ReadVarInt(byte[] buffer)
{
var value = 0;
var size = 0;
int b;
while (((b = ReadByte(buffer)) & 0x80) == 0x80)
{
value |= (b & 0x7F) << (size++*7);
if (size > 5)
{
throw new IOException("This VarInt is an imposter!");
}
}
return value | ((b & 0x7F) << (size*7));
}
internal static string ReadString(byte[] buffer, int length)
{
var data = Read(buffer, length);
return Encoding.UTF8.GetString(data);
}
internal static void WriteVarInt(int value)
{
while ((value & 128) != 0)
{
_buffer.Add((byte) (value & 127 | 128));
value = (int) ((uint) value) >> 7;
}
_buffer.Add((byte) value);
}
internal static void WriteShort(short value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
internal static void WriteString(string data)
{
var buffer = Encoding.UTF8.GetBytes(data);
WriteVarInt(buffer.Length);
_buffer.AddRange(buffer);
}
internal static void Write(byte b)
{
_stream.WriteByte(b);
}
internal static void Flush(int id = -1)
{
var buffer = _buffer.ToArray();
_buffer.Clear();
var add = 0;
var packetData = new[] {(byte) 0x00};
if (id >= 0)
{
WriteVarInt(id);
packetData = _buffer.ToArray();
add = packetData.Length;
_buffer.Clear();
}
WriteVarInt(buffer.Length + add);
var bufferLength = _buffer.ToArray();
_buffer.Clear();
_stream.Write(bufferLength, 0, bufferLength.Length);
_stream.Write(packetData, 0, packetData.Length);
_stream.Write(buffer, 0, buffer.Length);
}
#endregion
}
#region Server ping
/// <summary>
/// C# represenation of the following JSON file
/// https://gist.github.com/thinkofdeath/6927216
/// </summary>
class PingPayload
{
/// <summary>
/// Protocol that the server is using and the given name
/// </summary>
[JsonProperty(PropertyName = "version")]
public VersionPayload Version { get; set; }
[JsonProperty(PropertyName = "players")]
public PlayersPayload Players { get; set; }
[JsonProperty(PropertyName = "description")]
public string Motd { get; set; }
/// <summary>
/// Server icon, important to note that it's encoded in base 64
/// </summary>
[JsonProperty(PropertyName = "favicon")]
public string Icon { get; set; }
}
class VersionPayload
{
[JsonProperty(PropertyName = "protocol")]
public int Protocol { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
}
class PlayersPayload
{
[JsonProperty(PropertyName = "max")]
public int Max { get; set; }
[JsonProperty(PropertyName = "online")]
public int Online { get; set; }
[JsonProperty(PropertyName = "sample")]
public List<Player> Sample { get; set; }
}
class Player
{
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
}
#endregion
}
@hexxone
Copy link

hexxone commented Mar 9, 2022

Btw for anyone still having this problem, I solved the "too large" json-Strings here:

https://github.com/hexxone/mcswbot2/blob/cd69fe87d8620f5def016f41e782027351258363/mcswbot2/Minecraft/ServerInfo.cs#L94

It will read the stream in "batches" and wait until the expected length of packet-data was received until it starts parsing.
Also added the "real ping calculation" step from the official MC protocol spec :)

Hope this helps - Cheers

@imgoingdigital
Copy link

Hi, I placed the code outside the Main method so I can call it again as my intention was to ping the server at regular interval (say every five minutes).

It works like a charm on first ping, the succeeding ping delivers no payload. How can I get around this?

@Deniz-seckin55
Copy link

Hi, I'm having a problem with the response JSON.

The response that the server sent to the client is incomplete
I'm trying this on 1.20.1 on a Spigot server
Recieved JSON Data:
{"version":{"name":"Paper 1.20.1","protocol":763},"favicon":"data:image/png;base64,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

The error is given by "favicon" because it doesnt have a " to end the String.

The problem isnt byte array length since there is still left space.
Why isnt the server sending me the whole json data/cutting it out?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment