PDA

Просмотр полной версии : Работа с рендером и Directx9 [4]


kin4stat
18.12.2021, 02:46
Создание ASI-плагина с нуля (https://www.blast.hk/threads/89122/)

Хуки – что это такое и как с ними работать (https://www.blast.hk/threads/91079/)

Безопасная инициализация и работа с SAMP (https://www.blast.hk/threads/101433/)

Работа с рендером и Directx9 (https://www.blast.hk/threads/113060/)

Обработка событий окна + ImGui (https://www.blast.hk/threads/115851/)
В этом гайде будет рассказано про работу с рендером в Directx9 с помощью ImGui

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

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

/std:c++17

, в других версиях интерфейс может отличаться.

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

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

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

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

Скачиваем установщик (https://www.blast.hk/redirect/aHR0cHM6Ly93d3cubWljcm9zb2Z0LmNvbS9lbi11cy9kb3dubG 9hZC9kZXRhaWxzLmFzcHg_aWQ9NjgxMg), производим полную установку, перезагружаем наш пк(желательно)

В панели меню сверху жмем Проект, и выпадающем меню выбираем пункт Свойства: $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 из репозитория (https://github.com/ocornut/imgui) и распаковываем все cpp и h файлы из корневой папки репозитория в папку нашего проекта. Также из папки backends берем файлы


imgui_impl_win32.cpp

imgui_impl_win32.h

imgui_impl_dx9.h

imgui_impl_dx9.cpp
Далее переходим в VS и делаем так:




https://forum.antichat.xyz/attachments/27916735/

https://forum.antichat.xyz/attachments/27916735/




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

Хукать directx в gta можно двумя путями:


Хукать таблицу виртуальных методов

Хукать функцию в библиотеке 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 (https://www.blast.hk/members/90741/) теперь нужно надеятся что не закончится на 4 :D

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

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


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

AnWu
19.12.2021, 13:41
ну так я скинул


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

kin4stat
19.12.2021, 13:45
Инструкцию написали позавчера.



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

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

vcpkg install imgui


Второй способ:
Скачиваем ImGui из репозитория (https://github.com/ocornut/imgui) и распаковываем все cpp и h файлы из корневой папки репозитория в папку нашего проекта. Также из папки backends берем файлы

imgui_impl_win32.cpp

imgui_impl_win32.h

imgui_impl_dx9.h

imgui_impl_dx9.cpp
Далее переходим в VS и делаем так:


Люди до:



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

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


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

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


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

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


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