30: コードで生成する画像付きボタンでワープする

概要

13回の記事 ではキー入力で割り当てた場所にワープするチートを作り、27回の記事 ではそのDLLを有効化/無効化するボタンをGUIクライアントに追加しました。 しかし、キーでワープする方が早いですが、ワープ先が多いとどのキーがどの場所に対応するのかが覚えづらいです。
今回は、ワープ先の画像が付いたボタンをコードから生成し、そのボタンを押すことでワープする事を目標にやっていきます。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、Visual Studio 2019を使用しC++/CLIで記述する。ソースコードはまとめてページの一番下にある。 この記事は 25回 からの続きです。どんな感じかのデモは一番下のgifを参考にしていただければ。

サーバー側にワープ機能の追加

今回はワープさせる機能をサーバーに直接実装しちゃいます。そのため、以前用意したヘッダファイルが必要なので、まだ入れてなかったら以下のファイルを入れましょう。
18回 からplayer.h, player.cpp, general-cheats.h, general-cheats.cppを持ってきて、 20回 からgeneral-movement.hを持ってきましょう。

ワープは warp 場所の名前 のようなコマンドにするので、その内容をdllmain.cppに実装します。
まず、ヘッダファイルのインクルードは以下のようにします。 そして、Playerを以下のようにしてインスタンス化します。 次に、実際のコマンドの部分を以下のように実装します。 ここで赤線になってる WarpLocations は次から作成する連想配列(map)です。
これはこんな感じでキーで指定した値が取れるものです。
map<string, int> m;
m["hoge"] = 3;
m["fuga"] = 5;
cout << m["fuga"] << endl;  // 出力結果: 5
今回は WarpLocations["ワープ先の場所の名前"] = Vector3の座標 のようにしますが、 ワープ先は一杯登録してもコードが汚くならないように、新しく warp-locations.h を設け、そこに以下のようにして書きます。 ここで、strkeyという構造体が定義されてmapの三番目の所で使われているのが注意点です。 キーは char* なので単に map<const char*, Vector3> とすると文字列が格納されているアドレス自体がキーとして使われてしまいます。 よってアドレスでは無く、文字列の内容でキーを識別してほしい事になります。このようなキーの識別方法を変更するのに、mapの三番目の引数が使えます。 mapは内部でキーをソートして管理しており、そのソートに使われる比較用のクラスを三番目の所で指定する事ができます。 デフォルトではタダの < なので、char* a < char* b のようにキーがソートされるため、 アドレス単位で識別されますが、この比較方法を strcmp(a,b) < 0 に変更すれば、strcmp は第一引数の方がアルファベット順で早いとマイナス値を返すため、アルファベット順にキーがソートされます。 どうソートされるか自体はどうでもよいですが、ソートに使用する比較方法を変更する事で、mapがキーをどのように識別するかが変わるので、 strcmpを指定する事によりアドレスではなく文字列がアルファベット順で早いか遅いかそれとも一致するかで識別するようにできます。

これで後は、dllmain.cpp の方に #include "warp-locations.h" を追加すれば赤線が消えるので、ビルドして終わりですが、 Vector3 の再定義みたいなエラー>が出てくるかもしれません。これはwarp-locations.hでgeneral-movement.hをインクルードしており、 さらに、dllmain.cppでこのwarp-locations.hとgeneral-movement.hをインクルードしているため、間接的に二回general-movement.hをインクルードしてるからです。 このように複数の場所で同じものをインクルードしても干渉させないように、以下の用にgeneral-movement.hに #pragma once を入れましょう。 これでビルド通るはずなのでビルドしましょう。

ワープ先の写真や座標の下準備

画像付きボタンを作る前にまずはそもそもの画像や座標の情報が必要なので、スクショで取りましょう。 ここで注意する点は、画像の名前はwarp-locations.hのmapのキーの値と同じにしてください。 座標に関しては、13回の記事 のチートを使えば Ctrl-b でゲーム上で今いる場所の座標が表示されるので、それで取得してください。

クライアント側の実装

