Skip to content

Instantly share code, notes, and snippets.

@yushijinhun
Created January 23, 2016 08:37
Show Gist options
  • Save yushijinhun/f68eb19d183302ebc0e9 to your computer and use it in GitHub Desktop.
Save yushijinhun/f68eb19d183302ebc0e9 to your computer and use it in GitHub Desktop.
minecraft server pinger
package pulutalauncher.util.server;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
public class ServerStatus implements Serializable {
private static final long serialVersionUID = 1L;
public static class Player implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String id;
public Player(String name, String id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
@Override
public String toString() {
return "[name=" + name + ", id=" + id + "]";
}
}
private String description;
private int maxPlayers;
private int onlinePlayers;
private String version;
private String protocol;
private int ping;
private transient BufferedImage favicon;
public ServerStatus(String description, int maxPlayers, int onlinePlayers, String version, String protocol, BufferedImage favicon, int ping) {
this.description = description;
this.maxPlayers = maxPlayers;
this.onlinePlayers = onlinePlayers;
this.version = version;
this.protocol = protocol;
this.favicon = favicon;
this.ping = ping;
}
public String getDescription() {
return description;
}
public int getMaxPlayers() {
return maxPlayers;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public String getVersion() {
return version;
}
public String getProtocol() {
return protocol;
}
public BufferedImage getFavicon() {
return favicon;
}
public int getPing() {
return ping;
}
@Override
public String toString() {
return "ServerStatus [description=" + description + ", maxPlayers=" + maxPlayers + ", onlinePlayers=" + onlinePlayers + ", version=" + version + ", protocol=" + protocol + ", favicon=" + favicon + ", ping=" + ping + "]";
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
favicon = ImageIO.read(in);
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
ImageIO.write(favicon, "PNG", out);
}
}
package pulutalauncher.util.server;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Objects;
import javax.imageio.ImageIO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import com.github.to2mbn.jyal.util.Base64;
public class ServerStatusFetcher {
private static final Logger LOGGER = LogManager.getFormatterLogger();
private static final String FAVICON_HEADER = "data:image/png;base64,";
private String hostName;
private int port;
private int timeout;
public ServerStatusFetcher(String hostName) {
this(hostName, 25565, 8000);
}
public ServerStatusFetcher(String hostName, int port) {
this(hostName, port, 8000);
}
public ServerStatusFetcher(String hostName, int port, int timeout) {
Objects.requireNonNull(hostName);
if (port < 0) {
throw new IllegalArgumentException("port<0");
}
this.hostName = hostName;
this.port = port;
this.timeout = timeout;
}
public ServerStatus fetch() throws IOException {
LOGGER.debug("resolving host %s, port %d", hostName, port);
InetSocketAddress host = new InetSocketAddress(hostName, port);
LOGGER.debug("fetching server status: %s", host);
byte[] handshake = generateHandshakePackage(host);
byte[] responseData;
long t0 = System.currentTimeMillis();
try (Socket socket = new Socket()) {
socket.setSoTimeout(timeout);
socket.connect(host, timeout);
try (DataOutputStream out = new DataOutputStream(socket.getOutputStream()); DataInputStream in = new DataInputStream(socket.getInputStream())) {
LOGGER.debug("send handshake packet, length %d", handshake.length);
writeVarInt(out, handshake.length); // prepend size
out.write(handshake); // write handshake packet
out.writeByte(0x01); // size is only 1
out.writeByte(0x00); // packet id for ping
int size = readVarInt(in); // size of packet
LOGGER.debug("packet size: %d", size);
int id = readVarInt(in); // packet id
if (id == -1)
throw new IOException("Premature end of stream.");
if (id != 0x00)
throw new IOException("Invalid packet id:" + id); // we want a status response
int length = readVarInt(in); // length of json string
LOGGER.debug("data length: %d", length);
if (length == -1)
throw new IOException("Premature end of stream.");
if (length == 0)
throw new IOException("Invalid string length.");
responseData = new byte[length];
in.readFully(responseData); // read json string
}
}
long t1 = System.currentTimeMillis();
String response = new String(responseData, "UTF-8");
LOGGER.debug("server status response from %s: %s", host, response);
try {
return handle(new JSONObject(response), t1 - t0);
} catch (JSONException | IOException e) {
throw new IOException("failed to handle response", e);
}
}
private ServerStatus handle(JSONObject response, long ping) throws JSONException, IOException {
String description = response.optString("description", null);
int maxPlayers = 0;
int onlinePlayers = 0;
if (response.has("players")) {
JSONObject players = response.getJSONObject("players");
maxPlayers = players.optInt("max");
onlinePlayers = players.optInt("online");
}
String protocol = null;
String name = null;
if (response.has("version")) {
JSONObject version = response.getJSONObject("version");
protocol = version.optString("protocol", null);
name = version.optString("name", null);
}
BufferedImage favicon = null;
if (response.has("favicon")) {
favicon = getFavicon(response.getString("favicon"));
}
return new ServerStatus(description, maxPlayers, onlinePlayers, name, protocol, favicon, (int) ping);
}
private BufferedImage getFavicon(String favicon) throws IOException {
if (!favicon.startsWith(FAVICON_HEADER)) {
LOGGER.warn("bad favicon header: %s", favicon);
throw new IOException("bad favicon header");
}
String base64encoded = favicon.substring(FAVICON_HEADER.length());
byte[] decoded = Base64.decode(base64encoded.toCharArray());
return ImageIO.read(new ByteArrayInputStream(decoded));
}
private byte[] generateHandshakePackage(InetSocketAddress host) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout);
out.writeByte(0x00); // packet id for handshake
writeVarInt(out, 4); // protocol version
writeVarInt(out, host.getHostString().length()); // host length
out.writeBytes(host.getHostString()); // host string
out.writeShort(host.getPort()); // port
writeVarInt(out, 1); // state (1 for handshake)
return bout.toByteArray();
}
private int readVarInt(DataInputStream in) throws IOException {
int i = 0;
int j = 0;
for (;;) {
int k = in.readByte();
i |= (k & 0x7F) << j++ * 7;
if (j > 5) {
throw new IOException("VarInt too big: " + j);
}
if ((k & 0x80) != 128) {
break;
}
}
return i;
}
private void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
for (;;) {
if ((paramInt & 0xFFFFFF80) == 0) {
out.writeByte(paramInt);
return;
}
out.writeByte(paramInt & 0x7F | 0x80);
paramInt >>>= 7;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment