43: ESP
概要
前回、 WorldToScreenにより3D空間のゲーム内での座標をゲームウィンドウ上での座標に変換する仕組みを説明しました。WorldToScreenは、ESPという敵の位置をマーキングしたりするチートを行う際に必要なものでした。
今回は、WorldToScreenを用いてESPチートを実装する事を目標にやっていきます。
チート対象のゲームは、x86でオープンソースのAssault Cube v1.2.0.2を使います。
Windows対象で、Visual Studio 2019を使用し
C++
で記述する。ソースコードはまとめてページの一番下にある。この記事は、34 (DirectX Overlay)、 35 (EntityList)、 42 (WorldToScreen) の内容を用いるので、 まだ見ていない方はそちらから見ていただければ。
fov と スクリーンの横幅/高さ の取得
WorldToScreenで必要な要素は、前回の記事の最後の数式から、以下だとわかっています。- 変換対象の座標
P=(x, y, z)
- カメラ座標系の基底ベクトル
- ゲームウィンドウの幅と高さ
- fov(画角)
ラッキーな事に、AssaultCubeではどちらも
Settings -> Video settings
で明確な値がわかります。
なので、これを変えたりしてメモリスキャン掛ければいいだけです。
fov
fovですが、メモリスキャンで簡単に見つける事ができなかったので、この記事ではソースに値直書きで行きます。度でもラジアンでもヒットしなかったので、たぶんfov自体はグローバルなインスタンスのメンバとかには置かれてないのかもです。 Video settingsを開く時にあるアドレス (mitemlistというクラスのオフセット
1C
) に値が入るので、
設定画面開く時の処理内で、一々算出してる感がありました。そこら辺をリバースして、fov算出処理をまねれば良さげですが、面倒なので直書きでいきます。
スクリーンの横幅/高さ
CheatEngineでスキャンすれば、一つだけac_client.exe
からのオフセットで表されてるアドレスが出てくるので、
それを使えば良さげです。高さもセットでわかります。
表示が切れてますが、ac_client.exe+110C94
にスクリーン横幅、その次に高さがあります。
DirectX関連の処理
では実際にコードを書いていきます。いつものようにC++のDLLプロジェクトを立ち上げた後、34の記事 に書いた通りの設定を行い、DirectX9を使えるようにします。
DirectXのコードはごちゃごちゃするので、新しく D3DOverlay.h と D3DOverlay.cpp を作成します。
ここでは、オーバーレイウィンドウやDirectXでの描画を担う、D3DOverlayクラスを作成します。
ヘッダファイルは、以下のようになります。 まず、オーバーレイウィンドウを作成する
CreateOverlayWnd
関数 と DirectX関連の初期化を行う InitD3D
関数
がプライベート関数としてあり、これをパブリックのコンストラクタから呼び出すことにより、インスタンス生成時に pD3D
や pD3DDev
等が初期化されます。その他、
TrackGameWnd
, BeginRender
, EndRender
, IsWindowFocused
は、34の記事の物と全く同じ内容なので、
内容はこの記事の一番下にあるソースコード一覧から見ていただければ。DrawLine
及び DrawRect
が実際に敵を四角で囲うための描画関数で、
今回新しい部分ですが、基本的には二点を指定して線/四角をDirectXの関数を用いて描画してるだけですので、
一番下のソースから確認いただければと思います。
ベクトルや角度関連の処理
続いて、Vector3クラスを定義したり、行列関連のを定義したりなどをgeom.h と geom.cpp
に行っていきます。中身は行列の掛け算の定義とかそういうのなので、同じくソースを見てくださいになってしまいますが、Vector3等の構造体の定義の所に書いてある意味が分からない場合は、 20回の記事に解説があります。 ここでは、クラスは定義せずにグローバルにアクセスできる関数や構造体を定義しています。
プレイヤー(カメラ) のクラス
ここが本題です。このゲームはFPSなので プレイヤー=カメラ という事で、Player関連の処理とカメラ関連の処理を一束に束ねたPlayerクラスを作ります。 具体的には、
Player.h
と Player.cpp
に定義していきます。
※ ヘッダファイル最初作ると DWORD とかが赤線ひかれますが、Player.cppで pch.h
をincludeすれば治りますコンストラクタでまず、
ac_client.exe
のベースアドレスや、プレイヤーのアドレスを初期化すると共に、
ReadCurVideoSettings
関数により画面の横幅/高さ及びfovも初期化します。
画面の横幅/高さであったり、プレイヤーの座標/角度 など、ゲーム中に変わる物については GetCur...
にして、現在の値を逐次読み取るようにしています。WorldToScreenに関わる所は、
GetViewMatrix
, ViewTransform
, WorldToScreen
です。一つ一つ見ていきます。
ビュー行列の作成 (GetViewMatrix)
ビュー行列は以下でした。 \[ V=\begin{bmatrix}X_x & X_y & X_z & -P \cdot X \\ Y_x & Y_y & Y_z & -P \cdot Y \\ Z_x & Z_y & Z_z & -P \cdot Z \\ 0 & 0 & 0 & 1\end{bmatrix} \] ※ 尚、X,Y,Z はカメラの基底ベクトル(X=Xx,Xy,Xz)、Pは変換対象のワールド座標よって、まずはカメラの基底ベクトルを求める必要があります。
(基底ベクトルは、カメラの前後左右上下のベクトルを長さ1にしたものです)
カメラの前後左右上下のベクトルは、下図のオイラー角を使った回転行列を、カメラが
(yaw, pitch, roll) = (0, 0, 0)
の際の前後左右上下ベクトルに掛ける事により求める事ができます。
ややこしいので具体例を出します。
例として、カメラの前方向のベクトル
v'
を、カメラが (yaw, pitch, roll) = (0, 0, 0)
の際の前方向のベクトル v
から求める事を考えます。
(yaw, pitch, roll) = (0, 0, 0)
の時、上図の例だとx軸正が前方向ベクトルなので、v=(1,0,0)
であることがわかります。このカメラのオイラー角が、以下の値であった時、 \[ (yaw, pitch, roll) = (\theta_{yaw}, \theta_{pitch}, 0) \] この時のカメラの前方向ベクトル
v'
は、v
を用いて以下のように算出できます。
ロールは使われてないので計算しなくていいです。注意なのは、ロール→ピッチ→ヨーの順番で掛ける必要があるという点です。
以上のようにして、カメラの基底ベクトルを求めるのですが、そうなると以下二点の問題があります。
- カメラが
(yaw, pitch, roll) = (0, 0, 0)
の時にどこを向いてるのかわからない - そもそもワールドの軸は左手座標系なのか右手座標系なのか(y軸を上とした場合x軸は右向きが正か左が正か)がわからない
なので、ちょっとキモイ感じになってはいるのですが、以下のコードで動きました。 基底ベクトルは長さ1である必要があるので、
Normalize
をしています。基底ベクトルが分かった後、ビュー行列を構成して返しています。
ビュー変換 (ViewTransform)
ビュー変換は、ビュー行列をワールド座標に掛けて、カメラ座標系での座標に変換する物です。計算の際は同次座標を使うので、一回Vector4のコンストラクタで同次座標に変換し、その後
ToVector3
で戻してます。
WorldToScreen
WorldToScreenの数式は、以下でした。 \[ \begin{bmatrix} x_{cam} \\ y_{cam} \\ z_{cam} \\ 1\end{bmatrix} = V \begin{bmatrix} x_{world} \\ y_{world} \\ z_{world} \\ 1 \end{bmatrix} \] \[ x_{screen} = \frac{width}{2 \cdot tan(fov/2) \cdot z_{cam}} \cdot x_{cam} + \frac{width}{2} \] \[ y_{screen} = \frac{width}{2 \cdot tan(fov/2) \cdot z_{cam}} \cdot y_{cam} + \frac{height}{2} \] ※ 尚、X,Y,Z はカメラの基底ベクトル(X=Xx,Xy,Xz)、Pは変換対象のワールド座標この最初の行は
ViewTransform
で行い、最後の二行をこの関数の中で記述します。
ここで、cam.z>0
という制限があります。今回カメラにとって前方向をz軸正とした基底ベクトルを用いているので、
cam.z>0
とはつまり、
"カメラの前側にある" という条件です。前回の記事では説明不足でしたが、上の数式の z_cam
は、
正式には |z_cam|
の方が正しいです。ただ、絶対値への変換の処理を入れるのが面倒なのに加え、
どうせカメラより後ろ側は描画対象にならないのでこうしています。なので、カメラ後ろ側の場合は描画できないという事で、その際は
false
を返すようにしており、
変換後のスクリーン座標は、引数で返すようにしています。長くなりましたが、これでPlayerクラスの方の実装は終了です。
dllmain
最後にメイン関数です。ギリギリスクショに入りきらないので、重要な所を載せると以下みたいな感じです。 メッセージループ内でもしゲームウィンドウにフォーカスをしていた場合に、 エンティティリストを回すというループがあります。自分を除いた各エンティティ(敵)に対し、まず体力0でないかどうかを確認後、敵の座標(
entPos
)
ともう一つ、敵の足元らへんの座標(entPosBo
) を出しています。実際に動かしてみるとわかるのですが、
entPos
は敵の頭らへんの座標です。
なので、敵の全身を囲う場合は足元らへんの座標も欲しいのですが、ワールド座標で各エンティティの身長は大体 5.4
ぐらいだったのでz座標をその分小さくしてます。ワールド座標の方で計算してからWorldToScreenする事で、
遠くの物は小さく、近くの物は大きく を実現できるため、entPosBo
を求めてから entScreenPosBo
に変換しています。四角の横幅に関しては、適当に色々値変えながらやってみたら上記のでちょうど良かったのでそうしてます。 とはいえポイントは、やはり 遠くの物は小さく、近くの物は大きく を実現するために、敵と自分との距離を考慮した値となっています。
各エンティティの座標を求め、WorldToScreenをしてスクリーン座標に変換後、その座標に
DrawRect
をする事で四角で囲っているわけですが、四角で囲うのともう一つ、敵と自分との間に線もひきたかったので、DrawLine
も入れています。こんな感じでdllmain処理は終わりです。
やってみる
それでは早速ビルドしてインジェクトして動かしてみましょう!ちょっと上にズレたりしますが、こんな感じで敵が囲われるようになっていて、壁の向こう側の敵も検知できるようになってるはずです。 ESPは色んな改良方法があり、40の記事で使用した
TraceLine
を用いて、見えている敵と見えてない敵で色を変えたり、横に体力バーとか好きな情報を表示したりなどできます。
ソースコード
D3DOverlay.h
#pragma once
#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
class D3DOverlay {
public:
D3DOverlay(LPVOID hInstance, LPCSTR windowName, LPCSTR gameTitle);
~D3DOverlay();
void TrackGameWnd();
void BeginRender();
void EndRender();
BOOL IsWindowFocused();
void DrawLine(D3DXVECTOR2 from, D3DXVECTOR2 to, int color);
void DrawRect(D3DXVECTOR2 topleft, D3DXVECTOR2 bottomright, int color);
private:
IDirect3D9Ex* pD3D;
IDirect3DDevice9Ex* pD3DDev;
ID3DXFont* pD3DFont;
ID3DXLine* pD3DLine;
HWND hWnd, gameHWnd;
int width, height;
BOOL CreateOverlayWnd(LPVOID hInstance, LPCSTR windowName, LPCSTR gameTitle);
void InitD3D();
};
D3DOverlay.cpp
#include "pch.h"
#include "D3DOverlay.h"
LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam) {
switch (mes) {
case WM_PAINT:
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, mes, wParam, lParam);
}
}
D3DOverlay::D3DOverlay(LPVOID hInstance, LPCSTR windowName, LPCSTR gameTitle) {
CreateOverlayWnd(hInstance, windowName, gameTitle);
InitD3D();
}
D3DOverlay::~D3DOverlay() {
pD3DDev->Release();
pD3D->Release();
}
BOOL D3DOverlay::CreateOverlayWnd(LPVOID hInstance, LPCSTR windowName, LPCSTR gameTitle) {
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = (HINSTANCE)hInstance;
wcex.hIcon = 0;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
wcex.lpszMenuName = windowName;
wcex.lpszClassName = windowName;
wcex.hIconSm = 0;
RegisterClassEx(&wcex);
hWnd = CreateWindowEx(
WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED,
windowName,
windowName,
WS_POPUP,
1,
1,
width,
height,
NULL,
NULL,
(HINSTANCE)hInstance,
NULL);
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
ShowWindow(hWnd, SW_SHOWDEFAULT);
gameHWnd = FindWindowA(0, gameTitle);
if (gameHWnd) {
RECT rect;
GetWindowRect(gameHWnd, &rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
return TRUE;
}
else return FALSE;
}
void D3DOverlay::InitD3D() {
Direct3DCreate9Ex(D3D_SDK_VERSION, &pD3D);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = width;
d3dpp.BackBufferHeight = height;
d3dpp.Windowed = true;
d3dpp.hDeviceWindow = hWnd;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.MultiSampleQuality = D3DMULTISAMPLE_NONE;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
pD3D->CreateDeviceEx(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&d3dpp,
0,
&pD3DDev);
D3DXCreateLine(
pD3DDev,
&pD3DLine
);
}
void D3DOverlay::TrackGameWnd() {
RECT rect;
GetWindowRect(gameHWnd, &rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
MoveWindow(hWnd, rect.left, rect.top, width, height, true);
}
void D3DOverlay::BeginRender() {
pD3DDev->Clear(0, NULL, D3DCLEAR_TARGET, 0, 1.0f, 0);
pD3DDev->BeginScene();
}
void D3DOverlay::EndRender() {
pD3DDev->EndScene();
pD3DDev->Present(NULL, NULL, NULL, NULL);
}
BOOL D3DOverlay::IsWindowFocused() {
return gameHWnd == GetForegroundWindow();
}
void D3DOverlay::DrawLine(D3DXVECTOR2 from, D3DXVECTOR2 to, int color) {
D3DXVECTOR2 line[2] = { from, to };
pD3DLine->Draw(line, 2, color);
}
void D3DOverlay::DrawRect(D3DXVECTOR2 topleft, D3DXVECTOR2 bottomright, int color) {
DrawLine(D3DXVECTOR2(topleft.x, topleft.y), D3DXVECTOR2(bottomright.x, topleft.y), color);
DrawLine(D3DXVECTOR2(bottomright.x, topleft.y), D3DXVECTOR2(bottomright.x, bottomright.y), color);
DrawLine(D3DXVECTOR2(topleft.x, topleft.y), D3DXVECTOR2(topleft.x, bottomright.y), color);
DrawLine(D3DXVECTOR2(topleft.x, bottomright.y), D3DXVECTOR2(bottomright.x, bottomright.y), color);
}
geom.h
#pragma once
#include <cmath>
extern float PI;
typedef struct Vector2 {
float x;
float y;
Vector2() : x(0), y(0) {}
Vector2(float _x, float _y) : x(_x), y(_y) {}
Vector2 operator - () {
x = -x;
y = -y;
return *this;
}
};
typedef struct Vector3 {
float x;
float y;
float z;
Vector3() : x(0), y(0), z(0) {}
Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
Vector3 operator - () {
x = -x;
y = -y;
z = -z;
return *this;
}
Vector3 operator - (Vector3 v) {
x -= v.x;
y -= v.y;
z -= v.z;
return *this;
}
Vector3 operator * (float c) {
x *= c;
y *= c;
z *= c;
return *this;
}
Vector3 operator / (float c) {
x /= c;
y /= c;
z /= c;
return *this;
}
} Vector3;
typedef struct Vector4 {
float x;
float y;
float z;
float w;
Vector4() : x(0), y(0), z(0), w(0) {}
Vector4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {}
Vector4(Vector3 v) : x(v.x), y(v.y), z(v.z), w(1) {}
Vector4(Vector3 v, float _w) : x(v.x), y(v.y), z(v.z), w(_w) {}
static Vector3 ToVector3(Vector4 v);
} Vector4;
float dot(Vector3 a, Vector3 b);
typedef struct Mat44 {
float m[16];
Mat44() {
m[0] = 0; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 0; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = 0; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 0;
}
Mat44(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33) {
m[0] = m00; m[1] = m01; m[2] = m02; m[3] = m03;
m[4] = m10; m[5] = m11; m[6] = m12; m[7] = m13;
m[8] = m20; m[9] = m21; m[10] = m22; m[11] = m23;
m[12] = m30; m[13] = m31; m[14] = m32; m[15] = m33;
}
float operator[](int i) {
return m[i];
}
};
Vector4 mxv4(Mat44 m, Vector4 v);
typedef struct EulerAngle {
float yaw;
float pitch;
EulerAngle() : yaw(0), pitch(0) {}
EulerAngle(float _y, float _p) : yaw(_y), pitch(_p) {}
} EulerAngle;
float DegToRadian(float deg);
Vector3 Normalize(Vector3 v);
float GetDistance(Vector3 delta);
geom.cpp
#include "pch.h"
#include "geom.h"
float PI = 3.14159265359;
Vector3 Normalize(Vector3 v) {
float vlen = sqrt(pow(v.x, 2) + pow(v.y, 2) + pow(v.z, 2));
return v / vlen;
}
float dot(Vector3 a, Vector3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
Vector4 mxv4(Mat44 m, Vector4 v) {
Vector4 a;
a.x = m[0] * v.x + m[1] * v.y + m[2] * v.z + m[3] * v.w;
a.y = m[4] * v.x + m[5] * v.y + m[6] * v.z + m[7] * v.w;
a.z = m[8] * v.x + m[9] * v.y + m[10] * v.z + m[11] * v.w;
a.w = m[12] * v.x + m[13] * v.y + m[14] * v.z + m[15] * v.w;
return a;
}
Vector3 Vector4::ToVector3(Vector4 v) {
return Vector3(v.x / v.w, v.y / v.w, v.z / v.w);
}
float DegToRadian(float deg) {
return deg * (PI / 180);
}
float GetDistance(Vector3 delta) {
return (float)sqrt(pow(delta.x, 2) + pow(delta.y, 2) + pow(delta.z, 2));
}
Player.h
#pragma once
#include "geom.h"
class Player {
public:
DWORD screenWidth;
DWORD screenHeight;
Player(DWORD baseAddr);
Vector3 GetCurPos();
EulerAngle GetCurAngle();
Mat44 GetViewMatrix();
Vector3 ViewTransform(Vector3 world);
void ReadCurVideoSettings();
BOOL WorldToScreen(Vector3 world, Vector2* screen);
private:
DWORD baseAddr;
DWORD playerAddr;
float fov;
};
Player.cpp
#include "pch.h"
#include "Player.h"
Player::Player(DWORD baseAddr) {
this->baseAddr = baseAddr;
playerAddr = *(DWORD*)(baseAddr + 0x10f4f4);
ReadCurVideoSettings();
}
Vector3 Player::GetCurPos() {
Vector3 p(
*(float*)(playerAddr + 0x34),
*(float*)(playerAddr + 0x38),
*(float*)(playerAddr + 0x3c)
);
return p;
}
EulerAngle Player::GetCurAngle() {
EulerAngle e(
*(float*)(playerAddr + 0x40),
*(float*)(playerAddr + 0x44)
);
return e;
}
Mat44 Player::GetViewMatrix() {
Vector3 pos = GetCurPos();
EulerAngle e = GetCurAngle();
e.yaw = DegToRadian(e.yaw - 90);
e.pitch = DegToRadian(e.pitch);
Vector3 fv = Vector3(
cosf(e.yaw) * cosf(e.pitch),
sinf(e.yaw) * cosf(e.pitch),
sinf(e.pitch)
);
Vector3 CZ = Normalize(fv);
Vector3 rv = Vector3(
-sinf(e.yaw),
cosf(e.yaw),
0
);
Vector3 CX = Normalize(rv);
Vector3 uv = Vector3(
-sinf(e.pitch) * cosf(e.yaw),
-sinf(e.pitch) * sinf(e.yaw),
cosf(e.pitch)
);
Vector3 CY = Normalize(uv);
Mat44 V = Mat44(
CX.x, CX.y, CX.z, -dot(pos, CX),
CY.x, CY.y, CY.z, -dot(pos, CY),
CZ.x, CZ.y, CZ.z, -dot(pos, CZ),
0, 0, 0, 1
);
return V;
}
Vector3 Player::ViewTransform(Vector3 world) {
Mat44 V = GetViewMatrix();
Vector4 cam4 = mxv4(V, Vector4(world));
Vector3 cam = Vector4::ToVector3(cam4);
return cam;
}
void Player::ReadCurVideoSettings() {
fov = 90 * (PI / 180); // 直書き
screenWidth = *(DWORD*)(baseAddr + 0x110c94);
screenHeight = *(DWORD*)(baseAddr + 0x110c98);
}
BOOL Player::WorldToScreen(Vector3 world, Vector2* screen) {
Vector3 cam = ViewTransform(world);
if (cam.z > 0) {
ReadCurVideoSettings();
float a = screenWidth / (2 * tan(fov / 2) * cam.z);
screen->x = a * cam.x + screenWidth / 2;
screen->y = a * -cam.y + screenHeight / 2;
return true;
}
return false;
}
dllmain.cpp
#include "pch.h"
#include "geom.h"
#include "D3DOverlay.h"
#include "Player.h"
D3DOverlay* d3doverlay;
Player* player;
DWORD WINAPI ThreadMain(LPVOID params) {
DWORD baseAddr = (DWORD)GetModuleHandle("ac_client.exe");
DWORD entityListAddr = *(DWORD*)(baseAddr + 0x10f4f8);
DWORD playerAddr = *(DWORD*)(baseAddr + 0x10f4f4);
d3doverlay = new D3DOverlay(params, "AssaultCube Overlay", "AssaultCube");
player = new Player(baseAddr);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
d3doverlay->TrackGameWnd();
d3doverlay->BeginRender();
if (d3doverlay->IsWindowFocused()) {
int maxPlayer = *(int*)(baseAddr + 0x10F500);
for (int i = 1; i < maxPlayer; i++) {
DWORD entityAddr = *(DWORD*)(entityListAddr + 4 * i);
// skip if enemy is dead
if (*(int*)(entityAddr + 0xF8) < 1) continue;
Vector3 myPos = player->GetCurPos();
Vector3 entPos(
*(float*)(entityAddr + 0x34),
*(float*)(entityAddr + 0x38),
*(float*)(entityAddr + 0x3C)
);
Vector3 entPosBo = entPos;
entPosBo.z -= 5.4;
int color = D3DCOLOR_XRGB(0, 255, 0);
Vector2 entScreenPos;
Vector2 entScreenPosBo;
player->WorldToScreen(entPosBo, &entScreenPosBo);
if (player->WorldToScreen(entPos, &entScreenPos)) {
float rectW = 750 / GetDistance(entPos - myPos);
float rectH = entScreenPosBo.y - entScreenPos.y;
d3doverlay->DrawRect(
D3DXVECTOR2(entScreenPos.x - rectW, entScreenPos.y),
D3DXVECTOR2(entScreenPos.x + rectW, entScreenPos.y + rectH),
color);
d3doverlay->DrawLine(
D3DXVECTOR2(player->screenWidth / 2, player->screenHeight),
D3DXVECTOR2(entScreenPos.x, entScreenPos.y + rectH),
color);
}
}
}
d3doverlay->EndRender();
}
delete d3doverlay;
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;
}