-
-
Save csh/2480d14fbbb33b4bbae3 to your computer and use it in GitHub Desktop.
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 | |
} |
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?
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?
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