PDA

Просмотр полной версии : [Статья] Как написать бота с нуля [Borland C++ Builder 6]


dwa83
01.05.2012, 21:02
Попробую собрать воедино разбросанную по форуму информацию и применить её к созданию конкретной простенькой программы-бота. Я не говорю в этой теме как НУЖНО делать, а всего лишь показываю, как МОЖНО сделать. Расчитана данная статья на программистов, использующих среду 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 процесса по его имени.

DWORD PIDByProcName(AnsiString ProcessName)
{
PROCESSENTRY32 ProcessEntry;
HANDLE pHandle;
DWORD pid;
pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ProcessEntry.dwSize = sizeof(ProcessEntry);
pid=0;
bool loop=Process32First(pHandle, &ProcessEntry);
while (loop)
{
AnsiString nm=ProcessEntry.szExeFile;
if (nm==ProcessName)
{
pid = ProcessEntry.th32ProcessID;
CloseHandle(pHandle);
break;
}
loop=Process32Next(pHandle, &ProcessEntry);
}
return 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.


#include "Client.h"

DWORD CLIENT::PIDByProcName(AnsiString ProcessName)
{
PROCESSENTRY32 ProcessEntry;
HANDLE pHandle;
DWORD pid;
pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ProcessEntry.dwSize = sizeof(ProcessEntry);
pid=0;
bool loop=Process32First(pHandle, &ProcessEntry);
while (loop)
{
AnsiString nm=ProcessEntry.szExeFile;
if (nm==ProcessName)
{
pid = ProcessEntry.th32ProcessID;
CloseHandle(pHandle);
break;
}
loop=Process32Next(pHandle, &ProcessEntry);
}
return pid;
}

Теперь переходим к модулю 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 и добавим нашу функцию.

void CLIENT::Init()
{
pid=PIDByProcName("elementclient.exe");
}

На этом этапе можно проверить что у нас получилось. В главном модуле Unit1.cpp подключим модуль Bot, и обьявим экземпляр нашего бота.

#include "Bot.h"

BOT bot;

Затем кинем на форму два компонента, Button и Label. В событии создания формы вызовем функцию инициализации.

bot.client.Init(); // инициализация

В событии клика по кнопке напишем:

Label1->Caption=bot.client.pid; // выводим PID процесса клиента игры

Теперь запускаем PW и затем нашу программу. Нажмём на кнопку и мы должны будем увидеть PID процесса игры. Можно сравнить его со значением в диспетчере задач и убедиться в правильности. Как видим всё получилось правильно.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Теперь создадим ещё один модуль и сохраним под именем Reader. Он будет отвечать за считывание значений из клиента. В нём опишем и реализуем нашу структуру ридера. Перейдём к редактированию файла Reader.h

#include <vcl.h>

#ifndef ReaderH
#define ReaderH

struct READER
{
DWORD pid; // PID
};

#endif

Не забываем подключить 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. Он тоже будет его использовать.

void CLIENT::Init()
{
pid=PIDByProcName("elementclient.exe");
get.pid=pid;
}

Теперь будем учить нашу структуру "читальщика" памяти делать то, для чего она и нужна. Опишем в структуре ридера новую функцию, которая будет считывать четырёхбайтное значение, находящееся по определённому адресу. Обзовём её к примеру Read_32 и в качестве параметра будем давать ей адрес, по которому она считает значение и вернёт нам. Переходим к редактированию модуля Reader в описании структуры добавим обьявление функции

struct READER
{
DWORD pid; // PID
DWORD Read_32(DWORD addr);
};

Наконец то мы дошли, туда куда шли, а именно к открытию процесса и работе с ним. Пишем реализацию.

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 параметр - здесь указывается переменная-счётчик, в которую будет помещаться количество байт реально считанных данных(будем считать, что у нас будет считываться без ошибок, потому его мы не указываем и провирять это количество не будем)

Теперь давайте проверим как наш ридер справляется со своей задачей, попробуем считать прямо с нулевого адреса, с адреса начала процесса

в событие ранее добавленной кнопки напишем для теста

bot.client.Init();
Label1->Caption=bot.client.get.Read_32(0);

Запустим и нажмём на кнопку. Как видим считалось какое то значение которое валяется по адресу самого начала памяти клиента.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Ну считали мы его, только нафиг оно нам нужно? Давайте считывать нужные значения, например HP нашего персонажа. Для этого мы должны знать адрес, по которому это значение лежит. Заглянем в темку РУОФФ Адреса и оффсеты ([Ссылки могут видеть только зарегистрированные и активированные пользователи]) на форуме и посмотрим что там есть. Находим там такую вот вещь

Структура перса (HostPlayer Struct)
BaseAdress +0x1C +0x34
+494 HP, dword /Жизненная сила/

Что же это такое? Это цепочка смещений до структуры персонажа в памяти клиента. Так как адрес начала структуры игрока меняется мы не можем просто указать адрес этой структуры и считать оттуда, но есть такие значения, которые постоянно находятся по одному и тому же адресу, такому как 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 и получим следующее

//---------------------------------------------------------------------------
#include <vcl.h>
#include "Offsets.h"

#ifndef ReaderH
#define ReaderH

struct READER
{
DWORD pid; // PID
DWORD Read_32(DWORD addr);
DWORD PersStruct();
int myHP();
};

#endif


Итак оффсеты видны в модуле ридера, продолжим написание двух новых функций. Мы их уже обьявили, теперь добавим их тела в Reader.cpp


DWORD READER::PersStruct()
{
DWORD buff;
buff = Read_32(BA);
buff = Read_32(buff+D_GA);
return Read_32(buff+PERS_STRUCT);
}

int READER::myHP()
{
return Read_32(PersStruct()+MY_HP);
}


Ну вот и всё, значения можно считывать. Для полноты давайте объявим и напишем ещё пару функций, например получения уровня и текущего значения MP.

Добавим в модуль с оффсетами 2 новых

#define MY_MP 0x498
#define MY_LEVEL 0x48C

а в модуль ридера 2 новые функции

int READER::myMP()
{
return Read_32(PersStruct()+MY_MP);
}

int READER::myLevel()
{
return Read_32(PersStruct()+MY_LEVEL);
}


Посмотрим на результат нашей работы
Разместим ещё 2 элемента Label на нашей форме а в обработчике кнопки перепишем

bot.client.Init();
Label1->Caption=bot.client.get.myHP();
Label2->Caption=bot.client.get.myMP();
Label3->Caption=bot.client.get.myLevel();

запустим 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;
};

Значения координат редко когда нужны по отдельности(разве что кроме высоты), поэтому будем объединять их в одну структуру. Далее пропишем нужные нам новые функции внутри класса ридера

DWORD MobStruct(int nom);
DWORD mobType(int nom);
DWORD mobWID(int nom);
float mobDist(int nom);
COORDS mobCoords(int nom);

и приступим к написанию их "внутренностей" в 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, но всё же напишем отдельную функцию для этого.

обьявим её в классе, а в реализации напишем

float READER::Read_float(DWORD addr)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
float value;
ReadProcessMemory(hProcess,(void*)addr,&value,4,0);
CloseHandle(hProcess);
return value;
}

Продолжим писать наши функции считывания данных конкретного моба

float READER::mobDist(int nom)
{
DWORD buff=MobStruct(nom);
if (buff!=0) return Read_float(buff+MOB_DIST);
return 0;
}

COORDS READER::mobCoords(int nom)
{
COORDS value; value.x=0; value.y=0; value.z=0;
DWORD buff=MobStruct(nom);
if (buff!=0)
{
value.x = Read_float(buff+MOB_X);
value.y = Read_float(buff+MOB_Y);
value.z = Read_float(buff+MOB_Z);
}
return value;
}

Стоит сообщить о том, что 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;}


Немного перепишем в таймере вывод в Label

Label1->Caption="x - " + FloatToStr(GameX(crd.x));
Label2->Caption="y - " + FloatToStr(GameY(crd.y));
Label3->Caption="z - " + FloatToStr(GameZ(crd.z));

Запускаем, проверяем.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Ура, всё сходится. Ну почти(так как мы стоим не точно по координатам моба, да и клиент округляет координаты).

Думаю на этом раздел связанный со считыванием данных можно закончить.




В прошлом разделе мы учились считывать различные значения из клиента Perfect World. Но ведь ежу понятно, что на одном считывании данных бота не построишь, хоть целое ведро их насчитывай. Да и в самой игре их прекрасно видно. Нужно научить нашу прогу заставлять клиент выполнять какие-либо действия. Вот мы и подошли к инжектам.

Давайте попробуем понять, что же такое инжект. Инжект - это инъекция кода в сторонний процесс. Нам понадобится это для того, чтобы наш кусок кода работал прямо в клиенте. На примере выделения моба в таргет попробуем разобраться.
Гдето в недрах клиента зашита функция, которая выделяет моба в таргет. Она получает в качестве параметра WID моба. Что такое WID? Как помните из прошлого раздела, один из параметров, значение которого мы считывали, был WID моба. WID - это WorldID, то есть мировой идентификатор. Он есть во множестве структур, в структуре персонажа, мобов, лута и т.п. Представляет из себя число типа DWORD, которое уникально для всех обьектов в Perfect World, нет двух обьёктов, у которых был бы одинаковый WID. Если бросить из инвентаря два раза по монетке и умудриться попасть ими в одни и те же координаты, а затем поднимать, они не поднимутся вместе и считаются двумя разными кучками. Хоть и структуры в памяти у них одинаковые, но WID у них разный.

Пример: Допустим в клиенте есть функция

void Kill_a_kitten(int kitty)
{
Terminate(kitty);
}

а так же

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 (это значение инжектору для работы тоже нужно)

void CLIENT::Init()
{
pid=PIDByProcName("elementclient.exe");
get.pid=pid;
inject.pid=pid;
}

Теперь давайте определимся вот с чем. Нам нужно будет выделять в клиенте память под наши функции. Можно поступить двумя способами.
1)Перед каждым инжектом выделять память, инжектить нашу функцию а после окончания её работы освобождать память.
2)Выделить достаточное количество памяти в клиенте один раз в начале работы нашей программы и не освобождать её пока бот работает.

Я буду использовать второй вариант, так как с первым у меня были проблемы. Спустя определённое время работы бота память переставала выделяться. Я правда не в курсе почему такое происходило, потому не стал разбираться и пошёл более простым путём. Итак, будем выделять память один раз, значит будем делать это при инициализации в функции Init класса CLIENT. Допишем в неё такие строчки

HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
DWORD* pfunc = (DWORD*)VirtualAllocEx(hProcess,NULL,511,MEM_COMMI T,PAGE_READWRITE);
inject.pFunction = pfunc;
inject.pParams = pfunc+64;
CloseHandle(hProcess);

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,NUL L);
WriteProcessMemory(hProcess,pParams,Params,250,NUL L);

hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_ST ART_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 функцию, которая будет вызывать функцию-инжектор с нужными параметрами. Обьявим её в структуре инжектора

void TargetMob(DWORD wid);

и напишем реализацию

void INJECTOR::TargetMob(DWORD wid)
{
InjectAndExecute(&Target_THREAD,&wid);
}

Здесь функции-инжектору передаётся адрес нашей отдельной функции и адрес параметра для неё(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.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Так мы скопируем последовательность этих самых чисел. Вставим их пока что куда-нибудь в блокнот, пусть полежат. А мы займёмся функцией
void INJECTOR::TargetMob(DWORD wid)
{
InjectAndExecute(&Target_THREAD,&wid);
}

Обьявим в ней массив типа char и размером с количество байт нашего кода. Массив сразу инициируем нашими кодами, точнее не кодами а символами, которые имеют эти коды. Так как числа шестнадцатеричные, то нужно указывать перед ними x а так же символ \ который скажет, что это не число а символ, кодом которого является это число. То есть у символа \xBF - код BF(hex). Так же здесь определим переменную в которой укажем размер массива. Инициируем массив строчкой из наших символов(коды которых и являются этими числами).

char code[28]="\xBF\x11\x11\x11\x11\xBB\x90\x07\x63\x00\xA1\x22\x 22\x22\x22\x57\x8B\x48\x20\x81\xC1\xEC\x00\x00\x00 \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\x 22\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,NUL L);
//WriteProcessMemory(hProcess,pParams,Params,250,NUL L); параметры нам больше не нужны, удалим эту строчку

// pParam заменим на NULL, так как праметров у нас теперь нет
hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_ST ART_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\x 8B\x0D\x00\x00\x00\x00\x8B\x89\x00\x00\x00\x00\x8B \x89\x00\x00\x00\x00\xB8\x00\x00\x00\x00\xFF\xD0\x 61\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\x 8B\x8E\x22\x22\x22\x22\x6A\x01\xBB\x33\x33\x33\x33 \xFF\xD3\x89\xC7\x8D\x44\xE4\x0C\x50\x68\x44\x44\x 44\x44\x89\xF9\xBB\x55\x55\x55\x55\xFF\xD3\x8B\x8E \x66\x66\x66\x66\x6A\x00\x6A\x01\x57\x6A\x01\xBB\x 77\x77\x77\x77\xFF\xD3\xA1\x88\x88\x88\x88\x8B\x80 \x99\x99\x99\x99\x8B\x80\xAA\xAA\xAA\xAA\x8B\x40\x 30\x8B\x48\x04\xB8\xBB\xBB\xBB\xBB\x89\x41\x20\xB8 \xCC\xCC\xCC\xCC\x89\x41\x24\xB8\xDD\xDD\xDD\xDD\x 89\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);
}


