Гайд Создание ASI-плагина с нуля [1]

Делать мне было нечего, а работать не хотелось, поэтому вы видите этот гайд

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

Введение:
Для начала скажу, что вам понадобится Visual Studio(Так проще всего), и пакеты к ней, а именно - Разработка классических приложений на C++ и Разработка приложений на универсальной платформы Windows.
Все действия производились на Visual Studio 2019, в других версиях интерфейс может отличаться

Создание проекта:
И так, начнем с создания проекта. Жмем кнопку создать проект, и ищем "Библиотека динамической компоновки (DLL)" (Дело в том, что ASI это и есть DLL файл, только с измененным расширением).
Создаем проект. Я назвал его ASIPlugin.

После создания проекта мы видим перед собой окно редактора с подготовленным шаблоном. Шаблон содержит в себе подключение pch.h и функции DllMain.

Настройка проекта:
Начнем с настройки проекта.
В панели меню сверху жмем Проект, и выпадающем меню выбираем пункт Свойства: $ProjectName
Сверху, в выпадающем меню в открывшемся диалоге выбираем Конфигурация -> Все конфигурации.
После этого я обычно отключаю предварительно скомпилированные заголовки(pch.h), но вы можете их оставить(поэкспериментируйте сами)
Включить/Выключить можно в подменю C/C++ -> Предварительно откомпилированные заголовки -> Предварительно откомпилированный заголовок

После этого переходим в Дополнительно -> Расширение целевого файла, меняем .dll на .asi(чтобы подгружалось ASI Лоадером)
(ОПЦИОНАЛЬНО) После этого переходим в Общие -> Выходной каталог, здесь указываем путь до своей GTA

Настройка проекта окончена, переходим к написанию кода

Написание кода:
Функция DllMain - основная функция Dll библиотеки, которая в нашем случае играет роль Asi плагина. Эта функция вызывается при четырех условиях - создании/уничтожении потока, и при присоединении и отсоединении нашей библиотеки. Первые два условия в данный момент нас не особо интересуют, поэтому перейдем к другим двум. Функция принимает в себя 3 аргумента, один из которых зарезервирован системой(lpReserved). Остальные два аргумента показывают нам базовый адрес библиотеки(Адрес по которому начинается наша библиотека в оперативной памяти) и причину вызова функции. Причина вызова как я уже описал выше - имеет 4 значения: DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH и DLL_PROCESS_DETACH. На данные момент нас интересуют первое и последнее из них. Первое вызывается при присоединении к процессу, последнее - при отсоединении.

Дальше работаем с DLL_PROCESS_ATTACH(В нашем случае оно будет выполнять функцию int main, как в консольном приложении C/C++).
DLL_PROCESS_DETACH на данный момент нам не нужен, т.к. нам нечего освобождать после выгрузки.
Начну с того, что DllMain с причиной DLL_PROCESS_ATTACH вызывается еще до появления окна GTA, когда игра еще не инициализирована, поэтому мы не можем взаимодействовать с игрой на этом моменте, и придется дождаться ее инициализации, это можно сделать разными путями, но на этот раз сделаем через создание потока, но так лучше не делать, и в дальнейшем я возможно покажу как сделать лучше.
Дело в том, что потоки на процессоре могут выполняться совершенно параллельно, и может возникнуть ситуация когда сразу несколько потоков(в нашем случае это поток игры и наш поток) обращаются к одной и той же памяти. Возникает состояние гонки потоков, и это приводит к Undefined Behaviour (по-русски - все пойдет по пизде)
Начнем с того, что к case DLL_PROCESS_ATTACH: добавим break;, чтобы выполнение кода не пошло по другим веткам. Так как нас не интересуют события с потоками, скажем Windows, чтобы она вообще не дергала нас по этому поводу, вызвав функцию DisableThreadLibraryCalls(hModule);

