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
;
}
Хуки – что это такое и как с ними работать (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
;
}