Here i have and example of how the servers can be found and also how to extract information from the response.
This example only works for protcol HL1 (not source) and do not detect local games (if someone wants to do it...feel free)...
CSServerInfo.java
/**
* Only HL1 servers info
* @author Joaquin
*/
public class CSServerInfo {
StringBuffer ipAndPort = new StringBuffer();
StringBuffer serverName = new StringBuffer();
StringBuffer map = new StringBuffer();
StringBuffer gameDir = new StringBuffer();
StringBuffer gameDesc = new StringBuffer();
byte numPlayers;
byte maxPlayers;
byte netVersion;
byte dedicated;
byte os;
byte password;
byte isMod;
ModData modInfo = new ModData();
byte secure;
byte numBots;
private byte[] buffer;
/** Creates a new instance of CSServerInfo */
public CSServerInfo(byte[] buffer) {
this.buffer = buffer;
parseInformation();
}
public void parseInformation() {
int index = 4; // Discard first 4 bytes
char m = RawByteParser.parseChar(index, buffer);
if (m != 'm')return;
index++;
index += RawByteParser.parseString(index, buffer, ipAndPort);
index += RawByteParser.parseString(index, buffer, serverName);
index += RawByteParser.parseString(index, buffer, map);
index += RawByteParser.parseString(index, buffer, gameDir);
index += RawByteParser.parseString(index, buffer, gameDesc);
numPlayers = buffer[index];
index++;
maxPlayers = buffer[index];
index++;
netVersion = buffer[index];
index++;
dedicated = buffer[index];
index++;
os = buffer[index];
index++;
password = buffer[index];
index++;
isMod = buffer[index];
index++;
if (isMod == 1)modInfo.parse(buffer, index);
index += modInfo.length();
secure = buffer[index];
index++;
numBots = buffer[index];
index++;
}
public String toString() {
StringBuffer res = new StringBuffer("Server Info:\n");
res.append("\nIP: \t" + ipAndPort);
res.append("\nName: \t" + serverName);
res.append("\nMap: \t" + map);
res.append("\nGame Dir: \t" + gameDir);
res.append("\nDesc: \t" + gameDesc);
res.append("\nPlayers: \t" + numPlayers);
res.append("\nMax Plyrs:\t" + maxPlayers);
res.append("\nVersion: \t" + netVersion);
res.append("\nDedicated:\t" + (char)dedicated);
res.append("\nOS: \t" + (char)os);
res.append("\nPassword: \t" + (int)password);
//res.append("\nMod: \t" + isMod);
res.append("\n\t" + modInfo.toString().replaceAll("\n","\n\t"));
res.append("\nSecure: \t" + secure);
res.append("\nNum Bots:\t" + numBots);
return res.toString();
}
}
CSServerScanner.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Vector;
/**
*
* @author Joaquin
*/
public class CSServerScanner implements Runnable {
boolean __salir = false;
int __port;
Vector<CSServerInfo> servers = new Vector<CSServerInfo>();
// if you know a better way of do this...please let me know.
byte[] request = {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0x54,(byte)0x53,(byte)0x6F,(byte)0x75,(byte)0x72,(byte)0x63,(byte)0x65,(byte)0x20,(byte)0x45,(byte)0x6E,(byte)0x67,(byte)0x69,(byte)0x6E,(byte)0x65,(byte)0x20,(byte)0x51,(byte)0x75,(byte)0x65,(byte)0x72,(byte)0x79,(byte)0x00};
public CSServerScanner( int port ) {
__port = port;
}
public void scan() {
Thread t = new Thread(this);
t.setName("Scanner Thread");
t.start();
}
public void stop() {
__salir = true;
}
public void run() {
try {
byte[] buf = new byte[255];
DatagramSocket socket = new DatagramSocket(); // Creamos el socket
DatagramPacket paquete = new DatagramPacket(buf,1); // Creamos el paquete que se enviar� por broadcast
DatagramPacket paquete2 = new DatagramPacket(buf,255); // Creamos el paquete para recibir datos
socket.setBroadcast(true);
paquete.setAddress(InetAddress.getByName("255.255.255.255"));
paquete.setPort(__port);
paquete.setData(request);
long d0 = System.nanoTime();
socket.send(paquete);
socket.setSoTimeout(200);
while (!__salir) {
try {
socket.receive(paquete2);
long d1 = System.nanoTime();// Cronometramos el tiempo que tard� en responder
// Now you have the response from server...do what you want...
System.out.println("Server Found at " + (d1-d0)/1000000 + " ms");
System.out.println("" + new CSServerInfo(paquete2.getData()).toString());
} catch (Exception e) {
//e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
RawDataParser.java
/**
*
* @author Joaquin
*/
public class RawByteParser {
/** Creates a new instance of RawByteParser */
public RawByteParser() {
}
/**
* Extracts a C formatted string out of the byte buffer. The buffer is not
* affected by this method. The resulting string is stored into the StringBuffer.
* @param offset Index of the first character of the string to be extracted
* @param data Byte buffer where the string is.
* @param res StringBuffer where the string will be stored
* @return the length of the extracted string plus the null character.
*/
public static int parseString(int offset, byte[] data, StringBuffer res) {
int i = offset;
while (data[i] != 0) {
char c = RawByteParser.parseChar(i, data);
res.append(c);
i++;
}
return i - offset + 1;
}
/**
* Read a long C value from the data buffer.
* This method takes 4 bytes of the buffer and convert them into a single long.
* The buffer is not affected by this method.
* @param offset index of the first byte to be parsed
* @param data byte buffer.
* @return the long value.
*/
public static long parseLong(int offset, byte[] data) {
long a = (data[offset]) & 0xffL;
long b = (data[offset+1]<<8) & 0xff00L;
long c = (data[offset+2]<<16) & 0xff0000L;
long d = (data[offset+3]<<24) & 0xff000000L;
long res = a + b + c + d;
return res;
}
/**
* Read a byte value from the data buffer and returns the unsigned representation.
* The buffer is not affected by this method.
* @param offset index of the first byte to be parsed
* @param data byte buffer.
* @return the byte parsed.
*/
public static byte parseByte(int offset, byte[] data) {
return (byte)(data[offset] & 0xff);
}
/**
* Read a char value from the data buffer.
* The buffer is not affected by this method.
* @param offset index of the char to be parsed
* @param data byte buffer.
* @return the char parsed.
*/
public static char parseChar(int offset, byte[] data) {
return (char)(data[offset] & 0xff);
}
}
ModData.java
/**
* This class is used to extract the information of the mod out of the byte stream.
*
* @author Joaquin
*/
public class ModData {
private StringBuffer URLInfo = new StringBuffer();
private StringBuffer URLDL = new StringBuffer();
private long modVersion;
private long modSize;
private byte svOnly;
private byte cldll;
private int size = 0;
/** Creates a new instance of ModData */
public ModData() {
}
public void parse(byte[] data, int begin) {
//URLInfodata
int pos = begin;
pos += RawByteParser.parseString(pos, data, URLInfo);
pos += RawByteParser.parseString(pos, data, URLDL);
pos++; // jump over the empty string ("\0");
modVersion = RawByteParser.parseLong(pos, data);
pos+=4;
modSize = RawByteParser.parseLong(pos, data) / (1024*1024); // in MB
pos+=4;
svOnly = data[pos];
pos++;
cldll = data[pos];
pos++;
size = pos - begin;
}
public int length() {
return size;
}
public String toString() {
StringBuffer res = new StringBuffer("ModiInfo:\n");
res.append("\nURLInfo: \t" + URLInfo);
res.append("\nURLDL: \t" + URLDL);
res.append("\nVersion: \t" + modVersion);
res.append("\nSize: \t" + modSize + " mb");
res.append("\nServerSide:\t" + svOnly);
res.append("\nCustom dll:\t" + cldll);
return res.toString();
}
}
Main.java
import java.util.Iterator;
import java.util.Vector;
/**
*
* @author Joaquin
*/
public class Main {
/** Creates a new instance of Main */
public Main() {
Vector<CSServerScanner> scanners = new Vector<CSServerScanner>();
/*
* Server's port ranges from 27015 to 27020
*/
for (int i=27015; i<27021; i++) {
CSServerScanner scanner = new CSServerScanner(i);
scanner.scan();
scanners.add(scanner);
}
// We wait for 2 secons...
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
// Close all the scanners
Iterator<CSServerScanner> it = scanners.iterator();
while (it.hasNext()){
it.next().stop();
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
new Main();
}
}