Переходим к созданию функции ожидания инициализации и добавления сообщения на экран.
Для функции CreateThread требуется функция определенного типа, а для std::thread подойдет любая. В этом гайде я покажу оба варианта.
Создаем функции:
C++:
DWORD WINAPI InitializeAndLoad(LPVOID param) {

return 0;
}
Или (для std::thread)

C++:
void InitializeAndLoad() {

}

Функцию добавления сообщения на экран возьмем с plugin-sdk

Далее переходим к инициализации.
Игра хранит свою стадию по адресу 0xC8D4C0
И пока значение по адресу не станет 9(полная инициализация игры) - спим и ждем
C++:
while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1u));
}
Далее спокойно вызываем функцию AddMessageJumpQ, ведь мы знаем, что игра уже инициализирована
C++:
void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}
C++:
AddMessageJumpQ("~r~Hello from blast.hk", 5000, 0, false);
Теперь создадим поток инициализации в DllMain, передав ему нашу функцию (не забывайте что для std::thread нужно подключить заголовок thread):
std::thread(InitializeAndLoad).detach();
Либо:
CreateThread(0, 0, &InitializeAndLoad, 0, 0, 0);
Итого должно выйти примерно так:
C++:
#include "pch.h"
#include <thread>

void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}

void InitializeAndLoad() {
    while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100u));
    }
    AddMessageJumpQ("~r~Hello from blast.hk", 5000, 0, false);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        std::thread(InitializeAndLoad).detach();
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Либо так, в случае с CreateThread:

C++:
#include "pch.h"

void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}

DWORD WINAPI InitializeAndLoad(LPVOID) {
    while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
        Sleep(100);
    }
    AddMessageJumpQ("~r~Hello from blast.hk", 1000, 0, false);
    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        CreateThread(0, 0, &InitializeAndLoad, 0, 0, 0);
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Жмем Ctrl+Shift+B, ждем окончания сборки, заходим в игру и видим:
1622062765879.png
 
Последнее редактирование:

loganhackerdff

Известный
868
519
Потоки не в моде
C++:
mhook = new CCallHook((void*)0x00748DA3, eSafeCall(sc_registers | sc_flags), 6);
  mhook->enable(mainloop);
 

kin4stat

mq-team · kin4@naebalovo.team
Автор темы
Всефорумный модератор
2,746
4,831
Потоки не в моде
C++:
mhook = new CCallHook((void*)0x00748DA3, eSafeCall(sc_registers | sc_flags), 6);
  mhook->enable(mainloop);
Да господи я же написал что не в этот раз, ибо придется объяснять что такое хуки зачем куда и почему
 
  • Нравится
  • Ха-ха
Реакции: D3.Pheonix и F0RQU1N and

Gruzin Gang

Всефорумный Грузин
823
617
Делать мне было нечего, а работать не хотелось, поэтому вы видите этот гайд

В этом гайде мы создадим свой ASI-плагин с нуля, который выведет сообщение на экран.

Введение:
Для начала скажу, что вам понадобится Visual Studio(Так проще всего), и пакеты к ней, а именно - Разработка классических приложений на C++ и Разработка приложений на универсальной платформы Windows.
Все действия производились на Visual Studio 2019, в других версиях интерфейс может отличаться

Создание проекта:
И так, начнем с создания проекта. Жмем кнопку создать проект, и ищем "Библиотека динамической компоновки (DLL)" (Дело в том, что ASI это и есть DLL файл, только с измененным расширением).
Создаем проект. Я назвал его ASIPlugin.

После создания проекта мы видим перед собой окно редактора с подготовленным шаблоном. Шаблон содержит в себе подключение pch.h и функции DllMain.

Настройка проекта:
Начнем с настройки проекта.
В панели меню сверху жмем Проект, и выпадающем меню выбираем пункт Свойства: $ProjectName
Сверху, в выпадающем меню в открывшемся диалоге выбираем Конфигурация -> Все конфигурации.
После этого я обычно отключаю предварительно скомпилированные заголовки(pch.h), но вы можете их оставить(поэкспериментируйте сами)
Включить/Выключить можно в подменю C/C++ -> Предварительно откомпилированные заголовки -> Предварительно откомпилированный заголовок

