サーバー動いてるかな?

C++ Builder CE ネットワークプログラミング TIdUDPClientを使ってInquiry

ゲーム内サーバーブラウザは、マスターサーバー(今時だと名前があれですかね)から得られたアドバタイズしている各サーバーのIPアドレスとつなげるべきポート番号から現在のサーバーの情報を得て、一覧表示します。その時のパケットはUDPを使いますが、Indyを使うのであれば、TIdUDPClientを使うことになります。まず具体的なプロトコルを調べましょう。検索すると色々と出てきますが、正確なものはありませんので、無駄に時間を使うのは止めて、ちゃんと動いているgamedigをdebugモードで動かして検分してみましょう。

gamedig --debug --type battlefield2142 192.168.0.201:29900

のように–debugなるオプション付きで起動すれば情報が得られます。重要な部分だけを抜粋します、

192.168.0.201:29900 UDP(59687)-->
Buffer length: 11 bytes
fe fd 09 00 00 00 01 00 00 00 00


192.168.0.201:29900 <--UDP(59687)
Buffer length: 7 bytes
09 00 00 00 01 30 00
               0

Q#0 Registered RTT: 13ms
Q#0 UDP send finished by callback
Q#0 Received challenge key: 0
192.168.0.201:29900 UDP(59687)-->
Buffer length: 11 bytes
fe fd 00 00 00 00 01 ff ff ff 01

ここのやり取りは、challenge and responseになっています。最初に

fe fd 09 00 00 00 01 00 00 00 00

なるバイト列をサーバーのポート29900へ送ります。それに対してサーバーは、

09 00 00 00 01 30 00

なるキーを送ってきていますが、キーの値は0です。(サーバーで不変)最終的には、

fe fd 00 00 00 00 01 ff ff ff 01

なるバイト列を再度送ると、サーバーの現状を1400バイトのデータとして返してきますので、それを解釈すればよいことになります。データの冒頭は、

192.168.0.201:29900 <--UDP(59687)
Buffer length: 1400 bytes
00 00 00 00 01 73 70 6c 69 74 6e 75 6d 00 00 00 68 6f 73 74 6e
               s  p  l  i  t  n  u  m           h  o  s  t  n
61 6d 65 00 42 61 74 74 6c 65 66 69 65 6c 64 20 32 31 34 32 20
a  m  e     B  a  t  t  l  e  f  i  e  l  d     2  1  4  2
46 45 00 67 61 6d 65 6e 61 6d 65 00 73 74 65 6c 6c 61 00 67 61
F  E     g  a  m  e  n  a  m  e     s  t  e  l  l  a     g  a
6d 65 76 65 72 00 31 2e 31 30 2e 31 31 32 2e 30 00 6d 61 70 6e
m  e  v  e  r     1  .  1  0  .  1  1  2  .  0     m  a  p  n
61 6d 65 00 53 69 64 69 5f 50 6f 77 65 72 5f 50 6c 61 6e 74 5f
a  m  e     S  i  d  i  _  P  o  w  e  r  _  P  l  a  n  t  _
63 6f 6f 70 00 67 61 6d 65 74 79 70 65 00 67 70 6d 5f 63 6f 6f
c  o  o  p     g  a  m  e  t  y  p  e     g  p  m  _  c  o  o
70 00 67 61 6d 65 76 61 72 69 61 6e 74 00 62 66 32 31 34 32 00
p     g  a  m  e  v  a  r  i  a  n  t     b  f  2  1  4  2
6e 75 6d 70 6c 61 79 65 72 73 00 30 00 6d 61 78 70 6c 61 79 65
n  u  m  p  l  a  y  e  r  s     0     m  a  x  p  l  a  y  e
72 73 00 33 32 00 67 61 6d 65 6d 6f 64 65 00 6f 70 65 6e 70 6c
r  s     3  2     g  a  m  e  m  o  d  e     o  p  e  n  p  l

のようなバイト列ですが、asciiダンプから、順次hostname,gamename,gamever,mapname,gametype,numplayers等が得られていることがわかります。文字列の区切りはnullですね。Cでは極めて自然なので、これらを文字列に分解していくのはそれほど手間はかかりません。ここまで判明すれば、あとは実際にUDPパケットの送信と受信を行えば良いわけです。まずフォームは、

こんな感じで、Tlabelx3とを適当に配置します。それから主役のTIdUDPClientx1ですね。上図では隠れちゃっていますが、こちらのプロパティーは、

こんな感じです。次は、Unit1.hですね。

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <IdBaseComponent.hpp>
#include <IdComponent.hpp>
#include <IdUDPBase.hpp>
#include <IdUDPClient.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TIdUDPClient *IdUDPClient1;
	TLabel *Status;
	TLabel *MapName;
	TLabel *NumPlayers;
