[ Новые сообщения · Участники · Правила форума · Поиск · RSS ]
Страница 1 из 212»
Форум » Игры серии GTA » GTA San Andreas » Создание плагина для GTA
Создание плагина для GTA
DK22Pac
Друзья
TOP Скриптер
02.08.13 00:31
Offline
В этой статье я распишу, как создать простой плагин для GTASA.
Для написания кода и компиляции я использую MS Visual C++ Express Edition.
Создание проекта.
Создаём новый проект в MSVC++, тип - Empty Project (пустой проект). img
Добавляем новый файл - main.cpp в папку Sources (ПКМ на Source Files > Add > New Item). img
Настройка проекта.
Переключаемся на режим компиляции "Release". img
Идём в настройки (Project->Properties). Далее:
Configuration properties:
Target Extension - .asi (или .cleo)
Configuration type - Dynamic Library (.dll), "Применить".

C/C++:
General > Warning Level - Level1 (/W1), "Применить".
Code Generation > Runtime Library - Multi-threaded (/MT), "Применить".

Подключение хедеров.
Для написания этого плагина нам понадобятся следующие хедеры: Windows.h, CPatch.h. Файл CPatch.h можно поместить в папку с проектом, а лучше - в папку со стандартными хедерами MSVC++ (у меня это C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include).
Открываем файл main.cpp. Подключаем хедеры.
Код
#include <Windows.h>
#include <CPatch.h>

Подключение плагина
При загрузке плагина игрой, в нём (плагине) будет выполнена функция DllMain. Т.е. это функция, которая вызывается при подключении плагина (и не только при подключении - так что нам надо будет ещё проверить событие, в связи с котороым была вызвана эта функция).
Код
BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
       if(reason == DLL_PROCESS_ATTACH)
       {
           // Здесь будет наш код, который выполнится при подключении плагина.
       }
       return TRUE;
}

Внедрение в код игры.
Как будет работать наш плагин? Мы будем внедрять наш код в "тело" игрового кода. Например, если нам понадобится производить какие-либо действия над игровыми автомобилями, мы сделаем "внедрение" в код обработки автомобилей. Таким образом, при обработке автомобиля, игра будет выполнять не только дефолтные функции, но и будет "прыгать" в наш плагин и выполнять функции наши.
Если нам надо выполнить какие-то действия в цикле - внедряемся в главный цикл игры. Если надо выполнить действия при открытии меню - внедряемся в код обработки меню, и т.д.
Как сделать внедрение? Во-первых, надо знать место, куда "внедряться". Лично я нахожу нужные места с помощью IDA и базы listener'а по gta_sa.exe (что и вам советую). Второе - это метод RedirectCall класса CPatch (который описан в хедере CPatch.h). Он заменяет вызов одной функции на вызов другой.
Код
CPatch::RedirectCall(<адрес - место вызова>, <адрес - новая функция для вызова>)

Итак, мы подменим вызов стандартной функции на вызов нашей функции. А стандартную функцию вызовем уже в нашей функции:
Код
defaultCode:
DefaultFunc();
OtherFunc();

>>

pathcedCode:
OurFunc();
OtherFunc();

;

OurFunc:
DefaultFunc();
// Наш код

Надеюсь, идея понятна. Если нет - спрашивайте в комментариях.
Главный цикл игрового процесса
Главная функция игрового процесса - Idle (здесь представлен псевдо-код из Hex-Ray Decompiler) представлена только для ознакомления. Мы будем внедряться в эту функцию.
Я собираюсь заменить вызов CFont::InitPerFrame (её адрес - 0x719800, а вызываетя она по адресу 0x53E972) на вызов нашей функции.
Наша функция
Перейдём к коду нашей функции, для начала обьявим её (обьявление надо поместить сразу после подулючения хедеров, перед функцией DllMain):
Код
void OurFunc();

Теперь напишем сам код функции.
Код
#include <Windows.h>
#include <CPatch.h>

void OurFunc();

BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
       if(reason == DLL_PROCESS_ATTACH)
         CPatch::RedirectCall(0x53E972, OurFunc);
       return TRUE;
}

