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