PDA

Просмотр полной версии : [Руководство] Создание бота для PW. Часть 1. C# version


Kitsune
23.07.2010, 14:56
> Создание бота на C# 2.0 Часть 1 <

Данный гайд является основоположником серии гайдов посвященным созданию бота/ботов для игры PW.

Вступление:
Бот (сокр. от «робот») — в Википедии специальная программа для совершения рутинных операций.

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

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

Приступим:

Создадим новый проект WinForms
Накидываем на нашу форму 2 элемента Label и 2 элемента ProgressBar и расположим их как нам угодно.
Например так:

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


Данные наш бот будет получать из памяти клиента PW. Для этого нам придется прибегнуть к функции ReadProcessMemory из библиотеки kernel32.
.NET Framework позволяет нам подключать и использовать методы из библиотек написанных не на управляемом коде (managed code).

В этом нам поможет DllImport, который находится в пространстве имен System.Runtime.InteropServices.


// Импортируем функцию для чтения памяти чужого процесса
// из kernel32
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[In, Out] byte[] buffer,
UInt32 size,
out IntPtr lpNumberOfBytesRead
);

Покодив немного у меня получился такой класс для чтения памяти клиента PW.

Примечание: этот класс использует namespace: System.Diagnostics и System.Runtime.InteropServices соответственно надо не забыть их подключить.


public class Monitor
{
// Объявление события
public event EventHandler InfoUpdated;
// Метод, который вызывает событие
private void OnInfoUpdated(object sender, EventArgs e)
{
if (InfoUpdated != null) InfoUpdated(sender, e);
}

// Импортируем функцию для чтения памяти чужого процесса
// из kernel32
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[In, Out] byte[] buffer,
UInt32 size,
out IntPtr lpNumberOfBytesRead
);

// Определение базового адреса и оффсетов
// для текущей версии клиента руоффа (130)
private int baseAddress = 0x98B47C,
offsetHP = 0x464,
offsetMP = 0x468,
offsetMaxHP = 0x494,
offsetMaxMP = 0x498;

// Служебная переменная для хранения PID процесса
private Int32 PID = 0;
private Timer updateTimer;

// Свойство, для хранения текущего HP
public Int32 HP { get; set; }
// Свойство, для хранения текущего MP
public Int32 MP { get; set; }
// по аналогии
public Int32 MaxHP { get; set; }
// по аналогии
public Int32 MaxMP { get; set; }

public Monitor(int pid)
{
this.PID = pid;

updateTimer = new Timer();
updateTimer.Enabled = true;
updateTimer.Interval = 500;
updateTimer.Tick += new EventHandler(updateTimer_Tick);
}

/// <summary>
/// Данный метод вызывается каждый тик таймера
/// Здесь происходит чтение из памяти клиента значений
/// жизни и маны
/// </summary>
private void updateTimer_Tick(object sender, EventArgs e)
{
try
{
if (PID != 0)
{
IntPtr handle = Process.GetProcessById(PID).Handle;

HP = HighLevelReadIntFromMemory(handle, offsetHP);
MP = HighLevelReadIntFromMemory(handle, offsetMP);
MaxHP = HighLevelReadIntFromMemory(handle, offsetMaxHP);
MaxMP = HighLevelReadIntFromMemory(handle, offsetMaxMP);

OnInfoUpdated(this, new EventArgs());
}
}
catch (Exception ex)
{
updateTimer.Enabled = false;
MessageBox.Show(ex.Message);
}
}

/// <summary>
/// Читаем Int'овое значение из памяти приложения
/// с указанным оффсетом
/// </summary>
private Int32 HighLevelReadIntFromMemory(IntPtr handle, int offset)
{
byte[] buffer = new byte[0];
IntPtr read = IntPtr.Zero;
int temp = 0;
temp = LowLevelReadIntFromMemory(handle, baseAddress);
temp = LowLevelReadIntFromMemory(handle, temp + 0x20);
temp = LowLevelReadIntFromMemory(handle, temp + offset);
return temp;
}

/// <summary>
/// Читаем из памяти приложения массив байт по указанному адресу
/// и возвращаем целочисленное его представление
/// </summary>
private Int32 LowLevelReadIntFromMemory(IntPtr handle, int address)
{
byte[] buffer = new byte[4];
IntPtr read = IntPtr.Zero;
ReadProcessMemory(handle, (IntPtr)address, buffer, 4, out read);
return (int)BitConverter.ToUInt32(buffer, 0);
}
}