void OurFunc()
{
       // Проверяем нажатие клавиши "Q"
       if(HIBYTE(GetKeyState(0x51)) == 0xFF)
       {
           // Добавляем денег игроку (50$), используем методы CPatch - GetInt, SetInt
           CPatch::SetInt(0xB7CE50, CPatch::GetInt(0xB7CE50) + 50);
       }
       // Вызываем стандартную функцию игры (0x719800)
       ((void (__cdecl *)())0x719800)();
}

Код можно компилировать (Debug > Build Solution). Полученный файл будет находиться в папке Release.
Всё, можно задавать вопросы. Знаю, самое сложное - это адреса. Нужно время, чтобы разобраться в этом. Если вам интересно - качайте базу listener'а и пробуйте.
Отредактировал DK22Pac - Пятница, 02.08.13, 00:53

SHooZ
Друзья
05.08.13 19:49
Offline
а можно ли прибавлять как-то вот так:

int *moneyplayer = (int*)0xB7CE50;
*moneyplayer = 1000;

?

DK22Pac
Друзья
TOP Скриптер
06.08.13 03:33
Offline
SHooZ, можно.
CPatch.h ещё не доработан, скоро добавлю туда макрос
Код
WRITEINT(0xB7CE50, 1000);

Что будет работать как
Код
*(int *)0xB7CE50 = 1000;
Отредактировал DK22Pac - Вторник, 06.08.13, 03:38

Yurko_UA
Модераторы
Модератор
10.08.13 18:02
Offline
У меня компилятор при компиляции кричит на файл Cpatch.h

Добавлено (10.08.13, 17:02)
---------------------------------------------
Проблема решена, скомпилировало. только вот у меня в скрипте должно отнимать 1000 баксов, а отнимает 3000. Сколько не ставлю, оно отнимает/добавляет в 3 раза больше


DK22Pac
Друзья
TOP Скриптер
10.08.13 18:55
Offline
Yurko_UA, это потому, что в коде нет задержек.

Код
#include <Windows.h>
#include <CPatch.h>

void OurFunc();

// Игровой таймер
#define gTimer (*(unsigned int *)0xB7CB84)

// Обьявляем глобальную переменную для записи времени
unsigned int gLastTimeKeyPressed;

BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
    if(reason == DLL_PROCESS_ATTACH)
    {
       CPatch::RedirectCall(0x53E972, OurFunc);
       // Обнуляем таймер
       gLastTimeKeyPressed = 0;
    }
    return TRUE;
}

void OurFunc()
{
    // Проверяем нажатие клавиши "Q" и  таймер (если прошло 0,5 секунды после последнего нажатия клавиши)
    if(HIBYTE(GetKeyState(0x51)) == 0xFF && gTimer - gLastTimeKeyPressed > 500)
    {
       // Добавляем денег игроку (50$), используем методы CPatch - GetInt, SetInt
       CPatch::SetInt(0xB7CE50, CPatch::GetInt(0xB7CE50) + 50);
       // Устанавливаем новое время последнего нажатия клавиши
       gLastTimeKeyPressed = gTimer;
    }
    // Вызываем стандартную функцию игры (0x719800)
    ((void (__cdecl *)())0x719800)();
}
Отредактировал DK22Pac - Суббота, 10.08.13, 18:56

Yurko_UA
Модераторы
Модератор
10.08.13 19:10
Offline
DK22Pac, А как, например, тачку заспаунить?
И где вообще можно найти хорошую документацию по функциям CPatch.h ?

Добавлено (10.08.13, 18:10)
---------------------------------------------
DK22Pac, А что значит в этом условии



Код
if(HIBYTE(GetKeyState(0x51)) == 0xFF && gTimer - gLastTimeKeyPressed > 500)

0xFF ?