// Подбор лута/шахты по WID
void INJECTOR::GetLoot(DWORD wid, BYTE type)
{
char fdata[29]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x89\x11\x11\x11\x 11\x6A\x22\x68\x33\x33\x33\x33\xBB\x44\x44\x44\x44 \xFF\xD3\x61\xC3";
DWORD func=F_GET_LOOT;
DWORD ga=GA;
DWORD prs=PERS_STRUCT;
BYTE typ=0; if (type==2) typ=1;
memcpy(fdata+3,&ga,4);
memcpy(fdata+9,&prs,4);
fdata[14]=typ;
memcpy(fdata+16,&wid,4);
memcpy(fdata+21,&func,4);
InjectFunction(fdata,29);
}


// Открытие диалога с НПС по WID
void INJECTOR::OpenDialog(DWORD wid)
{
char fdata[30]="\x60\x8B\x15\x00\x00\x00\x00\x8B\x4A\x20\x68\x11\x 11\x11\x11\x81\xC1\xEC\x00\x00\x00\xBB\x22\x22\x22 \x22\xFF\xD3\x61\xC3";
DWORD func=F_DIALOG;
DWORD ba=BA;
memcpy(fdata+3,&ba,4);
memcpy(fdata+11,&wid,4);
memcpy(fdata+22,&func,4);
InjectFunction(fdata,30);
}


F_SKILL, F_MOVE1, F_MOVE2, F_MOVE3, F_GET_LOOT - это адреса соответствующих функций вызываемых в клиенте, их я вынес в модуль Offsets

Ну вот вроде бы с нижектами мы закончили.

В прошлом разделе мы разбирались с инжектами. Теперь давайте разберёмся с пакетами.

Сервер 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\x 11\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,lenfun c,NULL); // инжектим наш код

hProcThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_ST ART_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)
{
char code[28]="\xBF\x11\x11\x11\x11\xBB\x90\x07\x63\x00\xA1\x22\x 22\x22\x22\x57\x8B\x48\x20\x81\xC1\xEC\x00\x00\x00 \xFF\xD3\xC3";

int len=28;
DWORD ba=BA;
memcpy(code+1,&wid,4);
memcpy(code+11,&ba,4);
InjectAndExecute(code,len);
}

перепишем её

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); // инжектим
}

Запускаем нашу программу, жмём на кнопочку и смотрим - ближайший моб выделился. Значит всё прошло хорошо, и пакет удачно отправился серверу.
На этом о пакетах всё. Укажу ещё некоторые функции, которые могут понадобиться для работы бота.

// Убрать таргет
void INJECTOR::TargetOff()
{
PACKET pack;
pack.len=2;
pack.Bytes[0]='\x08';
pack.Bytes[1]='\x00';
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);
}

// Включение/выключение полёта
void INJECTOR::Fly(DWORD FlyID)
{
PACKET pack;
pack.len=10;
char Packet[10] = "\x28\x00\x01\x01\x0C\x00\xF7\x31\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
memcpy(pack.Bytes+6,&FlyID,4);
SendPacket(&pack);
}

// Вызов пета из ячейки с номером..
void INJECTOR::CallPet(int nom)
{
PACKET pack;
pack.len=6;
char Packet[6] = "\x64\x00\x01\x00\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
pack.Bytes[2]=nom;
SendPacket(&pack);
}

// Использование предмета в инвентаре
void INJECTOR::UseItem(DWORD id, BYTE cell)
{
PACKET pack;
pack.len=10;
char Packet[10] = "\x28\x00\x00\x01\x0D\x00\xAD\x21\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
pack.Bytes[4] = cell; // номер ячейки
memcpy(pack.Bytes+6,&id,4);
SendPacket(&pack);
}

// Продажа из инвентаря
void INJECTOR::Sell(BYTE nomcell, DWORD kol, DWORD ID)
{
PACKET pack;
pack.len=26;
char Packet[26] = "\x25\x00\x02\x00\x00\x00\x10\x00\x00\x00\x01\x00\x 00\x00\xFF\xFF\x00\x00\xFF\x00\x00\x00\xD0\x07\x00 \x00";
memcpy(pack.Bytes,Packet,pack.len);
pack.Bytes[18] = nomcell; // 18 - номер ячейки
memcpy(pack.Bytes+22,&kol,4); // 22-25 - количество
memcpy(pack.Bytes+14,&ID,4); // 14-17 - ID предмета
SendPacket(&pack);
}

// Покупка у НПС
void INJECTOR::Buy(BYTE NPCcell, DWORD kol, DWORD ID)
{
PACKET pack;
pack.len=50;
char Packet[50] = "\x25\x00\x01\x00\x00\x00\x28\x00\x00\x00\x00\x00\x 00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x 00\xA5\x21\x00\x00\x06\x00\x00\x00\x01\x00\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
pack.Bytes[42] = NPCcell; // 30 - номер ячейки
memcpy(pack.Bytes+46,&kol,4); // 34-27 - количество
memcpy(pack.Bytes+38,&ID,4); // 26-29 - ID предмета
SendPacket(&pack);
}


// Обычная атака пета
void INJECTOR::PetAttack(DWORD wid)
{
PACKET pack;
pack.len=11;
char Packet[11] = "\x67\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
memcpy(pack.Bytes+2,&wid,4); // 4 байта, начиная с 2 - WID монстра
SendPacket(&pack);
}


// Ремонт у НПС
void INJECTOR::Repair()
{
PACKET pack;
pack.len=16;
char Packet[16] = "\x25\x00\x03\x00\x00\x00\x06\x00\x00\x00\xFF\xFF\x FF\xFF\x00\x00";
memcpy(pack.Bytes,Packet,pack.len);
SendPacket(&pack);
}

// Воскрешение в ближгород
void INJECTOR::Res()
{
PACKET pack;
pack.len=2;
char Packet[2] = "\x04\x00";
memcpy(pack.Bytes,Packet,pack.len);
SendPacket(&pack);
}

// Режим пета Охрана
void INJECTOR::PetGuard()
{
PACKET pack;
pack.len=14;
char Packet[14] = "\x67\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x 00\x00";
memcpy(pack.Bytes,Packet,pack.len);
SendPacket(&pack);
}

// Включение скилла, используемого петом автоматически, как активного
void INJECTOR::PetDefSkill(DWORD skillid)
{
PACKET pack;
pack.len=14;
char Packet[14] = "\x67\x00\x00\x00\x00\x00\x05\x00\x00\x00\xF1\x02\x 00\x00";
memcpy(pack.Bytes,Packet,pack.len);
memcpy(pack.Bytes+10,&skillid,4);
SendPacket(&pack);
}



Продолжение... ([Ссылки могут видеть только зарегистрированные и активированные пользователи])

BritishColonist
01.05.2012, 22:13
Прочитал РАЗДЕЛ 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).

На этом вроде всё.
Рекомендую обратить внимание на сию критику.


Чуть позже продолжу чтение.)

dwa83
01.05.2012, 22:32
1. Слово "адрес" в английском пишется с двумя буквами d; "-то", "-либо", "-нибудь" и "-таки" пишутся с дефисом; слово "аккуратно" пишется не через "о";
В некоторых предложениях отсутствуют точки.
Постараюсь исправить, местами бегло писал.

2. Некоторые примеры кода не заключены в теги "CODE".
Это в основном отдельные строчки, которые не представляют из себя логически целостного кода, не разделённого комментариями. Я такие строки цветом выделил.

3. Функция PIDByProcName закрывает хендл "pHandle" лишь в случае успешного нахождения процесса; отсутствует проверка верхнего регистра имени процесса (а это действительно важно, ведь у некоторых процесс называется "ElementClient.exe", у некоторых - "elementclient.EXE", у кого-то - "ELEMENTCLIENT.EXE", не говоря уже о таком имени как "ELEME~1.EXE".
попробую исправить, раньше я не таким способом делал, осталось вспомнить КАК я делал раньше))

Вы ведь понимаете, что она открывает и закрывает хендл процесса 7 раз подряд?
Да, но ради простоты можно чем-нибудь и пожнртвовать..
Немного нарушена логика (последовательность) статьи, сложновато/бегло объяснены некоторые моменты (классы и структуры / пример со структурами str1 и str2).
Тут я исправит скорее всего не смогу, так как писал паралельно с созданием программы, и порядок действий сам собой напрашивался..

BritishColonist
01.05.2012, 22:48
Что ж, осилил вторую часть :D

Указатель на память для параметров я получаю так - к адресу начала выделенной памяти прибавляю 64 (64*DWORD=64*4=256) тоесть на 256 байт дальше от начала области памяти.
Зачем это тут умножение на размер DWORD? 64 в данном случае - количество байтов, а не переменных. Поэтому смело прибавляйте 256. Дальше по статье идёт пример, где записывается функция и параметры к ней по адресам +0 и +256 (якобы), а на самом деле - по +0 и +64, т.е. Ваша 'самая длинная и страшная' функция бега по координатам, вероятно, будет наполовину перезаписана параметрами, что вызовет краш клиента.

И инжектор "строчек кода" неудобен тем, что нельзя будет нормально отредактировать внедряемый код.

dwa83
01.05.2012, 22:56
данном случае - количество байтов
Нет, всё верно, проверенно.
У нас указатель типа DWORD. Если к нему прибавить 1, то на самом деле указатель сместится на размер DWORD. Видимо такая фишка у указателей в отличие от обычных чисел.
Или такой пример:
WORD mass[30];
Если использовать mass без скобок, он будет восприниматься как указатель на нулевой элемент массива. Но если указать mass+1, это воспринимается как указатель на 1 элемент массива, а его адрес смещён от нулевого на WORD. Поэтому фактически число, представляющее адрес, увеличится не на 1 а на 2 байта.

Даже проверил, а то покоя не даст ))
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Добавлено через 27 минут
И инжектор "строчек кода" неудобен тем, что нельзя будет нормально отредактировать внедряемый код. Мне кажется, что редактировать придётся только в случае глобального изменения клиента после обновления(в случае если инжекты напроч изменятся). После последнего обновления вроде ничего страшного не произошло, адрес изменился, но он вынесен в оффсеты и вписывается в эту строчку отдельно.

BritishColonist
02.05.2012, 01:33
dwa83, да, всё верно. Это я не обратил внимание, что там указатель на DWORD, а не просто DWORD.

Foreworld
04.05.2012, 22:19
Здравствуй dwa83! Я новичок в программирования. ТЫ можешь скинуть Код. В файлах Unit1, Bot, Client.
Зачем это? Это для того что бы можно было понять куда прописывать коды. Нужно ли прописывать коды находящегося в не Цитаты. Ну например hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); Нужно ли это вставлять в файлы. Если нужно, то куда. Как в этом месте понять: HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа - сюда что то нужно писать, или это обьяснение
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
Я очень прошу более подробнее написать\исправить статью.Пожалуйста. Просто не понятно что куда вставлять?

vogel
04.05.2012, 23:56
Просто не понятно что куда вставлять?
Может быть тогда стоит начать со знакомства с языком программирования и средой разработки ?
Ибо вот эта цитата подсказывает, что бота вы "с наскока" не напишите.

Foreworld
05.05.2012, 00:05
Нет, я не которое понимаю куда вставлять но. В статье присутствует такой код как hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); - Это в обще нужно куда то вставлять.
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю

Kitsune
05.05.2012, 02:23
Нет, я не которое понимаю куда вставлять но. В статье присутствует такой код как hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); - Это в обще нужно куда то вставлять.
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю

[Ссылки могут видеть только зарегистрированные и активированные пользователи]
MEGAFACEPALM

