Попробую собрать воедино разбросанную по форуму информацию и применить её к созданию конкретной простенькой программы-бота. Я не говорю в этой теме как НУЖНО делать, а всего лишь показываю, как МОЖНО сделать. Расчитана данная статья на программистов, использующих среду Borland C++ Builder(так как пример будет реализован именно в ней), но думаю что и другим пригодится. Всю статью разбил на несколько частей, так как объём получился не маленький, вероятность чего, кстати, ожидалась. Могут присутствовать какие-либо ошибки, так как я не гуру программирования. Замечания и указания на ошибки ожидаются.
Вообще почти все действия бота совершаются в зависимости от полученных из клиента данных, например бот должен начать хилить себя, когда считанное из клиента значение ХП меньше определённого значения. Итак, для начала нам нужно научиться получать различные данные из клиента. Для этого понадобится напрямую работать памятью клиента игры. Чтобы работать с памятью нужно сначала получить доступ к ней, то есть открыть для работы процесс клиента. Для этих целей существует функция OpenProcess, которая открывает существующий объект процесса. Посмотрим на параметры, которые получает эта функция, и что она нам возвращает:
HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
dwDesiredAccess Устанавливает уровень доступа к объекту процесса, мы будем использовать значение PROCESS_ALL_ACCESS, оно означает все возможные права доступа для объекта процесса. bInheritHandle будем выставлять в false, это будет значить, что дескриптор не может наследоваться. dwProcessId Идентификатор процесса, который открывается. Его нам ещё предстоит найти.
Возвращаемое значения типа HANDLE - дескриптор процесса, который мы открыли(если открытие не удалось, функция вернёт NULL).
Итак определим переменную hProc типа HANDLE и PID типа DWORD (PID - Process ID, идентификатор процесса, параметр, который имеет любой загруженный в память процесс).
HANDLE hProc;
DWORD PID;
Функция открытия теперь будет выглядеть так hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID);
Всё бы ничего, но параметр PID нам не известен. Будем его искать. Нам известен заголовок окна процесса "Perfect World" и имя самого процесса "elementclient.exe". Будем искать PID по имени процесса. В этом нам поможет библиотека Tlhelp32.h, поэтому включим её в наш проект строчкой #include <Tlhelp32.h>
Можно конечно обойтись и без неё, но не будем заморачиваться. Напишем функцию, которая будет возвращать PID процесса по его имени.
Вообще нужно бы создать отдельный модуль с именем например Client и писать туда всё что касается именно работы с клиентом, для нормальной структуризации нашего проекта, и затем объявить этот модуль в главном модуле проекта. В нем описать класс CLIENT(или структуру, кому что по вкусу), и вышеописанную функцию прописать внутри класса, чтобы можно было в главном модуле указать например CLIENT client;
и далее в программе вызвать функцию определения PID, например так DWORD PID = client.PIDByProcName("elementclient.exe");
Вроде бы всё выглядит аккуратно и красиво.
Вообще давайте сделаем отступление(касательно программирования в среде с++ builder) и определим сразу свои действия. Определим сразу, какие структуры у нас будут в программе использоваться, и создадим для каждой свой модуль, в котором они будут описаны и реализованы, а потом мы будем подключать к проекту нужный модуль. Итак структуры: BOT - структура будет содержать все действия, связанные с работой бота, в этой структуре будет указана структура типа CLIENT, тоесть к модулю Bot будет подключаться модуль Client, а уже модуль Bot будем включать в главный модуль проекта.
Позже реализуем структуру READER, которая будет отвечать за работу с памятью клиента. Основной её функцией, следуя из названия, будет считывание значений из клиента.
Если вы думаете, что так заморачиваться не стоит, то вы поменяете своё мнение, когда функциональность вашего бота солидно вырастет, и вы начнёте как котёнок искать концы в клубке своих же функций.
Модуль в среде с++ builder состоит из заголовочного файла *.h , в котором наш класс и его внутренности просто объявлены, а так же файла *.срр, в котором идёт уже реализация того, что мы описали в *.h. Нужно не забыть прописывать в файле *.cpp #include "*.h", для того, чтобы наша реализация "видела" объявление того, что собственно мы пишем(хотя при добавлении модуля это происходит автоматически). Итак давайте создадим новый проект(у вас наверное уже создан) и добавим к нему 2 новых модуля.
File->New->Unit
и сохраним их под именами Client и Bot.
При добавлении модуля среда автоматически создаёт 2 одноимённых пустых(почти) файла с расширениями cpp и h, автоматически подключает их к проекту(но не связывает друг с другом инклудами), а так же сразу инклюдит в файле *.cpp (реализации модуля) файл *.h (заголовочный файл модуля, в нём будут только объявления функций/классов). Напоминаю, что переключения между файлом реализации и описания в редакторе происходит правым кликом по закладке модуля и выборе пункта Open source/header file. Также это можно сделать в нижней части окна редактора нажатием на закладки.
В модуле Client в заголовочном файле Client.h опишем нашу структуру(далее мы будем её по надобности усложнять). Также не забываем включить сюда модули vcl.h и Tlhelp32.h
Код:
#include <vcl.h>
#include <Tlhelp32.h>
#ifndef ClientH
#define ClientH
struct CLIENT
{
DWORD pid; // Идентификатор Процесса
DWORD PIDByProcName(AnsiString ProcessName); // определение pid по имени процесса
};
#endif
Пока что такая простенькая структура. Переключимся на реализацию модуля(файл Client.cpp) и пишем там реализацию функции PIDByProcName.
Теперь переходим к модулю Bot и в Bot.h обьявляем структуру нашего бота. Внутри структуры объявляем структуру типа CLIENT. Также не забываем проверит, подключен ли Client.h, чтобы в этом модуле было известно, что представляет структура типа CLIENT.
Код:
#include "Client.h"
#ifndef BotH
#define BotH
// Структура бота
struct BOT
{
CLIENT client;
};
#endif
В файле реализации пока писать нечего, достаточно объявления, всё реализовано в других модулях.
Теперь у нас есть всё, чтобы открыть доступ к процессу. Давайте сначала напишем функцию инициализации, в которой будет происходить вся подготовка. Вызывать мы её будем при запуске бота. Переходим к объявлению структуры Client и описываем в структуре новую функцию Init().
Код:
struct CLIENT
{
DWORD pid; // Идентификатор Процесса
DWORD PIDByProcName(AnsiString ProcessName); // определение pid по имени процесса
void Init(); // инициализация
};
Перейдём к реализации в Client.cpp и добавим нашу функцию.
На этом этапе можно проверить что у нас получилось. В главном модуле Unit1.cpp подключим модуль Bot, и обьявим экземпляр нашего бота.
Код:
#include "Bot.h"
BOT bot;
Затем кинем на форму два компонента, Button и Label. В событии создания формы вызовем функцию инициализации.
Код:
bot.client.Init(); // инициализация
В событии клика по кнопке напишем:
Код:
Label1->Caption=bot.client.pid; // выводим PID процесса клиента игры
Теперь запускаем PW и затем нашу программу. Нажмём на кнопку и мы должны будем увидеть PID процесса игры. Можно сравнить его со значением в диспетчере задач и убедиться в правильности. Как видим всё получилось правильно.
Теперь создадим ещё один модуль и сохраним под именем Reader. Он будет отвечать за считывание значений из клиента. В нём опишем и реализуем нашу структуру ридера. Перейдём к редактированию файла Reader.h
Не забываем подключить vcl.h, иначе компилятор скажет "DWORD? не, не слышал". Пока что вот такая маленькая структура. Давайте "прикрутим" её к структуре Client. Добавим в неё ещё одну строчку.
Код:
READER get; // Ридер значений из клиента
В начале не забудем подключить новый созданный нами модуль. Строчку #include <vcl.h> можно убрать, так как она включена в Reader.h, который в свою очередь подключим здесь. Получилось так:
Код:
#include "Reader.h"
#include <Tlhelp32.h>
#ifndef ClientH
#define ClientH
struct CLIENT
{
public:
DWORD pid; // Идентификатор Процесса
READER get; // Ридер значений из клиента
DWORD PIDByProcName(AnsiString ProcessName); // определение pid по имени процесса
void Init(); // инициализация
};
#endif
Также давайте в функции инициализации отдадим ридеру копию значения PID. Он тоже будет его использовать.
Теперь будем учить нашу структуру "читальщика" памяти делать то, для чего она и нужна. Опишем в структуре ридера новую функцию, которая будет считывать четырёхбайтное значение, находящееся по определённому адресу. Обзовём её к примеру Read_32 и в качестве параметра будем давать ей адрес, по которому она считает значение и вернёт нам. Переходим к редактированию модуля Reader в описании структуры добавим обьявление функции
Наконец то мы дошли, туда куда шли, а именно к открытию процесса и работе с ним. Пишем реализацию.
Код:
DWORD READER::Read_32(DWORD addr)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid); // открываем процесс
DWORD value;
ReadProcessMemory(hProcess,(void*)addr,&value,4,0); // считываем значение по заданному адресу
CloseHandle(hProcess); // закроем процесс
return value; // вернём считанное значение
}
ReadProcessMemory считывает байты из памяти указанного процесса в указанное место
1 параметр - hProcess это хэндл процесса, который вернёт функция OpenProcess
2 параметр - это указатель на начало памяти, с которой считываются данные
3 параметр - это указатель на место, куда мы эти данные считываем
4 параметр - количество байт, которое нужно считать
5 параметр - здесь указывается переменная-счётчик, в которую будет помещаться количество байт реально считанных данных(будем считать, что у нас будет считываться без ошибок, потому его мы не указываем и провирять это количество не будем)
Теперь давайте проверим как наш ридер справляется со своей задачей, попробуем считать прямо с нулевого адреса, с адреса начала процесса
в событие ранее добавленной кнопки напишем для теста
Запустим и нажмём на кнопку. Как видим считалось какое то значение которое валяется по адресу самого начала памяти клиента.
Ну считали мы его, только нафиг оно нам нужно? Давайте считывать нужные значения, например HP нашего персонажа. Для этого мы должны знать адрес, по которому это значение лежит. Заглянем в темку РУОФФ Адреса и оффсеты на форуме и посмотрим что там есть. Находим там такую вот вещь
Что же это такое? Это цепочка смещений до структуры персонажа в памяти клиента. Так как адрес начала структуры игрока меняется мы не можем просто указать адрес этой структуры и считать оттуда, но есть такие значения, которые постоянно находятся по одному и тому же адресу, такому как BaseAdress и GameAdress, а так же есть неизменные смещения от начала какой либо структуры до полей этой структуры.
Вот пример, у нас есть 2 структуры, одна является одним из полей другой и создаётся динамически, первая так же в программе создаётся динамически
str1
{
int value1;
int value2;
};
str2
{
int byaka1;
str1* byaka2;
};
str2* buka // обьявим в программе указатель buka на структуру типа str2 и далее динамически создадим
При динамическом выделении памяти по указалелю byaka2 на структуру типа str1, адрес по которому эта память выделится не будет всегда один и тот же, но мы сможем узнать этот адрес, посмотрев значение указателя byaka2. Он и будет содержать значение адреса. Итак допустим в нашей программе структура str2 обьявлена как
str2* buka;
Тоесть тоже создаётся динамически, НО указатель на эту структуру всегда на одном и том же месте от начала прогаммы, допустим по адресу BaseAdress. Допустим в нашей прграмме этот BaseAdress равен 0x8. Это значит что указатель на динамическую структуру buka всегда находится по адресу со смещением от начала программы на 0x8. Считываем оттуда значение(а тут лежит buka - указатель на начало структуры типа str2, по сути её адрес), получаем адрес структуры. В начале структуры лежит значение int byaka1, занимающее 4 байта, а дальше лежит указатель на ещё одну динамическую структуру byaka2, следовательно цепочка оффсетов будет выглядеть так - BaseAddres + 0x4. В этой динамической структуре в свою очередь находится обычное значение int value2, судя по структуре оно находится на 4 байта дальше от начала структуры(а в начале лежит int value1 которое занимает эти 4 байта). Следовательно чтобы добраться до значения value2 нужно пройтись по цепочке смещений BaseAdress + 0x4 + 0x4.
То есть считав значение по BaseAdress и прибавив к нему определённое(тоже неизменное) смещение, мы получим адрес начала какой либо структуры в памяти.
Допустим по адресу BaseAdress у нас лежит значение 0x111, мы считываем его, прибавляем следующее значение из цепочки 0x1C, получаем 0x111+0x1C=12D. Считываем значение по этому адресу, там лежит к примеру 0x222. К этому значению прибавляем следующее смещение из цепочки 0x34, 0x222 + 0x34 = 0x256. Теперь если считать по этому адресу значение, это значение будет представлять из себя адрес начала структура персонажа. Считаем и получим к примеру 0x333, это будет найденный адрес начала структуры перса. +494 - это смещение от начала этой структуры до того места, где лежит HP. Итак если взять адрес начала структуры перса (в нашем примере 0x333) и прибавить к нему смещение до ХП получим 0x333 + 0x494, получим уже адрес не начала структуры а места где в структуре лежит ХП. Можем взять его по этому адресу и считать.
Так как в структуре персонажа находится много значений, для каждого своё смещение, разделим задачу на 2 функции, первая будет возвращать адрес начала структуры перса. а вторая именно значение ХП. Обьявим эти 2 функции в нашем ридере. В Reader.h добавим в класс обьявления двух новых функций
Код:
DWORD PersStruct(); // возвращает адрес начала структуры перса
int myHP(); // возвращает значение ХП
Сделаем ещё одно короткое отступление. Для всех оффсетов давайте сделаем отдельный модуль, где их и укажем. Позже можно будет их вынести в файл и программно считывать оттуда. Это на случай, если после обновления клиента оффсеты поменяются, тогда не предётся перекомпилировать прогу а просто в файле указа ть новые значения.
Итак, опять же создадим новый модуль с именем Offsets и объявим в Offsets.h нужные значения.
Код:
#define BA 0xA571E0 // base adress
#define D_GA 0x1C // смещение до GA
#define PERS_STRUCT 0x34
#define MY_HP 0x494
подключим его в файле Reader.h и получим следующее
запустим PW зайдём за какого либо персонажа в игру, запустим нашу прогу, нажмём кнопку и проверим считанные значения.
Как видим всё считывается верно.
Теперь сделаем считывание данных какого-нибудь моба.
Всё по аналогии, но не совсем. Идём в тему с оффсетами и видим целых 2 цепочки смещений
BA +0x1C +0x1C +0x24 +0x14 Count, dword /Количество/
BA +0x1C +0x1C +0x24 +0x18 +(i*0x4) +0x4 /i = 0 - 0x300/
Mоб не один, как наш персонаж, их много, и в памяти клиента структуры мобов расположены массивом, причём этих массивов два, упорядоченный и неупорядоченный.
Эти две цепочки смещений и приводят нас к данным этих массивов. Мы будем использовать неупорядоченный. В упорядоченном количество записей меняется так как количество мобов вокруг меняется, и номер одного и того же моба в этом массиве может изменяться со временем. Это для некоторых не очень удобно. Но у несортированного массива своя фишка. Один и тот же моб всегда в нём занимает одно и то же положение по порядку. Массив расчитан на 768(0x300 hex) мобов, но ведь вокруг нет столько, поэтому некоторые места пустуют. Причём существующие мобы расположены не по порядку в нём, а могут быть раскиданны по всему массиву.
Итак берём вторую цепочку смещений, она для несортированного массива мобов.
Прочитав адреса по цепочке BA +0x1C +0x1C +0x24 +0x18 +(i*0x4) +0x4 мы получим адрес структуры моба с номером i. Если вместо адреса структуры мы получили 0, значит моба в этом месте массива нет и место пустует. Напишем в ридере новую функцию, которая будет возвращать адрес структуры моба по номеру(по аналогии с адресом структуры перса, но у перса номера не было.
Добавим в модуль с оффсетами смещения до структуры моба а так же смещения до некоторых параметров моба (BA +0x1C уже добавлены, 0x1C определено у нас как D_GA, это смещение до GameAdress, который кстати тоже всегда в одном месте. (i*0x4) и +0x4 не будем выносить в оффсеты, но вы можете это сделать)
Код:
// Смещения до структуры моба
#define M_D1 0x1C // первое смещение
#define M_D2 0x24 // второе смещение
// структура моба BA + D_GA + M_D1 + M_D2 +
#define M_STRUCT 0x18
// параметры моба M_STRUCT+
#define MOB_X 0x03C
#define MOB_Z 0x040
#define MOB_Y 0x044
#define MOB_TYPE 0x0B4
#define MOB_WID 0x120
#define MOB_DIST 0x284
Оффсеты добавили, возьмемся за функции. Перед классом ридера опишем вспомогательную структуру, которая будет представлять координаты. Перед обьявлением READER напишем
Код:
struct COORDS
{
float x;
float y;
float z;
};
Значения координат редко когда нужны по отдельности(разве что кроме высоты), поэтому будем объединять их в одну структуру. Далее пропишем нужные нам новые функции внутри класса ридера
и приступим к написанию их "внутренностей" в Reader.cpp
Код:
DWORD READER::MobStruct(int nom)
{
DWORD buff;
buff = Read_32(BA);
buff = Read_32(buff+D_GA);
buff = Read_32(buff+M_D1);
buff = Read_32(buff+M_D2);
buff = Read_32(buff+M_STRUCT);
buff = Read_32(buff+nom*0x4);
if (buff!=0) return Read_32(buff+0x4); // если значение не 0, значит этот моб существует, вернём адрес его структуры
return 0; //иначе вернём 0
}
DWORD READER::mobType(int nom)
{
DWORD buff=MobStruct(nom);
if (buff!=0) return Read_32(buff+MOB_TYPE); // если функция вернула не 0, значит моб существует, считываем его тип
return 0; // нет этого моба, вернём 0 вместо типа
}
DWORD READER::mobWID(int nom)
{
DWORD buff=MobStruct(nom);
if (buff!=0) return Read_32(buff+MOB_WID);
return 0;
}
Дистанция и координаты являются дробными значениями(float). Допишем в ридере ещё одну функцию Read_float по аналогии с Read_32
Вообще можно было воспользоваться Read_32 и возвращаемое значение DWORD просто преобразовать в float, но всё же напишем отдельную функцию для этого.
Стоит сообщить о том, что NPC, чужие петы и свой вызванный пет тоже расположенны в этом же массиве и считаются "мобом" но у них значение типа разное.
Давайте инициализацию перенесём в событие создания окна, а вывод в Label зделаем внутри таймета с интервалом 200мс(чтобы не тыркать по кнопке), кнопку наверное можно удалить, если что добавим снова))
Код:
void __fastcall TForm1::FormCreate(TObject *Sender)
{
bot.client.Init();
Timer1->Enabled=true; // таймер изначально отключен. включается после инициализации
}
Теперь разместим на форме компонент Timer укажем ему интервал 200(по умолчанию отключим его, включим после инициализации) и в событии напишем поиск ближайшего моба и вывод его координат в добавленные ранее три компонента Label
Код:
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
int nom=-1; // номер ближайшего моба в массиве
float Dist=100000; // дистанция для сравнения, изначально установим в очень большую
for (int i=0; i<768; i++) // пройдёмся по всему списку мобов в клиенте, i - номер моба в списке
{
if ( bot.client.get.mobWID(i)!=0) // если место в списке занято мобом
{
if ( bot.client.get.mobType(i)==6) // если это действительно моб (6 - моб, 7 - NPC, 9 - пет)
{
if ( bot.client.get.mobDist(i)<Dist) // если дистанция меньше чем у предыдущего проверенного
{
Dist=bot.client.get.mobDist(i); // запомним дистанцию для следующего сравнения в списке
nom=i; // запомним номер моба в списке(если более близких мобов не будет, то это и будет номер ближайшего)
}
}
}
}
if (nom!=-1) // -1 будет только в том случае, когда вокруг вообще ни одного моба
{
COORDS crd = bot.client.get.mobCoords(nom);
Label1->Caption="x - " + FloatToStr(crd.x);
Label2->Caption="y - " + FloatToStr(crd.y);
Label3->Caption="z - " + FloatToStr(crd.z);
}
}
Запускаем игру, приходим персонажем в место, где водятся какие-нибудь безобидные мобы встаём рядом с одним из них и запускаем нашу прогу. Что мы видим?
Ерунда какая-то? Координаты совсем не сходятся. На самом деле всё считалось верно. именно в таком виде координаты и хранятся в клиенте а возле миникарты отображаются преобразованными в привычный игроку вид. Формулы, по которым происходит преобразование известны и довольно просты. Давайте для себя напишем вспомогательные функции, которые преобразовывают клиентский вариант координат в игровые и обратно. Давайте поместим их в модуле Bot перед обьявлением класса бота. Вообще в файлах *.h реализация не пишется, ну да ладно, мы же не какие-нибудь Петры Митричевы.
Код:
// из клиентских в игровые
float GameX(float px){return(px+4000)/10;}
float GameY(float py){return(py+5500)/10;}
float GameZ(float pz){return pz/10;}
// из игровых в клиентские
float ProcX(float gx){return(gx*10)-4000;}
float ProcY(float gy){return(gy*10)-5500;}
float ProcZ(float gz){return gz*10;}
В прошлом разделе мы учились считывать различные значения из клиента Perfect World. Но ведь ежу понятно, что на одном считывании данных бота не построишь, хоть целое ведро их насчитывай. Да и в самой игре их прекрасно видно. Нужно научить нашу прогу заставлять клиент выполнять какие-либо действия. Вот мы и подошли к инжектам.
Давайте попробуем понять, что же такое инжект. Инжект - это инъекция кода в сторонний процесс. Нам понадобится это для того, чтобы наш кусок кода работал прямо в клиенте. На примере выделения моба в таргет попробуем разобраться.
Гдето в недрах клиента зашита функция, которая выделяет моба в таргет. Она получает в качестве параметра WID моба. Что такое WID? Как помните из прошлого раздела, один из параметров, значение которого мы считывали, был WID моба. WID - это WorldID, то есть мировой идентификатор. Он есть во множестве структур, в структуре персонажа, мобов, лута и т.п. Представляет из себя число типа DWORD, которое уникально для всех обьектов в Perfect World, нет двух обьёктов, у которых был бы одинаковый WID. Если бросить из инвентаря два раза по монетке и умудриться попасть ими в одни и те же координаты, а затем поднимать, они не поднимутся вместе и считаются двумя разными кучками. Хоть и структуры в памяти у них одинаковые, но WID у них разный.
CatUnderMouse(), которая возвращает WID элемента, по которому мы щёлкнули мышкой
и где-то в программе их вызовы
int cat=CatUnderMouse(); // получаем WID
Kill_a_kitten(cat); // вызываем функцию с параметром
Мы щёлкаем мышкой по мобу, клиент находит этого моба в списке, получает его WID и вызывает функцию выделения его в таргет. WID моба мы получаем програмно, поэтому без всяких щелчков можем передать этот параметр клиентской функции, и произойдёт выделение. Нам нужно вот этот кусок
int cat=CatUnderMouse(); // получаем WID
Kill_a_kitten(cat); // вызываем функцию с параметром
скопировать с клиента, модифицировать и запихать в клиент обратно. Под модификацией я предпологаю просто подставление нужного нам WID
int cat=WID; // получаем WID
Kill_a_kitten(cat); // вызываем функцию с параметром
(Но это как пример, а не реально существующая в клиенте функция.)
Как это сделать? Мы опять же получаем доступ к процессу клиента, выделяем в нём дополнительную память, копируем в эту память нашу копию куска клиентского кода в виде функции и вызываем её. Эта функция в свою очередь начинает выполняться в клиенте и вызывает уже клиентскую родную функцию (в примере - Kill_a_kitten) с нашим параметром, а не вычисленным внутри клиента. Но клиент ведь уже откомпилирован и кода мы посмотреть не можем? Можем, но только если декомпилируем и посмотрим его в командах ассемблера. Затем мы можем найти нужный нам кусок кода, из которого идёт вызов клиентской функции таргета, скопировать его, подставить свой параметр, в программе написать функцию с этим куском кода(ассемблерные вставки в коде программы ещё никто не отменял). Затем скопировать эту нашу функцию в клиент и заставить работать. Теперь следовало бы вам почитать вот эту темку от Dinmaite "Поиск инжектов" или "Наш код в чужом процессе"
Давайте уже вернёмся к нашему боту и оснастим его модулем, который будет инжектить различные функции в клиент
Создадим новый модуль и сохраним его под именем Injector. Опишем в нём структуру INJECTOR
Код:
// Структура инжектора
struct INJECTOR
{
DWORD pid; // Идентификатор Процесса
void* pFunction; // Указатель на память для функций
void* pParams; // Указатель на память для параметров
};
Далее подключим этот модуль к модулю Client и в классе CLIENT добавим новое поле типа INJECTOR
Код:
#include "Reader.h"
#include "Injector.h"
#include <Tlhelp32.h>
#ifndef ClientH
#define ClientH
struct CLIENT
{
public:
DWORD pid; // Идентификатор Процесса
READER get; // Ридер значений
INJECTOR inject; // Инжектор
DWORD PIDByProcName(AnsiString ProcessName); // определение pid по имени процесса
void Init(); // Инициализация
};
#endif
Далее в функции Init добавим ещё одну строчку, в которой мы запишем в структуру инжектора копию значения PID (это значение инжектору для работы тоже нужно)
Теперь давайте определимся вот с чем. Нам нужно будет выделять в клиенте память под наши функции. Можно поступить двумя способами.
1)Перед каждым инжектом выделять память, инжектить нашу функцию а после окончания её работы освобождать память.
2)Выделить достаточное количество памяти в клиенте один раз в начале работы нашей программы и не освобождать её пока бот работает.
Я буду использовать второй вариант, так как с первым у меня были проблемы. Спустя определённое время работы бота память переставала выделяться. Я правда не в курсе почему такое происходило, потому не стал разбираться и пошёл более простым путём. Итак, будем выделять память один раз, значит будем делать это при инициализации в функции Init класса CLIENT. Допишем в неё такие строчки
C первой строчкой всё понятно, мы это уже разбирали.
VirtualAllocEx выделяет память в процессе. Параметры такие
1 параметр - это дескриптор процесса, его мы знаем как получить
2 параметр - желательный стартовый адрес для выделение памяти, мы указываем NULL, тогда функция сама решит, где лучше выделить
3 параметр - размер выделяемой памяти в байтах. 511 байт нам будет больше чем достаточно
4 параметр - тип распределения памяти, указываем MEM_COMMIT, в этом случае память реально выделится и очистится от мусора
5 параметр - тип защиты памяти, указываем PAGE_READWRITE, этим мы говорим, что хотим получить доступ на чтение и запись
Функция возвращает указатель на начало выделенной памяти. Если вернёт NULL, значит выделение не удалось. Мы будем хранить этот указатель в переменной внутри класса INJECT. Далее мы будем использовать этот указатель чтобы копировать нашу функцию в клиент по этому адресу. Так же мы здесь определяем указатель на память выделенную под параметры нашей функции. Замечу здесь вот что. Я выделяю один кусок памяти и использую одну его часть для размещения функции а вторую часть для размещения параметров. Указатель на память для параметров я получаю так - к адресу начала выделенной памяти прибавляю 64 (64*DWORD=64*4=256) тоесть на 256 байт дальше от начала области памяти. Получается что в начале блока памяти будет распологаться функция, а ровно с середины этого блока параметры.
После выделения памяти и получения указателей мы закрываем хэндл процесса.
Добавим в структуру инжектора описание функции, которая будет инжектить в клиент различные функции(по сути сам инжектор и есть)
Код:
BYTE InjectAndExecute(void *Func, void* Params);
получает как параметры указатель на функцию, которую надо инжектить, и указатель на данные параметров для этой функции.
Затем напишем реализацию этой функции в Injector.cpp
Код:
BYTE INJECTOR::InjectAndExecute(void *Func, void* Params)
{
HANDLE hProcThread;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid);
if (!hProcess) return 0; // выйдем из функции если не удалось открыть процесс
WriteProcessMemory(hProcess,pFunction,Func,250,NULL);
WriteProcessMemory(hProcess,pParams,Params,250,NULL);
hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)pFunction,pParams,NULL,NULL);
if(hProcThread==INVALID_HANDLE_VALUE) return 0; // не удалось создать поток
WaitForSingleObject(hProcThread,INFINITE); // ожидаем завершения работы потока
CloseHandle(hProcThread); // закрываем хэндл нашего потока
CloseHandle(hProcess); // закрываем хэндл процесса
return 1; // успешная инъекция и выполнение кода
}
Опять же не забудем в файле Injector.h подключить модуль vcl.h, так же подключим модуль с оффсетами
Код:
#include "Offsets.h"
#include <vcl.h>
#ifndef InjectorH
#define InjectorH
#include <vcl.h>
#ifndef InjectorH
#define InjectorH
// Структура инжектора
struct INJECTOR
{
DWORD pid; // Идентификатор Процесса
void* pFunction; // Указатель на память для функций
void* pParam; // Указатель на память для параметров
BYTE InjectAndExecute(void *Func, void* Params);
};
#endif
Пояснения к новой функции
Сначала открываем процесс для работы, как мы уже делали. PID у нас давно найден и хранится в переменной. Затем мы в ранее уже выделенную память копируем инжектируемую функцию и в следующей строчке параметры для неё WriteProcessMemory(hProcess,pFunction,Func,250,NUL L);
WriteProcessMemory(hProcess,pParams,Params,250,NUL L);
Мы копируем по 250 байт из места, где находится наша функция и так же параметры. Правда не указывая размеры этих данных мы так же скопируем хвостики, из не нужных нам данных, но пока не будем заморачиваться на счёт этого, далее мы модернизируем нашу функцию и избавимся от этого "недостатка".
WriteProcessMemory получает в качестве параметров хэндл процесса, в который будет писать, указатель на область памяти в которую будет писать, указатель на память откуда будет брать данные и размер этих данных.
Функция CreateRemoteThread создает поток, который запускается в виртуальном адресном пространстве другого процесса. HANDLE CreateRemoteThread(
HANDLE hProcess, // дескриптор процесса
LPSECURITY_ATTRIBUTES lpThreadAttributes,// дескриптор защиты (SD)
SIZE_T dwStackSize, // размер начального стека
LPTHREAD_START_ROUTINE lpStartAddress, // функция потока
LPVOID lpParameter, // параметры функции потока
DWORD dwCreationFlags, // параметры создания
LPDWORD lpThreadId // идентификатор потока
);
Этой функцией ма создаём поток в котором будет выполняться наша функция. Любая программа может иметь несколько потоков, которые выполняются параллельно (ведь Винда - это многозадачная операционка). То есть у клиента есть основной поток, мы создаём в нём ещё один поток в котором выполняется наша функция и считается частью клиента. После создания потока код в нём выполнится, выполнит какие то действия и окончив свою работу сообщит о завершении. В строчке WaitForSingleObject(hProcThread,INFINITE);
мы ожидаем завершение созданного нами потока после чего наша программа выполняется дальше.
Итак, инжектор функций у нас есть, но самой функции для инжекта нет. Давайте её создадим. Будем писать функцию таргета моба. Надеюсь указанную тему от Dinmaite вы читали. В файле Injector.cpp напишем нашу функцию. Просто отдельная функция не входящая ни в какую структуру.
Код:
void Target_THREAD(DWORD* WID)
{
DWORD Id = *WID;
__asm
{
MOV EDI, Id // Помещаем WID в регистр EDI
MOV EBX, 0x00630790 // Помещаем адрес клиентской функции таргета
MOV EAX,DWORD PTR DS:[BA] //
PUSH EDI // ; /Arg1
MOV ECX,DWORD PTR DS:[EAX+0x20] // ; |
ADD ECX,0x0EC // ; |
CALL EBX // ; \elementc.00606A70
}
}
Как видим здесь присутствует ассемблерная вставка, которая по сути является копией куска кода клиента, из которого вызывается клиентская функция выделения моба. Но WID подставляем мы сами.
Теперь добавим в структуру INJECTOR функцию, которая будет вызывать функцию-инжектор с нужными параметрами. Обьявим её в структуре инжектора
Здесь функции-инжектору передаётся адрес нашей отдельной функции и адрес параметра для неё(WID). А далее функция-инжектор копирует её и параметры в выделенную в клиенте память и создаёт из неё поток. Наша функция выполнится в клиенте. Проверим работоспособность? Разместим на форме кнопку. При щелчке на ней бедет происходить выделение ближайшего моба. Изменим ранее написанный нами в таймере код, чтобы он получал не координаты а WID ближайшего моба. Из таймера уберём и вставим в кнопку.
Код:
DWORD wid=0; // WID ближайшего моба в массиве
float Dist=100000; // дистанция для сравнения, изначально установим в очень большую
for (int i=0; i<768; i++) // пройдёмся по всему списку мобов в клиенте, i - номер моба в списке
{
if ( bot.client.get.mobWID(i)!=0) // если место в списке занято мобом
{
if ( bot.client.get.mobType(i)==6) // если это действительно моб (6 - моб, 7 - NPC, 9 - пет)
{
if ( bot.client.get.mobDist(i)<Dist) // если дистанция меньше чем у предыдущего проверенного
{
Dist=bot.client.get.mobDist(i); // запомним дистанцию для следующего сравнения в списке
wid=bot.client.get.mobWID(i); // запомним WID моба в списке(если более близких мобов не будет, то это и будет WID ближайшего)
}
}
}
}
bot.client.inject.TargetMob(wid); // инжект выделения моба
Войдём в игру, придём на место где есть какие-нибудь мобы. Запустим нашу программу и нажмём кнопку. Если вылета клиента не случилось, значит мы всё сделали правильно и ближайший моб выделится. У меня всё нормально прошло, и после клика по кнопке моб выделился.
Итак, с инжектами мы разобрались. Давайте немного модернизируем наш инжектор. Сейчас у нас происходит инжект функции таргета, которая вместе с параметром записывается в адресное пространство клиента. В новом потоке она начинает работать берёт параметр находящийся в этом же куске памяти но на 256 байт дальше. Затем этот параметр использует в ассемблерной вставке, где происходит вызов клиентской функции. Давайте всё это упростим. Мы будем инжектить не целую функцию а уже готовый кусок кода с уже проставленными параметрами. Передавать параметр в функцию станет не нужно. Этот кусок кода будет представлять из себя просто массив чисел(ведь любая программа в памяти так и выглядит). Нам нужен всего лиш этот кусок кода
Код:
MOV EDI, Id // Помещаем WID в регистр EDI
MOV EBX, 0x00630790 // Помещаем адрес клиентской функции таргета
MOV EAX,DWORD PTR DS:[BA] //
PUSH EDI // ; /Arg1
MOV ECX,DWORD PTR DS:[EAX+0x20] // ; |
ADD ECX,0x0EC // ; |
CALL EBX // ; \elementc.00606A70
Нам нужно преобразовать его в набор цыфр и поместить в массив. Кстати, сюда нужно добавить последнюю строчку RETN
Эта команда ассемблера производит возврат в место откуда этот кусок кода вызван. Аналогично return в функциях с++.
Для того чтобы узнать как этот кусок кода выглядит в цыфрах будем использовать программу OllyDbg. Я думаю она уже у вас есть.
Запустим Olly. Подключимся к любому процессу. Я для этого запустил паинт и подключился к нему. В меню File выбираем Attach.. и в списке выбираем например mspaint. В окне просмотра кода листаем в самый низ, видим память заполненную нулями. Будем ею пользоваться(надеюсь процесс мы не подвесим)).
Теперь щёлкаем 2 раза по любой из этих строчек, появится окно редактирования кода процесса. Начинаем вводить строки из ассемблерного кода, указанного выше(или просто копировать их туда). После ввода первой сразу появится запрос следующей строчки кода. Вместо неизвестных параметров будем писать просто цыфры. Вместо Id - 11111111, вместо BA - 22222222. Хотя BA нам известно, но давайте вынесем на случай его дальнейшего изменения(хотя после обновлений клиента данный код тоже может измениться и придётся массив составлять заново). Итак что же у нас получилось после ввода
То что выделенно - это и есть наш код так сказать в числах. Выделим наши строчки, нажмём на них правой кнопкой, в появившемся меню выберем Edit> и далее Binary copy.
Так мы скопируем последовательность этих самых чисел. Вставим их пока что куда-нибудь в блокнот, пусть полежат. А мы займёмся функцией
Обьявим в ней массив типа char и размером с количество байт нашего кода. Массив сразу инициируем нашими кодами, точнее не кодами а символами, которые имеют эти коды. Так как числа шестнадцатеричные, то нужно указывать перед ними x а так же символ \ который скажет, что это не число а символ, кодом которого является это число. То есть у символа \xBF - код BF(hex). Так же здесь определим переменную в которой укажем размер массива. Инициируем массив строчкой из наших символов(коды которых и являются этими числами).
char code[28]="\xBF\x11\x11\x11\x11\xBB\x90\x07\x63\x00\xA1\x22 \x22\x22\x22\x57\x8B\x48\x20\x81\xC1\xEC\x00\x00\x 00\xFF\xD3\xC3";
int len=28;
Теперь смотрим на массив и видим числа 11 11 11 11 и 22 22 22 22, которые мы указывали вместо неизвестных параметров. Первый - это WID, второй это BA. Давайте сразу эти числа запишем в массив.
DWORD ba=BA;
memcpy(code+1,&wid,4); // WID в массиве распологается начиная с первой позиции от начала(начало - нулевая позиция)
memcpy(code+11,&ba,4); // а BA начиная с 11ой
Функцию инжектора мы переделаем, и вызов её будет выглядеть так
InjectAndExecute(code,len);
Как параметры передаются указатель на массив нашего кода и его длинна.
Теперь наша функция выделения в таргет выглядит так
Код:
void INJECTOR::TargetMob(DWORD wid)
{
char code[28]="\xBF\x11\x11\x11\x11\xBB\x90\x07\x63\x00\xA1\x22\x22\x22\x22\x57\x8B\x48\x20\x81\xC1\xEC\x00\x00\x00\xFF\xD3\xC3";
DWORD ba=BA;
memcpy(code+1,&wid,4); // WID в массиве распологается начиная с первой позиции от начала(начало - нулевая позиция)
memcpy(code+11,&ba,4); // а BA начиная с 11ой
InjectAndExecute(code,28);
}
Функцию Target_THREAD можно выкинуть, она нам больше не нужна. Мы копируем в клиент не её, а массив кода(её аналог, но попроще).
Теперь переделаем инжектор так, чтобы он инжектил этот массив в клиент без всяких параметров, ведь параметры мы уже прописали прямо в код.
Код:
BYTE INJECTOR::InjectAndExecute(char* code, int len)
{
HANDLE hProcThread;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid);
if (!hProcess) return 0;
WriteProcessMemory(hProcess,pFunction,code,len,NULL);
//WriteProcessMemory(hProcess,pParams,Params,250,NULL); параметры нам больше не нужны, удалим эту строчку
// pParam заменим на NULL, так как праметров у нас теперь нет
hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)pFunction,NULL,NULL,NULL);
if(hProcThread==INVALID_HANDLE_VALUE) return 0;
WaitForSingleObject(hProcThread,INFINITE);
CloseHandle(hProcThread);
CloseHandle(hProcess);
return 1;
}
Давайте запустим нашу программу и попробуем нажать на кнопку на форме. Инжект прошёл успешно и ближайший моб удачно выделился.
Приведу некоторые другие инжекты
Код:
// Использование скилла по ID
void INJECTOR::Skill(DWORD id)
{
char fdata[39]="\x60\x6A\xFF\x6A\x00\x6A\x00\x68\x00\x00\x00\x00\x8B\x0D\x00\x00\x00\x00\x8B\x89\x00\x00\x00\x00\x8B\x89\x00\x00\x00\x00\xB8\x00\x00\x00\x00\xFF\xD0\x61\xC3";
DWORD func=F_SKILL;
DWORD ba=BA;
DWORD dga=D_GA;
DWORD ps=PERS_STRUCT;
memcpy(fdata+8,&id,4);
memcpy(fdata+14,&ba,4);
memcpy(fdata+20,&dga,4);
memcpy(fdata+26,&ps,4);
memcpy(fdata+31,&func,4);
InjectFunction(fdata,39);
}
/
Код:
/ Перемещение в координаты
// самая длинная и страшная функция)))
void INJECTOR::Move(float x, float y, float z, int walkmode)
{
char fdata[117]="\x60\xA1\x00\x00\x00\x00\x8B\xB0\x11\x11\x11\x11\x8B\x8E\x22\x22\x22\x22\x6A\x01\xBB\x33\x33\x33\x33\xFF\xD3\x89\xC7\x8D\x44\xE4\x0C\x50\x68\x44\x44\x44\x44\x89\xF9\xBB\x55\x55\x55\x55\xFF\xD3\x8B\x8E\x66\x66\x66\x66\x6A\x00\x6A\x01\x57\x6A\x01\xBB\x77\x77\x77\x77\xFF\xD3\xA1\x88\x88\x88\x88\x8B\x80\x99\x99\x99\x99\x8B\x80\xAA\xAA\xAA\xAA\x8B\x40\x30\x8B\x48\x04\xB8\xBB\xBB\xBB\xBB\x89\x41\x20\xB8\xCC\xCC\xCC\xCC\x89\x41\x24\xB8\xDD\xDD\xDD\xDD\x89\x41\x28\x61\xC3";
DWORD func1=F_MOVE1;
DWORD func2=F_MOVE2;
DWORD func3=F_MOVE3;
DWORD ga=GA;
DWORD ps=PERS_STRUCT;
DWORD maa=MY_ACTION_ARRAY;
DWORD wmode=1; if (walkmode==0) wmode=0;
memcpy(fdata+2,&ga,4);
memcpy(fdata+8,&ps,4);
memcpy(fdata+14,&maa,4);
memcpy(fdata+21,&func1,4);
memcpy(fdata+35,&wmode,4);
memcpy(fdata+42,&func2,4);
memcpy(fdata+50,&maa,4);
memcpy(fdata+62,&func3,4);
memcpy(fdata+69,&ga,4);
memcpy(fdata+75,&ps,4);
memcpy(fdata+81,&maa,4);
memcpy(fdata+92,&x,4);
memcpy(fdata+100,&z,4);
memcpy(fdata+108,&y,4);
InjectFunction(fdata,117);
}
В прошлом разделе мы разбирались с инжектами. Теперь давайте разберёмся с пакетами.
Сервер Perfect World взаимодействует с клиентом посредством пакетов, получает и отсылает их. Пакет это последовательность данных, получив которые сервер распознаёт их и в зависимости от того, что это за данные, делает какие то действия у себя. Вспомним к примеру нашу клиентскую функцию выделения моба. В результате её работы, в клиенте формируется пакет(последовательность чисел) определённой длинны, затем вызывается функция, которая этот пакет отправляет серверу. Сервер его получает, расшифровывает и видит что это пакет выделения моба с определённым WID. Выделяет его, и отсылает клиенту ответ, мол выделил. Клиент получает этот пакет и отображает выделение моба(ну и не только отображает). Так же как и различные внутренние функции клиента(как например таргет моба) мы можем использовать и функцию отправки пакета. Нужно передать ей как параметры адрес начала готового пакета и его длинну. Пакет мы готовим сами а не клиент. Итак, мы скопируем с клиента кусок ассемблерного кода, который вызывает клиентскую функцию отправки пакета. Будем подставлять в него свои параметры (такие как адрес готового пакета в памяти и его длинну) а потом будем его инжектить в клиент. Таким образом этот кусок кода, работая в клиенте, заставит клиентскую функцию отправки пакета отправить наш пакет серверу. Получается так что при инжекте функций действий пакет составлял сам клиент и отправлял его серверу. А при инжекте функции посылки пакета, пакет формируется нами пропуская использование клиентских функций, выполняющих какие то действия. В классе инжектора опишем новую функцию, которая будет отвечать за инжект кода, вызывающего клиентскую функцию отправки пакета с нашими параметрами. Напишем её реализацию. Не буду размусоливать и сразу укажу тут массив байт, который представляет собой кусок кода из клиента, вызывающего функцию отправки пакета.
Код:
// Инъекция и отправка пакета
BYTE INJECTOR::SendPacket(PACKET* pack)
{
HANDLE hProcThread;
char fdata[29]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33\xFF\xD0\x61\xC3";
int lenfunc=29;
DWORD func=F_SEND_PACKET;
DWORD ba=BA;
DWORD len=pack->len;
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid);
if (!hProcess) return 0;
WriteProcessMemory(hProcess,pParams,pack->Bytes,len,NULL); // инжектим данные пакета
DWORD addr=DWORD(pParams); // возьмём адрес расположения данных нашего пакета
memcpy(fdata+3,&ba,4);
memcpy(fdata+11,&len,4);
memcpy(fdata+16,&addr,4); // запишем адрес расположения пакета прямо в массив кода
memcpy(fdata+21,&func,4);
WriteProcessMemory(hProcess,pFunction,fdata,lenfunc,NULL); // инжектим наш код
hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)pFunction,NULL,NULL,NULL);
if(hProcThread==INVALID_HANDLE_VALUE) // не удалось создать поток
{
CloseHandle(hProcess);
return 0;
}
WaitForSingleObject(hProcThread, INFINITE); // ожидаем завершения работы потока
CloseHandle(hProcThread); // освобождаем
CloseHandle(hProcess);
return 1; // успешная инъекция и выполнение кода
}
F_SEND_PACKET - адрес функции посылки пакета, адрес этот известен и указан в теме с Оффсетами.
Здесь мы инжектим кроме самой функции ещё и данные. Данные эти и будут являться пакетом. Опишем вспомогательную структуру пакета в начале модуля перед описанием структуры инжектора
Код:
// Структура пакета
struct PACKET
{
int len; // длина
BYTE Bytes[60]; // данные пакета
};
Функция SendPacket(PACKET* pack) получает указатель на структуру пакета, который мы подготовили для отправки.
Давайте перепишем нашу функцию выделения моба, чтобы выделение происходило отправкой пакета серверу.
Функция выглядела вот так
void INJECTOR::TargetMob(DWORD wid)
{
PACKET pack; // структура пакета
pack.len=6; // длина данных пакета
char Packet[6] = "\x02\x00\x01\x00\x00\x00"; // массив данных пакета
memcpy(pack.Bytes,Packet,pack.len); // скопируем в структуру пакета данные массива
memcpy(pack.Bytes+2,&wid,4); // последние четыре байта - WID выделяемого моба
SendPacket(&pack); // инжектим
}
Запускаем нашу программу, жмём на кнопочку и смотрим - ближайший моб выделился. Значит всё прошло хорошо, и пакет удачно отправился серверу.
На этом о пакетах всё. Укажу ещё некоторые функции, которые могут понадобиться для работы бота.
// Использование скилла по ID. Нужно быть на расстоянии действия скилла
void INJECTOR::Skill2(DWORD skillid, DWORD mobwid)
{
PACKET pack;
pack.len=12;
char Packet[12] = "\x29\x00\x2B\x01\x00\x00\x00\x01\xF9\x16\x10\x80";
memcpy(pack.Bytes,Packet,pack.len);
memcpy(pack.Bytes+2,&skillid,4);
memcpy(pack.Bytes+8,&mobwid,4);
SendPacket(&pack);
}
Re: Как написать бота с нуля [Borland C++ Builder 6]
Прочитал РАЗДЕЛ 1.
В общем виде всё, казалось бы, довольно неплохо, но есть к чему придраться. А именно..
1. Слово "адрес" в английском пишется с двумя буквами d; "-то", "-либо", "-нибудь" и "-таки" пишутся с дефисом; слово "аккуратно" пишется не через "о";
В некоторых предложениях отсутствуют точки.
Ладно, на самом деле это мелочи.
2. Некоторые примеры кода не заключены в теги "CODE".
3. Функция PIDByProcName закрывает хендл "pHandle" лишь в случае успешного нахождения процесса; отсутствует проверка верхнего регистра имени процесса (а это действительно важно, ведь у некоторых процесс называется "ElementClient.exe", у некоторых - "elementclient.EXE", у кого-то - "ELEMENTCLIENT.EXE", не говоря уже о таком имени как "ELEME~1.EXE".
4. Ваша функция:
Код:
DWORD READER::MobStruct(int nom)
{
DWORD buff;
buff = Read_32((DWORD*)BA);
buff = Read_32((DWORD*)(buff+D_GA));
buff = Read_32((DWORD*)(buff+M_D1));
buff = Read_32((DWORD*)(buff+M_D2));
buff = Read_32((DWORD*)(buff+M_STRUCT));
buff = Read_32((DWORD*)(buff+nom*0x4));
if (buff!=0) return Read_32((DWORD*)(buff+0x4)); // если значение не 0, значит этот моб существует, вернём адрес его структуры
return 0; //иначе вернём 0
}
Вы ведь понимаете, что она открывает и закрывает хендл процесса 7 раз подряд?
5. Немного нарушена логика (последовательность) статьи, сложновато/бегло объяснены некоторые моменты (классы и структуры / пример со структурами str1 и str2).
На этом вроде всё.
Рекомендую обратить внимание на сию критику.
Чуть позже продолжу чтение.)
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от BritishColonist
1. Слово "адрес" в английском пишется с двумя буквами d; "-то", "-либо", "-нибудь" и "-таки" пишутся с дефисом; слово "аккуратно" пишется не через "о";
В некоторых предложениях отсутствуют точки.
Постараюсь исправить, местами бегло писал.
Цитата:
Сообщение от BritishColonist
2. Некоторые примеры кода не заключены в теги "CODE".
Это в основном отдельные строчки, которые не представляют из себя логически целостного кода, не разделённого комментариями. Я такие строки цветом выделил.
Цитата:
Сообщение от BritishColonist
3. Функция PIDByProcName закрывает хендл "pHandle" лишь в случае успешного нахождения процесса; отсутствует проверка верхнего регистра имени процесса (а это действительно важно, ведь у некоторых процесс называется "ElementClient.exe", у некоторых - "elementclient.EXE", у кого-то - "ELEMENTCLIENT.EXE", не говоря уже о таком имени как "ELEME~1.EXE".
попробую исправить, раньше я не таким способом делал, осталось вспомнить КАК я делал раньше))
Цитата:
Сообщение от BritishColonist
Вы ведь понимаете, что она открывает и закрывает хендл процесса 7 раз подряд?
Да, но ради простоты можно чем-нибудь и пожнртвовать..
Цитата:
Сообщение от BritishColonist
Немного нарушена логика (последовательность) статьи, сложновато/бегло объяснены некоторые моменты (классы и структуры / пример со структурами str1 и str2).
Тут я исправит скорее всего не смогу, так как писал паралельно с созданием программы, и порядок действий сам собой напрашивался..
Re: Как написать бота с нуля [Borland C++ Builder 6]
Что ж, осилил вторую часть :D
Цитата:
Сообщение от dwa83
Указатель на память для параметров я получаю так - к адресу начала выделенной памяти прибавляю 64 (64*DWORD=64*4=256) тоесть на 256 байт дальше от начала области памяти.
Зачем это тут умножение на размер DWORD? 64 в данном случае - количество байтов, а не переменных. Поэтому смело прибавляйте 256. Дальше по статье идёт пример, где записывается функция и параметры к ней по адресам +0 и +256 (якобы), а на самом деле - по +0 и +64, т.е. Ваша 'самая длинная и страшная' функция бега по координатам, вероятно, будет наполовину перезаписана параметрами, что вызовет краш клиента.
И инжектор "строчек кода" неудобен тем, что нельзя будет нормально отредактировать внедряемый код.
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от BritishColonist
данном случае - количество байтов
Нет, всё верно, проверенно.
У нас указатель типа DWORD. Если к нему прибавить 1, то на самом деле указатель сместится на размер DWORD. Видимо такая фишка у указателей в отличие от обычных чисел.
Или такой пример:
WORD mass[30];
Если использовать mass без скобок, он будет восприниматься как указатель на нулевой элемент массива. Но если указать mass+1, это воспринимается как указатель на 1 элемент массива, а его адрес смещён от нулевого на WORD. Поэтому фактически число, представляющее адрес, увеличится не на 1 а на 2 байта.
Даже проверил, а то покоя не даст ))
Добавлено через 27 минут
Цитата:
Сообщение от BritishColonist
И инжектор "строчек кода" неудобен тем, что нельзя будет нормально отредактировать внедряемый код.
Мне кажется, что редактировать придётся только в случае глобального изменения клиента после обновления(в случае если инжекты напроч изменятся). После последнего обновления вроде ничего страшного не произошло, адрес изменился, но он вынесен в оффсеты и вписывается в эту строчку отдельно.
________________
╔═╗
║ ˑ ˑ ╬ ╬
╚═╝
Последний раз редактировалось dwa83; 01.05.2012 в 23:24.
Причина: Добавлено сообщение
Re: Как написать бота с нуля [Borland C++ Builder 6]
Здравствуй dwa83! Я новичок в программирования. ТЫ можешь скинуть Код. В файлах Unit1, Bot, Client.
Зачем это? Это для того что бы можно было понять куда прописывать коды. Нужно ли прописывать коды находящегося в не Цитаты. Ну например hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); Нужно ли это вставлять в файлы. Если нужно, то куда. Как в этом месте понять: HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа - сюда что то нужно писать, или это обьяснение
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
Я очень прошу более подробнее написать\исправить статью.Пожалуйста. Просто не понятно что куда вставлять?
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от Foreworld
Просто не понятно что куда вставлять?
Может быть тогда стоит начать со знакомства с языком программирования и средой разработки ?
Ибо вот эта цитата подсказывает, что бота вы "с наскока" не напишите.
Re: Как написать бота с нуля [Borland C++ Builder 6]
Нет, я не которое понимаю куда вставлять но. В статье присутствует такой код как hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); - Это в обще нужно куда то вставлять.
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от Foreworld
Нет, я не которое понимаю куда вставлять но. В статье присутствует такой код как hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); - Это в обще нужно куда то вставлять.
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю
[Ссылки могут видеть только зарегистрированные пользователи. ]
MEGAFACEPALM
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от Foreworld
Таких как ты дофига. А как объяснить не фига!
Таких, как ты, ещё больше. Я бы сказал 95%.
Объяснить нифига? Сперва изучи хоть что-нибудь, а потом задавай вопросы, чтобы форумчанам было хотя бы понятно, чем тебе вообще можно помочь. С нуля тебя никто учить не собирается.
Кстати, в соседних темах (впрочем, как и в этой) есть много-много материала, с которым можно ознакомиться, в том числе и статьи для новичков. Используй поиск, в конце концов, пробегись по разделу вручную.
P.S. Ах да, первое, что тебе стоит получше выучить - родной язык. Да, такая вот горькая правда.
________________
Принимаю реквесты на статьи, программы. Всё будет запилено в лучшем виде :3
Делфи, правда, но не суть. Читаю уровни окружающих игроков. Вместо этого в А записывается непонятное семизначное число. Происходит это столько раз, сколько персов вокруг меня, то есть где-то в конце напортачил, видимо.
Re: Как написать бота с нуля [Borland C++ Builder 6]
Цитата:
Сообщение от Foreworld
Здравствуй dwa83! Я новичок в программирования. ТЫ можешь скинуть Код. В файлах Unit1, Bot, Client.
Зачем это? Это для того что бы можно было понять куда прописывать коды. Нужно ли прописывать коды находящегося в не Цитаты. Ну например hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); Нужно ли это вставлять в файлы. Если нужно, то куда. Как в этом месте понять: HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа - сюда что то нужно писать, или это обьяснение
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
Я очень прошу более подробнее написать\исправить статью.Пожалуйста. Просто не понятно что куда вставлять?
Всё это указанно предварительно, как будет выглядеть в программе, а дальше уже в коде используется, напримнр в функции
Цитата:
Сообщение от dwa83
DWORD READER::Read_32(DWORD addr) { HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid); // открываем процесс DWORD value; ReadProcessMemory(hProcess,addr,&value,4,0); // считываем значение по заданному адресу CloseHandle(hProcess); // закроем процесс return value; // вернём считанное значение }
. То есть, если не сказанно, что записать эту строчку в заголовочный файл или в файл реализации какого-либо модуля, значит делать этого пока что не нужно
Добавлено через 40 минут
Цитата:
Сообщение от dwa83
можешь скинуть Код. В файлах Unit1, Bot, Client.
ссылка на готовый проект в конце четвёртго раздела
sizeof(A) лучше просто записать как 4(не будет лишнего вычисления размера, ведь значения адресов всегда 4 байта)
И ещё, значения некоторых параметров сервер посылает клиенту только во время "плотного" взаимодействия с этим игроком, например во время его выделения, а пока не выделишь, эти значения считаются пока не нужными и не посылаются. Чтобы считать параметры нескольких персонажей. нужно выделить первого, считать параметры, выделить второго и тд.
Добавлено через 59 минут
Цитата:
Сообщение от Foreworld
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю
Re: Как написать бота с нуля [Borland C++ Builder 6]
A типа Dword.
За подсказки спасибо, но это не объясняет результат. По-моему я либо что-то сделал не так, либо чего-то ещё не сделал ) Ведь в А идут значения всех окружающих меня персов, точно, вот только вместо уровня персонажа, там что-то вроде 6756495.