rconクライアントの作成

C++ Builder CEネットワークプログラミング BF2142用 remoteconsole上位互換プログラムの作成#1 オートログインまで

IndyのTIdTelnetなるコンポーネントを使います。受信は、このコンポーネントのイベントルーチンIdTelnetDataAvailableを使います。

void __fastcall TForm1::IdTelnet1DataAvailable(TIdTelnet *Sender, const TIdBytes Buffer)

{
	ret = BytesToString(Buffer);
	Memo1->Lines->Add(ret); // Append incoming data to memo
	mesg->Add(ret);

}

Memo1に受け取った行を表示しつつ、TStringList* mesgにも追加しておきます。前記事

によれば、ログインのプロトコルは、connectしたらrconサーバーが送ってくる文字列(バナーとDigest Seed)からSeedを取り出さないといけません。それが、

void __fastcall TForm1::LoginClick(TObject *Sender)
{
	int pos,where;

	pos = 0;
	where = 0;

	for( int count = 0 ; count < mesg->Count ; count ++ ){
		AnsiString aline = mesg->Strings[count];
		pos = aline.AnsiPos("### Digest seed: ");
		if( pos > 0 ){
			pos += 17;
			where = count;
			break;
		}

	}

	AnsiString extracted = mesg->Strings[where].SubString(pos,16);
	extracted += "yourpassword";
	Label1->Caption = extracted;

	AnsiString input = extracted;
	AnsiString md5Hash = THashMD5::GetHashString(input);

	Edit1->Text = "login " + md5Hash;



}

読者がrconへのremoteconsoleを書く必要性は低いかもしれませんが、同様なclient(例えばMineCraftへのremoteconsole)を書く可能性は一定程度あるかもしれませんので、少し詳細に説明しましょうか。まず

	int pos,where;

	pos = 0;
	where = 0;

	for( int count = 0 ; count < mesg->Count ; count ++ ){
		AnsiString aline = mesg->Strings[count];
		pos = aline.AnsiPos("### Digest seed: ");
		if( pos > 0 ){
			pos += 17;
			where = count;
			break;
		}

	}

	AnsiString extracted = mesg->Strings[where].SubString(pos,16);

で今までにため込んだサーバーからの応答を全行スキャンします。ある行に、“### Digest seed: “なる文字列があれば、その位置をposに保存します。そもそも無ければ、posはゼロです。posに17を加えます。つまりposが指しているindexを文字数分進めます。”### Digest seed: “の長さは、17です。なので、posが今指している文字はseedの先頭文字です。ですから、ここからseedの文字数分を抜き出せばよいですね。それが、

AnsiString extracted = mesg->Strings[where].SubString(pos,16);

です。これに設定した”yourpassword”を足して、md5形式でhash化します。

	extracted += "yourpassword";
	Label1->Caption = extracted;

	AnsiString input = extracted;
	AnsiString md5Hash = THashMD5::GetHashString(input);

	Edit1->Text = "login " + md5Hash;

極めてストレートフォワードです。よろしいでしょうか?ことばで表現すると、以下のようです。

### Battlefield 2142 default RCON/admin ready.
### Digest seed: OWEYbQPrghYNoQVp

からseedを抜き出します。サーバーにログインするのは、このseedの末尾にパスワードを足して、全体をmd5形式でhash化します。“login “に続けて、このhash化した文字列を連結して送ってやれば認証され、

Authentication successful, rcon ready.

で認証成功です。画面的には、

ボタンは、“Connect”から“Login”をクリックします。さらに“Send”

プログラム全体を提示しておきましょう。フォームは上記のようにして、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 <IdGlobal.hpp>
#include <IdTCPClient.hpp>
#include <IdTCPConnection.hpp>
#include <IdTelnet.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TIdTelnet *IdTelnet1;
	TButton *Connect;
	TMemo *Memo1;
	TEdit *Edit1;
	TButton *Send;
	TButton *Disconnect;
	TButton *Login;
	TLabel *Label1;
	TButton *TextDump;
	void __fastcall IdTelnet1DataAvailable(TIdTelnet *Sender, const TIdBytes Buffer);
	void __fastcall ConnectClick(TObject *Sender);
	void __fastcall DisconnectClick(TObject *Sender);
	void __fastcall SendClick(TObject *Sender);
	void __fastcall LoginClick(TObject *Sender);
	void __fastcall TextDumpClick(TObject *Sender);
	void __fastcall FormClose(TObject *Sender, TCloseAction &Action);

private:	// ユーザー宣言
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

Unit1.cppは、

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

#include <vcl.h>
#pragma hdrstop
#include <IdHashMessageDigest.hpp>
#include <System.Hash.hpp>  // For THashMD5
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

TStringList* mesg;
AnsiString ret = "";

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	mesg = new TStringList();

}
//---------------------------------------------------------------------------
void __fastcall TForm1::IdTelnet1DataAvailable(TIdTelnet *Sender, const TIdBytes Buffer)

{
	ret = BytesToString(Buffer);
	Memo1->Lines->Add(ret); // Append incoming data to memo
	mesg->Add(ret);

}
//---------------------------------------------------------------------------
void __fastcall TForm1::ConnectClick(TObject *Sender)
{
    try {
		IdTelnet1->Host = "192.168.0.201";   // e.g., "192.168.1.10"
		IdTelnet1->Port = 4711; // usually 23
        IdTelnet1->Connect();
        Memo1->Lines->Add("Connected to Telnet server.");
    }
    catch (const Exception &e) {
        Memo1->Lines->Add("Connection failed: " + e.Message);
    }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::DisconnectClick(TObject *Sender)
{
	if (IdTelnet1->Connected()) {
		IdTelnet1->Disconnect();
        Memo1->Lines->Add("Disconnected.");
    }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::SendClick(TObject *Sender)
{
	AnsiString cmd = Edit1->Text + "\n";

	 if (IdTelnet1->Connected()) {
		IdTelnet1->SendString(cmd);
    } else {
        Memo1->Lines->Add("Not connected.");
    }

}

/*
### Battlefield 2142 default RCON/admin ready.
### Digest seed: OWEYbQPrghYNoQVp
*/

//---------------------------------------------------------------------------
void __fastcall TForm1::LoginClick(TObject *Sender)
{
	int pos,where;

	pos = 0;
	where = 0;

	for( int count = 0 ; count < mesg->Count ; count ++ ){
		AnsiString aline = mesg->Strings[count];
		pos = aline.AnsiPos("### Digest seed: ");
		if( pos > 0 ){
			pos += 17;
			where = count;
			break;
		}

	}

	AnsiString extracted = mesg->Strings[where].SubString(pos,16);
	extracted += "yourpasswd";
	Label1->Caption = extracted;


	AnsiString input = extracted;
	AnsiString md5Hash = THashMD5::GetHashString(input);

	Edit1->Text = "login " + md5Hash;



}
//---------------------------------------------------------------------------

void __fastcall TForm1::TextDumpClick(TObject *Sender)
{
	mesg->SaveToFile("dump.txt");

}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
	 if( IdTelnet1->Connected())
	 	IdTelnet1->Disconnect();
}
//---------------------------------------------------------------------------

最終的にはlogin後、

exec admin.listplayers

等とコマンド送って、playerリストを得ます。これを取り込んでTListViewに表示するのが次の回です。(予定)

コメント