Теперь как обычно: создадим объект нашего класса Monitor в теле главного класса.

private Monitor mon;


В конструкторе главного класса, в данном случаи это наша форма сделаем следующее:

Process[] p = Process.GetProcessesByName("elementclient");

if(p != null)
{
if (p.Length > 0)
{
// инициализируем новый объект класса Monitor
// передаем туда PID первого процесса из найденных
mon = new Monitor((int)p[0].Id);
// Подписываемся на событие, в котором будем обновлять
// пользовательский интерфейс
mon.InfoUpdated += new EventHandler(mon_InfoUpdated);
}
}


В тело нашего главного класса добавляем метод, который вызывается каждый раз когда срабатывает событие InfoUpdated класса Monitor.


private void mon_InfoUpdated(object sender, EventArgs e)
{
label1.Text = String.Format("HP: {0} / {1}", mon.HP, mon.MaxHP);
label2.Text = String.Format("MP: {0} / {1}", mon.MP, mon.MaxMP);

progressBar1.Value = (int)((mon.HP * 100) / Math.Max(mon.MaxHP, 1));
progressBar2.Value = (int)((mon.MP * 100) / Math.Max(mon.MaxMP, 1));
}


Примечание: не забываем подключить namespace: System.Diagnostics. Данный неймспейс содержит в себе класс для работы с процессами.
После сборки проекта и тестирования мы получили результат:

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

З.Ы. На скрине небольшая неувязочка со значением MP в клиенте и нашей програмке. Причина: окно PW заморозилось, когда фокус был с него снят.


В аттаче находится рабочий проект созданный на MS Visual Studio 2008 Professional.

Полезные ссылки:

Узнаем base address клиента для использования ботов и хаков ([Ссылки могут видеть только зарегистрированные и активированные пользователи])
Injection Codes - Delphi/C++/AutoIt ([Ссылки могут видеть только зарегистрированные и активированные пользователи])

Dunя
05.08.2010, 19:18
Пункт №2 не понял вообще. Можно подробнее Что? Где? как?

Kitsune
06.08.2010, 10:00
Dunя, в самом верху файла класса, где прописаны все using'и, добавляем новый
using System.Runtime.InteropServices;

Затем в теле класса добавляем метод, который будет вызываться из библиотеки Kernel32.


// Импортируем функцию для чтения памяти чужого процесса
// из kernel32
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[In, Out] byte[] buffer,
UInt32 size,
out IntPtr lpNumberOfBytesRead
);


А потом используем его по обстоятельствам :)

egoistu
12.09.2010, 09:04
Привет,
TBX1n помоги пожалуйсто с чтением других значений...
оффсеты вроде есть... ([Ссылки могут видеть только зарегистрированные и активированные пользователи])
и читает норм численые значения типа хп/мп перса... а вот текст или флоат не фочет...
никак не разберусь...

как получить имя перса, моба, хп/мах хп моба и так далее...
заранее спасибо за любую инфу...

(Я в Си Шарп новенкий, не смейтесь.... не издевайтесь :reddy:)

кстати спасибо за ранее написаное, там всё ясненько было ;)

Kitsune
13.09.2010, 17:27
[DllImport("kernel32.dll")]
private static extern Int32 ReadProcessMemory(
IntPtr hProcess,
Int32 lpBaseAddress,
[In, Out] byte[] buffer,
Int32 size,
out int lpNumberOfBytesRead
);

private Single ReadFloatFromMemory(int address)
{
int read = 0;
byte[] buffer = new byte[8];

ReadProcessMemory(ProcessHandle, address, buffer, 8, out read);

return BitConverter.ToSingle(buffer, 0);
}

private String ReadStringFromMemory(int address, int length)
{
int read = 0; string rtnStr = string.Empty;
byte[] buffer = new byte[length];

ReadProcessMemory(ProcessHandle, address, buffer, length, out read);

UnicodeEncoding enc = new UnicodeEncoding();
rtnStr = enc.GetString(buffer);

return rtnStr.Substring(0, rtnStr.IndexOf('\0'));
}


Вот мои 2 метода, для чтения Float и String из памяти клиента.

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

Nikita009
28.10.2010, 12:02
А ты можешь прогу написать чтоб только ХП показывал в свёрнутом клиенте

BotDruLife
30.12.2010, 07:34
Как читать данные по оффсетам разобрались!!! Большое-при-большое спасибо!!!

Напиши теперь как двигать персонажем, как магичить... и все такое!!! очень жду!!!

