18: 壁のすり抜け
概要
前回の記事で、 プレイヤー(カメラ)の向きであるクォータニオンを取得し、クォータニオンから前後左右上下ベクトルを算出する式を紹介しました。今回は、前(または後左右上)にある壁をすり抜ける事を目標にやっていきます。
より具体的には、このゲームは
WASD<space>
で移動できますが、Ctrl+WASD<space>
でその方向に+700.0
移動させるという事をして、壁を通り越します。チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、
C++
を言語として用いる。ソースコードはまとめてページの一番下にある。また、この記事は前回の記事の続きで、 かつ16回の記事で作成したヘッダーファイルを使用し、それに機能を追加する形でやっていくので、この二つから先にやるのが良いかと。
16回の記事の続きでやっている方は、一応別のプロジェクトとしてコピーなりしてやると良いと思います。
全体的な動作に関するファイルの改良
まず、クォータニオン型自体を、前作成したgeneral-movement.hに以下のように定義します。また、逆ベクトルである
(x,y,z) => (-x,-y,-z)
を算出するためのInvert
関数も定義しておきます。
そして、general-movement.cppを作成し、以下のようにInvert
関数の内容を書きます。
Playerクラスの改良
16回までは、プレイヤーの座標の情報とそれに伴う動作の関数しか書きませんでしたが、これに向き関連の情報/処理も追加していきます。まずは、player.hに以下(横に青い線や黄色い線がある所)を追加します。 Playerクラスのインスタンス化時にコンストラクタで
GetDirectionAddress
により向きのアドレスを取得し、
その後外部から、GetCurrentQuaternion
やGetCurrent***Vector
を使用して、現在のクォータニオンや、
現在の前後左右上下ベクトルを取得できるようにします。また、黄色い線の所の
MoveLocation
はオーバーロード(引数違いで同じ名前の関数を定義する事)しており、
これはdirection
のベクトルに、value
程の量移動するというコードです。では、player.cppにそれぞれの関数の内容を書いていきます。
前回の記事の最後の方で、クォータニオンがどういう順番で並んでいるのかについて話しましたが、自分は
w,y,x,z
の並び順
と仮定してやっているので、GetDirectionAddress
の部分がその並び順になっています。また、前上左のベクトルは前回の公式をそのまま使っています。
よく使う機能をまとめたファイルにウィンドウのフォーカス判定を追加
冒頭でも述べた通り、「Ctrl+WASD<space>
でその方向に+100
移動させる」という事をするので、
キープレスを認識する必要があります。キープレスの認識には、WindowsAPIである
GetAsyncKeyState
を使用しますが、
これだとゲームのウィンドウ意外のウィンドウで作業している時でも、キープレスに反応してしまうので、ウィンドウにフォーカスしているかどうか
の判定用の関数を、前のgeneral-cheats.h/general-cheats.cppに追加していきます。しかし、これは 13回の記事 にて解説しているので、説明は省略してコードだけ見せます。
まずは、general-cheats.hに以下の
isWindowFocused
を追加します。
続いて、general-cheats.cppでその実装を以下の用に追加します。
メインの処理の実装
上記で色んな関数を用意したので、メインの処理であるdllmain.cppは結構さっぱりしていて、以下だけです。Sleep
を入れてるのは、一回のキープレスで複数回実行されることを防止するためです。
Sleep
を入れても、CreateThread
している事からわかる通り、ゲームとは別のスレッドで動いているので、
ゲームは止まらないので安心です。boost
(一度にどれくらい進むか) や、wait
はお好みで調整してもらえれば。
実際にやってみる
それでは、ビルドしてやってみましょう。以下の用に、壁をすり抜けるはずです。プレイヤーではなくカメラの向きを使う利点
今回、プレイヤーの向きではなく、カメラの向きから前後左右上下ベクトルを算出して、そのベクトルを使ってプレイヤーを動かすという事をしています。 一見変に感じるかもですが、カメラの向きを使う事で、下図のような動きができるというメリットがあります。 実際に、上のgifでやっているのはこの動きです。もしプレイヤーの向きでやった場合、このPwnAdventure3では、プレイヤーを上に上図のように傾ける事はできないので、 この動きはできなくなります。
ソースコード
general-movement.h
typedef struct {
float x;
float y;
float z;
} Vector3;
typedef struct {
float x;
float y;
float z;
float w;
} Quaternion;
Vector3 Invert(Vector3 v);
general-movement.cpp
#include "pch.h"
#include "general-movement.h"
Vector3 Invert(Vector3 v) {
Vector3 v2;
v2.x = -1 * v.x;
v2.y = -1 * v.y;
v2.z = -1 * v.z;
return v2;
}
general-cheats.h
#pragma once
#include "pch.h"
#include <stdio.h>
#include <vector>
using namespace std;
typedef vector<UINT> VUINT;
void DbgPrint(const char* fmt, ...);
DWORD ResolveAddress(DWORD baseAddr, VUINT offsets);
bool isWindowFocused();
genral-cheats.cpp
#include "pch.h"
#include "general-cheats.h"
void DbgPrint(const char* fmt, ...) {
char buf[256];
va_list v1;
va_start(v1, fmt);
vsnprintf(buf, sizeof(buf), fmt, v1);
va_end(v1);
OutputDebugString(buf);
}
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;
}
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;
}
player.h
#pragma once
#include "general-cheats.h"
#include "general-movement.h"
using namespace std;
typedef struct {
DWORD xAddr;
DWORD yAddr;
DWORD zAddr;
} LocationAddr;
typedef struct {
DWORD xAddr;
DWORD yAddr;
DWORD zAddr;
DWORD wAddr;
} DirectionAddr;
class Player {
public:
Player() {
SetModuleBaseAddress("GameLogic.dll");
GetLocationAddress();
GetDirectionAddress();
}
Vector3 GetCurrentLocation();
void SetLocation(Vector3 target, vector<bool> mask);
void MoveLocation(Vector3 value, vector<bool> mask);
void MoveLocation(Vector3 direction, float value, vector<bool> mask);
Quaternion GetCurrentQuaternion();
Vector3 GetCurrentForwardVector();
Vector3 GetCurrentUpVector();
Vector3 GetCurrentLeftVector();
private:
DWORD baseAddr;
LocationAddr locAddr;
DirectionAddr dirAddr;
void SetModuleBaseAddress(LPCSTR);
void GetLocationAddress();
void GetDirectionAddress();
};
player.cpp
#include "pch.h"
#include "player.h"
void Player::SetModuleBaseAddress(LPCSTR filename) {
baseAddr = (DWORD)GetModuleHandle(filename);
}
// Location
void Player::GetLocationAddress() {
locAddr.yAddr = (DWORD)ResolveAddress(baseAddr + 0x97D7C, { 0x1C, 0x4, 0x280, 0x98 });
locAddr.xAddr = locAddr.yAddr - 0x4;
locAddr.zAddr = locAddr.yAddr - 0x8;
}
Vector3 Player::GetCurrentLocation() {
Vector3 v;
v.x = *(float*)locAddr.xAddr;
v.y = *(float*)locAddr.yAddr;
v.z = *(float*)locAddr.zAddr;
return v;
}
void Player::SetLocation(Vector3 target, vector<bool> mask) {
if (mask[0]) *(float*)locAddr.xAddr = target.x;
if (mask[1]) *(float*)locAddr.yAddr = target.y;
if (mask[2]) *(float*)locAddr.zAddr = target.z;
}
void Player::MoveLocation(Vector3 value, vector<bool> mask) {
Vector3 curLoc = GetCurrentLocation();
if (mask[0]) *(float*)locAddr.xAddr = curLoc.x + value.x;
if (mask[1]) *(float*)locAddr.yAddr = curLoc.y + value.y;
if (mask[2]) *(float*)locAddr.zAddr = curLoc.z + value.z;
}
void Player::MoveLocation(Vector3 direction, float value, vector<bool> mask) {
Vector3 curLoc = GetCurrentLocation();
if (mask[0]) *(float*)locAddr.xAddr = curLoc.x + (value * direction.x);
if (mask[1]) *(float*)locAddr.yAddr = curLoc.y + (value * direction.y);
if (mask[2]) *(float*)locAddr.zAddr = curLoc.z + (value * direction.z);
}
// Direction
void Player::GetDirectionAddress() {
DWORD yCamera = ResolveAddress(baseAddr + 0x97d80, { 0x4, 0x48, 0x4, 0x258, 0x78 });
dirAddr.wAddr = yCamera - 0x18;
dirAddr.yAddr = yCamera - 0x18 + 0x4;
dirAddr.xAddr = yCamera - 0x18 + 0x8;
dirAddr.zAddr = yCamera - 0x18 + 0xc;
}
Quaternion Player::GetCurrentQuaternion() {
Quaternion q;
q.x = *(float*)dirAddr.xAddr;
q.y = *(float*)dirAddr.yAddr;
q.z = *(float*)dirAddr.zAddr;
q.w = *(float*)dirAddr.wAddr;
return q;
}
Vector3 Player::GetCurrentForwardVector() {
Vector3 v;
Quaternion q = GetCurrentQuaternion();
v.x = 2 * (q.x * q.z + q.w * q.y);
v.y = 2 * (q.y * q.z - q.w * q.x) * (-1);
v.z = 1 - 2 * (q.x * q.x + q.y * q.y);
return v;
}
Vector3 Player::GetCurrentUpVector() {
Vector3 v;
Quaternion q = GetCurrentQuaternion();
v.x = 2 * (q.x * q.y - q.w * q.z);
v.y = (1 - 2 * (q.x * q.x + q.z * q.z)) * (-1);
v.z = 2 * (q.y * q.z + q.w * q.x);
return v;
}
Vector3 Player::GetCurrentLeftVector() {
Vector3 v;
Quaternion q = GetCurrentQuaternion();
v.x = 1 - 2 * (q.y * q.y + q.z * q.z);
v.y = 2 * (q.x * q.y + q.w * q.z) * (-1);
v.z = 2 * (q.x * q.z - q.w * q.y);
return v;
}
dllmain.cpp
#include "pch.h"
#include "general-cheats.h"
#include "player.h"
using namespace std;
Player* player;
DWORD WINAPI ThreadMain(LPVOID params) {
player = new Player();
float boost = 700.0;
DWORD wait = 250;
while (1) {
if (isWindowFocused()) {
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x57)) { // w
Vector3 fv = player->GetCurrentForwardVector();
player->MoveLocation(fv, boost, { 1, 1, 1 });
Sleep(wait);
}
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x53)) { // s
Vector3 bv = Invert(player->GetCurrentForwardVector());
player->MoveLocation(bv, boost, { 1, 1, 1 });
Sleep(wait);
}
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x41)) { // a
Vector3 lv = player->GetCurrentLeftVector();
player->MoveLocation(lv, boost, { 1, 1, 1 });
Sleep(wait);
}
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x44)) { // d
Vector3 rv = Invert(player->GetCurrentLeftVector());
player->MoveLocation(rv, boost, { 1, 1, 1 });
Sleep(wait);
}
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(VK_SPACE)) {
Vector3 uv = player->GetCurrentUpVector();
player->MoveLocation(uv, boost, { 0, 1, 0 });
Sleep(wait);
}
}
}
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;
}