13: キープレスで特定の場所にワープする

概要

ワープはプレイヤーの座標のアドレスを特定し、そこに値を書き込めむDLLを書けばよいだけなので、実装する事自体は簡単です。 しかし、他のチートコードを書くためにゲームを プレイ/解析/デバッグ している時、ワープチートを事前に用意しておくのとしないのとでは、作業効率に大きな差があります。 また、複数のワープ地点を用意し、行先を選べるようにするためには、ユーザーからの "入力" が必要になるので、
今回は、キープレスの受け取り方をメインに、様々なワープ地点に飛べるようにすることを目標にやっていきます。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、C++を言語として用いる。ソースコードはまとめてページの一番下にある。

プレイヤーの座標の特定

これに関しては、以前書いた座標特定の記事を参考にしてください。
CheatEngineで座標を特定し、そのアドレスへのポインタリストを作成しておいてください。
ただ、もしポインタリストを作成するのが面倒であれば、以下の自分が用意したポインタリストを使ってもよいです!
y座標 = GameLogic.dll+0x97D7C, 0x1C, 0x4, 0x280, 0x98
x座標 = y座標-0x4
z座標 = y座標-0x8

プレイヤー座標をメッセージボックスで出力する

まずは、ワープ地点の座標を知るために、プレイヤーの現在位置をメッセージボックスで表示する機能を作ります。 こうすれば、ワープしたい場所まで行って、そこの座標をどんどん表示してメモしていけば良い事になります。

それでは、VisualStudioでDLL/C++/Windowsのプロジェクトを作成し、x86/マルチバイト文字/Win32 の設定をしましょう。 このVisualStudioの設定がわからない方は、こちら の「ダメージを受ける命令を無効化」の部分の画像を参考にしてください。

コードの方を書いていきます。
全体像はページの最後の「現在座標を表示するDLL」にあるので、大事な所だけ抜粋して説明します。
まず、座標関連は以下の構造体でまとめています。 LocationAddrがプレイヤーの座標が格納されているアドレスの構造体で、 Locationが具体的な座標の値です。
(正直この二つはまとめたかったのですが、これしか思いつかず…)

そして、特定した座標のポインタリストから、各座標のアドレスを取得している所が以下です。
VUINTというのは typedef std::vector<UINT> VUINT; と自分で定義したものです。 ResolveAddressがポインタリストからアドレスを求める関数ですが、ループでDWORD型のアドレスcurAddr から、一回DWORD*のポインタ型に変換し、そのアドレスに入っている値*(DWORD*)curAddrを取り出しては、 その値にオフセットを足していくことをループで繰り返しています。

一番大事なのは、以下の部分です。 まず、GetAsyncKeyState()というのがキープレスを認識するWindowsAPIです。
引数には何のキーを認識するかという値が入り、キーコードという形式で与えます。キーコード一覧はこの記事 などから見れます。戻り値は、厳密には違うんですが、引数で与えられたキーが押されたときはtrueを返し、押されてない時はfalseを返す と思っておけばよいです。今回はCtrl-bを認識するようにしています。これをwhile(1)で無限ループさせています。
そして、キープレスがあったら、実際に座標のアドレスから具体的な座標の値を読み取った後、それをメッセージボックスで表示しています。
しかし、MessageBoxはフォーマットストリングとかが使えない(変数展開とかができない)ので、#include <sstream>すると使えるようになる、 std::stringstreamという物を使って、事前に表示する文字列を構築してからメッセージボックスで表示しています。
この書き方はC++の標準入力/標準出力とかで使われてるので、見慣れない方はC++でシンプルな標準入力/標準出力のプログラムを書いてみるとしっくりくると思います。

これで、ゲームにこのDLLをインジェクトしてCtrl-bを押すと、現在位置の情報が出てくるようになります。

ワープするコード

上記ができたら、いくつかワープしたい場所までプレイヤーを持っていき、そこの座標を取得してメモしていきましょう。
それが終わったら、後は以下のようにしてキープレスに応じてその座標に飛ぶようにするだけです。 このソースコードは、次の内容のコードもまとめて含んだコードとしてページの最後に貼ってあります。
これでDLLをビルドしてインジェクトすれば、以下のようにワープできるはずです。

