├── .gitignore ├── .settings └── org.eclipse.jdt.core.prefs ├── README.md └── vc └── min └── ryanshaw └── Query └── Query.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # Eclipse 9 | .classpath 10 | .project 11 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.7 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.7 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MCJQuery 2 | ======== -------------------------------------------------------------------------------- /vc/min/ryanshaw/Query/Query.java: -------------------------------------------------------------------------------- 1 | package vc.min.ryanshaw.Query; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetSocketAddress; 7 | import java.net.Socket; 8 | import java.nio.ByteBuffer; 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import org.xbill.DNS.Record; 17 | import org.xbill.DNS.Lookup; 18 | import org.xbill.DNS.SRVRecord; 19 | import org.xbill.DNS.TextParseException; 20 | import org.xbill.DNS.Type; 21 | 22 | /** 23 | * Send a query to a given minecraft server and store any metadata and the 24 | * player list. 25 | * 26 | * @author Ryan Shaw, Jonas Konrad 27 | */ 28 | public class Query { 29 | public static void main(String[] args){ 30 | Query query = new Query("p-n.ca", 25565, 0); 31 | System.out.println(query.pingServer()); 32 | try { 33 | query.sendQueryRequest(); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | /** 39 | * The target address and port 40 | */ 41 | private InetSocketAddress address; 42 | /** 43 | * null if no successful request has been sent, otherwise a Map 44 | * containing any metadata received except the player list 45 | */ 46 | private Map values; 47 | /** 48 | * null if no successful request has been sent, otherwise an 49 | * array containing all online player usernames 50 | */ 51 | private String[] onlineUsernames; 52 | private InetSocketAddress queryAddress; 53 | 54 | /** 55 | * Convenience constructor 56 | * 57 | * @see Query#Query(InetSocketAddress) 58 | * @param host 59 | * The target host 60 | * @param port 61 | * The target port 62 | */ 63 | public Query(String host, int port, int queryport) { 64 | this(new InetSocketAddress(host, queryport), new InetSocketAddress(host, port)); 65 | } 66 | 67 | /** 68 | * Create a new instance of this class 69 | * 70 | * @param address 71 | * The servers IP-address 72 | */ 73 | public Query(InetSocketAddress queryAddress, InetSocketAddress address) { 74 | this.address = address; 75 | this.queryAddress = queryAddress; 76 | if(queryAddress.getPort() == 0){ 77 | this.queryAddress = new InetSocketAddress(queryAddress.getHostName(), address.getPort()); 78 | } 79 | } 80 | 81 | /** 82 | * Try pinging the server and then sending the query 83 | * 84 | * @see Query#pingServer() 85 | * @see Query#sendQueryRequest() 86 | * @throws IOException 87 | * If the server cannot be pinged 88 | */ 89 | public void sendQuery() throws IOException { 90 | sendQueryRequest(); 91 | } 92 | 93 | /** 94 | * Try pinging the server 95 | * 96 | * @return true if the server can be reached within 1.5 second 97 | */ 98 | public boolean pingServer() { 99 | // try pinging the given server 100 | String service = "_minecraft._tcp." + address.getHostName(); 101 | try { 102 | Record[] records = new Lookup(service, Type.SRV).run(); 103 | if(records != null) 104 | for(Record record : records){ 105 | SRVRecord srv = (SRVRecord)record; 106 | String hostname = srv.getTarget().toString(); 107 | int port = srv.getPort(); 108 | System.out.println(hostname + ":"+ port); 109 | address = new InetSocketAddress(hostname, port); 110 | queryAddress = new InetSocketAddress(hostname, port); 111 | } 112 | } catch (TextParseException e1) { 113 | e1.printStackTrace(); 114 | } 115 | try { 116 | final Socket socket = new Socket(); 117 | socket.connect(address, 1500); 118 | socket.close(); 119 | return true; 120 | } catch(IOException e) { 121 | } 122 | return false; 123 | } 124 | 125 | /** 126 | * Get the additional values if the Query has been sent 127 | * 128 | * @return The data 129 | * @throws IllegalStateException 130 | * if the query has not been sent yet or there has been an error 131 | */ 132 | public Map getValues() { 133 | if(values == null) 134 | throw new IllegalStateException("Query has not been sent yet!"); 135 | else 136 | return values; 137 | } 138 | 139 | /** 140 | * Get the online usernames if the Query has been sent 141 | * 142 | * @return The username array 143 | * @throws IllegalStateException 144 | * if the query has not been sent yet or there has been an error 145 | */ 146 | public String[] getOnlineUsernames() { 147 | if(onlineUsernames == null) 148 | throw new IllegalStateException("Query has not been sent yet!"); 149 | else 150 | return onlineUsernames; 151 | } 152 | 153 | /** 154 | * Request the UDP query 155 | * 156 | * @throws IOException 157 | * if anything goes wrong during the request 158 | */ 159 | private void sendQueryRequest() throws IOException { 160 | InetSocketAddress local = queryAddress; 161 | if(queryAddress.getPort() == 0){ 162 | local = new InetSocketAddress(queryAddress.getAddress(), address.getPort()); 163 | } 164 | System.out.println(local); 165 | final DatagramSocket socket = new DatagramSocket(); 166 | try { 167 | final byte[] receiveData = new byte[10240]; 168 | socket.setSoTimeout(2000); 169 | sendPacket(socket, local, 0xFE, 0xFD, 0x09, 0x01, 0x01, 0x01, 0x01); 170 | final int challengeInteger; 171 | { 172 | receivePacket(socket, receiveData); 173 | byte byte1 = -1; 174 | int i = 0; 175 | byte[] buffer = new byte[11]; 176 | for(int count = 5; (byte1 = receiveData[count++]) != 0;) 177 | buffer[i++] = byte1; 178 | challengeInteger = Integer.parseInt(new String(buffer).trim()); 179 | } 180 | sendPacket(socket, local, 0xFE, 0xFD, 0x00, 0x01, 0x01, 0x01, 0x01, challengeInteger >> 24, challengeInteger >> 16, challengeInteger >> 8, challengeInteger, 0x00, 0x00, 0x00, 0x00); 181 | 182 | final int length = receivePacket(socket, receiveData).getLength(); 183 | values = new HashMap(); 184 | final AtomicInteger cursor = new AtomicInteger(5); 185 | while(cursor.get() < length) { 186 | final String s = readString(receiveData, cursor); 187 | if(s.length() == 0) 188 | break; 189 | else { 190 | final String v = readString(receiveData, cursor); 191 | values.put(s, v); 192 | } 193 | } 194 | readString(receiveData, cursor); 195 | final Set players = new HashSet(); 196 | while(cursor.get() < length) { 197 | final String name = readString(receiveData, cursor); 198 | if(name.length() > 0) 199 | players.add(name); 200 | } 201 | onlineUsernames = players.toArray(new String[players.size()]); 202 | } finally { 203 | socket.close(); 204 | } 205 | } 206 | 207 | /** 208 | * Helper method to send a datagram packet 209 | * 210 | * @param socket 211 | * The connection the packet should be sent through 212 | * @param targetAddress 213 | * The target IP 214 | * @param data 215 | * The byte data to be sent 216 | * @throws IOException 217 | */ 218 | private final static void sendPacket(DatagramSocket socket, InetSocketAddress targetAddress, byte... data) throws IOException { 219 | DatagramPacket sendPacket = new DatagramPacket(data, data.length, targetAddress.getAddress(), targetAddress.getPort()); 220 | socket.send(sendPacket); 221 | } 222 | 223 | /** 224 | * Helper method to send a datagram packet 225 | * 226 | * @see Query#sendPacket(DatagramSocket, InetSocketAddress, byte...) 227 | * @param socket 228 | * The connection the packet should be sent through 229 | * @param targetAddress 230 | * The target IP 231 | * @param data 232 | * The byte data to be sent, will be cast to bytes 233 | * @throws IOException 234 | */ 235 | private final static void sendPacket(DatagramSocket socket, InetSocketAddress targetAddress, int... data) throws IOException { 236 | final byte[] d = new byte[data.length]; 237 | int i = 0; 238 | for(int j : data) 239 | d[i++] = (byte)(j & 0xff); 240 | sendPacket(socket, targetAddress, d); 241 | } 242 | 243 | /** 244 | * Receive a packet from the given socket 245 | * 246 | * @param socket 247 | * the socket 248 | * @param buffer 249 | * the buffer for the information to be written into 250 | * @return the entire packet 251 | * @throws IOException 252 | */ 253 | private final static DatagramPacket receivePacket(DatagramSocket socket, byte[] buffer) throws IOException { 254 | final DatagramPacket dp = new DatagramPacket(buffer, buffer.length); 255 | socket.receive(dp); 256 | return dp; 257 | } 258 | 259 | /** 260 | * Read a String until 0x00 261 | * 262 | * @param array 263 | * The byte array 264 | * @param cursor 265 | * The mutable cursor (will be increased) 266 | * @return The string 267 | */ 268 | private final static String readString(byte[] array, AtomicInteger cursor) { 269 | final int startPosition = cursor.incrementAndGet(); 270 | for(; cursor.get() < array.length && array[cursor.get()] != 0; cursor.incrementAndGet()) 271 | ; 272 | return new String(Arrays.copyOfRange(array, startPosition, cursor.get())); 273 | } 274 | } 275 | --------------------------------------------------------------------------------