После этого переходим в Дополнительно -> Расширение целевого файла, меняем .dll на .asi(чтобы подгружалось ASI Лоадером)
(ОПЦИОНАЛЬНО) После этого переходим в Общие -> Выходной каталог, здесь указываем путь до своей GTA

Настройка проекта окончена, переходим к написанию кода

Написание кода:
Функция DllMain - основная функция Dll библиотеки, которая в нашем случае играет роль Asi плагина. Эта функция вызывается при четырех условиях - создании/уничтожении потока, и при присоединении и отсоединении нашей библиотеки. Первые два условия в данный момент нас не особо интересуют, поэтому перейдем к другим двум. Функция принимает в себя 3 аргумента, один из которых зарезервирован системой(lpReserved). Остальные два аргумента показывают нам базовый адрес библиотеки(Адрес по которому начинается наша библиотека в оперативной памяти) и причину вызова функции. Причина вызова как я уже описал выше - имеет 4 значения: DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH и DLL_PROCESS_DETACH. На данные момент нас интересуют первое и последнее из них. Первое вызывается при присоединении к процессу, последнее - при отсоединении.

Дальше работаем с DLL_PROCESS_ATTACH(В нашем случае оно будет выполнять функцию int main, как в консольном приложении C/C++).
DLL_PROCESS_DETACH на данный момент нам не нужен, т.к. нам нечего освобождать после выгрузки.
Начну с того, что DllMain с причиной DLL_PROCESS_ATTACH вызывается еще до появления окна GTA, когда игра еще не инициализирована, поэтому мы не можем взаимодействовать с игрой на этом моменте, и придется дождаться ее инициализации, это можно сделать разными путями, но на этот раз сделаем через создание потока, но так лучше не делать, и в дальнейшем я возможно покажу как сделать лучше.
Дело в том, что потоки на процессоре могут выполняться совершенно параллельно, и может возникнуть ситуация когда сразу несколько потоков(в нашем случае это поток игры и наш поток) обращаются к одной и той же памяти. Возникает состояние гонки потоков, и это приводит к Undefined Behaviour (по-русски - все пойдет по пизде)
Начнем с того, что к case DLL_PROCESS_ATTACH: добавим break;, чтобы выполнение кода не пошло по другим веткам. Так как нас не интересуют события с потоками, скажем Windows, чтобы она вообще не дергала нас по этому поводу, вызвав функцию DisableThreadLibraryCalls(hModule);

Переходим к созданию функции ожидания инициализации и добавления сообщения на экран.
Для функции CreateThread требуется функция определенного типа, а для std::thread подойдет любая. В этом гайде я покажу оба варианта.
Создаем функции:
C++:
DWORD WINAPI InitializeAndLoad(LPVOID param) {

return 0;
}
Или (для std::thread)

C++:
void InitializeAndLoad() {

}

Функцию добавления сообщения на экран возьмем с plugin-sdk

Далее переходим к инициализации.
Игра хранит свою стадию по адресу 0xC8D4C0
И пока значение по адресу не станет 9(полная инициализация игры) - спим и ждем
C++:
while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1u));
}
Далее спокойно вызываем функцию AddMessageJumpQ, ведь мы знаем, что игра уже инициализирована
C++:
void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}
C++:
AddMessageJumpQ("~r~Hello from blast.hk", 5000, 0, false);
Теперь создадим поток инициализации в DllMain, передав ему нашу функцию (не забывайте что для std::thread нужно подключить заголовок thread):
std::thread(InitializeAndLoad).detach();
Либо:
CreateThread(0, 0, &InitializeAndLoad, 0, 0, 0);
Итого должно выйти примерно так:
C++:
#include "pch.h"
#include <thread>

