├── .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 |
--------------------------------------------------------------------------------