WarpWindowのボタンを配置していきます。 現状、WarpWindowにはkey-warpのボタンがWarpTopPanelに一つあるだけだと思うので、このパネルの下にもう一つ「Panel」を追加しましょう。 プロパティは以下のようにします。 出来たら、「ImageList」というコンポーネントをWarpMainPanelにドラッグ&ドロップしましょう。 プロパティは デザインのNameを WarpImageList に変えるだけで大丈夫です。
完成したら以下のようになっているはずです。
ここからコードに移るので、WarpWindow.hのコードを開きましょう。
画像を追加してほしいタイミングは最初、つまりWarpWindowというフォームがインスタンス化された時なので、 コンストラクタの中に画像のボタン等を追加するコードを書いていきます。
まずは、以下のように書きましょう。 重要なのは以下の二つです。 WarpImageList->ColorDepthとその下の行では、画像全般の設定をしています。ColorDepth をこのように設定しないとなんか白黒の色が変な画像になってしまいます。 また、Size もこれくらいの値をセットしないとめっちゃ小さくなってしまいます。
画像は images ディレクトリのような一か所のフォルダにまとめて保存しておき、 System::IOネームスペースの関数である Directory::GetFiles によりフォルダ内の全ファイルを取得します。 それを for each文で回し、各画像自体はWarpImageListに、各画像のファイル名(ワープ先の名前)をlocationName に入れています。

続けて、以下のコードを足しましょう。 "画像ボタン" は「PictureBox」というコンポーネントで作りますが、一々デザインビューでワープ先の画像分 手動で追加するのは手間なので、上記のようにコードで作成します。 画像を配置する位置を決めるための計算がややこしいですが、以下の用に 左=>右, 上=>下 の順に生成しています。 上記のコードで赤線となっているのは、このPictureBox(画像)を押したときに発生するイベントハンドラーを定義しています。 画像をクリックしたら、チートサーバーに warp 場所の名前 のようなコマンドを送りたいため、 WarpWindow.hの下の方に以下のようにハンドラーの内容を記述しましょう。 ここでポイントとなるのが、warp 場所の名前場所の名前 をTagプロパティを介して受け渡しているという点です。 Tagプロパティとは何を入れても良い便利な入れ物です。これは謎なのですが、EventHandlerの第一引数はこのイベントを発生させたオブジェクトが入るのですが、 thisすなわちこのフォーム自体以外は入れる事ができません。しかし、上図のように PictureBox^ に型変換するとこのイベントを発生させたPictureBoxのオブジェクトを取る事ができます。

Tag に入れてる locationName の画像の名前はマネージドコードの String型ですが、 サーバー側にはこれを char* に変換して送る必要があります。この変換に使用する Marshal クラスが赤くなってるので、 using namespace System::Runtime::InteropServices; を追加しましょう。

最後に、上図のように画像の下に場所の名前のラベルを追加したいので、以下のようにして終了です。

実行してみる

これでサーバーをインジェクトしてクライアントを立ち上げてボタン押してみると以下のようになるはずです。 少し注意としては、ワープ地点はy座標を少し高めに設定しておかないとレンダリングが間に合わずに地面を突き抜けてしまう事があります。

ソースコード

サーバー側のdllmain.cpp

#include "pch.h"
#include "general-cheats.h"
#include "SocketServer.h"
#include "player.h"
#include "warp-locations.h"

using namespace std;

SocketServer* server;
Player* player;

typedef void* (Disable)();
typedef void* (Hook)();
typedef void* (UnHook)();
typedef void* (SetBoost)(float val);

void init() {
    std::vector<const char*> cheatDlls = {
        "demo-nop-damage2"
    };

    for (const char* cheatDll : cheatDlls) {
        char path[MAX_PATH];
        snprintf(path, MAX_PATH, "C:\\Users\\<username>\\Desktop\\dlli\\%s\\Debug\\%s.dll", cheatDll, cheatDll);
        LoadLibrary(path);
    }
}

DWORD WINAPI ThreadMain(LPVOID params) {
    server = new SocketServer();
    player = new Player();

    init();

    while (1) {
        CmdProtocol msg;
        server->RecvCmd(&msg);
        if (strcmp(msg.cmd, "load") == 0) {
            char path[MAX_PATH];
            snprintf(path, MAX_PATH, "C:\\Users\\<username>\\Desktop\\dlli\\%s\\Debug\\%s.dll", msg.argument, msg.argument);
            LoadLibrary(path);
        }
        if (strcmp(msg.cmd, "free") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            Disable* disable = (Disable*)GetProcAddress(hDll, "Disable");
            disable();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "hook") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            Hook* hook = (Hook*)GetProcAddress(hDll, "Hook");
            hook();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "unhook") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            UnHook* unhook = (UnHook*)GetProcAddress(hDll, "UnHook");
            unhook();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "boost") == 0) {
            HMODULE hDll = GetModuleHandle("quaternion.dll");
            SetBoost* setboost = (SetBoost*)GetProcAddress(hDll, "SetBoost");
            setboost(atof(msg.argument));
        }
        if (strcmp(msg.cmd, "warp") == 0) {
            player->SetLocation(WarpLocations[msg.argument], { 1, 1, 1 });
        }

        server->CloseSocket();
    }

    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        CreateThread(0, 0, ThreadMain, hModule, 0, 0);
    }
    return TRUE;
}