void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}

void InitializeAndLoad() {
    while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100u));
    }
    AddMessageJumpQ("~r~Hello from blast.hk", 5000, 0, false);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        std::thread(InitializeAndLoad).detach();
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Либо так, в случае с CreateThread:

C++:
#include "pch.h"

void AddMessageJumpQ(const char* text, unsigned int time, unsigned short flag, bool bPreviousBrief)
{
    ((void(__cdecl*)(const char*, unsigned int, unsigned short, bool))0x69F1E0)(text, time, flag, bPreviousBrief);
}

DWORD WINAPI InitializeAndLoad(LPVOID) {
    while (*reinterpret_cast<unsigned char*>(0xC8D4C0) != 9) {
        Sleep(100);
    }
    AddMessageJumpQ("~r~Hello from blast.hk", 1000, 0, false);
    return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        CreateThread(0, 0, &InitializeAndLoad, 0, 0, 0);
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Жмем Ctrl+Shift+B, ждем окончания сборки, заходим в игру и видим:
Посмотреть вложение 98531
в пизду я лучше пойду дальше луа пилить
 

SR_team

like pancake
BH Team
4,809
6,488
Разработка приложений на универсальной платформы Windows.
Нахуя? Это же metro-приложения для магазина Windows. Каким боком они к ASI относятся?

Начнем с настройки проекта.
В панели меню сверху жмем Проект, и выпадающем меню выбираем пункт Свойства: $ProjectName
Сверху, в выпадающем меню в открывшемся диалоге выбираем Конфигурация -> Все конфигурации.
После этого я обычно отключаю предварительно скомпилированные заголовки(pch.h), но вы можете их оставить(поэкспериментируйте сами)
Включить/Выключить можно в подменю C/C++ -> Предварительно откомпилированные заголовки -> Предварительно откомпилированный заголовок

После этого переходим в Дополнительно -> Расширение целевого файла, меняем .dll на .asi(чтобы подгружалось ASI Лоадером)
(ОПЦИОНАЛЬНО) После этого переходим в Общие -> Выходной каталог, здесь указываем путь до своей GTA

Настройка проекта окончена, переходим к написанию кода
добавь сюда скриншоты, без них хлебушки не осилят мышкой тыкать

Функция DllMain - основная функция Dll библиотеки, которая в нашем случае играет роль Asi плагина
Я тут недавно одному челу с курсачем помогал, так вот у него были проблемы с распознанием функций, Твои хлебушки так же могут начать херачить код в начало или конец DllMain.cpp. Добавь скрины

Первое вызывается при присоединении к процессу, последнее - при отсоединении.
А еще при присоединении и отсоединении происходит вызов конструкторов и деструкторов для глобальных объектов, можно написать свой класс, который будет все инициализировать и уничтожать, и просто создать его глобальный инстанс. Но в Windows такое не всегда работает без DllMain, но причин не помню, года 4+ назад тыкал это
 

AkrD1338

Участник
78
16
а как скомпилировать , если есть исходник прошу помогите
 

kin4stat

mq-team · kin4@naebalovo.team
Автор темы
Всефорумный модератор
2,746
4,831
  • Ха-ха
Реакции: Tema05 и ARMOR

kin4stat

mq-team · kin4@naebalovo.team
Автор темы
Всефорумный модератор
2,746
4,831
В срхуках не важен calling convention и для функций без аргументов можно оставить просто <>
А еще нет поддержки fastcall, и используется куча ассемблера который потом не перенести на x64 гыг
А, еще, нет автоопределения размера хука
 

SR_team

like pancake
BH Team
4,809
6,488
А еще нет поддержки fastcall, и используется куча ассемблера который потом не перенести на x64 гыг
А, еще, нет автоопределения размера хука
А как ты собираешься без ассемблера хукать что-то отличное от функций, например условия? И в x64 нет fastcall
 
  • Нравится
Реакции: Cake_