Foreworld
05.05.2012, 02:39
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
MEGAFACEPALM
Таких как ты дофига. А как объяснить не фига!/kidding

BritishColonist
05.05.2012, 03:22
Таких как ты дофига. А как объяснить не фига!/kidding

Таких, как ты, ещё больше. Я бы сказал 95%.
Объяснить нифига? Сперва изучи хоть что-нибудь, а потом задавай вопросы, чтобы форумчанам было хотя бы понятно, чем тебе вообще можно помочь. С нуля тебя никто учить не собирается.

Кстати, в соседних темах (впрочем, как и в этой) есть много-много материала, с которым можно ознакомиться, в том числе и статьи для новичков. Используй поиск, в конце концов, пробегись по разделу вручную.

P.S. Ах да, первое, что тебе стоит получше выучить - родной язык. Да, такая вот горькая правда.

ktulx
05.05.2012, 03:27
Очень интересно, ещё читаю )
Можете помочь мне?

//BA +0x1C +0x1C +0x20 +0x18 +(i*0x4) +0x4 /i = 0 - 0x300/

for i:=0 to 768 do begin

ReadProcessMemory (hProcess, Pointer($A571E0), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$1C), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$1C), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$20), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$18), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+i*$04), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$04), @A, sizeof(A), NoB);
if A<>0 then begin
ReadProcessMemory (hProcess, Pointer(A+$480), @A, sizeof(A), NoB);
ShowMessage(IntToStr(A));
end;
end;

Делфи, правда, но не суть. Читаю уровни окружающих игроков. Вместо этого в А записывается непонятное семизначное число. Происходит это столько раз, сколько персов вокруг меня, то есть где-то в конце напортачил, видимо.

Спасибо.

dwa83
05.05.2012, 06:41
Здравствуй dwa83! Я новичок в программирования. ТЫ можешь скинуть Код. В файлах Unit1, Bot, Client.
Зачем это? Это для того что бы можно было понять куда прописывать коды. Нужно ли прописывать коды находящегося в не Цитаты. Ну например hProc = OpenProcess(PROCESS_ALL_ACCESS,false,PID); Нужно ли это вставлять в файлы. Если нужно, то куда. Как в этом месте понять: HANDLE OpenProcess(
DWORD dwDesiredAccess, // флажок доступа - сюда что то нужно писать, или это обьяснение
BOOL bInheritHandle, // параметр дескриптора наследования
DWORD dwProcessId // идентификатор процесса
);
Я очень прошу более подробнее написать\исправить статью.Пожалуйста. Просто не понятно что куда вставлять?

Всё это указанно предварительно, как будет выглядеть в программе, а дальше уже в коде используется, напримнр в функции 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 минут
можешь скинуть Код. В файлах Unit1, Bot, Client.
ссылка на готовый проект в конце четвёртго раздела

Добавлено через 54 минуты
ktulx,
//BA +0x1C +0x1C +0x20 +0x18 +(i*0x4) +0x4 /i = 0 - 0x300/

for i:=0 to 767 do begin (всего 768, но отсчёт ведь от нуля)

ReadProcessMemory (hProcess, Pointer($A571E0), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$1C), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$1C), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$20), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$18), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+i*$04), @A, sizeof(A), NoB);
ReadProcessMemory (hProcess, Pointer(A+$04), @A, sizeof(A), NoB);
if A<>0 then begin

ReadProcessMemory (hProcess, Pointer(A+$480), @A, sizeof(A), NoB);
ShowMessage(IntToStr(A));
end;
end;

sizeof(A) лучше просто записать как 4(не будет лишнего вычисления размера, ведь значения адресов всегда 4 байта)
И ещё, значения некоторых параметров сервер посылает клиенту только во время "плотного" взаимодействия с этим игроком, например во время его выделения, а пока не выделишь, эти значения считаются пока не нужными и не посылаются. Чтобы считать параметры нескольких персонажей. нужно выделить первого, считать параметры, выделить второго и тд.

Добавлено через 59 минут
И OpenProcess кода в С++ нет. При запуске компиляции. Он не понемает этот код! Либо я что то не доганяю
vcl.h не забыли подключить к модулю?
//---------------------------------------------------------------------------
#include <vcl.h>
#include "Offsets.h"

#ifndef ReaderH
#define ReaderH

struct COORDS
{
float x;
float y;
float z;
};

struct READER
{
DWORD pid; // PID
DWORD Read_32(DWORD addr);
float Read_float(DWORD addr);
DWORD PersStruct();
. . .

ktulx
05.05.2012, 07:56
A типа Dword.
За подсказки спасибо, но это не объясняет результат. По-моему я либо что-то сделал не так, либо чего-то ещё не сделал ) Ведь в А идут значения всех окружающих меня персов, точно, вот только вместо уровня персонажа, там что-то вроде 6756495.

dwa83
05.05.2012, 08:05
ktulx, Я дополнил свой пост выше, почитайте. И ещё, IntToStr не обязательно использовать. ShowMessage сама преобразует(хотя в Delphi возможно и не так, но должно быть так же).
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Foreworld
05.05.2012, 11:17
Спасибо Большое! Про vcl.h я токо-что сейчас увидел. Я новичок в С++. Я его осваиваю через книги и видео выложенного в интернете!СПАСИБО!

Kitsune
05.05.2012, 11:58
Таких как ты дофига. А как объяснить не фига!
Что тебе здесь можно объяснять, если ты даже не знаешь среды программирования, в которой работаешь?
Ты думаешь такой вот красивый и волосы назад, сел за компьютер, запустил програмку, вставил пару строчек из интернета и получил бота? Наивный...

Этот раздел существует для советов и конкретных решений в области ПВ, но не для изучения C++ с нуля.

Хочешь учиться программировать? Скачай/купи книжки, прочитай, вникни, пробуй писать примеры из этих книжек: калькулятор, конвертор температур и прочее, что поможет тебе понять суть программирования и то, как ставить задачи. Когда освоишь базовые навыки можешь рискнуть окунуться головой в мир ботостроения, но здесь, на тебя упадет сверху большая глыба необъятных знаний из всех областей: потоки, винапи, сетевой программирование, инжекты, ассемблерные команды и т.д.

Так что не лелеять свою светлую мечту о том как ты копируешь пару строк чужого кода и у тебя получается супер-пупер бот...

ktulx
17.05.2012, 00:55
Привет!
Прошу помощи в использовании скиллов через пакеты :)

Пока что я делаю примерно так: читаю таргет, переворачиваю/перевожу его в Hex. Подгружаю код скилла из файла, переворачиваю/перевожу его в Hex. Составляю из всего этого пакет и отправляю.
Дело в том, что нихрена пока не работает ))) я сильно путаюсь. Например, молитва о ясности выглядит как "2900710000000001", после чего ещё добавляется ID таргета. 71 - код скилла, 2900 и ещё группа нулей - статичны - как с ними поступать? Прописывать вручную в пакет или как-то иначе? Не могу понять )

Спасибо.

vogel
17.05.2012, 01:19
1) Твой пакет должен в точности соответствовать аналогичному пакету клиента - поэтому все нули должны быть на месте.
2) чтобы скилл сработал, тот на кого ты его используешь - должен быть у тебя в таргете
3) чтобы скилл сработал, у тебя должно быть достаточно маны
4) чтобы скилл сработал, у тебя должно быть достаточно ярости / чи
5) чтобы скилл сработал, он должен быть готов к использованию, то есть кулдаун у него прошёл
6) чтобы скилл сработал, ты должен быть на расстоянии срабатывания до цели, на которую ты его используешь. Если расстояние дальше - ты должен подойти к цели.
7) чтобы скилл сработал, совершенно очевидно, что он должен быть у тебя выучен.

8) Ты обязан самостоятельно следить за выполнением условий 1-7. Оно тебе надо ? Если надо - юзай пакеты, если не уверен - инжекты.

ktulx
17.05.2012, 02:05
Если надо - юзай пакеты, если не уверен - инжекты
сурово ) Инжекты, как таковые, на практике ещё не изучал. Видимо, пора.

procedure Skill(SkillID:DWord);
begin
aParam.Param1:=SkillID;
InjectFunc(PID,@SkillCall,@aParam,SizeOf(aParam));
end;

Ругается на необъявленную переменную. Я вроде и понимаю, что здесь присваивается значение неизвестной переменной, но в "оригинальной" процедуре из темы на форуме она тоже ни локально, ни глобально не объявлена -__- Я туп, как ламер (с)

UPDATE
Вопрос снят. Проблема была в том, что пора спать /facepalm
Всем спасибо.

t2rex
21.05.2012, 13:12
в чем может быть проблема если не юзается пакет:
void INJECTOR::pack_useitem(DWORD id, int cell)
{
PACKET pack;
pack.len=10;
char PacketSell[10] = "\x28\x00\x00\x01\x0D\x00\xAD\x21\x00\x00";
memcpy(pack.Bytes,PacketSell,pack.len);
pack.Bytes[4] = cell; // номер ячейки
memcpy(pack.Bytes+6,&id,4);
sendpacket(&pack);
}
в обработчике кнопки

bot.client.inject.useitem(8620,4);

[Ссылки могут видеть только зарегистрированные и активированные пользователи] итем
4я ячейка(счет идет с лева на право, сверху-вниз)
при этом пакетные медитация, таргет работаю как надо.

BritishColonist
21.05.2012, 13:50
t2rex, возможно, тип переменной cell должен быть BYTE (или char).

t2rex
21.05.2012, 15:21
BritishColonist, нет в типах все правильно, ошибка намног тупее, я считать ячейки в инвентаре стал от 1, а надо было от 0

dwa83
21.05.2012, 16:58
Кстиати, лучше писать не PacketSell а просто Packet. Случайно по всем функциям скопипастил с именем пакет продажи.

BritishColonist
22.05.2012, 03:11
dwa83, ага, банальный копипаст очевиден. Причём с моего кода.
И скорее всего тип должен всё-таки быть char или BYTE, чтобы ровно один байт копировать в строку. Если всё работает правильно с int, то это просто какое-то совпадение.

dwa83
22.05.2012, 16:49
Причём с моего кода
Ну да) Когда ещё сам разбирался, первый пакет был пакетом продажи, остальные от него это имечко переняли) надо бы исправить.

parkour111
29.05.2012, 02:50
))

dwa83
02.06.2012, 12:39
то это просто какое-то совпадение.
Не совпадение, ячеек в инвентаре не может быть больше 256, потому сюда хоть LongInt может прийти, и при записи в массив в один байт даже значение Int нормально "вместится"). Допустим мы присваиваем переменной типа Char значение переменной типа Int, в переменную типа char перейдёт только младший байт из Int, а следующие ячейки массива не заполнятся старшими байтами.
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

BritishColonist
03.06.2012, 18:41
Не совпадение, ячеек в инвентаре не может быть больше 256, потому сюда хоть LongInt может прийти

Я говорю о размерности. Просто мало логики в копировании из int в char.
Не может быть больше 256? Значит правильнее использовать как раз-таки один байт, а не четыре.

dwa83
03.06.2012, 20:15
Я говорю о размерности. Просто мало логики в копировании из int в char.
Не может быть больше 256? Значит правильнее использовать как раз-таки один байт, а не четыре.
Убедили) Заменил на BYTE, в других местах вроде бы всё соответствует размерности, если нет - укажите, изменю.

sed99
07.09.2012, 12:25
Возник вопрос.
Как считать с клиента имя персонажа или моба.
Смещение уже нашел попытался найти, но выдало полный бред, если не сложно, то пожалуйста в виде рабочего кода. Я пишу на C++ Borland

Sirioga
07.09.2012, 14:08
Я пишу на C++ Borland
Код на другом языке думаю не составит труда прочитать?) Пишу на FreeBASIC, возможно тебе поможет чем нибудь.

Руоф 1.4.5 Build 2305
#include "windows.bi"

Dim PID As UInteger
Dim hProcess As Handle
Dim buffer As dWord
Dim outBuffer as WString * 8

GetWindowThreadProcessId(FindWindow("ElementClient Window", "Perfect World"), @PID)
hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, PID)

'---------------------------------------------
ReadProcessMemory(hProcess, &hA571E0, @buffer, sizeof(buffer), NULL)
ReadProcessMemory(hProcess, buffer+&h1C, @buffer, sizeof(buffer), NULL)
ReadProcessMemory(hProcess, buffer+&h34, @buffer, sizeof(buffer), NULL)
ReadProcessMemory(hProcess, buffer+&h66C, @buffer, sizeof(buffer), NULL)
ReadProcessMemory(hProcess, buffer+&h0, @outBuffer, sizeof(outBuffer), NULL)
'---------------------------------------------

