Skip to content

Instantly share code, notes, and snippets.

@zh32
Last active February 19, 2024 23:48
Show Gist options
  • Star 54 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save zh32/7190955 to your computer and use it in GitHub Desktop.
Save zh32/7190955 to your computer and use it in GitHub Desktop.
package de.zh32.slp;
import com.google.gson.Gson;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
/**
*
* @author zh32 <zh32 at zh32.de>
*/
public class ServerListPing17 {
private InetSocketAddress host;
private int timeout = 7000;
private Gson gson = new Gson();
public void setAddress(InetSocketAddress host) {
this.host = host;
}
public InetSocketAddress getAddress() {
return this.host;
}
void setTimeout(int timeout) {
this.timeout = timeout;
}
int getTimeout() {
return this.timeout;
}
public int readVarInt(DataInputStream in) throws IOException {
int i = 0;
int j = 0;
while (true) {
int k = in.readByte();
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new RuntimeException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
public void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
while (true) {
if ((paramInt & 0xFFFFFF80) == 0) {
out.writeByte(paramInt);
return;
}
out.writeByte(paramInt & 0x7F | 0x80);
paramInt >>>= 7;
}
}
public StatusResponse fetchData() throws IOException {
Socket socket = new Socket();
OutputStream outputStream;
DataOutputStream dataOutputStream;
InputStream inputStream;
InputStreamReader inputStreamReader;
socket.setSoTimeout(this.timeout);
socket.connect(host, timeout);
outputStream = socket.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream handshake = new DataOutputStream(b);
handshake.writeByte(0x00); //packet id for handshake
writeVarInt(handshake, 4); //protocol version
writeVarInt(handshake, this.host.getHostString().length()); //host length
handshake.writeBytes(this.host.getHostString()); //host string
handshake.writeShort(host.getPort()); //port
writeVarInt(handshake, 1); //state (1 for handshake)
writeVarInt(dataOutputStream, b.size()); //prepend size
dataOutputStream.write(b.toByteArray()); //write handshake packet
dataOutputStream.writeByte(0x01); //size is only 1
dataOutputStream.writeByte(0x00); //packet id for ping
DataInputStream dataInputStream = new DataInputStream(inputStream);
int size = readVarInt(dataInputStream); //size of packet
int id = readVarInt(dataInputStream); //packet id
if (id == -1) {
throw new IOException("Premature end of stream.");
}
if (id != 0x00) { //we want a status response
throw new IOException("Invalid packetID");
}
int length = readVarInt(dataInputStream); //length of json string
if (length == -1) {
throw new IOException("Premature end of stream.");
}
if (length == 0) {
throw new IOException("Invalid string length.");
}
byte[] in = new byte[length];
dataInputStream.readFully(in); //read json string
String json = new String(in);
long now = System.currentTimeMillis();
dataOutputStream.writeByte(0x09); //size of packet
dataOutputStream.writeByte(0x01); //0x01 for ping
dataOutputStream.writeLong(now); //time!?
readVarInt(dataInputStream);
id = readVarInt(dataInputStream);
if (id == -1) {
throw new IOException("Premature end of stream.");
}
if (id != 0x01) {
throw new IOException("Invalid packetID");
}
long pingtime = dataInputStream.readLong(); //read response
StatusResponse response = gson.fromJson(json, StatusResponse.class);
response.setTime((int) (now - pingtime));
dataOutputStream.close();
outputStream.close();
inputStreamReader.close();
inputStream.close();
socket.close();
return response;
}
public class StatusResponse {
private String description;
private Players players;
private Version version;
private String favicon;
private int time;
public String getDescription() {
return description;
}
public Players getPlayers() {
return players;
}
public Version getVersion() {
return version;
}
public String getFavicon() {
return favicon;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
}
public class Players {
private int max;
private int online;
private List<Player> sample;
public int getMax() {
return max;
}
public int getOnline() {
return online;
}
public List<Player> getSample() {
return sample;
}
}
public class Player {
private String name;
private String id;
public String getName() {
return name;
}
public String getId() {
return id;
}
}
public class Version {
private String name;
private String protocol;
public String getName() {
return name;
}
public String getProtocol() {
return protocol;
}
}
}
@scizzr
Copy link

scizzr commented Nov 3, 2013

Glad to see they updated to use a more friendly interface. Less byte and more JSON! :)

Copy link

ghost commented Dec 23, 2013

Thanks!

@winny-
Copy link

winny- commented May 2, 2014

Thanks! Found the VarInt code extremely helpful for an implementation in PHP.

@jamezrin
Copy link

It is not working anymore on 1.9 servers, the Json changed and this exception gets thrown
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 17 path $.description at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200) at com.google.gson.Gson.fromJson(Gson.java:810) at com.google.gson.Gson.fromJson(Gson.java:775) at com.google.gson.Gson.fromJson(Gson.java:724) at com.google.gson.Gson.fromJson(Gson.java:696)

