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;
//---------------------------------------------------------------------------
#endifUnit1.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に表示するのが次の回です。(予定)



コメント