DK22Pac
Друзья
TOP Скриптер
10.08.13 19:18
Offline
Yurko_UA, по нему нет документации. Я туда добавил только функции, связанные с базовым редактированием памяти:
Код
void Nop(int address, int size) - записывает указанное кол-во опкодов "NOP" (0x90) по адресу
void RedirectCall(int address, void *func) - добавляет вызов функции в указанном месте
void RedirectJump(int address, void *func) - добавляет переход на функцию в указанном месте
void SetChar(int address, char value) - запись значения по адресу, 1 байт
void SetShort(int address, short value) - запись значения по адресу, 2 байта
void SetInt(int address, int value) - запись значения по адресу, целое 4 байта
void SetFloat(int address, float value) - запись значения по адресу, дробное 4 байта
void SetPointer(int address, void *value) - запись значения по адресу, указатель 4 байта
void GetChar(int address, char *value) - получает значение по адресу (1 байт), результат записывает по указателю
void GetShort(int address, short *value) - получает значение по адресу (2 байта), результат записывает по указателю
void GetInt(int address, int *value) - получает значение по адресу (целое 4 байта), результат записывает по указателю
void GetFloat(int address, float *value) - получает значение по адресу (дробное 4 байт), результат записывает по указателю
char GetChar(int address) - возвращает значение по адресу (1 байт)
short GetShort(int address) - возвращает значение по адресу (2 байта)
int GetInt(int address) - возвращает значение по адресу (целое 4 байта)
float GetFloat(int address) - возвращает значение по адресу (дробное 4 байта)

Ещё один пример напишу позже.

Yurko_UA
Модераторы
Модератор
10.08.13 19:24
Offline

Код
#include <Windows.h>
#include <CPatch.h>

void OurFunc();

#define gTimer (*(unsigned int *)0xB7CB84)

unsigned int gLastTimeKeyPressed;

    BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
    {
    if(reason == DLL_PROCESS_ATTACH)
    {
    CPatch::RedirectCall(0x53E972, OurFunc);

    gLastTimeKeyPressed = 0;
    }
    return TRUE;
    }

void OurFunc()
{

    if(HIBYTE(GetKeyState(0x31)) == 0xFF && gTimer - gLastTimeKeyPressed > 500)
    {

           CPatch::SetInt(0xB7CE50, CPatch::GetInt(0xB7CE50) + 1000);
            
           gLastTimeKeyPressed = gTimer;
          ((void (__cdecl *)())0x719800)();
        }
    else  
    {
    if(HIBYTE(GetKeyState(0x32)) == 0xFF && gTimer - gLastTimeKeyPressed > 500)
    {

           CPatch::SetInt(0xB793E0, CPatch::GetInt(0xB793E0) + 150);
            
           gLastTimeKeyPressed = gTimer;
        }    
    }
        ((void (__cdecl *)())0x719800)();
    }


Вот код. Когда нажимаю 0x31 - условие срабатывает  и добавляется 1000 баксов, а когда нажимаю 0x32, то нифига.

Я на с++ никогда не работал, и не знаю, чем заменить оператор elseif

DK22Pac
Друзья
TOP Скриптер
10.08.13 19:26
Offline
Yurko_UA, состояние клавиши.
Код
void OurFunc()
{
     // Если прошло 0,5 секунды после последнего нажатия клавиши...
     if(gTimer - gLastTimeKeyPressed > 500)
     {
        // Проверяем нажатие клавиши
        if(HIBYTE(GetKeyState(0x31)) == 0xFF)
        {
           CPatch::SetInt(0xB7CE50, CPatch::GetInt(0xB7CE50) + 1000);
           // Устанавливаем новое время последнего нажатия клавиши
           gLastTimeKeyPressed = gTimer;
        }
        else if(HIBYTE(GetKeyState(0x32)) == 0xFF)
        {
           CPatch::SetInt(0xB7CE50, CPatch::GetInt(0xB7CE50) + 150);
           // Устанавливаем новое время последнего нажатия клавиши
           gLastTimeKeyPressed = gTimer;
        }
     }
Отредактировал DK22Pac - Суббота, 10.08.13, 19:34

Yurko_UA
Модераторы
Модератор
10.08.13 19:49
Offline
DK22Pac, А с этими плагинами можно вообще что-то серьерзное сделать? Миссию?

DK22Pac
Друзья
TOP Скриптер
12.08.13 18:24
Offline
Yurko_UA, можно сделать что угодно. Тут у тебя нет ограничений в этом плане.
А для миссий предназначен SCM-язык.

Добавлено (12.08.13, 17:24)
---------------------------------------------
Использование классов. Класс игрока (CPlayer)
Использовать операции работы с памятью для управления игрой - неудобно. У нас есть возможность определить основные классы и структуры игры, и работать с ними.
Класс, который описывает игрока и позволяет им управлять - CPlayer.
В данном архиве собраны хедеры для класса CPlayer и близких к нему классов (например, CPlayerData). Классы, которые описывают педов и транспорт, пока что отсутствуют.
Все эти файлы удобнее поместить в отдельную папку, - например, game, чтобы отсоединить их от своих файлов.
Сейчас попытаемся переделать созданную функцию OurFunc с использование класса CPlayer.
Подключаем хедер, который содержит класс:
Код
#include "game\CPlayer.h"

Для доступа к членам класса игрока используем запись:
Код
gPlayer._member

Или
Код
gpPlayer->_member

Все известные члены класса проименованы следующим образом:
m_<type>Name // member with type <type> "Name"

Где type используется для указания размера этого поля (b (byte) - один байт, w (word) - 2, dw (dword) - 4).
Таким образом, для прибавки денег игроку достаточно написать:
Код
gPlayer.m_dwMoney += 10000;

В конечную функцию также добавим установку кое-каких свойств для игрока.
Код
#include <Windows.h>
#include <CPatch.h>
#include "game\CPlayer.h"

void OurFunc();

BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
       if(reason == DLL_PROCESS_ATTACH)
         CPatch::RedirectCall(0x53E972, OurFunc);
       return TRUE;
}