@AppleSa
Copy link

AppleSa commented May 17, 2016

Thanks!Thanks!Thanks!

@zh32
Copy link
Author

zh32 commented Oct 19, 2016

This code is public domain. Thanks to @Thinkofname for the varint code.

@JusticePro
Copy link

It is not working anymore on 1.9 servers, the Json changed and this exception gets thrown
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 17 path $.description at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200) at com.google.gson.Gson.fromJson(Gson.java:810) at com.google.gson.Gson.fromJson(Gson.java:775) at com.google.gson.Gson.fromJson(Gson.java:724) at com.google.gson.Gson.fromJson(Gson.java:696)

1.9+:

package me.justicepro.serverspy;
import com.google.gson.Gson;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;

/**
 *
 * @author zh32 <zh32 at zh32.de>
 */
public class ServerListPing17 {
    
    private InetSocketAddress host;
    private int timeout = 7000;
    private Gson gson = new Gson();
    
    public void setAddress(InetSocketAddress host) {
        this.host = host;
    }
 
    public InetSocketAddress getAddress() {
        return this.host;
    }
 
    void setTimeout(int timeout) {
        this.timeout = timeout;
    }
 
    int getTimeout() {
        return this.timeout;
    }
 
    public int readVarInt(DataInputStream in) throws IOException {
        int i = 0;
        int j = 0;
        while (true) {
            int k = in.readByte();
            i |= (k & 0x7F) << j++ * 7;
            if (j > 5) throw new RuntimeException("VarInt too big");
            if ((k & 0x80) != 128) break;
        }
        return i;
    }
 
    public void writeVarInt(DataOutputStream out, int paramInt) throws IOException {
        while (true) {
            if ((paramInt & 0xFFFFFF80) == 0) {
              out.writeByte(paramInt);
              return;
            }

            out.writeByte(paramInt & 0x7F | 0x80);
            paramInt >>>= 7;
        }
    }
    
    public StatusResponse fetchData() throws IOException {

        Socket socket = new Socket();
        OutputStream outputStream;
        DataOutputStream dataOutputStream;
        InputStream inputStream;
        InputStreamReader inputStreamReader;

        socket.setSoTimeout(this.timeout);

        socket.connect(host, timeout);

        outputStream = socket.getOutputStream();
        dataOutputStream = new DataOutputStream(outputStream);

        inputStream = socket.getInputStream();
        inputStreamReader = new InputStreamReader(inputStream);

        ByteArrayOutputStream b = new ByteArrayOutputStream();
        DataOutputStream handshake = new DataOutputStream(b);
        handshake.writeByte(0x00); //packet id for handshake
        writeVarInt(handshake, 4); //protocol version
        writeVarInt(handshake, this.host.getHostString().length()); //host length
        handshake.writeBytes(this.host.getHostString()); //host string
        handshake.writeShort(host.getPort()); //port
        writeVarInt(handshake, 1); //state (1 for handshake)

        writeVarInt(dataOutputStream, b.size()); //prepend size
        dataOutputStream.write(b.toByteArray()); //write handshake packet


        dataOutputStream.writeByte(0x01); //size is only 1
        dataOutputStream.writeByte(0x00); //packet id for ping
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        int size = readVarInt(dataInputStream); //size of packet
        int id = readVarInt(dataInputStream); //packet id
        
        if (id == -1) {
            throw new IOException("Premature end of stream.");
        }
        
        if (id != 0x00) { //we want a status response
            throw new IOException("Invalid packetID");
        }
        int length = readVarInt(dataInputStream); //length of json string
        
        if (length == -1) {
            throw new IOException("Premature end of stream.");
        }

        if (length == 0) {
            throw new IOException("Invalid string length.");
        }
        
        byte[] in = new byte[length];
        dataInputStream.readFully(in);  //read json string
        String json = new String(in);
        
        
        long now = System.currentTimeMillis();
        dataOutputStream.writeByte(0x09); //size of packet
        dataOutputStream.writeByte(0x01); //0x01 for ping
        dataOutputStream.writeLong(now); //time!?

        readVarInt(dataInputStream);
        id = readVarInt(dataInputStream);
        if (id == -1) {
            throw new IOException("Premature end of stream.");
        }
        
        if (id != 0x01) {
            throw new IOException("Invalid packetID");
        }
        long pingtime = dataInputStream.readLong(); //read response
        
        StatusResponse response = gson.fromJson(json, StatusResponse.class);
        
        dataOutputStream.close();
        outputStream.close();
        inputStreamReader.close();
        inputStream.close();
        socket.close();
        
        return response;
    }
    
    
    public class StatusResponse {
        private Description description;
        private Players players;
        private Version version;
        private String favicon;
        private int time;

        public Description getDescription() {
            return description;
        }

        public Players getPlayers() {
            return players;
        }

        public Version getVersion() {
            return version;
        }

        public String getFavicon() {
            return favicon;
        }

        public int getTime() {
            return time;
        }      

        public void setTime(int time) {
            this.time = time;
        }
        
    }
    
    public class Players {
        private int max;
        private int online;
        private List<Player> sample;

        public int getMax() {
            return max;
        }

        public int getOnline() {
            return online;
        }

        public List<Player> getSample() {
            return sample;
        }        
    }
    
    public class Player {
        private String name;
        private String id;

        public String getName() {
            return name;
        }

        public String getId() {
            return id;
        }
        
    }
    
    public class Version {
        private String name;
        private int protocol;

        public String getName() {
            return name;
        }

        public int getProtocol() {
            return protocol;
        }
    }
    
    public class Description {
        private String text;

        public String getText() {
            return text;
        }

    }
    
}

@Weixiaojun666
Copy link

How can I use it?

@NPBeta
Copy link

NPBeta commented Jul 3, 2020

How can I use it?

自己人我就直接中文说了:

  1. 在你的项目里加入这个类,然后在你自己的代码里创建一个对应的实例。
  2. 创建一个 InetSocketAddress 对象,定义你服务器的域名(或 IP)和端口。
  3. 使用 setAddress() 方法设置你的服务器地址。
  4. 使用 fetchData() 方法拉取数据到 StatusResponse 对象中,并用其提供的方法获取你所需的数据。

Sorry for my poor English, but I think this below still can be useful.

  1. Insert this Class into your project, and create a corresponding instance.
  2. Create a object InetSocketAddress to define your server hostname / IP and port.
  3. Use method setAddress() to set your server address.
  4. Use method fetchData() to fetch data into object StatusResponse, and extract anything you need from it by using its own method.

@mosemister
Copy link

My custom server works with this code as the client, however does not work with the Vanilla client. Is there something else missing from this code that is in modern clients. Im using the 1.9+ version. I have tested this code on a regular vanilla client and it works too so im unsure why this code works on both my server and a regular vannila server, yet my server doesnt work with the vanilla client

@MrKammounYT
Copy link

MrKammounYT commented Aug 20, 2022

java.net.SocketTimeoutException: Read timed out
at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283)
at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309)
at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)
at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)
at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
at java.base/java.net.Socket$SocketInputStream.read(Socket.java:961)
at java.base/java.io.DataInputStream.readByte(DataInputStream.java:271)
at t.ServerListPinger.readVarInt(ServerListPinger.java:41)
at t.ServerListPinger.fetchData(ServerListPinger.java:95)
at src.main.main(main.java:15)

	InetSocketAddress inet = new InetSocketAddress("EagleMC.net", 25565);
		ServerListPinger slp = new ServerListPinger();
		slp.setAddress(inet);
		try {
			System.out.print("Players: "+slp.fetchData().getPlayers());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

@AnAverageBeing
Copy link

java.net.SocketTimeoutException: Read timed out at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283) at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309) at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350) at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803) at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966) at java.base/java.net.Socket$SocketInputStream.read(Socket.java:961) at java.base/java.io.DataInputStream.readByte(DataInputStream.java:271) at t.ServerListPinger.readVarInt(ServerListPinger.java:41) at t.ServerListPinger.fetchData(ServerListPinger.java:95) at src.main.main(main.java:15)

	InetSocketAddress inet = new InetSocketAddress("EagleMC.net", 25565);
		ServerListPinger slp = new ServerListPinger();
		slp.setAddress(inet);
		try {
			System.out.print("Players: "+slp.fetchData().getPlayers());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

bro use the server ip instead of domain
for example
if server address is mc.example.org
and this points to 69.69.69.69 then use
69.69.69.69 as ip

InetSocketAddress inet = new InetSocketAddress("69.69.69.69", 25565);
		ServerListPinger slp = new ServerListPinger();
		slp.setAddress(inet);
		try {
			System.out.print("Players: "+slp.fetchData().getPlayers());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}```

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