private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

最後に、Unit1.cpp。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

/*
00 00 00 00 01 73 70 6c 69 74 6e 75 6d 00 00 00 68 6f 73 74 6e
			   s  p  l  i  t  n  u  m           h  o  s  t  n
61 6d 65 00 42 61 74 74 6c 65 66 69 65 6c 64 20 32 31 34 32 20
a  m  e     B  a  t  t  l  e  f  i  e  l  d     2  1  4  2
46 45 00 67 61 6d 65 6e 61 6d 65 00 73 74 65 6c 6c 61 00 67 61
F  E     g  a  m  e  n  a  m  e     s  t  e  l  l  a     g  a
6d 65 76 65 72 00 31 2e 31 30 2e 31 31 32 2e 30 00 6d 61 70 6e
m  e  v  e  r     1  .  1  0  .  1  1  2  .  0     m  a  p  n
61 6d 65 00 42 72 69 64 67 65 5f 61 74 5f 52 65 6d 61 67 65 6e
a  m  e     B  r  i  d  g  e  _  a  t  _  R  e  m  a  g  e  n
5f 63 6f 6f 70 00 67 61 6d 65 74 79 70 65 00 67 70 6d 5f 63 6f
_  c  o  o  p     g  a  m  e  t  y  p  e     g  p  m  _  c  o
6f 70 00 67 61 6d 65 76 61 72 69 61 6e 74 00 62 66 32 31 34 32
o  p     g  a  m  e  v  a  r  i  a  n  t     b  f  2  1  4  2
00 6e 75 6d 70 6c 61 79 65 72 73 00 30 00 6d 61 78 70 6c 61 79
   n  u  m  p  l  a  y  e  r  s     0     m  a  x  p  l  a  y

hostname 0 1
gamename 2 3
gamever  4 5
napname  6 7
gametype 8 9
gamevariant 10 11
numplayers  12 13


	char *str = "abcedfg";
	AnsiString tx = StrPas(str);
	Edit1->Text = tx;

*/

void interp_bf2142_response(TIdBytes& buf)
{
	unsigned char* p = &buf[16];    // start point

	for( int count = 0 ; count < 30 ; count ++ ){
		AnsiString next =StrPas((const char*)p);
		//Form1->Memo1->Lines->Add(next);

		switch (count) {
			case 7:
				Form1->MapName->Caption = next;
				break;
			case 13:
				Form1->NumPlayers->Caption = next;
				break;
		default:


			;
		}
		while( *++p != '\0' )
			;
		++p;


	}

}

void get_aserver_info()
 {

	Form1->IdUDPClient1->Active = true;

	unsigned char challenge_req[] = {0xFE,0xFD,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00};
	TIdBytes sendbuf;
	TIdBytes recvbuf;

    recvbuf.set_length(8192);
	sendbuf.set_length(11);
	//sendbuf->set_Data(challange_req);
	memcpy(&sendbuf[0], challenge_req, 11);

	Form1->IdUDPClient1->SendBuffer(sendbuf);

	//Form1->Memo1->Lines->Add("init req");

	Form1->IdUDPClient1->ReceiveTimeout = 500;
	Form1->IdUDPClient1->ReceiveBuffer(recvbuf); // abondan
	//Form1->Memo1->Lines->Add("received: " + BytesToString(recvbuf));


	unsigned char challenge_req2[] = {0xFE,0xFD,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff,0x01};
	TIdBytes sendbuf2;

	sendbuf2.set_length(11);
	//sendbuf->set_Data(challange_req);
	memcpy(&sendbuf2[0], challenge_req2, 11);

	int bytes_rec;

	Form1->IdUDPClient1->SendBuffer(sendbuf2);
	//udp->SendBuffer(aentry.ip,StrToInt(aentry.port),sendbuf2);
	//Form1->Memo1->Lines->Add("2nd req");

	bytes_rec = Form1->IdUDPClient1->ReceiveBuffer(recvbuf);

	//Form1->Memo1->Lines->Add(IntToStr(bytes_rec) + " bytes received");

	if( bytes_rec == 0 ){
		Form1->Status->Caption = "Dead";
		Form1->MapName->Caption = "";
		Form1->NumPlayers->Caption = "";
	}
	else {
		Form1->Status->Caption = "Live";
		interp_bf2142_response(recvbuf);
	}





 }

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
    get_aserver_info();
}
//---------------------------------------------------------------------------

実行例は、

落ちている時は、

落ちている時に自動起動する仕組みは考え中です。

コメント