xselderx
01.02.2011, 18:29
Данные наш бот будет получать из памяти клиента PW. Для этого нам придется прибегнуть к функции ReadProcessMemory из библиотеки kernel32.
.NET Framework позволяет нам подключать и использовать методы из библиотек написанных не на управляемом коде (managed code).
непонятно((( и у меня нету System.Runtime.InteropServices. в ссылках

bohdan09
07.02.2011, 00:03
Неплохо, есть коменты к коду - респект жду продолжения уроков!!!!))

67boy
04.03.2011, 16:22
Класс спасибо, но как же еще чтобы полоса опыта выводилась???
Скажите плз!!!

Добавлено через 8 минут
И как поменять это для другой игры например??
Заранее спасибо за ответы!!!!

Teslatrooper
25.03.2011, 23:26
baseAddress = 0x009C1514,
offsetHP = 0x46C,
offsetMP = 0x470,
offsetMaxHP = 0x4A4,
offsetMaxMP = 0x4A8;

на РуОФФ код работоспособен с таким базовым адресом и оффсетами

eXziiii
26.03.2011, 00:41
Здравствуйте, нашел baseadress и offset hp и mp
Взял для базы этот код и применил для Runes of magic
А он всегда выдает 0\0 и 0\0.
В чем может быть беда?
У меня еще есть 1 offset который я не где не указываю 0x598, может дело в этом?
Спасибо
Вот код.

// Объявление события
public event EventHandler InfoUpdated;
// Метод, который вызывает событие
private void OnInfoUpdated(object sender, EventArgs e)
{
if (InfoUpdated != null) InfoUpdated(sender, e);
}

// Импортируем функцию для чтения памяти чужого процесса
// из kernel32
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[In, Out] byte[] buffer,
UInt32 size,
out IntPtr lpNumberOfBytesRead
);

// Определение базового адреса и оффсетов
// для текущей версии клиента руоффа (130)
private int baseAddress = 0x5BC394,
offsetHP = 0x2C8,
offsetMP = 0x2D4,
offsetMaxHP = 0x2D0,
offsetMaxMP = 0x2D8;

// Служебная переменная для хранения PID процесса
private Int32 PID = 0;
private Timer updateTimer;

// Свойство, для хранения текущего HP
public Int32 HP { get; set; }
// Свойство, для хранения текущего MP
public Int32 MP { get; set; }
// по аналогии
public Int32 MaxHP { get; set; }
// по аналогии
public Int32 MaxMP { get; set; }

public Monitor(int pid)
{
this.PID = pid;

updateTimer = new Timer();
updateTimer.Enabled = true;
updateTimer.Interval = 500;
updateTimer.Tick += new EventHandler(updateTimer_Tick);
}

/// <summary>
/// Данный метод вызывается каждый тик таймера
/// Здесь происходит чтение из памяти клиента значений
/// жизни и маны
/// </summary>
private void updateTimer_Tick(object sender, EventArgs e)
{
try
{
if (PID != 0)
{
IntPtr handle = Process.GetProcessById(PID).Handle;

HP = HighLevelReadIntFromMemory(handle, offsetHP);
MP = HighLevelReadIntFromMemory(handle, offsetMP);
MaxHP = HighLevelReadIntFromMemory(handle, offsetMaxHP);
MaxMP = HighLevelReadIntFromMemory(handle, offsetMaxMP);

OnInfoUpdated(this, new EventArgs());
}
}
catch (Exception ex)
{
updateTimer.Enabled = false;
MessageBox.Show(ex.Message);
}
}

/// <summary>
/// Читаем Int'овое значение из памяти приложения
/// с указанным оффсетом
/// </summary>
private Int32 HighLevelReadIntFromMemory(IntPtr handle, int offset)
{
byte[] buffer = new byte[0];
IntPtr read = IntPtr.Zero;
int temp = 0;
temp = LowLevelReadIntFromMemory(handle, baseAddress);
temp = LowLevelReadIntFromMemory(handle, temp + 0x20);
temp = LowLevelReadIntFromMemory(handle, temp + offset);
return temp;
}

/// <summary>
/// Читаем из памяти приложения массив байт по указанному адресу
/// и возвращаем целочисленное его представление
/// </summary>
private Int32 LowLevelReadIntFromMemory(IntPtr handle, int address)
{
byte[] buffer = new byte[4];
IntPtr read = IntPtr.Zero;
ReadProcessMemory(handle, (IntPtr)address, buffer, 4, out read);
return (int)BitConverter.ToUInt32(buffer, 0);
}