ゲーム画面にフォーカスしてる時だけキープレスに反応するようにする

実は、GetAsyncKeyStateだけだと、ゲーム画面にフォーカスしていない時でもキープレスに反応してしまってかなり不便です。
ここが地味にめんどくさい所ですが、ゲーム画面にフォーカスしている時だけキープレスに反応するようにします。

Windowsプログラミングでは、HWNDというウィンドウのハンドル(ウィンドウを指すポインタのようなもの)があります。 このハンドルを引数に渡し、ウィンドウ関連操作をWindowsAPIを使用して行うのですが、ゲームのウィンドウのハンドルがどこにあるのかはわからないですし、 メモリ上から探そうと思ってもめんどくさいです。それなので、愚直にEnumWindows()というWindowsAPIを使用してPCにある全ウィンドウのハンドルを列挙し、 そのウィンドウを使用してるプロセスのプロセスIDをGetWindowThreadProcessId()で調べ、DLLが動いてるプロセス、すなわちゲームのプロセスのIDと比較する事により、 ゲームのウィンドウを見つけていきます。

そのコードが以下です。 少し読みづらくなっているのは、EnumWindows()の仕様のせいです。
EnumWindows()は、例えば現状PCにウィンドウが3つ(Win1, Win2, Win3)あるとすると、 まずWin1の HWNDを第一引数にセットしたコールバック関数(EnumWndProc)を呼び出します。 このコールバック関数がreturn trueしたら、続いてWin2のハンドルを第一引数にセットしてコールバック関数を呼び出します。 もし、コールバック関数がfalseを返すと、そこでウィンドウの列挙は終了しますし、最後のウィンドウ(Win3)まで行ってもそこで列挙を終了します。
それなので要は、EnumWindows()は、一つ一つのウィンドウを列挙していって、その各々のウィンドウに対して第一引数にセットしたコールバック関数を呼び出す といったことをするWindowsAPIです。

今回は、そのコールバック関数にて、GetWindowThreadProcessId()によりそのウィンドウのプロセスIDをDWORD pidに格納し、 それをGetCurrentProcessId()により取得した、現在のプロセスID(DLLが動いてるプロセスであるゲームのプロセスのプロセスID)と比較し、マッチしたら グローバル変数gameWndにそのウィンドウのハンドルを格納し、return falseをして列挙を終了させる。
という事をしているわけです。

後は、以下のように、このウィンドウにフォーカスしている時 という条件を追加するだけです。 ここまで含めた全体のソースコードは、このページの最後の「キープレスでワープ完成版」にあります。

ソースコード

現在座標を表示するDLL

#include "pch.h"
#include <stdio.h>
#include <vector>
#include <sstream>

typedef std::vector<UINT> VUINT;

typedef struct LocationAddr {
    DWORD xaddr;
    DWORD yaddr;
    DWORD zaddr;
} LocationAddr;

typedef struct Location {
    float x;
    float y;
    float z;
} Location;

DWORD ResolveAddress(DWORD baseAddr, VUINT offsets) {
    DWORD curAddr = baseAddr;
    for (UINT i = 0; i < offsets.size(); ++i) {
        curAddr = *(DWORD*)curAddr;
        curAddr += offsets[i];
    }
    return curAddr;
}

DWORD WINAPI ThreadMain(LPVOID params) {
    DWORD modBase = (DWORD)GetModuleHandle("GameLogic.dll");
    DWORD baseAddr = modBase + 0x97D7C;
    VUINT offsets = { 0x1C, 0x4, 0x280, 0x98 };

    LocationAddr locAddr;
    Location     loc;

    locAddr.yaddr = (DWORD)ResolveAddress(baseAddr, offsets);
    locAddr.xaddr = locAddr.yaddr - 0x4;
    locAddr.zaddr = locAddr.yaddr - 0x8;

    while (1) {
        if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x42)) { // Ctrl-b
            loc.x = *(float*)locAddr.xaddr;
            loc.y = *(float*)locAddr.yaddr;
            loc.z = *(float*)locAddr.zaddr;

            std::stringstream ss;
            ss << "x:" << loc.x << " y:" << loc.y << " z:" << loc.z << std::endl;
            ss << std::hex << "x:" << locAddr.xaddr << " y:" << locAddr.yaddr << " z:" << locAddr.zaddr;
            MessageBox(NULL, ss.str().c_str(), "current coordinate", MB_OK);
        }
    }

    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;
}


