ANTICHAT

ANTICHAT (https://forum.antichat.xyz/index.php)
-   С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby (https://forum.antichat.xyz/forumdisplay.php?f=24)
-   -   Работа с рендером и Directx9 [4] (https://forum.antichat.xyz/showthread.php?t=1413060)

kin4stat 18.12.2021 02:46

  1. Создание ASI-плагина с нуля
  2. Хуки – что это такое и как с ними работать
  3. Безопасная инициализация и работа с SAMP
  4. Работа с рендером и Directx9
  5. Обработка событий окна + ImGui
В этом гайде будет рассказано про работу с рендером в Directx9 с помощью ImGui

При использовании на других ресурсах необходимо указание авторства и ссылки на оригинальную темы!

Все действия производились на Visual Studio 2019 с параметром
Код:

/std:c++17
, в других версиях интерфейс может отличаться.

И так, начнем:

Создаем новый проект, настраиваем его, добавляем библиотеку хуков(буду показывать на примере своих хуков, как настроить проект и подключить хуки можете посмотреть в гайдах [1] и [3])

В свойствах проекта, в вкладке общие, стандарт языка C++ ставим /std:c++17

Устанавливаем Directx9 SDK на наш пк и подключаем к проекту:

Скачиваем установщик, производим полную установку, перезагружаем наш пк(желательно)

В панели меню сверху жмем Проект, и выпадающем меню выбираем пункт Свойства: $ProjectName

Сверху, в выпадающем меню в открывшемся диалоге выбираем Конфигурация -> Все конфигурации.

Директории VC++(VC++ Directories) -> Директории включения файлов(Include directories) -> добавляем в конец
Код:

$(DXSDK_DIR)\Include\;
(точка с запятой в конце обязательна)

Директории VC++(VC++ Directories) -> Директории библиотек(Library Directories) -> добавляем в конец
Код:

$(DXSDK_DIR)\Lib\x86;
(точка с запятой в конце обязательна)

Компоновщик(Linker) -> Ввод(Input) -> Дополнительные файлы зависимостей(Additional Dependencies) -> добавляем
Код:

d3d9.lib;d3dx9.lib;
(точки с запятой опять же обязательны)

Подключаем ImGui к нашему проекту:

ImGui можно подключать двумя путями.

Самый простой -
Код:

vcpkg install imgui
Второй способ:

Скачиваем ImGui из репозитория и распаковываем все cpp и h файлы из корневой папки репозитория в папку нашего проекта. Также из папки backends берем файлы
  1. imgui_impl_win32.cpp
  2. imgui_impl_win32.h
  3. imgui_impl_dx9.h
  4. imgui_impl_dx9.cpp
Далее переходим в VS и делаем так:

Теперь перейдем к написанию кода:

Хукать directx в gta можно двумя путями:
  1. Хукать таблицу виртуальных методов
  2. Хукать функцию в библиотеке d3d9.dll

Покажу 2 и 3 способ, 1 способ делается на основе 3

И так, сначала расскажу что такое таблица виртуальных методов. Таблица виртуальных методов используется для разрешения виртуальных вызовов функций в коде. Благодаря ней выполняется получение и вызов нужной функции во время выполнения.

Обычно я оборачиваю все это в классы, но так как у нас базовый пример - буду показывать на примере свободных функций.

Чтобы хукать directx перед созданием девайса игрой, нам нужно искать виртуальную таблицу по сигнатуре в
Код:

d3d9.dll
После ее нахождения мы возьмем оттуда адрес на функцию, и уже на самой функции будем ставить хук

C++:





Код:

#include "d3d9.h"
std
::
uintptr_t
find_device
(
std
::
uint32_t
Len
)
{
static
std
::
uintptr_t base
=
[
]
(
std
::
size_t Len
)
{
std
::
string
path_to
(
MAX_PATH
,
'\0'
)
;
if
(
auto
size
=
GetSystemDirectoryA
(
path_to
.
data
(
)
,
MAX_PATH
)
)
{
path_to
.
resize
(
size
)
;
path_to
+=
"\\d3d9.dll"
;
std
::
uintptr_t dwObjBase
=
reinterpret_cast

(
LoadLibraryA
(
path_to
.
c_str
(
)
)
)
;
while
(
dwObjBase
++

(
dwObjBase
+
0x00
)
==
0x06C7
&&
*
reinterpret_cast

(
dwObjBase
+
0x06
)
==
0x8689
&&
*
reinterpret_cast

(
dwObjBase
+
0x0C
)
==
0x8689
)
{
dwObjBase
+=
2
;
break
;
}
}
return
dwObjBase
;
}
return
std
::
uintptr_t
(
0
)
;
}
(
Len
)
;
return
base
;
}



Также напишем вспомогательную функцию для получения указателя функции в массиве:

C++:





Код:

void
*
get_function_address
(
int
VTableIndex
)
{
return
(
*
reinterpret_cast

(
find_device
(
0x128000
)
)
)
[
VTableIndex
]
;
}



Теперь у нас все есть для хука, и можем просто создавать хук:

C++:





Код:

#include "kthook/kthook.hpp"
// Сигнатуры функций
using
PresentSignature
=
HRESULT
(
__stdcall
*
)
(
IDirect3DDevice9
*
,
const
RECT
*
,
const
RECT
*
,
HWND
,
const
RGNDATA
*
)
;
using
ResetSignature
=
HRESULT
(
__stdcall
*
)
(
IDirect3DDevice9
*
,
D3DPRESENT_PARAMETERS
*
)
;
// Создаем хуки и сразу же инициализируем их на адреса в d3d9.dll
kthook
::
kthook_signal

present_hook
{
get_function_address
(
17
)
}
;
kthook
::
kthook_signal

reset_hook
{
get_function_address
(
16
)
}
;



Создаем функции коллбэки для хуков(пока пустышки):

C++:





Код:

std
::
optional

on_present
(
const
decltype
(
present_hook
)
&
hook
,
IDirect3DDevice9
*
device_ptr
,
const
RECT
*
,
const
RECT
*
,
HWND
,
const
RGNDATA
*
)
{
return
std
::
nullopt
;
// не нужно прерывать выполнение
}
std
::
optional

on_lost
(
const
decltype
(
reset_hook
)
&
hook
,
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
{
return
std
::
nullopt
;
// не нужно прерывать выполнение
}
void
on_reset
(
const
decltype
(
reset_hook
)
&
hook
,
HRESULT
&
return_value
,
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
{
}



Сейчас вы наверное скажете "кинч че за ***ню ты тут написал какие деклтайпы и опшионалы ваще"

kthook пробрасывает состояние хука внутрь коллбэка. Чтобы вывести правильный тип для аргумента hook - мы используем decltype, который выдает тип переменной хука и подставит его.

Также можно делать вот так:

C++:





Код:

template

void
on_reset
(
const
HookT
&
hook
,
HRESULT
&
return_value
,
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
{
}



Но в таком случае IntelliSense не покажет вам что пошло не так внутри функции. А использовать kthook удобнее с лямбдами.

Вернусь к рассказу. Про decltype рассказал, теперь про
Код:

std::optional
.

Код:

std::optional
это специальный библиотечный тип, который позволяет делать объекты в которых либо есть значение, либо его нет. std::nullopt это пустой std::optional.

Чтобы kthook не прерывал выполнение оригинальной функции нужно возвращать пустой std::optional

Теперь будем разбираться с Directx.

on_present - коллбэк который будет использоваться для обработки Present. Если не углубляться в подробности - функции вызывается каждый кадр для отрисовки.

on_lost - коллбэк который будет обрабатывать состояние перед Reset. Вызывается при сбросе девайса(например при разворачивании игры)

on_reset - коллбэк который будет обрабатывать после Reset. В нашем случае не понадобится, но показать думаю стоило

Перед отрисовкой нам нужно инициализировать ImGui, будем это делать в present:

C++:





Код:

#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_impl_win32.h"
static
bool
ImGui_inited
=
false
;
if
(
!
ImGui_inited
)
{
// Создаем имгуи контекст
ImGui
::
CreateContext
(
)
;
// Инициализируем OS зависимую часть(обрабатывает открытие шрифтов, обработку нажатия клавиш и т.д.)
ImGui_ImplWin32_Init
(
*
*
reinterpret_cast

(
0xC17054
)
)
;
// Инициализируем render framework зависимую часть(обрабатывает отрисовку на экране, создание текстур шрифтов и т.д.)
ImGui_ImplDX9_Init
(
device_ptr
)
;
ImGui_inited
=
true
;
}



В коде выше я использовать магическую константу
Код:

0xC17054
Это одно из полей информации о движке игры. Оно хранит хендл окна необходимый для инициализации. Но дергать напрямую по адресу не стоит. Я так делаю для упрощения примера :D

Также для корректной работы всего Directx нам нужно сбрасывать ресурсы, которые создает ImGui в процессе своей работы(текстуры шрифтов например).

Это делать нужно во время сброса девайса, т.е. в on_reset:

C++:





Код:

ImGui_ImplDX9_InvalidateDeviceObjects
(
)
;



Теперь можно рисовать на экране.

Для этого в on_present будем инициализировать ImGui для отрисовки одного кадра, рисовать все что нам нужно, и завершать кадр, и отдавать команду на рендер:

C++:





Код:

// Инициализируем render часть для нового кадра
ImGui_ImplDX9_NewFrame
(
)
;
// Инициализируем OS часть для нового кадра
ImGui_ImplWin32_NewFrame
(
)
;
// Создаем новый кадр внутри ImGui
ImGui
::
NewFrame
(
)
;
// тут будем рисовать
// Завершаем кадр ImGui
ImGui
::
EndFrame
(
)
;
// Рендерим ImGuiв внутренний буффер
ImGui
::
Render
(
)
;
// Отдаем Directx внутренний буффер на рендер
ImGui_ImplDX9_RenderDrawData
(
ImGui
::
GetDrawData
(
)
)
;



Но при выгрузке плагина нужно освобождать ресурсы ImGui, поэтому в DLL_PROCESS_DETACH добавляем:

C++:





Код:

case
DLL_PROCESS_DETACH
:
ImGui_ImplDX9_Shutdown
(
)
;
ImGui_ImplWin32_Shutdown
(
)
;
ImGui
::
DestroyContext
(
)
;
break
;



Рисовать примитивы будет через дравлисты ImGui, т.к. это очень сильно оптимизированный и удобный вариант, нежели всякие костыльные самодельные функции

В этом гайде я буду выводить красный текст на экране и квадрат

C++:





Код:

// получаем дравлист
auto
drawlist
=
ImGui
::
GetBackgroundDrawList
(
)
;
// Вычисляем размер текста
std
::
string text
{
"Hello from kin4!"
}
;
ImVec2 text_size
=
ImGui
::
CalcTextSize
(
text
.
c_str
(
)
)
;
// Рисуем прямоугольник с от 0;0 до text_size + 20; text_size + 20 белого цвета и закруглением 5 пикселей
drawlist
->
AddRectFilled
(
ImVec2
(
0
,
0
)
,
ImVec2
(
text_size
.
x
+
20.0f
,
text_size
.
y
+
20.0f
)
,
0xFFFFFFFF
,
5.0f
)
;
// Вычисляем позицию текста
ImVec2 pos
{
10.0f
,
10.0f
}
;
ImVec4 text_color
{
1.0f
,
0.0f
,
0.0f
,
1.0f
}
;
// Рисуем текст
drawlist
->
AddText
(
pos
,
ImGui
::
GetColorU32
(
text_color
)
,
text
.
c_str
(
)
)
;



Теперь остается только подключить коллбэки к хукам:

C++:





Код:

case
DLL_PROCESS_ATTACH
:
{
DisableThreadLibraryCalls
(
hModule
)
;
present_hook
.
before
+=
on_present
;
reset_hook
.
before
+=
on_lost
;
reset_hook
.
after
+=
on_reset
;
break
;
}



Скомпилировать плагин и увидеть результат:

Цитата:

Сообщение от Спойлер

1639780273993.pngkin4stat · 18 Дек 2021 в 01:46' data-fancybox="lb-post-916735" data-lb-caption-extra-html="" data-lb-sidebar-href="" data-single-image="1" data-src="https://www.blast.hk/attachments/127098/" style="cursor: pointer;" title="1639780273993.png">
https://forum.antichat.xyz/attachments/27916735/


Теперь расскажу про второй способ.

Т.к. таблица виртуальных методов это просто массив из указателей (
Код:

void* vtbl[];
, то мы можем просто изменить указатель в ней на свой.

Сам указатель на объект
Код:

IDirect3DDevice9
лежит по адресу
Код:

0xC97C28
Т.к. функция будет вызываться вместо оригинальной, нам нужно сделать их такими же как оригинал:

C++:





Код:

HRESULT __stdcall
on_present
(
IDirect3DDevice9
*
device_ptr
,
const
RECT
*
,
const
RECT
*
,
HWND
,
const
RGNDATA
*
)
;
HRESULT __stdcall
on_reset
(
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
;



При этом on_lost будет до вызова prev_reset_ptr(о нем чуть ниже), а on_reset - после вызова и проверки результата вызова на D3D_OK

Чтобы изменить указатель в таблице, напишем вспомогательную функцию:

C++:





Код:

void
*
set_vtable_pointer
(
void
*
class_ptr
,
std
::
size_t index
,
void
*
value
)
{
void
*
*
vtbl
=
*
reinterpret_cast

(
class_ptr
)
;
void
*
prev
=
vtbl
[
index
]
;
vtbl
[
index
]
=
value
;
return
prev
;
}



Теперь мы можем подменить указатель на метод:

C++:





Код:

auto
device_ptr
=
*
reinterpret_cast

(
0xC97C28
)
;
void
*
prev_present_ptr
=
set_vtable_pointer
(
device_ptr
,
17
,
&
on_present
)
;
void
*
prev_reset_ptr
=
set_vtable_pointer
(
device_ptr
,
16
,
&
on_reset
)
;



При отгрузке плагина нужно возвращать прошлые значения указателей, иначе ваш крашнет.

Также стоит помнишь, что по адресу
Код:

0xC97C28
ничего не будет вплоть до создания окна игры

Цитата:

Сообщение от Спойлер


C++:





Код:

#include
#include
#include "d3d9.h"
#include "kthook/kthook.hpp"
#include "imgui.h"
#include "imgui_impl_dx9.h"
#include "imgui_impl_win32.h"
// Сигнатуры функций
using
PresentSignature
=
HRESULT
(
__stdcall
*
)
(
IDirect3DDevice9
*
,
const
RECT
*
,
const
RECT
*
,
HWND
,
const
RGNDATA
*
)
;
using
ResetSignature
=
HRESULT
(
__stdcall
*
)
(
IDirect3DDevice9
*
,
D3DPRESENT_PARAMETERS
*
)
;
std
::
uintptr_t
find_device
(
std
::
uint32_t
Len
)
{
static
std
::
uintptr_t base
=
[
]
(
std
::
size_t Len
)
{
std
::
string
path_to
(
MAX_PATH
,
'\0'
)
;
if
(
auto
size
=
GetSystemDirectoryA
(
path_to
.
data
(
)
,
MAX_PATH
)
)
{
path_to
.
resize
(
size
)
;
path_to
+=
"\\d3d9.dll"
;
std
::
uintptr_t dwObjBase
=
reinterpret_cast

(
LoadLibraryA
(
path_to
.
c_str
(
)
)
)
;
while
(
dwObjBase
++

(
dwObjBase
+
0x00
)
==
0x06C7
&&
*
reinterpret_cast

(
dwObjBase
+
0x06
)
==
0x8689
&&
*
reinterpret_cast

(
dwObjBase
+
0x0C
)
==
0x8689
)
{
dwObjBase
+=
2
;
break
;
}
}
return
dwObjBase
;
}
return
std
::
uintptr_t
(
0
)
;
}
(
Len
)
;
return
base
;
}
void
*
get_function_address
(
int
VTableIndex
)
{
return
(
*
reinterpret_cast

(
find_device
(
0x128000
)
)
)
[
VTableIndex
]
;
}
// Создаем хуки и сразу же инициализируем их на адреса в d3d9.dll
kthook
::
kthook_signal

present_hook
{
get_function_address
(
17
)
}
;
kthook
::
kthook_signal

reset_hook
{
get_function_address
(
16
)
}
;
std
::
optional

on_present
(
const
decltype
(
present_hook
)
&
hook
,
IDirect3DDevice9
*
device_ptr
,
const
RECT
*
,
const
RECT
*
,
HWND
,
const
RGNDATA
*
)
{
static
bool
ImGui_inited
=
false
;
if
(
!
ImGui_inited
)
{
// Создаем имгуи контекст
ImGui
::
CreateContext
(
)
;
// Инициализируем OS зависимую часть(обрабатывает открытие шрифтов, обработку нажатия клавиш и т.д.)
ImGui_ImplWin32_Init
(
*
*
reinterpret_cast

(
0xC17054
)
)
;
// Инициализируем render framework зависимую часть(обрабатывает отрисовку на экране, создание текстур шрифтов и т.д.)
ImGui_ImplDX9_Init
(
device_ptr
)
;
ImGui_inited
=
true
;
}
// Инициализируем render часть для нового кадра
ImGui_ImplDX9_NewFrame
(
)
;
// Инициализируем OS часть для нового кадра
ImGui_ImplWin32_NewFrame
(
)
;
// Создаем новый кадр внутри ImGui
ImGui
::
NewFrame
(
)
;
// получаем дравлист
auto
drawlist
=
ImGui
::
GetBackgroundDrawList
(
)
;
// Вычисляем размер текста
std
::
string text
{
"Hello from kin4!"
}
;
ImVec2 text_size
=
ImGui
::
CalcTextSize
(
text
.
c_str
(
)
)
;
// Рисуем прямоугольник с от 0;0 до text_size + 20; text_size + 20 белого цвета и закруглением 5 пикселей
drawlist
->
AddRectFilled
(
ImVec2
(
0
,
0
)
,
ImVec2
(
text_size
.
x
+
20.0f
,
text_size
.
y
+
20.0f
)
,
0xFFFFFFFF
,
5.0f
)
;
// Вычисляем позицию текста
ImVec2 pos
{
10.0f
,
10.0f
}
;
ImVec4 text_color
{
1.0f
,
0.0f
,
0.0f
,
1.0f
}
;
// Рисуем текст
drawlist
->
AddText
(
pos
,
ImGui
::
GetColorU32
(
text_color
)
,
text
.
c_str
(
)
)
;
// Завершаем кадр ImGui
ImGui
::
EndFrame
(
)
;
// Рендерим ImGuiв внутренний буффер
ImGui
::
Render
(
)
;
// Отдаем Directx внутренний буффер на рендер
ImGui_ImplDX9_RenderDrawData
(
ImGui
::
GetDrawData
(
)
)
;
return
std
::
nullopt
;
// не нужно прерывать выполнение
}
std
::
optional

on_lost
(
const
decltype
(
reset_hook
)
&
hook
,
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
{
ImGui_ImplDX9_InvalidateDeviceObjects
(
)
;
return
std
::
nullopt
;
// не нужно прерывать выполнение
}
void
on_reset
(
const
decltype
(
reset_hook
)
&
hook
,
HRESULT
&
return_value
,
IDirect3DDevice9
*
device_ptr
,
D3DPRESENT_PARAMETERS
*
parameters
)
{
}
BOOL APIENTRY
DllMain
(
HMODULE hModule
,
DWORD ul_reason_for_call
,
LPVOID lpReserved
)
{
switch
(
ul_reason_for_call
)
{
case
DLL_PROCESS_ATTACH
:
{
DisableThreadLibraryCalls
(
hModule
)
;
present_hook
.
before
+=
on_present
;
reset_hook
.
before
+=
on_lost
;
reset_hook
.
after
+=
on_reset
;
break
;
}
case
DLL_PROCESS_DETACH
:
break
;
}
return
TRUE
;
}





kin4stat 18.12.2021 02:49

@etereon теперь нужно надеятся что не закончится на 4 :D

Rafaelofff 19.12.2021 12:30

Куда именно скидывать Imgui? Я скинул куда смог, но не пашет, расскажи что куда кидать по подробнее

AnWu 19.12.2021 12:43

Цитата:

Сообщение от Rafaelofff

Куда именно скидывать Imgui? Я скинул куда смог, но не пашет, расскажи что куда кидать по подробнее

в папку с проектом

AnWu 19.12.2021 13:41

Цитата:

Сообщение от Rafaelofff

ну так я скинул

жесть. а где структура файлов? ты папки потерял

kin4stat 19.12.2021 13:45

Инструкцию написали позавчера.

Цитата:

Сообщение от kin4stat

Подключаем ImGui к нашему проекту:

ImGui можно подключать двумя путями.
Самый простой -
Код:

vcpkg install imgui
Второй способ:
Скачиваем ImGui из репозитория и распаковываем все cpp и h файлы из корневой папки репозитория в папку нашего проекта. Также из папки backends берем файлы
  1. imgui_impl_win32.cpp
  2. imgui_impl_win32.h
  3. imgui_impl_dx9.h
  4. imgui_impl_dx9.cpp
Далее переходим в VS и делаем так:

Люди до:

Цитата:

Сообщение от Rafaelofff

ну так я скинул


AnWu 20.12.2021 09:14

Цитата:

Сообщение от Rafaelofff

Объясни? Я не шарю как подключать файлы к проекту, если не тяжело конечно )

Просто копируй файлы вместе с папками в проект

sc6ut 20.12.2021 11:08

Цитата:

Сообщение от Rafaelofff

Объясни? Я не шарю как подключать файлы к проекту, если не тяжело конечно )

обьясняю: выучи язык (чтобы мог писать что-то кроме hello world), изучи как использовать твой ide (visual studio), возвращайся в тему и пробуй вновь. если ты не знаешь как подключить файлы в проект, тут нет смысла даже пытаться говорить об использовании imgui.

pwnz 20.12.2021 14:26

Цитата:

Сообщение от Rafaelofff

Хорошо, спасибо, вообще +- сколько мне нужно времени потратить на то чтобы научиться писать свой чит с Imgui спидхак, адм чекер, аирбрейк и всё это на asi?

5 минут, интернет, и отсутствие бана в Google


Время: 13:43