サーバー側のwarp-locations.h

#pragma once
#include <map>
#include "general-movement.h"

struct strkey {
    bool operator()(const char* a, const char* b) const {
        return strcmp(a, b) < 0;
    }
};

std::map<const char*, Vector3, strkey> WarpLocations = {
    {"Angry Bear Woods", Vector3(23306.3, 1299.9, 78270.123)},
    {"Ballmer Peak", Vector3(-11726.4, 10627.1, -2454.97)},
    {"Bear Woods", Vector3(-40404, 3242.52, -11314.1)},
    {"Bridge", Vector3(-13168.6, 867.574, 20057)},
    {"Cow Island", Vector3(-248752, 1514, 259755)},
    {"Gold Farm", Vector3(42904.8, 2231, 21431.9)},
    {"Pirate Bay", Vector3(58591.3, 309.947, 43392)},
    {"Sewer Exit", Vector3(-35814, 1203.15, -44167.1)},
    {"Spell Book", Vector3(-55980.4, 323, -43606.2)},
    {"Unbearable Woods", Vector3(63925.3, 2660, -7578.31)},
    {"Underground Sewer", Vector3(-41099.7, 750.585, -53473.1)},
    {"Village", Vector3(-17770.8, 2404.02, -39940)},
    {"Molten Cave", Vector3(2726.87, 1409.91, 52798.2) },
    {"Ice Spider Cave", Vector3(5138.38, 7112.91, 58225.1)},
    {"Tunnel", Vector3(-10732.7, 4399, 49984.8)},
    {"Fort Blox", Vector3(-6304.2, 2274.15, -14215.1)},
    {"Magmarok Cave", Vector3(-7111.94, 1536, 52252.7)}
};
※ general-movement.h に #pragma once 入れるの忘れずに

クライアント側のWarpWindow.hで追加したコード

using namespace System::IO;
using namespace System::Runtime::InteropServices;

// -------------------- 略 -------------------------------

WarpWindow(void)
{
    InitializeComponent();
            
    WarpImageList->ColorDepth = ColorDepth::Depth32Bit;
    WarpImageList->ImageSize = System::Drawing::Size(255, 255);

    System::Collections::Generic::List<String^> locationName;

    for each (String ^ path in Directory::GetFiles("C:\\Users\\<username>\\Desktop\\dlli\\cheat-client\\images")) {
        WarpImageList->Images->Add(Image::FromFile(path));
        locationName.Add(Path::ChangeExtension(Path::GetFileName(path), nullptr));
    }

    for (int i = 0; i < WarpImageList->Images->Count; i++) {
        PictureBox^ picBox = gcnew PictureBox();
        picBox->Location = Point(30 + (i % 3) * 240, (i / 3) * 200 + 20);
        picBox->Size = System::Drawing::Size(180, 130);
        picBox->BorderStyle = BorderStyle::Fixed3D;
        picBox->Tag = locationName[i];
        picBox->Click += gcnew EventHandler(this, &WarpWindow::WarpPictureBox_Click);
        picBox->Image = WarpImageList->Images[i];
        picBox->SizeMode = PictureBoxSizeMode::CenterImage;
        WarpMainPanel->Controls->Add(picBox);

        Label^ imgLabel = gcnew Label();
        imgLabel->Size = System::Drawing::Size(180, 20);
        imgLabel->Location = Point(20 + (i % 3) * 240, (i / 3) * 200 + 160);
        imgLabel->Text = locationName[i];
        imgLabel->Font = gcnew System::Drawing::Font("Microsoft Sans Serif", 12);
        WarpMainPanel->Controls->Add(imgLabel);
    }
}

// -------------------- 略 -------------------------------

private: System::Void WarpPictureBox_Click(System::Object^ sender, EventArgs^ e) {
    PictureBox^ pb = (PictureBox^)sender;
    String^ locName = (String^)pb->Tag;
    char* clocName = static_cast<char*>((Marshal::StringToHGlobalAnsi(locName)).ToPointer());

    SocketClient* sc = new SocketClient();
    sc->Send("warp", clocName);
}