CloseHandle(hProcess)

print outBuffer
sleep

Офсеты взял из темы sumikot`а ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).

dwa83
07.09.2012, 23:50
Считываю в массив а затем перевожу из юникода в анси. Я на билдере шестом пишу, потому приходится с юникодом колдовать, так как более поздние версии с ним норм работают.

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

Вот например функция считывания для структуры READER из этой статьи
addr - адрес строки юникода
mass - массив в который считываем
len - количество считываемых байт
void READER::Read_Mass(DWORD addr, char* mass, int len)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
ReadProcessMemory(hProcess,(void*)addr,mass,len,0) ;
CloseHandle(hProcess);
}

Преобразовываю вот такой ерундой(самому не обязательно такие "велосипеды" писать, должны быть готовые средства)
len - количество СИМВОЛОВ(не байт), или проще указать размерность_массива/2
AnsiString READER::UnicodeToAnsi(char *mass, int len)
{
AnsiString txt="";
for (int i=0; i<len; i++)
{
BYTE lo=mass[i*2];
BYTE hi=mass[i*2+1];
if ((lo==0)&&(hi==0)) break;
if ((lo==1)&&(hi==4)) {lo=168; hi=0;}
if ((lo==81)&&(hi==4)) {lo=184; hi=0;}
if (hi==4) lo+=176;
txt+=(char)lo;
}
return txt;
}

Применительно к статье можно использовать например так
DWORD str_addr=...вычисляем адрес строки по нужным оффсетам
char str[40]={0};
get.Read_Mass(str_addr, str, 40); // считываем
AnsiString string=get.UnicodeToAnsi(str, 20);

sed99
08.09.2012, 16:21
прописал все вроде как надо, но выдает ошибку:
[BCC32 Error] Reader.h(46): E2316 'READER::myName(char *)' is not a member of 'READER'

dwa83
11.09.2012, 21:43
прописал все вроде как надо, но выдает ошибку:
[BCC32 Error] Reader.h(46): E2316 'READER::myName(char *)' is not a member of 'READER'

Пишет что функция myName не является членом READER, вы видимо не обьявили эту функцию в h-файле внутри структуры READER, либо обьявление отличается от реализации, и потому считается другой функцией(проверьте одинаковость в обьявлении и самой реализации). Реализация функции то написана, но как член структуры она видимо не указана.

Внутри
struct READER
{

...
...
// обьявите функцию, чтобы компилятор знал что она существует как метод структуры(класса)
возвращаемый_тип myName(char *);
...
...
}

wolfser
13.10.2012, 13:01
Здравствуйте!!! Пытаюсь повторить программу но столкнулся с рядом проблем:
1. в MVS C++ 2010 отсутствуют шрифты «AnsiString», вышел из положения путем использования ссылок на WCHAR массив.
2. Чтобы вывести pid в элемент Label:
Label1->Caption=bot.client.pid;
его надо преобразовать в тип System string^, если ктот знает какой функцией это можно сделать подскажите пожалуйста.

Разобрался!!! DWORD это тот же unsigned long для работы с ним есть клас System .UInt32:

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e)
{

UInt32 Сpid;
Cpid=bot.client.pid;
this->label1->Text=Cpid.ToString();
}


3. При проверке программы путем вывода результатов в консоль:


#include <Windows.h>
#include "stdafx.h"
#include <TlHelp32.h>
#include <iostream>
using namespace std;

void Init(void);
DWORD pid; // Идентификатор Процесса
DWORD PIDByProcName(WCHAR *ProcessName); // определение pid по имени процесса

int _tmain(int argc, _TCHAR* argv[])
{

Init();
char c;
cout<<pid;
cin>>c;
return 0;
}
void Init(void)
{ pid= PIDByProcName(L"elementclient.exe"); }

DWORD PIDByProcName(WCHAR *ProcessName)
{
PROCESSENTRY32 ProcessEntry;
HANDLE pHandle;
DWORD pid;
pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ProcessEntry.dwSize = sizeof(ProcessEntry);
pid=0;
bool loop=Process32First(pHandle, &ProcessEntry);
while (loop)
{
loop=Process32Next(pHandle, &ProcessEntry);
if (*ProcessEntry.szExeFile == *ProcessName)
{
pid = ProcessEntry.th32ProcessID;
CloseHandle(pHandle);
break;
}
}
return pid;
}


Выдает ID "explorer.exe", хотя если вместо "elementclient.exe" подставить какой-то другой процесс ID выдает верный. Если можно подскажите почему?

Разобрался!!! оператор if сравнивает по первому элементу строки и выдает 1 соответствующий вариант которым является explorer.exe

coder007
25.10.2012, 19:57
Как считать имя персонажа? Пишу в MS VS 2008. Пока получается считать только один какой-то иероглиф.

ASDitsh
26.10.2012, 15:04
Читаю так
WCHAR ch[n];
ReadProcessMemory(hProcess,(void*)(name_addr),&ch,(n*2),0);
где n- кол-во считываемых символов.

wolfser
28.10.2012, 17:16
Для получения ID процесса использую класс Process:


void CClient::PIDByProcName(String ^PrNm)
{
Process ^PrEn;
PrEn=PrEn->GetProcessesByName(PrNm)[0];
Cpid=PrEn->Id;
ProcNm=PrEn->ToString();
}


клас Process позволяет определить сколько процессов с одинаковым именем запущенно и выбрать нужный.

Вопрос: можно ли читать память процесса и записывать в неё через класс Process?

ASDitsh
30.10.2012, 14:03
клас Process позволяет определить сколько процессов с одинаковым именем запущенно и выбрать нужный.

Вопрос: можно ли читать память процесса и записывать в неё через класс Process?
Да, можно. Зная pid процесса получаешь его handle, через OpenProcess. Затем этот handle передаешь в ф-и (Read/Write)ProcessMemory

wolfser
30.10.2012, 18:52
Да, можно. Зная pid процесса получаешь его handle, через OpenProcess. Затем этот handle передаешь в ф-и (Read/Write)ProcessMemory

Класс System:: Diagnostics::Process не имеет членов OpenProcess и (Read/Write)ProcessMemory. Есть возможность связать с файлом на диске для запуска приложения с параметрами метод Start(...\*.exe), и только при такой инициализации процесса задается сойство Handle.

Я его использцю для получения pid, путем подключения к уже запущеному процессу методом GetProcessesByName(String), этот метод возвращает массив компонентов Process связанных с запущенными процессами имеющими одно название.
Как на меня, то намного проще получить pid через класс Process чем описанным в данной теме методом.

Кому интересно:

//проверяю запущен хоть 1 клиент
Int32 colpr=System::Diagnostics::Process::GetProcessesBy Name(L"elementclient").Length;
if(colpr)
{
//если запущен добавляю итем в comboBox1 c именем перса
//для дальнейшего выбора нужного окна
Client *bot=new Client[colpr]; //создаю указатель структуру типа Client
//и выделяю память под масив типа Client
int i;
for (i=0; i<=colpr-1; i++)
{//инициализирую масыв типа Client
System::Diagnostics::Process ^process=process->GetProcessesByName(L"elementclient")[i];
(bot+i)->pid=process->Id;
//читаю имя перса
String ^str;
for (int j=0 ; j<=20; j++)
{
str=str+(((bot+i)->Read_Char((bot+i)->NmAdr()+0x0)+j)->ToString());
}
//добавляю итем с именем перса в comboBox1
comboBox1->Items->Add(str);
str="";//обнуляю имя перса
}
delete[] bot;// удаляю масив типа Client (делать объязательно!!! Чтоб не засорять память)
}
else
{
//если не запущено ни одного окна с игрой вывожу предупреждение
this->label3->Text = L"Клиент не запущен!!!";
}

Как видно из примера можно сразу определить количество запущенных окон с игрой, определить pid каждого окна и т.д. насколько позволит смекалка :agreed:

Ps: начал использовать класс Process чтоб облегчить себе поиск pid так как в МВСтудии 2010 нет строки AnsiString, а функции чтения и записи памяти использую предложеные в данной теме.

dwa83
16.11.2012, 13:29
void FindClients()
{
char classname[256];
kolbot=0; // количество запущеных клиентов
DWORD pid;

HWND Wd=FindWindow(0,0); // Найдем первое окно верхнего уровня любого класса
while (Wd!=0) // Если такое окно существует
{
GetClassName(Wd,classname,100); // получим имя класса окна
AnsiString cn=classname; // для удобства пихнём
if (cn=="ElementClient Window") // сравним с классом пв
{
SetWindowText(Wd,анси-что-то.c_str()); // если наше окно, переименуем во что-то

GetWindowThreadProcessId(Wd,&pid);
bot[kolbot].client.Init(pid); // тут в массив пид
bot[kolbot].client.wind=Wd; // тут в массив хэндл
kolbot++; // количество найденных окон прибавим
}
Application->ProcessMessages(); // Дадим возможность поработать другим
Wd=GetNextWindow(Wd,GW_HWNDNEXT); // Найдем следующее окно в системе.
}
}
я так сделал, но это bcb

let999
06.12.2012, 23:34
Доброго времени суток! Пишу на С++ MVS 2010. Делал всё по гайду. Проблема такая: при выполнении WaitForSingleObject(hProcThread, INFINITE); клиент вылетает с ошибкой (просто закрывается). В дебаге ничего не пишет. Дебаг не заходит в ассемблерную вставку. Код:

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; // успешная инъекция и выполнение кода
}

Ассемблерная вставка:

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
}
}

Подскажите кто советом, в чём может быть проблема?

qwer7074
07.12.2012, 05:40
Доброго времени суток! Пишу на С++ MVS 2010. Делал всё по гайду. Проблема такая: при выполнении WaitForSingleObject(hProcThread, INFINITE); клиент вылетает с ошибкой (просто закрывается). В дебаге ничего не пишет. Дебаг не заходит в ассемблерную вставку. Код:

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; // успешная инъекция и выполнение кода
}

Ассемблерная вставка:

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
}
}

Подскажите кто советом, в чём может быть проблема?

Адрес функции таргета 00681F50

let999
07.12.2012, 06:49
Спасибо, так и знал, что косячит в ассемблере

хм... исправил, косячит там же

qwer7074
07.12.2012, 10:31
Используй отправку пакетов для таргета
От этого же автора
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); // инжектим
}

let999
07.12.2012, 15:36
Спасибо, разберусь, попробую.

dwa83
11.12.2012, 08:34
Кстати, все адреса указанные в темке - давние. Их скорее всего нужно будет поменять на корректные.

SPONKE
12.01.2013, 19:37
Столкнулся с одной проблемой.
В 1 разделе PID определился правильно. Дошел до получения НР.
Выдает не четырёхбайтное значение а восьмибайтное - 4208432
Вывел значение PersStruct()
Label1->Caption=bot.client.get.PersStruct(); - четырёхбайтное
Может проблема в типе переменной функции myHP()?
int myHP() имеет тип Integer, PersStruct();тип DWORD. Пробовал изменять тип - одни эмоции компилятора)))

Использую Borland C++ Builder 6
Оффсеты взял правильные

Dinmaite
12.01.2013, 19:45
Читать из памяти следует в переменную типа DWORD, ведь integer может получать отрицательные значения. Кроме того integer в различных системах имеет различную длину.

SPONKE
12.01.2013, 20:46
Перебрал весь код - все по гайду.
Выдает одинаковое значение ХП, МР, Lvla и то неправильное:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]
Вчера ночью работало, утром уже нет %)

Решил проблему.
Открыл исходник бота - нашел строчки:

DWORD READER::PersStruct()
{
DWORD buff = Read_32(GA);
return Read_32(buff+PERS_STRUCT);
}

отсчет велся сразу с GA)))
и зачем было все так усложнять в гайде)))

Всем спасибо, двигаюсь дальше

sumikot
13.01.2013, 03:06
и зачем было все так усложнять в гайде)))
Затем, чтобы было понятно, откуда что берется.
Вообще, читать HP от GA (GA+0x20+OffsetHP) - это классика ботостроения. Так делали, когда еще не знали, что существует адрес BA. А чтобы чтение HP было быстрее, можно при запуске бота получить и запомнить адрес структуры перса - тогда чтение занимает только одно действие - PersStruct+OffsetHP.
Хотя, это все относится к стилю программиста - каждый пишет немного по своему.

dwa83
29.01.2013, 09:23
Читать из памяти следует в переменную типа DWORD, ведь integer может получать отрицательные значения. Кроме того integer в различных системах имеет различную длину.
Кстати да, здесь как то интуитивно было принято что хп в инт поместится и не может быть больше 65535, а до этого значения int аналогичен DWORD(руоф всё-таки, хотя может быть есть и папы у которых и зашкаливает хп, но наверное не будут сажать бота на тру-перса:))), но корректнее DWORD.

Добавлено через 7 минут
Во туплю спросоня/facepalm, последний бит же определяет знак, так что int сопоставим с DWORD аш до значения 2147483647, такого ХП точно нет и на фришках))

А вообще, все эти одинаково-длинные типы различаются только "на бумажке", в памяти же представляют фактически одно и то же - определённое количество байт. Потому, если я уверен на 1000%, что значение всегда будет в пределах положительного значения INT, то на автомате и пишу INT. Максимум компилятор варнингом пнёт тебя, но ясно что зря он так пугается)). Извиняюсь за отступление, не судите строго)

micro-st
31.01.2013, 14:18
Доброе время суток. Ребят а кто нибудь мне может помочь изминить адрес в скрипте? я его там вообще не вижу. Или как это можно сделать? я только зарегистрировался если можно напишите впочту.

deman4ik
26.02.2013, 17:47
Я не понял раздел пакеты, откуда брать данные для массива (как я понял они после обновы изменились), как их найти? Например откуда взялось "\x02\x00\x01\x00\x00\x00"; из значений для массива таргета.

Добавлено через 2 минуты
Ну или может кто скинет сразу готовые значения.

gurin
26.02.2013, 22:44
Я не понял раздел пакеты, откуда брать данные для массива (как я понял они после обновы изменились), как их найти? Например откуда взялось "\x02\x00\x01\x00\x00\x00"; из значений для массива таргета.

Добавлено через 2 минуты
Ну или может кто скинет сразу готовые значения.

пакеты меняются крайне редко и после этой обновы вроде не менялись. Ловятся PacketListener ([Ссылки могут видеть только зарегистрированные и активированные пользователи])-ом (дай бог N00bSa1b0t здоровья)

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

Что касаемо тарета, то там структура такая (значения в hex) 08 00 {00 00 00 00} , где то что в фигурных скобках - WID того кого берешь в таргет, просто 02 00 - это сброс таргета

Во вложении прикрепил то что когда-то нарыл на просторах интернета.

des99
27.02.2013, 18:25
Здравствуйте)
Возник вопрос. Когда пытаюсь считать имя персонажа то выводиться пустое значение. Вывожу таким образом

DWORD str_addr = 0xA591E0 + 0x1C + 0x34 + 0x66C + 0x0;
char str [40] = {0};
bot.client.get.Read_Mass(str_addr, str, 40);
Label5->Caption = bot.client.get.UnicodeToAnsi(str, 20);

Ошибок не выдает. Использовал в качестве примера это сообщение.

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

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

Вот например функция считывания для структуры READER из этой статьи
addr - адрес строки юникода
mass - массив в который считываем
len - количество считываемых байт
void READER::Read_Mass(DWORD addr, char* mass, int len)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
ReadProcessMemory(hProcess,(void*)addr,mass,len,0) ;
CloseHandle(hProcess);
}

Преобразовываю вот такой ерундой(самому не обязательно такие "велосипеды" писать, должны быть готовые средства)
len - количество СИМВОЛОВ(не байт), или проще указать размерность_массива/2
AnsiString READER::UnicodeToAnsi(char *mass, int len)
{
AnsiString txt="";
for (int i=0; i<len; i++)
{
BYTE lo=mass[i*2];
BYTE hi=mass[i*2+1];
if ((lo==0)&&(hi==0)) break;
if ((lo==1)&&(hi==4)) {lo=168; hi=0;}
if ((lo==81)&&(hi==4)) {lo=184; hi=0;}
if (hi==4) lo+=176;
txt+=(char)lo;
}
return txt;
}

Применительно к статье можно использовать например так
DWORD str_addr=...вычисляем адрес строки по нужным оффсетам
char str[40]={0};
get.Read_Mass(str_addr, str, 40); // считываем
AnsiString string=get.UnicodeToAnsi(str, 20);

Подскажите из-за чего такое может быть?

pw.assistant
28.02.2013, 14:27
DWORD str_addr = 0xA591E0 + 0x1C + 0x34 + 0x66C + 0x0;

вы неверно вычисляете адрес, адрес надо считать путем последовательного чтения значения участка памяти с последующим сложением смещения к результату, а не простым суммированием

правильно было бы так, правда пример на делфи


function ReadString(hProc: THandle; addr: int64): UnicodeString;
const
len = 255;
var
st: array [0..len] of widechar;
BytesCountOfRead: NativeUInt;
begin
if addr > 0 then
begin
ZeroMemory(@st, len*2);
ReadProcessMemory(hProc, ptr(addr), @st, len*2, BytesCountOfRead);
result := st;
end
else
result := '';
end;

function ChainReadAddr(hProc: THandle; const addresses: array of dword): DWORD;
var
BytesCountOfRead: NativeUInt;
i, len: Integer;
addr, NextAddr: DWORD;
begin
addr := 0;
len := length(addresses);
try
for i:=0 to len-1 do
begin
NextAddr := addresses[i];
if (addr+NextAddr+4 < $FFFFFFFF) then
begin
if i < len-1 then
ReadProcessMemory(hProc, ptr(addr+NextAddr), @addr, 4, BytesCountOfRead)
else
addr := addr+NextAddr;
end
else
begin
addr := 0;
break;
end;
end;
Result := addr;
except
Result := 0;
end;
end;

function ChainReadString(hProc: THandle; const addresses: array of dword): String;
var
addr: dword;
begin
addr := ChainReadAddr(hProc, addresses);
result := ReadString(hProc, addr);
end;


вызов


const
game_addr = $00B9029C;
GamerStructOfs = $34;
ofsGamerName = $670;

GamerName : array [1..4] of dword = (game_addr, GamerStructOfs, ofsGamerName, $0);

***

Name := ChainReadString(hProc, GamerName);

des99
02.03.2013, 14:33
Дошел до считывание координат мобов и наткнулся на такую ошибку

int nom=-1;
float Dist=100000;

for (int i=0; i<768; i++)
{
if (bot.client.get.mobWID(i)!=0)
//Суда цикл да же не заходит (проверил путем вывода в Label значения i)
{
if (bot.client.get.mobType(i) == 6)
{
if (bot.client.get.mobDist(i) < Dist)
{
Dist = bot.client.get.mobDist(i);
nom = i;
}
}
}
}

Подскажите в чем может быть дело :omg:

--------------

почему то в цикле он не захотел к i++ увеличивать, решил проблему путем прописи i++ в конце тела цикла

gurin
02.03.2013, 15:24
видимо
bot.client.get.mobWID(i)
всегда возвращает 0, что говорит о том что или мобов нет или функция неверно работает.

P.S. 768 итераций - не самый лучший вариант.

dwa83
07.03.2013, 19:15
P.S. 768 итераций - не самый лучший вариант.
ну да, ну да ладно, кому нужно, будут сортированный список использовать, мне просто так проще было


Здравствуйте)
Возник вопрос. Когда пытаюсь считать имя персонажа то выводиться пустое значение. Вывожу таким образом

Прочитайте в темке внимательнеей

Теперь сделаем считывание данных какого-нибудь моба.
Всё по аналогии, но не совсем. Идём в тему с оффсетами и видим целых 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, значит моба в этом месте массива нет и место пустует. Напишем в ридере новую функцию, которая будет возвращать адрес структуры моба по номеру(по аналогии с адресом структуры перса, но у перса номера не было.

des99
24.03.2013, 16:31
Здравствуйте!

Писал функцию подбора лута и наткнулся на проблему при инжекте кода клиент вылитает, подскажите в чем может быть дело?

инжектор подъема лута:
// Подбор лута
void INJECTOR::GetLoot(DWORD wid, BYTE type)
{
char fdata[29]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x89\x11\x11\x11\x 11\x6A\x22\x68\x33\x33\x33\x33\xBB\x44\x44\x44\x44 \xFF\xD3\x61\xC3";
DWORD func=0x00471860;
DWORD ga=D_GA;
DWORD prs=PERS_STRUCT;
BYTE typ=0;
if (type==2) typ=1;
memcpy(fdata+3,&ga,4);
memcpy(fdata+9,&prs,4);
fdata[14]=typ;
memcpy(fdata+16,&wid,4);
memcpy(fdata+21,&func,4);
InjectAndExecute(fdata,29);
}

заранее спасибо:wow:


--------------

все нашел и исправил = )

Xantrax
21.04.2013, 19:43
Посмотрите пожалуйста эту тему: [Ссылки могут видеть только зарегистрированные и активированные пользователи]
Аналогичная проблема с вылетом клиента при выделении моба.

dwa83
11.05.2013, 02:24
Проблема может быть в неправильных оффсетах или самой функции, после написания статьи было много обновлений и данные могли поменяться. Просьба указывать как именно исправили чтоб работало, у других с подобной проблемой будет меньше головной боли :)

des99
16.05.2013, 09:14
Внимательным быть, заместо адреса GA, подставил его смещение :notme: :d

__________________________________________________ ____
По части клиент-сервер возник вопрос, у сина инвиз похож на ГМ-инвиз и если так, то данные о синах/ГМ в инвизе подгружается в массив игроков которые находятся вблизи или нет?

leto2011
14.08.2013, 06:47
Всем добрый день!

Я пытаюсь написать бота по статьям данного сайта, за месяц получилось
сделать почти все, бот выделяет мобов ,бъет их собирает лут, пьет банки, вобщем сайт очень полезен)).
Но никак не получается сделать передвижение по координатам, голова уже идет кругом.

Вот сама функция инжекта движения:
void INJECTOR::MoveTo(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\x 33\xFF\xD3\x89\xC7\x8D\x44\xE4\x0C\x50\x68\x44\x44 \x44\x44\x89\xF9\xBB\x55\x55\x55\x55\xFF\xD3\x8B\x 8E\x66\x66\x66\x66\x6A\x00\x6A\x01\x57\x6A\x01\xBB \x77\x77\x77\x77\xFF\xD3\xA1\x88\x88\x88\x88\x8B\x 80\x99\x99\x99\x99\x8B\x80\xAA\xAA\xAA\xAA\x8B\x40 \x30\x8B\x48\x04\xB8\xBB\xBB\xBB\xBB\x89\x41\x20\x B8\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);

InjectAndExecute2(&fdata,117);
}

Функция InjectAndExecute2(void *code, int len); - работает 100% т.к. подбор лута и выделения в таргет через нее работают.

данные которые получает функция MoveTo:
GA = 0x00B9029C - 100% рабочий
PERS_STRUCT = 0x34 - 100% рабочий

MY_ACTION_ARRAY = 0x1050 - под вопросом
F_MOVE1 = 0x00479A40 - под вопросом
F_MOVE2 = 0x0047DD40 - под вопросом
F_MOVE3 = 0x00479EB0 - под вопросом

А вообще F_MOVE1, F_MOVE2, F_MOVE3 уже от безвыходности пробовал
перебором ставить разные значения с сайта :


#define F_MOVE1 0x0047B290//0x00495680//0x00479A40//0x0047B290//0x00479A40//0x0046E410 //Перемещение персонажа 0x00494620
#define F_MOVE2 0x0047F5A0//0x00499320//0x0047DD40//0x0047F5A0//0x0047DD40//0x004728E0 //Перемещение персонажа 0x00498290
#define F_MOVE3 0x0047B700//0x00495F20//0x00479EB0//0x0047B700//0x00479EB0//0x0046E880 //Перемещение персонажа 0x00494EC0

клиент постоянно вылетает.

И еще в некоторых статьях указываются непонятные оффсеты Walk_Ofs1=1204 - 4612
WalkMode=688 - 1672

Так вот вопрос) , что я делаю не так ?)) и для чего Walk_Ofs1 ?

Desmond Hume
14.08.2013, 07:26
По части клиент-сервер возник вопрос, у сина инвиз похож на ГМ-инвиз и если так, то данные о синах/ГМ в инвизе подгружается в массив игроков которые находятся вблизи или нет?
Нет, сервер не отсылает пакеты о координатах сина в инвизе. Это отлично видно через Pandora_s_Box — клиенту эта информация не передается.

flyland
05.12.2014, 04:12
Всем привет! Совсем не вдупляю как вывести Имя перса... Может потому что уже почти утро, а может и незнание)

Юзаю XE7 C++

Полностью по уроку. Все циферки выводит как надо, А Никнейм не хочет... Выводит какой то 1 знак.

Подскажите, гуру.

Ginrey
05.12.2014, 12:33
Полностью по уроку. Все циферки выводит как надо, А Никнейм не хочет... Выводит какой то 1 знак.
чтобы вывести ник создайте массив типа char и туда считывайте ник

leto2011
26.12.2014, 15:56
Всем привет!

Похоже тема с ником персонажа актуальна)
У меня таже проблема, не получается правильно считать ник своего персонажа, приведу код, может кто чем поможет).

void __fastcall TForm1::Button6Click(TObject *Sender)
{

DWORD str_addr;
str_addr=bot.client.get.Read_Name(); //...вычисляем адрес строки по нужным оффсетам
char str[40]={0};
bot.client.get.Read_Mass(str_addr, str, 40); // считываем
AnsiString string=bot.client.get.UnicodeToAnsi(str, 20);

Label16->Caption="Name - "+ string;
}


DWORD READER::Read_Name()
{
DWORD buff;
buff = Read_32(BA);
//buff = Read_32(buff+D_GA); //Пробовал и с D_GA и без как с некоторых постах
buff = Read_32(buff+PERS_STRUCT);
buff = Read_32(buff+MY_NAME);
buff = Read_32(buff+ZERO);

return buff;
}


void READER::Read_Mass(DWORD addr, char* mass, int len)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
ReadProcessMemory(hProcess,(void*)addr,mass,len,0) ;
CloseHandle(hProcess);

}


AnsiString READER::UnicodeToAnsi(char *mass, int len)
{
AnsiString txt="";
for (int i=0; i<len; i++)
{
BYTE lo=mass[i*2];
BYTE hi=mass[i*2+1];
if ((lo==0)&&(hi==0)) break;
if ((lo==1)&&(hi==4)) {lo=168; hi=0;}
if ((lo==81)&&(hi==4)) {lo=184; hi=0;}
if (hi==4) lo+=176;
txt+=(char)lo;
}
return txt;
}


где:

#define MY_NAME 0x670//0x6b0 //Пробовал разные значения
#define ZERO 0x0
#define BA 0x00C9DFAC
#define GA 0x00C9E74C
#define D_GA 0x1C

Уважаемые гуру, намекните хотя бы на правильное направление), заранее спасибо!

Smertig
26.12.2014, 21:03
DWORD READER::Read_Name()
{
DWORD buff;
buff = Read_32(BA);
buff = Read_32(buff+D_GA);
buff = Read_32(buff+PERS_STRUCT);
buff = Read_32(buff+MY_NAME);

return buff;
}
Не пишу на дельфи, остальных ошибок не вижу.

leto2011
26.12.2014, 21:30
Это не делфи ) это С++ 6 ).

но и так не работает...
DWORD READER::Read_Name()
{
DWORD buff;
buff = Read_32(BA);
buff = Read_32(buff+D_GA);
buff = Read_32(buff+PERS_STRUCT);
buff = Read_32(buff+MY_NAME);

return buff;
}

Simyr
27.12.2014, 12:40
имя перса вроде
GA
+pers_str
+pers_name
+0

bestbeer
27.12.2014, 13:28
Имя как и любой текст - массив байт, нужно прочитать эти байты в массив , а потом кодировать в текст. Я поступал следующим образом: 1) выделял большой массив (255) 2) считывал эти данные в массив 3) обрезал этот массив до нулей 4) конвертировал

Vb.net:

Declare Function ReadProcessMemory1 Lib "kernel32" Alias "ReadProcessMemory" (ByVal hProcess As Integer, ByVal lpBaseAddress As Integer, ByRef lpBuffer As Byte, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As Integer

Public Function Read_String() As String
Dim i As Integer
Dim StringBuffer As Byte() = New Byte(255) {}


If Me.Adres_And <> -1 Then
ReadProcessMemory1(Me.Hendle, (Me.Adres + Me.Adres_And), StringBuffer(0), StringBuffer.Length, Nothing) ' (Me.Adres + Me.Adres_And) - адрес структуры массива
Else
ReadProcessMemory1(Me.Hendle, (Me.Adres), StringBuffer(0), StringBuffer.Length, Nothing)
End If


For i = 0 To (StringBuffer.Count - 1) Step 2

If StringBuffer(i) = 0 And StringBuffer(i + 1) = 0 Then

If i > 0 Then
ReDim Preserve StringBuffer(i - 1)
Return System.Text.Encoding.Unicode.GetString(StringBuffe r)
Else
Return ""
End If

End If

Next

End Function

zasranecqwe1111
27.12.2014, 15:12
Всем привет!

Похоже тема с ником персонажа актуальна)
У меня таже проблема, не получается правильно считать ник своего персонажа, приведу код, может кто чем поможет).

void __fastcall TForm1::Button6Click(TObject *Sender)
{

DWORD str_addr;
str_addr=bot.client.get.Read_Name(); //...вычисляем адрес строки по нужным оффсетам
char str[40]={0};
bot.client.get.Read_Mass(str_addr, str, 40); // считываем
AnsiString string=bot.client.get.UnicodeToAnsi(str, 20);

Label16->Caption="Name - "+ string;
}


DWORD READER::Read_Name()
{
DWORD buff;
buff = Read_32(BA);
//buff = Read_32(buff+D_GA); //Пробовал и с D_GA и без как с некоторых постах
buff = Read_32(buff+PERS_STRUCT);
buff = Read_32(buff+MY_NAME);
buff = Read_32(buff+ZERO);

return buff;
}


void READER::Read_Mass(DWORD addr, char* mass, int len)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
ReadProcessMemory(hProcess,(void*)addr,mass,len,0) ;
CloseHandle(hProcess);

}


AnsiString READER::UnicodeToAnsi(char *mass, int len)
{
AnsiString txt="";
for (int i=0; i<len; i++)
{
BYTE lo=mass[i*2];
BYTE hi=mass[i*2+1];
if ((lo==0)&&(hi==0)) break;
if ((lo==1)&&(hi==4)) {lo=168; hi=0;}
if ((lo==81)&&(hi==4)) {lo=184; hi=0;}
if (hi==4) lo+=176;
txt+=(char)lo;
}
return txt;
}


где:

#define MY_NAME 0x670//0x6b0 //Пробовал разные значения
#define ZERO 0x0
#define BA 0x00C9DFAC
#define GA 0x00C9E74C
#define D_GA 0x1C

Уважаемые гуру, намекните хотя бы на правильное направление), заранее спасибо!





#define GA 0xC9E74C
#define PERS_STRUCT 0x30
#define MY_NAME 0x6B0

void READER::Read_Mass(DWORD addr, char* mass, int len)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ;
ReadProcessMemory(hProcess,(void*)addr,mass,len,0) ;
CloseHandle(hProcess);
}

AnsiString READER::UnicodeToAnsi(char *mass, int len)
{
AnsiString txt="";
for (int i=0; i<len; i++)
{
BYTE lo=mass[i*2];
BYTE hi=mass[i*2+1];
if ((lo==0)&&(hi==0)) break;
if ((lo==1)&&(hi==4)) {lo=168; hi=0;}
if ((lo==81)&&(hi==4)) {lo=184; hi=0;}
if (hi==4) lo+=176;
txt+=(char)lo;
}
return txt;
}

DWORD READER::PersStruct()
{
DWORD buff;
buff = Read_32(GA);
return Read_32(buff+PERS_STRUCT);
}

AnsiString READER::myName()
{
DWORD buff;
buff = Read_32(PersStruct()+MY_NAME);
char mass[70]={0};
Read_Mass(buff,mass,70);
return UnicodeToAnsi(mass, 35);
}




Скопировал 1 в 1 из рабочей версии программы
Если не запустится то ошибку надо искать в другом месте

leto2011
27.12.2014, 15:40
Огромное Вам спасибо!!!! )) все заработало))

Adrev
14.01.2015, 21:11
А как можно посмотреть пакеты, которые формируются в процессе. Допустим содержание пакета перемещения? Может можно как нибудь считывать данные...

Hilling
14.01.2015, 21:33
Пакеты лови через Packet Listener [Ссылки могут видеть только зарегистрированные и активированные пользователи]

Но про пакеты перемещения лучше забудь - намного проще инжектом бегать.

Fess88
23.01.2015, 16:47
Всем привет, прошу совета опять:)

C инжектами разобрался, вот теперь с пакетами засада:( Подскажите пожалуйста, по статье dwa83, раздел 3 (пакеты), у него идет строка:

char fdata[29]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x 11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33 \xFF\xD0\x61\xC3";

она актуальна на сегодняшний день?

Я пытаюсь таргет сделать, по его примеру, и вылетает клиент:( Вот пример моей функции:


SendPacket(PACKET *pack)
{
HANDLE hProcThread;
// базовый адрес адрес пакета // функция для отправки (33)
char fdata[30]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x 11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33 \xFF\xD0\x61\xC3";
int lenfunc=30;
DWORD func=F_SEND_PACKET;
DWORD ba=BA;
DWORD len=pack->len;
void* pParams = Alloc(); // Указатель на память для функций

void* pFunction = Alloc(); // Указатель на память для функций
cout << pParams << endl << pFunction << endl;
WriteProcessMemory(m_hProc,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(m_hProc,pFunction,fdata,lenfunc ,NULL); // инжектим наш код

hProcThread = CreateRemoteThread(m_hProc,NULL,NULL,(LPTHREAD_STA RT_ROUTINE)pFunction,NULL,NULL,NULL);
if(hProcThread==INVALID_HANDLE_VALUE) // не удалось создать поток
{
CloseHandle(m_hProc);
return 0;
}

WaitForSingleObject(hProcThread, INFINITE); // ожидаем завершения работы потока
CloseHandle(hProcThread); // освобождаем

CloseHandle(m_hProc);
return 1; // успешная инъекция и выполнение кода
}


и вот функция выделения:

TargetMobPack(DWORD wid)
{
PACKET pack; // структура пакета
pack.len=7; // длина данных пакета
char Packet[7] = "\x02\x00\x00\x00\x00\x00"; // массив данных пакета
memcpy(pack.Bytes,Packet,pack.len); // скопируем в структуру пакета данные массива
memcpy(pack.Bytes+2,&wid,4); // последние четыре байта - WID выделяемого моба
SendPacket(&pack); // инжектим

}

через отладчик (прога останавливаю после всех WriteMemoryProcess, не создавая поток: CreateRemoteThread - видно, что записалось вот что:


0FF90000 60 PUSHAD
0FF90001 8B0D ACEFCC00 MOV ECX,DWORD PTR DS:[CCEFAC] ; elementc.00CCF730
0FF90007 8B49 20 MOV ECX,DWORD PTR DS:[ECX+20]
0FF9000A 68 07000000 PUSH 7
0FF9000F 68 00008F08 PUSH 88F0000
0FF90014 B8 E0556F00 MOV EAX,6F55E0 ; ASCII "<i"
0FF90019 FFD0 CALL EAX
0FF9001B 61 POPAD
0FF9001C C3 RETN


WID у меня такой:
wid = 0x80108594;
Подскажи, куда копать плиз.

Если делаю поток, после всех WriteProcessMemory, то получаю вылет(

zasranecqwe1111
23.01.2015, 17:29
она актуальна на сегодняшний день?
Да

TargetMobPack(DWORD wid)
{
PACKET pack; // структура пакета
pack.len=7; // длина данных пакета
char Packet[7] = "\x02\x00\x00\x00\x00\x00"; // массив данных пакета
memcpy(pack.Bytes,Packet,pack.len); // скопируем в структуру пакета данные массива
memcpy(pack.Bytes+2,&wid,4); // последние четыре байта - WID выделяемого моба
SendPacket(&pack); // инжектим

}

Длинна пакета неверна, должна быть 6

char fdata[30] Тут тоже надо 29

Fess88
23.01.2015, 18:13
Поправил, теперь так:

TargetMobPack(DWORD wid)
{
PACKET pack; // структура пакета
pack.len=6; // длина данных пакета
char Packet[] = "\x02\x00\x00\x00\x00\x00"; // массив данных пакета
memcpy(pack.Bytes,Packet,pack.len); // скопируем в структуру пакета данные массива
memcpy(pack.Bytes+2,&wid,4); // последние четыре байта - WID выделяемого моба
SendPacket(&pack); // инжектим

}

вылетает все равно. По сути,
char Packet[] = "\x02\x00\x00\x00\x00\x00"; - в реальности здесь же 7 символов, т.к компилятор C++ добавляет автоматически в конец \0.

Здесь также переделал:
char fdata[]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x 11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33 \xFF\xD0\x61\xC3";
int lenfunc=29;

в итоге вылетает точно также:(

P.S. static const DWORD F_SEND_PACKET= 0x006F55E0;

zasranecqwe1111
23.01.2015, 19:36
А если написать char Packet[6] = "\x02\x00\x00\x00\x00\x00";
char fdata[29]="\x60\x8B\x0D\x00\x00\x00\x00\x8B\x49\x20\x68\x11\x 11\x11\x11\x68\x22\x22\x22\x22\xB8\x33\x33\x33\x33 \xFF\xD0\x61\xC3";

Скажу честно не знаю на сколько это принципиально, так как мне сложно даются строготипизированные языки

Fess88
23.01.2015, 19:42
Все, разобрался, оказывается проблема была в F_SEND_PACKET= 0x006F55E0

Заменил её на F_SEND_PACKET= 0x751D10, и все заработало!:ban:

Всем спасибо!

botowod1
26.01.2016, 18:36
Гуры!!! Возникла проблема с получением хендлера процесса PW. При открытия процесса хендлер не получаю.

DWORD READER::Read_32(DWORD addr)
{
HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,pid) ; // открываем процесс
if (hProcess == NULL)
{
return 0;
}
DWORD value;
ReadProcessMemory(hProcess, (LPCVOID)addr, &value, sizeof(DWORD) * 2, 0); // считываем значение по заданному адресу
CloseHandle(hProcess); // закроем процесс
return value; // вернём считанное значение
}

Пишу в Microsoft Visual Studio 10 в Windows 7
проваливаюсь сюда:

{
return 0;
}


Добавлено через 1 час 33 минуты
Вот тормознул... Нужно было запустить от админа... :crabe:

seergeeyy
23.02.2016, 21:51
Спасибо огромное автору, сделал неплохого бота
теперь пытаюсь сделать чтоб он умел ходить
но никак не могу понять где взять вот эти значения: fdata[117]="\x60\xA1\x00\x00\x00\x00\x8B\xB0\x11\x11\x11\x11\x 8B\x8E\x22\x22\x22\x22\x6A\x01\xBB\x33\x33\x33\x33 \xFF\xD3\x89\xC7\x8D\x44\xE4\x0C\x50\x68\x44\x44\x 44\x44\x89\xF9\xBB\x55\x55\x55\x55\xFF\xD3\x8B\x8E \x66\x66\x66\x66\x6A\x00\x6A\x01\x57\x6A\x01\xBB\x 77\x77\x77\x77\xFF\xD3\xA1\x88\x88\x88\x88\x8B\x80 \x99\x99\x99\x99\x8B\x80\xAA\xAA\xAA\xAA\x8B\x40\x 30\x8B\x48\x04\xB8\xBB\xBB\xBB\xBB\x89\x41\x20\xB8 \xCC\xCC\xCC\xCC\x89\x41\x24\xB8\xDD\xDD\xDD\xDD\x 89\x41\x28\x61\xC3";

/ Перемещение в координаты
// самая длинная и страшная функция)))
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\x 8B\x8E\x22\x22\x22\x22\x6A\x01\xBB\x33\x33\x33\x33 \xFF\xD3\x89\xC7\x8D\x44\xE4\x0C\x50\x68\x44\x44\x 44\x44\x89\xF9\xBB\x55\x55\x55\x55\xFF\xD3\x8B\x8E \x66\x66\x66\x66\x6A\x00\x6A\x01\x57\x6A\x01\xBB\x 77\x77\x77\x77\xFF\xD3\xA1\x88\x88\x88\x88\x8B\x80 \x99\x99\x99\x99\x8B\x80\xAA\xAA\xAA\xAA\x8B\x40\x 30\x8B\x48\x04\xB8\xBB\xBB\xBB\xBB\x89\x41\x20\xB8 \xCC\xCC\xCC\xCC\x89\x41\x24\xB8\xDD\xDD\xDD\xDD\x 89\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);
}
я так понимаю эти значения переводятся примерно в такой вид:

pushad
mov eax, dword ptr [PW_GAMERUN_ADDR]
mov esi, dword ptr [eax+$20]
mov ecx, dword ptr [esi+$FE0]
push 1
call CallAddress1
mov edi, eax
lea eax, dword ptr [esp+$0C]
push eax
push flying
mov ecx, edi
call CallAddress2
mov ecx, dword ptr [esi+$FE0]
push 0
push 1
push edi
push 1
call CallAddress3
mov eax, dword ptr [PW_GAMERUN_ADDR]
mov eax, dword ptr [eax+$20]
mov eax, dword ptr [eax+$FE0]
mov eax, dword ptr [eax+$30]
mov ecx, dword ptr [eax+$4]
mov eax, x
mov dword ptr[ecx+$20], eax
mov eax, z
mov dword ptr[ecx+$24], eax
mov eax, y
mov dword ptr[ecx+$28], eax
popad

но у меня структура в клиенте немного другая (нашел адрес в CE примерно по гайду ([Ссылки могут видеть только зарегистрированные и активированные пользователи])) :
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

у меня версия клиента старая( 1.3.4 версии, сборка 2265 )
и я не нашел даже приблизительно одинаковых строчек подряд во всем elementclient
например команды "mov eax, dword ptr [PW_GAMERUN_ADDR]" у меня вообще нету, все начинается с BaseAdress а не с GameAdress,
прошу помощи где мне взять эти строки ( walk оффсеты имеются)

Iana.
15.02.2017, 11:43
Спасибо большое за такое прекрасное руководство!
Уже на протяжении довольно долгого времени захожу на эту статью, перечитываю и сверяюсь на сколько больше начала понимать. Эта статья служит индикатором моего прогресса в С++ и win32 API.
А цель то в конечном итоге с полным пониманием каждого слова написать того самого бота.
Спасибо за Ваш труд!

dronte
27.04.2017, 13:12
Вопрос ко всем, как сейчас обстоят дела с таким вот неприкрытым доступом к памяти клиента?
никак скрывать от протектора это не нужно?? Вопрос особенно касательно инъекций и пакетов

//////
не удается никаким вменяемым образом распечатать Ник персонажа и Локацию в консоль, чтобы получились читаемые символы. пример выше - не помог, среда VS2017 C++

BJIoM
19.10.2017, 18:12
Не получается считать данные о персонаже из клиента, вместо моего текущего уровня (73) выдает -858993460
Сначала грешил на оффсеты, но CheatEngine по тому же адресу все нормально прочитал, как привести выводимое значение в нормальный вид?

Sirioga
19.10.2017, 22:56
Не получается считать данные о персонаже из клиента, вместо моего текущего уровня (73) выдает -858993460
Сначала грешил на оффсеты, но CheatEngine по тому же адресу все нормально прочитал, как привести выводимое значение в нормальный вид?

Может читаешь что-то не так? Покажи хотя бы код, офсеты, скажи версию PW, сервер...

BJIoM
20.10.2017, 12:15
Может читаешь что-то не так? Покажи хотя бы код, офсеты, скажи версию PW, сервер...

Версия PW - 1.5.5 RUOFF
Оффсеты:

#define BASE 0x0DFCBA0 //Базовый адрес
#define GAME 0x1C //Игра

#define CHARACTER 0x34 //Структура Игрока

#define CR_HP 0x4C8 //ХП
#define CR_MHP 0x51C //Макс. ХП
#define CR_MP 0x4CC //МП
#define CR_MMP 0x520 //Макс. МП
#define CR_LVL 0x4C0 //Уровень перса

Код по сути брал практически полностью отсюда, вот весь READER.h
#include <Windows.h>
#include <Tlhelp32.h>
#include <iostream>
using namespace std;

class READER
{
public:
DWORD PID;
DWORD PersStruct;

READER()
{
DWORD buff = read(GAME);
PersStruct = read(buff + CHARACTER);
}

//Получаем LVL
int myLVL()
{
return read(PersStruct + CR_LVL);
}

//Считываем данные из клиента
DWORD read(DWORD addr)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, PID); // открываем процесс
DWORD value;
ReadProcessMemory(hProcess, (void*)addr, &value, 4, 0); // считываем значение по заданному адресу
CloseHandle(hProcess); // закроем процесс
return value; // вернём считанное значение
}
};

Среда - VS2017

growl13
20.10.2017, 18:00
Ошибка в том что ты считываешь не по GameAddress, а по самому оффсету 1C :



READER()
{
DWORD buff = read(GAME);
PersStruct = read(buff + CHARACTER);
}

#define BASE 0x0DFCBA0 //Базовый адрес
#define GAME 0x1C //Игра


а нужно сначала считать значение в БА, прибавить к считанному значению GameOffset (1C) и считать по тому адресу что получил

DWORD temp1 = read(BASE);
DWORD temp2 = read(temp1 + GAME);
PersStruct = read(temp2 + CHARACTER);

или же можно использовать твой код, но подставлять фактический GameAddress, т.к. он статичен как и базовый(для руофа 0FE00200 вродь)

BJIoM
20.10.2017, 19:15
нужно сначала считать значение в БА, прибавить к считанному значению GameOffset (1C) и считать по тому адресу что получил

DWORD temp1 = read(BASE);
DWORD temp2 = read(temp1 + GAME);
PersStruct = read(temp2 + CHARACTER);

или же можно использовать твой код, но подставлять фактический GameAddress, т.к. он статичен как и базовый(для руофа 0FE00200 вродь)

Попробовал оба варианта и все равно считывает -858993460 вместо 75

Кстати, при попытке считать HP и MP выдается это же значение

growl13
20.10.2017, 22:24
Попробовал оба варианта и все равно считывает -858993460 вместо 75

Кстати, при попытке считать HP и MP выдается это же значение
Если число одинаковое, то скорей всего ты получаешь адрес структуры перса. Ты не забыл выполнить этот метод?
int myLVL()
{
return read(PersStruct + CR_LVL);
}

BJIoM
20.10.2017, 22:32
Если число одинаковое, то скорей всего ты получаешь адрес структуры перса. Ты не забыл выполнить этот метод?
int myLVL()
{
return read(PersStruct + CR_LVL);
}
в main.cpp как раз таки и идет вызов этого метода
Сегодня переписал чисто для себя этот метод, добавив вывод действий в лог
//Получаем LVL
int myLVL()
{
log.log("WORK", "Считываем уровень перса...");
int check = read(PersStruct + CR_LVL);
if ((check < 1) || (check > 105))
{
log.log("ERROR", "Считанное из памяти значение (" + to_string(check) + ") выходит за пределы диапазона уровней (1-105)");
return ERR_LVL;
}
else return check;
}
Аналогично сделал функции для хп и мп
В итоге лог:
[DEBUG] PID процесса "elementclient.exe": 10880
[WORK] Считываем уровень перса...
[ERROR] Считанное из памяти значение (-858993460) выходит за пределы диапазона уровней (1-105)
[WORK] Считываем здоровье перса...
[ERROR] Считанное из памяти значение (-858993460) отрицательно, хотя здоровье не может быть отрицательно
[WORK] Считываем ману перса...
[ERROR] Считанное из памяти значение (-858993460) отрицательно, хотя мана не может быть отрицательна

Sirioga
21.10.2017, 00:58
в main.cpp как раз таки и идет вызов этого метода
Сегодня переписал чисто для себя этот метод, добавив вывод действий в лог
Пробовал читать жёстко? Без ридера, используя лишь ReadProcessMemory?

Добавлено через 7 минут

И ещё, где ты вот это вот потерял? А то, открыть память открыл, прочитать прочитал, закрыл, а что открывал, откуда читал, не ясно...


hWindow = FindWindow("ElementClient Window", "Perfect World")
GetWindowThreadProcessId(hWindow, @PID)


FreeBASIC если что, winapi само собой...

BJIoM
21.10.2017, 01:46
Пробовал читать жёстко? Без ридера, используя лишь ReadProcessMemory?
Нет, попробую

И ещё, где ты вот это вот потерял? А то, открыть память открыл, прочитать прочитал, закрыл, а что открывал, откуда читал, не ясно...


hWindow = FindWindow("ElementClient Window", "Perfect World")
GetWindowThreadProcessId(hWindow, @PID)


Как я уже говорил, я взял код из гайда, просто переименовал метод (Read_32 -> read)
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; // вернём считанное значение
}
И вроде как по логике он и так должен работать

BJIoM
23.10.2017, 03:03
Заметил возможную причину неправильного считывания
Добавив в конструктор отправку инфы в лог (заодно убрал класс клиента, сделав поиск PID вашим методом прямо в классе READER, переименовав его в GAME) обнаружил такое

лог:
[DEBUG] Данные из BASE ADDRES: 3435973836
[DEBUG] + GA: 3435973836
[DEBUG] + CHARACTER: 3435973836

Код конструктора:
GAME()
{
HWND hWnd = FindWindow(NULL, "Perfect World");
GetWindowThreadProcessId(hWnd, &PID);
log.log("DEBUG", "PID процесса \"elementclient.exe\": " + to_string(PID));
DWORD buff = read(BA);
log.log("DEBUG", "Данные из BASE ADDRES: " + to_string(buff));
buff = read(buff + GA);
log.log("DEBUG", "+ GA: " + to_string(buff));
PersStruct = read(buff + CHARACTER);
log.log("DEBUG", "+ CHARACTER: " + to_string(PersStruct));
}
Почему-то получаются одинаковые значения и из-за этого уровень, здоровье и мана считываются неправильными значениями

sabbaot01
23.10.2017, 07:00
я в свое время на такую же проблему натыкался (как я тебя понимаю), но у меня были свои заморочки из-за нераспространенного языка. открывай cheat engine и смотри память клиента так - что ты там читаешь.
в лог выводи адрес и значение, лежащее в этом адресе.
основной затык у меня сейчас в считывании юникода и непонятных +0+0+0+0 страниц
почему оффсет ga+struct+name+0 дает ник перса, а не ga+struct+name

есть отличная тема по этому топику, может ты плохо искал: [Ссылки могут видеть только зарегистрированные и активированные пользователи]

HWND hWnd = FindWindow(NULL, "Perfect World");
GetWindowThreadProcessId(hWnd, &PID);
log.log("DEBUG", "PID процесса \"elementclient.exe\": " + to_string(PID));
DWORD buff = read(BA);
log.log("DEBUG", "Данные из BASE ADDRES: " + to_string(buff));
buff = read(buff + GA);
log.log("DEBUG", "+ GA: " + to_string(buff));
PersStruct = read(buff + CHARACTER);
log.log("DEBUG", "+ CHARACTER: " + to_string(PersStruct));
а сколько у тебя GA?
GA=+1C
CHARACTER=+20 или +34
не перепутал ли ты GA = 9C1514 c GA=+1C

BJIoM
23.10.2017, 12:16
открывай cheat engine и смотри память клиента так - что ты там читаешь.
Сначала грешил на оффсеты, но CheatEngine по тому же адресу все нормально прочитал, как привести выводимое значение в нормальный вид?

есть отличная тема по этому топику, может ты плохо искал: [Ссылки могут видеть только зарегистрированные и активированные пользователи]
Спасибо, гляну

а сколько у тебя GA?
GA=+1C
CHARACTER=+20 или +34
не перепутал ли ты GA = 9C1514 c GA=+1C
Оффсеты:

#define BASE 0x0DFCBA0 //Базовый адрес
#define GAME 0x1C //Игра

#define CHARACTER 0x34 //Структура Игрока

#define CR_HP 0x4C8 //ХП
#define CR_MHP 0x51C //Макс. ХП
#define CR_MP 0x4CC //МП
#define CR_MMP 0x520 //Макс. МП
#define CR_LVL 0x4C0 //Уровень перса

sabbaot01
23.10.2017, 17:22
Цитата:
открывай cheat engine и смотри память клиента так - что ты там читаешь.
Сначала грешил на оффсеты, но CheatEngine по тому же адресу все нормально прочитал, как привести выводимое значение в нормальный вид?
Я ЧИТАЛ ЭТО
вот так религия позволяет сделать в CheatEngine?
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Sirioga
23.10.2017, 18:40
Почему-то получаются одинаковые значения и из-за этого уровень, здоровье и мана считываются неправильными значениями
Покажи полный код своей канители, а не куски, что было ясно и понятно где затык.
Мне вообще кажется, что ты читаешь не из памяти клиента, а непойми откуда вообще.

BJIoM
23.10.2017, 18:54
GAME.h
#include <Windows.h>
#include <Tlhelp32.h>
#include "LOGGER.h"
#include "OFFSETS.h"
using namespace std;

class GAME
{
public:
DWORD PID;
DWORD PersStruct;
LOG log;

GAME()
{
HWND hWnd = FindWindow(NULL, "Perfect World");
GetWindowThreadProcessId(hWnd, &PID);
log.log("DEBUG", "PID процесса \"elementclient.exe\": " + to_string(PID));
DWORD buff = read(BA);
log.log("DEBUG", "Данные из BASE ADDRES: " + to_string(buff));
buff = read(buff + GA);
log.log("DEBUG", "+ GA: " + to_string(buff));
PersStruct = read(buff + CHARACTER);
log.log("DEBUG", "+ CHARACTER: " + to_string(PersStruct));
}

//Получаем LVL
int myLVL()
{
log.log("WORK", "Считываем уровень перса...");
int check = read(PersStruct + CR_LVL);
if ((check < 1) || (check > 105))
{
log.log("ERROR", "Считанное из памяти значение (" + to_string(check) + ") выходит за пределы диапазона уровней (1-105)");
return ERR_LVL;
}
else
{
log.log("FINE", "Уровень персонажа :" + to_string(check));
return check;
}
}

//Получаем HP
int myHP()
{
log.log("WORK", "Считываем здоровье перса...");
int check = read(PersStruct + CR_HP);
if ((check < 0))
{
log.log("ERROR", "Считанное из памяти значение (" + to_string(check) + ") отрицательно, хотя здоровье не может быть отрицательно");
return ERR_HP;
}
else
{
log.log("FINE", "Здоровье персонажа :" + to_string(check));
return check;
}
}

//Получаем MP
int myMP()
{
log.log("WORK", "Считываем ману перса...");
int check = read(PersStruct + CR_MP);
if ((check < 0))
{
log.log("ERROR", "Считанное из памяти значение (" + to_string(check) + ") отрицательно, хотя мана не может быть отрицательна");
return ERR_MP;
}
else
{
log.log("FINE", "Мана персонажа :" + to_string(check));
return check;
}
}

//Считываем данные из клиента
DWORD read(DWORD addr)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, PID); // открываем процесс
DWORD value;
ReadProcessMemory(hProcess,(void*) addr, &value, 4, 0); // считываем значение по заданному адресу
CloseHandle(hProcess); // закроем процесс
return value; // вернём считанное значение
}
};

OFFSETS.h
//ОФФСЕТЫ
#define BA 0x0DFCBA0 //Базовый адрес
#define GA 0x1C //Игра

#define CHARACTER 0x34 //Структура Игрока

#define CR_HP 0x4C8 //ХП
#define CR_MHP 0x51C //Макс. ХП
#define CR_MP 0x4CC //МП
#define CR_MMP 0x520 //Макс. МП
#define CR_LVL 0x4C0 //Уровень перса

//Коды ошибок
#define ERR_LVL 1 //Ошибка: Уровень за пределами интервала (1-105)
#define ERR_HP 2 //Ошибка: Отрицательный уровень здоровья
#define ERR_MP 3 //Ошибка: Отрицательный уровень маны

Main.cpp
#include "Headers\GAME.h"
#include <conio.h>
#include <locale.h>
using namespace std;

const bool bDebug = true;

int main()
{
setlocale(LC_ALL, "RUSSIAN");
GAME PW;
if (PW.myLVL() != ERR_LVL) cout << PW.myLVL() << "\n";
if (PW.myHP() != ERR_HP) cout << PW.myHP() << "\n";
if (PW.myMP() != ERR_MP) cout << PW.myMP() << "\n";
system("Pause");
return 0;
}

Логгер думаю не обязательно, но пусть
#include <string>
#include <iostream>
#include <fstream>
using namespace std;

extern const bool bDebug;

class LOG
{
public:
LOG()
{
ofstream log;
log.open("log.log", ios::trunc);
}

//Запись в лог
void log(string TAG, string TEXT)
{
ofstream log;
log.open("log.log", ios::app | ios::out);
if ((TAG != "DEBUG") || (bDebug == true))
{
log << "[" << TAG << "] " << TEXT << '\n';
cout << "[" << TAG << "] " << TEXT << '\n';
}
};
};

sabbaot01
29.10.2017, 15:43
пытаюсь в ооп, код на с++/с#/делфи тяжко читать, поэтому спрошу на авось:
как организовать классы с персонажами?
т.е. в качестве имени объектов я хотел бы иметь имя персонажа, но имя персонажа и прочее в классе персонажа присвается после создания такого объекта.
что обычно стоит подразумевается под this ?

dwa83
30.10.2017, 19:48
GAME PW;
выполнился конструктор класса. а далее

DWORD PID;
DWORD PersStruct;
LOG log;

GAME()
{
PID = 1;// может так к примеру попробовать, чтоб пид не был равен 0

HWND hWnd = FindWindow(NULL, "Perfect World");
GetWindowThreadProcessId(hWnd, &PID); // пид скорее всего = 0
Функция GetWindowThreadProcessId возвращает обратно идентификатор потока, который создал определяемое окно, но необязательно идентификатор процесса, который создал это окно..... Если этот параметр - не ПУСТО (NULL), функция GetWindowThreadProcessId копирует идентификатор процесса в переменную; иначе, она этого не делает..

//здесь значение пид в лог и сравнение с реальным в диспетчере


....
}

А вообще мне кажется, нецелевое использование GetWindowThreadProcessId, хотя хз.

пс:
this->свойство\метод
доступ к методу или свойству текущего класса(внутри реализации которого пишется).
Имеет смысл указывать, если вдруг имя метода класса или свойство совпадает с локальной(глобальной) функцией\переменной. Чтоб компилятор мог отличить их.


к примеру:

void Class1::Method1(int var1)
{
this->var1 = var1; // тут свойству класса Class1 с именем var1 присваивается значение другой переменной с таким же именем.
}

других причин обязательного использования this-> у меня, например, пока не было вроде.


}

Добавлено через 16 минут
т.е. в качестве имени объектов я хотел бы иметь имя персонажа, но имя персонажа и прочее в классе персонажа присвается после создания такого объекта.
может конструктор с параметром(именем перса к примеру), внутри которого по значению этого имени и инициализируются параметры отдельного экземпляра? Немного разывчатый вопрос..

class Pers
{
public:
Pers(char * GameName); // как-то инициализирует внутренности по игровому имени




}


Pers priest("Nagibator777");
Pers tank("AlioshaVoin");

А зачем? использование нескольких окон одновременно?

sabbaot01
31.10.2017, 09:27
А зачем? использование нескольких окон одновременно? да. несколько окон. возможно мой вопрос глупый.
перебираю все процессы, если elementclient, то вытаскиваю его pid в массив pwpids // [8909, 8934, 12345] 3 окна, допустим

у меня вот так:
class Pers():
инициализация объектов класса

создаем экземпляр класса:
firstwindow = Pers()
firstwindow.name // Nagibator777
firstwindow.lvl // 98

вот тут у меня загвоздка. я не должен явно инициализировать экземпляры класса. это где-то должно само делаться.

возможно, у меня пока сумбур в голове из-за ООП т.к. я только начал разбирать классы и вопрос мой выше, как ты сказал, дурацкий. не могу на это возразить. отстаю от тутошних "хакеров" на лет 5 в разработке по под пв, а может и больше.

BJIoM
31.10.2017, 14:33
//здесь значение пид в лог и сравнение с реальным в диспетчере
Чуть ниже в конструкторе
log.log("DEBUG", "PID процесса "elementclient.exe": " + to_string(PID));
Результат:
[DEBUG] PID процесса "elementclient.exe": 12664
PID из диспетчера:
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

dwa83
31.10.2017, 16:09
Чуть ниже в конструкторе

Результат:

Ну на первый взгляд вроде ничего бедв не предвещает. Нужно отлаживать последовательно, проверяя работоспособность всех ключевых строчек с начала выполнения. Правильно ли и без сбоев возвращается хэндл, без ошибок ли открывается процесс для работы, правильно ли после этого отрабатывает read. и тд

Можно попробовать тестить не из среды(вдруг не даёт доступ к чужому процессу по каким то причинам). Запустить скомпиленный эксешник от админа, например.

вопрос мой выше, как ты сказал, дурацкий
Нет, я не говорил, что дурацкий. Размытый.

Найди все клиенты, их пид в массив. А потом при создании экземпляров, передавай каждому свой пид в конструктор, и пусть на основе его каждый экземпляр инициализируется сам из своего клиента, например.

Много как можно сделать.

BJIoM
01.11.2017, 22:47
Можно попробовать тестить не из среды(вдруг не даёт доступ к чужому процессу по каким то причинам). Запустить скомпиленный эксешник от админа, например.
Точно, как то не додумался. Запуск от админа все хорошо выдал:wow:

akwoyning2
28.02.2018, 04:19
Ищу человека который сделает бота под определенный сервер за выгодную цену в пм