-
-
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 | |
} |
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which cause the deserialize to fail. How can I fix this problem?
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which cause the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096];
var buffer = new List<byte>();
while (true)
{
_stream.Read(batch, 0, batch.Length);
buffer.AddRange(batch);
if (!_stream.DataAvailable) break;
}
// buffer.ToArray();
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which cause the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();
Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which cause the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
Replace original code (98-100 line) with this one.
var batch = new byte[4096];
var buffer = new List<byte>();
while (true)
{
_stream.Read(batch, 0, batch.Length);
buffer.AddRange(batch);
if (!_stream.DataAvailable) break;
}
... also don't forget add buffer.ToArray() in the next lines where it's necessary, but better use different variable.
For example:
var batch = new byte[4096];
var bufferTemp = new List<byte>();
while (true)
{
_stream.Read(batch, 0, batch.Length);
bufferTemp .AddRange(batch);
if (!_stream.DataAvailable) break;
}
var buffer = bufferTemp.ToArray();
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which causes the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
Replace original code (98-100 line) with this one.
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!_stream.DataAvailable) break; }... also don't forget add buffer.ToArray() in the next lines where it's necessary, but better use different variable.
For exeample:
var batch = new byte[4096]; var bufferTemp = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); bufferTemp .AddRange(batch); if (!_stream.DataAvailable) break; } var buffer = bufferTemp.ToArray();
Unfortunately, the code still can not fetch the complete JSON string. Are there any other solutions?
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which causes the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
Replace original code (98-100 line) with this one.
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!_stream.DataAvailable) break; }... also don't forget add buffer.ToArray() in the next lines where it's necessary, but better use different variable.
For exeample:var batch = new byte[4096]; var bufferTemp = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); bufferTemp .AddRange(batch); if (!_stream.DataAvailable) break; } var buffer = bufferTemp.ToArray();Unfortunately, the code still can not fetch the complete JSON string. Are there any other solutions?
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which causes the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
Replace original code (98-100 line) with this one.
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!_stream.DataAvailable) break; }... also don't forget add buffer.ToArray() in the next lines where it's necessary, but better use different variable.
For exeample:var batch = new byte[4096]; var bufferTemp = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); bufferTemp .AddRange(batch); if (!_stream.DataAvailable) break; } var buffer = bufferTemp.ToArray();Unfortunately, the code still can not fetch the complete JSON string. Are there any other solutions?
I just tested this code using "mc.hypixel.net". It seems that the server did not return the complete JSON at one time. Which causes the deserialize to fail. How can I fix this problem?
Hi, try collect all data from stream like this:
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!stream.DataAvailable) break; } // buffer.ToArray();Could you please give a more specific place that I need to replace? (Maybe the line of the code is better). Thank you so much!
Replace original code (98-100 line) with this one.
var batch = new byte[4096]; var buffer = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); buffer.AddRange(batch); if (!_stream.DataAvailable) break; }... also don't forget add buffer.ToArray() in the next lines where it's necessary, but better use different variable.
For exeample:var batch = new byte[4096]; var bufferTemp = new List<byte>(); while (true) { _stream.Read(batch, 0, batch.Length); bufferTemp .AddRange(batch); if (!_stream.DataAvailable) break; } var buffer = bufferTemp.ToArray();Unfortunately, the code still can not fetch the complete JSON string. Are there any other solutions?
I get it. It also doesn't work for me in some cases.
The solution is almost correct, loop's working more faster than network communication (DataAvailable property depends on it).
If set breakpoint on line 122 and wait some time and so on you will read full response.
It remains to figure out how to fix this problem.
Btw for anyone still having this problem, I solved the "too large" json-Strings here:
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
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,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAdEUlEQVR4Xr16aZCdZ3XmtaW+37uv337X3tQtdbfUu6RuWbZkyS3Jsiw7LDKmYgfGmRS/5kcmkFSSGioLSWWjKJQ4hKwy2CkgdiBAJYSyQ2wHbIzBOKQqzJApU5OCqvxgIFX5M1XKc+5tX7W7bf+cV2+1vvutZ33OOe97GizTjmsnpGbc7OdCacGVZZpJhnP4x6yxzCqpbNPiSmIk0zIaZ5VQEjdyqYXRSnDmldFaK2Wk5EppJSUeUhjWKIGfUfmgXZoYZi1zMdNV5V0q8b7c6SxGZbzNcrpZSKkN6MFLlPDO5lPjzmVSsIgvepOVPcG55EJL1dAcxAkujHTCZSmTxgYeCgt6Sp8lQuKK1Y45xbRiRiTaWBuNja5TGi0Sw53hUnHNlJPgSLNgxxRT4EFavBjsCaXwEePxCfxgeKFv9cE6hBLy0jiNbzplcCUv+twwpaXwKo0uBMhWGRcmzh61iqc2KlChNZ4NjDumjFENoa3VPm+1OFNpyKQPBk8JrQ204hIDcUWW+6zMNPTRZCwhmeJYytDOa9CtOdMQkwm6VbOi7SEQBx1CAVwbjtdKQZoyHgwKx4xVAZLARSjPcA+ucIfmTSkN7lLMGRVJFAbCawadCuN1ZvJ2K8FrheVGhFg5CIUnUpkG9GClUiFMjs8qgQ8JgS9koMIWZcmEELEGHdII6xzIteNT4A2KYlZ6PGvwzxowYQRIggE2iTrtlWeMQTSMrmpIRDLpWq2ZhRWWSO3ABJgDvQIiFVbyMbwChkbChKHyNIq8hKBgCbAMAyUaB7vCIBWFaCE/VzS1bgQBaUltPUwqKKhZ20QIWDhXfn8CLXHBeAJilE8LGIjLC8g02MCkCEWnVbZh6dpF6CSNFZ7O1xaLqlaOPAGM4auJTBR3ITULp4+2YhHyNPTHbeYFs/g4OV1sOKODZGF9Vac5GUlegBu4gTQ6a7dgD6ktIDPIiXMrbVPDQWEmijdYAlVBdc4akjPZpcbPwSkOJ4rSBJDI94OcROhQVT1I0EID8A5t4NOwLAWx4C3QruDGsyxkMCDl4FkDo7V4O/w0qGhDyCUeUVCcgxhgOjwxGdNOQmRQBRjBP5WH0inPgQUR1kvKNE2QpMsihWXDbA0ZGOizDfI7oh4aZtZ7GKIpghQWskvbVV5U8GCCBJOJLMAQApm3YCBMAizwMgYFyP0CYpVkzAARHsiTMw+02k8+ALfl2grIA4qFswrgFbgBbkFAwXKeVl0VIA7oGqTBoMlUISHoTUWwHbNOAbXICA/ySsBzVGIS0Ay0ajAgECCD4wFdtsal9/l4j2j04BgKzgnJYA9AUnAVNBzEKmtllBzu54k24I92GOTYDsABPElkgGqBxRxEAIFCbNMHoE8JmYNUzyMEDQjTNuQebkpiUlCtJQV4DuOR0mZVOT6V5+XK1kZI0wCxSZnb1NlgXYBrgYaG4RHeZzWRB+bysqOVh2lqnXITuQPyBs7GGNuPEGG4hfgAe0UegLAk0oSiiI25SFUoO4q0AweDcxrvM+UCAzDAaIFSgFKgH7CVu7o1PgbPgxdDJODHuzLPt8EL/h9hyfhIIHm5lFC7cNw6uBJcAkrLQq5wC5DeRWhgzAfEEJ7A4BTDOe9LWXVUMVkcfaCz9Z5gawCtMDlIJceCyME5V2OSQhT8CkaoJMvwGYEpjc9hmqwznvanXacFQUFsQDPgH4TifJSuEMAMxhxwjFsoGWCv4QDQNDe4qZwal5AWREH4ha8hNKY2FNBepzMJewY1BkEPGOQ8Ahlc1wqgH0KUxBeiyaq0M+vaM8Wpn2rd8l6VpjByHzIHHPfRa6gF6jVRe0Awd4Ru0IUVHijBfAoLFYpf3zNyAgPYYypgbFpaxDMMETnbZ2HoQFblBBwhhfZwkRRoqxrROPEWGAhngqtCERwODZwFW8GD7YYhUQB7NLkbKCEEFanNdVaM4UOhsjaXMdGIOAhHJokIuxamrJpQByEzoJIcwXu/i+JoprudI2V9MM1m6urg6PwH/+Rv694RsneYF7cxGhOQASDaJ8A0k4aQlYBKAKYiUwSIQRccFteEvhGkkGDoQRxk5KqNkJbwdQqLjtzAK+uRHHGRhJQDYxA34aE+FkUXeI0nDKU3eJxBJwRJCHDkMMAOAeJmZ2cvXLh46fLdJ0+ea3UOnj5927mLd507f2pra+vy5Svd3kpVHxmyIfMQixrRy2YmlBWHnJGReRYWjnCEBBOaJGMKL6AeiE35COzTBHiws6W2mcKTZb8hVFJXLea8plv4IFMB3y7AOCFz3YSBlt12mkco0lNIRUKjCAChdK5Gch2ObrerVMk5ErBeWc21WwdvaZx/y80/VZsDztXBtYLrNm7ehzu/+8orwD8LxVqDYJpmbUM+6BkXSLBg5tqbsqxMIn1eggaIDkCVcA1H5JRmMB2tbcYGhR5D2UCiyTagSp8CEMCxbDqfKFFNzwAepNdIgywlUUwliHIRKI9AAFLweFEfbBWH2625TndCqfzVma6urq+trYFo7zujaUR56eLdI56B0vuF0Clw2QL1QQ/UC2+ElSMd46wp1JjxEWq2sFpELyTEJoJMRC2dwomRN8GKfYztCSCIN5R3CaBj5g9MTVFoCZpSS+uAJNApwi1ituA+b7eBSaBgXxK6vfF2p9eqZ52fLsqFGKZAPXgAA7curlSq0/NzRTqTF3N560hdHk7j9F/LLz3b+Coed8BdZLQ2FmVFMQkOjGSbQ9kRUMyLkjLovAPrDh45UgFPh3YyVVJSrmUj5JCj
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?
I had same problem, in debug I learned the json tree, and I solved it by
public string Motd { get { return (string)Description["extra"][0]["text"]; } }