views:

325

answers:

3

I'm developing an application that detects Source-based games running on the LAN. Following up on the specifications provided by Valve, I have it narrowed down to exactly what I want: making a UDP connection on port 27015, sending an A2S_INFO query (0xFFFFFFFF followed by "TSource Engine Query") and parsing the binary reply.

This is the code I'm working with:

Dim sIP As String = "192.168.0.154"
Dim nPort As Integer = 27015

Dim connectionMessage As String = "ÿÿÿÿTSource Engine Query" & Chr(0)

Dim endPoint As New IPEndPoint(IPAddress.Parse(sIP), nPort)

Dim client As Socket = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 6000)
client.SendTo(Encoding.ASCII.GetBytes(connectionMessage), endPoint)
Console.WriteLine("Message sent to " & sIP & ":" & nPort & vbCrLf & connectionMessage)
Dim sBuffer(1400) As Byte
Try
    client.ReceiveFrom(sBuffer, endPoint)
    MsgBox(System.Text.Encoding.Unicode.GetString(sBuffer))
Catch ex As SocketException
    MsgBox("Failed to receive response on " & sIP & ":" & nPort & ":" & vbCrLf & ex.Message)
End Try

I keep getting SocketError.TimedOut exceptions, informing me:

Failed to receive response on 192.168.0.154:27015: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond

I'm fairly certain this is very close to the answer, because the following very simple PHP version works like a charm:

$ip = "192.168.0.154";
$port = 27015;

$fp = fsockopen("udp://".$ip,$port, $errno, $errstr);
socket_set_timeout($fp, 6);

$prefix = "\xff\xff\xff\xff";
$command = "TSource Engine Query";
$msg = "$prefix$command";
fputs($fp, $msg, strlen($msg));

$response = "";
do {
    $response .= fgets($fp, 16);
    $status = socket_get_status($fp);
} while ($status['unread_bytes']);
fclose ($fp);

echo $response;

This provides me with a reply in accordance with the specifications.

I'm close, but something's amiss with my VB.NET code. What am I doing wrong?

The solution

The encoding type is not valid for transmitting binary data. I replaced the following:

Encoding.ASCII.GetBytes(connectionMessage)

with:

System.Text.Encoding.[Default].GetBytes(connectionMessage)

And it was instantly solved.

A more robust solution, suggested by "pub", might be to specify the code page (although I haven't tested this):

Encoding.GetEncoding("iso-8859-1").GetBytes(connectionMessage)
+1  A: 

Rather than opening a Socket, try http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.aspx

Frank Farmer
Thanks for the suggestion. I've immediately given it a swing, but I get precisely the same thing. The query is sent, but the response simply times out after `udpClient.Client.ReceiveTimeout`.
Paul Lammertsma
+1  A: 

Did you take a look at this Multi-Threaded Game Server Browser in VB.Net article at CodeProject? If you don't want to re-use the code that's already been written, I'm sure you can check how they did it to see if you can find your problem.

Hope that helps, peace.

Jorge Israel Peña
I had found and tried the Gameserver query library (in C#, http://www.codeproject.com/KB/game/gameserverinfo.aspx), but not this one. I'll give it a shot, thanks!
Paul Lammertsma
Sure enough, their version works. I'll piece together what's wrong in my code and update the OP with the solution.
Paul Lammertsma
Glad to have been of help.
Jorge Israel Peña
I've edited the OP with the solution.
Paul Lammertsma
+2  A: 

For those couple guys around the world who doesn't have iso-8859-1 code page set as default on computer, use:

Encoding.GetEncoding("iso-8859-1").GetBytes(connectionMessage)

pub
This is actually a more robust answer. I'll update the OP.
Paul Lammertsma