void OurFunc()
{
      if(HIBYTE(GetKeyState(0x51)) == 0xFF)
      {
         gPlayer.m_dwMoney += 10000;        // 10 000 $
         gPlayer.m_bFireProof = TRUE;       // Огнеустойчивость
         gPlayer.m_bDoesNotGetTired = TRUE; // Игрок не устаёт
         gPlayer.m_bFastReload = TRUE;      // Быстрая перезарядка
      }
}
Отредактировал DK22Pac - Понедельник, 12.08.13, 18:26

Yurko_UA
Модераторы
Модератор
25.08.13 19:06
Offline
Цитата (DK22Pac)
Классы, которые описывают педов и транспорт, пока что отсутствуют.
Всмысле "отсутствуют"? их не написали еще чтоли?

DK22Pac
Друзья
TOP Скриптер
26.08.13 15:22
Offline
Цитата (Yurko_UA)
Всмысле "отсутствуют"? их не написали еще чтоли?
Они есть, но в плохом виде.

Yurko_UA
Модераторы
Модератор
26.08.13 16:16
Offline
Цитата (DK22Pac)
Они есть, но в плохом виде.
А где можно хоть такие найти?

asdfjke
Пользователи
28.07.14 21:32
Offline
добавил в пример функцию рисования битмапа - выводит, прерывисто, только в  кадры на момент вызова. как сделать так, чтобы выводился и промежуточных кадрах? или может правильное место подключения подскажете? (плагин не использую - компилятор ругается ()

BoPoH
Друзья
29.07.14 02:30
Offline
Цитата asdfjke ()
или может правильное место подключения подскажете?

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

asdfjke
Пользователи
05.08.14 18:50
Offline
Ворон, сделал хук на present - в игре стал периодически исчезать "туман" с объектов, как с твоим fps mod3 и с directfont. У тебя все нормально работает или тоже есть такое? и можешь ли показать исходник своих хуков?

BoPoH
Друзья
07.08.14 00:06
Offline
Цитата asdfjke ()
Ворон, сделал хук на present - в игре стал периодически исчезать "туман" с объектов, как с твоим fps mod3 и с directfont. У тебя все нормально работает или тоже есть такое? и можешь ли показать исходник своих хуков?

А ты не забываешь вызывать функцию, на которую хук повесил?

asdfjke
Пользователи
07.08.14 17:27
Offline
Цитата BoPoH ()
А ты не забываешь вызывать функцию, на которую хук повесил?
нет. кстати, проблема исчезает с подключением ultraThing, думаю, с энбом тоже, однако, все же интересно что это за фигня

BoPoH
Друзья
08.08.14 03:51
Offline
Мне кажется, с хуком это врядли как-то связано.

Форум » Игры серии GTA » GTA San Andreas » Создание плагина для GTA
Страница 1 из 212»
Поиск: