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により向きのアドレスを取得し、 その後外部から、GetCurrentQuaternionGetCurrent***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;
}