[ICEBERG]
11.09.2011, 01:11
// 1.4.2 build 2305
int xBaseAddress = 0xA5BFCC,
xOffsetHP = 0x474,
xOffsetMaxHP = 0x4B4;
11.09.2011 проверенно работает

demon6
29.10.2011, 00:00
private Int32 HighLevelReadIntFromMemory(IntPtr handle, int offset) { byte[] buffer = new byte[0]; IntPtr read = IntPtr.Zero; int temp = 0; temp = LowLevelReadIntFromMemory(handle, baseAddress); temp = LowLevelReadIntFromMemory(handle, temp + 0x20); temp = LowLevelReadIntFromMemory(handle, temp + offset); return temp; }
а что здесь temp + 0x20 откуда эта цифра

lololol1
29.10.2011, 11:33
я все понел спасибо создателю темы!)

†Sweet†
29.10.2011, 21:34
А можно узнать.
Если я хочу писать на другую игру, .exe и т.д.
То как понять откуда брать оффсеты?
Например отсеивать с помощью CE?

Kitsune
09.12.2011, 18:30
glebkey, давно и не один раз.

opahopa
22.04.2013, 16:13
Приветствую. Ранее такого опыта не имел, собственно по топику все ясно.
Вопрос такой - в какую сторону рыть для реализации отправки пакетов(эмуляцию работы клиента? каким-то образом инжект в него?). Т.е. собственно для осуществления действий в ММО. Понятно, что в каждой игрушке свои особенности, но все же приблизательный общий алгоритм.

lcd1232
23.04.2013, 01:12
opahopa,
1) находите через CheatEngine асм код отправки пакетов ([Ссылки могут видеть только зарегистрированные и активированные пользователи]);
2) добавляете его как функцию к себе в проект;
3) обращаетесь через него
4) PROFIT??
5) ...

sizard
05.01.2014, 03:08
Спасибо за гайд! Но есть вопрос. Хочу чтоб бот собирал ресурс, например траву. Нашел статический адрес этой травы и как реализовать ее сбор персонажем?
Делаю бота для Forsaken world

cjrjkjr
14.11.2015, 18:58
Очень не хотелось поднимать такую старую тему, но интересуют вопросы:
Для чтения Int значения, использовался такой метод

private Int32 HighLevelReadIntFromMemory(IntPtr handle, int offset)
{
byte[] buffer = new byte[0];
IntPtr read = IntPtr.Zero;
int temp = 0;
temp = LowLevelReadIntFromMemory(handle, baseAddress);
temp = LowLevelReadIntFromMemory(handle, temp + 0x20);
temp = LowLevelReadIntFromMemory(handle, temp + offset);
return temp;
}
private Int32 LowLevelReadIntFromMemory(IntPtr handle, int address)
{
byte[] buffer = new byte[4];
IntPtr read = IntPtr.Zero;
ReadProcessMemory(handle, (IntPtr)address, buffer, 4, out read);
return (int)BitConverter.ToUInt32(buffer, 0);
}

А для чтения float и string

private Single ReadFloatFromMemory(int address)
{
int read = 0;
byte[] buffer = new byte[8];

ReadProcessMemory(ProcessHandle, address, buffer, 8, out read);

return BitConverter.ToSingle(buffer, 0);
}

private String ReadStringFromMemory(int address, int length)
{
int read = 0; string rtnStr = string.Empty;
byte[] buffer = new byte[length];

ReadProcessMemory(ProcessHandle, address, buffer, length, out read);

UnicodeEncoding enc = new UnicodeEncoding();
rtnStr = enc.GetString(buffer);

return rtnStr.Substring(0, rtnStr.IndexOf('\0'));
}

В последнем методе adress это BaseAdress + offset??
и ProcessHandle я так понял это тот же handle, но если сделать такую функцию

private Single ReadFloatFromMemory(IntPtr handle, int address)
{
IntPtr read = IntPtr.Zero;
byte[] buffer = new byte[8];
ReadProcessMemory(handle, (IntPtr)address, buffer, 8, out read);
return BitConverter.ToSingle(buffer, 0);
}

все так же не понятно что передавать в address, может кто более точно разъяснить как получать float и string значения?

---------------------------------------------------------------------------------
А, все не надо, сам разобрался

Эльза Скарлет
24.11.2015, 11:56
Никак не могу понять почему я получаю такие данные, есть у кого предположение?
[Ссылки могут видеть только зарегистрированные и активированные пользователи]

cjrjkjr
09.12.2015, 12:46
Скорее всего оффсеты не те