キープレスでワープ完成版

#include "pch.h"
#include <stdio.h>
#include <vector>
#include <sstream>

typedef std::vector<UINT> VUINT;

typedef struct LocationAddr {
    DWORD xaddr;
    DWORD yaddr;
    DWORD zaddr;
} LocationAddr;

typedef struct Location {
    float x;
    float y;
    float z;
} Location;

DWORD ResolveAddress(DWORD baseAddr, VUINT offsets) {
    DWORD curAddr = baseAddr;
    for (UINT i = 0; i < offsets.size(); ++i) {
        curAddr = *(DWORD*)curAddr;
        curAddr += offsets[i];
    }
    return curAddr;
}

void warp(LocationAddr locAddr, float x, float y, float z) {
    *(float*)locAddr.xaddr = x;
    *(float*)locAddr.yaddr = y;
    *(float*)locAddr.zaddr = z;
}

HWND gameWnd = NULL;

BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lparam) {
    DWORD pid;
    GetWindowThreadProcessId(hwnd, &pid);
    if (pid == GetCurrentProcessId()) {
        gameWnd = hwnd;
        return false;
    }
    return true;
}

bool isWindowFocused() {
    if (gameWnd == NULL)
        EnumWindows(EnumWndProc, (LPARAM)NULL);
    if (gameWnd != NULL && gameWnd == GetForegroundWindow())
        return true;
    return false;
}

DWORD WINAPI ThreadMain(LPVOID params) {
    DWORD modBase = (DWORD)GetModuleHandle("GameLogic.dll");
    DWORD baseAddr = modBase + 0x97D7C;
    VUINT offsets = { 0x1C, 0x4, 0x280, 0x98 };

    LocationAddr locAddr;
    Location     loc;

    locAddr.yaddr = (DWORD)ResolveAddress(baseAddr, offsets);
    locAddr.xaddr = locAddr.yaddr - 0x4;
    locAddr.zaddr = locAddr.yaddr - 0x8;

    while (1) {
        if (isWindowFocused()) {
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x42)) { // Ctrl-b
                loc.x = *(float*)locAddr.xaddr;
                loc.y = *(float*)locAddr.yaddr;
                loc.z = *(float*)locAddr.zaddr;

                std::stringstream ss;
                ss << "x:" << loc.x << " y:" << loc.y << " z:" << loc.z << std::endl;
                ss << std::hex << "x:" << locAddr.xaddr << " y:" << locAddr.yaddr << " z:" << locAddr.zaddr;
                MessageBox(NULL, ss.str().c_str(), "current coordinate", MB_OK);
            }


            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x43)) // c spell book
                warp(locAddr, -55980.4, 320.759, -43606.2);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x46)) // f rats cave entrance
                warp(locAddr, -41099.7, 750.585, -53473.1);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x47)) // g pwnie island entrance
                warp(locAddr, -35814, 1203.15, -44167.1);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x48)) // h houses
                warp(locAddr, -17770.8, 2404.02, -39940);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4B)) // k ballmer peak
                warp(locAddr, -11726.4, 10627.1, -2454.97);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4D)) // m bridge
                warp(locAddr, -13168.6, 865.574, 20057);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4E)) // n lots of bears
                warp(locAddr, -40404, 3242.52, -11314.1);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x51)) // q lots of angry-bears
                warp(locAddr, 23306.3, 1299.9, 78270.123);

            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x54)) // t pirate ship
                warp(locAddr, 58591.3, 309.947, 43392);
        }
    }

    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;
}