サーバクエリーのプロトコルとアクセス例



・クエリンのことかー!
あちこにあるHL2DMやその他Sourceエンジンのゲームサーバにアクセス してサーバの情報を引っ張ってくるプロトコルについてのお話れす。

サーバに要求を送ってサーバ名や現在のマップを調べたりプレイヤーの情報 を貰ったりと、要するに、内臓のサーバブラウザや、GameSpy、ASEがやってる ことですよ。通信にはUDPを使用します(昔のQuakeなんかだとクエリだけはTCP/IPで やってたような気がします)。

・データ構成要素の型
送受信するデータの構成要素の型は以下のようになっとります。数値は全部リトルエンディアンでデータ列上に格納 されているのでmemcpyでお手軽に出し入れOK。
データ型 説明
byte 8ビット符号付き整数値
short 16ビット符号付き整数値
long 32ビット符号付き整数値
float 32ビット浮動小数点値
string 終端がNULL(0x00)の文字列

・基本的な伝送方法
Steamで送受信するパケットはデータ部が最大1400バイト、それにIPヘッダの分をちょい足したサイズになります。

サーバへ要求を行う際のコマンドは1400バイト以内に収まりますが、サーバから返されるデータは1400バイトを超過 する可能性があります。そんな場合にはデータが複数のパケットに分割されて送られてきます。

分割されているか否かはデータの先頭で識別します。


・分割なしの応答データ
分割なしの場合は先頭の4バイトに以下のデータが入ってきます(long型変数にコピーしたら-1)。
FF FF FF FF


・分割ありの応答データ
分割なしの場合は先頭9バイトに以下のようなデータが入ってきます。
FE FF FF FF XX XX XX XX YY

構成データの詳細はこうなってるよ〜。
名称 データ型 説明
データ種別 long 0xFFFFFFFE(符号付きで-2)
要求ID long 上のXX XX XX XXの部分。
サーバの発行するユニークなIDです、これで分割されたパケットが互いに同じ応答データのグループに属することを識別することができます。
パケット番号 long 上のYYの部分。
このパケットが同一応答データグループ内で何個中、何番目のデータであるのかを示します。下位4ビットに全体のパケット数(2〜15)、 上位4ビットにパケットの番号が入ってくる(この番号は0ベースなので0〜14の値になることに注意)。

(例)14個中3番目(0ベースの番号)のパケットの場合 → 0x3E

・どんな要求があるの〜?
サーバに対する要求の種類は以下4つ♪

  • A2S_SERVERQUERY_GETCHALLENGE

  • 下のA2S_PLAYER、A2S_RULESの要求に必要な問い合わせ番号を得る。まあ、問い合わせ番号は合言葉みたいなモンだと思えばいいよ。

  • A2S_INFO

  • サーバの基本情報取得(サーバ名だとかマップ名がずらずらーっと取れる)。

  • A2S_PLAYER

  • プレイヤー情報取得。

  • A2S_RULES

  • サーバルールの取得。


    ・A2S_SERVERQUERY_GETCHALLENGE
    問い合わせ番号を貰うよ♪

  • 要求データフォーマット

  • 名称 データ型 説明
    リクエストコード byte 'W'(0x57)

    要求の例:
    FF FF FF FF 57


  • 応答データフォーマット

  • 名称 データ型 説明
    レスポンスコード byte 'A'(0x41)
    問い合わせ番号 long A2S_PLAYER、A2S_RULESで使う問い合わせ番号です

    応答の例:
    FF FF FF FF 41 32 42 59 45

    ・A2S_INFO
    サーバの基本情報を取得するよ♪

  • 要求データフォーマット

  • 名称 データ型 説明
    リクエストコード byte 'T'(0x54)
    キーワード string(20bytes) NULL終端の"Source Engine Query"

    要求の例:
    FF FF FF FF 54 53 6F 75 72 63 65 20 45 6E 67 69 6E 65 20 51 75 65 72 79 00


  • 応答データフォーマット

  • 名称 データ型 説明
    レスポンスコード byte 'I'(0x49)
    バージョン byte ネットワークバージョン、今は0x07がSteamのバージョンれす
    サーバ名 string 見てのとおりです
    マップ名 string 見てのとおりです
    ゲームディレクトリ string ゲームのフォルダ名です、"hl2mp"とか"cstrike"が入る
    ゲームの説明 string 見てのとおりです
    AppID short スチームアプリケーションのID、HL2DMは320、CS:Sは240とかになります
    プレイヤー数 byte プレイヤーの人数だよ
    最大プレイヤー数 byte 最大プレイヤー数
    BOT数 byte 稼動してるBOTの頭数
    サーバ稼動モード識別 byte 'l'ならLISTENサーバ、'd'ならDEDICATEDサーバ
    OS識別 byte 'l'ならLinux、'w'ならWindows
    パスワード要求 byte 0x01ならJOINにパスワードが必要
    セキュリティー適用 byte 0x01ならVACによるセキュリティが適用されている
    ゲームバージョン string "1.0.0.22"だとか、似たようなもんが入ってるよきっと

    応答の例:
    FF FF FF FF 49 02 67 61 6D 65 32 78 73 2E 63 6F	....I.game2xs.co
    6D 20 43 6F 75 6E 74 65 72 2D 53 74 72 69 6B 65	m.Counter-Strike
    20 53 6F 75 72 63 65 20 23 31 00 64 65 5F 64 75	.Source.#1.de_du
    73 74 00 63 73 74 72 69 6B 65 00 43 6F 75 6E 74	st.cstrike.Count
    65 72 2D 53 74 72 69 6B 65 3A 20 53 6F 75 72 63	er-Strike: Sourc
    65 00 F0 00 05 10 04 64 6C 00 00 31 2E 30 2E 30	e......dl..1.0.0
    2E 32 32 00

    ・A2S_PLAYER
    プレイヤー情報を取得するよ♪

  • 要求データフォーマット

  • 名称 データ型 説明
    リクエストコード byte 'U'(0x55)
    問い合わせ番号 long A2S_SERVERQUERY_GETCHALLENGEで取得した番号

    要求の例:
    FF FF FF FF 55 [4バイト分の問い合わせ番号]


  • 応答データフォーマット

  • 名称 データ型 説明
    レスポンスコード byte 'D'(0x44)
    プレイヤー数 byte プレイヤー人数
    プレイヤー数の分だけ以降のデータが連続して格納されてます
    プレイヤー番号 byte 0ベースのプレイヤー番号
    プレイヤー名 string 見てのとおりです
    フラッグ数 long 敵をぶっ倒した数
    接続時間 float 接続してる時間(秒)


    ・A2S_RULES
    サーバルールを取得するよ♪

  • 要求データフォーマット

  • 名称 データ型 説明
    リクエストコード byte 'V'(0x56)
    問い合わせ番号 long A2S_SERVERQUERY_GETCHALLENGEで取得した番号

    要求の例:
    FF FF FF FF 56 [4バイト分の問い合わせ番号]


  • 応答データフォーマット

  • 名称 データ型 説明
    レスポンスコード byte 'E'(0x45)
    ルール数 short サーバルールの数
    ルール数の分だけ以降のデータが連続して格納されてます
    ルール名称 string 見てのとおりです
    ルール設定値 string 見てのとおりです

    ・実際にやってみる
    実際に簡易プログラムを作ってそれぞれのプロトコルでサーバへアクセスしてみました。

    VC++2005 Expressのコンソールアプリケーションです。どーぞ→プロジェクト丸ごとゲット
    但し、パケットが1400バイトを超過するケースに出くわさなかったのでひょっとしたらそこらへん 動くかどうかしりまへん。



    先頭の「サーバアドレス」と「サーバポート」を適当に書き換えてあちこちのサーバに行ってみましょう。
    server_info.cpp
    // うんころりんこ〜♪
    
    #include "stdafx.h"
    #include <winsock2.h>
    
    //------------------------------------------------------------------------
    
    // サーバアドレス
    #define SERVER_ADDRESS			"127.0.0.1"
    
    // サーバポート
    #define SERVER_PORT				27015
    
    //------------------------------------------------------------------------
    
    // 最大パケットサイズ
    #define MAX_OF_PACKET_SIZE		1400
    
    // 最大パケット数
    #define MAX_OF_PACKET_COUNT		15
    
    // 最大パケット番号
    #define MAX_OF_PACKET_NUMBER	(MAX_OF_PACKET_COUNT - 1)
    
    // 受信バッファサイズ
    #define RBUFFER_SIZE			(MAX_OF_PACKET_SIZE * MAX_OF_PACKET_COUNT)
    
    // 非分割データ識別コード
    #define SINGLE_PACKET_CODE		0xFFFFFFFF
    // 分割データ識別コード
    #define DEVIDED_PACKET_CODE		0xFFFFFFFE
    
    // タイムアウト時間
    #define RECEIVE_TIMEOUT		2000
    
    // 通信クラス
    class CA2SCommunicator {
    private:
    	// データバッファ
    	char *m_DataBuffer;
    	// 接続してる?
    	bool m_Connected;
    	// ソケット
    	SOCKET m_Socket;
    	// データサイズ
    	int m_DataSize;
    	// データインデクス
    	int m_DataIndex;
    public:
    	// コンストラクタ
    	CA2SCommunicator() {
    		// つながてない
    		m_Connected = false;
    
    		// データねえよ
    		m_DataSize = 0;
    
    		// 予想されるデータ長だけ領域用意しちゃえ
    		m_DataBuffer = new char[RBUFFER_SIZE];
    	}
    	// デストラクタ
    	~CA2SCommunicator() {
    		if (m_Connected) {
    			// つながってんなら切断
    			Disconnect();
    		}
    		delete m_DataBuffer;
    	}	
    
    	// サーバに接続
    	bool Connect(const char* hostAddress, int port);
    
    	// 切断
    	void Disconnect() {
    		closesocket(m_Socket);
    		WSACleanup();
    		m_Connected = false;
    	}
    
    	// つながってる?
    	bool HasConnected() {
    		return m_Connected;
    	}
    
    	// データあんの?
    	bool IsAvailableData() {
    		return (m_DataSize > 0);
    	}
    
    	// 送信
    	bool SendData(const char *buffer, int size, bool setHeader = true);
    	// 受信
    	bool ReceiveData();
    
    private:
    	// データコピー
    	bool DataCopy(void *target, int size) {
    		if ((m_DataSize - m_DataIndex) < size) {
    			// もう取り出せない
    			return false;
    		}
    		memcpy(target, &m_DataBuffer[m_DataIndex], size);
    		m_DataIndex += size;
    		return true;
    	}
    
    public:
    	// データ取り出し
    	template<class T>
    	bool GetData(T *data) {
    		return DataCopy(data, sizeof(T));
    	}
    
    	// string取り出し
    	bool GetString(char **data) {
    		static char tempChar[255];
    		wchar_t tempWchar[255];
    		memset(tempChar, 0, sizeof(tempChar));
    		memset(tempWchar, 0, sizeof(tempWchar));
    
    		if ((m_DataSize - (m_DataIndex + strlen(&m_DataBuffer[m_DataIndex]) + 1)) < 0) {
    			// もう取り出せない
    			return false;
    		}
    
    		// Unicode変換
    		MultiByteToWideChar(
    			CP_UTF8,
    			0,
    			&m_DataBuffer[m_DataIndex],
    			(signed)strlen(&m_DataBuffer[m_DataIndex]),
    			tempWchar,
    			sizeof(tempWchar)
    		);
    
    		// ANSI変換
    		WideCharToMultiByte(
    						CP_ACP,
    						WC_NO_BEST_FIT_CHARS,
    						tempWchar,
    						(signed)wcslen(tempWchar),
    						tempChar,
    						sizeof(tempChar),
    						NULL,
    						NULL
    						);
    
    		*data = tempChar;
    		m_DataIndex += (signed)(strlen(&m_DataBuffer[m_DataIndex]) + 1);
    		return true;
    	}
    
    	// データ表示
    	template<class T>
    	bool ShowData(char *caption, T *data) {
    		T tempData;
    		bool available = DataCopy(&tempData, sizeof(tempData));
    		if (available) {
    			printf(caption, tempData);
    			printf("\n");
    		}
    		if (data != NULL) {
    			*data = tempData;
    		}
    		return available;
    	}
    
    	// string表示
    	bool ShowString(char *caption, char **data) {
    		char* tempData;
    		bool available = GetString(&tempData);
    		if (available) {
    			printf(caption, tempData);
    			printf("\n");
    		}
    		if (data != NULL) {
    			*data = tempData;
    		}
    		return available;
    	}
    };
    
    // サーバに接続
    bool CA2SCommunicator::Connect(const char* hostAddress, int port) {
    	if (m_Connected) {
    		printf("Connect:もう繋がってんだろバカ!\n");			
    	}
    
    	// みなくていいけどWinSockのバージョン確認してみんよ
    	WORD wVersionRequested = MAKEWORD( 2, 2 );
    	WSADATA wsaData;
    	int err = WSAStartup( wVersionRequested, &wsaData );
    	if(err != 0){
    		printf("Connect:WSAStartupだめじゃん\n");;
    		return false;
    	}
    
    	// Socketくれよ
    	m_Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    	if(m_Socket == INVALID_SOCKET ){
    		printf("Connect:socketだめじゃん\n");
    		return false;
    	}
    
    	// 受信タイムアウト時間設定
    	long timeout = RECEIVE_TIMEOUT;
    	setsockopt(m_Socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
    
    	// つなげれ
    	sockaddr_in sockAddr;
    	memset(&sockAddr, 0, sizeof(sockAddr));
    	sockAddr.sin_family = AF_INET;
    	sockAddr.sin_addr.s_addr = inet_addr(hostAddress);
    	sockAddr.sin_port = htons(port);
    
    	err = connect(m_Socket, (const sockaddr *)&sockAddr, sizeof(sockAddr)); 
    	if(err == SOCKET_ERROR ){
    		printf("Connect:connectだめじゃん\n");
    		WSACleanup();
    		return false;
    	}
    
    	// (・∀・)つながたよ〜
    	m_Connected = true;
    	return true;
    }
    
    // 送信
    bool CA2SCommunicator::SendData(const char *buffer, int size, bool setHeader) {
    	if (!m_Connected) {
    		printf("SendData:繋がってねえよボケ!\n");			
    	}
    
    	int offset = 0;
    	long packetCode = SINGLE_PACKET_CODE;
    	char tempBuffer[MAX_OF_PACKET_SIZE];
    	memset(tempBuffer, 0, sizeof(tempBuffer));
    
    	// 送信バッファ作成
    	if (setHeader) {
    		// ヘッダ挿入の指示がある場合のみ
    		memcpy(tempBuffer, &packetCode, sizeof(packetCode));
    		offset += sizeof(packetCode);
    	}
    	memcpy(&tempBuffer[offset], buffer, size);
    
    	int err = send(m_Socket, tempBuffer, size + offset, 0);
    	if(err == SOCKET_ERROR ){
    		printf("SendData:sendだめじゃん\n");
    		return false;
    	}
    	return true;
    }
    
    // 受信
    bool CA2SCommunicator::ReceiveData() {
    	if (!m_Connected) {
    		printf("ReceiveData:繋がってねえよボケ!\n");			
    	}
    
    	// サイズ初期化
    	m_DataSize = 0;
    
    	// バッファ初期化
    	memset(m_DataBuffer, 0, RBUFFER_SIZE);
    
    	// 1つ目のパケット受信
    	long packetCode;
    	char tempBuffer[MAX_OF_PACKET_SIZE];
    	int dataSize = recv(m_Socket, tempBuffer, sizeof(tempBuffer), 0);
    	if (dataSize < (signed)sizeof(packetCode)) {
    		// 最低限4バイトないとだめ
    		printf("ReceiveData:受信データないよ\n");
    		return false;
    	}
    
    	// 先頭のコードくれや
    	memcpy(&packetCode, tempBuffer, sizeof(packetCode));
    
    	if (packetCode == SINGLE_PACKET_CODE) {
    		// 非分割データ
    		m_DataSize = dataSize - sizeof(packetCode);
    		memcpy(m_DataBuffer, &tempBuffer[sizeof(packetCode)], m_DataSize);
    
    		// データインデックス初期化
    		m_DataIndex = 0;
    		return true;
    	} else if (packetCode != DEVIDED_PACKET_CODE) {
    		// コードが腐ってる
    		printf("ReceiveData:分割コード腐ってる:%d\n", packetCode);
    		return false;
    	} else if (dataSize < (sizeof(long) * 2 + sizeof(char))) {
    		// 分割ヘッダは5バイトないとだめ
    		printf("ReceiveData:分割ヘッダサイズ不足\n");
    		return false;
    	}
    
    	// 第一分割データの情報コピー
    	long requestID = 0;
    	long totalPacket;
    	long currentPacket;
    
    	// 残りのパケットのデータを取る
    	for (int i = 0; i < MAX_OF_PACKET_COUNT; i++) {
    		if (i > 0) {
    			// 1番以降のパケットを受信(0番はもう取ってある)
    			dataSize = recv(m_Socket, tempBuffer, sizeof(tempBuffer), 0);
    		}
    
    		int offset = 0;
    
    		// 分割コード
    		memcpy(&packetCode, tempBuffer, sizeof(packetCode));
    		offset += sizeof(packetCode);
    
    		// 要求ID
    		long oldRequestID = requestID;
    		memcpy(&requestID, &tempBuffer[offset], sizeof(requestID));
    		offset += sizeof(requestID);
    
    		// 全パケット数
    		totalPacket = (tempBuffer[offset] & 0x0F);
    		// 現在のパケット番号
    		currentPacket = (tempBuffer[offset] & 0xF0) >> 4;
    		offset += sizeof(char);
    
    		if (currentPacket != i) {
    			printf("ReceiveData:パケット番号が狂ってる\n");
    			return false;
    		} else if (totalPacket < 2 || totalPacket <= currentPacket) {
    			printf("ReceiveData:パケット個数が狂ってる\n");
    			return false;
    		} else if ( i > 0 && oldRequestID != requestID) {
    			printf("ReceiveData:要求IDの異なるパケットが割り込んできた\n");
    		}
    		memcpy(&m_DataBuffer[m_DataSize], &tempBuffer[offset], (dataSize - offset));
    		m_DataSize += (dataSize - offset);
    	}
    
    	// データインデックス初期化
    	m_DataIndex = 0;
    	return true;
    }
    
    //------------------------------------------------------------------------
    
    // A2S_SERVERQUERY_GETCHALLENGE
    #define A2S_SERVERQUERY_GETCHALLENGE {	\
    	'W'			\
    }
    #define A2S_SERVERQUERY_GETCHALLENGE_RES	'A'
    
    // A2S_INFO
    #define A2S_INFO {			\
    	'T',"Source Engine Query"	\
    }
    #define A2S_INFO_RES			'I'
    
    // A2S_PLAYER
    #define A2S_PLAYER {			\
    	'U', 0x00, 0x00, 0x00, 0x00	\
    }
    #define A2S_PLAYER_RES		'D'
    
    // A2S_RULES
    #define A2S_RULES {			\
    	'V', 0x00, 0x00, 0x00, 0x00	\
    }
    #define A2S_RULES_RES			'E'
    
    //------------------------------------------------------------------------
    
    // グローバル君
    CA2SCommunicator g_Com;
    
    //------------------------------------------------------------------------
    
    // A2S_SERVERQUERY_GETCHALLENGE
    bool GetChallengeNumber(long &challengeNumber) {
    	bool aborted = false;
    
    	// 送信
    	char request[] = A2S_SERVERQUERY_GETCHALLENGE;
    	aborted = !g_Com.SendData(request, sizeof(request));
    
    	if (!aborted) {
    		// 受信
    		aborted = !g_Com.ReceiveData();
    	}
    	if (!aborted && g_Com.IsAvailableData()) {
    		// レスポンスコード
    		char responseCode = '\0';
    		aborted = !g_Com.GetData(&responseCode);
    		aborted = (aborted || responseCode != A2S_SERVERQUERY_GETCHALLENGE_RES);
    		if (!aborted) {
    			// 問い合わせ番号取得
    			aborted = !g_Com.ShowData("[問い合わせ番号] %08X", &challengeNumber);
    //				g_Com.GetData(&challengeNumber);
    		}
    	}
    	return !aborted;
    }
    
    // A2S_INFO
    bool GetServerInfo() {
    	bool aborted = false;
    
    	// 送信
    	char request[] = A2S_INFO;
    	aborted = !g_Com.SendData(request, sizeof(request));
    
    	if (!aborted) {
    		// 受信
    		aborted = !g_Com.ReceiveData();
    	}
    	if (!aborted && g_Com.IsAvailableData()) {
    		// レスポンスコード
    		char responseCode = '\0';
    		aborted = !g_Com.GetData(&responseCode);
    		aborted = (aborted || responseCode != A2S_INFO_RES);
    		if (!aborted) {
    			int errCount = 0;
    			// サーバ情報を取り出し、表示
    			char tempChar;
    			short tempShort;
    
    			errCount += !g_Com.ShowData("[バージョン] %02X", &tempChar);
    			errCount += !g_Com.ShowString("[サーバ名] %s", NULL);
    			errCount += !g_Com.ShowString("[マップ名] %s", NULL);
    			errCount += !g_Com.ShowString("[ゲームディレクトリ] %s", NULL);
    			errCount += !g_Com.ShowString("[ゲームの説明] %s", NULL);
    			errCount += !g_Com.ShowData("[AppID] %d", &tempShort);
    			errCount += !g_Com.ShowData("[プレイヤー数] %d", &tempChar);
    			errCount += !g_Com.ShowData("[最大プレイヤー数] %d", &tempChar);
    			errCount += !g_Com.ShowData("[BOT数] %d", &tempChar);
    			errCount += !g_Com.ShowData("[サーバ稼動モード識別] %c", &tempChar);
    			errCount += !g_Com.ShowData("[OS識別] %c", &tempChar);
    			errCount += !g_Com.ShowData("[パスワード要求] %02X", &tempChar);
    			errCount += !g_Com.ShowData("[セキュリティー適用] %02X", &tempChar);
    			errCount += !g_Com.ShowString("[ゲームバージョン] %s", NULL);
    
    			aborted = (errCount > 0);
    		}
    	}
    	return !aborted;
    
    }
    
    // A2S_PLAYER
    bool GetPlayer(long challengeNumber) {
    	bool aborted = false;
    
    	// 送信
    	char request[] = A2S_PLAYER;
    	memcpy(&request[sizeof(char)], &challengeNumber, sizeof(challengeNumber));
    	aborted = !g_Com.SendData(request, sizeof(request));
    
    	if (!aborted) {
    		// 受信
    		aborted = !g_Com.ReceiveData();
    	}
    	if (!aborted && g_Com.IsAvailableData()) {
    		// レスポンスコード
    		char responseCode = '\0';
    		aborted = !g_Com.GetData(&responseCode);
    		aborted = (aborted || responseCode != A2S_PLAYER_RES);
    		if (!aborted) {
    			char playerCount = 0;
    			int errCount = 0;
    
    			// サーバ情報を取り出し、表示
    			char tempChar;
    			long tempLong;
    			float tempFloat;
    
    			errCount += !g_Com.ShowData("[プレイヤー数] %d", &playerCount);
    
    			for (int i = 0; i < playerCount && errCount == 0; i++) {
    				errCount += !g_Com.ShowData(" [プレイヤー番号] %d", &tempChar);
    				errCount += !g_Com.ShowString(" [プレイヤー名] %s", NULL);
    				errCount += !g_Com.ShowData(" [フラッグ数] %d", &tempLong);
    				errCount += !g_Com.ShowData(" [接続時間] %.1f", &tempFloat);
    			}
    			aborted = (errCount > 0);
    		}
    	}
    	return !aborted;
    }
    
    // A2S_RULES
    bool GetRules(long challengeNumber) {
    	bool aborted = false;
    
    	// 送信
    	char request[] = A2S_RULES;
    	memcpy(&request[sizeof(char)], &challengeNumber, sizeof(challengeNumber));
    	aborted = !g_Com.SendData(request, sizeof(request));
    
    	if (!aborted) {
    		// 受信
    		aborted = !g_Com.ReceiveData();
    	}
    	if (!aborted && g_Com.IsAvailableData()) {
    		// レスポンスコード
    		char responseCode = '\0';
    		aborted = !g_Com.GetData(&responseCode);
    		aborted = (aborted || responseCode != A2S_RULES_RES);
    		if (!aborted) {
    			short ruleCount = 0;
    			int errCount = 0;
    
    			// サーバ情報を取り出し、表示
    			errCount += !g_Com.ShowData("[ルール数] %d", &ruleCount);
    
    			for (int i = 0; i < ruleCount && errCount == 0; i++) {
    				errCount += !g_Com.ShowString(" [ルール名称] %s", NULL);
    				errCount += !g_Com.ShowString(" [ルール設定値] %s", NULL);
    			}
    			aborted = (errCount > 0);
    		}
    	}
    	return !aborted;
    }
    
    // めーいーんー
    int _tmain(int argc, _TCHAR* argv[])
    {
    	// 問い合わせ番号
    	long challengeNumber;
    
    	// 接続
    	bool aborted = !g_Com.Connect(SERVER_ADDRESS, SERVER_PORT);
    
    	//-------------------------------------
    	// A2S_SERVERQUERY_GETCHALLENGE
    	//-------------------------------------
    	if (!aborted) {
    		printf("《《A2S_SERVERQUERY_GETCHALLENGE》》\n");
    		aborted = !GetChallengeNumber(challengeNumber);
    	}
    
    	//-------------------------------------
    	// A2S_INFO
    	//-------------------------------------
    	if (!aborted) {
    		printf("《《A2S_INFO》》\n");
    		aborted = !GetServerInfo();
    	}
    
    	//-------------------------------------
    	// A2S_PLAYER
    	//-------------------------------------
    	if (!aborted) {
    		printf("《《A2S_PLAYER》》\n");
    		aborted = !GetPlayer(challengeNumber);
    	}
    
    	//-------------------------------------
    	// A2S_RULES
    	//-------------------------------------
    	if (!aborted) {
    		printf("《《A2S_RULES》》\n");
    		aborted = !GetRules(challengeNumber);
    	}
    
    	// 切断
    	g_Com.Disconnect();
    
    	if (aborted) {
    		printf("エラーが発生しています。\n");
    	}
    	return 0;
    }
    

    (´・ω・`)つもどる